##// END OF EJS Templates
users: added more secure way for fetching authentication tokens....
marcink -
r4316:674186c0 default
parent child Browse files
Show More
@@ -1,462 +1,466 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
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 config.add_route(
30 30 name='admin_audit_logs',
31 31 pattern='/audit_logs')
32 32
33 33 config.add_route(
34 34 name='admin_audit_log_entry',
35 35 pattern='/audit_logs/{audit_log_id}')
36 36
37 37 config.add_route(
38 38 name='pull_requests_global_0', # backward compat
39 39 pattern='/pull_requests/{pull_request_id:\d+}')
40 40 config.add_route(
41 41 name='pull_requests_global_1', # backward compat
42 42 pattern='/pull-requests/{pull_request_id:\d+}')
43 43 config.add_route(
44 44 name='pull_requests_global',
45 45 pattern='/pull-request/{pull_request_id:\d+}')
46 46
47 47 config.add_route(
48 48 name='admin_settings_open_source',
49 49 pattern='/settings/open_source')
50 50 config.add_route(
51 51 name='admin_settings_vcs_svn_generate_cfg',
52 52 pattern='/settings/vcs/svn_generate_cfg')
53 53
54 54 config.add_route(
55 55 name='admin_settings_system',
56 56 pattern='/settings/system')
57 57 config.add_route(
58 58 name='admin_settings_system_update',
59 59 pattern='/settings/system/updates')
60 60
61 61 config.add_route(
62 62 name='admin_settings_exception_tracker',
63 63 pattern='/settings/exceptions')
64 64 config.add_route(
65 65 name='admin_settings_exception_tracker_delete_all',
66 66 pattern='/settings/exceptions/delete')
67 67 config.add_route(
68 68 name='admin_settings_exception_tracker_show',
69 69 pattern='/settings/exceptions/{exception_id}')
70 70 config.add_route(
71 71 name='admin_settings_exception_tracker_delete',
72 72 pattern='/settings/exceptions/{exception_id}/delete')
73 73
74 74 config.add_route(
75 75 name='admin_settings_sessions',
76 76 pattern='/settings/sessions')
77 77 config.add_route(
78 78 name='admin_settings_sessions_cleanup',
79 79 pattern='/settings/sessions/cleanup')
80 80
81 81 config.add_route(
82 82 name='admin_settings_process_management',
83 83 pattern='/settings/process_management')
84 84 config.add_route(
85 85 name='admin_settings_process_management_data',
86 86 pattern='/settings/process_management/data')
87 87 config.add_route(
88 88 name='admin_settings_process_management_signal',
89 89 pattern='/settings/process_management/signal')
90 90 config.add_route(
91 91 name='admin_settings_process_management_master_signal',
92 92 pattern='/settings/process_management/master_signal')
93 93
94 94 # default settings
95 95 config.add_route(
96 96 name='admin_defaults_repositories',
97 97 pattern='/defaults/repositories')
98 98 config.add_route(
99 99 name='admin_defaults_repositories_update',
100 100 pattern='/defaults/repositories/update')
101 101
102 102 # admin settings
103 103
104 104 config.add_route(
105 105 name='admin_settings',
106 106 pattern='/settings')
107 107 config.add_route(
108 108 name='admin_settings_update',
109 109 pattern='/settings/update')
110 110
111 111 config.add_route(
112 112 name='admin_settings_global',
113 113 pattern='/settings/global')
114 114 config.add_route(
115 115 name='admin_settings_global_update',
116 116 pattern='/settings/global/update')
117 117
118 118 config.add_route(
119 119 name='admin_settings_vcs',
120 120 pattern='/settings/vcs')
121 121 config.add_route(
122 122 name='admin_settings_vcs_update',
123 123 pattern='/settings/vcs/update')
124 124 config.add_route(
125 125 name='admin_settings_vcs_svn_pattern_delete',
126 126 pattern='/settings/vcs/svn_pattern_delete')
127 127
128 128 config.add_route(
129 129 name='admin_settings_mapping',
130 130 pattern='/settings/mapping')
131 131 config.add_route(
132 132 name='admin_settings_mapping_update',
133 133 pattern='/settings/mapping/update')
134 134
135 135 config.add_route(
136 136 name='admin_settings_visual',
137 137 pattern='/settings/visual')
138 138 config.add_route(
139 139 name='admin_settings_visual_update',
140 140 pattern='/settings/visual/update')
141 141
142 142 config.add_route(
143 143 name='admin_settings_issuetracker',
144 144 pattern='/settings/issue-tracker')
145 145 config.add_route(
146 146 name='admin_settings_issuetracker_update',
147 147 pattern='/settings/issue-tracker/update')
148 148 config.add_route(
149 149 name='admin_settings_issuetracker_test',
150 150 pattern='/settings/issue-tracker/test')
151 151 config.add_route(
152 152 name='admin_settings_issuetracker_delete',
153 153 pattern='/settings/issue-tracker/delete')
154 154
155 155 config.add_route(
156 156 name='admin_settings_email',
157 157 pattern='/settings/email')
158 158 config.add_route(
159 159 name='admin_settings_email_update',
160 160 pattern='/settings/email/update')
161 161
162 162 config.add_route(
163 163 name='admin_settings_hooks',
164 164 pattern='/settings/hooks')
165 165 config.add_route(
166 166 name='admin_settings_hooks_update',
167 167 pattern='/settings/hooks/update')
168 168 config.add_route(
169 169 name='admin_settings_hooks_delete',
170 170 pattern='/settings/hooks/delete')
171 171
172 172 config.add_route(
173 173 name='admin_settings_search',
174 174 pattern='/settings/search')
175 175
176 176 config.add_route(
177 177 name='admin_settings_labs',
178 178 pattern='/settings/labs')
179 179 config.add_route(
180 180 name='admin_settings_labs_update',
181 181 pattern='/settings/labs/update')
182 182
183 183 # Automation EE feature
184 184 config.add_route(
185 185 'admin_settings_automation',
186 186 pattern=ADMIN_PREFIX + '/settings/automation')
187 187
188 188 # global permissions
189 189
190 190 config.add_route(
191 191 name='admin_permissions_application',
192 192 pattern='/permissions/application')
193 193 config.add_route(
194 194 name='admin_permissions_application_update',
195 195 pattern='/permissions/application/update')
196 196
197 197 config.add_route(
198 198 name='admin_permissions_global',
199 199 pattern='/permissions/global')
200 200 config.add_route(
201 201 name='admin_permissions_global_update',
202 202 pattern='/permissions/global/update')
203 203
204 204 config.add_route(
205 205 name='admin_permissions_object',
206 206 pattern='/permissions/object')
207 207 config.add_route(
208 208 name='admin_permissions_object_update',
209 209 pattern='/permissions/object/update')
210 210
211 211 # Branch perms EE feature
212 212 config.add_route(
213 213 name='admin_permissions_branch',
214 214 pattern='/permissions/branch')
215 215
216 216 config.add_route(
217 217 name='admin_permissions_ips',
218 218 pattern='/permissions/ips')
219 219
220 220 config.add_route(
221 221 name='admin_permissions_overview',
222 222 pattern='/permissions/overview')
223 223
224 224 config.add_route(
225 225 name='admin_permissions_auth_token_access',
226 226 pattern='/permissions/auth_token_access')
227 227
228 228 config.add_route(
229 229 name='admin_permissions_ssh_keys',
230 230 pattern='/permissions/ssh_keys')
231 231 config.add_route(
232 232 name='admin_permissions_ssh_keys_data',
233 233 pattern='/permissions/ssh_keys/data')
234 234 config.add_route(
235 235 name='admin_permissions_ssh_keys_update',
236 236 pattern='/permissions/ssh_keys/update')
237 237
238 238 # users admin
239 239 config.add_route(
240 240 name='users',
241 241 pattern='/users')
242 242
243 243 config.add_route(
244 244 name='users_data',
245 245 pattern='/users_data')
246 246
247 247 config.add_route(
248 248 name='users_create',
249 249 pattern='/users/create')
250 250
251 251 config.add_route(
252 252 name='users_new',
253 253 pattern='/users/new')
254 254
255 255 # user management
256 256 config.add_route(
257 257 name='user_edit',
258 258 pattern='/users/{user_id:\d+}/edit',
259 259 user_route=True)
260 260 config.add_route(
261 261 name='user_edit_advanced',
262 262 pattern='/users/{user_id:\d+}/edit/advanced',
263 263 user_route=True)
264 264 config.add_route(
265 265 name='user_edit_global_perms',
266 266 pattern='/users/{user_id:\d+}/edit/global_permissions',
267 267 user_route=True)
268 268 config.add_route(
269 269 name='user_edit_global_perms_update',
270 270 pattern='/users/{user_id:\d+}/edit/global_permissions/update',
271 271 user_route=True)
272 272 config.add_route(
273 273 name='user_update',
274 274 pattern='/users/{user_id:\d+}/update',
275 275 user_route=True)
276 276 config.add_route(
277 277 name='user_delete',
278 278 pattern='/users/{user_id:\d+}/delete',
279 279 user_route=True)
280 280 config.add_route(
281 281 name='user_enable_force_password_reset',
282 282 pattern='/users/{user_id:\d+}/password_reset_enable',
283 283 user_route=True)
284 284 config.add_route(
285 285 name='user_disable_force_password_reset',
286 286 pattern='/users/{user_id:\d+}/password_reset_disable',
287 287 user_route=True)
288 288 config.add_route(
289 289 name='user_create_personal_repo_group',
290 290 pattern='/users/{user_id:\d+}/create_repo_group',
291 291 user_route=True)
292 292
293 293 # user notice
294 294 config.add_route(
295 295 name='user_notice_dismiss',
296 296 pattern='/users/{user_id:\d+}/notice_dismiss',
297 297 user_route=True)
298 298
299 299 # user auth tokens
300 300 config.add_route(
301 301 name='edit_user_auth_tokens',
302 302 pattern='/users/{user_id:\d+}/edit/auth_tokens',
303 303 user_route=True)
304 304 config.add_route(
305 name='edit_user_auth_tokens_view',
306 pattern='/users/{user_id:\d+}/edit/auth_tokens/view',
307 user_route=True)
308 config.add_route(
305 309 name='edit_user_auth_tokens_add',
306 310 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
307 311 user_route=True)
308 312 config.add_route(
309 313 name='edit_user_auth_tokens_delete',
310 314 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete',
311 315 user_route=True)
312 316
313 317 # user ssh keys
314 318 config.add_route(
315 319 name='edit_user_ssh_keys',
316 320 pattern='/users/{user_id:\d+}/edit/ssh_keys',
317 321 user_route=True)
318 322 config.add_route(
319 323 name='edit_user_ssh_keys_generate_keypair',
320 324 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate',
321 325 user_route=True)
322 326 config.add_route(
323 327 name='edit_user_ssh_keys_add',
324 328 pattern='/users/{user_id:\d+}/edit/ssh_keys/new',
325 329 user_route=True)
326 330 config.add_route(
327 331 name='edit_user_ssh_keys_delete',
328 332 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete',
329 333 user_route=True)
330 334
331 335 # user emails
332 336 config.add_route(
333 337 name='edit_user_emails',
334 338 pattern='/users/{user_id:\d+}/edit/emails',
335 339 user_route=True)
336 340 config.add_route(
337 341 name='edit_user_emails_add',
338 342 pattern='/users/{user_id:\d+}/edit/emails/new',
339 343 user_route=True)
340 344 config.add_route(
341 345 name='edit_user_emails_delete',
342 346 pattern='/users/{user_id:\d+}/edit/emails/delete',
343 347 user_route=True)
344 348
345 349 # user IPs
346 350 config.add_route(
347 351 name='edit_user_ips',
348 352 pattern='/users/{user_id:\d+}/edit/ips',
349 353 user_route=True)
350 354 config.add_route(
351 355 name='edit_user_ips_add',
352 356 pattern='/users/{user_id:\d+}/edit/ips/new',
353 357 user_route_with_default=True) # enabled for default user too
354 358 config.add_route(
355 359 name='edit_user_ips_delete',
356 360 pattern='/users/{user_id:\d+}/edit/ips/delete',
357 361 user_route_with_default=True) # enabled for default user too
358 362
359 363 # user perms
360 364 config.add_route(
361 365 name='edit_user_perms_summary',
362 366 pattern='/users/{user_id:\d+}/edit/permissions_summary',
363 367 user_route=True)
364 368 config.add_route(
365 369 name='edit_user_perms_summary_json',
366 370 pattern='/users/{user_id:\d+}/edit/permissions_summary/json',
367 371 user_route=True)
368 372
369 373 # user user groups management
370 374 config.add_route(
371 375 name='edit_user_groups_management',
372 376 pattern='/users/{user_id:\d+}/edit/groups_management',
373 377 user_route=True)
374 378
375 379 config.add_route(
376 380 name='edit_user_groups_management_updates',
377 381 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
378 382 user_route=True)
379 383
380 384 # user audit logs
381 385 config.add_route(
382 386 name='edit_user_audit_logs',
383 387 pattern='/users/{user_id:\d+}/edit/audit', user_route=True)
384 388
385 389 config.add_route(
386 390 name='edit_user_audit_logs_download',
387 391 pattern='/users/{user_id:\d+}/edit/audit/download', user_route=True)
388 392
389 393 # user caches
390 394 config.add_route(
391 395 name='edit_user_caches',
392 396 pattern='/users/{user_id:\d+}/edit/caches',
393 397 user_route=True)
394 398 config.add_route(
395 399 name='edit_user_caches_update',
396 400 pattern='/users/{user_id:\d+}/edit/caches/update',
397 401 user_route=True)
398 402
399 403 # user-groups admin
400 404 config.add_route(
401 405 name='user_groups',
402 406 pattern='/user_groups')
403 407
404 408 config.add_route(
405 409 name='user_groups_data',
406 410 pattern='/user_groups_data')
407 411
408 412 config.add_route(
409 413 name='user_groups_new',
410 414 pattern='/user_groups/new')
411 415
412 416 config.add_route(
413 417 name='user_groups_create',
414 418 pattern='/user_groups/create')
415 419
416 420 # repos admin
417 421 config.add_route(
418 422 name='repos',
419 423 pattern='/repos')
420 424
421 425 config.add_route(
422 426 name='repos_data',
423 427 pattern='/repos_data')
424 428
425 429 config.add_route(
426 430 name='repo_new',
427 431 pattern='/repos/new')
428 432
429 433 config.add_route(
430 434 name='repo_create',
431 435 pattern='/repos/create')
432 436
433 437 # repo groups admin
434 438 config.add_route(
435 439 name='repo_groups',
436 440 pattern='/repo_groups')
437 441
438 442 config.add_route(
439 443 name='repo_groups_data',
440 444 pattern='/repo_groups_data')
441 445
442 446 config.add_route(
443 447 name='repo_group_new',
444 448 pattern='/repo_group/new')
445 449
446 450 config.add_route(
447 451 name='repo_group_create',
448 452 pattern='/repo_group/create')
449 453
450 454
451 455 def includeme(config):
452 456 from rhodecode.apps._base.navigation import includeme as nav_includeme
453 457
454 458 # Create admin navigation registry and add it to the pyramid registry.
455 459 nav_includeme(config)
456 460
457 461 # main admin routes
458 462 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
459 463 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
460 464
461 465 # Scan module for configuration decorators.
462 466 config.scan('.views', ignore='.tests')
@@ -1,794 +1,794 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import 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 94 'edit_user_audit_logs_download':
95 95 ADMIN_PREFIX + '/users/{user_id}/edit/audit/download',
96 96
97 97 }[name].format(**kwargs)
98 98
99 99 if params:
100 100 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
101 101 return base_url
102 102
103 103
104 104 class TestAdminUsersView(TestController):
105 105
106 106 def test_show_users(self):
107 107 self.log_user()
108 108 self.app.get(route_path('users'))
109 109
110 110 def test_show_users_data(self, xhr_header):
111 111 self.log_user()
112 112 response = self.app.get(route_path(
113 113 'users_data'), extra_environ=xhr_header)
114 114
115 115 all_users = User.query().filter(
116 116 User.username != User.DEFAULT_USER).count()
117 117 assert response.json['recordsTotal'] == all_users
118 118
119 119 def test_show_users_data_filtered(self, xhr_header):
120 120 self.log_user()
121 121 response = self.app.get(route_path(
122 122 'users_data', params={'search[value]': 'empty_search'}),
123 123 extra_environ=xhr_header)
124 124
125 125 all_users = User.query().filter(
126 126 User.username != User.DEFAULT_USER).count()
127 127 assert response.json['recordsTotal'] == all_users
128 128 assert response.json['recordsFiltered'] == 0
129 129
130 130 def test_auth_tokens_default_user(self):
131 131 self.log_user()
132 132 user = User.get_default_user()
133 133 response = self.app.get(
134 134 route_path('edit_user_auth_tokens', user_id=user.user_id),
135 135 status=302)
136 136
137 137 def test_auth_tokens(self):
138 138 self.log_user()
139 139
140 140 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
141 141 user_id = user.user_id
142 142 auth_tokens = user.auth_tokens
143 143 response = self.app.get(
144 144 route_path('edit_user_auth_tokens', user_id=user_id))
145 145 for token in auth_tokens:
146 response.mustcontain(token)
146 response.mustcontain(token[:4])
147 147 response.mustcontain('never')
148 148
149 149 @pytest.mark.parametrize("desc, lifetime", [
150 150 ('forever', -1),
151 151 ('5mins', 60*5),
152 152 ('30days', 60*60*24*30),
153 153 ])
154 154 def test_add_auth_token(self, desc, lifetime, user_util):
155 155 self.log_user()
156 156 user = user_util.create_user()
157 157 user_id = user.user_id
158 158
159 159 response = self.app.post(
160 160 route_path('edit_user_auth_tokens_add', user_id=user_id),
161 161 {'description': desc, 'lifetime': lifetime,
162 162 'csrf_token': self.csrf_token})
163 163 assert_session_flash(response, 'Auth token successfully created')
164 164
165 165 response = response.follow()
166 166 user = User.get(user_id)
167 167 for auth_token in user.auth_tokens:
168 response.mustcontain(auth_token)
168 response.mustcontain(auth_token[:4])
169 169
170 170 def test_delete_auth_token(self, user_util):
171 171 self.log_user()
172 172 user = user_util.create_user()
173 173 user_id = user.user_id
174 174 keys = user.auth_tokens
175 175 assert 2 == len(keys)
176 176
177 177 response = self.app.post(
178 178 route_path('edit_user_auth_tokens_add', user_id=user_id),
179 179 {'description': 'desc', 'lifetime': -1,
180 180 'csrf_token': self.csrf_token})
181 181 assert_session_flash(response, 'Auth token successfully created')
182 182 response.follow()
183 183
184 184 # now delete our key
185 185 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
186 186 assert 3 == len(keys)
187 187
188 188 response = self.app.post(
189 189 route_path('edit_user_auth_tokens_delete', user_id=user_id),
190 190 {'del_auth_token': keys[0].user_api_key_id,
191 191 'csrf_token': self.csrf_token})
192 192
193 193 assert_session_flash(response, 'Auth token successfully deleted')
194 194 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
195 195 assert 2 == len(keys)
196 196
197 197 def test_ips(self):
198 198 self.log_user()
199 199 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
200 200 response = self.app.get(route_path('edit_user_ips', user_id=user.user_id))
201 201 response.mustcontain('All IP addresses are allowed')
202 202
203 203 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
204 204 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
205 205 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
206 206 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
207 207 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
208 208 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
209 209 ('127_bad_ip', 'foobar', 'foobar', True),
210 210 ])
211 211 def test_ips_add(self, user_util, test_name, ip, ip_range, failure):
212 212 self.log_user()
213 213 user = user_util.create_user(username=test_name)
214 214 user_id = user.user_id
215 215
216 216 response = self.app.post(
217 217 route_path('edit_user_ips_add', user_id=user_id),
218 218 params={'new_ip': ip, 'csrf_token': self.csrf_token})
219 219
220 220 if failure:
221 221 assert_session_flash(
222 222 response, 'Please enter a valid IPv4 or IpV6 address')
223 223 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
224 224
225 225 response.mustcontain(no=[ip])
226 226 response.mustcontain(no=[ip_range])
227 227
228 228 else:
229 229 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
230 230 response.mustcontain(ip)
231 231 response.mustcontain(ip_range)
232 232
233 233 def test_ips_delete(self, user_util):
234 234 self.log_user()
235 235 user = user_util.create_user()
236 236 user_id = user.user_id
237 237 ip = '127.0.0.1/32'
238 238 ip_range = '127.0.0.1 - 127.0.0.1'
239 239 new_ip = UserModel().add_extra_ip(user_id, ip)
240 240 Session().commit()
241 241 new_ip_id = new_ip.ip_id
242 242
243 243 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
244 244 response.mustcontain(ip)
245 245 response.mustcontain(ip_range)
246 246
247 247 self.app.post(
248 248 route_path('edit_user_ips_delete', user_id=user_id),
249 249 params={'del_ip_id': new_ip_id, 'csrf_token': self.csrf_token})
250 250
251 251 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
252 252 response.mustcontain('All IP addresses are allowed')
253 253 response.mustcontain(no=[ip])
254 254 response.mustcontain(no=[ip_range])
255 255
256 256 def test_emails(self):
257 257 self.log_user()
258 258 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
259 259 response = self.app.get(
260 260 route_path('edit_user_emails', user_id=user.user_id))
261 261 response.mustcontain('No additional emails specified')
262 262
263 263 def test_emails_add(self, user_util):
264 264 self.log_user()
265 265 user = user_util.create_user()
266 266 user_id = user.user_id
267 267
268 268 self.app.post(
269 269 route_path('edit_user_emails_add', user_id=user_id),
270 270 params={'new_email': 'example@rhodecode.com',
271 271 'csrf_token': self.csrf_token})
272 272
273 273 response = self.app.get(
274 274 route_path('edit_user_emails', user_id=user_id))
275 275 response.mustcontain('example@rhodecode.com')
276 276
277 277 def test_emails_add_existing_email(self, user_util, user_regular):
278 278 existing_email = user_regular.email
279 279
280 280 self.log_user()
281 281 user = user_util.create_user()
282 282 user_id = user.user_id
283 283
284 284 response = self.app.post(
285 285 route_path('edit_user_emails_add', user_id=user_id),
286 286 params={'new_email': existing_email,
287 287 'csrf_token': self.csrf_token})
288 288 assert_session_flash(
289 289 response, 'This e-mail address is already taken')
290 290
291 291 response = self.app.get(
292 292 route_path('edit_user_emails', user_id=user_id))
293 293 response.mustcontain(no=[existing_email])
294 294
295 295 def test_emails_delete(self, user_util):
296 296 self.log_user()
297 297 user = user_util.create_user()
298 298 user_id = user.user_id
299 299
300 300 self.app.post(
301 301 route_path('edit_user_emails_add', user_id=user_id),
302 302 params={'new_email': 'example@rhodecode.com',
303 303 'csrf_token': self.csrf_token})
304 304
305 305 response = self.app.get(
306 306 route_path('edit_user_emails', user_id=user_id))
307 307 response.mustcontain('example@rhodecode.com')
308 308
309 309 user_email = UserEmailMap.query()\
310 310 .filter(UserEmailMap.email == 'example@rhodecode.com') \
311 311 .filter(UserEmailMap.user_id == user_id)\
312 312 .one()
313 313
314 314 del_email_id = user_email.email_id
315 315 self.app.post(
316 316 route_path('edit_user_emails_delete', user_id=user_id),
317 317 params={'del_email_id': del_email_id,
318 318 'csrf_token': self.csrf_token})
319 319
320 320 response = self.app.get(
321 321 route_path('edit_user_emails', user_id=user_id))
322 322 response.mustcontain(no=['example@rhodecode.com'])
323 323
324 324 def test_create(self, request, xhr_header):
325 325 self.log_user()
326 326 username = 'newtestuser'
327 327 password = 'test12'
328 328 password_confirmation = password
329 329 name = 'name'
330 330 lastname = 'lastname'
331 331 email = 'mail@mail.com'
332 332
333 333 self.app.get(route_path('users_new'))
334 334
335 335 response = self.app.post(route_path('users_create'), params={
336 336 'username': username,
337 337 'password': password,
338 338 'description': 'mr CTO',
339 339 'password_confirmation': password_confirmation,
340 340 'firstname': name,
341 341 'active': True,
342 342 'lastname': lastname,
343 343 'extern_name': 'rhodecode',
344 344 'extern_type': 'rhodecode',
345 345 'email': email,
346 346 'csrf_token': self.csrf_token,
347 347 })
348 348 user_link = h.link_to(
349 349 username,
350 350 route_path(
351 351 'user_edit', user_id=User.get_by_username(username).user_id))
352 352 assert_session_flash(response, 'Created user %s' % (user_link,))
353 353
354 354 @request.addfinalizer
355 355 def cleanup():
356 356 fixture.destroy_user(username)
357 357 Session().commit()
358 358
359 359 new_user = User.query().filter(User.username == username).one()
360 360
361 361 assert new_user.username == username
362 362 assert auth.check_password(password, new_user.password)
363 363 assert new_user.name == name
364 364 assert new_user.lastname == lastname
365 365 assert new_user.email == email
366 366
367 367 response = self.app.get(route_path('users_data'),
368 368 extra_environ=xhr_header)
369 369 response.mustcontain(username)
370 370
371 371 def test_create_err(self):
372 372 self.log_user()
373 373 username = 'new_user'
374 374 password = ''
375 375 name = 'name'
376 376 lastname = 'lastname'
377 377 email = 'errmail.com'
378 378
379 379 self.app.get(route_path('users_new'))
380 380
381 381 response = self.app.post(route_path('users_create'), params={
382 382 'username': username,
383 383 'password': password,
384 384 'name': name,
385 385 'active': False,
386 386 'lastname': lastname,
387 387 'description': 'mr CTO',
388 388 'email': email,
389 389 'csrf_token': self.csrf_token,
390 390 })
391 391
392 392 msg = u'Username "%(username)s" is forbidden'
393 393 msg = h.html_escape(msg % {'username': 'new_user'})
394 394 response.mustcontain('<span class="error-message">%s</span>' % msg)
395 395 response.mustcontain(
396 396 '<span class="error-message">Please enter a value</span>')
397 397 response.mustcontain(
398 398 '<span class="error-message">An email address must contain a'
399 399 ' single @</span>')
400 400
401 401 def get_user():
402 402 Session().query(User).filter(User.username == username).one()
403 403
404 404 with pytest.raises(NoResultFound):
405 405 get_user()
406 406
407 407 def test_new(self):
408 408 self.log_user()
409 409 self.app.get(route_path('users_new'))
410 410
411 411 @pytest.mark.parametrize("name, attrs", [
412 412 ('firstname', {'firstname': 'new_username'}),
413 413 ('lastname', {'lastname': 'new_username'}),
414 414 ('admin', {'admin': True}),
415 415 ('admin', {'admin': False}),
416 416 ('extern_type', {'extern_type': 'ldap'}),
417 417 ('extern_type', {'extern_type': None}),
418 418 ('extern_name', {'extern_name': 'test'}),
419 419 ('extern_name', {'extern_name': None}),
420 420 ('active', {'active': False}),
421 421 ('active', {'active': True}),
422 422 ('email', {'email': 'some@email.com'}),
423 423 ('language', {'language': 'de'}),
424 424 ('language', {'language': 'en'}),
425 425 ('description', {'description': 'hello CTO'}),
426 426 # ('new_password', {'new_password': 'foobar123',
427 427 # 'password_confirmation': 'foobar123'})
428 428 ])
429 429 def test_update(self, name, attrs, user_util):
430 430 self.log_user()
431 431 usr = user_util.create_user(
432 432 password='qweqwe',
433 433 email='testme@rhodecode.org',
434 434 extern_type='rhodecode',
435 435 extern_name='xxx',
436 436 )
437 437 user_id = usr.user_id
438 438 Session().commit()
439 439
440 440 params = usr.get_api_data()
441 441 cur_lang = params['language'] or 'en'
442 442 params.update({
443 443 'password_confirmation': '',
444 444 'new_password': '',
445 445 'language': cur_lang,
446 446 'csrf_token': self.csrf_token,
447 447 })
448 448 params.update({'new_password': ''})
449 449 params.update(attrs)
450 450 if name == 'email':
451 451 params['emails'] = [attrs['email']]
452 452 elif name == 'extern_type':
453 453 # cannot update this via form, expected value is original one
454 454 params['extern_type'] = "rhodecode"
455 455 elif name == 'extern_name':
456 456 # cannot update this via form, expected value is original one
457 457 params['extern_name'] = 'xxx'
458 458 # special case since this user is not
459 459 # logged in yet his data is not filled
460 460 # so we use creation data
461 461
462 462 response = self.app.post(
463 463 route_path('user_update', user_id=usr.user_id), params)
464 464 assert response.status_int == 302
465 465 assert_session_flash(response, 'User updated successfully')
466 466
467 467 updated_user = User.get(user_id)
468 468 updated_params = updated_user.get_api_data()
469 469 updated_params.update({'password_confirmation': ''})
470 470 updated_params.update({'new_password': ''})
471 471
472 472 del params['csrf_token']
473 473 assert params == updated_params
474 474
475 475 def test_update_and_migrate_password(
476 476 self, autologin_user, real_crypto_backend, user_util):
477 477
478 478 user = user_util.create_user()
479 479 temp_user = user.username
480 480 user.password = auth._RhodeCodeCryptoSha256().hash_create(
481 481 b'test123')
482 482 Session().add(user)
483 483 Session().commit()
484 484
485 485 params = user.get_api_data()
486 486
487 487 params.update({
488 488 'password_confirmation': 'qweqwe123',
489 489 'new_password': 'qweqwe123',
490 490 'language': 'en',
491 491 'csrf_token': autologin_user.csrf_token,
492 492 })
493 493
494 494 response = self.app.post(
495 495 route_path('user_update', user_id=user.user_id), params)
496 496 assert response.status_int == 302
497 497 assert_session_flash(response, 'User updated successfully')
498 498
499 499 # new password should be bcrypted, after log-in and transfer
500 500 user = User.get_by_username(temp_user)
501 501 assert user.password.startswith('$')
502 502
503 503 updated_user = User.get_by_username(temp_user)
504 504 updated_params = updated_user.get_api_data()
505 505 updated_params.update({'password_confirmation': 'qweqwe123'})
506 506 updated_params.update({'new_password': 'qweqwe123'})
507 507
508 508 del params['csrf_token']
509 509 assert params == updated_params
510 510
511 511 def test_delete(self):
512 512 self.log_user()
513 513 username = 'newtestuserdeleteme'
514 514
515 515 fixture.create_user(name=username)
516 516
517 517 new_user = Session().query(User)\
518 518 .filter(User.username == username).one()
519 519 response = self.app.post(
520 520 route_path('user_delete', user_id=new_user.user_id),
521 521 params={'csrf_token': self.csrf_token})
522 522
523 523 assert_session_flash(response, 'Successfully deleted user `{}`'.format(username))
524 524
525 525 def test_delete_owner_of_repository(self, request, user_util):
526 526 self.log_user()
527 527 obj_name = 'test_repo'
528 528 usr = user_util.create_user()
529 529 username = usr.username
530 530 fixture.create_repo(obj_name, cur_user=usr.username)
531 531
532 532 new_user = Session().query(User)\
533 533 .filter(User.username == username).one()
534 534 response = self.app.post(
535 535 route_path('user_delete', user_id=new_user.user_id),
536 536 params={'csrf_token': self.csrf_token})
537 537
538 538 msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \
539 539 'Switch owners or remove those repositories:%s' % (username, obj_name)
540 540 assert_session_flash(response, msg)
541 541 fixture.destroy_repo(obj_name)
542 542
543 543 def test_delete_owner_of_repository_detaching(self, request, user_util):
544 544 self.log_user()
545 545 obj_name = 'test_repo'
546 546 usr = user_util.create_user(auto_cleanup=False)
547 547 username = usr.username
548 548 fixture.create_repo(obj_name, cur_user=usr.username)
549 549 Session().commit()
550 550
551 551 new_user = Session().query(User)\
552 552 .filter(User.username == username).one()
553 553 response = self.app.post(
554 554 route_path('user_delete', user_id=new_user.user_id),
555 555 params={'user_repos': 'detach', 'csrf_token': self.csrf_token})
556 556
557 557 msg = 'Detached 1 repositories'
558 558 assert_session_flash(response, msg)
559 559 fixture.destroy_repo(obj_name)
560 560
561 561 def test_delete_owner_of_repository_deleting(self, request, user_util):
562 562 self.log_user()
563 563 obj_name = 'test_repo'
564 564 usr = user_util.create_user(auto_cleanup=False)
565 565 username = usr.username
566 566 fixture.create_repo(obj_name, cur_user=usr.username)
567 567
568 568 new_user = Session().query(User)\
569 569 .filter(User.username == username).one()
570 570 response = self.app.post(
571 571 route_path('user_delete', user_id=new_user.user_id),
572 572 params={'user_repos': 'delete', 'csrf_token': self.csrf_token})
573 573
574 574 msg = 'Deleted 1 repositories'
575 575 assert_session_flash(response, msg)
576 576
577 577 def test_delete_owner_of_repository_group(self, request, user_util):
578 578 self.log_user()
579 579 obj_name = 'test_group'
580 580 usr = user_util.create_user()
581 581 username = usr.username
582 582 fixture.create_repo_group(obj_name, cur_user=usr.username)
583 583
584 584 new_user = Session().query(User)\
585 585 .filter(User.username == username).one()
586 586 response = self.app.post(
587 587 route_path('user_delete', user_id=new_user.user_id),
588 588 params={'csrf_token': self.csrf_token})
589 589
590 590 msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \
591 591 'Switch owners or remove those repository groups:%s' % (username, obj_name)
592 592 assert_session_flash(response, msg)
593 593 fixture.destroy_repo_group(obj_name)
594 594
595 595 def test_delete_owner_of_repository_group_detaching(self, request, user_util):
596 596 self.log_user()
597 597 obj_name = 'test_group'
598 598 usr = user_util.create_user(auto_cleanup=False)
599 599 username = usr.username
600 600 fixture.create_repo_group(obj_name, cur_user=usr.username)
601 601
602 602 new_user = Session().query(User)\
603 603 .filter(User.username == username).one()
604 604 response = self.app.post(
605 605 route_path('user_delete', user_id=new_user.user_id),
606 606 params={'user_repo_groups': 'delete', 'csrf_token': self.csrf_token})
607 607
608 608 msg = 'Deleted 1 repository groups'
609 609 assert_session_flash(response, msg)
610 610
611 611 def test_delete_owner_of_repository_group_deleting(self, request, user_util):
612 612 self.log_user()
613 613 obj_name = 'test_group'
614 614 usr = user_util.create_user(auto_cleanup=False)
615 615 username = usr.username
616 616 fixture.create_repo_group(obj_name, cur_user=usr.username)
617 617
618 618 new_user = Session().query(User)\
619 619 .filter(User.username == username).one()
620 620 response = self.app.post(
621 621 route_path('user_delete', user_id=new_user.user_id),
622 622 params={'user_repo_groups': 'detach', 'csrf_token': self.csrf_token})
623 623
624 624 msg = 'Detached 1 repository groups'
625 625 assert_session_flash(response, msg)
626 626 fixture.destroy_repo_group(obj_name)
627 627
628 628 def test_delete_owner_of_user_group(self, request, user_util):
629 629 self.log_user()
630 630 obj_name = 'test_user_group'
631 631 usr = user_util.create_user()
632 632 username = usr.username
633 633 fixture.create_user_group(obj_name, cur_user=usr.username)
634 634
635 635 new_user = Session().query(User)\
636 636 .filter(User.username == username).one()
637 637 response = self.app.post(
638 638 route_path('user_delete', user_id=new_user.user_id),
639 639 params={'csrf_token': self.csrf_token})
640 640
641 641 msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \
642 642 'Switch owners or remove those user groups:%s' % (username, obj_name)
643 643 assert_session_flash(response, msg)
644 644 fixture.destroy_user_group(obj_name)
645 645
646 646 def test_delete_owner_of_user_group_detaching(self, request, user_util):
647 647 self.log_user()
648 648 obj_name = 'test_user_group'
649 649 usr = user_util.create_user(auto_cleanup=False)
650 650 username = usr.username
651 651 fixture.create_user_group(obj_name, cur_user=usr.username)
652 652
653 653 new_user = Session().query(User)\
654 654 .filter(User.username == username).one()
655 655 try:
656 656 response = self.app.post(
657 657 route_path('user_delete', user_id=new_user.user_id),
658 658 params={'user_user_groups': 'detach',
659 659 'csrf_token': self.csrf_token})
660 660
661 661 msg = 'Detached 1 user groups'
662 662 assert_session_flash(response, msg)
663 663 finally:
664 664 fixture.destroy_user_group(obj_name)
665 665
666 666 def test_delete_owner_of_user_group_deleting(self, request, user_util):
667 667 self.log_user()
668 668 obj_name = 'test_user_group'
669 669 usr = user_util.create_user(auto_cleanup=False)
670 670 username = usr.username
671 671 fixture.create_user_group(obj_name, cur_user=usr.username)
672 672
673 673 new_user = Session().query(User)\
674 674 .filter(User.username == username).one()
675 675 response = self.app.post(
676 676 route_path('user_delete', user_id=new_user.user_id),
677 677 params={'user_user_groups': 'delete', 'csrf_token': self.csrf_token})
678 678
679 679 msg = 'Deleted 1 user groups'
680 680 assert_session_flash(response, msg)
681 681
682 682 def test_edit(self, user_util):
683 683 self.log_user()
684 684 user = user_util.create_user()
685 685 self.app.get(route_path('user_edit', user_id=user.user_id))
686 686
687 687 def test_edit_default_user_redirect(self):
688 688 self.log_user()
689 689 user = User.get_default_user()
690 690 self.app.get(route_path('user_edit', user_id=user.user_id), status=302)
691 691
692 692 @pytest.mark.parametrize(
693 693 'repo_create, repo_create_write, user_group_create, repo_group_create,'
694 694 'fork_create, inherit_default_permissions, expect_error,'
695 695 'expect_form_error', [
696 696 ('hg.create.none', 'hg.create.write_on_repogroup.false',
697 697 'hg.usergroup.create.false', 'hg.repogroup.create.false',
698 698 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
699 699 ('hg.create.repository', 'hg.create.write_on_repogroup.false',
700 700 'hg.usergroup.create.false', 'hg.repogroup.create.false',
701 701 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
702 702 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
703 703 'hg.usergroup.create.true', 'hg.repogroup.create.true',
704 704 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
705 705 False),
706 706 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
707 707 'hg.usergroup.create.true', 'hg.repogroup.create.true',
708 708 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
709 709 True),
710 710 ('', '', '', '', '', '', True, False),
711 711 ])
712 712 def test_global_perms_on_user(
713 713 self, repo_create, repo_create_write, user_group_create,
714 714 repo_group_create, fork_create, expect_error, expect_form_error,
715 715 inherit_default_permissions, user_util):
716 716 self.log_user()
717 717 user = user_util.create_user()
718 718 uid = user.user_id
719 719
720 720 # ENABLE REPO CREATE ON A GROUP
721 721 perm_params = {
722 722 'inherit_default_permissions': False,
723 723 'default_repo_create': repo_create,
724 724 'default_repo_create_on_write': repo_create_write,
725 725 'default_user_group_create': user_group_create,
726 726 'default_repo_group_create': repo_group_create,
727 727 'default_fork_create': fork_create,
728 728 'default_inherit_default_permissions': inherit_default_permissions,
729 729 'csrf_token': self.csrf_token,
730 730 }
731 731 response = self.app.post(
732 732 route_path('user_edit_global_perms_update', user_id=uid),
733 733 params=perm_params)
734 734
735 735 if expect_form_error:
736 736 assert response.status_int == 200
737 737 response.mustcontain('Value must be one of')
738 738 else:
739 739 if expect_error:
740 740 msg = 'An error occurred during permissions saving'
741 741 else:
742 742 msg = 'User global permissions updated successfully'
743 743 ug = User.get(uid)
744 744 del perm_params['inherit_default_permissions']
745 745 del perm_params['csrf_token']
746 746 assert perm_params == ug.get_default_perms()
747 747 assert_session_flash(response, msg)
748 748
749 749 def test_global_permissions_initial_values(self, user_util):
750 750 self.log_user()
751 751 user = user_util.create_user()
752 752 uid = user.user_id
753 753 response = self.app.get(
754 754 route_path('user_edit_global_perms', user_id=uid))
755 755 default_user = User.get_default_user()
756 756 default_permissions = default_user.get_default_perms()
757 757 assert_response = response.assert_response()
758 758 expected_permissions = (
759 759 'default_repo_create', 'default_repo_create_on_write',
760 760 'default_fork_create', 'default_repo_group_create',
761 761 'default_user_group_create', 'default_inherit_default_permissions')
762 762 for permission in expected_permissions:
763 763 css_selector = '[name={}][checked=checked]'.format(permission)
764 764 element = assert_response.get_element(css_selector)
765 765 assert element.value == default_permissions[permission]
766 766
767 767 def test_perms_summary_page(self):
768 768 user = self.log_user()
769 769 response = self.app.get(
770 770 route_path('edit_user_perms_summary', user_id=user['user_id']))
771 771 for repo in Repository.query().all():
772 772 response.mustcontain(repo.repo_name)
773 773
774 774 def test_perms_summary_page_json(self):
775 775 user = self.log_user()
776 776 response = self.app.get(
777 777 route_path('edit_user_perms_summary_json', user_id=user['user_id']))
778 778 for repo in Repository.query().all():
779 779 response.mustcontain(repo.repo_name)
780 780
781 781 def test_audit_log_page(self):
782 782 user = self.log_user()
783 783 self.app.get(
784 784 route_path('edit_user_audit_logs', user_id=user['user_id']))
785 785
786 786 def test_audit_log_page_download(self):
787 787 user = self.log_user()
788 788 user_id = user['user_id']
789 789 response = self.app.get(
790 790 route_path('edit_user_audit_logs_download', user_id=user_id))
791 791
792 792 assert response.content_disposition == \
793 793 'attachment; filename=user_{}_audit_logs.json'.format(user_id)
794 794 assert response.content_type == "application/json"
@@ -1,1362 +1,1381 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 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.base import get_authn_registry, RhodeCodeExternalAuthPlugin
35 35 from rhodecode.authentication.plugins import auth_rhodecode
36 36 from rhodecode.events import trigger
37 37 from rhodecode.model.db import true, UserNotice
38 38
39 39 from rhodecode.lib import audit_logger, rc_cache
40 40 from rhodecode.lib.exceptions import (
41 41 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
42 42 UserOwnsUserGroupsException, DefaultUserException)
43 43 from rhodecode.lib.ext_json import json
44 44 from rhodecode.lib.auth import (
45 45 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
46 46 from rhodecode.lib import helpers as h
47 47 from rhodecode.lib.helpers import SqlPage
48 48 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
49 49 from rhodecode.model.auth_token import AuthTokenModel
50 50 from rhodecode.model.forms import (
51 51 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
52 52 UserExtraEmailForm, UserExtraIpForm)
53 53 from rhodecode.model.permission import PermissionModel
54 54 from rhodecode.model.repo_group import RepoGroupModel
55 55 from rhodecode.model.ssh_key import SshKeyModel
56 56 from rhodecode.model.user import UserModel
57 57 from rhodecode.model.user_group import UserGroupModel
58 58 from rhodecode.model.db import (
59 59 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
60 60 UserApiKeys, UserSshKeys, RepoGroup)
61 61 from rhodecode.model.meta import Session
62 62
63 63 log = logging.getLogger(__name__)
64 64
65 65
66 66 class AdminUsersView(BaseAppView, DataGridAppView):
67 67
68 68 def load_default_context(self):
69 69 c = self._get_local_tmpl_context()
70 70 return c
71 71
72 72 @LoginRequired()
73 73 @HasPermissionAllDecorator('hg.admin')
74 74 @view_config(
75 75 route_name='users', request_method='GET',
76 76 renderer='rhodecode:templates/admin/users/users.mako')
77 77 def users_list(self):
78 78 c = self.load_default_context()
79 79 return self._get_template_context(c)
80 80
81 81 @LoginRequired()
82 82 @HasPermissionAllDecorator('hg.admin')
83 83 @view_config(
84 84 # renderer defined below
85 85 route_name='users_data', request_method='GET',
86 86 renderer='json_ext', xhr=True)
87 87 def users_list_data(self):
88 88 self.load_default_context()
89 89 column_map = {
90 90 'first_name': 'name',
91 91 'last_name': 'lastname',
92 92 }
93 93 draw, start, limit = self._extract_chunk(self.request)
94 94 search_q, order_by, order_dir = self._extract_ordering(
95 95 self.request, column_map=column_map)
96 96 _render = self.request.get_partial_renderer(
97 97 'rhodecode:templates/data_table/_dt_elements.mako')
98 98
99 99 def user_actions(user_id, username):
100 100 return _render("user_actions", user_id, username)
101 101
102 102 users_data_total_count = User.query()\
103 103 .filter(User.username != User.DEFAULT_USER) \
104 104 .count()
105 105
106 106 users_data_total_inactive_count = User.query()\
107 107 .filter(User.username != User.DEFAULT_USER) \
108 108 .filter(User.active != true())\
109 109 .count()
110 110
111 111 # json generate
112 112 base_q = User.query().filter(User.username != User.DEFAULT_USER)
113 113 base_inactive_q = base_q.filter(User.active != true())
114 114
115 115 if search_q:
116 116 like_expression = u'%{}%'.format(safe_unicode(search_q))
117 117 base_q = base_q.filter(or_(
118 118 User.username.ilike(like_expression),
119 119 User._email.ilike(like_expression),
120 120 User.name.ilike(like_expression),
121 121 User.lastname.ilike(like_expression),
122 122 ))
123 123 base_inactive_q = base_q.filter(User.active != true())
124 124
125 125 users_data_total_filtered_count = base_q.count()
126 126 users_data_total_filtered_inactive_count = base_inactive_q.count()
127 127
128 128 sort_col = getattr(User, order_by, None)
129 129 if sort_col:
130 130 if order_dir == 'asc':
131 131 # handle null values properly to order by NULL last
132 132 if order_by in ['last_activity']:
133 133 sort_col = coalesce(sort_col, datetime.date.max)
134 134 sort_col = sort_col.asc()
135 135 else:
136 136 # handle null values properly to order by NULL last
137 137 if order_by in ['last_activity']:
138 138 sort_col = coalesce(sort_col, datetime.date.min)
139 139 sort_col = sort_col.desc()
140 140
141 141 base_q = base_q.order_by(sort_col)
142 142 base_q = base_q.offset(start).limit(limit)
143 143
144 144 users_list = base_q.all()
145 145
146 146 users_data = []
147 147 for user in users_list:
148 148 users_data.append({
149 149 "username": h.gravatar_with_user(self.request, user.username),
150 150 "email": user.email,
151 151 "first_name": user.first_name,
152 152 "last_name": user.last_name,
153 153 "last_login": h.format_date(user.last_login),
154 154 "last_activity": h.format_date(user.last_activity),
155 155 "active": h.bool2icon(user.active),
156 156 "active_raw": user.active,
157 157 "admin": h.bool2icon(user.admin),
158 158 "extern_type": user.extern_type,
159 159 "extern_name": user.extern_name,
160 160 "action": user_actions(user.user_id, user.username),
161 161 })
162 162 data = ({
163 163 'draw': draw,
164 164 'data': users_data,
165 165 'recordsTotal': users_data_total_count,
166 166 'recordsFiltered': users_data_total_filtered_count,
167 167 'recordsTotalInactive': users_data_total_inactive_count,
168 168 'recordsFilteredInactive': users_data_total_filtered_inactive_count
169 169 })
170 170
171 171 return data
172 172
173 173 def _set_personal_repo_group_template_vars(self, c_obj):
174 174 DummyUser = AttributeDict({
175 175 'username': '${username}',
176 176 'user_id': '${user_id}',
177 177 })
178 178 c_obj.default_create_repo_group = RepoGroupModel() \
179 179 .get_default_create_personal_repo_group()
180 180 c_obj.personal_repo_group_name = RepoGroupModel() \
181 181 .get_personal_group_name(DummyUser)
182 182
183 183 @LoginRequired()
184 184 @HasPermissionAllDecorator('hg.admin')
185 185 @view_config(
186 186 route_name='users_new', request_method='GET',
187 187 renderer='rhodecode:templates/admin/users/user_add.mako')
188 188 def users_new(self):
189 189 _ = self.request.translate
190 190 c = self.load_default_context()
191 191 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
192 192 self._set_personal_repo_group_template_vars(c)
193 193 return self._get_template_context(c)
194 194
195 195 @LoginRequired()
196 196 @HasPermissionAllDecorator('hg.admin')
197 197 @CSRFRequired()
198 198 @view_config(
199 199 route_name='users_create', request_method='POST',
200 200 renderer='rhodecode:templates/admin/users/user_add.mako')
201 201 def users_create(self):
202 202 _ = self.request.translate
203 203 c = self.load_default_context()
204 204 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
205 205 user_model = UserModel()
206 206 user_form = UserForm(self.request.translate)()
207 207 try:
208 208 form_result = user_form.to_python(dict(self.request.POST))
209 209 user = user_model.create(form_result)
210 210 Session().flush()
211 211 creation_data = user.get_api_data()
212 212 username = form_result['username']
213 213
214 214 audit_logger.store_web(
215 215 'user.create', action_data={'data': creation_data},
216 216 user=c.rhodecode_user)
217 217
218 218 user_link = h.link_to(
219 219 h.escape(username),
220 220 h.route_path('user_edit', user_id=user.user_id))
221 221 h.flash(h.literal(_('Created user %(user_link)s')
222 222 % {'user_link': user_link}), category='success')
223 223 Session().commit()
224 224 except formencode.Invalid as errors:
225 225 self._set_personal_repo_group_template_vars(c)
226 226 data = render(
227 227 'rhodecode:templates/admin/users/user_add.mako',
228 228 self._get_template_context(c), self.request)
229 229 html = formencode.htmlfill.render(
230 230 data,
231 231 defaults=errors.value,
232 232 errors=errors.error_dict or {},
233 233 prefix_error=False,
234 234 encoding="UTF-8",
235 235 force_defaults=False
236 236 )
237 237 return Response(html)
238 238 except UserCreationError as e:
239 239 h.flash(e, 'error')
240 240 except Exception:
241 241 log.exception("Exception creation of user")
242 242 h.flash(_('Error occurred during creation of user %s')
243 243 % self.request.POST.get('username'), category='error')
244 244 raise HTTPFound(h.route_path('users'))
245 245
246 246
247 247 class UsersView(UserAppView):
248 248 ALLOW_SCOPED_TOKENS = False
249 249 """
250 250 This view has alternative version inside EE, if modified please take a look
251 251 in there as well.
252 252 """
253 253
254 254 def get_auth_plugins(self):
255 255 valid_plugins = []
256 256 authn_registry = get_authn_registry(self.request.registry)
257 257 for plugin in authn_registry.get_plugins_for_authentication():
258 258 if isinstance(plugin, RhodeCodeExternalAuthPlugin):
259 259 valid_plugins.append(plugin)
260 260 elif plugin.name == 'rhodecode':
261 261 valid_plugins.append(plugin)
262 262
263 263 # extend our choices if user has set a bound plugin which isn't enabled at the
264 264 # moment
265 265 extern_type = self.db_user.extern_type
266 266 if extern_type not in [x.uid for x in valid_plugins]:
267 267 try:
268 268 plugin = authn_registry.get_plugin_by_uid(extern_type)
269 269 if plugin:
270 270 valid_plugins.append(plugin)
271 271
272 272 except Exception:
273 273 log.exception(
274 274 'Could not extend user plugins with `{}`'.format(extern_type))
275 275 return valid_plugins
276 276
277 277 def load_default_context(self):
278 278 req = self.request
279 279
280 280 c = self._get_local_tmpl_context()
281 281 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
282 282 c.allowed_languages = [
283 283 ('en', 'English (en)'),
284 284 ('de', 'German (de)'),
285 285 ('fr', 'French (fr)'),
286 286 ('it', 'Italian (it)'),
287 287 ('ja', 'Japanese (ja)'),
288 288 ('pl', 'Polish (pl)'),
289 289 ('pt', 'Portuguese (pt)'),
290 290 ('ru', 'Russian (ru)'),
291 291 ('zh', 'Chinese (zh)'),
292 292 ]
293 293
294 294 c.allowed_extern_types = [
295 295 (x.uid, x.get_display_name()) for x in self.get_auth_plugins()
296 296 ]
297 297
298 298 c.available_permissions = req.registry.settings['available_permissions']
299 299 PermissionModel().set_global_permission_choices(
300 300 c, gettext_translator=req.translate)
301 301
302 302 return c
303 303
304 304 @LoginRequired()
305 305 @HasPermissionAllDecorator('hg.admin')
306 306 @CSRFRequired()
307 307 @view_config(
308 308 route_name='user_update', request_method='POST',
309 309 renderer='rhodecode:templates/admin/users/user_edit.mako')
310 310 def user_update(self):
311 311 _ = self.request.translate
312 312 c = self.load_default_context()
313 313
314 314 user_id = self.db_user_id
315 315 c.user = self.db_user
316 316
317 317 c.active = 'profile'
318 318 c.extern_type = c.user.extern_type
319 319 c.extern_name = c.user.extern_name
320 320 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
321 321 available_languages = [x[0] for x in c.allowed_languages]
322 322 _form = UserForm(self.request.translate, edit=True,
323 323 available_languages=available_languages,
324 324 old_data={'user_id': user_id,
325 325 'email': c.user.email})()
326 326 form_result = {}
327 327 old_values = c.user.get_api_data()
328 328 try:
329 329 form_result = _form.to_python(dict(self.request.POST))
330 330 skip_attrs = ['extern_name']
331 331 # TODO: plugin should define if username can be updated
332 332 if c.extern_type != "rhodecode":
333 333 # forbid updating username for external accounts
334 334 skip_attrs.append('username')
335 335
336 336 UserModel().update_user(
337 337 user_id, skip_attrs=skip_attrs, **form_result)
338 338
339 339 audit_logger.store_web(
340 340 'user.edit', action_data={'old_data': old_values},
341 341 user=c.rhodecode_user)
342 342
343 343 Session().commit()
344 344 h.flash(_('User updated successfully'), category='success')
345 345 except formencode.Invalid as errors:
346 346 data = render(
347 347 'rhodecode:templates/admin/users/user_edit.mako',
348 348 self._get_template_context(c), self.request)
349 349 html = formencode.htmlfill.render(
350 350 data,
351 351 defaults=errors.value,
352 352 errors=errors.error_dict or {},
353 353 prefix_error=False,
354 354 encoding="UTF-8",
355 355 force_defaults=False
356 356 )
357 357 return Response(html)
358 358 except UserCreationError as e:
359 359 h.flash(e, 'error')
360 360 except Exception:
361 361 log.exception("Exception updating user")
362 362 h.flash(_('Error occurred during update of user %s')
363 363 % form_result.get('username'), category='error')
364 364 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
365 365
366 366 @LoginRequired()
367 367 @HasPermissionAllDecorator('hg.admin')
368 368 @CSRFRequired()
369 369 @view_config(
370 370 route_name='user_delete', request_method='POST',
371 371 renderer='rhodecode:templates/admin/users/user_edit.mako')
372 372 def user_delete(self):
373 373 _ = self.request.translate
374 374 c = self.load_default_context()
375 375 c.user = self.db_user
376 376
377 377 _repos = c.user.repositories
378 378 _repo_groups = c.user.repository_groups
379 379 _user_groups = c.user.user_groups
380 380 _artifacts = c.user.artifacts
381 381
382 382 handle_repos = None
383 383 handle_repo_groups = None
384 384 handle_user_groups = None
385 385 handle_artifacts = None
386 386
387 387 # calls for flash of handle based on handle case detach or delete
388 388 def set_handle_flash_repos():
389 389 handle = handle_repos
390 390 if handle == 'detach':
391 391 h.flash(_('Detached %s repositories') % len(_repos),
392 392 category='success')
393 393 elif handle == 'delete':
394 394 h.flash(_('Deleted %s repositories') % len(_repos),
395 395 category='success')
396 396
397 397 def set_handle_flash_repo_groups():
398 398 handle = handle_repo_groups
399 399 if handle == 'detach':
400 400 h.flash(_('Detached %s repository groups') % len(_repo_groups),
401 401 category='success')
402 402 elif handle == 'delete':
403 403 h.flash(_('Deleted %s repository groups') % len(_repo_groups),
404 404 category='success')
405 405
406 406 def set_handle_flash_user_groups():
407 407 handle = handle_user_groups
408 408 if handle == 'detach':
409 409 h.flash(_('Detached %s user groups') % len(_user_groups),
410 410 category='success')
411 411 elif handle == 'delete':
412 412 h.flash(_('Deleted %s user groups') % len(_user_groups),
413 413 category='success')
414 414
415 415 def set_handle_flash_artifacts():
416 416 handle = handle_artifacts
417 417 if handle == 'detach':
418 418 h.flash(_('Detached %s artifacts') % len(_artifacts),
419 419 category='success')
420 420 elif handle == 'delete':
421 421 h.flash(_('Deleted %s artifacts') % len(_artifacts),
422 422 category='success')
423 423
424 424 if _repos and self.request.POST.get('user_repos'):
425 425 handle_repos = self.request.POST['user_repos']
426 426
427 427 if _repo_groups and self.request.POST.get('user_repo_groups'):
428 428 handle_repo_groups = self.request.POST['user_repo_groups']
429 429
430 430 if _user_groups and self.request.POST.get('user_user_groups'):
431 431 handle_user_groups = self.request.POST['user_user_groups']
432 432
433 433 if _artifacts and self.request.POST.get('user_artifacts'):
434 434 handle_artifacts = self.request.POST['user_artifacts']
435 435
436 436 old_values = c.user.get_api_data()
437 437
438 438 try:
439 439 UserModel().delete(c.user, handle_repos=handle_repos,
440 440 handle_repo_groups=handle_repo_groups,
441 441 handle_user_groups=handle_user_groups,
442 442 handle_artifacts=handle_artifacts)
443 443
444 444 audit_logger.store_web(
445 445 'user.delete', action_data={'old_data': old_values},
446 446 user=c.rhodecode_user)
447 447
448 448 Session().commit()
449 449 set_handle_flash_repos()
450 450 set_handle_flash_repo_groups()
451 451 set_handle_flash_user_groups()
452 452 set_handle_flash_artifacts()
453 453 username = h.escape(old_values['username'])
454 454 h.flash(_('Successfully deleted user `{}`').format(username), category='success')
455 455 except (UserOwnsReposException, UserOwnsRepoGroupsException,
456 456 UserOwnsUserGroupsException, DefaultUserException) as e:
457 457 h.flash(e, category='warning')
458 458 except Exception:
459 459 log.exception("Exception during deletion of user")
460 460 h.flash(_('An error occurred during deletion of user'),
461 461 category='error')
462 462 raise HTTPFound(h.route_path('users'))
463 463
464 464 @LoginRequired()
465 465 @HasPermissionAllDecorator('hg.admin')
466 466 @view_config(
467 467 route_name='user_edit', request_method='GET',
468 468 renderer='rhodecode:templates/admin/users/user_edit.mako')
469 469 def user_edit(self):
470 470 _ = self.request.translate
471 471 c = self.load_default_context()
472 472 c.user = self.db_user
473 473
474 474 c.active = 'profile'
475 475 c.extern_type = c.user.extern_type
476 476 c.extern_name = c.user.extern_name
477 477 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
478 478
479 479 defaults = c.user.get_dict()
480 480 defaults.update({'language': c.user.user_data.get('language')})
481 481
482 482 data = render(
483 483 'rhodecode:templates/admin/users/user_edit.mako',
484 484 self._get_template_context(c), self.request)
485 485 html = formencode.htmlfill.render(
486 486 data,
487 487 defaults=defaults,
488 488 encoding="UTF-8",
489 489 force_defaults=False
490 490 )
491 491 return Response(html)
492 492
493 493 @LoginRequired()
494 494 @HasPermissionAllDecorator('hg.admin')
495 495 @view_config(
496 496 route_name='user_edit_advanced', request_method='GET',
497 497 renderer='rhodecode:templates/admin/users/user_edit.mako')
498 498 def user_edit_advanced(self):
499 499 _ = self.request.translate
500 500 c = self.load_default_context()
501 501
502 502 user_id = self.db_user_id
503 503 c.user = self.db_user
504 504
505 505 c.active = 'advanced'
506 506 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
507 507 c.personal_repo_group_name = RepoGroupModel()\
508 508 .get_personal_group_name(c.user)
509 509
510 510 c.user_to_review_rules = sorted(
511 511 (x.user for x in c.user.user_review_rules),
512 512 key=lambda u: u.username.lower())
513 513
514 514 c.first_admin = User.get_first_super_admin()
515 515 defaults = c.user.get_dict()
516 516
517 517 # Interim workaround if the user participated on any pull requests as a
518 518 # reviewer.
519 519 has_review = len(c.user.reviewer_pull_requests)
520 520 c.can_delete_user = not has_review
521 521 c.can_delete_user_message = ''
522 522 inactive_link = h.link_to(
523 523 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
524 524 if has_review == 1:
525 525 c.can_delete_user_message = h.literal(_(
526 526 'The user participates as reviewer in {} pull request and '
527 527 'cannot be deleted. \nYou can set the user to '
528 528 '"{}" instead of deleting it.').format(
529 529 has_review, inactive_link))
530 530 elif has_review:
531 531 c.can_delete_user_message = h.literal(_(
532 532 'The user participates as reviewer in {} pull requests and '
533 533 'cannot be deleted. \nYou can set the user to '
534 534 '"{}" instead of deleting it.').format(
535 535 has_review, inactive_link))
536 536
537 537 data = render(
538 538 'rhodecode:templates/admin/users/user_edit.mako',
539 539 self._get_template_context(c), self.request)
540 540 html = formencode.htmlfill.render(
541 541 data,
542 542 defaults=defaults,
543 543 encoding="UTF-8",
544 544 force_defaults=False
545 545 )
546 546 return Response(html)
547 547
548 548 @LoginRequired()
549 549 @HasPermissionAllDecorator('hg.admin')
550 550 @view_config(
551 551 route_name='user_edit_global_perms', request_method='GET',
552 552 renderer='rhodecode:templates/admin/users/user_edit.mako')
553 553 def user_edit_global_perms(self):
554 554 _ = self.request.translate
555 555 c = self.load_default_context()
556 556 c.user = self.db_user
557 557
558 558 c.active = 'global_perms'
559 559
560 560 c.default_user = User.get_default_user()
561 561 defaults = c.user.get_dict()
562 562 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
563 563 defaults.update(c.default_user.get_default_perms())
564 564 defaults.update(c.user.get_default_perms())
565 565
566 566 data = render(
567 567 'rhodecode:templates/admin/users/user_edit.mako',
568 568 self._get_template_context(c), self.request)
569 569 html = formencode.htmlfill.render(
570 570 data,
571 571 defaults=defaults,
572 572 encoding="UTF-8",
573 573 force_defaults=False
574 574 )
575 575 return Response(html)
576 576
577 577 @LoginRequired()
578 578 @HasPermissionAllDecorator('hg.admin')
579 579 @CSRFRequired()
580 580 @view_config(
581 581 route_name='user_edit_global_perms_update', request_method='POST',
582 582 renderer='rhodecode:templates/admin/users/user_edit.mako')
583 583 def user_edit_global_perms_update(self):
584 584 _ = self.request.translate
585 585 c = self.load_default_context()
586 586
587 587 user_id = self.db_user_id
588 588 c.user = self.db_user
589 589
590 590 c.active = 'global_perms'
591 591 try:
592 592 # first stage that verifies the checkbox
593 593 _form = UserIndividualPermissionsForm(self.request.translate)
594 594 form_result = _form.to_python(dict(self.request.POST))
595 595 inherit_perms = form_result['inherit_default_permissions']
596 596 c.user.inherit_default_permissions = inherit_perms
597 597 Session().add(c.user)
598 598
599 599 if not inherit_perms:
600 600 # only update the individual ones if we un check the flag
601 601 _form = UserPermissionsForm(
602 602 self.request.translate,
603 603 [x[0] for x in c.repo_create_choices],
604 604 [x[0] for x in c.repo_create_on_write_choices],
605 605 [x[0] for x in c.repo_group_create_choices],
606 606 [x[0] for x in c.user_group_create_choices],
607 607 [x[0] for x in c.fork_choices],
608 608 [x[0] for x in c.inherit_default_permission_choices])()
609 609
610 610 form_result = _form.to_python(dict(self.request.POST))
611 611 form_result.update({'perm_user_id': c.user.user_id})
612 612
613 613 PermissionModel().update_user_permissions(form_result)
614 614
615 615 # TODO(marcink): implement global permissions
616 616 # audit_log.store_web('user.edit.permissions')
617 617
618 618 Session().commit()
619 619
620 620 h.flash(_('User global permissions updated successfully'),
621 621 category='success')
622 622
623 623 except formencode.Invalid as errors:
624 624 data = render(
625 625 'rhodecode:templates/admin/users/user_edit.mako',
626 626 self._get_template_context(c), self.request)
627 627 html = formencode.htmlfill.render(
628 628 data,
629 629 defaults=errors.value,
630 630 errors=errors.error_dict or {},
631 631 prefix_error=False,
632 632 encoding="UTF-8",
633 633 force_defaults=False
634 634 )
635 635 return Response(html)
636 636 except Exception:
637 637 log.exception("Exception during permissions saving")
638 638 h.flash(_('An error occurred during permissions saving'),
639 639 category='error')
640 640
641 641 affected_user_ids = [user_id]
642 642 PermissionModel().trigger_permission_flush(affected_user_ids)
643 643 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
644 644
645 645 @LoginRequired()
646 646 @HasPermissionAllDecorator('hg.admin')
647 647 @CSRFRequired()
648 648 @view_config(
649 649 route_name='user_enable_force_password_reset', request_method='POST',
650 650 renderer='rhodecode:templates/admin/users/user_edit.mako')
651 651 def user_enable_force_password_reset(self):
652 652 _ = self.request.translate
653 653 c = self.load_default_context()
654 654
655 655 user_id = self.db_user_id
656 656 c.user = self.db_user
657 657
658 658 try:
659 659 c.user.update_userdata(force_password_change=True)
660 660
661 661 msg = _('Force password change enabled for user')
662 662 audit_logger.store_web('user.edit.password_reset.enabled',
663 663 user=c.rhodecode_user)
664 664
665 665 Session().commit()
666 666 h.flash(msg, category='success')
667 667 except Exception:
668 668 log.exception("Exception during password reset for user")
669 669 h.flash(_('An error occurred during password reset for user'),
670 670 category='error')
671 671
672 672 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
673 673
674 674 @LoginRequired()
675 675 @HasPermissionAllDecorator('hg.admin')
676 676 @CSRFRequired()
677 677 @view_config(
678 678 route_name='user_disable_force_password_reset', request_method='POST',
679 679 renderer='rhodecode:templates/admin/users/user_edit.mako')
680 680 def user_disable_force_password_reset(self):
681 681 _ = self.request.translate
682 682 c = self.load_default_context()
683 683
684 684 user_id = self.db_user_id
685 685 c.user = self.db_user
686 686
687 687 try:
688 688 c.user.update_userdata(force_password_change=False)
689 689
690 690 msg = _('Force password change disabled for user')
691 691 audit_logger.store_web(
692 692 'user.edit.password_reset.disabled',
693 693 user=c.rhodecode_user)
694 694
695 695 Session().commit()
696 696 h.flash(msg, category='success')
697 697 except Exception:
698 698 log.exception("Exception during password reset for user")
699 699 h.flash(_('An error occurred during password reset for user'),
700 700 category='error')
701 701
702 702 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
703 703
704 704 @LoginRequired()
705 705 @HasPermissionAllDecorator('hg.admin')
706 706 @CSRFRequired()
707 707 @view_config(
708 708 route_name='user_notice_dismiss', request_method='POST',
709 709 renderer='json_ext', xhr=True)
710 710 def user_notice_dismiss(self):
711 711 _ = self.request.translate
712 712 c = self.load_default_context()
713 713
714 714 user_id = self.db_user_id
715 715 c.user = self.db_user
716 716 user_notice_id = safe_int(self.request.POST.get('notice_id'))
717 717 notice = UserNotice().query()\
718 718 .filter(UserNotice.user_id == user_id)\
719 719 .filter(UserNotice.user_notice_id == user_notice_id)\
720 720 .scalar()
721 721 read = False
722 722 if notice:
723 723 notice.notice_read = True
724 724 Session().add(notice)
725 725 Session().commit()
726 726 read = True
727 727
728 728 return {'notice': user_notice_id, 'read': read}
729 729
730 730 @LoginRequired()
731 731 @HasPermissionAllDecorator('hg.admin')
732 732 @CSRFRequired()
733 733 @view_config(
734 734 route_name='user_create_personal_repo_group', request_method='POST',
735 735 renderer='rhodecode:templates/admin/users/user_edit.mako')
736 736 def user_create_personal_repo_group(self):
737 737 """
738 738 Create personal repository group for this user
739 739 """
740 740 from rhodecode.model.repo_group import RepoGroupModel
741 741
742 742 _ = self.request.translate
743 743 c = self.load_default_context()
744 744
745 745 user_id = self.db_user_id
746 746 c.user = self.db_user
747 747
748 748 personal_repo_group = RepoGroup.get_user_personal_repo_group(
749 749 c.user.user_id)
750 750 if personal_repo_group:
751 751 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
752 752
753 753 personal_repo_group_name = RepoGroupModel().get_personal_group_name(c.user)
754 754 named_personal_group = RepoGroup.get_by_group_name(
755 755 personal_repo_group_name)
756 756 try:
757 757
758 758 if named_personal_group and named_personal_group.user_id == c.user.user_id:
759 759 # migrate the same named group, and mark it as personal
760 760 named_personal_group.personal = True
761 761 Session().add(named_personal_group)
762 762 Session().commit()
763 763 msg = _('Linked repository group `%s` as personal' % (
764 764 personal_repo_group_name,))
765 765 h.flash(msg, category='success')
766 766 elif not named_personal_group:
767 767 RepoGroupModel().create_personal_repo_group(c.user)
768 768
769 769 msg = _('Created repository group `%s`' % (
770 770 personal_repo_group_name,))
771 771 h.flash(msg, category='success')
772 772 else:
773 773 msg = _('Repository group `%s` is already taken' % (
774 774 personal_repo_group_name,))
775 775 h.flash(msg, category='warning')
776 776 except Exception:
777 777 log.exception("Exception during repository group creation")
778 778 msg = _(
779 779 'An error occurred during repository group creation for user')
780 780 h.flash(msg, category='error')
781 781 Session().rollback()
782 782
783 783 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
784 784
785 785 @LoginRequired()
786 786 @HasPermissionAllDecorator('hg.admin')
787 787 @view_config(
788 788 route_name='edit_user_auth_tokens', request_method='GET',
789 789 renderer='rhodecode:templates/admin/users/user_edit.mako')
790 790 def auth_tokens(self):
791 791 _ = self.request.translate
792 792 c = self.load_default_context()
793 793 c.user = self.db_user
794 794
795 795 c.active = 'auth_tokens'
796 796
797 797 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
798 798 c.role_values = [
799 799 (x, AuthTokenModel.cls._get_role_name(x))
800 800 for x in AuthTokenModel.cls.ROLES]
801 801 c.role_options = [(c.role_values, _("Role"))]
802 802 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
803 803 c.user.user_id, show_expired=True)
804 804 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
805 805 return self._get_template_context(c)
806 806
807 @LoginRequired()
808 @HasPermissionAllDecorator('hg.admin')
809 @view_config(
810 route_name='edit_user_auth_tokens_view', request_method='POST',
811 renderer='json_ext', xhr=True)
812 def auth_tokens_view(self):
813 _ = self.request.translate
814 c = self.load_default_context()
815 c.user = self.db_user
816
817 auth_token_id = self.request.POST.get('auth_token_id')
818
819 if auth_token_id:
820 token = UserApiKeys.get_or_404(auth_token_id)
821
822 return {
823 'auth_token': token.api_key
824 }
825
807 826 def maybe_attach_token_scope(self, token):
808 827 # implemented in EE edition
809 828 pass
810 829
811 830 @LoginRequired()
812 831 @HasPermissionAllDecorator('hg.admin')
813 832 @CSRFRequired()
814 833 @view_config(
815 834 route_name='edit_user_auth_tokens_add', request_method='POST')
816 835 def auth_tokens_add(self):
817 836 _ = self.request.translate
818 837 c = self.load_default_context()
819 838
820 839 user_id = self.db_user_id
821 840 c.user = self.db_user
822 841
823 842 user_data = c.user.get_api_data()
824 843 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
825 844 description = self.request.POST.get('description')
826 845 role = self.request.POST.get('role')
827 846
828 847 token = UserModel().add_auth_token(
829 848 user=c.user.user_id,
830 849 lifetime_minutes=lifetime, role=role, description=description,
831 850 scope_callback=self.maybe_attach_token_scope)
832 851 token_data = token.get_api_data()
833 852
834 853 audit_logger.store_web(
835 854 'user.edit.token.add', action_data={
836 855 'data': {'token': token_data, 'user': user_data}},
837 856 user=self._rhodecode_user, )
838 857 Session().commit()
839 858
840 859 h.flash(_("Auth token successfully created"), category='success')
841 860 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
842 861
843 862 @LoginRequired()
844 863 @HasPermissionAllDecorator('hg.admin')
845 864 @CSRFRequired()
846 865 @view_config(
847 866 route_name='edit_user_auth_tokens_delete', request_method='POST')
848 867 def auth_tokens_delete(self):
849 868 _ = self.request.translate
850 869 c = self.load_default_context()
851 870
852 871 user_id = self.db_user_id
853 872 c.user = self.db_user
854 873
855 874 user_data = c.user.get_api_data()
856 875
857 876 del_auth_token = self.request.POST.get('del_auth_token')
858 877
859 878 if del_auth_token:
860 879 token = UserApiKeys.get_or_404(del_auth_token)
861 880 token_data = token.get_api_data()
862 881
863 882 AuthTokenModel().delete(del_auth_token, c.user.user_id)
864 883 audit_logger.store_web(
865 884 'user.edit.token.delete', action_data={
866 885 'data': {'token': token_data, 'user': user_data}},
867 886 user=self._rhodecode_user,)
868 887 Session().commit()
869 888 h.flash(_("Auth token successfully deleted"), category='success')
870 889
871 890 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
872 891
873 892 @LoginRequired()
874 893 @HasPermissionAllDecorator('hg.admin')
875 894 @view_config(
876 895 route_name='edit_user_ssh_keys', request_method='GET',
877 896 renderer='rhodecode:templates/admin/users/user_edit.mako')
878 897 def ssh_keys(self):
879 898 _ = self.request.translate
880 899 c = self.load_default_context()
881 900 c.user = self.db_user
882 901
883 902 c.active = 'ssh_keys'
884 903 c.default_key = self.request.GET.get('default_key')
885 904 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
886 905 return self._get_template_context(c)
887 906
888 907 @LoginRequired()
889 908 @HasPermissionAllDecorator('hg.admin')
890 909 @view_config(
891 910 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
892 911 renderer='rhodecode:templates/admin/users/user_edit.mako')
893 912 def ssh_keys_generate_keypair(self):
894 913 _ = self.request.translate
895 914 c = self.load_default_context()
896 915
897 916 c.user = self.db_user
898 917
899 918 c.active = 'ssh_keys_generate'
900 919 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
901 920 private_format = self.request.GET.get('private_format') \
902 921 or SshKeyModel.DEFAULT_PRIVATE_KEY_FORMAT
903 922 c.private, c.public = SshKeyModel().generate_keypair(
904 923 comment=comment, private_format=private_format)
905 924
906 925 return self._get_template_context(c)
907 926
908 927 @LoginRequired()
909 928 @HasPermissionAllDecorator('hg.admin')
910 929 @CSRFRequired()
911 930 @view_config(
912 931 route_name='edit_user_ssh_keys_add', request_method='POST')
913 932 def ssh_keys_add(self):
914 933 _ = self.request.translate
915 934 c = self.load_default_context()
916 935
917 936 user_id = self.db_user_id
918 937 c.user = self.db_user
919 938
920 939 user_data = c.user.get_api_data()
921 940 key_data = self.request.POST.get('key_data')
922 941 description = self.request.POST.get('description')
923 942
924 943 fingerprint = 'unknown'
925 944 try:
926 945 if not key_data:
927 946 raise ValueError('Please add a valid public key')
928 947
929 948 key = SshKeyModel().parse_key(key_data.strip())
930 949 fingerprint = key.hash_md5()
931 950
932 951 ssh_key = SshKeyModel().create(
933 952 c.user.user_id, fingerprint, key.keydata, description)
934 953 ssh_key_data = ssh_key.get_api_data()
935 954
936 955 audit_logger.store_web(
937 956 'user.edit.ssh_key.add', action_data={
938 957 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
939 958 user=self._rhodecode_user, )
940 959 Session().commit()
941 960
942 961 # Trigger an event on change of keys.
943 962 trigger(SshKeyFileChangeEvent(), self.request.registry)
944 963
945 964 h.flash(_("Ssh Key successfully created"), category='success')
946 965
947 966 except IntegrityError:
948 967 log.exception("Exception during ssh key saving")
949 968 err = 'Such key with fingerprint `{}` already exists, ' \
950 969 'please use a different one'.format(fingerprint)
951 970 h.flash(_('An error occurred during ssh key saving: {}').format(err),
952 971 category='error')
953 972 except Exception as e:
954 973 log.exception("Exception during ssh key saving")
955 974 h.flash(_('An error occurred during ssh key saving: {}').format(e),
956 975 category='error')
957 976
958 977 return HTTPFound(
959 978 h.route_path('edit_user_ssh_keys', user_id=user_id))
960 979
961 980 @LoginRequired()
962 981 @HasPermissionAllDecorator('hg.admin')
963 982 @CSRFRequired()
964 983 @view_config(
965 984 route_name='edit_user_ssh_keys_delete', request_method='POST')
966 985 def ssh_keys_delete(self):
967 986 _ = self.request.translate
968 987 c = self.load_default_context()
969 988
970 989 user_id = self.db_user_id
971 990 c.user = self.db_user
972 991
973 992 user_data = c.user.get_api_data()
974 993
975 994 del_ssh_key = self.request.POST.get('del_ssh_key')
976 995
977 996 if del_ssh_key:
978 997 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
979 998 ssh_key_data = ssh_key.get_api_data()
980 999
981 1000 SshKeyModel().delete(del_ssh_key, c.user.user_id)
982 1001 audit_logger.store_web(
983 1002 'user.edit.ssh_key.delete', action_data={
984 1003 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
985 1004 user=self._rhodecode_user,)
986 1005 Session().commit()
987 1006 # Trigger an event on change of keys.
988 1007 trigger(SshKeyFileChangeEvent(), self.request.registry)
989 1008 h.flash(_("Ssh key successfully deleted"), category='success')
990 1009
991 1010 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
992 1011
993 1012 @LoginRequired()
994 1013 @HasPermissionAllDecorator('hg.admin')
995 1014 @view_config(
996 1015 route_name='edit_user_emails', request_method='GET',
997 1016 renderer='rhodecode:templates/admin/users/user_edit.mako')
998 1017 def emails(self):
999 1018 _ = self.request.translate
1000 1019 c = self.load_default_context()
1001 1020 c.user = self.db_user
1002 1021
1003 1022 c.active = 'emails'
1004 1023 c.user_email_map = UserEmailMap.query() \
1005 1024 .filter(UserEmailMap.user == c.user).all()
1006 1025
1007 1026 return self._get_template_context(c)
1008 1027
1009 1028 @LoginRequired()
1010 1029 @HasPermissionAllDecorator('hg.admin')
1011 1030 @CSRFRequired()
1012 1031 @view_config(
1013 1032 route_name='edit_user_emails_add', request_method='POST')
1014 1033 def emails_add(self):
1015 1034 _ = self.request.translate
1016 1035 c = self.load_default_context()
1017 1036
1018 1037 user_id = self.db_user_id
1019 1038 c.user = self.db_user
1020 1039
1021 1040 email = self.request.POST.get('new_email')
1022 1041 user_data = c.user.get_api_data()
1023 1042 try:
1024 1043
1025 1044 form = UserExtraEmailForm(self.request.translate)()
1026 1045 data = form.to_python({'email': email})
1027 1046 email = data['email']
1028 1047
1029 1048 UserModel().add_extra_email(c.user.user_id, email)
1030 1049 audit_logger.store_web(
1031 1050 'user.edit.email.add',
1032 1051 action_data={'email': email, 'user': user_data},
1033 1052 user=self._rhodecode_user)
1034 1053 Session().commit()
1035 1054 h.flash(_("Added new email address `%s` for user account") % email,
1036 1055 category='success')
1037 1056 except formencode.Invalid as error:
1038 1057 h.flash(h.escape(error.error_dict['email']), category='error')
1039 1058 except IntegrityError:
1040 1059 log.warning("Email %s already exists", email)
1041 1060 h.flash(_('Email `{}` is already registered for another user.').format(email),
1042 1061 category='error')
1043 1062 except Exception:
1044 1063 log.exception("Exception during email saving")
1045 1064 h.flash(_('An error occurred during email saving'),
1046 1065 category='error')
1047 1066 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1048 1067
1049 1068 @LoginRequired()
1050 1069 @HasPermissionAllDecorator('hg.admin')
1051 1070 @CSRFRequired()
1052 1071 @view_config(
1053 1072 route_name='edit_user_emails_delete', request_method='POST')
1054 1073 def emails_delete(self):
1055 1074 _ = self.request.translate
1056 1075 c = self.load_default_context()
1057 1076
1058 1077 user_id = self.db_user_id
1059 1078 c.user = self.db_user
1060 1079
1061 1080 email_id = self.request.POST.get('del_email_id')
1062 1081 user_model = UserModel()
1063 1082
1064 1083 email = UserEmailMap.query().get(email_id).email
1065 1084 user_data = c.user.get_api_data()
1066 1085 user_model.delete_extra_email(c.user.user_id, email_id)
1067 1086 audit_logger.store_web(
1068 1087 'user.edit.email.delete',
1069 1088 action_data={'email': email, 'user': user_data},
1070 1089 user=self._rhodecode_user)
1071 1090 Session().commit()
1072 1091 h.flash(_("Removed email address from user account"),
1073 1092 category='success')
1074 1093 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1075 1094
1076 1095 @LoginRequired()
1077 1096 @HasPermissionAllDecorator('hg.admin')
1078 1097 @view_config(
1079 1098 route_name='edit_user_ips', request_method='GET',
1080 1099 renderer='rhodecode:templates/admin/users/user_edit.mako')
1081 1100 def ips(self):
1082 1101 _ = self.request.translate
1083 1102 c = self.load_default_context()
1084 1103 c.user = self.db_user
1085 1104
1086 1105 c.active = 'ips'
1087 1106 c.user_ip_map = UserIpMap.query() \
1088 1107 .filter(UserIpMap.user == c.user).all()
1089 1108
1090 1109 c.inherit_default_ips = c.user.inherit_default_permissions
1091 1110 c.default_user_ip_map = UserIpMap.query() \
1092 1111 .filter(UserIpMap.user == User.get_default_user()).all()
1093 1112
1094 1113 return self._get_template_context(c)
1095 1114
1096 1115 @LoginRequired()
1097 1116 @HasPermissionAllDecorator('hg.admin')
1098 1117 @CSRFRequired()
1099 1118 @view_config(
1100 1119 route_name='edit_user_ips_add', request_method='POST')
1101 1120 # NOTE(marcink): this view is allowed for default users, as we can
1102 1121 # edit their IP white list
1103 1122 def ips_add(self):
1104 1123 _ = self.request.translate
1105 1124 c = self.load_default_context()
1106 1125
1107 1126 user_id = self.db_user_id
1108 1127 c.user = self.db_user
1109 1128
1110 1129 user_model = UserModel()
1111 1130 desc = self.request.POST.get('description')
1112 1131 try:
1113 1132 ip_list = user_model.parse_ip_range(
1114 1133 self.request.POST.get('new_ip'))
1115 1134 except Exception as e:
1116 1135 ip_list = []
1117 1136 log.exception("Exception during ip saving")
1118 1137 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1119 1138 category='error')
1120 1139 added = []
1121 1140 user_data = c.user.get_api_data()
1122 1141 for ip in ip_list:
1123 1142 try:
1124 1143 form = UserExtraIpForm(self.request.translate)()
1125 1144 data = form.to_python({'ip': ip})
1126 1145 ip = data['ip']
1127 1146
1128 1147 user_model.add_extra_ip(c.user.user_id, ip, desc)
1129 1148 audit_logger.store_web(
1130 1149 'user.edit.ip.add',
1131 1150 action_data={'ip': ip, 'user': user_data},
1132 1151 user=self._rhodecode_user)
1133 1152 Session().commit()
1134 1153 added.append(ip)
1135 1154 except formencode.Invalid as error:
1136 1155 msg = error.error_dict['ip']
1137 1156 h.flash(msg, category='error')
1138 1157 except Exception:
1139 1158 log.exception("Exception during ip saving")
1140 1159 h.flash(_('An error occurred during ip saving'),
1141 1160 category='error')
1142 1161 if added:
1143 1162 h.flash(
1144 1163 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1145 1164 category='success')
1146 1165 if 'default_user' in self.request.POST:
1147 1166 # case for editing global IP list we do it for 'DEFAULT' user
1148 1167 raise HTTPFound(h.route_path('admin_permissions_ips'))
1149 1168 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1150 1169
1151 1170 @LoginRequired()
1152 1171 @HasPermissionAllDecorator('hg.admin')
1153 1172 @CSRFRequired()
1154 1173 @view_config(
1155 1174 route_name='edit_user_ips_delete', request_method='POST')
1156 1175 # NOTE(marcink): this view is allowed for default users, as we can
1157 1176 # edit their IP white list
1158 1177 def ips_delete(self):
1159 1178 _ = self.request.translate
1160 1179 c = self.load_default_context()
1161 1180
1162 1181 user_id = self.db_user_id
1163 1182 c.user = self.db_user
1164 1183
1165 1184 ip_id = self.request.POST.get('del_ip_id')
1166 1185 user_model = UserModel()
1167 1186 user_data = c.user.get_api_data()
1168 1187 ip = UserIpMap.query().get(ip_id).ip_addr
1169 1188 user_model.delete_extra_ip(c.user.user_id, ip_id)
1170 1189 audit_logger.store_web(
1171 1190 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1172 1191 user=self._rhodecode_user)
1173 1192 Session().commit()
1174 1193 h.flash(_("Removed ip address from user whitelist"), category='success')
1175 1194
1176 1195 if 'default_user' in self.request.POST:
1177 1196 # case for editing global IP list we do it for 'DEFAULT' user
1178 1197 raise HTTPFound(h.route_path('admin_permissions_ips'))
1179 1198 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1180 1199
1181 1200 @LoginRequired()
1182 1201 @HasPermissionAllDecorator('hg.admin')
1183 1202 @view_config(
1184 1203 route_name='edit_user_groups_management', request_method='GET',
1185 1204 renderer='rhodecode:templates/admin/users/user_edit.mako')
1186 1205 def groups_management(self):
1187 1206 c = self.load_default_context()
1188 1207 c.user = self.db_user
1189 1208 c.data = c.user.group_member
1190 1209
1191 1210 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1192 1211 for group in c.user.group_member]
1193 1212 c.groups = json.dumps(groups)
1194 1213 c.active = 'groups'
1195 1214
1196 1215 return self._get_template_context(c)
1197 1216
1198 1217 @LoginRequired()
1199 1218 @HasPermissionAllDecorator('hg.admin')
1200 1219 @CSRFRequired()
1201 1220 @view_config(
1202 1221 route_name='edit_user_groups_management_updates', request_method='POST')
1203 1222 def groups_management_updates(self):
1204 1223 _ = self.request.translate
1205 1224 c = self.load_default_context()
1206 1225
1207 1226 user_id = self.db_user_id
1208 1227 c.user = self.db_user
1209 1228
1210 1229 user_groups = set(self.request.POST.getall('users_group_id'))
1211 1230 user_groups_objects = []
1212 1231
1213 1232 for ugid in user_groups:
1214 1233 user_groups_objects.append(
1215 1234 UserGroupModel().get_group(safe_int(ugid)))
1216 1235 user_group_model = UserGroupModel()
1217 1236 added_to_groups, removed_from_groups = \
1218 1237 user_group_model.change_groups(c.user, user_groups_objects)
1219 1238
1220 1239 user_data = c.user.get_api_data()
1221 1240 for user_group_id in added_to_groups:
1222 1241 user_group = UserGroup.get(user_group_id)
1223 1242 old_values = user_group.get_api_data()
1224 1243 audit_logger.store_web(
1225 1244 'user_group.edit.member.add',
1226 1245 action_data={'user': user_data, 'old_data': old_values},
1227 1246 user=self._rhodecode_user)
1228 1247
1229 1248 for user_group_id in removed_from_groups:
1230 1249 user_group = UserGroup.get(user_group_id)
1231 1250 old_values = user_group.get_api_data()
1232 1251 audit_logger.store_web(
1233 1252 'user_group.edit.member.delete',
1234 1253 action_data={'user': user_data, 'old_data': old_values},
1235 1254 user=self._rhodecode_user)
1236 1255
1237 1256 Session().commit()
1238 1257 c.active = 'user_groups_management'
1239 1258 h.flash(_("Groups successfully changed"), category='success')
1240 1259
1241 1260 return HTTPFound(h.route_path(
1242 1261 'edit_user_groups_management', user_id=user_id))
1243 1262
1244 1263 @LoginRequired()
1245 1264 @HasPermissionAllDecorator('hg.admin')
1246 1265 @view_config(
1247 1266 route_name='edit_user_audit_logs', request_method='GET',
1248 1267 renderer='rhodecode:templates/admin/users/user_edit.mako')
1249 1268 def user_audit_logs(self):
1250 1269 _ = self.request.translate
1251 1270 c = self.load_default_context()
1252 1271 c.user = self.db_user
1253 1272
1254 1273 c.active = 'audit'
1255 1274
1256 1275 p = safe_int(self.request.GET.get('page', 1), 1)
1257 1276
1258 1277 filter_term = self.request.GET.get('filter')
1259 1278 user_log = UserModel().get_user_log(c.user, filter_term)
1260 1279
1261 1280 def url_generator(page_num):
1262 1281 query_params = {
1263 1282 'page': page_num
1264 1283 }
1265 1284 if filter_term:
1266 1285 query_params['filter'] = filter_term
1267 1286 return self.request.current_route_path(_query=query_params)
1268 1287
1269 1288 c.audit_logs = SqlPage(
1270 1289 user_log, page=p, items_per_page=10, url_maker=url_generator)
1271 1290 c.filter_term = filter_term
1272 1291 return self._get_template_context(c)
1273 1292
1274 1293 @LoginRequired()
1275 1294 @HasPermissionAllDecorator('hg.admin')
1276 1295 @view_config(
1277 1296 route_name='edit_user_audit_logs_download', request_method='GET',
1278 1297 renderer='string')
1279 1298 def user_audit_logs_download(self):
1280 1299 _ = self.request.translate
1281 1300 c = self.load_default_context()
1282 1301 c.user = self.db_user
1283 1302
1284 1303 user_log = UserModel().get_user_log(c.user, filter_term=None)
1285 1304
1286 1305 audit_log_data = {}
1287 1306 for entry in user_log:
1288 1307 audit_log_data[entry.user_log_id] = entry.get_dict()
1289 1308
1290 1309 response = Response(json.dumps(audit_log_data, indent=4))
1291 1310 response.content_disposition = str(
1292 1311 'attachment; filename=%s' % 'user_{}_audit_logs.json'.format(c.user.user_id))
1293 1312 response.content_type = 'application/json'
1294 1313
1295 1314 return response
1296 1315
1297 1316 @LoginRequired()
1298 1317 @HasPermissionAllDecorator('hg.admin')
1299 1318 @view_config(
1300 1319 route_name='edit_user_perms_summary', request_method='GET',
1301 1320 renderer='rhodecode:templates/admin/users/user_edit.mako')
1302 1321 def user_perms_summary(self):
1303 1322 _ = self.request.translate
1304 1323 c = self.load_default_context()
1305 1324 c.user = self.db_user
1306 1325
1307 1326 c.active = 'perms_summary'
1308 1327 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1309 1328
1310 1329 return self._get_template_context(c)
1311 1330
1312 1331 @LoginRequired()
1313 1332 @HasPermissionAllDecorator('hg.admin')
1314 1333 @view_config(
1315 1334 route_name='edit_user_perms_summary_json', request_method='GET',
1316 1335 renderer='json_ext')
1317 1336 def user_perms_summary_json(self):
1318 1337 self.load_default_context()
1319 1338 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1320 1339
1321 1340 return perm_user.permissions
1322 1341
1323 1342 @LoginRequired()
1324 1343 @HasPermissionAllDecorator('hg.admin')
1325 1344 @view_config(
1326 1345 route_name='edit_user_caches', request_method='GET',
1327 1346 renderer='rhodecode:templates/admin/users/user_edit.mako')
1328 1347 def user_caches(self):
1329 1348 _ = self.request.translate
1330 1349 c = self.load_default_context()
1331 1350 c.user = self.db_user
1332 1351
1333 1352 c.active = 'caches'
1334 1353 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1335 1354
1336 1355 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1337 1356 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1338 1357 c.backend = c.region.backend
1339 1358 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1340 1359
1341 1360 return self._get_template_context(c)
1342 1361
1343 1362 @LoginRequired()
1344 1363 @HasPermissionAllDecorator('hg.admin')
1345 1364 @CSRFRequired()
1346 1365 @view_config(
1347 1366 route_name='edit_user_caches_update', request_method='POST')
1348 1367 def user_caches_update(self):
1349 1368 _ = self.request.translate
1350 1369 c = self.load_default_context()
1351 1370 c.user = self.db_user
1352 1371
1353 1372 c.active = 'caches'
1354 1373 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1355 1374
1356 1375 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1357 1376 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1358 1377
1359 1378 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1360 1379
1361 1380 return HTTPFound(h.route_path(
1362 1381 'edit_user_caches', user_id=c.user.user_id))
@@ -1,157 +1,160 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 from rhodecode.apps._base import ADMIN_PREFIX
23 23
24 24
25 25 def includeme(config):
26 26
27 27 config.add_route(
28 28 name='my_account_profile',
29 29 pattern=ADMIN_PREFIX + '/my_account/profile')
30 30
31 31 # my account edit details
32 32 config.add_route(
33 33 name='my_account_edit',
34 34 pattern=ADMIN_PREFIX + '/my_account/edit')
35 35 config.add_route(
36 36 name='my_account_update',
37 37 pattern=ADMIN_PREFIX + '/my_account/update')
38 38
39 39 # my account password
40 40 config.add_route(
41 41 name='my_account_password',
42 42 pattern=ADMIN_PREFIX + '/my_account/password')
43 43
44 44 config.add_route(
45 45 name='my_account_password_update',
46 46 pattern=ADMIN_PREFIX + '/my_account/password/update')
47 47
48 48 # my account tokens
49 49 config.add_route(
50 50 name='my_account_auth_tokens',
51 51 pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
52 52 config.add_route(
53 name='my_account_auth_tokens_view',
54 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/view')
55 config.add_route(
53 56 name='my_account_auth_tokens_add',
54 57 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new')
55 58 config.add_route(
56 59 name='my_account_auth_tokens_delete',
57 60 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete')
58 61
59 62 # my account ssh keys
60 63 config.add_route(
61 64 name='my_account_ssh_keys',
62 65 pattern=ADMIN_PREFIX + '/my_account/ssh_keys')
63 66 config.add_route(
64 67 name='my_account_ssh_keys_generate',
65 68 pattern=ADMIN_PREFIX + '/my_account/ssh_keys/generate')
66 69 config.add_route(
67 70 name='my_account_ssh_keys_add',
68 71 pattern=ADMIN_PREFIX + '/my_account/ssh_keys/new')
69 72 config.add_route(
70 73 name='my_account_ssh_keys_delete',
71 74 pattern=ADMIN_PREFIX + '/my_account/ssh_keys/delete')
72 75
73 76 # my account user group membership
74 77 config.add_route(
75 78 name='my_account_user_group_membership',
76 79 pattern=ADMIN_PREFIX + '/my_account/user_group_membership')
77 80
78 81 # my account emails
79 82 config.add_route(
80 83 name='my_account_emails',
81 84 pattern=ADMIN_PREFIX + '/my_account/emails')
82 85 config.add_route(
83 86 name='my_account_emails_add',
84 87 pattern=ADMIN_PREFIX + '/my_account/emails/new')
85 88 config.add_route(
86 89 name='my_account_emails_delete',
87 90 pattern=ADMIN_PREFIX + '/my_account/emails/delete')
88 91
89 92 config.add_route(
90 93 name='my_account_repos',
91 94 pattern=ADMIN_PREFIX + '/my_account/repos')
92 95
93 96 config.add_route(
94 97 name='my_account_watched',
95 98 pattern=ADMIN_PREFIX + '/my_account/watched')
96 99
97 100 config.add_route(
98 101 name='my_account_bookmarks',
99 102 pattern=ADMIN_PREFIX + '/my_account/bookmarks')
100 103
101 104 config.add_route(
102 105 name='my_account_bookmarks_update',
103 106 pattern=ADMIN_PREFIX + '/my_account/bookmarks/update')
104 107
105 108 config.add_route(
106 109 name='my_account_goto_bookmark',
107 110 pattern=ADMIN_PREFIX + '/my_account/bookmark/{bookmark_id}')
108 111
109 112 config.add_route(
110 113 name='my_account_perms',
111 114 pattern=ADMIN_PREFIX + '/my_account/perms')
112 115
113 116 config.add_route(
114 117 name='my_account_notifications',
115 118 pattern=ADMIN_PREFIX + '/my_account/notifications')
116 119
117 120 config.add_route(
118 121 name='my_account_notifications_toggle_visibility',
119 122 pattern=ADMIN_PREFIX + '/my_account/toggle_visibility')
120 123
121 124 # my account pull requests
122 125 config.add_route(
123 126 name='my_account_pullrequests',
124 127 pattern=ADMIN_PREFIX + '/my_account/pull_requests')
125 128 config.add_route(
126 129 name='my_account_pullrequests_data',
127 130 pattern=ADMIN_PREFIX + '/my_account/pull_requests/data')
128 131
129 132 # notifications
130 133 config.add_route(
131 134 name='notifications_show_all',
132 135 pattern=ADMIN_PREFIX + '/notifications')
133 136
134 137 # notifications
135 138 config.add_route(
136 139 name='notifications_mark_all_read',
137 140 pattern=ADMIN_PREFIX + '/notifications/mark_all_read')
138 141
139 142 config.add_route(
140 143 name='notifications_show',
141 144 pattern=ADMIN_PREFIX + '/notifications/{notification_id}')
142 145
143 146 config.add_route(
144 147 name='notifications_update',
145 148 pattern=ADMIN_PREFIX + '/notifications/{notification_id}/update')
146 149
147 150 config.add_route(
148 151 name='notifications_delete',
149 152 pattern=ADMIN_PREFIX + '/notifications/{notification_id}/delete')
150 153
151 154 # channelstream test
152 155 config.add_route(
153 156 name='my_account_notifications_test_channelstream',
154 157 pattern=ADMIN_PREFIX + '/my_account/test_channelstream')
155 158
156 159 # Scan module for configuration decorators.
157 160 config.scan('.views', ignore='.tests')
@@ -1,111 +1,111 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.apps._base import ADMIN_PREFIX
24 24 from rhodecode.model.db import User
25 25 from rhodecode.tests import (
26 26 TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
27 27 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, assert_session_flash)
28 28 from rhodecode.tests.fixture import Fixture
29 29 from rhodecode.tests.utils import AssertResponse
30 30
31 31 fixture = Fixture()
32 32
33 33
34 34 def route_path(name, **kwargs):
35 35 return {
36 36 'my_account_auth_tokens':
37 37 ADMIN_PREFIX + '/my_account/auth_tokens',
38 38 'my_account_auth_tokens_add':
39 39 ADMIN_PREFIX + '/my_account/auth_tokens/new',
40 40 'my_account_auth_tokens_delete':
41 41 ADMIN_PREFIX + '/my_account/auth_tokens/delete',
42 42 }[name].format(**kwargs)
43 43
44 44
45 45 class TestMyAccountAuthTokens(TestController):
46 46
47 47 def test_my_account_auth_tokens(self):
48 48 usr = self.log_user('test_regular2', 'test12')
49 49 user = User.get(usr['user_id'])
50 50 response = self.app.get(route_path('my_account_auth_tokens'))
51 51 for token in user.auth_tokens:
52 response.mustcontain(token)
52 response.mustcontain(token[:4])
53 53 response.mustcontain('never')
54 54
55 55 def test_my_account_add_auth_tokens_wrong_csrf(self, user_util):
56 56 user = user_util.create_user(password='qweqwe')
57 57 self.log_user(user.username, 'qweqwe')
58 58
59 59 self.app.post(
60 60 route_path('my_account_auth_tokens_add'),
61 61 {'description': 'desc', 'lifetime': -1}, status=403)
62 62
63 63 @pytest.mark.parametrize("desc, lifetime", [
64 64 ('forever', -1),
65 65 ('5mins', 60*5),
66 66 ('30days', 60*60*24*30),
67 67 ])
68 68 def test_my_account_add_auth_tokens(self, desc, lifetime, user_util):
69 69 user = user_util.create_user(password='qweqwe')
70 70 user_id = user.user_id
71 71 self.log_user(user.username, 'qweqwe')
72 72
73 73 response = self.app.post(
74 74 route_path('my_account_auth_tokens_add'),
75 75 {'description': desc, 'lifetime': lifetime,
76 76 'csrf_token': self.csrf_token})
77 77 assert_session_flash(response, 'Auth token successfully created')
78 78
79 79 response = response.follow()
80 80 user = User.get(user_id)
81 81 for auth_token in user.auth_tokens:
82 response.mustcontain(auth_token)
82 response.mustcontain(auth_token[:4])
83 83
84 84 def test_my_account_delete_auth_token(self, user_util):
85 85 user = user_util.create_user(password='qweqwe')
86 86 user_id = user.user_id
87 87 self.log_user(user.username, 'qweqwe')
88 88
89 89 user = User.get(user_id)
90 90 keys = user.get_auth_tokens()
91 91 assert 2 == len(keys)
92 92
93 93 response = self.app.post(
94 94 route_path('my_account_auth_tokens_add'),
95 95 {'description': 'desc', 'lifetime': -1,
96 96 'csrf_token': self.csrf_token})
97 97 assert_session_flash(response, 'Auth token successfully created')
98 98 response.follow()
99 99
100 100 user = User.get(user_id)
101 101 keys = user.get_auth_tokens()
102 102 assert 3 == len(keys)
103 103
104 104 response = self.app.post(
105 105 route_path('my_account_auth_tokens_delete'),
106 106 {'del_auth_token': keys[0].user_api_key_id, 'csrf_token': self.csrf_token})
107 107 assert_session_flash(response, 'Auth token successfully deleted')
108 108
109 109 user = User.get(user_id)
110 110 keys = user.auth_tokens
111 111 assert 2 == len(keys)
@@ -1,801 +1,822 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import datetime
23 23 import string
24 24
25 25 import formencode
26 26 import formencode.htmlfill
27 27 import peppercorn
28 from pyramid.httpexceptions import HTTPFound
28 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
29 29 from pyramid.view import view_config
30 30
31 31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 32 from rhodecode import forms
33 33 from rhodecode.lib import helpers as h
34 34 from rhodecode.lib import audit_logger
35 35 from rhodecode.lib.ext_json import json
36 36 from rhodecode.lib.auth import (
37 37 LoginRequired, NotAnonymous, CSRFRequired,
38 38 HasRepoPermissionAny, HasRepoGroupPermissionAny, AuthUser)
39 39 from rhodecode.lib.channelstream import (
40 40 channelstream_request, ChannelstreamException)
41 41 from rhodecode.lib.utils2 import safe_int, md5, str2bool
42 42 from rhodecode.model.auth_token import AuthTokenModel
43 43 from rhodecode.model.comment import CommentsModel
44 44 from rhodecode.model.db import (
45 45 IntegrityError, or_, in_filter_generator,
46 46 Repository, UserEmailMap, UserApiKeys, UserFollowing,
47 47 PullRequest, UserBookmark, RepoGroup)
48 48 from rhodecode.model.meta import Session
49 49 from rhodecode.model.pull_request import PullRequestModel
50 50 from rhodecode.model.user import UserModel
51 51 from rhodecode.model.user_group import UserGroupModel
52 52 from rhodecode.model.validation_schema.schemas import user_schema
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56
57 57 class MyAccountView(BaseAppView, DataGridAppView):
58 58 ALLOW_SCOPED_TOKENS = False
59 59 """
60 60 This view has alternative version inside EE, if modified please take a look
61 61 in there as well.
62 62 """
63 63
64 64 def load_default_context(self):
65 65 c = self._get_local_tmpl_context()
66 66 c.user = c.auth_user.get_instance()
67 67 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
68 68
69 69 return c
70 70
71 71 @LoginRequired()
72 72 @NotAnonymous()
73 73 @view_config(
74 74 route_name='my_account_profile', request_method='GET',
75 75 renderer='rhodecode:templates/admin/my_account/my_account.mako')
76 76 def my_account_profile(self):
77 77 c = self.load_default_context()
78 78 c.active = 'profile'
79 79 c.extern_type = c.user.extern_type
80 80 return self._get_template_context(c)
81 81
82 82 @LoginRequired()
83 83 @NotAnonymous()
84 84 @view_config(
85 85 route_name='my_account_password', request_method='GET',
86 86 renderer='rhodecode:templates/admin/my_account/my_account.mako')
87 87 def my_account_password(self):
88 88 c = self.load_default_context()
89 89 c.active = 'password'
90 90 c.extern_type = c.user.extern_type
91 91
92 92 schema = user_schema.ChangePasswordSchema().bind(
93 93 username=c.user.username)
94 94
95 95 form = forms.Form(
96 96 schema,
97 97 action=h.route_path('my_account_password_update'),
98 98 buttons=(forms.buttons.save, forms.buttons.reset))
99 99
100 100 c.form = form
101 101 return self._get_template_context(c)
102 102
103 103 @LoginRequired()
104 104 @NotAnonymous()
105 105 @CSRFRequired()
106 106 @view_config(
107 107 route_name='my_account_password_update', request_method='POST',
108 108 renderer='rhodecode:templates/admin/my_account/my_account.mako')
109 109 def my_account_password_update(self):
110 110 _ = self.request.translate
111 111 c = self.load_default_context()
112 112 c.active = 'password'
113 113 c.extern_type = c.user.extern_type
114 114
115 115 schema = user_schema.ChangePasswordSchema().bind(
116 116 username=c.user.username)
117 117
118 118 form = forms.Form(
119 119 schema, buttons=(forms.buttons.save, forms.buttons.reset))
120 120
121 121 if c.extern_type != 'rhodecode':
122 122 raise HTTPFound(self.request.route_path('my_account_password'))
123 123
124 124 controls = self.request.POST.items()
125 125 try:
126 126 valid_data = form.validate(controls)
127 127 UserModel().update_user(c.user.user_id, **valid_data)
128 128 c.user.update_userdata(force_password_change=False)
129 129 Session().commit()
130 130 except forms.ValidationFailure as e:
131 131 c.form = e
132 132 return self._get_template_context(c)
133 133
134 134 except Exception:
135 135 log.exception("Exception updating password")
136 136 h.flash(_('Error occurred during update of user password'),
137 137 category='error')
138 138 else:
139 139 instance = c.auth_user.get_instance()
140 140 self.session.setdefault('rhodecode_user', {}).update(
141 141 {'password': md5(instance.password)})
142 142 self.session.save()
143 143 h.flash(_("Successfully updated password"), category='success')
144 144
145 145 raise HTTPFound(self.request.route_path('my_account_password'))
146 146
147 147 @LoginRequired()
148 148 @NotAnonymous()
149 149 @view_config(
150 150 route_name='my_account_auth_tokens', request_method='GET',
151 151 renderer='rhodecode:templates/admin/my_account/my_account.mako')
152 152 def my_account_auth_tokens(self):
153 153 _ = self.request.translate
154 154
155 155 c = self.load_default_context()
156 156 c.active = 'auth_tokens'
157 157 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
158 158 c.role_values = [
159 159 (x, AuthTokenModel.cls._get_role_name(x))
160 160 for x in AuthTokenModel.cls.ROLES]
161 161 c.role_options = [(c.role_values, _("Role"))]
162 162 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
163 163 c.user.user_id, show_expired=True)
164 164 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
165 165 return self._get_template_context(c)
166 166
167 @LoginRequired()
168 @NotAnonymous()
169 @CSRFRequired()
170 @view_config(
171 route_name='my_account_auth_tokens_view', request_method='POST', xhr=True,
172 renderer='json_ext')
173 def my_account_auth_tokens_view(self):
174 _ = self.request.translate
175 c = self.load_default_context()
176
177 auth_token_id = self.request.POST.get('auth_token_id')
178
179 if auth_token_id:
180 token = UserApiKeys.get_or_404(auth_token_id)
181 if token.user.user_id != c.user.user_id:
182 raise HTTPNotFound()
183
184 return {
185 'auth_token': token.api_key
186 }
187
167 188 def maybe_attach_token_scope(self, token):
168 189 # implemented in EE edition
169 190 pass
170 191
171 192 @LoginRequired()
172 193 @NotAnonymous()
173 194 @CSRFRequired()
174 195 @view_config(
175 196 route_name='my_account_auth_tokens_add', request_method='POST',)
176 197 def my_account_auth_tokens_add(self):
177 198 _ = self.request.translate
178 199 c = self.load_default_context()
179 200
180 201 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
181 202 description = self.request.POST.get('description')
182 203 role = self.request.POST.get('role')
183 204
184 205 token = UserModel().add_auth_token(
185 206 user=c.user.user_id,
186 207 lifetime_minutes=lifetime, role=role, description=description,
187 208 scope_callback=self.maybe_attach_token_scope)
188 209 token_data = token.get_api_data()
189 210
190 211 audit_logger.store_web(
191 212 'user.edit.token.add', action_data={
192 213 'data': {'token': token_data, 'user': 'self'}},
193 214 user=self._rhodecode_user, )
194 215 Session().commit()
195 216
196 217 h.flash(_("Auth token successfully created"), category='success')
197 218 return HTTPFound(h.route_path('my_account_auth_tokens'))
198 219
199 220 @LoginRequired()
200 221 @NotAnonymous()
201 222 @CSRFRequired()
202 223 @view_config(
203 224 route_name='my_account_auth_tokens_delete', request_method='POST')
204 225 def my_account_auth_tokens_delete(self):
205 226 _ = self.request.translate
206 227 c = self.load_default_context()
207 228
208 229 del_auth_token = self.request.POST.get('del_auth_token')
209 230
210 231 if del_auth_token:
211 232 token = UserApiKeys.get_or_404(del_auth_token)
212 233 token_data = token.get_api_data()
213 234
214 235 AuthTokenModel().delete(del_auth_token, c.user.user_id)
215 236 audit_logger.store_web(
216 237 'user.edit.token.delete', action_data={
217 238 'data': {'token': token_data, 'user': 'self'}},
218 239 user=self._rhodecode_user,)
219 240 Session().commit()
220 241 h.flash(_("Auth token successfully deleted"), category='success')
221 242
222 243 return HTTPFound(h.route_path('my_account_auth_tokens'))
223 244
224 245 @LoginRequired()
225 246 @NotAnonymous()
226 247 @view_config(
227 248 route_name='my_account_emails', request_method='GET',
228 249 renderer='rhodecode:templates/admin/my_account/my_account.mako')
229 250 def my_account_emails(self):
230 251 _ = self.request.translate
231 252
232 253 c = self.load_default_context()
233 254 c.active = 'emails'
234 255
235 256 c.user_email_map = UserEmailMap.query()\
236 257 .filter(UserEmailMap.user == c.user).all()
237 258
238 259 schema = user_schema.AddEmailSchema().bind(
239 260 username=c.user.username, user_emails=c.user.emails)
240 261
241 262 form = forms.RcForm(schema,
242 263 action=h.route_path('my_account_emails_add'),
243 264 buttons=(forms.buttons.save, forms.buttons.reset))
244 265
245 266 c.form = form
246 267 return self._get_template_context(c)
247 268
248 269 @LoginRequired()
249 270 @NotAnonymous()
250 271 @CSRFRequired()
251 272 @view_config(
252 273 route_name='my_account_emails_add', request_method='POST',
253 274 renderer='rhodecode:templates/admin/my_account/my_account.mako')
254 275 def my_account_emails_add(self):
255 276 _ = self.request.translate
256 277 c = self.load_default_context()
257 278 c.active = 'emails'
258 279
259 280 schema = user_schema.AddEmailSchema().bind(
260 281 username=c.user.username, user_emails=c.user.emails)
261 282
262 283 form = forms.RcForm(
263 284 schema, action=h.route_path('my_account_emails_add'),
264 285 buttons=(forms.buttons.save, forms.buttons.reset))
265 286
266 287 controls = self.request.POST.items()
267 288 try:
268 289 valid_data = form.validate(controls)
269 290 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
270 291 audit_logger.store_web(
271 292 'user.edit.email.add', action_data={
272 293 'data': {'email': valid_data['email'], 'user': 'self'}},
273 294 user=self._rhodecode_user,)
274 295 Session().commit()
275 296 except formencode.Invalid as error:
276 297 h.flash(h.escape(error.error_dict['email']), category='error')
277 298 except forms.ValidationFailure as e:
278 299 c.user_email_map = UserEmailMap.query() \
279 300 .filter(UserEmailMap.user == c.user).all()
280 301 c.form = e
281 302 return self._get_template_context(c)
282 303 except Exception:
283 304 log.exception("Exception adding email")
284 305 h.flash(_('Error occurred during adding email'),
285 306 category='error')
286 307 else:
287 308 h.flash(_("Successfully added email"), category='success')
288 309
289 310 raise HTTPFound(self.request.route_path('my_account_emails'))
290 311
291 312 @LoginRequired()
292 313 @NotAnonymous()
293 314 @CSRFRequired()
294 315 @view_config(
295 316 route_name='my_account_emails_delete', request_method='POST')
296 317 def my_account_emails_delete(self):
297 318 _ = self.request.translate
298 319 c = self.load_default_context()
299 320
300 321 del_email_id = self.request.POST.get('del_email_id')
301 322 if del_email_id:
302 323 email = UserEmailMap.get_or_404(del_email_id).email
303 324 UserModel().delete_extra_email(c.user.user_id, del_email_id)
304 325 audit_logger.store_web(
305 326 'user.edit.email.delete', action_data={
306 327 'data': {'email': email, 'user': 'self'}},
307 328 user=self._rhodecode_user,)
308 329 Session().commit()
309 330 h.flash(_("Email successfully deleted"),
310 331 category='success')
311 332 return HTTPFound(h.route_path('my_account_emails'))
312 333
313 334 @LoginRequired()
314 335 @NotAnonymous()
315 336 @CSRFRequired()
316 337 @view_config(
317 338 route_name='my_account_notifications_test_channelstream',
318 339 request_method='POST', renderer='json_ext')
319 340 def my_account_notifications_test_channelstream(self):
320 341 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
321 342 self._rhodecode_user.username, datetime.datetime.now())
322 343 payload = {
323 344 # 'channel': 'broadcast',
324 345 'type': 'message',
325 346 'timestamp': datetime.datetime.utcnow(),
326 347 'user': 'system',
327 348 'pm_users': [self._rhodecode_user.username],
328 349 'message': {
329 350 'message': message,
330 351 'level': 'info',
331 352 'topic': '/notifications'
332 353 }
333 354 }
334 355
335 356 registry = self.request.registry
336 357 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
337 358 channelstream_config = rhodecode_plugins.get('channelstream', {})
338 359
339 360 try:
340 361 channelstream_request(channelstream_config, [payload], '/message')
341 362 except ChannelstreamException as e:
342 363 log.exception('Failed to send channelstream data')
343 364 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
344 365 return {"response": 'Channelstream data sent. '
345 366 'You should see a new live message now.'}
346 367
347 368 def _load_my_repos_data(self, watched=False):
348 369
349 370 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(AuthUser.repo_read_perms)
350 371
351 372 if watched:
352 373 # repos user watch
353 374 repo_list = Session().query(
354 375 Repository
355 376 ) \
356 377 .join(
357 378 (UserFollowing, UserFollowing.follows_repo_id == Repository.repo_id)
358 379 ) \
359 380 .filter(
360 381 UserFollowing.user_id == self._rhodecode_user.user_id
361 382 ) \
362 383 .filter(or_(
363 384 # generate multiple IN to fix limitation problems
364 385 *in_filter_generator(Repository.repo_id, allowed_ids))
365 386 ) \
366 387 .order_by(Repository.repo_name) \
367 388 .all()
368 389
369 390 else:
370 391 # repos user is owner of
371 392 repo_list = Session().query(
372 393 Repository
373 394 ) \
374 395 .filter(
375 396 Repository.user_id == self._rhodecode_user.user_id
376 397 ) \
377 398 .filter(or_(
378 399 # generate multiple IN to fix limitation problems
379 400 *in_filter_generator(Repository.repo_id, allowed_ids))
380 401 ) \
381 402 .order_by(Repository.repo_name) \
382 403 .all()
383 404
384 405 _render = self.request.get_partial_renderer(
385 406 'rhodecode:templates/data_table/_dt_elements.mako')
386 407
387 408 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
388 409 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
389 410 short_name=False, admin=False)
390 411
391 412 repos_data = []
392 413 for repo in repo_list:
393 414 row = {
394 415 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
395 416 repo.private, repo.archived, repo.fork),
396 417 "name_raw": repo.repo_name.lower(),
397 418 }
398 419
399 420 repos_data.append(row)
400 421
401 422 # json used to render the grid
402 423 return json.dumps(repos_data)
403 424
404 425 @LoginRequired()
405 426 @NotAnonymous()
406 427 @view_config(
407 428 route_name='my_account_repos', request_method='GET',
408 429 renderer='rhodecode:templates/admin/my_account/my_account.mako')
409 430 def my_account_repos(self):
410 431 c = self.load_default_context()
411 432 c.active = 'repos'
412 433
413 434 # json used to render the grid
414 435 c.data = self._load_my_repos_data()
415 436 return self._get_template_context(c)
416 437
417 438 @LoginRequired()
418 439 @NotAnonymous()
419 440 @view_config(
420 441 route_name='my_account_watched', request_method='GET',
421 442 renderer='rhodecode:templates/admin/my_account/my_account.mako')
422 443 def my_account_watched(self):
423 444 c = self.load_default_context()
424 445 c.active = 'watched'
425 446
426 447 # json used to render the grid
427 448 c.data = self._load_my_repos_data(watched=True)
428 449 return self._get_template_context(c)
429 450
430 451 @LoginRequired()
431 452 @NotAnonymous()
432 453 @view_config(
433 454 route_name='my_account_bookmarks', request_method='GET',
434 455 renderer='rhodecode:templates/admin/my_account/my_account.mako')
435 456 def my_account_bookmarks(self):
436 457 c = self.load_default_context()
437 458 c.active = 'bookmarks'
438 459 c.bookmark_items = UserBookmark.get_bookmarks_for_user(
439 460 self._rhodecode_db_user.user_id, cache=False)
440 461 return self._get_template_context(c)
441 462
442 463 def _process_bookmark_entry(self, entry, user_id):
443 464 position = safe_int(entry.get('position'))
444 465 cur_position = safe_int(entry.get('cur_position'))
445 466 if position is None:
446 467 return
447 468
448 469 # check if this is an existing entry
449 470 is_new = False
450 471 db_entry = UserBookmark().get_by_position_for_user(cur_position, user_id)
451 472
452 473 if db_entry and str2bool(entry.get('remove')):
453 474 log.debug('Marked bookmark %s for deletion', db_entry)
454 475 Session().delete(db_entry)
455 476 return
456 477
457 478 if not db_entry:
458 479 # new
459 480 db_entry = UserBookmark()
460 481 is_new = True
461 482
462 483 should_save = False
463 484 default_redirect_url = ''
464 485
465 486 # save repo
466 487 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
467 488 repo = Repository.get(entry['bookmark_repo'])
468 489 perm_check = HasRepoPermissionAny(
469 490 'repository.read', 'repository.write', 'repository.admin')
470 491 if repo and perm_check(repo_name=repo.repo_name):
471 492 db_entry.repository = repo
472 493 should_save = True
473 494 default_redirect_url = '${repo_url}'
474 495 # save repo group
475 496 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
476 497 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
477 498 perm_check = HasRepoGroupPermissionAny(
478 499 'group.read', 'group.write', 'group.admin')
479 500
480 501 if repo_group and perm_check(group_name=repo_group.group_name):
481 502 db_entry.repository_group = repo_group
482 503 should_save = True
483 504 default_redirect_url = '${repo_group_url}'
484 505 # save generic info
485 506 elif entry.get('title') and entry.get('redirect_url'):
486 507 should_save = True
487 508
488 509 if should_save:
489 510 # mark user and position
490 511 db_entry.user_id = user_id
491 512 db_entry.position = position
492 513 db_entry.title = entry.get('title')
493 514 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
494 515 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
495 516
496 517 Session().add(db_entry)
497 518
498 519 @LoginRequired()
499 520 @NotAnonymous()
500 521 @CSRFRequired()
501 522 @view_config(
502 523 route_name='my_account_bookmarks_update', request_method='POST')
503 524 def my_account_bookmarks_update(self):
504 525 _ = self.request.translate
505 526 c = self.load_default_context()
506 527 c.active = 'bookmarks'
507 528
508 529 controls = peppercorn.parse(self.request.POST.items())
509 530 user_id = c.user.user_id
510 531
511 532 # validate positions
512 533 positions = {}
513 534 for entry in controls.get('bookmarks', []):
514 535 position = safe_int(entry['position'])
515 536 if position is None:
516 537 continue
517 538
518 539 if position in positions:
519 540 h.flash(_("Position {} is defined twice. "
520 541 "Please correct this error.").format(position), category='error')
521 542 return HTTPFound(h.route_path('my_account_bookmarks'))
522 543
523 544 entry['position'] = position
524 545 entry['cur_position'] = safe_int(entry.get('cur_position'))
525 546 positions[position] = entry
526 547
527 548 try:
528 549 for entry in positions.values():
529 550 self._process_bookmark_entry(entry, user_id)
530 551
531 552 Session().commit()
532 553 h.flash(_("Update Bookmarks"), category='success')
533 554 except IntegrityError:
534 555 h.flash(_("Failed to update bookmarks. "
535 556 "Make sure an unique position is used."), category='error')
536 557
537 558 return HTTPFound(h.route_path('my_account_bookmarks'))
538 559
539 560 @LoginRequired()
540 561 @NotAnonymous()
541 562 @view_config(
542 563 route_name='my_account_goto_bookmark', request_method='GET',
543 564 renderer='rhodecode:templates/admin/my_account/my_account.mako')
544 565 def my_account_goto_bookmark(self):
545 566
546 567 bookmark_id = self.request.matchdict['bookmark_id']
547 568 user_bookmark = UserBookmark().query()\
548 569 .filter(UserBookmark.user_id == self.request.user.user_id) \
549 570 .filter(UserBookmark.position == bookmark_id).scalar()
550 571
551 572 redirect_url = h.route_path('my_account_bookmarks')
552 573 if not user_bookmark:
553 574 raise HTTPFound(redirect_url)
554 575
555 576 # repository set
556 577 if user_bookmark.repository:
557 578 repo_name = user_bookmark.repository.repo_name
558 579 base_redirect_url = h.route_path(
559 580 'repo_summary', repo_name=repo_name)
560 581 if user_bookmark.redirect_url and \
561 582 '${repo_url}' in user_bookmark.redirect_url:
562 583 redirect_url = string.Template(user_bookmark.redirect_url)\
563 584 .safe_substitute({'repo_url': base_redirect_url})
564 585 else:
565 586 redirect_url = base_redirect_url
566 587 # repository group set
567 588 elif user_bookmark.repository_group:
568 589 repo_group_name = user_bookmark.repository_group.group_name
569 590 base_redirect_url = h.route_path(
570 591 'repo_group_home', repo_group_name=repo_group_name)
571 592 if user_bookmark.redirect_url and \
572 593 '${repo_group_url}' in user_bookmark.redirect_url:
573 594 redirect_url = string.Template(user_bookmark.redirect_url)\
574 595 .safe_substitute({'repo_group_url': base_redirect_url})
575 596 else:
576 597 redirect_url = base_redirect_url
577 598 # custom URL set
578 599 elif user_bookmark.redirect_url:
579 600 server_url = h.route_url('home').rstrip('/')
580 601 redirect_url = string.Template(user_bookmark.redirect_url) \
581 602 .safe_substitute({'server_url': server_url})
582 603
583 604 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
584 605 raise HTTPFound(redirect_url)
585 606
586 607 @LoginRequired()
587 608 @NotAnonymous()
588 609 @view_config(
589 610 route_name='my_account_perms', request_method='GET',
590 611 renderer='rhodecode:templates/admin/my_account/my_account.mako')
591 612 def my_account_perms(self):
592 613 c = self.load_default_context()
593 614 c.active = 'perms'
594 615
595 616 c.perm_user = c.auth_user
596 617 return self._get_template_context(c)
597 618
598 619 @LoginRequired()
599 620 @NotAnonymous()
600 621 @view_config(
601 622 route_name='my_account_notifications', request_method='GET',
602 623 renderer='rhodecode:templates/admin/my_account/my_account.mako')
603 624 def my_notifications(self):
604 625 c = self.load_default_context()
605 626 c.active = 'notifications'
606 627
607 628 return self._get_template_context(c)
608 629
609 630 @LoginRequired()
610 631 @NotAnonymous()
611 632 @CSRFRequired()
612 633 @view_config(
613 634 route_name='my_account_notifications_toggle_visibility',
614 635 request_method='POST', renderer='json_ext')
615 636 def my_notifications_toggle_visibility(self):
616 637 user = self._rhodecode_db_user
617 638 new_status = not user.user_data.get('notification_status', True)
618 639 user.update_userdata(notification_status=new_status)
619 640 Session().commit()
620 641 return user.user_data['notification_status']
621 642
622 643 @LoginRequired()
623 644 @NotAnonymous()
624 645 @view_config(
625 646 route_name='my_account_edit',
626 647 request_method='GET',
627 648 renderer='rhodecode:templates/admin/my_account/my_account.mako')
628 649 def my_account_edit(self):
629 650 c = self.load_default_context()
630 651 c.active = 'profile_edit'
631 652 c.extern_type = c.user.extern_type
632 653 c.extern_name = c.user.extern_name
633 654
634 655 schema = user_schema.UserProfileSchema().bind(
635 656 username=c.user.username, user_emails=c.user.emails)
636 657 appstruct = {
637 658 'username': c.user.username,
638 659 'email': c.user.email,
639 660 'firstname': c.user.firstname,
640 661 'lastname': c.user.lastname,
641 662 'description': c.user.description,
642 663 }
643 664 c.form = forms.RcForm(
644 665 schema, appstruct=appstruct,
645 666 action=h.route_path('my_account_update'),
646 667 buttons=(forms.buttons.save, forms.buttons.reset))
647 668
648 669 return self._get_template_context(c)
649 670
650 671 @LoginRequired()
651 672 @NotAnonymous()
652 673 @CSRFRequired()
653 674 @view_config(
654 675 route_name='my_account_update',
655 676 request_method='POST',
656 677 renderer='rhodecode:templates/admin/my_account/my_account.mako')
657 678 def my_account_update(self):
658 679 _ = self.request.translate
659 680 c = self.load_default_context()
660 681 c.active = 'profile_edit'
661 682 c.perm_user = c.auth_user
662 683 c.extern_type = c.user.extern_type
663 684 c.extern_name = c.user.extern_name
664 685
665 686 schema = user_schema.UserProfileSchema().bind(
666 687 username=c.user.username, user_emails=c.user.emails)
667 688 form = forms.RcForm(
668 689 schema, buttons=(forms.buttons.save, forms.buttons.reset))
669 690
670 691 controls = self.request.POST.items()
671 692 try:
672 693 valid_data = form.validate(controls)
673 694 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
674 695 'new_password', 'password_confirmation']
675 696 if c.extern_type != "rhodecode":
676 697 # forbid updating username for external accounts
677 698 skip_attrs.append('username')
678 699 old_email = c.user.email
679 700 UserModel().update_user(
680 701 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
681 702 **valid_data)
682 703 if old_email != valid_data['email']:
683 704 old = UserEmailMap.query() \
684 705 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
685 706 old.email = old_email
686 707 h.flash(_('Your account was updated successfully'), category='success')
687 708 Session().commit()
688 709 except forms.ValidationFailure as e:
689 710 c.form = e
690 711 return self._get_template_context(c)
691 712 except Exception:
692 713 log.exception("Exception updating user")
693 714 h.flash(_('Error occurred during update of user'),
694 715 category='error')
695 716 raise HTTPFound(h.route_path('my_account_profile'))
696 717
697 718 def _get_pull_requests_list(self, statuses):
698 719 draw, start, limit = self._extract_chunk(self.request)
699 720 search_q, order_by, order_dir = self._extract_ordering(self.request)
700 721 _render = self.request.get_partial_renderer(
701 722 'rhodecode:templates/data_table/_dt_elements.mako')
702 723
703 724 pull_requests = PullRequestModel().get_im_participating_in(
704 725 user_id=self._rhodecode_user.user_id,
705 726 statuses=statuses,
706 727 offset=start, length=limit, order_by=order_by,
707 728 order_dir=order_dir)
708 729
709 730 pull_requests_total_count = PullRequestModel().count_im_participating_in(
710 731 user_id=self._rhodecode_user.user_id, statuses=statuses)
711 732
712 733 data = []
713 734 comments_model = CommentsModel()
714 735 for pr in pull_requests:
715 736 repo_id = pr.target_repo_id
716 737 comments = comments_model.get_all_comments(
717 738 repo_id, pull_request=pr)
718 739 owned = pr.user_id == self._rhodecode_user.user_id
719 740
720 741 data.append({
721 742 'target_repo': _render('pullrequest_target_repo',
722 743 pr.target_repo.repo_name),
723 744 'name': _render('pullrequest_name',
724 745 pr.pull_request_id, pr.pull_request_state,
725 746 pr.work_in_progress, pr.target_repo.repo_name,
726 747 short=True),
727 748 'name_raw': pr.pull_request_id,
728 749 'status': _render('pullrequest_status',
729 750 pr.calculated_review_status()),
730 751 'title': _render('pullrequest_title', pr.title, pr.description),
731 752 'description': h.escape(pr.description),
732 753 'updated_on': _render('pullrequest_updated_on',
733 754 h.datetime_to_time(pr.updated_on)),
734 755 'updated_on_raw': h.datetime_to_time(pr.updated_on),
735 756 'created_on': _render('pullrequest_updated_on',
736 757 h.datetime_to_time(pr.created_on)),
737 758 'created_on_raw': h.datetime_to_time(pr.created_on),
738 759 'state': pr.pull_request_state,
739 760 'author': _render('pullrequest_author',
740 761 pr.author.full_contact, ),
741 762 'author_raw': pr.author.full_name,
742 763 'comments': _render('pullrequest_comments', len(comments)),
743 764 'comments_raw': len(comments),
744 765 'closed': pr.is_closed(),
745 766 'owned': owned
746 767 })
747 768
748 769 # json used to render the grid
749 770 data = ({
750 771 'draw': draw,
751 772 'data': data,
752 773 'recordsTotal': pull_requests_total_count,
753 774 'recordsFiltered': pull_requests_total_count,
754 775 })
755 776 return data
756 777
757 778 @LoginRequired()
758 779 @NotAnonymous()
759 780 @view_config(
760 781 route_name='my_account_pullrequests',
761 782 request_method='GET',
762 783 renderer='rhodecode:templates/admin/my_account/my_account.mako')
763 784 def my_account_pullrequests(self):
764 785 c = self.load_default_context()
765 786 c.active = 'pullrequests'
766 787 req_get = self.request.GET
767 788
768 789 c.closed = str2bool(req_get.get('pr_show_closed'))
769 790
770 791 return self._get_template_context(c)
771 792
772 793 @LoginRequired()
773 794 @NotAnonymous()
774 795 @view_config(
775 796 route_name='my_account_pullrequests_data',
776 797 request_method='GET', renderer='json_ext')
777 798 def my_account_pullrequests_data(self):
778 799 self.load_default_context()
779 800 req_get = self.request.GET
780 801 closed = str2bool(req_get.get('closed'))
781 802
782 803 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
783 804 if closed:
784 805 statuses += [PullRequest.STATUS_CLOSED]
785 806
786 807 data = self._get_pull_requests_list(statuses=statuses)
787 808 return data
788 809
789 810 @LoginRequired()
790 811 @NotAnonymous()
791 812 @view_config(
792 813 route_name='my_account_user_group_membership',
793 814 request_method='GET',
794 815 renderer='rhodecode:templates/admin/my_account/my_account.mako')
795 816 def my_account_user_group_membership(self):
796 817 c = self.load_default_context()
797 818 c.active = 'user_group_membership'
798 819 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
799 820 for group in self._rhodecode_db_user.group_member]
800 821 c.user_groups = json.dumps(groups)
801 822 return self._get_template_context(c)
@@ -1,560 +1,560 b''
1 1
2 2 // tables.less
3 3 // For use in RhodeCode application tables;
4 4 // see style guide documentation for guidelines.
5 5
6 6 // TABLES
7 7
8 8 .rctable,
9 9 table.rctable,
10 10 table.dataTable {
11 11 clear:both;
12 12 width: 100%;
13 13 margin: 0 auto @padding;
14 14 padding: 0;
15 15 vertical-align: baseline;
16 16 line-height:1.5em;
17 17 border: none;
18 18 outline: none;
19 19 border-collapse: collapse;
20 20 border-spacing: 0;
21 21 color: @grey2;
22 22
23 23 b {
24 24 font-weight: normal;
25 25 }
26 26
27 27 em {
28 28 font-weight: bold;
29 29 font-style: normal;
30 30 }
31 31
32 32 .td-user {
33 33 .rc-user {
34 34 white-space: nowrap;
35 35 }
36 36 }
37 37
38 38 .td-email {
39 39 white-space: nowrap;
40 40 }
41 41
42 42 th,
43 43 td {
44 44 height: auto;
45 45 max-width: 20%;
46 46 padding: .65em 0 .65em 1em;
47 47 vertical-align: middle;
48 48 border-bottom: @border-thickness solid @grey5;
49 49 white-space: normal;
50 50
51 51 &.td-radio,
52 52 &.td-checkbox {
53 53 padding-right: 0;
54 54 text-align: center;
55 55
56 56 input {
57 57 margin: 0 1em;
58 58 }
59 59 }
60 60
61 61 &.truncate-wrap {
62 62 white-space: nowrap !important;
63 63 }
64 64
65 65 pre {
66 66 margin: 0;
67 67 }
68 68
69 69 .show_more {
70 70 height: inherit;
71 71 }
72 72 }
73 73
74 74 .expired td {
75 75 background-color: @grey7;
76 76 }
77 77 .inactive td {
78 78 background-color: @grey6;
79 79 }
80 80 th {
81 81 text-align: left;
82 82 font-weight: @text-semibold-weight;
83 83 font-family: @text-semibold;
84 84 }
85 85
86 86 .hl {
87 87 td {
88 88 background-color: lighten(@alert4,25%);
89 89 }
90 90 }
91 91
92 92 // Special Data Cell Types
93 93 // See style guide for desciptions and examples.
94 94
95 95 td {
96 96
97 97 &.user {
98 98 padding-left: 1em;
99 99 }
100 100
101 101 &.td-rss {
102 102 width: 20px;
103 103 min-width: 0;
104 104 margin: 0;
105 105 }
106 106
107 107 &.quick_repo_menu {
108 108 width: 15px;
109 109 text-align: center;
110 110
111 111 &:hover {
112 112 background-color: @grey5;
113 113 }
114 114 }
115 115
116 116 &.td-icon {
117 117 min-width: 20px;
118 118 width: 20px;
119 119 }
120 120
121 121 &.td-hash {
122 122 min-width: 80px;
123 123 width: 200px;
124 124
125 125 .obsolete {
126 126 text-decoration: line-through;
127 127 color: lighten(@grey2,25%);
128 128 }
129 129 }
130 130
131 131 &.td-sha {
132 132 white-space: nowrap;
133 133 }
134 134
135 135 &.td-graphbox {
136 136 width: 100px;
137 137 max-width: 100px;
138 138 min-width: 100px;
139 139 }
140 140
141 141 &.td-time {
142 142 width: 160px;
143 143 white-space: nowrap;
144 144 }
145 145
146 146 &.annotate{
147 147 padding-right: 0;
148 148
149 149 div.annotatediv{
150 150 margin: 0 0.7em;
151 151 }
152 152 }
153 153
154 154 &.tags-col {
155 155 padding-right: 0;
156 156 }
157 157
158 158 &.td-description {
159 159 min-width: 350px;
160 160
161 161 &.truncate, .truncate-wrap {
162 162 white-space: nowrap;
163 163 overflow: hidden;
164 164 text-overflow: ellipsis;
165 165 max-width: 350px;
166 166 }
167 167 }
168 168
169 169 &.td-grid-name {
170 170 white-space: nowrap;
171 171 min-width: 300px;
172 172 }
173 173
174 174 &.td-componentname {
175 175 white-space: nowrap;
176 176 }
177 177
178 178 &.td-name {
179 179
180 180 }
181 181
182 182 &.td-journalaction {
183 183 min-width: 300px;
184 184
185 185 .journal_action_params {
186 186 // waiting for feedback
187 187 }
188 188 }
189 189
190 190 &.td-active {
191 191 padding-left: .65em;
192 192 }
193 193
194 194 &.td-issue-tracker-name {
195 195 width: 180px;
196 196 input {
197 197 width: 180px;
198 198 }
199 199
200 200 }
201 201
202 202 &.td-issue-tracker-regex {
203 203 white-space: nowrap;
204 204
205 205 min-width: 300px;
206 206 input {
207 207 min-width: 300px;
208 208 }
209 209
210 210 }
211 211
212 212 &.td-url {
213 213 white-space: nowrap;
214 214 }
215 215
216 216 &.td-comments {
217 217 min-width: 3em;
218 218 }
219 219
220 220 &.td-buttons {
221 221 padding: .3em 0;
222 222 }
223 223 &.td-align-top {
224 224 vertical-align: text-top
225 225 }
226 226 &.td-action {
227 227 // this is for the remove/delete/edit buttons
228 228 padding-right: 0;
229 229 min-width: 95px;
230 230 text-transform: capitalize;
231 231
232 232 i {
233 233 display: none;
234 234 }
235 235 }
236 236
237 237 // TODO: lisa: this needs to be cleaned up with the buttons
238 238 .grid_edit,
239 239 .grid_delete {
240 240 display: inline-block;
241 241 margin: 0 @padding/3 0 0;
242 242 font-family: @text-light;
243 243
244 244 i {
245 245 display: none;
246 246 }
247 247 }
248 248
249 249 .grid_edit + .grid_delete {
250 250 border-left: @border-thickness solid @grey5;
251 251 padding-left: @padding/2;
252 252 }
253 253
254 254 &.td-compare {
255 255
256 256 input {
257 257 margin-right: 1em;
258 258 }
259 259
260 260 .compare-radio-button {
261 261 margin: 0 1em 0 0;
262 262 }
263 263
264 264
265 265 }
266 266
267 267 &.td-tags {
268 268 padding: .5em 1em .5em 0;
269 269 width: 140px;
270 270
271 271 .tag {
272 272 margin: 1px;
273 273 float: left;
274 274 }
275 275 }
276 276
277 277 .icon-svn, .icon-hg, .icon-git {
278 278 font-size: 1.4em;
279 279 }
280 280
281 281 &.collapse_commit,
282 282 &.expand_commit {
283 283 padding-right: 0;
284 284 padding-left: 1em;
285 285 cursor: pointer;
286 286 width: 20px;
287 287 }
288 288 }
289 289
290 290 .perm_admin_row {
291 291 color: @grey4;
292 292 background-color: @grey6;
293 293 }
294 294
295 295 .noborder {
296 296 border: none;
297 297
298 298 td {
299 299 border: none;
300 300 }
301 301 }
302 302 }
303 303 .rctable.audit-log {
304 304 td {
305 305 vertical-align: top;
306 306 }
307 307 }
308 308
309 309 // TRUNCATING
310 310 // TODO: lisaq: should this possibly be moved out of tables.less?
311 311 // for truncated text
312 312 // used inside of table cells and in code block headers
313 313 .truncate-wrap {
314 314 white-space: nowrap !important;
315 315
316 316 //truncated text
317 317 .truncate {
318 318 max-width: 450px;
319 319 width: 300px;
320 320 overflow: hidden;
321 321 text-overflow: ellipsis;
322 322 -o-text-overflow: ellipsis;
323 323 -ms-text-overflow: ellipsis;
324 324
325 325 &.autoexpand {
326 326 width: 120px;
327 327 margin-right: 200px;
328 328 }
329 329 }
330 330 &:hover .truncate.autoexpand {
331 331 overflow: visible;
332 332 }
333 333
334 334 .tags-truncate {
335 335 width: 150px;
336 336 height: 22px;
337 337 overflow: hidden;
338 338
339 339 .tag {
340 340 display: inline-block;
341 341 }
342 342
343 343 &.truncate {
344 344 height: 22px;
345 345 max-height:2em;
346 346 width: 140px;
347 347 }
348 348 }
349 349 }
350 350
351 351 .apikeys_wrap {
352 352 margin-bottom: @padding;
353 353
354 354 table.rctable td:first-child {
355 width: 340px;
355 width: 120px;
356 356 }
357 357 }
358 358
359 359
360 360
361 361 // SPECIAL CASES
362 362
363 363 // Repository Followers
364 364 table.rctable.followers_data {
365 365 width: 75%;
366 366 margin: 0;
367 367 }
368 368
369 369 // Repository List
370 370 // Group Members List
371 371 table.rctable.group_members,
372 372 table#repo_list_table {
373 373 min-width: 600px;
374 374 }
375 375
376 376 #no_grid_data {
377 377 text-align: center;
378 378 }
379 379
380 380 #grid_data_loading {
381 381 text-align: center;
382 382 font-weight: 600;
383 383 font-size: 16px;
384 384 padding: 80px 20px;
385 385 }
386 386
387 387 // Keyboard mappings
388 388 table.keyboard-mappings {
389 389 th {
390 390 text-align: left;
391 391 font-weight: @text-semibold-weight;
392 392 font-family: @text-semibold;
393 393 }
394 394 }
395 395
396 396 // Branches, Tags, and Bookmarks
397 397 #obj_list_table.dataTable {
398 398 td.td-time {
399 399 padding-right: 1em;
400 400 }
401 401 }
402 402
403 403 // User Admin
404 404 .rctable.useremails,
405 405 .rctable.account_emails {
406 406 .tag,
407 407 .btn {
408 408 float: right;
409 409 }
410 410 .btn { //to line up with tags
411 411 margin-right: 1.65em;
412 412 }
413 413 }
414 414
415 415 // User List
416 416 #user_list_table {
417 417
418 418 td.td-user {
419 419 min-width: 100px;
420 420 }
421 421 }
422 422
423 423 // Pull Request List Table
424 424 #pull_request_list_table.dataTable {
425 425
426 426 //TODO: lisa: This needs to be removed once the description is adjusted
427 427 // for using an expand_commit button (see issue 765)
428 428 td {
429 429 vertical-align: middle;
430 430 }
431 431 }
432 432
433 433 // Settings (no border)
434 434 table.rctable.dl-settings {
435 435 td {
436 436 border: none;
437 437 vertical-align: baseline;
438 438 }
439 439 }
440 440
441 441
442 442 // Statistics
443 443 table.trending_language_tbl {
444 444 width: 100%;
445 445 line-height: 1em;
446 446
447 447 td div {
448 448 overflow: visible;
449 449 }
450 450 }
451 451
452 452 .trending_language_tbl, .trending_language_tbl td {
453 453 border: 0;
454 454 margin: 0;
455 455 padding: 0;
456 456 background: transparent;
457 457 }
458 458
459 459 .trending_language_tbl, .trending_language_tbl tr {
460 460 border-spacing: 0 3px;
461 461 }
462 462
463 463 .trending_language {
464 464 position: relative;
465 465 overflow: hidden;
466 466 color: @text-color;
467 467 width: 400px;
468 468
469 469 .lang-bar {
470 470 z-index: 1;
471 471 overflow: hidden;
472 472 background-color: @rcblue;
473 473 color: #FFF;
474 474 text-decoration: none;
475 475 }
476 476
477 477 }
478 478
479 479 // Changesets
480 480 #changesets.rctable {
481 481 th {
482 482 padding: 0 1em 0.65em 0;
483 483 }
484 484
485 485 // td must be fixed height for graph
486 486 td {
487 487 height: 32px;
488 488 padding: 0 1em 0 0;
489 489 vertical-align: middle;
490 490 white-space: nowrap;
491 491
492 492 &.td-description {
493 493 white-space: normal;
494 494 }
495 495
496 496 &.expand_commit {
497 497 padding-right: 0;
498 498 cursor: pointer;
499 499 width: 20px;
500 500 }
501 501 }
502 502 }
503 503
504 504 // Compare
505 505 table.compare_view_commits {
506 506 margin-top: @space;
507 507
508 508 td.td-time {
509 509 padding-left: .5em;
510 510 }
511 511
512 512 // special case to not show hover actions on hidden indicator
513 513 tr.compare_select_hidden:hover {
514 514 cursor: inherit;
515 515
516 516 td {
517 517 background-color: inherit;
518 518 }
519 519 }
520 520
521 521 tr:hover {
522 522 cursor: pointer;
523 523
524 524 td {
525 525 background-color: lighten(@alert4,25%);
526 526 }
527 527 }
528 528
529 529
530 530 }
531 531
532 532 .file_history {
533 533 td.td-actions {
534 534 text-align: right;
535 535 }
536 536 }
537 537
538 538
539 539 // Gist List
540 540 #gist_list_table {
541 541 td {
542 542 vertical-align: middle;
543 543
544 544 div{
545 545 display: inline-block;
546 546 vertical-align: middle;
547 547 }
548 548
549 549 img{
550 550 vertical-align: middle;
551 551 }
552 552
553 553 &.td-expire {
554 554 width: 200px;
555 555 }
556 556 &.td-gist-type {
557 557 width: 100px;
558 558 }
559 559 }
560 560 }
@@ -1,395 +1,397 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('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
34 34 pyroutes.register('hovercard_username', '/_hovercard/username/%(username)s', ['username']);
35 35 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
36 36 pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']);
37 37 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
38 38 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
39 39 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
40 40 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
41 41 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
42 42 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
43 43 pyroutes.register('admin_home', '/_admin', []);
44 44 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
45 45 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
46 46 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
47 47 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
48 48 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
49 49 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
50 50 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
51 51 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
52 52 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
53 53 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
54 54 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
55 55 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
56 56 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
57 57 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
58 58 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
59 59 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
60 60 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
61 61 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
62 62 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
63 63 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
64 64 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
65 65 pyroutes.register('admin_settings', '/_admin/settings', []);
66 66 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
67 67 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
68 68 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
69 69 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
70 70 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
71 71 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
72 72 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
73 73 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
74 74 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
75 75 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
76 76 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
77 77 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
78 78 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
79 79 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
80 80 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
81 81 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
82 82 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
83 83 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
84 84 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
85 85 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
86 86 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
87 87 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
88 88 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
89 89 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
90 90 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
91 91 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
92 92 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
93 93 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
94 94 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
95 95 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
96 96 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
97 97 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
98 98 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
99 99 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
100 100 pyroutes.register('users', '/_admin/users', []);
101 101 pyroutes.register('users_data', '/_admin/users_data', []);
102 102 pyroutes.register('users_create', '/_admin/users/create', []);
103 103 pyroutes.register('users_new', '/_admin/users/new', []);
104 104 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
105 105 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
106 106 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
107 107 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
108 108 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
109 109 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
110 110 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
111 111 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
112 112 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
113 113 pyroutes.register('user_notice_dismiss', '/_admin/users/%(user_id)s/notice_dismiss', ['user_id']);
114 pyroutes.register('edit_user_auth_tokens_view', '/_admin/users/%(user_id)s/edit/auth_tokens/view', ['user_id']);
114 115 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
115 116 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
116 117 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
117 118 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
118 119 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
119 120 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
120 121 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
121 122 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
122 123 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
123 124 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
124 125 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
125 126 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
126 127 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
127 128 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
128 129 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
129 130 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
130 131 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
131 132 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
132 133 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
133 134 pyroutes.register('user_groups', '/_admin/user_groups', []);
134 135 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
135 136 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
136 137 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
137 138 pyroutes.register('repos', '/_admin/repos', []);
138 139 pyroutes.register('repos_data', '/_admin/repos_data', []);
139 140 pyroutes.register('repo_new', '/_admin/repos/new', []);
140 141 pyroutes.register('repo_create', '/_admin/repos/create', []);
141 142 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
142 143 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
143 144 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
144 145 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
145 146 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
146 147 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
147 148 pyroutes.register('channelstream_proxy', '/_channelstream', []);
148 149 pyroutes.register('upload_file', '/_file_store/upload', []);
149 150 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
150 151 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
151 152 pyroutes.register('logout', '/_admin/logout', []);
152 153 pyroutes.register('reset_password', '/_admin/password_reset', []);
153 154 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
154 155 pyroutes.register('home', '/', []);
155 156 pyroutes.register('main_page_repos_data', '/_home_repos', []);
156 157 pyroutes.register('main_page_repo_groups_data', '/_home_repo_groups', []);
157 158 pyroutes.register('user_autocomplete_data', '/_users', []);
158 159 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
159 160 pyroutes.register('repo_list_data', '/_repos', []);
160 161 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
161 162 pyroutes.register('goto_switcher_data', '/_goto_data', []);
162 163 pyroutes.register('markup_preview', '/_markup_preview', []);
163 164 pyroutes.register('file_preview', '/_file_preview', []);
164 165 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
165 166 pyroutes.register('journal', '/_admin/journal', []);
166 167 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
167 168 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
168 169 pyroutes.register('journal_public', '/_admin/public_journal', []);
169 170 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
170 171 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
171 172 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
172 173 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
173 174 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
174 175 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
175 176 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
176 177 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
177 178 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
178 179 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
179 180 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
180 181 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
181 182 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
182 183 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
183 184 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
184 185 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
185 186 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
186 187 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
187 188 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
188 189 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
189 190 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
190 191 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
191 192 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
192 193 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
193 194 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
194 195 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
195 196 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
196 197 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 198 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 199 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 200 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
200 201 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
201 202 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
202 203 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
203 204 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
204 205 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
205 206 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
206 207 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
207 208 pyroutes.register('repo_files_check_head', '/%(repo_name)s/check_head/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
208 209 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
209 210 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
210 211 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
211 212 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
212 213 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
213 214 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
214 215 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
215 216 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
216 217 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
217 218 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
218 219 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
219 220 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
220 221 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
221 222 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
222 223 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
223 224 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
224 225 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
225 226 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']);
226 227 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
227 228 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
228 229 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
229 230 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
230 231 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
231 232 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
232 233 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
233 234 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
234 235 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
235 236 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
236 237 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
237 238 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
238 239 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
239 240 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
240 241 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
241 242 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
242 243 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
243 244 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
244 245 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']);
245 246 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
246 247 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
247 248 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
248 249 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
249 250 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
250 251 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
251 252 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
252 253 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
253 254 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
254 255 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
255 256 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
256 257 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
257 258 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
258 259 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
259 260 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
260 261 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
261 262 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
262 263 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
263 264 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
264 265 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
265 266 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
266 267 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
267 268 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
268 269 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
269 270 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
270 271 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
271 272 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
272 273 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
273 274 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
274 275 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
275 276 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
276 277 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
277 278 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
278 279 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
279 280 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
280 281 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
281 282 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
282 283 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
283 284 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
284 285 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
285 286 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
286 287 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
287 288 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
288 289 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
289 290 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
290 291 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
291 292 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
292 293 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
293 294 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
294 295 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
295 296 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
296 297 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
297 298 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
298 299 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
299 300 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
300 301 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
301 302 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
302 303 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
303 304 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
304 305 pyroutes.register('search', '/_admin/search', []);
305 306 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
306 307 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
307 308 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
308 309 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
309 310 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
310 311 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
311 312 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
312 313 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
313 314 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
314 315 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
316 pyroutes.register('my_account_auth_tokens_view', '/_admin/my_account/auth_tokens/view', []);
315 317 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
316 318 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
317 319 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
318 320 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
319 321 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
320 322 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
321 323 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
322 324 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
323 325 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
324 326 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
325 327 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
326 328 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
327 329 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
328 330 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
329 331 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
330 332 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
331 333 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
332 334 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
333 335 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
334 336 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
335 337 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
336 338 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
337 339 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
338 340 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
339 341 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
340 342 pyroutes.register('gists_show', '/_admin/gists', []);
341 343 pyroutes.register('gists_new', '/_admin/gists/new', []);
342 344 pyroutes.register('gists_create', '/_admin/gists/create', []);
343 345 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
344 346 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
345 347 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
346 348 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
347 349 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
348 350 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
349 351 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
350 352 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
351 353 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
352 354 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
353 355 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
354 356 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
355 357 pyroutes.register('apiv2', '/_admin/api', []);
356 358 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
357 359 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
358 360 pyroutes.register('login', '/_admin/login', []);
359 361 pyroutes.register('register', '/_admin/register', []);
360 362 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
361 363 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
362 364 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
363 365 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
364 366 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
365 367 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
366 368 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
367 369 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
368 370 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
369 371 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
370 372 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
371 373 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
372 374 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
373 375 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
374 376 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
375 377 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
376 378 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
377 379 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
378 380 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
379 381 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
380 382 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
381 383 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
382 384 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
383 385 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
384 386 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
385 387 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
386 388 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
387 389 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
388 390 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
389 391 pyroutes.register('repo_artifacts_update', '/%(repo_name)s/artifacts/update/%(uid)s', ['repo_name', 'uid']);
390 392 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
391 393 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
392 394 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
393 395 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
394 396 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
395 397 }
@@ -1,49 +1,118 b''
1 1 // # Copyright (C) 2010-2020 RhodeCode GmbH
2 2 // #
3 3 // # This program is free software: you can redistribute it and/or modify
4 4 // # it under the terms of the GNU Affero General Public License, version 3
5 5 // # (only), as published by the Free Software Foundation.
6 6 // #
7 7 // # This program is distributed in the hope that it will be useful,
8 8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 // # GNU General Public License for more details.
11 11 // #
12 12 // # You should have received a copy of the GNU Affero General Public License
13 13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 // #
15 15 // # This program is dual-licensed. If you wish to learn more about the
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 var generatePassword = function(length) {
20 20 if (length === undefined){
21 21 length = 8
22 22 }
23 23
24 24 var charset = "abcdefghijklnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
25 25 var gen_pass = "";
26 26
27 27 for (var i = 0, n = charset.length; i < length; ++i) {
28 28 gen_pass += charset.charAt(Math.floor(Math.random() * n));
29 29 }
30 30 return gen_pass;
31 31 };
32 32
33 33 /**
34 34 * User autocomplete
35 35 */
36 36 var UsersAutoComplete = function(input_id, user_id) {
37 37 $('#'+input_id).autocomplete({
38 38 serviceUrl: pyroutes.url('user_autocomplete_data'),
39 39 minChars:2,
40 40 maxHeight:400,
41 41 deferRequestBy: 300, //miliseconds
42 42 showNoSuggestionNotice: true,
43 43 tabDisabled: true,
44 44 autoSelectFirst: true,
45 45 params: { user_id: user_id },
46 46 formatResult: autocompleteFormatResult,
47 47 lookupFilter: autocompleteFilterResult
48 48 });
49 49 };
50
51 var _showAuthToken = function (authTokenId, showUrl) {
52
53 Swal.fire({
54 title: _gettext('Show this authentication token?'),
55 showCancelButton: true,
56 confirmButtonColor: '#84a5d2',
57 cancelButtonColor: '#e85e4d',
58 showClass: {
59 popup: 'swal2-noanimation',
60 backdrop: 'swal2-noanimation'
61 },
62 hideClass: {
63 popup: '',
64 backdrop: ''
65 },
66 confirmButtonText: _gettext('Show'),
67 showLoaderOnConfirm: true,
68 allowOutsideClick: function () {
69 !Swal.isLoading()
70 },
71 preConfirm: function () {
72
73 var postData = {
74 'auth_token_id': authTokenId,
75 'csrf_token': CSRF_TOKEN
76 };
77 return new Promise(function (resolve, reject) {
78 $.ajax({
79 type: 'POST',
80 data: postData,
81 url: showUrl,
82 headers: {'X-PARTIAL-XHR': true}
83 })
84 .done(function (data) {
85 resolve(data);
86 })
87 .fail(function (jqXHR, textStatus, errorThrown) {
88 //reject("Failed to fetch Authentication Token")
89 var message = formatErrorMessage(jqXHR, textStatus, errorThrown)
90 Swal.showValidationMessage('Request failed: {0}'.format(message)
91 )
92 });
93 })
94 }
95
96 })
97 .then(function (result) {
98 if (result.value) {
99 var tmpl = ('<code>{0}</code>' +
100 '<i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="{1}" title="Copy Token"></i>');
101
102 Swal.fire({
103 title: _gettext('Authentication Token'),
104 html: tmpl.format(result.value.auth_token, result.value.auth_token),
105 confirmButtonColor: '#84a5d2',
106 cancelButtonColor: '#e85e4d',
107 showClass: {
108 popup: 'swal2-noanimation',
109 backdrop: 'swal2-noanimation'
110 },
111 hideClass: {
112 popup: '',
113 backdrop: ''
114 },
115 })
116 }
117 })
118 } No newline at end of file
@@ -1,191 +1,199 b''
1 1 <div class="panel panel-default">
2 <script>
3 var showAuthToken = function(authTokenId) {
4 return _showAuthToken(authTokenId, pyroutes.url('my_account_auth_tokens_view'))
5 }
6 </script>
7
2 8 <div class="panel-heading">
3 9 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
4 10 </div>
5 11 <div class="panel-body">
6 12 <div class="apikeys_wrap">
7 13 <p>
8 14 ${_('Authentication tokens can be used to interact with the API, or VCS-over-http. '
9 15 'Each token can have a role. Token with a role can be used only in given context, '
10 16 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
11 17 </p>
12 18 <table class="rctable auth_tokens">
13 19 <tr>
14 20 <th>${_('Token')}</th>
15 21 <th>${_('Description')}</th>
16 22 <th>${_('Role')}</th>
17 23 <th>${_('Repository Scope')}</th>
18 24 <th>${_('Expiration')}</th>
19 25 <th>${_('Action')}</th>
20 26 </tr>
21 27 %if c.user_auth_tokens:
22 28 %for auth_token in c.user_auth_tokens:
23 29 <tr class="${('expired' if auth_token.expired else '')}">
24 <td class="truncate-wrap td-authtoken">
25 <div class="user_auth_tokens truncate autoexpand">
26 <code>${auth_token.api_key}</code>
30 <td class="td-authtoken">
31 <div class="user_auth_tokens">
32 <code class="cursor-pointer" onclick="showAuthToken(${auth_token.user_api_key_id})">
33 ${auth_token.token_obfuscated}
34 </code>
27 35 </div>
28 36 </td>
29 37 <td class="td-wrap">${auth_token.description}</td>
30 38 <td class="td-tags">
31 39 <span class="tag disabled">${auth_token.role_humanized}</span>
32 40 </td>
33 41 <td class="td">${auth_token.scope_humanized}</td>
34 42 <td class="td-exp">
35 43 %if auth_token.expires == -1:
36 44 ${_('never')}
37 45 %else:
38 46 %if auth_token.expired:
39 47 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
40 48 %else:
41 49 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
42 50 %endif
43 51 %endif
44 52 </td>
45 53 <td class="td-action">
46 54 ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), request=request)}
47 ${h.hidden('del_auth_token', auth_token.user_api_key_id)}
55 <input name="del_auth_token" type="hidden" value="${auth_token.user_api_key_id}">
48 56 <button class="btn btn-link btn-danger" type="submit"
49 57 onclick="submitConfirm(event, this, _gettext('Confirm to delete this auth token'), _gettext('Delete'), '${auth_token.token_obfuscated}')"
50 58 >
51 59 ${_('Delete')}
52 60 </button>
53 61 ${h.end_form()}
54 62 </td>
55 63 </tr>
56 64 %endfor
57 65 %else:
58 66 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
59 67 %endif
60 68 </table>
61 69 </div>
62 70
63 71 <div class="user_auth_tokens">
64 72 ${h.secure_form(h.route_path('my_account_auth_tokens_add'), request=request)}
65 73 <div class="form form-vertical">
66 74 <!-- fields -->
67 75 <div class="fields">
68 76 <div class="field">
69 77 <div class="label">
70 78 <label for="new_email">${_('New authentication token')}:</label>
71 79 </div>
72 80 <div class="input">
73 81 ${h.text('description', class_='medium', placeholder=_('Description'))}
74 82 ${h.hidden('lifetime')}
75 83 ${h.select('role', request.GET.get('token_role', ''), c.role_options)}
76 84
77 85 % if c.allow_scoped_tokens:
78 86 ${h.hidden('scope_repo_id')}
79 87 % else:
80 88 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
81 89 % endif
82 90 </div>
83 91 <p class="help-block">
84 92 ${_('Repository scope works only with tokens with VCS type.')}
85 93 </p>
86 94 </div>
87 95 <div class="buttons">
88 96 ${h.submit('save',_('Add'),class_="btn")}
89 97 ${h.reset('reset',_('Reset'),class_="btn")}
90 98 </div>
91 99 </div>
92 100 </div>
93 101 ${h.end_form()}
94 102 </div>
95 103 </div>
96 104 </div>
97 105
98 106 <script>
99 107 $(document).ready(function(){
100 108
101 109 var select2Options = {
102 110 'containerCssClass': "drop-menu",
103 111 'dropdownCssClass': "drop-menu-dropdown",
104 112 'dropdownAutoWidth': true
105 113 };
106 114 $("#role").select2(select2Options);
107 115
108 116 var preloadData = {
109 117 results: [
110 118 % for entry in c.lifetime_values:
111 119 {id:${entry[0]}, text:"${entry[1]}"}${'' if loop.last else ','}
112 120 % endfor
113 121 ]
114 122 };
115 123
116 124 $("#lifetime").select2({
117 125 containerCssClass: "drop-menu",
118 126 dropdownCssClass: "drop-menu-dropdown",
119 127 dropdownAutoWidth: true,
120 128 data: preloadData,
121 129 placeholder: "${_('Select or enter expiration date')}",
122 130 query: function(query) {
123 131 feedLifetimeOptions(query, preloadData);
124 132 }
125 133 });
126 134
127 135
128 136 var repoFilter = function(data) {
129 137 var results = [];
130 138
131 139 if (!data.results[0]) {
132 140 return data
133 141 }
134 142
135 143 $.each(data.results[0].children, function() {
136 144 // replace name to ID for submision
137 145 this.id = this.repo_id;
138 146 results.push(this);
139 147 });
140 148
141 149 data.results[0].children = results;
142 150 return data;
143 151 };
144 152
145 153 $("#scope_repo_id_disabled").select2(select2Options);
146 154
147 155 var selectVcsScope = function() {
148 156 // select vcs scope and disable input
149 157 $("#role").select2("val", "${c.role_vcs}").trigger('change');
150 158 $("#role").select2("readonly", true)
151 159 };
152 160
153 161 $("#scope_repo_id").select2({
154 162 cachedDataSource: {},
155 163 minimumInputLength: 2,
156 164 placeholder: "${_('repository scope')}",
157 165 dropdownAutoWidth: true,
158 166 containerCssClass: "drop-menu",
159 167 dropdownCssClass: "drop-menu-dropdown",
160 168 formatResult: formatRepoResult,
161 169 query: $.debounce(250, function(query){
162 170 self = this;
163 171 var cacheKey = query.term;
164 172 var cachedData = self.cachedDataSource[cacheKey];
165 173
166 174 if (cachedData) {
167 175 query.callback({results: cachedData.results});
168 176 } else {
169 177 $.ajax({
170 178 url: pyroutes.url('repo_list_data'),
171 179 data: {'query': query.term},
172 180 dataType: 'json',
173 181 type: 'GET',
174 182 success: function(data) {
175 183 data = repoFilter(data);
176 184 self.cachedDataSource[cacheKey] = data;
177 185 query.callback({results: data.results});
178 186 },
179 187 error: function(data, textStatus, errorThrown) {
180 188 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
181 189 }
182 190 })
183 191 }
184 192 })
185 193 });
186 194 $("#scope_repo_id").on('select2-selecting', function(e){
187 195 selectVcsScope()
188 196 });
189 197
190 198 });
191 199 </script>
@@ -1,196 +1,204 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default">
4 <script>
5 var showAuthToken = function(authTokenId) {
6 return _showAuthToken(authTokenId, pyroutes.url('edit_user_auth_tokens_view', {'user_id': '${c.user.user_id}'}))
7 }
8 </script>
9
4 10 <div class="panel-heading">
5 11 <h3 class="panel-title">
6 12 ${base.gravatar_with_user(c.user.username, 16, tooltip=False, _class='pull-left')}
7 13 &nbsp;- ${_('Authentication Tokens')}
8 14 </h3>
9 15 </div>
10 16 <div class="panel-body">
11 17 <div class="apikeys_wrap">
12 18 <p>
13 19 ${_('Authentication tokens can be used to interact with the API, or VCS-over-http. '
14 20 'Each token can have a role. Token with a role can be used only in given context, '
15 21 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
16 22 </p>
17 23 <table class="rctable auth_tokens">
18 24 <tr>
19 25 <th>${_('Token')}</th>
20 26 <th>${_('Description')}</th>
21 27 <th>${_('Role')}</th>
22 28 <th>${_('Repository Scope')}</th>
23 29 <th>${_('Expiration')}</th>
24 30 <th>${_('Action')}</th>
25 31 </tr>
26 32 %if c.user_auth_tokens:
27 33 %for auth_token in c.user_auth_tokens:
28 34 <tr class="${('expired' if auth_token.expired else '')}">
29 <td class="truncate-wrap td-authtoken">
30 <div class="user_auth_tokens truncate autoexpand">
31 <code>${auth_token.api_key}</code>
35 <td class="td-authtoken">
36 <div class="user_auth_tokens">
37 <code class="cursor-pointer" onclick="showAuthToken(${auth_token.user_api_key_id})">
38 ${auth_token.token_obfuscated}
39 </code>
32 40 </div>
33 41 </td>
34 42 <td class="td-wrap">${auth_token.description}</td>
35 43 <td class="td-tags">
36 44 <span class="tag disabled">${auth_token.role_humanized}</span>
37 45 </td>
38 46 <td class="td">${auth_token.scope_humanized}</td>
39 47 <td class="td-exp">
40 48 %if auth_token.expires == -1:
41 49 ${_('never')}
42 50 %else:
43 51 %if auth_token.expired:
44 52 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
45 53 %else:
46 54 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
47 55 %endif
48 56 %endif
49 57 </td>
50 58 <td class="td-action">
51 59 ${h.secure_form(h.route_path('edit_user_auth_tokens_delete', user_id=c.user.user_id), request=request)}
52 ${h.hidden('del_auth_token', auth_token.user_api_key_id)}
60 <input name="del_auth_token" type="hidden" value="${auth_token.user_api_key_id}">
53 61 <button class="btn btn-link btn-danger" type="submit"
54 62 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
55 63 ${_('Delete')}
56 64 </button>
57 65 ${h.end_form()}
58 66 </td>
59 67 </tr>
60 68 %endfor
61 69 %else:
62 70 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
63 71 %endif
64 72 </table>
65 73 </div>
66 74
67 75 <div class="user_auth_tokens">
68 76 ${h.secure_form(h.route_path('edit_user_auth_tokens_add', user_id=c.user.user_id), request=request)}
69 77 <div class="form form-vertical">
70 78 <!-- fields -->
71 79 <div class="fields">
72 80 <div class="field">
73 81 <div class="label">
74 82 <label for="new_email">${_('New authentication token')}:</label>
75 83 </div>
76 84 <div class="input">
77 85 ${h.text('description', class_='medium', placeholder=_('Description'))}
78 86 ${h.hidden('lifetime')}
79 87 ${h.select('role', '', c.role_options)}
80 88
81 89 % if c.allow_scoped_tokens:
82 90 ${h.hidden('scope_repo_id')}
83 91 % else:
84 92 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
85 93 % endif
86 94 </div>
87 95 <p class="help-block">
88 96 ${_('Repository scope works only with tokens with VCS type.')}
89 97 </p>
90 98 </div>
91 99 <div class="buttons">
92 100 ${h.submit('save',_('Add'),class_="btn")}
93 101 ${h.reset('reset',_('Reset'),class_="btn")}
94 102 </div>
95 103 </div>
96 104 </div>
97 105 ${h.end_form()}
98 106 </div>
99 107 </div>
100 108 </div>
101 109
102 110 <script>
103 111
104 112 $(document).ready(function(){
105 113
106 114 var select2Options = {
107 115 'containerCssClass': "drop-menu",
108 116 'dropdownCssClass': "drop-menu-dropdown",
109 117 'dropdownAutoWidth': true
110 118 };
111 119 $("#role").select2(select2Options);
112 120
113 121 var preloadData = {
114 122 results: [
115 123 % for entry in c.lifetime_values:
116 124 {id:${entry[0]}, text:"${entry[1]}"}${'' if loop.last else ','}
117 125 % endfor
118 126 ]
119 127 };
120 128
121 129 $("#lifetime").select2({
122 130 containerCssClass: "drop-menu",
123 131 dropdownCssClass: "drop-menu-dropdown",
124 132 dropdownAutoWidth: true,
125 133 data: preloadData,
126 134 placeholder: "${_('Select or enter expiration date')}",
127 135 query: function(query) {
128 136 feedLifetimeOptions(query, preloadData);
129 137 }
130 138 });
131 139
132 140
133 141 var repoFilter = function(data) {
134 142 var results = [];
135 143
136 144 if (!data.results[0]) {
137 145 return data
138 146 }
139 147
140 148 $.each(data.results[0].children, function() {
141 149 // replace name to ID for submision
142 150 this.id = this.repo_id;
143 151 results.push(this);
144 152 });
145 153
146 154 data.results[0].children = results;
147 155 return data;
148 156 };
149 157
150 158 $("#scope_repo_id_disabled").select2(select2Options);
151 159
152 160 var selectVcsScope = function() {
153 161 // select vcs scope and disable input
154 162 $("#role").select2("val", "${c.role_vcs}").trigger('change');
155 163 $("#role").select2("readonly", true)
156 164 };
157 165
158 166 $("#scope_repo_id").select2({
159 167 cachedDataSource: {},
160 168 minimumInputLength: 2,
161 169 placeholder: "${_('repository scope')}",
162 170 dropdownAutoWidth: true,
163 171 containerCssClass: "drop-menu",
164 172 dropdownCssClass: "drop-menu-dropdown",
165 173 formatResult: formatRepoResult,
166 174 query: $.debounce(250, function(query){
167 175 self = this;
168 176 var cacheKey = query.term;
169 177 var cachedData = self.cachedDataSource[cacheKey];
170 178
171 179 if (cachedData) {
172 180 query.callback({results: cachedData.results});
173 181 } else {
174 182 $.ajax({
175 183 url: pyroutes.url('repo_list_data'),
176 184 data: {'query': query.term},
177 185 dataType: 'json',
178 186 type: 'GET',
179 187 success: function(data) {
180 188 data = repoFilter(data);
181 189 self.cachedDataSource[cacheKey] = data;
182 190 query.callback({results: data.results});
183 191 },
184 192 error: function(data, textStatus, errorThrown) {
185 193 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
186 194 }
187 195 })
188 196 }
189 197 })
190 198 });
191 199 $("#scope_repo_id").on('select2-selecting', function(e){
192 200 selectVcsScope()
193 201 });
194 202
195 203 });
196 204 </script>
General Comments 0
You need to be logged in to leave comments. Login now