macaroons 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/localmed/ruby-macaroons.svg?branch=master)](https://travis-ci.org/localmed/ruby-macaroons)
|
3
|
+
[![Coverage Status](https://img.shields.io/coveralls/localmed/ruby-macaroons.svg)](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
|