]> git.ipfire.org Git - ipfire.org.git/commitdiff
people: Add UI to upload/delete SSH keys
authorMichael Tremer <michael.tremer@ipfire.org>
Thu, 18 Oct 2018 09:34:06 +0000 (10:34 +0100)
committerMichael Tremer <michael.tremer@ipfire.org>
Thu, 18 Oct 2018 09:34:06 +0000 (10:34 +0100)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/backend/accounts.py
src/templates/people/ssh-keys/delete.html [new file with mode: 0644]
src/templates/people/ssh-keys/error-invalid-key.html [new file with mode: 0644]
src/templates/people/ssh-keys/error.html [new file with mode: 0644]
src/templates/people/ssh-keys/upload.html [new file with mode: 0644]
src/web/__init__.py
src/web/people.py

index 64283de722a490f56344e884b6e73a3c757957bd..f5bc4f17da73ca5a036c02ea05ab1086ada5f1f8 100644 (file)
@@ -178,7 +178,11 @@ templates_people_modulesdir = $(templates_peopledir)/modules
 
 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
 
index 361f29b7874bb6fdfd32574ee5f6afa6cd6b2d21..9f67e4a460271081ed74357dacab6e2567dace00 100644 (file)
@@ -247,6 +247,32 @@ class Account(Object):
        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
@@ -260,6 +286,9 @@ class Account(Object):
 
                        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
@@ -597,6 +626,38 @@ class Account(Object):
 
                        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()
diff --git a/src/templates/people/ssh-keys/delete.html b/src/templates/people/ssh-keys/delete.html
new file mode 100644 (file)
index 0000000..3ab79a7
--- /dev/null
@@ -0,0 +1,38 @@
+{% 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 %}
diff --git a/src/templates/people/ssh-keys/error-invalid-key.html b/src/templates/people/ssh-keys/error-invalid-key.html
new file mode 100644 (file)
index 0000000..9a2a10c
--- /dev/null
@@ -0,0 +1,7 @@
+{% extends "error.html" %}
+
+{% block reason %}
+       <p class="card-text">
+               {{ _("The uploaded key is invalid: %s") % exception }}
+       </p>
+{% end block %}
diff --git a/src/templates/people/ssh-keys/error.html b/src/templates/people/ssh-keys/error.html
new file mode 100644 (file)
index 0000000..a2e11a6
--- /dev/null
@@ -0,0 +1,16 @@
+{% 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 %}
diff --git a/src/templates/people/ssh-keys/upload.html b/src/templates/people/ssh-keys/upload.html
new file mode 100644 (file)
index 0000000..7a5c7b6
--- /dev/null
@@ -0,0 +1,42 @@
+{% 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 %}
index f0099c19d3384ca970db84aa5ecaeb608ec4b6d9..041839cec02fe3f64e2fb6a847709ce92f1d81c0 100644 (file)
@@ -261,7 +261,9 @@ class Application(tornado.web.Application):
                        (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)
 
index d714cae3702a553438cc6dc37cb2e4178cc2f60a..8b537bfeb81e50271c756b1c858242903391d821 100644 (file)
@@ -3,6 +3,7 @@
 import datetime
 import ldap
 import logging
+import sshpubkeys
 import tornado.web
 
 from . import handlers_base as base
@@ -131,6 +132,83 @@ class SSHKeysDownloadHandler(base.BaseHandler):
                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):