pandexio 0.0.6 → 0.0.7

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/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
- - 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
- ```
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
- t.libs << 'test'
5
- t.verbose = true
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
@@ -1,156 +1,15 @@
1
- require 'time'
2
- require 'stringio'
3
- require 'openssl'
4
- require_relative 'request.rb'
5
- require_relative 'signing_algorithms.rb'
6
- require_relative 'signing_attributes.rb'
7
- require_relative 'signing_mechanisms.rb'
8
- require_relative 'signing_options.rb'
9
-
10
- module Pandexio
11
-
12
- LINE_BREAK = "\r\n"
13
- private_constant :LINE_BREAK
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
@@ -1,25 +1,25 @@
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
-
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
@@ -0,0 +1,13 @@
1
+ module Pandexio
2
+
3
+ class Scope
4
+
5
+ def initialize(params = {})
6
+ @document_ids = params.fetch(:document_ids, [])
7
+ end
8
+
9
+ attr_accessor :document_ids
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,7 @@
1
+ module Pandexio
2
+
3
+ class ScopePatterns
4
+ DOCUMENT_PATH_PATTERN = /(\/.*?documents\/)(?<documentid>[a-zA-Z0-9\-_]+)/i
5
+ end
6
+
7
+ end
@@ -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