← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:export-sshkey-getFullKeyText into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:export-sshkey-getFullKeyText into launchpad:master.

Commit message:
Export ISSHKey.getFullKeyText

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/446623

There are currently several cron jobs on loganberry that export email and SSH key data for the members of several teams, using `scripts/list-team-members`; this information is used as part of various integrations such as lists.ubuntu.com posting privileges, the ability for Ubuntu members to upload files to people.ubuntu.com using SFTP, and so on.  However, doing this with ad-hoc cron jobs on a Launchpad machine isn't ideal; it would be better if it were possible to get this information over the API, and then IS would only need a suitably-privileged bot account.

As far as I can see, the only API gap here is that we can only extract the raw key text for an SSH key, and not the "full" key text that's decorated with the key type and comment in a format suitable for adding to an OpenSSH `authorized_keys` file.  That transformation isn't quite trivial, so export a method that does it on the webservice.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:export-sshkey-getFullKeyText into launchpad:master.
diff --git a/lib/lp/registry/browser/person.py b/lib/lp/registry/browser/person.py
index 3c67245..3a61179 100644
--- a/lib/lp/registry/browser/person.py
+++ b/lib/lp/registry/browser/person.py
@@ -627,6 +627,14 @@ class PersonNavigation(BranchTraversalMixin, Navigation):
             raise NotFoundError(name)
         return snap
 
+    @stepthrough("+ssh-keys")
+    def traverse_ssh_keys(self, id):
+        """Traverse to this person's SSH keys."""
+        ssh_key = getUtility(ISSHKeySet).getByID(id)
+        if ssh_key is None or ssh_key.person != self.context:
+            return None
+        return ssh_key
+
 
 class PersonSetNavigation(Navigation):
     usedfor = IPersonSet
diff --git a/lib/lp/registry/interfaces/ssh.py b/lib/lp/registry/interfaces/ssh.py
index 5590a99..5559526 100644
--- a/lib/lp/registry/interfaces/ssh.py
+++ b/lib/lp/registry/interfaces/ssh.py
@@ -16,8 +16,10 @@ import http.client
 from lazr.enum import DBEnumeratedType, DBItem
 from lazr.restful.declarations import (
     error_status,
+    export_read_operation,
     exported,
     exported_as_webservice_entry,
+    operation_for_version,
 )
 from zope.interface import Interface
 from zope.schema import Choice, Int, TextLine
@@ -107,6 +109,8 @@ class ISSHKey(Interface):
     def destroySelf():
         """Remove this SSHKey from the database."""
 
+    @export_read_operation()
+    @operation_for_version("devel")
     def getFullKeyText():
         """Get the full text of the SSH key."""
 
diff --git a/lib/lp/registry/tests/test_ssh.py b/lib/lp/registry/tests/test_ssh.py
index 3353e66..a3448bf 100644
--- a/lib/lp/registry/tests/test_ssh.py
+++ b/lib/lp/registry/tests/test_ssh.py
@@ -12,9 +12,16 @@ from lp.registry.interfaces.ssh import (
     ISSHKeySet,
     SSHKeyAdditionError,
 )
-from lp.testing import TestCaseWithFactory, admin_logged_in, person_logged_in
+from lp.services.webapp.interfaces import OAuthPermission
+from lp.testing import (
+    TestCaseWithFactory,
+    admin_logged_in,
+    api_url,
+    person_logged_in,
+)
 from lp.testing.layers import DatabaseFunctionalLayer
 from lp.testing.mail_helpers import pop_notifications
+from lp.testing.pages import webservice_for_person
 
 
 class TestSSHKey(TestCaseWithFactory):
@@ -298,3 +305,22 @@ class TestSSHKeySet(TestCaseWithFactory):
         self.assertContentEqual(
             [person1_key1, person1_key2, person2_key1], keys
         )
+
+
+class TestSSHKeyWebservice(TestCaseWithFactory):
+    layer = DatabaseFunctionalLayer
+
+    def test_getFullKeyText(self):
+        person = self.factory.makePerson()
+        with person_logged_in(person):
+            key = self.factory.makeSSHKey(person, "ssh-rsa")
+            key_url = api_url(key)
+        expected = "ssh-rsa %s %s" % (key.keytext, key.comment)
+        webservice = webservice_for_person(
+            person,
+            permission=OAuthPermission.READ_PUBLIC,
+            default_api_version="devel",
+        )
+        response = webservice.named_get(key_url, "getFullKeyText")
+        self.assertEqual(200, response.status)
+        self.assertEqual(expected, response.jsonBody())