launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #23506
[Merge] lp:~cjwatson/launchpad/git-export-issue-access-token into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/git-export-issue-access-token into lp:launchpad with lp:~cjwatson/launchpad/git-honour-access-tokens as a prerequisite.
Commit message:
Add and export IGitRepository.issueAccessToken.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1824399 in Launchpad itself: "Add Git HTTPS push tokens for snapcraft experiment"
https://bugs.launchpad.net/launchpad/+bug/1824399
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/git-export-issue-access-token/+merge/366057
This is very basic and experimental, but I think it will fill the requirements for now.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/git-export-issue-access-token into lp:launchpad.
=== modified file 'lib/lp/code/interfaces/gitrepository.py'
--- lib/lp/code/interfaces/gitrepository.py 2019-04-01 10:09:31 +0000
+++ lib/lp/code/interfaces/gitrepository.py 2019-04-15 13:41:43 +0000
@@ -1,4 +1,4 @@
-# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2019 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Git repository interfaces."""
@@ -651,6 +651,22 @@
:return: A `ResultSet` of `IGitActivity`.
"""
+ @export_write_operation()
+ @operation_for_version("devel")
+ def issueAccessToken():
+ """Issue an access token for this repository.
+
+ Access tokens can be used to push to this repository over HTTPS.
+ They are only valid for a single repository, and have a short expiry
+ period (currently one week), so at the moment they are only suitable
+ in some limited situations.
+
+ This interface is experimental, and may be changed or removed
+ without notice.
+
+ :return: A serialised macaroon.
+ """
+
class IGitRepositoryModerateAttributes(Interface):
"""IGitRepository attributes that can be edited by more than one community.
=== modified file 'lib/lp/code/model/gitrepository.py'
--- lib/lp/code/model/gitrepository.py 2019-04-15 13:41:42 +0000
+++ lib/lp/code/model/gitrepository.py 2019-04-15 13:41:43 +0000
@@ -1428,6 +1428,13 @@
return DecoratedResultSet(
results, pre_iter_hook=preloadDataForActivities)
+ def issueAccessToken(self):
+ """See `IGitRepository`."""
+ issuer = getUtility(IMacaroonIssuer, "git-repository")
+ # Our security adapter has already done the checks we need, apart
+ # from forbidding anonymous users which is done by the issuer.
+ return removeSecurityProxy(issuer).issueMacaroon(self).serialize()
+
def canBeDeleted(self):
"""See `IGitRepository`."""
# Can't delete if the repository is associated with anything.
=== modified file 'lib/lp/code/model/tests/test_gitrepository.py'
--- lib/lp/code/model/tests/test_gitrepository.py 2019-04-15 13:41:42 +0000
+++ lib/lp/code/model/tests/test_gitrepository.py 2019-04-15 13:41:43 +0000
@@ -34,6 +34,7 @@
MatchesListwise,
MatchesSetwise,
MatchesStructure,
+ StartsWith,
)
import transaction
from zope.component import getUtility
@@ -158,6 +159,7 @@
api_url,
celebrity_logged_in,
login_person,
+ logout,
person_logged_in,
record_two_runs,
TestCaseWithFactory,
@@ -174,7 +176,10 @@
DoesNotSnapshot,
HasQueryCount,
)
-from lp.testing.pages import webservice_for_person
+from lp.testing.pages import (
+ LaunchpadWebServiceCaller,
+ webservice_for_person,
+ )
from lp.xmlrpc import faults
from lp.xmlrpc.interfaces import IPrivateApplication
@@ -3909,6 +3914,50 @@
"refs/other": Equals([]),
}))
+ def test_issueAccessToken(self):
+ # A user can request an access token via the webservice API.
+ self.pushConfig(
+ "launchpad", internal_macaroon_secret_key="some-secret")
+ repository = self.factory.makeGitRepository()
+ # Write access to the repository isn't checked at this stage
+ # (although the access token will only be useful if the user has
+ # some kind of write access).
+ requester = self.factory.makePerson()
+ with person_logged_in(requester):
+ repository_url = api_url(repository)
+ webservice = webservice_for_person(
+ requester, permission=OAuthPermission.WRITE_PUBLIC)
+ webservice.default_api_version = "devel"
+ response = webservice.named_post(repository_url, "issueAccessToken")
+ self.assertEqual(200, response.status)
+ macaroon = Macaroon.deserialize(json.loads(response.body))
+ with person_logged_in(ANONYMOUS):
+ self.assertThat(macaroon, MatchesStructure(
+ location=Equals(config.vhost.mainsite.hostname),
+ identifier=Equals("git-repository"),
+ caveats=MatchesListwise([
+ MatchesStructure.byEquality(
+ caveat_id="lp.git-repository %s" % repository.id),
+ MatchesStructure(
+ caveat_id=StartsWith("lp.openid-identifier ")),
+ MatchesStructure(caveat_id=StartsWith("lp.expires ")),
+ ])))
+
+ def test_issueAccessToken_anonymous(self):
+ # An anonymous user cannot request an access token via the
+ # webservice API.
+ repository = self.factory.makeGitRepository()
+ with person_logged_in(repository.owner):
+ repository_url = api_url(repository)
+ logout()
+ webservice = LaunchpadWebServiceCaller()
+ webservice.default_api_version = "devel"
+ response = webservice.named_post(repository_url, "issueAccessToken")
+ self.assertEqual(401, response.status)
+ self.assertEqual(
+ "git-repository macaroons may only be issued for a logged-in "
+ "user.", response.body)
+
class TestGitRepositoryMacaroonIssuer(MacaroonTestMixin, TestCaseWithFactory):
"""Test GitRepository macaroon issuing and verification."""
Follow ups