uri_signer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.rvmrc.sample +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +65 -0
- data/Rakefile +1 -0
- data/lib/uri_signer.rb +26 -0
- data/lib/uri_signer/errors.rb +10 -0
- data/lib/uri_signer/errors/missing_base_uri_error.rb +6 -0
- data/lib/uri_signer/errors/missing_http_method_error.rb +6 -0
- data/lib/uri_signer/errors/missing_query_hash_error.rb +7 -0
- data/lib/uri_signer/errors/missing_secret_error.rb +6 -0
- data/lib/uri_signer/errors/missing_signature_string_error.rb +6 -0
- data/lib/uri_signer/errors/missing_uri_error.rb +6 -0
- data/lib/uri_signer/helpers.rb +4 -0
- data/lib/uri_signer/helpers/hash.rb +12 -0
- data/lib/uri_signer/helpers/string.rb +59 -0
- data/lib/uri_signer/query_hash_parser.rb +47 -0
- data/lib/uri_signer/request_parser.rb +143 -0
- data/lib/uri_signer/request_signature.rb +123 -0
- data/lib/uri_signer/signer.rb +108 -0
- data/lib/uri_signer/uri_signature.rb +79 -0
- data/lib/uri_signer/version.rb +3 -0
- data/reload_yard +3 -0
- data/spec/query_hash_parser_spec.rb +79 -0
- data/spec/request_parser_spec.rb +250 -0
- data/spec/request_signature_spec.rb +146 -0
- data/spec/signer_spec.rb +91 -0
- data/spec/unit/helpers/hash_spec.rb +12 -0
- data/spec/unit/helpers/string_spec.rb +41 -0
- data/spec/uri_signature_spec.rb +41 -0
- data/uri_signer.gemspec +31 -0
- metadata +237 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
module UriSigner
|
2
|
+
# This is responsible for preparing the raw request in the format necessary for signing. API URI signatures come in a few different flavors. This one
|
3
|
+
# will take HTTP_METHOD & ESCAPED_BASE_URI & SORTED_AND_ESCAPED_QUERY_PARAMS
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
#
|
7
|
+
# request_signature = UriSigner::RequestSignature.new('get', 'https://api.example.com/core/people.json', { 'page' => 5, 'per_page' => 25 })
|
8
|
+
#
|
9
|
+
# request_signature.http_method
|
10
|
+
# # => 'GET'
|
11
|
+
#
|
12
|
+
# request_signature.base_uri
|
13
|
+
# # => 'https://api.example.com/core/people.json'
|
14
|
+
#
|
15
|
+
# request_signature.query_params
|
16
|
+
# # => { 'page' => 5, 'per_page' => 25' }
|
17
|
+
#
|
18
|
+
# request_signature.query_params?
|
19
|
+
# # => true
|
20
|
+
#
|
21
|
+
# request_signature.encoded_base_uri
|
22
|
+
# # => "https%3A%2F%2Fapi.example.com%2Fcore%2Fpeople.json"
|
23
|
+
#
|
24
|
+
# request_signature.encoded_query_params
|
25
|
+
# # => "page%3D5%26per_page%3D25"
|
26
|
+
#
|
27
|
+
# request_signature.signature
|
28
|
+
# # => "GET&https%3A%2F%2Fapi.example.com%2Fcore%2Fpeople.json&page%3D5%26per_page%3D25"
|
29
|
+
#
|
30
|
+
class RequestSignature
|
31
|
+
|
32
|
+
# The default separator used to join the http_method, encoded_base_uri, and encoded_query_params
|
33
|
+
attr_reader :separator
|
34
|
+
|
35
|
+
# Create a new RequestSignature instance
|
36
|
+
#
|
37
|
+
# @param http_method [String] The HTTP method from the request (GET, POST, PUT, or DELETE)
|
38
|
+
# @param base_uri [String] The base URI of the request. This is everything except the query string params
|
39
|
+
# @param query_params [Hash] A hash of the provided query string params
|
40
|
+
#
|
41
|
+
# It's required that you provide at least the http_method and base_uri. Params are optional
|
42
|
+
#
|
43
|
+
# @return [void]
|
44
|
+
def initialize(http_method, base_uri, query_params = {})
|
45
|
+
@http_method = http_method
|
46
|
+
@base_uri = base_uri
|
47
|
+
@query_params = query_params
|
48
|
+
@separator = '&'
|
49
|
+
|
50
|
+
raise UriSigner::Errors::MissingHttpMethodError.new("Please provide an HTTP method") unless http_method?
|
51
|
+
raise UriSigner::Errors::MissingBaseUriError.new("Please provide a Base URI") unless base_uri?
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the full signature string
|
55
|
+
#
|
56
|
+
# @return [String]
|
57
|
+
def signature
|
58
|
+
core_signature = [self.http_method, self.encoded_base_uri]
|
59
|
+
core_signature << self.encoded_query_params if self.query_params?
|
60
|
+
core_signature.join(self.separator)
|
61
|
+
end
|
62
|
+
alias :to_s :signature
|
63
|
+
|
64
|
+
# Returns the uppercased HTTP Method
|
65
|
+
#
|
66
|
+
# @return [String]
|
67
|
+
def http_method
|
68
|
+
@http_method.upcase
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns the base URI
|
72
|
+
#
|
73
|
+
# @return [String]
|
74
|
+
def base_uri
|
75
|
+
@base_uri
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns the encoded base_uri
|
79
|
+
#
|
80
|
+
# This can be used for comparison to ensure the escaping is what you want
|
81
|
+
#
|
82
|
+
# @return [String] Escaped string of the base_uri
|
83
|
+
def encoded_base_uri
|
84
|
+
self.base_uri.extend(UriSigner::Helpers::String).escaped
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns the Query String parameters
|
88
|
+
#
|
89
|
+
# @return [Hash] The keys are stringified
|
90
|
+
def query_params
|
91
|
+
@query_params.extend(UriSigner::Helpers::Hash).stringify_keys
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns true if query params were provided
|
95
|
+
#
|
96
|
+
# @return [Bool]
|
97
|
+
def query_params?
|
98
|
+
!@query_params.blank?
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns the encoded query params as a string
|
102
|
+
#
|
103
|
+
# This joins the keys and values in one string, then joins them. Then it will escape the final contents.
|
104
|
+
#
|
105
|
+
# @return [String] Escaped string of the query params
|
106
|
+
def encoded_query_params
|
107
|
+
query_params_string.extend(UriSigner::Helpers::String).escaped
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
def http_method?
|
112
|
+
!@http_method.blank?
|
113
|
+
end
|
114
|
+
|
115
|
+
def base_uri?
|
116
|
+
!@base_uri.blank?
|
117
|
+
end
|
118
|
+
|
119
|
+
def query_params_string
|
120
|
+
@query_params_string ||= UriSigner::QueryHashParser.new(self.query_params).to_s
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module UriSigner
|
2
|
+
# This is the object that wraps the other building blocks and can be used to sign requests.
|
3
|
+
#
|
4
|
+
# @example
|
5
|
+
# http_method = "get"
|
6
|
+
# uri = "https://api.example.com/core/people.json?page=5&per_page=25&order=name:desc&select=id,name"
|
7
|
+
# secret = "my_secret"
|
8
|
+
#
|
9
|
+
# signer = UriSigner::UriSigner.new(http_method, uri, secret)
|
10
|
+
#
|
11
|
+
# signer.http_method
|
12
|
+
# # => "GET"
|
13
|
+
#
|
14
|
+
# signer.uri
|
15
|
+
# # => "https://api.example.com/core/people.json?page=5&per_page=25&order=name:desc&select=id,name"
|
16
|
+
#
|
17
|
+
# signer.signature
|
18
|
+
# # => "1AaJvChjz%2BZYJKxWsUQWNK1a%2BeGjpCs6uwQKwPw1%2FV8%3D"
|
19
|
+
#
|
20
|
+
# signer.uri_with_signature
|
21
|
+
# # => "https://api.example.com/core/people.json?_signature=6G4xiABih7FGvjwB1JsYXoeETtBCOdshIu93X1hltzk%3D"
|
22
|
+
#
|
23
|
+
# signer.valid?("1AaJvChjz%2BZYJKxWsUQWNK1a%2BeGjpCs6uwQKwPw1%2FV8%3D")
|
24
|
+
# # => true
|
25
|
+
#
|
26
|
+
# signer.valid?('1234')
|
27
|
+
# # => false
|
28
|
+
#
|
29
|
+
class Signer
|
30
|
+
# Create a new UriSigner instance
|
31
|
+
#
|
32
|
+
# @param http_method [String] The HTTP method used to make the request (GET, POST, PUT, and DELETE)
|
33
|
+
# @param uri [String] The requested URI
|
34
|
+
# @param secret [String] The secret that is used to sign the request
|
35
|
+
#
|
36
|
+
# @return [void]
|
37
|
+
def initialize(http_method, uri, secret)
|
38
|
+
@http_method = http_method
|
39
|
+
@uri = uri
|
40
|
+
@secret = secret
|
41
|
+
|
42
|
+
raise UriSigner::Errors::MissingHttpMethodError.new("Please provide an HTTP method") unless http_method?
|
43
|
+
raise UriSigner::Errors::MissingUriError.new("Please provide a URI") unless uri?
|
44
|
+
raise UriSigner::Errors::MissingSecretError.new("Please provide a secret to sign the string") unless secret?
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the URI passed into the constructor
|
48
|
+
#
|
49
|
+
# @return [String]
|
50
|
+
def uri
|
51
|
+
@uri
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the URI with the signature appended to the query string
|
55
|
+
#
|
56
|
+
# return [String]
|
57
|
+
def uri_with_signature
|
58
|
+
separator = if request_parser.query_params? then '&' else '?' end
|
59
|
+
"%s%s_signature=%s" % [self.uri, separator, self.signature]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the uppercased HTTP Method
|
63
|
+
#
|
64
|
+
# @return [String]
|
65
|
+
def http_method
|
66
|
+
@http_method.to_s.upcase
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the signature
|
70
|
+
#
|
71
|
+
# @return [String]
|
72
|
+
def signature
|
73
|
+
uri_signature.signature
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns true if +other+ matches the proper signature
|
77
|
+
#
|
78
|
+
# @return [Bool]
|
79
|
+
def valid?(other)
|
80
|
+
self.signature === other
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
def uri?
|
85
|
+
!@uri.blank?
|
86
|
+
end
|
87
|
+
|
88
|
+
def http_method?
|
89
|
+
!@http_method.blank?
|
90
|
+
end
|
91
|
+
|
92
|
+
def secret?
|
93
|
+
!@secret.blank?
|
94
|
+
end
|
95
|
+
|
96
|
+
def request_parser
|
97
|
+
@request_parser ||= UriSigner::RequestParser.new(self.http_method, self.uri)
|
98
|
+
end
|
99
|
+
|
100
|
+
def request_signature
|
101
|
+
@request_signature ||= UriSigner::RequestSignature.new(request_parser.http_method, request_parser.base_uri, request_parser.query_params)
|
102
|
+
end
|
103
|
+
|
104
|
+
def uri_signature
|
105
|
+
@uri_signature ||= UriSigner::UriSignature.new(request_signature.signature, @secret)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module UriSigner
|
2
|
+
# This is the object that will be used to verify properly signed API URI requests
|
3
|
+
# The #secret is stored in the persistence layer for comparison. There is an API Key
|
4
|
+
# and a shared secret. All requests will be signed with the shared secret. The URI
|
5
|
+
# will also include a _signature param, where the client will sign the request and
|
6
|
+
# store it in the URI.
|
7
|
+
#
|
8
|
+
# The signing algorithm looks like this:
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# secret = "my_secret"
|
12
|
+
# string_to_sign = "http://api.example.com/url/to_sign.json"
|
13
|
+
#
|
14
|
+
# hmac = HMAC::SHA256.new(secret)
|
15
|
+
#
|
16
|
+
# hmac.digest
|
17
|
+
# # => "??B\230????șo\271$'\256A?d?\223L\244\225\231\exR\270U"
|
18
|
+
#
|
19
|
+
# hmac << string_to_sign
|
20
|
+
#
|
21
|
+
# hmac.digest
|
22
|
+
# # => "?m?j\2761\031\235\206\260?A?\f\263\216\221\fBH?fC\215Ļ\204\233\202@/e"
|
23
|
+
#
|
24
|
+
# encoded = Base64.encode64(hmac.digest).chomp
|
25
|
+
# # => "8W3xar4xGZ2GsOJBmAyzjpEMQkg/ZkONxLuEm4JAL2U="
|
26
|
+
#
|
27
|
+
# escaped = Rack::Utils.escape(encoded)
|
28
|
+
# # => "8W3xar4xGZ2GsOJBmAyzjpEMQkg%2FZkONxLuEm4JAL2U%3D"
|
29
|
+
#
|
30
|
+
# # The final signed string is "8W3xar4xGZ2GsOJBmAyzjpEMQkg%2FZkONxLuEm4JAL2U%3D"
|
31
|
+
#
|
32
|
+
class UriSignature
|
33
|
+
# Create a new UriSignature instance
|
34
|
+
#
|
35
|
+
# @param signature_string [String] the string that needs to be signed
|
36
|
+
# @param secret [String] the secret to use for the signature
|
37
|
+
#
|
38
|
+
# @return [void]
|
39
|
+
def initialize(signature_string, secret)
|
40
|
+
@signature_string = signature_string
|
41
|
+
@secret = secret
|
42
|
+
|
43
|
+
raise UriSigner::Errors::MissingSignatureStringError.new("Please provide a string to sign") unless signature_string?
|
44
|
+
raise UriSigner::Errors::MissingSecretError.new("Please provide a secret to sign the string") unless secret?
|
45
|
+
end
|
46
|
+
|
47
|
+
# Return the signature string that was provided in the constructor
|
48
|
+
#
|
49
|
+
# @return [String]
|
50
|
+
def signature_string
|
51
|
+
@signature_string
|
52
|
+
end
|
53
|
+
|
54
|
+
# Return the signature_string after being signed with the secret
|
55
|
+
#
|
56
|
+
# @return [String]
|
57
|
+
def signature
|
58
|
+
@signature ||= sign!
|
59
|
+
end
|
60
|
+
alias :to_s :signature
|
61
|
+
|
62
|
+
private
|
63
|
+
def signature_string?
|
64
|
+
!@signature_string.blank?
|
65
|
+
end
|
66
|
+
|
67
|
+
def secret?
|
68
|
+
!@secret.blank?
|
69
|
+
end
|
70
|
+
|
71
|
+
def sign!
|
72
|
+
extension = UriSigner::Helpers::String
|
73
|
+
|
74
|
+
hmac = self.signature_string.extend(extension).hmac_signed_with(@secret)
|
75
|
+
encoded = hmac.extend(extension).base64_encoded
|
76
|
+
escaped = encoded.extend(extension).escaped
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/reload_yard
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../lib/uri_signer'))
|
2
|
+
|
3
|
+
class CustomHash < Hash; end
|
4
|
+
|
5
|
+
describe UriSigner::QueryHashParser do
|
6
|
+
before do
|
7
|
+
@query_hash = { "name" => "bob", "id" => "2344" }
|
8
|
+
end
|
9
|
+
|
10
|
+
subject { described_class.new(@query_hash) }
|
11
|
+
|
12
|
+
it "converts the hash to a string" do
|
13
|
+
subject.to_s.should == "id=2344&name=bob"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "raises a MissingQueryHashError if no hash is provided" do
|
17
|
+
lambda { described_class.new('') }.should raise_error(UriSigner::Errors::MissingQueryHashError)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "raises a MissingQueryHashError if nil is provided" do
|
21
|
+
lambda { described_class.new(nil) }.should raise_error(UriSigner::Errors::MissingQueryHashError)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "raises a MissingQueryHashError if a string is provided" do
|
25
|
+
lambda { described_class.new('name=bob') }.should raise_error(UriSigner::Errors::MissingQueryHashError)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "allows a custom hash to be provided" do
|
29
|
+
hash = CustomHash.new
|
30
|
+
hash['name'] = 'bob'
|
31
|
+
lambda { described_class.new(hash) }.should_not raise_error
|
32
|
+
end
|
33
|
+
|
34
|
+
context "one param" do
|
35
|
+
before do
|
36
|
+
@query_hash = {"order"=>["name:desc", "id:desc"]}
|
37
|
+
end
|
38
|
+
|
39
|
+
subject { described_class.new(query_hash) }
|
40
|
+
|
41
|
+
it "converts a single key with multiple values to a string" do
|
42
|
+
query_hash = {"order"=>["name:desc", "id:desc"]}
|
43
|
+
parser = described_class.new(query_hash)
|
44
|
+
|
45
|
+
parser.to_s.should == "order=name:desc&order=id:desc"
|
46
|
+
end
|
47
|
+
|
48
|
+
it "converts a single key value to a string" do
|
49
|
+
query_hash = {"order"=>["name:desc"]}
|
50
|
+
parser = described_class.new(query_hash)
|
51
|
+
|
52
|
+
parser.to_s.should == "order=name:desc"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "Parsing multiple params with different values" do
|
57
|
+
before do
|
58
|
+
@query_hash = {"order"=>["name:desc", "id:desc"], "_signature"=>"1234", "where"=>["name:nate", "id:123"]}
|
59
|
+
end
|
60
|
+
|
61
|
+
subject { described_class.new(@query_hash) }
|
62
|
+
|
63
|
+
it "converts the hash to a string" do
|
64
|
+
subject.to_s.should == "_signature=1234&order=name:desc&order=id:desc&where=name:nate&where=id:123"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "Parsing multiple params with duplicate values" do
|
69
|
+
before do
|
70
|
+
@query_hash = {"order"=>["name:desc", "name:desc"], "_signature"=>"1234", "where"=>["name:nate", "name:nate"]}
|
71
|
+
end
|
72
|
+
|
73
|
+
subject { described_class.new(@query_hash) }
|
74
|
+
|
75
|
+
it "converts the hash to a string" do
|
76
|
+
subject.to_s.should == "_signature=1234&order=name:desc&order=name:desc&where=name:nate&where=name:nate"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,250 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../lib/uri_signer'))
|
2
|
+
|
3
|
+
describe UriSigner::RequestParser do
|
4
|
+
before do
|
5
|
+
@method = 'GET'
|
6
|
+
@raw_uri = "https://api.example.com/core/people.json?_signature=1234&page=12&per_page=34&order=name:desc&select=id&select=guid"
|
7
|
+
end
|
8
|
+
|
9
|
+
subject { described_class.new(@method, @raw_uri) }
|
10
|
+
|
11
|
+
it "responds to #http_method" do
|
12
|
+
subject.should respond_to(:http_method)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "responds to #https?" do
|
16
|
+
subject.should respond_to(:https?)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "responds to #http?" do
|
20
|
+
subject.should respond_to(:http?)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "responds to #raw_uri" do
|
24
|
+
subject.should respond_to(:raw_uri)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "responds to #base_uri" do
|
28
|
+
subject.should respond_to(:base_uri)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "responds to #parsed_uri" do
|
32
|
+
subject.should respond_to(:parsed_uri)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "responds to #query_params" do
|
36
|
+
subject.should respond_to(:query_params)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "responds to #query_params?" do
|
40
|
+
subject.should respond_to(:query_params?)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "responds to #signature" do
|
44
|
+
subject.should respond_to(:signature)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "responds to #signature?" do
|
48
|
+
subject.should respond_to(:signature?)
|
49
|
+
end
|
50
|
+
|
51
|
+
context "With HTML encoded URI String" do
|
52
|
+
before do
|
53
|
+
@raw_uri = "https://api.kissmetrics.dev/v1/accounts?page=2&per_page=2&_signature=lertaejeT%252BN3M1pCjBZo8gCRK%252BAfTmOquNvMZjmAfGw%253D"
|
54
|
+
end
|
55
|
+
|
56
|
+
subject { described_class.new(@method, @raw_uri) }
|
57
|
+
|
58
|
+
it "returns the #http_method" do
|
59
|
+
subject.http_method.should == "GET"
|
60
|
+
end
|
61
|
+
|
62
|
+
it "returns true for #https?" do
|
63
|
+
subject.https?.should be_true
|
64
|
+
end
|
65
|
+
|
66
|
+
it "returns the #base_uri" do
|
67
|
+
subject.base_uri.should == "https://api.kissmetrics.dev/v1/accounts"
|
68
|
+
end
|
69
|
+
|
70
|
+
it "returns the query values" do
|
71
|
+
subject.query_params.size.should eql(2)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "does not return the signature in the query values" do
|
75
|
+
subject.query_params.should_not have_key('_signature')
|
76
|
+
end
|
77
|
+
|
78
|
+
it "returns true for #query_params?" do
|
79
|
+
subject.query_params?.should be_true
|
80
|
+
end
|
81
|
+
|
82
|
+
it "includes the proper keys in the query value" do
|
83
|
+
subject.query_params.should include('page', 'per_page')
|
84
|
+
end
|
85
|
+
|
86
|
+
it "returns the signature from the url" do
|
87
|
+
subject.signature.should == "lertaejeT%2BN3M1pCjBZo8gCRK%2BAfTmOquNvMZjmAfGw%3D"
|
88
|
+
end
|
89
|
+
|
90
|
+
it "returns true for #signature?" do
|
91
|
+
subject.signature?.should be_true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "With valid values" do
|
96
|
+
it "returns the #http_method" do
|
97
|
+
subject.http_method.should == "GET"
|
98
|
+
end
|
99
|
+
|
100
|
+
it "returns true for #https?" do
|
101
|
+
subject.https?.should be_true
|
102
|
+
end
|
103
|
+
|
104
|
+
it "returns false for #http?" do
|
105
|
+
subject.http?.should be_false
|
106
|
+
end
|
107
|
+
|
108
|
+
it "returns the #base_uri" do
|
109
|
+
subject.base_uri.should == "https://api.example.com/core/people.json"
|
110
|
+
end
|
111
|
+
|
112
|
+
it "returns the #raw_uri" do
|
113
|
+
subject.raw_uri.should == "https://api.example.com/core/people.json?_signature=1234&page=12&per_page=34&order=name:desc&select=id&select=guid"
|
114
|
+
end
|
115
|
+
|
116
|
+
it "returns the query values" do
|
117
|
+
subject.query_params.size.should eql(4)
|
118
|
+
end
|
119
|
+
|
120
|
+
it "does not return the signature in the query values" do
|
121
|
+
subject.query_params.should_not have_key('_signature')
|
122
|
+
end
|
123
|
+
|
124
|
+
it "returns true for #query_params?" do
|
125
|
+
subject.query_params?.should be_true
|
126
|
+
end
|
127
|
+
|
128
|
+
it "includes the proper keys in the query value" do
|
129
|
+
subject.query_params.should include('page','per_page','order')
|
130
|
+
end
|
131
|
+
|
132
|
+
it "returns multiple values for query param key" do
|
133
|
+
subject.query_params['select'].should eql ['id', 'guid']
|
134
|
+
end
|
135
|
+
|
136
|
+
it "returns the parsed_uri as an Addressable::URI object" do
|
137
|
+
subject.parsed_uri.should be_a_kind_of(Addressable::URI)
|
138
|
+
end
|
139
|
+
|
140
|
+
it "returns the signature from the url" do
|
141
|
+
subject.signature.should == "1234"
|
142
|
+
end
|
143
|
+
|
144
|
+
it "returns true for #signature?" do
|
145
|
+
subject.signature?.should be_true
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context "without query params" do
|
150
|
+
before do
|
151
|
+
@raw_uri = "https://api.example.com/core/people.json"
|
152
|
+
end
|
153
|
+
|
154
|
+
subject { described_class.new(@method, @raw_uri) }
|
155
|
+
|
156
|
+
it "returns empty query values" do
|
157
|
+
subject.query_params.size.should eql(0)
|
158
|
+
end
|
159
|
+
|
160
|
+
it "returns false for #signature?" do
|
161
|
+
subject.signature?.should be_false
|
162
|
+
end
|
163
|
+
|
164
|
+
it "returns false for #query_params?" do
|
165
|
+
subject.query_params?.should be_false
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
context "Determining the scheme" do
|
170
|
+
before do
|
171
|
+
@raw_uri = "http://api.example.com"
|
172
|
+
end
|
173
|
+
|
174
|
+
subject { described_class.new(@method, @raw_uri) }
|
175
|
+
|
176
|
+
it "returns false for #https?" do
|
177
|
+
subject.https?.should be_false
|
178
|
+
end
|
179
|
+
|
180
|
+
it "returns true for #http?" do
|
181
|
+
subject.http?.should be_true
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
context "With a signature and other query string values in the URI" do
|
186
|
+
before do
|
187
|
+
@raw_uri = "https://api.example.com/test?page=3&per_page=23&order=name:desc&_signature=3434343434gh="
|
188
|
+
end
|
189
|
+
|
190
|
+
subject { described_class.new(@method, @raw_uri) }
|
191
|
+
|
192
|
+
it "returns true for #signature?" do
|
193
|
+
subject.signature?.should be_true
|
194
|
+
end
|
195
|
+
|
196
|
+
it "escapes the signature" do
|
197
|
+
subject.signature.should == "3434343434gh%3D"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
context "With a signature and no other query string values in the URI" do
|
202
|
+
before do
|
203
|
+
@raw_uri = "https://api.example.com/test?_signature=3434343434gh="
|
204
|
+
end
|
205
|
+
|
206
|
+
subject { described_class.new(@method, @raw_uri) }
|
207
|
+
|
208
|
+
it "returns true for #signature?" do
|
209
|
+
subject.signature?.should be_true
|
210
|
+
end
|
211
|
+
|
212
|
+
it "escapes the signature" do
|
213
|
+
subject.signature.should == "3434343434gh%3D"
|
214
|
+
end
|
215
|
+
|
216
|
+
it "returns empty query values" do
|
217
|
+
subject.query_params.size.should eql(0)
|
218
|
+
end
|
219
|
+
|
220
|
+
it "returns false for #query_params?" do
|
221
|
+
subject.query_params?.should be_false
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
context "Without a signature in the URI" do
|
226
|
+
before do
|
227
|
+
@raw_uri = "https://api.example.com"
|
228
|
+
end
|
229
|
+
|
230
|
+
subject { described_class.new(@method, @raw_uri) }
|
231
|
+
|
232
|
+
it "returns an empty string for #signature" do
|
233
|
+
subject.signature.should be_empty
|
234
|
+
end
|
235
|
+
|
236
|
+
it "returns false for #signature?" do
|
237
|
+
subject.signature?.should be_false
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
context "Validations" do
|
242
|
+
it "raises a UriSigner:MissingHttpMethodError if an empty string is provided" do
|
243
|
+
lambda { described_class.new('', @raw_uri)}.should raise_error(UriSigner::Errors::MissingHttpMethodError)
|
244
|
+
end
|
245
|
+
|
246
|
+
it "raises a UriSigner::MissingHttpMethodError if nil is provided" do
|
247
|
+
lambda { described_class.new(nil, @raw_uri)}.should raise_error(UriSigner::Errors::MissingHttpMethodError)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|