akami 1.0.0 → 1.1.0
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/CHANGELOG.md +4 -0
- data/README.md +10 -6
- data/akami.gemspec +1 -0
- data/lib/akami/c14n_helper.rb +8 -0
- data/lib/akami/core_ext/hash.rb +27 -0
- data/lib/akami/version.rb +1 -1
- data/lib/akami/wsse.rb +57 -10
- data/lib/akami/wsse/certs.rb +25 -0
- data/lib/akami/wsse/signature.rb +166 -0
- data/lib/akami/wsse/verify_signature.rb +88 -0
- data/lib/akami/xpath_helper.rb +17 -0
- data/spec/akami/wsse_spec.rb +6 -6
- metadata +50 -31
- data/lib/akami/core_ext/time.rb +0 -22
- data/spec/akami/core_ext/time_spec.rb +0 -12
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -21,19 +21,19 @@ Getting started
|
|
21
21
|
wsse = Akami.wsse
|
22
22
|
```
|
23
23
|
|
24
|
-
Set the credentials for wsse:UsernameToken basic auth:
|
24
|
+
Set the credentials for `wsse:UsernameToken` basic auth:
|
25
25
|
|
26
26
|
``` ruby
|
27
27
|
wsse.credentials "username", "password"
|
28
28
|
```
|
29
29
|
|
30
|
-
Set the credentials for wsse:UsernameToken digest auth:
|
30
|
+
Set the credentials for `wsse:UsernameToken` digest auth:
|
31
31
|
|
32
32
|
``` ruby
|
33
33
|
wsse.credentials "username", "password", :digest
|
34
34
|
```
|
35
35
|
|
36
|
-
Enable wsu:Timestamp headers. `wsu:Created` is automatically set to `Time.now`
|
36
|
+
Enable `wsu:Timestamp` headers. `wsu:Created` is automatically set to `Time.now`
|
37
37
|
and `wsu:Expires` is set to `Time.now + 60`:
|
38
38
|
|
39
39
|
``` ruby
|
@@ -45,7 +45,7 @@ Manually specify the values for `wsu:Created` and `wsu:Expires`:
|
|
45
45
|
``` ruby
|
46
46
|
wsse.created_at = Time.now
|
47
47
|
wsse.expires_at = Time.now + 60
|
48
|
-
|
48
|
+
```
|
49
49
|
|
50
50
|
Akami is based on an autovivificating Hash. So if you need to add custom tags, you can add them.
|
51
51
|
|
@@ -54,5 +54,9 @@ wsse["wsse:Security"]["wsse:UsernameToken"] = { "Organization" => "ACME" }
|
|
54
54
|
```
|
55
55
|
|
56
56
|
When generating the XML for the request, this Hash will be merged with another Hash containing
|
57
|
-
all the default tags and values.
|
58
|
-
overwrite the default values.
|
57
|
+
all the default tags and values.
|
58
|
+
This way you might digg into some code, but then you can even overwrite the default values.
|
59
|
+
|
60
|
+
``` ruby
|
61
|
+
wsse.to_xml
|
62
|
+
```
|
data/akami.gemspec
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
# NOTE: This could probably be removed, but I don't have the time at the moment
|
2
|
+
# determine that. It is used by signature.rb, and these methods used to
|
3
|
+
# be in Savon.
|
4
|
+
#
|
5
|
+
module Akami
|
6
|
+
module CoreExt
|
7
|
+
module Hash
|
8
|
+
|
9
|
+
# Returns a new hash with +self+ and +other_hash+ merged recursively.
|
10
|
+
def deep_merge(other_hash)
|
11
|
+
dup.deep_merge!(other_hash)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns a new Hash with +self+ and +other_hash+ merged recursively.
|
15
|
+
# Modifies the receiver in place.
|
16
|
+
def deep_merge!(other_hash)
|
17
|
+
other_hash.each_pair do |k,v|
|
18
|
+
tv = self[k]
|
19
|
+
self[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_merge(v) : v
|
20
|
+
end
|
21
|
+
self
|
22
|
+
end unless defined? deep_merge!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Hash.send :include, Akami::CoreExt::Hash
|
data/lib/akami/version.rb
CHANGED
data/lib/akami/wsse.rb
CHANGED
@@ -1,8 +1,14 @@
|
|
1
1
|
require "base64"
|
2
2
|
require "digest/sha1"
|
3
|
-
require "akami/core_ext/
|
3
|
+
require "akami/core_ext/hash"
|
4
|
+
require "akami/xpath_helper"
|
5
|
+
require "akami/c14n_helper"
|
6
|
+
require "time"
|
4
7
|
require "gyoku"
|
5
8
|
|
9
|
+
require "akami/wsse/verify_signature"
|
10
|
+
require "akami/wsse/signature"
|
11
|
+
|
6
12
|
module Akami
|
7
13
|
|
8
14
|
# = Akami::WSSE
|
@@ -40,7 +46,15 @@ module Akami
|
|
40
46
|
self.digest = digest
|
41
47
|
end
|
42
48
|
|
43
|
-
attr_accessor :username, :password, :created_at, :expires_at
|
49
|
+
attr_accessor :username, :password, :created_at, :expires_at, :signature, :verify_response
|
50
|
+
|
51
|
+
def sign_with=(klass)
|
52
|
+
@signature = klass
|
53
|
+
end
|
54
|
+
|
55
|
+
def signature?
|
56
|
+
!!@signature
|
57
|
+
end
|
44
58
|
|
45
59
|
# Returns whether to use WSSE digest. Defaults to +false+.
|
46
60
|
def digest?
|
@@ -64,9 +78,20 @@ module Akami
|
|
64
78
|
@wsu_timestamp = timestamp
|
65
79
|
end
|
66
80
|
|
81
|
+
# Hook for Soap::XML that allows us to add attributes to the env:Body tag
|
82
|
+
def body_attributes
|
83
|
+
if signature?
|
84
|
+
signature.body_attributes
|
85
|
+
else
|
86
|
+
{}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
67
90
|
# Returns the XML for a WSSE header.
|
68
91
|
def to_xml
|
69
|
-
if
|
92
|
+
if signature? and signature.have_document?
|
93
|
+
Gyoku.xml wsse_signature.merge!(hash)
|
94
|
+
elsif username_token? && timestamp?
|
70
95
|
Gyoku.xml wsse_username_token.merge!(wsu_timestamp) {
|
71
96
|
|key, v1, v2| v1.merge!(v2) {
|
72
97
|
|key, v1, v2| v1.merge!(v2)
|
@@ -100,23 +125,45 @@ module Akami
|
|
100
125
|
end
|
101
126
|
end
|
102
127
|
|
128
|
+
def wsse_signature
|
129
|
+
signature_hash = signature.to_token
|
130
|
+
|
131
|
+
# First key/value is tag/hash
|
132
|
+
tag, hash = signature_hash.shift
|
133
|
+
|
134
|
+
security_hash nil, tag, hash, signature_hash
|
135
|
+
end
|
136
|
+
|
103
137
|
# Returns a Hash containing wsu:Timestamp details.
|
104
138
|
def wsu_timestamp
|
105
139
|
security_hash :wsu, "Timestamp",
|
106
|
-
"wsu:Created" => (created_at || Time.now).
|
107
|
-
"wsu:Expires" => (expires_at || (created_at || Time.now) + 60).
|
140
|
+
"wsu:Created" => (created_at || Time.now).utc.xmlschema,
|
141
|
+
"wsu:Expires" => (expires_at || (created_at || Time.now) + 60).utc.xmlschema
|
108
142
|
end
|
109
143
|
|
110
144
|
# Returns a Hash containing wsse/wsu Security details for a given
|
111
145
|
# +namespace+, +tag+ and +hash+.
|
112
|
-
def security_hash(namespace, tag, hash)
|
113
|
-
|
146
|
+
def security_hash(namespace, tag, hash, extra_info = {})
|
147
|
+
key = [namespace, tag].compact.join(":")
|
148
|
+
|
149
|
+
sec_hash = {
|
114
150
|
"wsse:Security" => {
|
115
|
-
|
116
|
-
:attributes! => { "#{namespace}:#{tag}" => { "wsu:Id" => "#{tag}-#{count}", "xmlns:wsu" => WSU_NAMESPACE } }
|
151
|
+
key => hash
|
117
152
|
},
|
118
153
|
:attributes! => { "wsse:Security" => { "xmlns:wsse" => WSE_NAMESPACE } }
|
119
154
|
}
|
155
|
+
|
156
|
+
unless extra_info.empty?
|
157
|
+
sec_hash["wsse:Security"].merge!(extra_info)
|
158
|
+
end
|
159
|
+
|
160
|
+
if signature?
|
161
|
+
sec_hash[:attributes!].merge!("soapenv:mustUnderstand" => "1")
|
162
|
+
else
|
163
|
+
sec_hash["wsse:Security"].merge!(:attributes! => { key => { "wsu:Id" => "#{tag}-#{count}", "xmlns:wsu" => WSU_NAMESPACE } })
|
164
|
+
end
|
165
|
+
|
166
|
+
sec_hash
|
120
167
|
end
|
121
168
|
|
122
169
|
# Returns the WSSE password, encrypted for digest authentication.
|
@@ -137,7 +184,7 @@ module Akami
|
|
137
184
|
|
138
185
|
# Returns a WSSE timestamp.
|
139
186
|
def timestamp
|
140
|
-
@timestamp ||= Time.now.
|
187
|
+
@timestamp ||= Time.now.xmlschema
|
141
188
|
end
|
142
189
|
|
143
190
|
# Returns a new number with every call.
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Akami
|
2
|
+
class WSSE
|
3
|
+
# Contains certs for WSSE::Signature
|
4
|
+
class Certs
|
5
|
+
|
6
|
+
def initialize(certs = {})
|
7
|
+
certs.each do |key, value|
|
8
|
+
self.send :"#{key}=", value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_accessor :cert_file, :private_key_file, :private_key_password
|
13
|
+
|
14
|
+
# Returns an <tt>OpenSSL::X509::Certificate</tt> for the +cert_file+.
|
15
|
+
def cert
|
16
|
+
@cert ||= OpenSSL::X509::Certificate.new File.read(cert_file) if cert_file
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns an <tt>OpenSSL::PKey::RSA</tt> for the +private_key_file+.
|
20
|
+
def private_key
|
21
|
+
@private_key ||= OpenSSL::PKey::RSA.new(File.read(private_key_file), private_key_password) if private_key_file
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require "akami/wsse/certs"
|
2
|
+
|
3
|
+
module Akami
|
4
|
+
class WSSE
|
5
|
+
class Signature
|
6
|
+
include Akami::XPathHelper
|
7
|
+
include Akami::C14nHelper
|
8
|
+
|
9
|
+
class MissingCertificate < RuntimeError; end
|
10
|
+
|
11
|
+
# For a +Savon::WSSE::Certs+ object. To hold the certs we need to sign.
|
12
|
+
attr_accessor :certs
|
13
|
+
|
14
|
+
# Without a document, the document cannot be signed.
|
15
|
+
# Generate the document once, and then set document and recall #to_token
|
16
|
+
def document
|
17
|
+
@document ? @document.to_s : nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def document=(document)
|
21
|
+
@document = Nokogiri::XML(document)
|
22
|
+
end
|
23
|
+
|
24
|
+
ExclusiveXMLCanonicalizationAlgorithm = 'http://www.w3.org/2001/10/xml-exc-c14n#'.freeze
|
25
|
+
RSASHA1SignatureAlgorithm = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'.freeze
|
26
|
+
SHA1DigestAlgorithm = 'http://www.w3.org/2000/09/xmldsig#sha1'.freeze
|
27
|
+
|
28
|
+
X509v3ValueType = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3'.freeze
|
29
|
+
Base64EncodingType = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary'.freeze
|
30
|
+
|
31
|
+
SignatureNamespace = 'http://www.w3.org/2000/09/xmldsig#'.freeze
|
32
|
+
|
33
|
+
def initialize(certs = Certs.new)
|
34
|
+
@certs = certs
|
35
|
+
end
|
36
|
+
|
37
|
+
def have_document?
|
38
|
+
!!document
|
39
|
+
end
|
40
|
+
|
41
|
+
# Cache "now" so that digests match...
|
42
|
+
# TODO: figure out how we might want to expire this cache...
|
43
|
+
def now
|
44
|
+
@now ||= Time.now
|
45
|
+
end
|
46
|
+
|
47
|
+
def body_id
|
48
|
+
@body_id ||= "Body-#{uid}".freeze
|
49
|
+
end
|
50
|
+
|
51
|
+
def security_token_id
|
52
|
+
@security_token_id ||= "SecurityToken-#{uid}".freeze
|
53
|
+
end
|
54
|
+
|
55
|
+
def body_attributes
|
56
|
+
{
|
57
|
+
"xmlns:wsu" => Akami::WSSE::WSU_NAMESPACE,
|
58
|
+
"wsu:Id" => body_id,
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_token
|
63
|
+
return {} unless have_document?
|
64
|
+
|
65
|
+
sig = signed_info.merge(key_info).merge(signature_value)
|
66
|
+
sig.merge! :order! => []
|
67
|
+
[ "SignedInfo", "SignatureValue", "KeyInfo" ].each do |key|
|
68
|
+
sig[:order!] << key if sig[key]
|
69
|
+
end
|
70
|
+
|
71
|
+
token = {
|
72
|
+
"Signature" => sig,
|
73
|
+
:attributes! => { "Signature" => { "xmlns" => SignatureNamespace } },
|
74
|
+
}
|
75
|
+
|
76
|
+
token.deep_merge!(binary_security_token) if certs.cert
|
77
|
+
|
78
|
+
token.merge! :order! => []
|
79
|
+
[ "wsse:BinarySecurityToken", "Signature" ].each do |key|
|
80
|
+
token[:order!] << key if token[key]
|
81
|
+
end
|
82
|
+
|
83
|
+
token
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def binary_security_token
|
89
|
+
{
|
90
|
+
"wsse:BinarySecurityToken" => Base64.encode64(certs.cert.to_der).gsub("\n", ''),
|
91
|
+
:attributes! => { "wsse:BinarySecurityToken" => {
|
92
|
+
"wsu:Id" => security_token_id,
|
93
|
+
'EncodingType' => Base64EncodingType,
|
94
|
+
'ValueType' => X509v3ValueType,
|
95
|
+
"xmlns:wsu" => Akami::WSSE::WSU_NAMESPACE,
|
96
|
+
} }
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
def key_info
|
101
|
+
{
|
102
|
+
"KeyInfo" => {
|
103
|
+
"wsse:SecurityTokenReference" => {
|
104
|
+
"wsse:Reference/" => nil,
|
105
|
+
:attributes! => { "wsse:Reference/" => {
|
106
|
+
"ValueType" => X509v3ValueType,
|
107
|
+
"URI" => "##{security_token_id}",
|
108
|
+
} }
|
109
|
+
},
|
110
|
+
:attributes! => { "wsse:SecurityTokenReference" => { "xmlns:wsu" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" } },
|
111
|
+
},
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
def signature_value
|
116
|
+
{ "SignatureValue" => the_signature }
|
117
|
+
rescue MissingCertificate
|
118
|
+
{}
|
119
|
+
end
|
120
|
+
|
121
|
+
def signed_info
|
122
|
+
{
|
123
|
+
"SignedInfo" => {
|
124
|
+
"CanonicalizationMethod/" => nil,
|
125
|
+
"SignatureMethod/" => nil,
|
126
|
+
"Reference" => [
|
127
|
+
#signed_info_transforms.merge(signed_info_digest_method).merge({ "DigestValue" => timestamp_digest }),
|
128
|
+
signed_info_transforms.merge(signed_info_digest_method).merge({ "DigestValue" => body_digest }),
|
129
|
+
],
|
130
|
+
:attributes! => {
|
131
|
+
"CanonicalizationMethod/" => { "Algorithm" => ExclusiveXMLCanonicalizationAlgorithm },
|
132
|
+
"SignatureMethod/" => { "Algorithm" => RSASHA1SignatureAlgorithm },
|
133
|
+
"Reference" => { "URI" => ["##{body_id}"] },
|
134
|
+
},
|
135
|
+
:order! => [ "CanonicalizationMethod/", "SignatureMethod/", "Reference" ],
|
136
|
+
},
|
137
|
+
}
|
138
|
+
end
|
139
|
+
|
140
|
+
def the_signature
|
141
|
+
raise MissingCertificate, "Expected a private_key for signing" unless certs.private_key
|
142
|
+
signed_info = at_xpath(@document, "//Envelope/Header/Security/Signature/SignedInfo")
|
143
|
+
signed_info = signed_info ? canonicalize(signed_info) : ""
|
144
|
+
signature = certs.private_key.sign(OpenSSL::Digest::SHA1.new, signed_info)
|
145
|
+
Base64.encode64(signature).gsub("\n", '') # TODO: DRY calls to Base64.encode64(...).gsub("\n", '')
|
146
|
+
end
|
147
|
+
|
148
|
+
def body_digest
|
149
|
+
body = canonicalize(at_xpath(@document, "//Envelope/Body"))
|
150
|
+
Base64.encode64(OpenSSL::Digest::SHA1.digest(body)).strip
|
151
|
+
end
|
152
|
+
|
153
|
+
def signed_info_digest_method
|
154
|
+
{ "DigestMethod/" => nil, :attributes! => { "DigestMethod/" => { "Algorithm" => SHA1DigestAlgorithm } } }
|
155
|
+
end
|
156
|
+
|
157
|
+
def signed_info_transforms
|
158
|
+
{ "Transforms" => { "Transform/" => nil, :attributes! => { "Transform/" => { "Algorithm" => ExclusiveXMLCanonicalizationAlgorithm } } } }
|
159
|
+
end
|
160
|
+
|
161
|
+
def uid
|
162
|
+
OpenSSL::Digest::SHA1.hexdigest([Time.now, rand].collect(&:to_s).join('/'))
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Akami
|
2
|
+
class WSSE
|
3
|
+
class InvalidSignature < RuntimeError; end
|
4
|
+
|
5
|
+
class VerifySignature
|
6
|
+
include Akami::XPathHelper
|
7
|
+
include Akami::C14nHelper
|
8
|
+
|
9
|
+
class InvalidDigest < RuntimeError; end
|
10
|
+
class InvalidSignedValue < RuntimeError; end
|
11
|
+
|
12
|
+
attr_reader :response_body, :document
|
13
|
+
|
14
|
+
def initialize(response_body)
|
15
|
+
@response_body = response_body
|
16
|
+
@document = create_document
|
17
|
+
end
|
18
|
+
|
19
|
+
def generate_digest(element)
|
20
|
+
element = element_for_xpath(element) if element.is_a? String
|
21
|
+
xml = canonicalize(element)
|
22
|
+
digest(xml).strip
|
23
|
+
end
|
24
|
+
|
25
|
+
def supplied_digest(element)
|
26
|
+
element = element_for_xpath(element) if element.is_a? String
|
27
|
+
find_digest_value element.attributes["Id"]
|
28
|
+
end
|
29
|
+
|
30
|
+
def signature_value
|
31
|
+
element = element_for_xpath("//Security/Signature/SignatureValue")
|
32
|
+
element ? element.text : ""
|
33
|
+
end
|
34
|
+
|
35
|
+
def certificate
|
36
|
+
certificate_value = element_for_xpath("//Security/BinarySecurityToken").text.strip
|
37
|
+
OpenSSL::X509::Certificate.new Base64.decode64(certificate_value)
|
38
|
+
end
|
39
|
+
|
40
|
+
def valid?
|
41
|
+
verify
|
42
|
+
rescue InvalidDigest, InvalidSignedValue
|
43
|
+
return false
|
44
|
+
end
|
45
|
+
|
46
|
+
def verify!
|
47
|
+
verify
|
48
|
+
rescue InvalidDigest, InvalidSignedValue => e
|
49
|
+
raise InvalidSignature, e.message
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def verify
|
55
|
+
xpath(document, "//Security/Signature/SignedInfo/Reference").each do |ref|
|
56
|
+
element_id = ref.attributes["URI"][1..-1] # strip leading '#'
|
57
|
+
element = element_for_xpath(%(//*[@wsu:Id="#{element_id}"]))
|
58
|
+
raise InvalidDigest, "Invalid Digest for #{element_id}" unless supplied_digest(element) == generate_digest(element)
|
59
|
+
end
|
60
|
+
|
61
|
+
data = canonicalize(signed_info)
|
62
|
+
signature = Base64.decode64(signature_value)
|
63
|
+
|
64
|
+
certificate.public_key.verify(OpenSSL::Digest::SHA1.new, signature, data) or raise InvalidSignedValue, "Could not verify the signature value"
|
65
|
+
end
|
66
|
+
|
67
|
+
def create_document
|
68
|
+
Nokogiri::XML response_body
|
69
|
+
end
|
70
|
+
|
71
|
+
def element_for_xpath(xpath)
|
72
|
+
document.at_xpath xpath
|
73
|
+
end
|
74
|
+
|
75
|
+
def signed_info
|
76
|
+
at_xpath document, "//Security/Signature/SignedInfo"
|
77
|
+
end
|
78
|
+
|
79
|
+
def find_digest_value(id)
|
80
|
+
at_xpath(document, %(//Security/Signature/SignedInfo/Reference[@URI="##{id}"]/DigestValue)).text
|
81
|
+
end
|
82
|
+
|
83
|
+
def digest(string)
|
84
|
+
Base64.encode64 OpenSSL::Digest::SHA1.digest(string)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Akami
|
2
|
+
module XPathHelper
|
3
|
+
def at_xpath(node, xpath)
|
4
|
+
return unless node
|
5
|
+
node.at_xpath local_name_xpath(xpath)
|
6
|
+
end
|
7
|
+
|
8
|
+
def xpath(node, xpath)
|
9
|
+
return unless node
|
10
|
+
node.xpath local_name_xpath(xpath)
|
11
|
+
end
|
12
|
+
|
13
|
+
def local_name_xpath(xpath)
|
14
|
+
xpath.gsub(%r{([/]*)([A-Za-z]+)([/]*)}) { "#{$1}*[local-name()='#{$2}']#{$3}" }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/spec/akami/wsse_spec.rb
CHANGED
@@ -171,14 +171,14 @@ describe Akami do
|
|
171
171
|
it "contains a wsu:Created node defaulting to Time.now" do
|
172
172
|
created_at = Time.now
|
173
173
|
Timecop.freeze created_at do
|
174
|
-
wsse.to_xml.should include("<wsu:Created>#{created_at.
|
174
|
+
wsse.to_xml.should include("<wsu:Created>#{created_at.utc.xmlschema}</wsu:Created>")
|
175
175
|
end
|
176
176
|
end
|
177
177
|
|
178
178
|
it "contains a wsu:Expires node defaulting to Time.now + 60 seconds" do
|
179
179
|
created_at = Time.now
|
180
180
|
Timecop.freeze created_at do
|
181
|
-
wsse.to_xml.should include("<wsu:Expires>#{(created_at + 60).
|
181
|
+
wsse.to_xml.should include("<wsu:Expires>#{(created_at + 60).utc.xmlschema}</wsu:Expires>")
|
182
182
|
end
|
183
183
|
end
|
184
184
|
end
|
@@ -187,11 +187,11 @@ describe Akami do
|
|
187
187
|
before { wsse.created_at = Time.now + 86400 }
|
188
188
|
|
189
189
|
it "contains a wsu:Created node with the given time" do
|
190
|
-
wsse.to_xml.should include("<wsu:Created>#{wsse.created_at.
|
190
|
+
wsse.to_xml.should include("<wsu:Created>#{wsse.created_at.utc.xmlschema}</wsu:Created>")
|
191
191
|
end
|
192
192
|
|
193
193
|
it "contains a wsu:Expires node set to #created_at + 60 seconds" do
|
194
|
-
wsse.to_xml.should include("<wsu:Expires>#{(wsse.created_at + 60).
|
194
|
+
wsse.to_xml.should include("<wsu:Expires>#{(wsse.created_at + 60).utc.xmlschema}</wsu:Expires>")
|
195
195
|
end
|
196
196
|
end
|
197
197
|
|
@@ -201,12 +201,12 @@ describe Akami do
|
|
201
201
|
it "contains a wsu:Created node defaulting to Time.now" do
|
202
202
|
created_at = Time.now
|
203
203
|
Timecop.freeze created_at do
|
204
|
-
wsse.to_xml.should include("<wsu:Created>#{created_at.
|
204
|
+
wsse.to_xml.should include("<wsu:Created>#{created_at.utc.xmlschema}</wsu:Created>")
|
205
205
|
end
|
206
206
|
end
|
207
207
|
|
208
208
|
it "contains a wsu:Expires node set to the given time" do
|
209
|
-
wsse.to_xml.should include("<wsu:Expires>#{wsse.expires_at.
|
209
|
+
wsse.to_xml.should include("<wsu:Expires>#{wsse.expires_at.utc.xmlschema}</wsu:Expires>")
|
210
210
|
end
|
211
211
|
end
|
212
212
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: akami
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 19
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
|
10
|
-
version: 1.0.0
|
10
|
+
version: 1.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Daniel Harrington
|
@@ -15,12 +15,10 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2012-06-06 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
|
-
|
22
|
-
prerelease: false
|
23
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
21
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
24
22
|
none: false
|
25
23
|
requirements:
|
26
24
|
- - ">="
|
@@ -31,12 +29,28 @@ dependencies:
|
|
31
29
|
- 4
|
32
30
|
- 0
|
33
31
|
version: 0.4.0
|
32
|
+
name: gyoku
|
34
33
|
type: :runtime
|
35
|
-
|
34
|
+
prerelease: false
|
35
|
+
requirement: *id001
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
|
-
|
37
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 7
|
43
|
+
segments:
|
44
|
+
- 1
|
45
|
+
- 5
|
46
|
+
- 2
|
47
|
+
version: 1.5.2
|
48
|
+
name: nokogiri
|
49
|
+
type: :runtime
|
38
50
|
prerelease: false
|
39
|
-
requirement:
|
51
|
+
requirement: *id002
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
40
54
|
none: false
|
41
55
|
requirements:
|
42
56
|
- - ~>
|
@@ -47,12 +61,12 @@ dependencies:
|
|
47
61
|
- 8
|
48
62
|
- 7
|
49
63
|
version: 0.8.7
|
64
|
+
name: rake
|
50
65
|
type: :development
|
51
|
-
version_requirements: *id002
|
52
|
-
- !ruby/object:Gem::Dependency
|
53
|
-
name: rspec
|
54
66
|
prerelease: false
|
55
|
-
requirement:
|
67
|
+
requirement: *id003
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
56
70
|
none: false
|
57
71
|
requirements:
|
58
72
|
- - ~>
|
@@ -63,12 +77,12 @@ dependencies:
|
|
63
77
|
- 5
|
64
78
|
- 0
|
65
79
|
version: 2.5.0
|
80
|
+
name: rspec
|
66
81
|
type: :development
|
67
|
-
version_requirements: *id003
|
68
|
-
- !ruby/object:Gem::Dependency
|
69
|
-
name: mocha
|
70
82
|
prerelease: false
|
71
|
-
requirement:
|
83
|
+
requirement: *id004
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
72
86
|
none: false
|
73
87
|
requirements:
|
74
88
|
- - ~>
|
@@ -79,12 +93,12 @@ dependencies:
|
|
79
93
|
- 9
|
80
94
|
- 8
|
81
95
|
version: 0.9.8
|
96
|
+
name: mocha
|
82
97
|
type: :development
|
83
|
-
version_requirements: *id004
|
84
|
-
- !ruby/object:Gem::Dependency
|
85
|
-
name: timecop
|
86
98
|
prerelease: false
|
87
|
-
requirement:
|
99
|
+
requirement: *id005
|
100
|
+
- !ruby/object:Gem::Dependency
|
101
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
88
102
|
none: false
|
89
103
|
requirements:
|
90
104
|
- - ~>
|
@@ -95,12 +109,12 @@ dependencies:
|
|
95
109
|
- 3
|
96
110
|
- 5
|
97
111
|
version: 0.3.5
|
112
|
+
name: timecop
|
98
113
|
type: :development
|
99
|
-
version_requirements: *id005
|
100
|
-
- !ruby/object:Gem::Dependency
|
101
|
-
name: autotest
|
102
114
|
prerelease: false
|
103
|
-
requirement:
|
115
|
+
requirement: *id006
|
116
|
+
- !ruby/object:Gem::Dependency
|
117
|
+
version_requirements: &id007 !ruby/object:Gem::Requirement
|
104
118
|
none: false
|
105
119
|
requirements:
|
106
120
|
- - ">="
|
@@ -109,8 +123,10 @@ dependencies:
|
|
109
123
|
segments:
|
110
124
|
- 0
|
111
125
|
version: "0"
|
126
|
+
name: autotest
|
112
127
|
type: :development
|
113
|
-
|
128
|
+
prerelease: false
|
129
|
+
requirement: *id007
|
114
130
|
description: Building Web Service Security
|
115
131
|
email:
|
116
132
|
- me@rubiii.com
|
@@ -131,10 +147,14 @@ files:
|
|
131
147
|
- Rakefile
|
132
148
|
- akami.gemspec
|
133
149
|
- lib/akami.rb
|
134
|
-
- lib/akami/
|
150
|
+
- lib/akami/c14n_helper.rb
|
151
|
+
- lib/akami/core_ext/hash.rb
|
135
152
|
- lib/akami/version.rb
|
136
153
|
- lib/akami/wsse.rb
|
137
|
-
-
|
154
|
+
- lib/akami/wsse/certs.rb
|
155
|
+
- lib/akami/wsse/signature.rb
|
156
|
+
- lib/akami/wsse/verify_signature.rb
|
157
|
+
- lib/akami/xpath_helper.rb
|
138
158
|
- spec/akami/wsse_spec.rb
|
139
159
|
- spec/spec_helper.rb
|
140
160
|
homepage: https://github.com/rubiii/akami
|
@@ -166,11 +186,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
166
186
|
requirements: []
|
167
187
|
|
168
188
|
rubyforge_project: akami
|
169
|
-
rubygems_version: 1.8.
|
189
|
+
rubygems_version: 1.8.21
|
170
190
|
signing_key:
|
171
191
|
specification_version: 3
|
172
192
|
summary: Web Service Security
|
173
193
|
test_files:
|
174
|
-
- spec/akami/core_ext/time_spec.rb
|
175
194
|
- spec/akami/wsse_spec.rb
|
176
195
|
- spec/spec_helper.rb
|
data/lib/akami/core_ext/time.rb
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
module Akami
|
2
|
-
module CoreExt
|
3
|
-
module Time
|
4
|
-
|
5
|
-
# Returns an xs:dateTime formatted String.
|
6
|
-
def xs_datetime
|
7
|
-
zone = if utc_offset < 0
|
8
|
-
"-#{"%02d" % (- utc_offset / 3600)}:#{"%02d" % ((- utc_offset % 3600) / 60)}"
|
9
|
-
elsif utc_offset > 0
|
10
|
-
"+#{"%02d" % (utc_offset / 3600)}:#{"%02d" % ((utc_offset % 3600) / 60)}"
|
11
|
-
else
|
12
|
-
"Z"
|
13
|
-
end
|
14
|
-
|
15
|
-
strftime "%Y-%m-%dT%H:%M:%S#{zone}"
|
16
|
-
end
|
17
|
-
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
Time.send :include, Akami::CoreExt::Time
|