Merge ~cjwatson/launchpad:archive-subscriber-signing-key-fingerprint into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 8549286e05b54cba75b69b5bf63649043532cba9
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:archive-subscriber-signing-key-fingerprint
Merge into: launchpad:master
Diff against target: 154 lines (+88/-8)
2 files modified
lib/lp/soyuz/browser/tests/test_archive_webservice.py (+82/-1)
lib/lp/soyuz/interfaces/archive.py (+6/-7)
Reviewer Review Type Date Requested Status
Thiago F. Pappacena (community) Approve
Review via email: mp+384345@code.launchpad.net

Commit message

Allow subscribers to see Archive.signing_key_fingerprint

Description of the change

They can already use Archive.getSigningKeyData, and by definition they're allowed to use the archive via apt, so it doesn't make sense to stop them seeing the fingerprint.

To post a comment you must log in.
Revision history for this message
Thiago F. Pappacena (pappacena) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/soyuz/browser/tests/test_archive_webservice.py b/lib/lp/soyuz/browser/tests/test_archive_webservice.py
2index 66be27d..b9ad921 100644
3--- a/lib/lp/soyuz/browser/tests/test_archive_webservice.py
4+++ b/lib/lp/soyuz/browser/tests/test_archive_webservice.py
5@@ -1,4 +1,4 @@
6-# Copyright 2010-2019 Canonical Ltd. This software is licensed under the
7+# Copyright 2010-2020 Canonical Ltd. This software is licensed under the
8 # GNU Affero General Public License version 3 (see the file LICENSE).
9
10 from __future__ import absolute_import, print_function, unicode_literals
11@@ -7,7 +7,9 @@ __metaclass__ = type
12
13 from datetime import timedelta
14 import json
15+import os.path
16
17+import responses
18 from testtools.matchers import (
19 Contains,
20 ContainsDict,
21@@ -18,9 +20,11 @@ from testtools.matchers import (
22 MatchesStructure,
23 )
24 from zope.component import getUtility
25+from zope.security.proxy import removeSecurityProxy
26
27 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
28 from lp.registry.interfaces.pocket import PackagePublishingPocket
29+from lp.services.gpg.interfaces import IGPGHandler
30 from lp.services.webapp.interfaces import OAuthPermission
31 from lp.soyuz.enums import (
32 ArchivePermissionType,
33@@ -39,6 +43,7 @@ from lp.testing import (
34 record_two_runs,
35 TestCaseWithFactory,
36 )
37+from lp.testing.gpgkeys import gpgkeysdir
38 from lp.testing.layers import DatabaseFunctionalLayer
39 from lp.testing.matchers import HasQueryCount
40 from lp.testing.pages import (
41@@ -134,6 +139,82 @@ class TestArchiveWebservice(TestCaseWithFactory):
42 self.assertEqual(401, ws.delete(ppa_url).status)
43
44
45+class TestSigningKey(TestCaseWithFactory):
46+ """Test signing-key-related information for archives.
47+
48+ We just use `responses` to mock the keyserver here; the details of its
49+ implementation aren't especially important, we can't use
50+ `InProcessKeyServerFixture` because the keyserver operations are
51+ synchronous, and `responses` is much faster than `KeyServerTac`.
52+ """
53+
54+ layer = DatabaseFunctionalLayer
55+
56+ def _setUpSigningKey(self, archive):
57+ key_path = os.path.join(gpgkeysdir, "ppa-sample@canonical.com.sec")
58+ gpghandler = getUtility(IGPGHandler)
59+ with open(key_path, "rb") as key_file:
60+ secret_key = gpghandler.importSecretKey(key_file.read())
61+ public_key = gpghandler.retrieveKey(secret_key.fingerprint)
62+ public_key_data = public_key.export()
63+ removeSecurityProxy(archive).signing_key_fingerprint = (
64+ public_key.fingerprint)
65+ key_url = gpghandler.getURLForKeyInServer(
66+ public_key.fingerprint, action="get")
67+ responses.add("GET", key_url, body=public_key_data)
68+ gpghandler.resetLocalState()
69+ return public_key.fingerprint, public_key_data
70+
71+ @responses.activate
72+ def test_signing_key_public(self):
73+ # Anyone can read signing key information for public archives.
74+ archive = self.factory.makeArchive()
75+ fingerprint, public_key_data = self._setUpSigningKey(archive)
76+ archive_url = api_url(archive)
77+ ws = webservice_for_person(
78+ self.factory.makePerson(), default_api_version="devel")
79+ ws_archive = self.getWebserviceJSON(ws, archive_url)
80+ self.assertEqual(fingerprint, ws_archive["signing_key_fingerprint"])
81+ response = ws.named_get(archive_url, "getSigningKeyData")
82+ self.assertEqual(200, response.status)
83+ self.assertEqual(public_key_data, response.jsonBody())
84+
85+ @responses.activate
86+ def test_signing_key_private_subscriber(self):
87+ # Subscribers can read signing key information for private archives.
88+ archive = self.factory.makeArchive(private=True)
89+ fingerprint, public_key_data = self._setUpSigningKey(archive)
90+ subscriber = self.factory.makePerson()
91+ with person_logged_in(archive.owner):
92+ archive.newSubscription(subscriber, archive.owner)
93+ archive_url = api_url(archive)
94+ ws = webservice_for_person(
95+ subscriber, permission=OAuthPermission.READ_PRIVATE,
96+ default_api_version="devel")
97+ ws_archive = self.getWebserviceJSON(ws, archive_url)
98+ self.assertEqual(fingerprint, ws_archive["signing_key_fingerprint"])
99+ response = ws.named_get(archive_url, "getSigningKeyData")
100+ self.assertEqual(200, response.status)
101+ self.assertEqual(public_key_data, response.jsonBody())
102+
103+ @responses.activate
104+ def test_signing_key_private_non_subscriber(self):
105+ # Non-subscribers cannot read signing key information (or indeed
106+ # anything else) for private archives.
107+ archive = self.factory.makeArchive(private=True)
108+ fingerprint, public_key_data = self._setUpSigningKey(archive)
109+ archive_url = api_url(archive)
110+ ws = webservice_for_person(
111+ self.factory.makePerson(), permission=OAuthPermission.READ_PRIVATE,
112+ default_api_version="devel")
113+ ws_archive = self.getWebserviceJSON(ws, archive_url)
114+ self.assertEqual(
115+ "tag:launchpad.net:2008:redacted",
116+ ws_archive["signing_key_fingerprint"])
117+ response = ws.named_get(archive_url, "getSigningKeyData")
118+ self.assertEqual(401, response.status)
119+
120+
121 class TestExternalDependencies(TestCaseWithFactory):
122
123 layer = DatabaseFunctionalLayer
124diff --git a/lib/lp/soyuz/interfaces/archive.py b/lib/lp/soyuz/interfaces/archive.py
125index 6347193..8d34ec4 100644
126--- a/lib/lp/soyuz/interfaces/archive.py
127+++ b/lib/lp/soyuz/interfaces/archive.py
128@@ -461,6 +461,12 @@ class IArchiveSubscriberView(Interface):
129 "explicit publish flag and any other constraints."))
130 series_with_sources = Attribute(
131 "DistroSeries to which this archive has published sources")
132+ signing_key_fingerprint = exported(
133+ Text(
134+ title=_("Archive signing key fingerprint"), required=False,
135+ description=_("A OpenPGP signing key fingerprint (40 chars) "
136+ "for this PPA or None if there is no signing "
137+ "key available.")))
138 signing_key = Object(
139 title=_('Repository signing key.'), required=False, schema=IGPGKey)
140
141@@ -1183,13 +1189,6 @@ class IArchiveView(IHasBuildRecords):
142 description=_(
143 "The password used by the build farm to access the archive."))
144
145- signing_key_fingerprint = exported(
146- Text(
147- title=_("Archive signing key fingerprint"), required=False,
148- description=_("A OpenPGP signing key fingerprint (40 chars) "
149- "for this PPA or None if there is no signing "
150- "key available.")))
151-
152 @call_with(eager_load=True)
153 @rename_parameters_as(
154 name="binary_name", distroarchseries="distro_arch_series")

Subscribers

People subscribed via source and target branches

to status/vote changes: