omniauth-saml 0.9.1 → 0.9.2
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 +24 -8
- data/lib/omniauth-saml/version.rb +1 -1
- data/lib/omniauth/strategies/saml.rb +3 -1
- data/lib/omniauth/strategies/saml/auth_request.rb +1 -1
- data/lib/omniauth/strategies/saml/auth_response.rb +28 -21
- data/lib/omniauth/strategies/saml/xml_security.rb +1 -1
- data/spec/omniauth/strategies/saml/auth_request_spec.rb +75 -0
- data/spec/omniauth/strategies/saml/auth_response_spec.rb +90 -0
- data/spec/omniauth/strategies/saml/validation_error_spec.rb +5 -0
- data/spec/omniauth/strategies/saml_spec.rb +108 -12
- data/spec/shared/validating_method.rb +129 -0
- data/spec/spec_helper.rb +18 -0
- metadata +109 -79
data/README.md
CHANGED
@@ -4,6 +4,13 @@ A generic SAML strategy for OmniAuth.
|
|
4
4
|
|
5
5
|
https://github.com/PracticallyGreen/omniauth-saml
|
6
6
|
|
7
|
+
## Requirements
|
8
|
+
|
9
|
+
* [OmniAuth](http://www.omniauth.org/) 1.0+
|
10
|
+
* Ruby 1.9.2
|
11
|
+
|
12
|
+
## Usage
|
13
|
+
|
7
14
|
Use the SAML strategy as a middleware in your application:
|
8
15
|
|
9
16
|
```ruby
|
@@ -12,6 +19,7 @@ use OmniAuth::Strategies::SAML,
|
|
12
19
|
:assertion_consumer_service_url => "consumer_service_url",
|
13
20
|
:issuer => "issuer",
|
14
21
|
:idp_sso_target_url => "idp_sso_target_url",
|
22
|
+
:idp_cert => "-----BEGIN CERTIFICATE-----\n...-----END CERTIFICATE-----",
|
15
23
|
:idp_cert_fingerprint => "E7:91:B2:E1:...",
|
16
24
|
:name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
17
25
|
```
|
@@ -32,6 +40,7 @@ Rails.application.config.middleware.use OmniAuth::Builder do
|
|
32
40
|
:assertion_consumer_service_url => "consumer_service_url",
|
33
41
|
:issuer => "rails-application",
|
34
42
|
:idp_sso_target_url => "idp_sso_target_url",
|
43
|
+
:idp_cert => "-----BEGIN CERTIFICATE-----\n...-----END CERTIFICATE-----",
|
35
44
|
:idp_cert_fingerprint => "E7:91:B2:E1:...",
|
36
45
|
:name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
37
46
|
end
|
@@ -49,9 +58,13 @@ end
|
|
49
58
|
* `:idp_sso_target_url` - The URL to which the authentication request should be sent.
|
50
59
|
This would be on the identity provider. **Required**.
|
51
60
|
|
52
|
-
* `:
|
53
|
-
|
54
|
-
|
61
|
+
* `:idp_cert` - The identity provider's certificate in PEM format. Takes precedence
|
62
|
+
over the fingerprint option below. This option or `:idp_cert_fingerprint` must
|
63
|
+
be present.
|
64
|
+
|
65
|
+
* `:idp_cert_fingerprint` - The SHA1 fingerprint of the certificate, e.g.
|
66
|
+
"90:CC:16:F0:8D:...". This is provided from the identity provider when setting up
|
67
|
+
the relationship. This option or `:idp_cert` must be present.
|
55
68
|
|
56
69
|
* `:name_identifier_format` - Describes the format of the username required by this
|
57
70
|
application. If you need the email address, use "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress".
|
@@ -59,15 +72,18 @@ end
|
|
59
72
|
other options. Note that the identity provider might not support all options.
|
60
73
|
Optional.
|
61
74
|
|
75
|
+
## Authors
|
76
|
+
|
77
|
+
Authored by Raecoo Cao, Todd W Saxton, Ryan Wilcox, Rajiv Aaron Manglani, and Steven Anderson.
|
78
|
+
|
79
|
+
Maintained by [Rajiv Aaron Manglani](http://www.rajivmanglani.com/).
|
80
|
+
|
62
81
|
## License
|
63
82
|
|
64
|
-
Copyright (c) 2011-2012 [Practically Green, Inc.](http://
|
83
|
+
Copyright (c) 2011-2012 [Practically Green, Inc.](http://www.practicallygreen.com/).
|
65
84
|
All rights reserved. Released under the MIT license.
|
66
85
|
|
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.
|
86
|
+
Portions Copyright (c) 2007 Sun Microsystems Inc.
|
71
87
|
|
72
88
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
73
89
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -20,9 +20,11 @@ module OmniAuth
|
|
20
20
|
begin
|
21
21
|
response = OmniAuth::Strategies::SAML::AuthResponse.new(request.params['SAMLResponse'])
|
22
22
|
response.settings = options
|
23
|
+
|
23
24
|
@name_id = response.name_id
|
24
25
|
@attributes = response.attributes
|
25
|
-
|
26
|
+
|
27
|
+
return fail!(:invalid_ticket, 'Invalid SAML Ticket') if @name_id.nil? || @name_id.empty? || !response.valid?
|
26
28
|
super
|
27
29
|
rescue ArgumentError => e
|
28
30
|
fail!(:invalid_ticket, 'Invalid SAML Response')
|
@@ -18,7 +18,7 @@ module OmniAuth
|
|
18
18
|
self.document = OmniAuth::Strategies::SAML::XMLSecurity::SignedDocument.new(Base64.decode64(response))
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
21
|
+
def valid?
|
22
22
|
validate(soft = true)
|
23
23
|
end
|
24
24
|
|
@@ -29,39 +29,33 @@ module OmniAuth
|
|
29
29
|
# The value of the user identifier as designated by the initialization request response
|
30
30
|
def name_id
|
31
31
|
@name_id ||= begin
|
32
|
-
node =
|
33
|
-
node ||=
|
34
|
-
node.nil? ? nil : node.text
|
32
|
+
node = xpath("/p:Response/a:Assertion[@ID='#{signed_element_id}']/a:Subject/a:NameID")
|
33
|
+
node ||= xpath("/p:Response[@ID='#{signed_element_id}']/a:Assertion/a:Subject/a:NameID")
|
34
|
+
node.nil? ? nil : strip(node.text)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
38
|
# A hash of all the attributes with the response. Assuming there is only one value for each key
|
39
39
|
def attributes
|
40
40
|
@attr_statements ||= begin
|
41
|
-
|
42
|
-
|
43
|
-
stmt_element = REXML::XPath.first(document, "/p:Response/a:Assertion/a:AttributeStatement", { "p" => PROTOCOL, "a" => ASSERTION })
|
41
|
+
stmt_element = xpath("/p:Response/a:Assertion/a:AttributeStatement")
|
44
42
|
return {} if stmt_element.nil?
|
45
43
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
result[name] = value
|
51
|
-
end
|
44
|
+
{}.tap do |result|
|
45
|
+
stmt_element.elements.each do |attr_element|
|
46
|
+
name = attr_element.attributes["Name"]
|
47
|
+
value = strip(attr_element.elements.first.text)
|
52
48
|
|
53
|
-
|
54
|
-
|
49
|
+
result[name] = result[name.to_sym] = value
|
50
|
+
end
|
55
51
|
end
|
56
|
-
|
57
|
-
result
|
58
52
|
end
|
59
53
|
end
|
60
54
|
|
61
55
|
# When this user session should expire at latest
|
62
56
|
def session_expires_at
|
63
57
|
@expires_at ||= begin
|
64
|
-
node =
|
58
|
+
node = xpath("/p:Response/a:Assertion/a:AuthnStatement")
|
65
59
|
parse_time(node, "SessionNotOnOrAfter")
|
66
60
|
end
|
67
61
|
end
|
@@ -69,7 +63,7 @@ module OmniAuth
|
|
69
63
|
# Conditions (if any) for the assertion to run
|
70
64
|
def conditions
|
71
65
|
@conditions ||= begin
|
72
|
-
|
66
|
+
xpath("/p:Response/a:Assertion[@ID='#{signed_element_id}']/a:Conditions")
|
73
67
|
end
|
74
68
|
end
|
75
69
|
|
@@ -103,7 +97,7 @@ module OmniAuth
|
|
103
97
|
|
104
98
|
def get_fingerprint
|
105
99
|
if settings.idp_cert
|
106
|
-
cert = OpenSSL::X509::Certificate.new(settings.idp_cert)
|
100
|
+
cert = OpenSSL::X509::Certificate.new(settings.idp_cert.gsub(/^ +/, ''))
|
107
101
|
Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":")
|
108
102
|
else
|
109
103
|
settings.idp_cert_fingerprint
|
@@ -135,7 +129,20 @@ module OmniAuth
|
|
135
129
|
end
|
136
130
|
end
|
137
131
|
|
132
|
+
def strip(string)
|
133
|
+
return string unless string
|
134
|
+
string.gsub(/^\s+/, '').gsub(/\s+$/, '')
|
135
|
+
end
|
136
|
+
|
137
|
+
def xpath(path)
|
138
|
+
REXML::XPath.first(document, path, { "p" => PROTOCOL, "a" => ASSERTION })
|
139
|
+
end
|
140
|
+
|
141
|
+
def signed_element_id
|
142
|
+
doc_id = document.signed_element_id
|
143
|
+
doc_id[1, doc_id.size]
|
144
|
+
end
|
138
145
|
end
|
139
146
|
end
|
140
147
|
end
|
141
|
-
end
|
148
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OmniAuth::Strategies::SAML::AuthRequest do
|
4
|
+
describe :create do
|
5
|
+
let(:url) do
|
6
|
+
described_class.new.create(
|
7
|
+
{
|
8
|
+
:idp_sso_target_url => 'example.com',
|
9
|
+
:assertion_consumer_service_url => 'http://example.com/auth/saml/callback',
|
10
|
+
:issuer => 'This is an issuer',
|
11
|
+
:name_identifier_format => 'Some Policy'
|
12
|
+
},
|
13
|
+
{
|
14
|
+
:some_param => 'foo',
|
15
|
+
:some_other => 'bar'
|
16
|
+
}
|
17
|
+
)
|
18
|
+
end
|
19
|
+
let(:saml_request) { url.match(/SAMLRequest=(.*)/)[1] }
|
20
|
+
|
21
|
+
describe "the url" do
|
22
|
+
subject { url }
|
23
|
+
|
24
|
+
it "should contain a SAMLRequest query string param" do
|
25
|
+
subject.should match /^example\.com\?SAMLRequest=/
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should contain any other parameters passed through" do
|
29
|
+
subject.should match /^example\.com\?SAMLRequest=(.*)&some_param=foo&some_other=bar/
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "the saml request" do
|
34
|
+
subject { saml_request }
|
35
|
+
|
36
|
+
let(:decoded) do
|
37
|
+
cgi_unescaped = CGI.unescape(subject)
|
38
|
+
base64_decoded = Base64.decode64(cgi_unescaped)
|
39
|
+
Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(base64_decoded)
|
40
|
+
end
|
41
|
+
|
42
|
+
let(:xml) { REXML::Document.new(decoded) }
|
43
|
+
let(:root_element) { REXML::XPath.first(xml, '//samlp:AuthnRequest') }
|
44
|
+
|
45
|
+
it "should contain base64 encoded and zlib deflated xml" do
|
46
|
+
decoded.should match /^<samlp:AuthnRequest/
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should contain a uuid with an underscore in front" do
|
50
|
+
UUID.any_instance.stub(:generate).and_return('MY_UUID')
|
51
|
+
|
52
|
+
root_element.attributes['ID'].should == '_MY_UUID'
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should contain the current time as the IssueInstant" do
|
56
|
+
t = Time.now
|
57
|
+
Time.stub(:now).and_return(t)
|
58
|
+
|
59
|
+
root_element.attributes['IssueInstant'].should == t.utc.iso8601
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should contain the callback url in the settings" do
|
63
|
+
root_element.attributes['AssertionConsumerServiceURL'].should == 'http://example.com/auth/saml/callback'
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should contain the issuer" do
|
67
|
+
REXML::XPath.first(xml, '//saml:Issuer').text.should == 'This is an issuer'
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should contain the name identifier format" do
|
71
|
+
REXML::XPath.first(xml, '//samlp:NameIDPolicy').attributes['Format'].should == 'Some Policy'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OmniAuth::Strategies::SAML::AuthResponse do
|
4
|
+
let(:xml) { :example_response }
|
5
|
+
subject { described_class.new(load_xml(xml)) }
|
6
|
+
|
7
|
+
describe :initialize do
|
8
|
+
context "when the response is nil" do
|
9
|
+
it "should raise an exception" do
|
10
|
+
expect { described_class.new(nil) }.to raise_error ArgumentError
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe :name_id do
|
16
|
+
it "should load the name id from the assertion" do
|
17
|
+
subject.name_id.should == 'THISISANAMEID'
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when the response contains the signed_element_id" do
|
21
|
+
let(:xml) { :response_contains_signed_element }
|
22
|
+
|
23
|
+
it "should load the name id from the assertion" do
|
24
|
+
subject.name_id.should == 'THISISANAMEID'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe :attributes do
|
30
|
+
it "should return all of the attributes as a hash" do
|
31
|
+
subject.attributes.should == {
|
32
|
+
:forename => 'Steven',
|
33
|
+
:surname => 'Anderson',
|
34
|
+
:address_1 => '24 Made Up Drive',
|
35
|
+
:address_2 => nil,
|
36
|
+
:companyName => 'Test Company Ltd',
|
37
|
+
:postcode => 'XX2 4XX',
|
38
|
+
:city => 'Newcastle',
|
39
|
+
:country => 'United Kingdom',
|
40
|
+
:userEmailID => 'steve@example.com',
|
41
|
+
:county => 'TYNESIDE',
|
42
|
+
:versionID => '1',
|
43
|
+
:bundleID => '1',
|
44
|
+
|
45
|
+
'forename' => 'Steven',
|
46
|
+
'surname' => 'Anderson',
|
47
|
+
'address_1' => '24 Made Up Drive',
|
48
|
+
'address_2' => nil,
|
49
|
+
'companyName' => 'Test Company Ltd',
|
50
|
+
'postcode' => 'XX2 4XX',
|
51
|
+
'city' => 'Newcastle',
|
52
|
+
'country' => 'United Kingdom',
|
53
|
+
'userEmailID' => 'steve@example.com',
|
54
|
+
'county' => 'TYNESIDE',
|
55
|
+
'versionID' => '1',
|
56
|
+
'bundleID' => '1'
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
context "when no attributes exist in the XML" do
|
61
|
+
let(:xml) { :no_attributes }
|
62
|
+
|
63
|
+
it "should return an empty hash" do
|
64
|
+
subject.attributes.should == {}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe :session_expires_at do
|
70
|
+
it "should return the SessionNotOnOrAfter as a Ruby date" do
|
71
|
+
subject.session_expires_at.to_i.should == Time.new(2012, 04, 8, 12, 0, 24, 0).to_i
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe :conditions do
|
76
|
+
it "should return the conditions element from the XML" do
|
77
|
+
subject.conditions.attributes['NotOnOrAfter'].should == '2012-03-08T16:30:01.336Z'
|
78
|
+
subject.conditions.attributes['NotBefore'].should == '2012-03-08T16:20:01.336Z'
|
79
|
+
REXML::XPath.first(subject.conditions, '//saml:Audience').text.should include 'AUDIENCE'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe :valid? do
|
84
|
+
it_should_behave_like 'a validating method', true
|
85
|
+
end
|
86
|
+
|
87
|
+
describe :validate! do
|
88
|
+
it_should_behave_like 'a validating method', false
|
89
|
+
end
|
90
|
+
end
|
@@ -1,18 +1,29 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
|
-
|
3
|
+
RSpec::Matchers.define :fail_with do |message|
|
4
|
+
match do |actual|
|
5
|
+
actual.redirect? && actual.location == "/auth/failure?message=#{message}"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def post_xml(xml=:example_response)
|
10
|
+
post "/auth/saml/callback", {'SAMLResponse' => load_xml(xml)}
|
11
|
+
end
|
4
12
|
|
13
|
+
describe OmniAuth::Strategies::SAML, :type => :strategy do
|
5
14
|
include OmniAuth::Test::StrategyTestCase
|
6
15
|
|
7
|
-
|
8
|
-
|
9
|
-
|
16
|
+
let(:auth_hash){ last_request.env['omniauth.auth'] }
|
17
|
+
let(:saml_options) do
|
18
|
+
{
|
19
|
+
:assertion_consumer_service_url => "http://localhost:3000/auth/saml/callback",
|
10
20
|
:issuer => "https://saml.issuer.url/issuers/29490",
|
11
21
|
:idp_sso_target_url => "https://idp.sso.target_url/signon/29490",
|
12
|
-
:idp_cert_fingerprint => "
|
22
|
+
:idp_cert_fingerprint => "E6:87:89:FB:F2:5F:CD:B0:31:32:7E:05:44:84:53:B1:EC:4E:3F:FA",
|
13
23
|
:name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
14
|
-
}
|
24
|
+
}
|
15
25
|
end
|
26
|
+
let(:strategy) { [OmniAuth::Strategies::SAML, saml_options] }
|
16
27
|
|
17
28
|
describe 'GET /auth/saml' do
|
18
29
|
before do
|
@@ -25,13 +36,98 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
25
36
|
end
|
26
37
|
|
27
38
|
describe 'POST /auth/saml/callback' do
|
39
|
+
subject { last_response }
|
28
40
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
41
|
+
let(:xml) { :example_response }
|
42
|
+
|
43
|
+
before :each do
|
44
|
+
Time.stub(:now).and_return(Time.new(2012, 3, 8, 16, 25, 00, 0))
|
33
45
|
end
|
34
46
|
|
35
|
-
|
47
|
+
context "when the response is valid" do
|
48
|
+
before :each do
|
49
|
+
post_xml
|
50
|
+
end
|
36
51
|
|
52
|
+
it "should set the uid to the nameID in the SAML response" do
|
53
|
+
auth_hash['uid'].should == 'THISISANAMEID'
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should set the raw info to all attributes" do
|
57
|
+
auth_hash['extra']['raw_info'].to_hash.should == {
|
58
|
+
'forename' => 'Steven',
|
59
|
+
'surname' => 'Anderson',
|
60
|
+
'address_1' => '24 Made Up Drive',
|
61
|
+
'address_2' => nil,
|
62
|
+
'companyName' => 'Test Company Ltd',
|
63
|
+
'postcode' => 'XX2 4XX',
|
64
|
+
'city' => 'Newcastle',
|
65
|
+
'country' => 'United Kingdom',
|
66
|
+
'userEmailID' => 'steve@example.com',
|
67
|
+
'county' => 'TYNESIDE',
|
68
|
+
'versionID' => '1',
|
69
|
+
'bundleID' => '1'
|
70
|
+
}
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "when there is no SAMLResponse parameter" do
|
75
|
+
before :each do
|
76
|
+
post '/auth/saml/callback'
|
77
|
+
end
|
78
|
+
|
79
|
+
it { should fail_with(:invalid_ticket) }
|
80
|
+
end
|
81
|
+
|
82
|
+
context "when there is no name id in the XML" do
|
83
|
+
before :each do
|
84
|
+
post_xml :no_name_id
|
85
|
+
end
|
86
|
+
|
87
|
+
it { should fail_with(:invalid_ticket) }
|
88
|
+
end
|
89
|
+
|
90
|
+
context "when the fingerprint is invalid" do
|
91
|
+
before :each do
|
92
|
+
saml_options[:idp_cert_fingerprint] = "E6:87:89:FB:F2:5F:CD:B0:31:32:7E:05:44:84:53:B1:EC:4E:3F:FB"
|
93
|
+
post_xml
|
94
|
+
end
|
95
|
+
|
96
|
+
it { should fail_with(:invalid_ticket) }
|
97
|
+
end
|
98
|
+
|
99
|
+
context "when the digest is invalid" do
|
100
|
+
before :each do
|
101
|
+
post_xml :digest_mismatch
|
102
|
+
end
|
103
|
+
|
104
|
+
it { should fail_with(:invalid_ticket) }
|
105
|
+
end
|
106
|
+
|
107
|
+
context "when the signature is invalid" do
|
108
|
+
before :each do
|
109
|
+
post_xml :invalid_signature
|
110
|
+
end
|
111
|
+
|
112
|
+
it { should fail_with(:invalid_ticket) }
|
113
|
+
end
|
114
|
+
|
115
|
+
context "when the time is before the NotBefore date" do
|
116
|
+
before :each do
|
117
|
+
Time.stub(:now).and_return(Time.new(2000, 3, 8, 16, 25, 00, 0))
|
118
|
+
post_xml
|
119
|
+
end
|
120
|
+
|
121
|
+
it { should fail_with(:invalid_ticket) }
|
122
|
+
end
|
123
|
+
|
124
|
+
context "when the time is after the NotOnOrAfter date" do
|
125
|
+
before :each do
|
126
|
+
Time.stub(:now).and_return(Time.new(3000, 3, 8, 16, 25, 00, 0))
|
127
|
+
post_xml
|
128
|
+
end
|
129
|
+
|
130
|
+
it { should fail_with(:invalid_ticket) }
|
131
|
+
end
|
132
|
+
end
|
37
133
|
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
def assert_is_valid(soft)
|
2
|
+
if soft
|
3
|
+
it { should be_valid }
|
4
|
+
else
|
5
|
+
it "should be valid" do
|
6
|
+
expect { subject.validate! }.not_to raise_error
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def assert_is_not_valid(soft)
|
12
|
+
if soft
|
13
|
+
it { should_not be_valid }
|
14
|
+
else
|
15
|
+
it "should be invalid" do
|
16
|
+
expect { subject.validate! }.to raise_error
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def stub_validate_to_fail(soft)
|
22
|
+
if soft
|
23
|
+
subject.document.stub(:validate).and_return(false)
|
24
|
+
else
|
25
|
+
subject.document.stub(:validate).and_raise(Exception)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
shared_examples_for 'a validating method' do |soft|
|
30
|
+
before :each do
|
31
|
+
subject.settings = mock(Object, :idp_cert_fingerprint => 'FINGERPRINT', :idp_cert => nil)
|
32
|
+
subject.document.stub(:validate).and_return(true)
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when the response is empty" do
|
36
|
+
subject { described_class.new('') }
|
37
|
+
|
38
|
+
assert_is_not_valid(soft)
|
39
|
+
end
|
40
|
+
|
41
|
+
context "when the settings are nil" do
|
42
|
+
before :each do
|
43
|
+
subject.settings = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
assert_is_not_valid(soft)
|
47
|
+
end
|
48
|
+
|
49
|
+
context "when there is no idp_cert_fingerprint and idp_cert" do
|
50
|
+
before :each do
|
51
|
+
subject.settings = mock(Object, :idp_cert_fingerprint => nil, :idp_cert => nil)
|
52
|
+
end
|
53
|
+
|
54
|
+
assert_is_not_valid(soft)
|
55
|
+
end
|
56
|
+
|
57
|
+
context "when conditions are not given" do
|
58
|
+
let(:xml) { :no_conditions }
|
59
|
+
|
60
|
+
assert_is_valid(soft)
|
61
|
+
end
|
62
|
+
|
63
|
+
context "when the current time is before the NotBefore time" do
|
64
|
+
before :each do
|
65
|
+
Time.stub(:now).and_return(Time.new(2000, 01, 01, 10, 00, 00, 0))
|
66
|
+
end
|
67
|
+
|
68
|
+
assert_is_not_valid(soft)
|
69
|
+
end
|
70
|
+
|
71
|
+
context "when the current time is after the NotOnOrAfter time" do
|
72
|
+
before :each do
|
73
|
+
# We're assuming here that this code will be out of use in 1000 years...
|
74
|
+
Time.stub(:now).and_return(Time.new(3012, 01, 01, 10, 00, 00, 0))
|
75
|
+
end
|
76
|
+
|
77
|
+
assert_is_not_valid(soft)
|
78
|
+
end
|
79
|
+
|
80
|
+
context "when the current time is between the NotBefore and NotOnOrAfter times" do
|
81
|
+
before :each do
|
82
|
+
Time.stub(:now).and_return(Time.new(2012, 3, 8, 16, 25, 00, 0))
|
83
|
+
end
|
84
|
+
|
85
|
+
assert_is_valid(soft)
|
86
|
+
end
|
87
|
+
|
88
|
+
context "when skip_conditions option is given" do
|
89
|
+
before :each do
|
90
|
+
subject.options[:skip_conditions] = true
|
91
|
+
end
|
92
|
+
|
93
|
+
assert_is_valid(soft)
|
94
|
+
end
|
95
|
+
|
96
|
+
context "when the SAML document is valid" do
|
97
|
+
before :each do
|
98
|
+
subject.document.should_receive(:validate).with('FINGERPRINT', soft).and_return(true)
|
99
|
+
subject.options[:skip_conditions] = true
|
100
|
+
end
|
101
|
+
|
102
|
+
assert_is_valid(soft)
|
103
|
+
end
|
104
|
+
|
105
|
+
context "when the SAML document is valid and the idp_cert is given" do
|
106
|
+
let(:cert) do
|
107
|
+
filename = File.expand_path(File.join('..', '..', 'support', "example_cert.pem"), __FILE__)
|
108
|
+
IO.read(filename)
|
109
|
+
end
|
110
|
+
let(:expected) { 'E6:87:89:FB:F2:5F:CD:B0:31:32:7E:05:44:84:53:B1:EC:4E:3F:FA' }
|
111
|
+
|
112
|
+
before :each do
|
113
|
+
subject.settings.stub(:idp_cert).and_return(cert)
|
114
|
+
subject.document.should_receive(:validate).with(expected, soft).and_return(true)
|
115
|
+
subject.options[:skip_conditions] = true
|
116
|
+
end
|
117
|
+
|
118
|
+
assert_is_valid(soft)
|
119
|
+
end
|
120
|
+
|
121
|
+
context "when the SAML document is invalid" do
|
122
|
+
before :each do
|
123
|
+
stub_validate_to_fail(soft)
|
124
|
+
subject.options[:skip_conditions] = true
|
125
|
+
end
|
126
|
+
|
127
|
+
assert_is_not_valid(soft)
|
128
|
+
end
|
129
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start
|
3
|
+
|
4
|
+
require 'omniauth-saml'
|
5
|
+
require 'rack/test'
|
6
|
+
require 'rexml/document'
|
7
|
+
require 'rexml/xpath'
|
8
|
+
require 'base64'
|
9
|
+
require File.expand_path('../shared/validating_method.rb', __FILE__)
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
config.include Rack::Test::Methods
|
13
|
+
end
|
14
|
+
|
15
|
+
def load_xml(filename=:example_response)
|
16
|
+
filename = File.expand_path(File.join('..', 'support', "#{filename.to_s}.xml"), __FILE__)
|
17
|
+
Base64.encode64(IO.read(filename))
|
18
|
+
end
|
metadata
CHANGED
@@ -1,83 +1,113 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: omniauth-saml
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.2
|
5
5
|
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 9
|
9
|
-
- 1
|
10
|
-
version: 0.9.1
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Raecoo Cao
|
14
9
|
- Ryan Wilcox
|
15
10
|
- Rajiv Aaron Manglani
|
11
|
+
- Steven Anderson
|
16
12
|
autorequire:
|
17
13
|
bindir: bin
|
18
14
|
cert_chain: []
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
dependencies:
|
23
|
-
- !ruby/object:Gem::Dependency
|
15
|
+
date: 2012-03-30 00:00:00.000000000Z
|
16
|
+
dependencies:
|
17
|
+
- !ruby/object:Gem::Dependency
|
24
18
|
name: omniauth
|
25
|
-
|
26
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
requirement: &70159259960020 !ruby/object:Gem::Requirement
|
27
20
|
none: false
|
28
|
-
requirements:
|
21
|
+
requirements:
|
29
22
|
- - ~>
|
30
|
-
- !ruby/object:Gem::Version
|
31
|
-
|
32
|
-
segments:
|
33
|
-
- 1
|
34
|
-
- 0
|
35
|
-
version: "1.0"
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: '1.0'
|
36
25
|
type: :runtime
|
37
|
-
version_requirements: *id001
|
38
|
-
- !ruby/object:Gem::Dependency
|
39
|
-
name: XMLCanonicalizer
|
40
26
|
prerelease: false
|
41
|
-
|
27
|
+
version_requirements: *70159259960020
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: xmlcanonicalizer
|
30
|
+
requirement: &70159259959000 !ruby/object:Gem::Requirement
|
42
31
|
none: false
|
43
|
-
requirements:
|
44
|
-
- -
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
|
47
|
-
segments:
|
48
|
-
- 1
|
49
|
-
- 0
|
50
|
-
- 1
|
51
|
-
version: 1.0.1
|
32
|
+
requirements:
|
33
|
+
- - =
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 0.1.1
|
52
36
|
type: :runtime
|
53
|
-
version_requirements: *id002
|
54
|
-
- !ruby/object:Gem::Dependency
|
55
|
-
name: uuid
|
56
37
|
prerelease: false
|
57
|
-
|
38
|
+
version_requirements: *70159259959000
|
39
|
+
- !ruby/object:Gem::Dependency
|
40
|
+
name: uuid
|
41
|
+
requirement: &70159259958260 !ruby/object:Gem::Requirement
|
58
42
|
none: false
|
59
|
-
requirements:
|
43
|
+
requirements:
|
60
44
|
- - ~>
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
|
63
|
-
segments:
|
64
|
-
- 2
|
65
|
-
- 3
|
66
|
-
version: "2.3"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2.3'
|
67
47
|
type: :runtime
|
68
|
-
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: *70159259958260
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: guard
|
52
|
+
requirement: &70159259957340 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - =
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 1.0.1
|
58
|
+
type: :development
|
59
|
+
prerelease: false
|
60
|
+
version_requirements: *70159259957340
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: guard-rspec
|
63
|
+
requirement: &70159259956340 !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - =
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.6.0
|
69
|
+
type: :development
|
70
|
+
prerelease: false
|
71
|
+
version_requirements: *70159259956340
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: rspec
|
74
|
+
requirement: &70159259955720 !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - =
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '2.8'
|
80
|
+
type: :development
|
81
|
+
prerelease: false
|
82
|
+
version_requirements: *70159259955720
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: simplecov
|
85
|
+
requirement: &70159259954920 !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - =
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 0.6.1
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: *70159259954920
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rack-test
|
96
|
+
requirement: &70159259954240 !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - =
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 0.6.1
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: *70159259954240
|
69
105
|
description: A generic SAML strategy for OmniAuth.
|
70
|
-
email:
|
71
|
-
- raecoo@gmail.com
|
72
|
-
- rwilcox@wilcoxd.com
|
73
|
-
- rajiv@alum.mit.edu
|
106
|
+
email: rajiv@alum.mit.edu
|
74
107
|
executables: []
|
75
|
-
|
76
108
|
extensions: []
|
77
|
-
|
78
109
|
extra_rdoc_files: []
|
79
|
-
|
80
|
-
files:
|
110
|
+
files:
|
81
111
|
- README.md
|
82
112
|
- lib/omniauth/strategies/saml/auth_request.rb
|
83
113
|
- lib/omniauth/strategies/saml/auth_response.rb
|
@@ -86,40 +116,40 @@ files:
|
|
86
116
|
- lib/omniauth/strategies/saml.rb
|
87
117
|
- lib/omniauth-saml/version.rb
|
88
118
|
- lib/omniauth-saml.rb
|
119
|
+
- spec/omniauth/strategies/saml/auth_request_spec.rb
|
120
|
+
- spec/omniauth/strategies/saml/auth_response_spec.rb
|
121
|
+
- spec/omniauth/strategies/saml/validation_error_spec.rb
|
89
122
|
- spec/omniauth/strategies/saml_spec.rb
|
90
|
-
|
123
|
+
- spec/shared/validating_method.rb
|
124
|
+
- spec/spec_helper.rb
|
91
125
|
homepage: https://github.com/PracticallyGreen/omniauth-saml
|
92
126
|
licenses: []
|
93
|
-
|
94
127
|
post_install_message:
|
95
128
|
rdoc_options: []
|
96
|
-
|
97
|
-
require_paths:
|
129
|
+
require_paths:
|
98
130
|
- lib
|
99
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
132
|
none: false
|
101
|
-
requirements:
|
102
|
-
- -
|
103
|
-
- !ruby/object:Gem::Version
|
104
|
-
|
105
|
-
|
106
|
-
- 0
|
107
|
-
version: "0"
|
108
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ! '>='
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
138
|
none: false
|
110
|
-
requirements:
|
111
|
-
- -
|
112
|
-
- !ruby/object:Gem::Version
|
113
|
-
|
114
|
-
segments:
|
115
|
-
- 0
|
116
|
-
version: "0"
|
139
|
+
requirements:
|
140
|
+
- - ! '>='
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
117
143
|
requirements: []
|
118
|
-
|
119
144
|
rubyforge_project:
|
120
|
-
rubygems_version: 1.
|
145
|
+
rubygems_version: 1.8.10
|
121
146
|
signing_key:
|
122
147
|
specification_version: 3
|
123
148
|
summary: A generic SAML strategy for OmniAuth.
|
124
|
-
test_files:
|
149
|
+
test_files:
|
150
|
+
- spec/omniauth/strategies/saml/auth_request_spec.rb
|
151
|
+
- spec/omniauth/strategies/saml/auth_response_spec.rb
|
152
|
+
- spec/omniauth/strategies/saml/validation_error_spec.rb
|
125
153
|
- spec/omniauth/strategies/saml_spec.rb
|
154
|
+
- spec/shared/validating_method.rb
|
155
|
+
- spec/spec_helper.rb
|