bollard 1.0.3 → 2.0.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 +4 -4
- data/Gemfile.lock +1 -29
- data/bollard.gemspec +0 -2
- data/lib/bollard.rb +24 -7
- data/lib/bollard/signature.rb +13 -46
- data/lib/bollard/token.rb +64 -0
- data/lib/bollard/version.rb +2 -2
- data/spec/lib/bollard/signature_spec.rb +13 -61
- data/spec/lib/bollard/token_spec.rb +86 -0
- data/spec/lib/bollard_spec.rb +49 -24
- data/spec/spec_helper.rb +0 -1
- metadata +5 -33
- data/lib/bollard/post.rb +0 -40
- data/spec/lib/bollard/post_spec.rb +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9687b9297437ec802e5f2e5dcda2ca60354abf21
|
4
|
+
data.tar.gz: 17cc8bbce3ecf75cad13ab9a5c86b9794799979f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a0275e28c88eada5209443534874fd211b1f6a686dad5d801c77f575d992737e77395b71243fa6eef0e7d3c748df728e2c6f7f58e30a7e482febba25750b161e
|
7
|
+
data.tar.gz: f369b668086ffc82f4c129dc36b2b908675b09be7ced1ac0487d2b2e819e0a1fab1c3e1a0c415cd36045b58976098ed7963908bfb4466ef0211b4c4a79c1af5b
|
data/Gemfile.lock
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
bollard (
|
4
|
+
bollard (2.0.0)
|
5
5
|
jwt
|
6
|
-
rest-client
|
7
6
|
|
8
7
|
GEM
|
9
8
|
remote: https://rubygems.org/
|
@@ -13,32 +12,14 @@ GEM
|
|
13
12
|
i18n (>= 0.7, < 2)
|
14
13
|
minitest (~> 5.1)
|
15
14
|
tzinfo (~> 1.1)
|
16
|
-
addressable (2.5.2)
|
17
|
-
public_suffix (>= 2.0.2, < 4.0)
|
18
15
|
byebug (10.0.2)
|
19
16
|
concurrent-ruby (1.0.5)
|
20
|
-
crack (0.4.3)
|
21
|
-
safe_yaml (~> 1.0.0)
|
22
17
|
diff-lcs (1.3)
|
23
|
-
domain_name (0.5.20180417)
|
24
|
-
unf (>= 0.0.5, < 1.0.0)
|
25
|
-
hashdiff (0.3.7)
|
26
|
-
http-cookie (1.0.3)
|
27
|
-
domain_name (~> 0.5)
|
28
18
|
i18n (1.1.0)
|
29
19
|
concurrent-ruby (~> 1.0)
|
30
20
|
jwt (2.1.0)
|
31
|
-
mime-types (3.2.2)
|
32
|
-
mime-types-data (~> 3.2015)
|
33
|
-
mime-types-data (3.2018.0812)
|
34
21
|
minitest (5.11.3)
|
35
|
-
netrc (0.11.0)
|
36
|
-
public_suffix (3.0.3)
|
37
22
|
rake (12.3.1)
|
38
|
-
rest-client (2.0.2)
|
39
|
-
http-cookie (>= 1.0.2, < 2.0)
|
40
|
-
mime-types (>= 1.16, < 4.0)
|
41
|
-
netrc (~> 0.8)
|
42
23
|
rspec (3.8.0)
|
43
24
|
rspec-core (~> 3.8.0)
|
44
25
|
rspec-expectations (~> 3.8.0)
|
@@ -52,17 +33,9 @@ GEM
|
|
52
33
|
diff-lcs (>= 1.2.0, < 2.0)
|
53
34
|
rspec-support (~> 3.8.0)
|
54
35
|
rspec-support (3.8.0)
|
55
|
-
safe_yaml (1.0.4)
|
56
36
|
thread_safe (0.3.6)
|
57
37
|
tzinfo (1.2.5)
|
58
38
|
thread_safe (~> 0.1)
|
59
|
-
unf (0.1.4)
|
60
|
-
unf_ext
|
61
|
-
unf_ext (0.0.7.5)
|
62
|
-
webmock (2.3.2)
|
63
|
-
addressable (>= 2.3.6)
|
64
|
-
crack (>= 0.3.2)
|
65
|
-
hashdiff
|
66
39
|
|
67
40
|
PLATFORMS
|
68
41
|
ruby
|
@@ -73,7 +46,6 @@ DEPENDENCIES
|
|
73
46
|
byebug
|
74
47
|
rake
|
75
48
|
rspec (~> 3.7)
|
76
|
-
webmock (~> 2.3)
|
77
49
|
|
78
50
|
BUNDLED WITH
|
79
51
|
1.16.1
|
data/bollard.gemspec
CHANGED
@@ -18,11 +18,9 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.test_files = `git ls-files -- {spec,gemfiles}/*`.split("\n")
|
19
19
|
|
20
20
|
s.add_dependency "jwt"
|
21
|
-
s.add_dependency "rest-client"
|
22
21
|
|
23
22
|
s.add_development_dependency "activesupport", ">= 3.1"
|
24
23
|
s.add_development_dependency "rspec", "~> 3.7"
|
25
|
-
s.add_development_dependency "webmock", "~> 2.3"
|
26
24
|
s.add_development_dependency "byebug"
|
27
25
|
s.add_development_dependency "rake"
|
28
26
|
end
|
data/lib/bollard.rb
CHANGED
@@ -1,14 +1,31 @@
|
|
1
|
+
require 'bollard/version'
|
2
|
+
require 'bollard/token'
|
1
3
|
require 'bollard/signature'
|
2
|
-
require 'bollard/post'
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
require 'securerandom'
|
6
|
+
|
7
|
+
class Bollard
|
8
|
+
def self.generate_secret(length: 32)
|
9
|
+
SecureRandom.hex((length / 2.0).ceil)[0...length]
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def initialize(signing_secret)
|
14
|
+
@signing_secret = signing_secret
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def generate_token(payload, **args)
|
19
|
+
Token.generate(payload, signing_secret, **args)
|
8
20
|
end
|
9
21
|
|
10
22
|
|
11
|
-
def
|
12
|
-
|
23
|
+
def verify_payload(payload, token, **args)
|
24
|
+
Token.new(token, signing_secret).verify_payload(payload, **args)
|
13
25
|
end
|
26
|
+
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :signing_secret
|
14
31
|
end
|
data/lib/bollard/signature.rb
CHANGED
@@ -1,64 +1,32 @@
|
|
1
1
|
require 'digest'
|
2
|
-
require 'jwt'
|
3
|
-
|
4
|
-
module Bollard
|
5
|
-
class SignatureVerificationError < RuntimeError
|
6
|
-
attr_reader :message, :sig_header, :http_body
|
7
|
-
|
8
|
-
def initialize(message, sig_header, http_body: nil)
|
9
|
-
@message = message
|
10
|
-
@sig_header = sig_header
|
11
|
-
@http_body = http_body
|
12
|
-
end
|
13
|
-
end
|
14
2
|
|
3
|
+
class Bollard
|
15
4
|
class Signature
|
16
5
|
EXPECTED_ALGORITHM = "sig_v1".freeze
|
17
6
|
|
18
|
-
|
19
|
-
|
20
|
-
iat = Time.now.to_i
|
21
|
-
payload = { iat: iat, exp: iat + ttl, sig_v1: Digest::SHA256.hexdigest(data) }
|
22
|
-
JWT.encode(payload, secret, 'HS256')
|
7
|
+
def self.calculate_signature(payload)
|
8
|
+
Digest::SHA256.hexdigest(payload)
|
23
9
|
end
|
24
10
|
|
25
11
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
# - the header does not match the expected format
|
30
|
-
# - no hash found with the expected algorithm
|
31
|
-
# - hash doesn't match the expected hash
|
32
|
-
#
|
33
|
-
# Returns true otherwise
|
34
|
-
def self.verify(payload, header, secret, tolerance: nil)
|
35
|
-
begin
|
36
|
-
decoded_token = JWT.decode(header, secret, true, { exp_leeway: tolerance })
|
37
|
-
rescue JWT::DecodeError => e
|
38
|
-
raise SignatureVerificationError.new(e.message, header, http_body: payload)
|
39
|
-
end
|
40
|
-
|
41
|
-
provided_hash = decoded_token[0][EXPECTED_ALGORITHM]
|
42
|
-
if provided_hash.blank?
|
43
|
-
raise SignatureVerificationError.new(
|
44
|
-
"No hash found with expected algorithm #{EXPECTED_ALGORITHM}",
|
45
|
-
header, http_body: payload
|
46
|
-
)
|
47
|
-
end
|
12
|
+
def initialize(signature)
|
13
|
+
@signature = signature
|
14
|
+
end
|
48
15
|
|
49
|
-
expected_hash = Digest::SHA256.hexdigest(payload)
|
50
|
-
unless secure_compare(provided_hash, expected_hash)
|
51
|
-
raise SignatureVerificationError.new("Hash mismatch for payload", header, http_body: payload)
|
52
|
-
end
|
53
16
|
|
54
|
-
|
17
|
+
def match?(payload)
|
18
|
+
secure_compare(signature, self.class.calculate_signature(payload))
|
55
19
|
end
|
56
20
|
|
57
21
|
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :signature
|
25
|
+
|
58
26
|
# Constant time string comparison to prevent timing attacks
|
59
27
|
|
60
28
|
# Code borrowed from ActiveSupport
|
61
|
-
def
|
29
|
+
def secure_compare(a, b)
|
62
30
|
return false unless a.bytesize == b.bytesize
|
63
31
|
|
64
32
|
l = a.unpack "C#{a.bytesize}"
|
@@ -67,6 +35,5 @@ module Bollard
|
|
67
35
|
b.each_byte { |byte| res |= byte ^ l.shift }
|
68
36
|
res.zero?
|
69
37
|
end
|
70
|
-
private_class_method :secure_compare
|
71
38
|
end
|
72
39
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'jwt'
|
2
|
+
|
3
|
+
class Bollard
|
4
|
+
SignatureVerificationError = Class.new(RuntimeError)
|
5
|
+
|
6
|
+
class Token
|
7
|
+
|
8
|
+
# Generate the token header for a given payload.
|
9
|
+
# The token becomes invalid after `ttl` seconds.
|
10
|
+
#
|
11
|
+
# Returns a JWT with an iat, exp, and signature data
|
12
|
+
def self.generate(payload, signing_secret, ttl: 600)
|
13
|
+
iat = Time.now.to_i
|
14
|
+
signature = Signature.calculate_signature(payload)
|
15
|
+
jwt_payload = { iat: iat, exp: iat + ttl, Signature::EXPECTED_ALGORITHM => signature }
|
16
|
+
JWT.encode(jwt_payload, signing_secret, 'HS256')
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def initialize(token, signing_secret)
|
21
|
+
@token = token
|
22
|
+
@signing_secret = signing_secret
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# Verifies the token header for a given payload.
|
27
|
+
#
|
28
|
+
# Raises a SignatureVerificationError in the following cases:
|
29
|
+
# - the header does not match the expected format
|
30
|
+
# - no hash found with the expected algorithm
|
31
|
+
# - hash doesn't match the expected hash
|
32
|
+
#
|
33
|
+
# Returns true otherwise
|
34
|
+
def verify_payload(payload, tolerance: nil)
|
35
|
+
token_data, header = decode_token(tolerance)
|
36
|
+
signature = extract_signature(token_data)
|
37
|
+
verify_data(signature, payload)
|
38
|
+
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
attr_reader :token, :signing_secret
|
46
|
+
|
47
|
+
def decode_token(tolerance)
|
48
|
+
JWT.decode(token, signing_secret, true, { exp_leeway: tolerance })
|
49
|
+
rescue JWT::DecodeError => e
|
50
|
+
raise SignatureVerificationError.new(e.message)
|
51
|
+
end
|
52
|
+
|
53
|
+
def extract_signature(token_data)
|
54
|
+
signature = token_data[Signature::EXPECTED_ALGORITHM]
|
55
|
+
return Signature.new(signature) unless signature.blank?
|
56
|
+
raise SignatureVerificationError.new("No signature found with expected algorithm #{Signature::EXPECTED_ALGORITHM}")
|
57
|
+
end
|
58
|
+
|
59
|
+
def verify_data(signature, payload)
|
60
|
+
return true if signature.match?(payload)
|
61
|
+
raise SignatureVerificationError.new("Hash mismatch for payload")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/bollard/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = "
|
1
|
+
class Bollard
|
2
|
+
VERSION = "2.0.0"
|
3
3
|
end
|
@@ -2,74 +2,26 @@ require "spec_helper"
|
|
2
2
|
require 'digest'
|
3
3
|
|
4
4
|
RSpec.describe Bollard::Signature do
|
5
|
-
describe ".
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
it "generates a valid JWT for the given payload" do
|
10
|
-
expect { JWT.decode(jwt, "super-secret", true) }.not_to raise_error
|
11
|
-
end
|
12
|
-
|
13
|
-
it "adds an issued-at field" do
|
14
|
-
travel_to(Time.now) do
|
15
|
-
expect(jwt_payload["iat"]).to eq Time.now.to_i
|
16
|
-
end
|
5
|
+
describe ".calculate_signature" do
|
6
|
+
it "returns a signature" do
|
7
|
+
expect(Bollard::Signature.calculate_signature("Some test data")).to be_present
|
17
8
|
end
|
18
9
|
|
19
|
-
it "
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
context "when given a ttl" do
|
26
|
-
let(:jwt) { Bollard::Signature.generate("Some data", "super-secret", ttl: 100) }
|
27
|
-
|
28
|
-
it "sets the expires-at field using the configurable ttl" do
|
29
|
-
travel_to(Time.now) do
|
30
|
-
expect(jwt_payload["exp"]).to eq(Time.now.to_i + 100)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
it "adds a hash of the data" do
|
36
|
-
expect(jwt_payload["sig_v1"]).to eq(Digest::SHA256.hexdigest("Some data"))
|
37
|
-
end
|
38
|
-
|
39
|
-
it "signs the jwt with the secret so it can be verified" do
|
40
|
-
expect { JWT.decode(jwt, "not-the-same-secret", true) }.to raise_error(JWT::VerificationError, "Signature verification raised")
|
10
|
+
it "returns an SHA256 hex digest of the given data" do
|
11
|
+
expected_digest = "6e6ff23ec852afdf8fc294da163a55b2d246ec45b9659d290dc8871aea1502c0"
|
12
|
+
expect(Bollard::Signature.calculate_signature("Some test data")).to eq expected_digest
|
41
13
|
end
|
42
14
|
end
|
43
15
|
|
44
|
-
describe "
|
45
|
-
it "
|
46
|
-
|
47
|
-
expect
|
48
|
-
end
|
49
|
-
|
50
|
-
it "verifies the payload matches the given hash" do
|
51
|
-
jwt = Bollard::Signature.generate("Some data", "super-secret")
|
52
|
-
expect { Bollard::Signature.verify("Some different data", jwt, "super-secret") }.to raise_error(Bollard::SignatureVerificationError, "Hash mismatch for payload")
|
53
|
-
end
|
54
|
-
|
55
|
-
it "verifies the format of the token" do
|
56
|
-
jwt = JWT.encode({ sig_v2: Digest::SHA256.hexdigest("Some data") }, "super-secret", 'HS256')
|
57
|
-
|
58
|
-
expect { Bollard::Signature.verify("Some data", jwt, "super-secret") }.to raise_error(Bollard::SignatureVerificationError, "No hash found with expected algorithm #{Bollard::Signature::EXPECTED_ALGORITHM}")
|
59
|
-
end
|
60
|
-
|
61
|
-
it "ensures that the jwt hasn't expired" do
|
62
|
-
jwt = Bollard::Signature.generate("Some data", "super-secret")
|
63
|
-
travel_to(Time.now + 1200) do
|
64
|
-
expect { Bollard::Signature.verify("Some data", jwt, "super-secret") }.to raise_error(Bollard::SignatureVerificationError, "Signature has expired")
|
65
|
-
end
|
16
|
+
describe "#match?" do
|
17
|
+
it "returns true if the given signature matches the given payload" do
|
18
|
+
expected_digest = "6e6ff23ec852afdf8fc294da163a55b2d246ec45b9659d290dc8871aea1502c0"
|
19
|
+
expect(Bollard::Signature.new(expected_digest).match?("Some test data")).to be true
|
66
20
|
end
|
67
21
|
|
68
|
-
it "
|
69
|
-
|
70
|
-
|
71
|
-
expect { Bollard::Signature.verify("Some data", jwt, "super-secret", tolerance: 1200) }.not_to raise_error
|
72
|
-
end
|
22
|
+
it "returns false if the given signature doesn't match the given payload" do
|
23
|
+
expected_digest = "6e6ff23ec852afdf8fc294da163a55b2d246ec45b9659d290dc8871aea1502c0"
|
24
|
+
expect(Bollard::Signature.new(expected_digest).match?("Some other test data")).to be false
|
73
25
|
end
|
74
26
|
end
|
75
27
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Bollard::Token do
|
4
|
+
describe ".generate" do
|
5
|
+
it "returns a valid JWT" do
|
6
|
+
token = Bollard::Token.generate("Some test data", "-signing-secret-")
|
7
|
+
expect { JWT.decode(token, nil, false) }.not_to raise_error
|
8
|
+
end
|
9
|
+
|
10
|
+
it "returns a JWT signed with the given secret" do
|
11
|
+
token = Bollard::Token.generate("Some test data", "-signing-secret-")
|
12
|
+
expect { JWT.decode(token, "-signing-secret-", true) }.not_to raise_error
|
13
|
+
end
|
14
|
+
|
15
|
+
it "returns a JWT with an issued at timestamp (iat)" do
|
16
|
+
travel_to(Time.now) do
|
17
|
+
token = Bollard::Token.generate("Some test data", "-signing-secret-")
|
18
|
+
payload, header = JWT.decode(token, nil, false)
|
19
|
+
expect(payload['iat']).to eq Time.now.to_i
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "returns a JWT with an expiry timestamp (exp)" do
|
24
|
+
travel_to(Time.now) do
|
25
|
+
token = Bollard::Token.generate("Some test data", "-signing-secret-")
|
26
|
+
payload, header = JWT.decode(token, nil, false)
|
27
|
+
expect(payload['exp']).to eq Time.now.to_i + 600
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "returns a JWT with an expiry timestamp (exp) set to the expire after the given TTL" do
|
32
|
+
travel_to(Time.now) do
|
33
|
+
token = Bollard::Token.generate("Some test data", "-signing-secret-", ttl: 10)
|
34
|
+
payload, header = JWT.decode(token, nil, false)
|
35
|
+
expect(payload['exp']).to eq Time.now.to_i + 10
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#verify_payload" do
|
41
|
+
it "returns true if given a payload that matches the signature from the token" do
|
42
|
+
token = Bollard::Token.generate("Some test data", "-signing-secret-")
|
43
|
+
expect { Bollard::Token.new(token, "-signing-secret-").verify_payload("Some test data") }.not_to raise_error
|
44
|
+
end
|
45
|
+
|
46
|
+
it "raises an error if given a token that wasn't signed with the signing secret" do
|
47
|
+
token = Bollard::Token.generate("Some test data", "-signing-secret-")
|
48
|
+
expect { Bollard::Token.new(token, "-different-secret-").verify_payload("Some test data") }.to raise_error(
|
49
|
+
Bollard::SignatureVerificationError, "Signature verification raised"
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "raises an error if given a token that has expired" do
|
54
|
+
token = Bollard::Token.generate("Some test data", "-signing-secret-")
|
55
|
+
travel_to(Time.now + 1000) do
|
56
|
+
expect do
|
57
|
+
Bollard::Token.new(token, "-signing-secret-").verify_payload("Some test data")
|
58
|
+
end.to raise_error(Bollard::SignatureVerificationError, "Signature has expired")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
it "allows some tolerance in the expiry if custom tolerance passed in" do
|
63
|
+
token = Bollard::Token.generate("Some test data", "-signing-secret-")
|
64
|
+
travel_to(Time.now + 1000) do
|
65
|
+
expect do
|
66
|
+
Bollard::Token.new(token, "-signing-secret-").verify_payload("Some test data", tolerance: 1000)
|
67
|
+
end.not_to raise_error
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it "raises an error if given a token that doesn't contain expected signature information" do
|
72
|
+
token = JWT.encode({}, "-signing-secret-")
|
73
|
+
expected_error_message = "No signature found with expected algorithm #{Bollard::Signature::EXPECTED_ALGORITHM}"
|
74
|
+
expect do
|
75
|
+
Bollard::Token.new(token, "-signing-secret-").verify_payload("Some test data")
|
76
|
+
end.to raise_error(Bollard::SignatureVerificationError, expected_error_message)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "raises an error if given a payload that doesn't match the signature contained in the token" do
|
80
|
+
token = Bollard::Token.generate("Some test data", "-signing-secret-")
|
81
|
+
expect do
|
82
|
+
Bollard::Token.new(token, "-signing-secret-").verify_payload("Some other test data")
|
83
|
+
end.to raise_error(Bollard::SignatureVerificationError, "Hash mismatch for payload")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/spec/lib/bollard_spec.rb
CHANGED
@@ -1,45 +1,70 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
3
|
RSpec.describe Bollard do
|
4
|
-
describe ".
|
5
|
-
|
4
|
+
describe ".generate_secret" do
|
5
|
+
it "generates a secret key" do
|
6
|
+
expect(Bollard.generate_secret).to be_present
|
7
|
+
end
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
+
it "generates a secret key of the desired length" do
|
10
|
+
secret_key = Bollard.generate_secret(length: 67)
|
11
|
+
expect(secret_key.length).to be(67)
|
9
12
|
end
|
10
13
|
|
11
|
-
it "
|
12
|
-
Bollard.
|
13
|
-
|
14
|
-
|
14
|
+
it "doesn't generate the same secret key twice" do
|
15
|
+
secret_key = Bollard.generate_secret
|
16
|
+
other_secret_key = Bollard.generate_secret
|
17
|
+
|
18
|
+
expect(secret_key).not_to eq other_secret_key
|
15
19
|
end
|
20
|
+
end
|
21
|
+
|
16
22
|
|
17
|
-
|
18
|
-
|
19
|
-
|
23
|
+
describe "#generate_token" do
|
24
|
+
let(:bollard) { Bollard.new("-signing-secret-") }
|
25
|
+
|
26
|
+
it "returns a token" do
|
27
|
+
expect(bollard.generate_token("Some test data")).to be_present
|
20
28
|
end
|
21
29
|
|
22
|
-
it "
|
23
|
-
Bollard.
|
24
|
-
|
30
|
+
it "generates a new token using Bollard::Token with the given payload" do
|
31
|
+
expect(Bollard::Token).to receive(:generate).with("Some test data", "-signing-secret-", {})
|
32
|
+
|
33
|
+
bollard.generate_token("Some test data")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "passes on any given arguments" do
|
37
|
+
expect(Bollard::Token).to receive(:generate).with("Some test data", "-signing-secret-", ttl: 10)
|
38
|
+
|
39
|
+
bollard.generate_token("Some test data", ttl: 10)
|
25
40
|
end
|
26
41
|
end
|
27
42
|
|
28
|
-
|
29
|
-
|
30
|
-
|
43
|
+
|
44
|
+
describe "#verify_payload" do
|
45
|
+
let(:bollard) { Bollard.new("-signing-secret-") }
|
46
|
+
|
47
|
+
it "verifies a payload" do
|
48
|
+
token = bollard.generate_token("Some test data")
|
49
|
+
expect(bollard.verify_payload("Some test data", token)).to eq true
|
31
50
|
end
|
32
51
|
|
33
|
-
it "
|
34
|
-
|
52
|
+
it "verifies the payload using Bollard::Token" do
|
53
|
+
token = instance_double(Bollard::Token, verify_payload: true)
|
54
|
+
allow(Bollard::Token).to receive(:new).with("-token-", "-signing-secret-").and_return(token)
|
55
|
+
|
56
|
+
bollard.verify_payload("Some test data", "-token-")
|
35
57
|
|
36
|
-
expect(
|
58
|
+
expect(token).to have_received(:verify_payload).with("Some test data", {})
|
37
59
|
end
|
38
60
|
|
39
|
-
it "
|
40
|
-
Bollard
|
61
|
+
it "passes on any given arguments" do
|
62
|
+
token = instance_double(Bollard::Token, verify_payload: true)
|
63
|
+
allow(Bollard::Token).to receive(:new).with("-token-", "-signing-secret-").and_return(token)
|
64
|
+
|
65
|
+
bollard.verify_payload("Some test data", "-token-", tolerance: 100)
|
41
66
|
|
42
|
-
expect(
|
67
|
+
expect(token).to have_received(:verify_payload).with("Some test data", tolerance: 100)
|
43
68
|
end
|
44
69
|
end
|
45
70
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bollard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Dilley
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-08-
|
11
|
+
date: 2018-08-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jwt
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: rest-client
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: activesupport
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,20 +52,6 @@ dependencies:
|
|
66
52
|
- - "~>"
|
67
53
|
- !ruby/object:Gem::Version
|
68
54
|
version: '3.7'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: webmock
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '2.3'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '2.3'
|
83
55
|
- !ruby/object:Gem::Dependency
|
84
56
|
name: byebug
|
85
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,11 +94,11 @@ files:
|
|
122
94
|
- Rakefile
|
123
95
|
- bollard.gemspec
|
124
96
|
- lib/bollard.rb
|
125
|
-
- lib/bollard/post.rb
|
126
97
|
- lib/bollard/signature.rb
|
98
|
+
- lib/bollard/token.rb
|
127
99
|
- lib/bollard/version.rb
|
128
|
-
- spec/lib/bollard/post_spec.rb
|
129
100
|
- spec/lib/bollard/signature_spec.rb
|
101
|
+
- spec/lib/bollard/token_spec.rb
|
130
102
|
- spec/lib/bollard_spec.rb
|
131
103
|
- spec/spec_helper.rb
|
132
104
|
homepage: https://github.com/vinomofo/bollard
|
@@ -154,7 +126,7 @@ signing_key:
|
|
154
126
|
specification_version: 4
|
155
127
|
summary: Send a secure post somewhere
|
156
128
|
test_files:
|
157
|
-
- spec/lib/bollard/post_spec.rb
|
158
129
|
- spec/lib/bollard/signature_spec.rb
|
130
|
+
- spec/lib/bollard/token_spec.rb
|
159
131
|
- spec/lib/bollard_spec.rb
|
160
132
|
- spec/spec_helper.rb
|
data/lib/bollard/post.rb
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
require 'rest-client'
|
2
|
-
|
3
|
-
module Bollard
|
4
|
-
class PostError < RuntimeError
|
5
|
-
attr_reader :response
|
6
|
-
|
7
|
-
def initialize(message, response: nil)
|
8
|
-
@response = response
|
9
|
-
super(message)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
class Post
|
14
|
-
def initialize(url, payload, signing_secret, extra_headers, auth_header)
|
15
|
-
@url = url
|
16
|
-
@payload = payload
|
17
|
-
@signing_secret = signing_secret
|
18
|
-
@auth_header = auth_header
|
19
|
-
@extra_headers = extra_headers
|
20
|
-
end
|
21
|
-
|
22
|
-
def perform
|
23
|
-
RestClient.post(@url, @payload, headers)
|
24
|
-
rescue RestClient::ExceptionWithResponse => e
|
25
|
-
raise PostError.new(e.response.body, response: e.response)
|
26
|
-
rescue RestClient::Exception => e
|
27
|
-
raise PostError.new(e.message)
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def headers
|
33
|
-
@extra_headers.merge({ @auth_header => signature })
|
34
|
-
end
|
35
|
-
|
36
|
-
def signature
|
37
|
-
Signature.generate(@payload, @signing_secret)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
RSpec.describe Bollard::Post do
|
4
|
-
it "posts the payload to the given URL" do
|
5
|
-
stub_request(:post, "https://test.localhost/")
|
6
|
-
|
7
|
-
post = Bollard::Post.new("https://test.localhost/", "{}", "secret", {}, "Bollard-Signature")
|
8
|
-
post.perform
|
9
|
-
|
10
|
-
expect(WebMock).to have_requested(:post, "https://test.localhost").with(body: "{}")
|
11
|
-
end
|
12
|
-
|
13
|
-
it "adds the correct signature header to the request" do
|
14
|
-
allow(Bollard::Signature).to receive(:generate).and_return("valid_signature")
|
15
|
-
stub_request(:post, "https://test.localhost/")
|
16
|
-
|
17
|
-
post = Bollard::Post.new("https://test.localhost/", "{}", "secret", {}, "Bollard-Signature")
|
18
|
-
post.perform
|
19
|
-
|
20
|
-
expect(WebMock).to have_requested(:post, "https://test.localhost")
|
21
|
-
.with(headers: { "Bollard-Signature" => "valid_signature" })
|
22
|
-
end
|
23
|
-
|
24
|
-
it "adds extra headers to the request" do
|
25
|
-
stub_request(:post, "https://test.localhost/")
|
26
|
-
|
27
|
-
post = Bollard::Post.new("https://test.localhost/", "{}", "secret", { content_type: :json, accept: :json }, "Bollard-Signature")
|
28
|
-
post.perform
|
29
|
-
|
30
|
-
expect(WebMock).to have_requested(:post, "https://test.localhost")
|
31
|
-
.with(headers: { content_type: 'application/json', accept: 'application/json' })
|
32
|
-
end
|
33
|
-
end
|