# HG changeset patch # User Tim Freund # Date 2014-11-17 20:40:35 # Node ID 66c208bf56fe1907074b111d535d44b0e8c26585 # Parent 3e84ac8ed57918b3c81cbc73d8df9e67fdd771b4 ssh: user management of ssh keys Add user interface for managing SSH based access. The work in this commit is based heavily off of the existing API key code for the sake of consistency. Updates to use Bootstrap, request.authuser, POST methods and pytest by Anton Schur . Additional Bootstrap fixes by Dominik Ruf. The original code has been heavily modified by Mads Kiilerich. diff --git a/kallithea/config/routing.py b/kallithea/config/routing.py --- a/kallithea/config/routing.py +++ b/kallithea/config/routing.py @@ -358,6 +358,13 @@ def make_map(config): m.connect("my_account_api_keys_delete", "/my_account/api_keys/delete", action="my_account_api_keys_delete", conditions=dict(method=["POST"])) + m.connect("my_account_ssh_keys", "/my_account/ssh_keys", + action="my_account_ssh_keys", conditions=dict(method=["GET"])) + m.connect("my_account_ssh_keys", "/my_account/ssh_keys", + action="my_account_ssh_keys_add", conditions=dict(method=["POST"])) + m.connect("my_account_ssh_keys_delete", "/my_account/ssh_keys/delete", + action="my_account_ssh_keys_delete", conditions=dict(method=["POST"])) + # ADMIN GIST with rmap.submapper(path_prefix=ADMIN_PREFIX, controller='admin/gists') as m: diff --git a/kallithea/controllers/admin/my_account.py b/kallithea/controllers/admin/my_account.py --- a/kallithea/controllers/admin/my_account.py +++ b/kallithea/controllers/admin/my_account.py @@ -39,13 +39,14 @@ from kallithea.config.routing import url from kallithea.lib import helpers as h from kallithea.lib import auth_modules from kallithea.lib.auth import LoginRequired, AuthUser -from kallithea.lib.base import BaseController, render +from kallithea.lib.base import BaseController, render, IfSshEnabled from kallithea.lib.utils2 import generate_api_key, safe_int from kallithea.model.db import Repository, UserEmailMap, User, UserFollowing from kallithea.model.forms import UserForm, PasswordChangeForm from kallithea.model.user import UserModel from kallithea.model.repo import RepoModel from kallithea.model.api_key import ApiKeyModel +from kallithea.model.ssh_key import SshKeyModel from kallithea.model.meta import Session log = logging.getLogger(__name__) @@ -259,3 +260,28 @@ class MyAccountController(BaseController h.flash(_("API key successfully deleted"), category='success') raise HTTPFound(location=url('my_account_api_keys')) + + @IfSshEnabled + def my_account_ssh_keys(self): + c.active = 'ssh_keys' + self.__load_data() + c.user_ssh_keys = SshKeyModel().get_ssh_keys(request.authuser.user_id) + return render('admin/my_account/my_account.html') + + @IfSshEnabled + def my_account_ssh_keys_add(self): + description = request.POST.get('description') + public_key = request.POST.get('public_key') + new_ssh_key = SshKeyModel().create(request.authuser.user_id, + description, public_key) + Session().commit() + h.flash(_("SSH key %s successfully added") % new_ssh_key.fingerprint, category='success') + raise HTTPFound(location=url('my_account_ssh_keys')) + + @IfSshEnabled + def my_account_ssh_keys_delete(self): + public_key = request.POST.get('del_public_key') + SshKeyModel().delete(public_key, request.authuser.user_id) + Session().commit() + h.flash(_("SSH key successfully deleted"), category='success') + raise HTTPFound(location=url('my_account_ssh_keys')) diff --git a/kallithea/templates/admin/my_account/my_account.html b/kallithea/templates/admin/my_account/my_account.html --- a/kallithea/templates/admin/my_account/my_account.html +++ b/kallithea/templates/admin/my_account/my_account.html @@ -25,6 +25,9 @@
  • ${_('Profile')}
  • ${_('Email Addresses')}
  • ${_('Password')}
  • + %if c.ssh_enabled: +
  • ${_('SSH Keys')}
  • + %endif
  • ${_('API Keys')}
  • ${_('Owned Repositories')}
  • ${_('Watched Repositories')}
  • diff --git a/kallithea/templates/admin/my_account/my_account_ssh_keys.html b/kallithea/templates/admin/my_account/my_account_ssh_keys.html new file mode 100644 --- /dev/null +++ b/kallithea/templates/admin/my_account/my_account_ssh_keys.html @@ -0,0 +1,63 @@ + + %if c.user_ssh_keys: + + + + + + %for ssh_key in c.user_ssh_keys: + + + + + + %endfor + %else: + + + + %endif +
    ${_('Fingerprint')}${_('Description')}${_('Action')}
    + ${ssh_key.fingerprint} + + ${ssh_key.description} + + ${h.form(url('my_account_ssh_keys_delete'))} + ${h.hidden('del_public_key', ssh_key.public_key)} + + ${h.end_form()} +
    +
    ${_('No SSH keys have been added')}
    +
    + +
    + ${h.form(url('my_account_ssh_keys'))} +
    +
    + +
    +
    + +
    + ${h.textarea('public_key', '', class_='form-control', placeholder=_('Public key (contents of e.g. ~/.ssh/id_rsa.pub)'), cols=80, rows=5)} +
    +
    +
    + +
    + ${h.text('description', class_='form-control', placeholder=_('Description'))} +
    +
    +
    +
    + ${h.submit('save', _('Add'), class_="btn btn-default")} + ${h.reset('reset', _('Reset'), class_="btn btn-default")} +
    +
    +
    + ${h.end_form()} +
    diff --git a/kallithea/tests/functional/test_my_account.py b/kallithea/tests/functional/test_my_account.py --- a/kallithea/tests/functional/test_my_account.py +++ b/kallithea/tests/functional/test_my_account.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from kallithea.model.db import User, UserFollowing, Repository, UserApiKeys +from kallithea.model.db import User, UserFollowing, Repository, UserApiKeys, UserSshKeys from kallithea.tests.base import * from kallithea.tests.fixture import Fixture from kallithea.lib import helpers as h @@ -249,3 +249,47 @@ class TestMyAccountController(TestContro self.checkSessionFlash(response, 'API key successfully reset') response = response.follow() response.mustcontain(no=[api_key]) + + def test_my_account_add_ssh_key(self): + description = u'something' + public_key = u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC6Ycnc2oUZHQnQwuqgZqTTdMDZD7ataf3JM7oG2Fw8JR6cdmz4QZLe5mfDwaFwG2pWHLRpVqzfrD/Pn3rIO++bgCJH5ydczrl1WScfryV1hYMJ/4EzLGM657J1/q5EI+b9SntKjf4ax+KP322L0TNQGbZUHLbfG2MwHMrYBQpHUQ== me@localhost' + fingerprint = u'Ke3oUCNJM87P0jJTb3D+e3shjceP2CqMpQKVd75E9I8' + + self.log_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS) + response = self.app.post(url('my_account_ssh_keys'), + {'description': description, + 'public_key': public_key, + '_authentication_token': self.authentication_token()}) + self.checkSessionFlash(response, 'SSH key %s successfully added' % fingerprint) + + response = response.follow() + response.mustcontain(fingerprint) + user_id = response.session['authuser']['user_id'] + ssh_key = UserSshKeys.query().filter(UserSshKeys.user_id == user_id).one() + assert ssh_key.fingerprint == fingerprint + assert ssh_key.description == description + Session().delete(ssh_key) + Session().commit() + + def test_my_account_remove_ssh_key(self): + description = u'' + public_key = u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC6Ycnc2oUZHQnQwuqgZqTTdMDZD7ataf3JM7oG2Fw8JR6cdmz4QZLe5mfDwaFwG2pWHLRpVqzfrD/Pn3rIO++bgCJH5ydczrl1WScfryV1hYMJ/4EzLGM657J1/q5EI+b9SntKjf4ax+KP322L0TNQGbZUHLbfG2MwHMrYBQpHUQ== me@localhost' + fingerprint = u'Ke3oUCNJM87P0jJTb3D+e3shjceP2CqMpQKVd75E9I8' + + self.log_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS) + response = self.app.post(url('my_account_ssh_keys'), + {'description': description, + 'public_key': public_key, + '_authentication_token': self.authentication_token()}) + self.checkSessionFlash(response, 'SSH key %s successfully added' % fingerprint) + response.follow() + user_id = response.session['authuser']['user_id'] + ssh_key = UserSshKeys.query().filter(UserSshKeys.user_id == user_id).one() + assert ssh_key.description == description + + response = self.app.post(url('my_account_ssh_keys_delete'), + {'del_public_key': ssh_key.public_key, + '_authentication_token': self.authentication_token()}) + self.checkSessionFlash(response, 'SSH key successfully deleted') + keys = UserSshKeys.query().all() + assert 0 == len(keys)