##// END OF EJS Templates
audit-logs: expose download user audit logs as JSON file....
ergo -
r3970:36c4e038 default
parent child Browse files
Show More
@@ -1,450 +1,454 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 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_exception_tracker',
64 64 pattern='/settings/exceptions')
65 65 config.add_route(
66 66 name='admin_settings_exception_tracker_delete_all',
67 67 pattern='/settings/exceptions/delete')
68 68 config.add_route(
69 69 name='admin_settings_exception_tracker_show',
70 70 pattern='/settings/exceptions/{exception_id}')
71 71 config.add_route(
72 72 name='admin_settings_exception_tracker_delete',
73 73 pattern='/settings/exceptions/{exception_id}/delete')
74 74
75 75 config.add_route(
76 76 name='admin_settings_sessions',
77 77 pattern='/settings/sessions')
78 78 config.add_route(
79 79 name='admin_settings_sessions_cleanup',
80 80 pattern='/settings/sessions/cleanup')
81 81
82 82 config.add_route(
83 83 name='admin_settings_process_management',
84 84 pattern='/settings/process_management')
85 85 config.add_route(
86 86 name='admin_settings_process_management_data',
87 87 pattern='/settings/process_management/data')
88 88 config.add_route(
89 89 name='admin_settings_process_management_signal',
90 90 pattern='/settings/process_management/signal')
91 91 config.add_route(
92 92 name='admin_settings_process_management_master_signal',
93 93 pattern='/settings/process_management/master_signal')
94 94
95 95 # default settings
96 96 config.add_route(
97 97 name='admin_defaults_repositories',
98 98 pattern='/defaults/repositories')
99 99 config.add_route(
100 100 name='admin_defaults_repositories_update',
101 101 pattern='/defaults/repositories/update')
102 102
103 103 # admin settings
104 104
105 105 config.add_route(
106 106 name='admin_settings',
107 107 pattern='/settings')
108 108 config.add_route(
109 109 name='admin_settings_update',
110 110 pattern='/settings/update')
111 111
112 112 config.add_route(
113 113 name='admin_settings_global',
114 114 pattern='/settings/global')
115 115 config.add_route(
116 116 name='admin_settings_global_update',
117 117 pattern='/settings/global/update')
118 118
119 119 config.add_route(
120 120 name='admin_settings_vcs',
121 121 pattern='/settings/vcs')
122 122 config.add_route(
123 123 name='admin_settings_vcs_update',
124 124 pattern='/settings/vcs/update')
125 125 config.add_route(
126 126 name='admin_settings_vcs_svn_pattern_delete',
127 127 pattern='/settings/vcs/svn_pattern_delete')
128 128
129 129 config.add_route(
130 130 name='admin_settings_mapping',
131 131 pattern='/settings/mapping')
132 132 config.add_route(
133 133 name='admin_settings_mapping_update',
134 134 pattern='/settings/mapping/update')
135 135
136 136 config.add_route(
137 137 name='admin_settings_visual',
138 138 pattern='/settings/visual')
139 139 config.add_route(
140 140 name='admin_settings_visual_update',
141 141 pattern='/settings/visual/update')
142 142
143 143
144 144 config.add_route(
145 145 name='admin_settings_issuetracker',
146 146 pattern='/settings/issue-tracker')
147 147 config.add_route(
148 148 name='admin_settings_issuetracker_update',
149 149 pattern='/settings/issue-tracker/update')
150 150 config.add_route(
151 151 name='admin_settings_issuetracker_test',
152 152 pattern='/settings/issue-tracker/test')
153 153 config.add_route(
154 154 name='admin_settings_issuetracker_delete',
155 155 pattern='/settings/issue-tracker/delete')
156 156
157 157 config.add_route(
158 158 name='admin_settings_email',
159 159 pattern='/settings/email')
160 160 config.add_route(
161 161 name='admin_settings_email_update',
162 162 pattern='/settings/email/update')
163 163
164 164 config.add_route(
165 165 name='admin_settings_hooks',
166 166 pattern='/settings/hooks')
167 167 config.add_route(
168 168 name='admin_settings_hooks_update',
169 169 pattern='/settings/hooks/update')
170 170 config.add_route(
171 171 name='admin_settings_hooks_delete',
172 172 pattern='/settings/hooks/delete')
173 173
174 174 config.add_route(
175 175 name='admin_settings_search',
176 176 pattern='/settings/search')
177 177
178 178 config.add_route(
179 179 name='admin_settings_labs',
180 180 pattern='/settings/labs')
181 181 config.add_route(
182 182 name='admin_settings_labs_update',
183 183 pattern='/settings/labs/update')
184 184
185 185 # Automation EE feature
186 186 config.add_route(
187 187 'admin_settings_automation',
188 188 pattern=ADMIN_PREFIX + '/settings/automation')
189 189
190 190 # global permissions
191 191
192 192 config.add_route(
193 193 name='admin_permissions_application',
194 194 pattern='/permissions/application')
195 195 config.add_route(
196 196 name='admin_permissions_application_update',
197 197 pattern='/permissions/application/update')
198 198
199 199 config.add_route(
200 200 name='admin_permissions_global',
201 201 pattern='/permissions/global')
202 202 config.add_route(
203 203 name='admin_permissions_global_update',
204 204 pattern='/permissions/global/update')
205 205
206 206 config.add_route(
207 207 name='admin_permissions_object',
208 208 pattern='/permissions/object')
209 209 config.add_route(
210 210 name='admin_permissions_object_update',
211 211 pattern='/permissions/object/update')
212 212
213 213 # Branch perms EE feature
214 214 config.add_route(
215 215 name='admin_permissions_branch',
216 216 pattern='/permissions/branch')
217 217
218 218 config.add_route(
219 219 name='admin_permissions_ips',
220 220 pattern='/permissions/ips')
221 221
222 222 config.add_route(
223 223 name='admin_permissions_overview',
224 224 pattern='/permissions/overview')
225 225
226 226 config.add_route(
227 227 name='admin_permissions_auth_token_access',
228 228 pattern='/permissions/auth_token_access')
229 229
230 230 config.add_route(
231 231 name='admin_permissions_ssh_keys',
232 232 pattern='/permissions/ssh_keys')
233 233 config.add_route(
234 234 name='admin_permissions_ssh_keys_data',
235 235 pattern='/permissions/ssh_keys/data')
236 236 config.add_route(
237 237 name='admin_permissions_ssh_keys_update',
238 238 pattern='/permissions/ssh_keys/update')
239 239
240 240 # users admin
241 241 config.add_route(
242 242 name='users',
243 243 pattern='/users')
244 244
245 245 config.add_route(
246 246 name='users_data',
247 247 pattern='/users_data')
248 248
249 249 config.add_route(
250 250 name='users_create',
251 251 pattern='/users/create')
252 252
253 253 config.add_route(
254 254 name='users_new',
255 255 pattern='/users/new')
256 256
257 257 # user management
258 258 config.add_route(
259 259 name='user_edit',
260 260 pattern='/users/{user_id:\d+}/edit',
261 261 user_route=True)
262 262 config.add_route(
263 263 name='user_edit_advanced',
264 264 pattern='/users/{user_id:\d+}/edit/advanced',
265 265 user_route=True)
266 266 config.add_route(
267 267 name='user_edit_global_perms',
268 268 pattern='/users/{user_id:\d+}/edit/global_permissions',
269 269 user_route=True)
270 270 config.add_route(
271 271 name='user_edit_global_perms_update',
272 272 pattern='/users/{user_id:\d+}/edit/global_permissions/update',
273 273 user_route=True)
274 274 config.add_route(
275 275 name='user_update',
276 276 pattern='/users/{user_id:\d+}/update',
277 277 user_route=True)
278 278 config.add_route(
279 279 name='user_delete',
280 280 pattern='/users/{user_id:\d+}/delete',
281 281 user_route=True)
282 282 config.add_route(
283 283 name='user_enable_force_password_reset',
284 284 pattern='/users/{user_id:\d+}/password_reset_enable',
285 285 user_route=True)
286 286 config.add_route(
287 287 name='user_disable_force_password_reset',
288 288 pattern='/users/{user_id:\d+}/password_reset_disable',
289 289 user_route=True)
290 290 config.add_route(
291 291 name='user_create_personal_repo_group',
292 292 pattern='/users/{user_id:\d+}/create_repo_group',
293 293 user_route=True)
294 294
295 295 # user auth tokens
296 296 config.add_route(
297 297 name='edit_user_auth_tokens',
298 298 pattern='/users/{user_id:\d+}/edit/auth_tokens',
299 299 user_route=True)
300 300 config.add_route(
301 301 name='edit_user_auth_tokens_add',
302 302 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
303 303 user_route=True)
304 304 config.add_route(
305 305 name='edit_user_auth_tokens_delete',
306 306 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete',
307 307 user_route=True)
308 308
309 309 # user ssh keys
310 310 config.add_route(
311 311 name='edit_user_ssh_keys',
312 312 pattern='/users/{user_id:\d+}/edit/ssh_keys',
313 313 user_route=True)
314 314 config.add_route(
315 315 name='edit_user_ssh_keys_generate_keypair',
316 316 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate',
317 317 user_route=True)
318 318 config.add_route(
319 319 name='edit_user_ssh_keys_add',
320 320 pattern='/users/{user_id:\d+}/edit/ssh_keys/new',
321 321 user_route=True)
322 322 config.add_route(
323 323 name='edit_user_ssh_keys_delete',
324 324 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete',
325 325 user_route=True)
326 326
327 327 # user emails
328 328 config.add_route(
329 329 name='edit_user_emails',
330 330 pattern='/users/{user_id:\d+}/edit/emails',
331 331 user_route=True)
332 332 config.add_route(
333 333 name='edit_user_emails_add',
334 334 pattern='/users/{user_id:\d+}/edit/emails/new',
335 335 user_route=True)
336 336 config.add_route(
337 337 name='edit_user_emails_delete',
338 338 pattern='/users/{user_id:\d+}/edit/emails/delete',
339 339 user_route=True)
340 340
341 341 # user IPs
342 342 config.add_route(
343 343 name='edit_user_ips',
344 344 pattern='/users/{user_id:\d+}/edit/ips',
345 345 user_route=True)
346 346 config.add_route(
347 347 name='edit_user_ips_add',
348 348 pattern='/users/{user_id:\d+}/edit/ips/new',
349 349 user_route_with_default=True) # enabled for default user too
350 350 config.add_route(
351 351 name='edit_user_ips_delete',
352 352 pattern='/users/{user_id:\d+}/edit/ips/delete',
353 353 user_route_with_default=True) # enabled for default user too
354 354
355 355 # user perms
356 356 config.add_route(
357 357 name='edit_user_perms_summary',
358 358 pattern='/users/{user_id:\d+}/edit/permissions_summary',
359 359 user_route=True)
360 360 config.add_route(
361 361 name='edit_user_perms_summary_json',
362 362 pattern='/users/{user_id:\d+}/edit/permissions_summary/json',
363 363 user_route=True)
364 364
365 365 # user user groups management
366 366 config.add_route(
367 367 name='edit_user_groups_management',
368 368 pattern='/users/{user_id:\d+}/edit/groups_management',
369 369 user_route=True)
370 370
371 371 config.add_route(
372 372 name='edit_user_groups_management_updates',
373 373 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
374 374 user_route=True)
375 375
376 376 # user audit logs
377 377 config.add_route(
378 378 name='edit_user_audit_logs',
379 379 pattern='/users/{user_id:\d+}/edit/audit', user_route=True)
380 380
381 config.add_route(
382 name='edit_user_audit_logs_download',
383 pattern='/users/{user_id:\d+}/edit/audit/download', user_route=True)
384
381 385 # user caches
382 386 config.add_route(
383 387 name='edit_user_caches',
384 388 pattern='/users/{user_id:\d+}/edit/caches',
385 389 user_route=True)
386 390 config.add_route(
387 391 name='edit_user_caches_update',
388 392 pattern='/users/{user_id:\d+}/edit/caches/update',
389 393 user_route=True)
390 394
391 395 # user-groups admin
392 396 config.add_route(
393 397 name='user_groups',
394 398 pattern='/user_groups')
395 399
396 400 config.add_route(
397 401 name='user_groups_data',
398 402 pattern='/user_groups_data')
399 403
400 404 config.add_route(
401 405 name='user_groups_new',
402 406 pattern='/user_groups/new')
403 407
404 408 config.add_route(
405 409 name='user_groups_create',
406 410 pattern='/user_groups/create')
407 411
408 412 # repos admin
409 413 config.add_route(
410 414 name='repos',
411 415 pattern='/repos')
412 416
413 417 config.add_route(
414 418 name='repo_new',
415 419 pattern='/repos/new')
416 420
417 421 config.add_route(
418 422 name='repo_create',
419 423 pattern='/repos/create')
420 424
421 425 # repo groups admin
422 426 config.add_route(
423 427 name='repo_groups',
424 428 pattern='/repo_groups')
425 429
426 430 config.add_route(
427 431 name='repo_groups_data',
428 432 pattern='/repo_groups_data')
429 433
430 434 config.add_route(
431 435 name='repo_group_new',
432 436 pattern='/repo_group/new')
433 437
434 438 config.add_route(
435 439 name='repo_group_create',
436 440 pattern='/repo_group/create')
437 441
438 442
439 443 def includeme(config):
440 444 from rhodecode.apps._base.navigation import includeme as nav_includeme
441 445
442 446 # Create admin navigation registry and add it to the pyramid registry.
443 447 nav_includeme(config)
444 448
445 449 # main admin routes
446 450 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
447 451 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
448 452
449 453 # Scan module for configuration decorators.
450 454 config.scan('.views', ignore='.tests')
@@ -1,781 +1,790 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 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 pytest
22 22 from sqlalchemy.orm.exc import NoResultFound
23 23
24 24 from rhodecode.lib import auth
25 25 from rhodecode.lib import helpers as h
26 26 from rhodecode.model.db import User, UserApiKeys, UserEmailMap, Repository
27 27 from rhodecode.model.meta import Session
28 28 from rhodecode.model.user import UserModel
29 29
30 30 from rhodecode.tests import (
31 31 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
32 32 from rhodecode.tests.fixture import Fixture
33 33
34 34 fixture = Fixture()
35 35
36 36
37 37 def route_path(name, params=None, **kwargs):
38 38 import urllib
39 39 from rhodecode.apps._base import ADMIN_PREFIX
40 40
41 41 base_url = {
42 42 'users':
43 43 ADMIN_PREFIX + '/users',
44 44 'users_data':
45 45 ADMIN_PREFIX + '/users_data',
46 46 'users_create':
47 47 ADMIN_PREFIX + '/users/create',
48 48 'users_new':
49 49 ADMIN_PREFIX + '/users/new',
50 50 'user_edit':
51 51 ADMIN_PREFIX + '/users/{user_id}/edit',
52 52 'user_edit_advanced':
53 53 ADMIN_PREFIX + '/users/{user_id}/edit/advanced',
54 54 'user_edit_global_perms':
55 55 ADMIN_PREFIX + '/users/{user_id}/edit/global_permissions',
56 56 'user_edit_global_perms_update':
57 57 ADMIN_PREFIX + '/users/{user_id}/edit/global_permissions/update',
58 58 'user_update':
59 59 ADMIN_PREFIX + '/users/{user_id}/update',
60 60 'user_delete':
61 61 ADMIN_PREFIX + '/users/{user_id}/delete',
62 62 'user_create_personal_repo_group':
63 63 ADMIN_PREFIX + '/users/{user_id}/create_repo_group',
64 64
65 65 'edit_user_auth_tokens':
66 66 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens',
67 67 'edit_user_auth_tokens_add':
68 68 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/new',
69 69 'edit_user_auth_tokens_delete':
70 70 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/delete',
71 71
72 72 'edit_user_emails':
73 73 ADMIN_PREFIX + '/users/{user_id}/edit/emails',
74 74 'edit_user_emails_add':
75 75 ADMIN_PREFIX + '/users/{user_id}/edit/emails/new',
76 76 'edit_user_emails_delete':
77 77 ADMIN_PREFIX + '/users/{user_id}/edit/emails/delete',
78 78
79 79 'edit_user_ips':
80 80 ADMIN_PREFIX + '/users/{user_id}/edit/ips',
81 81 'edit_user_ips_add':
82 82 ADMIN_PREFIX + '/users/{user_id}/edit/ips/new',
83 83 'edit_user_ips_delete':
84 84 ADMIN_PREFIX + '/users/{user_id}/edit/ips/delete',
85 85
86 86 'edit_user_perms_summary':
87 87 ADMIN_PREFIX + '/users/{user_id}/edit/permissions_summary',
88 88 'edit_user_perms_summary_json':
89 89 ADMIN_PREFIX + '/users/{user_id}/edit/permissions_summary/json',
90 90
91 91 'edit_user_audit_logs':
92 92 ADMIN_PREFIX + '/users/{user_id}/edit/audit',
93 93
94 'edit_user_audit_logs_download':
95 ADMIN_PREFIX + '/users/{user_id}/edit/audit/download',
96
94 97 }[name].format(**kwargs)
95 98
96 99 if params:
97 100 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
98 101 return base_url
99 102
100 103
101 104 class TestAdminUsersView(TestController):
102 105
103 106 def test_show_users(self):
104 107 self.log_user()
105 108 self.app.get(route_path('users'))
106 109
107 110 def test_show_users_data(self, xhr_header):
108 111 self.log_user()
109 112 response = self.app.get(route_path(
110 113 'users_data'), extra_environ=xhr_header)
111 114
112 115 all_users = User.query().filter(
113 116 User.username != User.DEFAULT_USER).count()
114 117 assert response.json['recordsTotal'] == all_users
115 118
116 119 def test_show_users_data_filtered(self, xhr_header):
117 120 self.log_user()
118 121 response = self.app.get(route_path(
119 122 'users_data', params={'search[value]': 'empty_search'}),
120 123 extra_environ=xhr_header)
121 124
122 125 all_users = User.query().filter(
123 126 User.username != User.DEFAULT_USER).count()
124 127 assert response.json['recordsTotal'] == all_users
125 128 assert response.json['recordsFiltered'] == 0
126 129
127 130 def test_auth_tokens_default_user(self):
128 131 self.log_user()
129 132 user = User.get_default_user()
130 133 response = self.app.get(
131 134 route_path('edit_user_auth_tokens', user_id=user.user_id),
132 135 status=302)
133 136
134 137 def test_auth_tokens(self):
135 138 self.log_user()
136 139
137 140 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
138 141 user_id = user.user_id
139 142 auth_tokens = user.auth_tokens
140 143 response = self.app.get(
141 144 route_path('edit_user_auth_tokens', user_id=user_id))
142 145 for token in auth_tokens:
143 146 response.mustcontain(token)
144 147 response.mustcontain('never')
145 148
146 149 @pytest.mark.parametrize("desc, lifetime", [
147 150 ('forever', -1),
148 151 ('5mins', 60*5),
149 152 ('30days', 60*60*24*30),
150 153 ])
151 154 def test_add_auth_token(self, desc, lifetime, user_util):
152 155 self.log_user()
153 156 user = user_util.create_user()
154 157 user_id = user.user_id
155 158
156 159 response = self.app.post(
157 160 route_path('edit_user_auth_tokens_add', user_id=user_id),
158 161 {'description': desc, 'lifetime': lifetime,
159 162 'csrf_token': self.csrf_token})
160 163 assert_session_flash(response, 'Auth token successfully created')
161 164
162 165 response = response.follow()
163 166 user = User.get(user_id)
164 167 for auth_token in user.auth_tokens:
165 168 response.mustcontain(auth_token)
166 169
167 170 def test_delete_auth_token(self, user_util):
168 171 self.log_user()
169 172 user = user_util.create_user()
170 173 user_id = user.user_id
171 174 keys = user.auth_tokens
172 175 assert 2 == len(keys)
173 176
174 177 response = self.app.post(
175 178 route_path('edit_user_auth_tokens_add', user_id=user_id),
176 179 {'description': 'desc', 'lifetime': -1,
177 180 'csrf_token': self.csrf_token})
178 181 assert_session_flash(response, 'Auth token successfully created')
179 182 response.follow()
180 183
181 184 # now delete our key
182 185 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
183 186 assert 3 == len(keys)
184 187
185 188 response = self.app.post(
186 189 route_path('edit_user_auth_tokens_delete', user_id=user_id),
187 190 {'del_auth_token': keys[0].user_api_key_id,
188 191 'csrf_token': self.csrf_token})
189 192
190 193 assert_session_flash(response, 'Auth token successfully deleted')
191 194 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
192 195 assert 2 == len(keys)
193 196
194 197 def test_ips(self):
195 198 self.log_user()
196 199 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
197 200 response = self.app.get(route_path('edit_user_ips', user_id=user.user_id))
198 201 response.mustcontain('All IP addresses are allowed')
199 202
200 203 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
201 204 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
202 205 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
203 206 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
204 207 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
205 208 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
206 209 ('127_bad_ip', 'foobar', 'foobar', True),
207 210 ])
208 211 def test_ips_add(self, user_util, test_name, ip, ip_range, failure):
209 212 self.log_user()
210 213 user = user_util.create_user(username=test_name)
211 214 user_id = user.user_id
212 215
213 216 response = self.app.post(
214 217 route_path('edit_user_ips_add', user_id=user_id),
215 218 params={'new_ip': ip, 'csrf_token': self.csrf_token})
216 219
217 220 if failure:
218 221 assert_session_flash(
219 222 response, 'Please enter a valid IPv4 or IpV6 address')
220 223 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
221 224
222 225 response.mustcontain(no=[ip])
223 226 response.mustcontain(no=[ip_range])
224 227
225 228 else:
226 229 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
227 230 response.mustcontain(ip)
228 231 response.mustcontain(ip_range)
229 232
230 233 def test_ips_delete(self, user_util):
231 234 self.log_user()
232 235 user = user_util.create_user()
233 236 user_id = user.user_id
234 237 ip = '127.0.0.1/32'
235 238 ip_range = '127.0.0.1 - 127.0.0.1'
236 239 new_ip = UserModel().add_extra_ip(user_id, ip)
237 240 Session().commit()
238 241 new_ip_id = new_ip.ip_id
239 242
240 243 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
241 244 response.mustcontain(ip)
242 245 response.mustcontain(ip_range)
243 246
244 247 self.app.post(
245 248 route_path('edit_user_ips_delete', user_id=user_id),
246 249 params={'del_ip_id': new_ip_id, 'csrf_token': self.csrf_token})
247 250
248 251 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
249 252 response.mustcontain('All IP addresses are allowed')
250 253 response.mustcontain(no=[ip])
251 254 response.mustcontain(no=[ip_range])
252 255
253 256 def test_emails(self):
254 257 self.log_user()
255 258 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
256 259 response = self.app.get(
257 260 route_path('edit_user_emails', user_id=user.user_id))
258 261 response.mustcontain('No additional emails specified')
259 262
260 263 def test_emails_add(self, user_util):
261 264 self.log_user()
262 265 user = user_util.create_user()
263 266 user_id = user.user_id
264 267
265 268 self.app.post(
266 269 route_path('edit_user_emails_add', user_id=user_id),
267 270 params={'new_email': 'example@rhodecode.com',
268 271 'csrf_token': self.csrf_token})
269 272
270 273 response = self.app.get(
271 274 route_path('edit_user_emails', user_id=user_id))
272 275 response.mustcontain('example@rhodecode.com')
273 276
274 277 def test_emails_add_existing_email(self, user_util, user_regular):
275 278 existing_email = user_regular.email
276 279
277 280 self.log_user()
278 281 user = user_util.create_user()
279 282 user_id = user.user_id
280 283
281 284 response = self.app.post(
282 285 route_path('edit_user_emails_add', user_id=user_id),
283 286 params={'new_email': existing_email,
284 287 'csrf_token': self.csrf_token})
285 288 assert_session_flash(
286 289 response, 'This e-mail address is already taken')
287 290
288 291 response = self.app.get(
289 292 route_path('edit_user_emails', user_id=user_id))
290 293 response.mustcontain(no=[existing_email])
291 294
292 295 def test_emails_delete(self, user_util):
293 296 self.log_user()
294 297 user = user_util.create_user()
295 298 user_id = user.user_id
296 299
297 300 self.app.post(
298 301 route_path('edit_user_emails_add', user_id=user_id),
299 302 params={'new_email': 'example@rhodecode.com',
300 303 'csrf_token': self.csrf_token})
301 304
302 305 response = self.app.get(
303 306 route_path('edit_user_emails', user_id=user_id))
304 307 response.mustcontain('example@rhodecode.com')
305 308
306 309 user_email = UserEmailMap.query()\
307 310 .filter(UserEmailMap.email == 'example@rhodecode.com') \
308 311 .filter(UserEmailMap.user_id == user_id)\
309 312 .one()
310 313
311 314 del_email_id = user_email.email_id
312 315 self.app.post(
313 316 route_path('edit_user_emails_delete', user_id=user_id),
314 317 params={'del_email_id': del_email_id,
315 318 'csrf_token': self.csrf_token})
316 319
317 320 response = self.app.get(
318 321 route_path('edit_user_emails', user_id=user_id))
319 322 response.mustcontain(no=['example@rhodecode.com'])
320 323
321
322 324 def test_create(self, request, xhr_header):
323 325 self.log_user()
324 326 username = 'newtestuser'
325 327 password = 'test12'
326 328 password_confirmation = password
327 329 name = 'name'
328 330 lastname = 'lastname'
329 331 email = 'mail@mail.com'
330 332
331 333 self.app.get(route_path('users_new'))
332 334
333 335 response = self.app.post(route_path('users_create'), params={
334 336 'username': username,
335 337 'password': password,
336 338 'password_confirmation': password_confirmation,
337 339 'firstname': name,
338 340 'active': True,
339 341 'lastname': lastname,
340 342 'extern_name': 'rhodecode',
341 343 'extern_type': 'rhodecode',
342 344 'email': email,
343 345 'csrf_token': self.csrf_token,
344 346 })
345 347 user_link = h.link_to(
346 348 username,
347 349 route_path(
348 350 'user_edit', user_id=User.get_by_username(username).user_id))
349 351 assert_session_flash(response, 'Created user %s' % (user_link,))
350 352
351 353 @request.addfinalizer
352 354 def cleanup():
353 355 fixture.destroy_user(username)
354 356 Session().commit()
355 357
356 358 new_user = User.query().filter(User.username == username).one()
357 359
358 360 assert new_user.username == username
359 361 assert auth.check_password(password, new_user.password)
360 362 assert new_user.name == name
361 363 assert new_user.lastname == lastname
362 364 assert new_user.email == email
363 365
364 366 response = self.app.get(route_path('users_data'),
365 367 extra_environ=xhr_header)
366 368 response.mustcontain(username)
367 369
368 370 def test_create_err(self):
369 371 self.log_user()
370 372 username = 'new_user'
371 373 password = ''
372 374 name = 'name'
373 375 lastname = 'lastname'
374 376 email = 'errmail.com'
375 377
376 378 self.app.get(route_path('users_new'))
377 379
378 380 response = self.app.post(route_path('users_create'), params={
379 381 'username': username,
380 382 'password': password,
381 383 'name': name,
382 384 'active': False,
383 385 'lastname': lastname,
384 386 'email': email,
385 387 'csrf_token': self.csrf_token,
386 388 })
387 389
388 390 msg = u'Username "%(username)s" is forbidden'
389 391 msg = h.html_escape(msg % {'username': 'new_user'})
390 392 response.mustcontain('<span class="error-message">%s</span>' % msg)
391 393 response.mustcontain(
392 394 '<span class="error-message">Please enter a value</span>')
393 395 response.mustcontain(
394 396 '<span class="error-message">An email address must contain a'
395 397 ' single @</span>')
396 398
397 399 def get_user():
398 400 Session().query(User).filter(User.username == username).one()
399 401
400 402 with pytest.raises(NoResultFound):
401 403 get_user()
402 404
403 405 def test_new(self):
404 406 self.log_user()
405 407 self.app.get(route_path('users_new'))
406 408
407 409 @pytest.mark.parametrize("name, attrs", [
408 410 ('firstname', {'firstname': 'new_username'}),
409 411 ('lastname', {'lastname': 'new_username'}),
410 412 ('admin', {'admin': True}),
411 413 ('admin', {'admin': False}),
412 414 ('extern_type', {'extern_type': 'ldap'}),
413 415 ('extern_type', {'extern_type': None}),
414 416 ('extern_name', {'extern_name': 'test'}),
415 417 ('extern_name', {'extern_name': None}),
416 418 ('active', {'active': False}),
417 419 ('active', {'active': True}),
418 420 ('email', {'email': 'some@email.com'}),
419 421 ('language', {'language': 'de'}),
420 422 ('language', {'language': 'en'}),
421 423 # ('new_password', {'new_password': 'foobar123',
422 424 # 'password_confirmation': 'foobar123'})
423 425 ])
424 426 def test_update(self, name, attrs, user_util):
425 427 self.log_user()
426 428 usr = user_util.create_user(
427 429 password='qweqwe',
428 430 email='testme@rhodecode.org',
429 431 extern_type='rhodecode',
430 432 extern_name='xxx',
431 433 )
432 434 user_id = usr.user_id
433 435 Session().commit()
434 436
435 437 params = usr.get_api_data()
436 438 cur_lang = params['language'] or 'en'
437 439 params.update({
438 440 'password_confirmation': '',
439 441 'new_password': '',
440 442 'language': cur_lang,
441 443 'csrf_token': self.csrf_token,
442 444 })
443 445 params.update({'new_password': ''})
444 446 params.update(attrs)
445 447 if name == 'email':
446 448 params['emails'] = [attrs['email']]
447 449 elif name == 'extern_type':
448 450 # cannot update this via form, expected value is original one
449 451 params['extern_type'] = "rhodecode"
450 452 elif name == 'extern_name':
451 453 # cannot update this via form, expected value is original one
452 454 params['extern_name'] = 'xxx'
453 455 # special case since this user is not
454 456 # logged in yet his data is not filled
455 457 # so we use creation data
456 458
457 459 response = self.app.post(
458 460 route_path('user_update', user_id=usr.user_id), params)
459 461 assert response.status_int == 302
460 462 assert_session_flash(response, 'User updated successfully')
461 463
462 464 updated_user = User.get(user_id)
463 465 updated_params = updated_user.get_api_data()
464 466 updated_params.update({'password_confirmation': ''})
465 467 updated_params.update({'new_password': ''})
466 468
467 469 del params['csrf_token']
468 470 assert params == updated_params
469 471
470 472 def test_update_and_migrate_password(
471 473 self, autologin_user, real_crypto_backend, user_util):
472 474
473 475 user = user_util.create_user()
474 476 temp_user = user.username
475 477 user.password = auth._RhodeCodeCryptoSha256().hash_create(
476 478 b'test123')
477 479 Session().add(user)
478 480 Session().commit()
479 481
480 482 params = user.get_api_data()
481 483
482 484 params.update({
483 485 'password_confirmation': 'qweqwe123',
484 486 'new_password': 'qweqwe123',
485 487 'language': 'en',
486 488 'csrf_token': autologin_user.csrf_token,
487 489 })
488 490
489 491 response = self.app.post(
490 492 route_path('user_update', user_id=user.user_id), params)
491 493 assert response.status_int == 302
492 494 assert_session_flash(response, 'User updated successfully')
493 495
494 496 # new password should be bcrypted, after log-in and transfer
495 497 user = User.get_by_username(temp_user)
496 498 assert user.password.startswith('$')
497 499
498 500 updated_user = User.get_by_username(temp_user)
499 501 updated_params = updated_user.get_api_data()
500 502 updated_params.update({'password_confirmation': 'qweqwe123'})
501 503 updated_params.update({'new_password': 'qweqwe123'})
502 504
503 505 del params['csrf_token']
504 506 assert params == updated_params
505 507
506 508 def test_delete(self):
507 509 self.log_user()
508 510 username = 'newtestuserdeleteme'
509 511
510 512 fixture.create_user(name=username)
511 513
512 514 new_user = Session().query(User)\
513 515 .filter(User.username == username).one()
514 516 response = self.app.post(
515 517 route_path('user_delete', user_id=new_user.user_id),
516 518 params={'csrf_token': self.csrf_token})
517 519
518 520 assert_session_flash(response, 'Successfully deleted user')
519 521
520 522 def test_delete_owner_of_repository(self, request, user_util):
521 523 self.log_user()
522 524 obj_name = 'test_repo'
523 525 usr = user_util.create_user()
524 526 username = usr.username
525 527 fixture.create_repo(obj_name, cur_user=usr.username)
526 528
527 529 new_user = Session().query(User)\
528 530 .filter(User.username == username).one()
529 531 response = self.app.post(
530 532 route_path('user_delete', user_id=new_user.user_id),
531 533 params={'csrf_token': self.csrf_token})
532 534
533 535 msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \
534 'Switch owners or remove those repositories:%s' % (username,
535 obj_name)
536 'Switch owners or remove those repositories:%s' % (username, obj_name)
536 537 assert_session_flash(response, msg)
537 538 fixture.destroy_repo(obj_name)
538 539
539 540 def test_delete_owner_of_repository_detaching(self, request, user_util):
540 541 self.log_user()
541 542 obj_name = 'test_repo'
542 543 usr = user_util.create_user(auto_cleanup=False)
543 544 username = usr.username
544 545 fixture.create_repo(obj_name, cur_user=usr.username)
545 546
546 547 new_user = Session().query(User)\
547 548 .filter(User.username == username).one()
548 549 response = self.app.post(
549 550 route_path('user_delete', user_id=new_user.user_id),
550 551 params={'user_repos': 'detach', 'csrf_token': self.csrf_token})
551 552
552 553 msg = 'Detached 1 repositories'
553 554 assert_session_flash(response, msg)
554 555 fixture.destroy_repo(obj_name)
555 556
556 557 def test_delete_owner_of_repository_deleting(self, request, user_util):
557 558 self.log_user()
558 559 obj_name = 'test_repo'
559 560 usr = user_util.create_user(auto_cleanup=False)
560 561 username = usr.username
561 562 fixture.create_repo(obj_name, cur_user=usr.username)
562 563
563 564 new_user = Session().query(User)\
564 565 .filter(User.username == username).one()
565 566 response = self.app.post(
566 567 route_path('user_delete', user_id=new_user.user_id),
567 568 params={'user_repos': 'delete', 'csrf_token': self.csrf_token})
568 569
569 570 msg = 'Deleted 1 repositories'
570 571 assert_session_flash(response, msg)
571 572
572 573 def test_delete_owner_of_repository_group(self, request, user_util):
573 574 self.log_user()
574 575 obj_name = 'test_group'
575 576 usr = user_util.create_user()
576 577 username = usr.username
577 578 fixture.create_repo_group(obj_name, cur_user=usr.username)
578 579
579 580 new_user = Session().query(User)\
580 581 .filter(User.username == username).one()
581 582 response = self.app.post(
582 583 route_path('user_delete', user_id=new_user.user_id),
583 584 params={'csrf_token': self.csrf_token})
584 585
585 586 msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \
586 'Switch owners or remove those repository groups:%s' % (username,
587 obj_name)
587 'Switch owners or remove those repository groups:%s' % (username, obj_name)
588 588 assert_session_flash(response, msg)
589 589 fixture.destroy_repo_group(obj_name)
590 590
591 591 def test_delete_owner_of_repository_group_detaching(self, request, user_util):
592 592 self.log_user()
593 593 obj_name = 'test_group'
594 594 usr = user_util.create_user(auto_cleanup=False)
595 595 username = usr.username
596 596 fixture.create_repo_group(obj_name, cur_user=usr.username)
597 597
598 598 new_user = Session().query(User)\
599 599 .filter(User.username == username).one()
600 600 response = self.app.post(
601 601 route_path('user_delete', user_id=new_user.user_id),
602 602 params={'user_repo_groups': 'delete', 'csrf_token': self.csrf_token})
603 603
604 604 msg = 'Deleted 1 repository groups'
605 605 assert_session_flash(response, msg)
606 606
607 607 def test_delete_owner_of_repository_group_deleting(self, request, user_util):
608 608 self.log_user()
609 609 obj_name = 'test_group'
610 610 usr = user_util.create_user(auto_cleanup=False)
611 611 username = usr.username
612 612 fixture.create_repo_group(obj_name, cur_user=usr.username)
613 613
614 614 new_user = Session().query(User)\
615 615 .filter(User.username == username).one()
616 616 response = self.app.post(
617 617 route_path('user_delete', user_id=new_user.user_id),
618 618 params={'user_repo_groups': 'detach', 'csrf_token': self.csrf_token})
619 619
620 620 msg = 'Detached 1 repository groups'
621 621 assert_session_flash(response, msg)
622 622 fixture.destroy_repo_group(obj_name)
623 623
624 624 def test_delete_owner_of_user_group(self, request, user_util):
625 625 self.log_user()
626 626 obj_name = 'test_user_group'
627 627 usr = user_util.create_user()
628 628 username = usr.username
629 629 fixture.create_user_group(obj_name, cur_user=usr.username)
630 630
631 631 new_user = Session().query(User)\
632 632 .filter(User.username == username).one()
633 633 response = self.app.post(
634 634 route_path('user_delete', user_id=new_user.user_id),
635 635 params={'csrf_token': self.csrf_token})
636 636
637 637 msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \
638 'Switch owners or remove those user groups:%s' % (username,
639 obj_name)
638 'Switch owners or remove those user groups:%s' % (username, obj_name)
640 639 assert_session_flash(response, msg)
641 640 fixture.destroy_user_group(obj_name)
642 641
643 642 def test_delete_owner_of_user_group_detaching(self, request, user_util):
644 643 self.log_user()
645 644 obj_name = 'test_user_group'
646 645 usr = user_util.create_user(auto_cleanup=False)
647 646 username = usr.username
648 647 fixture.create_user_group(obj_name, cur_user=usr.username)
649 648
650 649 new_user = Session().query(User)\
651 650 .filter(User.username == username).one()
652 651 try:
653 652 response = self.app.post(
654 653 route_path('user_delete', user_id=new_user.user_id),
655 654 params={'user_user_groups': 'detach',
656 655 'csrf_token': self.csrf_token})
657 656
658 657 msg = 'Detached 1 user groups'
659 658 assert_session_flash(response, msg)
660 659 finally:
661 660 fixture.destroy_user_group(obj_name)
662 661
663 662 def test_delete_owner_of_user_group_deleting(self, request, user_util):
664 663 self.log_user()
665 664 obj_name = 'test_user_group'
666 665 usr = user_util.create_user(auto_cleanup=False)
667 666 username = usr.username
668 667 fixture.create_user_group(obj_name, cur_user=usr.username)
669 668
670 669 new_user = Session().query(User)\
671 670 .filter(User.username == username).one()
672 671 response = self.app.post(
673 672 route_path('user_delete', user_id=new_user.user_id),
674 673 params={'user_user_groups': 'delete', 'csrf_token': self.csrf_token})
675 674
676 675 msg = 'Deleted 1 user groups'
677 676 assert_session_flash(response, msg)
678 677
679 678 def test_edit(self, user_util):
680 679 self.log_user()
681 680 user = user_util.create_user()
682 681 self.app.get(route_path('user_edit', user_id=user.user_id))
683 682
684 683 def test_edit_default_user_redirect(self):
685 684 self.log_user()
686 685 user = User.get_default_user()
687 686 self.app.get(route_path('user_edit', user_id=user.user_id), status=302)
688 687
689 688 @pytest.mark.parametrize(
690 689 'repo_create, repo_create_write, user_group_create, repo_group_create,'
691 690 'fork_create, inherit_default_permissions, expect_error,'
692 691 'expect_form_error', [
693 692 ('hg.create.none', 'hg.create.write_on_repogroup.false',
694 693 'hg.usergroup.create.false', 'hg.repogroup.create.false',
695 694 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
696 695 ('hg.create.repository', 'hg.create.write_on_repogroup.false',
697 696 'hg.usergroup.create.false', 'hg.repogroup.create.false',
698 697 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
699 698 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
700 699 'hg.usergroup.create.true', 'hg.repogroup.create.true',
701 700 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
702 701 False),
703 702 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
704 703 'hg.usergroup.create.true', 'hg.repogroup.create.true',
705 704 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
706 705 True),
707 706 ('', '', '', '', '', '', True, False),
708 707 ])
709 708 def test_global_perms_on_user(
710 709 self, repo_create, repo_create_write, user_group_create,
711 710 repo_group_create, fork_create, expect_error, expect_form_error,
712 711 inherit_default_permissions, user_util):
713 712 self.log_user()
714 713 user = user_util.create_user()
715 714 uid = user.user_id
716 715
717 716 # ENABLE REPO CREATE ON A GROUP
718 717 perm_params = {
719 718 'inherit_default_permissions': False,
720 719 'default_repo_create': repo_create,
721 720 'default_repo_create_on_write': repo_create_write,
722 721 'default_user_group_create': user_group_create,
723 722 'default_repo_group_create': repo_group_create,
724 723 'default_fork_create': fork_create,
725 724 'default_inherit_default_permissions': inherit_default_permissions,
726 725 'csrf_token': self.csrf_token,
727 726 }
728 727 response = self.app.post(
729 728 route_path('user_edit_global_perms_update', user_id=uid),
730 729 params=perm_params)
731 730
732 731 if expect_form_error:
733 732 assert response.status_int == 200
734 733 response.mustcontain('Value must be one of')
735 734 else:
736 735 if expect_error:
737 736 msg = 'An error occurred during permissions saving'
738 737 else:
739 738 msg = 'User global permissions updated successfully'
740 739 ug = User.get(uid)
741 740 del perm_params['inherit_default_permissions']
742 741 del perm_params['csrf_token']
743 742 assert perm_params == ug.get_default_perms()
744 743 assert_session_flash(response, msg)
745 744
746 745 def test_global_permissions_initial_values(self, user_util):
747 746 self.log_user()
748 747 user = user_util.create_user()
749 748 uid = user.user_id
750 749 response = self.app.get(
751 750 route_path('user_edit_global_perms', user_id=uid))
752 751 default_user = User.get_default_user()
753 752 default_permissions = default_user.get_default_perms()
754 753 assert_response = response.assert_response()
755 754 expected_permissions = (
756 755 'default_repo_create', 'default_repo_create_on_write',
757 756 'default_fork_create', 'default_repo_group_create',
758 757 'default_user_group_create', 'default_inherit_default_permissions')
759 758 for permission in expected_permissions:
760 759 css_selector = '[name={}][checked=checked]'.format(permission)
761 760 element = assert_response.get_element(css_selector)
762 761 assert element.value == default_permissions[permission]
763 762
764 763 def test_perms_summary_page(self):
765 764 user = self.log_user()
766 765 response = self.app.get(
767 766 route_path('edit_user_perms_summary', user_id=user['user_id']))
768 767 for repo in Repository.query().all():
769 768 response.mustcontain(repo.repo_name)
770 769
771 770 def test_perms_summary_page_json(self):
772 771 user = self.log_user()
773 772 response = self.app.get(
774 773 route_path('edit_user_perms_summary_json', user_id=user['user_id']))
775 774 for repo in Repository.query().all():
776 775 response.mustcontain(repo.repo_name)
777 776
778 777 def test_audit_log_page(self):
779 778 user = self.log_user()
780 779 self.app.get(
781 780 route_path('edit_user_audit_logs', user_id=user['user_id']))
781
782 def test_audit_log_page_download(self):
783 user = self.log_user()
784 user_id = user['user_id']
785 response = self.app.get(
786 route_path('edit_user_audit_logs_download', user_id=user_id))
787
788 assert response.content_disposition == \
789 'attachment; filename=user_{}_audit_logs.json'.format(user_id)
790 assert response.content_type == "application/json"
@@ -1,1265 +1,1288 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import datetime
23 23 import formencode
24 24 import formencode.htmlfill
25 25
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.view import view_config
28 28 from pyramid.renderers import render
29 29 from pyramid.response import Response
30 30
31 31 from rhodecode import events
32 32 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
33 33 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
34 34 from rhodecode.authentication.plugins import auth_rhodecode
35 35 from rhodecode.events import trigger
36 36 from rhodecode.model.db import true
37 37
38 38 from rhodecode.lib import audit_logger, rc_cache
39 39 from rhodecode.lib.exceptions import (
40 40 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
41 41 UserOwnsUserGroupsException, DefaultUserException)
42 42 from rhodecode.lib.ext_json import json
43 43 from rhodecode.lib.auth import (
44 44 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
45 45 from rhodecode.lib import helpers as h
46 46 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
47 47 from rhodecode.model.auth_token import AuthTokenModel
48 48 from rhodecode.model.forms import (
49 49 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
50 50 UserExtraEmailForm, UserExtraIpForm)
51 51 from rhodecode.model.permission import PermissionModel
52 52 from rhodecode.model.repo_group import RepoGroupModel
53 53 from rhodecode.model.ssh_key import SshKeyModel
54 54 from rhodecode.model.user import UserModel
55 55 from rhodecode.model.user_group import UserGroupModel
56 56 from rhodecode.model.db import (
57 57 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
58 58 UserApiKeys, UserSshKeys, RepoGroup)
59 59 from rhodecode.model.meta import Session
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63
64 64 class AdminUsersView(BaseAppView, DataGridAppView):
65 65
66 66 def load_default_context(self):
67 67 c = self._get_local_tmpl_context()
68 68 return c
69 69
70 70 @LoginRequired()
71 71 @HasPermissionAllDecorator('hg.admin')
72 72 @view_config(
73 73 route_name='users', request_method='GET',
74 74 renderer='rhodecode:templates/admin/users/users.mako')
75 75 def users_list(self):
76 76 c = self.load_default_context()
77 77 return self._get_template_context(c)
78 78
79 79 @LoginRequired()
80 80 @HasPermissionAllDecorator('hg.admin')
81 81 @view_config(
82 82 # renderer defined below
83 83 route_name='users_data', request_method='GET',
84 84 renderer='json_ext', xhr=True)
85 85 def users_list_data(self):
86 86 self.load_default_context()
87 87 column_map = {
88 88 'first_name': 'name',
89 89 'last_name': 'lastname',
90 90 }
91 91 draw, start, limit = self._extract_chunk(self.request)
92 92 search_q, order_by, order_dir = self._extract_ordering(
93 93 self.request, column_map=column_map)
94 94 _render = self.request.get_partial_renderer(
95 95 'rhodecode:templates/data_table/_dt_elements.mako')
96 96
97 97 def user_actions(user_id, username):
98 98 return _render("user_actions", user_id, username)
99 99
100 100 users_data_total_count = User.query()\
101 101 .filter(User.username != User.DEFAULT_USER) \
102 102 .count()
103 103
104 104 users_data_total_inactive_count = User.query()\
105 105 .filter(User.username != User.DEFAULT_USER) \
106 106 .filter(User.active != true())\
107 107 .count()
108 108
109 109 # json generate
110 110 base_q = User.query().filter(User.username != User.DEFAULT_USER)
111 111 base_inactive_q = base_q.filter(User.active != true())
112 112
113 113 if search_q:
114 114 like_expression = u'%{}%'.format(safe_unicode(search_q))
115 115 base_q = base_q.filter(or_(
116 116 User.username.ilike(like_expression),
117 117 User._email.ilike(like_expression),
118 118 User.name.ilike(like_expression),
119 119 User.lastname.ilike(like_expression),
120 120 ))
121 121 base_inactive_q = base_q.filter(User.active != true())
122 122
123 123 users_data_total_filtered_count = base_q.count()
124 124 users_data_total_filtered_inactive_count = base_inactive_q.count()
125 125
126 126 sort_col = getattr(User, order_by, None)
127 127 if sort_col:
128 128 if order_dir == 'asc':
129 129 # handle null values properly to order by NULL last
130 130 if order_by in ['last_activity']:
131 131 sort_col = coalesce(sort_col, datetime.date.max)
132 132 sort_col = sort_col.asc()
133 133 else:
134 134 # handle null values properly to order by NULL last
135 135 if order_by in ['last_activity']:
136 136 sort_col = coalesce(sort_col, datetime.date.min)
137 137 sort_col = sort_col.desc()
138 138
139 139 base_q = base_q.order_by(sort_col)
140 140 base_q = base_q.offset(start).limit(limit)
141 141
142 142 users_list = base_q.all()
143 143
144 144 users_data = []
145 145 for user in users_list:
146 146 users_data.append({
147 147 "username": h.gravatar_with_user(self.request, user.username),
148 148 "email": user.email,
149 149 "first_name": user.first_name,
150 150 "last_name": user.last_name,
151 151 "last_login": h.format_date(user.last_login),
152 152 "last_activity": h.format_date(user.last_activity),
153 153 "active": h.bool2icon(user.active),
154 154 "active_raw": user.active,
155 155 "admin": h.bool2icon(user.admin),
156 156 "extern_type": user.extern_type,
157 157 "extern_name": user.extern_name,
158 158 "action": user_actions(user.user_id, user.username),
159 159 })
160 160 data = ({
161 161 'draw': draw,
162 162 'data': users_data,
163 163 'recordsTotal': users_data_total_count,
164 164 'recordsFiltered': users_data_total_filtered_count,
165 165 'recordsTotalInactive': users_data_total_inactive_count,
166 166 'recordsFilteredInactive': users_data_total_filtered_inactive_count
167 167 })
168 168
169 169 return data
170 170
171 171 def _set_personal_repo_group_template_vars(self, c_obj):
172 172 DummyUser = AttributeDict({
173 173 'username': '${username}',
174 174 'user_id': '${user_id}',
175 175 })
176 176 c_obj.default_create_repo_group = RepoGroupModel() \
177 177 .get_default_create_personal_repo_group()
178 178 c_obj.personal_repo_group_name = RepoGroupModel() \
179 179 .get_personal_group_name(DummyUser)
180 180
181 181 @LoginRequired()
182 182 @HasPermissionAllDecorator('hg.admin')
183 183 @view_config(
184 184 route_name='users_new', request_method='GET',
185 185 renderer='rhodecode:templates/admin/users/user_add.mako')
186 186 def users_new(self):
187 187 _ = self.request.translate
188 188 c = self.load_default_context()
189 189 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
190 190 self._set_personal_repo_group_template_vars(c)
191 191 return self._get_template_context(c)
192 192
193 193 @LoginRequired()
194 194 @HasPermissionAllDecorator('hg.admin')
195 195 @CSRFRequired()
196 196 @view_config(
197 197 route_name='users_create', request_method='POST',
198 198 renderer='rhodecode:templates/admin/users/user_add.mako')
199 199 def users_create(self):
200 200 _ = self.request.translate
201 201 c = self.load_default_context()
202 202 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
203 203 user_model = UserModel()
204 204 user_form = UserForm(self.request.translate)()
205 205 try:
206 206 form_result = user_form.to_python(dict(self.request.POST))
207 207 user = user_model.create(form_result)
208 208 Session().flush()
209 209 creation_data = user.get_api_data()
210 210 username = form_result['username']
211 211
212 212 audit_logger.store_web(
213 213 'user.create', action_data={'data': creation_data},
214 214 user=c.rhodecode_user)
215 215
216 216 user_link = h.link_to(
217 217 h.escape(username),
218 218 h.route_path('user_edit', user_id=user.user_id))
219 219 h.flash(h.literal(_('Created user %(user_link)s')
220 220 % {'user_link': user_link}), category='success')
221 221 Session().commit()
222 222 except formencode.Invalid as errors:
223 223 self._set_personal_repo_group_template_vars(c)
224 224 data = render(
225 225 'rhodecode:templates/admin/users/user_add.mako',
226 226 self._get_template_context(c), self.request)
227 227 html = formencode.htmlfill.render(
228 228 data,
229 229 defaults=errors.value,
230 230 errors=errors.error_dict or {},
231 231 prefix_error=False,
232 232 encoding="UTF-8",
233 233 force_defaults=False
234 234 )
235 235 return Response(html)
236 236 except UserCreationError as e:
237 237 h.flash(e, 'error')
238 238 except Exception:
239 239 log.exception("Exception creation of user")
240 240 h.flash(_('Error occurred during creation of user %s')
241 241 % self.request.POST.get('username'), category='error')
242 242 raise HTTPFound(h.route_path('users'))
243 243
244 244
245 245 class UsersView(UserAppView):
246 246 ALLOW_SCOPED_TOKENS = False
247 247 """
248 248 This view has alternative version inside EE, if modified please take a look
249 249 in there as well.
250 250 """
251 251
252 252 def load_default_context(self):
253 253 c = self._get_local_tmpl_context()
254 254 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
255 255 c.allowed_languages = [
256 256 ('en', 'English (en)'),
257 257 ('de', 'German (de)'),
258 258 ('fr', 'French (fr)'),
259 259 ('it', 'Italian (it)'),
260 260 ('ja', 'Japanese (ja)'),
261 261 ('pl', 'Polish (pl)'),
262 262 ('pt', 'Portuguese (pt)'),
263 263 ('ru', 'Russian (ru)'),
264 264 ('zh', 'Chinese (zh)'),
265 265 ]
266 266 req = self.request
267 267
268 268 c.available_permissions = req.registry.settings['available_permissions']
269 269 PermissionModel().set_global_permission_choices(
270 270 c, gettext_translator=req.translate)
271 271
272 272 return c
273 273
274 274 @LoginRequired()
275 275 @HasPermissionAllDecorator('hg.admin')
276 276 @CSRFRequired()
277 277 @view_config(
278 278 route_name='user_update', request_method='POST',
279 279 renderer='rhodecode:templates/admin/users/user_edit.mako')
280 280 def user_update(self):
281 281 _ = self.request.translate
282 282 c = self.load_default_context()
283 283
284 284 user_id = self.db_user_id
285 285 c.user = self.db_user
286 286
287 287 c.active = 'profile'
288 288 c.extern_type = c.user.extern_type
289 289 c.extern_name = c.user.extern_name
290 290 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
291 291 available_languages = [x[0] for x in c.allowed_languages]
292 292 _form = UserForm(self.request.translate, edit=True,
293 293 available_languages=available_languages,
294 294 old_data={'user_id': user_id,
295 295 'email': c.user.email})()
296 296 form_result = {}
297 297 old_values = c.user.get_api_data()
298 298 try:
299 299 form_result = _form.to_python(dict(self.request.POST))
300 300 skip_attrs = ['extern_type', 'extern_name']
301 301 # TODO: plugin should define if username can be updated
302 302 if c.extern_type != "rhodecode":
303 303 # forbid updating username for external accounts
304 304 skip_attrs.append('username')
305 305
306 306 UserModel().update_user(
307 307 user_id, skip_attrs=skip_attrs, **form_result)
308 308
309 309 audit_logger.store_web(
310 310 'user.edit', action_data={'old_data': old_values},
311 311 user=c.rhodecode_user)
312 312
313 313 Session().commit()
314 314 h.flash(_('User updated successfully'), category='success')
315 315 except formencode.Invalid as errors:
316 316 data = render(
317 317 'rhodecode:templates/admin/users/user_edit.mako',
318 318 self._get_template_context(c), self.request)
319 319 html = formencode.htmlfill.render(
320 320 data,
321 321 defaults=errors.value,
322 322 errors=errors.error_dict or {},
323 323 prefix_error=False,
324 324 encoding="UTF-8",
325 325 force_defaults=False
326 326 )
327 327 return Response(html)
328 328 except UserCreationError as e:
329 329 h.flash(e, 'error')
330 330 except Exception:
331 331 log.exception("Exception updating user")
332 332 h.flash(_('Error occurred during update of user %s')
333 333 % form_result.get('username'), category='error')
334 334 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
335 335
336 336 @LoginRequired()
337 337 @HasPermissionAllDecorator('hg.admin')
338 338 @CSRFRequired()
339 339 @view_config(
340 340 route_name='user_delete', request_method='POST',
341 341 renderer='rhodecode:templates/admin/users/user_edit.mako')
342 342 def user_delete(self):
343 343 _ = self.request.translate
344 344 c = self.load_default_context()
345 345 c.user = self.db_user
346 346
347 347 _repos = c.user.repositories
348 348 _repo_groups = c.user.repository_groups
349 349 _user_groups = c.user.user_groups
350 350
351 351 handle_repos = None
352 352 handle_repo_groups = None
353 353 handle_user_groups = None
354 354 # dummy call for flash of handle
355 355 set_handle_flash_repos = lambda: None
356 356 set_handle_flash_repo_groups = lambda: None
357 357 set_handle_flash_user_groups = lambda: None
358 358
359 359 if _repos and self.request.POST.get('user_repos'):
360 360 do = self.request.POST['user_repos']
361 361 if do == 'detach':
362 362 handle_repos = 'detach'
363 363 set_handle_flash_repos = lambda: h.flash(
364 364 _('Detached %s repositories') % len(_repos),
365 365 category='success')
366 366 elif do == 'delete':
367 367 handle_repos = 'delete'
368 368 set_handle_flash_repos = lambda: h.flash(
369 369 _('Deleted %s repositories') % len(_repos),
370 370 category='success')
371 371
372 372 if _repo_groups and self.request.POST.get('user_repo_groups'):
373 373 do = self.request.POST['user_repo_groups']
374 374 if do == 'detach':
375 375 handle_repo_groups = 'detach'
376 376 set_handle_flash_repo_groups = lambda: h.flash(
377 377 _('Detached %s repository groups') % len(_repo_groups),
378 378 category='success')
379 379 elif do == 'delete':
380 380 handle_repo_groups = 'delete'
381 381 set_handle_flash_repo_groups = lambda: h.flash(
382 382 _('Deleted %s repository groups') % len(_repo_groups),
383 383 category='success')
384 384
385 385 if _user_groups and self.request.POST.get('user_user_groups'):
386 386 do = self.request.POST['user_user_groups']
387 387 if do == 'detach':
388 388 handle_user_groups = 'detach'
389 389 set_handle_flash_user_groups = lambda: h.flash(
390 390 _('Detached %s user groups') % len(_user_groups),
391 391 category='success')
392 392 elif do == 'delete':
393 393 handle_user_groups = 'delete'
394 394 set_handle_flash_user_groups = lambda: h.flash(
395 395 _('Deleted %s user groups') % len(_user_groups),
396 396 category='success')
397 397
398 398 old_values = c.user.get_api_data()
399 399 try:
400 400 UserModel().delete(c.user, handle_repos=handle_repos,
401 401 handle_repo_groups=handle_repo_groups,
402 402 handle_user_groups=handle_user_groups)
403 403
404 404 audit_logger.store_web(
405 405 'user.delete', action_data={'old_data': old_values},
406 406 user=c.rhodecode_user)
407 407
408 408 Session().commit()
409 409 set_handle_flash_repos()
410 410 set_handle_flash_repo_groups()
411 411 set_handle_flash_user_groups()
412 412 h.flash(_('Successfully deleted user'), category='success')
413 413 except (UserOwnsReposException, UserOwnsRepoGroupsException,
414 414 UserOwnsUserGroupsException, DefaultUserException) as e:
415 415 h.flash(e, category='warning')
416 416 except Exception:
417 417 log.exception("Exception during deletion of user")
418 418 h.flash(_('An error occurred during deletion of user'),
419 419 category='error')
420 420 raise HTTPFound(h.route_path('users'))
421 421
422 422 @LoginRequired()
423 423 @HasPermissionAllDecorator('hg.admin')
424 424 @view_config(
425 425 route_name='user_edit', request_method='GET',
426 426 renderer='rhodecode:templates/admin/users/user_edit.mako')
427 427 def user_edit(self):
428 428 _ = self.request.translate
429 429 c = self.load_default_context()
430 430 c.user = self.db_user
431 431
432 432 c.active = 'profile'
433 433 c.extern_type = c.user.extern_type
434 434 c.extern_name = c.user.extern_name
435 435 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
436 436
437 437 defaults = c.user.get_dict()
438 438 defaults.update({'language': c.user.user_data.get('language')})
439 439
440 440 data = render(
441 441 'rhodecode:templates/admin/users/user_edit.mako',
442 442 self._get_template_context(c), self.request)
443 443 html = formencode.htmlfill.render(
444 444 data,
445 445 defaults=defaults,
446 446 encoding="UTF-8",
447 447 force_defaults=False
448 448 )
449 449 return Response(html)
450 450
451 451 @LoginRequired()
452 452 @HasPermissionAllDecorator('hg.admin')
453 453 @view_config(
454 454 route_name='user_edit_advanced', request_method='GET',
455 455 renderer='rhodecode:templates/admin/users/user_edit.mako')
456 456 def user_edit_advanced(self):
457 457 _ = self.request.translate
458 458 c = self.load_default_context()
459 459
460 460 user_id = self.db_user_id
461 461 c.user = self.db_user
462 462
463 463 c.active = 'advanced'
464 464 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
465 465 c.personal_repo_group_name = RepoGroupModel()\
466 466 .get_personal_group_name(c.user)
467 467
468 468 c.user_to_review_rules = sorted(
469 469 (x.user for x in c.user.user_review_rules),
470 470 key=lambda u: u.username.lower())
471 471
472 472 c.first_admin = User.get_first_super_admin()
473 473 defaults = c.user.get_dict()
474 474
475 475 # Interim workaround if the user participated on any pull requests as a
476 476 # reviewer.
477 477 has_review = len(c.user.reviewer_pull_requests)
478 478 c.can_delete_user = not has_review
479 479 c.can_delete_user_message = ''
480 480 inactive_link = h.link_to(
481 481 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
482 482 if has_review == 1:
483 483 c.can_delete_user_message = h.literal(_(
484 484 'The user participates as reviewer in {} pull request and '
485 485 'cannot be deleted. \nYou can set the user to '
486 486 '"{}" instead of deleting it.').format(
487 487 has_review, inactive_link))
488 488 elif has_review:
489 489 c.can_delete_user_message = h.literal(_(
490 490 'The user participates as reviewer in {} pull requests and '
491 491 'cannot be deleted. \nYou can set the user to '
492 492 '"{}" instead of deleting it.').format(
493 493 has_review, inactive_link))
494 494
495 495 data = render(
496 496 'rhodecode:templates/admin/users/user_edit.mako',
497 497 self._get_template_context(c), self.request)
498 498 html = formencode.htmlfill.render(
499 499 data,
500 500 defaults=defaults,
501 501 encoding="UTF-8",
502 502 force_defaults=False
503 503 )
504 504 return Response(html)
505 505
506 506 @LoginRequired()
507 507 @HasPermissionAllDecorator('hg.admin')
508 508 @view_config(
509 509 route_name='user_edit_global_perms', request_method='GET',
510 510 renderer='rhodecode:templates/admin/users/user_edit.mako')
511 511 def user_edit_global_perms(self):
512 512 _ = self.request.translate
513 513 c = self.load_default_context()
514 514 c.user = self.db_user
515 515
516 516 c.active = 'global_perms'
517 517
518 518 c.default_user = User.get_default_user()
519 519 defaults = c.user.get_dict()
520 520 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
521 521 defaults.update(c.default_user.get_default_perms())
522 522 defaults.update(c.user.get_default_perms())
523 523
524 524 data = render(
525 525 'rhodecode:templates/admin/users/user_edit.mako',
526 526 self._get_template_context(c), self.request)
527 527 html = formencode.htmlfill.render(
528 528 data,
529 529 defaults=defaults,
530 530 encoding="UTF-8",
531 531 force_defaults=False
532 532 )
533 533 return Response(html)
534 534
535 535 @LoginRequired()
536 536 @HasPermissionAllDecorator('hg.admin')
537 537 @CSRFRequired()
538 538 @view_config(
539 539 route_name='user_edit_global_perms_update', request_method='POST',
540 540 renderer='rhodecode:templates/admin/users/user_edit.mako')
541 541 def user_edit_global_perms_update(self):
542 542 _ = self.request.translate
543 543 c = self.load_default_context()
544 544
545 545 user_id = self.db_user_id
546 546 c.user = self.db_user
547 547
548 548 c.active = 'global_perms'
549 549 try:
550 550 # first stage that verifies the checkbox
551 551 _form = UserIndividualPermissionsForm(self.request.translate)
552 552 form_result = _form.to_python(dict(self.request.POST))
553 553 inherit_perms = form_result['inherit_default_permissions']
554 554 c.user.inherit_default_permissions = inherit_perms
555 555 Session().add(c.user)
556 556
557 557 if not inherit_perms:
558 558 # only update the individual ones if we un check the flag
559 559 _form = UserPermissionsForm(
560 560 self.request.translate,
561 561 [x[0] for x in c.repo_create_choices],
562 562 [x[0] for x in c.repo_create_on_write_choices],
563 563 [x[0] for x in c.repo_group_create_choices],
564 564 [x[0] for x in c.user_group_create_choices],
565 565 [x[0] for x in c.fork_choices],
566 566 [x[0] for x in c.inherit_default_permission_choices])()
567 567
568 568 form_result = _form.to_python(dict(self.request.POST))
569 569 form_result.update({'perm_user_id': c.user.user_id})
570 570
571 571 PermissionModel().update_user_permissions(form_result)
572 572
573 573 # TODO(marcink): implement global permissions
574 574 # audit_log.store_web('user.edit.permissions')
575 575
576 576 Session().commit()
577 577
578 578 h.flash(_('User global permissions updated successfully'),
579 579 category='success')
580 580
581 581 except formencode.Invalid as errors:
582 582 data = render(
583 583 'rhodecode:templates/admin/users/user_edit.mako',
584 584 self._get_template_context(c), self.request)
585 585 html = formencode.htmlfill.render(
586 586 data,
587 587 defaults=errors.value,
588 588 errors=errors.error_dict or {},
589 589 prefix_error=False,
590 590 encoding="UTF-8",
591 591 force_defaults=False
592 592 )
593 593 return Response(html)
594 594 except Exception:
595 595 log.exception("Exception during permissions saving")
596 596 h.flash(_('An error occurred during permissions saving'),
597 597 category='error')
598 598
599 599 affected_user_ids = [user_id]
600 600 PermissionModel().trigger_permission_flush(affected_user_ids)
601 601 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
602 602
603 603 @LoginRequired()
604 604 @HasPermissionAllDecorator('hg.admin')
605 605 @CSRFRequired()
606 606 @view_config(
607 607 route_name='user_enable_force_password_reset', request_method='POST',
608 608 renderer='rhodecode:templates/admin/users/user_edit.mako')
609 609 def user_enable_force_password_reset(self):
610 610 _ = self.request.translate
611 611 c = self.load_default_context()
612 612
613 613 user_id = self.db_user_id
614 614 c.user = self.db_user
615 615
616 616 try:
617 617 c.user.update_userdata(force_password_change=True)
618 618
619 619 msg = _('Force password change enabled for user')
620 620 audit_logger.store_web('user.edit.password_reset.enabled',
621 621 user=c.rhodecode_user)
622 622
623 623 Session().commit()
624 624 h.flash(msg, category='success')
625 625 except Exception:
626 626 log.exception("Exception during password reset for user")
627 627 h.flash(_('An error occurred during password reset for user'),
628 628 category='error')
629 629
630 630 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
631 631
632 632 @LoginRequired()
633 633 @HasPermissionAllDecorator('hg.admin')
634 634 @CSRFRequired()
635 635 @view_config(
636 636 route_name='user_disable_force_password_reset', request_method='POST',
637 637 renderer='rhodecode:templates/admin/users/user_edit.mako')
638 638 def user_disable_force_password_reset(self):
639 639 _ = self.request.translate
640 640 c = self.load_default_context()
641 641
642 642 user_id = self.db_user_id
643 643 c.user = self.db_user
644 644
645 645 try:
646 646 c.user.update_userdata(force_password_change=False)
647 647
648 648 msg = _('Force password change disabled for user')
649 649 audit_logger.store_web(
650 650 'user.edit.password_reset.disabled',
651 651 user=c.rhodecode_user)
652 652
653 653 Session().commit()
654 654 h.flash(msg, category='success')
655 655 except Exception:
656 656 log.exception("Exception during password reset for user")
657 657 h.flash(_('An error occurred during password reset for user'),
658 658 category='error')
659 659
660 660 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
661 661
662 662 @LoginRequired()
663 663 @HasPermissionAllDecorator('hg.admin')
664 664 @CSRFRequired()
665 665 @view_config(
666 666 route_name='user_create_personal_repo_group', request_method='POST',
667 667 renderer='rhodecode:templates/admin/users/user_edit.mako')
668 668 def user_create_personal_repo_group(self):
669 669 """
670 670 Create personal repository group for this user
671 671 """
672 672 from rhodecode.model.repo_group import RepoGroupModel
673 673
674 674 _ = self.request.translate
675 675 c = self.load_default_context()
676 676
677 677 user_id = self.db_user_id
678 678 c.user = self.db_user
679 679
680 680 personal_repo_group = RepoGroup.get_user_personal_repo_group(
681 681 c.user.user_id)
682 682 if personal_repo_group:
683 683 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
684 684
685 685 personal_repo_group_name = RepoGroupModel().get_personal_group_name(c.user)
686 686 named_personal_group = RepoGroup.get_by_group_name(
687 687 personal_repo_group_name)
688 688 try:
689 689
690 690 if named_personal_group and named_personal_group.user_id == c.user.user_id:
691 691 # migrate the same named group, and mark it as personal
692 692 named_personal_group.personal = True
693 693 Session().add(named_personal_group)
694 694 Session().commit()
695 695 msg = _('Linked repository group `%s` as personal' % (
696 696 personal_repo_group_name,))
697 697 h.flash(msg, category='success')
698 698 elif not named_personal_group:
699 699 RepoGroupModel().create_personal_repo_group(c.user)
700 700
701 701 msg = _('Created repository group `%s`' % (
702 702 personal_repo_group_name,))
703 703 h.flash(msg, category='success')
704 704 else:
705 705 msg = _('Repository group `%s` is already taken' % (
706 706 personal_repo_group_name,))
707 707 h.flash(msg, category='warning')
708 708 except Exception:
709 709 log.exception("Exception during repository group creation")
710 710 msg = _(
711 711 'An error occurred during repository group creation for user')
712 712 h.flash(msg, category='error')
713 713 Session().rollback()
714 714
715 715 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
716 716
717 717 @LoginRequired()
718 718 @HasPermissionAllDecorator('hg.admin')
719 719 @view_config(
720 720 route_name='edit_user_auth_tokens', request_method='GET',
721 721 renderer='rhodecode:templates/admin/users/user_edit.mako')
722 722 def auth_tokens(self):
723 723 _ = self.request.translate
724 724 c = self.load_default_context()
725 725 c.user = self.db_user
726 726
727 727 c.active = 'auth_tokens'
728 728
729 729 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
730 730 c.role_values = [
731 731 (x, AuthTokenModel.cls._get_role_name(x))
732 732 for x in AuthTokenModel.cls.ROLES]
733 733 c.role_options = [(c.role_values, _("Role"))]
734 734 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
735 735 c.user.user_id, show_expired=True)
736 736 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
737 737 return self._get_template_context(c)
738 738
739 739 def maybe_attach_token_scope(self, token):
740 740 # implemented in EE edition
741 741 pass
742 742
743 743 @LoginRequired()
744 744 @HasPermissionAllDecorator('hg.admin')
745 745 @CSRFRequired()
746 746 @view_config(
747 747 route_name='edit_user_auth_tokens_add', request_method='POST')
748 748 def auth_tokens_add(self):
749 749 _ = self.request.translate
750 750 c = self.load_default_context()
751 751
752 752 user_id = self.db_user_id
753 753 c.user = self.db_user
754 754
755 755 user_data = c.user.get_api_data()
756 756 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
757 757 description = self.request.POST.get('description')
758 758 role = self.request.POST.get('role')
759 759
760 760 token = UserModel().add_auth_token(
761 761 user=c.user.user_id,
762 762 lifetime_minutes=lifetime, role=role, description=description,
763 763 scope_callback=self.maybe_attach_token_scope)
764 764 token_data = token.get_api_data()
765 765
766 766 audit_logger.store_web(
767 767 'user.edit.token.add', action_data={
768 768 'data': {'token': token_data, 'user': user_data}},
769 769 user=self._rhodecode_user, )
770 770 Session().commit()
771 771
772 772 h.flash(_("Auth token successfully created"), category='success')
773 773 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
774 774
775 775 @LoginRequired()
776 776 @HasPermissionAllDecorator('hg.admin')
777 777 @CSRFRequired()
778 778 @view_config(
779 779 route_name='edit_user_auth_tokens_delete', request_method='POST')
780 780 def auth_tokens_delete(self):
781 781 _ = self.request.translate
782 782 c = self.load_default_context()
783 783
784 784 user_id = self.db_user_id
785 785 c.user = self.db_user
786 786
787 787 user_data = c.user.get_api_data()
788 788
789 789 del_auth_token = self.request.POST.get('del_auth_token')
790 790
791 791 if del_auth_token:
792 792 token = UserApiKeys.get_or_404(del_auth_token)
793 793 token_data = token.get_api_data()
794 794
795 795 AuthTokenModel().delete(del_auth_token, c.user.user_id)
796 796 audit_logger.store_web(
797 797 'user.edit.token.delete', action_data={
798 798 'data': {'token': token_data, 'user': user_data}},
799 799 user=self._rhodecode_user,)
800 800 Session().commit()
801 801 h.flash(_("Auth token successfully deleted"), category='success')
802 802
803 803 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
804 804
805 805 @LoginRequired()
806 806 @HasPermissionAllDecorator('hg.admin')
807 807 @view_config(
808 808 route_name='edit_user_ssh_keys', request_method='GET',
809 809 renderer='rhodecode:templates/admin/users/user_edit.mako')
810 810 def ssh_keys(self):
811 811 _ = self.request.translate
812 812 c = self.load_default_context()
813 813 c.user = self.db_user
814 814
815 815 c.active = 'ssh_keys'
816 816 c.default_key = self.request.GET.get('default_key')
817 817 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
818 818 return self._get_template_context(c)
819 819
820 820 @LoginRequired()
821 821 @HasPermissionAllDecorator('hg.admin')
822 822 @view_config(
823 823 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
824 824 renderer='rhodecode:templates/admin/users/user_edit.mako')
825 825 def ssh_keys_generate_keypair(self):
826 826 _ = self.request.translate
827 827 c = self.load_default_context()
828 828
829 829 c.user = self.db_user
830 830
831 831 c.active = 'ssh_keys_generate'
832 832 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
833 833 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
834 834
835 835 return self._get_template_context(c)
836 836
837 837 @LoginRequired()
838 838 @HasPermissionAllDecorator('hg.admin')
839 839 @CSRFRequired()
840 840 @view_config(
841 841 route_name='edit_user_ssh_keys_add', request_method='POST')
842 842 def ssh_keys_add(self):
843 843 _ = self.request.translate
844 844 c = self.load_default_context()
845 845
846 846 user_id = self.db_user_id
847 847 c.user = self.db_user
848 848
849 849 user_data = c.user.get_api_data()
850 850 key_data = self.request.POST.get('key_data')
851 851 description = self.request.POST.get('description')
852 852
853 853 fingerprint = 'unknown'
854 854 try:
855 855 if not key_data:
856 856 raise ValueError('Please add a valid public key')
857 857
858 858 key = SshKeyModel().parse_key(key_data.strip())
859 859 fingerprint = key.hash_md5()
860 860
861 861 ssh_key = SshKeyModel().create(
862 862 c.user.user_id, fingerprint, key.keydata, description)
863 863 ssh_key_data = ssh_key.get_api_data()
864 864
865 865 audit_logger.store_web(
866 866 'user.edit.ssh_key.add', action_data={
867 867 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
868 868 user=self._rhodecode_user, )
869 869 Session().commit()
870 870
871 871 # Trigger an event on change of keys.
872 872 trigger(SshKeyFileChangeEvent(), self.request.registry)
873 873
874 874 h.flash(_("Ssh Key successfully created"), category='success')
875 875
876 876 except IntegrityError:
877 877 log.exception("Exception during ssh key saving")
878 878 err = 'Such key with fingerprint `{}` already exists, ' \
879 879 'please use a different one'.format(fingerprint)
880 880 h.flash(_('An error occurred during ssh key saving: {}').format(err),
881 881 category='error')
882 882 except Exception as e:
883 883 log.exception("Exception during ssh key saving")
884 884 h.flash(_('An error occurred during ssh key saving: {}').format(e),
885 885 category='error')
886 886
887 887 return HTTPFound(
888 888 h.route_path('edit_user_ssh_keys', user_id=user_id))
889 889
890 890 @LoginRequired()
891 891 @HasPermissionAllDecorator('hg.admin')
892 892 @CSRFRequired()
893 893 @view_config(
894 894 route_name='edit_user_ssh_keys_delete', request_method='POST')
895 895 def ssh_keys_delete(self):
896 896 _ = self.request.translate
897 897 c = self.load_default_context()
898 898
899 899 user_id = self.db_user_id
900 900 c.user = self.db_user
901 901
902 902 user_data = c.user.get_api_data()
903 903
904 904 del_ssh_key = self.request.POST.get('del_ssh_key')
905 905
906 906 if del_ssh_key:
907 907 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
908 908 ssh_key_data = ssh_key.get_api_data()
909 909
910 910 SshKeyModel().delete(del_ssh_key, c.user.user_id)
911 911 audit_logger.store_web(
912 912 'user.edit.ssh_key.delete', action_data={
913 913 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
914 914 user=self._rhodecode_user,)
915 915 Session().commit()
916 916 # Trigger an event on change of keys.
917 917 trigger(SshKeyFileChangeEvent(), self.request.registry)
918 918 h.flash(_("Ssh key successfully deleted"), category='success')
919 919
920 920 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
921 921
922 922 @LoginRequired()
923 923 @HasPermissionAllDecorator('hg.admin')
924 924 @view_config(
925 925 route_name='edit_user_emails', request_method='GET',
926 926 renderer='rhodecode:templates/admin/users/user_edit.mako')
927 927 def emails(self):
928 928 _ = self.request.translate
929 929 c = self.load_default_context()
930 930 c.user = self.db_user
931 931
932 932 c.active = 'emails'
933 933 c.user_email_map = UserEmailMap.query() \
934 934 .filter(UserEmailMap.user == c.user).all()
935 935
936 936 return self._get_template_context(c)
937 937
938 938 @LoginRequired()
939 939 @HasPermissionAllDecorator('hg.admin')
940 940 @CSRFRequired()
941 941 @view_config(
942 942 route_name='edit_user_emails_add', request_method='POST')
943 943 def emails_add(self):
944 944 _ = self.request.translate
945 945 c = self.load_default_context()
946 946
947 947 user_id = self.db_user_id
948 948 c.user = self.db_user
949 949
950 950 email = self.request.POST.get('new_email')
951 951 user_data = c.user.get_api_data()
952 952 try:
953 953
954 954 form = UserExtraEmailForm(self.request.translate)()
955 955 data = form.to_python({'email': email})
956 956 email = data['email']
957 957
958 958 UserModel().add_extra_email(c.user.user_id, email)
959 959 audit_logger.store_web(
960 960 'user.edit.email.add',
961 961 action_data={'email': email, 'user': user_data},
962 962 user=self._rhodecode_user)
963 963 Session().commit()
964 964 h.flash(_("Added new email address `%s` for user account") % email,
965 965 category='success')
966 966 except formencode.Invalid as error:
967 967 h.flash(h.escape(error.error_dict['email']), category='error')
968 968 except IntegrityError:
969 969 log.warning("Email %s already exists", email)
970 970 h.flash(_('Email `{}` is already registered for another user.').format(email),
971 971 category='error')
972 972 except Exception:
973 973 log.exception("Exception during email saving")
974 974 h.flash(_('An error occurred during email saving'),
975 975 category='error')
976 976 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
977 977
978 978 @LoginRequired()
979 979 @HasPermissionAllDecorator('hg.admin')
980 980 @CSRFRequired()
981 981 @view_config(
982 982 route_name='edit_user_emails_delete', request_method='POST')
983 983 def emails_delete(self):
984 984 _ = self.request.translate
985 985 c = self.load_default_context()
986 986
987 987 user_id = self.db_user_id
988 988 c.user = self.db_user
989 989
990 990 email_id = self.request.POST.get('del_email_id')
991 991 user_model = UserModel()
992 992
993 993 email = UserEmailMap.query().get(email_id).email
994 994 user_data = c.user.get_api_data()
995 995 user_model.delete_extra_email(c.user.user_id, email_id)
996 996 audit_logger.store_web(
997 997 'user.edit.email.delete',
998 998 action_data={'email': email, 'user': user_data},
999 999 user=self._rhodecode_user)
1000 1000 Session().commit()
1001 1001 h.flash(_("Removed email address from user account"),
1002 1002 category='success')
1003 1003 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1004 1004
1005 1005 @LoginRequired()
1006 1006 @HasPermissionAllDecorator('hg.admin')
1007 1007 @view_config(
1008 1008 route_name='edit_user_ips', request_method='GET',
1009 1009 renderer='rhodecode:templates/admin/users/user_edit.mako')
1010 1010 def ips(self):
1011 1011 _ = self.request.translate
1012 1012 c = self.load_default_context()
1013 1013 c.user = self.db_user
1014 1014
1015 1015 c.active = 'ips'
1016 1016 c.user_ip_map = UserIpMap.query() \
1017 1017 .filter(UserIpMap.user == c.user).all()
1018 1018
1019 1019 c.inherit_default_ips = c.user.inherit_default_permissions
1020 1020 c.default_user_ip_map = UserIpMap.query() \
1021 1021 .filter(UserIpMap.user == User.get_default_user()).all()
1022 1022
1023 1023 return self._get_template_context(c)
1024 1024
1025 1025 @LoginRequired()
1026 1026 @HasPermissionAllDecorator('hg.admin')
1027 1027 @CSRFRequired()
1028 1028 @view_config(
1029 1029 route_name='edit_user_ips_add', request_method='POST')
1030 1030 # NOTE(marcink): this view is allowed for default users, as we can
1031 1031 # edit their IP white list
1032 1032 def ips_add(self):
1033 1033 _ = self.request.translate
1034 1034 c = self.load_default_context()
1035 1035
1036 1036 user_id = self.db_user_id
1037 1037 c.user = self.db_user
1038 1038
1039 1039 user_model = UserModel()
1040 1040 desc = self.request.POST.get('description')
1041 1041 try:
1042 1042 ip_list = user_model.parse_ip_range(
1043 1043 self.request.POST.get('new_ip'))
1044 1044 except Exception as e:
1045 1045 ip_list = []
1046 1046 log.exception("Exception during ip saving")
1047 1047 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1048 1048 category='error')
1049 1049 added = []
1050 1050 user_data = c.user.get_api_data()
1051 1051 for ip in ip_list:
1052 1052 try:
1053 1053 form = UserExtraIpForm(self.request.translate)()
1054 1054 data = form.to_python({'ip': ip})
1055 1055 ip = data['ip']
1056 1056
1057 1057 user_model.add_extra_ip(c.user.user_id, ip, desc)
1058 1058 audit_logger.store_web(
1059 1059 'user.edit.ip.add',
1060 1060 action_data={'ip': ip, 'user': user_data},
1061 1061 user=self._rhodecode_user)
1062 1062 Session().commit()
1063 1063 added.append(ip)
1064 1064 except formencode.Invalid as error:
1065 1065 msg = error.error_dict['ip']
1066 1066 h.flash(msg, category='error')
1067 1067 except Exception:
1068 1068 log.exception("Exception during ip saving")
1069 1069 h.flash(_('An error occurred during ip saving'),
1070 1070 category='error')
1071 1071 if added:
1072 1072 h.flash(
1073 1073 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1074 1074 category='success')
1075 1075 if 'default_user' in self.request.POST:
1076 1076 # case for editing global IP list we do it for 'DEFAULT' user
1077 1077 raise HTTPFound(h.route_path('admin_permissions_ips'))
1078 1078 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1079 1079
1080 1080 @LoginRequired()
1081 1081 @HasPermissionAllDecorator('hg.admin')
1082 1082 @CSRFRequired()
1083 1083 @view_config(
1084 1084 route_name='edit_user_ips_delete', request_method='POST')
1085 1085 # NOTE(marcink): this view is allowed for default users, as we can
1086 1086 # edit their IP white list
1087 1087 def ips_delete(self):
1088 1088 _ = self.request.translate
1089 1089 c = self.load_default_context()
1090 1090
1091 1091 user_id = self.db_user_id
1092 1092 c.user = self.db_user
1093 1093
1094 1094 ip_id = self.request.POST.get('del_ip_id')
1095 1095 user_model = UserModel()
1096 1096 user_data = c.user.get_api_data()
1097 1097 ip = UserIpMap.query().get(ip_id).ip_addr
1098 1098 user_model.delete_extra_ip(c.user.user_id, ip_id)
1099 1099 audit_logger.store_web(
1100 1100 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1101 1101 user=self._rhodecode_user)
1102 1102 Session().commit()
1103 1103 h.flash(_("Removed ip address from user whitelist"), category='success')
1104 1104
1105 1105 if 'default_user' in self.request.POST:
1106 1106 # case for editing global IP list we do it for 'DEFAULT' user
1107 1107 raise HTTPFound(h.route_path('admin_permissions_ips'))
1108 1108 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1109 1109
1110 1110 @LoginRequired()
1111 1111 @HasPermissionAllDecorator('hg.admin')
1112 1112 @view_config(
1113 1113 route_name='edit_user_groups_management', request_method='GET',
1114 1114 renderer='rhodecode:templates/admin/users/user_edit.mako')
1115 1115 def groups_management(self):
1116 1116 c = self.load_default_context()
1117 1117 c.user = self.db_user
1118 1118 c.data = c.user.group_member
1119 1119
1120 1120 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1121 1121 for group in c.user.group_member]
1122 1122 c.groups = json.dumps(groups)
1123 1123 c.active = 'groups'
1124 1124
1125 1125 return self._get_template_context(c)
1126 1126
1127 1127 @LoginRequired()
1128 1128 @HasPermissionAllDecorator('hg.admin')
1129 1129 @CSRFRequired()
1130 1130 @view_config(
1131 1131 route_name='edit_user_groups_management_updates', request_method='POST')
1132 1132 def groups_management_updates(self):
1133 1133 _ = self.request.translate
1134 1134 c = self.load_default_context()
1135 1135
1136 1136 user_id = self.db_user_id
1137 1137 c.user = self.db_user
1138 1138
1139 1139 user_groups = set(self.request.POST.getall('users_group_id'))
1140 1140 user_groups_objects = []
1141 1141
1142 1142 for ugid in user_groups:
1143 1143 user_groups_objects.append(
1144 1144 UserGroupModel().get_group(safe_int(ugid)))
1145 1145 user_group_model = UserGroupModel()
1146 1146 added_to_groups, removed_from_groups = \
1147 1147 user_group_model.change_groups(c.user, user_groups_objects)
1148 1148
1149 1149 user_data = c.user.get_api_data()
1150 1150 for user_group_id in added_to_groups:
1151 1151 user_group = UserGroup.get(user_group_id)
1152 1152 old_values = user_group.get_api_data()
1153 1153 audit_logger.store_web(
1154 1154 'user_group.edit.member.add',
1155 1155 action_data={'user': user_data, 'old_data': old_values},
1156 1156 user=self._rhodecode_user)
1157 1157
1158 1158 for user_group_id in removed_from_groups:
1159 1159 user_group = UserGroup.get(user_group_id)
1160 1160 old_values = user_group.get_api_data()
1161 1161 audit_logger.store_web(
1162 1162 'user_group.edit.member.delete',
1163 1163 action_data={'user': user_data, 'old_data': old_values},
1164 1164 user=self._rhodecode_user)
1165 1165
1166 1166 Session().commit()
1167 1167 c.active = 'user_groups_management'
1168 1168 h.flash(_("Groups successfully changed"), category='success')
1169 1169
1170 1170 return HTTPFound(h.route_path(
1171 1171 'edit_user_groups_management', user_id=user_id))
1172 1172
1173 1173 @LoginRequired()
1174 1174 @HasPermissionAllDecorator('hg.admin')
1175 1175 @view_config(
1176 1176 route_name='edit_user_audit_logs', request_method='GET',
1177 1177 renderer='rhodecode:templates/admin/users/user_edit.mako')
1178 1178 def user_audit_logs(self):
1179 1179 _ = self.request.translate
1180 1180 c = self.load_default_context()
1181 1181 c.user = self.db_user
1182 1182
1183 1183 c.active = 'audit'
1184 1184
1185 1185 p = safe_int(self.request.GET.get('page', 1), 1)
1186 1186
1187 1187 filter_term = self.request.GET.get('filter')
1188 1188 user_log = UserModel().get_user_log(c.user, filter_term)
1189 1189
1190 1190 def url_generator(**kw):
1191 1191 if filter_term:
1192 1192 kw['filter'] = filter_term
1193 1193 return self.request.current_route_path(_query=kw)
1194 1194
1195 1195 c.audit_logs = h.Page(
1196 1196 user_log, page=p, items_per_page=10, url=url_generator)
1197 1197 c.filter_term = filter_term
1198 1198 return self._get_template_context(c)
1199 1199
1200 1200 @LoginRequired()
1201 1201 @HasPermissionAllDecorator('hg.admin')
1202 1202 @view_config(
1203 route_name='edit_user_audit_logs_download', request_method='GET',
1204 renderer='string')
1205 def user_audit_logs_download(self):
1206 _ = self.request.translate
1207 c = self.load_default_context()
1208 c.user = self.db_user
1209
1210 user_log = UserModel().get_user_log(c.user, filter_term=None)
1211
1212 audit_log_data = {}
1213 for entry in user_log:
1214 audit_log_data[entry.user_log_id] = entry.get_dict()
1215
1216 response = Response(json.dumps(audit_log_data, indent=4))
1217 response.content_disposition = str(
1218 'attachment; filename=%s' % 'user_{}_audit_logs.json'.format(c.user.user_id))
1219 response.content_type = 'application/json'
1220
1221 return response
1222
1223 @LoginRequired()
1224 @HasPermissionAllDecorator('hg.admin')
1225 @view_config(
1203 1226 route_name='edit_user_perms_summary', request_method='GET',
1204 1227 renderer='rhodecode:templates/admin/users/user_edit.mako')
1205 1228 def user_perms_summary(self):
1206 1229 _ = self.request.translate
1207 1230 c = self.load_default_context()
1208 1231 c.user = self.db_user
1209 1232
1210 1233 c.active = 'perms_summary'
1211 1234 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1212 1235
1213 1236 return self._get_template_context(c)
1214 1237
1215 1238 @LoginRequired()
1216 1239 @HasPermissionAllDecorator('hg.admin')
1217 1240 @view_config(
1218 1241 route_name='edit_user_perms_summary_json', request_method='GET',
1219 1242 renderer='json_ext')
1220 1243 def user_perms_summary_json(self):
1221 1244 self.load_default_context()
1222 1245 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1223 1246
1224 1247 return perm_user.permissions
1225 1248
1226 1249 @LoginRequired()
1227 1250 @HasPermissionAllDecorator('hg.admin')
1228 1251 @view_config(
1229 1252 route_name='edit_user_caches', request_method='GET',
1230 1253 renderer='rhodecode:templates/admin/users/user_edit.mako')
1231 1254 def user_caches(self):
1232 1255 _ = self.request.translate
1233 1256 c = self.load_default_context()
1234 1257 c.user = self.db_user
1235 1258
1236 1259 c.active = 'caches'
1237 1260 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1238 1261
1239 1262 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1240 1263 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1241 1264 c.backend = c.region.backend
1242 1265 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1243 1266
1244 1267 return self._get_template_context(c)
1245 1268
1246 1269 @LoginRequired()
1247 1270 @HasPermissionAllDecorator('hg.admin')
1248 1271 @CSRFRequired()
1249 1272 @view_config(
1250 1273 route_name='edit_user_caches_update', request_method='POST')
1251 1274 def user_caches_update(self):
1252 1275 _ = self.request.translate
1253 1276 c = self.load_default_context()
1254 1277 c.user = self.db_user
1255 1278
1256 1279 c.active = 'caches'
1257 1280 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1258 1281
1259 1282 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1260 1283 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1261 1284
1262 1285 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1263 1286
1264 1287 return HTTPFound(h.route_path(
1265 1288 'edit_user_caches', user_id=c.user.user_id))
@@ -1,379 +1,380 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('favicon', '/favicon.ico', []);
16 16 pyroutes.register('robots', '/robots.txt', []);
17 17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
18 18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
19 19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
20 20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
21 21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
22 22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
23 23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
24 24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
25 25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
26 26 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
27 27 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
28 28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
29 29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
30 30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
31 31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
32 32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
33 33 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
34 34 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
35 35 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
36 36 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
37 37 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
38 38 pyroutes.register('admin_home', '/_admin', []);
39 39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
40 40 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
41 41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
42 42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
43 43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
44 44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
45 45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
46 46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
47 47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
48 48 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
49 49 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
50 50 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
51 51 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
52 52 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
53 53 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
54 54 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
55 55 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
56 56 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
57 57 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
58 58 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
59 59 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
60 60 pyroutes.register('admin_settings', '/_admin/settings', []);
61 61 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
62 62 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
63 63 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
64 64 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
65 65 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
66 66 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
67 67 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
68 68 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
69 69 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
70 70 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
71 71 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
72 72 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
73 73 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
74 74 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
75 75 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
76 76 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
77 77 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
78 78 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
79 79 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
80 80 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
81 81 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
82 82 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
83 83 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
84 84 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
85 85 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
86 86 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
87 87 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
88 88 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
89 89 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
90 90 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
91 91 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
92 92 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
93 93 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
94 94 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
95 95 pyroutes.register('users', '/_admin/users', []);
96 96 pyroutes.register('users_data', '/_admin/users_data', []);
97 97 pyroutes.register('users_create', '/_admin/users/create', []);
98 98 pyroutes.register('users_new', '/_admin/users/new', []);
99 99 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
100 100 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
101 101 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
102 102 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
103 103 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
104 104 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
105 105 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
106 106 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
107 107 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
108 108 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
109 109 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
110 110 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
111 111 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
112 112 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
113 113 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
114 114 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
115 115 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
116 116 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
117 117 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
118 118 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
119 119 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
120 120 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
121 121 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
122 122 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
123 123 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
124 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
124 125 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
125 126 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
126 127 pyroutes.register('user_groups', '/_admin/user_groups', []);
127 128 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
128 129 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
129 130 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
130 131 pyroutes.register('repos', '/_admin/repos', []);
131 132 pyroutes.register('repo_new', '/_admin/repos/new', []);
132 133 pyroutes.register('repo_create', '/_admin/repos/create', []);
133 134 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
134 135 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
135 136 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
136 137 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
137 138 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
138 139 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
139 140 pyroutes.register('channelstream_proxy', '/_channelstream', []);
140 141 pyroutes.register('upload_file', '/_file_store/upload', []);
141 142 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
142 143 pyroutes.register('logout', '/_admin/logout', []);
143 144 pyroutes.register('reset_password', '/_admin/password_reset', []);
144 145 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
145 146 pyroutes.register('home', '/', []);
146 147 pyroutes.register('user_autocomplete_data', '/_users', []);
147 148 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
148 149 pyroutes.register('repo_list_data', '/_repos', []);
149 150 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
150 151 pyroutes.register('goto_switcher_data', '/_goto_data', []);
151 152 pyroutes.register('markup_preview', '/_markup_preview', []);
152 153 pyroutes.register('file_preview', '/_file_preview', []);
153 154 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
154 155 pyroutes.register('journal', '/_admin/journal', []);
155 156 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
156 157 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
157 158 pyroutes.register('journal_public', '/_admin/public_journal', []);
158 159 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
159 160 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
160 161 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
161 162 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
162 163 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
163 164 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
164 165 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
165 166 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
166 167 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
167 168 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
168 169 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
169 170 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
170 171 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
171 172 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
172 173 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
173 174 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
174 175 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
175 176 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
176 177 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
177 178 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
178 179 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
179 180 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
180 181 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
181 182 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
182 183 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
183 184 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
184 185 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
185 186 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
186 187 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
187 188 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
188 189 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
189 190 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
190 191 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
191 192 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
192 193 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
193 194 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
194 195 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
195 196 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
196 197 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 198 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 199 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 200 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
200 201 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
201 202 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
202 203 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
203 204 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
204 205 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
205 206 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
206 207 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
207 208 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
208 209 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
209 210 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
210 211 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
211 212 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
212 213 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
213 214 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
214 215 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
215 216 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
216 217 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
217 218 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
218 219 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
219 220 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
220 221 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
221 222 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
222 223 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
223 224 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
224 225 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
225 226 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
226 227 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
227 228 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
228 229 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
229 230 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
230 231 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
231 232 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
232 233 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
233 234 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
234 235 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
235 236 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
236 237 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
237 238 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
238 239 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
239 240 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
240 241 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
241 242 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
242 243 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
243 244 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
244 245 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
245 246 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
246 247 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
247 248 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
248 249 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
249 250 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
250 251 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
251 252 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
252 253 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
253 254 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
254 255 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
255 256 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
256 257 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
257 258 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
258 259 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
259 260 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
260 261 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
261 262 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
262 263 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
263 264 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
264 265 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
265 266 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
266 267 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
267 268 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
268 269 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
269 270 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
270 271 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
271 272 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
272 273 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
273 274 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
274 275 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
275 276 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
276 277 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
277 278 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
278 279 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
279 280 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
280 281 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
281 282 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
282 283 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
283 284 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
284 285 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
285 286 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
286 287 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
287 288 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
288 289 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
289 290 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
290 291 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
291 292 pyroutes.register('search', '/_admin/search', []);
292 293 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
293 294 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
294 295 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
295 296 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
296 297 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
297 298 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
298 299 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
299 300 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
300 301 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
301 302 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
302 303 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
303 304 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
304 305 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
305 306 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
306 307 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
307 308 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
308 309 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
309 310 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
310 311 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
311 312 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
312 313 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
313 314 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
314 315 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
315 316 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
316 317 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
317 318 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
318 319 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
319 320 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
320 321 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
321 322 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
322 323 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
323 324 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
324 325 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
325 326 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
326 327 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
327 328 pyroutes.register('gists_show', '/_admin/gists', []);
328 329 pyroutes.register('gists_new', '/_admin/gists/new', []);
329 330 pyroutes.register('gists_create', '/_admin/gists/create', []);
330 331 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
331 332 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
332 333 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
333 334 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
334 335 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
335 336 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
336 337 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
337 338 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
338 339 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
339 340 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
340 341 pyroutes.register('apiv2', '/_admin/api', []);
341 342 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
342 343 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
343 344 pyroutes.register('login', '/_admin/login', []);
344 345 pyroutes.register('register', '/_admin/register', []);
345 346 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
346 347 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
347 348 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
348 349 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
349 350 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
350 351 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
351 352 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
352 353 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
353 354 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
354 355 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
355 356 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
356 357 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
357 358 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
358 359 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
359 360 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
360 361 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
361 362 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
362 363 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
363 364 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
364 365 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
365 366 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
366 367 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
367 368 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
368 369 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
369 370 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
370 371 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
371 372 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
372 373 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
373 374 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
374 375 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
375 376 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
376 377 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
377 378 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
378 379 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
379 380 }
@@ -1,24 +1,25 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%namespace name="base" file="/base/base.mako"/>
3 3
4 4
5 5 <div class="panel panel-default">
6 6 <div class="panel-heading">
7 7 <h3 class="panel-title">${_('User Audit Logs')} -
8 8 ${_ungettext('%s entry', '%s entries', c.audit_logs.item_count) % (c.audit_logs.item_count)}
9 9 </h3>
10 <a href="${h.route_path('edit_user_audit_logs_download', user_id=c.user.user_id)}" class="panel-edit">${_('Download as JSON')}</a>
10 11 </div>
11 12 <div class="panel-body">
12 13
13 14 ${h.form(None, id_="filter_form", method="get")}
14 15 <input class="q_filter_box ${'' if c.filter_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.filter_term or ''}" placeholder="${_('audit filter...')}"/>
15 16 <input type='submit' value="${_('filter')}" class="btn" />
16 17 ${h.end_form()}
17 18
18 19 <p class="filterexample" style="position: inherit" onclick="$('#search-help').toggle()">${_('Example Queries')}</p>
19 20 <pre id="search-help" style="display: none">${h.tooltip(h.journal_filter_help(request))}</pre>
20 21
21 22 <%include file="/admin/admin_log_base.mako" />
22 23
23 24 </div>
24 25 </div>
General Comments 0
You need to be logged in to leave comments. Login now