omniauth-saml 0.9.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.

Potentially problematic release.


This version of omniauth-saml might be problematic. Click here for more details.

data/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # OmniAuth SAML
2
+
3
+ A generic SAML strategy for OmniAuth.
4
+
5
+ https://github.com/PracticallyGreen/omniauth-saml
6
+
7
+ Use the SAML strategy as a middleware in your application:
8
+
9
+ ```ruby
10
+ require 'omniauth'
11
+ use OmniAuth::Strategies::SAML,
12
+ :assertion_consumer_service_url => "consumer_service_url",
13
+ :issuer => "issuer",
14
+ :idp_sso_target_url => "idp_sso_target_url",
15
+ :idp_cert_fingerprint => "E7:91:B2:E1:...",
16
+ :name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
17
+ ```
18
+
19
+ or in your Rails application:
20
+
21
+ in `Gemfile`:
22
+
23
+ ```ruby
24
+ gem 'omniauth-saml'
25
+ ```
26
+
27
+ and in `config/initializers/omniauth.rb`:
28
+
29
+ ```ruby
30
+ Rails.application.config.middleware.use OmniAuth::Builder do
31
+ provider :saml,
32
+ :assertion_consumer_service_url => "consumer_service_url",
33
+ :issuer => "rails-application",
34
+ :idp_sso_target_url => "idp_sso_target_url",
35
+ :idp_cert_fingerprint => "E7:91:B2:E1:...",
36
+ :name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
37
+ end
38
+ ```
39
+
40
+ ## Options
41
+
42
+ * `:assertion_consumer_service_url` - The URL at which the SAML assertion should be
43
+ received. With OmniAuth this is typically `http://example.com/auth/callback`.
44
+ **Required**.
45
+
46
+ * `:issuer` - The name of your application. Some identity providers might need this
47
+ to establish the identity of the service provider requesting the login. **Required**.
48
+
49
+ * `:idp_sso_target_url` - The URL to which the authentication request should be sent.
50
+ This would be on the identity provider. **Required**.
51
+
52
+ * `:idp_cert_fingerprint` - The certificate fingerprint, e.g. "90:CC:16:F0:8D:...".
53
+ This is provided from the identity provider when setting up the relationship.
54
+ Optional.
55
+
56
+ * `:name_identifier_format` - Describes the format of the username required by this
57
+ application. If you need the email address, use "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress".
58
+ See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf section 8.3 for
59
+ other options. Note that the identity provider might not support all options.
60
+ Optional.
61
+
62
+ ## License
63
+
64
+ Copyright (c) 2011-2012 [Practically Green, Inc.](http://practicallygreen.com/) and [Rajiv Aaron Manglani](http://www.rajivmanglani.com/).
65
+ All rights reserved. Released under the MIT license.
66
+
67
+ Portions Copyright (c) 2007 Sun Microsystems Inc.
68
+ Portions Copyright (c) 2007 Todd W Saxton.
69
+ Portions Copyright (c) 2011 Raecoo Cao.
70
+ Portions Copyright (c) 2011 Ryan Wilcox.
71
+
72
+ Permission is hereby granted, free of charge, to any person obtaining a copy
73
+ of this software and associated documentation files (the "Software"), to deal
74
+ in the Software without restriction, including without limitation the rights
75
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
76
+ copies of the Software, and to permit persons to whom the Software is
77
+ furnished to do so, subject to the following conditions:
78
+
79
+ The above copyright notice and this permission notice shall be included in
80
+ all copies or substantial portions of the Software.
81
+
82
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
83
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
84
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
85
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
86
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
87
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
88
+ THE SOFTWARE.
@@ -0,0 +1 @@
1
+ require 'omniauth/strategies/saml'
@@ -0,0 +1,5 @@
1
+ module OmniAuth
2
+ module SAML
3
+ VERSION = "0.9.0"
4
+ end
5
+ end
@@ -0,0 +1,47 @@
1
+ require 'omniauth'
2
+
3
+ module OmniAuth
4
+ module Strategies
5
+ class SAML
6
+ include OmniAuth::Strategy
7
+ autoload :AuthRequest, 'omniauth/strategies/saml/auth_request'
8
+ autoload :AuthResponse, 'omniauth/strategies/saml/auth_response'
9
+ autoload :ValidationError, 'omniauth/strategies/saml/validation_error'
10
+ autoload :XMLSecurity, 'omniauth/strategies/saml/xml_security'
11
+
12
+ option :name_identifier_format, "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
13
+
14
+ def request_phase
15
+ request = OmniAuth::Strategies::SAML::AuthRequest.new
16
+ redirect(request.create(options))
17
+ end
18
+
19
+ def callback_phase
20
+ begin
21
+ response = OmniAuth::Strategies::SAML::AuthResponse.new(request.params['SAMLResponse'])
22
+ response.settings = options
23
+ @name_id = response.name_id
24
+ @extra_attributes = response.attributes
25
+ return fail!(:invalid_ticket, 'Invalid SAML Ticket') if @name_id.nil? || @name_id.empty?
26
+ super
27
+ rescue ArgumentError => e
28
+ fail!(:invalid_ticket, 'Invalid SAML Response')
29
+ end
30
+ end
31
+
32
+ uid { @name_id }
33
+
34
+ info do
35
+ {
36
+ :name => @extra_attributes['urn:oid:0.9.2342.19200300.100.1.1'],
37
+ :email => @extra_attributes['urn:oid:0.9.2342.19200300.100.1.3']
38
+ }
39
+ end
40
+
41
+ extra { @extra_attributes }
42
+
43
+ end
44
+ end
45
+ end
46
+
47
+ OmniAuth.config.add_camelization 'saml', 'SAML'
@@ -0,0 +1,38 @@
1
+ require "base64"
2
+ require "uuid"
3
+ require "zlib"
4
+ require "cgi"
5
+
6
+ module OmniAuth
7
+ module Strategies
8
+ class SAML
9
+ class AuthRequest
10
+
11
+ def create(settings, params = {})
12
+ uuid = "_" + UUID.new.generate
13
+ time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
14
+
15
+ request =
16
+ "<samlp:AuthnRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" ID=\"#{uuid}\" Version=\"2.0\" IssueInstant=\"#{time}\" ProtocolBinding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" AssertionConsumerServiceURL=\"#{settings[:assertion_consumer_service_url]}\">" +
17
+ "<saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">#{settings[:issuer]}</saml:Issuer>\n" +
18
+ "<samlp:NameIDPolicy xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" Format=\"#{settings[:name_identifier_format]}\" AllowCreate=\"true\"></samlp:NameIDPolicy>\n" +
19
+ "<samlp:RequestedAuthnContext xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" Comparison=\"exact\">" +
20
+ "<saml:AuthnContextClassRef xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef></samlp:RequestedAuthnContext>\n" +
21
+ "</samlp:AuthnRequest>"
22
+
23
+ deflated_request = Zlib::Deflate.deflate(request, 9)[2..-5]
24
+ base64_request = Base64.encode64(deflated_request)
25
+ encoded_request = CGI.escape(base64_request)
26
+ request_params = "?SAMLRequest=" + encoded_request
27
+
28
+ params.each_pair do |key, value|
29
+ request_params << "&#{key}=#{CGI.escape(value.to_s)}"
30
+ end
31
+
32
+ settings[:idp_sso_target_url] + request_params
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,141 @@
1
+ require "time"
2
+
3
+ module OmniAuth
4
+ module Strategies
5
+ class SAML
6
+ class AuthResponse
7
+
8
+ ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
9
+ PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
10
+ DSIG = "http://www.w3.org/2000/09/xmldsig#"
11
+
12
+ attr_accessor :options, :response, :document, :settings
13
+
14
+ def initialize(response, options = {})
15
+ raise ArgumentError.new("Response cannot be nil") if response.nil?
16
+ self.options = options
17
+ self.response = response
18
+ self.document = OmniAuth::Strategies::SAML::XMLSecurity::SignedDocument.new(Base64.decode64(response))
19
+ end
20
+
21
+ def is_valid?
22
+ validate(soft = true)
23
+ end
24
+
25
+ def validate!
26
+ validate(soft = false)
27
+ end
28
+
29
+ # The value of the user identifier as designated by the initialization request response
30
+ def name_id
31
+ @name_id ||= begin
32
+ node = REXML::XPath.first(document, "/p:Response/a:Assertion[@ID='#{document.signed_element_id[1,document.signed_element_id.size]}']/a:Subject/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
33
+ node ||= REXML::XPath.first(document, "/p:Response[@ID='#{document.signed_element_id[1,document.signed_element_id.size]}']/a:Assertion/a:Subject/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
34
+ node.nil? ? nil : node.text
35
+ end
36
+ end
37
+
38
+ # A hash of all the attributes with the response. Assuming there is only one value for each key
39
+ def attributes
40
+ @attr_statements ||= begin
41
+ result = {}
42
+
43
+ stmt_element = REXML::XPath.first(document, "/p:Response/a:Assertion/a:AttributeStatement", { "p" => PROTOCOL, "a" => ASSERTION })
44
+ return {} if stmt_element.nil?
45
+
46
+ stmt_element.elements.each do |attr_element|
47
+ name = attr_element.attributes["Name"]
48
+ value = attr_element.elements.first.text
49
+
50
+ result[name] = value
51
+ end
52
+
53
+ result.keys.each do |key|
54
+ result[key.intern] = result[key]
55
+ end
56
+
57
+ result
58
+ end
59
+ end
60
+
61
+ # When this user session should expire at latest
62
+ def session_expires_at
63
+ @expires_at ||= begin
64
+ node = REXML::XPath.first(document, "/p:Response/a:Assertion/a:AuthnStatement", { "p" => PROTOCOL, "a" => ASSERTION })
65
+ parse_time(node, "SessionNotOnOrAfter")
66
+ end
67
+ end
68
+
69
+ # Conditions (if any) for the assertion to run
70
+ def conditions
71
+ @conditions ||= begin
72
+ REXML::XPath.first(document, "/p:Response/a:Assertion[@ID='#{document.signed_element_id[1,document.signed_element_id.size]}']/a:Conditions", { "p" => PROTOCOL, "a" => ASSERTION })
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def validation_error(message)
79
+ raise OmniAuth::Strategies::SAML::ValidationError.new(message)
80
+ end
81
+
82
+ def validate(soft = true)
83
+ validate_response_state(soft) &&
84
+ validate_conditions(soft) &&
85
+ document.validate(get_fingerprint, soft)
86
+ end
87
+
88
+ def validate_response_state(soft = true)
89
+ if response.empty?
90
+ return soft ? false : validation_error("Blank response")
91
+ end
92
+
93
+ if settings.nil?
94
+ return soft ? false : validation_error("No settings on response")
95
+ end
96
+
97
+ if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil?
98
+ return soft ? false : validation_error("No fingerprint or certificate on settings")
99
+ end
100
+
101
+ true
102
+ end
103
+
104
+ def get_fingerprint
105
+ if settings.idp_cert
106
+ cert = OpenSSL::X509::Certificate.new(settings.idp_cert)
107
+ Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":")
108
+ else
109
+ settings.idp_cert_fingerprint
110
+ end
111
+ end
112
+
113
+ def validate_conditions(soft = true)
114
+ return true if conditions.nil?
115
+ return true if options[:skip_conditions]
116
+
117
+ if not_before = parse_time(conditions, "NotBefore")
118
+ if Time.now.utc < not_before
119
+ return soft ? false : validation_error("Current time is earlier than NotBefore condition")
120
+ end
121
+ end
122
+
123
+ if not_on_or_after = parse_time(conditions, "NotOnOrAfter")
124
+ if Time.now.utc >= not_on_or_after
125
+ return soft ? false : validation_error("Current time is on or after NotOnOrAfter condition")
126
+ end
127
+ end
128
+
129
+ true
130
+ end
131
+
132
+ def parse_time(node, attribute)
133
+ if node && node.attributes[attribute]
134
+ Time.parse(node.attributes[attribute])
135
+ end
136
+ end
137
+
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,8 @@
1
+ module OmniAuth
2
+ module Strategies
3
+ class SAML
4
+ class ValidationError < Exception
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,126 @@
1
+ # The contents of this file are subject to the terms
2
+ # of the Common Development and Distribution License
3
+ # (the License). You may not use this file except in
4
+ # compliance with the License.
5
+ #
6
+ # You can obtain a copy of the License at
7
+ # https://opensso.dev.java.net/public/CDDLv1.0.html or
8
+ # opensso/legal/CDDLv1.0.txt
9
+ # See the License for the specific language governing
10
+ # permission and limitations under the License.
11
+ #
12
+ # When distributing Covered Code, include this CDDL
13
+ # Header Notice in each file and include the License file
14
+ # at opensso/legal/CDDLv1.0.txt.
15
+ # If applicable, add the following below the CDDL Header,
16
+ # with the fields enclosed by brackets [] replaced by
17
+ # your own identifying information:
18
+ # "Portions Copyrighted [year] [name of copyright owner]"
19
+ #
20
+ # $Id: xml_sec.rb,v 1.6 2007/10/24 00:28:41 todddd Exp $
21
+ #
22
+ # Copyright 2007 Sun Microsystems Inc. All Rights Reserved
23
+ # Portions Copyrighted 2007 Todd W Saxton.
24
+
25
+ require 'rubygems'
26
+ require "rexml/document"
27
+ require "rexml/xpath"
28
+ require "openssl"
29
+ require "xmlcanonicalizer"
30
+ require "digest/sha1"
31
+
32
+ module OmniAuth
33
+ module Strategies
34
+ class SAML
35
+
36
+ module XMLSecurity
37
+
38
+ class SignedDocument < REXML::Document
39
+ DSIG = "http://www.w3.org/2000/09/xmldsig#"
40
+
41
+ attr_accessor :signed_element_id
42
+
43
+ def initialize(response)
44
+ super(response)
45
+ extract_signed_element_id
46
+ end
47
+
48
+ def validate(idp_cert_fingerprint, soft = true)
49
+ # get cert from response
50
+ base64_cert = self.elements["//ds:X509Certificate"].text
51
+ cert_text = Base64.decode64(base64_cert)
52
+ cert = OpenSSL::X509::Certificate.new(cert_text)
53
+
54
+ # check cert matches registered idp cert
55
+ fingerprint = Digest::SHA1.hexdigest(cert.to_der)
56
+
57
+ if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
58
+ return soft ? false : (raise OmniAuth::Strategies::SAML::ValidationError.new("Fingerprint mismatch"))
59
+ end
60
+
61
+ validate_doc(base64_cert, soft)
62
+ end
63
+
64
+ def validate_doc(base64_cert, soft = true)
65
+ # validate references
66
+
67
+ # check for inclusive namespaces
68
+
69
+ inclusive_namespaces = []
70
+ inclusive_namespace_element = REXML::XPath.first(self, "//ec:InclusiveNamespaces")
71
+
72
+ if inclusive_namespace_element
73
+ prefix_list = inclusive_namespace_element.attributes.get_attribute('PrefixList').value
74
+ inclusive_namespaces = prefix_list.split(" ")
75
+ end
76
+
77
+ # remove signature node
78
+ sig_element = REXML::XPath.first(self, "//ds:Signature", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"})
79
+ sig_element.remove
80
+
81
+ # check digests
82
+ REXML::XPath.each(sig_element, "//ds:Reference", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"}) do |ref|
83
+ uri = ref.attributes.get_attribute("URI").value
84
+ hashed_element = REXML::XPath.first(self, "//[@ID='#{uri[1,uri.size]}']")
85
+ canoner = XML::Util::XmlCanonicalizer.new(false, true)
86
+ canoner.inclusive_namespaces = inclusive_namespaces if canoner.respond_to?(:inclusive_namespaces) && !inclusive_namespaces.empty?
87
+ canon_hashed_element = canoner.canonicalize(hashed_element)
88
+ hash = Base64.encode64(Digest::SHA1.digest(canon_hashed_element)).chomp
89
+ digest_value = REXML::XPath.first(ref, "//ds:DigestValue", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"}).text
90
+
91
+ if hash != digest_value
92
+ return soft ? false : (raise OmniAuth::Strategies::SAML::ValidationError.new("Digest mismatch"))
93
+ end
94
+ end
95
+
96
+ # verify signature
97
+ canoner = XML::Util::XmlCanonicalizer.new(false, true)
98
+ signed_info_element = REXML::XPath.first(sig_element, "//ds:SignedInfo", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"})
99
+ canon_string = canoner.canonicalize(signed_info_element)
100
+
101
+ base64_signature = REXML::XPath.first(sig_element, "//ds:SignatureValue", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"}).text
102
+ signature = Base64.decode64(base64_signature)
103
+
104
+ # get certificate object
105
+ cert_text = Base64.decode64(base64_cert)
106
+ cert = OpenSSL::X509::Certificate.new(cert_text)
107
+
108
+ if !cert.public_key.verify(OpenSSL::Digest::SHA1.new, signature, canon_string)
109
+ return soft ? false : (raise OmniAuth::Strategies::SAML::ValidationError.new("Key validation error"))
110
+ end
111
+
112
+ return true
113
+ end
114
+
115
+ private
116
+
117
+ def extract_signed_element_id
118
+ reference_element = REXML::XPath.first(self, "//ds:Signature/ds:SignedInfo/ds:Reference", {"ds"=>DSIG})
119
+ self.signed_element_id = reference_element.attribute("URI").value unless reference_element.nil?
120
+ end
121
+ end
122
+ end
123
+
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,37 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ describe OmniAuth::Strategies::SAML, :type => :strategy do
4
+
5
+ include OmniAuth::Test::StrategyTestCase
6
+
7
+ def strategy
8
+ [OmniAuth::Strategies::SAML, {
9
+ :assertion_consumer_service_url => "http://consumer.service.url/auth/saml/callback",
10
+ :issuer => "https://saml.issuer.url/issuers/29490",
11
+ :idp_sso_target_url => "https://idp.sso.target_url/signon/29490",
12
+ :idp_cert_fingerprint => "E7:91:B2:E1:4C:65:2C:49:F3:33:74:0A:58:5A:7E:55:F7:15:7A:33",
13
+ :name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
14
+ }]
15
+ end
16
+
17
+ describe 'GET /auth/saml' do
18
+ before do
19
+ get '/auth/saml'
20
+ end
21
+
22
+ it 'should get authentication page' do
23
+ last_response.should be_redirect
24
+ end
25
+ end
26
+
27
+ describe 'POST /auth/saml/callback' do
28
+
29
+ it 'should raise ArgumentError exception without the SAMLResponse parameter' do
30
+ post '/auth/saml/callback'
31
+ last_response.should be_redirect
32
+ last_response.location.should == '/auth/failure?message=invalid_ticket'
33
+ end
34
+
35
+ end
36
+
37
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: omniauth-saml
3
+ version: !ruby/object:Gem::Version
4
+ hash: 59
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 9
9
+ - 0
10
+ version: 0.9.0
11
+ platform: ruby
12
+ authors:
13
+ - Raecoo Cao
14
+ - Ryan Wilcox
15
+ - Rajiv Aaron Manglani
16
+ autorequire:
17
+ bindir: bin
18
+ cert_chain: []
19
+
20
+ date: 2012-02-14 00:00:00 -05:00
21
+ default_executable:
22
+ dependencies:
23
+ - !ruby/object:Gem::Dependency
24
+ name: omniauth
25
+ prerelease: false
26
+ requirement: &id001 !ruby/object:Gem::Requirement
27
+ none: false
28
+ requirements:
29
+ - - ~>
30
+ - !ruby/object:Gem::Version
31
+ hash: 15
32
+ segments:
33
+ - 1
34
+ - 0
35
+ version: "1.0"
36
+ type: :runtime
37
+ version_requirements: *id001
38
+ - !ruby/object:Gem::Dependency
39
+ name: XMLCanonicalizer
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ hash: 21
47
+ segments:
48
+ - 1
49
+ - 0
50
+ - 1
51
+ version: 1.0.1
52
+ type: :runtime
53
+ version_requirements: *id002
54
+ - !ruby/object:Gem::Dependency
55
+ name: uuid
56
+ prerelease: false
57
+ requirement: &id003 !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ hash: 5
63
+ segments:
64
+ - 2
65
+ - 3
66
+ version: "2.3"
67
+ type: :runtime
68
+ version_requirements: *id003
69
+ description: A generic SAML strategy for OmniAuth.
70
+ email:
71
+ - raecoo@gmail.com
72
+ - rwilcox@wilcoxd.com
73
+ - rajiv@alum.mit.edu
74
+ executables: []
75
+
76
+ extensions: []
77
+
78
+ extra_rdoc_files: []
79
+
80
+ files:
81
+ - README.md
82
+ - lib/omniauth/strategies/saml/auth_request.rb
83
+ - lib/omniauth/strategies/saml/auth_response.rb
84
+ - lib/omniauth/strategies/saml/validation_error.rb
85
+ - lib/omniauth/strategies/saml/xml_security.rb
86
+ - lib/omniauth/strategies/saml.rb
87
+ - lib/omniauth-saml/version.rb
88
+ - lib/omniauth-saml.rb
89
+ - spec/omniauth/strategies/saml_spec.rb
90
+ has_rdoc: true
91
+ homepage: https://github.com/PracticallyGreen/omniauth-saml
92
+ licenses: []
93
+
94
+ post_install_message:
95
+ rdoc_options: []
96
+
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ hash: 3
105
+ segments:
106
+ - 0
107
+ version: "0"
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ hash: 3
114
+ segments:
115
+ - 0
116
+ version: "0"
117
+ requirements: []
118
+
119
+ rubyforge_project:
120
+ rubygems_version: 1.6.2
121
+ signing_key:
122
+ specification_version: 3
123
+ summary: A generic SAML strategy for OmniAuth.
124
+ test_files:
125
+ - spec/omniauth/strategies/saml_spec.rb