pandexio 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/module.rb +151 -0
- data/lib/request.rb +25 -0
- data/lib/signing_algorithms.rb +37 -0
- data/lib/signing_attributes.rb +22 -0
- data/lib/signing_mechanisms.rb +17 -0
- data/lib/signing_options.rb +30 -0
- data/test/test_header_signing.rb +35 -0
- data/test/test_query_string_signing.rb +44 -0
- metadata +51 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d09ed0b91e468d7feba7093a009224a310e77766
|
4
|
+
data.tar.gz: ac4d71c496c6769bc4b95a0e7514597ef3c0df7a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 48d13f7f4055315ad4ff67efd8d6ceecbe83355f1acf27ba695af5618389902c67ba606d6018d9bcbcc0fed9847c1352aa62b687c431916fb31b823cde364174
|
7
|
+
data.tar.gz: 44d1a250babf03e9083bcac1f2f53b73477879563d056b0ef2c9d062aea05b5707d4f598c9c89ac069b85c10404ceb7eaa608d2246a829ef831d7585b7a27592
|
data/lib/module.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'stringio'
|
3
|
+
require 'digest/hmac'
|
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, b[0].codepoints
|
20
|
+
|
21
|
+
max_i = [a_codepoints.size, b_codepoints.size].min
|
22
|
+
|
23
|
+
for i in 0..max_i
|
24
|
+
a_codepoint = a_codepoints[i]
|
25
|
+
b_codepoint = b_codepoints[i]
|
26
|
+
return -1 if a_codepoint < b_codepoint
|
27
|
+
return 1 if a_codepoint > b_codepoint
|
28
|
+
end
|
29
|
+
|
30
|
+
return 0
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.build_canonical_query_string(query_parameters)
|
35
|
+
|
36
|
+
temp_query_parameters = query_parameters.dup
|
37
|
+
|
38
|
+
query_sort = ->(a,b) { ordinal_key_value_sort(a, b) }
|
39
|
+
temp_query_parameters = temp_query_parameters.sort(&query_sort)
|
40
|
+
|
41
|
+
canonical_query_string = StringIO.new
|
42
|
+
|
43
|
+
temp_query_parameters.each do |key, value|
|
44
|
+
next if key == SigningAttributes::ALGORITHM || key == SigningAttributes::CREDENTIAL || key == SigningAttributes::SIGNED_HEADERS || key == SigningAttributes::SIGNATURE
|
45
|
+
canonical_query_string << "&" if canonical_query_string.length > 0
|
46
|
+
canonical_query_string << "#{key}=#{value}"
|
47
|
+
end
|
48
|
+
|
49
|
+
return canonical_query_string.string
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.build_canonical_headers(headers)
|
54
|
+
|
55
|
+
temp_headers = {}
|
56
|
+
|
57
|
+
headers.each do |key, value|
|
58
|
+
next if key == SigningAttributes::AUTHORIZATION
|
59
|
+
temp_headers[key.downcase.strip] = value
|
60
|
+
end
|
61
|
+
|
62
|
+
header_sort = ->(a,b) { ordinal_key_value_sort(a, b) }
|
63
|
+
temp_headers = temp_headers.sort(&header_sort)
|
64
|
+
|
65
|
+
canonical_headers, signed_headers = StringIO.new, StringIO.new
|
66
|
+
|
67
|
+
temp_headers.each do |key, value|
|
68
|
+
next if key == SigningAttributes::AUTHORIZATION
|
69
|
+
canonical_headers << LINE_BREAK if canonical_headers.length > 0
|
70
|
+
canonical_headers << "#{key}:#{value}"
|
71
|
+
signed_headers << ";" if signed_headers.length > 0
|
72
|
+
signed_headers << "#{key}"
|
73
|
+
end
|
74
|
+
|
75
|
+
return canonical_headers.string, signed_headers.string
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.build_canonical_payload(payload, digest)
|
80
|
+
return digest.hexdigest(payload)
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.build_canonical_request(request, digest)
|
84
|
+
canonical_query_string = build_canonical_query_string(request.query_parameters)
|
85
|
+
canonical_headers, signed_headers = build_canonical_headers(request.headers)
|
86
|
+
canonical_payload = build_canonical_payload(request.payload, digest)
|
87
|
+
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}"
|
88
|
+
return canonical_request, signed_headers
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.build_string_to_sign(canonical_request, signing_options)
|
92
|
+
return "#{signing_options.algorithm}#{LINE_BREAK}#{signing_options.date.iso8601}#{LINE_BREAK}#{canonical_request}"
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.generate_signature(string_to_sign, signing_options, digest)
|
96
|
+
return Digest::HMAC.hexdigest(string_to_sign, signing_options.domain_key, digest)
|
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 = normalized_request.dup
|
119
|
+
|
120
|
+
append = -> (p) do
|
121
|
+
p[SigningAttributes::DATE] = signing_options.date.iso8601
|
122
|
+
p[SigningAttributes::EXPIRES] = signing_options.expires
|
123
|
+
p[SigningAttributes::ORIGINATOR] = signing_options.originator
|
124
|
+
p[SigningAttributes::EMAIL_ADDRESS] = signing_options.email_address
|
125
|
+
p[SigningAttributes::DISPLAY_NAME] = signing_options.display_name
|
126
|
+
p[SigningAttributes::THUMBNAIL] = signing_options.thumbnail if !signing_options.thumbnail.nil? && signing_options.thumbnail.is_a?(String) && !signing_options.thumbnail.empty?
|
127
|
+
end
|
128
|
+
|
129
|
+
append.call(
|
130
|
+
signing_options.mechanism == SigningMechanisms::QUERY_STRING ? authorized_request.query_parameters :
|
131
|
+
signing_options.mechanism == SigningMechanisms::HEADER ? authorized_request.headers : {})
|
132
|
+
|
133
|
+
digest = SigningAlgorithms.to_d(signing_options.algorithm)
|
134
|
+
canonical_request, signed_headers = build_canonical_request(authorized_request, digest)
|
135
|
+
string_to_sign = build_string_to_sign(canonical_request, signing_options)
|
136
|
+
signature = generate_signature(string_to_sign, signing_options, digest)
|
137
|
+
|
138
|
+
if signing_options.mechanism == SigningMechanisms::QUERY_STRING
|
139
|
+
authorized_request.query_parameters[SigningAttributes::ALGORITHM] = signing_options.algorithm
|
140
|
+
authorized_request.query_parameters[SigningAttributes::CREDENTIAL] = signing_options.domain_id
|
141
|
+
authorized_request.query_parameters[SigningAttributes::SIGNED_HEADERS] = signed_headers
|
142
|
+
authorized_request.query_parameters[SigningAttributes::SIGNATURE] = signature
|
143
|
+
elsif signing_options.mechanism == SigningMechanisms::HEADER
|
144
|
+
authorized_request.headers[SigningAttributes::AUTHORIZATION] = "#{signing_options.algorithm} Credential=#{signing_options.domain_id}, SignedHeaders=#{signed_headers}, Signature=#{signature}"
|
145
|
+
end
|
146
|
+
|
147
|
+
return authorized_request
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
data/lib/request.rb
ADDED
@@ -0,0 +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
|
+
|
25
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Pandexio
|
2
|
+
|
3
|
+
class SigningAlgorithms
|
4
|
+
|
5
|
+
PDX_HMAC_MD5 = "PDX-HMAC-MD5"
|
6
|
+
PDX_HMAC_SHA1 = "PDX-HMAC-SHA1"
|
7
|
+
PDX_HMAC_SHA256 = "PDX-HMAC-SHA256"
|
8
|
+
PDX_HMAC_SHA384 = "PDX-HMAC-SHA384"
|
9
|
+
PDX_HMAC_SHA512 = "PDX-HMAC-SHA512"
|
10
|
+
|
11
|
+
def self.is_v(a)
|
12
|
+
|
13
|
+
return !a.nil? && a.is_a?(String) &&
|
14
|
+
(a == PDX_HMAC_MD5 ||
|
15
|
+
a == PDX_HMAC_SHA1 ||
|
16
|
+
a == PDX_HMAC_SHA256 ||
|
17
|
+
a == PDX_HMAC_SHA384 ||
|
18
|
+
a == PDX_HMAC_SHA512)
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.to_d(a)
|
23
|
+
|
24
|
+
return case a
|
25
|
+
when SigningAlgorithms::PDX_HMAC_MD5; Digest::MD5
|
26
|
+
when SigningAlgorithms::PDX_HMAC_SHA1; Digest::SHA1
|
27
|
+
when SigningAlgorithms::PDX_HMAC_SHA256; Digest::SHA256
|
28
|
+
when SigningAlgorithms::PDX_HMAC_SHA384; Digest::SHA384
|
29
|
+
when SigningAlgorithms::PDX_HMAC_SHA512; Digest::SHA512
|
30
|
+
else raise 'Invalid signing algorithm'
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Pandexio
|
2
|
+
|
3
|
+
class SigningAttributes
|
4
|
+
# Used by "headers" only
|
5
|
+
AUTHORIZATION = "Authorization"
|
6
|
+
|
7
|
+
# Used by "query_parameters" only
|
8
|
+
ALGORITHM = "X-Pdx-Algorithm"
|
9
|
+
CREDENTIAL = "X-Pdx-Credential"
|
10
|
+
SIGNED_HEADERS = "X-Pdx-SignedHeaders"
|
11
|
+
SIGNATURE = "X-Pdx-Signature"
|
12
|
+
|
13
|
+
# Used by "headers" and "query_parameters"
|
14
|
+
DATE = "X-Pdx-Date"
|
15
|
+
EXPIRES = "X-Pdx-Expires"
|
16
|
+
ORIGINATOR = "X-Pdx-Originator"
|
17
|
+
EMAIL_ADDRESS = "X-Pdx-EmailAddress"
|
18
|
+
DISPLAY_NAME = "X-Pdx-DisplayName"
|
19
|
+
THUMBNAIL = "X-Pdx-Thumbnail"
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Pandexio
|
2
|
+
|
3
|
+
class SigningOptions
|
4
|
+
def initialize(params = {})
|
5
|
+
@algorithm = params.fetch(:algorithm, nil)
|
6
|
+
@mechanism = params.fetch(:mechanism, nil)
|
7
|
+
@domain_id = params.fetch(:domain_id, nil)
|
8
|
+
@domain_key = params.fetch(:domain_key, nil)
|
9
|
+
@date = params.fetch(:date, nil)
|
10
|
+
@expires = params.fetch(:expires, nil)
|
11
|
+
@originator = params.fetch(:originator, nil)
|
12
|
+
@email_address = params.fetch(:email_address, nil)
|
13
|
+
@display_name = params.fetch(:display_name, nil)
|
14
|
+
@thumbnail = params.fetch(:thumbnail, nil)
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_accessor :algorithm
|
18
|
+
attr_accessor :mechanism
|
19
|
+
attr_accessor :domain_id
|
20
|
+
attr_accessor :domain_key
|
21
|
+
attr_accessor :date
|
22
|
+
attr_accessor :expires
|
23
|
+
attr_accessor :originator
|
24
|
+
attr_accessor :email_address
|
25
|
+
attr_accessor :display_name
|
26
|
+
attr_accessor :thumbnail
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require_relative '../lib/module.rb'
|
3
|
+
|
4
|
+
describe Pandexio do
|
5
|
+
before do
|
6
|
+
normalized_request = Pandexio::Request.new(
|
7
|
+
:method => "PUT",
|
8
|
+
:path => "/asdf/qwer/1234/title",
|
9
|
+
:query_parameters => { "nonce" => "987654321", "Baseline" => "5" },
|
10
|
+
:headers => { "sample" => "example", "Host" => "localhost" },
|
11
|
+
:payload => "testing")
|
12
|
+
|
13
|
+
date = Time.utc(2014, 11, 21, 13, 43, 15)
|
14
|
+
|
15
|
+
signing_options = Pandexio::SigningOptions.new(
|
16
|
+
:algorithm => Pandexio::SigningAlgorithms::PDX_HMAC_SHA256,
|
17
|
+
:mechanism => Pandexio::SigningMechanisms::HEADER,
|
18
|
+
:domain_id => "1234567890",
|
19
|
+
:domain_key => "asdfjklqwerzxcv",
|
20
|
+
:date => date,
|
21
|
+
:expires => 90,
|
22
|
+
:originator => "HeaderSigningTest",
|
23
|
+
:email_address => "Anonymous",
|
24
|
+
:display_name => "Anonymous")
|
25
|
+
|
26
|
+
@authorized_request = Pandexio::to_authorized_request(normalized_request, signing_options)
|
27
|
+
@authorized_request = Pandexio::to_authorized_request(normalized_request, signing_options)
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#header_signing" do
|
31
|
+
it "returns the correct authorization header" do
|
32
|
+
@authorized_request.headers["Authorization"].must_equal "PDX-HMAC-SHA256 Credential=1234567890, SignedHeaders=host;sample;x-pdx-date;x-pdx-displayname;x-pdx-emailaddress;x-pdx-expires;x-pdx-originator, Signature=a2e3dbc31b712bec6071dc7c5770bc60d4b03afa20e8329e6f4f6a2d74d32709"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require_relative '../lib/module.rb'
|
3
|
+
|
4
|
+
describe Pandexio do
|
5
|
+
before do
|
6
|
+
normalized_request = Pandexio::Request.new(
|
7
|
+
:method => "PUT",
|
8
|
+
:path => "/asdf/qwer/1234/title",
|
9
|
+
:query_parameters => { "nonce" => "987654321", "Baseline" => "5" },
|
10
|
+
:headers => { "sample" => "example", "Host" => "localhost" },
|
11
|
+
:payload => "testing")
|
12
|
+
|
13
|
+
date = Time.utc(2014, 11, 21, 13, 43, 15)
|
14
|
+
|
15
|
+
signing_options = Pandexio::SigningOptions.new(
|
16
|
+
:algorithm => Pandexio::SigningAlgorithms::PDX_HMAC_SHA256,
|
17
|
+
:mechanism => Pandexio::SigningMechanisms::QUERY_STRING,
|
18
|
+
:domain_id => "1234567890",
|
19
|
+
:domain_key => "asdfjklqwerzxcv",
|
20
|
+
:date => date,
|
21
|
+
:expires => 90,
|
22
|
+
:originator => "QueryStringSigningTest",
|
23
|
+
:email_address => "Anonymous",
|
24
|
+
:display_name => "Anonymous")
|
25
|
+
|
26
|
+
@authorized_request = Pandexio::to_authorized_request(normalized_request, signing_options)
|
27
|
+
@authorized_request = Pandexio::to_authorized_request(normalized_request, signing_options)
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#query_string_signing" do
|
31
|
+
it "returns the correct algorithm as a query parameter" do
|
32
|
+
@authorized_request.query_parameters["X-Pdx-Algorithm"].must_equal "PDX-HMAC-SHA256"
|
33
|
+
end
|
34
|
+
it "returns the correct credential as a query parameter" do
|
35
|
+
@authorized_request.query_parameters["X-Pdx-Credential"].must_equal "1234567890"
|
36
|
+
end
|
37
|
+
it "returns the correct signed_headers value as a query parameter" do
|
38
|
+
@authorized_request.query_parameters["X-Pdx-SignedHeaders"].must_equal "host;sample"
|
39
|
+
end
|
40
|
+
it "returns the correct signature as a query parameter" do
|
41
|
+
@authorized_request.query_parameters["X-Pdx-Signature"].must_equal "6ab83c6a331ba2d684d2557f1e415f3aee86bee105da1f5ad1bc4cc1cdf42f1a"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
metadata
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pandexio
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brandon Varilone
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-06-10 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Pandexio SDK for Ruby
|
14
|
+
email: bvarilone@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/module.rb
|
20
|
+
- lib/request.rb
|
21
|
+
- lib/signing_algorithms.rb
|
22
|
+
- lib/signing_attributes.rb
|
23
|
+
- lib/signing_mechanisms.rb
|
24
|
+
- lib/signing_options.rb
|
25
|
+
- test/test_header_signing.rb
|
26
|
+
- test/test_query_string_signing.rb
|
27
|
+
homepage: http://rubygems.org/gems/pandexio
|
28
|
+
licenses:
|
29
|
+
- MIT
|
30
|
+
metadata: {}
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options: []
|
33
|
+
require_paths:
|
34
|
+
- lib
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
requirements: []
|
46
|
+
rubyforge_project:
|
47
|
+
rubygems_version: 2.4.4
|
48
|
+
signing_key:
|
49
|
+
specification_version: 4
|
50
|
+
summary: ''
|
51
|
+
test_files: []
|