← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:authserver-fixture into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:authserver-fixture into launchpad:master.

Commit message:
Move in-process authserver to lp.services.authserver.testing

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

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

Since this is used by multiple test files, it should have a proper home rather than being imported from lp.snappy.tests.test_snapbuildbehaviour.  Also make the fixture a bit more general by pointing more configuration entries at the in-process authserver, and refactor some librarian tests to use it.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:authserver-fixture into launchpad:master.
diff --git a/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py b/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py
index 2337a42..1fe69f3 100644
--- a/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py
+++ b/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py
@@ -77,13 +77,13 @@ from lp.buildmaster.tests.test_buildfarmjobbehaviour import (
 from lp.oci.interfaces.ocirecipe import OCI_RECIPE_ALLOW_CREATE
 from lp.oci.model.ocirecipebuildbehaviour import OCIRecipeBuildBehaviour
 from lp.registry.interfaces.series import SeriesStatus
+from lp.services.authserver.testing import InProcessAuthServerFixture
 from lp.services.config import config
 from lp.services.features.testing import FeatureFixture
 from lp.services.log.logger import DevNullLogger
 from lp.services.propertycache import get_property_cache
 from lp.services.statsd.tests import StatsMixin
 from lp.services.webapp import canonical_url
-from lp.snappy.tests.test_snapbuildbehaviour import InProcessAuthServerFixture
 from lp.soyuz.adapters.archivedependencies import (
     get_sources_list_for_building,
     )
diff --git a/lib/lp/services/authserver/testing.py b/lib/lp/services/authserver/testing.py
new file mode 100644
index 0000000..ab47c87
--- /dev/null
+++ b/lib/lp/services/authserver/testing.py
@@ -0,0 +1,60 @@
+# Copyright 2021 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""In-process authserver fixture."""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+    'InProcessAuthServerFixture',
+    ]
+
+from textwrap import dedent
+
+import fixtures
+from twisted.internet import reactor
+from twisted.web import (
+    server,
+    xmlrpc,
+    )
+from zope.component import getUtility
+from zope.publisher.xmlrpc import TestRequest
+
+from lp.services.authserver.xmlrpc import AuthServerAPIView
+from lp.services.config import config
+from lp.xmlrpc.interfaces import IPrivateApplication
+
+
+class InProcessAuthServer(xmlrpc.XMLRPC):
+
+    def __init__(self, *args, **kwargs):
+        xmlrpc.XMLRPC.__init__(self, *args, **kwargs)
+        private_root = getUtility(IPrivateApplication)
+        self.authserver = AuthServerAPIView(
+            private_root.authserver, TestRequest())
+
+    def __getattr__(self, name):
+        if name.startswith("xmlrpc_"):
+            return getattr(self.authserver, name[len("xmlrpc_"):])
+        else:
+            raise AttributeError("%r has no attribute '%s'" % name)
+
+
+class InProcessAuthServerFixture(fixtures.Fixture, xmlrpc.XMLRPC):
+    """A fixture that runs an in-process authserver."""
+
+    def _setUp(self):
+        listener = reactor.listenTCP(0, server.Site(InProcessAuthServer()))
+        self.addCleanup(listener.stopListening)
+        config.push("in-process-auth-server-fixture", dedent("""
+            [builddmaster]
+            authentication_endpoint: http://localhost:{port}/
+
+            [codehosting]
+            authentication_endpoint: http://localhost:{port}/
+
+            [librarian]
+            authentication_endpoint: http://localhost:{port}/
+            """).format(port=listener.getHost().port))
+        self.addCleanup(config.pop, "in-process-auth-server-fixture")
diff --git a/lib/lp/services/librarianserver/tests/test_db.py b/lib/lp/services/librarianserver/tests/test_db.py
index ac1552e..5329ae4 100644
--- a/lib/lp/services/librarianserver/tests/test_db.py
+++ b/lib/lp/services/librarianserver/tests/test_db.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2021 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 from __future__ import absolute_import, print_function, unicode_literals
@@ -10,23 +10,25 @@ from pymacaroons import Macaroon
 from testtools.testcase import ExpectedException
 from testtools.twistedsupport import AsynchronousDeferredRunTest
 import transaction
-from twisted.internet import (
-    defer,
-    reactor,
-    )
+from twisted.internet import defer
 from twisted.internet.threads import deferToThread
