pandexio 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +78 -78
- data/Rakefile +8 -8
- data/lib/pandexio.rb +14 -155
- data/lib/request.rb +24 -24
- data/lib/scope.rb +13 -0
- data/lib/scope_patterns.rb +7 -0
- data/lib/signer.rb +192 -0
- data/lib/signing_algorithms.rb +36 -36
- data/lib/signing_attributes.rb +21 -21
- data/lib/signing_mechanisms.rb +16 -16
- data/lib/signing_options.rb +29 -29
- data/test/test_header_signing.rb +327 -296
- data/test/test_query_string_signing.rb +408 -368
- data/test/test_scope.rb +37 -0
- data/test/test_scope_patterns.rb +56 -0
- data/test/test_signer.rb +112 -0
- metadata +15 -7
- checksums.yaml +0 -7
data/README.md
CHANGED
@@ -1,78 +1,78 @@
|
|
1
|
-
#Pandexio SDK for Ruby
|
2
|
-
|
3
|
-
##Overview
|
4
|
-
This Pandexio SDK enables Ruby server applications to easily generate signed requests that can be consumed by the Pandexio REST API (https://platform.pandexio.com) and the Pandexio Hosted Model (https://hosted.pandexio.com).
|
5
|
-
|
6
|
-
##Definitions
|
7
|
-
|
8
|
-
###Pandexio::Request
|
9
|
-
- type - class
|
10
|
-
- attributes:
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
###Pandexio::SigningOptions
|
18
|
-
- type - class
|
19
|
-
- attributes:
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
###Pandexio::to_authorized_request
|
39
|
-
- type - method
|
40
|
-
- arguments
|
41
|
-
|
42
|
-
|
43
|
-
- result
|
44
|
-
|
45
|
-
|
46
|
-
##Signing a Request
|
47
|
-
Take the following steps to create a signed Pandexio request.
|
48
|
-
|
49
|
-
1. Create an instance of Pandexio::Request containing details of the request to be made, the *normalized_request*.
|
50
|
-
2. Create an instance of Pandexio::SigningOptions. These *signing_options* specify the SDK will sign the request.
|
51
|
-
3. Call Pandexio::to_authorized_request, passing in the *normalized_request* and the *signing_options*.
|
52
|
-
4. Build an HTTP request using the Pandexio::Request, *authorized_request*, returned by Pandexio::to_authorized_request and make the HTTP request using the HTTP client of choice.
|
53
|
-
|
54
|
-
##All Together
|
55
|
-
Here's an example showing the generation of a signed Pandexio request to retrieve an extra large document cover thumbnail for a given document:
|
56
|
-
|
57
|
-
```ruby
|
58
|
-
require 'pandexio'
|
59
|
-
|
60
|
-
normalized_request = Pandexio::Request.new(
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
signing_options = Pandexio::SigningOptions.new(
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
authorized_request = Pandexio::to_authorized_request(normalized_request, signing_options)
|
78
|
-
```
|
1
|
+
#Pandexio SDK for Ruby
|
2
|
+
|
3
|
+
##Overview
|
4
|
+
This Pandexio SDK enables Ruby server applications to easily generate signed requests that can be consumed by the Pandexio REST API (https://platform.pandexio.com) and the Pandexio Hosted Model (https://hosted.pandexio.com).
|
5
|
+
|
6
|
+
##Definitions
|
7
|
+
|
8
|
+
###Pandexio::Request
|
9
|
+
- type - class
|
10
|
+
- attributes:
|
11
|
+
- method - required. The request HTTP verb (GET, POST, PUT, PATCH, DELETE).
|
12
|
+
- path - required. The path part of the request URL (e.g., /v2/documents/a4df7a95-d1f6-4664-b208-74568990bd15).
|
13
|
+
- query_parameters - optional. A hash containing the query parameters (e.g., { "coverSize" => "xl" }).
|
14
|
+
- headers - required. A hash containing the headers (e.g., { "Host" => "platform.pandexio.com" }). Host header must be included.
|
15
|
+
- payload - optional. UTF-8 encoded request body. Only applicable for POST, PUT, and PATCH requests.
|
16
|
+
|
17
|
+
###Pandexio::SigningOptions
|
18
|
+
- type - class
|
19
|
+
- attributes:
|
20
|
+
- algorithm - required. The HMAC algorithm to use. Choose from any specified in
|
21
|
+
- Pandexio::SigningAlgorithms::PDX_HMAC_MD5 - MD5 HMAC
|
22
|
+
- Pandexio::SigningAlgorithms::PDX_HMAC_SHA1 - SHA1 HMAC
|
23
|
+
- Pandexio::SigningAlgorithms::PDX_HMAC_SHA256 - SHA256 HMAC
|
24
|
+
- Pandexio::SigningAlgorithms::PDX_HMAC_SHA384 - SHA384 HMAC
|
25
|
+
- Pandexio::SigningAlgorithms::PDX_HMAC_SHA512 - SHA512 HMAC
|
26
|
+
- mechanism - required. Specifies which signing mechanism to use. Signing mechanisms include:
|
27
|
+
- Pandexio::SigningMechanisms::QUERY_STRING - Sign the request using query string parameters
|
28
|
+
- Pandexio::SigningMechanisms::HEADER - Sign the request using headers
|
29
|
+
- domain_id - required. Public API key.
|
30
|
+
- domain_key - required. Shared secret API key used to generate HMAC signatures.
|
31
|
+
- date - required. ISO8601 date/time when the request was made.
|
32
|
+
- expires - required. Number of seconds before the request signature expires.
|
33
|
+
- originator - required. The name of the application, feature, etc. making the request.
|
34
|
+
- email_address - required. The email address of the user making the request.
|
35
|
+
- display_name - required. The display name of the user making the request.
|
36
|
+
- profile_image - optional. The profile image thumbnail, either data URL or HTTP URL of the user making the request.
|
37
|
+
|
38
|
+
###Pandexio::to_authorized_request
|
39
|
+
- type - method
|
40
|
+
- arguments
|
41
|
+
- Pandexio::Request *normalized_request* - A non-signed request containing information about the request to be made.
|
42
|
+
- Pandexio::SigningOptions *signing_options* - The details specifying how to sign the *normalized_request*
|
43
|
+
- result
|
44
|
+
- Pandexio::Request *authorized_request* - A signed request containing information about the request to be made as well as signing information as headers or query string parameters, depending on the *signing_options*.
|
45
|
+
|
46
|
+
##Signing a Request
|
47
|
+
Take the following steps to create a signed Pandexio request.
|
48
|
+
|
49
|
+
1. Create an instance of Pandexio::Request containing details of the request to be made, the *normalized_request*.
|
50
|
+
2. Create an instance of Pandexio::SigningOptions. These *signing_options* specify the SDK will sign the request.
|
51
|
+
3. Call Pandexio::to_authorized_request, passing in the *normalized_request* and the *signing_options*.
|
52
|
+
4. Build an HTTP request using the Pandexio::Request, *authorized_request*, returned by Pandexio::to_authorized_request and make the HTTP request using the HTTP client of choice.
|
53
|
+
|
54
|
+
##All Together
|
55
|
+
Here's an example showing the generation of a signed Pandexio request to retrieve an extra large document cover thumbnail for a given document:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
require 'pandexio'
|
59
|
+
|
60
|
+
normalized_request = Pandexio::Request.new(
|
61
|
+
:method => "GET",
|
62
|
+
:path => "/v2/documents/a4df7a95-d1f6-4664-b208-74568990bd15/cover",
|
63
|
+
:query_parameters => { "coverSize" => "xl" },
|
64
|
+
:headers => { "Host" => "platform.pandexio.com" })
|
65
|
+
|
66
|
+
signing_options = Pandexio::SigningOptions.new(
|
67
|
+
:algorithm => Pandexio::SigningAlgorithms::PDX_HMAC_SHA256,
|
68
|
+
:mechanism => Pandexio::SigningMechanisms::QUERY_STRING,
|
69
|
+
:domain_id => "1234567890",
|
70
|
+
:domain_key => "asdfjklqwerzxcv",
|
71
|
+
:date => Time.utc(2015, 6, 10, 13, 22, 46),
|
72
|
+
:expires => 90,
|
73
|
+
:originator => "Demo",
|
74
|
+
:email_address => "terry@contoso.com",
|
75
|
+
:display_name => "Terry Contoso")
|
76
|
+
|
77
|
+
authorized_request = Pandexio::to_authorized_request(normalized_request, signing_options)
|
78
|
+
```
|
data/Rakefile
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
require 'rake/testtask'
|
2
|
-
|
3
|
-
Rake::TestTask.new do |t|
|
4
|
-
|
5
|
-
|
6
|
-
end
|
7
|
-
|
8
|
-
desc "Run tests"
|
1
|
+
require 'rake/testtask'
|
2
|
+
|
3
|
+
Rake::TestTask.new do |t|
|
4
|
+
t.libs << 'test'
|
5
|
+
t.verbose = true
|
6
|
+
end
|
7
|
+
|
8
|
+
desc "Run tests"
|
9
9
|
task :default => :test
|
data/lib/pandexio.rb
CHANGED
@@ -1,156 +1,15 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
def self.ordinal_key_value_sort(a, b)
|
18
|
-
|
19
|
-
a_codepoints, b_codepoints = a[0].codepoints.to_a, b[0].codepoints.to_a
|
20
|
-
|
21
|
-
min = [a_codepoints.length, b_codepoints.length].min - 1
|
22
|
-
|
23
|
-
for i in 0..min
|
24
|
-
a_codepoint = a_codepoints[i]
|
25
|
-
b_codepoint = b_codepoints[i]
|
26
|
-
c = a_codepoint <=> b_codepoint
|
27
|
-
return c if c != 0
|
28
|
-
end
|
29
|
-
|
30
|
-
return a_codepoints.length <=> b_codepoints.length
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.build_canonical_query_string(query_parameters)
|
35
|
-
|
36
|
-
temp_query_parameters = query_parameters.dup
|
37
|
-
|
38
|
-
temp_query_parameters = temp_query_parameters.sort { |a, b| ordinal_key_value_sort(a, b) }
|
39
|
-
|
40
|
-
canonical_query_string = StringIO.new
|
41
|
-
|
42
|
-
temp_query_parameters.each do |key, value|
|
43
|
-
next if key == SigningAttributes::ALGORITHM || key == SigningAttributes::CREDENTIAL || key == SigningAttributes::SIGNED_HEADERS || key == SigningAttributes::SIGNATURE
|
44
|
-
canonical_query_string << "&" if canonical_query_string.length > 0
|
45
|
-
canonical_query_string << "#{key}=#{value}"
|
46
|
-
end
|
47
|
-
|
48
|
-
return canonical_query_string.string
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
def self.build_canonical_headers(headers)
|
53
|
-
|
54
|
-
temp_headers = {}
|
55
|
-
|
56
|
-
headers.each do |key, value|
|
57
|
-
next if key == SigningAttributes::AUTHORIZATION
|
58
|
-
temp_headers[key.downcase.strip] = value
|
59
|
-
end
|
60
|
-
|
61
|
-
temp_headers = temp_headers.sort { |a, b| ordinal_key_value_sort(a, b) }
|
62
|
-
|
63
|
-
canonical_headers, signed_headers = StringIO.new, StringIO.new
|
64
|
-
|
65
|
-
temp_headers.each do |key, value|
|
66
|
-
next if key == SigningAttributes::AUTHORIZATION
|
67
|
-
canonical_headers << LINE_BREAK if canonical_headers.length > 0
|
68
|
-
canonical_headers << "#{key}:#{value}"
|
69
|
-
signed_headers << ";" if signed_headers.length > 0
|
70
|
-
signed_headers << "#{key}"
|
71
|
-
end
|
72
|
-
|
73
|
-
return canonical_headers.string, signed_headers.string
|
74
|
-
|
75
|
-
end
|
76
|
-
|
77
|
-
def self.build_canonical_payload(payload, digest)
|
78
|
-
canonical_payload = digest.hexdigest(payload).encode('UTF-8')
|
79
|
-
return canonical_payload;
|
80
|
-
end
|
81
|
-
|
82
|
-
def self.build_canonical_request(request, digest)
|
83
|
-
canonical_query_string = build_canonical_query_string(request.query_parameters)
|
84
|
-
canonical_headers, signed_headers = build_canonical_headers(request.headers)
|
85
|
-
canonical_payload = build_canonical_payload(request.payload, digest)
|
86
|
-
canonical_request = "#{request.method}#{LINE_BREAK}#{request.path}#{LINE_BREAK}#{canonical_query_string}#{LINE_BREAK}#{canonical_headers}#{LINE_BREAK}#{signed_headers}#{LINE_BREAK}#{canonical_payload}"
|
87
|
-
return canonical_request, signed_headers
|
88
|
-
end
|
89
|
-
|
90
|
-
def self.build_string_to_sign(canonical_request, signing_options)
|
91
|
-
signing_string = "#{signing_options.algorithm}#{LINE_BREAK}#{signing_options.date.iso8601}#{LINE_BREAK}#{canonical_request}".encode('UTF-8')
|
92
|
-
return signing_string
|
93
|
-
end
|
94
|
-
|
95
|
-
def self.generate_signature(string_to_sign, signing_options, digest)
|
96
|
-
return OpenSSL::HMAC.hexdigest(digest, signing_options.domain_key, string_to_sign)
|
97
|
-
end
|
98
|
-
|
99
|
-
public
|
100
|
-
|
101
|
-
def self.to_authorized_request(normalized_request, signing_options)
|
102
|
-
|
103
|
-
raise ArgumentError, 'normalized_request must be of type Pandexio::Request and cannot be nil' unless !normalized_request.nil? && normalized_request.is_a?(Request)
|
104
|
-
raise ArgumentError, 'normalized_request.query_parameters must be of type Hash and cannot be nil' unless !normalized_request.query_parameters.nil? && normalized_request.query_parameters.is_a?(Hash)
|
105
|
-
raise ArgumentError, 'normalized_request.headers must be of type Hash and cannot be nil' unless !normalized_request.headers.nil? && normalized_request.headers.is_a?(Hash)
|
106
|
-
|
107
|
-
raise ArgumentError, 'signing_options must be of type Pandexio::SigningOptions cannot be nil' unless !signing_options.nil? && signing_options.is_a?(SigningOptions)
|
108
|
-
raise ArgumentError, 'signing_options.domain_id must be of type String and cannot be nil or empty' unless !signing_options.domain_id.nil? && signing_options.domain_id.is_a?(String) && !signing_options.domain_id.empty?
|
109
|
-
raise ArgumentError, 'signing_options.domain_key must be of type String and cannot be nil or empty' unless !signing_options.domain_key.nil? && signing_options.domain_key.is_a?(String) && !signing_options.domain_key.empty?
|
110
|
-
raise ArgumentError, 'signing_options.algorithm must be of type String and cannot be nil or empty' unless SigningAlgorithms.is_v(signing_options.algorithm)
|
111
|
-
raise ArgumentError, 'signing_options.mechanism must be a valid signing mechanism' unless SigningMechanisms.is_v(signing_options.mechanism)
|
112
|
-
raise ArgumentError, 'signing_options.date must be of type Time and cannot be nil' unless !signing_options.date.nil? && signing_options.date.is_a?(Time)
|
113
|
-
raise ArgumentError, 'signing_options.expires must be of type Fixnum and cannot be nil or empty' unless !signing_options.expires.nil? && signing_options.expires.is_a?(Fixnum) && signing_options.expires > 0
|
114
|
-
raise ArgumentError, 'signing_options.originator must be of type String and cannot be nil or empty' unless !signing_options.originator.nil? && signing_options.originator.is_a?(String) && !signing_options.originator.empty?
|
115
|
-
raise ArgumentError, 'signing_options.email_address must be of type String and cannot be nil or empty' unless !signing_options.email_address.nil? && signing_options.email_address.is_a?(String) && !signing_options.email_address.empty?
|
116
|
-
raise ArgumentError, 'signing_options.display_name must be of type String and cannot be nil or empty' unless !signing_options.display_name.nil? && signing_options.display_name.is_a?(String) && !signing_options.display_name.empty?
|
117
|
-
|
118
|
-
authorized_request = Pandexio::Request.new(
|
119
|
-
:method => normalized_request.method,
|
120
|
-
:path => normalized_request.path,
|
121
|
-
:query_parameters => normalized_request.query_parameters.clone,
|
122
|
-
:headers => normalized_request.headers.clone,
|
123
|
-
:payload => normalized_request.payload)
|
124
|
-
|
125
|
-
append = lambda { |p|
|
126
|
-
p[SigningAttributes::DATE] = signing_options.date.iso8601
|
127
|
-
p[SigningAttributes::EXPIRES] = signing_options.expires
|
128
|
-
p[SigningAttributes::ORIGINATOR] = signing_options.originator
|
129
|
-
p[SigningAttributes::EMAIL_ADDRESS] = signing_options.email_address
|
130
|
-
p[SigningAttributes::DISPLAY_NAME] = signing_options.display_name
|
131
|
-
p[SigningAttributes::PROFILE_IMAGE] = signing_options.profile_image if !signing_options.profile_image.nil? && signing_options.profile_image.is_a?(String) && !signing_options.profile_image.empty?
|
132
|
-
}
|
133
|
-
|
134
|
-
append.call(
|
135
|
-
signing_options.mechanism == SigningMechanisms::QUERY_STRING ? authorized_request.query_parameters :
|
136
|
-
signing_options.mechanism == SigningMechanisms::HEADER ? authorized_request.headers : {})
|
137
|
-
|
138
|
-
digest = SigningAlgorithms.to_d(signing_options.algorithm)
|
139
|
-
canonical_request, signed_headers = build_canonical_request(authorized_request, digest)
|
140
|
-
string_to_sign = build_string_to_sign(canonical_request, signing_options)
|
141
|
-
signature = generate_signature(string_to_sign, signing_options, digest)
|
142
|
-
|
143
|
-
if signing_options.mechanism == SigningMechanisms::QUERY_STRING
|
144
|
-
authorized_request.query_parameters[SigningAttributes::ALGORITHM] = signing_options.algorithm
|
145
|
-
authorized_request.query_parameters[SigningAttributes::CREDENTIAL] = signing_options.domain_id
|
146
|
-
authorized_request.query_parameters[SigningAttributes::SIGNED_HEADERS] = signed_headers
|
147
|
-
authorized_request.query_parameters[SigningAttributes::SIGNATURE] = signature
|
148
|
-
elsif signing_options.mechanism == SigningMechanisms::HEADER
|
149
|
-
authorized_request.headers[SigningAttributes::AUTHORIZATION] = "#{signing_options.algorithm} Credential=#{signing_options.domain_id}, SignedHeaders=#{signed_headers}, Signature=#{signature}"
|
150
|
-
end
|
151
|
-
|
152
|
-
return authorized_request
|
153
|
-
|
154
|
-
end
|
155
|
-
|
1
|
+
require_relative 'signer.rb'
|
2
|
+
|
3
|
+
module Pandexio
|
4
|
+
|
5
|
+
def self.to_authorized_request(normalized_request, signing_options)
|
6
|
+
|
7
|
+
signer = Pandexio::Signer.new()
|
8
|
+
|
9
|
+
authorized_request = signer.sign(normalized_request, signing_options)
|
10
|
+
|
11
|
+
return authorized_request
|
12
|
+
|
13
|
+
end
|
14
|
+
|
156
15
|
end
|
data/lib/request.rb
CHANGED
@@ -1,25 +1,25 @@
|
|
1
|
-
module Pandexio
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
1
|
+
module Pandexio
|
2
|
+
|
3
|
+
class Request
|
4
|
+
|
5
|
+
def initialize(params = {})
|
6
|
+
@method = params.fetch(:method, nil)
|
7
|
+
@path = params.fetch(:path, nil)
|
8
|
+
@query_parameters = params.fetch(:query_parameters, {})
|
9
|
+
@headers = params.fetch(:headers, {})
|
10
|
+
@payload = params.fetch(:payload, nil)
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :method
|
14
|
+
attr_accessor :path
|
15
|
+
attr_accessor :query_parameters
|
16
|
+
attr_accessor :headers
|
17
|
+
attr_accessor :payload
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
"#{@method} #{@path}#{LINE_BREAK}query_parameters: #{query_parameters}#{LINE_BREAK}headers: #{headers}#{LINE_BREAK}payload: #{payload}"
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
25
|
end
|
data/lib/scope.rb
ADDED
data/lib/signer.rb
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'stringio'
|
3
|
+
require 'openssl'
|
4
|
+
require_relative 'request.rb'
|
5
|
+
require_relative 'scope.rb'
|
6
|
+
require_relative 'scope_patterns.rb'
|
7
|
+
require_relative 'signing_algorithms.rb'
|
8
|
+
require_relative 'signing_attributes.rb'
|
9
|
+
require_relative 'signing_mechanisms.rb'
|
10
|
+
require_relative 'signing_options.rb'
|
11
|
+
|
12
|
+
module Pandexio
|
13
|
+
|
14
|
+
class Signer
|
15
|
+
|
16
|
+
LINE_BREAK = "\r\n"
|
17
|
+
private_constant :LINE_BREAK
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def extract_scope(normalized_request)
|
22
|
+
scope = Pandexio::Scope.new()
|
23
|
+
|
24
|
+
match = normalized_request.path.match(Pandexio::ScopePatterns::DOCUMENT_PATH_PATTERN)
|
25
|
+
if !match.nil?
|
26
|
+
document_id = match["documentid"]
|
27
|
+
scope.document_ids.push(document_id)
|
28
|
+
end
|
29
|
+
|
30
|
+
normalized_request.query_parameters.each do |key, value|
|
31
|
+
next if key.casecmp('documentids') != 0
|
32
|
+
document_ids = value.split(',')
|
33
|
+
scope.document_ids.concat(document_ids)
|
34
|
+
end
|
35
|
+
|
36
|
+
return scope
|
37
|
+
end
|
38
|
+
|
39
|
+
def ordinal_key_value_sort(a, b)
|
40
|
+
|
41
|
+
a_codepoints, b_codepoints = a[0].codepoints.to_a, b[0].codepoints.to_a
|
42
|
+
|
43
|
+
min = [a_codepoints.length, b_codepoints.length].min - 1
|
44
|
+
|
45
|
+
for i in 0..min
|
46
|
+
a_codepoint = a_codepoints[i]
|
47
|
+
b_codepoint = b_codepoints[i]
|
48
|
+
c = a_codepoint <=> b_codepoint
|
49
|
+
return c if c != 0
|
50
|
+
end
|
51
|
+
|
52
|
+
return a_codepoints.length <=> b_codepoints.length
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
def build_canonical_query_string(query_parameters)
|
57
|
+
|
58
|
+
temp_query_parameters = query_parameters.sort { |a, b| ordinal_key_value_sort(a, b) }
|
59
|
+
|
60
|
+
canonical_query_string = StringIO.new
|
61
|
+
|
62
|
+
temp_query_parameters.each do |key, value|
|
63
|
+
next if key.casecmp(SigningAttributes::ALGORITHM) == 0 ||
|
64
|
+
key.casecmp(SigningAttributes::CREDENTIAL) == 0 ||
|
65
|
+
key.casecmp(SigningAttributes::SIGNED_HEADERS) == 0 ||
|
66
|
+
key.casecmp(SigningAttributes::SIGNATURE) == 0
|
67
|
+
canonical_query_string << "&" if canonical_query_string.length > 0
|
68
|
+
canonical_query_string << "#{key}=#{value}"
|
69
|
+
end
|
70
|
+
|
71
|
+
return canonical_query_string.string
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
def build_canonical_headers(headers)
|
76
|
+
|
77
|
+
temp_headers = {}
|
78
|
+
|
79
|
+
headers.each do |key, value|
|
80
|
+
next if key.casecmp(SigningAttributes::AUTHORIZATION) == 0
|
81
|
+
temp_headers[key.downcase.strip] = value
|
82
|
+
end
|
83
|
+
|
84
|
+
temp_headers = temp_headers.sort { |a, b| ordinal_key_value_sort(a, b) }
|
85
|
+
|
86
|
+
canonical_headers, signed_headers = StringIO.new, StringIO.new
|
87
|
+
|
88
|
+
temp_headers.each do |key, value|
|
89
|
+
canonical_headers << LINE_BREAK if canonical_headers.length > 0
|
90
|
+
canonical_headers << "#{key}:#{value}"
|
91
|
+
signed_headers << ";" if signed_headers.length > 0
|
92
|
+
signed_headers << "#{key}"
|
93
|
+
end
|
94
|
+
|
95
|
+
return canonical_headers.string, signed_headers.string
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
def build_canonical_payload(payload, digest)
|
100
|
+
canonical_payload = digest.hexdigest(payload).encode('UTF-8')
|
101
|
+
return canonical_payload;
|
102
|
+
end
|
103
|
+
|
104
|
+
def build_canonical_request(request, digest)
|
105
|
+
canonical_query_string = build_canonical_query_string(request.query_parameters)
|
106
|
+
canonical_headers, signed_headers = build_canonical_headers(request.headers)
|
107
|
+
canonical_payload = build_canonical_payload(request.payload, digest)
|
108
|
+
canonical_request = "#{request.method}#{LINE_BREAK}#{request.path}#{LINE_BREAK}#{canonical_query_string}#{LINE_BREAK}#{canonical_headers}#{LINE_BREAK}#{signed_headers}#{LINE_BREAK}#{canonical_payload}"
|
109
|
+
return canonical_request, signed_headers
|
110
|
+
end
|
111
|
+
|
112
|
+
def build_string_to_sign(canonical_request, signing_options)
|
113
|
+
signing_string = "#{signing_options.algorithm}#{LINE_BREAK}#{signing_options.date.iso8601}#{LINE_BREAK}#{canonical_request}".encode('UTF-8')
|
114
|
+
return signing_string
|
115
|
+
end
|
116
|
+
|
117
|
+
def generate_signature(string_to_sign, signing_options, digest)
|
118
|
+
return OpenSSL::HMAC.hexdigest(digest, signing_options.domain_key, string_to_sign)
|
119
|
+
end
|
120
|
+
|
121
|
+
def build_authorized_request(normalized_request, signing_options)
|
122
|
+
authorized_request = Pandexio::Request.new(
|
123
|
+
:method => normalized_request.method,
|
124
|
+
:path => normalized_request.path,
|
125
|
+
:query_parameters => normalized_request.query_parameters.clone,
|
126
|
+
:headers => normalized_request.headers.clone,
|
127
|
+
:payload => normalized_request.payload)
|
128
|
+
|
129
|
+
append = lambda { |p|
|
130
|
+
p[SigningAttributes::DATE] = signing_options.date.iso8601
|
131
|
+
p[SigningAttributes::EXPIRES] = signing_options.expires
|
132
|
+
p[SigningAttributes::ORIGINATOR] = signing_options.originator
|
133
|
+
p[SigningAttributes::EMAIL_ADDRESS] = signing_options.email_address
|
134
|
+
p[SigningAttributes::DISPLAY_NAME] = signing_options.display_name
|
135
|
+
p[SigningAttributes::PROFILE_IMAGE] = signing_options.profile_image if !signing_options.profile_image.nil? && signing_options.profile_image.is_a?(String) && !signing_options.profile_image.empty?
|
136
|
+
}
|
137
|
+
|
138
|
+
append.call(
|
139
|
+
signing_options.mechanism == SigningMechanisms::QUERY_STRING ? authorized_request.query_parameters :
|
140
|
+
signing_options.mechanism == SigningMechanisms::HEADER ? authorized_request.headers : {})
|
141
|
+
|
142
|
+
digest = SigningAlgorithms.to_d(signing_options.algorithm)
|
143
|
+
canonical_request, signed_headers = build_canonical_request(authorized_request, digest)
|
144
|
+
string_to_sign = build_string_to_sign(canonical_request, signing_options)
|
145
|
+
signature = generate_signature(string_to_sign, signing_options, digest)
|
146
|
+
|
147
|
+
if signing_options.mechanism == SigningMechanisms::QUERY_STRING
|
148
|
+
authorized_request.query_parameters[SigningAttributes::ALGORITHM] = signing_options.algorithm
|
149
|
+
authorized_request.query_parameters[SigningAttributes::CREDENTIAL] = signing_options.domain_id
|
150
|
+
authorized_request.query_parameters[SigningAttributes::SIGNED_HEADERS] = signed_headers
|
151
|
+
authorized_request.query_parameters[SigningAttributes::SIGNATURE] = signature
|
152
|
+
elsif signing_options.mechanism == SigningMechanisms::HEADER
|
153
|
+
authorized_request.headers[SigningAttributes::AUTHORIZATION] = "#{signing_options.algorithm} Credential=#{signing_options.domain_id}, SignedHeaders=#{signed_headers}, Signature=#{signature}"
|
154
|
+
end
|
155
|
+
|
156
|
+
return authorized_request
|
157
|
+
end
|
158
|
+
|
159
|
+
public
|
160
|
+
|
161
|
+
def sign(normalized_request, signing_options)
|
162
|
+
scope, authorized_request = scope_and_sign(normalized_request, signing_options)
|
163
|
+
return authorized_request
|
164
|
+
end
|
165
|
+
|
166
|
+
def scope_and_sign(normalized_request, signing_options)
|
167
|
+
|
168
|
+
raise ArgumentError, 'normalized_request must be of type Pandexio::Request and cannot be nil' unless !normalized_request.nil? && normalized_request.is_a?(Request)
|
169
|
+
raise ArgumentError, 'normalized_request.query_parameters must be of type Hash and cannot be nil' unless !normalized_request.query_parameters.nil? && normalized_request.query_parameters.is_a?(Hash)
|
170
|
+
raise ArgumentError, 'normalized_request.headers must be of type Hash and cannot be nil' unless !normalized_request.headers.nil? && normalized_request.headers.is_a?(Hash)
|
171
|
+
|
172
|
+
raise ArgumentError, 'signing_options must be of type Pandexio::SigningOptions cannot be nil' unless !signing_options.nil? && signing_options.is_a?(SigningOptions)
|
173
|
+
raise ArgumentError, 'signing_options.domain_id must be of type String and cannot be nil or empty' unless !signing_options.domain_id.nil? && signing_options.domain_id.is_a?(String) && !signing_options.domain_id.empty?
|
174
|
+
raise ArgumentError, 'signing_options.domain_key must be of type String and cannot be nil or empty' unless !signing_options.domain_key.nil? && signing_options.domain_key.is_a?(String) && !signing_options.domain_key.empty?
|
175
|
+
raise ArgumentError, 'signing_options.algorithm must be of type String and cannot be nil or empty' unless SigningAlgorithms.is_v(signing_options.algorithm)
|
176
|
+
raise ArgumentError, 'signing_options.mechanism must be a valid signing mechanism' unless SigningMechanisms.is_v(signing_options.mechanism)
|
177
|
+
raise ArgumentError, 'signing_options.date must be of type Time and cannot be nil' unless !signing_options.date.nil? && signing_options.date.is_a?(Time)
|
178
|
+
raise ArgumentError, 'signing_options.expires must be of type Fixnum and cannot be nil or empty' unless !signing_options.expires.nil? && signing_options.expires.is_a?(Fixnum) && signing_options.expires > 0
|
179
|
+
raise ArgumentError, 'signing_options.originator must be of type String and cannot be nil or empty' unless !signing_options.originator.nil? && signing_options.originator.is_a?(String) && !signing_options.originator.empty?
|
180
|
+
raise ArgumentError, 'signing_options.email_address must be of type String and cannot be nil or empty' unless !signing_options.email_address.nil? && signing_options.email_address.is_a?(String) && !signing_options.email_address.empty?
|
181
|
+
raise ArgumentError, 'signing_options.display_name must be of type String and cannot be nil or empty' unless !signing_options.display_name.nil? && signing_options.display_name.is_a?(String) && !signing_options.display_name.empty?
|
182
|
+
|
183
|
+
scope = extract_scope(normalized_request)
|
184
|
+
authorized_request = build_authorized_request(normalized_request, signing_options)
|
185
|
+
|
186
|
+
return scope, authorized_request
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|