uri_signer 0.0.1
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.
- 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
|