akami 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|