omniauth-saml 0.9.0

Sign up to get free protection for your applications and to get access to all the features.

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