##// END OF EJS Templates
events: add event to catch permission changed so we can flush affected users permission caches
marcink -
r2849:21c71a09 default
parent child Browse files
Show More
@@ -0,0 +1,38 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 from rhodecode import events
24
25 log = logging.getLogger(__name__)
26
27
28 def trigger_user_permission_flush(event):
29 """
30 Subscriber to the `UserPermissionChange`. This triggers the
31 automatic flush of permission caches, so the users affected receive new permissions
32 Right Away
33 """
34 affected_user_ids = event.user_ids
35
36
37 def includeme(config):
38 config.add_subscriber(trigger_user_permission_flush, events.UserPermissionsChange)
@@ -1,424 +1,426 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 from rhodecode.apps._base import ADMIN_PREFIX
23 23
24 24
25 25 def admin_routes(config):
26 26 """
27 27 Admin prefixed routes
28 28 """
29 29
30 30 config.add_route(
31 31 name='admin_audit_logs',
32 32 pattern='/audit_logs')
33 33
34 34 config.add_route(
35 35 name='admin_audit_log_entry',
36 36 pattern='/audit_logs/{audit_log_id}')
37 37
38 38 config.add_route(
39 39 name='pull_requests_global_0', # backward compat
40 40 pattern='/pull_requests/{pull_request_id:\d+}')
41 41 config.add_route(
42 42 name='pull_requests_global_1', # backward compat
43 43 pattern='/pull-requests/{pull_request_id:\d+}')
44 44 config.add_route(
45 45 name='pull_requests_global',
46 46 pattern='/pull-request/{pull_request_id:\d+}')
47 47
48 48 config.add_route(
49 49 name='admin_settings_open_source',
50 50 pattern='/settings/open_source')
51 51 config.add_route(
52 52 name='admin_settings_vcs_svn_generate_cfg',
53 53 pattern='/settings/vcs/svn_generate_cfg')
54 54
55 55 config.add_route(
56 56 name='admin_settings_system',
57 57 pattern='/settings/system')
58 58 config.add_route(
59 59 name='admin_settings_system_update',
60 60 pattern='/settings/system/updates')
61 61
62 62 config.add_route(
63 63 name='admin_settings_sessions',
64 64 pattern='/settings/sessions')
65 65 config.add_route(
66 66 name='admin_settings_sessions_cleanup',
67 67 pattern='/settings/sessions/cleanup')
68 68
69 69 config.add_route(
70 70 name='admin_settings_process_management',
71 71 pattern='/settings/process_management')
72 72 config.add_route(
73 73 name='admin_settings_process_management_data',
74 74 pattern='/settings/process_management/data')
75 75 config.add_route(
76 76 name='admin_settings_process_management_signal',
77 77 pattern='/settings/process_management/signal')
78 78 config.add_route(
79 79 name='admin_settings_process_management_master_signal',
80 80 pattern='/settings/process_management/master_signal')
81 81
82 82 # default settings
83 83 config.add_route(
84 84 name='admin_defaults_repositories',
85 85 pattern='/defaults/repositories')
86 86 config.add_route(
87 87 name='admin_defaults_repositories_update',
88 88 pattern='/defaults/repositories/update')
89 89
90 90 # admin settings
91 91
92 92 config.add_route(
93 93 name='admin_settings',
94 94 pattern='/settings')
95 95 config.add_route(
96 96 name='admin_settings_update',
97 97 pattern='/settings/update')
98 98
99 99 config.add_route(
100 100 name='admin_settings_global',
101 101 pattern='/settings/global')
102 102 config.add_route(
103 103 name='admin_settings_global_update',
104 104 pattern='/settings/global/update')
105 105
106 106 config.add_route(
107 107 name='admin_settings_vcs',
108 108 pattern='/settings/vcs')
109 109 config.add_route(
110 110 name='admin_settings_vcs_update',
111 111 pattern='/settings/vcs/update')
112 112 config.add_route(
113 113 name='admin_settings_vcs_svn_pattern_delete',
114 114 pattern='/settings/vcs/svn_pattern_delete')
115 115
116 116 config.add_route(
117 117 name='admin_settings_mapping',
118 118 pattern='/settings/mapping')
119 119 config.add_route(
120 120 name='admin_settings_mapping_update',
121 121 pattern='/settings/mapping/update')
122 122
123 123 config.add_route(
124 124 name='admin_settings_visual',
125 125 pattern='/settings/visual')
126 126 config.add_route(
127 127 name='admin_settings_visual_update',
128 128 pattern='/settings/visual/update')
129 129
130 130
131 131 config.add_route(
132 132 name='admin_settings_issuetracker',
133 133 pattern='/settings/issue-tracker')
134 134 config.add_route(
135 135 name='admin_settings_issuetracker_update',
136 136 pattern='/settings/issue-tracker/update')
137 137 config.add_route(
138 138 name='admin_settings_issuetracker_test',
139 139 pattern='/settings/issue-tracker/test')
140 140 config.add_route(
141 141 name='admin_settings_issuetracker_delete',
142 142 pattern='/settings/issue-tracker/delete')
143 143
144 144 config.add_route(
145 145 name='admin_settings_email',
146 146 pattern='/settings/email')
147 147 config.add_route(
148 148 name='admin_settings_email_update',
149 149 pattern='/settings/email/update')
150 150
151 151 config.add_route(
152 152 name='admin_settings_hooks',
153 153 pattern='/settings/hooks')
154 154 config.add_route(
155 155 name='admin_settings_hooks_update',
156 156 pattern='/settings/hooks/update')
157 157 config.add_route(
158 158 name='admin_settings_hooks_delete',
159 159 pattern='/settings/hooks/delete')
160 160
161 161 config.add_route(
162 162 name='admin_settings_search',
163 163 pattern='/settings/search')
164 164
165 165 config.add_route(
166 166 name='admin_settings_labs',
167 167 pattern='/settings/labs')
168 168 config.add_route(
169 169 name='admin_settings_labs_update',
170 170 pattern='/settings/labs/update')
171 171
172 172 # Automation EE feature
173 173 config.add_route(
174 174 'admin_settings_automation',
175 175 pattern=ADMIN_PREFIX + '/settings/automation')
176 176
177 177 # global permissions
178 178
179 179 config.add_route(
180 180 name='admin_permissions_application',
181 181 pattern='/permissions/application')
182 182 config.add_route(
183 183 name='admin_permissions_application_update',
184 184 pattern='/permissions/application/update')
185 185
186 186 config.add_route(
187 187 name='admin_permissions_global',
188 188 pattern='/permissions/global')
189 189 config.add_route(
190 190 name='admin_permissions_global_update',
191 191 pattern='/permissions/global/update')
192 192
193 193 config.add_route(
194 194 name='admin_permissions_object',
195 195 pattern='/permissions/object')
196 196 config.add_route(
197 197 name='admin_permissions_object_update',
198 198 pattern='/permissions/object/update')
199 199
200 200 config.add_route(
201 201 name='admin_permissions_ips',
202 202 pattern='/permissions/ips')
203 203
204 204 config.add_route(
205 205 name='admin_permissions_overview',
206 206 pattern='/permissions/overview')
207 207
208 208 config.add_route(
209 209 name='admin_permissions_auth_token_access',
210 210 pattern='/permissions/auth_token_access')
211 211
212 212 config.add_route(
213 213 name='admin_permissions_ssh_keys',
214 214 pattern='/permissions/ssh_keys')
215 215 config.add_route(
216 216 name='admin_permissions_ssh_keys_data',
217 217 pattern='/permissions/ssh_keys/data')
218 218 config.add_route(
219 219 name='admin_permissions_ssh_keys_update',
220 220 pattern='/permissions/ssh_keys/update')
221 221
222 222 # users admin
223 223 config.add_route(
224 224 name='users',
225 225 pattern='/users')
226 226
227 227 config.add_route(
228 228 name='users_data',
229 229 pattern='/users_data')
230 230
231 231 config.add_route(
232 232 name='users_create',
233 233 pattern='/users/create')
234 234
235 235 config.add_route(
236 236 name='users_new',
237 237 pattern='/users/new')
238 238
239 239 # user management
240 240 config.add_route(
241 241 name='user_edit',
242 242 pattern='/users/{user_id:\d+}/edit',
243 243 user_route=True)
244 244 config.add_route(
245 245 name='user_edit_advanced',
246 246 pattern='/users/{user_id:\d+}/edit/advanced',
247 247 user_route=True)
248 248 config.add_route(
249 249 name='user_edit_global_perms',
250 250 pattern='/users/{user_id:\d+}/edit/global_permissions',
251 251 user_route=True)
252 252 config.add_route(
253 253 name='user_edit_global_perms_update',
254 254 pattern='/users/{user_id:\d+}/edit/global_permissions/update',
255 255 user_route=True)
256 256 config.add_route(
257 257 name='user_update',
258 258 pattern='/users/{user_id:\d+}/update',
259 259 user_route=True)
260 260 config.add_route(
261 261 name='user_delete',
262 262 pattern='/users/{user_id:\d+}/delete',
263 263 user_route=True)
264 264 config.add_route(
265 265 name='user_force_password_reset',
266 266 pattern='/users/{user_id:\d+}/password_reset',
267 267 user_route=True)
268 268 config.add_route(
269 269 name='user_create_personal_repo_group',
270 270 pattern='/users/{user_id:\d+}/create_repo_group',
271 271 user_route=True)
272 272
273 273 # user auth tokens
274 274 config.add_route(
275 275 name='edit_user_auth_tokens',
276 276 pattern='/users/{user_id:\d+}/edit/auth_tokens',
277 277 user_route=True)
278 278 config.add_route(
279 279 name='edit_user_auth_tokens_add',
280 280 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
281 281 user_route=True)
282 282 config.add_route(
283 283 name='edit_user_auth_tokens_delete',
284 284 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete',
285 285 user_route=True)
286 286
287 287 # user ssh keys
288 288 config.add_route(
289 289 name='edit_user_ssh_keys',
290 290 pattern='/users/{user_id:\d+}/edit/ssh_keys',
291 291 user_route=True)
292 292 config.add_route(
293 293 name='edit_user_ssh_keys_generate_keypair',
294 294 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate',
295 295 user_route=True)
296 296 config.add_route(
297 297 name='edit_user_ssh_keys_add',
298 298 pattern='/users/{user_id:\d+}/edit/ssh_keys/new',
299 299 user_route=True)
300 300 config.add_route(
301 301 name='edit_user_ssh_keys_delete',
302 302 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete',
303 303 user_route=True)
304 304
305 305 # user emails
306 306 config.add_route(
307 307 name='edit_user_emails',
308 308 pattern='/users/{user_id:\d+}/edit/emails',
309 309 user_route=True)
310 310 config.add_route(
311 311 name='edit_user_emails_add',
312 312 pattern='/users/{user_id:\d+}/edit/emails/new',
313 313 user_route=True)
314 314 config.add_route(
315 315 name='edit_user_emails_delete',
316 316 pattern='/users/{user_id:\d+}/edit/emails/delete',
317 317 user_route=True)
318 318
319 319 # user IPs
320 320 config.add_route(
321 321 name='edit_user_ips',
322 322 pattern='/users/{user_id:\d+}/edit/ips',
323 323 user_route=True)
324 324 config.add_route(
325 325 name='edit_user_ips_add',
326 326 pattern='/users/{user_id:\d+}/edit/ips/new',
327 327 user_route_with_default=True) # enabled for default user too
328 328 config.add_route(
329 329 name='edit_user_ips_delete',
330 330 pattern='/users/{user_id:\d+}/edit/ips/delete',
331 331 user_route_with_default=True) # enabled for default user too
332 332
333 333 # user perms
334 334 config.add_route(
335 335 name='edit_user_perms_summary',
336 336 pattern='/users/{user_id:\d+}/edit/permissions_summary',
337 337 user_route=True)
338 338 config.add_route(
339 339 name='edit_user_perms_summary_json',
340 340 pattern='/users/{user_id:\d+}/edit/permissions_summary/json',
341 341 user_route=True)
342 342
343 343 # user user groups management
344 344 config.add_route(
345 345 name='edit_user_groups_management',
346 346 pattern='/users/{user_id:\d+}/edit/groups_management',
347 347 user_route=True)
348 348
349 349 config.add_route(
350 350 name='edit_user_groups_management_updates',
351 351 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
352 352 user_route=True)
353 353
354 354 # user audit logs
355 355 config.add_route(
356 356 name='edit_user_audit_logs',
357 357 pattern='/users/{user_id:\d+}/edit/audit', user_route=True)
358 358
359 359 # user caches
360 360 config.add_route(
361 361 name='edit_user_caches',
362 362 pattern='/users/{user_id:\d+}/edit/caches',
363 363 user_route=True)
364 364 config.add_route(
365 365 name='edit_user_caches_update',
366 366 pattern='/users/{user_id:\d+}/edit/caches/update',
367 367 user_route=True)
368 368
369 369 # user-groups admin
370 370 config.add_route(
371 371 name='user_groups',
372 372 pattern='/user_groups')
373 373
374 374 config.add_route(
375 375 name='user_groups_data',
376 376 pattern='/user_groups_data')
377 377
378 378 config.add_route(
379 379 name='user_groups_new',
380 380 pattern='/user_groups/new')
381 381
382 382 config.add_route(
383 383 name='user_groups_create',
384 384 pattern='/user_groups/create')
385 385
386 386 # repos admin
387 387 config.add_route(
388 388 name='repos',
389 389 pattern='/repos')
390 390
391 391 config.add_route(
392 392 name='repo_new',
393 393 pattern='/repos/new')
394 394
395 395 config.add_route(
396 396 name='repo_create',
397 397 pattern='/repos/create')
398 398
399 399 # repo groups admin
400 400 config.add_route(
401 401 name='repo_groups',
402 402 pattern='/repo_groups')
403 403
404 404 config.add_route(
405 405 name='repo_group_new',
406 406 pattern='/repo_group/new')
407 407
408 408 config.add_route(
409 409 name='repo_group_create',
410 410 pattern='/repo_group/create')
411 411
412 412
413 413 def includeme(config):
414 414 from rhodecode.apps.admin.navigation import includeme as nav_includeme
415 415
416 416 # Create admin navigation registry and add it to the pyramid registry.
417 417 nav_includeme(config)
418 418
419 419 # main admin routes
420 420 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
421 421 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
422 422
423 config.include('.subscribers')
424
423 425 # Scan module for configuration decorators.
424 426 config.scan('.views', ignore='.tests')
@@ -1,100 +1,109 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 from pyramid.view import view_config
24 24 from pyramid.httpexceptions import HTTPFound
25 25
26 from rhodecode import events
26 27 from rhodecode.apps._base import RepoGroupAppView
27 28 from rhodecode.lib import helpers as h
28 29 from rhodecode.lib import audit_logger
29 30 from rhodecode.lib.auth import (
30 31 LoginRequired, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
31 32 from rhodecode.model.repo_group import RepoGroupModel
32 33 from rhodecode.model.forms import RepoGroupPermsForm
33 34 from rhodecode.model.meta import Session
34 35
35 36 log = logging.getLogger(__name__)
36 37
37 38
38 39 class RepoGroupPermissionsView(RepoGroupAppView):
39 40 def load_default_context(self):
40 41 c = self._get_local_tmpl_context()
41 42
42 43 return c
43 44
44 45 @LoginRequired()
45 46 @HasRepoGroupPermissionAnyDecorator('group.admin')
46 47 @view_config(
47 48 route_name='edit_repo_group_perms', request_method='GET',
48 49 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
49 50 def edit_repo_group_permissions(self):
50 51 c = self.load_default_context()
51 52 c.active = 'permissions'
52 53 c.repo_group = self.db_repo_group
53 54 return self._get_template_context(c)
54 55
55 56 @LoginRequired()
56 57 @HasRepoGroupPermissionAnyDecorator('group.admin')
57 58 @CSRFRequired()
58 59 @view_config(
59 60 route_name='edit_repo_group_perms_update', request_method='POST',
60 61 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
61 62 def edit_repo_groups_permissions_update(self):
62 63 _ = self.request.translate
63 64 c = self.load_default_context()
64 65 c.active = 'perms'
65 66 c.repo_group = self.db_repo_group
66 67
67 68 valid_recursive_choices = ['none', 'repos', 'groups', 'all']
68 69 form = RepoGroupPermsForm(self.request.translate, valid_recursive_choices)()\
69 70 .to_python(self.request.POST)
70 71
71 72 if not c.rhodecode_user.is_admin:
72 73 if self._revoke_perms_on_yourself(form):
73 74 msg = _('Cannot change permission for yourself as admin')
74 75 h.flash(msg, category='warning')
75 76 raise HTTPFound(
76 77 h.route_path('edit_repo_group_perms',
77 78 repo_group_name=self.db_repo_group_name))
78 79
79 80 # iterate over all members(if in recursive mode) of this groups and
80 81 # set the permissions !
81 82 # this can be potentially heavy operation
82 83 changes = RepoGroupModel().update_permissions(
83 84 c.repo_group,
84 85 form['perm_additions'], form['perm_updates'], form['perm_deletions'],
85 86 form['recursive'])
86 87
87 88 action_data = {
88 89 'added': changes['added'],
89 90 'updated': changes['updated'],
90 91 'deleted': changes['deleted'],
91 92 }
92 93 audit_logger.store_web(
93 94 'repo_group.edit.permissions', action_data=action_data,
94 95 user=c.rhodecode_user)
95 96
96 97 Session().commit()
97 98 h.flash(_('Repository Group permissions updated'), category='success')
99
100 affected_user_ids = []
101 for change in changes['added'] + changes['updated'] + changes['deleted']:
102 if change['type'] == 'user':
103 affected_user_ids.append(change['id'])
104
105 events.trigger(events.UserPermissionsChange(affected_user_ids))
106
98 107 raise HTTPFound(
99 108 h.route_path('edit_repo_group_perms',
100 109 repo_group_name=self.db_repo_group_name))
@@ -1,89 +1,95 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 from pyramid.httpexceptions import HTTPFound
24 24 from pyramid.view import view_config
25 25
26 from rhodecode import events
26 27 from rhodecode.apps._base import RepoAppView
27 28 from rhodecode.lib import helpers as h
28 29 from rhodecode.lib import audit_logger
29 30 from rhodecode.lib.auth import (
30 31 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
31 32 from rhodecode.model.forms import RepoPermsForm
32 33 from rhodecode.model.meta import Session
33 34 from rhodecode.model.repo import RepoModel
34 35
35 36 log = logging.getLogger(__name__)
36 37
37 38
38 39 class RepoSettingsPermissionsView(RepoAppView):
39 40
40 41 def load_default_context(self):
41 42 c = self._get_local_tmpl_context()
42
43
44 43 return c
45 44
46 45 @LoginRequired()
47 46 @HasRepoPermissionAnyDecorator('repository.admin')
48 47 @view_config(
49 48 route_name='edit_repo_perms', request_method='GET',
50 49 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
51 50 def edit_permissions(self):
52 51 c = self.load_default_context()
53 52 c.active = 'permissions'
54 53 return self._get_template_context(c)
55 54
56 55 @LoginRequired()
57 56 @HasRepoPermissionAnyDecorator('repository.admin')
58 57 @CSRFRequired()
59 58 @view_config(
60 59 route_name='edit_repo_perms', request_method='POST',
61 60 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
62 61 def edit_permissions_update(self):
63 62 _ = self.request.translate
64 63 c = self.load_default_context()
65 64 c.active = 'permissions'
66 65 data = self.request.POST
67 66 # store private flag outside of HTML to verify if we can modify
68 67 # default user permissions, prevents submission of FAKE post data
69 68 # into the form for private repos
70 69 data['repo_private'] = self.db_repo.private
71 70 form = RepoPermsForm(self.request.translate)().to_python(data)
72 71 changes = RepoModel().update_permissions(
73 72 self.db_repo_name, form['perm_additions'], form['perm_updates'],
74 73 form['perm_deletions'])
75 74
76 75 action_data = {
77 76 'added': changes['added'],
78 77 'updated': changes['updated'],
79 78 'deleted': changes['deleted'],
80 79 }
81 80 audit_logger.store_web(
82 81 'repo.edit.permissions', action_data=action_data,
83 82 user=self._rhodecode_user, repo=self.db_repo)
84 83
85 84 Session().commit()
86 85 h.flash(_('Repository permissions updated'), category='success')
87 86
87 affected_user_ids = []
88 for change in changes['added'] + changes['updated'] + changes['deleted']:
89 if change['type'] == 'user':
90 affected_user_ids.append(change['id'])
91
92 events.trigger(events.UserPermissionsChange(affected_user_ids))
93
88 94 raise HTTPFound(
89 95 h.route_path('edit_repo_perms', repo_name=self.db_repo_name))
@@ -1,519 +1,534 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 import peppercorn
24 24 import formencode
25 25 import formencode.htmlfill
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.view import view_config
28 28 from pyramid.response import Response
29 29 from pyramid.renderers import render
30 30
31 from rhodecode import events
31 32 from rhodecode.lib.exceptions import (
32 33 RepoGroupAssignmentError, UserGroupAssignedException)
33 34 from rhodecode.model.forms import (
34 35 UserGroupPermsForm, UserGroupForm, UserIndividualPermissionsForm,
35 36 UserPermissionsForm)
36 37 from rhodecode.model.permission import PermissionModel
37 38
38 39 from rhodecode.apps._base import UserGroupAppView
39 40 from rhodecode.lib.auth import (
40 41 LoginRequired, HasUserGroupPermissionAnyDecorator, CSRFRequired)
41 42 from rhodecode.lib import helpers as h, audit_logger
42 43 from rhodecode.lib.utils2 import str2bool
43 44 from rhodecode.model.db import User
44 45 from rhodecode.model.meta import Session
45 46 from rhodecode.model.user_group import UserGroupModel
46 47
47 48 log = logging.getLogger(__name__)
48 49
49 50
50 51 class UserGroupsView(UserGroupAppView):
51 52
52 53 def load_default_context(self):
53 54 c = self._get_local_tmpl_context()
54 55
55 56 PermissionModel().set_global_permission_choices(
56 57 c, gettext_translator=self.request.translate)
57 58
58 59 return c
59 60
60 61 @LoginRequired()
61 62 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
62 63 @view_config(
63 64 route_name='user_group_members_data', request_method='GET',
64 65 renderer='json_ext', xhr=True)
65 66 def user_group_members(self):
66 67 """
67 68 Return members of given user group
68 69 """
69 70 self.load_default_context()
70 71 user_group = self.db_user_group
71 72 group_members_obj = sorted((x.user for x in user_group.members),
72 73 key=lambda u: u.username.lower())
73 74
74 75 group_members = [
75 76 {
76 77 'id': user.user_id,
77 78 'first_name': user.first_name,
78 79 'last_name': user.last_name,
79 80 'username': user.username,
80 81 'icon_link': h.gravatar_url(user.email, 30),
81 82 'value_display': h.person(user.email),
82 83 'value': user.username,
83 84 'value_type': 'user',
84 85 'active': user.active,
85 86 }
86 87 for user in group_members_obj
87 88 ]
88 89
89 90 return {
90 91 'members': group_members
91 92 }
92 93
93 94 @LoginRequired()
94 95 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
95 96 @view_config(
96 97 route_name='edit_user_group_perms_summary', request_method='GET',
97 98 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
98 99 def user_group_perms_summary(self):
99 100 c = self.load_default_context()
100 101 c.user_group = self.db_user_group
101 102 c.active = 'perms_summary'
102 103 c.permissions = UserGroupModel().get_perms_summary(
103 104 c.user_group.users_group_id)
104 105 return self._get_template_context(c)
105 106
106 107 @LoginRequired()
107 108 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
108 109 @view_config(
109 110 route_name='edit_user_group_perms_summary_json', request_method='GET',
110 111 renderer='json_ext')
111 112 def user_group_perms_summary_json(self):
112 113 self.load_default_context()
113 114 user_group = self.db_user_group
114 115 return UserGroupModel().get_perms_summary(user_group.users_group_id)
115 116
116 117 def _revoke_perms_on_yourself(self, form_result):
117 118 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
118 119 form_result['perm_updates'])
119 120 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
120 121 form_result['perm_additions'])
121 122 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
122 123 form_result['perm_deletions'])
123 124 admin_perm = 'usergroup.admin'
124 125 if _updates and _updates[0][1] != admin_perm or \
125 126 _additions and _additions[0][1] != admin_perm or \
126 127 _deletions and _deletions[0][1] != admin_perm:
127 128 return True
128 129 return False
129 130
130 131 @LoginRequired()
131 132 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
132 133 @CSRFRequired()
133 134 @view_config(
134 135 route_name='user_groups_update', request_method='POST',
135 136 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
136 137 def user_group_update(self):
137 138 _ = self.request.translate
138 139
139 140 user_group = self.db_user_group
140 141 user_group_id = user_group.users_group_id
141 142
142 143 c = self.load_default_context()
143 144 c.user_group = user_group
144 145 c.group_members_obj = [x.user for x in c.user_group.members]
145 146 c.group_members_obj.sort(key=lambda u: u.username.lower())
146 147 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
147 148 c.active = 'settings'
148 149
149 150 users_group_form = UserGroupForm(
150 151 self.request.translate, edit=True,
151 152 old_data=c.user_group.get_dict(), allow_disabled=True)()
152 153
153 154 old_values = c.user_group.get_api_data()
154 155 user_group_name = self.request.POST.get('users_group_name')
155 156 try:
156 157 form_result = users_group_form.to_python(self.request.POST)
157 158 pstruct = peppercorn.parse(self.request.POST.items())
158 159 form_result['users_group_members'] = pstruct['user_group_members']
159 160
160 161 user_group, added_members, removed_members = \
161 162 UserGroupModel().update(c.user_group, form_result)
162 163 updated_user_group = form_result['users_group_name']
163 164
164 165 for user_id in added_members:
165 166 user = User.get(user_id)
166 167 user_data = user.get_api_data()
167 168 audit_logger.store_web(
168 169 'user_group.edit.member.add',
169 170 action_data={'user': user_data, 'old_data': old_values},
170 171 user=self._rhodecode_user)
171 172
172 173 for user_id in removed_members:
173 174 user = User.get(user_id)
174 175 user_data = user.get_api_data()
175 176 audit_logger.store_web(
176 177 'user_group.edit.member.delete',
177 178 action_data={'user': user_data, 'old_data': old_values},
178 179 user=self._rhodecode_user)
179 180
180 181 audit_logger.store_web(
181 182 'user_group.edit', action_data={'old_data': old_values},
182 183 user=self._rhodecode_user)
183 184
184 185 h.flash(_('Updated user group %s') % updated_user_group,
185 186 category='success')
187
188 affected_user_ids = []
189 for user_id in added_members + removed_members:
190 affected_user_ids.append(user_id)
191 events.trigger(events.UserPermissionsChange(affected_user_ids))
192
186 193 Session().commit()
187 194 except formencode.Invalid as errors:
188 195 defaults = errors.value
189 196 e = errors.error_dict or {}
190 197
191 198 data = render(
192 199 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
193 200 self._get_template_context(c), self.request)
194 201 html = formencode.htmlfill.render(
195 202 data,
196 203 defaults=defaults,
197 204 errors=e,
198 205 prefix_error=False,
199 206 encoding="UTF-8",
200 207 force_defaults=False
201 208 )
202 209 return Response(html)
203 210
204 211 except Exception:
205 212 log.exception("Exception during update of user group")
206 213 h.flash(_('Error occurred during update of user group %s')
207 214 % user_group_name, category='error')
208 215
209 216 raise HTTPFound(
210 217 h.route_path('edit_user_group', user_group_id=user_group_id))
211 218
212 219 @LoginRequired()
213 220 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
214 221 @CSRFRequired()
215 222 @view_config(
216 223 route_name='user_groups_delete', request_method='POST',
217 224 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
218 225 def user_group_delete(self):
219 226 _ = self.request.translate
220 227 user_group = self.db_user_group
221 228
222 229 self.load_default_context()
223 230 force = str2bool(self.request.POST.get('force'))
224 231
225 232 old_values = user_group.get_api_data()
226 233 try:
227 234 UserGroupModel().delete(user_group, force=force)
228 235 audit_logger.store_web(
229 236 'user.delete', action_data={'old_data': old_values},
230 237 user=self._rhodecode_user)
231 238 Session().commit()
232 239 h.flash(_('Successfully deleted user group'), category='success')
233 240 except UserGroupAssignedException as e:
234 241 h.flash(str(e), category='error')
235 242 except Exception:
236 243 log.exception("Exception during deletion of user group")
237 244 h.flash(_('An error occurred during deletion of user group'),
238 245 category='error')
239 246 raise HTTPFound(h.route_path('user_groups'))
240 247
241 248 @LoginRequired()
242 249 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
243 250 @view_config(
244 251 route_name='edit_user_group', request_method='GET',
245 252 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
246 253 def user_group_edit(self):
247 254 user_group = self.db_user_group
248 255
249 256 c = self.load_default_context()
250 257 c.user_group = user_group
251 258 c.group_members_obj = [x.user for x in c.user_group.members]
252 259 c.group_members_obj.sort(key=lambda u: u.username.lower())
253 260 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
254 261
255 262 c.active = 'settings'
256 263
257 264 defaults = user_group.get_dict()
258 265 # fill owner
259 266 if user_group.user:
260 267 defaults.update({'user': user_group.user.username})
261 268 else:
262 269 replacement_user = User.get_first_super_admin().username
263 270 defaults.update({'user': replacement_user})
264 271
265 272 data = render(
266 273 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
267 274 self._get_template_context(c), self.request)
268 275 html = formencode.htmlfill.render(
269 276 data,
270 277 defaults=defaults,
271 278 encoding="UTF-8",
272 279 force_defaults=False
273 280 )
274 281 return Response(html)
275 282
276 283 @LoginRequired()
277 284 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
278 285 @view_config(
279 286 route_name='edit_user_group_perms', request_method='GET',
280 287 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
281 288 def user_group_edit_perms(self):
282 289 user_group = self.db_user_group
283 290 c = self.load_default_context()
284 291 c.user_group = user_group
285 292 c.active = 'perms'
286 293
287 294 defaults = {}
288 295 # fill user group users
289 296 for p in c.user_group.user_user_group_to_perm:
290 297 defaults.update({'u_perm_%s' % p.user.user_id:
291 298 p.permission.permission_name})
292 299
293 300 for p in c.user_group.user_group_user_group_to_perm:
294 301 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
295 302 p.permission.permission_name})
296 303
297 304 data = render(
298 305 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
299 306 self._get_template_context(c), self.request)
300 307 html = formencode.htmlfill.render(
301 308 data,
302 309 defaults=defaults,
303 310 encoding="UTF-8",
304 311 force_defaults=False
305 312 )
306 313 return Response(html)
307 314
308 315 @LoginRequired()
309 316 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
310 317 @CSRFRequired()
311 318 @view_config(
312 319 route_name='edit_user_group_perms_update', request_method='POST',
313 320 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
314 321 def user_group_update_perms(self):
315 322 """
316 323 grant permission for given user group
317 324 """
318 325 _ = self.request.translate
319 326
320 327 user_group = self.db_user_group
321 328 user_group_id = user_group.users_group_id
322 329 c = self.load_default_context()
323 330 c.user_group = user_group
324 331 form = UserGroupPermsForm(self.request.translate)().to_python(self.request.POST)
325 332
326 333 if not self._rhodecode_user.is_admin:
327 334 if self._revoke_perms_on_yourself(form):
328 335 msg = _('Cannot change permission for yourself as admin')
329 336 h.flash(msg, category='warning')
330 337 raise HTTPFound(
331 338 h.route_path('edit_user_group_perms',
332 339 user_group_id=user_group_id))
333 340
334 341 try:
335 342 changes = UserGroupModel().update_permissions(
336 343 user_group,
337 344 form['perm_additions'], form['perm_updates'],
338 345 form['perm_deletions'])
339 346
340 347 except RepoGroupAssignmentError:
341 348 h.flash(_('Target group cannot be the same'), category='error')
342 349 raise HTTPFound(
343 350 h.route_path('edit_user_group_perms',
344 351 user_group_id=user_group_id))
345 352
346 353 action_data = {
347 354 'added': changes['added'],
348 355 'updated': changes['updated'],
349 356 'deleted': changes['deleted'],
350 357 }
351 358 audit_logger.store_web(
352 359 'user_group.edit.permissions', action_data=action_data,
353 360 user=self._rhodecode_user)
354 361
355 362 Session().commit()
356 363 h.flash(_('User Group permissions updated'), category='success')
364
365 affected_user_ids = []
366 for change in changes['added'] + changes['updated'] + changes['deleted']:
367 if change['type'] == 'user':
368 affected_user_ids.append(change['id'])
369
370 events.trigger(events.UserPermissionsChange(affected_user_ids))
371
357 372 raise HTTPFound(
358 373 h.route_path('edit_user_group_perms', user_group_id=user_group_id))
359 374
360 375 @LoginRequired()
361 376 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
362 377 @view_config(
363 378 route_name='edit_user_group_global_perms', request_method='GET',
364 379 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
365 380 def user_group_global_perms_edit(self):
366 381 user_group = self.db_user_group
367 382 c = self.load_default_context()
368 383 c.user_group = user_group
369 384 c.active = 'global_perms'
370 385
371 386 c.default_user = User.get_default_user()
372 387 defaults = c.user_group.get_dict()
373 388 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
374 389 defaults.update(c.user_group.get_default_perms())
375 390
376 391 data = render(
377 392 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
378 393 self._get_template_context(c), self.request)
379 394 html = formencode.htmlfill.render(
380 395 data,
381 396 defaults=defaults,
382 397 encoding="UTF-8",
383 398 force_defaults=False
384 399 )
385 400 return Response(html)
386 401
387 402 @LoginRequired()
388 403 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
389 404 @CSRFRequired()
390 405 @view_config(
391 406 route_name='edit_user_group_global_perms_update', request_method='POST',
392 407 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
393 408 def user_group_global_perms_update(self):
394 409 _ = self.request.translate
395 410 user_group = self.db_user_group
396 411 user_group_id = self.db_user_group.users_group_id
397 412
398 413 c = self.load_default_context()
399 414 c.user_group = user_group
400 415 c.active = 'global_perms'
401 416
402 417 try:
403 418 # first stage that verifies the checkbox
404 419 _form = UserIndividualPermissionsForm(self.request.translate)
405 420 form_result = _form.to_python(dict(self.request.POST))
406 421 inherit_perms = form_result['inherit_default_permissions']
407 422 user_group.inherit_default_permissions = inherit_perms
408 423 Session().add(user_group)
409 424
410 425 if not inherit_perms:
411 426 # only update the individual ones if we un check the flag
412 427 _form = UserPermissionsForm(
413 428 self.request.translate,
414 429 [x[0] for x in c.repo_create_choices],
415 430 [x[0] for x in c.repo_create_on_write_choices],
416 431 [x[0] for x in c.repo_group_create_choices],
417 432 [x[0] for x in c.user_group_create_choices],
418 433 [x[0] for x in c.fork_choices],
419 434 [x[0] for x in c.inherit_default_permission_choices])()
420 435
421 436 form_result = _form.to_python(dict(self.request.POST))
422 437 form_result.update(
423 438 {'perm_user_group_id': user_group.users_group_id})
424 439
425 440 PermissionModel().update_user_group_permissions(form_result)
426 441
427 442 Session().commit()
428 443 h.flash(_('User Group global permissions updated successfully'),
429 444 category='success')
430 445
431 446 except formencode.Invalid as errors:
432 447 defaults = errors.value
433 448
434 449 data = render(
435 450 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
436 451 self._get_template_context(c), self.request)
437 452 html = formencode.htmlfill.render(
438 453 data,
439 454 defaults=defaults,
440 455 errors=errors.error_dict or {},
441 456 prefix_error=False,
442 457 encoding="UTF-8",
443 458 force_defaults=False
444 459 )
445 460 return Response(html)
446 461 except Exception:
447 462 log.exception("Exception during permissions saving")
448 463 h.flash(_('An error occurred during permissions saving'),
449 464 category='error')
450 465
451 466 raise HTTPFound(
452 467 h.route_path('edit_user_group_global_perms',
453 468 user_group_id=user_group_id))
454 469
455 470 @LoginRequired()
456 471 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
457 472 @view_config(
458 473 route_name='edit_user_group_advanced', request_method='GET',
459 474 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
460 475 def user_group_edit_advanced(self):
461 476 user_group = self.db_user_group
462 477
463 478 c = self.load_default_context()
464 479 c.user_group = user_group
465 480 c.active = 'advanced'
466 481 c.group_members_obj = sorted(
467 482 (x.user for x in c.user_group.members),
468 483 key=lambda u: u.username.lower())
469 484
470 485 c.group_to_repos = sorted(
471 486 (x.repository for x in c.user_group.users_group_repo_to_perm),
472 487 key=lambda u: u.repo_name.lower())
473 488
474 489 c.group_to_repo_groups = sorted(
475 490 (x.group for x in c.user_group.users_group_repo_group_to_perm),
476 491 key=lambda u: u.group_name.lower())
477 492
478 493 c.group_to_review_rules = sorted(
479 494 (x.users_group for x in c.user_group.user_group_review_rules),
480 495 key=lambda u: u.users_group_name.lower())
481 496
482 497 return self._get_template_context(c)
483 498
484 499 @LoginRequired()
485 500 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
486 501 @CSRFRequired()
487 502 @view_config(
488 503 route_name='edit_user_group_advanced_sync', request_method='POST',
489 504 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
490 505 def user_group_edit_advanced_set_synchronization(self):
491 506 _ = self.request.translate
492 507 user_group = self.db_user_group
493 508 user_group_id = user_group.users_group_id
494 509
495 510 existing = user_group.group_data.get('extern_type')
496 511
497 512 if existing:
498 513 new_state = user_group.group_data
499 514 new_state['extern_type'] = None
500 515 else:
501 516 new_state = user_group.group_data
502 517 new_state['extern_type'] = 'manual'
503 518 new_state['extern_type_set_by'] = self._rhodecode_user.username
504 519
505 520 try:
506 521 user_group.group_data = new_state
507 522 Session().add(user_group)
508 523 Session().commit()
509 524
510 525 h.flash(_('User Group synchronization updated successfully'),
511 526 category='success')
512 527 except Exception:
513 528 log.exception("Exception during sync settings saving")
514 529 h.flash(_('An error occurred during synchronization update'),
515 530 category='error')
516 531
517 532 raise HTTPFound(
518 533 h.route_path('edit_user_group_advanced',
519 534 user_group_id=user_group_id))
@@ -1,82 +1,84 b''
1 1 # Copyright (C) 2016-2018 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 import logging
20 20 from pyramid.threadlocal import get_current_registry
21 21 from rhodecode.events.base import RhodecodeEvent
22 22
23 23
24 24 log = logging.getLogger(__name__)
25 25
26 26
27 27 def trigger(event, registry=None):
28 28 """
29 29 Helper method to send an event. This wraps the pyramid logic to send an
30 30 event.
31 31 """
32 32 # For the first step we are using pyramids thread locals here. If the
33 33 # event mechanism works out as a good solution we should think about
34 34 # passing the registry as an argument to get rid of it.
35 35 registry = registry or get_current_registry()
36 36 registry.notify(event)
37 37 log.debug('event %s triggered using registry %s', event, registry)
38 38
39 39 # Until we can work around the problem that VCS operations do not have a
40 40 # pyramid context to work with, we send the events to integrations directly
41 41
42 42 # Later it will be possible to use regular pyramid subscribers ie:
43 43 # config.add_subscriber(
44 44 # 'rhodecode.integrations.integrations_event_handler',
45 45 # 'rhodecode.events.RhodecodeEvent')
46 46 # trigger(event, request.registry)
47 47
48 48 from rhodecode.integrations import integrations_event_handler
49 49 if isinstance(event, RhodecodeEvent):
50 50 integrations_event_handler(event)
51 51
52
52 53 from rhodecode.events.user import ( # noqa
53 54 UserPreCreate,
54 55 UserPostCreate,
55 56 UserPreUpdate,
56 UserRegistered
57 UserRegistered,
58 UserPermissionsChange,
57 59 )
58 60
59 61 from rhodecode.events.repo import ( # noqa
60 62 RepoEvent,
61 63 RepoPreCreateEvent, RepoCreateEvent,
62 64 RepoPreDeleteEvent, RepoDeleteEvent,
63 65 RepoPrePushEvent, RepoPushEvent,
64 66 RepoPrePullEvent, RepoPullEvent,
65 67 )
66 68
67 69 from rhodecode.events.repo_group import ( # noqa
68 70 RepoGroupEvent,
69 71 RepoGroupCreateEvent,
70 72 RepoGroupUpdateEvent,
71 73 RepoGroupDeleteEvent,
72 74 )
73 75
74 76 from rhodecode.events.pullrequest import ( # noqa
75 77 PullRequestEvent,
76 78 PullRequestCreateEvent,
77 79 PullRequestUpdateEvent,
78 80 PullRequestCommentEvent,
79 81 PullRequestReviewEvent,
80 82 PullRequestMergeEvent,
81 83 PullRequestCloseEvent,
82 84 )
@@ -1,85 +1,104 b''
1 1 # Copyright (C) 2016-2018 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18 import logging
19 19
20 20 from zope.interface import implementer
21 21
22 22 from rhodecode.translation import lazy_ugettext
23 23 from rhodecode.events.base import RhodecodeEvent
24 24 from rhodecode.events.interfaces import (
25 25 IUserRegistered, IUserPreCreate, IUserPreUpdate)
26 26
27 27 log = logging.getLogger(__name__)
28 28
29 29
30 30 @implementer(IUserRegistered)
31 31 class UserRegistered(RhodecodeEvent):
32 32 """
33 33 An instance of this class is emitted as an :term:`event` whenever a user
34 34 account is registered.
35 35 """
36 36 name = 'user-register'
37 37 display_name = lazy_ugettext('user registered')
38 38
39 39 def __init__(self, user, session):
40 40 super(UserRegistered, self).__init__()
41 41 self.user = user
42 42 self.session = session
43 43
44 44
45 45 @implementer(IUserPreCreate)
46 46 class UserPreCreate(RhodecodeEvent):
47 47 """
48 48 An instance of this class is emitted as an :term:`event` before a new user
49 49 object is created.
50 50 """
51 51 name = 'user-pre-create'
52 52 display_name = lazy_ugettext('user pre create')
53 53
54 54 def __init__(self, user_data):
55 55 super(UserPreCreate, self).__init__()
56 56 self.user_data = user_data
57 57
58 58
59 59 @implementer(IUserPreCreate)
60 60 class UserPostCreate(RhodecodeEvent):
61 61 """
62 62 An instance of this class is emitted as an :term:`event` after a new user
63 63 object is created.
64 64 """
65 65 name = 'user-post-create'
66 66 display_name = lazy_ugettext('user post create')
67 67
68 68 def __init__(self, user_data):
69 69 super(UserPostCreate, self).__init__()
70 70 self.user_data = user_data
71 71
72 72
73 73 @implementer(IUserPreUpdate)
74 74 class UserPreUpdate(RhodecodeEvent):
75 75 """
76 76 An instance of this class is emitted as an :term:`event` before a user
77 77 object is updated.
78 78 """
79 79 name = 'user-pre-update'
80 80 display_name = lazy_ugettext('user pre update')
81 81
82 82 def __init__(self, user, user_data):
83 83 super(UserPreUpdate, self).__init__()
84 84 self.user = user
85 85 self.user_data = user_data
86
87
88 class UserPermissionsChange(RhodecodeEvent):
89 """
90 This event should be triggered on an event that permissions of user might changed.
91 Currently this should be triggered on:
92
93 - user added/removed from user group
94 - repo permissions changed
95 - repo group permissions changed
96 - user group permissions changed
97
98 """
99 name = 'user-permissions-change'
100 display_name = lazy_ugettext('user permissions change')
101
102 def __init__(self, user_ids):
103 super(UserPermissionsChange, self).__init__()
104 self.user_ids = user_ids
@@ -1,327 +1,328 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20 import io
21 21 import re
22 22 import datetime
23 23 import logging
24 24 import Queue
25 25 import subprocess32
26 26 import os
27 27
28 28
29 29 from dateutil.parser import parse
30 30 from pyramid.i18n import get_localizer
31 31 from pyramid.threadlocal import get_current_request
32 32 from pyramid.interfaces import IRoutesMapper
33 33 from pyramid.settings import asbool
34 34 from pyramid.path import AssetResolver
35 35 from threading import Thread
36 36
37 37 from rhodecode.translation import _ as tsf
38 38 from rhodecode.config.jsroutes import generate_jsroutes_content
39 39 from rhodecode.lib import auth
40 40 from rhodecode.lib.base import get_auth_user
41 41
42 42
43 43 import rhodecode
44 44
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 def add_renderer_globals(event):
50 50 from rhodecode.lib import helpers
51 51
52 52 # TODO: When executed in pyramid view context the request is not available
53 53 # in the event. Find a better solution to get the request.
54 54 request = event['request'] or get_current_request()
55 55
56 56 # Add Pyramid translation as '_' to context
57 57 event['_'] = request.translate
58 58 event['_ungettext'] = request.plularize
59 59 event['h'] = helpers
60 60
61 61
62 62 def add_localizer(event):
63 63 request = event.request
64 64 localizer = request.localizer
65 65
66 66 def auto_translate(*args, **kwargs):
67 67 return localizer.translate(tsf(*args, **kwargs))
68 68
69 69 request.translate = auto_translate
70 70 request.plularize = localizer.pluralize
71 71
72 72
73 73 def set_user_lang(event):
74 74 request = event.request
75 75 cur_user = getattr(request, 'user', None)
76 76
77 77 if cur_user:
78 78 user_lang = cur_user.get_instance().user_data.get('language')
79 79 if user_lang:
80 80 log.debug('lang: setting current user:%s language to: %s', cur_user, user_lang)
81 81 event.request._LOCALE_ = user_lang
82 82
83 83
84 84 def add_request_user_context(event):
85 85 """
86 86 Adds auth user into request context
87 87 """
88 88 request = event.request
89 89 # access req_id as soon as possible
90 90 req_id = request.req_id
91 91
92 92 if hasattr(request, 'vcs_call'):
93 93 # skip vcs calls
94 94 return
95 95
96 96 if hasattr(request, 'rpc_method'):
97 97 # skip api calls
98 98 return
99 99
100 100 auth_user = get_auth_user(request)
101 101 request.user = auth_user
102 102 request.environ['rc_auth_user'] = auth_user
103 103 request.environ['rc_req_id'] = req_id
104 104
105
105 106 def inject_app_settings(event):
106 107 settings = event.app.registry.settings
107 108 # inject info about available permissions
108 109 auth.set_available_permissions(settings)
109 110
110 111
111 112 def scan_repositories_if_enabled(event):
112 113 """
113 114 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
114 115 does a repository scan if enabled in the settings.
115 116 """
116 117 settings = event.app.registry.settings
117 118 vcs_server_enabled = settings['vcs.server.enable']
118 119 import_on_startup = settings['startup.import_repos']
119 120 if vcs_server_enabled and import_on_startup:
120 121 from rhodecode.model.scm import ScmModel
121 122 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_base_path
122 123 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
123 124 repo2db_mapper(repositories, remove_obsolete=False)
124 125
125 126
126 127 def write_metadata_if_needed(event):
127 128 """
128 129 Writes upgrade metadata
129 130 """
130 131 import rhodecode
131 132 from rhodecode.lib import system_info
132 133 from rhodecode.lib import ext_json
133 134
134 135 fname = '.rcmetadata.json'
135 136 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
136 137 metadata_destination = os.path.join(ini_loc, fname)
137 138
138 139 def get_update_age():
139 140 now = datetime.datetime.utcnow()
140 141
141 142 with open(metadata_destination, 'rb') as f:
142 143 data = ext_json.json.loads(f.read())
143 144 if 'created_on' in data:
144 145 update_date = parse(data['created_on'])
145 146 diff = now - update_date
146 147 return diff.total_seconds() / 60.0
147 148
148 149 return 0
149 150
150 151 def write():
151 152 configuration = system_info.SysInfo(
152 153 system_info.rhodecode_config)()['value']
153 154 license_token = configuration['config']['license_token']
154 155
155 156 setup = dict(
156 157 workers=configuration['config']['server:main'].get(
157 158 'workers', '?'),
158 159 worker_type=configuration['config']['server:main'].get(
159 160 'worker_class', 'sync'),
160 161 )
161 162 dbinfo = system_info.SysInfo(system_info.database_info)()['value']
162 163 del dbinfo['url']
163 164
164 165 metadata = dict(
165 166 desc='upgrade metadata info',
166 167 license_token=license_token,
167 168 created_on=datetime.datetime.utcnow().isoformat(),
168 169 usage=system_info.SysInfo(system_info.usage_info)()['value'],
169 170 platform=system_info.SysInfo(system_info.platform_type)()['value'],
170 171 database=dbinfo,
171 172 cpu=system_info.SysInfo(system_info.cpu)()['value'],
172 173 memory=system_info.SysInfo(system_info.memory)()['value'],
173 174 setup=setup
174 175 )
175 176
176 177 with open(metadata_destination, 'wb') as f:
177 178 f.write(ext_json.json.dumps(metadata))
178 179
179 180 settings = event.app.registry.settings
180 181 if settings.get('metadata.skip'):
181 182 return
182 183
183 184 # only write this every 24h, workers restart caused unwanted delays
184 185 try:
185 186 age_in_min = get_update_age()
186 187 except Exception:
187 188 age_in_min = 0
188 189
189 190 if age_in_min > 60 * 60 * 24:
190 191 return
191 192
192 193 try:
193 194 write()
194 195 except Exception:
195 196 pass
196 197
197 198
198 199 def write_js_routes_if_enabled(event):
199 200 registry = event.app.registry
200 201
201 202 mapper = registry.queryUtility(IRoutesMapper)
202 203 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
203 204
204 205 def _extract_route_information(route):
205 206 """
206 207 Convert a route into tuple(name, path, args), eg:
207 208 ('show_user', '/profile/%(username)s', ['username'])
208 209 """
209 210
210 211 routepath = route.pattern
211 212 pattern = route.pattern
212 213
213 214 def replace(matchobj):
214 215 if matchobj.group(1):
215 216 return "%%(%s)s" % matchobj.group(1).split(':')[0]
216 217 else:
217 218 return "%%(%s)s" % matchobj.group(2)
218 219
219 220 routepath = _argument_prog.sub(replace, routepath)
220 221
221 222 if not routepath.startswith('/'):
222 223 routepath = '/'+routepath
223 224
224 225 return (
225 226 route.name,
226 227 routepath,
227 228 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
228 229 for arg in _argument_prog.findall(pattern)]
229 230 )
230 231
231 232 def get_routes():
232 233 # pyramid routes
233 234 for route in mapper.get_routes():
234 235 if not route.name.startswith('__'):
235 236 yield _extract_route_information(route)
236 237
237 238 if asbool(registry.settings.get('generate_js_files', 'false')):
238 239 static_path = AssetResolver().resolve('rhodecode:public').abspath()
239 240 jsroutes = get_routes()
240 241 jsroutes_file_content = generate_jsroutes_content(jsroutes)
241 242 jsroutes_file_path = os.path.join(
242 243 static_path, 'js', 'rhodecode', 'routes.js')
243 244
244 245 try:
245 246 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
246 247 f.write(jsroutes_file_content)
247 248 except Exception:
248 249 log.exception('Failed to write routes.js into %s', jsroutes_file_path)
249 250
250 251
251 252 class Subscriber(object):
252 253 """
253 254 Base class for subscribers to the pyramid event system.
254 255 """
255 256 def __call__(self, event):
256 257 self.run(event)
257 258
258 259 def run(self, event):
259 260 raise NotImplementedError('Subclass has to implement this.')
260 261
261 262
262 263 class AsyncSubscriber(Subscriber):
263 264 """
264 265 Subscriber that handles the execution of events in a separate task to not
265 266 block the execution of the code which triggers the event. It puts the
266 267 received events into a queue from which the worker process takes them in
267 268 order.
268 269 """
269 270 def __init__(self):
270 271 self._stop = False
271 272 self._eventq = Queue.Queue()
272 273 self._worker = self.create_worker()
273 274 self._worker.start()
274 275
275 276 def __call__(self, event):
276 277 self._eventq.put(event)
277 278
278 279 def create_worker(self):
279 280 worker = Thread(target=self.do_work)
280 281 worker.daemon = True
281 282 return worker
282 283
283 284 def stop_worker(self):
284 285 self._stop = False
285 286 self._eventq.put(None)
286 287 self._worker.join()
287 288
288 289 def do_work(self):
289 290 while not self._stop:
290 291 event = self._eventq.get()
291 292 if event is not None:
292 293 self.run(event)
293 294
294 295
295 296 class AsyncSubprocessSubscriber(AsyncSubscriber):
296 297 """
297 298 Subscriber that uses the subprocess32 module to execute a command if an
298 299 event is received. Events are handled asynchronously.
299 300 """
300 301
301 302 def __init__(self, cmd, timeout=None):
302 303 super(AsyncSubprocessSubscriber, self).__init__()
303 304 self._cmd = cmd
304 305 self._timeout = timeout
305 306
306 307 def run(self, event):
307 308 cmd = self._cmd
308 309 timeout = self._timeout
309 310 log.debug('Executing command %s.', cmd)
310 311
311 312 try:
312 313 output = subprocess32.check_output(
313 314 cmd, timeout=timeout, stderr=subprocess32.STDOUT)
314 315 log.debug('Command finished %s', cmd)
315 316 if output:
316 317 log.debug('Command output: %s', output)
317 318 except subprocess32.TimeoutExpired as e:
318 319 log.exception('Timeout while executing command.')
319 320 if e.output:
320 321 log.error('Command output: %s', e.output)
321 322 except subprocess32.CalledProcessError as e:
322 323 log.exception('Error while executing command.')
323 324 if e.output:
324 325 log.error('Command output: %s', e.output)
325 326 except:
326 327 log.exception(
327 328 'Exception while executing command %s.', cmd)
General Comments 0
You need to be logged in to leave comments. Login now