##// END OF EJS Templates
repository-groups: use lazy loaded admin dashboard
marcink -
r3623:58c253a3 default
parent child Browse files
Show More
@@ -0,0 +1,54 b''
1 # -*- coding: utf-8 -*-
2
3 import logging
4
5 from alembic.migration import MigrationContext
6 from alembic.operations import Operations
7 from sqlalchemy import String, Column
8 from sqlalchemy.sql import text
9
10 from rhodecode.lib.dbmigrate.versions import _reset_base
11 from rhodecode.model import meta, init_model_encryption
12 from rhodecode.model.db import RepoGroup
13
14
15 log = logging.getLogger(__name__)
16
17
18 def upgrade(migrate_engine):
19 """
20 Upgrade operations go here.
21 Don't create your own engine; bind migrate_engine to your metadata
22 """
23 _reset_base(migrate_engine)
24 from rhodecode.lib.dbmigrate.schema import db_4_16_0_2
25
26 init_model_encryption(db_4_16_0_2)
27
28 context = MigrationContext.configure(migrate_engine.connect())
29 op = Operations(context)
30
31 repo_group = db_4_16_0_2.RepoGroup.__table__
32
33 with op.batch_alter_table(repo_group.name) as batch_op:
34 batch_op.add_column(
35 Column("repo_group_name_hash", String(1024), nullable=True, unique=False))
36
37 _generate_repo_group_name_hashes(db_4_16_0_2, op, meta.Session)
38
39
40 def downgrade(migrate_engine):
41 pass
42
43
44 def _generate_repo_group_name_hashes(models, op, session):
45 repo_groups = models.RepoGroup.get_all()
46 for repo_group in repo_groups:
47 print(repo_group.group_name)
48 hash_ = RepoGroup.hash_repo_group_name(repo_group.group_name)
49 params = {'hash': hash_, 'id': repo_group.group_id}
50 query = text(
51 'UPDATE groups SET repo_group_name_hash = :hash'
52 ' WHERE group_id = :id').bindparams(**params)
53 op.execute(query)
54 session().commit()
@@ -0,0 +1,39 b''
1 # -*- coding: utf-8 -*-
2
3 import logging
4
5 from alembic.migration import MigrationContext
6 from alembic.operations import Operations
7
8 from rhodecode.lib.dbmigrate.versions import _reset_base
9 from rhodecode.model import init_model_encryption
10
11
12 log = logging.getLogger(__name__)
13
14
15 def upgrade(migrate_engine):
16 """
17 Upgrade operations go here.
18 Don't create your own engine; bind migrate_engine to your metadata
19 """
20 _reset_base(migrate_engine)
21 from rhodecode.lib.dbmigrate.schema import db_4_16_0_2
22
23 init_model_encryption(db_4_16_0_2)
24
25 context = MigrationContext.configure(migrate_engine.connect())
26 op = Operations(context)
27
28 repo_group = db_4_16_0_2.RepoGroup.__table__
29
30 with op.batch_alter_table(repo_group.name) as batch_op:
31 batch_op.alter_column("repo_group_name_hash", nullable=False)
32
33
34 def downgrade(migrate_engine):
35 pass
36
37
38 def _generate_repo_group_name_hashes(models, op, session):
39 pass
@@ -1,57 +1,57 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import sys
22 import sys
23 import platform
23 import platform
24
24
25 VERSION = tuple(open(os.path.join(
25 VERSION = tuple(open(os.path.join(
26 os.path.dirname(__file__), 'VERSION')).read().split('.'))
26 os.path.dirname(__file__), 'VERSION')).read().split('.'))
27
27
28 BACKENDS = {
28 BACKENDS = {
29 'hg': 'Mercurial repository',
29 'hg': 'Mercurial repository',
30 'git': 'Git repository',
30 'git': 'Git repository',
31 'svn': 'Subversion repository',
31 'svn': 'Subversion repository',
32 }
32 }
33
33
34 CELERY_ENABLED = False
34 CELERY_ENABLED = False
35 CELERY_EAGER = False
35 CELERY_EAGER = False
36
36
37 # link to config for pyramid
37 # link to config for pyramid
38 CONFIG = {}
38 CONFIG = {}
39
39
40 # Populated with the settings dictionary from application init in
40 # Populated with the settings dictionary from application init in
41 # rhodecode.conf.environment.load_pyramid_environment
41 # rhodecode.conf.environment.load_pyramid_environment
42 PYRAMID_SETTINGS = {}
42 PYRAMID_SETTINGS = {}
43
43
44 # Linked module for extensions
44 # Linked module for extensions
45 EXTENSIONS = {}
45 EXTENSIONS = {}
46
46
47 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
47 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
48 __dbversion__ = 95 # defines current db version for migrations
48 __dbversion__ = 97 # defines current db version for migrations
49 __platform__ = platform.system()
49 __platform__ = platform.system()
50 __license__ = 'AGPLv3, and Commercial License'
50 __license__ = 'AGPLv3, and Commercial License'
51 __author__ = 'RhodeCode GmbH'
51 __author__ = 'RhodeCode GmbH'
52 __url__ = 'https://code.rhodecode.com'
52 __url__ = 'https://code.rhodecode.com'
53
53
54 is_windows = __platform__ in ['Windows']
54 is_windows = __platform__ in ['Windows']
55 is_unix = not is_windows
55 is_unix = not is_windows
56 is_test = False
56 is_test = False
57 disable_error_handler = False
57 disable_error_handler = False
@@ -1,446 +1,450 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 from rhodecode.apps._base import ADMIN_PREFIX
22 from rhodecode.apps._base import ADMIN_PREFIX
23
23
24
24
25 def admin_routes(config):
25 def admin_routes(config):
26 """
26 """
27 Admin prefixed routes
27 Admin prefixed routes
28 """
28 """
29
29
30 config.add_route(
30 config.add_route(
31 name='admin_audit_logs',
31 name='admin_audit_logs',
32 pattern='/audit_logs')
32 pattern='/audit_logs')
33
33
34 config.add_route(
34 config.add_route(
35 name='admin_audit_log_entry',
35 name='admin_audit_log_entry',
36 pattern='/audit_logs/{audit_log_id}')
36 pattern='/audit_logs/{audit_log_id}')
37
37
38 config.add_route(
38 config.add_route(
39 name='pull_requests_global_0', # backward compat
39 name='pull_requests_global_0', # backward compat
40 pattern='/pull_requests/{pull_request_id:\d+}')
40 pattern='/pull_requests/{pull_request_id:\d+}')
41 config.add_route(
41 config.add_route(
42 name='pull_requests_global_1', # backward compat
42 name='pull_requests_global_1', # backward compat
43 pattern='/pull-requests/{pull_request_id:\d+}')
43 pattern='/pull-requests/{pull_request_id:\d+}')
44 config.add_route(
44 config.add_route(
45 name='pull_requests_global',
45 name='pull_requests_global',
46 pattern='/pull-request/{pull_request_id:\d+}')
46 pattern='/pull-request/{pull_request_id:\d+}')
47
47
48 config.add_route(
48 config.add_route(
49 name='admin_settings_open_source',
49 name='admin_settings_open_source',
50 pattern='/settings/open_source')
50 pattern='/settings/open_source')
51 config.add_route(
51 config.add_route(
52 name='admin_settings_vcs_svn_generate_cfg',
52 name='admin_settings_vcs_svn_generate_cfg',
53 pattern='/settings/vcs/svn_generate_cfg')
53 pattern='/settings/vcs/svn_generate_cfg')
54
54
55 config.add_route(
55 config.add_route(
56 name='admin_settings_system',
56 name='admin_settings_system',
57 pattern='/settings/system')
57 pattern='/settings/system')
58 config.add_route(
58 config.add_route(
59 name='admin_settings_system_update',
59 name='admin_settings_system_update',
60 pattern='/settings/system/updates')
60 pattern='/settings/system/updates')
61
61
62 config.add_route(
62 config.add_route(
63 name='admin_settings_exception_tracker',
63 name='admin_settings_exception_tracker',
64 pattern='/settings/exceptions')
64 pattern='/settings/exceptions')
65 config.add_route(
65 config.add_route(
66 name='admin_settings_exception_tracker_delete_all',
66 name='admin_settings_exception_tracker_delete_all',
67 pattern='/settings/exceptions/delete')
67 pattern='/settings/exceptions/delete')
68 config.add_route(
68 config.add_route(
69 name='admin_settings_exception_tracker_show',
69 name='admin_settings_exception_tracker_show',
70 pattern='/settings/exceptions/{exception_id}')
70 pattern='/settings/exceptions/{exception_id}')
71 config.add_route(
71 config.add_route(
72 name='admin_settings_exception_tracker_delete',
72 name='admin_settings_exception_tracker_delete',
73 pattern='/settings/exceptions/{exception_id}/delete')
73 pattern='/settings/exceptions/{exception_id}/delete')
74
74
75 config.add_route(
75 config.add_route(
76 name='admin_settings_sessions',
76 name='admin_settings_sessions',
77 pattern='/settings/sessions')
77 pattern='/settings/sessions')
78 config.add_route(
78 config.add_route(
79 name='admin_settings_sessions_cleanup',
79 name='admin_settings_sessions_cleanup',
80 pattern='/settings/sessions/cleanup')
80 pattern='/settings/sessions/cleanup')
81
81
82 config.add_route(
82 config.add_route(
83 name='admin_settings_process_management',
83 name='admin_settings_process_management',
84 pattern='/settings/process_management')
84 pattern='/settings/process_management')
85 config.add_route(
85 config.add_route(
86 name='admin_settings_process_management_data',
86 name='admin_settings_process_management_data',
87 pattern='/settings/process_management/data')
87 pattern='/settings/process_management/data')
88 config.add_route(
88 config.add_route(
89 name='admin_settings_process_management_signal',
89 name='admin_settings_process_management_signal',
90 pattern='/settings/process_management/signal')
90 pattern='/settings/process_management/signal')
91 config.add_route(
91 config.add_route(
92 name='admin_settings_process_management_master_signal',
92 name='admin_settings_process_management_master_signal',
93 pattern='/settings/process_management/master_signal')
93 pattern='/settings/process_management/master_signal')
94
94
95 # default settings
95 # default settings
96 config.add_route(
96 config.add_route(
97 name='admin_defaults_repositories',
97 name='admin_defaults_repositories',
98 pattern='/defaults/repositories')
98 pattern='/defaults/repositories')
99 config.add_route(
99 config.add_route(
100 name='admin_defaults_repositories_update',
100 name='admin_defaults_repositories_update',
101 pattern='/defaults/repositories/update')
101 pattern='/defaults/repositories/update')
102
102
103 # admin settings
103 # admin settings
104
104
105 config.add_route(
105 config.add_route(
106 name='admin_settings',
106 name='admin_settings',
107 pattern='/settings')
107 pattern='/settings')
108 config.add_route(
108 config.add_route(
109 name='admin_settings_update',
109 name='admin_settings_update',
110 pattern='/settings/update')
110 pattern='/settings/update')
111
111
112 config.add_route(
112 config.add_route(
113 name='admin_settings_global',
113 name='admin_settings_global',
114 pattern='/settings/global')
114 pattern='/settings/global')
115 config.add_route(
115 config.add_route(
116 name='admin_settings_global_update',
116 name='admin_settings_global_update',
117 pattern='/settings/global/update')
117 pattern='/settings/global/update')
118
118
119 config.add_route(
119 config.add_route(
120 name='admin_settings_vcs',
120 name='admin_settings_vcs',
121 pattern='/settings/vcs')
121 pattern='/settings/vcs')
122 config.add_route(
122 config.add_route(
123 name='admin_settings_vcs_update',
123 name='admin_settings_vcs_update',
124 pattern='/settings/vcs/update')
124 pattern='/settings/vcs/update')
125 config.add_route(
125 config.add_route(
126 name='admin_settings_vcs_svn_pattern_delete',
126 name='admin_settings_vcs_svn_pattern_delete',
127 pattern='/settings/vcs/svn_pattern_delete')
127 pattern='/settings/vcs/svn_pattern_delete')
128
128
129 config.add_route(
129 config.add_route(
130 name='admin_settings_mapping',
130 name='admin_settings_mapping',
131 pattern='/settings/mapping')
131 pattern='/settings/mapping')
132 config.add_route(
132 config.add_route(
133 name='admin_settings_mapping_update',
133 name='admin_settings_mapping_update',
134 pattern='/settings/mapping/update')
134 pattern='/settings/mapping/update')
135
135
136 config.add_route(
136 config.add_route(
137 name='admin_settings_visual',
137 name='admin_settings_visual',
138 pattern='/settings/visual')
138 pattern='/settings/visual')
139 config.add_route(
139 config.add_route(
140 name='admin_settings_visual_update',
140 name='admin_settings_visual_update',
141 pattern='/settings/visual/update')
141 pattern='/settings/visual/update')
142
142
143
143
144 config.add_route(
144 config.add_route(
145 name='admin_settings_issuetracker',
145 name='admin_settings_issuetracker',
146 pattern='/settings/issue-tracker')
146 pattern='/settings/issue-tracker')
147 config.add_route(
147 config.add_route(
148 name='admin_settings_issuetracker_update',
148 name='admin_settings_issuetracker_update',
149 pattern='/settings/issue-tracker/update')
149 pattern='/settings/issue-tracker/update')
150 config.add_route(
150 config.add_route(
151 name='admin_settings_issuetracker_test',
151 name='admin_settings_issuetracker_test',
152 pattern='/settings/issue-tracker/test')
152 pattern='/settings/issue-tracker/test')
153 config.add_route(
153 config.add_route(
154 name='admin_settings_issuetracker_delete',
154 name='admin_settings_issuetracker_delete',
155 pattern='/settings/issue-tracker/delete')
155 pattern='/settings/issue-tracker/delete')
156
156
157 config.add_route(
157 config.add_route(
158 name='admin_settings_email',
158 name='admin_settings_email',
159 pattern='/settings/email')
159 pattern='/settings/email')
160 config.add_route(
160 config.add_route(
161 name='admin_settings_email_update',
161 name='admin_settings_email_update',
162 pattern='/settings/email/update')
162 pattern='/settings/email/update')
163
163
164 config.add_route(
164 config.add_route(
165 name='admin_settings_hooks',
165 name='admin_settings_hooks',
166 pattern='/settings/hooks')
166 pattern='/settings/hooks')
167 config.add_route(
167 config.add_route(
168 name='admin_settings_hooks_update',
168 name='admin_settings_hooks_update',
169 pattern='/settings/hooks/update')
169 pattern='/settings/hooks/update')
170 config.add_route(
170 config.add_route(
171 name='admin_settings_hooks_delete',
171 name='admin_settings_hooks_delete',
172 pattern='/settings/hooks/delete')
172 pattern='/settings/hooks/delete')
173
173
174 config.add_route(
174 config.add_route(
175 name='admin_settings_search',
175 name='admin_settings_search',
176 pattern='/settings/search')
176 pattern='/settings/search')
177
177
178 config.add_route(
178 config.add_route(
179 name='admin_settings_labs',
179 name='admin_settings_labs',
180 pattern='/settings/labs')
180 pattern='/settings/labs')
181 config.add_route(
181 config.add_route(
182 name='admin_settings_labs_update',
182 name='admin_settings_labs_update',
183 pattern='/settings/labs/update')
183 pattern='/settings/labs/update')
184
184
185 # Automation EE feature
185 # Automation EE feature
186 config.add_route(
186 config.add_route(
187 'admin_settings_automation',
187 'admin_settings_automation',
188 pattern=ADMIN_PREFIX + '/settings/automation')
188 pattern=ADMIN_PREFIX + '/settings/automation')
189
189
190 # global permissions
190 # global permissions
191
191
192 config.add_route(
192 config.add_route(
193 name='admin_permissions_application',
193 name='admin_permissions_application',
194 pattern='/permissions/application')
194 pattern='/permissions/application')
195 config.add_route(
195 config.add_route(
196 name='admin_permissions_application_update',
196 name='admin_permissions_application_update',
197 pattern='/permissions/application/update')
197 pattern='/permissions/application/update')
198
198
199 config.add_route(
199 config.add_route(
200 name='admin_permissions_global',
200 name='admin_permissions_global',
201 pattern='/permissions/global')
201 pattern='/permissions/global')
202 config.add_route(
202 config.add_route(
203 name='admin_permissions_global_update',
203 name='admin_permissions_global_update',
204 pattern='/permissions/global/update')
204 pattern='/permissions/global/update')
205
205
206 config.add_route(
206 config.add_route(
207 name='admin_permissions_object',
207 name='admin_permissions_object',
208 pattern='/permissions/object')
208 pattern='/permissions/object')
209 config.add_route(
209 config.add_route(
210 name='admin_permissions_object_update',
210 name='admin_permissions_object_update',
211 pattern='/permissions/object/update')
211 pattern='/permissions/object/update')
212
212
213 # Branch perms EE feature
213 # Branch perms EE feature
214 config.add_route(
214 config.add_route(
215 name='admin_permissions_branch',
215 name='admin_permissions_branch',
216 pattern='/permissions/branch')
216 pattern='/permissions/branch')
217
217
218 config.add_route(
218 config.add_route(
219 name='admin_permissions_ips',
219 name='admin_permissions_ips',
220 pattern='/permissions/ips')
220 pattern='/permissions/ips')
221
221
222 config.add_route(
222 config.add_route(
223 name='admin_permissions_overview',
223 name='admin_permissions_overview',
224 pattern='/permissions/overview')
224 pattern='/permissions/overview')
225
225
226 config.add_route(
226 config.add_route(
227 name='admin_permissions_auth_token_access',
227 name='admin_permissions_auth_token_access',
228 pattern='/permissions/auth_token_access')
228 pattern='/permissions/auth_token_access')
229
229
230 config.add_route(
230 config.add_route(
231 name='admin_permissions_ssh_keys',
231 name='admin_permissions_ssh_keys',
232 pattern='/permissions/ssh_keys')
232 pattern='/permissions/ssh_keys')
233 config.add_route(
233 config.add_route(
234 name='admin_permissions_ssh_keys_data',
234 name='admin_permissions_ssh_keys_data',
235 pattern='/permissions/ssh_keys/data')
235 pattern='/permissions/ssh_keys/data')
236 config.add_route(
236 config.add_route(
237 name='admin_permissions_ssh_keys_update',
237 name='admin_permissions_ssh_keys_update',
238 pattern='/permissions/ssh_keys/update')
238 pattern='/permissions/ssh_keys/update')
239
239
240 # users admin
240 # users admin
241 config.add_route(
241 config.add_route(
242 name='users',
242 name='users',
243 pattern='/users')
243 pattern='/users')
244
244
245 config.add_route(
245 config.add_route(
246 name='users_data',
246 name='users_data',
247 pattern='/users_data')
247 pattern='/users_data')
248
248
249 config.add_route(
249 config.add_route(
250 name='users_create',
250 name='users_create',
251 pattern='/users/create')
251 pattern='/users/create')
252
252
253 config.add_route(
253 config.add_route(
254 name='users_new',
254 name='users_new',
255 pattern='/users/new')
255 pattern='/users/new')
256
256
257 # user management
257 # user management
258 config.add_route(
258 config.add_route(
259 name='user_edit',
259 name='user_edit',
260 pattern='/users/{user_id:\d+}/edit',
260 pattern='/users/{user_id:\d+}/edit',
261 user_route=True)
261 user_route=True)
262 config.add_route(
262 config.add_route(
263 name='user_edit_advanced',
263 name='user_edit_advanced',
264 pattern='/users/{user_id:\d+}/edit/advanced',
264 pattern='/users/{user_id:\d+}/edit/advanced',
265 user_route=True)
265 user_route=True)
266 config.add_route(
266 config.add_route(
267 name='user_edit_global_perms',
267 name='user_edit_global_perms',
268 pattern='/users/{user_id:\d+}/edit/global_permissions',
268 pattern='/users/{user_id:\d+}/edit/global_permissions',
269 user_route=True)
269 user_route=True)
270 config.add_route(
270 config.add_route(
271 name='user_edit_global_perms_update',
271 name='user_edit_global_perms_update',
272 pattern='/users/{user_id:\d+}/edit/global_permissions/update',
272 pattern='/users/{user_id:\d+}/edit/global_permissions/update',
273 user_route=True)
273 user_route=True)
274 config.add_route(
274 config.add_route(
275 name='user_update',
275 name='user_update',
276 pattern='/users/{user_id:\d+}/update',
276 pattern='/users/{user_id:\d+}/update',
277 user_route=True)
277 user_route=True)
278 config.add_route(
278 config.add_route(
279 name='user_delete',
279 name='user_delete',
280 pattern='/users/{user_id:\d+}/delete',
280 pattern='/users/{user_id:\d+}/delete',
281 user_route=True)
281 user_route=True)
282 config.add_route(
282 config.add_route(
283 name='user_enable_force_password_reset',
283 name='user_enable_force_password_reset',
284 pattern='/users/{user_id:\d+}/password_reset_enable',
284 pattern='/users/{user_id:\d+}/password_reset_enable',
285 user_route=True)
285 user_route=True)
286 config.add_route(
286 config.add_route(
287 name='user_disable_force_password_reset',
287 name='user_disable_force_password_reset',
288 pattern='/users/{user_id:\d+}/password_reset_disable',
288 pattern='/users/{user_id:\d+}/password_reset_disable',
289 user_route=True)
289 user_route=True)
290 config.add_route(
290 config.add_route(
291 name='user_create_personal_repo_group',
291 name='user_create_personal_repo_group',
292 pattern='/users/{user_id:\d+}/create_repo_group',
292 pattern='/users/{user_id:\d+}/create_repo_group',
293 user_route=True)
293 user_route=True)
294
294
295 # user auth tokens
295 # user auth tokens
296 config.add_route(
296 config.add_route(
297 name='edit_user_auth_tokens',
297 name='edit_user_auth_tokens',
298 pattern='/users/{user_id:\d+}/edit/auth_tokens',
298 pattern='/users/{user_id:\d+}/edit/auth_tokens',
299 user_route=True)
299 user_route=True)
300 config.add_route(
300 config.add_route(
301 name='edit_user_auth_tokens_add',
301 name='edit_user_auth_tokens_add',
302 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
302 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
303 user_route=True)
303 user_route=True)
304 config.add_route(
304 config.add_route(
305 name='edit_user_auth_tokens_delete',
305 name='edit_user_auth_tokens_delete',
306 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete',
306 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete',
307 user_route=True)
307 user_route=True)
308
308
309 # user ssh keys
309 # user ssh keys
310 config.add_route(
310 config.add_route(
311 name='edit_user_ssh_keys',
311 name='edit_user_ssh_keys',
312 pattern='/users/{user_id:\d+}/edit/ssh_keys',
312 pattern='/users/{user_id:\d+}/edit/ssh_keys',
313 user_route=True)
313 user_route=True)
314 config.add_route(
314 config.add_route(
315 name='edit_user_ssh_keys_generate_keypair',
315 name='edit_user_ssh_keys_generate_keypair',
316 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate',
316 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate',
317 user_route=True)
317 user_route=True)
318 config.add_route(
318 config.add_route(
319 name='edit_user_ssh_keys_add',
319 name='edit_user_ssh_keys_add',
320 pattern='/users/{user_id:\d+}/edit/ssh_keys/new',
320 pattern='/users/{user_id:\d+}/edit/ssh_keys/new',
321 user_route=True)
321 user_route=True)
322 config.add_route(
322 config.add_route(
323 name='edit_user_ssh_keys_delete',
323 name='edit_user_ssh_keys_delete',
324 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete',
324 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete',
325 user_route=True)
325 user_route=True)
326
326
327 # user emails
327 # user emails
328 config.add_route(
328 config.add_route(
329 name='edit_user_emails',
329 name='edit_user_emails',
330 pattern='/users/{user_id:\d+}/edit/emails',
330 pattern='/users/{user_id:\d+}/edit/emails',
331 user_route=True)
331 user_route=True)
332 config.add_route(
332 config.add_route(
333 name='edit_user_emails_add',
333 name='edit_user_emails_add',
334 pattern='/users/{user_id:\d+}/edit/emails/new',
334 pattern='/users/{user_id:\d+}/edit/emails/new',
335 user_route=True)
335 user_route=True)
336 config.add_route(
336 config.add_route(
337 name='edit_user_emails_delete',
337 name='edit_user_emails_delete',
338 pattern='/users/{user_id:\d+}/edit/emails/delete',
338 pattern='/users/{user_id:\d+}/edit/emails/delete',
339 user_route=True)
339 user_route=True)
340
340
341 # user IPs
341 # user IPs
342 config.add_route(
342 config.add_route(
343 name='edit_user_ips',
343 name='edit_user_ips',
344 pattern='/users/{user_id:\d+}/edit/ips',
344 pattern='/users/{user_id:\d+}/edit/ips',
345 user_route=True)
345 user_route=True)
346 config.add_route(
346 config.add_route(
347 name='edit_user_ips_add',
347 name='edit_user_ips_add',
348 pattern='/users/{user_id:\d+}/edit/ips/new',
348 pattern='/users/{user_id:\d+}/edit/ips/new',
349 user_route_with_default=True) # enabled for default user too
349 user_route_with_default=True) # enabled for default user too
350 config.add_route(
350 config.add_route(
351 name='edit_user_ips_delete',
351 name='edit_user_ips_delete',
352 pattern='/users/{user_id:\d+}/edit/ips/delete',
352 pattern='/users/{user_id:\d+}/edit/ips/delete',
353 user_route_with_default=True) # enabled for default user too
353 user_route_with_default=True) # enabled for default user too
354
354
355 # user perms
355 # user perms
356 config.add_route(
356 config.add_route(
357 name='edit_user_perms_summary',
357 name='edit_user_perms_summary',
358 pattern='/users/{user_id:\d+}/edit/permissions_summary',
358 pattern='/users/{user_id:\d+}/edit/permissions_summary',
359 user_route=True)
359 user_route=True)
360 config.add_route(
360 config.add_route(
361 name='edit_user_perms_summary_json',
361 name='edit_user_perms_summary_json',
362 pattern='/users/{user_id:\d+}/edit/permissions_summary/json',
362 pattern='/users/{user_id:\d+}/edit/permissions_summary/json',
363 user_route=True)
363 user_route=True)
364
364
365 # user user groups management
365 # user user groups management
366 config.add_route(
366 config.add_route(
367 name='edit_user_groups_management',
367 name='edit_user_groups_management',
368 pattern='/users/{user_id:\d+}/edit/groups_management',
368 pattern='/users/{user_id:\d+}/edit/groups_management',
369 user_route=True)
369 user_route=True)
370
370
371 config.add_route(
371 config.add_route(
372 name='edit_user_groups_management_updates',
372 name='edit_user_groups_management_updates',
373 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
373 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
374 user_route=True)
374 user_route=True)
375
375
376 # user audit logs
376 # user audit logs
377 config.add_route(
377 config.add_route(
378 name='edit_user_audit_logs',
378 name='edit_user_audit_logs',
379 pattern='/users/{user_id:\d+}/edit/audit', user_route=True)
379 pattern='/users/{user_id:\d+}/edit/audit', user_route=True)
380
380
381 # user caches
381 # user caches
382 config.add_route(
382 config.add_route(
383 name='edit_user_caches',
383 name='edit_user_caches',
384 pattern='/users/{user_id:\d+}/edit/caches',
384 pattern='/users/{user_id:\d+}/edit/caches',
385 user_route=True)
385 user_route=True)
386 config.add_route(
386 config.add_route(
387 name='edit_user_caches_update',
387 name='edit_user_caches_update',
388 pattern='/users/{user_id:\d+}/edit/caches/update',
388 pattern='/users/{user_id:\d+}/edit/caches/update',
389 user_route=True)
389 user_route=True)
390
390
391 # user-groups admin
391 # user-groups admin
392 config.add_route(
392 config.add_route(
393 name='user_groups',
393 name='user_groups',
394 pattern='/user_groups')
394 pattern='/user_groups')
395
395
396 config.add_route(
396 config.add_route(
397 name='user_groups_data',
397 name='user_groups_data',
398 pattern='/user_groups_data')
398 pattern='/user_groups_data')
399
399
400 config.add_route(
400 config.add_route(
401 name='user_groups_new',
401 name='user_groups_new',
402 pattern='/user_groups/new')
402 pattern='/user_groups/new')
403
403
404 config.add_route(
404 config.add_route(
405 name='user_groups_create',
405 name='user_groups_create',
406 pattern='/user_groups/create')
406 pattern='/user_groups/create')
407
407
408 # repos admin
408 # repos admin
409 config.add_route(
409 config.add_route(
410 name='repos',
410 name='repos',
411 pattern='/repos')
411 pattern='/repos')
412
412
413 config.add_route(
413 config.add_route(
414 name='repo_new',
414 name='repo_new',
415 pattern='/repos/new')
415 pattern='/repos/new')
416
416
417 config.add_route(
417 config.add_route(
418 name='repo_create',
418 name='repo_create',
419 pattern='/repos/create')
419 pattern='/repos/create')
420
420
421 # repo groups admin
421 # repo groups admin
422 config.add_route(
422 config.add_route(
423 name='repo_groups',
423 name='repo_groups',
424 pattern='/repo_groups')
424 pattern='/repo_groups')
425
425
426 config.add_route(
426 config.add_route(
427 name='repo_groups_data',
428 pattern='/repo_groups_data')
429
430 config.add_route(
427 name='repo_group_new',
431 name='repo_group_new',
428 pattern='/repo_group/new')
432 pattern='/repo_group/new')
429
433
430 config.add_route(
434 config.add_route(
431 name='repo_group_create',
435 name='repo_group_create',
432 pattern='/repo_group/create')
436 pattern='/repo_group/create')
433
437
434
438
435 def includeme(config):
439 def includeme(config):
436 from rhodecode.apps._base.navigation import includeme as nav_includeme
440 from rhodecode.apps._base.navigation import includeme as nav_includeme
437
441
438 # Create admin navigation registry and add it to the pyramid registry.
442 # Create admin navigation registry and add it to the pyramid registry.
439 nav_includeme(config)
443 nav_includeme(config)
440
444
441 # main admin routes
445 # main admin routes
442 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
446 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
443 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
447 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
444
448
445 # Scan module for configuration decorators.
449 # Scan module for configuration decorators.
446 config.scan('.views', ignore='.tests')
450 config.scan('.views', ignore='.tests')
@@ -1,176 +1,194 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import pytest
22 import pytest
23
23
24 from rhodecode.apps._base import ADMIN_PREFIX
24 from rhodecode.apps._base import ADMIN_PREFIX
25 from rhodecode.lib import helpers as h
25 from rhodecode.lib import helpers as h
26 from rhodecode.model.db import Repository, UserRepoToPerm, User
26 from rhodecode.model.db import Repository, UserRepoToPerm, User, RepoGroup
27 from rhodecode.model.meta import Session
27 from rhodecode.model.meta import Session
28 from rhodecode.model.repo_group import RepoGroupModel
28 from rhodecode.model.repo_group import RepoGroupModel
29 from rhodecode.tests import (
29 from rhodecode.tests import (
30 assert_session_flash, TEST_USER_REGULAR_LOGIN, TESTS_TMP_PATH, TestController)
30 assert_session_flash, TEST_USER_REGULAR_LOGIN, TESTS_TMP_PATH)
31 from rhodecode.tests.fixture import Fixture
31 from rhodecode.tests.fixture import Fixture
32
32
33 fixture = Fixture()
33 fixture = Fixture()
34
34
35
35
36 def route_path(name, params=None, **kwargs):
36 def route_path(name, params=None, **kwargs):
37 import urllib
37 import urllib
38
38
39 base_url = {
39 base_url = {
40 'repo_groups': ADMIN_PREFIX + '/repo_groups',
40 'repo_groups': ADMIN_PREFIX + '/repo_groups',
41 'repo_groups_data': ADMIN_PREFIX + '/repo_groups_data',
41 'repo_group_new': ADMIN_PREFIX + '/repo_group/new',
42 'repo_group_new': ADMIN_PREFIX + '/repo_group/new',
42 'repo_group_create': ADMIN_PREFIX + '/repo_group/create',
43 'repo_group_create': ADMIN_PREFIX + '/repo_group/create',
43
44
44 }[name].format(**kwargs)
45 }[name].format(**kwargs)
45
46
46 if params:
47 if params:
47 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
48 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
48 return base_url
49 return base_url
49
50
50
51
51 def _get_permission_for_user(user, repo):
52 def _get_permission_for_user(user, repo):
52 perm = UserRepoToPerm.query()\
53 perm = UserRepoToPerm.query()\
53 .filter(UserRepoToPerm.repository ==
54 .filter(UserRepoToPerm.repository ==
54 Repository.get_by_repo_name(repo))\
55 Repository.get_by_repo_name(repo))\
55 .filter(UserRepoToPerm.user == User.get_by_username(user))\
56 .filter(UserRepoToPerm.user == User.get_by_username(user))\
56 .all()
57 .all()
57 return perm
58 return perm
58
59
59
60
60 @pytest.mark.usefixtures("app")
61 @pytest.mark.usefixtures("app")
61 class TestAdminRepositoryGroups(object):
62 class TestAdminRepositoryGroups(object):
63
62 def test_show_repo_groups(self, autologin_user):
64 def test_show_repo_groups(self, autologin_user):
63 response = self.app.get(route_path('repo_groups'))
65 self.app.get(route_path('repo_groups'))
64 response.mustcontain('data: []')
66
67 def test_show_repo_groups_data(self, autologin_user, xhr_header):
68 response = self.app.get(route_path(
69 'repo_groups_data'), extra_environ=xhr_header)
70
71 all_repo_groups = RepoGroup.query().count()
72 assert response.json['recordsTotal'] == all_repo_groups
65
73
66 def test_show_repo_groups_after_creating_group(self, autologin_user):
74 def test_show_repo_groups_data_filtered(self, autologin_user, xhr_header):
75 response = self.app.get(route_path(
76 'repo_groups_data', params={'search[value]': 'empty_search'}),
77 extra_environ=xhr_header)
78
79 all_repo_groups = RepoGroup.query().count()
80 assert response.json['recordsTotal'] == all_repo_groups
81 assert response.json['recordsFiltered'] == 0
82
83 def test_show_repo_groups_after_creating_group(self, autologin_user, xhr_header):
67 fixture.create_repo_group('test_repo_group')
84 fixture.create_repo_group('test_repo_group')
68 response = self.app.get(route_path('repo_groups'))
85 response = self.app.get(route_path(
86 'repo_groups_data'), extra_environ=xhr_header)
69 response.mustcontain('"name_raw": "test_repo_group"')
87 response.mustcontain('"name_raw": "test_repo_group"')
70 fixture.destroy_repo_group('test_repo_group')
88 fixture.destroy_repo_group('test_repo_group')
71
89
72 def test_new(self, autologin_user):
90 def test_new(self, autologin_user):
73 self.app.get(route_path('repo_group_new'))
91 self.app.get(route_path('repo_group_new'))
74
92
75 def test_new_with_parent_group(self, autologin_user, user_util):
93 def test_new_with_parent_group(self, autologin_user, user_util):
76 gr = user_util.create_repo_group()
94 gr = user_util.create_repo_group()
77
95
78 self.app.get(route_path('repo_group_new'),
96 self.app.get(route_path('repo_group_new'),
79 params=dict(parent_group=gr.group_name))
97 params=dict(parent_group=gr.group_name))
80
98
81 def test_new_by_regular_user_no_permission(self, autologin_regular_user):
99 def test_new_by_regular_user_no_permission(self, autologin_regular_user):
82 self.app.get(route_path('repo_group_new'), status=403)
100 self.app.get(route_path('repo_group_new'), status=403)
83
101
84 @pytest.mark.parametrize('repo_group_name', [
102 @pytest.mark.parametrize('repo_group_name', [
85 'git_repo',
103 'git_repo',
86 'git_repo_Δ…Δ‡',
104 'git_repo_Δ…Δ‡',
87 'hg_repo',
105 'hg_repo',
88 '12345',
106 '12345',
89 'hg_repo_Δ…Δ‡',
107 'hg_repo_Δ…Δ‡',
90 ])
108 ])
91 def test_create(self, autologin_user, repo_group_name, csrf_token):
109 def test_create(self, autologin_user, repo_group_name, csrf_token):
92 repo_group_name_unicode = repo_group_name.decode('utf8')
110 repo_group_name_unicode = repo_group_name.decode('utf8')
93 description = 'description for newly created repo group'
111 description = 'description for newly created repo group'
94
112
95 response = self.app.post(
113 response = self.app.post(
96 route_path('repo_group_create'),
114 route_path('repo_group_create'),
97 fixture._get_group_create_params(
115 fixture._get_group_create_params(
98 group_name=repo_group_name,
116 group_name=repo_group_name,
99 group_description=description,
117 group_description=description,
100 csrf_token=csrf_token))
118 csrf_token=csrf_token))
101
119
102 # run the check page that triggers the flash message
120 # run the check page that triggers the flash message
103 repo_gr_url = h.route_path(
121 repo_gr_url = h.route_path(
104 'repo_group_home', repo_group_name=repo_group_name)
122 'repo_group_home', repo_group_name=repo_group_name)
105
123
106 assert_session_flash(
124 assert_session_flash(
107 response,
125 response,
108 'Created repository group <a href="%s">%s</a>' % (
126 'Created repository group <a href="%s">%s</a>' % (
109 repo_gr_url, repo_group_name_unicode))
127 repo_gr_url, repo_group_name_unicode))
110
128
111 # # test if the repo group was created in the database
129 # # test if the repo group was created in the database
112 new_repo_group = RepoGroupModel()._get_repo_group(
130 new_repo_group = RepoGroupModel()._get_repo_group(
113 repo_group_name_unicode)
131 repo_group_name_unicode)
114 assert new_repo_group is not None
132 assert new_repo_group is not None
115
133
116 assert new_repo_group.group_name == repo_group_name_unicode
134 assert new_repo_group.group_name == repo_group_name_unicode
117 assert new_repo_group.group_description == description
135 assert new_repo_group.group_description == description
118
136
119 # test if the repository is visible in the list ?
137 # test if the repository is visible in the list ?
120 response = self.app.get(repo_gr_url)
138 response = self.app.get(repo_gr_url)
121 response.mustcontain(repo_group_name)
139 response.mustcontain(repo_group_name)
122
140
123 # test if the repository group was created on filesystem
141 # test if the repository group was created on filesystem
124 is_on_filesystem = os.path.isdir(
142 is_on_filesystem = os.path.isdir(
125 os.path.join(TESTS_TMP_PATH, repo_group_name))
143 os.path.join(TESTS_TMP_PATH, repo_group_name))
126 if not is_on_filesystem:
144 if not is_on_filesystem:
127 self.fail('no repo group %s in filesystem' % repo_group_name)
145 self.fail('no repo group %s in filesystem' % repo_group_name)
128
146
129 RepoGroupModel().delete(repo_group_name_unicode)
147 RepoGroupModel().delete(repo_group_name_unicode)
130 Session().commit()
148 Session().commit()
131
149
132 @pytest.mark.parametrize('repo_group_name', [
150 @pytest.mark.parametrize('repo_group_name', [
133 'git_repo',
151 'git_repo',
134 'git_repo_Δ…Δ‡',
152 'git_repo_Δ…Δ‡',
135 'hg_repo',
153 'hg_repo',
136 '12345',
154 '12345',
137 'hg_repo_Δ…Δ‡',
155 'hg_repo_Δ…Δ‡',
138 ])
156 ])
139 def test_create_subgroup(self, autologin_user, user_util, repo_group_name, csrf_token):
157 def test_create_subgroup(self, autologin_user, user_util, repo_group_name, csrf_token):
140 parent_group = user_util.create_repo_group()
158 parent_group = user_util.create_repo_group()
141 parent_group_name = parent_group.group_name
159 parent_group_name = parent_group.group_name
142
160
143 expected_group_name = '{}/{}'.format(
161 expected_group_name = '{}/{}'.format(
144 parent_group_name, repo_group_name)
162 parent_group_name, repo_group_name)
145 expected_group_name_unicode = expected_group_name.decode('utf8')
163 expected_group_name_unicode = expected_group_name.decode('utf8')
146
164
147 try:
165 try:
148 response = self.app.post(
166 response = self.app.post(
149 route_path('repo_group_create'),
167 route_path('repo_group_create'),
150 fixture._get_group_create_params(
168 fixture._get_group_create_params(
151 group_name=repo_group_name,
169 group_name=repo_group_name,
152 group_parent_id=parent_group.group_id,
170 group_parent_id=parent_group.group_id,
153 group_description='Test desciption',
171 group_description='Test desciption',
154 csrf_token=csrf_token))
172 csrf_token=csrf_token))
155
173
156 assert_session_flash(
174 assert_session_flash(
157 response,
175 response,
158 u'Created repository group <a href="%s">%s</a>' % (
176 u'Created repository group <a href="%s">%s</a>' % (
159 h.route_path('repo_group_home',
177 h.route_path('repo_group_home',
160 repo_group_name=expected_group_name),
178 repo_group_name=expected_group_name),
161 expected_group_name_unicode))
179 expected_group_name_unicode))
162 finally:
180 finally:
163 RepoGroupModel().delete(expected_group_name_unicode)
181 RepoGroupModel().delete(expected_group_name_unicode)
164 Session().commit()
182 Session().commit()
165
183
166 def test_user_with_creation_permissions_cannot_create_subgroups(
184 def test_user_with_creation_permissions_cannot_create_subgroups(
167 self, autologin_regular_user, user_util):
185 self, autologin_regular_user, user_util):
168
186
169 user_util.grant_user_permission(
187 user_util.grant_user_permission(
170 TEST_USER_REGULAR_LOGIN, 'hg.repogroup.create.true')
188 TEST_USER_REGULAR_LOGIN, 'hg.repogroup.create.true')
171 parent_group = user_util.create_repo_group()
189 parent_group = user_util.create_repo_group()
172 parent_group_id = parent_group.group_id
190 parent_group_id = parent_group.group_id
173 self.app.get(
191 self.app.get(
174 route_path('repo_group_new',
192 route_path('repo_group_new',
175 params=dict(parent_group=parent_group_id), ),
193 params=dict(parent_group=parent_group_id), ),
176 status=403)
194 status=403)
@@ -1,215 +1,361 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20 import datetime
21 import logging
21 import logging
22 import formencode
22 import formencode
23 import formencode.htmlfill
23 import formencode.htmlfill
24
24
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from pyramid.renderers import render
27 from pyramid.renderers import render
28 from pyramid.response import Response
28 from pyramid.response import Response
29
29
30 from rhodecode import events
30 from rhodecode import events
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32
32
33 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
35 LoginRequired, CSRFRequired, NotAnonymous,
34 LoginRequired, CSRFRequired, NotAnonymous,
36 HasPermissionAny, HasRepoGroupPermissionAny)
35 HasPermissionAny, HasRepoGroupPermissionAny)
37 from rhodecode.lib import helpers as h, audit_logger
36 from rhodecode.lib import helpers as h, audit_logger
38 from rhodecode.lib.utils2 import safe_int, safe_unicode
37 from rhodecode.lib.utils2 import safe_int, safe_unicode, datetime_to_time
39 from rhodecode.model.forms import RepoGroupForm
38 from rhodecode.model.forms import RepoGroupForm
40 from rhodecode.model.repo_group import RepoGroupModel
39 from rhodecode.model.repo_group import RepoGroupModel
41 from rhodecode.model.scm import RepoGroupList
40 from rhodecode.model.scm import RepoGroupList
42 from rhodecode.model.db import Session, RepoGroup
41 from rhodecode.model.db import (
42 or_, count, func, in_filter_generator, Session, RepoGroup, User, Repository)
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
47 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
48
48
49 def load_default_context(self):
49 def load_default_context(self):
50 c = self._get_local_tmpl_context()
50 c = self._get_local_tmpl_context()
51
51
52 return c
52 return c
53
53
54 def _load_form_data(self, c):
54 def _load_form_data(self, c):
55 allow_empty_group = False
55 allow_empty_group = False
56
56
57 if self._can_create_repo_group():
57 if self._can_create_repo_group():
58 # we're global admin, we're ok and we can create TOP level groups
58 # we're global admin, we're ok and we can create TOP level groups
59 allow_empty_group = True
59 allow_empty_group = True
60
60
61 # override the choices for this form, we need to filter choices
61 # override the choices for this form, we need to filter choices
62 # and display only those we have ADMIN right
62 # and display only those we have ADMIN right
63 groups_with_admin_rights = RepoGroupList(
63 groups_with_admin_rights = RepoGroupList(
64 RepoGroup.query().all(),
64 RepoGroup.query().all(),
65 perm_set=['group.admin'])
65 perm_set=['group.admin'])
66 c.repo_groups = RepoGroup.groups_choices(
66 c.repo_groups = RepoGroup.groups_choices(
67 groups=groups_with_admin_rights,
67 groups=groups_with_admin_rights,
68 show_empty_group=allow_empty_group)
68 show_empty_group=allow_empty_group)
69
69
70 def _can_create_repo_group(self, parent_group_id=None):
70 def _can_create_repo_group(self, parent_group_id=None):
71 is_admin = HasPermissionAny('hg.admin')('group create controller')
71 is_admin = HasPermissionAny('hg.admin')('group create controller')
72 create_repo_group = HasPermissionAny(
72 create_repo_group = HasPermissionAny(
73 'hg.repogroup.create.true')('group create controller')
73 'hg.repogroup.create.true')('group create controller')
74 if is_admin or (create_repo_group and not parent_group_id):
74 if is_admin or (create_repo_group and not parent_group_id):
75 # we're global admin, or we have global repo group create
75 # we're global admin, or we have global repo group create
76 # permission
76 # permission
77 # we're ok and we can create TOP level groups
77 # we're ok and we can create TOP level groups
78 return True
78 return True
79 elif parent_group_id:
79 elif parent_group_id:
80 # we check the permission if we can write to parent group
80 # we check the permission if we can write to parent group
81 group = RepoGroup.get(parent_group_id)
81 group = RepoGroup.get(parent_group_id)
82 group_name = group.group_name if group else None
82 group_name = group.group_name if group else None
83 if HasRepoGroupPermissionAny('group.admin')(
83 if HasRepoGroupPermissionAny('group.admin')(
84 group_name, 'check if user is an admin of group'):
84 group_name, 'check if user is an admin of group'):
85 # we're an admin of passed in group, we're ok.
85 # we're an admin of passed in group, we're ok.
86 return True
86 return True
87 else:
87 else:
88 return False
88 return False
89 return False
89 return False
90
90
91 # permission check in data loading of
92 # `repo_group_list_data` via RepoGroupList
91 @LoginRequired()
93 @LoginRequired()
92 @NotAnonymous()
94 @NotAnonymous()
93 # perms check inside
94 @view_config(
95 @view_config(
95 route_name='repo_groups', request_method='GET',
96 route_name='repo_groups', request_method='GET',
96 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
97 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
97 def repo_group_list(self):
98 def repo_group_list(self):
98 c = self.load_default_context()
99 c = self.load_default_context()
100 return self._get_template_context(c)
99
101
100 repo_group_list = RepoGroup.get_all_repo_groups()
102 # permission check inside
101 repo_group_list_acl = RepoGroupList(
103 @LoginRequired()
102 repo_group_list, perm_set=['group.admin'])
104 @NotAnonymous()
103 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
105 @view_config(
104 repo_group_list=repo_group_list_acl, admin=True)
106 route_name='repo_groups_data', request_method='GET',
105 c.data = json.dumps(repo_group_data)
107 renderer='json_ext', xhr=True)
106 return self._get_template_context(c)
108 def repo_group_list_data(self):
109 self.load_default_context()
110 column_map = {
111 'name_raw': 'group_name_hash',
112 'desc': 'group_description',
113 'last_change_raw': 'updated_on',
114 'top_level_repos': 'repos_total',
115 'owner': 'user_username',
116 }
117 draw, start, limit = self._extract_chunk(self.request)
118 search_q, order_by, order_dir = self._extract_ordering(
119 self.request, column_map=column_map)
120
121 _render = self.request.get_partial_renderer(
122 'rhodecode:templates/data_table/_dt_elements.mako')
123 c = _render.get_call_context()
124
125 def quick_menu(repo_group_name):
126 return _render('quick_repo_group_menu', repo_group_name)
127
128 def repo_group_lnk(repo_group_name):
129 return _render('repo_group_name', repo_group_name)
130
131 def last_change(last_change):
132 if isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
133 delta = datetime.timedelta(
134 seconds=(datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
135 last_change = last_change + delta
136 return _render("last_change", last_change)
137
138 def desc(desc, personal):
139 return _render(
140 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
141
142 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
143 return _render(
144 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
145
146 def user_profile(username):
147 return _render('user_profile', username)
148
149 auth_repo_group_list = RepoGroupList(
150 RepoGroup.query().all(), perm_set=['group.admin'])
151
152 allowed_ids = [-1]
153 for repo_group in auth_repo_group_list:
154 allowed_ids.append(repo_group.group_id)
155
156 repo_groups_data_total_count = RepoGroup.query()\
157 .filter(or_(
158 # generate multiple IN to fix limitation problems
159 *in_filter_generator(RepoGroup.group_id, allowed_ids)
160 )) \
161 .count()
162
163 repo_groups_data_total_inactive_count = RepoGroup.query()\
164 .filter(RepoGroup.group_id.in_(allowed_ids))\
165 .count()
166
167 repo_count = count(Repository.repo_id)
168 base_q = Session.query(
169 RepoGroup.group_name,
170 RepoGroup.group_name_hash,
171 RepoGroup.group_description,
172 RepoGroup.group_id,
173 RepoGroup.personal,
174 RepoGroup.updated_on,
175 User,
176 repo_count.label('repos_count')
177 ) \
178 .filter(or_(
179 # generate multiple IN to fix limitation problems
180 *in_filter_generator(RepoGroup.group_id, allowed_ids)
181 )) \
182 .outerjoin(Repository) \
183 .join(User, User.user_id == RepoGroup.user_id) \
184 .group_by(RepoGroup, User)
185
186 if search_q:
187 like_expression = u'%{}%'.format(safe_unicode(search_q))
188 base_q = base_q.filter(or_(
189 RepoGroup.group_name.ilike(like_expression),
190 ))
191
192 repo_groups_data_total_filtered_count = base_q.count()
193 # the inactive isn't really used, but we still make it same as other data grids
194 # which use inactive (users,user groups)
195 repo_groups_data_total_filtered_inactive_count = repo_groups_data_total_filtered_count
196
197 sort_defined = False
198 if order_by == 'group_name':
199 sort_col = func.lower(RepoGroup.group_name)
200 sort_defined = True
201 elif order_by == 'repos_total':
202 sort_col = repo_count
203 sort_defined = True
204 elif order_by == 'user_username':
205 sort_col = User.username
206 else:
207 sort_col = getattr(RepoGroup, order_by, None)
208
209 if sort_defined or sort_col:
210 if order_dir == 'asc':
211 sort_col = sort_col.asc()
212 else:
213 sort_col = sort_col.desc()
214
215 base_q = base_q.order_by(sort_col)
216 base_q = base_q.offset(start).limit(limit)
217
218 # authenticated access to user groups
219 auth_repo_group_list = base_q.all()
220
221 repo_groups_data = []
222 for repo_gr in auth_repo_group_list:
223 row = {
224 "menu": quick_menu(repo_gr.group_name),
225 "name": repo_group_lnk(repo_gr.group_name),
226 "name_raw": repo_gr.group_name,
227 "last_change": last_change(repo_gr.updated_on),
228 "last_change_raw": datetime_to_time(repo_gr.updated_on),
229
230 "last_changeset": "",
231 "last_changeset_raw": "",
232
233 "desc": desc(repo_gr.group_description, repo_gr.personal),
234 "owner": user_profile(repo_gr.User.username),
235 "top_level_repos": repo_gr.repos_count,
236 "action": repo_group_actions(
237 repo_gr.group_id, repo_gr.group_name, repo_gr.repos_count),
238
239 }
240
241 repo_groups_data.append(row)
242
243 data = ({
244 'draw': draw,
245 'data': repo_groups_data,
246 'recordsTotal': repo_groups_data_total_count,
247 'recordsTotalInactive': repo_groups_data_total_inactive_count,
248 'recordsFiltered': repo_groups_data_total_filtered_count,
249 'recordsFilteredInactive': repo_groups_data_total_filtered_inactive_count,
250 })
251
252 return data
107
253
108 @LoginRequired()
254 @LoginRequired()
109 @NotAnonymous()
255 @NotAnonymous()
110 # perm checks inside
256 # perm checks inside
111 @view_config(
257 @view_config(
112 route_name='repo_group_new', request_method='GET',
258 route_name='repo_group_new', request_method='GET',
113 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
259 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
114 def repo_group_new(self):
260 def repo_group_new(self):
115 c = self.load_default_context()
261 c = self.load_default_context()
116
262
117 # perm check for admin, create_group perm or admin of parent_group
263 # perm check for admin, create_group perm or admin of parent_group
118 parent_group_id = safe_int(self.request.GET.get('parent_group'))
264 parent_group_id = safe_int(self.request.GET.get('parent_group'))
119 if not self._can_create_repo_group(parent_group_id):
265 if not self._can_create_repo_group(parent_group_id):
120 raise HTTPForbidden()
266 raise HTTPForbidden()
121
267
122 self._load_form_data(c)
268 self._load_form_data(c)
123
269
124 defaults = {} # Future proof for default of repo group
270 defaults = {} # Future proof for default of repo group
125 data = render(
271 data = render(
126 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
272 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
127 self._get_template_context(c), self.request)
273 self._get_template_context(c), self.request)
128 html = formencode.htmlfill.render(
274 html = formencode.htmlfill.render(
129 data,
275 data,
130 defaults=defaults,
276 defaults=defaults,
131 encoding="UTF-8",
277 encoding="UTF-8",
132 force_defaults=False
278 force_defaults=False
133 )
279 )
134 return Response(html)
280 return Response(html)
135
281
136 @LoginRequired()
282 @LoginRequired()
137 @NotAnonymous()
283 @NotAnonymous()
138 @CSRFRequired()
284 @CSRFRequired()
139 # perm checks inside
285 # perm checks inside
140 @view_config(
286 @view_config(
141 route_name='repo_group_create', request_method='POST',
287 route_name='repo_group_create', request_method='POST',
142 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
288 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
143 def repo_group_create(self):
289 def repo_group_create(self):
144 c = self.load_default_context()
290 c = self.load_default_context()
145 _ = self.request.translate
291 _ = self.request.translate
146
292
147 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
293 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
148 can_create = self._can_create_repo_group(parent_group_id)
294 can_create = self._can_create_repo_group(parent_group_id)
149
295
150 self._load_form_data(c)
296 self._load_form_data(c)
151 # permissions for can create group based on parent_id are checked
297 # permissions for can create group based on parent_id are checked
152 # here in the Form
298 # here in the Form
153 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
299 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
154 repo_group_form = RepoGroupForm(
300 repo_group_form = RepoGroupForm(
155 self.request.translate, available_groups=available_groups,
301 self.request.translate, available_groups=available_groups,
156 can_create_in_root=can_create)()
302 can_create_in_root=can_create)()
157
303
158 repo_group_name = self.request.POST.get('group_name')
304 repo_group_name = self.request.POST.get('group_name')
159 try:
305 try:
160 owner = self._rhodecode_user
306 owner = self._rhodecode_user
161 form_result = repo_group_form.to_python(dict(self.request.POST))
307 form_result = repo_group_form.to_python(dict(self.request.POST))
162 copy_permissions = form_result.get('group_copy_permissions')
308 copy_permissions = form_result.get('group_copy_permissions')
163 repo_group = RepoGroupModel().create(
309 repo_group = RepoGroupModel().create(
164 group_name=form_result['group_name_full'],
310 group_name=form_result['group_name_full'],
165 group_description=form_result['group_description'],
311 group_description=form_result['group_description'],
166 owner=owner.user_id,
312 owner=owner.user_id,
167 copy_permissions=form_result['group_copy_permissions']
313 copy_permissions=form_result['group_copy_permissions']
168 )
314 )
169 Session().flush()
315 Session().flush()
170
316
171 repo_group_data = repo_group.get_api_data()
317 repo_group_data = repo_group.get_api_data()
172 audit_logger.store_web(
318 audit_logger.store_web(
173 'repo_group.create', action_data={'data': repo_group_data},
319 'repo_group.create', action_data={'data': repo_group_data},
174 user=self._rhodecode_user)
320 user=self._rhodecode_user)
175
321
176 Session().commit()
322 Session().commit()
177
323
178 _new_group_name = form_result['group_name_full']
324 _new_group_name = form_result['group_name_full']
179
325
180 repo_group_url = h.link_to(
326 repo_group_url = h.link_to(
181 _new_group_name,
327 _new_group_name,
182 h.route_path('repo_group_home', repo_group_name=_new_group_name))
328 h.route_path('repo_group_home', repo_group_name=_new_group_name))
183 h.flash(h.literal(_('Created repository group %s')
329 h.flash(h.literal(_('Created repository group %s')
184 % repo_group_url), category='success')
330 % repo_group_url), category='success')
185
331
186 except formencode.Invalid as errors:
332 except formencode.Invalid as errors:
187 data = render(
333 data = render(
188 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
334 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
189 self._get_template_context(c), self.request)
335 self._get_template_context(c), self.request)
190 html = formencode.htmlfill.render(
336 html = formencode.htmlfill.render(
191 data,
337 data,
192 defaults=errors.value,
338 defaults=errors.value,
193 errors=errors.error_dict or {},
339 errors=errors.error_dict or {},
194 prefix_error=False,
340 prefix_error=False,
195 encoding="UTF-8",
341 encoding="UTF-8",
196 force_defaults=False
342 force_defaults=False
197 )
343 )
198 return Response(html)
344 return Response(html)
199 except Exception:
345 except Exception:
200 log.exception("Exception during creation of repository group")
346 log.exception("Exception during creation of repository group")
201 h.flash(_('Error occurred during creation of repository group %s')
347 h.flash(_('Error occurred during creation of repository group %s')
202 % repo_group_name, category='error')
348 % repo_group_name, category='error')
203 raise HTTPFound(h.route_path('home'))
349 raise HTTPFound(h.route_path('home'))
204
350
205 affected_user_ids = [self._rhodecode_user.user_id]
351 affected_user_ids = [self._rhodecode_user.user_id]
206 if copy_permissions:
352 if copy_permissions:
207 user_group_perms = repo_group.permissions(expand_from_user_groups=True)
353 user_group_perms = repo_group.permissions(expand_from_user_groups=True)
208 copy_perms = [perm['user_id'] for perm in user_group_perms]
354 copy_perms = [perm['user_id'] for perm in user_group_perms]
209 # also include those newly created by copy
355 # also include those newly created by copy
210 affected_user_ids.extend(copy_perms)
356 affected_user_ids.extend(copy_perms)
211 events.trigger(events.UserPermissionsChange(affected_user_ids))
357 events.trigger(events.UserPermissionsChange(affected_user_ids))
212
358
213 raise HTTPFound(
359 raise HTTPFound(
214 h.route_path('repo_group_home',
360 h.route_path('repo_group_home',
215 repo_group_name=form_result['group_name_full']))
361 repo_group_name=form_result['group_name_full']))
@@ -1,4981 +1,5012 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import string
28 import hashlib
29 import hashlib
29 import logging
30 import logging
30 import datetime
31 import datetime
31 import warnings
32 import warnings
32 import ipaddress
33 import ipaddress
33 import functools
34 import functools
34 import traceback
35 import traceback
35 import collections
36 import collections
36
37
37 from sqlalchemy import (
38 from sqlalchemy import (
38 or_, and_, not_, func, TypeDecorator, event,
39 or_, and_, not_, func, TypeDecorator, event,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
40 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
41 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
41 Text, Float, PickleType)
42 Text, Float, PickleType)
42 from sqlalchemy.sql.expression import true, false, case
43 from sqlalchemy.sql.expression import true, false, case
43 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
44 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
44 from sqlalchemy.orm import (
45 from sqlalchemy.orm import (
45 relationship, joinedload, class_mapper, validates, aliased)
46 relationship, joinedload, class_mapper, validates, aliased)
46 from sqlalchemy.ext.declarative import declared_attr
47 from sqlalchemy.ext.declarative import declared_attr
47 from sqlalchemy.ext.hybrid import hybrid_property
48 from sqlalchemy.ext.hybrid import hybrid_property
48 from sqlalchemy.exc import IntegrityError # pragma: no cover
49 from sqlalchemy.exc import IntegrityError # pragma: no cover
49 from sqlalchemy.dialects.mysql import LONGTEXT
50 from sqlalchemy.dialects.mysql import LONGTEXT
50 from zope.cachedescriptors.property import Lazy as LazyProperty
51 from zope.cachedescriptors.property import Lazy as LazyProperty
51 from pyramid import compat
52 from pyramid import compat
52 from pyramid.threadlocal import get_current_request
53 from pyramid.threadlocal import get_current_request
54 from webhelpers.text import collapse, remove_formatting
53
55
54 from rhodecode.translation import _
56 from rhodecode.translation import _
55 from rhodecode.lib.vcs import get_vcs_instance
57 from rhodecode.lib.vcs import get_vcs_instance
56 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
58 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
57 from rhodecode.lib.utils2 import (
59 from rhodecode.lib.utils2 import (
58 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
60 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
61 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
60 glob2re, StrictAttributeDict, cleaned_uri)
62 glob2re, StrictAttributeDict, cleaned_uri)
61 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
63 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
62 JsonRaw
64 JsonRaw
63 from rhodecode.lib.ext_json import json
65 from rhodecode.lib.ext_json import json
64 from rhodecode.lib.caching_query import FromCache
66 from rhodecode.lib.caching_query import FromCache
65 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
67 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
66 from rhodecode.lib.encrypt2 import Encryptor
68 from rhodecode.lib.encrypt2 import Encryptor
67 from rhodecode.model.meta import Base, Session
69 from rhodecode.model.meta import Base, Session
68
70
69 URL_SEP = '/'
71 URL_SEP = '/'
70 log = logging.getLogger(__name__)
72 log = logging.getLogger(__name__)
71
73
72 # =============================================================================
74 # =============================================================================
73 # BASE CLASSES
75 # BASE CLASSES
74 # =============================================================================
76 # =============================================================================
75
77
76 # this is propagated from .ini file rhodecode.encrypted_values.secret or
78 # this is propagated from .ini file rhodecode.encrypted_values.secret or
77 # beaker.session.secret if first is not set.
79 # beaker.session.secret if first is not set.
78 # and initialized at environment.py
80 # and initialized at environment.py
79 ENCRYPTION_KEY = None
81 ENCRYPTION_KEY = None
80
82
81 # used to sort permissions by types, '#' used here is not allowed to be in
83 # used to sort permissions by types, '#' used here is not allowed to be in
82 # usernames, and it's very early in sorted string.printable table.
84 # usernames, and it's very early in sorted string.printable table.
83 PERMISSION_TYPE_SORT = {
85 PERMISSION_TYPE_SORT = {
84 'admin': '####',
86 'admin': '####',
85 'write': '###',
87 'write': '###',
86 'read': '##',
88 'read': '##',
87 'none': '#',
89 'none': '#',
88 }
90 }
89
91
90
92
91 def display_user_sort(obj):
93 def display_user_sort(obj):
92 """
94 """
93 Sort function used to sort permissions in .permissions() function of
95 Sort function used to sort permissions in .permissions() function of
94 Repository, RepoGroup, UserGroup. Also it put the default user in front
96 Repository, RepoGroup, UserGroup. Also it put the default user in front
95 of all other resources
97 of all other resources
96 """
98 """
97
99
98 if obj.username == User.DEFAULT_USER:
100 if obj.username == User.DEFAULT_USER:
99 return '#####'
101 return '#####'
100 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
102 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
101 return prefix + obj.username
103 return prefix + obj.username
102
104
103
105
104 def display_user_group_sort(obj):
106 def display_user_group_sort(obj):
105 """
107 """
106 Sort function used to sort permissions in .permissions() function of
108 Sort function used to sort permissions in .permissions() function of
107 Repository, RepoGroup, UserGroup. Also it put the default user in front
109 Repository, RepoGroup, UserGroup. Also it put the default user in front
108 of all other resources
110 of all other resources
109 """
111 """
110
112
111 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
113 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
112 return prefix + obj.users_group_name
114 return prefix + obj.users_group_name
113
115
114
116
115 def _hash_key(k):
117 def _hash_key(k):
116 return sha1_safe(k)
118 return sha1_safe(k)
117
119
118
120
119 def in_filter_generator(qry, items, limit=500):
121 def in_filter_generator(qry, items, limit=500):
120 """
122 """
121 Splits IN() into multiple with OR
123 Splits IN() into multiple with OR
122 e.g.::
124 e.g.::
123 cnt = Repository.query().filter(
125 cnt = Repository.query().filter(
124 or_(
126 or_(
125 *in_filter_generator(Repository.repo_id, range(100000))
127 *in_filter_generator(Repository.repo_id, range(100000))
126 )).count()
128 )).count()
127 """
129 """
128 if not items:
130 if not items:
129 # empty list will cause empty query which might cause security issues
131 # empty list will cause empty query which might cause security issues
130 # this can lead to hidden unpleasant results
132 # this can lead to hidden unpleasant results
131 items = [-1]
133 items = [-1]
132
134
133 parts = []
135 parts = []
134 for chunk in xrange(0, len(items), limit):
136 for chunk in xrange(0, len(items), limit):
135 parts.append(
137 parts.append(
136 qry.in_(items[chunk: chunk + limit])
138 qry.in_(items[chunk: chunk + limit])
137 )
139 )
138
140
139 return parts
141 return parts
140
142
141
143
142 base_table_args = {
144 base_table_args = {
143 'extend_existing': True,
145 'extend_existing': True,
144 'mysql_engine': 'InnoDB',
146 'mysql_engine': 'InnoDB',
145 'mysql_charset': 'utf8',
147 'mysql_charset': 'utf8',
146 'sqlite_autoincrement': True
148 'sqlite_autoincrement': True
147 }
149 }
148
150
149
151
150 class EncryptedTextValue(TypeDecorator):
152 class EncryptedTextValue(TypeDecorator):
151 """
153 """
152 Special column for encrypted long text data, use like::
154 Special column for encrypted long text data, use like::
153
155
154 value = Column("encrypted_value", EncryptedValue(), nullable=False)
156 value = Column("encrypted_value", EncryptedValue(), nullable=False)
155
157
156 This column is intelligent so if value is in unencrypted form it return
158 This column is intelligent so if value is in unencrypted form it return
157 unencrypted form, but on save it always encrypts
159 unencrypted form, but on save it always encrypts
158 """
160 """
159 impl = Text
161 impl = Text
160
162
161 def process_bind_param(self, value, dialect):
163 def process_bind_param(self, value, dialect):
162 """
164 """
163 Setter for storing value
165 Setter for storing value
164 """
166 """
165 import rhodecode
167 import rhodecode
166 if not value:
168 if not value:
167 return value
169 return value
168
170
169 # protect against double encrypting if values is already encrypted
171 # protect against double encrypting if values is already encrypted
170 if value.startswith('enc$aes$') \
172 if value.startswith('enc$aes$') \
171 or value.startswith('enc$aes_hmac$') \
173 or value.startswith('enc$aes_hmac$') \
172 or value.startswith('enc2$'):
174 or value.startswith('enc2$'):
173 raise ValueError('value needs to be in unencrypted format, '
175 raise ValueError('value needs to be in unencrypted format, '
174 'ie. not starting with enc$ or enc2$')
176 'ie. not starting with enc$ or enc2$')
175
177
176 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
178 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
177 if algo == 'aes':
179 if algo == 'aes':
178 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
180 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
179 elif algo == 'fernet':
181 elif algo == 'fernet':
180 return Encryptor(ENCRYPTION_KEY).encrypt(value)
182 return Encryptor(ENCRYPTION_KEY).encrypt(value)
181 else:
183 else:
182 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
184 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
183
185
184 def process_result_value(self, value, dialect):
186 def process_result_value(self, value, dialect):
185 """
187 """
186 Getter for retrieving value
188 Getter for retrieving value
187 """
189 """
188
190
189 import rhodecode
191 import rhodecode
190 if not value:
192 if not value:
191 return value
193 return value
192
194
193 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
195 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
194 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
196 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
195 if algo == 'aes':
197 if algo == 'aes':
196 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
198 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
197 elif algo == 'fernet':
199 elif algo == 'fernet':
198 return Encryptor(ENCRYPTION_KEY).decrypt(value)
200 return Encryptor(ENCRYPTION_KEY).decrypt(value)
199 else:
201 else:
200 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
202 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
201 return decrypted_data
203 return decrypted_data
202
204
203
205
204 class BaseModel(object):
206 class BaseModel(object):
205 """
207 """
206 Base Model for all classes
208 Base Model for all classes
207 """
209 """
208
210
209 @classmethod
211 @classmethod
210 def _get_keys(cls):
212 def _get_keys(cls):
211 """return column names for this model """
213 """return column names for this model """
212 return class_mapper(cls).c.keys()
214 return class_mapper(cls).c.keys()
213
215
214 def get_dict(self):
216 def get_dict(self):
215 """
217 """
216 return dict with keys and values corresponding
218 return dict with keys and values corresponding
217 to this model data """
219 to this model data """
218
220
219 d = {}
221 d = {}
220 for k in self._get_keys():
222 for k in self._get_keys():
221 d[k] = getattr(self, k)
223 d[k] = getattr(self, k)
222
224
223 # also use __json__() if present to get additional fields
225 # also use __json__() if present to get additional fields
224 _json_attr = getattr(self, '__json__', None)
226 _json_attr = getattr(self, '__json__', None)
225 if _json_attr:
227 if _json_attr:
226 # update with attributes from __json__
228 # update with attributes from __json__
227 if callable(_json_attr):
229 if callable(_json_attr):
228 _json_attr = _json_attr()
230 _json_attr = _json_attr()
229 for k, val in _json_attr.iteritems():
231 for k, val in _json_attr.iteritems():
230 d[k] = val
232 d[k] = val
231 return d
233 return d
232
234
233 def get_appstruct(self):
235 def get_appstruct(self):
234 """return list with keys and values tuples corresponding
236 """return list with keys and values tuples corresponding
235 to this model data """
237 to this model data """
236
238
237 lst = []
239 lst = []
238 for k in self._get_keys():
240 for k in self._get_keys():
239 lst.append((k, getattr(self, k),))
241 lst.append((k, getattr(self, k),))
240 return lst
242 return lst
241
243
242 def populate_obj(self, populate_dict):
244 def populate_obj(self, populate_dict):
243 """populate model with data from given populate_dict"""
245 """populate model with data from given populate_dict"""
244
246
245 for k in self._get_keys():
247 for k in self._get_keys():
246 if k in populate_dict:
248 if k in populate_dict:
247 setattr(self, k, populate_dict[k])
249 setattr(self, k, populate_dict[k])
248
250
249 @classmethod
251 @classmethod
250 def query(cls):
252 def query(cls):
251 return Session().query(cls)
253 return Session().query(cls)
252
254
253 @classmethod
255 @classmethod
254 def get(cls, id_):
256 def get(cls, id_):
255 if id_:
257 if id_:
256 return cls.query().get(id_)
258 return cls.query().get(id_)
257
259
258 @classmethod
260 @classmethod
259 def get_or_404(cls, id_):
261 def get_or_404(cls, id_):
260 from pyramid.httpexceptions import HTTPNotFound
262 from pyramid.httpexceptions import HTTPNotFound
261
263
262 try:
264 try:
263 id_ = int(id_)
265 id_ = int(id_)
264 except (TypeError, ValueError):
266 except (TypeError, ValueError):
265 raise HTTPNotFound()
267 raise HTTPNotFound()
266
268
267 res = cls.query().get(id_)
269 res = cls.query().get(id_)
268 if not res:
270 if not res:
269 raise HTTPNotFound()
271 raise HTTPNotFound()
270 return res
272 return res
271
273
272 @classmethod
274 @classmethod
273 def getAll(cls):
275 def getAll(cls):
274 # deprecated and left for backward compatibility
276 # deprecated and left for backward compatibility
275 return cls.get_all()
277 return cls.get_all()
276
278
277 @classmethod
279 @classmethod
278 def get_all(cls):
280 def get_all(cls):
279 return cls.query().all()
281 return cls.query().all()
280
282
281 @classmethod
283 @classmethod
282 def delete(cls, id_):
284 def delete(cls, id_):
283 obj = cls.query().get(id_)
285 obj = cls.query().get(id_)
284 Session().delete(obj)
286 Session().delete(obj)
285
287
286 @classmethod
288 @classmethod
287 def identity_cache(cls, session, attr_name, value):
289 def identity_cache(cls, session, attr_name, value):
288 exist_in_session = []
290 exist_in_session = []
289 for (item_cls, pkey), instance in session.identity_map.items():
291 for (item_cls, pkey), instance in session.identity_map.items():
290 if cls == item_cls and getattr(instance, attr_name) == value:
292 if cls == item_cls and getattr(instance, attr_name) == value:
291 exist_in_session.append(instance)
293 exist_in_session.append(instance)
292 if exist_in_session:
294 if exist_in_session:
293 if len(exist_in_session) == 1:
295 if len(exist_in_session) == 1:
294 return exist_in_session[0]
296 return exist_in_session[0]
295 log.exception(
297 log.exception(
296 'multiple objects with attr %s and '
298 'multiple objects with attr %s and '
297 'value %s found with same name: %r',
299 'value %s found with same name: %r',
298 attr_name, value, exist_in_session)
300 attr_name, value, exist_in_session)
299
301
300 def __repr__(self):
302 def __repr__(self):
301 if hasattr(self, '__unicode__'):
303 if hasattr(self, '__unicode__'):
302 # python repr needs to return str
304 # python repr needs to return str
303 try:
305 try:
304 return safe_str(self.__unicode__())
306 return safe_str(self.__unicode__())
305 except UnicodeDecodeError:
307 except UnicodeDecodeError:
306 pass
308 pass
307 return '<DB:%s>' % (self.__class__.__name__)
309 return '<DB:%s>' % (self.__class__.__name__)
308
310
309
311
310 class RhodeCodeSetting(Base, BaseModel):
312 class RhodeCodeSetting(Base, BaseModel):
311 __tablename__ = 'rhodecode_settings'
313 __tablename__ = 'rhodecode_settings'
312 __table_args__ = (
314 __table_args__ = (
313 UniqueConstraint('app_settings_name'),
315 UniqueConstraint('app_settings_name'),
314 base_table_args
316 base_table_args
315 )
317 )
316
318
317 SETTINGS_TYPES = {
319 SETTINGS_TYPES = {
318 'str': safe_str,
320 'str': safe_str,
319 'int': safe_int,
321 'int': safe_int,
320 'unicode': safe_unicode,
322 'unicode': safe_unicode,
321 'bool': str2bool,
323 'bool': str2bool,
322 'list': functools.partial(aslist, sep=',')
324 'list': functools.partial(aslist, sep=',')
323 }
325 }
324 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
326 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
325 GLOBAL_CONF_KEY = 'app_settings'
327 GLOBAL_CONF_KEY = 'app_settings'
326
328
327 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
329 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
328 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
330 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
329 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
331 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
330 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
332 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
331
333
332 def __init__(self, key='', val='', type='unicode'):
334 def __init__(self, key='', val='', type='unicode'):
333 self.app_settings_name = key
335 self.app_settings_name = key
334 self.app_settings_type = type
336 self.app_settings_type = type
335 self.app_settings_value = val
337 self.app_settings_value = val
336
338
337 @validates('_app_settings_value')
339 @validates('_app_settings_value')
338 def validate_settings_value(self, key, val):
340 def validate_settings_value(self, key, val):
339 assert type(val) == unicode
341 assert type(val) == unicode
340 return val
342 return val
341
343
342 @hybrid_property
344 @hybrid_property
343 def app_settings_value(self):
345 def app_settings_value(self):
344 v = self._app_settings_value
346 v = self._app_settings_value
345 _type = self.app_settings_type
347 _type = self.app_settings_type
346 if _type:
348 if _type:
347 _type = self.app_settings_type.split('.')[0]
349 _type = self.app_settings_type.split('.')[0]
348 # decode the encrypted value
350 # decode the encrypted value
349 if 'encrypted' in self.app_settings_type:
351 if 'encrypted' in self.app_settings_type:
350 cipher = EncryptedTextValue()
352 cipher = EncryptedTextValue()
351 v = safe_unicode(cipher.process_result_value(v, None))
353 v = safe_unicode(cipher.process_result_value(v, None))
352
354
353 converter = self.SETTINGS_TYPES.get(_type) or \
355 converter = self.SETTINGS_TYPES.get(_type) or \
354 self.SETTINGS_TYPES['unicode']
356 self.SETTINGS_TYPES['unicode']
355 return converter(v)
357 return converter(v)
356
358
357 @app_settings_value.setter
359 @app_settings_value.setter
358 def app_settings_value(self, val):
360 def app_settings_value(self, val):
359 """
361 """
360 Setter that will always make sure we use unicode in app_settings_value
362 Setter that will always make sure we use unicode in app_settings_value
361
363
362 :param val:
364 :param val:
363 """
365 """
364 val = safe_unicode(val)
366 val = safe_unicode(val)
365 # encode the encrypted value
367 # encode the encrypted value
366 if 'encrypted' in self.app_settings_type:
368 if 'encrypted' in self.app_settings_type:
367 cipher = EncryptedTextValue()
369 cipher = EncryptedTextValue()
368 val = safe_unicode(cipher.process_bind_param(val, None))
370 val = safe_unicode(cipher.process_bind_param(val, None))
369 self._app_settings_value = val
371 self._app_settings_value = val
370
372
371 @hybrid_property
373 @hybrid_property
372 def app_settings_type(self):
374 def app_settings_type(self):
373 return self._app_settings_type
375 return self._app_settings_type
374
376
375 @app_settings_type.setter
377 @app_settings_type.setter
376 def app_settings_type(self, val):
378 def app_settings_type(self, val):
377 if val.split('.')[0] not in self.SETTINGS_TYPES:
379 if val.split('.')[0] not in self.SETTINGS_TYPES:
378 raise Exception('type must be one of %s got %s'
380 raise Exception('type must be one of %s got %s'
379 % (self.SETTINGS_TYPES.keys(), val))
381 % (self.SETTINGS_TYPES.keys(), val))
380 self._app_settings_type = val
382 self._app_settings_type = val
381
383
382 @classmethod
384 @classmethod
383 def get_by_prefix(cls, prefix):
385 def get_by_prefix(cls, prefix):
384 return RhodeCodeSetting.query()\
386 return RhodeCodeSetting.query()\
385 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
387 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
386 .all()
388 .all()
387
389
388 def __unicode__(self):
390 def __unicode__(self):
389 return u"<%s('%s:%s[%s]')>" % (
391 return u"<%s('%s:%s[%s]')>" % (
390 self.__class__.__name__,
392 self.__class__.__name__,
391 self.app_settings_name, self.app_settings_value,
393 self.app_settings_name, self.app_settings_value,
392 self.app_settings_type
394 self.app_settings_type
393 )
395 )
394
396
395
397
396 class RhodeCodeUi(Base, BaseModel):
398 class RhodeCodeUi(Base, BaseModel):
397 __tablename__ = 'rhodecode_ui'
399 __tablename__ = 'rhodecode_ui'
398 __table_args__ = (
400 __table_args__ = (
399 UniqueConstraint('ui_key'),
401 UniqueConstraint('ui_key'),
400 base_table_args
402 base_table_args
401 )
403 )
402
404
403 HOOK_REPO_SIZE = 'changegroup.repo_size'
405 HOOK_REPO_SIZE = 'changegroup.repo_size'
404 # HG
406 # HG
405 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
407 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
406 HOOK_PULL = 'outgoing.pull_logger'
408 HOOK_PULL = 'outgoing.pull_logger'
407 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
409 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
408 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
410 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
409 HOOK_PUSH = 'changegroup.push_logger'
411 HOOK_PUSH = 'changegroup.push_logger'
410 HOOK_PUSH_KEY = 'pushkey.key_push'
412 HOOK_PUSH_KEY = 'pushkey.key_push'
411
413
412 # TODO: johbo: Unify way how hooks are configured for git and hg,
414 # TODO: johbo: Unify way how hooks are configured for git and hg,
413 # git part is currently hardcoded.
415 # git part is currently hardcoded.
414
416
415 # SVN PATTERNS
417 # SVN PATTERNS
416 SVN_BRANCH_ID = 'vcs_svn_branch'
418 SVN_BRANCH_ID = 'vcs_svn_branch'
417 SVN_TAG_ID = 'vcs_svn_tag'
419 SVN_TAG_ID = 'vcs_svn_tag'
418
420
419 ui_id = Column(
421 ui_id = Column(
420 "ui_id", Integer(), nullable=False, unique=True, default=None,
422 "ui_id", Integer(), nullable=False, unique=True, default=None,
421 primary_key=True)
423 primary_key=True)
422 ui_section = Column(
424 ui_section = Column(
423 "ui_section", String(255), nullable=True, unique=None, default=None)
425 "ui_section", String(255), nullable=True, unique=None, default=None)
424 ui_key = Column(
426 ui_key = Column(
425 "ui_key", String(255), nullable=True, unique=None, default=None)
427 "ui_key", String(255), nullable=True, unique=None, default=None)
426 ui_value = Column(
428 ui_value = Column(
427 "ui_value", String(255), nullable=True, unique=None, default=None)
429 "ui_value", String(255), nullable=True, unique=None, default=None)
428 ui_active = Column(
430 ui_active = Column(
429 "ui_active", Boolean(), nullable=True, unique=None, default=True)
431 "ui_active", Boolean(), nullable=True, unique=None, default=True)
430
432
431 def __repr__(self):
433 def __repr__(self):
432 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
434 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
433 self.ui_key, self.ui_value)
435 self.ui_key, self.ui_value)
434
436
435
437
436 class RepoRhodeCodeSetting(Base, BaseModel):
438 class RepoRhodeCodeSetting(Base, BaseModel):
437 __tablename__ = 'repo_rhodecode_settings'
439 __tablename__ = 'repo_rhodecode_settings'
438 __table_args__ = (
440 __table_args__ = (
439 UniqueConstraint(
441 UniqueConstraint(
440 'app_settings_name', 'repository_id',
442 'app_settings_name', 'repository_id',
441 name='uq_repo_rhodecode_setting_name_repo_id'),
443 name='uq_repo_rhodecode_setting_name_repo_id'),
442 base_table_args
444 base_table_args
443 )
445 )
444
446
445 repository_id = Column(
447 repository_id = Column(
446 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
448 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
447 nullable=False)
449 nullable=False)
448 app_settings_id = Column(
450 app_settings_id = Column(
449 "app_settings_id", Integer(), nullable=False, unique=True,
451 "app_settings_id", Integer(), nullable=False, unique=True,
450 default=None, primary_key=True)
452 default=None, primary_key=True)
451 app_settings_name = Column(
453 app_settings_name = Column(
452 "app_settings_name", String(255), nullable=True, unique=None,
454 "app_settings_name", String(255), nullable=True, unique=None,
453 default=None)
455 default=None)
454 _app_settings_value = Column(
456 _app_settings_value = Column(
455 "app_settings_value", String(4096), nullable=True, unique=None,
457 "app_settings_value", String(4096), nullable=True, unique=None,
456 default=None)
458 default=None)
457 _app_settings_type = Column(
459 _app_settings_type = Column(
458 "app_settings_type", String(255), nullable=True, unique=None,
460 "app_settings_type", String(255), nullable=True, unique=None,
459 default=None)
461 default=None)
460
462
461 repository = relationship('Repository')
463 repository = relationship('Repository')
462
464
463 def __init__(self, repository_id, key='', val='', type='unicode'):
465 def __init__(self, repository_id, key='', val='', type='unicode'):
464 self.repository_id = repository_id
466 self.repository_id = repository_id
465 self.app_settings_name = key
467 self.app_settings_name = key
466 self.app_settings_type = type
468 self.app_settings_type = type
467 self.app_settings_value = val
469 self.app_settings_value = val
468
470
469 @validates('_app_settings_value')
471 @validates('_app_settings_value')
470 def validate_settings_value(self, key, val):
472 def validate_settings_value(self, key, val):
471 assert type(val) == unicode
473 assert type(val) == unicode
472 return val
474 return val
473
475
474 @hybrid_property
476 @hybrid_property
475 def app_settings_value(self):
477 def app_settings_value(self):
476 v = self._app_settings_value
478 v = self._app_settings_value
477 type_ = self.app_settings_type
479 type_ = self.app_settings_type
478 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
480 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
479 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
481 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
480 return converter(v)
482 return converter(v)
481
483
482 @app_settings_value.setter
484 @app_settings_value.setter
483 def app_settings_value(self, val):
485 def app_settings_value(self, val):
484 """
486 """
485 Setter that will always make sure we use unicode in app_settings_value
487 Setter that will always make sure we use unicode in app_settings_value
486
488
487 :param val:
489 :param val:
488 """
490 """
489 self._app_settings_value = safe_unicode(val)
491 self._app_settings_value = safe_unicode(val)
490
492
491 @hybrid_property
493 @hybrid_property
492 def app_settings_type(self):
494 def app_settings_type(self):
493 return self._app_settings_type
495 return self._app_settings_type
494
496
495 @app_settings_type.setter
497 @app_settings_type.setter
496 def app_settings_type(self, val):
498 def app_settings_type(self, val):
497 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
499 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
498 if val not in SETTINGS_TYPES:
500 if val not in SETTINGS_TYPES:
499 raise Exception('type must be one of %s got %s'
501 raise Exception('type must be one of %s got %s'
500 % (SETTINGS_TYPES.keys(), val))
502 % (SETTINGS_TYPES.keys(), val))
501 self._app_settings_type = val
503 self._app_settings_type = val
502
504
503 def __unicode__(self):
505 def __unicode__(self):
504 return u"<%s('%s:%s:%s[%s]')>" % (
506 return u"<%s('%s:%s:%s[%s]')>" % (
505 self.__class__.__name__, self.repository.repo_name,
507 self.__class__.__name__, self.repository.repo_name,
506 self.app_settings_name, self.app_settings_value,
508 self.app_settings_name, self.app_settings_value,
507 self.app_settings_type
509 self.app_settings_type
508 )
510 )
509
511
510
512
511 class RepoRhodeCodeUi(Base, BaseModel):
513 class RepoRhodeCodeUi(Base, BaseModel):
512 __tablename__ = 'repo_rhodecode_ui'
514 __tablename__ = 'repo_rhodecode_ui'
513 __table_args__ = (
515 __table_args__ = (
514 UniqueConstraint(
516 UniqueConstraint(
515 'repository_id', 'ui_section', 'ui_key',
517 'repository_id', 'ui_section', 'ui_key',
516 name='uq_repo_rhodecode_ui_repository_id_section_key'),
518 name='uq_repo_rhodecode_ui_repository_id_section_key'),
517 base_table_args
519 base_table_args
518 )
520 )
519
521
520 repository_id = Column(
522 repository_id = Column(
521 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
523 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
522 nullable=False)
524 nullable=False)
523 ui_id = Column(
525 ui_id = Column(
524 "ui_id", Integer(), nullable=False, unique=True, default=None,
526 "ui_id", Integer(), nullable=False, unique=True, default=None,
525 primary_key=True)
527 primary_key=True)
526 ui_section = Column(
528 ui_section = Column(
527 "ui_section", String(255), nullable=True, unique=None, default=None)
529 "ui_section", String(255), nullable=True, unique=None, default=None)
528 ui_key = Column(
530 ui_key = Column(
529 "ui_key", String(255), nullable=True, unique=None, default=None)
531 "ui_key", String(255), nullable=True, unique=None, default=None)
530 ui_value = Column(
532 ui_value = Column(
531 "ui_value", String(255), nullable=True, unique=None, default=None)
533 "ui_value", String(255), nullable=True, unique=None, default=None)
532 ui_active = Column(
534 ui_active = Column(
533 "ui_active", Boolean(), nullable=True, unique=None, default=True)
535 "ui_active", Boolean(), nullable=True, unique=None, default=True)
534
536
535 repository = relationship('Repository')
537 repository = relationship('Repository')
536
538
537 def __repr__(self):
539 def __repr__(self):
538 return '<%s[%s:%s]%s=>%s]>' % (
540 return '<%s[%s:%s]%s=>%s]>' % (
539 self.__class__.__name__, self.repository.repo_name,
541 self.__class__.__name__, self.repository.repo_name,
540 self.ui_section, self.ui_key, self.ui_value)
542 self.ui_section, self.ui_key, self.ui_value)
541
543
542
544
543 class User(Base, BaseModel):
545 class User(Base, BaseModel):
544 __tablename__ = 'users'
546 __tablename__ = 'users'
545 __table_args__ = (
547 __table_args__ = (
546 UniqueConstraint('username'), UniqueConstraint('email'),
548 UniqueConstraint('username'), UniqueConstraint('email'),
547 Index('u_username_idx', 'username'),
549 Index('u_username_idx', 'username'),
548 Index('u_email_idx', 'email'),
550 Index('u_email_idx', 'email'),
549 base_table_args
551 base_table_args
550 )
552 )
551
553
552 DEFAULT_USER = 'default'
554 DEFAULT_USER = 'default'
553 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
555 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
554 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
556 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
555
557
556 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
558 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
557 username = Column("username", String(255), nullable=True, unique=None, default=None)
559 username = Column("username", String(255), nullable=True, unique=None, default=None)
558 password = Column("password", String(255), nullable=True, unique=None, default=None)
560 password = Column("password", String(255), nullable=True, unique=None, default=None)
559 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
561 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
560 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
562 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
561 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
563 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
562 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
564 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
563 _email = Column("email", String(255), nullable=True, unique=None, default=None)
565 _email = Column("email", String(255), nullable=True, unique=None, default=None)
564 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
566 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
565 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
567 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
566
568
567 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
569 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
568 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
570 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
569 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
571 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
570 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
572 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
571 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
573 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
572 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
574 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
573
575
574 user_log = relationship('UserLog')
576 user_log = relationship('UserLog')
575 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
577 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
576
578
577 repositories = relationship('Repository')
579 repositories = relationship('Repository')
578 repository_groups = relationship('RepoGroup')
580 repository_groups = relationship('RepoGroup')
579 user_groups = relationship('UserGroup')
581 user_groups = relationship('UserGroup')
580
582
581 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
583 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
582 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
584 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
583
585
584 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
586 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
585 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
587 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
586 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
588 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
587
589
588 group_member = relationship('UserGroupMember', cascade='all')
590 group_member = relationship('UserGroupMember', cascade='all')
589
591
590 notifications = relationship('UserNotification', cascade='all')
592 notifications = relationship('UserNotification', cascade='all')
591 # notifications assigned to this user
593 # notifications assigned to this user
592 user_created_notifications = relationship('Notification', cascade='all')
594 user_created_notifications = relationship('Notification', cascade='all')
593 # comments created by this user
595 # comments created by this user
594 user_comments = relationship('ChangesetComment', cascade='all')
596 user_comments = relationship('ChangesetComment', cascade='all')
595 # user profile extra info
597 # user profile extra info
596 user_emails = relationship('UserEmailMap', cascade='all')
598 user_emails = relationship('UserEmailMap', cascade='all')
597 user_ip_map = relationship('UserIpMap', cascade='all')
599 user_ip_map = relationship('UserIpMap', cascade='all')
598 user_auth_tokens = relationship('UserApiKeys', cascade='all')
600 user_auth_tokens = relationship('UserApiKeys', cascade='all')
599 user_ssh_keys = relationship('UserSshKeys', cascade='all')
601 user_ssh_keys = relationship('UserSshKeys', cascade='all')
600
602
601 # gists
603 # gists
602 user_gists = relationship('Gist', cascade='all')
604 user_gists = relationship('Gist', cascade='all')
603 # user pull requests
605 # user pull requests
604 user_pull_requests = relationship('PullRequest', cascade='all')
606 user_pull_requests = relationship('PullRequest', cascade='all')
605 # external identities
607 # external identities
606 extenal_identities = relationship(
608 extenal_identities = relationship(
607 'ExternalIdentity',
609 'ExternalIdentity',
608 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
610 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
609 cascade='all')
611 cascade='all')
610 # review rules
612 # review rules
611 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
613 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
612
614
613 def __unicode__(self):
615 def __unicode__(self):
614 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
616 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
615 self.user_id, self.username)
617 self.user_id, self.username)
616
618
617 @hybrid_property
619 @hybrid_property
618 def email(self):
620 def email(self):
619 return self._email
621 return self._email
620
622
621 @email.setter
623 @email.setter
622 def email(self, val):
624 def email(self, val):
623 self._email = val.lower() if val else None
625 self._email = val.lower() if val else None
624
626
625 @hybrid_property
627 @hybrid_property
626 def first_name(self):
628 def first_name(self):
627 from rhodecode.lib import helpers as h
629 from rhodecode.lib import helpers as h
628 if self.name:
630 if self.name:
629 return h.escape(self.name)
631 return h.escape(self.name)
630 return self.name
632 return self.name
631
633
632 @hybrid_property
634 @hybrid_property
633 def last_name(self):
635 def last_name(self):
634 from rhodecode.lib import helpers as h
636 from rhodecode.lib import helpers as h
635 if self.lastname:
637 if self.lastname:
636 return h.escape(self.lastname)
638 return h.escape(self.lastname)
637 return self.lastname
639 return self.lastname
638
640
639 @hybrid_property
641 @hybrid_property
640 def api_key(self):
642 def api_key(self):
641 """
643 """
642 Fetch if exist an auth-token with role ALL connected to this user
644 Fetch if exist an auth-token with role ALL connected to this user
643 """
645 """
644 user_auth_token = UserApiKeys.query()\
646 user_auth_token = UserApiKeys.query()\
645 .filter(UserApiKeys.user_id == self.user_id)\
647 .filter(UserApiKeys.user_id == self.user_id)\
646 .filter(or_(UserApiKeys.expires == -1,
648 .filter(or_(UserApiKeys.expires == -1,
647 UserApiKeys.expires >= time.time()))\
649 UserApiKeys.expires >= time.time()))\
648 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
650 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
649 if user_auth_token:
651 if user_auth_token:
650 user_auth_token = user_auth_token.api_key
652 user_auth_token = user_auth_token.api_key
651
653
652 return user_auth_token
654 return user_auth_token
653
655
654 @api_key.setter
656 @api_key.setter
655 def api_key(self, val):
657 def api_key(self, val):
656 # don't allow to set API key this is deprecated for now
658 # don't allow to set API key this is deprecated for now
657 self._api_key = None
659 self._api_key = None
658
660
659 @property
661 @property
660 def reviewer_pull_requests(self):
662 def reviewer_pull_requests(self):
661 return PullRequestReviewers.query() \
663 return PullRequestReviewers.query() \
662 .options(joinedload(PullRequestReviewers.pull_request)) \
664 .options(joinedload(PullRequestReviewers.pull_request)) \
663 .filter(PullRequestReviewers.user_id == self.user_id) \
665 .filter(PullRequestReviewers.user_id == self.user_id) \
664 .all()
666 .all()
665
667
666 @property
668 @property
667 def firstname(self):
669 def firstname(self):
668 # alias for future
670 # alias for future
669 return self.name
671 return self.name
670
672
671 @property
673 @property
672 def emails(self):
674 def emails(self):
673 other = UserEmailMap.query()\
675 other = UserEmailMap.query()\
674 .filter(UserEmailMap.user == self) \
676 .filter(UserEmailMap.user == self) \
675 .order_by(UserEmailMap.email_id.asc()) \
677 .order_by(UserEmailMap.email_id.asc()) \
676 .all()
678 .all()
677 return [self.email] + [x.email for x in other]
679 return [self.email] + [x.email for x in other]
678
680
679 @property
681 @property
680 def auth_tokens(self):
682 def auth_tokens(self):
681 auth_tokens = self.get_auth_tokens()
683 auth_tokens = self.get_auth_tokens()
682 return [x.api_key for x in auth_tokens]
684 return [x.api_key for x in auth_tokens]
683
685
684 def get_auth_tokens(self):
686 def get_auth_tokens(self):
685 return UserApiKeys.query()\
687 return UserApiKeys.query()\
686 .filter(UserApiKeys.user == self)\
688 .filter(UserApiKeys.user == self)\
687 .order_by(UserApiKeys.user_api_key_id.asc())\
689 .order_by(UserApiKeys.user_api_key_id.asc())\
688 .all()
690 .all()
689
691
690 @LazyProperty
692 @LazyProperty
691 def feed_token(self):
693 def feed_token(self):
692 return self.get_feed_token()
694 return self.get_feed_token()
693
695
694 def get_feed_token(self, cache=True):
696 def get_feed_token(self, cache=True):
695 feed_tokens = UserApiKeys.query()\
697 feed_tokens = UserApiKeys.query()\
696 .filter(UserApiKeys.user == self)\
698 .filter(UserApiKeys.user == self)\
697 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
699 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
698 if cache:
700 if cache:
699 feed_tokens = feed_tokens.options(
701 feed_tokens = feed_tokens.options(
700 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
702 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
701
703
702 feed_tokens = feed_tokens.all()
704 feed_tokens = feed_tokens.all()
703 if feed_tokens:
705 if feed_tokens:
704 return feed_tokens[0].api_key
706 return feed_tokens[0].api_key
705 return 'NO_FEED_TOKEN_AVAILABLE'
707 return 'NO_FEED_TOKEN_AVAILABLE'
706
708
707 @classmethod
709 @classmethod
708 def get(cls, user_id, cache=False):
710 def get(cls, user_id, cache=False):
709 if not user_id:
711 if not user_id:
710 return
712 return
711
713
712 user = cls.query()
714 user = cls.query()
713 if cache:
715 if cache:
714 user = user.options(
716 user = user.options(
715 FromCache("sql_cache_short", "get_users_%s" % user_id))
717 FromCache("sql_cache_short", "get_users_%s" % user_id))
716 return user.get(user_id)
718 return user.get(user_id)
717
719
718 @classmethod
720 @classmethod
719 def extra_valid_auth_tokens(cls, user, role=None):
721 def extra_valid_auth_tokens(cls, user, role=None):
720 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
722 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
721 .filter(or_(UserApiKeys.expires == -1,
723 .filter(or_(UserApiKeys.expires == -1,
722 UserApiKeys.expires >= time.time()))
724 UserApiKeys.expires >= time.time()))
723 if role:
725 if role:
724 tokens = tokens.filter(or_(UserApiKeys.role == role,
726 tokens = tokens.filter(or_(UserApiKeys.role == role,
725 UserApiKeys.role == UserApiKeys.ROLE_ALL))
727 UserApiKeys.role == UserApiKeys.ROLE_ALL))
726 return tokens.all()
728 return tokens.all()
727
729
728 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
730 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
729 from rhodecode.lib import auth
731 from rhodecode.lib import auth
730
732
731 log.debug('Trying to authenticate user: %s via auth-token, '
733 log.debug('Trying to authenticate user: %s via auth-token, '
732 'and roles: %s', self, roles)
734 'and roles: %s', self, roles)
733
735
734 if not auth_token:
736 if not auth_token:
735 return False
737 return False
736
738
737 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
739 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
738 tokens_q = UserApiKeys.query()\
740 tokens_q = UserApiKeys.query()\
739 .filter(UserApiKeys.user_id == self.user_id)\
741 .filter(UserApiKeys.user_id == self.user_id)\
740 .filter(or_(UserApiKeys.expires == -1,
742 .filter(or_(UserApiKeys.expires == -1,
741 UserApiKeys.expires >= time.time()))
743 UserApiKeys.expires >= time.time()))
742
744
743 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
745 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
744
746
745 crypto_backend = auth.crypto_backend()
747 crypto_backend = auth.crypto_backend()
746 enc_token_map = {}
748 enc_token_map = {}
747 plain_token_map = {}
749 plain_token_map = {}
748 for token in tokens_q:
750 for token in tokens_q:
749 if token.api_key.startswith(crypto_backend.ENC_PREF):
751 if token.api_key.startswith(crypto_backend.ENC_PREF):
750 enc_token_map[token.api_key] = token
752 enc_token_map[token.api_key] = token
751 else:
753 else:
752 plain_token_map[token.api_key] = token
754 plain_token_map[token.api_key] = token
753 log.debug(
755 log.debug(
754 'Found %s plain and %s encrypted user tokens to check for authentication',
756 'Found %s plain and %s encrypted user tokens to check for authentication',
755 len(plain_token_map), len(enc_token_map))
757 len(plain_token_map), len(enc_token_map))
756
758
757 # plain token match comes first
759 # plain token match comes first
758 match = plain_token_map.get(auth_token)
760 match = plain_token_map.get(auth_token)
759
761
760 # check encrypted tokens now
762 # check encrypted tokens now
761 if not match:
763 if not match:
762 for token_hash, token in enc_token_map.items():
764 for token_hash, token in enc_token_map.items():
763 # NOTE(marcink): this is expensive to calculate, but most secure
765 # NOTE(marcink): this is expensive to calculate, but most secure
764 if crypto_backend.hash_check(auth_token, token_hash):
766 if crypto_backend.hash_check(auth_token, token_hash):
765 match = token
767 match = token
766 break
768 break
767
769
768 if match:
770 if match:
769 log.debug('Found matching token %s', match)
771 log.debug('Found matching token %s', match)
770 if match.repo_id:
772 if match.repo_id:
771 log.debug('Found scope, checking for scope match of token %s', match)
773 log.debug('Found scope, checking for scope match of token %s', match)
772 if match.repo_id == scope_repo_id:
774 if match.repo_id == scope_repo_id:
773 return True
775 return True
774 else:
776 else:
775 log.debug(
777 log.debug(
776 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
778 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
777 'and calling scope is:%s, skipping further checks',
779 'and calling scope is:%s, skipping further checks',
778 match.repo, scope_repo_id)
780 match.repo, scope_repo_id)
779 return False
781 return False
780 else:
782 else:
781 return True
783 return True
782
784
783 return False
785 return False
784
786
785 @property
787 @property
786 def ip_addresses(self):
788 def ip_addresses(self):
787 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
789 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
788 return [x.ip_addr for x in ret]
790 return [x.ip_addr for x in ret]
789
791
790 @property
792 @property
791 def username_and_name(self):
793 def username_and_name(self):
792 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
794 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
793
795
794 @property
796 @property
795 def username_or_name_or_email(self):
797 def username_or_name_or_email(self):
796 full_name = self.full_name if self.full_name is not ' ' else None
798 full_name = self.full_name if self.full_name is not ' ' else None
797 return self.username or full_name or self.email
799 return self.username or full_name or self.email
798
800
799 @property
801 @property
800 def full_name(self):
802 def full_name(self):
801 return '%s %s' % (self.first_name, self.last_name)
803 return '%s %s' % (self.first_name, self.last_name)
802
804
803 @property
805 @property
804 def full_name_or_username(self):
806 def full_name_or_username(self):
805 return ('%s %s' % (self.first_name, self.last_name)
807 return ('%s %s' % (self.first_name, self.last_name)
806 if (self.first_name and self.last_name) else self.username)
808 if (self.first_name and self.last_name) else self.username)
807
809
808 @property
810 @property
809 def full_contact(self):
811 def full_contact(self):
810 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
812 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
811
813
812 @property
814 @property
813 def short_contact(self):
815 def short_contact(self):
814 return '%s %s' % (self.first_name, self.last_name)
816 return '%s %s' % (self.first_name, self.last_name)
815
817
816 @property
818 @property
817 def is_admin(self):
819 def is_admin(self):
818 return self.admin
820 return self.admin
819
821
820 def AuthUser(self, **kwargs):
822 def AuthUser(self, **kwargs):
821 """
823 """
822 Returns instance of AuthUser for this user
824 Returns instance of AuthUser for this user
823 """
825 """
824 from rhodecode.lib.auth import AuthUser
826 from rhodecode.lib.auth import AuthUser
825 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
827 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
826
828
827 @hybrid_property
829 @hybrid_property
828 def user_data(self):
830 def user_data(self):
829 if not self._user_data:
831 if not self._user_data:
830 return {}
832 return {}
831
833
832 try:
834 try:
833 return json.loads(self._user_data)
835 return json.loads(self._user_data)
834 except TypeError:
836 except TypeError:
835 return {}
837 return {}
836
838
837 @user_data.setter
839 @user_data.setter
838 def user_data(self, val):
840 def user_data(self, val):
839 if not isinstance(val, dict):
841 if not isinstance(val, dict):
840 raise Exception('user_data must be dict, got %s' % type(val))
842 raise Exception('user_data must be dict, got %s' % type(val))
841 try:
843 try:
842 self._user_data = json.dumps(val)
844 self._user_data = json.dumps(val)
843 except Exception:
845 except Exception:
844 log.error(traceback.format_exc())
846 log.error(traceback.format_exc())
845
847
846 @classmethod
848 @classmethod
847 def get_by_username(cls, username, case_insensitive=False,
849 def get_by_username(cls, username, case_insensitive=False,
848 cache=False, identity_cache=False):
850 cache=False, identity_cache=False):
849 session = Session()
851 session = Session()
850
852
851 if case_insensitive:
853 if case_insensitive:
852 q = cls.query().filter(
854 q = cls.query().filter(
853 func.lower(cls.username) == func.lower(username))
855 func.lower(cls.username) == func.lower(username))
854 else:
856 else:
855 q = cls.query().filter(cls.username == username)
857 q = cls.query().filter(cls.username == username)
856
858
857 if cache:
859 if cache:
858 if identity_cache:
860 if identity_cache:
859 val = cls.identity_cache(session, 'username', username)
861 val = cls.identity_cache(session, 'username', username)
860 if val:
862 if val:
861 return val
863 return val
862 else:
864 else:
863 cache_key = "get_user_by_name_%s" % _hash_key(username)
865 cache_key = "get_user_by_name_%s" % _hash_key(username)
864 q = q.options(
866 q = q.options(
865 FromCache("sql_cache_short", cache_key))
867 FromCache("sql_cache_short", cache_key))
866
868
867 return q.scalar()
869 return q.scalar()
868
870
869 @classmethod
871 @classmethod
870 def get_by_auth_token(cls, auth_token, cache=False):
872 def get_by_auth_token(cls, auth_token, cache=False):
871 q = UserApiKeys.query()\
873 q = UserApiKeys.query()\
872 .filter(UserApiKeys.api_key == auth_token)\
874 .filter(UserApiKeys.api_key == auth_token)\
873 .filter(or_(UserApiKeys.expires == -1,
875 .filter(or_(UserApiKeys.expires == -1,
874 UserApiKeys.expires >= time.time()))
876 UserApiKeys.expires >= time.time()))
875 if cache:
877 if cache:
876 q = q.options(
878 q = q.options(
877 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
879 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
878
880
879 match = q.first()
881 match = q.first()
880 if match:
882 if match:
881 return match.user
883 return match.user
882
884
883 @classmethod
885 @classmethod
884 def get_by_email(cls, email, case_insensitive=False, cache=False):
886 def get_by_email(cls, email, case_insensitive=False, cache=False):
885
887
886 if case_insensitive:
888 if case_insensitive:
887 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
889 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
888
890
889 else:
891 else:
890 q = cls.query().filter(cls.email == email)
892 q = cls.query().filter(cls.email == email)
891
893
892 email_key = _hash_key(email)
894 email_key = _hash_key(email)
893 if cache:
895 if cache:
894 q = q.options(
896 q = q.options(
895 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
897 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
896
898
897 ret = q.scalar()
899 ret = q.scalar()
898 if ret is None:
900 if ret is None:
899 q = UserEmailMap.query()
901 q = UserEmailMap.query()
900 # try fetching in alternate email map
902 # try fetching in alternate email map
901 if case_insensitive:
903 if case_insensitive:
902 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
904 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
903 else:
905 else:
904 q = q.filter(UserEmailMap.email == email)
906 q = q.filter(UserEmailMap.email == email)
905 q = q.options(joinedload(UserEmailMap.user))
907 q = q.options(joinedload(UserEmailMap.user))
906 if cache:
908 if cache:
907 q = q.options(
909 q = q.options(
908 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
910 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
909 ret = getattr(q.scalar(), 'user', None)
911 ret = getattr(q.scalar(), 'user', None)
910
912
911 return ret
913 return ret
912
914
913 @classmethod
915 @classmethod
914 def get_from_cs_author(cls, author):
916 def get_from_cs_author(cls, author):
915 """
917 """
916 Tries to get User objects out of commit author string
918 Tries to get User objects out of commit author string
917
919
918 :param author:
920 :param author:
919 """
921 """
920 from rhodecode.lib.helpers import email, author_name
922 from rhodecode.lib.helpers import email, author_name
921 # Valid email in the attribute passed, see if they're in the system
923 # Valid email in the attribute passed, see if they're in the system
922 _email = email(author)
924 _email = email(author)
923 if _email:
925 if _email:
924 user = cls.get_by_email(_email, case_insensitive=True)
926 user = cls.get_by_email(_email, case_insensitive=True)
925 if user:
927 if user:
926 return user
928 return user
927 # Maybe we can match by username?
929 # Maybe we can match by username?
928 _author = author_name(author)
930 _author = author_name(author)
929 user = cls.get_by_username(_author, case_insensitive=True)
931 user = cls.get_by_username(_author, case_insensitive=True)
930 if user:
932 if user:
931 return user
933 return user
932
934
933 def update_userdata(self, **kwargs):
935 def update_userdata(self, **kwargs):
934 usr = self
936 usr = self
935 old = usr.user_data
937 old = usr.user_data
936 old.update(**kwargs)
938 old.update(**kwargs)
937 usr.user_data = old
939 usr.user_data = old
938 Session().add(usr)
940 Session().add(usr)
939 log.debug('updated userdata with ', kwargs)
941 log.debug('updated userdata with ', kwargs)
940
942
941 def update_lastlogin(self):
943 def update_lastlogin(self):
942 """Update user lastlogin"""
944 """Update user lastlogin"""
943 self.last_login = datetime.datetime.now()
945 self.last_login = datetime.datetime.now()
944 Session().add(self)
946 Session().add(self)
945 log.debug('updated user %s lastlogin', self.username)
947 log.debug('updated user %s lastlogin', self.username)
946
948
947 def update_password(self, new_password):
949 def update_password(self, new_password):
948 from rhodecode.lib.auth import get_crypt_password
950 from rhodecode.lib.auth import get_crypt_password
949
951
950 self.password = get_crypt_password(new_password)
952 self.password = get_crypt_password(new_password)
951 Session().add(self)
953 Session().add(self)
952
954
953 @classmethod
955 @classmethod
954 def get_first_super_admin(cls):
956 def get_first_super_admin(cls):
955 user = User.query()\
957 user = User.query()\
956 .filter(User.admin == true()) \
958 .filter(User.admin == true()) \
957 .order_by(User.user_id.asc()) \
959 .order_by(User.user_id.asc()) \
958 .first()
960 .first()
959
961
960 if user is None:
962 if user is None:
961 raise Exception('FATAL: Missing administrative account!')
963 raise Exception('FATAL: Missing administrative account!')
962 return user
964 return user
963
965
964 @classmethod
966 @classmethod
965 def get_all_super_admins(cls, only_active=False):
967 def get_all_super_admins(cls, only_active=False):
966 """
968 """
967 Returns all admin accounts sorted by username
969 Returns all admin accounts sorted by username
968 """
970 """
969 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
971 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
970 if only_active:
972 if only_active:
971 qry = qry.filter(User.active == true())
973 qry = qry.filter(User.active == true())
972 return qry.all()
974 return qry.all()
973
975
974 @classmethod
976 @classmethod
975 def get_default_user(cls, cache=False, refresh=False):
977 def get_default_user(cls, cache=False, refresh=False):
976 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
978 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
977 if user is None:
979 if user is None:
978 raise Exception('FATAL: Missing default account!')
980 raise Exception('FATAL: Missing default account!')
979 if refresh:
981 if refresh:
980 # The default user might be based on outdated state which
982 # The default user might be based on outdated state which
981 # has been loaded from the cache.
983 # has been loaded from the cache.
982 # A call to refresh() ensures that the
984 # A call to refresh() ensures that the
983 # latest state from the database is used.
985 # latest state from the database is used.
984 Session().refresh(user)
986 Session().refresh(user)
985 return user
987 return user
986
988
987 def _get_default_perms(self, user, suffix=''):
989 def _get_default_perms(self, user, suffix=''):
988 from rhodecode.model.permission import PermissionModel
990 from rhodecode.model.permission import PermissionModel
989 return PermissionModel().get_default_perms(user.user_perms, suffix)
991 return PermissionModel().get_default_perms(user.user_perms, suffix)
990
992
991 def get_default_perms(self, suffix=''):
993 def get_default_perms(self, suffix=''):
992 return self._get_default_perms(self, suffix)
994 return self._get_default_perms(self, suffix)
993
995
994 def get_api_data(self, include_secrets=False, details='full'):
996 def get_api_data(self, include_secrets=False, details='full'):
995 """
997 """
996 Common function for generating user related data for API
998 Common function for generating user related data for API
997
999
998 :param include_secrets: By default secrets in the API data will be replaced
1000 :param include_secrets: By default secrets in the API data will be replaced
999 by a placeholder value to prevent exposing this data by accident. In case
1001 by a placeholder value to prevent exposing this data by accident. In case
1000 this data shall be exposed, set this flag to ``True``.
1002 this data shall be exposed, set this flag to ``True``.
1001
1003
1002 :param details: details can be 'basic|full' basic gives only a subset of
1004 :param details: details can be 'basic|full' basic gives only a subset of
1003 the available user information that includes user_id, name and emails.
1005 the available user information that includes user_id, name and emails.
1004 """
1006 """
1005 user = self
1007 user = self
1006 user_data = self.user_data
1008 user_data = self.user_data
1007 data = {
1009 data = {
1008 'user_id': user.user_id,
1010 'user_id': user.user_id,
1009 'username': user.username,
1011 'username': user.username,
1010 'firstname': user.name,
1012 'firstname': user.name,
1011 'lastname': user.lastname,
1013 'lastname': user.lastname,
1012 'email': user.email,
1014 'email': user.email,
1013 'emails': user.emails,
1015 'emails': user.emails,
1014 }
1016 }
1015 if details == 'basic':
1017 if details == 'basic':
1016 return data
1018 return data
1017
1019
1018 auth_token_length = 40
1020 auth_token_length = 40
1019 auth_token_replacement = '*' * auth_token_length
1021 auth_token_replacement = '*' * auth_token_length
1020
1022
1021 extras = {
1023 extras = {
1022 'auth_tokens': [auth_token_replacement],
1024 'auth_tokens': [auth_token_replacement],
1023 'active': user.active,
1025 'active': user.active,
1024 'admin': user.admin,
1026 'admin': user.admin,
1025 'extern_type': user.extern_type,
1027 'extern_type': user.extern_type,
1026 'extern_name': user.extern_name,
1028 'extern_name': user.extern_name,
1027 'last_login': user.last_login,
1029 'last_login': user.last_login,
1028 'last_activity': user.last_activity,
1030 'last_activity': user.last_activity,
1029 'ip_addresses': user.ip_addresses,
1031 'ip_addresses': user.ip_addresses,
1030 'language': user_data.get('language')
1032 'language': user_data.get('language')
1031 }
1033 }
1032 data.update(extras)
1034 data.update(extras)
1033
1035
1034 if include_secrets:
1036 if include_secrets:
1035 data['auth_tokens'] = user.auth_tokens
1037 data['auth_tokens'] = user.auth_tokens
1036 return data
1038 return data
1037
1039
1038 def __json__(self):
1040 def __json__(self):
1039 data = {
1041 data = {
1040 'full_name': self.full_name,
1042 'full_name': self.full_name,
1041 'full_name_or_username': self.full_name_or_username,
1043 'full_name_or_username': self.full_name_or_username,
1042 'short_contact': self.short_contact,
1044 'short_contact': self.short_contact,
1043 'full_contact': self.full_contact,
1045 'full_contact': self.full_contact,
1044 }
1046 }
1045 data.update(self.get_api_data())
1047 data.update(self.get_api_data())
1046 return data
1048 return data
1047
1049
1048
1050
1049 class UserApiKeys(Base, BaseModel):
1051 class UserApiKeys(Base, BaseModel):
1050 __tablename__ = 'user_api_keys'
1052 __tablename__ = 'user_api_keys'
1051 __table_args__ = (
1053 __table_args__ = (
1052 Index('uak_api_key_idx', 'api_key', unique=True),
1054 Index('uak_api_key_idx', 'api_key', unique=True),
1053 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1055 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1054 base_table_args
1056 base_table_args
1055 )
1057 )
1056 __mapper_args__ = {}
1058 __mapper_args__ = {}
1057
1059
1058 # ApiKey role
1060 # ApiKey role
1059 ROLE_ALL = 'token_role_all'
1061 ROLE_ALL = 'token_role_all'
1060 ROLE_HTTP = 'token_role_http'
1062 ROLE_HTTP = 'token_role_http'
1061 ROLE_VCS = 'token_role_vcs'
1063 ROLE_VCS = 'token_role_vcs'
1062 ROLE_API = 'token_role_api'
1064 ROLE_API = 'token_role_api'
1063 ROLE_FEED = 'token_role_feed'
1065 ROLE_FEED = 'token_role_feed'
1064 ROLE_PASSWORD_RESET = 'token_password_reset'
1066 ROLE_PASSWORD_RESET = 'token_password_reset'
1065
1067
1066 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1068 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1067
1069
1068 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1070 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1069 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1071 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1070 api_key = Column("api_key", String(255), nullable=False, unique=True)
1072 api_key = Column("api_key", String(255), nullable=False, unique=True)
1071 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1073 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1072 expires = Column('expires', Float(53), nullable=False)
1074 expires = Column('expires', Float(53), nullable=False)
1073 role = Column('role', String(255), nullable=True)
1075 role = Column('role', String(255), nullable=True)
1074 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1076 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1075
1077
1076 # scope columns
1078 # scope columns
1077 repo_id = Column(
1079 repo_id = Column(
1078 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1080 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1079 nullable=True, unique=None, default=None)
1081 nullable=True, unique=None, default=None)
1080 repo = relationship('Repository', lazy='joined')
1082 repo = relationship('Repository', lazy='joined')
1081
1083
1082 repo_group_id = Column(
1084 repo_group_id = Column(
1083 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1085 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1084 nullable=True, unique=None, default=None)
1086 nullable=True, unique=None, default=None)
1085 repo_group = relationship('RepoGroup', lazy='joined')
1087 repo_group = relationship('RepoGroup', lazy='joined')
1086
1088
1087 user = relationship('User', lazy='joined')
1089 user = relationship('User', lazy='joined')
1088
1090
1089 def __unicode__(self):
1091 def __unicode__(self):
1090 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1092 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1091
1093
1092 def __json__(self):
1094 def __json__(self):
1093 data = {
1095 data = {
1094 'auth_token': self.api_key,
1096 'auth_token': self.api_key,
1095 'role': self.role,
1097 'role': self.role,
1096 'scope': self.scope_humanized,
1098 'scope': self.scope_humanized,
1097 'expired': self.expired
1099 'expired': self.expired
1098 }
1100 }
1099 return data
1101 return data
1100
1102
1101 def get_api_data(self, include_secrets=False):
1103 def get_api_data(self, include_secrets=False):
1102 data = self.__json__()
1104 data = self.__json__()
1103 if include_secrets:
1105 if include_secrets:
1104 return data
1106 return data
1105 else:
1107 else:
1106 data['auth_token'] = self.token_obfuscated
1108 data['auth_token'] = self.token_obfuscated
1107 return data
1109 return data
1108
1110
1109 @hybrid_property
1111 @hybrid_property
1110 def description_safe(self):
1112 def description_safe(self):
1111 from rhodecode.lib import helpers as h
1113 from rhodecode.lib import helpers as h
1112 return h.escape(self.description)
1114 return h.escape(self.description)
1113
1115
1114 @property
1116 @property
1115 def expired(self):
1117 def expired(self):
1116 if self.expires == -1:
1118 if self.expires == -1:
1117 return False
1119 return False
1118 return time.time() > self.expires
1120 return time.time() > self.expires
1119
1121
1120 @classmethod
1122 @classmethod
1121 def _get_role_name(cls, role):
1123 def _get_role_name(cls, role):
1122 return {
1124 return {
1123 cls.ROLE_ALL: _('all'),
1125 cls.ROLE_ALL: _('all'),
1124 cls.ROLE_HTTP: _('http/web interface'),
1126 cls.ROLE_HTTP: _('http/web interface'),
1125 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1127 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1126 cls.ROLE_API: _('api calls'),
1128 cls.ROLE_API: _('api calls'),
1127 cls.ROLE_FEED: _('feed access'),
1129 cls.ROLE_FEED: _('feed access'),
1128 }.get(role, role)
1130 }.get(role, role)
1129
1131
1130 @property
1132 @property
1131 def role_humanized(self):
1133 def role_humanized(self):
1132 return self._get_role_name(self.role)
1134 return self._get_role_name(self.role)
1133
1135
1134 def _get_scope(self):
1136 def _get_scope(self):
1135 if self.repo:
1137 if self.repo:
1136 return 'Repository: {}'.format(self.repo.repo_name)
1138 return 'Repository: {}'.format(self.repo.repo_name)
1137 if self.repo_group:
1139 if self.repo_group:
1138 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1140 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1139 return 'Global'
1141 return 'Global'
1140
1142
1141 @property
1143 @property
1142 def scope_humanized(self):
1144 def scope_humanized(self):
1143 return self._get_scope()
1145 return self._get_scope()
1144
1146
1145 @property
1147 @property
1146 def token_obfuscated(self):
1148 def token_obfuscated(self):
1147 if self.api_key:
1149 if self.api_key:
1148 return self.api_key[:4] + "****"
1150 return self.api_key[:4] + "****"
1149
1151
1150
1152
1151 class UserEmailMap(Base, BaseModel):
1153 class UserEmailMap(Base, BaseModel):
1152 __tablename__ = 'user_email_map'
1154 __tablename__ = 'user_email_map'
1153 __table_args__ = (
1155 __table_args__ = (
1154 Index('uem_email_idx', 'email'),
1156 Index('uem_email_idx', 'email'),
1155 UniqueConstraint('email'),
1157 UniqueConstraint('email'),
1156 base_table_args
1158 base_table_args
1157 )
1159 )
1158 __mapper_args__ = {}
1160 __mapper_args__ = {}
1159
1161
1160 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1162 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1161 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1163 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1162 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1164 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1163 user = relationship('User', lazy='joined')
1165 user = relationship('User', lazy='joined')
1164
1166
1165 @validates('_email')
1167 @validates('_email')
1166 def validate_email(self, key, email):
1168 def validate_email(self, key, email):
1167 # check if this email is not main one
1169 # check if this email is not main one
1168 main_email = Session().query(User).filter(User.email == email).scalar()
1170 main_email = Session().query(User).filter(User.email == email).scalar()
1169 if main_email is not None:
1171 if main_email is not None:
1170 raise AttributeError('email %s is present is user table' % email)
1172 raise AttributeError('email %s is present is user table' % email)
1171 return email
1173 return email
1172
1174
1173 @hybrid_property
1175 @hybrid_property
1174 def email(self):
1176 def email(self):
1175 return self._email
1177 return self._email
1176
1178
1177 @email.setter
1179 @email.setter
1178 def email(self, val):
1180 def email(self, val):
1179 self._email = val.lower() if val else None
1181 self._email = val.lower() if val else None
1180
1182
1181
1183
1182 class UserIpMap(Base, BaseModel):
1184 class UserIpMap(Base, BaseModel):
1183 __tablename__ = 'user_ip_map'
1185 __tablename__ = 'user_ip_map'
1184 __table_args__ = (
1186 __table_args__ = (
1185 UniqueConstraint('user_id', 'ip_addr'),
1187 UniqueConstraint('user_id', 'ip_addr'),
1186 base_table_args
1188 base_table_args
1187 )
1189 )
1188 __mapper_args__ = {}
1190 __mapper_args__ = {}
1189
1191
1190 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1192 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1191 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1193 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1192 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1194 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1193 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1195 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1194 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1196 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1195 user = relationship('User', lazy='joined')
1197 user = relationship('User', lazy='joined')
1196
1198
1197 @hybrid_property
1199 @hybrid_property
1198 def description_safe(self):
1200 def description_safe(self):
1199 from rhodecode.lib import helpers as h
1201 from rhodecode.lib import helpers as h
1200 return h.escape(self.description)
1202 return h.escape(self.description)
1201
1203
1202 @classmethod
1204 @classmethod
1203 def _get_ip_range(cls, ip_addr):
1205 def _get_ip_range(cls, ip_addr):
1204 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1206 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1205 return [str(net.network_address), str(net.broadcast_address)]
1207 return [str(net.network_address), str(net.broadcast_address)]
1206
1208
1207 def __json__(self):
1209 def __json__(self):
1208 return {
1210 return {
1209 'ip_addr': self.ip_addr,
1211 'ip_addr': self.ip_addr,
1210 'ip_range': self._get_ip_range(self.ip_addr),
1212 'ip_range': self._get_ip_range(self.ip_addr),
1211 }
1213 }
1212
1214
1213 def __unicode__(self):
1215 def __unicode__(self):
1214 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1216 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1215 self.user_id, self.ip_addr)
1217 self.user_id, self.ip_addr)
1216
1218
1217
1219
1218 class UserSshKeys(Base, BaseModel):
1220 class UserSshKeys(Base, BaseModel):
1219 __tablename__ = 'user_ssh_keys'
1221 __tablename__ = 'user_ssh_keys'
1220 __table_args__ = (
1222 __table_args__ = (
1221 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1223 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1222
1224
1223 UniqueConstraint('ssh_key_fingerprint'),
1225 UniqueConstraint('ssh_key_fingerprint'),
1224
1226
1225 base_table_args
1227 base_table_args
1226 )
1228 )
1227 __mapper_args__ = {}
1229 __mapper_args__ = {}
1228
1230
1229 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1231 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1230 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1232 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1231 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1233 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1232
1234
1233 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1235 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1234
1236
1235 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1237 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1236 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1238 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1237 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1239 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1238
1240
1239 user = relationship('User', lazy='joined')
1241 user = relationship('User', lazy='joined')
1240
1242
1241 def __json__(self):
1243 def __json__(self):
1242 data = {
1244 data = {
1243 'ssh_fingerprint': self.ssh_key_fingerprint,
1245 'ssh_fingerprint': self.ssh_key_fingerprint,
1244 'description': self.description,
1246 'description': self.description,
1245 'created_on': self.created_on
1247 'created_on': self.created_on
1246 }
1248 }
1247 return data
1249 return data
1248
1250
1249 def get_api_data(self):
1251 def get_api_data(self):
1250 data = self.__json__()
1252 data = self.__json__()
1251 return data
1253 return data
1252
1254
1253
1255
1254 class UserLog(Base, BaseModel):
1256 class UserLog(Base, BaseModel):
1255 __tablename__ = 'user_logs'
1257 __tablename__ = 'user_logs'
1256 __table_args__ = (
1258 __table_args__ = (
1257 base_table_args,
1259 base_table_args,
1258 )
1260 )
1259
1261
1260 VERSION_1 = 'v1'
1262 VERSION_1 = 'v1'
1261 VERSION_2 = 'v2'
1263 VERSION_2 = 'v2'
1262 VERSIONS = [VERSION_1, VERSION_2]
1264 VERSIONS = [VERSION_1, VERSION_2]
1263
1265
1264 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1266 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1265 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1267 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1266 username = Column("username", String(255), nullable=True, unique=None, default=None)
1268 username = Column("username", String(255), nullable=True, unique=None, default=None)
1267 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1269 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1268 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1270 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1269 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1271 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1270 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1272 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1271 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1273 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1272
1274
1273 version = Column("version", String(255), nullable=True, default=VERSION_1)
1275 version = Column("version", String(255), nullable=True, default=VERSION_1)
1274 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1276 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1275 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1277 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1276
1278
1277 def __unicode__(self):
1279 def __unicode__(self):
1278 return u"<%s('id:%s:%s')>" % (
1280 return u"<%s('id:%s:%s')>" % (
1279 self.__class__.__name__, self.repository_name, self.action)
1281 self.__class__.__name__, self.repository_name, self.action)
1280
1282
1281 def __json__(self):
1283 def __json__(self):
1282 return {
1284 return {
1283 'user_id': self.user_id,
1285 'user_id': self.user_id,
1284 'username': self.username,
1286 'username': self.username,
1285 'repository_id': self.repository_id,
1287 'repository_id': self.repository_id,
1286 'repository_name': self.repository_name,
1288 'repository_name': self.repository_name,
1287 'user_ip': self.user_ip,
1289 'user_ip': self.user_ip,
1288 'action_date': self.action_date,
1290 'action_date': self.action_date,
1289 'action': self.action,
1291 'action': self.action,
1290 }
1292 }
1291
1293
1292 @hybrid_property
1294 @hybrid_property
1293 def entry_id(self):
1295 def entry_id(self):
1294 return self.user_log_id
1296 return self.user_log_id
1295
1297
1296 @property
1298 @property
1297 def action_as_day(self):
1299 def action_as_day(self):
1298 return datetime.date(*self.action_date.timetuple()[:3])
1300 return datetime.date(*self.action_date.timetuple()[:3])
1299
1301
1300 user = relationship('User')
1302 user = relationship('User')
1301 repository = relationship('Repository', cascade='')
1303 repository = relationship('Repository', cascade='')
1302
1304
1303
1305
1304 class UserGroup(Base, BaseModel):
1306 class UserGroup(Base, BaseModel):
1305 __tablename__ = 'users_groups'
1307 __tablename__ = 'users_groups'
1306 __table_args__ = (
1308 __table_args__ = (
1307 base_table_args,
1309 base_table_args,
1308 )
1310 )
1309
1311
1310 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1312 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1311 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1313 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1312 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1314 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1313 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1315 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1314 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1316 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1315 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1317 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1316 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1318 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1317 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1319 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1318
1320
1319 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1321 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1320 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1322 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1321 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1323 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1322 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1324 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1323 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1325 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1324 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1326 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1325
1327
1326 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1328 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1327 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1329 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1328
1330
1329 @classmethod
1331 @classmethod
1330 def _load_group_data(cls, column):
1332 def _load_group_data(cls, column):
1331 if not column:
1333 if not column:
1332 return {}
1334 return {}
1333
1335
1334 try:
1336 try:
1335 return json.loads(column) or {}
1337 return json.loads(column) or {}
1336 except TypeError:
1338 except TypeError:
1337 return {}
1339 return {}
1338
1340
1339 @hybrid_property
1341 @hybrid_property
1340 def description_safe(self):
1342 def description_safe(self):
1341 from rhodecode.lib import helpers as h
1343 from rhodecode.lib import helpers as h
1342 return h.escape(self.user_group_description)
1344 return h.escape(self.user_group_description)
1343
1345
1344 @hybrid_property
1346 @hybrid_property
1345 def group_data(self):
1347 def group_data(self):
1346 return self._load_group_data(self._group_data)
1348 return self._load_group_data(self._group_data)
1347
1349
1348 @group_data.expression
1350 @group_data.expression
1349 def group_data(self, **kwargs):
1351 def group_data(self, **kwargs):
1350 return self._group_data
1352 return self._group_data
1351
1353
1352 @group_data.setter
1354 @group_data.setter
1353 def group_data(self, val):
1355 def group_data(self, val):
1354 try:
1356 try:
1355 self._group_data = json.dumps(val)
1357 self._group_data = json.dumps(val)
1356 except Exception:
1358 except Exception:
1357 log.error(traceback.format_exc())
1359 log.error(traceback.format_exc())
1358
1360
1359 @classmethod
1361 @classmethod
1360 def _load_sync(cls, group_data):
1362 def _load_sync(cls, group_data):
1361 if group_data:
1363 if group_data:
1362 return group_data.get('extern_type')
1364 return group_data.get('extern_type')
1363
1365
1364 @property
1366 @property
1365 def sync(self):
1367 def sync(self):
1366 return self._load_sync(self.group_data)
1368 return self._load_sync(self.group_data)
1367
1369
1368 def __unicode__(self):
1370 def __unicode__(self):
1369 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1371 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1370 self.users_group_id,
1372 self.users_group_id,
1371 self.users_group_name)
1373 self.users_group_name)
1372
1374
1373 @classmethod
1375 @classmethod
1374 def get_by_group_name(cls, group_name, cache=False,
1376 def get_by_group_name(cls, group_name, cache=False,
1375 case_insensitive=False):
1377 case_insensitive=False):
1376 if case_insensitive:
1378 if case_insensitive:
1377 q = cls.query().filter(func.lower(cls.users_group_name) ==
1379 q = cls.query().filter(func.lower(cls.users_group_name) ==
1378 func.lower(group_name))
1380 func.lower(group_name))
1379
1381
1380 else:
1382 else:
1381 q = cls.query().filter(cls.users_group_name == group_name)
1383 q = cls.query().filter(cls.users_group_name == group_name)
1382 if cache:
1384 if cache:
1383 q = q.options(
1385 q = q.options(
1384 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1386 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1385 return q.scalar()
1387 return q.scalar()
1386
1388
1387 @classmethod
1389 @classmethod
1388 def get(cls, user_group_id, cache=False):
1390 def get(cls, user_group_id, cache=False):
1389 if not user_group_id:
1391 if not user_group_id:
1390 return
1392 return
1391
1393
1392 user_group = cls.query()
1394 user_group = cls.query()
1393 if cache:
1395 if cache:
1394 user_group = user_group.options(
1396 user_group = user_group.options(
1395 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1397 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1396 return user_group.get(user_group_id)
1398 return user_group.get(user_group_id)
1397
1399
1398 def permissions(self, with_admins=True, with_owner=True,
1400 def permissions(self, with_admins=True, with_owner=True,
1399 expand_from_user_groups=False):
1401 expand_from_user_groups=False):
1400 """
1402 """
1401 Permissions for user groups
1403 Permissions for user groups
1402 """
1404 """
1403 _admin_perm = 'usergroup.admin'
1405 _admin_perm = 'usergroup.admin'
1404
1406
1405 owner_row = []
1407 owner_row = []
1406 if with_owner:
1408 if with_owner:
1407 usr = AttributeDict(self.user.get_dict())
1409 usr = AttributeDict(self.user.get_dict())
1408 usr.owner_row = True
1410 usr.owner_row = True
1409 usr.permission = _admin_perm
1411 usr.permission = _admin_perm
1410 owner_row.append(usr)
1412 owner_row.append(usr)
1411
1413
1412 super_admin_ids = []
1414 super_admin_ids = []
1413 super_admin_rows = []
1415 super_admin_rows = []
1414 if with_admins:
1416 if with_admins:
1415 for usr in User.get_all_super_admins():
1417 for usr in User.get_all_super_admins():
1416 super_admin_ids.append(usr.user_id)
1418 super_admin_ids.append(usr.user_id)
1417 # if this admin is also owner, don't double the record
1419 # if this admin is also owner, don't double the record
1418 if usr.user_id == owner_row[0].user_id:
1420 if usr.user_id == owner_row[0].user_id:
1419 owner_row[0].admin_row = True
1421 owner_row[0].admin_row = True
1420 else:
1422 else:
1421 usr = AttributeDict(usr.get_dict())
1423 usr = AttributeDict(usr.get_dict())
1422 usr.admin_row = True
1424 usr.admin_row = True
1423 usr.permission = _admin_perm
1425 usr.permission = _admin_perm
1424 super_admin_rows.append(usr)
1426 super_admin_rows.append(usr)
1425
1427
1426 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1428 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1427 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1429 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1428 joinedload(UserUserGroupToPerm.user),
1430 joinedload(UserUserGroupToPerm.user),
1429 joinedload(UserUserGroupToPerm.permission),)
1431 joinedload(UserUserGroupToPerm.permission),)
1430
1432
1431 # get owners and admins and permissions. We do a trick of re-writing
1433 # get owners and admins and permissions. We do a trick of re-writing
1432 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1434 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1433 # has a global reference and changing one object propagates to all
1435 # has a global reference and changing one object propagates to all
1434 # others. This means if admin is also an owner admin_row that change
1436 # others. This means if admin is also an owner admin_row that change
1435 # would propagate to both objects
1437 # would propagate to both objects
1436 perm_rows = []
1438 perm_rows = []
1437 for _usr in q.all():
1439 for _usr in q.all():
1438 usr = AttributeDict(_usr.user.get_dict())
1440 usr = AttributeDict(_usr.user.get_dict())
1439 # if this user is also owner/admin, mark as duplicate record
1441 # if this user is also owner/admin, mark as duplicate record
1440 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1442 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1441 usr.duplicate_perm = True
1443 usr.duplicate_perm = True
1442 usr.permission = _usr.permission.permission_name
1444 usr.permission = _usr.permission.permission_name
1443 perm_rows.append(usr)
1445 perm_rows.append(usr)
1444
1446
1445 # filter the perm rows by 'default' first and then sort them by
1447 # filter the perm rows by 'default' first and then sort them by
1446 # admin,write,read,none permissions sorted again alphabetically in
1448 # admin,write,read,none permissions sorted again alphabetically in
1447 # each group
1449 # each group
1448 perm_rows = sorted(perm_rows, key=display_user_sort)
1450 perm_rows = sorted(perm_rows, key=display_user_sort)
1449
1451
1450 user_groups_rows = []
1452 user_groups_rows = []
1451 if expand_from_user_groups:
1453 if expand_from_user_groups:
1452 for ug in self.permission_user_groups(with_members=True):
1454 for ug in self.permission_user_groups(with_members=True):
1453 for user_data in ug.members:
1455 for user_data in ug.members:
1454 user_groups_rows.append(user_data)
1456 user_groups_rows.append(user_data)
1455
1457
1456 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1458 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1457
1459
1458 def permission_user_groups(self, with_members=False):
1460 def permission_user_groups(self, with_members=False):
1459 q = UserGroupUserGroupToPerm.query()\
1461 q = UserGroupUserGroupToPerm.query()\
1460 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1462 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1461 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1463 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1462 joinedload(UserGroupUserGroupToPerm.target_user_group),
1464 joinedload(UserGroupUserGroupToPerm.target_user_group),
1463 joinedload(UserGroupUserGroupToPerm.permission),)
1465 joinedload(UserGroupUserGroupToPerm.permission),)
1464
1466
1465 perm_rows = []
1467 perm_rows = []
1466 for _user_group in q.all():
1468 for _user_group in q.all():
1467 entry = AttributeDict(_user_group.user_group.get_dict())
1469 entry = AttributeDict(_user_group.user_group.get_dict())
1468 entry.permission = _user_group.permission.permission_name
1470 entry.permission = _user_group.permission.permission_name
1469 if with_members:
1471 if with_members:
1470 entry.members = [x.user.get_dict()
1472 entry.members = [x.user.get_dict()
1471 for x in _user_group.user_group.members]
1473 for x in _user_group.user_group.members]
1472 perm_rows.append(entry)
1474 perm_rows.append(entry)
1473
1475
1474 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1476 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1475 return perm_rows
1477 return perm_rows
1476
1478
1477 def _get_default_perms(self, user_group, suffix=''):
1479 def _get_default_perms(self, user_group, suffix=''):
1478 from rhodecode.model.permission import PermissionModel
1480 from rhodecode.model.permission import PermissionModel
1479 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1481 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1480
1482
1481 def get_default_perms(self, suffix=''):
1483 def get_default_perms(self, suffix=''):
1482 return self._get_default_perms(self, suffix)
1484 return self._get_default_perms(self, suffix)
1483
1485
1484 def get_api_data(self, with_group_members=True, include_secrets=False):
1486 def get_api_data(self, with_group_members=True, include_secrets=False):
1485 """
1487 """
1486 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1488 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1487 basically forwarded.
1489 basically forwarded.
1488
1490
1489 """
1491 """
1490 user_group = self
1492 user_group = self
1491 data = {
1493 data = {
1492 'users_group_id': user_group.users_group_id,
1494 'users_group_id': user_group.users_group_id,
1493 'group_name': user_group.users_group_name,
1495 'group_name': user_group.users_group_name,
1494 'group_description': user_group.user_group_description,
1496 'group_description': user_group.user_group_description,
1495 'active': user_group.users_group_active,
1497 'active': user_group.users_group_active,
1496 'owner': user_group.user.username,
1498 'owner': user_group.user.username,
1497 'sync': user_group.sync,
1499 'sync': user_group.sync,
1498 'owner_email': user_group.user.email,
1500 'owner_email': user_group.user.email,
1499 }
1501 }
1500
1502
1501 if with_group_members:
1503 if with_group_members:
1502 users = []
1504 users = []
1503 for user in user_group.members:
1505 for user in user_group.members:
1504 user = user.user
1506 user = user.user
1505 users.append(user.get_api_data(include_secrets=include_secrets))
1507 users.append(user.get_api_data(include_secrets=include_secrets))
1506 data['users'] = users
1508 data['users'] = users
1507
1509
1508 return data
1510 return data
1509
1511
1510
1512
1511 class UserGroupMember(Base, BaseModel):
1513 class UserGroupMember(Base, BaseModel):
1512 __tablename__ = 'users_groups_members'
1514 __tablename__ = 'users_groups_members'
1513 __table_args__ = (
1515 __table_args__ = (
1514 base_table_args,
1516 base_table_args,
1515 )
1517 )
1516
1518
1517 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1519 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1518 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1520 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1519 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1521 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1520
1522
1521 user = relationship('User', lazy='joined')
1523 user = relationship('User', lazy='joined')
1522 users_group = relationship('UserGroup')
1524 users_group = relationship('UserGroup')
1523
1525
1524 def __init__(self, gr_id='', u_id=''):
1526 def __init__(self, gr_id='', u_id=''):
1525 self.users_group_id = gr_id
1527 self.users_group_id = gr_id
1526 self.user_id = u_id
1528 self.user_id = u_id
1527
1529
1528
1530
1529 class RepositoryField(Base, BaseModel):
1531 class RepositoryField(Base, BaseModel):
1530 __tablename__ = 'repositories_fields'
1532 __tablename__ = 'repositories_fields'
1531 __table_args__ = (
1533 __table_args__ = (
1532 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1534 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1533 base_table_args,
1535 base_table_args,
1534 )
1536 )
1535
1537
1536 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1538 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1537
1539
1538 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1540 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1539 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1541 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1540 field_key = Column("field_key", String(250))
1542 field_key = Column("field_key", String(250))
1541 field_label = Column("field_label", String(1024), nullable=False)
1543 field_label = Column("field_label", String(1024), nullable=False)
1542 field_value = Column("field_value", String(10000), nullable=False)
1544 field_value = Column("field_value", String(10000), nullable=False)
1543 field_desc = Column("field_desc", String(1024), nullable=False)
1545 field_desc = Column("field_desc", String(1024), nullable=False)
1544 field_type = Column("field_type", String(255), nullable=False, unique=None)
1546 field_type = Column("field_type", String(255), nullable=False, unique=None)
1545 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1547 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1546
1548
1547 repository = relationship('Repository')
1549 repository = relationship('Repository')
1548
1550
1549 @property
1551 @property
1550 def field_key_prefixed(self):
1552 def field_key_prefixed(self):
1551 return 'ex_%s' % self.field_key
1553 return 'ex_%s' % self.field_key
1552
1554
1553 @classmethod
1555 @classmethod
1554 def un_prefix_key(cls, key):
1556 def un_prefix_key(cls, key):
1555 if key.startswith(cls.PREFIX):
1557 if key.startswith(cls.PREFIX):
1556 return key[len(cls.PREFIX):]
1558 return key[len(cls.PREFIX):]
1557 return key
1559 return key
1558
1560
1559 @classmethod
1561 @classmethod
1560 def get_by_key_name(cls, key, repo):
1562 def get_by_key_name(cls, key, repo):
1561 row = cls.query()\
1563 row = cls.query()\
1562 .filter(cls.repository == repo)\
1564 .filter(cls.repository == repo)\
1563 .filter(cls.field_key == key).scalar()
1565 .filter(cls.field_key == key).scalar()
1564 return row
1566 return row
1565
1567
1566
1568
1567 class Repository(Base, BaseModel):
1569 class Repository(Base, BaseModel):
1568 __tablename__ = 'repositories'
1570 __tablename__ = 'repositories'
1569 __table_args__ = (
1571 __table_args__ = (
1570 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1572 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1571 base_table_args,
1573 base_table_args,
1572 )
1574 )
1573 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1575 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1574 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1576 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1575 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1577 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1576
1578
1577 STATE_CREATED = 'repo_state_created'
1579 STATE_CREATED = 'repo_state_created'
1578 STATE_PENDING = 'repo_state_pending'
1580 STATE_PENDING = 'repo_state_pending'
1579 STATE_ERROR = 'repo_state_error'
1581 STATE_ERROR = 'repo_state_error'
1580
1582
1581 LOCK_AUTOMATIC = 'lock_auto'
1583 LOCK_AUTOMATIC = 'lock_auto'
1582 LOCK_API = 'lock_api'
1584 LOCK_API = 'lock_api'
1583 LOCK_WEB = 'lock_web'
1585 LOCK_WEB = 'lock_web'
1584 LOCK_PULL = 'lock_pull'
1586 LOCK_PULL = 'lock_pull'
1585
1587
1586 NAME_SEP = URL_SEP
1588 NAME_SEP = URL_SEP
1587
1589
1588 repo_id = Column(
1590 repo_id = Column(
1589 "repo_id", Integer(), nullable=False, unique=True, default=None,
1591 "repo_id", Integer(), nullable=False, unique=True, default=None,
1590 primary_key=True)
1592 primary_key=True)
1591 _repo_name = Column(
1593 _repo_name = Column(
1592 "repo_name", Text(), nullable=False, default=None)
1594 "repo_name", Text(), nullable=False, default=None)
1593 _repo_name_hash = Column(
1595 _repo_name_hash = Column(
1594 "repo_name_hash", String(255), nullable=False, unique=True)
1596 "repo_name_hash", String(255), nullable=False, unique=True)
1595 repo_state = Column("repo_state", String(255), nullable=True)
1597 repo_state = Column("repo_state", String(255), nullable=True)
1596
1598
1597 clone_uri = Column(
1599 clone_uri = Column(
1598 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1600 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1599 default=None)
1601 default=None)
1600 push_uri = Column(
1602 push_uri = Column(
1601 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1603 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1602 default=None)
1604 default=None)
1603 repo_type = Column(
1605 repo_type = Column(
1604 "repo_type", String(255), nullable=False, unique=False, default=None)
1606 "repo_type", String(255), nullable=False, unique=False, default=None)
1605 user_id = Column(
1607 user_id = Column(
1606 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1608 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1607 unique=False, default=None)
1609 unique=False, default=None)
1608 private = Column(
1610 private = Column(
1609 "private", Boolean(), nullable=True, unique=None, default=None)
1611 "private", Boolean(), nullable=True, unique=None, default=None)
1610 archived = Column(
1612 archived = Column(
1611 "archived", Boolean(), nullable=True, unique=None, default=None)
1613 "archived", Boolean(), nullable=True, unique=None, default=None)
1612 enable_statistics = Column(
1614 enable_statistics = Column(
1613 "statistics", Boolean(), nullable=True, unique=None, default=True)
1615 "statistics", Boolean(), nullable=True, unique=None, default=True)
1614 enable_downloads = Column(
1616 enable_downloads = Column(
1615 "downloads", Boolean(), nullable=True, unique=None, default=True)
1617 "downloads", Boolean(), nullable=True, unique=None, default=True)
1616 description = Column(
1618 description = Column(
1617 "description", String(10000), nullable=True, unique=None, default=None)
1619 "description", String(10000), nullable=True, unique=None, default=None)
1618 created_on = Column(
1620 created_on = Column(
1619 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1621 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1620 default=datetime.datetime.now)
1622 default=datetime.datetime.now)
1621 updated_on = Column(
1623 updated_on = Column(
1622 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1624 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1623 default=datetime.datetime.now)
1625 default=datetime.datetime.now)
1624 _landing_revision = Column(
1626 _landing_revision = Column(
1625 "landing_revision", String(255), nullable=False, unique=False,
1627 "landing_revision", String(255), nullable=False, unique=False,
1626 default=None)
1628 default=None)
1627 enable_locking = Column(
1629 enable_locking = Column(
1628 "enable_locking", Boolean(), nullable=False, unique=None,
1630 "enable_locking", Boolean(), nullable=False, unique=None,
1629 default=False)
1631 default=False)
1630 _locked = Column(
1632 _locked = Column(
1631 "locked", String(255), nullable=True, unique=False, default=None)
1633 "locked", String(255), nullable=True, unique=False, default=None)
1632 _changeset_cache = Column(
1634 _changeset_cache = Column(
1633 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1635 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1634
1636
1635 fork_id = Column(
1637 fork_id = Column(
1636 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1638 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1637 nullable=True, unique=False, default=None)
1639 nullable=True, unique=False, default=None)
1638 group_id = Column(
1640 group_id = Column(
1639 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1641 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1640 unique=False, default=None)
1642 unique=False, default=None)
1641
1643
1642 user = relationship('User', lazy='joined')
1644 user = relationship('User', lazy='joined')
1643 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1645 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1644 group = relationship('RepoGroup', lazy='joined')
1646 group = relationship('RepoGroup', lazy='joined')
1645 repo_to_perm = relationship(
1647 repo_to_perm = relationship(
1646 'UserRepoToPerm', cascade='all',
1648 'UserRepoToPerm', cascade='all',
1647 order_by='UserRepoToPerm.repo_to_perm_id')
1649 order_by='UserRepoToPerm.repo_to_perm_id')
1648 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1650 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1649 stats = relationship('Statistics', cascade='all', uselist=False)
1651 stats = relationship('Statistics', cascade='all', uselist=False)
1650
1652
1651 followers = relationship(
1653 followers = relationship(
1652 'UserFollowing',
1654 'UserFollowing',
1653 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1655 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1654 cascade='all')
1656 cascade='all')
1655 extra_fields = relationship(
1657 extra_fields = relationship(
1656 'RepositoryField', cascade="all, delete, delete-orphan")
1658 'RepositoryField', cascade="all, delete, delete-orphan")
1657 logs = relationship('UserLog')
1659 logs = relationship('UserLog')
1658 comments = relationship(
1660 comments = relationship(
1659 'ChangesetComment', cascade="all, delete, delete-orphan")
1661 'ChangesetComment', cascade="all, delete, delete-orphan")
1660 pull_requests_source = relationship(
1662 pull_requests_source = relationship(
1661 'PullRequest',
1663 'PullRequest',
1662 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1664 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1663 cascade="all, delete, delete-orphan")
1665 cascade="all, delete, delete-orphan")
1664 pull_requests_target = relationship(
1666 pull_requests_target = relationship(
1665 'PullRequest',
1667 'PullRequest',
1666 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1668 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1667 cascade="all, delete, delete-orphan")
1669 cascade="all, delete, delete-orphan")
1668 ui = relationship('RepoRhodeCodeUi', cascade="all")
1670 ui = relationship('RepoRhodeCodeUi', cascade="all")
1669 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1671 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1670 integrations = relationship('Integration',
1672 integrations = relationship('Integration',
1671 cascade="all, delete, delete-orphan")
1673 cascade="all, delete, delete-orphan")
1672
1674
1673 scoped_tokens = relationship('UserApiKeys', cascade="all")
1675 scoped_tokens = relationship('UserApiKeys', cascade="all")
1674
1676
1675 def __unicode__(self):
1677 def __unicode__(self):
1676 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1678 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1677 safe_unicode(self.repo_name))
1679 safe_unicode(self.repo_name))
1678
1680
1679 @hybrid_property
1681 @hybrid_property
1680 def description_safe(self):
1682 def description_safe(self):
1681 from rhodecode.lib import helpers as h
1683 from rhodecode.lib import helpers as h
1682 return h.escape(self.description)
1684 return h.escape(self.description)
1683
1685
1684 @hybrid_property
1686 @hybrid_property
1685 def landing_rev(self):
1687 def landing_rev(self):
1686 # always should return [rev_type, rev]
1688 # always should return [rev_type, rev]
1687 if self._landing_revision:
1689 if self._landing_revision:
1688 _rev_info = self._landing_revision.split(':')
1690 _rev_info = self._landing_revision.split(':')
1689 if len(_rev_info) < 2:
1691 if len(_rev_info) < 2:
1690 _rev_info.insert(0, 'rev')
1692 _rev_info.insert(0, 'rev')
1691 return [_rev_info[0], _rev_info[1]]
1693 return [_rev_info[0], _rev_info[1]]
1692 return [None, None]
1694 return [None, None]
1693
1695
1694 @landing_rev.setter
1696 @landing_rev.setter
1695 def landing_rev(self, val):
1697 def landing_rev(self, val):
1696 if ':' not in val:
1698 if ':' not in val:
1697 raise ValueError('value must be delimited with `:` and consist '
1699 raise ValueError('value must be delimited with `:` and consist '
1698 'of <rev_type>:<rev>, got %s instead' % val)
1700 'of <rev_type>:<rev>, got %s instead' % val)
1699 self._landing_revision = val
1701 self._landing_revision = val
1700
1702
1701 @hybrid_property
1703 @hybrid_property
1702 def locked(self):
1704 def locked(self):
1703 if self._locked:
1705 if self._locked:
1704 user_id, timelocked, reason = self._locked.split(':')
1706 user_id, timelocked, reason = self._locked.split(':')
1705 lock_values = int(user_id), timelocked, reason
1707 lock_values = int(user_id), timelocked, reason
1706 else:
1708 else:
1707 lock_values = [None, None, None]
1709 lock_values = [None, None, None]
1708 return lock_values
1710 return lock_values
1709
1711
1710 @locked.setter
1712 @locked.setter
1711 def locked(self, val):
1713 def locked(self, val):
1712 if val and isinstance(val, (list, tuple)):
1714 if val and isinstance(val, (list, tuple)):
1713 self._locked = ':'.join(map(str, val))
1715 self._locked = ':'.join(map(str, val))
1714 else:
1716 else:
1715 self._locked = None
1717 self._locked = None
1716
1718
1717 @hybrid_property
1719 @hybrid_property
1718 def changeset_cache(self):
1720 def changeset_cache(self):
1719 from rhodecode.lib.vcs.backends.base import EmptyCommit
1721 from rhodecode.lib.vcs.backends.base import EmptyCommit
1720 dummy = EmptyCommit().__json__()
1722 dummy = EmptyCommit().__json__()
1721 if not self._changeset_cache:
1723 if not self._changeset_cache:
1722 return dummy
1724 return dummy
1723 try:
1725 try:
1724 return json.loads(self._changeset_cache)
1726 return json.loads(self._changeset_cache)
1725 except TypeError:
1727 except TypeError:
1726 return dummy
1728 return dummy
1727 except Exception:
1729 except Exception:
1728 log.error(traceback.format_exc())
1730 log.error(traceback.format_exc())
1729 return dummy
1731 return dummy
1730
1732
1731 @changeset_cache.setter
1733 @changeset_cache.setter
1732 def changeset_cache(self, val):
1734 def changeset_cache(self, val):
1733 try:
1735 try:
1734 self._changeset_cache = json.dumps(val)
1736 self._changeset_cache = json.dumps(val)
1735 except Exception:
1737 except Exception:
1736 log.error(traceback.format_exc())
1738 log.error(traceback.format_exc())
1737
1739
1738 @hybrid_property
1740 @hybrid_property
1739 def repo_name(self):
1741 def repo_name(self):
1740 return self._repo_name
1742 return self._repo_name
1741
1743
1742 @repo_name.setter
1744 @repo_name.setter
1743 def repo_name(self, value):
1745 def repo_name(self, value):
1744 self._repo_name = value
1746 self._repo_name = value
1745 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1747 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1746
1748
1747 @classmethod
1749 @classmethod
1748 def normalize_repo_name(cls, repo_name):
1750 def normalize_repo_name(cls, repo_name):
1749 """
1751 """
1750 Normalizes os specific repo_name to the format internally stored inside
1752 Normalizes os specific repo_name to the format internally stored inside
1751 database using URL_SEP
1753 database using URL_SEP
1752
1754
1753 :param cls:
1755 :param cls:
1754 :param repo_name:
1756 :param repo_name:
1755 """
1757 """
1756 return cls.NAME_SEP.join(repo_name.split(os.sep))
1758 return cls.NAME_SEP.join(repo_name.split(os.sep))
1757
1759
1758 @classmethod
1760 @classmethod
1759 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1761 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1760 session = Session()
1762 session = Session()
1761 q = session.query(cls).filter(cls.repo_name == repo_name)
1763 q = session.query(cls).filter(cls.repo_name == repo_name)
1762
1764
1763 if cache:
1765 if cache:
1764 if identity_cache:
1766 if identity_cache:
1765 val = cls.identity_cache(session, 'repo_name', repo_name)
1767 val = cls.identity_cache(session, 'repo_name', repo_name)
1766 if val:
1768 if val:
1767 return val
1769 return val
1768 else:
1770 else:
1769 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1771 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1770 q = q.options(
1772 q = q.options(
1771 FromCache("sql_cache_short", cache_key))
1773 FromCache("sql_cache_short", cache_key))
1772
1774
1773 return q.scalar()
1775 return q.scalar()
1774
1776
1775 @classmethod
1777 @classmethod
1776 def get_by_id_or_repo_name(cls, repoid):
1778 def get_by_id_or_repo_name(cls, repoid):
1777 if isinstance(repoid, (int, long)):
1779 if isinstance(repoid, (int, long)):
1778 try:
1780 try:
1779 repo = cls.get(repoid)
1781 repo = cls.get(repoid)
1780 except ValueError:
1782 except ValueError:
1781 repo = None
1783 repo = None
1782 else:
1784 else:
1783 repo = cls.get_by_repo_name(repoid)
1785 repo = cls.get_by_repo_name(repoid)
1784 return repo
1786 return repo
1785
1787
1786 @classmethod
1788 @classmethod
1787 def get_by_full_path(cls, repo_full_path):
1789 def get_by_full_path(cls, repo_full_path):
1788 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1790 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1789 repo_name = cls.normalize_repo_name(repo_name)
1791 repo_name = cls.normalize_repo_name(repo_name)
1790 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1792 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1791
1793
1792 @classmethod
1794 @classmethod
1793 def get_repo_forks(cls, repo_id):
1795 def get_repo_forks(cls, repo_id):
1794 return cls.query().filter(Repository.fork_id == repo_id)
1796 return cls.query().filter(Repository.fork_id == repo_id)
1795
1797
1796 @classmethod
1798 @classmethod
1797 def base_path(cls):
1799 def base_path(cls):
1798 """
1800 """
1799 Returns base path when all repos are stored
1801 Returns base path when all repos are stored
1800
1802
1801 :param cls:
1803 :param cls:
1802 """
1804 """
1803 q = Session().query(RhodeCodeUi)\
1805 q = Session().query(RhodeCodeUi)\
1804 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1806 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1805 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1807 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1806 return q.one().ui_value
1808 return q.one().ui_value
1807
1809
1808 @classmethod
1810 @classmethod
1809 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1811 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1810 case_insensitive=True, archived=False):
1812 case_insensitive=True, archived=False):
1811 q = Repository.query()
1813 q = Repository.query()
1812
1814
1813 if not archived:
1815 if not archived:
1814 q = q.filter(Repository.archived.isnot(true()))
1816 q = q.filter(Repository.archived.isnot(true()))
1815
1817
1816 if not isinstance(user_id, Optional):
1818 if not isinstance(user_id, Optional):
1817 q = q.filter(Repository.user_id == user_id)
1819 q = q.filter(Repository.user_id == user_id)
1818
1820
1819 if not isinstance(group_id, Optional):
1821 if not isinstance(group_id, Optional):
1820 q = q.filter(Repository.group_id == group_id)
1822 q = q.filter(Repository.group_id == group_id)
1821
1823
1822 if case_insensitive:
1824 if case_insensitive:
1823 q = q.order_by(func.lower(Repository.repo_name))
1825 q = q.order_by(func.lower(Repository.repo_name))
1824 else:
1826 else:
1825 q = q.order_by(Repository.repo_name)
1827 q = q.order_by(Repository.repo_name)
1826
1828
1827 return q.all()
1829 return q.all()
1828
1830
1829 @property
1831 @property
1830 def forks(self):
1832 def forks(self):
1831 """
1833 """
1832 Return forks of this repo
1834 Return forks of this repo
1833 """
1835 """
1834 return Repository.get_repo_forks(self.repo_id)
1836 return Repository.get_repo_forks(self.repo_id)
1835
1837
1836 @property
1838 @property
1837 def parent(self):
1839 def parent(self):
1838 """
1840 """
1839 Returns fork parent
1841 Returns fork parent
1840 """
1842 """
1841 return self.fork
1843 return self.fork
1842
1844
1843 @property
1845 @property
1844 def just_name(self):
1846 def just_name(self):
1845 return self.repo_name.split(self.NAME_SEP)[-1]
1847 return self.repo_name.split(self.NAME_SEP)[-1]
1846
1848
1847 @property
1849 @property
1848 def groups_with_parents(self):
1850 def groups_with_parents(self):
1849 groups = []
1851 groups = []
1850 if self.group is None:
1852 if self.group is None:
1851 return groups
1853 return groups
1852
1854
1853 cur_gr = self.group
1855 cur_gr = self.group
1854 groups.insert(0, cur_gr)
1856 groups.insert(0, cur_gr)
1855 while 1:
1857 while 1:
1856 gr = getattr(cur_gr, 'parent_group', None)
1858 gr = getattr(cur_gr, 'parent_group', None)
1857 cur_gr = cur_gr.parent_group
1859 cur_gr = cur_gr.parent_group
1858 if gr is None:
1860 if gr is None:
1859 break
1861 break
1860 groups.insert(0, gr)
1862 groups.insert(0, gr)
1861
1863
1862 return groups
1864 return groups
1863
1865
1864 @property
1866 @property
1865 def groups_and_repo(self):
1867 def groups_and_repo(self):
1866 return self.groups_with_parents, self
1868 return self.groups_with_parents, self
1867
1869
1868 @LazyProperty
1870 @LazyProperty
1869 def repo_path(self):
1871 def repo_path(self):
1870 """
1872 """
1871 Returns base full path for that repository means where it actually
1873 Returns base full path for that repository means where it actually
1872 exists on a filesystem
1874 exists on a filesystem
1873 """
1875 """
1874 q = Session().query(RhodeCodeUi).filter(
1876 q = Session().query(RhodeCodeUi).filter(
1875 RhodeCodeUi.ui_key == self.NAME_SEP)
1877 RhodeCodeUi.ui_key == self.NAME_SEP)
1876 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1878 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1877 return q.one().ui_value
1879 return q.one().ui_value
1878
1880
1879 @property
1881 @property
1880 def repo_full_path(self):
1882 def repo_full_path(self):
1881 p = [self.repo_path]
1883 p = [self.repo_path]
1882 # we need to split the name by / since this is how we store the
1884 # we need to split the name by / since this is how we store the
1883 # names in the database, but that eventually needs to be converted
1885 # names in the database, but that eventually needs to be converted
1884 # into a valid system path
1886 # into a valid system path
1885 p += self.repo_name.split(self.NAME_SEP)
1887 p += self.repo_name.split(self.NAME_SEP)
1886 return os.path.join(*map(safe_unicode, p))
1888 return os.path.join(*map(safe_unicode, p))
1887
1889
1888 @property
1890 @property
1889 def cache_keys(self):
1891 def cache_keys(self):
1890 """
1892 """
1891 Returns associated cache keys for that repo
1893 Returns associated cache keys for that repo
1892 """
1894 """
1893 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1895 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1894 repo_id=self.repo_id)
1896 repo_id=self.repo_id)
1895 return CacheKey.query()\
1897 return CacheKey.query()\
1896 .filter(CacheKey.cache_args == invalidation_namespace)\
1898 .filter(CacheKey.cache_args == invalidation_namespace)\
1897 .order_by(CacheKey.cache_key)\
1899 .order_by(CacheKey.cache_key)\
1898 .all()
1900 .all()
1899
1901
1900 @property
1902 @property
1901 def cached_diffs_relative_dir(self):
1903 def cached_diffs_relative_dir(self):
1902 """
1904 """
1903 Return a relative to the repository store path of cached diffs
1905 Return a relative to the repository store path of cached diffs
1904 used for safe display for users, who shouldn't know the absolute store
1906 used for safe display for users, who shouldn't know the absolute store
1905 path
1907 path
1906 """
1908 """
1907 return os.path.join(
1909 return os.path.join(
1908 os.path.dirname(self.repo_name),
1910 os.path.dirname(self.repo_name),
1909 self.cached_diffs_dir.split(os.path.sep)[-1])
1911 self.cached_diffs_dir.split(os.path.sep)[-1])
1910
1912
1911 @property
1913 @property
1912 def cached_diffs_dir(self):
1914 def cached_diffs_dir(self):
1913 path = self.repo_full_path
1915 path = self.repo_full_path
1914 return os.path.join(
1916 return os.path.join(
1915 os.path.dirname(path),
1917 os.path.dirname(path),
1916 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1918 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1917
1919
1918 def cached_diffs(self):
1920 def cached_diffs(self):
1919 diff_cache_dir = self.cached_diffs_dir
1921 diff_cache_dir = self.cached_diffs_dir
1920 if os.path.isdir(diff_cache_dir):
1922 if os.path.isdir(diff_cache_dir):
1921 return os.listdir(diff_cache_dir)
1923 return os.listdir(diff_cache_dir)
1922 return []
1924 return []
1923
1925
1924 def shadow_repos(self):
1926 def shadow_repos(self):
1925 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1927 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1926 return [
1928 return [
1927 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1929 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1928 if x.startswith(shadow_repos_pattern)]
1930 if x.startswith(shadow_repos_pattern)]
1929
1931
1930 def get_new_name(self, repo_name):
1932 def get_new_name(self, repo_name):
1931 """
1933 """
1932 returns new full repository name based on assigned group and new new
1934 returns new full repository name based on assigned group and new new
1933
1935
1934 :param group_name:
1936 :param group_name:
1935 """
1937 """
1936 path_prefix = self.group.full_path_splitted if self.group else []
1938 path_prefix = self.group.full_path_splitted if self.group else []
1937 return self.NAME_SEP.join(path_prefix + [repo_name])
1939 return self.NAME_SEP.join(path_prefix + [repo_name])
1938
1940
1939 @property
1941 @property
1940 def _config(self):
1942 def _config(self):
1941 """
1943 """
1942 Returns db based config object.
1944 Returns db based config object.
1943 """
1945 """
1944 from rhodecode.lib.utils import make_db_config
1946 from rhodecode.lib.utils import make_db_config
1945 return make_db_config(clear_session=False, repo=self)
1947 return make_db_config(clear_session=False, repo=self)
1946
1948
1947 def permissions(self, with_admins=True, with_owner=True,
1949 def permissions(self, with_admins=True, with_owner=True,
1948 expand_from_user_groups=False):
1950 expand_from_user_groups=False):
1949 """
1951 """
1950 Permissions for repositories
1952 Permissions for repositories
1951 """
1953 """
1952 _admin_perm = 'repository.admin'
1954 _admin_perm = 'repository.admin'
1953
1955
1954 owner_row = []
1956 owner_row = []
1955 if with_owner:
1957 if with_owner:
1956 usr = AttributeDict(self.user.get_dict())
1958 usr = AttributeDict(self.user.get_dict())
1957 usr.owner_row = True
1959 usr.owner_row = True
1958 usr.permission = _admin_perm
1960 usr.permission = _admin_perm
1959 usr.permission_id = None
1961 usr.permission_id = None
1960 owner_row.append(usr)
1962 owner_row.append(usr)
1961
1963
1962 super_admin_ids = []
1964 super_admin_ids = []
1963 super_admin_rows = []
1965 super_admin_rows = []
1964 if with_admins:
1966 if with_admins:
1965 for usr in User.get_all_super_admins():
1967 for usr in User.get_all_super_admins():
1966 super_admin_ids.append(usr.user_id)
1968 super_admin_ids.append(usr.user_id)
1967 # if this admin is also owner, don't double the record
1969 # if this admin is also owner, don't double the record
1968 if usr.user_id == owner_row[0].user_id:
1970 if usr.user_id == owner_row[0].user_id:
1969 owner_row[0].admin_row = True
1971 owner_row[0].admin_row = True
1970 else:
1972 else:
1971 usr = AttributeDict(usr.get_dict())
1973 usr = AttributeDict(usr.get_dict())
1972 usr.admin_row = True
1974 usr.admin_row = True
1973 usr.permission = _admin_perm
1975 usr.permission = _admin_perm
1974 usr.permission_id = None
1976 usr.permission_id = None
1975 super_admin_rows.append(usr)
1977 super_admin_rows.append(usr)
1976
1978
1977 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1979 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1978 q = q.options(joinedload(UserRepoToPerm.repository),
1980 q = q.options(joinedload(UserRepoToPerm.repository),
1979 joinedload(UserRepoToPerm.user),
1981 joinedload(UserRepoToPerm.user),
1980 joinedload(UserRepoToPerm.permission),)
1982 joinedload(UserRepoToPerm.permission),)
1981
1983
1982 # get owners and admins and permissions. We do a trick of re-writing
1984 # get owners and admins and permissions. We do a trick of re-writing
1983 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1985 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1984 # has a global reference and changing one object propagates to all
1986 # has a global reference and changing one object propagates to all
1985 # others. This means if admin is also an owner admin_row that change
1987 # others. This means if admin is also an owner admin_row that change
1986 # would propagate to both objects
1988 # would propagate to both objects
1987 perm_rows = []
1989 perm_rows = []
1988 for _usr in q.all():
1990 for _usr in q.all():
1989 usr = AttributeDict(_usr.user.get_dict())
1991 usr = AttributeDict(_usr.user.get_dict())
1990 # if this user is also owner/admin, mark as duplicate record
1992 # if this user is also owner/admin, mark as duplicate record
1991 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1993 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1992 usr.duplicate_perm = True
1994 usr.duplicate_perm = True
1993 # also check if this permission is maybe used by branch_permissions
1995 # also check if this permission is maybe used by branch_permissions
1994 if _usr.branch_perm_entry:
1996 if _usr.branch_perm_entry:
1995 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
1997 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
1996
1998
1997 usr.permission = _usr.permission.permission_name
1999 usr.permission = _usr.permission.permission_name
1998 usr.permission_id = _usr.repo_to_perm_id
2000 usr.permission_id = _usr.repo_to_perm_id
1999 perm_rows.append(usr)
2001 perm_rows.append(usr)
2000
2002
2001 # filter the perm rows by 'default' first and then sort them by
2003 # filter the perm rows by 'default' first and then sort them by
2002 # admin,write,read,none permissions sorted again alphabetically in
2004 # admin,write,read,none permissions sorted again alphabetically in
2003 # each group
2005 # each group
2004 perm_rows = sorted(perm_rows, key=display_user_sort)
2006 perm_rows = sorted(perm_rows, key=display_user_sort)
2005
2007
2006 user_groups_rows = []
2008 user_groups_rows = []
2007 if expand_from_user_groups:
2009 if expand_from_user_groups:
2008 for ug in self.permission_user_groups(with_members=True):
2010 for ug in self.permission_user_groups(with_members=True):
2009 for user_data in ug.members:
2011 for user_data in ug.members:
2010 user_groups_rows.append(user_data)
2012 user_groups_rows.append(user_data)
2011
2013
2012 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2014 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2013
2015
2014 def permission_user_groups(self, with_members=True):
2016 def permission_user_groups(self, with_members=True):
2015 q = UserGroupRepoToPerm.query()\
2017 q = UserGroupRepoToPerm.query()\
2016 .filter(UserGroupRepoToPerm.repository == self)
2018 .filter(UserGroupRepoToPerm.repository == self)
2017 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2019 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2018 joinedload(UserGroupRepoToPerm.users_group),
2020 joinedload(UserGroupRepoToPerm.users_group),
2019 joinedload(UserGroupRepoToPerm.permission),)
2021 joinedload(UserGroupRepoToPerm.permission),)
2020
2022
2021 perm_rows = []
2023 perm_rows = []
2022 for _user_group in q.all():
2024 for _user_group in q.all():
2023 entry = AttributeDict(_user_group.users_group.get_dict())
2025 entry = AttributeDict(_user_group.users_group.get_dict())
2024 entry.permission = _user_group.permission.permission_name
2026 entry.permission = _user_group.permission.permission_name
2025 if with_members:
2027 if with_members:
2026 entry.members = [x.user.get_dict()
2028 entry.members = [x.user.get_dict()
2027 for x in _user_group.users_group.members]
2029 for x in _user_group.users_group.members]
2028 perm_rows.append(entry)
2030 perm_rows.append(entry)
2029
2031
2030 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2032 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2031 return perm_rows
2033 return perm_rows
2032
2034
2033 def get_api_data(self, include_secrets=False):
2035 def get_api_data(self, include_secrets=False):
2034 """
2036 """
2035 Common function for generating repo api data
2037 Common function for generating repo api data
2036
2038
2037 :param include_secrets: See :meth:`User.get_api_data`.
2039 :param include_secrets: See :meth:`User.get_api_data`.
2038
2040
2039 """
2041 """
2040 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2042 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2041 # move this methods on models level.
2043 # move this methods on models level.
2042 from rhodecode.model.settings import SettingsModel
2044 from rhodecode.model.settings import SettingsModel
2043 from rhodecode.model.repo import RepoModel
2045 from rhodecode.model.repo import RepoModel
2044
2046
2045 repo = self
2047 repo = self
2046 _user_id, _time, _reason = self.locked
2048 _user_id, _time, _reason = self.locked
2047
2049
2048 data = {
2050 data = {
2049 'repo_id': repo.repo_id,
2051 'repo_id': repo.repo_id,
2050 'repo_name': repo.repo_name,
2052 'repo_name': repo.repo_name,
2051 'repo_type': repo.repo_type,
2053 'repo_type': repo.repo_type,
2052 'clone_uri': repo.clone_uri or '',
2054 'clone_uri': repo.clone_uri or '',
2053 'push_uri': repo.push_uri or '',
2055 'push_uri': repo.push_uri or '',
2054 'url': RepoModel().get_url(self),
2056 'url': RepoModel().get_url(self),
2055 'private': repo.private,
2057 'private': repo.private,
2056 'created_on': repo.created_on,
2058 'created_on': repo.created_on,
2057 'description': repo.description_safe,
2059 'description': repo.description_safe,
2058 'landing_rev': repo.landing_rev,
2060 'landing_rev': repo.landing_rev,
2059 'owner': repo.user.username,
2061 'owner': repo.user.username,
2060 'fork_of': repo.fork.repo_name if repo.fork else None,
2062 'fork_of': repo.fork.repo_name if repo.fork else None,
2061 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2063 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2062 'enable_statistics': repo.enable_statistics,
2064 'enable_statistics': repo.enable_statistics,
2063 'enable_locking': repo.enable_locking,
2065 'enable_locking': repo.enable_locking,
2064 'enable_downloads': repo.enable_downloads,
2066 'enable_downloads': repo.enable_downloads,
2065 'last_changeset': repo.changeset_cache,
2067 'last_changeset': repo.changeset_cache,
2066 'locked_by': User.get(_user_id).get_api_data(
2068 'locked_by': User.get(_user_id).get_api_data(
2067 include_secrets=include_secrets) if _user_id else None,
2069 include_secrets=include_secrets) if _user_id else None,
2068 'locked_date': time_to_datetime(_time) if _time else None,
2070 'locked_date': time_to_datetime(_time) if _time else None,
2069 'lock_reason': _reason if _reason else None,
2071 'lock_reason': _reason if _reason else None,
2070 }
2072 }
2071
2073
2072 # TODO: mikhail: should be per-repo settings here
2074 # TODO: mikhail: should be per-repo settings here
2073 rc_config = SettingsModel().get_all_settings()
2075 rc_config = SettingsModel().get_all_settings()
2074 repository_fields = str2bool(
2076 repository_fields = str2bool(
2075 rc_config.get('rhodecode_repository_fields'))
2077 rc_config.get('rhodecode_repository_fields'))
2076 if repository_fields:
2078 if repository_fields:
2077 for f in self.extra_fields:
2079 for f in self.extra_fields:
2078 data[f.field_key_prefixed] = f.field_value
2080 data[f.field_key_prefixed] = f.field_value
2079
2081
2080 return data
2082 return data
2081
2083
2082 @classmethod
2084 @classmethod
2083 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2085 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2084 if not lock_time:
2086 if not lock_time:
2085 lock_time = time.time()
2087 lock_time = time.time()
2086 if not lock_reason:
2088 if not lock_reason:
2087 lock_reason = cls.LOCK_AUTOMATIC
2089 lock_reason = cls.LOCK_AUTOMATIC
2088 repo.locked = [user_id, lock_time, lock_reason]
2090 repo.locked = [user_id, lock_time, lock_reason]
2089 Session().add(repo)
2091 Session().add(repo)
2090 Session().commit()
2092 Session().commit()
2091
2093
2092 @classmethod
2094 @classmethod
2093 def unlock(cls, repo):
2095 def unlock(cls, repo):
2094 repo.locked = None
2096 repo.locked = None
2095 Session().add(repo)
2097 Session().add(repo)
2096 Session().commit()
2098 Session().commit()
2097
2099
2098 @classmethod
2100 @classmethod
2099 def getlock(cls, repo):
2101 def getlock(cls, repo):
2100 return repo.locked
2102 return repo.locked
2101
2103
2102 def is_user_lock(self, user_id):
2104 def is_user_lock(self, user_id):
2103 if self.lock[0]:
2105 if self.lock[0]:
2104 lock_user_id = safe_int(self.lock[0])
2106 lock_user_id = safe_int(self.lock[0])
2105 user_id = safe_int(user_id)
2107 user_id = safe_int(user_id)
2106 # both are ints, and they are equal
2108 # both are ints, and they are equal
2107 return all([lock_user_id, user_id]) and lock_user_id == user_id
2109 return all([lock_user_id, user_id]) and lock_user_id == user_id
2108
2110
2109 return False
2111 return False
2110
2112
2111 def get_locking_state(self, action, user_id, only_when_enabled=True):
2113 def get_locking_state(self, action, user_id, only_when_enabled=True):
2112 """
2114 """
2113 Checks locking on this repository, if locking is enabled and lock is
2115 Checks locking on this repository, if locking is enabled and lock is
2114 present returns a tuple of make_lock, locked, locked_by.
2116 present returns a tuple of make_lock, locked, locked_by.
2115 make_lock can have 3 states None (do nothing) True, make lock
2117 make_lock can have 3 states None (do nothing) True, make lock
2116 False release lock, This value is later propagated to hooks, which
2118 False release lock, This value is later propagated to hooks, which
2117 do the locking. Think about this as signals passed to hooks what to do.
2119 do the locking. Think about this as signals passed to hooks what to do.
2118
2120
2119 """
2121 """
2120 # TODO: johbo: This is part of the business logic and should be moved
2122 # TODO: johbo: This is part of the business logic and should be moved
2121 # into the RepositoryModel.
2123 # into the RepositoryModel.
2122
2124
2123 if action not in ('push', 'pull'):
2125 if action not in ('push', 'pull'):
2124 raise ValueError("Invalid action value: %s" % repr(action))
2126 raise ValueError("Invalid action value: %s" % repr(action))
2125
2127
2126 # defines if locked error should be thrown to user
2128 # defines if locked error should be thrown to user
2127 currently_locked = False
2129 currently_locked = False
2128 # defines if new lock should be made, tri-state
2130 # defines if new lock should be made, tri-state
2129 make_lock = None
2131 make_lock = None
2130 repo = self
2132 repo = self
2131 user = User.get(user_id)
2133 user = User.get(user_id)
2132
2134
2133 lock_info = repo.locked
2135 lock_info = repo.locked
2134
2136
2135 if repo and (repo.enable_locking or not only_when_enabled):
2137 if repo and (repo.enable_locking or not only_when_enabled):
2136 if action == 'push':
2138 if action == 'push':
2137 # check if it's already locked !, if it is compare users
2139 # check if it's already locked !, if it is compare users
2138 locked_by_user_id = lock_info[0]
2140 locked_by_user_id = lock_info[0]
2139 if user.user_id == locked_by_user_id:
2141 if user.user_id == locked_by_user_id:
2140 log.debug(
2142 log.debug(
2141 'Got `push` action from user %s, now unlocking', user)
2143 'Got `push` action from user %s, now unlocking', user)
2142 # unlock if we have push from user who locked
2144 # unlock if we have push from user who locked
2143 make_lock = False
2145 make_lock = False
2144 else:
2146 else:
2145 # we're not the same user who locked, ban with
2147 # we're not the same user who locked, ban with
2146 # code defined in settings (default is 423 HTTP Locked) !
2148 # code defined in settings (default is 423 HTTP Locked) !
2147 log.debug('Repo %s is currently locked by %s', repo, user)
2149 log.debug('Repo %s is currently locked by %s', repo, user)
2148 currently_locked = True
2150 currently_locked = True
2149 elif action == 'pull':
2151 elif action == 'pull':
2150 # [0] user [1] date
2152 # [0] user [1] date
2151 if lock_info[0] and lock_info[1]:
2153 if lock_info[0] and lock_info[1]:
2152 log.debug('Repo %s is currently locked by %s', repo, user)
2154 log.debug('Repo %s is currently locked by %s', repo, user)
2153 currently_locked = True
2155 currently_locked = True
2154 else:
2156 else:
2155 log.debug('Setting lock on repo %s by %s', repo, user)
2157 log.debug('Setting lock on repo %s by %s', repo, user)
2156 make_lock = True
2158 make_lock = True
2157
2159
2158 else:
2160 else:
2159 log.debug('Repository %s do not have locking enabled', repo)
2161 log.debug('Repository %s do not have locking enabled', repo)
2160
2162
2161 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2163 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2162 make_lock, currently_locked, lock_info)
2164 make_lock, currently_locked, lock_info)
2163
2165
2164 from rhodecode.lib.auth import HasRepoPermissionAny
2166 from rhodecode.lib.auth import HasRepoPermissionAny
2165 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2167 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2166 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2168 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2167 # if we don't have at least write permission we cannot make a lock
2169 # if we don't have at least write permission we cannot make a lock
2168 log.debug('lock state reset back to FALSE due to lack '
2170 log.debug('lock state reset back to FALSE due to lack '
2169 'of at least read permission')
2171 'of at least read permission')
2170 make_lock = False
2172 make_lock = False
2171
2173
2172 return make_lock, currently_locked, lock_info
2174 return make_lock, currently_locked, lock_info
2173
2175
2174 @property
2176 @property
2175 def last_db_change(self):
2177 def last_db_change(self):
2176 return self.updated_on
2178 return self.updated_on
2177
2179
2178 @property
2180 @property
2179 def clone_uri_hidden(self):
2181 def clone_uri_hidden(self):
2180 clone_uri = self.clone_uri
2182 clone_uri = self.clone_uri
2181 if clone_uri:
2183 if clone_uri:
2182 import urlobject
2184 import urlobject
2183 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2185 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2184 if url_obj.password:
2186 if url_obj.password:
2185 clone_uri = url_obj.with_password('*****')
2187 clone_uri = url_obj.with_password('*****')
2186 return clone_uri
2188 return clone_uri
2187
2189
2188 @property
2190 @property
2189 def push_uri_hidden(self):
2191 def push_uri_hidden(self):
2190 push_uri = self.push_uri
2192 push_uri = self.push_uri
2191 if push_uri:
2193 if push_uri:
2192 import urlobject
2194 import urlobject
2193 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2195 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2194 if url_obj.password:
2196 if url_obj.password:
2195 push_uri = url_obj.with_password('*****')
2197 push_uri = url_obj.with_password('*****')
2196 return push_uri
2198 return push_uri
2197
2199
2198 def clone_url(self, **override):
2200 def clone_url(self, **override):
2199 from rhodecode.model.settings import SettingsModel
2201 from rhodecode.model.settings import SettingsModel
2200
2202
2201 uri_tmpl = None
2203 uri_tmpl = None
2202 if 'with_id' in override:
2204 if 'with_id' in override:
2203 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2205 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2204 del override['with_id']
2206 del override['with_id']
2205
2207
2206 if 'uri_tmpl' in override:
2208 if 'uri_tmpl' in override:
2207 uri_tmpl = override['uri_tmpl']
2209 uri_tmpl = override['uri_tmpl']
2208 del override['uri_tmpl']
2210 del override['uri_tmpl']
2209
2211
2210 ssh = False
2212 ssh = False
2211 if 'ssh' in override:
2213 if 'ssh' in override:
2212 ssh = True
2214 ssh = True
2213 del override['ssh']
2215 del override['ssh']
2214
2216
2215 # we didn't override our tmpl from **overrides
2217 # we didn't override our tmpl from **overrides
2216 if not uri_tmpl:
2218 if not uri_tmpl:
2217 rc_config = SettingsModel().get_all_settings(cache=True)
2219 rc_config = SettingsModel().get_all_settings(cache=True)
2218 if ssh:
2220 if ssh:
2219 uri_tmpl = rc_config.get(
2221 uri_tmpl = rc_config.get(
2220 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2222 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2221 else:
2223 else:
2222 uri_tmpl = rc_config.get(
2224 uri_tmpl = rc_config.get(
2223 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2225 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2224
2226
2225 request = get_current_request()
2227 request = get_current_request()
2226 return get_clone_url(request=request,
2228 return get_clone_url(request=request,
2227 uri_tmpl=uri_tmpl,
2229 uri_tmpl=uri_tmpl,
2228 repo_name=self.repo_name,
2230 repo_name=self.repo_name,
2229 repo_id=self.repo_id, **override)
2231 repo_id=self.repo_id, **override)
2230
2232
2231 def set_state(self, state):
2233 def set_state(self, state):
2232 self.repo_state = state
2234 self.repo_state = state
2233 Session().add(self)
2235 Session().add(self)
2234 #==========================================================================
2236 #==========================================================================
2235 # SCM PROPERTIES
2237 # SCM PROPERTIES
2236 #==========================================================================
2238 #==========================================================================
2237
2239
2238 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2240 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2239 return get_commit_safe(
2241 return get_commit_safe(
2240 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2242 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2241
2243
2242 def get_changeset(self, rev=None, pre_load=None):
2244 def get_changeset(self, rev=None, pre_load=None):
2243 warnings.warn("Use get_commit", DeprecationWarning)
2245 warnings.warn("Use get_commit", DeprecationWarning)
2244 commit_id = None
2246 commit_id = None
2245 commit_idx = None
2247 commit_idx = None
2246 if isinstance(rev, compat.string_types):
2248 if isinstance(rev, compat.string_types):
2247 commit_id = rev
2249 commit_id = rev
2248 else:
2250 else:
2249 commit_idx = rev
2251 commit_idx = rev
2250 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2252 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2251 pre_load=pre_load)
2253 pre_load=pre_load)
2252
2254
2253 def get_landing_commit(self):
2255 def get_landing_commit(self):
2254 """
2256 """
2255 Returns landing commit, or if that doesn't exist returns the tip
2257 Returns landing commit, or if that doesn't exist returns the tip
2256 """
2258 """
2257 _rev_type, _rev = self.landing_rev
2259 _rev_type, _rev = self.landing_rev
2258 commit = self.get_commit(_rev)
2260 commit = self.get_commit(_rev)
2259 if isinstance(commit, EmptyCommit):
2261 if isinstance(commit, EmptyCommit):
2260 return self.get_commit()
2262 return self.get_commit()
2261 return commit
2263 return commit
2262
2264
2263 def update_commit_cache(self, cs_cache=None, config=None):
2265 def update_commit_cache(self, cs_cache=None, config=None):
2264 """
2266 """
2265 Update cache of last changeset for repository, keys should be::
2267 Update cache of last changeset for repository, keys should be::
2266
2268
2267 short_id
2269 short_id
2268 raw_id
2270 raw_id
2269 revision
2271 revision
2270 parents
2272 parents
2271 message
2273 message
2272 date
2274 date
2273 author
2275 author
2274
2276
2275 :param cs_cache:
2277 :param cs_cache:
2276 """
2278 """
2277 from rhodecode.lib.vcs.backends.base import BaseChangeset
2279 from rhodecode.lib.vcs.backends.base import BaseChangeset
2278 if cs_cache is None:
2280 if cs_cache is None:
2279 # use no-cache version here
2281 # use no-cache version here
2280 scm_repo = self.scm_instance(cache=False, config=config)
2282 scm_repo = self.scm_instance(cache=False, config=config)
2281
2283
2282 empty = not scm_repo or scm_repo.is_empty()
2284 empty = not scm_repo or scm_repo.is_empty()
2283 if not empty:
2285 if not empty:
2284 cs_cache = scm_repo.get_commit(
2286 cs_cache = scm_repo.get_commit(
2285 pre_load=["author", "date", "message", "parents"])
2287 pre_load=["author", "date", "message", "parents"])
2286 else:
2288 else:
2287 cs_cache = EmptyCommit()
2289 cs_cache = EmptyCommit()
2288
2290
2289 if isinstance(cs_cache, BaseChangeset):
2291 if isinstance(cs_cache, BaseChangeset):
2290 cs_cache = cs_cache.__json__()
2292 cs_cache = cs_cache.__json__()
2291
2293
2292 def is_outdated(new_cs_cache):
2294 def is_outdated(new_cs_cache):
2293 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2295 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2294 new_cs_cache['revision'] != self.changeset_cache['revision']):
2296 new_cs_cache['revision'] != self.changeset_cache['revision']):
2295 return True
2297 return True
2296 return False
2298 return False
2297
2299
2298 # check if we have maybe already latest cached revision
2300 # check if we have maybe already latest cached revision
2299 if is_outdated(cs_cache) or not self.changeset_cache:
2301 if is_outdated(cs_cache) or not self.changeset_cache:
2300 _default = datetime.datetime.utcnow()
2302 _default = datetime.datetime.utcnow()
2301 last_change = cs_cache.get('date') or _default
2303 last_change = cs_cache.get('date') or _default
2302 if self.updated_on and self.updated_on > last_change:
2304 if self.updated_on and self.updated_on > last_change:
2303 # we check if last update is newer than the new value
2305 # we check if last update is newer than the new value
2304 # if yes, we use the current timestamp instead. Imagine you get
2306 # if yes, we use the current timestamp instead. Imagine you get
2305 # old commit pushed 1y ago, we'd set last update 1y to ago.
2307 # old commit pushed 1y ago, we'd set last update 1y to ago.
2306 last_change = _default
2308 last_change = _default
2307 log.debug('updated repo %s with new cs cache %s',
2309 log.debug('updated repo %s with new cs cache %s',
2308 self.repo_name, cs_cache)
2310 self.repo_name, cs_cache)
2309 self.updated_on = last_change
2311 self.updated_on = last_change
2310 self.changeset_cache = cs_cache
2312 self.changeset_cache = cs_cache
2311 Session().add(self)
2313 Session().add(self)
2312 Session().commit()
2314 Session().commit()
2313 else:
2315 else:
2314 log.debug('Skipping update_commit_cache for repo:`%s` '
2316 log.debug('Skipping update_commit_cache for repo:`%s` '
2315 'commit already with latest changes', self.repo_name)
2317 'commit already with latest changes', self.repo_name)
2316
2318
2317 @property
2319 @property
2318 def tip(self):
2320 def tip(self):
2319 return self.get_commit('tip')
2321 return self.get_commit('tip')
2320
2322
2321 @property
2323 @property
2322 def author(self):
2324 def author(self):
2323 return self.tip.author
2325 return self.tip.author
2324
2326
2325 @property
2327 @property
2326 def last_change(self):
2328 def last_change(self):
2327 return self.scm_instance().last_change
2329 return self.scm_instance().last_change
2328
2330
2329 def get_comments(self, revisions=None):
2331 def get_comments(self, revisions=None):
2330 """
2332 """
2331 Returns comments for this repository grouped by revisions
2333 Returns comments for this repository grouped by revisions
2332
2334
2333 :param revisions: filter query by revisions only
2335 :param revisions: filter query by revisions only
2334 """
2336 """
2335 cmts = ChangesetComment.query()\
2337 cmts = ChangesetComment.query()\
2336 .filter(ChangesetComment.repo == self)
2338 .filter(ChangesetComment.repo == self)
2337 if revisions:
2339 if revisions:
2338 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2340 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2339 grouped = collections.defaultdict(list)
2341 grouped = collections.defaultdict(list)
2340 for cmt in cmts.all():
2342 for cmt in cmts.all():
2341 grouped[cmt.revision].append(cmt)
2343 grouped[cmt.revision].append(cmt)
2342 return grouped
2344 return grouped
2343
2345
2344 def statuses(self, revisions=None):
2346 def statuses(self, revisions=None):
2345 """
2347 """
2346 Returns statuses for this repository
2348 Returns statuses for this repository
2347
2349
2348 :param revisions: list of revisions to get statuses for
2350 :param revisions: list of revisions to get statuses for
2349 """
2351 """
2350 statuses = ChangesetStatus.query()\
2352 statuses = ChangesetStatus.query()\
2351 .filter(ChangesetStatus.repo == self)\
2353 .filter(ChangesetStatus.repo == self)\
2352 .filter(ChangesetStatus.version == 0)
2354 .filter(ChangesetStatus.version == 0)
2353
2355
2354 if revisions:
2356 if revisions:
2355 # Try doing the filtering in chunks to avoid hitting limits
2357 # Try doing the filtering in chunks to avoid hitting limits
2356 size = 500
2358 size = 500
2357 status_results = []
2359 status_results = []
2358 for chunk in xrange(0, len(revisions), size):
2360 for chunk in xrange(0, len(revisions), size):
2359 status_results += statuses.filter(
2361 status_results += statuses.filter(
2360 ChangesetStatus.revision.in_(
2362 ChangesetStatus.revision.in_(
2361 revisions[chunk: chunk+size])
2363 revisions[chunk: chunk+size])
2362 ).all()
2364 ).all()
2363 else:
2365 else:
2364 status_results = statuses.all()
2366 status_results = statuses.all()
2365
2367
2366 grouped = {}
2368 grouped = {}
2367
2369
2368 # maybe we have open new pullrequest without a status?
2370 # maybe we have open new pullrequest without a status?
2369 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2371 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2370 status_lbl = ChangesetStatus.get_status_lbl(stat)
2372 status_lbl = ChangesetStatus.get_status_lbl(stat)
2371 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2373 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2372 for rev in pr.revisions:
2374 for rev in pr.revisions:
2373 pr_id = pr.pull_request_id
2375 pr_id = pr.pull_request_id
2374 pr_repo = pr.target_repo.repo_name
2376 pr_repo = pr.target_repo.repo_name
2375 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2377 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2376
2378
2377 for stat in status_results:
2379 for stat in status_results:
2378 pr_id = pr_repo = None
2380 pr_id = pr_repo = None
2379 if stat.pull_request:
2381 if stat.pull_request:
2380 pr_id = stat.pull_request.pull_request_id
2382 pr_id = stat.pull_request.pull_request_id
2381 pr_repo = stat.pull_request.target_repo.repo_name
2383 pr_repo = stat.pull_request.target_repo.repo_name
2382 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2384 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2383 pr_id, pr_repo]
2385 pr_id, pr_repo]
2384 return grouped
2386 return grouped
2385
2387
2386 # ==========================================================================
2388 # ==========================================================================
2387 # SCM CACHE INSTANCE
2389 # SCM CACHE INSTANCE
2388 # ==========================================================================
2390 # ==========================================================================
2389
2391
2390 def scm_instance(self, **kwargs):
2392 def scm_instance(self, **kwargs):
2391 import rhodecode
2393 import rhodecode
2392
2394
2393 # Passing a config will not hit the cache currently only used
2395 # Passing a config will not hit the cache currently only used
2394 # for repo2dbmapper
2396 # for repo2dbmapper
2395 config = kwargs.pop('config', None)
2397 config = kwargs.pop('config', None)
2396 cache = kwargs.pop('cache', None)
2398 cache = kwargs.pop('cache', None)
2397 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2399 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2398 # if cache is NOT defined use default global, else we have a full
2400 # if cache is NOT defined use default global, else we have a full
2399 # control over cache behaviour
2401 # control over cache behaviour
2400 if cache is None and full_cache and not config:
2402 if cache is None and full_cache and not config:
2401 return self._get_instance_cached()
2403 return self._get_instance_cached()
2402 return self._get_instance(cache=bool(cache), config=config)
2404 return self._get_instance(cache=bool(cache), config=config)
2403
2405
2404 def _get_instance_cached(self):
2406 def _get_instance_cached(self):
2405 from rhodecode.lib import rc_cache
2407 from rhodecode.lib import rc_cache
2406
2408
2407 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2409 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2408 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2410 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2409 repo_id=self.repo_id)
2411 repo_id=self.repo_id)
2410 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2412 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2411
2413
2412 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2414 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2413 def get_instance_cached(repo_id, context_id):
2415 def get_instance_cached(repo_id, context_id):
2414 return self._get_instance()
2416 return self._get_instance()
2415
2417
2416 # we must use thread scoped cache here,
2418 # we must use thread scoped cache here,
2417 # because each thread of gevent needs it's own not shared connection and cache
2419 # because each thread of gevent needs it's own not shared connection and cache
2418 # we also alter `args` so the cache key is individual for every green thread.
2420 # we also alter `args` so the cache key is individual for every green thread.
2419 inv_context_manager = rc_cache.InvalidationContext(
2421 inv_context_manager = rc_cache.InvalidationContext(
2420 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2422 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2421 thread_scoped=True)
2423 thread_scoped=True)
2422 with inv_context_manager as invalidation_context:
2424 with inv_context_manager as invalidation_context:
2423 args = (self.repo_id, inv_context_manager.cache_key)
2425 args = (self.repo_id, inv_context_manager.cache_key)
2424 # re-compute and store cache if we get invalidate signal
2426 # re-compute and store cache if we get invalidate signal
2425 if invalidation_context.should_invalidate():
2427 if invalidation_context.should_invalidate():
2426 instance = get_instance_cached.refresh(*args)
2428 instance = get_instance_cached.refresh(*args)
2427 else:
2429 else:
2428 instance = get_instance_cached(*args)
2430 instance = get_instance_cached(*args)
2429
2431
2430 log.debug(
2432 log.debug(
2431 'Repo instance fetched in %.3fs', inv_context_manager.compute_time)
2433 'Repo instance fetched in %.3fs', inv_context_manager.compute_time)
2432 return instance
2434 return instance
2433
2435
2434 def _get_instance(self, cache=True, config=None):
2436 def _get_instance(self, cache=True, config=None):
2435 config = config or self._config
2437 config = config or self._config
2436 custom_wire = {
2438 custom_wire = {
2437 'cache': cache # controls the vcs.remote cache
2439 'cache': cache # controls the vcs.remote cache
2438 }
2440 }
2439 repo = get_vcs_instance(
2441 repo = get_vcs_instance(
2440 repo_path=safe_str(self.repo_full_path),
2442 repo_path=safe_str(self.repo_full_path),
2441 config=config,
2443 config=config,
2442 with_wire=custom_wire,
2444 with_wire=custom_wire,
2443 create=False,
2445 create=False,
2444 _vcs_alias=self.repo_type)
2446 _vcs_alias=self.repo_type)
2445
2447
2446 return repo
2448 return repo
2447
2449
2448 def __json__(self):
2450 def __json__(self):
2449 return {'landing_rev': self.landing_rev}
2451 return {'landing_rev': self.landing_rev}
2450
2452
2451 def get_dict(self):
2453 def get_dict(self):
2452
2454
2453 # Since we transformed `repo_name` to a hybrid property, we need to
2455 # Since we transformed `repo_name` to a hybrid property, we need to
2454 # keep compatibility with the code which uses `repo_name` field.
2456 # keep compatibility with the code which uses `repo_name` field.
2455
2457
2456 result = super(Repository, self).get_dict()
2458 result = super(Repository, self).get_dict()
2457 result['repo_name'] = result.pop('_repo_name', None)
2459 result['repo_name'] = result.pop('_repo_name', None)
2458 return result
2460 return result
2459
2461
2460
2462
2461 class RepoGroup(Base, BaseModel):
2463 class RepoGroup(Base, BaseModel):
2462 __tablename__ = 'groups'
2464 __tablename__ = 'groups'
2463 __table_args__ = (
2465 __table_args__ = (
2464 UniqueConstraint('group_name', 'group_parent_id'),
2466 UniqueConstraint('group_name', 'group_parent_id'),
2465 base_table_args,
2467 base_table_args,
2466 )
2468 )
2467 __mapper_args__ = {'order_by': 'group_name'}
2469 __mapper_args__ = {'order_by': 'group_name'}
2468
2470
2469 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2471 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2470
2472
2471 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2473 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2472 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2474 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2475 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2473 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2476 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2474 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2477 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2475 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2478 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2476 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2479 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2477 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2480 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2478 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2481 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2479 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2482 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2480
2483
2481 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2484 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2482 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2485 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2483 parent_group = relationship('RepoGroup', remote_side=group_id)
2486 parent_group = relationship('RepoGroup', remote_side=group_id)
2484 user = relationship('User')
2487 user = relationship('User')
2485 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
2488 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
2486
2489
2487 def __init__(self, group_name='', parent_group=None):
2490 def __init__(self, group_name='', parent_group=None):
2488 self.group_name = group_name
2491 self.group_name = group_name
2489 self.parent_group = parent_group
2492 self.parent_group = parent_group
2490
2493
2491 def __unicode__(self):
2494 def __unicode__(self):
2492 return u"<%s('id:%s:%s')>" % (
2495 return u"<%s('id:%s:%s')>" % (
2493 self.__class__.__name__, self.group_id, self.group_name)
2496 self.__class__.__name__, self.group_id, self.group_name)
2494
2497
2498 @hybrid_property
2499 def group_name(self):
2500 return self._group_name
2501
2502 @group_name.setter
2503 def group_name(self, value):
2504 self._group_name = value
2505 self.group_name_hash = self.hash_repo_group_name(value)
2506
2495 @validates('group_parent_id')
2507 @validates('group_parent_id')
2496 def validate_group_parent_id(self, key, val):
2508 def validate_group_parent_id(self, key, val):
2497 """
2509 """
2498 Check cycle references for a parent group to self
2510 Check cycle references for a parent group to self
2499 """
2511 """
2500 if self.group_id and val:
2512 if self.group_id and val:
2501 assert val != self.group_id
2513 assert val != self.group_id
2502
2514
2503 return val
2515 return val
2504
2516
2505 @hybrid_property
2517 @hybrid_property
2506 def description_safe(self):
2518 def description_safe(self):
2507 from rhodecode.lib import helpers as h
2519 from rhodecode.lib import helpers as h
2508 return h.escape(self.group_description)
2520 return h.escape(self.group_description)
2509
2521
2510 @classmethod
2522 @classmethod
2523 def hash_repo_group_name(cls, repo_group_name):
2524 val = remove_formatting(repo_group_name)
2525 val = safe_str(val).lower()
2526 chars = []
2527 for c in val:
2528 if c not in string.ascii_letters:
2529 c = str(ord(c))
2530 chars.append(c)
2531
2532 return ''.join(chars)
2533
2534 @classmethod
2511 def _generate_choice(cls, repo_group):
2535 def _generate_choice(cls, repo_group):
2512 from webhelpers.html import literal as _literal
2536 from webhelpers.html import literal as _literal
2513 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2537 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2514 return repo_group.group_id, _name(repo_group.full_path_splitted)
2538 return repo_group.group_id, _name(repo_group.full_path_splitted)
2515
2539
2516 @classmethod
2540 @classmethod
2517 def groups_choices(cls, groups=None, show_empty_group=True):
2541 def groups_choices(cls, groups=None, show_empty_group=True):
2518 if not groups:
2542 if not groups:
2519 groups = cls.query().all()
2543 groups = cls.query().all()
2520
2544
2521 repo_groups = []
2545 repo_groups = []
2522 if show_empty_group:
2546 if show_empty_group:
2523 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2547 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2524
2548
2525 repo_groups.extend([cls._generate_choice(x) for x in groups])
2549 repo_groups.extend([cls._generate_choice(x) for x in groups])
2526
2550
2527 repo_groups = sorted(
2551 repo_groups = sorted(
2528 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2552 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2529 return repo_groups
2553 return repo_groups
2530
2554
2531 @classmethod
2555 @classmethod
2532 def url_sep(cls):
2556 def url_sep(cls):
2533 return URL_SEP
2557 return URL_SEP
2534
2558
2535 @classmethod
2559 @classmethod
2536 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2560 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2537 if case_insensitive:
2561 if case_insensitive:
2538 gr = cls.query().filter(func.lower(cls.group_name)
2562 gr = cls.query().filter(func.lower(cls.group_name)
2539 == func.lower(group_name))
2563 == func.lower(group_name))
2540 else:
2564 else:
2541 gr = cls.query().filter(cls.group_name == group_name)
2565 gr = cls.query().filter(cls.group_name == group_name)
2542 if cache:
2566 if cache:
2543 name_key = _hash_key(group_name)
2567 name_key = _hash_key(group_name)
2544 gr = gr.options(
2568 gr = gr.options(
2545 FromCache("sql_cache_short", "get_group_%s" % name_key))
2569 FromCache("sql_cache_short", "get_group_%s" % name_key))
2546 return gr.scalar()
2570 return gr.scalar()
2547
2571
2548 @classmethod
2572 @classmethod
2549 def get_user_personal_repo_group(cls, user_id):
2573 def get_user_personal_repo_group(cls, user_id):
2550 user = User.get(user_id)
2574 user = User.get(user_id)
2551 if user.username == User.DEFAULT_USER:
2575 if user.username == User.DEFAULT_USER:
2552 return None
2576 return None
2553
2577
2554 return cls.query()\
2578 return cls.query()\
2555 .filter(cls.personal == true()) \
2579 .filter(cls.personal == true()) \
2556 .filter(cls.user == user) \
2580 .filter(cls.user == user) \
2557 .order_by(cls.group_id.asc()) \
2581 .order_by(cls.group_id.asc()) \
2558 .first()
2582 .first()
2559
2583
2560 @classmethod
2584 @classmethod
2561 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2585 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2562 case_insensitive=True):
2586 case_insensitive=True):
2563 q = RepoGroup.query()
2587 q = RepoGroup.query()
2564
2588
2565 if not isinstance(user_id, Optional):
2589 if not isinstance(user_id, Optional):
2566 q = q.filter(RepoGroup.user_id == user_id)
2590 q = q.filter(RepoGroup.user_id == user_id)
2567
2591
2568 if not isinstance(group_id, Optional):
2592 if not isinstance(group_id, Optional):
2569 q = q.filter(RepoGroup.group_parent_id == group_id)
2593 q = q.filter(RepoGroup.group_parent_id == group_id)
2570
2594
2571 if case_insensitive:
2595 if case_insensitive:
2572 q = q.order_by(func.lower(RepoGroup.group_name))
2596 q = q.order_by(func.lower(RepoGroup.group_name))
2573 else:
2597 else:
2574 q = q.order_by(RepoGroup.group_name)
2598 q = q.order_by(RepoGroup.group_name)
2575 return q.all()
2599 return q.all()
2576
2600
2577 @property
2601 @property
2578 def parents(self):
2602 def parents(self):
2579 parents_recursion_limit = 10
2603 parents_recursion_limit = 10
2580 groups = []
2604 groups = []
2581 if self.parent_group is None:
2605 if self.parent_group is None:
2582 return groups
2606 return groups
2583 cur_gr = self.parent_group
2607 cur_gr = self.parent_group
2584 groups.insert(0, cur_gr)
2608 groups.insert(0, cur_gr)
2585 cnt = 0
2609 cnt = 0
2586 while 1:
2610 while 1:
2587 cnt += 1
2611 cnt += 1
2588 gr = getattr(cur_gr, 'parent_group', None)
2612 gr = getattr(cur_gr, 'parent_group', None)
2589 cur_gr = cur_gr.parent_group
2613 cur_gr = cur_gr.parent_group
2590 if gr is None:
2614 if gr is None:
2591 break
2615 break
2592 if cnt == parents_recursion_limit:
2616 if cnt == parents_recursion_limit:
2593 # this will prevent accidental infinit loops
2617 # this will prevent accidental infinit loops
2594 log.error('more than %s parents found for group %s, stopping '
2618 log.error('more than %s parents found for group %s, stopping '
2595 'recursive parent fetching', parents_recursion_limit, self)
2619 'recursive parent fetching', parents_recursion_limit, self)
2596 break
2620 break
2597
2621
2598 groups.insert(0, gr)
2622 groups.insert(0, gr)
2599 return groups
2623 return groups
2600
2624
2601 @property
2625 @property
2602 def last_db_change(self):
2626 def last_db_change(self):
2603 return self.updated_on
2627 return self.updated_on
2604
2628
2605 @property
2629 @property
2606 def children(self):
2630 def children(self):
2607 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2631 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2608
2632
2609 @property
2633 @property
2610 def name(self):
2634 def name(self):
2611 return self.group_name.split(RepoGroup.url_sep())[-1]
2635 return self.group_name.split(RepoGroup.url_sep())[-1]
2612
2636
2613 @property
2637 @property
2614 def full_path(self):
2638 def full_path(self):
2615 return self.group_name
2639 return self.group_name
2616
2640
2617 @property
2641 @property
2618 def full_path_splitted(self):
2642 def full_path_splitted(self):
2619 return self.group_name.split(RepoGroup.url_sep())
2643 return self.group_name.split(RepoGroup.url_sep())
2620
2644
2621 @property
2645 @property
2622 def repositories(self):
2646 def repositories(self):
2623 return Repository.query()\
2647 return Repository.query()\
2624 .filter(Repository.group == self)\
2648 .filter(Repository.group == self)\
2625 .order_by(Repository.repo_name)
2649 .order_by(Repository.repo_name)
2626
2650
2627 @property
2651 @property
2628 def repositories_recursive_count(self):
2652 def repositories_recursive_count(self):
2629 cnt = self.repositories.count()
2653 cnt = self.repositories.count()
2630
2654
2631 def children_count(group):
2655 def children_count(group):
2632 cnt = 0
2656 cnt = 0
2633 for child in group.children:
2657 for child in group.children:
2634 cnt += child.repositories.count()
2658 cnt += child.repositories.count()
2635 cnt += children_count(child)
2659 cnt += children_count(child)
2636 return cnt
2660 return cnt
2637
2661
2638 return cnt + children_count(self)
2662 return cnt + children_count(self)
2639
2663
2640 def _recursive_objects(self, include_repos=True):
2664 def _recursive_objects(self, include_repos=True):
2641 all_ = []
2665 all_ = []
2642
2666
2643 def _get_members(root_gr):
2667 def _get_members(root_gr):
2644 if include_repos:
2668 if include_repos:
2645 for r in root_gr.repositories:
2669 for r in root_gr.repositories:
2646 all_.append(r)
2670 all_.append(r)
2647 childs = root_gr.children.all()
2671 childs = root_gr.children.all()
2648 if childs:
2672 if childs:
2649 for gr in childs:
2673 for gr in childs:
2650 all_.append(gr)
2674 all_.append(gr)
2651 _get_members(gr)
2675 _get_members(gr)
2652
2676
2653 _get_members(self)
2677 _get_members(self)
2654 return [self] + all_
2678 return [self] + all_
2655
2679
2656 def recursive_groups_and_repos(self):
2680 def recursive_groups_and_repos(self):
2657 """
2681 """
2658 Recursive return all groups, with repositories in those groups
2682 Recursive return all groups, with repositories in those groups
2659 """
2683 """
2660 return self._recursive_objects()
2684 return self._recursive_objects()
2661
2685
2662 def recursive_groups(self):
2686 def recursive_groups(self):
2663 """
2687 """
2664 Returns all children groups for this group including children of children
2688 Returns all children groups for this group including children of children
2665 """
2689 """
2666 return self._recursive_objects(include_repos=False)
2690 return self._recursive_objects(include_repos=False)
2667
2691
2668 def get_new_name(self, group_name):
2692 def get_new_name(self, group_name):
2669 """
2693 """
2670 returns new full group name based on parent and new name
2694 returns new full group name based on parent and new name
2671
2695
2672 :param group_name:
2696 :param group_name:
2673 """
2697 """
2674 path_prefix = (self.parent_group.full_path_splitted if
2698 path_prefix = (self.parent_group.full_path_splitted if
2675 self.parent_group else [])
2699 self.parent_group else [])
2676 return RepoGroup.url_sep().join(path_prefix + [group_name])
2700 return RepoGroup.url_sep().join(path_prefix + [group_name])
2677
2701
2678 def permissions(self, with_admins=True, with_owner=True,
2702 def permissions(self, with_admins=True, with_owner=True,
2679 expand_from_user_groups=False):
2703 expand_from_user_groups=False):
2680 """
2704 """
2681 Permissions for repository groups
2705 Permissions for repository groups
2682 """
2706 """
2683 _admin_perm = 'group.admin'
2707 _admin_perm = 'group.admin'
2684
2708
2685 owner_row = []
2709 owner_row = []
2686 if with_owner:
2710 if with_owner:
2687 usr = AttributeDict(self.user.get_dict())
2711 usr = AttributeDict(self.user.get_dict())
2688 usr.owner_row = True
2712 usr.owner_row = True
2689 usr.permission = _admin_perm
2713 usr.permission = _admin_perm
2690 owner_row.append(usr)
2714 owner_row.append(usr)
2691
2715
2692 super_admin_ids = []
2716 super_admin_ids = []
2693 super_admin_rows = []
2717 super_admin_rows = []
2694 if with_admins:
2718 if with_admins:
2695 for usr in User.get_all_super_admins():
2719 for usr in User.get_all_super_admins():
2696 super_admin_ids.append(usr.user_id)
2720 super_admin_ids.append(usr.user_id)
2697 # if this admin is also owner, don't double the record
2721 # if this admin is also owner, don't double the record
2698 if usr.user_id == owner_row[0].user_id:
2722 if usr.user_id == owner_row[0].user_id:
2699 owner_row[0].admin_row = True
2723 owner_row[0].admin_row = True
2700 else:
2724 else:
2701 usr = AttributeDict(usr.get_dict())
2725 usr = AttributeDict(usr.get_dict())
2702 usr.admin_row = True
2726 usr.admin_row = True
2703 usr.permission = _admin_perm
2727 usr.permission = _admin_perm
2704 super_admin_rows.append(usr)
2728 super_admin_rows.append(usr)
2705
2729
2706 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2730 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2707 q = q.options(joinedload(UserRepoGroupToPerm.group),
2731 q = q.options(joinedload(UserRepoGroupToPerm.group),
2708 joinedload(UserRepoGroupToPerm.user),
2732 joinedload(UserRepoGroupToPerm.user),
2709 joinedload(UserRepoGroupToPerm.permission),)
2733 joinedload(UserRepoGroupToPerm.permission),)
2710
2734
2711 # get owners and admins and permissions. We do a trick of re-writing
2735 # get owners and admins and permissions. We do a trick of re-writing
2712 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2736 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2713 # has a global reference and changing one object propagates to all
2737 # has a global reference and changing one object propagates to all
2714 # others. This means if admin is also an owner admin_row that change
2738 # others. This means if admin is also an owner admin_row that change
2715 # would propagate to both objects
2739 # would propagate to both objects
2716 perm_rows = []
2740 perm_rows = []
2717 for _usr in q.all():
2741 for _usr in q.all():
2718 usr = AttributeDict(_usr.user.get_dict())
2742 usr = AttributeDict(_usr.user.get_dict())
2719 # if this user is also owner/admin, mark as duplicate record
2743 # if this user is also owner/admin, mark as duplicate record
2720 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2744 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2721 usr.duplicate_perm = True
2745 usr.duplicate_perm = True
2722 usr.permission = _usr.permission.permission_name
2746 usr.permission = _usr.permission.permission_name
2723 perm_rows.append(usr)
2747 perm_rows.append(usr)
2724
2748
2725 # filter the perm rows by 'default' first and then sort them by
2749 # filter the perm rows by 'default' first and then sort them by
2726 # admin,write,read,none permissions sorted again alphabetically in
2750 # admin,write,read,none permissions sorted again alphabetically in
2727 # each group
2751 # each group
2728 perm_rows = sorted(perm_rows, key=display_user_sort)
2752 perm_rows = sorted(perm_rows, key=display_user_sort)
2729
2753
2730 user_groups_rows = []
2754 user_groups_rows = []
2731 if expand_from_user_groups:
2755 if expand_from_user_groups:
2732 for ug in self.permission_user_groups(with_members=True):
2756 for ug in self.permission_user_groups(with_members=True):
2733 for user_data in ug.members:
2757 for user_data in ug.members:
2734 user_groups_rows.append(user_data)
2758 user_groups_rows.append(user_data)
2735
2759
2736 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2760 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2737
2761
2738 def permission_user_groups(self, with_members=False):
2762 def permission_user_groups(self, with_members=False):
2739 q = UserGroupRepoGroupToPerm.query()\
2763 q = UserGroupRepoGroupToPerm.query()\
2740 .filter(UserGroupRepoGroupToPerm.group == self)
2764 .filter(UserGroupRepoGroupToPerm.group == self)
2741 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2765 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2742 joinedload(UserGroupRepoGroupToPerm.users_group),
2766 joinedload(UserGroupRepoGroupToPerm.users_group),
2743 joinedload(UserGroupRepoGroupToPerm.permission),)
2767 joinedload(UserGroupRepoGroupToPerm.permission),)
2744
2768
2745 perm_rows = []
2769 perm_rows = []
2746 for _user_group in q.all():
2770 for _user_group in q.all():
2747 entry = AttributeDict(_user_group.users_group.get_dict())
2771 entry = AttributeDict(_user_group.users_group.get_dict())
2748 entry.permission = _user_group.permission.permission_name
2772 entry.permission = _user_group.permission.permission_name
2749 if with_members:
2773 if with_members:
2750 entry.members = [x.user.get_dict()
2774 entry.members = [x.user.get_dict()
2751 for x in _user_group.users_group.members]
2775 for x in _user_group.users_group.members]
2752 perm_rows.append(entry)
2776 perm_rows.append(entry)
2753
2777
2754 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2778 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2755 return perm_rows
2779 return perm_rows
2756
2780
2757 def get_api_data(self):
2781 def get_api_data(self):
2758 """
2782 """
2759 Common function for generating api data
2783 Common function for generating api data
2760
2784
2761 """
2785 """
2762 group = self
2786 group = self
2763 data = {
2787 data = {
2764 'group_id': group.group_id,
2788 'group_id': group.group_id,
2765 'group_name': group.group_name,
2789 'group_name': group.group_name,
2766 'group_description': group.description_safe,
2790 'group_description': group.description_safe,
2767 'parent_group': group.parent_group.group_name if group.parent_group else None,
2791 'parent_group': group.parent_group.group_name if group.parent_group else None,
2768 'repositories': [x.repo_name for x in group.repositories],
2792 'repositories': [x.repo_name for x in group.repositories],
2769 'owner': group.user.username,
2793 'owner': group.user.username,
2770 }
2794 }
2771 return data
2795 return data
2772
2796
2797 def get_dict(self):
2798 # Since we transformed `group_name` to a hybrid property, we need to
2799 # keep compatibility with the code which uses `group_name` field.
2800 result = super(RepoGroup, self).get_dict()
2801 result['group_name'] = result.pop('_group_name', None)
2802 return result
2803
2773
2804
2774 class Permission(Base, BaseModel):
2805 class Permission(Base, BaseModel):
2775 __tablename__ = 'permissions'
2806 __tablename__ = 'permissions'
2776 __table_args__ = (
2807 __table_args__ = (
2777 Index('p_perm_name_idx', 'permission_name'),
2808 Index('p_perm_name_idx', 'permission_name'),
2778 base_table_args,
2809 base_table_args,
2779 )
2810 )
2780
2811
2781 PERMS = [
2812 PERMS = [
2782 ('hg.admin', _('RhodeCode Super Administrator')),
2813 ('hg.admin', _('RhodeCode Super Administrator')),
2783
2814
2784 ('repository.none', _('Repository no access')),
2815 ('repository.none', _('Repository no access')),
2785 ('repository.read', _('Repository read access')),
2816 ('repository.read', _('Repository read access')),
2786 ('repository.write', _('Repository write access')),
2817 ('repository.write', _('Repository write access')),
2787 ('repository.admin', _('Repository admin access')),
2818 ('repository.admin', _('Repository admin access')),
2788
2819
2789 ('group.none', _('Repository group no access')),
2820 ('group.none', _('Repository group no access')),
2790 ('group.read', _('Repository group read access')),
2821 ('group.read', _('Repository group read access')),
2791 ('group.write', _('Repository group write access')),
2822 ('group.write', _('Repository group write access')),
2792 ('group.admin', _('Repository group admin access')),
2823 ('group.admin', _('Repository group admin access')),
2793
2824
2794 ('usergroup.none', _('User group no access')),
2825 ('usergroup.none', _('User group no access')),
2795 ('usergroup.read', _('User group read access')),
2826 ('usergroup.read', _('User group read access')),
2796 ('usergroup.write', _('User group write access')),
2827 ('usergroup.write', _('User group write access')),
2797 ('usergroup.admin', _('User group admin access')),
2828 ('usergroup.admin', _('User group admin access')),
2798
2829
2799 ('branch.none', _('Branch no permissions')),
2830 ('branch.none', _('Branch no permissions')),
2800 ('branch.merge', _('Branch access by web merge')),
2831 ('branch.merge', _('Branch access by web merge')),
2801 ('branch.push', _('Branch access by push')),
2832 ('branch.push', _('Branch access by push')),
2802 ('branch.push_force', _('Branch access by push with force')),
2833 ('branch.push_force', _('Branch access by push with force')),
2803
2834
2804 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2835 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2805 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2836 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2806
2837
2807 ('hg.usergroup.create.false', _('User Group creation disabled')),
2838 ('hg.usergroup.create.false', _('User Group creation disabled')),
2808 ('hg.usergroup.create.true', _('User Group creation enabled')),
2839 ('hg.usergroup.create.true', _('User Group creation enabled')),
2809
2840
2810 ('hg.create.none', _('Repository creation disabled')),
2841 ('hg.create.none', _('Repository creation disabled')),
2811 ('hg.create.repository', _('Repository creation enabled')),
2842 ('hg.create.repository', _('Repository creation enabled')),
2812 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2843 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2813 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2844 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2814
2845
2815 ('hg.fork.none', _('Repository forking disabled')),
2846 ('hg.fork.none', _('Repository forking disabled')),
2816 ('hg.fork.repository', _('Repository forking enabled')),
2847 ('hg.fork.repository', _('Repository forking enabled')),
2817
2848
2818 ('hg.register.none', _('Registration disabled')),
2849 ('hg.register.none', _('Registration disabled')),
2819 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2850 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2820 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2851 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2821
2852
2822 ('hg.password_reset.enabled', _('Password reset enabled')),
2853 ('hg.password_reset.enabled', _('Password reset enabled')),
2823 ('hg.password_reset.hidden', _('Password reset hidden')),
2854 ('hg.password_reset.hidden', _('Password reset hidden')),
2824 ('hg.password_reset.disabled', _('Password reset disabled')),
2855 ('hg.password_reset.disabled', _('Password reset disabled')),
2825
2856
2826 ('hg.extern_activate.manual', _('Manual activation of external account')),
2857 ('hg.extern_activate.manual', _('Manual activation of external account')),
2827 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2858 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2828
2859
2829 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2860 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2830 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2861 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2831 ]
2862 ]
2832
2863
2833 # definition of system default permissions for DEFAULT user, created on
2864 # definition of system default permissions for DEFAULT user, created on
2834 # system setup
2865 # system setup
2835 DEFAULT_USER_PERMISSIONS = [
2866 DEFAULT_USER_PERMISSIONS = [
2836 # object perms
2867 # object perms
2837 'repository.read',
2868 'repository.read',
2838 'group.read',
2869 'group.read',
2839 'usergroup.read',
2870 'usergroup.read',
2840 # branch, for backward compat we need same value as before so forced pushed
2871 # branch, for backward compat we need same value as before so forced pushed
2841 'branch.push_force',
2872 'branch.push_force',
2842 # global
2873 # global
2843 'hg.create.repository',
2874 'hg.create.repository',
2844 'hg.repogroup.create.false',
2875 'hg.repogroup.create.false',
2845 'hg.usergroup.create.false',
2876 'hg.usergroup.create.false',
2846 'hg.create.write_on_repogroup.true',
2877 'hg.create.write_on_repogroup.true',
2847 'hg.fork.repository',
2878 'hg.fork.repository',
2848 'hg.register.manual_activate',
2879 'hg.register.manual_activate',
2849 'hg.password_reset.enabled',
2880 'hg.password_reset.enabled',
2850 'hg.extern_activate.auto',
2881 'hg.extern_activate.auto',
2851 'hg.inherit_default_perms.true',
2882 'hg.inherit_default_perms.true',
2852 ]
2883 ]
2853
2884
2854 # defines which permissions are more important higher the more important
2885 # defines which permissions are more important higher the more important
2855 # Weight defines which permissions are more important.
2886 # Weight defines which permissions are more important.
2856 # The higher number the more important.
2887 # The higher number the more important.
2857 PERM_WEIGHTS = {
2888 PERM_WEIGHTS = {
2858 'repository.none': 0,
2889 'repository.none': 0,
2859 'repository.read': 1,
2890 'repository.read': 1,
2860 'repository.write': 3,
2891 'repository.write': 3,
2861 'repository.admin': 4,
2892 'repository.admin': 4,
2862
2893
2863 'group.none': 0,
2894 'group.none': 0,
2864 'group.read': 1,
2895 'group.read': 1,
2865 'group.write': 3,
2896 'group.write': 3,
2866 'group.admin': 4,
2897 'group.admin': 4,
2867
2898
2868 'usergroup.none': 0,
2899 'usergroup.none': 0,
2869 'usergroup.read': 1,
2900 'usergroup.read': 1,
2870 'usergroup.write': 3,
2901 'usergroup.write': 3,
2871 'usergroup.admin': 4,
2902 'usergroup.admin': 4,
2872
2903
2873 'branch.none': 0,
2904 'branch.none': 0,
2874 'branch.merge': 1,
2905 'branch.merge': 1,
2875 'branch.push': 3,
2906 'branch.push': 3,
2876 'branch.push_force': 4,
2907 'branch.push_force': 4,
2877
2908
2878 'hg.repogroup.create.false': 0,
2909 'hg.repogroup.create.false': 0,
2879 'hg.repogroup.create.true': 1,
2910 'hg.repogroup.create.true': 1,
2880
2911
2881 'hg.usergroup.create.false': 0,
2912 'hg.usergroup.create.false': 0,
2882 'hg.usergroup.create.true': 1,
2913 'hg.usergroup.create.true': 1,
2883
2914
2884 'hg.fork.none': 0,
2915 'hg.fork.none': 0,
2885 'hg.fork.repository': 1,
2916 'hg.fork.repository': 1,
2886 'hg.create.none': 0,
2917 'hg.create.none': 0,
2887 'hg.create.repository': 1
2918 'hg.create.repository': 1
2888 }
2919 }
2889
2920
2890 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2921 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2891 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2922 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2892 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2923 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2893
2924
2894 def __unicode__(self):
2925 def __unicode__(self):
2895 return u"<%s('%s:%s')>" % (
2926 return u"<%s('%s:%s')>" % (
2896 self.__class__.__name__, self.permission_id, self.permission_name
2927 self.__class__.__name__, self.permission_id, self.permission_name
2897 )
2928 )
2898
2929
2899 @classmethod
2930 @classmethod
2900 def get_by_key(cls, key):
2931 def get_by_key(cls, key):
2901 return cls.query().filter(cls.permission_name == key).scalar()
2932 return cls.query().filter(cls.permission_name == key).scalar()
2902
2933
2903 @classmethod
2934 @classmethod
2904 def get_default_repo_perms(cls, user_id, repo_id=None):
2935 def get_default_repo_perms(cls, user_id, repo_id=None):
2905 q = Session().query(UserRepoToPerm, Repository, Permission)\
2936 q = Session().query(UserRepoToPerm, Repository, Permission)\
2906 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2937 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2907 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2938 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2908 .filter(UserRepoToPerm.user_id == user_id)
2939 .filter(UserRepoToPerm.user_id == user_id)
2909 if repo_id:
2940 if repo_id:
2910 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2941 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2911 return q.all()
2942 return q.all()
2912
2943
2913 @classmethod
2944 @classmethod
2914 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
2945 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
2915 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
2946 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
2916 .join(
2947 .join(
2917 Permission,
2948 Permission,
2918 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
2949 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
2919 .join(
2950 .join(
2920 UserRepoToPerm,
2951 UserRepoToPerm,
2921 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
2952 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
2922 .filter(UserRepoToPerm.user_id == user_id)
2953 .filter(UserRepoToPerm.user_id == user_id)
2923
2954
2924 if repo_id:
2955 if repo_id:
2925 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
2956 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
2926 return q.order_by(UserToRepoBranchPermission.rule_order).all()
2957 return q.order_by(UserToRepoBranchPermission.rule_order).all()
2927
2958
2928 @classmethod
2959 @classmethod
2929 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2960 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2930 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2961 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2931 .join(
2962 .join(
2932 Permission,
2963 Permission,
2933 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2964 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2934 .join(
2965 .join(
2935 Repository,
2966 Repository,
2936 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2967 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2937 .join(
2968 .join(
2938 UserGroup,
2969 UserGroup,
2939 UserGroupRepoToPerm.users_group_id ==
2970 UserGroupRepoToPerm.users_group_id ==
2940 UserGroup.users_group_id)\
2971 UserGroup.users_group_id)\
2941 .join(
2972 .join(
2942 UserGroupMember,
2973 UserGroupMember,
2943 UserGroupRepoToPerm.users_group_id ==
2974 UserGroupRepoToPerm.users_group_id ==
2944 UserGroupMember.users_group_id)\
2975 UserGroupMember.users_group_id)\
2945 .filter(
2976 .filter(
2946 UserGroupMember.user_id == user_id,
2977 UserGroupMember.user_id == user_id,
2947 UserGroup.users_group_active == true())
2978 UserGroup.users_group_active == true())
2948 if repo_id:
2979 if repo_id:
2949 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2980 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2950 return q.all()
2981 return q.all()
2951
2982
2952 @classmethod
2983 @classmethod
2953 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
2984 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
2954 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
2985 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
2955 .join(
2986 .join(
2956 Permission,
2987 Permission,
2957 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
2988 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
2958 .join(
2989 .join(
2959 UserGroupRepoToPerm,
2990 UserGroupRepoToPerm,
2960 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
2991 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
2961 .join(
2992 .join(
2962 UserGroup,
2993 UserGroup,
2963 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
2994 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
2964 .join(
2995 .join(
2965 UserGroupMember,
2996 UserGroupMember,
2966 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
2997 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
2967 .filter(
2998 .filter(
2968 UserGroupMember.user_id == user_id,
2999 UserGroupMember.user_id == user_id,
2969 UserGroup.users_group_active == true())
3000 UserGroup.users_group_active == true())
2970
3001
2971 if repo_id:
3002 if repo_id:
2972 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3003 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
2973 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3004 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
2974
3005
2975 @classmethod
3006 @classmethod
2976 def get_default_group_perms(cls, user_id, repo_group_id=None):
3007 def get_default_group_perms(cls, user_id, repo_group_id=None):
2977 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3008 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2978 .join(
3009 .join(
2979 Permission,
3010 Permission,
2980 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3011 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
2981 .join(
3012 .join(
2982 RepoGroup,
3013 RepoGroup,
2983 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3014 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
2984 .filter(UserRepoGroupToPerm.user_id == user_id)
3015 .filter(UserRepoGroupToPerm.user_id == user_id)
2985 if repo_group_id:
3016 if repo_group_id:
2986 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3017 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2987 return q.all()
3018 return q.all()
2988
3019
2989 @classmethod
3020 @classmethod
2990 def get_default_group_perms_from_user_group(
3021 def get_default_group_perms_from_user_group(
2991 cls, user_id, repo_group_id=None):
3022 cls, user_id, repo_group_id=None):
2992 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3023 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2993 .join(
3024 .join(
2994 Permission,
3025 Permission,
2995 UserGroupRepoGroupToPerm.permission_id ==
3026 UserGroupRepoGroupToPerm.permission_id ==
2996 Permission.permission_id)\
3027 Permission.permission_id)\
2997 .join(
3028 .join(
2998 RepoGroup,
3029 RepoGroup,
2999 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3030 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3000 .join(
3031 .join(
3001 UserGroup,
3032 UserGroup,
3002 UserGroupRepoGroupToPerm.users_group_id ==
3033 UserGroupRepoGroupToPerm.users_group_id ==
3003 UserGroup.users_group_id)\
3034 UserGroup.users_group_id)\
3004 .join(
3035 .join(
3005 UserGroupMember,
3036 UserGroupMember,
3006 UserGroupRepoGroupToPerm.users_group_id ==
3037 UserGroupRepoGroupToPerm.users_group_id ==
3007 UserGroupMember.users_group_id)\
3038 UserGroupMember.users_group_id)\
3008 .filter(
3039 .filter(
3009 UserGroupMember.user_id == user_id,
3040 UserGroupMember.user_id == user_id,
3010 UserGroup.users_group_active == true())
3041 UserGroup.users_group_active == true())
3011 if repo_group_id:
3042 if repo_group_id:
3012 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3043 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3013 return q.all()
3044 return q.all()
3014
3045
3015 @classmethod
3046 @classmethod
3016 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3047 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3017 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3048 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3018 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3049 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3019 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3050 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3020 .filter(UserUserGroupToPerm.user_id == user_id)
3051 .filter(UserUserGroupToPerm.user_id == user_id)
3021 if user_group_id:
3052 if user_group_id:
3022 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3053 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3023 return q.all()
3054 return q.all()
3024
3055
3025 @classmethod
3056 @classmethod
3026 def get_default_user_group_perms_from_user_group(
3057 def get_default_user_group_perms_from_user_group(
3027 cls, user_id, user_group_id=None):
3058 cls, user_id, user_group_id=None):
3028 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3059 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3029 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3060 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3030 .join(
3061 .join(
3031 Permission,
3062 Permission,
3032 UserGroupUserGroupToPerm.permission_id ==
3063 UserGroupUserGroupToPerm.permission_id ==
3033 Permission.permission_id)\
3064 Permission.permission_id)\
3034 .join(
3065 .join(
3035 TargetUserGroup,
3066 TargetUserGroup,
3036 UserGroupUserGroupToPerm.target_user_group_id ==
3067 UserGroupUserGroupToPerm.target_user_group_id ==
3037 TargetUserGroup.users_group_id)\
3068 TargetUserGroup.users_group_id)\
3038 .join(
3069 .join(
3039 UserGroup,
3070 UserGroup,
3040 UserGroupUserGroupToPerm.user_group_id ==
3071 UserGroupUserGroupToPerm.user_group_id ==
3041 UserGroup.users_group_id)\
3072 UserGroup.users_group_id)\
3042 .join(
3073 .join(
3043 UserGroupMember,
3074 UserGroupMember,
3044 UserGroupUserGroupToPerm.user_group_id ==
3075 UserGroupUserGroupToPerm.user_group_id ==
3045 UserGroupMember.users_group_id)\
3076 UserGroupMember.users_group_id)\
3046 .filter(
3077 .filter(
3047 UserGroupMember.user_id == user_id,
3078 UserGroupMember.user_id == user_id,
3048 UserGroup.users_group_active == true())
3079 UserGroup.users_group_active == true())
3049 if user_group_id:
3080 if user_group_id:
3050 q = q.filter(
3081 q = q.filter(
3051 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3082 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3052
3083
3053 return q.all()
3084 return q.all()
3054
3085
3055
3086
3056 class UserRepoToPerm(Base, BaseModel):
3087 class UserRepoToPerm(Base, BaseModel):
3057 __tablename__ = 'repo_to_perm'
3088 __tablename__ = 'repo_to_perm'
3058 __table_args__ = (
3089 __table_args__ = (
3059 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3090 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3060 base_table_args
3091 base_table_args
3061 )
3092 )
3062
3093
3063 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3094 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3064 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3095 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3065 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3096 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3066 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3097 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3067
3098
3068 user = relationship('User')
3099 user = relationship('User')
3069 repository = relationship('Repository')
3100 repository = relationship('Repository')
3070 permission = relationship('Permission')
3101 permission = relationship('Permission')
3071
3102
3072 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
3103 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
3073
3104
3074 @classmethod
3105 @classmethod
3075 def create(cls, user, repository, permission):
3106 def create(cls, user, repository, permission):
3076 n = cls()
3107 n = cls()
3077 n.user = user
3108 n.user = user
3078 n.repository = repository
3109 n.repository = repository
3079 n.permission = permission
3110 n.permission = permission
3080 Session().add(n)
3111 Session().add(n)
3081 return n
3112 return n
3082
3113
3083 def __unicode__(self):
3114 def __unicode__(self):
3084 return u'<%s => %s >' % (self.user, self.repository)
3115 return u'<%s => %s >' % (self.user, self.repository)
3085
3116
3086
3117
3087 class UserUserGroupToPerm(Base, BaseModel):
3118 class UserUserGroupToPerm(Base, BaseModel):
3088 __tablename__ = 'user_user_group_to_perm'
3119 __tablename__ = 'user_user_group_to_perm'
3089 __table_args__ = (
3120 __table_args__ = (
3090 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3121 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3091 base_table_args
3122 base_table_args
3092 )
3123 )
3093
3124
3094 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3125 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3095 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3126 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3096 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3127 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3097 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3128 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3098
3129
3099 user = relationship('User')
3130 user = relationship('User')
3100 user_group = relationship('UserGroup')
3131 user_group = relationship('UserGroup')
3101 permission = relationship('Permission')
3132 permission = relationship('Permission')
3102
3133
3103 @classmethod
3134 @classmethod
3104 def create(cls, user, user_group, permission):
3135 def create(cls, user, user_group, permission):
3105 n = cls()
3136 n = cls()
3106 n.user = user
3137 n.user = user
3107 n.user_group = user_group
3138 n.user_group = user_group
3108 n.permission = permission
3139 n.permission = permission
3109 Session().add(n)
3140 Session().add(n)
3110 return n
3141 return n
3111
3142
3112 def __unicode__(self):
3143 def __unicode__(self):
3113 return u'<%s => %s >' % (self.user, self.user_group)
3144 return u'<%s => %s >' % (self.user, self.user_group)
3114
3145
3115
3146
3116 class UserToPerm(Base, BaseModel):
3147 class UserToPerm(Base, BaseModel):
3117 __tablename__ = 'user_to_perm'
3148 __tablename__ = 'user_to_perm'
3118 __table_args__ = (
3149 __table_args__ = (
3119 UniqueConstraint('user_id', 'permission_id'),
3150 UniqueConstraint('user_id', 'permission_id'),
3120 base_table_args
3151 base_table_args
3121 )
3152 )
3122
3153
3123 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3154 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3124 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3155 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3125 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3156 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3126
3157
3127 user = relationship('User')
3158 user = relationship('User')
3128 permission = relationship('Permission', lazy='joined')
3159 permission = relationship('Permission', lazy='joined')
3129
3160
3130 def __unicode__(self):
3161 def __unicode__(self):
3131 return u'<%s => %s >' % (self.user, self.permission)
3162 return u'<%s => %s >' % (self.user, self.permission)
3132
3163
3133
3164
3134 class UserGroupRepoToPerm(Base, BaseModel):
3165 class UserGroupRepoToPerm(Base, BaseModel):
3135 __tablename__ = 'users_group_repo_to_perm'
3166 __tablename__ = 'users_group_repo_to_perm'
3136 __table_args__ = (
3167 __table_args__ = (
3137 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3168 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3138 base_table_args
3169 base_table_args
3139 )
3170 )
3140
3171
3141 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3172 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3142 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3173 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3143 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3174 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3144 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3175 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3145
3176
3146 users_group = relationship('UserGroup')
3177 users_group = relationship('UserGroup')
3147 permission = relationship('Permission')
3178 permission = relationship('Permission')
3148 repository = relationship('Repository')
3179 repository = relationship('Repository')
3149 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3180 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3150
3181
3151 @classmethod
3182 @classmethod
3152 def create(cls, users_group, repository, permission):
3183 def create(cls, users_group, repository, permission):
3153 n = cls()
3184 n = cls()
3154 n.users_group = users_group
3185 n.users_group = users_group
3155 n.repository = repository
3186 n.repository = repository
3156 n.permission = permission
3187 n.permission = permission
3157 Session().add(n)
3188 Session().add(n)
3158 return n
3189 return n
3159
3190
3160 def __unicode__(self):
3191 def __unicode__(self):
3161 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3192 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3162
3193
3163
3194
3164 class UserGroupUserGroupToPerm(Base, BaseModel):
3195 class UserGroupUserGroupToPerm(Base, BaseModel):
3165 __tablename__ = 'user_group_user_group_to_perm'
3196 __tablename__ = 'user_group_user_group_to_perm'
3166 __table_args__ = (
3197 __table_args__ = (
3167 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3198 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3168 CheckConstraint('target_user_group_id != user_group_id'),
3199 CheckConstraint('target_user_group_id != user_group_id'),
3169 base_table_args
3200 base_table_args
3170 )
3201 )
3171
3202
3172 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)
3203 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)
3173 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3204 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3174 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3205 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3175 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3206 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3176
3207
3177 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3208 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3178 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3209 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3179 permission = relationship('Permission')
3210 permission = relationship('Permission')
3180
3211
3181 @classmethod
3212 @classmethod
3182 def create(cls, target_user_group, user_group, permission):
3213 def create(cls, target_user_group, user_group, permission):
3183 n = cls()
3214 n = cls()
3184 n.target_user_group = target_user_group
3215 n.target_user_group = target_user_group
3185 n.user_group = user_group
3216 n.user_group = user_group
3186 n.permission = permission
3217 n.permission = permission
3187 Session().add(n)
3218 Session().add(n)
3188 return n
3219 return n
3189
3220
3190 def __unicode__(self):
3221 def __unicode__(self):
3191 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3222 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3192
3223
3193
3224
3194 class UserGroupToPerm(Base, BaseModel):
3225 class UserGroupToPerm(Base, BaseModel):
3195 __tablename__ = 'users_group_to_perm'
3226 __tablename__ = 'users_group_to_perm'
3196 __table_args__ = (
3227 __table_args__ = (
3197 UniqueConstraint('users_group_id', 'permission_id',),
3228 UniqueConstraint('users_group_id', 'permission_id',),
3198 base_table_args
3229 base_table_args
3199 )
3230 )
3200
3231
3201 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3232 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3202 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3233 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3203 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3234 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3204
3235
3205 users_group = relationship('UserGroup')
3236 users_group = relationship('UserGroup')
3206 permission = relationship('Permission')
3237 permission = relationship('Permission')
3207
3238
3208
3239
3209 class UserRepoGroupToPerm(Base, BaseModel):
3240 class UserRepoGroupToPerm(Base, BaseModel):
3210 __tablename__ = 'user_repo_group_to_perm'
3241 __tablename__ = 'user_repo_group_to_perm'
3211 __table_args__ = (
3242 __table_args__ = (
3212 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3243 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3213 base_table_args
3244 base_table_args
3214 )
3245 )
3215
3246
3216 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3247 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3217 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3248 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3218 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3249 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3219 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3250 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3220
3251
3221 user = relationship('User')
3252 user = relationship('User')
3222 group = relationship('RepoGroup')
3253 group = relationship('RepoGroup')
3223 permission = relationship('Permission')
3254 permission = relationship('Permission')
3224
3255
3225 @classmethod
3256 @classmethod
3226 def create(cls, user, repository_group, permission):
3257 def create(cls, user, repository_group, permission):
3227 n = cls()
3258 n = cls()
3228 n.user = user
3259 n.user = user
3229 n.group = repository_group
3260 n.group = repository_group
3230 n.permission = permission
3261 n.permission = permission
3231 Session().add(n)
3262 Session().add(n)
3232 return n
3263 return n
3233
3264
3234
3265
3235 class UserGroupRepoGroupToPerm(Base, BaseModel):
3266 class UserGroupRepoGroupToPerm(Base, BaseModel):
3236 __tablename__ = 'users_group_repo_group_to_perm'
3267 __tablename__ = 'users_group_repo_group_to_perm'
3237 __table_args__ = (
3268 __table_args__ = (
3238 UniqueConstraint('users_group_id', 'group_id'),
3269 UniqueConstraint('users_group_id', 'group_id'),
3239 base_table_args
3270 base_table_args
3240 )
3271 )
3241
3272
3242 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)
3273 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)
3243 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3274 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3244 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3275 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3245 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3276 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3246
3277
3247 users_group = relationship('UserGroup')
3278 users_group = relationship('UserGroup')
3248 permission = relationship('Permission')
3279 permission = relationship('Permission')
3249 group = relationship('RepoGroup')
3280 group = relationship('RepoGroup')
3250
3281
3251 @classmethod
3282 @classmethod
3252 def create(cls, user_group, repository_group, permission):
3283 def create(cls, user_group, repository_group, permission):
3253 n = cls()
3284 n = cls()
3254 n.users_group = user_group
3285 n.users_group = user_group
3255 n.group = repository_group
3286 n.group = repository_group
3256 n.permission = permission
3287 n.permission = permission
3257 Session().add(n)
3288 Session().add(n)
3258 return n
3289 return n
3259
3290
3260 def __unicode__(self):
3291 def __unicode__(self):
3261 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3292 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3262
3293
3263
3294
3264 class Statistics(Base, BaseModel):
3295 class Statistics(Base, BaseModel):
3265 __tablename__ = 'statistics'
3296 __tablename__ = 'statistics'
3266 __table_args__ = (
3297 __table_args__ = (
3267 base_table_args
3298 base_table_args
3268 )
3299 )
3269
3300
3270 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3301 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3271 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3302 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3272 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3303 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3273 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3304 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3274 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3305 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3275 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3306 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3276
3307
3277 repository = relationship('Repository', single_parent=True)
3308 repository = relationship('Repository', single_parent=True)
3278
3309
3279
3310
3280 class UserFollowing(Base, BaseModel):
3311 class UserFollowing(Base, BaseModel):
3281 __tablename__ = 'user_followings'
3312 __tablename__ = 'user_followings'
3282 __table_args__ = (
3313 __table_args__ = (
3283 UniqueConstraint('user_id', 'follows_repository_id'),
3314 UniqueConstraint('user_id', 'follows_repository_id'),
3284 UniqueConstraint('user_id', 'follows_user_id'),
3315 UniqueConstraint('user_id', 'follows_user_id'),
3285 base_table_args
3316 base_table_args
3286 )
3317 )
3287
3318
3288 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3319 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3289 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3320 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3290 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3321 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3291 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3322 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3292 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3323 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3293
3324
3294 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3325 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3295
3326
3296 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3327 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3297 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3328 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3298
3329
3299 @classmethod
3330 @classmethod
3300 def get_repo_followers(cls, repo_id):
3331 def get_repo_followers(cls, repo_id):
3301 return cls.query().filter(cls.follows_repo_id == repo_id)
3332 return cls.query().filter(cls.follows_repo_id == repo_id)
3302
3333
3303
3334
3304 class CacheKey(Base, BaseModel):
3335 class CacheKey(Base, BaseModel):
3305 __tablename__ = 'cache_invalidation'
3336 __tablename__ = 'cache_invalidation'
3306 __table_args__ = (
3337 __table_args__ = (
3307 UniqueConstraint('cache_key'),
3338 UniqueConstraint('cache_key'),
3308 Index('key_idx', 'cache_key'),
3339 Index('key_idx', 'cache_key'),
3309 base_table_args,
3340 base_table_args,
3310 )
3341 )
3311
3342
3312 CACHE_TYPE_FEED = 'FEED'
3343 CACHE_TYPE_FEED = 'FEED'
3313 CACHE_TYPE_README = 'README'
3344 CACHE_TYPE_README = 'README'
3314 # namespaces used to register process/thread aware caches
3345 # namespaces used to register process/thread aware caches
3315 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3346 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3316 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3347 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3317
3348
3318 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3349 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3319 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3350 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3320 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3351 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3321 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3352 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3322
3353
3323 def __init__(self, cache_key, cache_args=''):
3354 def __init__(self, cache_key, cache_args=''):
3324 self.cache_key = cache_key
3355 self.cache_key = cache_key
3325 self.cache_args = cache_args
3356 self.cache_args = cache_args
3326 self.cache_active = False
3357 self.cache_active = False
3327
3358
3328 def __unicode__(self):
3359 def __unicode__(self):
3329 return u"<%s('%s:%s[%s]')>" % (
3360 return u"<%s('%s:%s[%s]')>" % (
3330 self.__class__.__name__,
3361 self.__class__.__name__,
3331 self.cache_id, self.cache_key, self.cache_active)
3362 self.cache_id, self.cache_key, self.cache_active)
3332
3363
3333 def _cache_key_partition(self):
3364 def _cache_key_partition(self):
3334 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3365 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3335 return prefix, repo_name, suffix
3366 return prefix, repo_name, suffix
3336
3367
3337 def get_prefix(self):
3368 def get_prefix(self):
3338 """
3369 """
3339 Try to extract prefix from existing cache key. The key could consist
3370 Try to extract prefix from existing cache key. The key could consist
3340 of prefix, repo_name, suffix
3371 of prefix, repo_name, suffix
3341 """
3372 """
3342 # this returns prefix, repo_name, suffix
3373 # this returns prefix, repo_name, suffix
3343 return self._cache_key_partition()[0]
3374 return self._cache_key_partition()[0]
3344
3375
3345 def get_suffix(self):
3376 def get_suffix(self):
3346 """
3377 """
3347 get suffix that might have been used in _get_cache_key to
3378 get suffix that might have been used in _get_cache_key to
3348 generate self.cache_key. Only used for informational purposes
3379 generate self.cache_key. Only used for informational purposes
3349 in repo_edit.mako.
3380 in repo_edit.mako.
3350 """
3381 """
3351 # prefix, repo_name, suffix
3382 # prefix, repo_name, suffix
3352 return self._cache_key_partition()[2]
3383 return self._cache_key_partition()[2]
3353
3384
3354 @classmethod
3385 @classmethod
3355 def delete_all_cache(cls):
3386 def delete_all_cache(cls):
3356 """
3387 """
3357 Delete all cache keys from database.
3388 Delete all cache keys from database.
3358 Should only be run when all instances are down and all entries
3389 Should only be run when all instances are down and all entries
3359 thus stale.
3390 thus stale.
3360 """
3391 """
3361 cls.query().delete()
3392 cls.query().delete()
3362 Session().commit()
3393 Session().commit()
3363
3394
3364 @classmethod
3395 @classmethod
3365 def set_invalidate(cls, cache_uid, delete=False):
3396 def set_invalidate(cls, cache_uid, delete=False):
3366 """
3397 """
3367 Mark all caches of a repo as invalid in the database.
3398 Mark all caches of a repo as invalid in the database.
3368 """
3399 """
3369
3400
3370 try:
3401 try:
3371 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3402 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3372 if delete:
3403 if delete:
3373 qry.delete()
3404 qry.delete()
3374 log.debug('cache objects deleted for cache args %s',
3405 log.debug('cache objects deleted for cache args %s',
3375 safe_str(cache_uid))
3406 safe_str(cache_uid))
3376 else:
3407 else:
3377 qry.update({"cache_active": False})
3408 qry.update({"cache_active": False})
3378 log.debug('cache objects marked as invalid for cache args %s',
3409 log.debug('cache objects marked as invalid for cache args %s',
3379 safe_str(cache_uid))
3410 safe_str(cache_uid))
3380
3411
3381 Session().commit()
3412 Session().commit()
3382 except Exception:
3413 except Exception:
3383 log.exception(
3414 log.exception(
3384 'Cache key invalidation failed for cache args %s',
3415 'Cache key invalidation failed for cache args %s',
3385 safe_str(cache_uid))
3416 safe_str(cache_uid))
3386 Session().rollback()
3417 Session().rollback()
3387
3418
3388 @classmethod
3419 @classmethod
3389 def get_active_cache(cls, cache_key):
3420 def get_active_cache(cls, cache_key):
3390 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3421 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3391 if inv_obj:
3422 if inv_obj:
3392 return inv_obj
3423 return inv_obj
3393 return None
3424 return None
3394
3425
3395
3426
3396 class ChangesetComment(Base, BaseModel):
3427 class ChangesetComment(Base, BaseModel):
3397 __tablename__ = 'changeset_comments'
3428 __tablename__ = 'changeset_comments'
3398 __table_args__ = (
3429 __table_args__ = (
3399 Index('cc_revision_idx', 'revision'),
3430 Index('cc_revision_idx', 'revision'),
3400 base_table_args,
3431 base_table_args,
3401 )
3432 )
3402
3433
3403 COMMENT_OUTDATED = u'comment_outdated'
3434 COMMENT_OUTDATED = u'comment_outdated'
3404 COMMENT_TYPE_NOTE = u'note'
3435 COMMENT_TYPE_NOTE = u'note'
3405 COMMENT_TYPE_TODO = u'todo'
3436 COMMENT_TYPE_TODO = u'todo'
3406 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3437 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3407
3438
3408 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3439 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3409 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3440 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3410 revision = Column('revision', String(40), nullable=True)
3441 revision = Column('revision', String(40), nullable=True)
3411 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3442 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3412 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3443 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3413 line_no = Column('line_no', Unicode(10), nullable=True)
3444 line_no = Column('line_no', Unicode(10), nullable=True)
3414 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3445 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3415 f_path = Column('f_path', Unicode(1000), nullable=True)
3446 f_path = Column('f_path', Unicode(1000), nullable=True)
3416 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3447 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3417 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3448 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3418 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3449 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3419 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3450 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3420 renderer = Column('renderer', Unicode(64), nullable=True)
3451 renderer = Column('renderer', Unicode(64), nullable=True)
3421 display_state = Column('display_state', Unicode(128), nullable=True)
3452 display_state = Column('display_state', Unicode(128), nullable=True)
3422
3453
3423 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3454 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3424 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3455 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3425
3456
3426 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3457 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3427 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3458 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3428
3459
3429 author = relationship('User', lazy='joined')
3460 author = relationship('User', lazy='joined')
3430 repo = relationship('Repository')
3461 repo = relationship('Repository')
3431 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3462 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3432 pull_request = relationship('PullRequest', lazy='joined')
3463 pull_request = relationship('PullRequest', lazy='joined')
3433 pull_request_version = relationship('PullRequestVersion')
3464 pull_request_version = relationship('PullRequestVersion')
3434
3465
3435 @classmethod
3466 @classmethod
3436 def get_users(cls, revision=None, pull_request_id=None):
3467 def get_users(cls, revision=None, pull_request_id=None):
3437 """
3468 """
3438 Returns user associated with this ChangesetComment. ie those
3469 Returns user associated with this ChangesetComment. ie those
3439 who actually commented
3470 who actually commented
3440
3471
3441 :param cls:
3472 :param cls:
3442 :param revision:
3473 :param revision:
3443 """
3474 """
3444 q = Session().query(User)\
3475 q = Session().query(User)\
3445 .join(ChangesetComment.author)
3476 .join(ChangesetComment.author)
3446 if revision:
3477 if revision:
3447 q = q.filter(cls.revision == revision)
3478 q = q.filter(cls.revision == revision)
3448 elif pull_request_id:
3479 elif pull_request_id:
3449 q = q.filter(cls.pull_request_id == pull_request_id)
3480 q = q.filter(cls.pull_request_id == pull_request_id)
3450 return q.all()
3481 return q.all()
3451
3482
3452 @classmethod
3483 @classmethod
3453 def get_index_from_version(cls, pr_version, versions):
3484 def get_index_from_version(cls, pr_version, versions):
3454 num_versions = [x.pull_request_version_id for x in versions]
3485 num_versions = [x.pull_request_version_id for x in versions]
3455 try:
3486 try:
3456 return num_versions.index(pr_version) +1
3487 return num_versions.index(pr_version) +1
3457 except (IndexError, ValueError):
3488 except (IndexError, ValueError):
3458 return
3489 return
3459
3490
3460 @property
3491 @property
3461 def outdated(self):
3492 def outdated(self):
3462 return self.display_state == self.COMMENT_OUTDATED
3493 return self.display_state == self.COMMENT_OUTDATED
3463
3494
3464 def outdated_at_version(self, version):
3495 def outdated_at_version(self, version):
3465 """
3496 """
3466 Checks if comment is outdated for given pull request version
3497 Checks if comment is outdated for given pull request version
3467 """
3498 """
3468 return self.outdated and self.pull_request_version_id != version
3499 return self.outdated and self.pull_request_version_id != version
3469
3500
3470 def older_than_version(self, version):
3501 def older_than_version(self, version):
3471 """
3502 """
3472 Checks if comment is made from previous version than given
3503 Checks if comment is made from previous version than given
3473 """
3504 """
3474 if version is None:
3505 if version is None:
3475 return self.pull_request_version_id is not None
3506 return self.pull_request_version_id is not None
3476
3507
3477 return self.pull_request_version_id < version
3508 return self.pull_request_version_id < version
3478
3509
3479 @property
3510 @property
3480 def resolved(self):
3511 def resolved(self):
3481 return self.resolved_by[0] if self.resolved_by else None
3512 return self.resolved_by[0] if self.resolved_by else None
3482
3513
3483 @property
3514 @property
3484 def is_todo(self):
3515 def is_todo(self):
3485 return self.comment_type == self.COMMENT_TYPE_TODO
3516 return self.comment_type == self.COMMENT_TYPE_TODO
3486
3517
3487 @property
3518 @property
3488 def is_inline(self):
3519 def is_inline(self):
3489 return self.line_no and self.f_path
3520 return self.line_no and self.f_path
3490
3521
3491 def get_index_version(self, versions):
3522 def get_index_version(self, versions):
3492 return self.get_index_from_version(
3523 return self.get_index_from_version(
3493 self.pull_request_version_id, versions)
3524 self.pull_request_version_id, versions)
3494
3525
3495 def __repr__(self):
3526 def __repr__(self):
3496 if self.comment_id:
3527 if self.comment_id:
3497 return '<DB:Comment #%s>' % self.comment_id
3528 return '<DB:Comment #%s>' % self.comment_id
3498 else:
3529 else:
3499 return '<DB:Comment at %#x>' % id(self)
3530 return '<DB:Comment at %#x>' % id(self)
3500
3531
3501 def get_api_data(self):
3532 def get_api_data(self):
3502 comment = self
3533 comment = self
3503 data = {
3534 data = {
3504 'comment_id': comment.comment_id,
3535 'comment_id': comment.comment_id,
3505 'comment_type': comment.comment_type,
3536 'comment_type': comment.comment_type,
3506 'comment_text': comment.text,
3537 'comment_text': comment.text,
3507 'comment_status': comment.status_change,
3538 'comment_status': comment.status_change,
3508 'comment_f_path': comment.f_path,
3539 'comment_f_path': comment.f_path,
3509 'comment_lineno': comment.line_no,
3540 'comment_lineno': comment.line_no,
3510 'comment_author': comment.author,
3541 'comment_author': comment.author,
3511 'comment_created_on': comment.created_on,
3542 'comment_created_on': comment.created_on,
3512 'comment_resolved_by': self.resolved
3543 'comment_resolved_by': self.resolved
3513 }
3544 }
3514 return data
3545 return data
3515
3546
3516 def __json__(self):
3547 def __json__(self):
3517 data = dict()
3548 data = dict()
3518 data.update(self.get_api_data())
3549 data.update(self.get_api_data())
3519 return data
3550 return data
3520
3551
3521
3552
3522 class ChangesetStatus(Base, BaseModel):
3553 class ChangesetStatus(Base, BaseModel):
3523 __tablename__ = 'changeset_statuses'
3554 __tablename__ = 'changeset_statuses'
3524 __table_args__ = (
3555 __table_args__ = (
3525 Index('cs_revision_idx', 'revision'),
3556 Index('cs_revision_idx', 'revision'),
3526 Index('cs_version_idx', 'version'),
3557 Index('cs_version_idx', 'version'),
3527 UniqueConstraint('repo_id', 'revision', 'version'),
3558 UniqueConstraint('repo_id', 'revision', 'version'),
3528 base_table_args
3559 base_table_args
3529 )
3560 )
3530
3561
3531 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3562 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3532 STATUS_APPROVED = 'approved'
3563 STATUS_APPROVED = 'approved'
3533 STATUS_REJECTED = 'rejected'
3564 STATUS_REJECTED = 'rejected'
3534 STATUS_UNDER_REVIEW = 'under_review'
3565 STATUS_UNDER_REVIEW = 'under_review'
3535
3566
3536 STATUSES = [
3567 STATUSES = [
3537 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3568 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3538 (STATUS_APPROVED, _("Approved")),
3569 (STATUS_APPROVED, _("Approved")),
3539 (STATUS_REJECTED, _("Rejected")),
3570 (STATUS_REJECTED, _("Rejected")),
3540 (STATUS_UNDER_REVIEW, _("Under Review")),
3571 (STATUS_UNDER_REVIEW, _("Under Review")),
3541 ]
3572 ]
3542
3573
3543 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3574 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3544 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3575 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3545 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3576 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3546 revision = Column('revision', String(40), nullable=False)
3577 revision = Column('revision', String(40), nullable=False)
3547 status = Column('status', String(128), nullable=False, default=DEFAULT)
3578 status = Column('status', String(128), nullable=False, default=DEFAULT)
3548 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3579 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3549 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3580 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3550 version = Column('version', Integer(), nullable=False, default=0)
3581 version = Column('version', Integer(), nullable=False, default=0)
3551 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3582 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3552
3583
3553 author = relationship('User', lazy='joined')
3584 author = relationship('User', lazy='joined')
3554 repo = relationship('Repository')
3585 repo = relationship('Repository')
3555 comment = relationship('ChangesetComment', lazy='joined')
3586 comment = relationship('ChangesetComment', lazy='joined')
3556 pull_request = relationship('PullRequest', lazy='joined')
3587 pull_request = relationship('PullRequest', lazy='joined')
3557
3588
3558 def __unicode__(self):
3589 def __unicode__(self):
3559 return u"<%s('%s[v%s]:%s')>" % (
3590 return u"<%s('%s[v%s]:%s')>" % (
3560 self.__class__.__name__,
3591 self.__class__.__name__,
3561 self.status, self.version, self.author
3592 self.status, self.version, self.author
3562 )
3593 )
3563
3594
3564 @classmethod
3595 @classmethod
3565 def get_status_lbl(cls, value):
3596 def get_status_lbl(cls, value):
3566 return dict(cls.STATUSES).get(value)
3597 return dict(cls.STATUSES).get(value)
3567
3598
3568 @property
3599 @property
3569 def status_lbl(self):
3600 def status_lbl(self):
3570 return ChangesetStatus.get_status_lbl(self.status)
3601 return ChangesetStatus.get_status_lbl(self.status)
3571
3602
3572 def get_api_data(self):
3603 def get_api_data(self):
3573 status = self
3604 status = self
3574 data = {
3605 data = {
3575 'status_id': status.changeset_status_id,
3606 'status_id': status.changeset_status_id,
3576 'status': status.status,
3607 'status': status.status,
3577 }
3608 }
3578 return data
3609 return data
3579
3610
3580 def __json__(self):
3611 def __json__(self):
3581 data = dict()
3612 data = dict()
3582 data.update(self.get_api_data())
3613 data.update(self.get_api_data())
3583 return data
3614 return data
3584
3615
3585
3616
3586 class _SetState(object):
3617 class _SetState(object):
3587 """
3618 """
3588 Context processor allowing changing state for sensitive operation such as
3619 Context processor allowing changing state for sensitive operation such as
3589 pull request update or merge
3620 pull request update or merge
3590 """
3621 """
3591
3622
3592 def __init__(self, pull_request, pr_state, back_state=None):
3623 def __init__(self, pull_request, pr_state, back_state=None):
3593 self._pr = pull_request
3624 self._pr = pull_request
3594 self._org_state = back_state or pull_request.pull_request_state
3625 self._org_state = back_state or pull_request.pull_request_state
3595 self._pr_state = pr_state
3626 self._pr_state = pr_state
3596
3627
3597 def __enter__(self):
3628 def __enter__(self):
3598 log.debug('StateLock: entering set state context, setting state to: `%s`',
3629 log.debug('StateLock: entering set state context, setting state to: `%s`',
3599 self._pr_state)
3630 self._pr_state)
3600 self._pr.pull_request_state = self._pr_state
3631 self._pr.pull_request_state = self._pr_state
3601 Session().add(self._pr)
3632 Session().add(self._pr)
3602 Session().commit()
3633 Session().commit()
3603
3634
3604 def __exit__(self, exc_type, exc_val, exc_tb):
3635 def __exit__(self, exc_type, exc_val, exc_tb):
3605 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3636 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3606 self._org_state)
3637 self._org_state)
3607 self._pr.pull_request_state = self._org_state
3638 self._pr.pull_request_state = self._org_state
3608 Session().add(self._pr)
3639 Session().add(self._pr)
3609 Session().commit()
3640 Session().commit()
3610
3641
3611
3642
3612 class _PullRequestBase(BaseModel):
3643 class _PullRequestBase(BaseModel):
3613 """
3644 """
3614 Common attributes of pull request and version entries.
3645 Common attributes of pull request and version entries.
3615 """
3646 """
3616
3647
3617 # .status values
3648 # .status values
3618 STATUS_NEW = u'new'
3649 STATUS_NEW = u'new'
3619 STATUS_OPEN = u'open'
3650 STATUS_OPEN = u'open'
3620 STATUS_CLOSED = u'closed'
3651 STATUS_CLOSED = u'closed'
3621
3652
3622 # available states
3653 # available states
3623 STATE_CREATING = u'creating'
3654 STATE_CREATING = u'creating'
3624 STATE_UPDATING = u'updating'
3655 STATE_UPDATING = u'updating'
3625 STATE_MERGING = u'merging'
3656 STATE_MERGING = u'merging'
3626 STATE_CREATED = u'created'
3657 STATE_CREATED = u'created'
3627
3658
3628 title = Column('title', Unicode(255), nullable=True)
3659 title = Column('title', Unicode(255), nullable=True)
3629 description = Column(
3660 description = Column(
3630 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3661 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3631 nullable=True)
3662 nullable=True)
3632 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3663 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3633
3664
3634 # new/open/closed status of pull request (not approve/reject/etc)
3665 # new/open/closed status of pull request (not approve/reject/etc)
3635 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3666 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3636 created_on = Column(
3667 created_on = Column(
3637 'created_on', DateTime(timezone=False), nullable=False,
3668 'created_on', DateTime(timezone=False), nullable=False,
3638 default=datetime.datetime.now)
3669 default=datetime.datetime.now)
3639 updated_on = Column(
3670 updated_on = Column(
3640 'updated_on', DateTime(timezone=False), nullable=False,
3671 'updated_on', DateTime(timezone=False), nullable=False,
3641 default=datetime.datetime.now)
3672 default=datetime.datetime.now)
3642
3673
3643 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3674 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3644
3675
3645 @declared_attr
3676 @declared_attr
3646 def user_id(cls):
3677 def user_id(cls):
3647 return Column(
3678 return Column(
3648 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3679 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3649 unique=None)
3680 unique=None)
3650
3681
3651 # 500 revisions max
3682 # 500 revisions max
3652 _revisions = Column(
3683 _revisions = Column(
3653 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3684 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3654
3685
3655 @declared_attr
3686 @declared_attr
3656 def source_repo_id(cls):
3687 def source_repo_id(cls):
3657 # TODO: dan: rename column to source_repo_id
3688 # TODO: dan: rename column to source_repo_id
3658 return Column(
3689 return Column(
3659 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3690 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3660 nullable=False)
3691 nullable=False)
3661
3692
3662 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3693 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3663
3694
3664 @hybrid_property
3695 @hybrid_property
3665 def source_ref(self):
3696 def source_ref(self):
3666 return self._source_ref
3697 return self._source_ref
3667
3698
3668 @source_ref.setter
3699 @source_ref.setter
3669 def source_ref(self, val):
3700 def source_ref(self, val):
3670 parts = (val or '').split(':')
3701 parts = (val or '').split(':')
3671 if len(parts) != 3:
3702 if len(parts) != 3:
3672 raise ValueError(
3703 raise ValueError(
3673 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3704 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3674 self._source_ref = safe_unicode(val)
3705 self._source_ref = safe_unicode(val)
3675
3706
3676 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3707 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3677
3708
3678 @hybrid_property
3709 @hybrid_property
3679 def target_ref(self):
3710 def target_ref(self):
3680 return self._target_ref
3711 return self._target_ref
3681
3712
3682 @target_ref.setter
3713 @target_ref.setter
3683 def target_ref(self, val):
3714 def target_ref(self, val):
3684 parts = (val or '').split(':')
3715 parts = (val or '').split(':')
3685 if len(parts) != 3:
3716 if len(parts) != 3:
3686 raise ValueError(
3717 raise ValueError(
3687 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3718 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3688 self._target_ref = safe_unicode(val)
3719 self._target_ref = safe_unicode(val)
3689
3720
3690 @declared_attr
3721 @declared_attr
3691 def target_repo_id(cls):
3722 def target_repo_id(cls):
3692 # TODO: dan: rename column to target_repo_id
3723 # TODO: dan: rename column to target_repo_id
3693 return Column(
3724 return Column(
3694 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3725 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3695 nullable=False)
3726 nullable=False)
3696
3727
3697 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3728 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3698
3729
3699 # TODO: dan: rename column to last_merge_source_rev
3730 # TODO: dan: rename column to last_merge_source_rev
3700 _last_merge_source_rev = Column(
3731 _last_merge_source_rev = Column(
3701 'last_merge_org_rev', String(40), nullable=True)
3732 'last_merge_org_rev', String(40), nullable=True)
3702 # TODO: dan: rename column to last_merge_target_rev
3733 # TODO: dan: rename column to last_merge_target_rev
3703 _last_merge_target_rev = Column(
3734 _last_merge_target_rev = Column(
3704 'last_merge_other_rev', String(40), nullable=True)
3735 'last_merge_other_rev', String(40), nullable=True)
3705 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3736 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3706 merge_rev = Column('merge_rev', String(40), nullable=True)
3737 merge_rev = Column('merge_rev', String(40), nullable=True)
3707
3738
3708 reviewer_data = Column(
3739 reviewer_data = Column(
3709 'reviewer_data_json', MutationObj.as_mutable(
3740 'reviewer_data_json', MutationObj.as_mutable(
3710 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3741 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3711
3742
3712 @property
3743 @property
3713 def reviewer_data_json(self):
3744 def reviewer_data_json(self):
3714 return json.dumps(self.reviewer_data)
3745 return json.dumps(self.reviewer_data)
3715
3746
3716 @hybrid_property
3747 @hybrid_property
3717 def description_safe(self):
3748 def description_safe(self):
3718 from rhodecode.lib import helpers as h
3749 from rhodecode.lib import helpers as h
3719 return h.escape(self.description)
3750 return h.escape(self.description)
3720
3751
3721 @hybrid_property
3752 @hybrid_property
3722 def revisions(self):
3753 def revisions(self):
3723 return self._revisions.split(':') if self._revisions else []
3754 return self._revisions.split(':') if self._revisions else []
3724
3755
3725 @revisions.setter
3756 @revisions.setter
3726 def revisions(self, val):
3757 def revisions(self, val):
3727 self._revisions = ':'.join(val)
3758 self._revisions = ':'.join(val)
3728
3759
3729 @hybrid_property
3760 @hybrid_property
3730 def last_merge_status(self):
3761 def last_merge_status(self):
3731 return safe_int(self._last_merge_status)
3762 return safe_int(self._last_merge_status)
3732
3763
3733 @last_merge_status.setter
3764 @last_merge_status.setter
3734 def last_merge_status(self, val):
3765 def last_merge_status(self, val):
3735 self._last_merge_status = val
3766 self._last_merge_status = val
3736
3767
3737 @declared_attr
3768 @declared_attr
3738 def author(cls):
3769 def author(cls):
3739 return relationship('User', lazy='joined')
3770 return relationship('User', lazy='joined')
3740
3771
3741 @declared_attr
3772 @declared_attr
3742 def source_repo(cls):
3773 def source_repo(cls):
3743 return relationship(
3774 return relationship(
3744 'Repository',
3775 'Repository',
3745 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3776 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3746
3777
3747 @property
3778 @property
3748 def source_ref_parts(self):
3779 def source_ref_parts(self):
3749 return self.unicode_to_reference(self.source_ref)
3780 return self.unicode_to_reference(self.source_ref)
3750
3781
3751 @declared_attr
3782 @declared_attr
3752 def target_repo(cls):
3783 def target_repo(cls):
3753 return relationship(
3784 return relationship(
3754 'Repository',
3785 'Repository',
3755 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3786 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3756
3787
3757 @property
3788 @property
3758 def target_ref_parts(self):
3789 def target_ref_parts(self):
3759 return self.unicode_to_reference(self.target_ref)
3790 return self.unicode_to_reference(self.target_ref)
3760
3791
3761 @property
3792 @property
3762 def shadow_merge_ref(self):
3793 def shadow_merge_ref(self):
3763 return self.unicode_to_reference(self._shadow_merge_ref)
3794 return self.unicode_to_reference(self._shadow_merge_ref)
3764
3795
3765 @shadow_merge_ref.setter
3796 @shadow_merge_ref.setter
3766 def shadow_merge_ref(self, ref):
3797 def shadow_merge_ref(self, ref):
3767 self._shadow_merge_ref = self.reference_to_unicode(ref)
3798 self._shadow_merge_ref = self.reference_to_unicode(ref)
3768
3799
3769 @staticmethod
3800 @staticmethod
3770 def unicode_to_reference(raw):
3801 def unicode_to_reference(raw):
3771 """
3802 """
3772 Convert a unicode (or string) to a reference object.
3803 Convert a unicode (or string) to a reference object.
3773 If unicode evaluates to False it returns None.
3804 If unicode evaluates to False it returns None.
3774 """
3805 """
3775 if raw:
3806 if raw:
3776 refs = raw.split(':')
3807 refs = raw.split(':')
3777 return Reference(*refs)
3808 return Reference(*refs)
3778 else:
3809 else:
3779 return None
3810 return None
3780
3811
3781 @staticmethod
3812 @staticmethod
3782 def reference_to_unicode(ref):
3813 def reference_to_unicode(ref):
3783 """
3814 """
3784 Convert a reference object to unicode.
3815 Convert a reference object to unicode.
3785 If reference is None it returns None.
3816 If reference is None it returns None.
3786 """
3817 """
3787 if ref:
3818 if ref:
3788 return u':'.join(ref)
3819 return u':'.join(ref)
3789 else:
3820 else:
3790 return None
3821 return None
3791
3822
3792 def get_api_data(self, with_merge_state=True):
3823 def get_api_data(self, with_merge_state=True):
3793 from rhodecode.model.pull_request import PullRequestModel
3824 from rhodecode.model.pull_request import PullRequestModel
3794
3825
3795 pull_request = self
3826 pull_request = self
3796 if with_merge_state:
3827 if with_merge_state:
3797 merge_status = PullRequestModel().merge_status(pull_request)
3828 merge_status = PullRequestModel().merge_status(pull_request)
3798 merge_state = {
3829 merge_state = {
3799 'status': merge_status[0],
3830 'status': merge_status[0],
3800 'message': safe_unicode(merge_status[1]),
3831 'message': safe_unicode(merge_status[1]),
3801 }
3832 }
3802 else:
3833 else:
3803 merge_state = {'status': 'not_available',
3834 merge_state = {'status': 'not_available',
3804 'message': 'not_available'}
3835 'message': 'not_available'}
3805
3836
3806 merge_data = {
3837 merge_data = {
3807 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3838 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3808 'reference': (
3839 'reference': (
3809 pull_request.shadow_merge_ref._asdict()
3840 pull_request.shadow_merge_ref._asdict()
3810 if pull_request.shadow_merge_ref else None),
3841 if pull_request.shadow_merge_ref else None),
3811 }
3842 }
3812
3843
3813 data = {
3844 data = {
3814 'pull_request_id': pull_request.pull_request_id,
3845 'pull_request_id': pull_request.pull_request_id,
3815 'url': PullRequestModel().get_url(pull_request),
3846 'url': PullRequestModel().get_url(pull_request),
3816 'title': pull_request.title,
3847 'title': pull_request.title,
3817 'description': pull_request.description,
3848 'description': pull_request.description,
3818 'status': pull_request.status,
3849 'status': pull_request.status,
3819 'state': pull_request.pull_request_state,
3850 'state': pull_request.pull_request_state,
3820 'created_on': pull_request.created_on,
3851 'created_on': pull_request.created_on,
3821 'updated_on': pull_request.updated_on,
3852 'updated_on': pull_request.updated_on,
3822 'commit_ids': pull_request.revisions,
3853 'commit_ids': pull_request.revisions,
3823 'review_status': pull_request.calculated_review_status(),
3854 'review_status': pull_request.calculated_review_status(),
3824 'mergeable': merge_state,
3855 'mergeable': merge_state,
3825 'source': {
3856 'source': {
3826 'clone_url': pull_request.source_repo.clone_url(),
3857 'clone_url': pull_request.source_repo.clone_url(),
3827 'repository': pull_request.source_repo.repo_name,
3858 'repository': pull_request.source_repo.repo_name,
3828 'reference': {
3859 'reference': {
3829 'name': pull_request.source_ref_parts.name,
3860 'name': pull_request.source_ref_parts.name,
3830 'type': pull_request.source_ref_parts.type,
3861 'type': pull_request.source_ref_parts.type,
3831 'commit_id': pull_request.source_ref_parts.commit_id,
3862 'commit_id': pull_request.source_ref_parts.commit_id,
3832 },
3863 },
3833 },
3864 },
3834 'target': {
3865 'target': {
3835 'clone_url': pull_request.target_repo.clone_url(),
3866 'clone_url': pull_request.target_repo.clone_url(),
3836 'repository': pull_request.target_repo.repo_name,
3867 'repository': pull_request.target_repo.repo_name,
3837 'reference': {
3868 'reference': {
3838 'name': pull_request.target_ref_parts.name,
3869 'name': pull_request.target_ref_parts.name,
3839 'type': pull_request.target_ref_parts.type,
3870 'type': pull_request.target_ref_parts.type,
3840 'commit_id': pull_request.target_ref_parts.commit_id,
3871 'commit_id': pull_request.target_ref_parts.commit_id,
3841 },
3872 },
3842 },
3873 },
3843 'merge': merge_data,
3874 'merge': merge_data,
3844 'author': pull_request.author.get_api_data(include_secrets=False,
3875 'author': pull_request.author.get_api_data(include_secrets=False,
3845 details='basic'),
3876 details='basic'),
3846 'reviewers': [
3877 'reviewers': [
3847 {
3878 {
3848 'user': reviewer.get_api_data(include_secrets=False,
3879 'user': reviewer.get_api_data(include_secrets=False,
3849 details='basic'),
3880 details='basic'),
3850 'reasons': reasons,
3881 'reasons': reasons,
3851 'review_status': st[0][1].status if st else 'not_reviewed',
3882 'review_status': st[0][1].status if st else 'not_reviewed',
3852 }
3883 }
3853 for obj, reviewer, reasons, mandatory, st in
3884 for obj, reviewer, reasons, mandatory, st in
3854 pull_request.reviewers_statuses()
3885 pull_request.reviewers_statuses()
3855 ]
3886 ]
3856 }
3887 }
3857
3888
3858 return data
3889 return data
3859
3890
3860 def set_state(self, pull_request_state, final_state=None):
3891 def set_state(self, pull_request_state, final_state=None):
3861 """
3892 """
3862 # goes from initial state to updating to initial state.
3893 # goes from initial state to updating to initial state.
3863 # initial state can be changed by specifying back_state=
3894 # initial state can be changed by specifying back_state=
3864 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
3895 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
3865 pull_request.merge()
3896 pull_request.merge()
3866
3897
3867 :param pull_request_state:
3898 :param pull_request_state:
3868 :param final_state:
3899 :param final_state:
3869
3900
3870 """
3901 """
3871
3902
3872 return _SetState(self, pull_request_state, back_state=final_state)
3903 return _SetState(self, pull_request_state, back_state=final_state)
3873
3904
3874
3905
3875 class PullRequest(Base, _PullRequestBase):
3906 class PullRequest(Base, _PullRequestBase):
3876 __tablename__ = 'pull_requests'
3907 __tablename__ = 'pull_requests'
3877 __table_args__ = (
3908 __table_args__ = (
3878 base_table_args,
3909 base_table_args,
3879 )
3910 )
3880
3911
3881 pull_request_id = Column(
3912 pull_request_id = Column(
3882 'pull_request_id', Integer(), nullable=False, primary_key=True)
3913 'pull_request_id', Integer(), nullable=False, primary_key=True)
3883
3914
3884 def __repr__(self):
3915 def __repr__(self):
3885 if self.pull_request_id:
3916 if self.pull_request_id:
3886 return '<DB:PullRequest #%s>' % self.pull_request_id
3917 return '<DB:PullRequest #%s>' % self.pull_request_id
3887 else:
3918 else:
3888 return '<DB:PullRequest at %#x>' % id(self)
3919 return '<DB:PullRequest at %#x>' % id(self)
3889
3920
3890 reviewers = relationship('PullRequestReviewers',
3921 reviewers = relationship('PullRequestReviewers',
3891 cascade="all, delete, delete-orphan")
3922 cascade="all, delete, delete-orphan")
3892 statuses = relationship('ChangesetStatus',
3923 statuses = relationship('ChangesetStatus',
3893 cascade="all, delete, delete-orphan")
3924 cascade="all, delete, delete-orphan")
3894 comments = relationship('ChangesetComment',
3925 comments = relationship('ChangesetComment',
3895 cascade="all, delete, delete-orphan")
3926 cascade="all, delete, delete-orphan")
3896 versions = relationship('PullRequestVersion',
3927 versions = relationship('PullRequestVersion',
3897 cascade="all, delete, delete-orphan",
3928 cascade="all, delete, delete-orphan",
3898 lazy='dynamic')
3929 lazy='dynamic')
3899
3930
3900 @classmethod
3931 @classmethod
3901 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3932 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3902 internal_methods=None):
3933 internal_methods=None):
3903
3934
3904 class PullRequestDisplay(object):
3935 class PullRequestDisplay(object):
3905 """
3936 """
3906 Special object wrapper for showing PullRequest data via Versions
3937 Special object wrapper for showing PullRequest data via Versions
3907 It mimics PR object as close as possible. This is read only object
3938 It mimics PR object as close as possible. This is read only object
3908 just for display
3939 just for display
3909 """
3940 """
3910
3941
3911 def __init__(self, attrs, internal=None):
3942 def __init__(self, attrs, internal=None):
3912 self.attrs = attrs
3943 self.attrs = attrs
3913 # internal have priority over the given ones via attrs
3944 # internal have priority over the given ones via attrs
3914 self.internal = internal or ['versions']
3945 self.internal = internal or ['versions']
3915
3946
3916 def __getattr__(self, item):
3947 def __getattr__(self, item):
3917 if item in self.internal:
3948 if item in self.internal:
3918 return getattr(self, item)
3949 return getattr(self, item)
3919 try:
3950 try:
3920 return self.attrs[item]
3951 return self.attrs[item]
3921 except KeyError:
3952 except KeyError:
3922 raise AttributeError(
3953 raise AttributeError(
3923 '%s object has no attribute %s' % (self, item))
3954 '%s object has no attribute %s' % (self, item))
3924
3955
3925 def __repr__(self):
3956 def __repr__(self):
3926 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3957 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3927
3958
3928 def versions(self):
3959 def versions(self):
3929 return pull_request_obj.versions.order_by(
3960 return pull_request_obj.versions.order_by(
3930 PullRequestVersion.pull_request_version_id).all()
3961 PullRequestVersion.pull_request_version_id).all()
3931
3962
3932 def is_closed(self):
3963 def is_closed(self):
3933 return pull_request_obj.is_closed()
3964 return pull_request_obj.is_closed()
3934
3965
3935 @property
3966 @property
3936 def pull_request_version_id(self):
3967 def pull_request_version_id(self):
3937 return getattr(pull_request_obj, 'pull_request_version_id', None)
3968 return getattr(pull_request_obj, 'pull_request_version_id', None)
3938
3969
3939 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3970 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3940
3971
3941 attrs.author = StrictAttributeDict(
3972 attrs.author = StrictAttributeDict(
3942 pull_request_obj.author.get_api_data())
3973 pull_request_obj.author.get_api_data())
3943 if pull_request_obj.target_repo:
3974 if pull_request_obj.target_repo:
3944 attrs.target_repo = StrictAttributeDict(
3975 attrs.target_repo = StrictAttributeDict(
3945 pull_request_obj.target_repo.get_api_data())
3976 pull_request_obj.target_repo.get_api_data())
3946 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3977 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3947
3978
3948 if pull_request_obj.source_repo:
3979 if pull_request_obj.source_repo:
3949 attrs.source_repo = StrictAttributeDict(
3980 attrs.source_repo = StrictAttributeDict(
3950 pull_request_obj.source_repo.get_api_data())
3981 pull_request_obj.source_repo.get_api_data())
3951 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3982 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3952
3983
3953 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3984 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3954 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3985 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3955 attrs.revisions = pull_request_obj.revisions
3986 attrs.revisions = pull_request_obj.revisions
3956
3987
3957 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3988 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3958 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3989 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3959 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3990 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3960
3991
3961 return PullRequestDisplay(attrs, internal=internal_methods)
3992 return PullRequestDisplay(attrs, internal=internal_methods)
3962
3993
3963 def is_closed(self):
3994 def is_closed(self):
3964 return self.status == self.STATUS_CLOSED
3995 return self.status == self.STATUS_CLOSED
3965
3996
3966 def __json__(self):
3997 def __json__(self):
3967 return {
3998 return {
3968 'revisions': self.revisions,
3999 'revisions': self.revisions,
3969 }
4000 }
3970
4001
3971 def calculated_review_status(self):
4002 def calculated_review_status(self):
3972 from rhodecode.model.changeset_status import ChangesetStatusModel
4003 from rhodecode.model.changeset_status import ChangesetStatusModel
3973 return ChangesetStatusModel().calculated_review_status(self)
4004 return ChangesetStatusModel().calculated_review_status(self)
3974
4005
3975 def reviewers_statuses(self):
4006 def reviewers_statuses(self):
3976 from rhodecode.model.changeset_status import ChangesetStatusModel
4007 from rhodecode.model.changeset_status import ChangesetStatusModel
3977 return ChangesetStatusModel().reviewers_statuses(self)
4008 return ChangesetStatusModel().reviewers_statuses(self)
3978
4009
3979 @property
4010 @property
3980 def workspace_id(self):
4011 def workspace_id(self):
3981 from rhodecode.model.pull_request import PullRequestModel
4012 from rhodecode.model.pull_request import PullRequestModel
3982 return PullRequestModel()._workspace_id(self)
4013 return PullRequestModel()._workspace_id(self)
3983
4014
3984 def get_shadow_repo(self):
4015 def get_shadow_repo(self):
3985 workspace_id = self.workspace_id
4016 workspace_id = self.workspace_id
3986 vcs_obj = self.target_repo.scm_instance()
4017 vcs_obj = self.target_repo.scm_instance()
3987 shadow_repository_path = vcs_obj._get_shadow_repository_path(
4018 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3988 self.target_repo.repo_id, workspace_id)
4019 self.target_repo.repo_id, workspace_id)
3989 if os.path.isdir(shadow_repository_path):
4020 if os.path.isdir(shadow_repository_path):
3990 return vcs_obj._get_shadow_instance(shadow_repository_path)
4021 return vcs_obj._get_shadow_instance(shadow_repository_path)
3991
4022
3992
4023
3993 class PullRequestVersion(Base, _PullRequestBase):
4024 class PullRequestVersion(Base, _PullRequestBase):
3994 __tablename__ = 'pull_request_versions'
4025 __tablename__ = 'pull_request_versions'
3995 __table_args__ = (
4026 __table_args__ = (
3996 base_table_args,
4027 base_table_args,
3997 )
4028 )
3998
4029
3999 pull_request_version_id = Column(
4030 pull_request_version_id = Column(
4000 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4031 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4001 pull_request_id = Column(
4032 pull_request_id = Column(
4002 'pull_request_id', Integer(),
4033 'pull_request_id', Integer(),
4003 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4034 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4004 pull_request = relationship('PullRequest')
4035 pull_request = relationship('PullRequest')
4005
4036
4006 def __repr__(self):
4037 def __repr__(self):
4007 if self.pull_request_version_id:
4038 if self.pull_request_version_id:
4008 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4039 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4009 else:
4040 else:
4010 return '<DB:PullRequestVersion at %#x>' % id(self)
4041 return '<DB:PullRequestVersion at %#x>' % id(self)
4011
4042
4012 @property
4043 @property
4013 def reviewers(self):
4044 def reviewers(self):
4014 return self.pull_request.reviewers
4045 return self.pull_request.reviewers
4015
4046
4016 @property
4047 @property
4017 def versions(self):
4048 def versions(self):
4018 return self.pull_request.versions
4049 return self.pull_request.versions
4019
4050
4020 def is_closed(self):
4051 def is_closed(self):
4021 # calculate from original
4052 # calculate from original
4022 return self.pull_request.status == self.STATUS_CLOSED
4053 return self.pull_request.status == self.STATUS_CLOSED
4023
4054
4024 def calculated_review_status(self):
4055 def calculated_review_status(self):
4025 return self.pull_request.calculated_review_status()
4056 return self.pull_request.calculated_review_status()
4026
4057
4027 def reviewers_statuses(self):
4058 def reviewers_statuses(self):
4028 return self.pull_request.reviewers_statuses()
4059 return self.pull_request.reviewers_statuses()
4029
4060
4030
4061
4031 class PullRequestReviewers(Base, BaseModel):
4062 class PullRequestReviewers(Base, BaseModel):
4032 __tablename__ = 'pull_request_reviewers'
4063 __tablename__ = 'pull_request_reviewers'
4033 __table_args__ = (
4064 __table_args__ = (
4034 base_table_args,
4065 base_table_args,
4035 )
4066 )
4036
4067
4037 @hybrid_property
4068 @hybrid_property
4038 def reasons(self):
4069 def reasons(self):
4039 if not self._reasons:
4070 if not self._reasons:
4040 return []
4071 return []
4041 return self._reasons
4072 return self._reasons
4042
4073
4043 @reasons.setter
4074 @reasons.setter
4044 def reasons(self, val):
4075 def reasons(self, val):
4045 val = val or []
4076 val = val or []
4046 if any(not isinstance(x, compat.string_types) for x in val):
4077 if any(not isinstance(x, compat.string_types) for x in val):
4047 raise Exception('invalid reasons type, must be list of strings')
4078 raise Exception('invalid reasons type, must be list of strings')
4048 self._reasons = val
4079 self._reasons = val
4049
4080
4050 pull_requests_reviewers_id = Column(
4081 pull_requests_reviewers_id = Column(
4051 'pull_requests_reviewers_id', Integer(), nullable=False,
4082 'pull_requests_reviewers_id', Integer(), nullable=False,
4052 primary_key=True)
4083 primary_key=True)
4053 pull_request_id = Column(
4084 pull_request_id = Column(
4054 "pull_request_id", Integer(),
4085 "pull_request_id", Integer(),
4055 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4086 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4056 user_id = Column(
4087 user_id = Column(
4057 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4088 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4058 _reasons = Column(
4089 _reasons = Column(
4059 'reason', MutationList.as_mutable(
4090 'reason', MutationList.as_mutable(
4060 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4091 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4061
4092
4062 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4093 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4063 user = relationship('User')
4094 user = relationship('User')
4064 pull_request = relationship('PullRequest')
4095 pull_request = relationship('PullRequest')
4065
4096
4066 rule_data = Column(
4097 rule_data = Column(
4067 'rule_data_json',
4098 'rule_data_json',
4068 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4099 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4069
4100
4070 def rule_user_group_data(self):
4101 def rule_user_group_data(self):
4071 """
4102 """
4072 Returns the voting user group rule data for this reviewer
4103 Returns the voting user group rule data for this reviewer
4073 """
4104 """
4074
4105
4075 if self.rule_data and 'vote_rule' in self.rule_data:
4106 if self.rule_data and 'vote_rule' in self.rule_data:
4076 user_group_data = {}
4107 user_group_data = {}
4077 if 'rule_user_group_entry_id' in self.rule_data:
4108 if 'rule_user_group_entry_id' in self.rule_data:
4078 # means a group with voting rules !
4109 # means a group with voting rules !
4079 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4110 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4080 user_group_data['name'] = self.rule_data['rule_name']
4111 user_group_data['name'] = self.rule_data['rule_name']
4081 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4112 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4082
4113
4083 return user_group_data
4114 return user_group_data
4084
4115
4085 def __unicode__(self):
4116 def __unicode__(self):
4086 return u"<%s('id:%s')>" % (self.__class__.__name__,
4117 return u"<%s('id:%s')>" % (self.__class__.__name__,
4087 self.pull_requests_reviewers_id)
4118 self.pull_requests_reviewers_id)
4088
4119
4089
4120
4090 class Notification(Base, BaseModel):
4121 class Notification(Base, BaseModel):
4091 __tablename__ = 'notifications'
4122 __tablename__ = 'notifications'
4092 __table_args__ = (
4123 __table_args__ = (
4093 Index('notification_type_idx', 'type'),
4124 Index('notification_type_idx', 'type'),
4094 base_table_args,
4125 base_table_args,
4095 )
4126 )
4096
4127
4097 TYPE_CHANGESET_COMMENT = u'cs_comment'
4128 TYPE_CHANGESET_COMMENT = u'cs_comment'
4098 TYPE_MESSAGE = u'message'
4129 TYPE_MESSAGE = u'message'
4099 TYPE_MENTION = u'mention'
4130 TYPE_MENTION = u'mention'
4100 TYPE_REGISTRATION = u'registration'
4131 TYPE_REGISTRATION = u'registration'
4101 TYPE_PULL_REQUEST = u'pull_request'
4132 TYPE_PULL_REQUEST = u'pull_request'
4102 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4133 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4103
4134
4104 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4135 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4105 subject = Column('subject', Unicode(512), nullable=True)
4136 subject = Column('subject', Unicode(512), nullable=True)
4106 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4137 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4107 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4138 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4108 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4139 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4109 type_ = Column('type', Unicode(255))
4140 type_ = Column('type', Unicode(255))
4110
4141
4111 created_by_user = relationship('User')
4142 created_by_user = relationship('User')
4112 notifications_to_users = relationship('UserNotification', lazy='joined',
4143 notifications_to_users = relationship('UserNotification', lazy='joined',
4113 cascade="all, delete, delete-orphan")
4144 cascade="all, delete, delete-orphan")
4114
4145
4115 @property
4146 @property
4116 def recipients(self):
4147 def recipients(self):
4117 return [x.user for x in UserNotification.query()\
4148 return [x.user for x in UserNotification.query()\
4118 .filter(UserNotification.notification == self)\
4149 .filter(UserNotification.notification == self)\
4119 .order_by(UserNotification.user_id.asc()).all()]
4150 .order_by(UserNotification.user_id.asc()).all()]
4120
4151
4121 @classmethod
4152 @classmethod
4122 def create(cls, created_by, subject, body, recipients, type_=None):
4153 def create(cls, created_by, subject, body, recipients, type_=None):
4123 if type_ is None:
4154 if type_ is None:
4124 type_ = Notification.TYPE_MESSAGE
4155 type_ = Notification.TYPE_MESSAGE
4125
4156
4126 notification = cls()
4157 notification = cls()
4127 notification.created_by_user = created_by
4158 notification.created_by_user = created_by
4128 notification.subject = subject
4159 notification.subject = subject
4129 notification.body = body
4160 notification.body = body
4130 notification.type_ = type_
4161 notification.type_ = type_
4131 notification.created_on = datetime.datetime.now()
4162 notification.created_on = datetime.datetime.now()
4132
4163
4133 # For each recipient link the created notification to his account
4164 # For each recipient link the created notification to his account
4134 for u in recipients:
4165 for u in recipients:
4135 assoc = UserNotification()
4166 assoc = UserNotification()
4136 assoc.user_id = u.user_id
4167 assoc.user_id = u.user_id
4137 assoc.notification = notification
4168 assoc.notification = notification
4138
4169
4139 # if created_by is inside recipients mark his notification
4170 # if created_by is inside recipients mark his notification
4140 # as read
4171 # as read
4141 if u.user_id == created_by.user_id:
4172 if u.user_id == created_by.user_id:
4142 assoc.read = True
4173 assoc.read = True
4143 Session().add(assoc)
4174 Session().add(assoc)
4144
4175
4145 Session().add(notification)
4176 Session().add(notification)
4146
4177
4147 return notification
4178 return notification
4148
4179
4149
4180
4150 class UserNotification(Base, BaseModel):
4181 class UserNotification(Base, BaseModel):
4151 __tablename__ = 'user_to_notification'
4182 __tablename__ = 'user_to_notification'
4152 __table_args__ = (
4183 __table_args__ = (
4153 UniqueConstraint('user_id', 'notification_id'),
4184 UniqueConstraint('user_id', 'notification_id'),
4154 base_table_args
4185 base_table_args
4155 )
4186 )
4156
4187
4157 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4188 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4158 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4189 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4159 read = Column('read', Boolean, default=False)
4190 read = Column('read', Boolean, default=False)
4160 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4191 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4161
4192
4162 user = relationship('User', lazy="joined")
4193 user = relationship('User', lazy="joined")
4163 notification = relationship('Notification', lazy="joined",
4194 notification = relationship('Notification', lazy="joined",
4164 order_by=lambda: Notification.created_on.desc(),)
4195 order_by=lambda: Notification.created_on.desc(),)
4165
4196
4166 def mark_as_read(self):
4197 def mark_as_read(self):
4167 self.read = True
4198 self.read = True
4168 Session().add(self)
4199 Session().add(self)
4169
4200
4170
4201
4171 class Gist(Base, BaseModel):
4202 class Gist(Base, BaseModel):
4172 __tablename__ = 'gists'
4203 __tablename__ = 'gists'
4173 __table_args__ = (
4204 __table_args__ = (
4174 Index('g_gist_access_id_idx', 'gist_access_id'),
4205 Index('g_gist_access_id_idx', 'gist_access_id'),
4175 Index('g_created_on_idx', 'created_on'),
4206 Index('g_created_on_idx', 'created_on'),
4176 base_table_args
4207 base_table_args
4177 )
4208 )
4178
4209
4179 GIST_PUBLIC = u'public'
4210 GIST_PUBLIC = u'public'
4180 GIST_PRIVATE = u'private'
4211 GIST_PRIVATE = u'private'
4181 DEFAULT_FILENAME = u'gistfile1.txt'
4212 DEFAULT_FILENAME = u'gistfile1.txt'
4182
4213
4183 ACL_LEVEL_PUBLIC = u'acl_public'
4214 ACL_LEVEL_PUBLIC = u'acl_public'
4184 ACL_LEVEL_PRIVATE = u'acl_private'
4215 ACL_LEVEL_PRIVATE = u'acl_private'
4185
4216
4186 gist_id = Column('gist_id', Integer(), primary_key=True)
4217 gist_id = Column('gist_id', Integer(), primary_key=True)
4187 gist_access_id = Column('gist_access_id', Unicode(250))
4218 gist_access_id = Column('gist_access_id', Unicode(250))
4188 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4219 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4189 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4220 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4190 gist_expires = Column('gist_expires', Float(53), nullable=False)
4221 gist_expires = Column('gist_expires', Float(53), nullable=False)
4191 gist_type = Column('gist_type', Unicode(128), nullable=False)
4222 gist_type = Column('gist_type', Unicode(128), nullable=False)
4192 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4223 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4193 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4224 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4194 acl_level = Column('acl_level', Unicode(128), nullable=True)
4225 acl_level = Column('acl_level', Unicode(128), nullable=True)
4195
4226
4196 owner = relationship('User')
4227 owner = relationship('User')
4197
4228
4198 def __repr__(self):
4229 def __repr__(self):
4199 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4230 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4200
4231
4201 @hybrid_property
4232 @hybrid_property
4202 def description_safe(self):
4233 def description_safe(self):
4203 from rhodecode.lib import helpers as h
4234 from rhodecode.lib import helpers as h
4204 return h.escape(self.gist_description)
4235 return h.escape(self.gist_description)
4205
4236
4206 @classmethod
4237 @classmethod
4207 def get_or_404(cls, id_):
4238 def get_or_404(cls, id_):
4208 from pyramid.httpexceptions import HTTPNotFound
4239 from pyramid.httpexceptions import HTTPNotFound
4209
4240
4210 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4241 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4211 if not res:
4242 if not res:
4212 raise HTTPNotFound()
4243 raise HTTPNotFound()
4213 return res
4244 return res
4214
4245
4215 @classmethod
4246 @classmethod
4216 def get_by_access_id(cls, gist_access_id):
4247 def get_by_access_id(cls, gist_access_id):
4217 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4248 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4218
4249
4219 def gist_url(self):
4250 def gist_url(self):
4220 from rhodecode.model.gist import GistModel
4251 from rhodecode.model.gist import GistModel
4221 return GistModel().get_url(self)
4252 return GistModel().get_url(self)
4222
4253
4223 @classmethod
4254 @classmethod
4224 def base_path(cls):
4255 def base_path(cls):
4225 """
4256 """
4226 Returns base path when all gists are stored
4257 Returns base path when all gists are stored
4227
4258
4228 :param cls:
4259 :param cls:
4229 """
4260 """
4230 from rhodecode.model.gist import GIST_STORE_LOC
4261 from rhodecode.model.gist import GIST_STORE_LOC
4231 q = Session().query(RhodeCodeUi)\
4262 q = Session().query(RhodeCodeUi)\
4232 .filter(RhodeCodeUi.ui_key == URL_SEP)
4263 .filter(RhodeCodeUi.ui_key == URL_SEP)
4233 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4264 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4234 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4265 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4235
4266
4236 def get_api_data(self):
4267 def get_api_data(self):
4237 """
4268 """
4238 Common function for generating gist related data for API
4269 Common function for generating gist related data for API
4239 """
4270 """
4240 gist = self
4271 gist = self
4241 data = {
4272 data = {
4242 'gist_id': gist.gist_id,
4273 'gist_id': gist.gist_id,
4243 'type': gist.gist_type,
4274 'type': gist.gist_type,
4244 'access_id': gist.gist_access_id,
4275 'access_id': gist.gist_access_id,
4245 'description': gist.gist_description,
4276 'description': gist.gist_description,
4246 'url': gist.gist_url(),
4277 'url': gist.gist_url(),
4247 'expires': gist.gist_expires,
4278 'expires': gist.gist_expires,
4248 'created_on': gist.created_on,
4279 'created_on': gist.created_on,
4249 'modified_at': gist.modified_at,
4280 'modified_at': gist.modified_at,
4250 'content': None,
4281 'content': None,
4251 'acl_level': gist.acl_level,
4282 'acl_level': gist.acl_level,
4252 }
4283 }
4253 return data
4284 return data
4254
4285
4255 def __json__(self):
4286 def __json__(self):
4256 data = dict(
4287 data = dict(
4257 )
4288 )
4258 data.update(self.get_api_data())
4289 data.update(self.get_api_data())
4259 return data
4290 return data
4260 # SCM functions
4291 # SCM functions
4261
4292
4262 def scm_instance(self, **kwargs):
4293 def scm_instance(self, **kwargs):
4263 """
4294 """
4264 Get explicit Mercurial repository used
4295 Get explicit Mercurial repository used
4265 :param kwargs:
4296 :param kwargs:
4266 :return:
4297 :return:
4267 """
4298 """
4268 from rhodecode.model.gist import GistModel
4299 from rhodecode.model.gist import GistModel
4269 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4300 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4270 return get_vcs_instance(
4301 return get_vcs_instance(
4271 repo_path=safe_str(full_repo_path), create=False,
4302 repo_path=safe_str(full_repo_path), create=False,
4272 _vcs_alias=GistModel.vcs_backend)
4303 _vcs_alias=GistModel.vcs_backend)
4273
4304
4274
4305
4275 class ExternalIdentity(Base, BaseModel):
4306 class ExternalIdentity(Base, BaseModel):
4276 __tablename__ = 'external_identities'
4307 __tablename__ = 'external_identities'
4277 __table_args__ = (
4308 __table_args__ = (
4278 Index('local_user_id_idx', 'local_user_id'),
4309 Index('local_user_id_idx', 'local_user_id'),
4279 Index('external_id_idx', 'external_id'),
4310 Index('external_id_idx', 'external_id'),
4280 base_table_args
4311 base_table_args
4281 )
4312 )
4282
4313
4283 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4314 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4284 external_username = Column('external_username', Unicode(1024), default=u'')
4315 external_username = Column('external_username', Unicode(1024), default=u'')
4285 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4316 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4286 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4317 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4287 access_token = Column('access_token', String(1024), default=u'')
4318 access_token = Column('access_token', String(1024), default=u'')
4288 alt_token = Column('alt_token', String(1024), default=u'')
4319 alt_token = Column('alt_token', String(1024), default=u'')
4289 token_secret = Column('token_secret', String(1024), default=u'')
4320 token_secret = Column('token_secret', String(1024), default=u'')
4290
4321
4291 @classmethod
4322 @classmethod
4292 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4323 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4293 """
4324 """
4294 Returns ExternalIdentity instance based on search params
4325 Returns ExternalIdentity instance based on search params
4295
4326
4296 :param external_id:
4327 :param external_id:
4297 :param provider_name:
4328 :param provider_name:
4298 :return: ExternalIdentity
4329 :return: ExternalIdentity
4299 """
4330 """
4300 query = cls.query()
4331 query = cls.query()
4301 query = query.filter(cls.external_id == external_id)
4332 query = query.filter(cls.external_id == external_id)
4302 query = query.filter(cls.provider_name == provider_name)
4333 query = query.filter(cls.provider_name == provider_name)
4303 if local_user_id:
4334 if local_user_id:
4304 query = query.filter(cls.local_user_id == local_user_id)
4335 query = query.filter(cls.local_user_id == local_user_id)
4305 return query.first()
4336 return query.first()
4306
4337
4307 @classmethod
4338 @classmethod
4308 def user_by_external_id_and_provider(cls, external_id, provider_name):
4339 def user_by_external_id_and_provider(cls, external_id, provider_name):
4309 """
4340 """
4310 Returns User instance based on search params
4341 Returns User instance based on search params
4311
4342
4312 :param external_id:
4343 :param external_id:
4313 :param provider_name:
4344 :param provider_name:
4314 :return: User
4345 :return: User
4315 """
4346 """
4316 query = User.query()
4347 query = User.query()
4317 query = query.filter(cls.external_id == external_id)
4348 query = query.filter(cls.external_id == external_id)
4318 query = query.filter(cls.provider_name == provider_name)
4349 query = query.filter(cls.provider_name == provider_name)
4319 query = query.filter(User.user_id == cls.local_user_id)
4350 query = query.filter(User.user_id == cls.local_user_id)
4320 return query.first()
4351 return query.first()
4321
4352
4322 @classmethod
4353 @classmethod
4323 def by_local_user_id(cls, local_user_id):
4354 def by_local_user_id(cls, local_user_id):
4324 """
4355 """
4325 Returns all tokens for user
4356 Returns all tokens for user
4326
4357
4327 :param local_user_id:
4358 :param local_user_id:
4328 :return: ExternalIdentity
4359 :return: ExternalIdentity
4329 """
4360 """
4330 query = cls.query()
4361 query = cls.query()
4331 query = query.filter(cls.local_user_id == local_user_id)
4362 query = query.filter(cls.local_user_id == local_user_id)
4332 return query
4363 return query
4333
4364
4334 @classmethod
4365 @classmethod
4335 def load_provider_plugin(cls, plugin_id):
4366 def load_provider_plugin(cls, plugin_id):
4336 from rhodecode.authentication.base import loadplugin
4367 from rhodecode.authentication.base import loadplugin
4337 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4368 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4338 auth_plugin = loadplugin(_plugin_id)
4369 auth_plugin = loadplugin(_plugin_id)
4339 return auth_plugin
4370 return auth_plugin
4340
4371
4341
4372
4342 class Integration(Base, BaseModel):
4373 class Integration(Base, BaseModel):
4343 __tablename__ = 'integrations'
4374 __tablename__ = 'integrations'
4344 __table_args__ = (
4375 __table_args__ = (
4345 base_table_args
4376 base_table_args
4346 )
4377 )
4347
4378
4348 integration_id = Column('integration_id', Integer(), primary_key=True)
4379 integration_id = Column('integration_id', Integer(), primary_key=True)
4349 integration_type = Column('integration_type', String(255))
4380 integration_type = Column('integration_type', String(255))
4350 enabled = Column('enabled', Boolean(), nullable=False)
4381 enabled = Column('enabled', Boolean(), nullable=False)
4351 name = Column('name', String(255), nullable=False)
4382 name = Column('name', String(255), nullable=False)
4352 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4383 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4353 default=False)
4384 default=False)
4354
4385
4355 settings = Column(
4386 settings = Column(
4356 'settings_json', MutationObj.as_mutable(
4387 'settings_json', MutationObj.as_mutable(
4357 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4388 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4358 repo_id = Column(
4389 repo_id = Column(
4359 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4390 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4360 nullable=True, unique=None, default=None)
4391 nullable=True, unique=None, default=None)
4361 repo = relationship('Repository', lazy='joined')
4392 repo = relationship('Repository', lazy='joined')
4362
4393
4363 repo_group_id = Column(
4394 repo_group_id = Column(
4364 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4395 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4365 nullable=True, unique=None, default=None)
4396 nullable=True, unique=None, default=None)
4366 repo_group = relationship('RepoGroup', lazy='joined')
4397 repo_group = relationship('RepoGroup', lazy='joined')
4367
4398
4368 @property
4399 @property
4369 def scope(self):
4400 def scope(self):
4370 if self.repo:
4401 if self.repo:
4371 return repr(self.repo)
4402 return repr(self.repo)
4372 if self.repo_group:
4403 if self.repo_group:
4373 if self.child_repos_only:
4404 if self.child_repos_only:
4374 return repr(self.repo_group) + ' (child repos only)'
4405 return repr(self.repo_group) + ' (child repos only)'
4375 else:
4406 else:
4376 return repr(self.repo_group) + ' (recursive)'
4407 return repr(self.repo_group) + ' (recursive)'
4377 if self.child_repos_only:
4408 if self.child_repos_only:
4378 return 'root_repos'
4409 return 'root_repos'
4379 return 'global'
4410 return 'global'
4380
4411
4381 def __repr__(self):
4412 def __repr__(self):
4382 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4413 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4383
4414
4384
4415
4385 class RepoReviewRuleUser(Base, BaseModel):
4416 class RepoReviewRuleUser(Base, BaseModel):
4386 __tablename__ = 'repo_review_rules_users'
4417 __tablename__ = 'repo_review_rules_users'
4387 __table_args__ = (
4418 __table_args__ = (
4388 base_table_args
4419 base_table_args
4389 )
4420 )
4390
4421
4391 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4422 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4392 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4423 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4393 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4424 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4394 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4425 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4395 user = relationship('User')
4426 user = relationship('User')
4396
4427
4397 def rule_data(self):
4428 def rule_data(self):
4398 return {
4429 return {
4399 'mandatory': self.mandatory
4430 'mandatory': self.mandatory
4400 }
4431 }
4401
4432
4402
4433
4403 class RepoReviewRuleUserGroup(Base, BaseModel):
4434 class RepoReviewRuleUserGroup(Base, BaseModel):
4404 __tablename__ = 'repo_review_rules_users_groups'
4435 __tablename__ = 'repo_review_rules_users_groups'
4405 __table_args__ = (
4436 __table_args__ = (
4406 base_table_args
4437 base_table_args
4407 )
4438 )
4408
4439
4409 VOTE_RULE_ALL = -1
4440 VOTE_RULE_ALL = -1
4410
4441
4411 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4442 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4412 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4443 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4413 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4444 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4414 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4445 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4415 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4446 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4416 users_group = relationship('UserGroup')
4447 users_group = relationship('UserGroup')
4417
4448
4418 def rule_data(self):
4449 def rule_data(self):
4419 return {
4450 return {
4420 'mandatory': self.mandatory,
4451 'mandatory': self.mandatory,
4421 'vote_rule': self.vote_rule
4452 'vote_rule': self.vote_rule
4422 }
4453 }
4423
4454
4424 @property
4455 @property
4425 def vote_rule_label(self):
4456 def vote_rule_label(self):
4426 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4457 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4427 return 'all must vote'
4458 return 'all must vote'
4428 else:
4459 else:
4429 return 'min. vote {}'.format(self.vote_rule)
4460 return 'min. vote {}'.format(self.vote_rule)
4430
4461
4431
4462
4432 class RepoReviewRule(Base, BaseModel):
4463 class RepoReviewRule(Base, BaseModel):
4433 __tablename__ = 'repo_review_rules'
4464 __tablename__ = 'repo_review_rules'
4434 __table_args__ = (
4465 __table_args__ = (
4435 base_table_args
4466 base_table_args
4436 )
4467 )
4437
4468
4438 repo_review_rule_id = Column(
4469 repo_review_rule_id = Column(
4439 'repo_review_rule_id', Integer(), primary_key=True)
4470 'repo_review_rule_id', Integer(), primary_key=True)
4440 repo_id = Column(
4471 repo_id = Column(
4441 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4472 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4442 repo = relationship('Repository', backref='review_rules')
4473 repo = relationship('Repository', backref='review_rules')
4443
4474
4444 review_rule_name = Column('review_rule_name', String(255))
4475 review_rule_name = Column('review_rule_name', String(255))
4445 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4476 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4446 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4477 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4447 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4478 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4448
4479
4449 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4480 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4450 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4481 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4451 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4482 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4452 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4483 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4453
4484
4454 rule_users = relationship('RepoReviewRuleUser')
4485 rule_users = relationship('RepoReviewRuleUser')
4455 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4486 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4456
4487
4457 def _validate_pattern(self, value):
4488 def _validate_pattern(self, value):
4458 re.compile('^' + glob2re(value) + '$')
4489 re.compile('^' + glob2re(value) + '$')
4459
4490
4460 @hybrid_property
4491 @hybrid_property
4461 def source_branch_pattern(self):
4492 def source_branch_pattern(self):
4462 return self._branch_pattern or '*'
4493 return self._branch_pattern or '*'
4463
4494
4464 @source_branch_pattern.setter
4495 @source_branch_pattern.setter
4465 def source_branch_pattern(self, value):
4496 def source_branch_pattern(self, value):
4466 self._validate_pattern(value)
4497 self._validate_pattern(value)
4467 self._branch_pattern = value or '*'
4498 self._branch_pattern = value or '*'
4468
4499
4469 @hybrid_property
4500 @hybrid_property
4470 def target_branch_pattern(self):
4501 def target_branch_pattern(self):
4471 return self._target_branch_pattern or '*'
4502 return self._target_branch_pattern or '*'
4472
4503
4473 @target_branch_pattern.setter
4504 @target_branch_pattern.setter
4474 def target_branch_pattern(self, value):
4505 def target_branch_pattern(self, value):
4475 self._validate_pattern(value)
4506 self._validate_pattern(value)
4476 self._target_branch_pattern = value or '*'
4507 self._target_branch_pattern = value or '*'
4477
4508
4478 @hybrid_property
4509 @hybrid_property
4479 def file_pattern(self):
4510 def file_pattern(self):
4480 return self._file_pattern or '*'
4511 return self._file_pattern or '*'
4481
4512
4482 @file_pattern.setter
4513 @file_pattern.setter
4483 def file_pattern(self, value):
4514 def file_pattern(self, value):
4484 self._validate_pattern(value)
4515 self._validate_pattern(value)
4485 self._file_pattern = value or '*'
4516 self._file_pattern = value or '*'
4486
4517
4487 def matches(self, source_branch, target_branch, files_changed):
4518 def matches(self, source_branch, target_branch, files_changed):
4488 """
4519 """
4489 Check if this review rule matches a branch/files in a pull request
4520 Check if this review rule matches a branch/files in a pull request
4490
4521
4491 :param source_branch: source branch name for the commit
4522 :param source_branch: source branch name for the commit
4492 :param target_branch: target branch name for the commit
4523 :param target_branch: target branch name for the commit
4493 :param files_changed: list of file paths changed in the pull request
4524 :param files_changed: list of file paths changed in the pull request
4494 """
4525 """
4495
4526
4496 source_branch = source_branch or ''
4527 source_branch = source_branch or ''
4497 target_branch = target_branch or ''
4528 target_branch = target_branch or ''
4498 files_changed = files_changed or []
4529 files_changed = files_changed or []
4499
4530
4500 branch_matches = True
4531 branch_matches = True
4501 if source_branch or target_branch:
4532 if source_branch or target_branch:
4502 if self.source_branch_pattern == '*':
4533 if self.source_branch_pattern == '*':
4503 source_branch_match = True
4534 source_branch_match = True
4504 else:
4535 else:
4505 if self.source_branch_pattern.startswith('re:'):
4536 if self.source_branch_pattern.startswith('re:'):
4506 source_pattern = self.source_branch_pattern[3:]
4537 source_pattern = self.source_branch_pattern[3:]
4507 else:
4538 else:
4508 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4539 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4509 source_branch_regex = re.compile(source_pattern)
4540 source_branch_regex = re.compile(source_pattern)
4510 source_branch_match = bool(source_branch_regex.search(source_branch))
4541 source_branch_match = bool(source_branch_regex.search(source_branch))
4511 if self.target_branch_pattern == '*':
4542 if self.target_branch_pattern == '*':
4512 target_branch_match = True
4543 target_branch_match = True
4513 else:
4544 else:
4514 if self.target_branch_pattern.startswith('re:'):
4545 if self.target_branch_pattern.startswith('re:'):
4515 target_pattern = self.target_branch_pattern[3:]
4546 target_pattern = self.target_branch_pattern[3:]
4516 else:
4547 else:
4517 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4548 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4518 target_branch_regex = re.compile(target_pattern)
4549 target_branch_regex = re.compile(target_pattern)
4519 target_branch_match = bool(target_branch_regex.search(target_branch))
4550 target_branch_match = bool(target_branch_regex.search(target_branch))
4520
4551
4521 branch_matches = source_branch_match and target_branch_match
4552 branch_matches = source_branch_match and target_branch_match
4522
4553
4523 files_matches = True
4554 files_matches = True
4524 if self.file_pattern != '*':
4555 if self.file_pattern != '*':
4525 files_matches = False
4556 files_matches = False
4526 if self.file_pattern.startswith('re:'):
4557 if self.file_pattern.startswith('re:'):
4527 file_pattern = self.file_pattern[3:]
4558 file_pattern = self.file_pattern[3:]
4528 else:
4559 else:
4529 file_pattern = glob2re(self.file_pattern)
4560 file_pattern = glob2re(self.file_pattern)
4530 file_regex = re.compile(file_pattern)
4561 file_regex = re.compile(file_pattern)
4531 for filename in files_changed:
4562 for filename in files_changed:
4532 if file_regex.search(filename):
4563 if file_regex.search(filename):
4533 files_matches = True
4564 files_matches = True
4534 break
4565 break
4535
4566
4536 return branch_matches and files_matches
4567 return branch_matches and files_matches
4537
4568
4538 @property
4569 @property
4539 def review_users(self):
4570 def review_users(self):
4540 """ Returns the users which this rule applies to """
4571 """ Returns the users which this rule applies to """
4541
4572
4542 users = collections.OrderedDict()
4573 users = collections.OrderedDict()
4543
4574
4544 for rule_user in self.rule_users:
4575 for rule_user in self.rule_users:
4545 if rule_user.user.active:
4576 if rule_user.user.active:
4546 if rule_user.user not in users:
4577 if rule_user.user not in users:
4547 users[rule_user.user.username] = {
4578 users[rule_user.user.username] = {
4548 'user': rule_user.user,
4579 'user': rule_user.user,
4549 'source': 'user',
4580 'source': 'user',
4550 'source_data': {},
4581 'source_data': {},
4551 'data': rule_user.rule_data()
4582 'data': rule_user.rule_data()
4552 }
4583 }
4553
4584
4554 for rule_user_group in self.rule_user_groups:
4585 for rule_user_group in self.rule_user_groups:
4555 source_data = {
4586 source_data = {
4556 'user_group_id': rule_user_group.users_group.users_group_id,
4587 'user_group_id': rule_user_group.users_group.users_group_id,
4557 'name': rule_user_group.users_group.users_group_name,
4588 'name': rule_user_group.users_group.users_group_name,
4558 'members': len(rule_user_group.users_group.members)
4589 'members': len(rule_user_group.users_group.members)
4559 }
4590 }
4560 for member in rule_user_group.users_group.members:
4591 for member in rule_user_group.users_group.members:
4561 if member.user.active:
4592 if member.user.active:
4562 key = member.user.username
4593 key = member.user.username
4563 if key in users:
4594 if key in users:
4564 # skip this member as we have him already
4595 # skip this member as we have him already
4565 # this prevents from override the "first" matched
4596 # this prevents from override the "first" matched
4566 # users with duplicates in multiple groups
4597 # users with duplicates in multiple groups
4567 continue
4598 continue
4568
4599
4569 users[key] = {
4600 users[key] = {
4570 'user': member.user,
4601 'user': member.user,
4571 'source': 'user_group',
4602 'source': 'user_group',
4572 'source_data': source_data,
4603 'source_data': source_data,
4573 'data': rule_user_group.rule_data()
4604 'data': rule_user_group.rule_data()
4574 }
4605 }
4575
4606
4576 return users
4607 return users
4577
4608
4578 def user_group_vote_rule(self, user_id):
4609 def user_group_vote_rule(self, user_id):
4579
4610
4580 rules = []
4611 rules = []
4581 if not self.rule_user_groups:
4612 if not self.rule_user_groups:
4582 return rules
4613 return rules
4583
4614
4584 for user_group in self.rule_user_groups:
4615 for user_group in self.rule_user_groups:
4585 user_group_members = [x.user_id for x in user_group.users_group.members]
4616 user_group_members = [x.user_id for x in user_group.users_group.members]
4586 if user_id in user_group_members:
4617 if user_id in user_group_members:
4587 rules.append(user_group)
4618 rules.append(user_group)
4588 return rules
4619 return rules
4589
4620
4590 def __repr__(self):
4621 def __repr__(self):
4591 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4622 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4592 self.repo_review_rule_id, self.repo)
4623 self.repo_review_rule_id, self.repo)
4593
4624
4594
4625
4595 class ScheduleEntry(Base, BaseModel):
4626 class ScheduleEntry(Base, BaseModel):
4596 __tablename__ = 'schedule_entries'
4627 __tablename__ = 'schedule_entries'
4597 __table_args__ = (
4628 __table_args__ = (
4598 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4629 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4599 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4630 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4600 base_table_args,
4631 base_table_args,
4601 )
4632 )
4602
4633
4603 schedule_types = ['crontab', 'timedelta', 'integer']
4634 schedule_types = ['crontab', 'timedelta', 'integer']
4604 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4635 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4605
4636
4606 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4637 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4607 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4638 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4608 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4639 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4609
4640
4610 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4641 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4611 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4642 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4612
4643
4613 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4644 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4614 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4645 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4615
4646
4616 # task
4647 # task
4617 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4648 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4618 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4649 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4619 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4650 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4620 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4651 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4621
4652
4622 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4653 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4623 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4654 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4624
4655
4625 @hybrid_property
4656 @hybrid_property
4626 def schedule_type(self):
4657 def schedule_type(self):
4627 return self._schedule_type
4658 return self._schedule_type
4628
4659
4629 @schedule_type.setter
4660 @schedule_type.setter
4630 def schedule_type(self, val):
4661 def schedule_type(self, val):
4631 if val not in self.schedule_types:
4662 if val not in self.schedule_types:
4632 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4663 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4633 val, self.schedule_type))
4664 val, self.schedule_type))
4634
4665
4635 self._schedule_type = val
4666 self._schedule_type = val
4636
4667
4637 @classmethod
4668 @classmethod
4638 def get_uid(cls, obj):
4669 def get_uid(cls, obj):
4639 args = obj.task_args
4670 args = obj.task_args
4640 kwargs = obj.task_kwargs
4671 kwargs = obj.task_kwargs
4641 if isinstance(args, JsonRaw):
4672 if isinstance(args, JsonRaw):
4642 try:
4673 try:
4643 args = json.loads(args)
4674 args = json.loads(args)
4644 except ValueError:
4675 except ValueError:
4645 args = tuple()
4676 args = tuple()
4646
4677
4647 if isinstance(kwargs, JsonRaw):
4678 if isinstance(kwargs, JsonRaw):
4648 try:
4679 try:
4649 kwargs = json.loads(kwargs)
4680 kwargs = json.loads(kwargs)
4650 except ValueError:
4681 except ValueError:
4651 kwargs = dict()
4682 kwargs = dict()
4652
4683
4653 dot_notation = obj.task_dot_notation
4684 dot_notation = obj.task_dot_notation
4654 val = '.'.join(map(safe_str, [
4685 val = '.'.join(map(safe_str, [
4655 sorted(dot_notation), args, sorted(kwargs.items())]))
4686 sorted(dot_notation), args, sorted(kwargs.items())]))
4656 return hashlib.sha1(val).hexdigest()
4687 return hashlib.sha1(val).hexdigest()
4657
4688
4658 @classmethod
4689 @classmethod
4659 def get_by_schedule_name(cls, schedule_name):
4690 def get_by_schedule_name(cls, schedule_name):
4660 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4691 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4661
4692
4662 @classmethod
4693 @classmethod
4663 def get_by_schedule_id(cls, schedule_id):
4694 def get_by_schedule_id(cls, schedule_id):
4664 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4695 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4665
4696
4666 @property
4697 @property
4667 def task(self):
4698 def task(self):
4668 return self.task_dot_notation
4699 return self.task_dot_notation
4669
4700
4670 @property
4701 @property
4671 def schedule(self):
4702 def schedule(self):
4672 from rhodecode.lib.celerylib.utils import raw_2_schedule
4703 from rhodecode.lib.celerylib.utils import raw_2_schedule
4673 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4704 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4674 return schedule
4705 return schedule
4675
4706
4676 @property
4707 @property
4677 def args(self):
4708 def args(self):
4678 try:
4709 try:
4679 return list(self.task_args or [])
4710 return list(self.task_args or [])
4680 except ValueError:
4711 except ValueError:
4681 return list()
4712 return list()
4682
4713
4683 @property
4714 @property
4684 def kwargs(self):
4715 def kwargs(self):
4685 try:
4716 try:
4686 return dict(self.task_kwargs or {})
4717 return dict(self.task_kwargs or {})
4687 except ValueError:
4718 except ValueError:
4688 return dict()
4719 return dict()
4689
4720
4690 def _as_raw(self, val):
4721 def _as_raw(self, val):
4691 if hasattr(val, 'de_coerce'):
4722 if hasattr(val, 'de_coerce'):
4692 val = val.de_coerce()
4723 val = val.de_coerce()
4693 if val:
4724 if val:
4694 val = json.dumps(val)
4725 val = json.dumps(val)
4695
4726
4696 return val
4727 return val
4697
4728
4698 @property
4729 @property
4699 def schedule_definition_raw(self):
4730 def schedule_definition_raw(self):
4700 return self._as_raw(self.schedule_definition)
4731 return self._as_raw(self.schedule_definition)
4701
4732
4702 @property
4733 @property
4703 def args_raw(self):
4734 def args_raw(self):
4704 return self._as_raw(self.task_args)
4735 return self._as_raw(self.task_args)
4705
4736
4706 @property
4737 @property
4707 def kwargs_raw(self):
4738 def kwargs_raw(self):
4708 return self._as_raw(self.task_kwargs)
4739 return self._as_raw(self.task_kwargs)
4709
4740
4710 def __repr__(self):
4741 def __repr__(self):
4711 return '<DB:ScheduleEntry({}:{})>'.format(
4742 return '<DB:ScheduleEntry({}:{})>'.format(
4712 self.schedule_entry_id, self.schedule_name)
4743 self.schedule_entry_id, self.schedule_name)
4713
4744
4714
4745
4715 @event.listens_for(ScheduleEntry, 'before_update')
4746 @event.listens_for(ScheduleEntry, 'before_update')
4716 def update_task_uid(mapper, connection, target):
4747 def update_task_uid(mapper, connection, target):
4717 target.task_uid = ScheduleEntry.get_uid(target)
4748 target.task_uid = ScheduleEntry.get_uid(target)
4718
4749
4719
4750
4720 @event.listens_for(ScheduleEntry, 'before_insert')
4751 @event.listens_for(ScheduleEntry, 'before_insert')
4721 def set_task_uid(mapper, connection, target):
4752 def set_task_uid(mapper, connection, target):
4722 target.task_uid = ScheduleEntry.get_uid(target)
4753 target.task_uid = ScheduleEntry.get_uid(target)
4723
4754
4724
4755
4725 class _BaseBranchPerms(BaseModel):
4756 class _BaseBranchPerms(BaseModel):
4726 @classmethod
4757 @classmethod
4727 def compute_hash(cls, value):
4758 def compute_hash(cls, value):
4728 return sha1_safe(value)
4759 return sha1_safe(value)
4729
4760
4730 @hybrid_property
4761 @hybrid_property
4731 def branch_pattern(self):
4762 def branch_pattern(self):
4732 return self._branch_pattern or '*'
4763 return self._branch_pattern or '*'
4733
4764
4734 @hybrid_property
4765 @hybrid_property
4735 def branch_hash(self):
4766 def branch_hash(self):
4736 return self._branch_hash
4767 return self._branch_hash
4737
4768
4738 def _validate_glob(self, value):
4769 def _validate_glob(self, value):
4739 re.compile('^' + glob2re(value) + '$')
4770 re.compile('^' + glob2re(value) + '$')
4740
4771
4741 @branch_pattern.setter
4772 @branch_pattern.setter
4742 def branch_pattern(self, value):
4773 def branch_pattern(self, value):
4743 self._validate_glob(value)
4774 self._validate_glob(value)
4744 self._branch_pattern = value or '*'
4775 self._branch_pattern = value or '*'
4745 # set the Hash when setting the branch pattern
4776 # set the Hash when setting the branch pattern
4746 self._branch_hash = self.compute_hash(self._branch_pattern)
4777 self._branch_hash = self.compute_hash(self._branch_pattern)
4747
4778
4748 def matches(self, branch):
4779 def matches(self, branch):
4749 """
4780 """
4750 Check if this the branch matches entry
4781 Check if this the branch matches entry
4751
4782
4752 :param branch: branch name for the commit
4783 :param branch: branch name for the commit
4753 """
4784 """
4754
4785
4755 branch = branch or ''
4786 branch = branch or ''
4756
4787
4757 branch_matches = True
4788 branch_matches = True
4758 if branch:
4789 if branch:
4759 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4790 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4760 branch_matches = bool(branch_regex.search(branch))
4791 branch_matches = bool(branch_regex.search(branch))
4761
4792
4762 return branch_matches
4793 return branch_matches
4763
4794
4764
4795
4765 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4796 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4766 __tablename__ = 'user_to_repo_branch_permissions'
4797 __tablename__ = 'user_to_repo_branch_permissions'
4767 __table_args__ = (
4798 __table_args__ = (
4768 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4799 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4769 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4800 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4770 )
4801 )
4771
4802
4772 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4803 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4773
4804
4774 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4805 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4775 repo = relationship('Repository', backref='user_branch_perms')
4806 repo = relationship('Repository', backref='user_branch_perms')
4776
4807
4777 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4808 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4778 permission = relationship('Permission')
4809 permission = relationship('Permission')
4779
4810
4780 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4811 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4781 user_repo_to_perm = relationship('UserRepoToPerm')
4812 user_repo_to_perm = relationship('UserRepoToPerm')
4782
4813
4783 rule_order = Column('rule_order', Integer(), nullable=False)
4814 rule_order = Column('rule_order', Integer(), nullable=False)
4784 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4815 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4785 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4816 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4786
4817
4787 def __unicode__(self):
4818 def __unicode__(self):
4788 return u'<UserBranchPermission(%s => %r)>' % (
4819 return u'<UserBranchPermission(%s => %r)>' % (
4789 self.user_repo_to_perm, self.branch_pattern)
4820 self.user_repo_to_perm, self.branch_pattern)
4790
4821
4791
4822
4792 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4823 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4793 __tablename__ = 'user_group_to_repo_branch_permissions'
4824 __tablename__ = 'user_group_to_repo_branch_permissions'
4794 __table_args__ = (
4825 __table_args__ = (
4795 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4826 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4796 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4827 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4797 )
4828 )
4798
4829
4799 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4830 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4800
4831
4801 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4832 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4802 repo = relationship('Repository', backref='user_group_branch_perms')
4833 repo = relationship('Repository', backref='user_group_branch_perms')
4803
4834
4804 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4835 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4805 permission = relationship('Permission')
4836 permission = relationship('Permission')
4806
4837
4807 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
4838 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
4808 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4839 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4809
4840
4810 rule_order = Column('rule_order', Integer(), nullable=False)
4841 rule_order = Column('rule_order', Integer(), nullable=False)
4811 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4842 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4812 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4843 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4813
4844
4814 def __unicode__(self):
4845 def __unicode__(self):
4815 return u'<UserBranchPermission(%s => %r)>' % (
4846 return u'<UserBranchPermission(%s => %r)>' % (
4816 self.user_group_repo_to_perm, self.branch_pattern)
4847 self.user_group_repo_to_perm, self.branch_pattern)
4817
4848
4818
4849
4819 class UserBookmark(Base, BaseModel):
4850 class UserBookmark(Base, BaseModel):
4820 __tablename__ = 'user_bookmarks'
4851 __tablename__ = 'user_bookmarks'
4821 __table_args__ = (
4852 __table_args__ = (
4822 UniqueConstraint('user_id', 'bookmark_repo_id'),
4853 UniqueConstraint('user_id', 'bookmark_repo_id'),
4823 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
4854 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
4824 UniqueConstraint('user_id', 'bookmark_position'),
4855 UniqueConstraint('user_id', 'bookmark_position'),
4825 base_table_args
4856 base_table_args
4826 )
4857 )
4827
4858
4828 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
4859 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
4829 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
4860 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
4830 position = Column("bookmark_position", Integer(), nullable=False)
4861 position = Column("bookmark_position", Integer(), nullable=False)
4831 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
4862 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
4832 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
4863 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
4833 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4864 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4834
4865
4835 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
4866 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
4836 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
4867 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
4837
4868
4838 user = relationship("User")
4869 user = relationship("User")
4839
4870
4840 repository = relationship("Repository")
4871 repository = relationship("Repository")
4841 repository_group = relationship("RepoGroup")
4872 repository_group = relationship("RepoGroup")
4842
4873
4843 @classmethod
4874 @classmethod
4844 def get_by_position_for_user(cls, position, user_id):
4875 def get_by_position_for_user(cls, position, user_id):
4845 return cls.query() \
4876 return cls.query() \
4846 .filter(UserBookmark.user_id == user_id) \
4877 .filter(UserBookmark.user_id == user_id) \
4847 .filter(UserBookmark.position == position).scalar()
4878 .filter(UserBookmark.position == position).scalar()
4848
4879
4849 @classmethod
4880 @classmethod
4850 def get_bookmarks_for_user(cls, user_id):
4881 def get_bookmarks_for_user(cls, user_id):
4851 return cls.query() \
4882 return cls.query() \
4852 .filter(UserBookmark.user_id == user_id) \
4883 .filter(UserBookmark.user_id == user_id) \
4853 .options(joinedload(UserBookmark.repository)) \
4884 .options(joinedload(UserBookmark.repository)) \
4854 .options(joinedload(UserBookmark.repository_group)) \
4885 .options(joinedload(UserBookmark.repository_group)) \
4855 .order_by(UserBookmark.position.asc()) \
4886 .order_by(UserBookmark.position.asc()) \
4856 .all()
4887 .all()
4857
4888
4858 def __unicode__(self):
4889 def __unicode__(self):
4859 return u'<UserBookmark(%d @ %r)>' % (self.position, self.redirect_url)
4890 return u'<UserBookmark(%d @ %r)>' % (self.position, self.redirect_url)
4860
4891
4861
4892
4862 class FileStore(Base, BaseModel):
4893 class FileStore(Base, BaseModel):
4863 __tablename__ = 'file_store'
4894 __tablename__ = 'file_store'
4864 __table_args__ = (
4895 __table_args__ = (
4865 base_table_args
4896 base_table_args
4866 )
4897 )
4867
4898
4868 file_store_id = Column('file_store_id', Integer(), primary_key=True)
4899 file_store_id = Column('file_store_id', Integer(), primary_key=True)
4869 file_uid = Column('file_uid', String(1024), nullable=False)
4900 file_uid = Column('file_uid', String(1024), nullable=False)
4870 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
4901 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
4871 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
4902 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
4872 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
4903 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
4873
4904
4874 # sha256 hash
4905 # sha256 hash
4875 file_hash = Column('file_hash', String(512), nullable=False)
4906 file_hash = Column('file_hash', String(512), nullable=False)
4876 file_size = Column('file_size', Integer(), nullable=False)
4907 file_size = Column('file_size', Integer(), nullable=False)
4877
4908
4878 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4909 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4879 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
4910 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
4880 accessed_count = Column('accessed_count', Integer(), default=0)
4911 accessed_count = Column('accessed_count', Integer(), default=0)
4881
4912
4882 enabled = Column('enabled', Boolean(), nullable=False, default=True)
4913 enabled = Column('enabled', Boolean(), nullable=False, default=True)
4883
4914
4884 # if repo/repo_group reference is set, check for permissions
4915 # if repo/repo_group reference is set, check for permissions
4885 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
4916 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
4886
4917
4887 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
4918 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
4888 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
4919 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
4889
4920
4890 # scope limited to user, which requester have access to
4921 # scope limited to user, which requester have access to
4891 scope_user_id = Column(
4922 scope_user_id = Column(
4892 'scope_user_id', Integer(), ForeignKey('users.user_id'),
4923 'scope_user_id', Integer(), ForeignKey('users.user_id'),
4893 nullable=True, unique=None, default=None)
4924 nullable=True, unique=None, default=None)
4894 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
4925 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
4895
4926
4896 # scope limited to user group, which requester have access to
4927 # scope limited to user group, which requester have access to
4897 scope_user_group_id = Column(
4928 scope_user_group_id = Column(
4898 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
4929 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
4899 nullable=True, unique=None, default=None)
4930 nullable=True, unique=None, default=None)
4900 user_group = relationship('UserGroup', lazy='joined')
4931 user_group = relationship('UserGroup', lazy='joined')
4901
4932
4902 # scope limited to repo, which requester have access to
4933 # scope limited to repo, which requester have access to
4903 scope_repo_id = Column(
4934 scope_repo_id = Column(
4904 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4935 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4905 nullable=True, unique=None, default=None)
4936 nullable=True, unique=None, default=None)
4906 repo = relationship('Repository', lazy='joined')
4937 repo = relationship('Repository', lazy='joined')
4907
4938
4908 # scope limited to repo group, which requester have access to
4939 # scope limited to repo group, which requester have access to
4909 scope_repo_group_id = Column(
4940 scope_repo_group_id = Column(
4910 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
4941 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
4911 nullable=True, unique=None, default=None)
4942 nullable=True, unique=None, default=None)
4912 repo_group = relationship('RepoGroup', lazy='joined')
4943 repo_group = relationship('RepoGroup', lazy='joined')
4913
4944
4914 @classmethod
4945 @classmethod
4915 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
4946 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
4916 file_description='', enabled=True, check_acl=True,
4947 file_description='', enabled=True, check_acl=True,
4917 user_id=None, scope_repo_id=None, scope_repo_group_id=None):
4948 user_id=None, scope_repo_id=None, scope_repo_group_id=None):
4918
4949
4919 store_entry = FileStore()
4950 store_entry = FileStore()
4920 store_entry.file_uid = file_uid
4951 store_entry.file_uid = file_uid
4921 store_entry.file_display_name = file_display_name
4952 store_entry.file_display_name = file_display_name
4922 store_entry.file_org_name = filename
4953 store_entry.file_org_name = filename
4923 store_entry.file_size = file_size
4954 store_entry.file_size = file_size
4924 store_entry.file_hash = file_hash
4955 store_entry.file_hash = file_hash
4925 store_entry.file_description = file_description
4956 store_entry.file_description = file_description
4926
4957
4927 store_entry.check_acl = check_acl
4958 store_entry.check_acl = check_acl
4928 store_entry.enabled = enabled
4959 store_entry.enabled = enabled
4929
4960
4930 store_entry.user_id = user_id
4961 store_entry.user_id = user_id
4931 store_entry.scope_repo_id = scope_repo_id
4962 store_entry.scope_repo_id = scope_repo_id
4932 store_entry.scope_repo_group_id = scope_repo_group_id
4963 store_entry.scope_repo_group_id = scope_repo_group_id
4933 return store_entry
4964 return store_entry
4934
4965
4935 @classmethod
4966 @classmethod
4936 def bump_access_counter(cls, file_uid, commit=True):
4967 def bump_access_counter(cls, file_uid, commit=True):
4937 FileStore().query()\
4968 FileStore().query()\
4938 .filter(FileStore.file_uid == file_uid)\
4969 .filter(FileStore.file_uid == file_uid)\
4939 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
4970 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
4940 FileStore.accessed_on: datetime.datetime.now()})
4971 FileStore.accessed_on: datetime.datetime.now()})
4941 if commit:
4972 if commit:
4942 Session().commit()
4973 Session().commit()
4943
4974
4944 def __repr__(self):
4975 def __repr__(self):
4945 return '<FileStore({})>'.format(self.file_store_id)
4976 return '<FileStore({})>'.format(self.file_store_id)
4946
4977
4947
4978
4948 class DbMigrateVersion(Base, BaseModel):
4979 class DbMigrateVersion(Base, BaseModel):
4949 __tablename__ = 'db_migrate_version'
4980 __tablename__ = 'db_migrate_version'
4950 __table_args__ = (
4981 __table_args__ = (
4951 base_table_args,
4982 base_table_args,
4952 )
4983 )
4953
4984
4954 repository_id = Column('repository_id', String(250), primary_key=True)
4985 repository_id = Column('repository_id', String(250), primary_key=True)
4955 repository_path = Column('repository_path', Text)
4986 repository_path = Column('repository_path', Text)
4956 version = Column('version', Integer)
4987 version = Column('version', Integer)
4957
4988
4958 @classmethod
4989 @classmethod
4959 def set_version(cls, version):
4990 def set_version(cls, version):
4960 """
4991 """
4961 Helper for forcing a different version, usually for debugging purposes via ishell.
4992 Helper for forcing a different version, usually for debugging purposes via ishell.
4962 """
4993 """
4963 ver = DbMigrateVersion.query().first()
4994 ver = DbMigrateVersion.query().first()
4964 ver.version = version
4995 ver.version = version
4965 Session().commit()
4996 Session().commit()
4966
4997
4967
4998
4968 class DbSession(Base, BaseModel):
4999 class DbSession(Base, BaseModel):
4969 __tablename__ = 'db_session'
5000 __tablename__ = 'db_session'
4970 __table_args__ = (
5001 __table_args__ = (
4971 base_table_args,
5002 base_table_args,
4972 )
5003 )
4973
5004
4974 def __repr__(self):
5005 def __repr__(self):
4975 return '<DB:DbSession({})>'.format(self.id)
5006 return '<DB:DbSession({})>'.format(self.id)
4976
5007
4977 id = Column('id', Integer())
5008 id = Column('id', Integer())
4978 namespace = Column('namespace', String(255), primary_key=True)
5009 namespace = Column('namespace', String(255), primary_key=True)
4979 accessed = Column('accessed', DateTime, nullable=False)
5010 accessed = Column('accessed', DateTime, nullable=False)
4980 created = Column('created', DateTime, nullable=False)
5011 created = Column('created', DateTime, nullable=False)
4981 data = Column('data', PickleType, nullable=False)
5012 data = Column('data', PickleType, nullable=False)
@@ -1,97 +1,112 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Repository groups administration')}
5 ${_('Repository groups administration')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_group_count">0</span> ${_('repository groups')}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_group_count">0</span>
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_nav()">
16 <%def name="menu_bar_nav()">
17 ${self.menu_items(active='admin')}
17 ${self.menu_items(active='admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box">
21 <div class="box">
22 <div class="title">
22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 <ul class="links">
24 <ul class="links">
25 %if h.HasPermissionAny('hg.admin','hg.repogroup.create.true')():
25 %if h.HasPermissionAny('hg.admin','hg.repogroup.create.true')():
26 <li>
26 <li>
27 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-success">${_(u'Add Repository Group')}</a>
27 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-success">${_(u'Add Repository Group')}</a>
28 </li>
28 </li>
29 %endif
29 %endif
30 </ul>
30 </ul>
31 </div>
31 </div>
32 <div id="repos_list_wrap">
32 <div id="repos_list_wrap">
33 <table id="group_list_table" class="display"></table>
33 <table id="group_list_table" class="display"></table>
34 </div>
34 </div>
35 </div>
35 </div>
36
36
37 <script>
37 <script>
38 $(document).ready(function() {
38 $(document).ready(function() {
39
39 var $repoGroupsListTable = $('#group_list_table');
40 var get_datatable_count = function(){
41 var api = $('#group_list_table').dataTable().api();
42 $('#repo_group_count').text(api.page.info().recordsDisplay);
43 };
44
40
45 // repo group list
41 // repo group list
46 $('#group_list_table').DataTable({
42 $repoGroupsListTable.DataTable({
47 data: ${c.data|n},
43 processing: true,
44 serverSide: true,
45 ajax: {
46 "url": "${h.route_path('repo_groups_data')}",
47 "dataSrc": function (json) {
48 var filteredCount = json.recordsFiltered;
49 var filteredInactiveCount = json.recordsFilteredInactive;
50 var totalInactive = json.recordsTotalInactive;
51 var total = json.recordsTotal;
52
53 var _text = _gettext(
54 "{0} of {1} repository groups").format(
55 filteredCount, total);
56
57 if (total === filteredCount) {
58 _text = _gettext("{0} repository groups").format(total);
59 }
60 $('#repo_group_count').text(_text);
61 return json.data;
62 },
63 },
64
48 dom: 'rtp',
65 dom: 'rtp',
49 pageLength: ${c.visual.admin_grid_items},
66 pageLength: ${c.visual.admin_grid_items},
50 order: [[ 0, "asc" ]],
67 order: [[ 0, "asc" ]],
51 columns: [
68 columns: [
52 { data: {"_": "name",
69 { data: {"_": "name",
53 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
70 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
54 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
71 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
55 { data: {"_": "desc",
72 { data: {"_": "desc",
56 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
73 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
57 { data: {"_": "last_change",
74 { data: {"_": "last_change",
58 "sort": "last_change_raw",
75 "sort": "last_change_raw",
59 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
76 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
60 { data: {"_": "top_level_repos",
77 { data: {"_": "top_level_repos",
61 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
78 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
62 { data: {"_": "owner",
79 { data: {"_": "owner",
63 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
80 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
64 { data: {"_": "action",
81 { data: {"_": "action",
65 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
82 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
66 ],
83 ],
67 language: {
84 language: {
68 paginate: DEFAULT_GRID_PAGINATION,
85 paginate: DEFAULT_GRID_PAGINATION,
86 sProcessing: _gettext('loading...'),
69 emptyTable: _gettext("No repository groups available yet.")
87 emptyTable: _gettext("No repository groups available yet.")
70 },
88 },
71 "initComplete": function( settings, json ) {
72 get_datatable_count();
73 quick_repo_menu();
74 }
75 });
89 });
76
90
77 // update the counter when doing search
91 $repoGroupsListTable.on('xhr.dt', function(e, settings, json, xhr){
78 $('#group_list_table').on( 'search.dt', function (e,settings) {
92 $repoGroupsListTable.css('opacity', 1);
79 get_datatable_count();
93 });
94
95 $repoGroupsListTable.on('preXhr.dt', function(e, settings, data){
96 $repoGroupsListTable.css('opacity', 0.3);
80 });
97 });
81
98
82 // filter, filter both grids
99 // filter
83 $('#q_filter').on( 'keyup', function () {
100 $('#q_filter').on('keyup',
101 $.debounce(250, function() {
102 $repoGroupsListTable.DataTable().search(
103 $('#q_filter').val()
104 ).draw();
105 })
106 );
107 });
84
108
85 var repo_group_api = $('#group_list_table').dataTable().api();
109 </script>
86 repo_group_api
87 .columns(0)
88 .search(this.value)
89 .draw();
90 });
91
110
92 // refilter table if page load via back button
93 $("#q_filter").trigger('keyup');
94 });
95 </script>
96 </%def>
111 </%def>
97
112
@@ -1,101 +1,101 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Repositories administration')}
5 ${_('Repositories administration')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_count">0</span> ${_('repositories')}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_count">0</span> ${_('repositories')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_nav()">
16 <%def name="menu_bar_nav()">
17 ${self.menu_items(active='admin')}
17 ${self.menu_items(active='admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box">
21 <div class="box">
22 <div class="title">
22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 <ul class="links">
24 <ul class="links">
25 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
25 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
26 <li>
26 <li>
27 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success">${_(u'Add Repository')}</a>
27 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success">${_(u'Add Repository')}</a>
28 </li>
28 </li>
29 %endif
29 %endif
30 </ul>
30 </ul>
31 </div>
31 </div>
32 <div id="repos_list_wrap">
32 <div id="repos_list_wrap">
33 <table id="repo_list_table" class="display"></table>
33 <table id="repo_list_table" class="display"></table>
34 </div>
34 </div>
35 </div>
35 </div>
36
36
37 <script>
37 <script>
38 $(document).ready(function() {
38 $(document).ready(function() {
39
39
40 var get_datatable_count = function(){
40 var get_datatable_count = function(){
41 var api = $('#repo_list_table').dataTable().api();
41 var api = $('#repo_list_table').dataTable().api();
42 $('#repo_count').text(api.page.info().recordsDisplay);
42 $('#repo_count').text(api.page.info().recordsDisplay);
43 };
43 };
44
44
45
45
46 // repo list
46 // repo list
47 $('#repo_list_table').DataTable({
47 $('#repo_list_table').DataTable({
48 data: ${c.data|n},
48 data: ${c.data|n},
49 dom: 'rtp',
49 dom: 'rtp',
50 pageLength: ${c.visual.admin_grid_items},
50 pageLength: ${c.visual.admin_grid_items},
51 order: [[ 0, "asc" ]],
51 order: [[ 0, "asc" ]],
52 columns: [
52 columns: [
53 { data: {"_": "name",
53 { data: {"_": "name",
54 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
54 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
55 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
55 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
56 { data: {"_": "desc",
56 { data: {"_": "desc",
57 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
57 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
58 { data: {"_": "last_change",
58 { data: {"_": "last_change",
59 "sort": "last_change_raw",
59 "sort": "last_change_raw",
60 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
60 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
61 { data: {"_": "last_changeset",
61 { data: {"_": "last_changeset",
62 "sort": "last_changeset_raw",
62 "sort": "last_changeset_raw",
63 "type": Number}, title: "${_('Commit')}", className: "td-commit" },
63 "type": Number}, title: "${_('Commit')}", className: "td-commit" },
64 { data: {"_": "owner",
64 { data: {"_": "owner",
65 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
65 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
66 { data: {"_": "state",
66 { data: {"_": "state",
67 "sort": "state"}, title: "${_('State')}", className: "td-tags td-state" },
67 "sort": "state"}, title: "${_('State')}", className: "td-tags td-state" },
68 { data: {"_": "action",
68 { data: {"_": "action",
69 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
69 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
70 ],
70 ],
71 language: {
71 language: {
72 paginate: DEFAULT_GRID_PAGINATION,
72 paginate: DEFAULT_GRID_PAGINATION,
73 emptyTable:_gettext("No repositories available yet.")
73 emptyTable:_gettext("No repositories available yet.")
74 },
74 },
75 "initComplete": function( settings, json ) {
75 "initComplete": function( settings, json ) {
76 get_datatable_count();
76 get_datatable_count();
77 quick_repo_menu();
77 quick_repo_menu();
78 }
78 }
79 });
79 });
80
80
81 // update the counter when doing search
81 // update the counter when doing search
82 $('#repo_list_table').on( 'search.dt', function (e,settings) {
82 $('#repo_list_table').on( 'search.dt', function (e,settings) {
83 get_datatable_count();
83 get_datatable_count();
84 });
84 });
85
85
86 // filter, filter both grids
86 // filter, filter both grids
87 $('#q_filter').on( 'keyup', function () {
87 $('#q_filter').on( 'keyup', function () {
88 var repo_api = $('#repo_list_table').dataTable().api();
88 var repo_api = $('#repo_list_table').dataTable().api();
89 repo_api
89 repo_api
90 .columns(0)
90 .columns(0)
91 .search(this.value)
91 .search(this.value)
92 .draw();
92 .draw();
93 });
93 });
94
94
95 // refilter table if page load via back button
95 // refilter table if page load via back button
96 $("#q_filter").trigger('keyup');
96 $("#q_filter").trigger('keyup');
97 });
97 });
98
98
99 </script>
99 </script>
100
100
101 </%def>
101 </%def>
@@ -1,152 +1,157 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.tests import TestController
23 from rhodecode.tests import TestController
24 from rhodecode.tests.fixture import Fixture
24 from rhodecode.tests.fixture import Fixture
25
25
26
26
27 def route_path(name, params=None, **kwargs):
27 def route_path(name, params=None, **kwargs):
28 import urllib
28 import urllib
29 from rhodecode.apps._base import ADMIN_PREFIX
29 from rhodecode.apps._base import ADMIN_PREFIX
30
30
31 base_url = {
31 base_url = {
32 'home': '/',
32 'home': '/',
33 'repos':
33 'repos':
34 ADMIN_PREFIX + '/repos',
34 ADMIN_PREFIX + '/repos',
35 'repo_groups':
35 'repo_groups':
36 ADMIN_PREFIX + '/repo_groups',
36 ADMIN_PREFIX + '/repo_groups',
37 'repo_groups_data':
38 ADMIN_PREFIX + '/repo_groups_data',
37 'user_groups':
39 'user_groups':
38 ADMIN_PREFIX + '/user_groups',
40 ADMIN_PREFIX + '/user_groups',
39 'user_groups_data':
41 'user_groups_data':
40 ADMIN_PREFIX + '/user_groups_data',
42 ADMIN_PREFIX + '/user_groups_data',
41 }[name].format(**kwargs)
43 }[name].format(**kwargs)
42
44
43 if params:
45 if params:
44 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
46 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
45 return base_url
47 return base_url
46
48
47
49
48 fixture = Fixture()
50 fixture = Fixture()
49
51
50
52
51 class TestAdminDelegatedUser(TestController):
53 class TestAdminDelegatedUser(TestController):
52
54
53 def test_regular_user_cannot_see_admin_interfaces(
55 def test_regular_user_cannot_see_admin_interfaces(
54 self, user_util, xhr_header):
56 self, user_util, xhr_header):
55 user = user_util.create_user(password='qweqwe')
57 user = user_util.create_user(password='qweqwe')
56 self.log_user(user.username, 'qweqwe')
58 self.log_user(user.username, 'qweqwe')
57
59
58 # check if in home view, such user doesn't see the "admin" menus
60 # check if in home view, such user doesn't see the "admin" menus
59 response = self.app.get(route_path('home'))
61 response = self.app.get(route_path('home'))
60
62
61 assert_response = response.assert_response()
63 assert_response = response.assert_response()
62
64
63 assert_response.no_element_exists('li.local-admin-repos')
65 assert_response.no_element_exists('li.local-admin-repos')
64 assert_response.no_element_exists('li.local-admin-repo-groups')
66 assert_response.no_element_exists('li.local-admin-repo-groups')
65 assert_response.no_element_exists('li.local-admin-user-groups')
67 assert_response.no_element_exists('li.local-admin-user-groups')
66
68
67 response = self.app.get(route_path('repos'), status=200)
69 response = self.app.get(route_path('repos'), status=200)
68 response.mustcontain('data: []')
70 response.mustcontain('data: []')
69
71
70 response = self.app.get(route_path('repo_groups'), status=200)
72 response = self.app.get(route_path('repo_groups_data'),
71 response.mustcontain('data: []')
73 status=200, extra_environ=xhr_header)
74 assert response.json['data'] == []
72
75
73 response = self.app.get(route_path('user_groups_data'),
76 response = self.app.get(route_path('user_groups_data'),
74 status=200, extra_environ=xhr_header)
77 status=200, extra_environ=xhr_header)
75 assert response.json['data'] == []
78 assert response.json['data'] == []
76
79
77 def test_regular_user_can_see_admin_interfaces_if_owner(
80 def test_regular_user_can_see_admin_interfaces_if_owner(
78 self, user_util, xhr_header):
81 self, user_util, xhr_header):
79 user = user_util.create_user(password='qweqwe')
82 user = user_util.create_user(password='qweqwe')
80 username = user.username
83 username = user.username
81
84
82 repo = user_util.create_repo(owner=username)
85 repo = user_util.create_repo(owner=username)
83 repo_name = repo.repo_name
86 repo_name = repo.repo_name
84
87
85 repo_group = user_util.create_repo_group(owner=username)
88 repo_group = user_util.create_repo_group(owner=username)
86 repo_group_name = repo_group.group_name
89 repo_group_name = repo_group.group_name
87
90
88 user_group = user_util.create_user_group(owner=username)
91 user_group = user_util.create_user_group(owner=username)
89 user_group_name = user_group.users_group_name
92 user_group_name = user_group.users_group_name
90
93
91 self.log_user(username, 'qweqwe')
94 self.log_user(username, 'qweqwe')
92 # check if in home view, such user doesn't see the "admin" menus
95 # check if in home view, such user doesn't see the "admin" menus
93 response = self.app.get(route_path('home'))
96 response = self.app.get(route_path('home'))
94
97
95 assert_response = response.assert_response()
98 assert_response = response.assert_response()
96
99
97 assert_response.one_element_exists('li.local-admin-repos')
100 assert_response.one_element_exists('li.local-admin-repos')
98 assert_response.one_element_exists('li.local-admin-repo-groups')
101 assert_response.one_element_exists('li.local-admin-repo-groups')
99 assert_response.one_element_exists('li.local-admin-user-groups')
102 assert_response.one_element_exists('li.local-admin-user-groups')
100
103
101 # admin interfaces have visible elements
104 # admin interfaces have visible elements
102 response = self.app.get(route_path('repos'), status=200)
105 response = self.app.get(route_path('repos'), status=200)
103 response.mustcontain('"name_raw": "{}"'.format(repo_name))
106 response.mustcontain('"name_raw": "{}"'.format(repo_name))
104
107
105 response = self.app.get(route_path('repo_groups'), status=200)
108 response = self.app.get(route_path('repo_groups_data'),
109 extra_environ=xhr_header, status=200)
106 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
110 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
107
111
108 response = self.app.get(route_path('user_groups_data'),
112 response = self.app.get(route_path('user_groups_data'),
109 extra_environ=xhr_header, status=200)
113 extra_environ=xhr_header, status=200)
110 response.mustcontain('"name_raw": "{}"'.format(user_group_name))
114 response.mustcontain('"name_raw": "{}"'.format(user_group_name))
111
115
112 def test_regular_user_can_see_admin_interfaces_if_admin_perm(
116 def test_regular_user_can_see_admin_interfaces_if_admin_perm(
113 self, user_util, xhr_header):
117 self, user_util, xhr_header):
114 user = user_util.create_user(password='qweqwe')
118 user = user_util.create_user(password='qweqwe')
115 username = user.username
119 username = user.username
116
120
117 repo = user_util.create_repo()
121 repo = user_util.create_repo()
118 repo_name = repo.repo_name
122 repo_name = repo.repo_name
119
123
120 repo_group = user_util.create_repo_group()
124 repo_group = user_util.create_repo_group()
121 repo_group_name = repo_group.group_name
125 repo_group_name = repo_group.group_name
122
126
123 user_group = user_util.create_user_group()
127 user_group = user_util.create_user_group()
124 user_group_name = user_group.users_group_name
128 user_group_name = user_group.users_group_name
125
129
126 user_util.grant_user_permission_to_repo(
130 user_util.grant_user_permission_to_repo(
127 repo, user, 'repository.admin')
131 repo, user, 'repository.admin')
128 user_util.grant_user_permission_to_repo_group(
132 user_util.grant_user_permission_to_repo_group(
129 repo_group, user, 'group.admin')
133 repo_group, user, 'group.admin')
130 user_util.grant_user_permission_to_user_group(
134 user_util.grant_user_permission_to_user_group(
131 user_group, user, 'usergroup.admin')
135 user_group, user, 'usergroup.admin')
132
136
133 self.log_user(username, 'qweqwe')
137 self.log_user(username, 'qweqwe')
134 # check if in home view, such user doesn't see the "admin" menus
138 # check if in home view, such user doesn't see the "admin" menus
135 response = self.app.get(route_path('home'))
139 response = self.app.get(route_path('home'))
136
140
137 assert_response = response.assert_response()
141 assert_response = response.assert_response()
138
142
139 assert_response.one_element_exists('li.local-admin-repos')
143 assert_response.one_element_exists('li.local-admin-repos')
140 assert_response.one_element_exists('li.local-admin-repo-groups')
144 assert_response.one_element_exists('li.local-admin-repo-groups')
141 assert_response.one_element_exists('li.local-admin-user-groups')
145 assert_response.one_element_exists('li.local-admin-user-groups')
142
146
143 # admin interfaces have visible elements
147 # admin interfaces have visible elements
144 response = self.app.get(route_path('repos'), status=200)
148 response = self.app.get(route_path('repos'), status=200)
145 response.mustcontain('"name_raw": "{}"'.format(repo_name))
149 response.mustcontain('"name_raw": "{}"'.format(repo_name))
146
150
147 response = self.app.get(route_path('repo_groups'), status=200)
151 response = self.app.get(route_path('repo_groups_data'),
152 extra_environ=xhr_header, status=200)
148 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
153 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
149
154
150 response = self.app.get(route_path('user_groups_data'),
155 response = self.app.get(route_path('user_groups_data'),
151 extra_environ=xhr_header, status=200)
156 extra_environ=xhr_header, status=200)
152 response.mustcontain('"name_raw": "{}"'.format(user_group_name))
157 response.mustcontain('"name_raw": "{}"'.format(user_group_name))
General Comments 0
You need to be logged in to leave comments. Login now