omniauth-saml 0.9.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of omniauth-saml might be problematic. Click here for more details.
- data/CHANGELOG.md +30 -0
- data/README.md +7 -3
- data/lib/omniauth-saml.rb +1 -0
- data/lib/omniauth-saml/version.rb +1 -1
- data/lib/omniauth/strategies/saml.rb +23 -16
- data/spec/omniauth/strategies/saml_spec.rb +9 -35
- data/spec/spec_helper.rb +0 -1
- metadata +62 -48
- data/lib/omniauth/strategies/saml/auth_request.rb +0 -38
- data/lib/omniauth/strategies/saml/auth_response.rb +0 -148
- data/lib/omniauth/strategies/saml/xml_security.rb +0 -126
- data/spec/omniauth/strategies/saml/auth_request_spec.rb +0 -75
- data/spec/omniauth/strategies/saml/auth_response_spec.rb +0 -90
- data/spec/omniauth/strategies/saml/validation_error_spec.rb +0 -5
- data/spec/shared/validating_method.rb +0 -129
data/CHANGELOG.md
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# OmniAuth SAML Version History
|
2
|
+
|
3
|
+
A generic SAML strategy for OmniAuth.
|
4
|
+
|
5
|
+
https://github.com/PracticallyGreen/omniauth-saml
|
6
|
+
|
7
|
+
## 1.0.0 (2012-11-12)
|
8
|
+
|
9
|
+
* remove SAML code and port to ruby-saml gem
|
10
|
+
* fix incompatibility with OmniAuth 1.1
|
11
|
+
|
12
|
+
## 0.9.2 (2012-03-30)
|
13
|
+
|
14
|
+
* validate the SAML response
|
15
|
+
* 100% test coverage
|
16
|
+
* now requires ruby 1.9.2+
|
17
|
+
|
18
|
+
## 0.9.1 (2012-02-23)
|
19
|
+
|
20
|
+
* return first and last name in the info hash
|
21
|
+
* no longer use LDAP OIDs for name and email selection
|
22
|
+
* return SAML attributes as the omniauth raw_info hash
|
23
|
+
|
24
|
+
## 0.9.0 (2012-02-14)
|
25
|
+
|
26
|
+
* initial release
|
27
|
+
* extracts commits from omniauth 0-3-stable branch
|
28
|
+
* port to omniauth 1.0 strategy format
|
29
|
+
* update README with more documentation and license
|
30
|
+
* package as the `omniauth-saml` gem
|
data/README.md
CHANGED
@@ -6,7 +6,7 @@ https://github.com/PracticallyGreen/omniauth-saml
|
|
6
6
|
|
7
7
|
## Requirements
|
8
8
|
|
9
|
-
* [OmniAuth](http://www.omniauth.org/) 1.
|
9
|
+
* [OmniAuth](http://www.omniauth.org/) 1.1+
|
10
10
|
* Ruby 1.9.2
|
11
11
|
|
12
12
|
## Usage
|
@@ -46,6 +46,8 @@ Rails.application.config.middleware.use OmniAuth::Builder do
|
|
46
46
|
end
|
47
47
|
```
|
48
48
|
|
49
|
+
For IdP-initiated SSO, users should directly access the IdP SSO target URL. Set the `href` of your application's login link to the value of `idp_sso_target_url`. For SP-initiated SSO, link to `/auth/saml`.
|
50
|
+
|
49
51
|
## Options
|
50
52
|
|
51
53
|
* `:assertion_consumer_service_url` - The URL at which the SAML assertion should be
|
@@ -70,7 +72,9 @@ end
|
|
70
72
|
application. If you need the email address, use "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress".
|
71
73
|
See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf section 8.3 for
|
72
74
|
other options. Note that the identity provider might not support all options.
|
73
|
-
Optional.
|
75
|
+
Used during SP-initiated SSO. Optional.
|
76
|
+
|
77
|
+
* See the `Onelogin::Saml::Settings` class in the [Ruby SAML gem](https://github.com/onelogin/ruby-saml) for additional supported options.
|
74
78
|
|
75
79
|
## Authors
|
76
80
|
|
@@ -80,7 +84,7 @@ Maintained by [Rajiv Aaron Manglani](http://www.rajivmanglani.com/).
|
|
80
84
|
|
81
85
|
## License
|
82
86
|
|
83
|
-
Copyright (c) 2011-2012 [Practically Green, Inc.](http://www.practicallygreen.com/).
|
87
|
+
Copyright (c) 2011-2012 [Practically Green, Inc.](http://www.practicallygreen.com/).
|
84
88
|
All rights reserved. Released under the MIT license.
|
85
89
|
|
86
90
|
Portions Copyright (c) 2007 Sun Microsystems Inc.
|
data/lib/omniauth-saml.rb
CHANGED
@@ -1,34 +1,42 @@
|
|
1
1
|
require 'omniauth'
|
2
|
+
require 'ruby-saml'
|
2
3
|
|
3
4
|
module OmniAuth
|
4
5
|
module Strategies
|
5
6
|
class SAML
|
6
7
|
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
8
|
|
12
9
|
option :name_identifier_format, "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
13
10
|
|
14
11
|
def request_phase
|
15
|
-
request =
|
16
|
-
|
12
|
+
request = Onelogin::Saml::Authrequest.new
|
13
|
+
settings = Onelogin::Saml::Settings.new(options)
|
14
|
+
|
15
|
+
redirect(request.create(settings))
|
17
16
|
end
|
18
17
|
|
19
18
|
def callback_phase
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
unless request.params['SAMLResponse']
|
20
|
+
raise OmniAuth::Strategies::SAML::ValidationError.new("SAML response missing")
|
21
|
+
end
|
22
|
+
|
23
|
+
response = Onelogin::Saml::Response.new(request.params['SAMLResponse'])
|
24
|
+
response.settings = Onelogin::Saml::Settings.new(options)
|
23
25
|
|
24
|
-
|
25
|
-
|
26
|
+
@name_id = response.name_id
|
27
|
+
@attributes = response.attributes
|
26
28
|
|
27
|
-
|
28
|
-
|
29
|
-
rescue ArgumentError => e
|
30
|
-
fail!(:invalid_ticket, 'Invalid SAML Response')
|
29
|
+
if @name_id.nil? || @name_id.empty?
|
30
|
+
raise OmniAuth::Strategies::SAML::ValidationError.new("SAML response missing 'name_id'")
|
31
31
|
end
|
32
|
+
|
33
|
+
response.validate!
|
34
|
+
|
35
|
+
super
|
36
|
+
rescue OmniAuth::Strategies::SAML::ValidationError
|
37
|
+
fail!(:invalid_ticket, $!)
|
38
|
+
rescue Onelogin::Saml::ValidationError
|
39
|
+
fail!(:invalid_ticket, $!)
|
32
40
|
end
|
33
41
|
|
34
42
|
uid { @name_id }
|
@@ -43,7 +51,6 @@ module OmniAuth
|
|
43
51
|
end
|
44
52
|
|
45
53
|
extra { { :raw_info => @attributes } }
|
46
|
-
|
47
54
|
end
|
48
55
|
end
|
49
56
|
end
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
RSpec::Matchers.define :fail_with do |message|
|
4
4
|
match do |actual|
|
5
|
-
actual.redirect? &&
|
5
|
+
actual.redirect? && /\?.*message=#{message}/ === actual.location
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
@@ -19,7 +19,7 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
19
19
|
:assertion_consumer_service_url => "http://localhost:3000/auth/saml/callback",
|
20
20
|
:issuer => "https://saml.issuer.url/issuers/29490",
|
21
21
|
:idp_sso_target_url => "https://idp.sso.target_url/signon/29490",
|
22
|
-
:idp_cert_fingerprint => "
|
22
|
+
:idp_cert_fingerprint => "C1:59:74:2B:E8:0C:6C:A9:41:0F:6E:83:F6:D1:52:25:45:58:89:FB",
|
23
23
|
:name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
24
24
|
}
|
25
25
|
end
|
@@ -41,7 +41,7 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
41
41
|
let(:xml) { :example_response }
|
42
42
|
|
43
43
|
before :each do
|
44
|
-
Time.stub(:now).and_return(Time.new(2012,
|
44
|
+
Time.stub(:now).and_return(Time.new(2012, 11, 8, 20, 40, 00, 0))
|
45
45
|
end
|
46
46
|
|
47
47
|
context "when the response is valid" do
|
@@ -50,23 +50,15 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
50
50
|
end
|
51
51
|
|
52
52
|
it "should set the uid to the nameID in the SAML response" do
|
53
|
-
auth_hash['uid'].should == '
|
53
|
+
auth_hash['uid'].should == '_1f6fcf6be5e13b08b1e3610e7ff59f205fbd814f23'
|
54
54
|
end
|
55
55
|
|
56
56
|
it "should set the raw info to all attributes" do
|
57
57
|
auth_hash['extra']['raw_info'].to_hash.should == {
|
58
|
-
'
|
59
|
-
'
|
60
|
-
'
|
61
|
-
'
|
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'
|
58
|
+
'first_name' => 'Rajiv',
|
59
|
+
'last_name' => 'Manglani',
|
60
|
+
'email' => 'user@example.com',
|
61
|
+
'company_name' => 'Example Company'
|
70
62
|
}
|
71
63
|
end
|
72
64
|
end
|
@@ -89,7 +81,7 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
89
81
|
|
90
82
|
context "when the fingerprint is invalid" do
|
91
83
|
before :each do
|
92
|
-
saml_options[:idp_cert_fingerprint] = "
|
84
|
+
saml_options[:idp_cert_fingerprint] = "00:00:00:00:00:0C:6C:A9:41:0F:6E:83:F6:D1:52:25:45:58:89:FB"
|
93
85
|
post_xml
|
94
86
|
end
|
95
87
|
|
@@ -111,23 +103,5 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
111
103
|
|
112
104
|
it { should fail_with(:invalid_ticket) }
|
113
105
|
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
106
|
end
|
133
107
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: omniauth-saml
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -12,96 +12,120 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2012-
|
15
|
+
date: 2012-11-13 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: omniauth
|
19
|
-
requirement:
|
19
|
+
requirement: !ruby/object:Gem::Requirement
|
20
20
|
none: false
|
21
21
|
requirements:
|
22
22
|
- - ~>
|
23
23
|
- !ruby/object:Gem::Version
|
24
|
-
version: '1.
|
24
|
+
version: '1.1'
|
25
25
|
type: :runtime
|
26
26
|
prerelease: false
|
27
|
-
version_requirements:
|
28
|
-
- !ruby/object:Gem::Dependency
|
29
|
-
name: xmlcanonicalizer
|
30
|
-
requirement: &70159259959000 !ruby/object:Gem::Requirement
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
31
28
|
none: false
|
32
29
|
requirements:
|
33
|
-
- -
|
30
|
+
- - ~>
|
34
31
|
- !ruby/object:Gem::Version
|
35
|
-
version:
|
36
|
-
type: :runtime
|
37
|
-
prerelease: false
|
38
|
-
version_requirements: *70159259959000
|
32
|
+
version: '1.1'
|
39
33
|
- !ruby/object:Gem::Dependency
|
40
|
-
name:
|
41
|
-
requirement:
|
34
|
+
name: ruby-saml
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
42
36
|
none: false
|
43
37
|
requirements:
|
44
38
|
- - ~>
|
45
39
|
- !ruby/object:Gem::Version
|
46
|
-
version: '
|
40
|
+
version: '0.6'
|
47
41
|
type: :runtime
|
48
42
|
prerelease: false
|
49
|
-
version_requirements:
|
43
|
+
version_requirements: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ~>
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0.6'
|
50
49
|
- !ruby/object:Gem::Dependency
|
51
50
|
name: guard
|
52
|
-
requirement:
|
51
|
+
requirement: !ruby/object:Gem::Requirement
|
53
52
|
none: false
|
54
53
|
requirements:
|
55
|
-
- -
|
54
|
+
- - ~>
|
56
55
|
- !ruby/object:Gem::Version
|
57
|
-
version: 1.0
|
56
|
+
version: '1.0'
|
58
57
|
type: :development
|
59
58
|
prerelease: false
|
60
|
-
version_requirements:
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ~>
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '1.0'
|
61
65
|
- !ruby/object:Gem::Dependency
|
62
66
|
name: guard-rspec
|
63
|
-
requirement:
|
67
|
+
requirement: !ruby/object:Gem::Requirement
|
64
68
|
none: false
|
65
69
|
requirements:
|
66
|
-
- -
|
70
|
+
- - ~>
|
67
71
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
72
|
+
version: '2.1'
|
69
73
|
type: :development
|
70
74
|
prerelease: false
|
71
|
-
version_requirements:
|
75
|
+
version_requirements: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ~>
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '2.1'
|
72
81
|
- !ruby/object:Gem::Dependency
|
73
82
|
name: rspec
|
74
|
-
requirement:
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
75
84
|
none: false
|
76
85
|
requirements:
|
77
|
-
- -
|
86
|
+
- - ~>
|
78
87
|
- !ruby/object:Gem::Version
|
79
88
|
version: '2.8'
|
80
89
|
type: :development
|
81
90
|
prerelease: false
|
82
|
-
version_requirements:
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.8'
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: simplecov
|
85
|
-
requirement:
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
86
100
|
none: false
|
87
101
|
requirements:
|
88
|
-
- -
|
102
|
+
- - ~>
|
89
103
|
- !ruby/object:Gem::Version
|
90
|
-
version: 0.6
|
104
|
+
version: '0.6'
|
91
105
|
type: :development
|
92
106
|
prerelease: false
|
93
|
-
version_requirements:
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ~>
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0.6'
|
94
113
|
- !ruby/object:Gem::Dependency
|
95
114
|
name: rack-test
|
96
|
-
requirement:
|
115
|
+
requirement: !ruby/object:Gem::Requirement
|
97
116
|
none: false
|
98
117
|
requirements:
|
99
|
-
- -
|
118
|
+
- - ~>
|
100
119
|
- !ruby/object:Gem::Version
|
101
|
-
version: 0.6
|
120
|
+
version: '0.6'
|
102
121
|
type: :development
|
103
122
|
prerelease: false
|
104
|
-
version_requirements:
|
123
|
+
version_requirements: !ruby/object:Gem::Requirement
|
124
|
+
none: false
|
125
|
+
requirements:
|
126
|
+
- - ~>
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0.6'
|
105
129
|
description: A generic SAML strategy for OmniAuth.
|
106
130
|
email: rajiv@alum.mit.edu
|
107
131
|
executables: []
|
@@ -109,18 +133,12 @@ extensions: []
|
|
109
133
|
extra_rdoc_files: []
|
110
134
|
files:
|
111
135
|
- README.md
|
112
|
-
-
|
113
|
-
- lib/omniauth/strategies/saml/auth_response.rb
|
136
|
+
- CHANGELOG.md
|
114
137
|
- lib/omniauth/strategies/saml/validation_error.rb
|
115
|
-
- lib/omniauth/strategies/saml/xml_security.rb
|
116
138
|
- lib/omniauth/strategies/saml.rb
|
117
139
|
- lib/omniauth-saml/version.rb
|
118
140
|
- 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
|
122
141
|
- spec/omniauth/strategies/saml_spec.rb
|
123
|
-
- spec/shared/validating_method.rb
|
124
142
|
- spec/spec_helper.rb
|
125
143
|
homepage: https://github.com/PracticallyGreen/omniauth-saml
|
126
144
|
licenses: []
|
@@ -142,14 +160,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
142
160
|
version: '0'
|
143
161
|
requirements: []
|
144
162
|
rubyforge_project:
|
145
|
-
rubygems_version: 1.8.
|
163
|
+
rubygems_version: 1.8.23
|
146
164
|
signing_key:
|
147
165
|
specification_version: 3
|
148
166
|
summary: A generic SAML strategy for OmniAuth.
|
149
167
|
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
|
153
168
|
- spec/omniauth/strategies/saml_spec.rb
|
154
|
-
- spec/shared/validating_method.rb
|
155
169
|
- spec/spec_helper.rb
|
@@ -1,38 +0,0 @@
|
|
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
|
@@ -1,148 +0,0 @@
|
|
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 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 = 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
|
-
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
|
-
stmt_element = xpath("/p:Response/a:Assertion/a:AttributeStatement")
|
42
|
-
return {} if stmt_element.nil?
|
43
|
-
|
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)
|
48
|
-
|
49
|
-
result[name] = result[name.to_sym] = value
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
# When this user session should expire at latest
|
56
|
-
def session_expires_at
|
57
|
-
@expires_at ||= begin
|
58
|
-
node = xpath("/p:Response/a:Assertion/a:AuthnStatement")
|
59
|
-
parse_time(node, "SessionNotOnOrAfter")
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
# Conditions (if any) for the assertion to run
|
64
|
-
def conditions
|
65
|
-
@conditions ||= begin
|
66
|
-
xpath("/p:Response/a:Assertion[@ID='#{signed_element_id}']/a:Conditions")
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
private
|
71
|
-
|
72
|
-
def validation_error(message)
|
73
|
-
raise OmniAuth::Strategies::SAML::ValidationError.new(message)
|
74
|
-
end
|
75
|
-
|
76
|
-
def validate(soft = true)
|
77
|
-
validate_response_state(soft) &&
|
78
|
-
validate_conditions(soft) &&
|
79
|
-
document.validate(get_fingerprint, soft)
|
80
|
-
end
|
81
|
-
|
82
|
-
def validate_response_state(soft = true)
|
83
|
-
if response.empty?
|
84
|
-
return soft ? false : validation_error("Blank response")
|
85
|
-
end
|
86
|
-
|
87
|
-
if settings.nil?
|
88
|
-
return soft ? false : validation_error("No settings on response")
|
89
|
-
end
|
90
|
-
|
91
|
-
if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil?
|
92
|
-
return soft ? false : validation_error("No fingerprint or certificate on settings")
|
93
|
-
end
|
94
|
-
|
95
|
-
true
|
96
|
-
end
|
97
|
-
|
98
|
-
def get_fingerprint
|
99
|
-
if settings.idp_cert
|
100
|
-
cert = OpenSSL::X509::Certificate.new(settings.idp_cert.gsub(/^ +/, ''))
|
101
|
-
Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":")
|
102
|
-
else
|
103
|
-
settings.idp_cert_fingerprint
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def validate_conditions(soft = true)
|
108
|
-
return true if conditions.nil?
|
109
|
-
return true if options[:skip_conditions]
|
110
|
-
|
111
|
-
if not_before = parse_time(conditions, "NotBefore")
|
112
|
-
if Time.now.utc < not_before
|
113
|
-
return soft ? false : validation_error("Current time is earlier than NotBefore condition")
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
if not_on_or_after = parse_time(conditions, "NotOnOrAfter")
|
118
|
-
if Time.now.utc >= not_on_or_after
|
119
|
-
return soft ? false : validation_error("Current time is on or after NotOnOrAfter condition")
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
true
|
124
|
-
end
|
125
|
-
|
126
|
-
def parse_time(node, attribute)
|
127
|
-
if node && node.attributes[attribute]
|
128
|
-
Time.parse(node.attributes[attribute])
|
129
|
-
end
|
130
|
-
end
|
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
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
@@ -1,126 +0,0 @@
|
|
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
|
@@ -1,75 +0,0 @@
|
|
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
|
@@ -1,90 +0,0 @@
|
|
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,129 +0,0 @@
|
|
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
|