GoogleSSO 1.0.1

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.
@@ -0,0 +1,6 @@
1
+ require 'xmlcanonicalizer'
2
+ require 'processresponse'
3
+
4
+ class GoogleSSO
5
+ VERSION = '1.0'
6
+ end
@@ -0,0 +1,183 @@
1
+ require "openssl"
2
+ require "openssl/x509"
3
+ require "base64"
4
+ require "rexml/document"
5
+ require "rexml/xpath"
6
+ require "zlib"
7
+ require "digest/sha1"
8
+ require "xmlcanonicalizer"
9
+
10
+ include REXML
11
+ include OpenSSL
12
+
13
+ class XmlProcessResponse
14
+ attr_accessor :acs, :encodedresponse, :authenticateform, :logger
15
+
16
+ def initialize(certificate_path, private_key_path)
17
+ @certificate_path = certificate_path
18
+ @private_key_path = private_key_path
19
+ libpath = "#{Gem.dir}/gems/GoogleSSO-#{GoogleSSO::VERSION}/lib"
20
+ @signature_template_path = "#{libpath}/SignatureTemplate.xml"
21
+ @response_template_path = "#{libpath}/SamlResponseTemplate.xml"
22
+
23
+ @issueinstant = ""
24
+ @providername = ""
25
+
26
+ @acs = ""
27
+ @encodedresponse = ""
28
+ @authenticateform = ""
29
+
30
+ @logger = Logger.new("response.log")
31
+ @logger.level = Logger::DEBUG
32
+ end
33
+
34
+ def process_response(samlRequest, relayState, username)
35
+ xml_string = decodeAuthnRequestXML(samlRequest)
36
+ getRequestAttributes(xml_string)
37
+
38
+ samlResponse = createSamlResponse(username)
39
+
40
+ @logger.debug("\nSAML Response\n" + samlResponse.to_s()) if @logger
41
+
42
+ signedResponse = signXML(samlResponse)
43
+
44
+ @logger.debug("\nSigned Response\n" + signedResponse.to_s()) if @logger
45
+
46
+ @encodedresponse = Base64.encode64(signedResponse)
47
+ # @authenticateform = "<form name='acsForm' id='acsForm' action='#{@acs}' method='post'>"+
48
+ # WHY IS THIS ENCODED??
49
+ #" <textarea name='SAMLResponse'>#{@encodedresponse}</textarea>"+
50
+
51
+ @authenticateform = "<form name='acsForm' id='acsForm' action='#{@acs}' method='post'>"+
52
+ " SAMLResponse:<textarea rows='10' cols='80' name='SAMLResponse'>#{signedResponse}</textarea>"+
53
+ " RelayState:<textarea rows='10' cols='80' name='RelayState'>#{relayState}</textarea>"+
54
+ " <p><input type='submit' /></p>"+
55
+ "</form>"
56
+ end
57
+
58
+ # private
59
+
60
+ def decodeAuthnRequestXML(encodedRequestXmlString)
61
+ unzipper = Zlib::Inflate.new( -Zlib::MAX_WBITS )
62
+ return unzipper.inflate(Base64.decode64( encodedRequestXmlString ))
63
+ end
64
+
65
+ def getRequestAttributes(xmlString)
66
+ doc = Document.new( xmlString )
67
+ @issueinstant = doc.root.attributes["IssueInstant"]
68
+ @providername = doc.root.attributes["ProviderName"]
69
+ @acs = doc.root.attributes["AssertionConsumerServiceURL"]
70
+ end
71
+
72
+ def createSamlResponse(authenticatedUser)
73
+ current_time = Time.new().utc().strftime("%Y-%m-%dT%H:%M:%SZ")
74
+ # 20 minutes after issued time
75
+ notOnOrAfter = (Time.new().utc()+60*20).strftime("%Y-%m-%dT%H:%M:%SZ")
76
+
77
+ samlResponse = ""
78
+ File.open(@response_template_path).each { |line|
79
+ samlResponse += line
80
+ }
81
+
82
+ samlResponse.sub!("<USERNAME_STRING>", authenticatedUser)
83
+ samlResponse.sub!("<RESPONSE_ID>", generateUniqueHexCode(42))
84
+ samlResponse.gsub!("<ISSUE_INSTANT>", current_time)
85
+ samlResponse.sub!("<AUTHN_INSTANT>", current_time)
86
+ samlResponse.sub!("<NOT_BEFORE>", @issueinstant)
87
+ samlResponse.sub!("<NOT_ON_OR_AFTER>", notOnOrAfter)
88
+ samlResponse.sub!("<ASSERTION_ID>", generateUniqueHexCode(42))
89
+
90
+ return samlResponse
91
+ end
92
+
93
+ def signXML(xml)
94
+ signature = ""
95
+ File.open(@signature_template_path).each { |line|
96
+ signature += line
97
+ }
98
+
99
+ document = Document.new(xml)
100
+ sigDoc = Document.new(signature)
101
+
102
+ # 3. Apply digesting algorithms over the resource, and calculate the digest value.
103
+ digestValue = calculateDigest(document)
104
+
105
+ # 4. Enclose the details in the <Reference> element.
106
+ # 5. Collect all <Reference> elements inside the <SignedInfo> element. Indicate the canonicalization and signature methodologies.
107
+ digestElement = XPath.first(sigDoc, "//DigestValue")
108
+ digestElement.add_text(digestValue)
109
+
110
+ # 6. Canonicalize contents of <SignedInfo>, apply the signature algorithm, and generate the XML Digital signature.
111
+ signedElement = XPath.first(sigDoc, "//SignedInfo")
112
+ signatureValue = calculateSignatureValue(signedElement)
113
+
114
+ # 7. Enclose the signature within the <SignatureValue> element.
115
+ signatureValueElement = XPath.first(sigDoc, "//SignatureValue")
116
+ signatureValueElement.add_text(signatureValue)
117
+
118
+ # 8. Add relevant key information, if any, and produce the <Signature> element.
119
+ cert = ""
120
+ File.open(@certificate_path).each { |line|
121
+ cert += line
122
+ }
123
+
124
+ cert.sub!(/.*BEGIN CERTIFICATE-----\s*(.*)\s*-----END CERT.*/m, '\1')
125
+
126
+ certNode = XPath.first(sigDoc, "//X509Certificate")
127
+ certNode.add_text(cert)
128
+
129
+ # 9. put it all together
130
+ status_child = document.elements["//samlp:Status"]
131
+ status_child.parent.insert_before(status_child, sigDoc.root)
132
+ retval = document.to_s()
133
+
134
+ # XPath.first(document, "//Signature").remove
135
+
136
+ # canoner = XmlCanonicalizer.new(false, true)
137
+ # canon_element = canoner.canonicalize(document)
138
+
139
+ return retval
140
+ end
141
+
142
+ def calculateSignatureValue(element)
143
+ element.add_namespace("http://www.w3.org/2000/09/xmldsig#")
144
+ element.add_namespace("xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol")
145
+ element.add_namespace("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#")
146
+
147
+ canoner = XmlCanonicalizer.new(false, true)
148
+ canon_element = canoner.canonicalize(element)
149
+
150
+ pkey = PKey::RSA.new(File::read(@private_key_path))
151
+ signature = Base64.encode64(pkey.sign(OpenSSL::Digest::SHA1.new, canon_element.to_s.chomp).chomp).chomp
152
+
153
+ element.delete_attribute("xmlns")
154
+ element.delete_attribute("xmlns:samlp")
155
+ element.delete_attribute("xmlns:xenc")
156
+ return signature
157
+ end
158
+
159
+ def calculateDigest(element)
160
+ canoner = XmlCanonicalizer.new(false, true)
161
+ canon_element = canoner.canonicalize(element)
162
+
163
+ element_hash = Base64.encode64(Digest::SHA1.digest(canon_element.to_s.chomp).chomp).chomp
164
+
165
+ return element_hash
166
+ end
167
+
168
+ def generateUniqueHexCode( codeLength )
169
+ validChars = ("A".."F").to_a + ("0".."9").to_a
170
+ length = validChars.size
171
+
172
+ validStartChars = ("A".."F").to_a
173
+ startLength = validStartChars.size
174
+
175
+ hexCode = ""
176
+ hexCode << validStartChars[rand(startLength-1)]
177
+
178
+ 1.upto(codeLength-1) { |i| hexCode << validChars[rand(length-1)] }
179
+
180
+ return hexCode
181
+ end
182
+
183
+ end
@@ -0,0 +1,395 @@
1
+ require "rexml/document"
2
+ require "base64"
3
+
4
+ include REXML
5
+
6
+
7
+ class REXML::Instruction
8
+ def write(writer, indent=-1, transitive=false, ie_hack=false)
9
+ indent(writer, indent)
10
+ writer << START.sub(/\\/u, '')
11
+ writer << @target
12
+ writer << ' '
13
+ writer << @content if @content != nil
14
+ writer << STOP.sub(/\\/u, '')
15
+ end
16
+ end
17
+
18
+ class REXML::Attribute
19
+ def <=>(a2)
20
+ if (self === a2)
21
+ return 0
22
+ elsif (self == nil)
23
+ return -1
24
+ elsif (a2 == nil)
25
+ return 1
26
+ elsif (self.prefix() == a2.prefix())
27
+ return self.name()<=>a2.name()
28
+ end
29
+ if (self.prefix() == nil)
30
+ return -1
31
+ elsif (a2.prefix() == nil)
32
+ return 1
33
+ end
34
+ ret = self.namespace()<=>a2.namespace()
35
+ if (ret == 0)
36
+ ret = self.prefix()<=>a2.prefix()
37
+ end
38
+ return ret
39
+ end
40
+ end
41
+
42
+ class REXML::Element
43
+ def search_namespace(prefix)
44
+ if (self.namespace(prefix) == nil)
45
+ return (self.parent().search_namespace(prefix)) if (self.parent() != nil)
46
+ else
47
+ return self.namespace(prefix)
48
+ end
49
+ end
50
+ def rendered=(rendered)
51
+ @rendered = rendered
52
+ end
53
+ def rendered?()
54
+ return @rendered
55
+ end
56
+ def node_namespaces()
57
+ ns = Array.new()
58
+ ns.push(self.prefix())
59
+ self.attributes().each_attribute{|a|
60
+ if (a.prefix() == "xmlns" or (a.prefix() == "" && a.local_name() == "xmlns"))
61
+ ns.push("xmlns")
62
+ end
63
+ }
64
+ self.prefixes().each { |prefix| ns.push(prefix) }
65
+ ns
66
+ end
67
+ end
68
+
69
+ class NamespaceNode
70
+ attr_reader :prefix, :uri
71
+ def initialize(prefix, uri)
72
+ @prefix = prefix
73
+ @uri = uri
74
+ end
75
+ end
76
+
77
+ class XmlCanonicalizer
78
+ attr_accessor :prefix_list, :logger
79
+
80
+ BEFORE_DOC_ELEMENT = 0
81
+ INSIDE_DOC_ELEMENT = 1
82
+ AFTER_DOC_ELEMENT = 2
83
+
84
+ NODE_TYPE_ATTRIBUTE = 3
85
+ NODE_TYPE_WHITESPACE = 4
86
+ NODE_TYPE_COMMENT = 5
87
+ NODE_TYPE_PI = 6
88
+ NODE_TYPE_TEXT = 7
89
+
90
+
91
+ def initialize(with_comments, excl_c14n)
92
+ @with_comments = with_comments
93
+ @exclusive = excl_c14n
94
+ @res = ""
95
+ @state = BEFORE_DOC_ELEMENT
96
+ @xnl = Array.new()
97
+ @prevVisibleNamespacesStart = 0
98
+ @prevVisibleNamespacesEnd = 0
99
+ @visibleNamespaces = Array.new()
100
+ @inclusive_namespaces = Array.new()
101
+ @prefix_list = nil
102
+ @rendered_prefixes = Array.new()
103
+ @logger = Logger.new("xmlcanonicalizer.log")
104
+ @logger.level = Logger::DEBUG
105
+ end
106
+
107
+ def add_inclusive_namespaces(prefix_list, element, visible_namespaces)
108
+ namespaces = element.attributes()
109
+ namespaces.each_attribute{|ns|
110
+ if (ns.prefix=="xmlns")
111
+ if (prefix_list.include?(ns.local_name()))
112
+ visible_namespaces.push(NamespaceNode.new("xmlns:"+ns.local_name(), ns.value()))
113
+ end
114
+ end
115
+ }
116
+ parent = element.parent()
117
+ add_inclusive_namespaces(prefix_list, parent, visible_namespaces) if (parent)
118
+ visible_namespaces
119
+ end
120
+
121
+ def canonicalize(document)
122
+ write_document_node(document)
123
+ @logger.debug("\nCanonicalized result\n" + @res.to_s()) if @logger
124
+ @res
125
+ end
126
+
127
+ def canonicalize_element(element, logging = true)
128
+ @logger.debug("Canonicalize element:\n" + element.to_s()) if @logger
129
+ @inclusive_namespaces = add_inclusive_namespaces(@prefix_list, element, @inclusive_namespaces) if (@prefix_list)
130
+ @preserve_document = element.document()
131
+ tmp_parent = element.parent()
132
+ body_string = remove_whitespace(element.to_s().gsub("\n","").gsub("\t","").gsub("\r",""))
133
+ document = Document.new(body_string)
134
+ tmp_parent.delete_element(element)
135
+ element = tmp_parent.add_element(document.root())
136
+ @preserve_element = element
137
+ document = Document.new(element.to_s())
138
+ ns = element.namespace(element.prefix())
139
+ document.root().add_namespace(element.prefix(), ns)
140
+ write_document_node(document)
141
+ @logger.debug("Canonicalized result:\n" + @res.to_s()) if @logger
142
+ @res
143
+ end
144
+
145
+ def write_document_node(document)
146
+ @state = BEFORE_DOC_ELEMENT
147
+ if (document.class().to_s() == "REXML::Element")
148
+ write_node(document)
149
+ else
150
+ document.each_child{|child|
151
+ write_node(child)
152
+ }
153
+ end
154
+ @res
155
+ end
156
+
157
+ def write_node(node)
158
+ visible = is_node_visible(node)
159
+ if ((node.node_type() == :text) && white_text?(node.value()))
160
+ res = node.value()
161
+ res.gsub("\r\n","\n")
162
+ #res = res.delete(" ").delete("\t")
163
+ res.delete("\r")
164
+ @res = @res + res
165
+ #write_text_node(node,visible) if (@state == INSIDE_DOC_ELEMENT)
166
+ return
167
+ end
168
+ if (node.node_type() == :text)
169
+ write_text_node(node, visible)
170
+ return
171
+ end
172
+ if (node.node_type() == :element)
173
+ write_element_node(node, visible) if (!node.rendered?())
174
+ node.rendered=(true)
175
+ end
176
+ if (node.node_type() == :processing_instruction)
177
+ end
178
+ if (node.node_type() == :comment)
179
+ end
180
+ end
181
+
182
+ def write_element_node(node, visible)
183
+ savedPrevVisibleNamespacesStart = @prevVisibleNamespacesStart
184
+ savedPrevVisibleNamespacesEnd = @prevVisibleNamespacesEnd
185
+ savedVisibleNamespacesSize = @visibleNamespaces.size()
186
+ state = @state
187
+ state = INSIDE_DOC_ELEMENT if (visible && state == BEFORE_DOC_ELEMENT)
188
+ @res = @res + "<" + node.expanded_name() if (visible)
189
+ write_namespace_axis(node, visible)
190
+ write_attribute_axis(node)
191
+ @res = @res + ">" if (visible)
192
+ node.each_child{|child|
193
+ write_node(child)
194
+ }
195
+ @res = @res + "</" +node.expanded_name() + ">" if (visible)
196
+ @state = AFTER_DOC_ELEMENT if (visible && state == BEFORE_DOC_ELEMENT)
197
+ @prevVisibleNamespacesStart = savedPrevVisibleNamespacesStart
198
+ @prevVisibleNamespacesEnd = savedPrevVisibleNamespacesEnd
199
+ @visibleNamespaces.slice!(savedVisibleNamespacesSize, @visibleNamespaces.size() - savedVisibleNamespacesSize) if (@visibleNamespaces.size() > savedVisibleNamespacesSize)
200
+ end
201
+
202
+ def write_namespace_axis(node, visible)
203
+ doc = node.document()
204
+ has_empty_namespace = false
205
+ list = Array.new()
206
+ cur = node
207
+ #while ((cur != nil) && (cur != doc) && (cur.node_type() != :document))
208
+ namespaces = cur.node_namespaces()
209
+ namespaces.each{|prefix|
210
+ next if ((prefix == "xmlns") && (node.namespace(prefix) == ""))
211
+ namespace = cur.namespace(prefix)
212
+ next if (is_namespace_node(namespace))
213
+ next if (node.namespace(prefix) != cur.namespace(prefix))
214
+ next if (prefix == "xml" && namespace == "http://www.w3.org/XML/1998/namespace")
215
+ next if (!is_node_visible(cur))
216
+ rendered = is_namespace_rendered(prefix, namespace)
217
+ @visibleNamespaces.push(NamespaceNode.new("xmlns:"+prefix,namespace)) if (visible)
218
+ if ((!rendered) && !list.include?(prefix))
219
+ list.push(prefix)
220
+ end
221
+ has_empty_namespace = true if (prefix == nil)
222
+ }
223
+ if (visible && !has_empty_namespace && !is_namespace_rendered(nil, nil))
224
+ @res = @res + ' xmlns=""'
225
+ end
226
+ #TODO: ns of inclusive_list
227
+ #=begin
228
+ if ((@prefix_list) && (node.to_s() == node.parent().to_s()))
229
+ #list.push(node.prefix())
230
+ @inclusive_namespaces.each{|ns|
231
+ prefix = ns.prefix().split(":")[1]
232
+ list.push(prefix) if (!list.include?(prefix) && (!node.attributes.prefixes.include?(prefix)))
233
+ }
234
+ @prefix_list = nil
235
+ end
236
+ #=end
237
+ list.sort!()
238
+ list.insert(0, "xmlns") unless list.delete("xmlns").nil?
239
+ list.each{|prefix|
240
+ next if (prefix == "")
241
+ next if (@rendered_prefixes.include?(prefix))
242
+ @rendered_prefixes.push(prefix)
243
+ ns = node.namespace(prefix)
244
+ ns = @preserve_element.namespace(prefix) if (ns == nil)
245
+ @res = @res + normalize_string(" " + prefix + '="' + ns + '"', NODE_TYPE_TEXT) if (prefix == "xmlns")
246
+ @res = @res + normalize_string(" xmlns:" + prefix + '="' + ns + '"', NODE_TYPE_TEXT) if (prefix != nil && prefix != "xmlns")
247
+ }
248
+ if (visible)
249
+ @prevVisibleNamespacesStart = @prevVisibleNamespacesEnd
250
+ @prevVisibleNamespacesEnd = @visibleNamespaces.size()
251
+ end
252
+ end
253
+
254
+ def write_attribute_axis(node)
255
+ list = Array.new()
256
+ node.attributes().sort.each{|key, attr|
257
+ list.push(attr) if (!is_namespace_node(attr.value()) && !is_namespace_decl(attr)) # && is_node_visible(
258
+ }
259
+ if (!@exclusive && node.parent() != nil && node.parent().parent() != nil)
260
+ cur = node.parent()
261
+ while (cur != nil)
262
+ #next if (cur.attributes() == nil)
263
+ cur.each_attribute{|attribute|
264
+ next if (attribute.prefix() != "xml")
265
+ next if (attribute.prefix().index("xmlns") == 0)
266
+ next if (node.namespace(attribute.prefix()) == attribute.value())
267
+ found = true
268
+ list.each{|n|
269
+ if (n.prefix() == "xml" && n.value() == attritbute.value())
270
+ found = true
271
+ break
272
+ end
273
+ }
274
+ next if (found)
275
+ list.push(attribute)
276
+ }
277
+ end
278
+ end
279
+ list.each{|attribute|
280
+ if (attribute != nil)
281
+ if (attribute.name() != "xmlns")
282
+ @res = @res + " " + normalize_string(attribute.to_string(), NODE_TYPE_ATTRIBUTE).gsub("'",'"')
283
+ end
284
+ # else
285
+ # @res = @res + " " + normalize_string(attribute.name()+'="'+attribute.to_s()+'"', NODE_TYPE_ATTRIBUTE).gsub("'",'"')
286
+ #end
287
+ end
288
+ }
289
+ end
290
+
291
+ def is_namespace_node(namespace_uri)
292
+ return (namespace_uri == "http://www.w3.org/2000/xmlns/")
293
+ end
294
+
295
+ def is_namespace_rendered(prefix, uri)
296
+ is_empty_ns = prefix == nil && uri == nil
297
+ if (is_empty_ns)
298
+ start = 0
299
+ else
300
+ start = @prevVisibleNamespacesStart
301
+ end
302
+ @visibleNamespaces.each{|ns|
303
+ if (ns.prefix() == "xmlns:"+prefix.to_s() && ns.uri() == uri)
304
+ return true
305
+ end
306
+ }
307
+ return is_empty_ns
308
+ #(@visibleNamespaces.size()-1).downto(start) {|i|
309
+ # ns = @visibleNamespaces[i]
310
+ # return true if (ns.prefix() == "xmlns:"+prefix.to_s() && ns.uri() == uri)
311
+ # #p = ns.prefix() if (ns.prefix().index("xmlns") == 0)
312
+ # #return ns.uri() == uri if (p == prefix)
313
+ #}
314
+ #return is_empty_ns
315
+ end
316
+
317
+ def is_node_visible(node)
318
+ return true if (@xnl.size() == 0)
319
+ @xnl.each{|element|
320
+ return true if (element == node)
321
+ }
322
+ return false
323
+ end
324
+
325
+ def normalize_string(input, type)
326
+ sb = ""
327
+ return input
328
+ end
329
+ #input.each_byte{|b|
330
+ # if (b ==60 && (type == NODE_TYPE_ATTRIBUTE || is_text_node(type)))
331
+ # sb = sb + "&lt;"
332
+ # elsif (b == 62 && is_text_node(type))
333
+ # sb = sb + "&gt;"
334
+ # elsif (b == 38 && (is_text_node(type) || is_text_node(type))) #Ampersand
335
+ # sb = sb + "&amp;"
336
+ # elsif (b == 34 && is_text_node(type)) #Quote
337
+ # sb = sb + "&quot;"
338
+ # elsif (b == 9 && is_text_node(type)) #Tabulator
339
+ # sb = sb + "&#x9;"
340
+ # elsif (b == 11 && is_text_node(type)) #CR
341
+ # sb = sb + "&#xA;"
342
+ # elsif (b == 13 && (type == NODE_TYPE_ATTRIBUTE || (is_text_node(type) && type != NODE_TYPE_WHITESPACE) || type == NODE_TYPE_COMMENT || type == NODE_TYPE_PI))
343
+ # sb = sb + "&#xD;"
344
+ # elsif (b == 13)
345
+ # next
346
+ # else
347
+ # sb = sb.concat(b)
348
+ # end
349
+ #}
350
+ #sb
351
+ #end
352
+
353
+ def write_text_node(node, visible)
354
+ if (visible)
355
+ @res = @res + normalize_string(node.value(), node.node_type())
356
+ end
357
+ end
358
+
359
+ def white_text?(text)
360
+ return true if ((text.strip() == "") || (text.strip() == nil))
361
+ return false
362
+ end
363
+
364
+ def is_namespace_decl(attribute)
365
+ #return true if (attribute.name() == "xmlns")
366
+ return true if (attribute.prefix().index("xmlns") == 0)
367
+ return false
368
+ end
369
+
370
+ def is_text_node(type)
371
+ return true if (type == NODE_TYPE_TEXT || type == NODE_TYPE_CDATA || type == NODE_TYPE_WHITESPACE)
372
+ return false
373
+ end
374
+
375
+ def remove_whitespace(string)
376
+ new_string = ""
377
+ in_white = false
378
+ string.each_byte{|b|
379
+ #if (in_white && b == 32)
380
+ #else
381
+ if !(in_white && b == 32)
382
+ new_string = new_string + b.chr()
383
+ end
384
+ if (b == 62) #>
385
+ in_white = true
386
+ end
387
+ if (b == 60) #<
388
+ in_white = false
389
+ end
390
+ }
391
+ new_string
392
+ end
393
+ end
394
+
395
+
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.2
3
+ specification_version: 1
4
+ name: GoogleSSO
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.0.1
7
+ date: 2007-06-14 00:00:00 +02:00
8
+ summary: "Google SSO: SAML signing and processing."
9
+ require_paths:
10
+ - lib
11
+ email: NN
12
+ homepage: http://rubyforge.org
13
+ rubyforge_project:
14
+ description: "Google SSO: SAML signing and processing."
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - NN
31
+ files:
32
+ - lib/GoogleSSO.rb
33
+ - lib/processresponse.rb
34
+ - lib/xmlcanonicalizer.rb
35
+ test_files: []
36
+
37
+ rdoc_options: []
38
+
39
+ extra_rdoc_files: []
40
+
41
+ executables: []
42
+
43
+ extensions: []
44
+
45
+ requirements: []
46
+
47
+ dependencies: []
48
+