-from twisted.web import (
-    server,
-    xmlrpc,
-    )
+from zope.interface import implementer
 
+from lp.services.authserver.testing import InProcessAuthServerFixture
 from lp.services.database.interfaces import IStore
+from lp.services.librarian.interfaces import ILibraryFileAlias
 from lp.services.librarian.model import LibraryFileContent
 from lp.services.librarianserver import db
+from lp.services.macaroons.interfaces import (
+    BadMacaroonContext,
+    IMacaroonIssuer,
+    NO_USER,
+    )
+from lp.services.macaroons.model import MacaroonIssuerBase
 from lp.testing import TestCase
 from lp.testing.dbuser import switch_dbuser
+from lp.testing.fixture import ZopeUtilityFixture
 from lp.testing.layers import LaunchpadZopelessLayer
-from lp.xmlrpc import faults
 
 
 class DBTestCase(TestCase):
@@ -61,26 +63,31 @@ class DBTestCase(TestCase):
         self.assertEqual('text/unknown', alias.mimetype)
 
 
-class FakeAuthServer(xmlrpc.XMLRPC):
-    """A fake authserver.
+@implementer(IMacaroonIssuer)
+class DummyMacaroonIssuer(MacaroonIssuerBase):
 
-    This saves us from needing to start an appserver in tests.
-    """
+    identifier = 'test'
+    _root_secret = 'test'
+    _verified_user = NO_USER
 
-    def __init__(self):
-        xmlrpc.XMLRPC.__init__(self)
-        self.macaroons = set()
+    def checkIssuingContext(self, context, **kwargs):
+        """See `MacaroonIssuerBase`."""
+        if not ILibraryFileAlias.providedBy(context):
+            raise BadMacaroonContext(context)
+        return context.id
 
-    def registerMacaroon(self, macaroon, context):
-        self.macaroons.add((macaroon.serialize(), context))
+    def checkVerificationContext(self, context, **kwargs):
+        """See `IMacaroonIssuerBase`."""
+        if not ILibraryFileAlias.providedBy(context):
+            raise BadMacaroonContext(context)
+        return context
 
-    def xmlrpc_verifyMacaroon(self, macaroon_raw, context_type, context):
-        if context_type != 'LibraryFileAlias':
-            return faults.Unauthorized()
-        if (macaroon_raw, context) in self.macaroons:
-            return True
-        else:
-            return faults.Unauthorized()
+    def verifyPrimaryCaveat(self, verified, caveat_value, context, **kwargs):
+        """See `MacaroonIssuerBase`."""
+        ok = caveat_value == str(context.id)
+        if ok:
+            verified.user = self._verified_user
+        return ok
 
 
 class TestLibrarianStuff(TestCase):
@@ -144,28 +151,19 @@ class TestLibrarianStuff(TestCase):
         self.assertRaises(
             LookupError, unrestricted_library.getAlias, 1, None, '/')
 
-    def setUpAuthServer(self):
-        authserver = FakeAuthServer()
-        authserver_listener = reactor.listenTCP(0, server.Site(authserver))
-        self.addCleanup(authserver_listener.stopListening)
-        authserver_port = authserver_listener.getHost().port
-        authserver_url = 'http://localhost:%d/' % authserver_port
-        self.pushConfig('librarian', authentication_endpoint=authserver_url)
-        return authserver
-
     @defer.inlineCallbacks
     def test_getAlias_with_macaroon(self):
         # Library.getAlias() uses the authserver to verify macaroons.
-        authserver = self.setUpAuthServer()
+        issuer = DummyMacaroonIssuer()
+        self.useFixture(ZopeUtilityFixture(
+            issuer, IMacaroonIssuer, name='test'))
+        self.useFixture(InProcessAuthServerFixture())
         unrestricted_library = db.Library(restricted=False)
         alias = unrestricted_library.getAlias(1, None, '/')
         alias.restricted = True
         transaction.commit()
         restricted_library = db.Library(restricted=True)
-        macaroon = Macaroon()
-        with ExpectedException(LookupError):
-            yield deferToThread(restricted_library.getAlias, 1, macaroon, '/')
-        authserver.registerMacaroon(macaroon, 1)
+        macaroon = issuer.issueMacaroon(alias)
         alias = yield deferToThread(
             restricted_library.getAlias, 1, macaroon, '/')
         self.assertEqual(1, alias.id)
