macaroons 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +4 -0
- data/.travis.yml +15 -0
- data/Gemfile +5 -0
- data/LICENSE +20 -0
- data/README.md +120 -0
- data/lib/macaroons/caveat.rb +26 -0
- data/lib/macaroons/errors.rb +5 -0
- data/lib/macaroons/macaroons.rb +63 -0
- data/lib/macaroons/raw_macaroon.rb +90 -0
- data/lib/macaroons/serializers/base.rb +13 -0
- data/lib/macaroons/serializers/binary.rb +89 -0
- data/lib/macaroons/serializers/json.rb +28 -0
- data/lib/macaroons/utils.rb +49 -0
- data/lib/macaroons/verifier.rb +116 -0
- data/lib/macaroons/version.rb +3 -0
- data/lib/macaroons.rb +24 -0
- data/macaroons.gemspec +28 -0
- data/spec/integration_spec.rb +262 -0
- data/spec/spec_helper.rb +8 -0
- metadata +181 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1b8c0a4138d4382ec9066b5c95912acba6d473cd
|
4
|
+
data.tar.gz: d7c04a35aa9e133a51cc87538ef5eb93b0210e20
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 581dba3fd34883934f9cec9c7ea0bc85657471df394b15ed3a79ecfed7aa1bf508a4d40f3665512b7ad01720446c305a7378d1cfe4586591b3f093471000e3d5
|
7
|
+
data.tar.gz: 67bc07973505ed6ba9853622274c23725143bc9ae539877cf480233cabd5ddcfce5418f8a023bdab9e25fe9a1ce467f9b6047bb157f05aaa409f608c7706c0b8
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
Gemfile.lock
|
4
|
+
/.config
|
5
|
+
/coverage/
|
6
|
+
/InstalledFiles
|
7
|
+
/pkg/
|
8
|
+
/spec/reports/
|
9
|
+
/test/tmp/
|
10
|
+
/test/version_tmp/
|
11
|
+
/tmp/
|
12
|
+
|
13
|
+
## Documentation cache and generated files:
|
14
|
+
/.yardoc/
|
15
|
+
/_yardoc/
|
16
|
+
/doc/
|
17
|
+
/rdoc/
|
18
|
+
api.txt
|
19
|
+
|
20
|
+
## Environment normalisation:
|
21
|
+
/.bundle/
|
22
|
+
/lib/bundler/man/
|
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 2.1.0
|
4
|
+
- 2.0.0
|
5
|
+
- ruby-head
|
6
|
+
before_install:
|
7
|
+
- wget https://github.com/jedisct1/libsodium/releases/download/0.7.0/libsodium-0.7.0.tar.gz
|
8
|
+
- tar xzvf libsodium-0.7.0.tar.gz
|
9
|
+
- cd libsodium-0.7.0
|
10
|
+
- ./configure && make && make check && sudo make install
|
11
|
+
- sudo ldconfig
|
12
|
+
- cd ..
|
13
|
+
install: "bundle install"
|
14
|
+
script: rspec .
|
15
|
+
after_success: coveralls
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2014 LocalMed, Inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
# Macaroons
|
2
|
+
[](https://travis-ci.org/localmed/ruby-macaroons)
|
3
|
+
[](https://coveralls.io/r/localmed/ruby-macaroons?branch=master)
|
4
|
+
|
5
|
+
This is a Ruby implementation of Macaroons. It is still under active development but is in a useable state - please report any bugs in the issue tracker.
|
6
|
+
|
7
|
+
## What is a Macaroon?
|
8
|
+
Macaroons, like cookies, are a form of bearer credential. Unlike opaque tokens, macaroons embed *caveats* that define specific authorization requirements for the *target service*, the service that issued the root macaroon and which is capable of verifying the integrity of macaroons it recieves.
|
9
|
+
|
10
|
+
Macaroons allow for delegation and attenuation of authorization. They are simple and fast to verify, and decouple authorization policy from the enforcement of that policy.
|
11
|
+
|
12
|
+
Simple examples are outlined below. For more in-depth examples check out the [functional tests](https://github.com/localmed/ruby-macaroons/blob/master/spec/integration_spec.rb) and [references](#references).
|
13
|
+
|
14
|
+
## Installing
|
15
|
+
|
16
|
+
Macaroons requires a sodium library like libsodium or tweetnacl to be installed on the host system.
|
17
|
+
|
18
|
+
To install [libsodium](https://github.com/jedisct1/libsodium):
|
19
|
+
|
20
|
+
For OS X users, libsodium is available via homebrew and can be installed with:
|
21
|
+
|
22
|
+
brew install libsodium
|
23
|
+
|
24
|
+
For other systems, please see the [libsodium documentation](http://doc.libsodium.org/).
|
25
|
+
|
26
|
+
### Macaroons gem
|
27
|
+
|
28
|
+
Once you have libsodium installed, add this line to your application's Gemfile:
|
29
|
+
|
30
|
+
gem 'macaroons'
|
31
|
+
|
32
|
+
And then execute:
|
33
|
+
|
34
|
+
$ bundle
|
35
|
+
|
36
|
+
Or install it manually:
|
37
|
+
|
38
|
+
$ gem install macaroons
|
39
|
+
|
40
|
+
Inside of your Ruby program:
|
41
|
+
|
42
|
+
require 'macaroons'
|
43
|
+
|
44
|
+
## Quickstart
|
45
|
+
|
46
|
+
key => Very secret key used to sign the macaroon
|
47
|
+
identifier => An identifier, to remind you which key was used to sign the macaroon
|
48
|
+
location => The location at which the macaroon is created
|
49
|
+
|
50
|
+
# Construct a Macaroon.
|
51
|
+
m = Macaroon.new(key: key, identifier: identifier, location: 'http://foo.com')
|
52
|
+
|
53
|
+
# Add first party caveat
|
54
|
+
m.add_first_party_caveat('caveat_1')
|
55
|
+
|
56
|
+
# List all first party caveats
|
57
|
+
m.first_party_caveats
|
58
|
+
|
59
|
+
# Add third party caveat
|
60
|
+
m.add_third_party_caveat('caveat_key', 'caveat_id', 'http://foo.com')
|
61
|
+
|
62
|
+
# List all third party caveats
|
63
|
+
m.third_party_caveats
|
64
|
+
|
65
|
+
## Example with first- and third-party caveats
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
|
69
|
+
# Create macaroon. Sign with a key and identifier (a way to remember which key was used)
|
70
|
+
m = Macaroon.new(
|
71
|
+
location: 'http://mybank/',
|
72
|
+
identifier: 'we used our other secret key',
|
73
|
+
key: 'this is a different super-secret key; never use the same secret twice'
|
74
|
+
)
|
75
|
+
|
76
|
+
# Add a first party caveat
|
77
|
+
m.add_first_party_caveat('account = 3735928559')
|
78
|
+
|
79
|
+
# Add a third party caveat
|
80
|
+
caveat_key = '4; guaranteed random by a fair toss of the dice'
|
81
|
+
identifier = 'this was how we remind auth of key/pred'
|
82
|
+
m.add_third_party_caveat(caveat_key, identifier, 'http://auth.mybank/')
|
83
|
+
|
84
|
+
# User collects a discharge macaroon (likely from a separate service), that proves the claims in the third-party caveat and which may add additional caveats of its own
|
85
|
+
discharge = Macaroon.new(
|
86
|
+
location: 'http://auth.mybank/',
|
87
|
+
identifier: identifier,
|
88
|
+
caveat: Ocaveat_key
|
89
|
+
)
|
90
|
+
discharge.add_first_party_caveat('time < 2015-01-01T00:00')
|
91
|
+
|
92
|
+
# discharge macaroons are bound to the root macaroon so they cannot be reused
|
93
|
+
protected_discharge = m.prepare_for_request(discharge)
|
94
|
+
|
95
|
+
# The user sends their macaroon along with their discharge macaroons, and we verify them
|
96
|
+
v = Macaroon::Verifier.new()
|
97
|
+
v.satisfy_exact('account = 3735928559')
|
98
|
+
v.satisfy_exact('time < 2015-01-01T00:00')
|
99
|
+
verified = v.verify(
|
100
|
+
macaroon: m,
|
101
|
+
key: 'this is a different super-secret key; never use the same secret twice',
|
102
|
+
discharge_macaroons: [protected_discharge]
|
103
|
+
)
|
104
|
+
```
|
105
|
+
|
106
|
+
## More Macaroons
|
107
|
+
|
108
|
+
[PyMacaroons](https://github.com/ecordell/pymacaroons) is available for Python. PyMacaroons and Ruby-Macaroons are completely compatible (they can be used interchangibly within the same target service).
|
109
|
+
|
110
|
+
The [libmacaroons library](https://github.com/rescrv/libmacaroons) comes with Python and Go bindings.
|
111
|
+
|
112
|
+
PyMacaroons, libmacaroons, and Ruby-Macaroons all use the same underlying cryptographic library (libsodium).
|
113
|
+
|
114
|
+
## References
|
115
|
+
|
116
|
+
- [The Macaroon Paper](http://research.google.com/pubs/pub41892.html)
|
117
|
+
- [Mozilla Macaroon Tech Talk](https://air.mozilla.org/macaroons-cookies-with-contextual-caveats-for-decentralized-authorization-in-the-cloud/)
|
118
|
+
- [libmacaroons](https://github.com/rescrv/libmacaroons)
|
119
|
+
- [PyMacaroons](https://github.com/ecordell/pymacaroons)
|
120
|
+
- [libnacl](https://github.com/saltstack/libnacl)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Macaroons
|
2
|
+
class Caveat
|
3
|
+
def initialize(caveat_id, verification_id=nil, caveat_location=nil)
|
4
|
+
@caveat_id = caveat_id
|
5
|
+
@verification_id = verification_id
|
6
|
+
@caveat_location = caveat_location
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :caveat_id
|
10
|
+
attr_accessor :verification_id
|
11
|
+
attr_accessor :caveat_location
|
12
|
+
|
13
|
+
def first_party?
|
14
|
+
verification_id.nil?
|
15
|
+
end
|
16
|
+
|
17
|
+
def third_party?
|
18
|
+
!first_party?
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_h
|
22
|
+
{'cid' => @caveat_id, 'vid' => @verification_id, 'cl' => @caveat_location}
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'macaroons/raw_macaroon'
|
2
|
+
|
3
|
+
module Macaroons
|
4
|
+
class Macaroon
|
5
|
+
def initialize(key: nil, identifier: nil, location: nil, raw_macaroon: nil)
|
6
|
+
@raw_macaroon = raw_macaroon || RawMacaroon.new(key: key, identifier: identifier, location: location)
|
7
|
+
end
|
8
|
+
|
9
|
+
def identifier
|
10
|
+
@raw_macaroon.identifier
|
11
|
+
end
|
12
|
+
|
13
|
+
def location
|
14
|
+
@raw_macaroon.location
|
15
|
+
end
|
16
|
+
|
17
|
+
def signature
|
18
|
+
@raw_macaroon.signature
|
19
|
+
end
|
20
|
+
|
21
|
+
def caveats
|
22
|
+
@raw_macaroon.caveats
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.from_binary(serialized)
|
26
|
+
raw_macaroon = RawMacaroon.from_binary(serialized: serialized)
|
27
|
+
macaroon = Macaroons::Macaroon.new(raw_macaroon: raw_macaroon)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.from_json(serialized)
|
31
|
+
raw_macaroon = RawMacaroon.from_json(serialized: serialized)
|
32
|
+
macaroon = Macaroons::Macaroon.new(raw_macaroon: raw_macaroon)
|
33
|
+
end
|
34
|
+
|
35
|
+
def serialize
|
36
|
+
@raw_macaroon.serialize()
|
37
|
+
end
|
38
|
+
|
39
|
+
def serialize_json
|
40
|
+
@raw_macaroon.serialize_json()
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_first_party_caveat(predicate)
|
44
|
+
@raw_macaroon.add_first_party_caveat(predicate)
|
45
|
+
end
|
46
|
+
|
47
|
+
def first_party_caveats
|
48
|
+
caveats.select(&:first_party?)
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_third_party_caveat(caveat_key, caveat_id, caveat_location)
|
52
|
+
@raw_macaroon.add_third_party_caveat(caveat_key, caveat_id, caveat_location)
|
53
|
+
end
|
54
|
+
|
55
|
+
def third_party_caveats
|
56
|
+
caveats.select(&:third_party?)
|
57
|
+
end
|
58
|
+
|
59
|
+
def prepare_for_request(macaroon)
|
60
|
+
@raw_macaroon.prepare_for_request(macaroon)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
require 'rbnacl'
|
4
|
+
|
5
|
+
require 'macaroons/caveat'
|
6
|
+
require 'macaroons/utils'
|
7
|
+
require 'macaroons/serializers/binary'
|
8
|
+
require 'macaroons/serializers/json'
|
9
|
+
|
10
|
+
module Macaroons
|
11
|
+
class RawMacaroon
|
12
|
+
|
13
|
+
def initialize(key: nil, identifier: nil, location: nil)
|
14
|
+
if key.nil? || identifier.nil? || location.nil?
|
15
|
+
raise ArgumentError, 'Must provide all three: (key, id, location)'
|
16
|
+
end
|
17
|
+
|
18
|
+
@key = key
|
19
|
+
@identifier = identifier
|
20
|
+
@location = location
|
21
|
+
@signature = create_initial_macaroon_signature(key, identifier)
|
22
|
+
@caveats = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.from_binary(serialized: serialized)
|
26
|
+
Macaroons::BinarySerializer.new().deserialize(serialized)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.from_json(serialized: serialized)
|
30
|
+
Macaroons::JsonSerializer.new().deserialize(serialized)
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :identifier
|
34
|
+
attr_reader :key
|
35
|
+
attr_reader :location
|
36
|
+
attr_accessor :caveats
|
37
|
+
attr_accessor :signature
|
38
|
+
|
39
|
+
def signature
|
40
|
+
Utils.hexlify(@signature).downcase
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_first_party_caveat(predicate)
|
44
|
+
caveat = Caveat.new(predicate)
|
45
|
+
@caveats << caveat
|
46
|
+
@signature = Utils.sign_first_party_caveat(@signature, predicate)
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_third_party_caveat(caveat_key, caveat_id, caveat_location)
|
50
|
+
derived_caveat_key = Utils.truncate_or_pad(Utils.hmac('macaroons-key-generator', caveat_key))
|
51
|
+
truncated_or_padded_signature = Utils.truncate_or_pad(@signature)
|
52
|
+
box = RbNaCl::SimpleBox.from_secret_key(truncated_or_padded_signature)
|
53
|
+
ciphertext = box.encrypt(derived_caveat_key)
|
54
|
+
verification_id = Base64.strict_encode64(ciphertext)
|
55
|
+
caveat = Caveat.new(caveat_id, verification_id, caveat_location)
|
56
|
+
@caveats << caveat
|
57
|
+
@signature = Utils.sign_third_party_caveat(@signature, verification_id, caveat_id)
|
58
|
+
end
|
59
|
+
|
60
|
+
def serialize
|
61
|
+
Macaroons::BinarySerializer.new().serialize(self)
|
62
|
+
end
|
63
|
+
|
64
|
+
def serialize_json
|
65
|
+
Macaroons::JsonSerializer.new().serialize(self)
|
66
|
+
end
|
67
|
+
|
68
|
+
def prepare_for_request(macaroon)
|
69
|
+
bound_macaroon = Marshal.load( Marshal.dump( macaroon ) )
|
70
|
+
raw = bound_macaroon.instance_variable_get(:@raw_macaroon)
|
71
|
+
raw.signature = bind_signature(macaroon.signature)
|
72
|
+
bound_macaroon
|
73
|
+
end
|
74
|
+
|
75
|
+
def bind_signature(signature)
|
76
|
+
key = Utils.truncate_or_pad('0')
|
77
|
+
hash1 = Utils.hmac(key, Utils.unhexlify(self.signature))
|
78
|
+
hash2 = Utils.hmac(key, Utils.unhexlify(signature))
|
79
|
+
Utils.hmac(key, hash1 + hash2)
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def create_initial_macaroon_signature(key, identifier)
|
85
|
+
derived_key = Utils.generate_derived_key(key)
|
86
|
+
Utils.hmac(derived_key, identifier)
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
require 'macaroons/serializers/base'
|
4
|
+
|
5
|
+
module Macaroons
|
6
|
+
class BinarySerializer < BaseSerializer
|
7
|
+
PACKET_PREFIX_LENGTH = 4
|
8
|
+
|
9
|
+
def serialize(macaroon)
|
10
|
+
combined = packetize('location', macaroon.location)
|
11
|
+
combined += packetize('identifier', macaroon.identifier)
|
12
|
+
|
13
|
+
for caveat in macaroon.caveats
|
14
|
+
combined += packetize('cid', caveat.caveat_id)
|
15
|
+
|
16
|
+
if caveat.verification_id and caveat.caveat_location
|
17
|
+
combined += packetize('vid', caveat.verification_id)
|
18
|
+
combined += packetize('cl', caveat.caveat_location)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
combined += packetize(
|
23
|
+
'signature',
|
24
|
+
Utils.unhexlify(macaroon.signature)
|
25
|
+
)
|
26
|
+
Base64.urlsafe_encode64(combined)
|
27
|
+
end
|
28
|
+
|
29
|
+
def deserialize(serialized)
|
30
|
+
caveats = []
|
31
|
+
decoded = Base64.urlsafe_decode64(serialized)
|
32
|
+
|
33
|
+
index = 0
|
34
|
+
|
35
|
+
while index < decoded.length
|
36
|
+
packet_length = decoded[index..(index + PACKET_PREFIX_LENGTH - 1)].to_i(16)
|
37
|
+
stripped_packet = decoded[index + PACKET_PREFIX_LENGTH..(index + packet_length - 2)]
|
38
|
+
|
39
|
+
key, value = depacketize(stripped_packet)
|
40
|
+
|
41
|
+
case key
|
42
|
+
when 'location'
|
43
|
+
location = value
|
44
|
+
when 'identifier'
|
45
|
+
identifier = value
|
46
|
+
when 'cid'
|
47
|
+
caveats << Caveat.new(value)
|
48
|
+
when 'vid'
|
49
|
+
caveats[-1].verification_id = value
|
50
|
+
when 'cl'
|
51
|
+
caveats[-1].caveat_location = value
|
52
|
+
when 'signature'
|
53
|
+
signature = value
|
54
|
+
else
|
55
|
+
raise KeyError, 'Invalid key in binary macaroon. Macaroon may be corrupted.'
|
56
|
+
end
|
57
|
+
|
58
|
+
index = index + packet_length
|
59
|
+
end
|
60
|
+
macaroon = Macaroons::RawMacaroon.new(key: 'no_key', identifier: identifier, location: location)
|
61
|
+
macaroon.caveats = caveats
|
62
|
+
macaroon.signature = signature
|
63
|
+
macaroon
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def packetize(key, data)
|
69
|
+
# The 2 covers the space and the newline
|
70
|
+
packet_size = PACKET_PREFIX_LENGTH + 2 + key.length + data.length
|
71
|
+
if packet_size > 65535
|
72
|
+
# Due to packet structure, length of packet must be less than 0xFFFF
|
73
|
+
raise ArgumentError, 'Data is too long for a binary packet.'
|
74
|
+
end
|
75
|
+
packet_size_hex = packet_size.to_s(16)
|
76
|
+
header = packet_size_hex.to_s.rjust(4, '0')
|
77
|
+
packet_content = "#{key} #{data}\n"
|
78
|
+
packet = "#{header}#{packet_content}"
|
79
|
+
packet
|
80
|
+
end
|
81
|
+
|
82
|
+
def depacketize(packet)
|
83
|
+
key = packet.split(" ")[0]
|
84
|
+
value = packet[key.length + 1..-1]
|
85
|
+
[key, value]
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Macaroons
|
4
|
+
class JsonSerializer
|
5
|
+
|
6
|
+
def serialize(macaroon)
|
7
|
+
serialized = {
|
8
|
+
location: macaroon.location,
|
9
|
+
identifier: macaroon.identifier,
|
10
|
+
caveats: macaroon.caveats.map!(&:to_h),
|
11
|
+
signature: macaroon.signature
|
12
|
+
}
|
13
|
+
return serialized.to_json
|
14
|
+
end
|
15
|
+
|
16
|
+
def deserialize(serialized)
|
17
|
+
deserialized = JSON.parse(serialized)
|
18
|
+
macaroon = Macaroons::RawMacaroon.new(key: 'no_key', identifier: deserialized['identifier'], location: deserialized['location'])
|
19
|
+
deserialized['caveats'].each do |c|
|
20
|
+
caveat = Macaroons::Caveat.new(c['cid'], c['vid'], c['cl'])
|
21
|
+
macaroon.caveats << c
|
22
|
+
end
|
23
|
+
macaroon.signature = Utils.unhexlify(deserialized['signature'])
|
24
|
+
macaroon
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Macaroons
|
4
|
+
module Utils
|
5
|
+
|
6
|
+
def self.convert_to_bytes(string)
|
7
|
+
string.encode('us-ascii') unless string.nil?
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.hexlify(value)
|
11
|
+
value.unpack('C*').map { |byte| '%02X' % byte }.join('')
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.unhexlify(value)
|
15
|
+
[value].pack('H*')
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.truncate_or_pad(string, size=nil)
|
19
|
+
size = size.nil? ? 32 : size
|
20
|
+
if string.length > size
|
21
|
+
string[0, size]
|
22
|
+
elsif string.length > size
|
23
|
+
string + '\0'*(size-string.length)
|
24
|
+
else
|
25
|
+
string
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.hmac(key, data, digest=nil)
|
30
|
+
digest = OpenSSL::Digest.new('sha256') if digest.nil?
|
31
|
+
OpenSSL::HMAC.digest(digest, key, data)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.sign_first_party_caveat(signature, predicate)
|
35
|
+
Utils.hmac(signature, predicate)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.sign_third_party_caveat(signature, verification_id, caveat_id)
|
39
|
+
verification_id_hash = Utils.hmac(signature, verification_id)
|
40
|
+
caveat_id_hash = Utils.hmac(signature, caveat_id)
|
41
|
+
combined = verification_id_hash + caveat_id_hash
|
42
|
+
Utils.hmac(signature, combined)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.generate_derived_key(key)
|
46
|
+
Utils.hmac('macaroons-key-generator', key)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'rbnacl'
|
2
|
+
|
3
|
+
require 'macaroons/errors'
|
4
|
+
|
5
|
+
module Macaroons
|
6
|
+
class Verifier
|
7
|
+
attr_accessor :predicates
|
8
|
+
attr_accessor :callbacks
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@predicates = []
|
12
|
+
@callbacks = []
|
13
|
+
@calculated_signature = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def satisfy_exact(predicate)
|
17
|
+
raise ArgumentError, 'Must provide predicate' unless predicate
|
18
|
+
@predicates << predicate
|
19
|
+
end
|
20
|
+
|
21
|
+
def satisfy_general(callback = nil, &block)
|
22
|
+
raise ArgumentError, 'Must provide callback or block' unless callback || block_given?
|
23
|
+
callback = block if block_given?
|
24
|
+
@callbacks << callback
|
25
|
+
end
|
26
|
+
|
27
|
+
def verify(macaroon: nil, key: nil, discharge_macaroons: nil)
|
28
|
+
raise ArgumentError, 'Macaroon and Key required' if macaroon.nil? || key.nil?
|
29
|
+
key = Utils.generate_derived_key(key)
|
30
|
+
verify_discharge(root: macaroon, macaroon: macaroon, key: key, discharge_macaroons:discharge_macaroons)
|
31
|
+
end
|
32
|
+
|
33
|
+
def verify_discharge(root: root, macaroon: macaroon, key: key, discharge_macaroons: [])
|
34
|
+
@calculated_signature = Utils.hmac(key, macaroon.identifier)
|
35
|
+
|
36
|
+
verify_caveats(macaroon, discharge_macaroons)
|
37
|
+
|
38
|
+
if root != macaroon
|
39
|
+
raw = root.instance_variable_get(:@raw_macaroon)
|
40
|
+
@calculated_signature = raw.bind_signature(Utils.hexlify(@calculated_signature).downcase)
|
41
|
+
end
|
42
|
+
|
43
|
+
raise SignatureMismatchError, 'Signatures do not match.' unless signatures_match(Utils.unhexlify(macaroon.signature), @calculated_signature)
|
44
|
+
|
45
|
+
return true
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def verify_caveats(macaroon, discharge_macaroons)
|
51
|
+
for caveat in macaroon.caveats
|
52
|
+
if caveat.first_party?
|
53
|
+
caveat_met = verify_first_party_caveat(caveat)
|
54
|
+
else
|
55
|
+
caveat_met = verify_third_party_caveat(caveat, macaroon, discharge_macaroons)
|
56
|
+
end
|
57
|
+
raise CaveatUnsatisfiedError, "Caveat not met. Unable to satisfy: #{caveat.caveat_id}" unless caveat_met
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def verify_first_party_caveat(caveat)
|
62
|
+
caveat_met = false
|
63
|
+
if @predicates.include? caveat.caveat_id
|
64
|
+
caveat_met = true
|
65
|
+
else
|
66
|
+
@callbacks.each do |callback|
|
67
|
+
caveat_met = true if callback.call(caveat.caveat_id)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
@calculated_signature = Utils.sign_first_party_caveat(@calculated_signature, caveat.caveat_id) if caveat_met
|
71
|
+
return caveat_met
|
72
|
+
end
|
73
|
+
|
74
|
+
def verify_third_party_caveat(caveat, root_macaroon, discharge_macaroons)
|
75
|
+
caveat_met = false
|
76
|
+
|
77
|
+
caveat_macaroon = discharge_macaroons.find { |m| m.identifier == caveat.caveat_id }
|
78
|
+
raise CaveatUnsatisfiedError, "Caveat not met. No discharge macaroon found for identifier: #{caveat.caveat_id}" unless caveat_macaroon
|
79
|
+
|
80
|
+
caveat_key = extract_caveat_key(@calculated_signature, caveat)
|
81
|
+
caveat_macaroon_verifier = Verifier.new()
|
82
|
+
caveat_macaroon_verifier.predicates = @predicates
|
83
|
+
caveat_macaroon_verifier.callbacks = @callbacks
|
84
|
+
|
85
|
+
caveat_met = caveat_macaroon_verifier.verify_discharge(
|
86
|
+
root: root_macaroon,
|
87
|
+
macaroon: caveat_macaroon,
|
88
|
+
key: caveat_key,
|
89
|
+
discharge_macaroons: discharge_macaroons
|
90
|
+
)
|
91
|
+
if caveat_met
|
92
|
+
@calculated_signature = Utils.sign_third_party_caveat(@calculated_signature, caveat.verification_id, caveat.caveat_id)
|
93
|
+
end
|
94
|
+
return caveat_met
|
95
|
+
end
|
96
|
+
|
97
|
+
def extract_caveat_key(signature, caveat)
|
98
|
+
key = Utils.truncate_or_pad(signature)
|
99
|
+
box = RbNaCl::SimpleBox.from_secret_key(key)
|
100
|
+
decoded_vid = Base64.strict_decode64(caveat.verification_id)
|
101
|
+
box.decrypt(decoded_vid)
|
102
|
+
end
|
103
|
+
|
104
|
+
def signatures_match(a, b)
|
105
|
+
# Constant time compare, taken from Rack
|
106
|
+
return false unless a.bytesize == b.bytesize
|
107
|
+
|
108
|
+
l = a.unpack("C*")
|
109
|
+
|
110
|
+
r, i = 0, -1
|
111
|
+
b.each_byte { |v| r |= v ^ l[i+=1] }
|
112
|
+
r == 0
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
data/lib/macaroons.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'macaroons/macaroons'
|
2
|
+
require 'macaroons/verifier'
|
3
|
+
|
4
|
+
module Macaroon
|
5
|
+
class << self
|
6
|
+
def new(location: location, identifier: identifier, key: key)
|
7
|
+
Macaroons::Macaroon.new(location:location, identifier:identifier, key:key)
|
8
|
+
end
|
9
|
+
|
10
|
+
def from_binary(serialized)
|
11
|
+
Macaroons::Macaroon.from_binary(serialized)
|
12
|
+
end
|
13
|
+
|
14
|
+
def from_json(serialized)
|
15
|
+
Macaroons::Macaroon.from_json(serialized)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Verifier
|
20
|
+
def self.new()
|
21
|
+
Macaroons::Verifier.new()
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/macaroons.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'macaroons/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'macaroons'
|
8
|
+
spec.version = Macaroons::VERSION
|
9
|
+
spec.authors = ["Evan Cordell", "Peter Browne", "Joel James"]
|
10
|
+
spec.email = ["ecordell@localmed.com", "pete@localmed.com", "joel.james@localmed.com"]
|
11
|
+
spec.summary = "Macaroons library in Ruby"
|
12
|
+
spec.description = "Macaroons library in Ruby"
|
13
|
+
|
14
|
+
spec.files = `git ls-files`.split($/)
|
15
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
16
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
spec.required_ruby_version = "~> 2.0"
|
19
|
+
spec.add_dependency "json"
|
20
|
+
spec.add_dependency "rbnacl", "~> 3.1.2"
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "> 1.3"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
spec.add_development_dependency "rspec", "~> 3.1.0"
|
25
|
+
spec.add_development_dependency "pry"
|
26
|
+
spec.add_development_dependency "pry-stack_explorer"
|
27
|
+
spec.add_development_dependency "rspec_junit_formatter"
|
28
|
+
end
|
@@ -0,0 +1,262 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'macaroons'
|
3
|
+
require 'macaroons/errors'
|
4
|
+
|
5
|
+
describe 'Macaroon' do
|
6
|
+
context 'without caveats' do
|
7
|
+
it 'should have correct signature' do
|
8
|
+
m = Macaroon.new(
|
9
|
+
location: 'http://mybank/',
|
10
|
+
identifier: 'we used our secret key',
|
11
|
+
key: 'this is our super secret key; only we should know it'
|
12
|
+
)
|
13
|
+
expect(m.signature).to eql('e3d9e02908526c4c0039ae15114115d97fdd68bf2ba379b342aaf0f617d0552f')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'with first party caveat' do
|
18
|
+
it 'should have correct signature' do
|
19
|
+
m = Macaroon.new(
|
20
|
+
location: 'http://mybank/',
|
21
|
+
identifier: 'we used our secret key',
|
22
|
+
key: 'this is our super secret key; only we should know it'
|
23
|
+
)
|
24
|
+
m.add_first_party_caveat('test = caveat')
|
25
|
+
expect(m.signature).to eql('197bac7a044af33332865b9266e26d493bdd668a660e44d88ce1a998c23dbd67')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'when serilizing as binary' do
|
30
|
+
it 'should serialize properly' do
|
31
|
+
m = Macaroon.new(
|
32
|
+
location: 'http://mybank/',
|
33
|
+
identifier: 'we used our secret key',
|
34
|
+
key: 'this is our super secret key; only we should know it'
|
35
|
+
)
|
36
|
+
m.add_first_party_caveat('test = caveat')
|
37
|
+
expect(m.serialize()).to eql('MDAxY2xvY2F0aW9uIGh0dHA6Ly9teWJhbmsvCjAwMjZpZGVudGlmaWVyIHdlIHVzZWQgb3VyIHNlY3JldCBrZXkKMDAxNmNpZCB0ZXN0ID0gY2F2ZWF0CjAwMmZzaWduYXR1cmUgGXusegRK8zMyhluSZuJtSTvdZopmDkTYjOGpmMI9vWcK')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when deserializing binary' do
|
42
|
+
it 'should deserialize properly' do
|
43
|
+
m = Macaroon.from_binary(
|
44
|
+
'MDAxY2xvY2F0aW9uIGh0dHA6Ly9teWJhbmsvCjAwMjZpZGVudGlmaWVyIHdlIHVzZWQgb3VyIHNlY3JldCBrZXkKMDAxNmNpZCB0ZXN0ID0gY2F2ZWF0CjAwMmZzaWduYXR1cmUgGXusegRK8zMyhluSZuJtSTvdZopmDkTYjOGpmMI9vWcK'
|
45
|
+
)
|
46
|
+
expect(m.signature).to eql('197bac7a044af33332865b9266e26d493bdd668a660e44d88ce1a998c23dbd67')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'when serilizing as json' do
|
51
|
+
it 'should serialize properly' do
|
52
|
+
m = Macaroon.new(
|
53
|
+
location: 'http://mybank/',
|
54
|
+
identifier: 'we used our secret key',
|
55
|
+
key: 'this is our super secret key; only we should know it'
|
56
|
+
)
|
57
|
+
m.add_first_party_caveat('test = caveat')
|
58
|
+
expect(m.serialize_json()).to eql('{"location":"http://mybank/","identifier":"we used our secret key","caveats":[{"cid":"test = caveat","vid":null,"cl":null}],"signature":"197bac7a044af33332865b9266e26d493bdd668a660e44d88ce1a998c23dbd67"}')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'when deserializing json' do
|
63
|
+
it 'should deserialize properly' do
|
64
|
+
m = Macaroon.from_json(
|
65
|
+
'{"location":"http://mybank/","identifier":"we used our secret key","caveats":[{"cid":"test = caveat","vid":null,"cl":null}],"signature":"197bac7a044af33332865b9266e26d493bdd668a660e44d88ce1a998c23dbd67"}'
|
66
|
+
)
|
67
|
+
expect(m.signature).to eql('197bac7a044af33332865b9266e26d493bdd668a660e44d88ce1a998c23dbd67')
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'when serializing/deserializing binary with first and third caveats' do
|
72
|
+
it 'should serialize/deserialize properly' do
|
73
|
+
m = Macaroon.new(
|
74
|
+
location: 'http://mybank/',
|
75
|
+
identifier: 'we used our other secret key',
|
76
|
+
key: 'this is a different super-secret key; never use the same secret twice'
|
77
|
+
)
|
78
|
+
m.add_first_party_caveat('account = 3735928559')
|
79
|
+
caveat_key = '4; guaranteed random by a fair toss of the dice'
|
80
|
+
identifier = 'this was how we remind auth of key/pred'
|
81
|
+
m.add_third_party_caveat(caveat_key, identifier, 'http://auth.mybank/')
|
82
|
+
n = Macaroon.from_binary(m.serialize())
|
83
|
+
expect(m.signature).to eql(n.signature)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'when serializing/deserializing json with first and third caveats' do
|
88
|
+
it 'should serialize/deserialize properly' do
|
89
|
+
m = Macaroon.new(
|
90
|
+
location: 'http://mybank/',
|
91
|
+
identifier: 'we used our other secret key',
|
92
|
+
key: 'this is a different super-secret key; never use the same secret twice'
|
93
|
+
)
|
94
|
+
m.add_first_party_caveat('account = 3735928559')
|
95
|
+
caveat_key = '4; guaranteed random by a fair toss of the dice'
|
96
|
+
identifier = 'this was how we remind auth of key/pred'
|
97
|
+
m.add_third_party_caveat(caveat_key, identifier, 'http://auth.mybank/')
|
98
|
+
n = Macaroon.from_json(m.serialize_json())
|
99
|
+
expect(m.signature).to eql(n.signature)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'when preparing a macaroon for request' do
|
104
|
+
it 'should bind the signature to the root' do
|
105
|
+
m = Macaroon.new(
|
106
|
+
location: 'http://mybank/',
|
107
|
+
identifier: 'we used our other secret key',
|
108
|
+
key: 'this is a different super-secret key; never use the same secret twice'
|
109
|
+
)
|
110
|
+
m.add_first_party_caveat('account = 3735928559')
|
111
|
+
caveat_key = '4; guaranteed random by a fair toss of the dice'
|
112
|
+
identifier = 'this was how we remind auth of key/pred'
|
113
|
+
m.add_third_party_caveat(caveat_key, identifier, 'http://auth.mybank/')
|
114
|
+
|
115
|
+
discharge = Macaroon.new(
|
116
|
+
location: 'http://auth.mybank/',
|
117
|
+
identifier: identifier,
|
118
|
+
key: caveat_key
|
119
|
+
)
|
120
|
+
discharge.add_first_party_caveat('time < 2015-01-01T00:00')
|
121
|
+
protected_discharge = m.prepare_for_request(discharge)
|
122
|
+
|
123
|
+
expect(discharge.signature).not_to eql(protected_discharge.signature)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe 'Verifier' do
|
129
|
+
context 'verifying first party exact caveats' do
|
130
|
+
before(:all) do
|
131
|
+
@m = Macaroon.new(
|
132
|
+
location: 'http://mybank/',
|
133
|
+
identifier: 'we used our secret key',
|
134
|
+
key: 'this is our super secret key; only we should know it'
|
135
|
+
)
|
136
|
+
@m.add_first_party_caveat('test = caveat')
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'all caveats met' do
|
140
|
+
it 'should verify the macaroon' do
|
141
|
+
v = Macaroon::Verifier.new()
|
142
|
+
v.satisfy_exact('test = caveat')
|
143
|
+
verified = v.verify(
|
144
|
+
macaroon: @m,
|
145
|
+
key: 'this is our super secret key; only we should know it'
|
146
|
+
)
|
147
|
+
expect(verified).to be(true)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
context 'not all caveats met' do
|
151
|
+
it 'should raise an error' do
|
152
|
+
v = Macaroon::Verifier.new()
|
153
|
+
expect {
|
154
|
+
v.verify(
|
155
|
+
macaroon: @m,
|
156
|
+
key: 'this is our super secret key; only we should know it'
|
157
|
+
)
|
158
|
+
}.to raise_error
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context 'verifying first party general caveats' do
|
164
|
+
before(:all) do
|
165
|
+
@m = Macaroon.new(
|
166
|
+
location: 'http://mybank/',
|
167
|
+
identifier: 'we used our secret key',
|
168
|
+
key: 'this is our super secret key; only we should know it'
|
169
|
+
)
|
170
|
+
@m.add_first_party_caveat('general caveat')
|
171
|
+
end
|
172
|
+
|
173
|
+
context 'all caveats met' do
|
174
|
+
it 'should verify the macaroon' do
|
175
|
+
v = Macaroon::Verifier.new()
|
176
|
+
v.satisfy_general { |predicate| predicate == 'general caveat' }
|
177
|
+
verified = v.verify(
|
178
|
+
macaroon: @m,
|
179
|
+
key: 'this is our super secret key; only we should know it'
|
180
|
+
)
|
181
|
+
expect(verified).to be(true)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
context 'not all caveats met' do
|
185
|
+
it 'should raise an error' do
|
186
|
+
v = Macaroon::Verifier.new()
|
187
|
+
v.satisfy_general { |predicate| predicate == 'unmet' }
|
188
|
+
expect {
|
189
|
+
v.verify(
|
190
|
+
macaroon: @m,
|
191
|
+
key: 'this is our super secret key; only we should know it'
|
192
|
+
)
|
193
|
+
}.to raise_error
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context 'verifying third party caveats' do
|
199
|
+
before(:all) do
|
200
|
+
@m = Macaroon.new(
|
201
|
+
location: 'http://mybank/',
|
202
|
+
identifier: 'we used our other secret key',
|
203
|
+
key: 'this is a different super-secret key; never use the same secret twice'
|
204
|
+
)
|
205
|
+
@m.add_first_party_caveat('account = 3735928559')
|
206
|
+
caveat_key = '4; guaranteed random by a fair toss of the dice'
|
207
|
+
identifier = 'this was how we remind auth of key/pred'
|
208
|
+
@m.add_third_party_caveat(caveat_key, identifier, 'http://auth.mybank/')
|
209
|
+
|
210
|
+
discharge = Macaroon.new(
|
211
|
+
location: 'http://auth.mybank/',
|
212
|
+
identifier: identifier,
|
213
|
+
key: caveat_key
|
214
|
+
)
|
215
|
+
discharge.add_first_party_caveat('time < 2015-01-01T00:00')
|
216
|
+
@protected_discharge = @m.prepare_for_request(discharge)
|
217
|
+
end
|
218
|
+
|
219
|
+
context 'all caveats met and discharges provided' do
|
220
|
+
it 'should verify the macaroon' do
|
221
|
+
v = Macaroon::Verifier.new()
|
222
|
+
v.satisfy_exact('account = 3735928559')
|
223
|
+
v.satisfy_exact('time < 2015-01-01T00:00')
|
224
|
+
verified = v.verify(
|
225
|
+
macaroon: @m,
|
226
|
+
key: 'this is a different super-secret key; never use the same secret twice',
|
227
|
+
discharge_macaroons: [@protected_discharge]
|
228
|
+
)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
context 'not all caveats met' do
|
233
|
+
it 'should raise an error' do
|
234
|
+
v = Macaroon::Verifier.new()
|
235
|
+
v.satisfy_exact('account = 3735928559')
|
236
|
+
expect {
|
237
|
+
v.verify(
|
238
|
+
macaroon: @m,
|
239
|
+
key: 'this is a different super-secret key; never use the same secret twice',
|
240
|
+
discharge_macaroons: [@protected_discharge]
|
241
|
+
)
|
242
|
+
}.to raise_error
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
context 'not all discharges provided' do
|
247
|
+
it 'should raise an error' do
|
248
|
+
v = Macaroon::Verifier.new()
|
249
|
+
v.satisfy_exact('account = 3735928559')
|
250
|
+
v.satisfy_exact('time < 2015-01-01T00:00')
|
251
|
+
expect {
|
252
|
+
v.verify(
|
253
|
+
macaroon: @m,
|
254
|
+
key: 'this is a different super-secret key; never use the same secret twice',
|
255
|
+
discharge_macaroons: []
|
256
|
+
)
|
257
|
+
}.to raise_error
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: macaroons
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Evan Cordell
|
8
|
+
- Peter Browne
|
9
|
+
- Joel James
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2014-10-17 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: json
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - '>='
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '0'
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: rbnacl
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ~>
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 3.1.2
|
36
|
+
type: :runtime
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ~>
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 3.1.2
|
43
|
+
- !ruby/object:Gem::Dependency
|
44
|
+
name: bundler
|
45
|
+
requirement: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - '>'
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '1.3'
|
50
|
+
type: :development
|
51
|
+
prerelease: false
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - '>'
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '1.3'
|
57
|
+
- !ruby/object:Gem::Dependency
|
58
|
+
name: rake
|
59
|
+
requirement: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
type: :development
|
65
|
+
prerelease: false
|
66
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: rspec
|
73
|
+
requirement: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 3.1.0
|
78
|
+
type: :development
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ~>
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: 3.1.0
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: pry
|
87
|
+
requirement: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
type: :development
|
93
|
+
prerelease: false
|
94
|
+
version_requirements: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
- !ruby/object:Gem::Dependency
|
100
|
+
name: pry-stack_explorer
|
101
|
+
requirement: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
type: :development
|
107
|
+
prerelease: false
|
108
|
+
version_requirements: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
name: rspec_junit_formatter
|
115
|
+
requirement: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - '>='
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
type: :development
|
121
|
+
prerelease: false
|
122
|
+
version_requirements: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - '>='
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
description: Macaroons library in Ruby
|
128
|
+
email:
|
129
|
+
- ecordell@localmed.com
|
130
|
+
- pete@localmed.com
|
131
|
+
- joel.james@localmed.com
|
132
|
+
executables: []
|
133
|
+
extensions: []
|
134
|
+
extra_rdoc_files: []
|
135
|
+
files:
|
136
|
+
- .gitignore
|
137
|
+
- .rspec
|
138
|
+
- .travis.yml
|
139
|
+
- Gemfile
|
140
|
+
- LICENSE
|
141
|
+
- README.md
|
142
|
+
- lib/macaroons.rb
|
143
|
+
- lib/macaroons/caveat.rb
|
144
|
+
- lib/macaroons/errors.rb
|
145
|
+
- lib/macaroons/macaroons.rb
|
146
|
+
- lib/macaroons/raw_macaroon.rb
|
147
|
+
- lib/macaroons/serializers/base.rb
|
148
|
+
- lib/macaroons/serializers/binary.rb
|
149
|
+
- lib/macaroons/serializers/json.rb
|
150
|
+
- lib/macaroons/utils.rb
|
151
|
+
- lib/macaroons/verifier.rb
|
152
|
+
- lib/macaroons/version.rb
|
153
|
+
- macaroons.gemspec
|
154
|
+
- spec/integration_spec.rb
|
155
|
+
- spec/spec_helper.rb
|
156
|
+
homepage:
|
157
|
+
licenses: []
|
158
|
+
metadata: {}
|
159
|
+
post_install_message:
|
160
|
+
rdoc_options: []
|
161
|
+
require_paths:
|
162
|
+
- lib
|
163
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - ~>
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: '2.0'
|
168
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - '>='
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0'
|
173
|
+
requirements: []
|
174
|
+
rubyforge_project:
|
175
|
+
rubygems_version: 2.2.2
|
176
|
+
signing_key:
|
177
|
+
specification_version: 4
|
178
|
+
summary: Macaroons library in Ruby
|
179
|
+
test_files:
|
180
|
+
- spec/integration_spec.rb
|
181
|
+
- spec/spec_helper.rb
|