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