oa-enterprise 0.2.6 → 0.3.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.
- data/Gemfile +4 -0
- data/README.rdoc +34 -1
- data/lib/omniauth/enterprise.rb +1 -0
- data/lib/omniauth/strategies/ldap/adaptor.rb +7 -10
- data/lib/omniauth/strategies/ldap.rb +50 -48
- 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/lib/omniauth/strategies/saml.rb +50 -0
- data/lib/omniauth/version.rb +2 -2
- data/oa-enterprise.gemspec +19 -19
- data/spec/omniauth/strategies/saml_spec.rb +37 -0
- metadata +127 -25
data/Gemfile
CHANGED
data/README.rdoc
CHANGED
|
@@ -66,7 +66,40 @@ are not familiar with these authentication methods, please just avoid them.
|
|
|
66
66
|
|
|
67
67
|
Direct users to '/auth/ldap' to have them authenticated via your
|
|
68
68
|
company's LDAP server.
|
|
69
|
-
|
|
69
|
+
|
|
70
|
+
== SAML
|
|
71
|
+
|
|
72
|
+
Use the SAML strategy as a middleware in your application:
|
|
73
|
+
|
|
74
|
+
require 'omniauth/enterprise'
|
|
75
|
+
use OmniAuth::Strategies::SAML,
|
|
76
|
+
:assertion_consumer_service_url => "consumer_service_url",
|
|
77
|
+
:issuer => "issuer",
|
|
78
|
+
:idp_sso_target_url => "idp_sso_target_url",
|
|
79
|
+
:idp_cert_fingerprint => "E7:91:B2:E1:...",
|
|
80
|
+
:name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
|
81
|
+
|
|
82
|
+
:assertion_consumer_service_url
|
|
83
|
+
The URL at which the SAML assertion should be received.
|
|
84
|
+
|
|
85
|
+
:issuer
|
|
86
|
+
The name of your application. Some identity providers might need this to establish the
|
|
87
|
+
identity of the service provider requesting the login.
|
|
88
|
+
|
|
89
|
+
:idp_sso_target_url
|
|
90
|
+
The URL to which the authentication request should be sent. This would be on the identity provider.
|
|
91
|
+
|
|
92
|
+
:idp_cert_fingerprint
|
|
93
|
+
The certificate fingerprint, e.g. "90:CC:16:F0:8D:A6:D1:C6:BB:27:2D:BA:93:80:1A:1F:16:8E:4E:08".
|
|
94
|
+
This is provided from the identity provider when setting up the relationship.
|
|
95
|
+
|
|
96
|
+
:name_identifier_format
|
|
97
|
+
Describes the format of the username required by this application.
|
|
98
|
+
If you need the email address, use "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress".
|
|
99
|
+
See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf section 8.3 for
|
|
100
|
+
other options. Note that the identity provider might not support all options.
|
|
101
|
+
|
|
102
|
+
|
|
70
103
|
== Multiple Strategies
|
|
71
104
|
|
|
72
105
|
If you're using multiple strategies together, use OmniAuth's Builder. That's
|
data/lib/omniauth/enterprise.rb
CHANGED
|
@@ -14,14 +14,13 @@ module OmniAuth
|
|
|
14
14
|
class AuthenticationError < StandardError; end
|
|
15
15
|
class ConnectionError < StandardError; end
|
|
16
16
|
|
|
17
|
-
VALID_ADAPTER_CONFIGURATION_KEYS = [:host, :port, :method, :bind_dn, :password,
|
|
18
|
-
:try_sasl, :sasl_mechanisms, :uid, :base, :allow_anonymous]
|
|
17
|
+
VALID_ADAPTER_CONFIGURATION_KEYS = [:host, :port, :method, :bind_dn, :password, :try_sasl, :sasl_mechanisms, :uid, :base, :allow_anonymous]
|
|
19
18
|
|
|
20
19
|
MUST_HAVE_KEYS = [:host, :port, :method, :uid, :base]
|
|
21
20
|
|
|
22
21
|
METHOD = {
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
:ssl => :simple_tls,
|
|
23
|
+
:tls => :start_tls,
|
|
25
24
|
:plain => nil,
|
|
26
25
|
}
|
|
27
26
|
|
|
@@ -63,7 +62,6 @@ module OmniAuth
|
|
|
63
62
|
@connection, @uri, @with_start_tls = begin
|
|
64
63
|
uri = construct_uri(host, port, method == :simple_tls)
|
|
65
64
|
with_start_tls = method == :start_tls
|
|
66
|
-
puts ({:uri => uri, :with_start_tls => with_start_tls}).inspect
|
|
67
65
|
[Net::LDAP::Connection.new(config), uri, with_start_tls]
|
|
68
66
|
rescue Net::LDAP::LdapError
|
|
69
67
|
raise ConnectionError, $!.message
|
|
@@ -91,9 +89,9 @@ module OmniAuth
|
|
|
91
89
|
# Attempt 2: SIMPLE with credentials if password block
|
|
92
90
|
# Attempt 3: SIMPLE ANONYMOUS if 1 and 2 fail and allow anonymous is set to true
|
|
93
91
|
if try_sasl and sasl_bind(bind_dn, options)
|
|
94
|
-
|
|
92
|
+
puts "bound with sasl"
|
|
95
93
|
elsif simple_bind(bind_dn, options)
|
|
96
|
-
|
|
94
|
+
puts "bound with simple"
|
|
97
95
|
elsif allow_anonymous and bind_as_anonymous(options)
|
|
98
96
|
puts "bound as anonymous"
|
|
99
97
|
else
|
|
@@ -127,12 +125,12 @@ module OmniAuth
|
|
|
127
125
|
end
|
|
128
126
|
|
|
129
127
|
def search(options={}, &block)
|
|
130
|
-
base = options[:base]
|
|
128
|
+
base = options[:base] || @base
|
|
131
129
|
filter = options[:filter]
|
|
132
130
|
limit = options[:limit]
|
|
133
131
|
|
|
134
132
|
args = {
|
|
135
|
-
:base =>
|
|
133
|
+
:base => base,
|
|
136
134
|
:filter => filter,
|
|
137
135
|
:size => limit
|
|
138
136
|
}
|
|
@@ -226,7 +224,6 @@ module OmniAuth
|
|
|
226
224
|
end
|
|
227
225
|
|
|
228
226
|
def sasl_bind_setup_gss_spnego(bind_dn, options)
|
|
229
|
-
puts options.inspect
|
|
230
227
|
user,psw = [bind_dn, options[:password]||@password]
|
|
231
228
|
raise LdapError.new( "invalid binding information" ) unless (user && psw)
|
|
232
229
|
|
|
@@ -9,19 +9,21 @@ module OmniAuth
|
|
|
9
9
|
include OmniAuth::Strategy
|
|
10
10
|
|
|
11
11
|
autoload :Adaptor, 'omniauth/strategies/ldap/adaptor'
|
|
12
|
-
@@config
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
12
|
+
@@config = {
|
|
13
|
+
'name' => 'cn',
|
|
14
|
+
'first_name' => 'givenName',
|
|
15
|
+
'last_name' => 'sn',
|
|
16
|
+
'email' => ['mail', "email", 'userPrincipalName'],
|
|
17
|
+
'phone' => ['telephoneNumber', 'homePhone', 'facsimileTelephoneNumber'],
|
|
18
|
+
'mobile_number' => ['mobile', 'mobileTelephoneNumber'],
|
|
19
|
+
'nickname' => ['uid', 'userid', 'sAMAccountName'],
|
|
20
|
+
'title' => 'title',
|
|
21
|
+
'location' => {"%0, %1, %2, %3 %4" => [['address', 'postalAddress', 'homePostalAddress', 'street', 'streetAddress'], ['l'], ['st'],['co'],['postOfficeBox']]},
|
|
22
|
+
'uid' => 'dn',
|
|
23
|
+
'url' => ['wwwhomepage'],
|
|
24
|
+
'image' => 'jpegPhoto',
|
|
25
|
+
'description' => 'description'
|
|
26
|
+
}
|
|
25
27
|
|
|
26
28
|
# Initialize the LDAP Middleware
|
|
27
29
|
#
|
|
@@ -44,7 +46,7 @@ module OmniAuth
|
|
|
44
46
|
end
|
|
45
47
|
end
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
def get_credentials
|
|
48
50
|
OmniAuth::Form.build(:title => (options[:title] || "LDAP Authentication")) do
|
|
49
51
|
text_field 'Login', 'username'
|
|
50
52
|
password_field 'Password', 'password'
|
|
@@ -52,28 +54,28 @@ module OmniAuth
|
|
|
52
54
|
end
|
|
53
55
|
|
|
54
56
|
def callback_phase
|
|
55
|
-
|
|
57
|
+
begin
|
|
56
58
|
creds = session['omniauth.ldap']
|
|
57
59
|
session.delete 'omniauth.ldap'
|
|
58
|
-
|
|
60
|
+
@ldap_user_info = {}
|
|
59
61
|
begin
|
|
60
|
-
|
|
62
|
+
(@adaptor.bind(:allow_anonymous => true) unless @adaptor.bound?)
|
|
61
63
|
rescue Exception => e
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
puts "failed to bind with the default credentials: " + e.message
|
|
65
|
+
end
|
|
64
66
|
@ldap_user_info = @adaptor.search(:filter => Net::LDAP::Filter.eq(@adaptor.uid, @name_proc.call(creds['username'])),:limit => 1) if @adaptor.bound?
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
bind_dn = creds['username']
|
|
68
|
+
bind_dn = @ldap_user_info[:dn].to_a.first if @ldap_user_info[:dn]
|
|
67
69
|
@adaptor.bind(:bind_dn => bind_dn, :password => creds['password'])
|
|
68
70
|
@ldap_user_info = @adaptor.search(:filter => Net::LDAP::Filter.eq(@adaptor.uid, @name_proc.call(creds['username'])),:limit => 1) if @ldap_user_info.empty?
|
|
69
|
-
|
|
71
|
+
@user_info = self.class.map_user(@@config, @ldap_user_info)
|
|
70
72
|
|
|
71
73
|
@env['omniauth.auth'] = auth_hash
|
|
72
74
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
rescue Exception => e
|
|
76
|
+
return fail!(:invalid_credentials, e)
|
|
77
|
+
end
|
|
78
|
+
call_app!
|
|
77
79
|
end
|
|
78
80
|
|
|
79
81
|
def auth_hash
|
|
@@ -84,28 +86,28 @@ module OmniAuth
|
|
|
84
86
|
})
|
|
85
87
|
end
|
|
86
88
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
89
|
+
def self.map_user(mapper, object)
|
|
90
|
+
user = {}
|
|
91
|
+
mapper.each do |key, value|
|
|
92
|
+
case value
|
|
93
|
+
when String
|
|
94
|
+
user[key] = object[value.downcase.to_sym].to_s if object[value.downcase.to_sym]
|
|
95
|
+
when Array
|
|
96
|
+
value.each {|v| (user[key] = object[v.downcase.to_sym].to_s; break;) if object[v.downcase.to_sym]}
|
|
97
|
+
when Hash
|
|
98
|
+
value.map do |key1, value1|
|
|
99
|
+
pattern = key1.dup
|
|
100
|
+
value1.each_with_index do |v,i|
|
|
101
|
+
part = '';
|
|
102
|
+
v.each {|v1| (part = object[v1.downcase.to_sym].to_s; break;) if object[v1.downcase.to_sym]}
|
|
103
|
+
pattern.gsub!("%#{i}",part||'')
|
|
104
|
+
end
|
|
105
|
+
user[key] = pattern
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
user
|
|
110
|
+
end
|
|
109
111
|
end
|
|
110
112
|
end
|
|
111
113
|
end
|
|
@@ -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 alle 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,50 @@
|
|
|
1
|
+
require 'omniauth/enterprise'
|
|
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
|
+
@@settings = {}
|
|
13
|
+
|
|
14
|
+
def initialize(app, options={})
|
|
15
|
+
super(app, :saml)
|
|
16
|
+
@@settings = {
|
|
17
|
+
:assertion_consumer_service_url => options[:assertion_consumer_service_url],
|
|
18
|
+
:issuer => options[:issuer],
|
|
19
|
+
:idp_sso_target_url => options[:idp_sso_target_url],
|
|
20
|
+
:idp_cert_fingerprint => options[:idp_cert_fingerprint],
|
|
21
|
+
:name_identifier_format => options[:name_identifier_format] || "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
|
22
|
+
}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def request_phase
|
|
26
|
+
request = OmniAuth::Strategies::SAML::AuthRequest.new
|
|
27
|
+
redirect(request.create(@@settings))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def callback_phase
|
|
31
|
+
begin
|
|
32
|
+
response = OmniAuth::Strategies::SAML::AuthResponse.new(request.params['SAMLResponse'])
|
|
33
|
+
response.settings = @@settings
|
|
34
|
+
@name_id = response.name_id
|
|
35
|
+
return fail!(:invalid_ticket, 'Invalid SAML Ticket') if @name_id.nil? || @name_id.empty?
|
|
36
|
+
super
|
|
37
|
+
rescue ArgumentError => e
|
|
38
|
+
fail!(:invalid_ticket, 'Invalid SAML Response')
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def auth_hash
|
|
43
|
+
OmniAuth::Utils.deep_merge(super, {
|
|
44
|
+
'uid' => @name_id
|
|
45
|
+
})
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/omniauth/version.rb
CHANGED
data/oa-enterprise.gemspec
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
#
|
|
1
|
+
# encoding: utf-8
|
|
2
2
|
require File.expand_path('../lib/omniauth/version', __FILE__)
|
|
3
3
|
|
|
4
4
|
Gem::Specification.new do |gem|
|
|
5
|
-
gem.
|
|
6
|
-
gem.
|
|
7
|
-
gem.
|
|
8
|
-
gem.
|
|
9
|
-
gem.
|
|
10
|
-
gem.
|
|
11
|
-
gem.
|
|
12
|
-
gem.
|
|
5
|
+
gem.add_dependency 'addressable', '~> 2.2.6'
|
|
6
|
+
gem.add_dependency 'net-ldap', '~> 0.2.2'
|
|
7
|
+
gem.add_dependency 'nokogiri', '~> 1.5.0'
|
|
8
|
+
gem.add_dependency 'oa-core', OmniAuth::Version::STRING
|
|
9
|
+
gem.add_dependency 'pyu-ruby-sasl', '~> 0.0.3.1'
|
|
10
|
+
gem.add_dependency 'rubyntlm', '~> 0.1.1'
|
|
11
|
+
gem.add_dependency 'uuid'
|
|
12
|
+
gem.add_dependency 'XMLCanonicalizer', '~> 1.0.1'
|
|
13
13
|
gem.add_development_dependency 'rack-test', '~> 0.5'
|
|
14
14
|
gem.add_development_dependency 'rake', '~> 0.8'
|
|
15
|
+
gem.add_development_dependency 'rdiscount', '~> 1.6'
|
|
15
16
|
gem.add_development_dependency 'rspec', '~> 2.5'
|
|
16
|
-
gem.add_development_dependency '
|
|
17
|
+
gem.add_development_dependency 'simplecov', '~> 0.4'
|
|
18
|
+
gem.add_development_dependency 'webmock', '~> 1.7'
|
|
17
19
|
gem.add_development_dependency 'yard', '~> 0.7'
|
|
18
|
-
gem.
|
|
19
|
-
gem.name = 'oa-enterprise'
|
|
20
|
-
gem.version = OmniAuth::Version::STRING
|
|
20
|
+
gem.authors = ['James A. Rosen', 'Ping Yu', 'Michael Bleigh', 'Erik Michaels-Ober', 'Raecoo Cao']
|
|
21
21
|
gem.description = %q{Enterprise strategies for OmniAuth.}
|
|
22
|
-
gem.
|
|
23
|
-
gem.email = ['james.a.rosen@gmail.com', 'ping@intridea.com', 'michael@intridea.com', 'sferik@gmail.com']
|
|
24
|
-
gem.homepage = 'http://github.com/intridea/omniauth'
|
|
25
|
-
gem.authors = ['James A. Rosen', 'Ping Yu', 'Michael Bleigh', 'Erik Michaels-Ober']
|
|
26
|
-
gem.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
|
|
22
|
+
gem.email = ['james.a.rosen@gmail.com', 'ping@intridea.com', 'michael@intridea.com', 'sferik@gmail.com', 'raecoo@intridea.com']
|
|
27
23
|
gem.files = `git ls-files`.split("\n")
|
|
28
|
-
gem.
|
|
24
|
+
gem.homepage = 'http://github.com/intridea/omniauth'
|
|
25
|
+
gem.name = 'oa-enterprise'
|
|
29
26
|
gem.require_paths = ['lib']
|
|
30
27
|
gem.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') if gem.respond_to? :required_rubygems_version=
|
|
28
|
+
gem.summary = gem.description
|
|
29
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
30
|
+
gem.version = OmniAuth::Version::STRING
|
|
31
31
|
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
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: oa-enterprise
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 19
|
|
4
5
|
prerelease:
|
|
5
|
-
|
|
6
|
+
segments:
|
|
7
|
+
- 0
|
|
8
|
+
- 3
|
|
9
|
+
- 0
|
|
10
|
+
version: 0.3.0
|
|
6
11
|
platform: ruby
|
|
7
12
|
authors:
|
|
8
13
|
- James A. Rosen
|
|
9
14
|
- Ping Yu
|
|
10
15
|
- Michael Bleigh
|
|
11
16
|
- Erik Michaels-Ober
|
|
17
|
+
- Raecoo Cao
|
|
12
18
|
autorequire:
|
|
13
19
|
bindir: bin
|
|
14
20
|
cert_chain: []
|
|
15
21
|
|
|
16
|
-
date: 2011-
|
|
22
|
+
date: 2011-09-22 00:00:00 -05:00
|
|
23
|
+
default_executable:
|
|
17
24
|
dependencies:
|
|
18
25
|
- !ruby/object:Gem::Dependency
|
|
19
26
|
name: addressable
|
|
@@ -21,31 +28,46 @@ dependencies:
|
|
|
21
28
|
requirement: &id001 !ruby/object:Gem::Requirement
|
|
22
29
|
none: false
|
|
23
30
|
requirements:
|
|
24
|
-
- -
|
|
31
|
+
- - ~>
|
|
25
32
|
- !ruby/object:Gem::Version
|
|
26
|
-
|
|
33
|
+
hash: 11
|
|
34
|
+
segments:
|
|
35
|
+
- 2
|
|
36
|
+
- 2
|
|
37
|
+
- 6
|
|
38
|
+
version: 2.2.6
|
|
27
39
|
type: :runtime
|
|
28
40
|
version_requirements: *id001
|
|
29
41
|
- !ruby/object:Gem::Dependency
|
|
30
|
-
name:
|
|
42
|
+
name: net-ldap
|
|
31
43
|
prerelease: false
|
|
32
44
|
requirement: &id002 !ruby/object:Gem::Requirement
|
|
33
45
|
none: false
|
|
34
46
|
requirements:
|
|
35
47
|
- - ~>
|
|
36
48
|
- !ruby/object:Gem::Version
|
|
37
|
-
|
|
49
|
+
hash: 19
|
|
50
|
+
segments:
|
|
51
|
+
- 0
|
|
52
|
+
- 2
|
|
53
|
+
- 2
|
|
54
|
+
version: 0.2.2
|
|
38
55
|
type: :runtime
|
|
39
56
|
version_requirements: *id002
|
|
40
57
|
- !ruby/object:Gem::Dependency
|
|
41
|
-
name:
|
|
58
|
+
name: nokogiri
|
|
42
59
|
prerelease: false
|
|
43
60
|
requirement: &id003 !ruby/object:Gem::Requirement
|
|
44
61
|
none: false
|
|
45
62
|
requirements:
|
|
46
63
|
- - ~>
|
|
47
64
|
- !ruby/object:Gem::Version
|
|
48
|
-
|
|
65
|
+
hash: 3
|
|
66
|
+
segments:
|
|
67
|
+
- 1
|
|
68
|
+
- 5
|
|
69
|
+
- 0
|
|
70
|
+
version: 1.5.0
|
|
49
71
|
type: :runtime
|
|
50
72
|
version_requirements: *id003
|
|
51
73
|
- !ruby/object:Gem::Dependency
|
|
@@ -56,7 +78,12 @@ dependencies:
|
|
|
56
78
|
requirements:
|
|
57
79
|
- - "="
|
|
58
80
|
- !ruby/object:Gem::Version
|
|
59
|
-
|
|
81
|
+
hash: 19
|
|
82
|
+
segments:
|
|
83
|
+
- 0
|
|
84
|
+
- 3
|
|
85
|
+
- 0
|
|
86
|
+
version: 0.3.0
|
|
60
87
|
type: :runtime
|
|
61
88
|
version_requirements: *id004
|
|
62
89
|
- !ruby/object:Gem::Dependency
|
|
@@ -67,6 +94,12 @@ dependencies:
|
|
|
67
94
|
requirements:
|
|
68
95
|
- - ~>
|
|
69
96
|
- !ruby/object:Gem::Version
|
|
97
|
+
hash: 65
|
|
98
|
+
segments:
|
|
99
|
+
- 0
|
|
100
|
+
- 0
|
|
101
|
+
- 3
|
|
102
|
+
- 1
|
|
70
103
|
version: 0.0.3.1
|
|
71
104
|
type: :runtime
|
|
72
105
|
version_requirements: *id005
|
|
@@ -78,30 +111,43 @@ dependencies:
|
|
|
78
111
|
requirements:
|
|
79
112
|
- - ~>
|
|
80
113
|
- !ruby/object:Gem::Version
|
|
114
|
+
hash: 25
|
|
115
|
+
segments:
|
|
116
|
+
- 0
|
|
117
|
+
- 1
|
|
118
|
+
- 1
|
|
81
119
|
version: 0.1.1
|
|
82
120
|
type: :runtime
|
|
83
121
|
version_requirements: *id006
|
|
84
122
|
- !ruby/object:Gem::Dependency
|
|
85
|
-
name:
|
|
123
|
+
name: uuid
|
|
86
124
|
prerelease: false
|
|
87
125
|
requirement: &id007 !ruby/object:Gem::Requirement
|
|
88
126
|
none: false
|
|
89
127
|
requirements:
|
|
90
|
-
- -
|
|
128
|
+
- - ">="
|
|
91
129
|
- !ruby/object:Gem::Version
|
|
92
|
-
|
|
93
|
-
|
|
130
|
+
hash: 3
|
|
131
|
+
segments:
|
|
132
|
+
- 0
|
|
133
|
+
version: "0"
|
|
134
|
+
type: :runtime
|
|
94
135
|
version_requirements: *id007
|
|
95
136
|
- !ruby/object:Gem::Dependency
|
|
96
|
-
name:
|
|
137
|
+
name: XMLCanonicalizer
|
|
97
138
|
prerelease: false
|
|
98
139
|
requirement: &id008 !ruby/object:Gem::Requirement
|
|
99
140
|
none: false
|
|
100
141
|
requirements:
|
|
101
142
|
- - ~>
|
|
102
143
|
- !ruby/object:Gem::Version
|
|
103
|
-
|
|
104
|
-
|
|
144
|
+
hash: 21
|
|
145
|
+
segments:
|
|
146
|
+
- 1
|
|
147
|
+
- 0
|
|
148
|
+
- 1
|
|
149
|
+
version: 1.0.1
|
|
150
|
+
type: :runtime
|
|
105
151
|
version_requirements: *id008
|
|
106
152
|
- !ruby/object:Gem::Dependency
|
|
107
153
|
name: rack-test
|
|
@@ -111,6 +157,10 @@ dependencies:
|
|
|
111
157
|
requirements:
|
|
112
158
|
- - ~>
|
|
113
159
|
- !ruby/object:Gem::Version
|
|
160
|
+
hash: 1
|
|
161
|
+
segments:
|
|
162
|
+
- 0
|
|
163
|
+
- 5
|
|
114
164
|
version: "0.5"
|
|
115
165
|
type: :development
|
|
116
166
|
version_requirements: *id009
|
|
@@ -122,59 +172,95 @@ dependencies:
|
|
|
122
172
|
requirements:
|
|
123
173
|
- - ~>
|
|
124
174
|
- !ruby/object:Gem::Version
|
|
175
|
+
hash: 27
|
|
176
|
+
segments:
|
|
177
|
+
- 0
|
|
178
|
+
- 8
|
|
125
179
|
version: "0.8"
|
|
126
180
|
type: :development
|
|
127
181
|
version_requirements: *id010
|
|
128
182
|
- !ruby/object:Gem::Dependency
|
|
129
|
-
name:
|
|
183
|
+
name: rdiscount
|
|
130
184
|
prerelease: false
|
|
131
185
|
requirement: &id011 !ruby/object:Gem::Requirement
|
|
132
186
|
none: false
|
|
133
187
|
requirements:
|
|
134
188
|
- - ~>
|
|
135
189
|
- !ruby/object:Gem::Version
|
|
136
|
-
|
|
190
|
+
hash: 3
|
|
191
|
+
segments:
|
|
192
|
+
- 1
|
|
193
|
+
- 6
|
|
194
|
+
version: "1.6"
|
|
137
195
|
type: :development
|
|
138
196
|
version_requirements: *id011
|
|
139
197
|
- !ruby/object:Gem::Dependency
|
|
140
|
-
name:
|
|
198
|
+
name: rspec
|
|
141
199
|
prerelease: false
|
|
142
200
|
requirement: &id012 !ruby/object:Gem::Requirement
|
|
143
201
|
none: false
|
|
144
202
|
requirements:
|
|
145
203
|
- - ~>
|
|
146
204
|
- !ruby/object:Gem::Version
|
|
147
|
-
|
|
205
|
+
hash: 9
|
|
206
|
+
segments:
|
|
207
|
+
- 2
|
|
208
|
+
- 5
|
|
209
|
+
version: "2.5"
|
|
148
210
|
type: :development
|
|
149
211
|
version_requirements: *id012
|
|
150
212
|
- !ruby/object:Gem::Dependency
|
|
151
|
-
name:
|
|
213
|
+
name: simplecov
|
|
152
214
|
prerelease: false
|
|
153
215
|
requirement: &id013 !ruby/object:Gem::Requirement
|
|
154
216
|
none: false
|
|
155
217
|
requirements:
|
|
156
218
|
- - ~>
|
|
157
219
|
- !ruby/object:Gem::Version
|
|
158
|
-
|
|
220
|
+
hash: 3
|
|
221
|
+
segments:
|
|
222
|
+
- 0
|
|
223
|
+
- 4
|
|
224
|
+
version: "0.4"
|
|
159
225
|
type: :development
|
|
160
226
|
version_requirements: *id013
|
|
161
227
|
- !ruby/object:Gem::Dependency
|
|
162
|
-
name:
|
|
228
|
+
name: webmock
|
|
163
229
|
prerelease: false
|
|
164
230
|
requirement: &id014 !ruby/object:Gem::Requirement
|
|
165
231
|
none: false
|
|
166
232
|
requirements:
|
|
167
233
|
- - ~>
|
|
168
234
|
- !ruby/object:Gem::Version
|
|
169
|
-
|
|
235
|
+
hash: 1
|
|
236
|
+
segments:
|
|
237
|
+
- 1
|
|
238
|
+
- 7
|
|
239
|
+
version: "1.7"
|
|
170
240
|
type: :development
|
|
171
241
|
version_requirements: *id014
|
|
242
|
+
- !ruby/object:Gem::Dependency
|
|
243
|
+
name: yard
|
|
244
|
+
prerelease: false
|
|
245
|
+
requirement: &id015 !ruby/object:Gem::Requirement
|
|
246
|
+
none: false
|
|
247
|
+
requirements:
|
|
248
|
+
- - ~>
|
|
249
|
+
- !ruby/object:Gem::Version
|
|
250
|
+
hash: 5
|
|
251
|
+
segments:
|
|
252
|
+
- 0
|
|
253
|
+
- 7
|
|
254
|
+
version: "0.7"
|
|
255
|
+
type: :development
|
|
256
|
+
version_requirements: *id015
|
|
172
257
|
description: Enterprise strategies for OmniAuth.
|
|
173
258
|
email:
|
|
174
259
|
- james.a.rosen@gmail.com
|
|
175
260
|
- ping@intridea.com
|
|
176
261
|
- michael@intridea.com
|
|
177
262
|
- sferik@gmail.com
|
|
263
|
+
- raecoo@intridea.com
|
|
178
264
|
executables: []
|
|
179
265
|
|
|
180
266
|
extensions: []
|
|
@@ -196,13 +282,20 @@ files:
|
|
|
196
282
|
- lib/omniauth/strategies/cas/service_ticket_validator.rb
|
|
197
283
|
- lib/omniauth/strategies/ldap.rb
|
|
198
284
|
- lib/omniauth/strategies/ldap/adaptor.rb
|
|
285
|
+
- lib/omniauth/strategies/saml.rb
|
|
286
|
+
- lib/omniauth/strategies/saml/auth_request.rb
|
|
287
|
+
- lib/omniauth/strategies/saml/auth_response.rb
|
|
288
|
+
- lib/omniauth/strategies/saml/validation_error.rb
|
|
289
|
+
- lib/omniauth/strategies/saml/xml_security.rb
|
|
199
290
|
- lib/omniauth/version.rb
|
|
200
291
|
- oa-enterprise.gemspec
|
|
201
292
|
- spec/fixtures/cas_failure.xml
|
|
202
293
|
- spec/fixtures/cas_success.xml
|
|
203
294
|
- spec/omniauth/strategies/cas_spec.rb
|
|
204
295
|
- spec/omniauth/strategies/ldap_spec.rb
|
|
296
|
+
- spec/omniauth/strategies/saml_spec.rb
|
|
205
297
|
- spec/spec_helper.rb
|
|
298
|
+
has_rdoc: true
|
|
206
299
|
homepage: http://github.com/intridea/omniauth
|
|
207
300
|
licenses: []
|
|
208
301
|
|
|
@@ -216,17 +309,25 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
216
309
|
requirements:
|
|
217
310
|
- - ">="
|
|
218
311
|
- !ruby/object:Gem::Version
|
|
312
|
+
hash: 3
|
|
313
|
+
segments:
|
|
314
|
+
- 0
|
|
219
315
|
version: "0"
|
|
220
316
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
221
317
|
none: false
|
|
222
318
|
requirements:
|
|
223
319
|
- - ">="
|
|
224
320
|
- !ruby/object:Gem::Version
|
|
321
|
+
hash: 23
|
|
322
|
+
segments:
|
|
323
|
+
- 1
|
|
324
|
+
- 3
|
|
325
|
+
- 6
|
|
225
326
|
version: 1.3.6
|
|
226
327
|
requirements: []
|
|
227
328
|
|
|
228
329
|
rubyforge_project:
|
|
229
|
-
rubygems_version: 1.
|
|
330
|
+
rubygems_version: 1.6.2
|
|
230
331
|
signing_key:
|
|
231
332
|
specification_version: 3
|
|
232
333
|
summary: Enterprise strategies for OmniAuth.
|
|
@@ -235,4 +336,5 @@ test_files:
|
|
|
235
336
|
- spec/fixtures/cas_success.xml
|
|
236
337
|
- spec/omniauth/strategies/cas_spec.rb
|
|
237
338
|
- spec/omniauth/strategies/ldap_spec.rb
|
|
339
|
+
- spec/omniauth/strategies/saml_spec.rb
|
|
238
340
|
- spec/spec_helper.rb
|