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 | name='admin_permissions_auth_token_access', |
|
106 | name='admin_permissions_auth_token_access', | |
107 | pattern='/permissions/auth_token_access') |
|
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 | # users admin |
|
119 | # users admin | |
110 | config.add_route( |
|
120 | config.add_route( | |
111 | name='users', |
|
121 | name='users', |
@@ -20,7 +20,9 b'' | |||||
20 |
|
20 | |||
21 | import pytest |
|
21 | import pytest | |
22 | from rhodecode.model.db import User, UserIpMap |
|
22 | from rhodecode.model.db import User, UserIpMap | |
|
23 | from rhodecode.model.meta import Session | |||
23 | from rhodecode.model.permission import PermissionModel |
|
24 | from rhodecode.model.permission import PermissionModel | |
|
25 | from rhodecode.model.ssh_key import SshKeyModel | |||
24 | from rhodecode.tests import ( |
|
26 | from rhodecode.tests import ( | |
25 | TestController, clear_all_caches, assert_session_flash) |
|
27 | TestController, clear_all_caches, assert_session_flash) | |
26 |
|
28 | |||
@@ -55,7 +57,14 b' def route_path(name, params=None, **kwar' | |||||
55 | 'admin_permissions_ips': |
|
57 | 'admin_permissions_ips': | |
56 | ADMIN_PREFIX + '/permissions/ips', |
|
58 | ADMIN_PREFIX + '/permissions/ips', | |
57 | 'admin_permissions_overview': |
|
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 | }[name].format(**kwargs) |
|
69 | }[name].format(**kwargs) | |
61 |
|
70 | |||
@@ -248,3 +257,30 b' class TestAdminPermissionsController(Tes' | |||||
248 | def test_index_overview(self): |
|
257 | def test_index_overview(self): | |
249 | self.log_user() |
|
258 | self.log_user() | |
250 | self.app.get(route_path('admin_permissions_overview')) |
|
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 | import re |
|
21 | import re | |
22 | import logging |
|
22 | import logging | |
23 | import formencode |
|
23 | import formencode | |
|
24 | import datetime | |||
24 | from pyramid.interfaces import IRoutesMapper |
|
25 | from pyramid.interfaces import IRoutesMapper | |
25 |
|
26 | |||
26 | from pyramid.view import view_config |
|
27 | from pyramid.view import view_config | |
@@ -28,13 +29,15 b' from pyramid.httpexceptions import HTTPF' | |||||
28 | from pyramid.renderers import render |
|
29 | from pyramid.renderers import render | |
29 | from pyramid.response import Response |
|
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 | from rhodecode.lib import helpers as h |
|
36 | from rhodecode.lib import helpers as h | |
34 | from rhodecode.lib.auth import ( |
|
37 | from rhodecode.lib.auth import ( | |
35 | LoginRequired, HasPermissionAllDecorator, CSRFRequired) |
|
38 | LoginRequired, HasPermissionAllDecorator, CSRFRequired) | |
36 | from rhodecode.lib.utils2 import aslist |
|
39 | from rhodecode.lib.utils2 import aslist, safe_unicode | |
37 | from rhodecode.model.db import User, UserIpMap |
|
40 | from rhodecode.model.db import or_, joinedload, coalesce, User, UserIpMap, UserSshKeys | |
38 | from rhodecode.model.forms import ( |
|
41 | from rhodecode.model.forms import ( | |
39 | ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm) |
|
42 | ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm) | |
40 | from rhodecode.model.meta import Session |
|
43 | from rhodecode.model.meta import Session | |
@@ -45,7 +48,7 b' from rhodecode.model.settings import Set' | |||||
45 | log = logging.getLogger(__name__) |
|
48 | log = logging.getLogger(__name__) | |
46 |
|
49 | |||
47 |
|
50 | |||
48 | class AdminPermissionsView(BaseAppView): |
|
51 | class AdminPermissionsView(BaseAppView, DataGridAppView): | |
49 | def load_default_context(self): |
|
52 | def load_default_context(self): | |
50 | c = self._get_local_tmpl_context() |
|
53 | c = self._get_local_tmpl_context() | |
51 |
|
54 | |||
@@ -367,3 +370,106 b' class AdminPermissionsView(BaseAppView):' | |||||
367 |
|
370 | |||
368 | c.whitelist_views = whitelist_views |
|
371 | c.whitelist_views = whitelist_views | |
369 | return self._get_template_context(c) |
|
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 | pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []); |
|
56 | pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []); | |
57 | pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []); |
|
57 | pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []); | |
58 | pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []); |
|
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 | pyroutes.register('users', '/_admin/users', []); |
|
62 | pyroutes.register('users', '/_admin/users', []); | |
60 | pyroutes.register('users_data', '/_admin/users_data', []); |
|
63 | pyroutes.register('users_data', '/_admin/users_data', []); | |
61 | pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']); |
|
64 | pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']); |
@@ -44,6 +44,9 b'' | |||||
44 | <li class="${'active' if c.active=='auth_token_access' else ''}"> |
|
44 | <li class="${'active' if c.active=='auth_token_access' else ''}"> | |
45 | <a href="${h.route_path('admin_permissions_auth_token_access')}">${_('AuthToken Access')}</a> |
|
45 | <a href="${h.route_path('admin_permissions_auth_token_access')}">${_('AuthToken Access')}</a> | |
46 | </li> |
|
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 | <li class="${'active' if c.active=='perms' else ''}"> |
|
50 | <li class="${'active' if c.active=='perms' else ''}"> | |
48 | <a href="${h.route_path('admin_permissions_overview')}">${_('Overview')}</a> |
|
51 | <a href="${h.route_path('admin_permissions_overview')}">${_('Overview')}</a> | |
49 | </li> |
|
52 | </li> |
General Comments 0
You need to be logged in to leave comments.
Login now