Merge ~cjwatson/launchpad:cibuild-macaroons into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 163146dfdb0fde5d0a9496a5729c0e73ab08c020
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:cibuild-macaroons
Merge into: launchpad:master
Diff against target: 466 lines (+315/-5)
6 files modified
lib/lp/code/configure.zcml (+8/-0)
lib/lp/code/model/cibuild.py (+63/-0)
lib/lp/code/model/tests/test_cibuild.py (+149/-0)
lib/lp/code/xmlrpc/tests/test_git.py (+84/-0)
lib/lp/services/authserver/interfaces.py (+4/-3)
lib/lp/services/authserver/xmlrpc.py (+7/-2)
Reviewer Review Type Date Requested Status
Jürgen Gmach Approve
Review via email: mp+419976@code.launchpad.net

Commit message

Add CIBuild macaroons

Description of the change

This implements the same macaroon-based authorization protocol used by several other build types, allowing builders to clone private repositories associated with the CI build they're running.

To post a comment you must log in.
Revision history for this message
Jürgen Gmach (jugmac00) wrote :

Looks good from comparing with the other build types (not that I fully understand every bit) :)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/lib/lp/code/configure.zcml b/lib/lp/code/configure.zcml
index 882d3da..99a5bf0 100644
--- a/lib/lp/code/configure.zcml
+++ b/lib/lp/code/configure.zcml
@@ -1307,6 +1307,14 @@
1307 <allow interface="lp.buildmaster.interfaces.buildfarmjob.ISpecificBuildFarmJobSource" />1307 <allow interface="lp.buildmaster.interfaces.buildfarmjob.ISpecificBuildFarmJobSource" />
1308 </securedutility>1308 </securedutility>
13091309
1310 <!-- CIBuildMacaroonIssuer -->
1311 <securedutility
1312 class="lp.code.model.cibuild.CIBuildMacaroonIssuer"
1313 provides="lp.services.macaroons.interfaces.IMacaroonIssuer"
1314 name="ci-build">
1315 <allow interface="lp.services.macaroons.interfaces.IMacaroonIssuerPublic" />
1316 </securedutility>
1317
1310 <!-- CIBuildBehaviour -->1318 <!-- CIBuildBehaviour -->
1311 <adapter1319 <adapter
1312 for="lp.code.interfaces.cibuild.ICIBuild"1320 for="lp.code.interfaces.cibuild.ICIBuild"
diff --git a/lib/lp/code/model/cibuild.py b/lib/lp/code/model/cibuild.py
index 912d31f..dd2390f 100644
--- a/lib/lp/code/model/cibuild.py
+++ b/lib/lp/code/model/cibuild.py
@@ -25,6 +25,7 @@ from storm.store import EmptyResultSet
25from zope.component import getUtility25from zope.component import getUtility
26from zope.event import notify26from zope.event import notify
27from zope.interface import implementer27from zope.interface import implementer
28from zope.security.proxy import removeSecurityProxy
2829
29from lp.app.errors import NotFoundError30from lp.app.errors import NotFoundError
30from lp.app.interfaces.launchpad import ILaunchpadCelebrities31from lp.app.interfaces.launchpad import ILaunchpadCelebrities
@@ -51,6 +52,7 @@ from lp.code.interfaces.cibuild import (
51 MissingConfiguration,52 MissingConfiguration,
52 )53 )
53from lp.code.interfaces.githosting import IGitHostingClient54from lp.code.interfaces.githosting import IGitHostingClient
55from lp.code.interfaces.gitrepository import IGitRepository
54from lp.code.interfaces.revisionstatus import IRevisionStatusReportSet56from lp.code.interfaces.revisionstatus import IRevisionStatusReportSet
55from lp.code.model.gitref import GitRef57from lp.code.model.gitref import GitRef
56from lp.code.model.lpcraft import load_configuration58from lp.code.model.lpcraft import load_configuration
@@ -72,6 +74,12 @@ from lp.services.librarian.model import (
72 LibraryFileAlias,74 LibraryFileAlias,
73 LibraryFileContent,75 LibraryFileContent,
74 )76 )
77from lp.services.macaroons.interfaces import (
78 BadMacaroonContext,
79 IMacaroonIssuer,
80 NO_USER,
81 )
82from lp.services.macaroons.model import MacaroonIssuerBase
75from lp.services.propertycache import cachedproperty83from lp.services.propertycache import cachedproperty
76from lp.soyuz.model.distroarchseries import DistroArchSeries84from lp.soyuz.model.distroarchseries import DistroArchSeries
7785
@@ -596,3 +604,58 @@ class CIBuildSet(SpecificBuildFarmJobSourceMixin):
596 def deleteByGitRepository(self, git_repository):604 def deleteByGitRepository(self, git_repository):
597 """See `ICIBuildSet`."""605 """See `ICIBuildSet`."""
598 self.findByGitRepository(git_repository).remove()606 self.findByGitRepository(git_repository).remove()
607
608
609@implementer(IMacaroonIssuer)
610class CIBuildMacaroonIssuer(MacaroonIssuerBase):
611
612 identifier = "ci-build"
613 issuable_via_authserver = True
614
615 def checkIssuingContext(self, context, **kwargs):
616 """See `MacaroonIssuerBase`.
617
618 For issuing, the context is an `ICIBuild`.
619 """
620 if not ICIBuild.providedBy(context):
621 raise BadMacaroonContext(context)
622 return removeSecurityProxy(context).id
623
624 def checkVerificationContext(self, context, **kwargs):
625 """See `MacaroonIssuerBase`."""
626 if not IGitRepository.providedBy(context):
627 raise BadMacaroonContext(context)
628 return context
629
630 def verifyPrimaryCaveat(self, verified, caveat_value, context, user=None,
631 **kwargs):
632 """See `MacaroonIssuerBase`.
633
634 For verification, the context is an `IGitRepository`. We check that
635 the repository or archive is needed to build the `ICIBuild` that is
636 the context of the macaroon, and that the context build is currently
637 building.
638 """
639 # CI builds only support free-floating macaroons for Git
640 # authentication, not ones bound to a user.
641 if user:
642 return False
643 verified.user = NO_USER
644
645 if context is None:
646 # We're only verifying that the macaroon could be valid for some
647 # context.
648 return True
649 if not IGitRepository.providedBy(context):
650 return False
651
652 try:
653 build_id = int(caveat_value)
654 except ValueError:
655 return False
656 clauses = [
657 CIBuild.id == build_id,
658 CIBuild.status == BuildStatus.BUILDING,
659 CIBuild.git_repository == context,
660 ]
661 return not IStore(CIBuild).find(CIBuild, *clauses).is_empty()
diff --git a/lib/lp/code/model/tests/test_cibuild.py b/lib/lp/code/model/tests/test_cibuild.py
index d4cc4ec..3f9682e 100644
--- a/lib/lp/code/model/tests/test_cibuild.py
+++ b/lib/lp/code/model/tests/test_cibuild.py
@@ -12,14 +12,17 @@ from textwrap import dedent
12from unittest.mock import Mock12from unittest.mock import Mock
1313
14from fixtures import MockPatchObject14from fixtures import MockPatchObject
15from pymacaroons import Macaroon
15import pytz16import pytz
16from storm.locals import Store17from storm.locals import Store
17from testtools.matchers import (18from testtools.matchers import (
18 Equals,19 Equals,
20 MatchesListwise,
19 MatchesSetwise,21 MatchesSetwise,
20 MatchesStructure,22 MatchesStructure,
21 )23 )
22from zope.component import getUtility24from zope.component import getUtility
25from zope.publisher.xmlrpc import TestRequest
23from zope.security.proxy import removeSecurityProxy26from zope.security.proxy import removeSecurityProxy
2427
25from lp.app.enums import InformationType28from lp.app.enums import InformationType
@@ -53,7 +56,11 @@ from lp.code.model.cibuild import (
53from lp.code.model.lpcraft import load_configuration56from lp.code.model.lpcraft import load_configuration
54from lp.code.tests.helpers import GitHostingFixture57from lp.code.tests.helpers import GitHostingFixture
55from lp.registry.interfaces.series import SeriesStatus58from lp.registry.interfaces.series import SeriesStatus
59from lp.services.authserver.xmlrpc import AuthServerAPIView
60from lp.services.config import config
56from lp.services.log.logger import BufferLogger61from lp.services.log.logger import BufferLogger
62from lp.services.macaroons.interfaces import IMacaroonIssuer
63from lp.services.macaroons.testing import MacaroonTestMixin
57from lp.services.propertycache import clear_property_cache64from lp.services.propertycache import clear_property_cache
58from lp.testing import (65from lp.testing import (
59 person_logged_in,66 person_logged_in,
@@ -62,6 +69,7 @@ from lp.testing import (
62 )69 )
63from lp.testing.layers import LaunchpadZopelessLayer70from lp.testing.layers import LaunchpadZopelessLayer
64from lp.testing.matchers import HasQueryCount71from lp.testing.matchers import HasQueryCount
72from lp.xmlrpc.interfaces import IPrivateApplication
6573
6674
67class TestGetAllCommitsForPaths(TestCaseWithFactory):75class TestGetAllCommitsForPaths(TestCaseWithFactory):
@@ -983,3 +991,144 @@ class TestDetermineDASesToBuild(TestCaseWithFactory):
983 "name in Ubuntu %s\n" % distro_series.name,991 "name in Ubuntu %s\n" % distro_series.name,
984 logger.getLogBuffer()992 logger.getLogBuffer()
985 )993 )
994
995
996class TestCIBuildMacaroonIssuer(MacaroonTestMixin, TestCaseWithFactory):
997 """Test CIBuild macaroon issuing and verification."""
998
999 layer = LaunchpadZopelessLayer
1000
1001 def setUp(self):
1002 super().setUp()
1003 self.pushConfig(
1004 "launchpad", internal_macaroon_secret_key="some-secret")
1005
1006 def test_issueMacaroon_good(self):
1007 build = self.factory.makeCIBuild(
1008 git_repository=self.factory.makeGitRepository(
1009 information_type=InformationType.USERDATA))
1010 issuer = getUtility(IMacaroonIssuer, "ci-build")
1011 macaroon = removeSecurityProxy(issuer).issueMacaroon(build)
1012 self.assertThat(macaroon, MatchesStructure(
1013 location=Equals("launchpad.test"),
1014 identifier=Equals("ci-build"),
1015 caveats=MatchesListwise([
1016 MatchesStructure.byEquality(
1017 caveat_id="lp.ci-build %s" % build.id),
1018 ])))
1019
1020 def test_issueMacaroon_via_authserver(self):
1021 build = self.factory.makeCIBuild(
1022 git_repository=self.factory.makeGitRepository(
1023 information_type=InformationType.USERDATA))
1024 private_root = getUtility(IPrivateApplication)
1025 authserver = AuthServerAPIView(private_root.authserver, TestRequest())
1026 macaroon = Macaroon.deserialize(
1027 authserver.issueMacaroon("ci-build", "CIBuild", build.id))
1028 self.assertThat(macaroon, MatchesStructure(
1029 location=Equals("launchpad.test"),
1030 identifier=Equals("ci-build"),
1031 caveats=MatchesListwise([
1032 MatchesStructure.byEquality(
1033 caveat_id="lp.ci-build %s" % build.id),
1034 ])))
1035
1036 def test_verifyMacaroon_good_repository(self):
1037 build = self.factory.makeCIBuild(
1038 git_repository=self.factory.makeGitRepository(
1039 information_type=InformationType.USERDATA))
1040 build.updateStatus(BuildStatus.BUILDING)
1041 issuer = removeSecurityProxy(getUtility(IMacaroonIssuer, "ci-build"))
1042 macaroon = issuer.issueMacaroon(build)
1043 self.assertMacaroonVerifies(issuer, macaroon, build.git_repository)
1044
1045 def test_verifyMacaroon_good_no_context(self):
1046 build = self.factory.makeCIBuild(
1047 git_repository=self.factory.makeGitRepository(
1048 information_type=InformationType.USERDATA))
1049 build.updateStatus(BuildStatus.BUILDING)
1050 issuer = removeSecurityProxy(getUtility(IMacaroonIssuer, "ci-build"))
1051 macaroon = issuer.issueMacaroon(build)
1052 self.assertMacaroonVerifies(
1053 issuer, macaroon, None, require_context=False)
1054 self.assertMacaroonVerifies(
1055 issuer, macaroon, build.git_repository, require_context=False)
1056
1057 def test_verifyMacaroon_no_context_but_require_context(self):
1058 build = self.factory.makeCIBuild(
1059 git_repository=self.factory.makeGitRepository(
1060 information_type=InformationType.USERDATA))
1061 build.updateStatus(BuildStatus.BUILDING)
1062 issuer = removeSecurityProxy(getUtility(IMacaroonIssuer, "ci-build"))
1063 macaroon = issuer.issueMacaroon(build)
1064 self.assertMacaroonDoesNotVerify(
1065 ["Expected macaroon verification context but got None."],
1066 issuer, macaroon, None)
1067
1068 def test_verifyMacaroon_wrong_location(self):
1069 build = self.factory.makeCIBuild(
1070 git_repository=self.factory.makeGitRepository(
1071 information_type=InformationType.USERDATA))
1072 build.updateStatus(BuildStatus.BUILDING)
1073 issuer = removeSecurityProxy(getUtility(IMacaroonIssuer, "ci-build"))
1074 macaroon = Macaroon(
1075 location="another-location", key=issuer._root_secret)
1076 self.assertMacaroonDoesNotVerify(
1077 ["Macaroon has unknown location 'another-location'."],
1078 issuer, macaroon, build.git_repository)
1079 self.assertMacaroonDoesNotVerify(
1080 ["Macaroon has unknown location 'another-location'."],
1081 issuer, macaroon, build.git_repository, require_context=False)
1082
1083 def test_verifyMacaroon_wrong_key(self):
1084 build = self.factory.makeCIBuild(
1085 git_repository=self.factory.makeGitRepository(
1086 information_type=InformationType.USERDATA))
1087 build.updateStatus(BuildStatus.BUILDING)
1088 issuer = removeSecurityProxy(getUtility(IMacaroonIssuer, "ci-build"))
1089 macaroon = Macaroon(
1090 location=config.vhost.mainsite.hostname, key="another-secret")
1091 self.assertMacaroonDoesNotVerify(
1092 ["Signatures do not match"],
1093 issuer, macaroon, build.git_repository)
1094 self.assertMacaroonDoesNotVerify(
1095 ["Signatures do not match"],
1096 issuer, macaroon, build.git_repository, require_context=False)
1097
1098 def test_verifyMacaroon_not_building(self):
1099 build = self.factory.makeCIBuild(
1100 git_repository=self.factory.makeGitRepository(
1101 information_type=InformationType.USERDATA))
1102 issuer = removeSecurityProxy(
1103 getUtility(IMacaroonIssuer, "ci-build"))
1104 macaroon = issuer.issueMacaroon(build)
1105 self.assertMacaroonDoesNotVerify(
1106 ["Caveat check for 'lp.ci-build %s' failed." % build.id],
1107 issuer, macaroon, build.git_repository)
1108
1109 def test_verifyMacaroon_wrong_build(self):
1110 build = self.factory.makeCIBuild(
1111 git_repository=self.factory.makeGitRepository(
1112 information_type=InformationType.USERDATA))
1113 build.updateStatus(BuildStatus.BUILDING)
1114 other_build = self.factory.makeCIBuild(
1115 git_repository=self.factory.makeGitRepository(
1116 information_type=InformationType.USERDATA))
1117 other_build.updateStatus(BuildStatus.BUILDING)
1118 issuer = removeSecurityProxy(getUtility(IMacaroonIssuer, "ci-build"))
1119 macaroon = issuer.issueMacaroon(other_build)
1120 self.assertMacaroonDoesNotVerify(
1121 ["Caveat check for 'lp.ci-build %s' failed." % other_build.id],
1122 issuer, macaroon, build.git_repository)
1123
1124 def test_verifyMacaroon_wrong_repository(self):
1125 build = self.factory.makeCIBuild(
1126 git_repository=self.factory.makeGitRepository(
1127 information_type=InformationType.USERDATA))
1128 other_repository = self.factory.makeGitRepository()
1129 build.updateStatus(BuildStatus.BUILDING)
1130 issuer = removeSecurityProxy(getUtility(IMacaroonIssuer, "ci-build"))
1131 macaroon = issuer.issueMacaroon(build)
1132 self.assertMacaroonDoesNotVerify(
1133 ["Caveat check for 'lp.ci-build %s' failed." % build.id],
1134 issuer, macaroon, other_repository)
diff --git a/lib/lp/code/xmlrpc/tests/test_git.py b/lib/lp/code/xmlrpc/tests/test_git.py
index 9dd57c1..f87abb7 100644
--- a/lib/lp/code/xmlrpc/tests/test_git.py
+++ b/lib/lp/code/xmlrpc/tests/test_git.py
@@ -1803,6 +1803,47 @@ class TestGitAPI(TestGitAPIMixin, TestCaseWithFactory):
1803 repository.registrant, path, permission="read",1803 repository.registrant, path, permission="read",
1804 macaroon_raw=macaroons[0].serialize())1804 macaroon_raw=macaroons[0].serialize())
18051805
1806 def test_translatePath_private_ci_build(self):
1807 # A builder with a suitable macaroon can read from a repository
1808 # associated with a running private CI build.
1809 self.pushConfig(
1810 "launchpad", internal_macaroon_secret_key="some-secret")
1811 with person_logged_in(self.factory.makePerson()) as owner:
1812 repositories = [
1813 self.factory.makeGitRepository(
1814 owner=owner, information_type=InformationType.USERDATA)
1815 for _ in range(2)]
1816 builds = [
1817 self.factory.makeCIBuild(git_repository=repository)
1818 for repository in repositories]
1819 issuer = getUtility(IMacaroonIssuer, "ci-build")
1820 macaroons = [
1821 removeSecurityProxy(issuer).issueMacaroon(build)
1822 for build in builds]
1823 repository = repositories[0]
1824 path = "/%s" % repository.unique_name
1825 self.assertUnauthorized(
1826 LAUNCHPAD_SERVICES, path, permission="write",
1827 macaroon_raw=macaroons[0].serialize())
1828 removeSecurityProxy(builds[0]).updateStatus(BuildStatus.BUILDING)
1829 self.assertTranslates(
1830 LAUNCHPAD_SERVICES, path, repository, False, permission="read",
1831 macaroon_raw=macaroons[0].serialize(), private=True)
1832 self.assertUnauthorized(
1833 LAUNCHPAD_SERVICES, path, permission="read",
1834 macaroon_raw=macaroons[1].serialize())
1835 self.assertUnauthorized(
1836 LAUNCHPAD_SERVICES, path, permission="read",
1837 macaroon_raw=Macaroon(
1838 location=config.vhost.mainsite.hostname, identifier="another",
1839 key="another-secret").serialize())
1840 self.assertUnauthorized(
1841 LAUNCHPAD_SERVICES, path, permission="read",
1842 macaroon_raw="nonsense")
1843 self.assertUnauthorized(
1844 removeSecurityProxy(repository).registrant, path,
1845 permission="read", macaroon_raw=macaroons[0].serialize())
1846
1806 def test_translatePath_user_macaroon(self):1847 def test_translatePath_user_macaroon(self):
1807 # A user with a suitable macaroon can write to the corresponding1848 # A user with a suitable macaroon can write to the corresponding
1808 # repository, but not others, even if they own them.1849 # repository, but not others, even if they own them.
@@ -2345,6 +2386,32 @@ class TestGitAPI(TestGitAPIMixin, TestCaseWithFactory):
2345 faults.Unauthorized, None,2386 faults.Unauthorized, None,
2346 "authenticateWithPassword", username, "nonsense")2387 "authenticateWithPassword", username, "nonsense")
23472388
2389 def test_authenticateWithPassword_private_ci_build(self):
2390 self.pushConfig(
2391 "launchpad", internal_macaroon_secret_key="some-secret")
2392 with person_logged_in(self.factory.makePerson()) as owner:
2393 repository = self.factory.makeGitRepository(
2394 owner=owner, information_type=InformationType.USERDATA)
2395 build = self.factory.makeCIBuild(git_repository=repository)
2396 issuer = getUtility(IMacaroonIssuer, "ci-build")
2397 macaroon = removeSecurityProxy(issuer).issueMacaroon(build)
2398 for username in ("", "+launchpad-services"):
2399 self.assertEqual(
2400 {"macaroon": macaroon.serialize(),
2401 "user": "+launchpad-services"},
2402 self.assertDoesNotFault(
2403 None, "authenticateWithPassword",
2404 username, macaroon.serialize()))
2405 other_macaroon = Macaroon(
2406 identifier="another", key="another-secret")
2407 self.assertFault(
2408 faults.Unauthorized, None,
2409 "authenticateWithPassword",
2410 username, other_macaroon.serialize())
2411 self.assertFault(
2412 faults.Unauthorized, None,
2413 "authenticateWithPassword", username, "nonsense")
2414
2348 def test_authenticateWithPassword_user_macaroon(self):2415 def test_authenticateWithPassword_user_macaroon(self):
2349 # A user with a suitable macaroon can authenticate using it, in2416 # A user with a suitable macaroon can authenticate using it, in
2350 # which case we return both the macaroon and the uid for use by2417 # which case we return both the macaroon and the uid for use by
@@ -2524,6 +2591,23 @@ class TestGitAPI(TestGitAPIMixin, TestCaseWithFactory):
2524 LAUNCHPAD_SERVICES, ref.repository, [path], {path: []},2591 LAUNCHPAD_SERVICES, ref.repository, [path], {path: []},
2525 macaroon_raw=macaroon.serialize())2592 macaroon_raw=macaroon.serialize())
25262593
2594 def test_checkRefPermissions_private_ci_build(self):
2595 # A builder with a suitable macaroon cannot write to a repository,
2596 # even if it is associated with a running private CI build.
2597 self.pushConfig(
2598 "launchpad", internal_macaroon_secret_key="some-secret")
2599 with person_logged_in(self.factory.makePerson()) as owner:
2600 [ref] = self.factory.makeGitRefs(
2601 owner=owner, information_type=InformationType.USERDATA)
2602 build = self.factory.makeCIBuild(git_repository=ref.repository)
2603 issuer = getUtility(IMacaroonIssuer, "ci-build")
2604 macaroon = removeSecurityProxy(issuer).issueMacaroon(build)
2605 build.updateStatus(BuildStatus.BUILDING)
2606 path = ref.path.encode("UTF-8")
2607 self.assertHasRefPermissions(
2608 LAUNCHPAD_SERVICES, ref.repository, [path], {path: []},
2609 macaroon_raw=macaroon.serialize())
2610
2527 def test_checkRefPermissions_user_macaroon(self):2611 def test_checkRefPermissions_user_macaroon(self):
2528 # A user with a suitable macaroon has their ordinary privileges on2612 # A user with a suitable macaroon has their ordinary privileges on
2529 # the corresponding repository, but not others, even if they own2613 # the corresponding repository, but not others, even if they own
diff --git a/lib/lp/services/authserver/interfaces.py b/lib/lp/services/authserver/interfaces.py
index a1ec353..d94755f 100644
--- a/lib/lp/services/authserver/interfaces.py
+++ b/lib/lp/services/authserver/interfaces.py
@@ -33,8 +33,8 @@ class IAuthServer(Interface):
33 `issuable_via_authserver` is True are permitted.33 `issuable_via_authserver` is True are permitted.
34 :param context_type: A string identifying the type of context for34 :param context_type: A string identifying the type of context for
35 which to issue the macaroon. Currently only 'LibraryFileAlias',35 which to issue the macaroon. Currently only 'LibraryFileAlias',
36 'BinaryPackageBuild', 'LiveFSBuild', 'SnapBuild', and36 'BinaryPackageBuild', 'LiveFSBuild', 'SnapBuild',
37 'OCIRecipeBuild' are supported.37 'OCIRecipeBuild', and 'CIBuild' are supported.
38 :param context: The context for which to issue the macaroon. Note38 :param context: The context for which to issue the macaroon. Note
39 that this is passed over XML-RPC, so it should be plain data39 that this is passed over XML-RPC, so it should be plain data
40 (e.g. an ID) rather than a database object.40 (e.g. an ID) rather than a database object.
@@ -47,7 +47,8 @@ class IAuthServer(Interface):
47 :param macaroon_raw: A serialised macaroon.47 :param macaroon_raw: A serialised macaroon.
48 :param context_type: A string identifying the type of context to48 :param context_type: A string identifying the type of context to
49 check. Currently only 'LibraryFileAlias', 'BinaryPackageBuild',49 check. Currently only 'LibraryFileAlias', 'BinaryPackageBuild',
50 'LiveFSBuild', 'SnapBuild', and 'OCIRecipeBuild' are supported.50 'LiveFSBuild', 'SnapBuild', 'OCIRecipeBuild', and 'CIBuild' are
51 supported.
51 :param context: The context to check. Note that this is passed over52 :param context: The context to check. Note that this is passed over
52 XML-RPC, so it should be plain data (e.g. an ID) rather than a53 XML-RPC, so it should be plain data (e.g. an ID) rather than a
53 database object.54 database object.
diff --git a/lib/lp/services/authserver/xmlrpc.py b/lib/lp/services/authserver/xmlrpc.py
index dbee3e9..1c91bdf 100644
--- a/lib/lp/services/authserver/xmlrpc.py
+++ b/lib/lp/services/authserver/xmlrpc.py
@@ -15,6 +15,7 @@ from zope.interface import implementer
15from zope.interface.interfaces import ComponentLookupError15from zope.interface.interfaces import ComponentLookupError
16from zope.security.proxy import removeSecurityProxy16from zope.security.proxy import removeSecurityProxy
1717
18from lp.code.interfaces.cibuild import ICIBuildSet
18from lp.oci.interfaces.ocirecipebuild import IOCIRecipeBuildSet19from lp.oci.interfaces.ocirecipebuild import IOCIRecipeBuildSet
19from lp.registry.interfaces.person import IPersonSet20from lp.registry.interfaces.person import IPersonSet
20from lp.services.authserver.interfaces import (21from lp.services.authserver.interfaces import (
@@ -58,7 +59,8 @@ class AuthServerAPIView(LaunchpadXMLRPCView):
5859
59 :param context_type: A string identifying the type of context.60 :param context_type: A string identifying the type of context.
60 Currently only 'LibraryFileAlias', 'BinaryPackageBuild',61 Currently only 'LibraryFileAlias', 'BinaryPackageBuild',
61 'LiveFSBuild', 'SnapBuild', and 'OCIRecipeBuild' are supported.62 'LiveFSBuild', 'SnapBuild', 'OCIRecipeBuild', and 'CIBuild' are
63 supported.
62 :param context: The context as plain data (e.g. an ID).64 :param context: The context as plain data (e.g. an ID).
63 :return: The resolved context, or None.65 :return: The resolved context, or None.
64 """66 """
@@ -78,8 +80,11 @@ class AuthServerAPIView(LaunchpadXMLRPCView):
78 # The context is a `SnapBuild` ID.80 # The context is a `SnapBuild` ID.
79 return getUtility(ISnapBuildSet).getByID(context)81 return getUtility(ISnapBuildSet).getByID(context)
80 elif context_type == 'OCIRecipeBuild':82 elif context_type == 'OCIRecipeBuild':
81 # The context is an OCIRecipe ID.83 # The context is an `OCIRecipeBuild` ID.
82 return getUtility(IOCIRecipeBuildSet).getByID(context)84 return getUtility(IOCIRecipeBuildSet).getByID(context)
85 elif context_type == 'CIBuild':
86 # The context is a `CIBuild` ID.
87 return getUtility(ICIBuildSet).getByID(context)
83 else:88 else:
84 return None89 return None
8590

Subscribers

People subscribed via source and target branches

to status/vote changes: