summaryrefslogtreecommitdiff
path: root/macaroonbakery/tests
diff options
context:
space:
mode:
authorColin Watson <cjwatson@debian.org>2017-12-12 15:20:49 +0000
committerColin Watson <cjwatson@debian.org>2017-12-12 15:20:49 +0000
commit9e4403035a9953c99117083e6373ae3c441a76b5 (patch)
treed91b137df6767bfb8cb72de6b9fd21efb0c3dee4 /macaroonbakery/tests
parent949b7072cabce0daed6c94993ad44c8ea8648dbd (diff)
Import py-macaroon-bakery_1.1.0.orig.tar.gz
Diffstat (limited to 'macaroonbakery/tests')
-rw-r--r--macaroonbakery/tests/common.py5
-rw-r--r--macaroonbakery/tests/test_agent.py171
-rw-r--r--macaroonbakery/tests/test_authorizer.py2
-rw-r--r--macaroonbakery/tests/test_bakery.py87
-rw-r--r--macaroonbakery/tests/test_checker.py34
-rw-r--r--macaroonbakery/tests/test_checkers.py7
-rw-r--r--macaroonbakery/tests/test_client.py130
-rw-r--r--macaroonbakery/tests/test_codec.py7
-rw-r--r--macaroonbakery/tests/test_discharge.py5
-rw-r--r--macaroonbakery/tests/test_discharge_all.py5
-rw-r--r--macaroonbakery/tests/test_keyring.py16
-rw-r--r--macaroonbakery/tests/test_macaroon.py12
-rw-r--r--macaroonbakery/tests/test_oven.py8
-rw-r--r--macaroonbakery/tests/test_store.py2
-rw-r--r--macaroonbakery/tests/test_time.py19
-rw-r--r--macaroonbakery/tests/test_utils.py74
16 files changed, 376 insertions, 208 deletions
diff --git a/macaroonbakery/tests/common.py b/macaroonbakery/tests/common.py
index f238dfd..cfbfc52 100644
--- a/macaroonbakery/tests/common.py
+++ b/macaroonbakery/tests/common.py
@@ -2,10 +2,9 @@
# Licensed under the LGPLv3, see LICENCE file for details.
from datetime import datetime, timedelta
-import pytz
-
-import macaroonbakery as bakery
+import macaroonbakery.bakery as bakery
import macaroonbakery.checkers as checkers
+import pytz
class _StoppedClock(object):
diff --git a/macaroonbakery/tests/test_agent.py b/macaroonbakery/tests/test_agent.py
index 67f5b84..3b38337 100644
--- a/macaroonbakery/tests/test_agent.py
+++ b/macaroonbakery/tests/test_agent.py
@@ -1,27 +1,22 @@
# Copyright 2017 Canonical Ltd.
# Licensed under the LGPLv3, see LICENCE file for details.
-import base64
-from datetime import datetime, timedelta
import json
+import logging
import os
import tempfile
+from datetime import datetime, timedelta
from unittest import TestCase
-import nacl.encoding
-import requests.cookies
-import six
-from six.moves.urllib.parse import parse_qs
-from six.moves.http_cookies import SimpleCookie
-from httmock import (
- HTTMock,
- urlmatch,
- response
-)
-
-import macaroonbakery as bakery
-import macaroonbakery.httpbakery as httpbakery
+import macaroonbakery.bakery as bakery
import macaroonbakery.checkers as checkers
+import macaroonbakery.httpbakery as httpbakery
import macaroonbakery.httpbakery.agent as agent
+import requests.cookies
+
+from httmock import HTTMock, response, urlmatch
+from six.moves.urllib.parse import parse_qs
+
+log = logging.getLogger(__name__)
class TestAgents(TestCase):
@@ -44,73 +39,31 @@ class TestAgents(TestCase):
os.remove(self.bad_key_agent_filename)
os.remove(self.no_username_agent_filename)
- def test_load_agents(self):
- cookies, key = agent.load_agent_file(self.agent_filename)
- self.assertEqual(key.encode(nacl.encoding.Base64Encoder),
- b'CqoSgj06Zcgb4/S6RT4DpTjLAfKoznEY3JsShSjKJEU=')
- self.assertEqual(
- key.public_key.encode(nacl.encoding.Base64Encoder),
- b'YAhRSsth3a36mRYqQGQaLiS4QJax0p356nd+B8x7UQE=')
-
- value = cookies.get('agent-login', domain='1.example.com')
- jv = base64.b64decode(value)
- if six.PY3:
- jv = jv.decode('utf-8')
- data = json.loads(jv)
- self.assertEqual(data['username'], 'user-1')
- self.assertEqual(data['public_key'],
- 'YAhRSsth3a36mRYqQGQaLiS4QJax0p356nd+B8x7UQE=')
-
- value = cookies.get('agent-login', domain='2.example.com',
- path='/discharger')
- jv = base64.b64decode(value)
- if six.PY3:
- jv = jv.decode('utf-8')
- data = json.loads(jv)
- self.assertEqual(data['username'], 'user-2')
- self.assertEqual(data['public_key'],
- 'YAhRSsth3a36mRYqQGQaLiS4QJax0p356nd+B8x7UQE=')
-
- def test_load_agents_into_cookies(self):
- cookies = requests.cookies.RequestsCookieJar()
- c1, key = agent.load_agent_file(
- self.agent_filename,
- cookies=cookies,
- )
- self.assertEqual(c1, cookies)
- self.assertEqual(
- key.encode(nacl.encoding.Base64Encoder),
- b'CqoSgj06Zcgb4/S6RT4DpTjLAfKoznEY3JsShSjKJEU=',
- )
- self.assertEqual(
- key.public_key.encode(nacl.encoding.Base64Encoder),
- b'YAhRSsth3a36mRYqQGQaLiS4QJax0p356nd+B8x7UQE=',
- )
-
- value = cookies.get('agent-login', domain='1.example.com')
- jv = base64.b64decode(value)
- if six.PY3:
- jv = jv.decode('utf-8')
- data = json.loads(jv)
- self.assertEqual(data['username'], 'user-1')
- self.assertEqual(data['public_key'], 'YAhRSsth3a36mRYqQGQaLiS4QJax0p356nd+B8x7UQE=')
-
- value = cookies.get('agent-login', domain='2.example.com',
- path='/discharger')
- jv = base64.b64decode(value)
- if six.PY3:
- jv = jv.decode('utf-8')
- data = json.loads(jv)
- self.assertEqual(data['username'], 'user-2')
- self.assertEqual(data['public_key'], 'YAhRSsth3a36mRYqQGQaLiS4QJax0p356nd+B8x7UQE=')
-
- def test_load_agents_with_bad_key(self):
+ def test_load_auth_info(self):
+ auth_info = agent.load_auth_info(self.agent_filename)
+ self.assertEqual(str(auth_info.key), 'CqoSgj06Zcgb4/S6RT4DpTjLAfKoznEY3JsShSjKJEU=')
+ self.assertEqual(str(auth_info.key.public_key), 'YAhRSsth3a36mRYqQGQaLiS4QJax0p356nd+B8x7UQE=')
+ self.assertEqual(auth_info.agents, [
+ agent.Agent(url='https://1.example.com/', username='user-1'),
+ agent.Agent(url='https://2.example.com/discharger', username='user-2'),
+ agent.Agent(url='http://0.3.2.1', username='test-user'),
+ ])
+
+ def test_invalid_agent_json(self):
+ with self.assertRaises(agent.AgentFileFormatError):
+ agent.read_auth_info('}')
+
+ def test_invalid_read_auth_info_arg(self):
with self.assertRaises(agent.AgentFileFormatError):
- agent.load_agent_file(self.bad_key_agent_filename)
+ agent.read_auth_info(0)
- def test_load_agents_with_no_username(self):
+ def test_load_auth_info_with_bad_key(self):
with self.assertRaises(agent.AgentFileFormatError):
- agent.load_agent_file(self.no_username_agent_filename)
+ agent.load_auth_info(self.bad_key_agent_filename)
+
+ def test_load_auth_info_with_no_username(self):
+ with self.assertRaises(agent.AgentFileFormatError):
+ agent.load_auth_info(self.no_username_agent_filename)
def test_agent_login(self):
discharge_key = bakery.generate_key()
@@ -138,7 +91,8 @@ class TestAgents(TestCase):
content='done')
except bakery.PermissionDenied:
caveats = [
- checkers.Caveat(location='http://0.3.2.1', condition='is-ok')
+ checkers.Caveat(location='http://0.3.2.1',
+ condition='is-ok')
]
m = server_bakery.oven.macaroon(
version=bakery.LATEST_VERSION,
@@ -177,11 +131,11 @@ class TestAgents(TestCase):
return {
'status_code': 200,
'content': {
- 'Macaroon': m.serialize_json()
+ 'Macaroon': m.to_dict()
}
}
- key = bakery.generate_key()
+ auth_info = agent.load_auth_info(self.agent_filename)
@urlmatch(path='.*/login')
def login(url, request):
@@ -190,7 +144,7 @@ class TestAgents(TestCase):
version=bakery.LATEST_VERSION,
expiry=datetime.utcnow() + timedelta(days=1),
caveats=[bakery.local_third_party_caveat(
- key.public_key,
+ auth_info.key.public_key,
version=httpbakery.request_version(request.headers))],
ops=[bakery.Op(entity='agent', action='login')])
return {
@@ -204,17 +158,7 @@ class TestAgents(TestCase):
HTTMock(discharge), \
HTTMock(login):
client = httpbakery.Client(interaction_methods=[
- agent.AgentInteractor(
- agent.AuthInfo(
- key=key,
- agents=[
- agent.Agent(
- username='test-user',
- url=u'http://0.3.2.1'
- )
- ],
- ),
- ),
+ agent.AgentInteractor(auth_info),
])
resp = requests.get(
'http://0.1.2.3/here',
@@ -315,25 +259,26 @@ class TestAgents(TestCase):
key = bakery.generate_key()
- @urlmatch(path='.*/visit?$')
+ @urlmatch(path='.*/visit')
def visit(url, request):
if request.headers.get('Accept') == 'application/json':
return {
'status_code': 200,
'content': {
- 'agent': request.url
+ 'agent': '/agent-visit',
}
}
- cs = SimpleCookie()
- cookies = request.headers.get('Cookie')
- if cookies is not None:
- cs.load(str(cookies))
- public_key = None
- for c in cs:
- if c == 'agent-login':
- json_cookie = json.loads(
- base64.b64decode(cs[c].value).decode('utf-8'))
- public_key = bakery.PublicKey.deserialize(json_cookie.get('public_key'))
+ raise Exception('unexpected call to visit without Accept header')
+
+ @urlmatch(path='.*/agent-visit')
+ def agent_visit(url, request):
+ if request.method != "POST":
+ raise Exception('unexpected method')
+ log.info('agent_visit url {}'.format(url))
+ body = json.loads(request.body.decode('utf-8'))
+ if body['username'] != 'test-user':
+ raise Exception('unexpected username in body {!r}'.format(request.body))
+ public_key = bakery.PublicKey.deserialize(body['public_key'])
ms = httpbakery.extract_macaroons(request.headers)
if len(ms) == 0:
b = bakery.Bakery(key=discharge_key)
@@ -356,11 +301,11 @@ class TestAgents(TestCase):
return {
'status_code': 200,
'content': {
- 'agent-login': True
+ 'agent_login': True
}
}
- @urlmatch(path='.*/wait?$')
+ @urlmatch(path='.*/wait$')
def wait(url, request):
class EmptyChecker(bakery.ThirdPartyCaveatChecker):
def check_third_party_caveat(self, ctx, info):
@@ -385,12 +330,14 @@ class TestAgents(TestCase):
with HTTMock(server_get), \
HTTMock(discharge), \
HTTMock(visit), \
- HTTMock(wait):
+ HTTMock(wait), \
+ HTTMock(agent_visit):
client = httpbakery.Client(interaction_methods=[
agent.AgentInteractor(
agent.AuthInfo(
key=key,
- agents=[agent.Agent(username='test-user', url=u'http://0.3.2.1')],
+ agents=[agent.Agent(username='test-user',
+ url=u'http://0.3.2.1')],
),
),
])
@@ -414,11 +361,13 @@ agent_file = '''
}, {
"url": "https://2.example.com/discharger",
"username": "user-2"
+ }, {
+ "url": "http://0.3.2.1",
+ "username": "test-user"
}]
}
'''
-
bad_key_agent_file = '''
{
"key": {
diff --git a/macaroonbakery/tests/test_authorizer.py b/macaroonbakery/tests/test_authorizer.py
index f90d2b5..d5539b7 100644
--- a/macaroonbakery/tests/test_authorizer.py
+++ b/macaroonbakery/tests/test_authorizer.py
@@ -2,7 +2,7 @@
# Licensed under the LGPLv3, see LICENCE file for details.
from unittest import TestCase
-import macaroonbakery as bakery
+import macaroonbakery.bakery as bakery
import macaroonbakery.checkers as checkers
diff --git a/macaroonbakery/tests/test_bakery.py b/macaroonbakery/tests/test_bakery.py
index 5a13cff..a6c3e58 100644
--- a/macaroonbakery/tests/test_bakery.py
+++ b/macaroonbakery/tests/test_bakery.py
@@ -2,19 +2,11 @@
# Licensed under the LGPLv3, see LICENCE file for details.
from unittest import TestCase
+import macaroonbakery.httpbakery as httpbakery
import requests
+from mock import patch
-from mock import (
- patch,
-)
-
-from httmock import (
- HTTMock,
- urlmatch,
- response
-)
-
-import macaroonbakery.httpbakery as httpbakery
+from httmock import HTTMock, response, urlmatch
ID_PATH = 'http://example.com/someprotecteurl'
@@ -29,7 +21,7 @@ json_macaroon = {
}, {
u'cid': u'allow read-no-terms write'
}, {
- u'cid': u'time-before 2016-07-19T14:29:14.312669464Z'
+ u'cid': u'time-before 2158-07-19T14:29:14.312669464Z'
}],
u'location': u'charmstore',
u'signature': u'52d17cb11f5c84d58441bc0ffd7cc396'
@@ -41,7 +33,7 @@ discharge_token = [{
u'caveats': [{
u'cid': u'declared username someone'
}, {
- u'cid': u'time-before 2016-08-15T15:55:52.428319076Z'
+ u'cid': u'time-before 2158-08-15T15:55:52.428319076Z'
}, {
u'cid': u'origin '
}],
@@ -57,7 +49,7 @@ discharged_macaroon = {
}, {
u'cid': u'declared username someone'
}, {
- u'cid': u'time-before 2016-07-19T15:55:52.432439055Z'
+ u'cid': u'time-before 2158-07-19T15:55:52.432439055Z'
}],
u'location': u'',
u'signature': u'3513db5503ab17f9576760cd28'
@@ -167,6 +159,17 @@ def wait_after_401(url, request):
}
+@urlmatch(path='.*/wait')
+def wait_on_error(url, request):
+ return {
+ 'status_code': 500,
+ 'content': {
+ 'DischargeToken': discharge_token,
+ 'Macaroon': discharged_macaroon
+ }
+ }
+
+
class TestBakery(TestCase):
def assert_cookie_security(self, cookies, name, secure):
@@ -185,12 +188,14 @@ class TestBakery(TestCase):
auth=client.auth())
resp.raise_for_status()
assert 'macaroon-test' in client.cookies.keys()
- self.assert_cookie_security(client.cookies, 'macaroon-test', secure=False)
+ self.assert_cookie_security(client.cookies, 'macaroon-test',
+ secure=False)
@patch('webbrowser.open')
def test_407_then_401_on_discharge(self, mock_open):
client = httpbakery.Client()
- with HTTMock(first_407_then_200), HTTMock(discharge_401), HTTMock(wait_after_401):
+ with HTTMock(first_407_then_200), HTTMock(discharge_401), \
+ HTTMock(wait_after_401):
resp = requests.get(
ID_PATH,
cookies=client.cookies,
@@ -200,6 +205,53 @@ class TestBakery(TestCase):
mock_open.assert_called_once_with(u'http://example.com/visit', new=1)
assert 'macaroon-test' in client.cookies.keys()
+ @patch('webbrowser.open')
+ def test_407_then_error_on_wait(self, mock_open):
+ client = httpbakery.Client()
+ with HTTMock(first_407_then_200), HTTMock(discharge_401),\
+ HTTMock(wait_on_error):
+ with self.assertRaises(httpbakery.InteractionError) as exc:
+ requests.get(
+ ID_PATH,
+ cookies=client.cookies,
+ auth=client.auth(),
+ )
+ self.assertEqual(str(exc.exception),
+ 'cannot start interactive session: cannot get '
+ 'http://example.com/wait')
+ mock_open.assert_called_once_with(u'http://example.com/visit', new=1)
+
+ def test_407_then_no_interaction_methods(self):
+ client = httpbakery.Client(interaction_methods=[])
+ with HTTMock(first_407_then_200), HTTMock(discharge_401):
+ with self.assertRaises(httpbakery.InteractionError) as exc:
+ requests.get(
+ ID_PATH,
+ cookies=client.cookies,
+ auth=client.auth(),
+ )
+ self.assertEqual(str(exc.exception),
+ 'cannot start interactive session: interaction '
+ 'required but not possible')
+
+ def test_407_then_unknown_interaction_methods(self):
+ class UnknownInteractor(httpbakery.Interactor):
+ def kind(self):
+ return 'unknown'
+ client = httpbakery.Client(interaction_methods=[UnknownInteractor()])
+ with HTTMock(first_407_then_200), HTTMock(discharge_401):
+ with self.assertRaises(httpbakery.InteractionError) as exc:
+ requests.get(
+ ID_PATH,
+ cookies=client.cookies,
+ auth=client.auth(),
+ )
+ self.assertEqual(
+ str(exc.exception),
+ 'cannot start interactive session: no methods supported; '
+ 'supported [unknown]; provided [interactive]'
+ )
+
def test_cookie_with_port(self):
client = httpbakery.Client()
with HTTMock(first_407_then_200_with_port):
@@ -219,4 +271,5 @@ class TestBakery(TestCase):
auth=client.auth())
resp.raise_for_status()
assert 'macaroon-test' in client.cookies.keys()
- self.assert_cookie_security(client.cookies, 'macaroon-test', secure=True)
+ self.assert_cookie_security(client.cookies, 'macaroon-test',
+ secure=True)
diff --git a/macaroonbakery/tests/test_checker.py b/macaroonbakery/tests/test_checker.py
index 643c756..6b61768 100644
--- a/macaroonbakery/tests/test_checker.py
+++ b/macaroonbakery/tests/test_checker.py
@@ -1,17 +1,16 @@
# Copyright 2017 Canonical Ltd.
# Licensed under the LGPLv3, see LICENCE file for details.
import base64
-from collections import namedtuple
import json
-from unittest import TestCase
+from collections import namedtuple
from datetime import timedelta
+from unittest import TestCase
-from pymacaroons.verifier import Verifier, FirstPartyCaveatVerifierDelegate
-import pymacaroons
-
-import macaroonbakery as bakery
+import macaroonbakery.bakery as bakery
import macaroonbakery.checkers as checkers
-from macaroonbakery.tests.common import test_context, epoch, test_checker
+import pymacaroons
+from macaroonbakery.tests.common import epoch, test_checker, test_context
+from pymacaroons.verifier import FirstPartyCaveatVerifierDelegate, Verifier
class TestChecker(TestCase):
@@ -53,7 +52,8 @@ class TestChecker(TestCase):
client = _Client(locator)
ctx = test_context.with_value(_DISCHARGE_USER_KEY, 'bob')
- auth_info = client.do(ctx, ts, [bakery.Op(entity='something', action='read')])
+ auth_info = client.do(ctx, ts, [bakery.Op(entity='something',
+ action='read')])
self.assertEqual(self._discharges,
[_DischargeRecord(location='ids', user='bob')])
self.assertIsNotNone(auth_info)
@@ -98,7 +98,8 @@ class TestChecker(TestCase):
self.assertIsNotNone(auth_info)
self.assertIsNone(auth_info.identity)
self.assertEqual(len(auth_info.macaroons), 1)
- self.assertEqual(auth_info.macaroons[0][0].identifier_bytes, m[0].identifier_bytes)
+ self.assertEqual(auth_info.macaroons[0][0].identifier_bytes,
+ m[0].identifier_bytes)
def test_capability_multiple_entities(self):
locator = _DischargerLocator()
@@ -168,8 +169,10 @@ class TestChecker(TestCase):
self.assertIsNotNone(auth_info)
self.assertIsNone(auth_info.identity)
self.assertEqual(len(auth_info.macaroons), 2)
- self.assertEqual(auth_info.macaroons[0][0].identifier_bytes, m1[0].identifier_bytes)
- self.assertEqual(auth_info.macaroons[1][0].identifier_bytes, m2[0].identifier_bytes)
+ self.assertEqual(auth_info.macaroons[0][0].identifier_bytes,
+ m1[0].identifier_bytes)
+ self.assertEqual(auth_info.macaroons[1][0].identifier_bytes,
+ m2[0].identifier_bytes)
def test_combine_capabilities(self):
locator = _DischargerLocator()
@@ -560,8 +563,10 @@ class TestChecker(TestCase):
# Try them the other way around and we should authenticate as alice.
client3 = _Client(locator)
- client3.add_macaroon(ts, '1.alice', client2._macaroons[ts.name()]['authn'])
- client3.add_macaroon(ts, '2.bob', client1._macaroons[ts.name()]['authn'])
+ client3.add_macaroon(ts, '1.alice',
+ client2._macaroons[ts.name()]['authn'])
+ client3.add_macaroon(ts, '2.bob',
+ client1._macaroons[ts.name()]['authn'])
auth_info = client3.do(test_context, ts, [bakery.LOGIN_OP])
self.assertEqual(auth_info.identity.id(), 'alice')
@@ -890,7 +895,8 @@ class _BasicAuthIdService(bakery.IdentityClient):
return bakery.SimpleIdentity(user), None
def declared_identity(self, ctx, declared):
- raise bakery.IdentityError('no identity declarations in basic auth id service')
+ raise bakery.IdentityError('no identity declarations in basic auth'
+ ' id service')
_BASIC_AUTH_KEY = checkers.ContextKey('user-key')
diff --git a/macaroonbakery/tests/test_checkers.py b/macaroonbakery/tests/test_checkers.py
index f552fa4..28da06e 100644
--- a/macaroonbakery/tests/test_checkers.py
+++ b/macaroonbakery/tests/test_checkers.py
@@ -3,11 +3,10 @@
from datetime import datetime, timedelta
from unittest import TestCase
-import six
-import pytz
-from pymacaroons import Macaroon, MACAROON_V2
-
import macaroonbakery.checkers as checkers
+import pytz
+import six
+from pymacaroons import MACAROON_V2, Macaroon
# A frozen time for the tests.
NOW = datetime(
diff --git a/macaroonbakery/tests/test_client.py b/macaroonbakery/tests/test_client.py
index e1a4009..ab20c3b 100644
--- a/macaroonbakery/tests/test_client.py
+++ b/macaroonbakery/tests/test_client.py
@@ -3,23 +3,24 @@
import base64
import datetime
import json
-from unittest import TestCase
-try:
- from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
-except ImportError:
- from http.server import HTTPServer, BaseHTTPRequestHandler
import threading
+from unittest import TestCase
-from httmock import (
- HTTMock,
- urlmatch
-)
+import macaroonbakery.bakery as bakery
+import macaroonbakery.checkers as checkers
+import macaroonbakery.httpbakery as httpbakery
+import pymacaroons
import requests
+import macaroonbakery._utils as utils
+
+from httmock import HTTMock, urlmatch
from six.moves.urllib.parse import parse_qs
+from six.moves.urllib.request import Request
-import macaroonbakery as bakery
-import macaroonbakery.httpbakery as httpbakery
-import macaroonbakery.checkers as checkers
+try:
+ from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
+except ImportError:
+ from http.server import HTTPServer, BaseHTTPRequestHandler
AGES = datetime.datetime.utcnow() + datetime.timedelta(days=1)
TEST_OP = bakery.Op(entity='test', action='test')
@@ -30,7 +31,7 @@ class TestClient(TestCase):
b = new_bakery('loc', None, None)
def handler(*args):
- GetHandler(b, None, None, None, None, *args)
+ GetHandler(b, None, None, None, None, AGES, *args)
try:
httpd = HTTPServer(('', 0), handler)
thread = threading.Thread(target=httpd.serve_forever)
@@ -58,7 +59,7 @@ class TestClient(TestCase):
b = new_bakery('loc', None, None)
def handler(*args):
- GetHandler(b, None, None, None, None, *args)
+ GetHandler(b, None, None, None, None, AGES, *args)
try:
httpd = HTTPServer(('', 0), handler)
thread = threading.Thread(target=httpd.serve_forever)
@@ -81,7 +82,7 @@ class TestClient(TestCase):
finally:
httpd.shutdown()
- def test_repeated_request_with_body(self):
+ def test_expiry_cookie_is_set(self):
class _DischargerLocator(bakery.ThirdPartyLocator):
def __init__(self):
self.key = bakery.generate_key()
@@ -100,7 +101,8 @@ class TestClient(TestCase):
def discharge(url, request):
qs = parse_qs(request.body)
content = {q: qs[q][0] for q in qs}
- m = httpbakery.discharge(checkers.AuthContext(), content, d.key, d, alwaysOK3rd)
+ m = httpbakery.discharge(checkers.AuthContext(), content, d.key, d,
+ alwaysOK3rd)
return {
'status_code': 200,
'content': {
@@ -108,8 +110,10 @@ class TestClient(TestCase):
}
}
+ ages = datetime.datetime.utcnow() + datetime.timedelta(days=1)
+
def handler(*args):
- GetHandler(b, 'http://1.2.3.4', None, None, None, *args)
+ GetHandler(b, 'http://1.2.3.4', None, None, None, ages, *args)
try:
httpd = HTTPServer(('', 0), handler)
thread = threading.Thread(target=httpd.serve_forever)
@@ -122,10 +126,64 @@ class TestClient(TestCase):
cookies=client.cookies,
auth=client.auth())
resp.raise_for_status()
+ m = bakery.Macaroon.from_dict(json.loads(
+ base64.b64decode(client.cookies.get('macaroon-test')).decode('utf-8'))[0])
+ t = checkers.macaroons_expiry_time(
+ checkers.Namespace(), [m.macaroon])
+ self.assertEquals(ages, t)
self.assertEquals(resp.text, 'done')
finally:
httpd.shutdown()
+ def test_expiry_cookie_set_in_past(self):
+ class _DischargerLocator(bakery.ThirdPartyLocator):
+ def __init__(self):
+ self.key = bakery.generate_key()
+
+ def third_party_info(self, loc):
+ if loc == 'http://1.2.3.4':
+ return bakery.ThirdPartyInfo(
+ public_key=self.key.public_key,
+ version=bakery.LATEST_VERSION,
+ )
+
+ d = _DischargerLocator()
+ b = new_bakery('loc', d, None)
+
+ @urlmatch(path='.*/discharge')
+ def discharge(url, request):
+ qs = parse_qs(request.body)
+ content = {q: qs[q][0] for q in qs}
+ m = httpbakery.discharge(checkers.AuthContext(), content, d.key, d,
+ alwaysOK3rd)
+ return {
+ 'status_code': 200,
+ 'content': {
+ 'Macaroon': m.to_dict()
+ }
+ }
+
+ ages = datetime.datetime.utcnow() - datetime.timedelta(days=1)
+
+ def handler(*args):
+ GetHandler(b, 'http://1.2.3.4', None, None, None, ages, *args)
+ try:
+ httpd = HTTPServer(('', 0), handler)
+ thread = threading.Thread(target=httpd.serve_forever)
+ thread.start()
+ client = httpbakery.Client()
+ with HTTMock(discharge):
+ with self.assertRaises(httpbakery.BakeryException) as ctx:
+ requests.get(
+ url='http://' + httpd.server_address[0] + ':' +
+ str(httpd.server_address[1]),
+ cookies=client.cookies,
+ auth=client.auth())
+ self.assertEqual(ctx.exception.args[0],
+ 'too many (3) discharge requests')
+ finally:
+ httpd.shutdown()
+
def test_too_many_discharge(self):
class _DischargerLocator(bakery.ThirdPartyLocator):
def __init__(self):
@@ -155,7 +213,7 @@ class TestClient(TestCase):
}
def handler(*args):
- GetHandler(b, 'http://1.2.3.4', None, None, None, *args)
+ GetHandler(b, 'http://1.2.3.4', None, None, None, AGES, *args)
try:
httpd = HTTPServer(('', 0), handler)
thread = threading.Thread(target=httpd.serve_forever)
@@ -199,7 +257,7 @@ class TestClient(TestCase):
ThirdPartyCaveatCheckerF(check))
def handler(*args):
- GetHandler(b, 'http://1.2.3.4', None, None, None, *args)
+ GetHandler(b, 'http://1.2.3.4', None, None, None, AGES, *args)
try:
httpd = HTTPServer(('', 0), handler)
thread = threading.Thread(target=httpd.serve_forever)
@@ -244,7 +302,7 @@ class TestClient(TestCase):
}
def handler(*args):
- GetHandler(b, 'http://1.2.3.4', None, None, None, *args)
+ GetHandler(b, 'http://1.2.3.4', None, None, None, AGES, *args)
try:
httpd = HTTPServer(('', 0), handler)
@@ -273,11 +331,34 @@ class TestClient(TestCase):
finally:
httpd.shutdown()
+ def test_extract_macaroons_from_request(self):
+ def encode_macaroon(m):
+ macaroons = '[' + utils.macaroon_to_json_string(m) + ']'
+ return base64.urlsafe_b64encode(utils.to_bytes(macaroons)).decode('ascii')
+
+ req = Request('http://example.com')
+ m1 = pymacaroons.Macaroon(version=pymacaroons.MACAROON_V2, identifier='one')
+ req.add_header('Macaroons', encode_macaroon(m1))
+ m2 = pymacaroons.Macaroon(version=pymacaroons.MACAROON_V2, identifier='two')
+ jar = requests.cookies.RequestsCookieJar()
+ jar.set_cookie(utils.cookie(
+ name='macaroon-auth',
+ value=encode_macaroon(m2),
+ url='http://example.com',
+ ))
+ jar.add_cookie_header(req)
+
+ macaroons = httpbakery.extract_macaroons(req)
+ self.assertEquals(len(macaroons), 2)
+ macaroons.sort(key=lambda ms: ms[0].identifier)
+ self.assertEquals(macaroons[0][0].identifier, m1.identifier)
+ self.assertEquals(macaroons[1][0].identifier, m2.identifier)
+
class GetHandler(BaseHTTPRequestHandler):
'''A mock HTTP server that serves a GET request'''
def __init__(self, bakery, auth_location, mutate_error,
- caveats, version, *args):
+ caveats, version, expiry, *args):
'''
@param bakery used to check incoming requests and macaroons
for discharge-required errors.
@@ -288,14 +369,17 @@ class GetHandler(BaseHTTPRequestHandler):
discharge-required error before responding to the client.
@param caveats called to get caveats to add to the returned
macaroon.
- @param holds the version of the bakery that the
+ @param version holds the version of the bakery that the
server will purport to serve.
+ @param expiry holds the expiry for the macaroon that will be created
+ in _write_discharge_error
'''
self._bakery = bakery
self._auth_location = auth_location
self._mutate_error = mutate_error
self._caveats = caveats
self._server_version = version
+ self._expiry = expiry
BaseHTTPRequestHandler.__init__(self, *args)
def do_GET(self):
@@ -333,7 +417,7 @@ class GetHandler(BaseHTTPRequestHandler):
caveats.extend(self._caveats)
m = self._bakery.oven.macaroon(
- version=bakery.LATEST_VERSION, expiry=AGES,
+ version=bakery.LATEST_VERSION, expiry=self._expiry,
caveats=caveats, ops=[TEST_OP])
content, headers = httpbakery.discharge_required_response(
diff --git a/macaroonbakery/tests/test_codec.py b/macaroonbakery/tests/test_codec.py
index d4fbc57..d82a794 100644
--- a/macaroonbakery/tests/test_codec.py
+++ b/macaroonbakery/tests/test_codec.py
@@ -3,12 +3,11 @@
import base64
from unittest import TestCase
+import macaroonbakery.bakery as bakery
+import macaroonbakery.checkers as checkers
import nacl.public
import six
-
-import macaroonbakery as bakery
-from macaroonbakery import codec
-import macaroonbakery.checkers as checkers
+from macaroonbakery.bakery import _codec as codec
class TestCodec(TestCase):
diff --git a/macaroonbakery/tests/test_discharge.py b/macaroonbakery/tests/test_discharge.py
index 433483a..27bae63 100644
--- a/macaroonbakery/tests/test_discharge.py
+++ b/macaroonbakery/tests/test_discharge.py
@@ -2,11 +2,10 @@
# Licensed under the LGPLv3, see LICENCE file for details.
import unittest
-from pymacaroons import MACAROON_V1, Macaroon
-
-import macaroonbakery as bakery
+import macaroonbakery.bakery as bakery
import macaroonbakery.checkers as checkers
from macaroonbakery.tests import common
+from pymacaroons import MACAROON_V1, Macaroon
class TestDischarge(unittest.TestCase):
diff --git a/macaroonbakery/tests/test_discharge_all.py b/macaroonbakery/tests/test_discharge_all.py
index 7999f5f..cab8a07 100644
--- a/macaroonbakery/tests/test_discharge_all.py
+++ b/macaroonbakery/tests/test_discharge_all.py
@@ -2,11 +2,10 @@
# Licensed under the LGPLv3, see LICENCE file for details.
import unittest
-from pymacaroons.verifier import Verifier
-
-import macaroonbakery as bakery
+import macaroonbakery.bakery as bakery
import macaroonbakery.checkers as checkers
from macaroonbakery.tests import common
+from pymacaroons.verifier import Verifier
def always_ok(predicate):
diff --git a/macaroonbakery/tests/test_keyring.py b/macaroonbakery/tests/test_keyring.py
index 438ab1b..3503145 100644
--- a/macaroonbakery/tests/test_keyring.py
+++ b/macaroonbakery/tests/test_keyring.py
@@ -2,11 +2,11 @@
# Licensed under the LGPLv3, see LICENCE file for details.
import unittest
-from httmock import urlmatch, HTTMock
-
-import macaroonbakery as bakery
+import macaroonbakery.bakery as bakery
import macaroonbakery.httpbakery as httpbakery
+from httmock import HTTMock, urlmatch
+
class TestKeyRing(unittest.TestCase):
@@ -19,7 +19,7 @@ class TestKeyRing(unittest.TestCase):
'status_code': 200,
'content': {
'Version': bakery.LATEST_VERSION,
- 'PublicKey': key.public_key.encode().decode('utf-8')
+ 'PublicKey': str(key.public_key),
}
}
@@ -41,7 +41,7 @@ class TestKeyRing(unittest.TestCase):
'status_code': 200,
'content': {
'Version': bakery.LATEST_VERSION,
- 'PublicKey': key.public_key.encode().decode('utf-8')
+ 'PublicKey': str(key.public_key),
}
}
@@ -64,7 +64,7 @@ class TestKeyRing(unittest.TestCase):
return {
'status_code': 200,
'content': {
- 'PublicKey': key.public_key.encode().decode('utf-8')
+ 'PublicKey': str(key.public_key),
}
}
@@ -79,7 +79,7 @@ class TestKeyRing(unittest.TestCase):
def test_allow_insecure(self):
kr = httpbakery.ThirdPartyLocator()
- with self.assertRaises(bakery.error.ThirdPartyInfoNotFound):
+ with self.assertRaises(bakery.ThirdPartyInfoNotFound):
kr.third_party_info('http://0.1.2.3/')
def test_fallback(self):
@@ -96,7 +96,7 @@ class TestKeyRing(unittest.TestCase):
return {
'status_code': 200,
'content': {
- 'PublicKey': key.public_key.encode().decode('utf-8')
+ 'PublicKey': str(key.public_key),
}
}
diff --git a/macaroonbakery/tests/test_macaroon.py b/macaroonbakery/tests/test_macaroon.py
index 93bbbb8..bcbbf80 100644
--- a/macaroonbakery/tests/test_macaroon.py
+++ b/macaroonbakery/tests/test_macaroon.py
@@ -3,13 +3,12 @@
import json
from unittest import TestCase
-import six
-import pymacaroons
-from pymacaroons import serializers
-
-import macaroonbakery as bakery
+import macaroonbakery.bakery as bakery
import macaroonbakery.checkers as checkers
+import pymacaroons
+import six
from macaroonbakery.tests import common
+from pymacaroons import serializers
class TestMacaroon(TestCase):
@@ -25,7 +24,8 @@ class TestMacaroon(TestCase):
self.assertEquals(m.version, bakery.LATEST_VERSION)
def test_add_first_party_caveat(self):
- m = bakery.Macaroon('rootkey', 'some id', 'here', bakery.LATEST_VERSION)
+ m = bakery.Macaroon('rootkey', 'some id', 'here',
+ bakery.LATEST_VERSION)
m.add_caveat(checkers.Caveat('test_condition'))
caveats = m.first_party_caveats()
self.assertEquals(len(caveats), 1)
diff --git a/macaroonbakery/tests/test_oven.py b/macaroonbakery/tests/test_oven.py
index ae235de..3c29767 100644
--- a/macaroonbakery/tests/test_oven.py
+++ b/macaroonbakery/tests/test_oven.py
@@ -1,11 +1,10 @@
# Copyright 2017 Canonical Ltd.
# Licensed under the LGPLv3, see LICENCE file for details.
-from unittest import TestCase
-
import copy
from datetime import datetime, timedelta
+from unittest import TestCase
-import macaroonbakery as bakery
+import macaroonbakery.bakery as bakery
EPOCH = datetime(1900, 11, 17, 19, 00, 13, 0, None)
AGES = EPOCH + timedelta(days=10)
@@ -88,7 +87,8 @@ class TestOven(TestCase):
ops_store=bakery.MemoryOpsStore())
ops = []
for i in range(30000):
- ops.append(bakery.Op(entity='entity' + str(i), action='action' + str(i)))
+ ops.append(bakery.Op(entity='entity' + str(i),
+ action='action' + str(i)))
m = test_oven.macaroon(bakery.LATEST_VERSION, AGES,
None, ops)
diff --git a/macaroonbakery/tests/test_store.py b/macaroonbakery/tests/test_store.py
index 5afa7be..8a54f59 100644
--- a/macaroonbakery/tests/test_store.py
+++ b/macaroonbakery/tests/test_store.py
@@ -2,7 +2,7 @@
# Licensed under the LGPLv3, see LICENCE file for details.
from unittest import TestCase
-import macaroonbakery as bakery
+import macaroonbakery.bakery as bakery
class TestOven(TestCase):
diff --git a/macaroonbakery/tests/test_time.py b/macaroonbakery/tests/test_time.py
index 38826e1..2685e56 100644
--- a/macaroonbakery/tests/test_time.py
+++ b/macaroonbakery/tests/test_time.py
@@ -1,16 +1,15 @@
# Copyright 2017 Canonical Ltd.
# Licensed under the LGPLv3, see LICENCE file for details.
+from collections import namedtuple
from datetime import timedelta
from unittest import TestCase
-from collections import namedtuple
-import pyrfc3339
+import macaroonbakery.checkers as checkers
import pymacaroons
+import pyrfc3339
from pymacaroons import Macaroon
-import macaroonbakery.checkers as checkers
-
-t1 = pyrfc3339.parse('2017-10-26T16:19:47.441402074Z')
+t1 = pyrfc3339.parse('2017-10-26T16:19:47.441402074Z', produce_naive=True)
t2 = t1 + timedelta(hours=1)
t3 = t2 + timedelta(hours=1)
@@ -118,9 +117,17 @@ class TestExpireTime(TestCase):
]
for test in tests:
print('test ', test.about)
- t = checkers.macaroons_expiry_time(checkers.Namespace(), test.macaroons)
+ t = checkers.macaroons_expiry_time(checkers.Namespace(),
+ test.macaroons)
self.assertEqual(t, test.expectTime)
+ def test_macaroons_expire_time_skips_third_party(self):
+ m1 = newMacaroon([checkers.time_before_caveat(t1).condition])
+ m2 = newMacaroon()
+ m2.add_third_party_caveat('https://example.com', 'a-key', '123')
+ t = checkers.macaroons_expiry_time(checkers.Namespace(), [m1, m2])
+ self.assertEqual(t1, t)
+
def newMacaroon(conds=[]):
m = Macaroon(key='key', version=2)
diff --git a/macaroonbakery/tests/test_utils.py b/macaroonbakery/tests/test_utils.py
new file mode 100644
index 0000000..fcc8839
--- /dev/null
+++ b/macaroonbakery/tests/test_utils.py
@@ -0,0 +1,74 @@
+# Copyright 2017 Canonical Ltd.
+# Licensed under the LGPLv3, see LICENCE file for details.
+
+import json
+from datetime import datetime
+from unittest import TestCase
+
+import macaroonbakery.bakery as bakery
+import pymacaroons
+import pytz
+from macaroonbakery._utils import cookie
+from pymacaroons.serializers import json_serializer
+
+
+class CookieTest(TestCase):
+
+ def test_cookie_expires_naive(self):
+ timestamp = datetime.utcnow()
+ c = cookie('http://example.com', 'test', 'value', expires=timestamp)
+ self.assertEqual(
+ c.expires, int((timestamp - datetime(1970, 1, 1)).total_seconds()))
+
+ def test_cookie_expires_with_timezone(self):
+ timestamp = datetime.now(pytz.UTC)
+ self.assertRaises(
+ ValueError, cookie, 'http://example.com', 'test', 'value',
+ expires=timestamp)
+
+
+class TestB64Decode(TestCase):
+ def test_decode(self):
+ test_cases = [{
+ 'about': 'empty string',
+ 'input': '',
+ 'expect': '',
+ }, {
+ 'about': 'standard encoding, padded',
+ 'input': 'Z29+IQ==',
+ 'expect': 'go~!',
+ }, {
+ 'about': 'URL encoding, padded',
+ 'input': 'Z29-IQ==',
+ 'expect': 'go~!',
+ }, {
+ 'about': 'standard encoding, not padded',
+ 'input': 'Z29+IQ',
+ 'expect': 'go~!',
+ }, {
+ 'about': 'URL encoding, not padded',
+ 'input': 'Z29-IQ',
+ 'expect': 'go~!',
+ }, {
+ 'about': 'standard encoding, not enough much padding',
+ 'input': 'Z29+IQ=',
+ 'expect_error': 'illegal base64 data at input byte 8',
+ }]
+ for test in test_cases:
+ if test.get('expect_error'):
+ with self.assertRaises(ValueError, msg=test['about']) as e:
+ bakery.b64decode(test['input'])
+ self.assertEqual(str(e.exception), 'Incorrect padding')
+ else:
+ self.assertEqual(bakery.b64decode(test['input']), test['expect'].encode('utf-8'), msg=test['about'])
+
+
+class MacaroonToDictTest(TestCase):
+ def test_macaroon_to_dict(self):
+ m = pymacaroons.Macaroon(
+ key=b'rootkey', identifier=b'some id', location='here', version=2)
+ as_dict = bakery.macaroon_to_dict(m)
+ data = json.dumps(as_dict)
+ m1 = pymacaroons.Macaroon.deserialize(data, json_serializer.JsonSerializer())
+ self.assertEqual(m1.signature, m.signature)
+ pymacaroons.Verifier().verify(m1, b'rootkey')