##// END OF EJS Templates
ssh-keys: expose last access time on admin summary page for SSH keys.
marcink -
r2134:5a024fcf default
parent child Browse files
Show More
@@ -1,481 +1,482 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 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 re
22 22 import logging
23 23 import formencode
24 24 import formencode.htmlfill
25 25 import datetime
26 26 from pyramid.interfaces import IRoutesMapper
27 27
28 28 from pyramid.view import view_config
29 29 from pyramid.httpexceptions import HTTPFound
30 30 from pyramid.renderers import render
31 31 from pyramid.response import Response
32 32
33 33 from rhodecode.apps._base import BaseAppView, DataGridAppView
34 34 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
35 35 from rhodecode.events import trigger
36 36
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib.auth import (
39 39 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
40 40 from rhodecode.lib.utils2 import aslist, safe_unicode
41 41 from rhodecode.model.db import (
42 42 or_, coalesce, User, UserIpMap, UserSshKeys)
43 43 from rhodecode.model.forms import (
44 44 ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm)
45 45 from rhodecode.model.meta import Session
46 46 from rhodecode.model.permission import PermissionModel
47 47 from rhodecode.model.settings import SettingsModel
48 48
49 49
50 50 log = logging.getLogger(__name__)
51 51
52 52
53 53 class AdminPermissionsView(BaseAppView, DataGridAppView):
54 54 def load_default_context(self):
55 55 c = self._get_local_tmpl_context()
56 56
57 57 self._register_global_c(c)
58 58 PermissionModel().set_global_permission_choices(
59 59 c, gettext_translator=self.request.translate)
60 60 return c
61 61
62 62 @LoginRequired()
63 63 @HasPermissionAllDecorator('hg.admin')
64 64 @view_config(
65 65 route_name='admin_permissions_application', request_method='GET',
66 66 renderer='rhodecode:templates/admin/permissions/permissions.mako')
67 67 def permissions_application(self):
68 68 c = self.load_default_context()
69 69 c.active = 'application'
70 70
71 71 c.user = User.get_default_user(refresh=True)
72 72
73 73 app_settings = SettingsModel().get_all_settings()
74 74 defaults = {
75 75 'anonymous': c.user.active,
76 76 'default_register_message': app_settings.get(
77 77 'rhodecode_register_message')
78 78 }
79 79 defaults.update(c.user.get_default_perms())
80 80
81 81 data = render('rhodecode:templates/admin/permissions/permissions.mako',
82 82 self._get_template_context(c), self.request)
83 83 html = formencode.htmlfill.render(
84 84 data,
85 85 defaults=defaults,
86 86 encoding="UTF-8",
87 87 force_defaults=False
88 88 )
89 89 return Response(html)
90 90
91 91 @LoginRequired()
92 92 @HasPermissionAllDecorator('hg.admin')
93 93 @CSRFRequired()
94 94 @view_config(
95 95 route_name='admin_permissions_application_update', request_method='POST',
96 96 renderer='rhodecode:templates/admin/permissions/permissions.mako')
97 97 def permissions_application_update(self):
98 98 _ = self.request.translate
99 99 c = self.load_default_context()
100 100 c.active = 'application'
101 101
102 102 _form = ApplicationPermissionsForm(
103 103 [x[0] for x in c.register_choices],
104 104 [x[0] for x in c.password_reset_choices],
105 105 [x[0] for x in c.extern_activate_choices])()
106 106
107 107 try:
108 108 form_result = _form.to_python(dict(self.request.POST))
109 109 form_result.update({'perm_user_name': User.DEFAULT_USER})
110 110 PermissionModel().update_application_permissions(form_result)
111 111
112 112 settings = [
113 113 ('register_message', 'default_register_message'),
114 114 ]
115 115 for setting, form_key in settings:
116 116 sett = SettingsModel().create_or_update_setting(
117 117 setting, form_result[form_key])
118 118 Session().add(sett)
119 119
120 120 Session().commit()
121 121 h.flash(_('Application permissions updated successfully'),
122 122 category='success')
123 123
124 124 except formencode.Invalid as errors:
125 125 defaults = errors.value
126 126
127 127 data = render(
128 128 'rhodecode:templates/admin/permissions/permissions.mako',
129 129 self._get_template_context(c), self.request)
130 130 html = formencode.htmlfill.render(
131 131 data,
132 132 defaults=defaults,
133 133 errors=errors.error_dict or {},
134 134 prefix_error=False,
135 135 encoding="UTF-8",
136 136 force_defaults=False
137 137 )
138 138 return Response(html)
139 139
140 140 except Exception:
141 141 log.exception("Exception during update of permissions")
142 142 h.flash(_('Error occurred during update of permissions'),
143 143 category='error')
144 144
145 145 raise HTTPFound(h.route_path('admin_permissions_application'))
146 146
147 147 @LoginRequired()
148 148 @HasPermissionAllDecorator('hg.admin')
149 149 @view_config(
150 150 route_name='admin_permissions_object', request_method='GET',
151 151 renderer='rhodecode:templates/admin/permissions/permissions.mako')
152 152 def permissions_objects(self):
153 153 c = self.load_default_context()
154 154 c.active = 'objects'
155 155
156 156 c.user = User.get_default_user(refresh=True)
157 157 defaults = {}
158 158 defaults.update(c.user.get_default_perms())
159 159
160 160 data = render(
161 161 'rhodecode:templates/admin/permissions/permissions.mako',
162 162 self._get_template_context(c), self.request)
163 163 html = formencode.htmlfill.render(
164 164 data,
165 165 defaults=defaults,
166 166 encoding="UTF-8",
167 167 force_defaults=False
168 168 )
169 169 return Response(html)
170 170
171 171 @LoginRequired()
172 172 @HasPermissionAllDecorator('hg.admin')
173 173 @CSRFRequired()
174 174 @view_config(
175 175 route_name='admin_permissions_object_update', request_method='POST',
176 176 renderer='rhodecode:templates/admin/permissions/permissions.mako')
177 177 def permissions_objects_update(self):
178 178 _ = self.request.translate
179 179 c = self.load_default_context()
180 180 c.active = 'objects'
181 181
182 182 _form = ObjectPermissionsForm(
183 183 [x[0] for x in c.repo_perms_choices],
184 184 [x[0] for x in c.group_perms_choices],
185 185 [x[0] for x in c.user_group_perms_choices])()
186 186
187 187 try:
188 188 form_result = _form.to_python(dict(self.request.POST))
189 189 form_result.update({'perm_user_name': User.DEFAULT_USER})
190 190 PermissionModel().update_object_permissions(form_result)
191 191
192 192 Session().commit()
193 193 h.flash(_('Object permissions updated successfully'),
194 194 category='success')
195 195
196 196 except formencode.Invalid as errors:
197 197 defaults = errors.value
198 198
199 199 data = render(
200 200 'rhodecode:templates/admin/permissions/permissions.mako',
201 201 self._get_template_context(c), self.request)
202 202 html = formencode.htmlfill.render(
203 203 data,
204 204 defaults=defaults,
205 205 errors=errors.error_dict or {},
206 206 prefix_error=False,
207 207 encoding="UTF-8",
208 208 force_defaults=False
209 209 )
210 210 return Response(html)
211 211 except Exception:
212 212 log.exception("Exception during update of permissions")
213 213 h.flash(_('Error occurred during update of permissions'),
214 214 category='error')
215 215
216 216 raise HTTPFound(h.route_path('admin_permissions_object'))
217 217
218 218 @LoginRequired()
219 219 @HasPermissionAllDecorator('hg.admin')
220 220 @view_config(
221 221 route_name='admin_permissions_global', request_method='GET',
222 222 renderer='rhodecode:templates/admin/permissions/permissions.mako')
223 223 def permissions_global(self):
224 224 c = self.load_default_context()
225 225 c.active = 'global'
226 226
227 227 c.user = User.get_default_user(refresh=True)
228 228 defaults = {}
229 229 defaults.update(c.user.get_default_perms())
230 230
231 231 data = render(
232 232 'rhodecode:templates/admin/permissions/permissions.mako',
233 233 self._get_template_context(c), self.request)
234 234 html = formencode.htmlfill.render(
235 235 data,
236 236 defaults=defaults,
237 237 encoding="UTF-8",
238 238 force_defaults=False
239 239 )
240 240 return Response(html)
241 241
242 242 @LoginRequired()
243 243 @HasPermissionAllDecorator('hg.admin')
244 244 @CSRFRequired()
245 245 @view_config(
246 246 route_name='admin_permissions_global_update', request_method='POST',
247 247 renderer='rhodecode:templates/admin/permissions/permissions.mako')
248 248 def permissions_global_update(self):
249 249 _ = self.request.translate
250 250 c = self.load_default_context()
251 251 c.active = 'global'
252 252
253 253 _form = UserPermissionsForm(
254 254 [x[0] for x in c.repo_create_choices],
255 255 [x[0] for x in c.repo_create_on_write_choices],
256 256 [x[0] for x in c.repo_group_create_choices],
257 257 [x[0] for x in c.user_group_create_choices],
258 258 [x[0] for x in c.fork_choices],
259 259 [x[0] for x in c.inherit_default_permission_choices])()
260 260
261 261 try:
262 262 form_result = _form.to_python(dict(self.request.POST))
263 263 form_result.update({'perm_user_name': User.DEFAULT_USER})
264 264 PermissionModel().update_user_permissions(form_result)
265 265
266 266 Session().commit()
267 267 h.flash(_('Global permissions updated successfully'),
268 268 category='success')
269 269
270 270 except formencode.Invalid as errors:
271 271 defaults = errors.value
272 272
273 273 data = render(
274 274 'rhodecode:templates/admin/permissions/permissions.mako',
275 275 self._get_template_context(c), self.request)
276 276 html = formencode.htmlfill.render(
277 277 data,
278 278 defaults=defaults,
279 279 errors=errors.error_dict or {},
280 280 prefix_error=False,
281 281 encoding="UTF-8",
282 282 force_defaults=False
283 283 )
284 284 return Response(html)
285 285 except Exception:
286 286 log.exception("Exception during update of permissions")
287 287 h.flash(_('Error occurred during update of permissions'),
288 288 category='error')
289 289
290 290 raise HTTPFound(h.route_path('admin_permissions_global'))
291 291
292 292 @LoginRequired()
293 293 @HasPermissionAllDecorator('hg.admin')
294 294 @view_config(
295 295 route_name='admin_permissions_ips', request_method='GET',
296 296 renderer='rhodecode:templates/admin/permissions/permissions.mako')
297 297 def permissions_ips(self):
298 298 c = self.load_default_context()
299 299 c.active = 'ips'
300 300
301 301 c.user = User.get_default_user(refresh=True)
302 302 c.user_ip_map = (
303 303 UserIpMap.query().filter(UserIpMap.user == c.user).all())
304 304
305 305 return self._get_template_context(c)
306 306
307 307 @LoginRequired()
308 308 @HasPermissionAllDecorator('hg.admin')
309 309 @view_config(
310 310 route_name='admin_permissions_overview', request_method='GET',
311 311 renderer='rhodecode:templates/admin/permissions/permissions.mako')
312 312 def permissions_overview(self):
313 313 c = self.load_default_context()
314 314 c.active = 'perms'
315 315
316 316 c.user = User.get_default_user(refresh=True)
317 317 c.perm_user = c.user.AuthUser()
318 318 return self._get_template_context(c)
319 319
320 320 @LoginRequired()
321 321 @HasPermissionAllDecorator('hg.admin')
322 322 @view_config(
323 323 route_name='admin_permissions_auth_token_access', request_method='GET',
324 324 renderer='rhodecode:templates/admin/permissions/permissions.mako')
325 325 def auth_token_access(self):
326 326 from rhodecode import CONFIG
327 327
328 328 c = self.load_default_context()
329 329 c.active = 'auth_token_access'
330 330
331 331 c.user = User.get_default_user(refresh=True)
332 332 c.perm_user = c.user.AuthUser()
333 333
334 334 mapper = self.request.registry.queryUtility(IRoutesMapper)
335 335 c.view_data = []
336 336
337 337 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
338 338 introspector = self.request.registry.introspector
339 339
340 340 view_intr = {}
341 341 for view_data in introspector.get_category('views'):
342 342 intr = view_data['introspectable']
343 343
344 344 if 'route_name' in intr and intr['attr']:
345 345 view_intr[intr['route_name']] = '{}:{}'.format(
346 346 str(intr['derived_callable'].func_name), intr['attr']
347 347 )
348 348
349 349 c.whitelist_key = 'api_access_controllers_whitelist'
350 350 c.whitelist_file = CONFIG.get('__file__')
351 351 whitelist_views = aslist(
352 352 CONFIG.get(c.whitelist_key), sep=',')
353 353
354 354 for route_info in mapper.get_routes():
355 355 if not route_info.name.startswith('__'):
356 356 routepath = route_info.pattern
357 357
358 358 def replace(matchobj):
359 359 if matchobj.group(1):
360 360 return "{%s}" % matchobj.group(1).split(':')[0]
361 361 else:
362 362 return "{%s}" % matchobj.group(2)
363 363
364 364 routepath = _argument_prog.sub(replace, routepath)
365 365
366 366 if not routepath.startswith('/'):
367 367 routepath = '/' + routepath
368 368
369 369 view_fqn = view_intr.get(route_info.name, 'NOT AVAILABLE')
370 370 active = view_fqn in whitelist_views
371 371 c.view_data.append((route_info.name, view_fqn, routepath, active))
372 372
373 373 c.whitelist_views = whitelist_views
374 374 return self._get_template_context(c)
375 375
376 376 def ssh_enabled(self):
377 377 return self.request.registry.settings.get(
378 378 'ssh.generate_authorized_keyfile')
379 379
380 380 @LoginRequired()
381 381 @HasPermissionAllDecorator('hg.admin')
382 382 @view_config(
383 383 route_name='admin_permissions_ssh_keys', request_method='GET',
384 384 renderer='rhodecode:templates/admin/permissions/permissions.mako')
385 385 def ssh_keys(self):
386 386 c = self.load_default_context()
387 387 c.active = 'ssh_keys'
388 388 c.ssh_enabled = self.ssh_enabled()
389 389 return self._get_template_context(c)
390 390
391 391 @LoginRequired()
392 392 @HasPermissionAllDecorator('hg.admin')
393 393 @view_config(
394 394 route_name='admin_permissions_ssh_keys_data', request_method='GET',
395 395 renderer='json_ext', xhr=True)
396 396 def ssh_keys_data(self):
397 397 _ = self.request.translate
398 398 column_map = {
399 399 'fingerprint': 'ssh_key_fingerprint',
400 400 'username': User.username
401 401 }
402 402 draw, start, limit = self._extract_chunk(self.request)
403 403 search_q, order_by, order_dir = self._extract_ordering(
404 404 self.request, column_map=column_map)
405 405
406 406 ssh_keys_data_total_count = UserSshKeys.query()\
407 407 .count()
408 408
409 409 # json generate
410 410 base_q = UserSshKeys.query().join(UserSshKeys.user)
411 411
412 412 if search_q:
413 413 like_expression = u'%{}%'.format(safe_unicode(search_q))
414 414 base_q = base_q.filter(or_(
415 415 User.username.ilike(like_expression),
416 416 UserSshKeys.ssh_key_fingerprint.ilike(like_expression),
417 417 ))
418 418
419 419 users_data_total_filtered_count = base_q.count()
420 420
421 421 sort_col = self._get_order_col(order_by, UserSshKeys)
422 422 if sort_col:
423 423 if order_dir == 'asc':
424 424 # handle null values properly to order by NULL last
425 425 if order_by in ['created_on']:
426 426 sort_col = coalesce(sort_col, datetime.date.max)
427 427 sort_col = sort_col.asc()
428 428 else:
429 429 # handle null values properly to order by NULL last
430 430 if order_by in ['created_on']:
431 431 sort_col = coalesce(sort_col, datetime.date.min)
432 432 sort_col = sort_col.desc()
433 433
434 434 base_q = base_q.order_by(sort_col)
435 435 base_q = base_q.offset(start).limit(limit)
436 436
437 437 ssh_keys = base_q.all()
438 438
439 439 ssh_keys_data = []
440 440 for ssh_key in ssh_keys:
441 441 ssh_keys_data.append({
442 442 "username": h.gravatar_with_user(self.request, ssh_key.user.username),
443 443 "fingerprint": ssh_key.ssh_key_fingerprint,
444 444 "description": ssh_key.description,
445 445 "created_on": h.format_date(ssh_key.created_on),
446 "accessed_on": h.format_date(ssh_key.accessed_on),
446 447 "action": h.link_to(
447 448 _('Edit'), h.route_path('edit_user_ssh_keys',
448 449 user_id=ssh_key.user.user_id))
449 450 })
450 451
451 452 data = ({
452 453 'draw': draw,
453 454 'data': ssh_keys_data,
454 455 'recordsTotal': ssh_keys_data_total_count,
455 456 'recordsFiltered': users_data_total_filtered_count,
456 457 })
457 458
458 459 return data
459 460
460 461 @LoginRequired()
461 462 @HasPermissionAllDecorator('hg.admin')
462 463 @CSRFRequired()
463 464 @view_config(
464 465 route_name='admin_permissions_ssh_keys_update', request_method='POST',
465 466 renderer='rhodecode:templates/admin/permissions/permissions.mako')
466 467 def ssh_keys_update(self):
467 468 _ = self.request.translate
468 469 self.load_default_context()
469 470
470 471 ssh_enabled = self.ssh_enabled()
471 472 key_file = self.request.registry.settings.get(
472 473 'ssh.authorized_keys_file_path')
473 474 if ssh_enabled:
474 475 trigger(SshKeyFileChangeEvent(), self.request.registry)
475 476 h.flash(_('Updated SSH keys file: {}').format(key_file),
476 477 category='success')
477 478 else:
478 479 h.flash(_('SSH key support is disabled in .ini file'),
479 480 category='warning')
480 481
481 482 raise HTTPFound(h.route_path('admin_permissions_ssh_keys'))
@@ -1,91 +1,93 b''
1 1
2 2 <div class="panel panel-default">
3 3 <div class="panel-heading">
4 4 <h3 class="panel-title">${_('SSH Keys')} - <span id="ssh_keys_count"></span></h3>
5 5
6 6 ${h.secure_form(h.route_path('admin_permissions_ssh_keys_update'), request=request)}
7 7 <button class="btn btn-link pull-right" type="submit">${_('Update SSH keys file')}</button>
8 8 ${h.end_form()}
9 9 </div>
10 10 <div class="panel-body">
11 11 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
12 12
13 13 <div id="repos_list_wrap">
14 14 <table id="ssh_keys_table" class="display"></table>
15 15 </div>
16 16 </div>
17 17 </div>
18 18
19 19
20 20 <script type="text/javascript">
21 21
22 22 $(document).ready(function() {
23 23 var $sshKeyListTable = $('#ssh_keys_table');
24 24
25 25 var getDatatableCount = function(){
26 26 var table = $sshKeyListTable.dataTable();
27 27 var page = table.api().page.info();
28 28 var active = page.recordsDisplay;
29 29 var total = page.recordsTotal;
30 30
31 31 var _text = _gettext("{0} out of {1} ssh keys").format(active, total);
32 32 $('#ssh_keys_count').text(_text);
33 33 };
34 34
35 35 // user list
36 36 $sshKeyListTable.DataTable({
37 37 processing: true,
38 38 serverSide: true,
39 39 ajax: "${h.route_path('admin_permissions_ssh_keys_data')}",
40 40 dom: 'rtp',
41 41 pageLength: ${c.visual.admin_grid_items},
42 42 order: [[ 0, "asc" ]],
43 43 columns: [
44 44 { data: {"_": "username",
45 45 "sort": "username"}, title: "${_('Username')}", className: "td-user" },
46 46 { data: {"_": "fingerprint",
47 47 "sort": "fingerprint"}, title: "${_('Fingerprint')}", className: "td-type" },
48 48 { data: {"_": "description",
49 49 "sort": "description"}, title: "${_('Description')}", className: "td-type" },
50 50 { data: {"_": "created_on",
51 51 "sort": "created_on"}, title: "${_('Created on')}", className: "td-time" },
52 { data: {"_": "accessed_on",
53 "sort": "accessed_on"}, title: "${_('Accessed on')}", className: "td-time" },
52 54 { data: {"_": "action",
53 55 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
54 56 ],
55 57 language: {
56 58 paginate: DEFAULT_GRID_PAGINATION,
57 59 sProcessing: _gettext('loading...'),
58 60 emptyTable: _gettext("No ssh keys available yet.")
59 61 },
60 62
61 63 "createdRow": function ( row, data, index ) {
62 64 if (!data['active_raw']){
63 65 $(row).addClass('closed')
64 66 }
65 67 }
66 68 });
67 69
68 70 $sshKeyListTable.on('xhr.dt', function(e, settings, json, xhr){
69 71 $sshKeyListTable.css('opacity', 1);
70 72 });
71 73
72 74 $sshKeyListTable.on('preXhr.dt', function(e, settings, data){
73 75 $sshKeyListTable.css('opacity', 0.3);
74 76 });
75 77
76 78 // refresh counters on draw
77 79 $sshKeyListTable.on('draw.dt', function(){
78 80 getDatatableCount();
79 81 });
80 82
81 83 // filter
82 84 $('#q_filter').on('keyup',
83 85 $.debounce(250, function() {
84 86 $sshKeyListTable.DataTable().search(
85 87 $('#q_filter').val()
86 88 ).draw();
87 89 })
88 90 );
89 91
90 92 });
91 93 </script>
General Comments 0
You need to be logged in to leave comments. Login now