omniauth-saml 0.9.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of omniauth-saml might be problematic. Click here for more details.
- data/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
|