##// END OF EJS Templates
ssh-keys: added admin panel for managing globally all SSH Keys....
marcink -
r2042:e367c94a default
parent child Browse files
Show More
@@ -0,0 +1,91 b''
1
2 <div class="panel panel-default">
3 <div class="panel-heading">
4 <h3 class="panel-title">${_('SSH Keys')} - <span id="ssh_keys_count"></span></h3>
5
6 ${h.secure_form(h.route_path('admin_permissions_ssh_keys_update'), method='POST', request=request)}
7 <button class="btn btn-link pull-right" type="submit">${_('Update SSH keys file')}</button>
8 ${h.end_form()}
9 </div>
10 <div class="panel-body">
11 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
12
13 <div id="repos_list_wrap">
14 <table id="ssh_keys_table" class="display"></table>
15 </div>
16 </div>
17 </div>
18
19
20 <script type="text/javascript">
21
22 $(document).ready(function() {
23 var $sshKeyListTable = $('#ssh_keys_table');
24
25 var getDatatableCount = function(){
26 var table = $sshKeyListTable.dataTable();
27 var page = table.api().page.info();
28 var active = page.recordsDisplay;
29 var total = page.recordsTotal;
30
31 var _text = _gettext("{0} out of {1} ssh keys").format(active, total);
32 $('#ssh_keys_count').text(_text);
33 };
34
35 // user list
36 $sshKeyListTable.DataTable({
37 processing: true,
38 serverSide: true,
39 ajax: "${h.route_path('admin_permissions_ssh_keys_data')}",
40 dom: 'rtp',
41 pageLength: ${c.visual.admin_grid_items},
42 order: [[ 0, "asc" ]],
43 columns: [
44 { data: {"_": "username",
45 "sort": "username"}, title: "${_('Username')}", className: "td-user" },
46 { data: {"_": "fingerprint",
47 "sort": "fingerprint"}, title: "${_('Fingerprint')}", className: "td-type" },
48 { data: {"_": "description",
49 "sort": "description"}, title: "${_('Description')}", className: "td-type" },
50 { data: {"_": "created_on",
51 "sort": "created_on"}, title: "${_('Created on')}", className: "td-time" },
52 { data: {"_": "action",
53 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
54 ],
55 language: {
56 paginate: DEFAULT_GRID_PAGINATION,
57 sProcessing: _gettext('loading...'),
58 emptyTable: _gettext("No ssh keys available yet.")
59 },
60
61 "createdRow": function ( row, data, index ) {
62 if (!data['active_raw']){
63 $(row).addClass('closed')
64 }
65 }
66 });
67
68 $sshKeyListTable.on('xhr.dt', function(e, settings, json, xhr){
69 $sshKeyListTable.css('opacity', 1);
70 });
71
72 $sshKeyListTable.on('preXhr.dt', function(e, settings, data){
73 $sshKeyListTable.css('opacity', 0.3);
74 });
75
76 // refresh counters on draw
77 $sshKeyListTable.on('draw.dt', function(){
78 getDatatableCount();
79 });
80
81 // filter
82 $('#q_filter').on('keyup',
83 $.debounce(250, function() {
84 $sshKeyListTable.DataTable().search(
85 $('#q_filter').val()
86 ).draw();
87 })
88 );
89
90 });
91 </script>
@@ -106,6 +106,16 b' def admin_routes(config):'
106 106 name='admin_permissions_auth_token_access',
107 107 pattern='/permissions/auth_token_access')
108 108
109 config.add_route(
110 name='admin_permissions_ssh_keys',
111 pattern='/permissions/ssh_keys')
112 config.add_route(
113 name='admin_permissions_ssh_keys_data',
114 pattern='/permissions/ssh_keys/data')
115 config.add_route(
116 name='admin_permissions_ssh_keys_update',
117 pattern='/permissions/ssh_keys/update')
118
109 119 # users admin
110 120 config.add_route(
111 121 name='users',
@@ -20,7 +20,9 b''
20 20
21 21 import pytest
22 22 from rhodecode.model.db import User, UserIpMap
23 from rhodecode.model.meta import Session
23 24 from rhodecode.model.permission import PermissionModel
25 from rhodecode.model.ssh_key import SshKeyModel
24 26 from rhodecode.tests import (
25 27 TestController, clear_all_caches, assert_session_flash)
26 28
@@ -55,7 +57,14 b' def route_path(name, params=None, **kwar'
55 57 'admin_permissions_ips':
56 58 ADMIN_PREFIX + '/permissions/ips',
57 59 'admin_permissions_overview':
58 ADMIN_PREFIX + '/permissions/overview'
60 ADMIN_PREFIX + '/permissions/overview',
61
62 'admin_permissions_ssh_keys':
63 ADMIN_PREFIX + '/permissions/ssh_keys',
64 'admin_permissions_ssh_keys_data':
65 ADMIN_PREFIX + '/permissions/ssh_keys/data',
66 'admin_permissions_ssh_keys_update':
67 ADMIN_PREFIX + '/permissions/ssh_keys/update'
59 68
60 69 }[name].format(**kwargs)
61 70
@@ -248,3 +257,30 b' class TestAdminPermissionsController(Tes'
248 257 def test_index_overview(self):
249 258 self.log_user()
250 259 self.app.get(route_path('admin_permissions_overview'))
260
261 def test_ssh_keys(self):
262 self.log_user()
263 self.app.get(route_path('admin_permissions_ssh_keys'), status=200)
264
265 def test_ssh_keys_data(self, user_util, xhr_header):
266 self.log_user()
267 response = self.app.get(route_path('admin_permissions_ssh_keys_data'),
268 extra_environ=xhr_header)
269 assert response.json == {u'data': [], u'draw': None,
270 u'recordsFiltered': 0, u'recordsTotal': 0}
271
272 dummy_user = user_util.create_user()
273 SshKeyModel().create(dummy_user, 'ab:cd:ef', 'KEYKEY', 'test_key')
274 Session().commit()
275 response = self.app.get(route_path('admin_permissions_ssh_keys_data'),
276 extra_environ=xhr_header)
277 assert response.json['data'][0]['fingerprint'] == 'ab:cd:ef'
278
279 def test_ssh_keys_update(self):
280 self.log_user()
281 response = self.app.post(
282 route_path('admin_permissions_ssh_keys_update'),
283 dict(csrf_token=self.csrf_token), status=302)
284
285 assert_session_flash(
286 response, 'SSH key support is disabled in .ini file')
@@ -21,6 +21,7 b''
21 21 import re
22 22 import logging
23 23 import formencode
24 import datetime
24 25 from pyramid.interfaces import IRoutesMapper
25 26
26 27 from pyramid.view import view_config
@@ -28,13 +29,15 b' from pyramid.httpexceptions import HTTPF'
28 29 from pyramid.renderers import render
29 30 from pyramid.response import Response
30 31
31 from rhodecode.apps._base import BaseAppView
32 from rhodecode.apps._base import BaseAppView, DataGridAppView
33 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
34 from rhodecode.events import trigger
32 35
33 36 from rhodecode.lib import helpers as h
34 37 from rhodecode.lib.auth import (
35 38 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
36 from rhodecode.lib.utils2 import aslist
37 from rhodecode.model.db import User, UserIpMap
39 from rhodecode.lib.utils2 import aslist, safe_unicode
40 from rhodecode.model.db import or_, joinedload, coalesce, User, UserIpMap, UserSshKeys
38 41 from rhodecode.model.forms import (
39 42 ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm)
40 43 from rhodecode.model.meta import Session
@@ -45,7 +48,7 b' from rhodecode.model.settings import Set'
45 48 log = logging.getLogger(__name__)
46 49
47 50
48 class AdminPermissionsView(BaseAppView):
51 class AdminPermissionsView(BaseAppView, DataGridAppView):
49 52 def load_default_context(self):
50 53 c = self._get_local_tmpl_context()
51 54
@@ -367,3 +370,106 b' class AdminPermissionsView(BaseAppView):'
367 370
368 371 c.whitelist_views = whitelist_views
369 372 return self._get_template_context(c)
373
374 @LoginRequired()
375 @HasPermissionAllDecorator('hg.admin')
376 @view_config(
377 route_name='admin_permissions_ssh_keys', request_method='GET',
378 renderer='rhodecode:templates/admin/permissions/permissions.mako')
379 def ssh_keys(self):
380 c = self.load_default_context()
381 c.active = 'ssh_keys'
382 return self._get_template_context(c)
383
384 @LoginRequired()
385 @HasPermissionAllDecorator('hg.admin')
386 @view_config(
387 route_name='admin_permissions_ssh_keys_data', request_method='GET',
388 renderer='json_ext', xhr=True)
389 def ssh_keys_data(self):
390 _ = self.request.translate
391 column_map = {
392 'fingerprint': 'ssh_key_fingerprint',
393 'username': User.username
394 }
395 draw, start, limit = self._extract_chunk(self.request)
396 search_q, order_by, order_dir = self._extract_ordering(
397 self.request, column_map=column_map)
398
399 ssh_keys_data_total_count = UserSshKeys.query()\
400 .count()
401
402 # json generate
403 base_q = UserSshKeys.query().join(UserSshKeys.user)
404
405 if search_q:
406 like_expression = u'%{}%'.format(safe_unicode(search_q))
407 base_q = base_q.filter(or_(
408 User.username.ilike(like_expression),
409 UserSshKeys.ssh_key_fingerprint.ilike(like_expression),
410 ))
411
412 users_data_total_filtered_count = base_q.count()
413
414 sort_col = self._get_order_col(order_by, UserSshKeys)
415 if sort_col:
416 if order_dir == 'asc':
417 # handle null values properly to order by NULL last
418 if order_by in ['created_on']:
419 sort_col = coalesce(sort_col, datetime.date.max)
420 sort_col = sort_col.asc()
421 else:
422 # handle null values properly to order by NULL last
423 if order_by in ['created_on']:
424 sort_col = coalesce(sort_col, datetime.date.min)
425 sort_col = sort_col.desc()
426
427 base_q = base_q.order_by(sort_col)
428 base_q = base_q.offset(start).limit(limit)
429
430 ssh_keys = base_q.all()
431
432 ssh_keys_data = []
433 for ssh_key in ssh_keys:
434 ssh_keys_data.append({
435 "username": h.gravatar_with_user(self.request, ssh_key.user.username),
436 "fingerprint": ssh_key.ssh_key_fingerprint,
437 "description": ssh_key.description,
438 "created_on": h.format_date(ssh_key.created_on),
439 "action": h.link_to(
440 _('Edit'), h.route_path('edit_user_ssh_keys',
441 user_id=ssh_key.user.user_id))
442 })
443
444 data = ({
445 'draw': draw,
446 'data': ssh_keys_data,
447 'recordsTotal': ssh_keys_data_total_count,
448 'recordsFiltered': users_data_total_filtered_count,
449 })
450
451 return data
452
453 @LoginRequired()
454 @HasPermissionAllDecorator('hg.admin')
455 @CSRFRequired()
456 @view_config(
457 route_name='admin_permissions_ssh_keys_update', request_method='POST',
458 renderer='rhodecode:templates/admin/permissions/permissions.mako')
459 def ssh_keys_update(self):
460 _ = self.request.translate
461 self.load_default_context()
462
463 ssh_enabled = self.request.registry.settings.get(
464 'ssh.generate_authorized_keyfile')
465 key_file = self.request.registry.settings.get(
466 'ssh.authorized_keys_file_path')
467 if ssh_enabled:
468 trigger(SshKeyFileChangeEvent(), self.request.registry)
469 h.flash(_('Updated SSH keys file: {}').format(key_file),
470 category='success')
471 else:
472 h.flash(_('SSH key support is disabled in .ini file'),
473 category='warning')
474
475 raise HTTPFound(h.route_path('admin_permissions_ssh_keys'))
@@ -56,6 +56,9 b' function registerRCRoutes() {'
56 56 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
57 57 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
58 58 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
59 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
60 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
61 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
59 62 pyroutes.register('users', '/_admin/users', []);
60 63 pyroutes.register('users_data', '/_admin/users_data', []);
61 64 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
@@ -44,6 +44,9 b''
44 44 <li class="${'active' if c.active=='auth_token_access' else ''}">
45 45 <a href="${h.route_path('admin_permissions_auth_token_access')}">${_('AuthToken Access')}</a>
46 46 </li>
47 <li class="${'active' if c.active=='ssh_keys' else ''}">
48 <a href="${h.route_path('admin_permissions_ssh_keys')}">${_('SSH Keys')}</a>
49 </li>
47 50 <li class="${'active' if c.active=='perms' else ''}">
48 51 <a href="${h.route_path('admin_permissions_overview')}">${_('Overview')}</a>
49 52 </li>
General Comments 0
You need to be logged in to leave comments. Login now