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.
@@ -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