akami 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,7 @@
1
+ ## 1.1.0 (2012-06-06)
2
+
3
+ * Feature: [#3](https://github.com/rubiii/akami/pull/3) - WSSE signing.
4
+
1
5
  ## 1.0.0 (2011-07-03)
2
6
 
3
7
  * Initial version extracted from the [Savon](http://rubygems.org/gems/savon) library.
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. This way you might digg into some code, but then you can even
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
+ ```
@@ -14,6 +14,7 @@ Gem::Specification.new do |s|
14
14
  s.rubyforge_project = s.name
15
15
 
16
16
  s.add_dependency "gyoku", ">= 0.4.0"
17
+ s.add_dependency "nokogiri", ">= 1.5.2"
17
18
 
18
19
  s.add_development_dependency "rake", "~> 0.8.7"
19
20
  s.add_development_dependency "rspec", "~> 2.5.0"
@@ -0,0 +1,8 @@
1
+ module Akami
2
+ module C14nHelper
3
+ def canonicalize(xml)
4
+ return unless xml
5
+ xml.canonicalize Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
6
+ end
7
+ end
8
+ end
@@ -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
@@ -1,5 +1,5 @@
1
1
  module Akami
2
2
 
3
- VERSION = "1.0.0"
3
+ VERSION = "1.1.0"
4
4
 
5
5
  end
@@ -1,8 +1,14 @@
1
1
  require "base64"
2
2
  require "digest/sha1"
3
- require "akami/core_ext/time"
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 username_token? && timestamp?
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).xs_datetime,
107
- "wsu:Expires" => (expires_at || (created_at || Time.now) + 60).xs_datetime
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
- "#{namespace}:#{tag}" => hash,
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.xs_datetime
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
@@ -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.xs_datetime}</wsu:Created>")
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).xs_datetime}</wsu:Expires>")
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.xs_datetime}</wsu:Created>")
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).xs_datetime}</wsu:Expires>")
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.xs_datetime}</wsu:Created>")
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.xs_datetime}</wsu:Expires>")
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: 23
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
+ - 1
8
9
  - 0
9
- - 0
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: 2011-07-03 00:00:00 Z
18
+ date: 2012-06-06 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
- name: gyoku
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
- version_requirements: *id001
34
+ prerelease: false
35
+ requirement: *id001
36
36
  - !ruby/object:Gem::Dependency
37
- name: rake
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: &id002 !ruby/object:Gem::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: &id003 !ruby/object:Gem::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: &id004 !ruby/object:Gem::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: &id005 !ruby/object:Gem::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: &id006 !ruby/object:Gem::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
- version_requirements: *id006
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/core_ext/time.rb
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
- - spec/akami/core_ext/time_spec.rb
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.5
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
@@ -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
@@ -1,12 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe Time do
4
-
5
- describe "#xs_datetime" do
6
- it "returns an xs:dateTime formatted String" do
7
- time = Time.utc(2011, 01, 04, 13, 45, 55)
8
- time.xs_datetime.should == "2011-01-04T13:45:55Z"
9
- end
10
- end
11
-
12
- end