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 +88 -0
- data/lib/omniauth-saml.rb +1 -0
- data/lib/omniauth-saml/version.rb +5 -0
- data/lib/omniauth/strategies/saml.rb +47 -0
- data/lib/omniauth/strategies/saml/auth_request.rb +38 -0
- data/lib/omniauth/strategies/saml/auth_response.rb +141 -0
- data/lib/omniauth/strategies/saml/validation_error.rb +8 -0
- data/lib/omniauth/strategies/saml/xml_security.rb +126 -0
- data/spec/omniauth/strategies/saml_spec.rb +37 -0
- metadata +125 -0
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,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,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
|