ecordell / pymacaroons

A Python Macaroon Library
pymacaroons.readthedocs.org
MIT License
79 stars 23 forks source link

Import at the same time as libmacaroons? #8

Closed rogpeppe closed 9 years ago

rogpeppe commented 9 years ago

I'm making a library that tests compatibility between different macaroons implementations [1]. To this end, I would like to be able to import both pymacaroons and the libmacaroons python library at the same time. Unfortunately they both use the "macaroons" identifier. Is there any way to work around this?

[1] https://github.com/go-macaroon/macarooncompat

ecordell commented 9 years ago

Hey @rogpeppe, I just saw your repository yesterday and was planning to get in touch.

I did not realize that I was conflicting with the name, for some reason I was thinking libmacaroons had you import libmacaroons. I'm updating this package so that it doesn't conflict, so anything version >0.5.1 should be fine.

This is how you import now (all docs have been updated as well):

from pymacaroons import Macaroon, Verifier, Caveat

# less common imports
from pymacaroons.utils import *
from pymacaroons.exceptions import *

And for testing in the same file you can always do a from pymacaroons import Macaroon as PyMacaroon, etc.

In the past I've verified that there's parity with libmacaroons but it would be good to have automated tests like you're suggesting. Let me know if there's anything I can do to help!

(one note: libmacaroons uses a string of null bytes instead of a random nonce in its third party caveat implementation - if you want to get them to match in tests you need to fix the same nonce)

rogpeppe commented 9 years ago

Great, thanks for the changes!

Is there any way to fake out the random source used by pymacaroons so that it always uses a zero nonce?

ecordell commented 9 years ago

Yup, here's an example from the tests (even using a null nonce):

    def test_third_party_caveat(self):
        m = Macaroon(
            location='http://mybank/',
            identifier='we used our other secret key',
            key='this is a different super-secret key; \
never use the same secret twice'
        )
        m.add_first_party_caveat('account = 3735928559')
        caveat_key = '4; guaranteed random by a fair toss of the dice'
        predicate = 'user = Alice'
        identifier = 'this was how we remind auth of key/pred'
        # use a fixed nonce to ensure the same signature
        m._raw_macaroon._add_third_party_caveat_direct(
            'http://auth.mybank/',
            caveat_key.encode('ascii'),
            identifier,
            nonce=truncate_or_pad(b'\0', size=24)
        )
        assert_equal(
            m.signature,
            '3cd1effbec018d6cbc1d17384d4924e3a708f25c2c0404d1a6af4f0578867fd0'
        )

It's a little messy since you have to mess with some of the internals, but I don't really think that fixing the nonce should be part of the public API.

rogpeppe commented 9 years ago

Perhaps a cleaner way to do it is this:

   libnacl.utils.rand_nonce = zero_nonce_generator

Then you only need to know that pymacaroons uses the default libnacl nonce generator, and not any of the other details. It's similar to the approach I use in Go actually.

ecordell commented 9 years ago

That's a good idea and I've updated my tests accordingly.

Not sure how familiar you are with python/mock/patching, but this is how you patch it:

    @patch('libnacl.secret.libnacl.utils.rand_nonce')
    def test_third_party_caveat(self, rand_nonce):
        # use a fixed nonce to ensure the same signature
        from libnacl import crypto_box_NONCEBYTES
        rand_nonce.return_value = truncate_or_pad(
            b'\0',
            size=crypto_box_NONCEBYTES
        )
        m = Macaroon(
            location='http://mybank/',
            identifier='we used our other secret key',
            key='this is a different super-secret key; \
never use the same secret twice'
        )
        m.add_first_party_caveat('account = 3735928559')
        caveat_key = '4; guaranteed random by a fair toss of the dice'
        predicate = 'user = Alice'
        identifier = 'this was how we remind auth of key/pred'
        m.add_third_party_caveat('http://auth.mybank/', caveat_key, identifier)
        assert_equal(
            m.signature,
            '6b99edb2ec6d7a4382071d7d41a0bf7dfa27d87d2f9fea86e330d7850ffda2b2'
        )

Patch handles temporarily mocking the implementation, and restricts the changes to one module (so you aren't replacing rand_nonce everywhere it's imported).

rogpeppe commented 9 years ago

Cool, thanks.