templates_people_ssh_keys_DATA = \
src/templates/people/ssh-keys/base.html \
- src/templates/people/ssh-keys/index.html
+ src/templates/people/ssh-keys/delete.html \
+ src/templates/people/ssh-keys/error.html \
+ src/templates/people/ssh-keys/error-invalid-key.html \
+ src/templates/people/ssh-keys/index.html \
+ src/templates/people/ssh-keys/upload.html
templates_people_ssh_keysdir = $(templates_peopledir)/ssh-keys
def _set_string(self, key, value):
return self._set_strings(key, [value,])
+ def _add(self, key, values):
+ modlist = [
+ (ldap.MOD_ADD, key, values),
+ ]
+
+ self._modify(modlist)
+
+ def _add_strings(self, key, values):
+ return self._add(key, [e.encode() for e in values])
+
+ def _add_string(self, key, value):
+ return self._add_strings(key, [value,])
+
+ def _delete(self, key, values):
+ modlist = [
+ (ldap.MOD_DELETE, key, values),
+ ]
+
+ self._modify(modlist)
+
+ def _delete_strings(self, key, values):
+ return self._delete(key, [e.encode() for e in values])
+
+ def _delete_string(self, key, value):
+ return self._delete_strings(key, [value,])
+
def passwd(self, new_password):
"""
Sets a new password
Raises exceptions from the server on any other errors.
"""
+ if not password:
+ return
+
logging.debug("Checking credentials for %s" % self.dn)
# Create a new LDAP connection
return key
+ def add_ssh_key(self, key):
+ k = sshpubkeys.SSHKey()
+
+ # Try to parse the key
+ k.parse(key)
+
+ # Check for types and sufficient sizes
+ if k.key_type == b"ssh-rsa":
+ if k.bits < 4096:
+ raise sshpubkeys.TooShortKeyError("RSA keys cannot be smaller than 4096 bits")
+
+ elif k.key_type == b"ssh-dss":
+ raise sshpubkeys.InvalidKeyError("DSA keys are not supported")
+
+ # Ignore any duplicates
+ if key in (k.keydata for k in self.ssh_keys):
+ logging.debug("SSH Key has already been added for %s: %s" % (self, key))
+ return
+
+ # Save key to LDAP
+ self._add_string("sshPublicKey", key)
+
+ # Append to cache
+ self.ssh_keys.append(k)
+
+ def delete_ssh_key(self, key):
+ if not key in (k.keydata for k in self.ssh_keys):
+ return
+
+ # Delete key from LDAP
+ self._delete_string("sshPublicKey", key)
+
if __name__ == "__main__":
a = Accounts()
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ account }} - {{ _("Delete SSH Key") }}{% end block %}
+
+{% block main %}
+ <div class="row justify-content-center">
+ <div class="col col-md-8">
+ <h4 class="mb-4">{{ _("Delete SSH Key") }}</h4>
+
+ <form method="POST" action="">
+ {% raw xsrf_form_html() %}
+
+ <div class="form-group">
+ <label>{{ _("Fingerprints") }}</label>
+
+ {% for h in (key.hash_md5(), key.hash_sha256(), key.hash_sha512()) %}
+ <p class="text-monospace small text-truncate mb-0">
+ {{ h }}
+ </p>
+ {% end %}
+ </div>
+
+ <div class="form-group">
+ <label>{{ _("Current Password") }}</label>
+
+ <input type="password" class="form-control" name="password"
+ placeholder="{{ _("Current Password") }}">
+
+ <small class="form-text text-muted">
+ {{ _("To authorize uploading a new SSH key, your password is required") }}
+ </small>
+ </div>
+
+ <input class="btn btn-primary btn-block" type="submit" value="{{ _("Delete SSH Key") }}">
+ </form>
+ </div>
+ </div>
+{% end block %}
--- /dev/null
+{% extends "error.html" %}
+
+{% block reason %}
+ <p class="card-text">
+ {{ _("The uploaded key is invalid: %s") % exception }}
+ </p>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ account }} - {{ _("Error Adding SSH Key") }}{% end block %}
+
+{% block main %}
+ <h1 class="mb-0">{{ _("Error") }}</h1>
+ <h6>{{ _("Your SSH Key could not be added") }}</h6>
+
+ <div class="card card-body bg-danger text-white">
+ {% block reason %}{% end block %}
+
+ <p class="card-text">
+ {{ _("Please go back and try again") }}
+ </p>
+ </div>
+{% end block %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ account }} - {{ _("Upload New SSH Key") }}{% end block %}
+
+{% block main %}
+ <div class="row justify-content-center">
+ <div class="col col-md-8">
+ <h4 class="mb-4">{{ _("Upload New SSH Key") }}</h4>
+
+ <form method="POST" action="">
+ {% raw xsrf_form_html() %}
+
+ <div class="form-group">
+ <textarea class="form-control" name="key" rows="3" required
+ placeholder="{{ _("SSH Key") }}"></textarea>
+
+ <small class="form-text text-muted">
+ {{ _("The SSH key must be conforming to these criteria:") }}
+
+ <ul>
+ <li>{{ _("Supported key types are: Ed25519, ECDSA and RSA") }}</li>
+ <li>{{ _("RSA keys must be at least 4096 bits long") }}</li>
+ </ul>
+ </small>
+ </div>
+
+ <div class="form-group">
+ <label>{{ _("Current Password") }}</label>
+
+ <input type="password" class="form-control" name="password"
+ placeholder="{{ _("Current Password") }}">
+
+ <small class="form-text text-muted">
+ {{ _("To authorize uploading a new SSH key, your password is required") }}
+ </small>
+ </div>
+
+ <input class="btn btn-primary btn-block" type="submit" value="{{ _("Upload SSH Key") }}">
+ </form>
+ </div>
+ </div>
+{% end block %}
(r"/users/(\w+)/edit", people.UserEditHandler),
(r"/users/(\w+)/passwd", people.UserPasswdHandler),
(r"/users/(\w+)/ssh-keys", people.SSHKeysIndexHandler),
+ (r"/users/(\w+)/ssh-keys/(MD5\:.*)/delete", people.SSHKeysDeleteHandler),
(r"/users/(\w+)/ssh-keys/(MD5\:.*)", people.SSHKeysDownloadHandler),
+ (r"/users/(\w+)/ssh-keys/upload", people.SSHKeysUploadHandler),
(r"/users/(\w+)/sip", people.SIPHandler),
] + authentication_handlers)
import datetime
import ldap
import logging
+import sshpubkeys
import tornado.web
from . import handlers_base as base
self.finish(key.keydata)
+class SSHKeysUploadHandler(base.BaseHandler):
+ @tornado.web.authenticated
+ def get(self, uid):
+ account = self.backend.accounts.get_by_uid(uid)
+ if not account:
+ raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
+
+ # Check for permissions
+ if not account.can_be_managed_by(self.current_user):
+ raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
+
+ self.render("people/ssh-keys/upload.html", account=account)
+
+ @tornado.web.authenticated
+ def post(self, uid):
+ account = self.backend.accounts.get_by_uid(uid)
+ if not account:
+ raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
+
+ # Check for permissions
+ if not account.can_be_managed_by(self.current_user):
+ raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
+
+ key = self.get_argument("key")
+
+ # Verify password
+ password = self.get_argument("password")
+ if not account.check_password(password):
+ raise tornado.web.HTTPError(403, "Incorrect password for %s" % account)
+
+ # Try to add new SSH key
+ try:
+ account.add_ssh_key(key)
+
+ except sshpubkeys.InvalidKeyException as e:
+ self.render("people/ssh-keys/error-invalid-key.html", account=account, exception=e)
+ return
+
+ self.redirect("/users/%s/ssh-keys" % account.uid)
+
+
+class SSHKeysDeleteHandler(base.BaseHandler):
+ @tornado.web.authenticated
+ def get(self, uid, hash_md5):
+ account = self.backend.accounts.get_by_uid(uid)
+ if not account:
+ raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
+
+ # Get SSH key
+ key = account.get_ssh_key_by_hash_md5(hash_md5)
+ if not key:
+ raise tornado.web.HTTPError(404, "Could not find key: %s" % hash_md5)
+
+ self.render("people/ssh-keys/delete.html", account=account, key=key)
+
+ @tornado.web.authenticated
+ def post(self, uid, hash_md5):
+ account = self.backend.accounts.get_by_uid(uid)
+ if not account:
+ raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
+
+ # Get SSH key
+ key = account.get_ssh_key_by_hash_md5(hash_md5)
+ if not key:
+ raise tornado.web.HTTPError(404, "Could not find key: %s" % hash_md5)
+
+ # Verify password
+ password = self.get_argument("password")
+ if not account.check_password(password):
+ raise tornado.web.HTTPError(403, "Incorrect password for %s" % account)
+
+ # Delete the key
+ account.delete_ssh_key(key.keydata)
+
+ self.redirect("/users/%s/ssh-keys" % account.uid)
+
+
class SIPHandler(base.BaseHandler):
@tornado.web.authenticated
def get(self, uid):