@@ -173,13 +171,16 @@ class TestLibrarianStuff(TestCase):
     @defer.inlineCallbacks
     def test_getAlias_with_wrong_macaroon(self):
         # A macaroon for a different LFA doesn't work.
-        authserver = self.setUpAuthServer()
+        issuer = DummyMacaroonIssuer()
+        self.useFixture(ZopeUtilityFixture(
+            issuer, IMacaroonIssuer, name='test'))
+        self.useFixture(InProcessAuthServerFixture())
         unrestricted_library = db.Library(restricted=False)
         alias = unrestricted_library.getAlias(1, None, '/')
         alias.restricted = True
+        other_alias = unrestricted_library.getAlias(2, None, '/')
         transaction.commit()
-        macaroon = Macaroon()
-        authserver.registerMacaroon(macaroon, 2)
+        macaroon = issuer.issueMacaroon(other_alias)
         restricted_library = db.Library(restricted=True)
         with ExpectedException(LookupError):
             yield deferToThread(restricted_library.getAlias, 1, macaroon, '/')
diff --git a/lib/lp/snappy/tests/test_snapbuildbehaviour.py b/lib/lp/snappy/tests/test_snapbuildbehaviour.py
index c64749d..d223596 100644
--- a/lib/lp/snappy/tests/test_snapbuildbehaviour.py
+++ b/lib/lp/snappy/tests/test_snapbuildbehaviour.py
@@ -1,4 +1,4 @@
-# Copyright 2015-2020 Canonical Ltd.  This software is licensed under the
+# Copyright 2015-2021 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Test snap package build behaviour."""
@@ -34,17 +34,9 @@ from testtools.twistedsupport import (
     AsynchronousDeferredRunTestForBrokenTwisted,
     )
 import transaction
-from twisted.internet import (
-    defer,
-    reactor,
-    )
-from twisted.web import (
-    server,
-    xmlrpc,
-    )
+from twisted.internet import defer
 from zope.component import getUtility
 from zope.proxy import isProxy
-from zope.publisher.xmlrpc import TestRequest
 from zope.security.proxy import removeSecurityProxy
 
 from lp.app.enums import InformationType
@@ -78,7 +70,7 @@ from lp.buildmaster.tests.test_buildfarmjobbehaviour import (
     )
 from lp.registry.interfaces.pocket import PackagePublishingPocket
 from lp.registry.interfaces.series import SeriesStatus
-from lp.services.authserver.xmlrpc import AuthServerAPIView
+from lp.services.authserver.testing import InProcessAuthServerFixture
 from lp.services.config import config
 from lp.services.features.testing import FeatureFixture
 from lp.services.log.logger import (
@@ -114,35 +106,6 @@ from lp.testing.gpgkeys import (
     )
 from lp.testing.keyserver import InProcessKeyServerFixture
 from lp.testing.layers import LaunchpadZopelessLayer
-from lp.xmlrpc.interfaces import IPrivateApplication
-
-
-class InProcessAuthServer(xmlrpc.XMLRPC):
-
-    def __init__(self, *args, **kwargs):
-        xmlrpc.XMLRPC.__init__(self, *args, **kwargs)
-        private_root = getUtility(IPrivateApplication)
-        self.authserver = AuthServerAPIView(
-            private_root.authserver, TestRequest())
-
-    def __getattr__(self, name):
-        if name.startswith("xmlrpc_"):
-            return getattr(self.authserver, name[len("xmlrpc_"):])
-        else:
-            raise AttributeError("%r has no attribute '%s'" % name)
-
-
-class InProcessAuthServerFixture(fixtures.Fixture, xmlrpc.XMLRPC):
-    """A fixture that runs an in-process authserver."""
-
-    def _setUp(self):
-        listener = reactor.listenTCP(0, server.Site(InProcessAuthServer()))
-        self.addCleanup(listener.stopListening)
-        config.push("in-process-auth-server-fixture", dedent("""
-            [builddmaster]
-            authentication_endpoint: http://localhost:%d/
-            """) % listener.getHost().port)
-        self.addCleanup(config.pop, "in-process-auth-server-fixture")
 
 
 class FormatAsRfc3339TestCase(TestCase):