summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PKG-INFO2
-rw-r--r--debian/changelog6
-rw-r--r--macaroonbakery.egg-info/PKG-INFO2
-rw-r--r--macaroonbakery/bakery/_discharge.py11
-rw-r--r--macaroonbakery/bakery/_oven.py22
-rw-r--r--macaroonbakery/httpbakery/_error.py17
-rw-r--r--macaroonbakery/tests/common.py2
-rw-r--r--macaroonbakery/tests/test_discharge.py51
-rw-r--r--macaroonbakery/tests/test_httpbakery.py32
-rwxr-xr-xsetup.py4
10 files changed, 120 insertions, 29 deletions
diff --git a/PKG-INFO b/PKG-INFO
index fe69309..571fa8a 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: macaroonbakery
-Version: 1.2.0
+Version: 1.2.1
Summary: A Python library port for bakery, higher level operation to work with macaroons
Home-page: https://github.com/go-macaroon-bakery/py-macaroon-bakery
Author: Juju UI Team
diff --git a/debian/changelog b/debian/changelog
index 8acb97a..cb40c94 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+py-macaroon-bakery (1.2.1-1) UNRELEASED; urgency=medium
+
+ * New upstream release.
+
+ -- Colin Watson <cjwatson@debian.org> Mon, 15 Oct 2018 10:58:48 +0100
+
py-macaroon-bakery (1.2.0-1) unstable; urgency=medium
* debian/watch: Switch to PyPI, which upstream updates more reliably than
diff --git a/macaroonbakery.egg-info/PKG-INFO b/macaroonbakery.egg-info/PKG-INFO
index fe69309..571fa8a 100644
--- a/macaroonbakery.egg-info/PKG-INFO
+++ b/macaroonbakery.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: macaroonbakery
-Version: 1.2.0
+Version: 1.2.1
Summary: A Python library port for bakery, higher level operation to work with macaroons
Home-page: https://github.com/go-macaroon-bakery/py-macaroon-bakery
Author: Juju UI Team
diff --git a/macaroonbakery/bakery/_discharge.py b/macaroonbakery/bakery/_discharge.py
index 1831209..32284b7 100644
--- a/macaroonbakery/bakery/_discharge.py
+++ b/macaroonbakery/bakery/_discharge.py
@@ -33,9 +33,12 @@ def discharge_all(m, get_discharge, local_key=None):
It returns a list of macaroon with m as the first element, followed by all
the discharge macaroons.
All the discharge macaroons will be bound to the primary macaroon.
+
The get_discharge function is passed a context (AuthContext),
- the caveat(Caveat) to be discharged and encrypted_caveat (bytes)will be
+ the caveat(pymacaroons.Caveat) to be discharged and encrypted_caveat (bytes) will be
passed the external caveat payload found in m, if any.
+ It should return a bakery.Macaroon object holding the discharge
+ macaroon for the third party caveat.
'''
primary = m.macaroon
discharges = [primary]
@@ -161,7 +164,7 @@ def discharge(ctx, id, caveat, key, checker, locator):
raise VerificationError(exc.args[0])
if cond == checkers.COND_NEED_DECLARED:
- cav_info = cav_info._replace(condition=arg.encode('utf-8'))
+ cav_info = cav_info._replace(condition=arg)
caveats = _check_need_declared(ctx, cav_info, checker)
else:
caveats = checker.check_third_party_caveat(ctx, cav_info)
@@ -185,7 +188,7 @@ def discharge(ctx, id, caveat, key, checker, locator):
def _check_need_declared(ctx, cav_info, checker):
- arg = cav_info.condition.decode('utf-8')
+ arg = cav_info.condition
i = arg.find(' ')
if i <= 0:
raise VerificationError(
@@ -197,7 +200,7 @@ def _check_need_declared(ctx, cav_info, checker):
raise VerificationError('need-declared caveat with empty required attribute')
if len(need_declared) == 0:
raise VerificationError('need-declared caveat with no required attributes')
- cav_info = cav_info._replace(condition=arg[i + 1:].encode('utf-8'))
+ cav_info = cav_info._replace(condition=arg[i + 1:])
caveats = checker.check_third_party_caveat(ctx, cav_info)
declared = {}
for cav in caveats:
diff --git a/macaroonbakery/bakery/_oven.py b/macaroonbakery/bakery/_oven.py
index 414a164..d0a2a23 100644
--- a/macaroonbakery/bakery/_oven.py
+++ b/macaroonbakery/bakery/_oven.py
@@ -28,10 +28,6 @@ from macaroonbakery._utils import (
)
from ._internal import id_pb2
from pymacaroons import MACAROON_V2, Verifier
-from pymacaroons.exceptions import (
- MacaroonInvalidSignatureException,
- MacaroonUnmetCaveatException,
-)
class Oven:
@@ -183,10 +179,20 @@ class Oven:
v.satisfy_general(validator)
try:
v.verify(macaroons[0], root_key, macaroons[1:])
- except (MacaroonUnmetCaveatException,
- MacaroonInvalidSignatureException) as exc:
- raise VerificationError(
- 'verification failed: {}'.format(exc.args[0]))
+ except Exception as exc:
+ # Unfortunately pymacaroons doesn't control
+ # the set of exceptions that can be raised here.
+ # Possible candidates are:
+ # pymacaroons.exceptions.MacaroonUnmetCaveatException
+ # pymacaroons.exceptions.MacaroonInvalidSignatureException
+ # ValueError
+ # nacl.exceptions.CryptoError
+ #
+ # There may be others too, so just catch everything.
+ raise six.raise_from(
+ VerificationError('verification failed: {}'.format(str(exc))),
+ exc,
+ )
if (self.ops_store is not None
and len(ops) == 1
diff --git a/macaroonbakery/httpbakery/_error.py b/macaroonbakery/httpbakery/_error.py
index ff75f13..0ef7e7b 100644
--- a/macaroonbakery/httpbakery/_error.py
+++ b/macaroonbakery/httpbakery/_error.py
@@ -20,7 +20,7 @@ class DischargeError(Exception):
'''This is thrown by Client when a third party has refused a discharge'''
def __init__(self, msg):
super(DischargeError, self).__init__(
- 'third party refused discharge: {}'.format(msg))
+ 'third party refused dischargex: {}'.format(msg))
class InteractionError(Exception):
@@ -106,11 +106,16 @@ class Error(namedtuple('Error', 'code, message, version, info')):
'''Create an error from a JSON-deserialized object
@param serialized the object holding the serialized error {dict}
'''
- code = serialized.get('Code')
- message = serialized.get('Message')
- info = ErrorInfo.from_dict(serialized.get('Info'))
- return Error(code=code, message=message, info=info,
- version=bakery.LATEST_VERSION)
+ # Some servers return lower case field names for message and code.
+ # The Go client is tolerant of this, so be similarly tolerant here.
+ def field(name):
+ return serialized.get(name) or serialized.get(name.lower())
+ return Error(
+ code=field('Code'),
+ message=field('Message'),
+ info=ErrorInfo.from_dict(field('Info')),
+ version=bakery.LATEST_VERSION,
+ )
def interaction_method(self, kind, x):
''' Checks whether the error is an InteractionRequired error
diff --git a/macaroonbakery/tests/common.py b/macaroonbakery/tests/common.py
index 972b3ad..aacdaf3 100644
--- a/macaroonbakery/tests/common.py
+++ b/macaroonbakery/tests/common.py
@@ -80,7 +80,7 @@ class ThirdPartyStrcmpChecker(bakery.ThirdPartyCaveatChecker):
condition = cav_info.condition.decode('utf-8')
if condition != self.str:
raise bakery.ThirdPartyCaveatCheckFailed(
- '{} doesn\'t match {}'.format(condition, self.str))
+ '{} doesn\'t match {}'.format(repr(condition), repr(self.str)))
return []
diff --git a/macaroonbakery/tests/test_discharge.py b/macaroonbakery/tests/test_discharge.py
index 0802070..5360317 100644
--- a/macaroonbakery/tests/test_discharge.py
+++ b/macaroonbakery/tests/test_discharge.py
@@ -1,5 +1,6 @@
# Copyright 2017 Canonical Ltd.
# Licensed under the LGPLv3, see LICENCE file for details.
+import os
import unittest
import macaroonbakery.bakery as bakery
@@ -351,14 +352,14 @@ class TestDischarge(unittest.TestCase):
# Since no declarations are added by the discharger,
class ThirdPartyCaveatCheckerF(bakery.ThirdPartyCaveatChecker):
def check_third_party_caveat(self, ctx, cav_info):
- if cav_info.condition == b'x':
+ if cav_info.condition == 'x':
return [checkers.declared_caveat('foo', 'fooval1')]
- if cav_info.condition == b'y':
+ if cav_info.condition == 'y':
return [
checkers.declared_caveat('foo', 'fooval2'),
checkers.declared_caveat('baz', 'bazval')
]
- raise common.ThirdPartyCaveatCheckFailed('not matched')
+ raise bakery.ThirdPartyCaveatCheckFailed('not matched')
def get_discharge(cav, payload):
return bakery.discharge(
@@ -448,7 +449,7 @@ class TestDischarge(unittest.TestCase):
location='as2-loc')]
if self._loc == 'as2-loc':
return []
- raise common.ThirdPartyCaveatCheckFailed(
+ raise bakery.ThirdPartyCaveatCheckFailed(
'unknown location {}'.format(self._loc))
def get_discharge(cav, payload):
@@ -472,3 +473,45 @@ class TestDischarge(unittest.TestCase):
len(cav.caveat_id) > 3):
self.fail('caveat id on caveat {} of macaroon {} '
'is too big ({})'.format(j, i, cav.id))
+
+ def test_third_party_discharge_macaroon_wrong_root_key_and_third_party_caveat(self):
+
+ root_keys = bakery.MemoryKeyStore()
+ ts = bakery.Bakery(
+ key=bakery.generate_key(),
+ checker=common.test_checker(),
+ root_key_store=root_keys,
+ identity_client=common.OneIdentity(),
+ )
+ locator = bakery.ThirdPartyStore()
+ bs = common.new_bakery('bs-loc', locator)
+
+ # ts creates a macaroon with a third party caveat addressed to bs.
+ ts_macaroon = ts.oven.macaroon(bakery.LATEST_VERSION,
+ common.ages,
+ None, [bakery.LOGIN_OP])
+ ts_macaroon.add_caveat(
+ checkers.Caveat(location='bs-loc', condition='true'),
+ ts.oven.key, locator,
+ )
+
+ def get_discharge(cav, payload):
+ return bakery.discharge(
+ common.test_context,
+ cav.caveat_id_bytes,
+ payload,
+ bs.oven.key,
+ common.ThirdPartyStrcmpChecker('true'),
+ bs.oven.locator,
+ )
+
+ d = bakery.discharge_all(ts_macaroon, get_discharge)
+
+ # The authorization should succeed at first.
+ ts.checker.auth([d]).allow(common.test_context, [bakery.LOGIN_OP])
+ # Corrupt the root key and try again.
+ # We should get a DischargeRequiredError because the verification has failed.
+ root_keys._key = os.urandom(24)
+ with self.assertRaises(bakery.PermissionDenied) as err:
+ ts.checker.auth([d]).allow(common.test_context, [bakery.LOGIN_OP])
+ self.assertEqual(str(err.exception), 'verification failed: Decryption failed. Ciphertext failed verification')
diff --git a/macaroonbakery/tests/test_httpbakery.py b/macaroonbakery/tests/test_httpbakery.py
index 4aac850..c372f13 100644
--- a/macaroonbakery/tests/test_httpbakery.py
+++ b/macaroonbakery/tests/test_httpbakery.py
@@ -1,6 +1,7 @@
from unittest import TestCase
-from macaroonbakery.httpbakery import WebBrowserInteractionInfo
+import macaroonbakery.httpbakery as httpbakery
+import macaroonbakery.bakery as bakery
class TestWebBrowserInteractionInfo(TestCase):
@@ -9,8 +10,35 @@ class TestWebBrowserInteractionInfo(TestCase):
info_dict = {
'VisitURL': 'https://example.com/visit',
'WaitTokenURL': 'https://example.com/wait'}
- interaction_info = WebBrowserInteractionInfo.from_dict(info_dict)
+ interaction_info = httpbakery.WebBrowserInteractionInfo.from_dict(info_dict)
self.assertEqual(
interaction_info.visit_url, 'https://example.com/visit')
self.assertEqual(
interaction_info.wait_token_url, 'https://example.com/wait')
+
+
+class TestError(TestCase):
+
+ def test_from_dict_upper_case_fields(self):
+ err = httpbakery.Error.from_dict({
+ 'Message': 'm',
+ 'Code': 'c',
+ })
+ self.assertEqual(err, httpbakery.Error(
+ code='c',
+ message='m',
+ info=None,
+ version=bakery.LATEST_VERSION,
+ ))
+
+ def test_from_dict_lower_case_fields(self):
+ err = httpbakery.Error.from_dict({
+ 'message': 'm',
+ 'code': 'c',
+ })
+ self.assertEqual(err, httpbakery.Error(
+ code='c',
+ message='m',
+ info=None,
+ version=bakery.LATEST_VERSION,
+ ))
diff --git a/setup.py b/setup.py
index 1b29451..fefbee9 100755
--- a/setup.py
+++ b/setup.py
@@ -13,8 +13,8 @@ from setuptools import (
PROJECT_NAME = 'macaroonbakery'
-# version 1.2.0
-VERSION = (1, 2, 0)
+# version 1.2.1
+VERSION = (1, 2, 1)
def get_version():