saml_idp 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +16 -2
- data/app/controllers/saml_idp/idp_controller.rb +11 -0
- data/lib/saml_idp.rb +2 -2
- data/lib/saml_idp/assertion_builder.rb +11 -1
- data/lib/saml_idp/configurator.rb +1 -0
- data/lib/saml_idp/controller.rb +39 -4
- data/lib/saml_idp/encryptor.rb +86 -0
- data/lib/saml_idp/logout_builder.rb +42 -0
- data/lib/saml_idp/logout_request_builder.rb +36 -0
- data/lib/saml_idp/logout_response_builder.rb +35 -0
- data/lib/saml_idp/metadata_builder.rb +3 -0
- data/lib/saml_idp/request.rb +94 -10
- data/lib/saml_idp/response_builder.rb +1 -1
- data/lib/saml_idp/saml_response.rb +12 -4
- data/lib/saml_idp/service_provider.rb +7 -2
- data/lib/saml_idp/signable.rb +1 -1
- data/lib/saml_idp/signed_info_builder.rb +1 -1
- data/lib/saml_idp/version.rb +1 -1
- data/lib/saml_idp/xml_security.rb +15 -2
- data/saml_idp.gemspec +7 -1
- data/spec/lib/saml_idp/assertion_builder_spec.rb +24 -0
- data/spec/lib/saml_idp/configurator_spec.rb +1 -0
- data/spec/lib/saml_idp/controller_spec.rb +32 -2
- data/spec/lib/saml_idp/encryptor_spec.rb +27 -0
- data/spec/lib/saml_idp/logout_request_builder_spec.rb +43 -0
- data/spec/lib/saml_idp/logout_response_builder_spec.rb +41 -0
- data/spec/lib/saml_idp/metadata_builder_spec.rb +7 -0
- data/spec/lib/saml_idp/request_spec.rb +77 -19
- data/spec/lib/saml_idp/saml_response_spec.rb +32 -0
- data/spec/support/saml_request_macros.rb +20 -0
- data/spec/xml_security_spec.rb +2 -1
- metadata +40 -6
data/README.md
CHANGED
@@ -36,12 +36,15 @@ Add to your `routes.rb` file, for example:
|
|
36
36
|
get '/saml/auth' => 'saml_idp#new'
|
37
37
|
get '/saml/metadata' => 'saml_idp#show'
|
38
38
|
post '/saml/auth' => 'saml_idp#create'
|
39
|
+
match '/saml/logout' => 'saml_idp#logout', via: [:get, :post, :delete]
|
39
40
|
```
|
40
41
|
|
41
42
|
Create a controller that looks like this, customize to your own situation:
|
42
43
|
|
43
44
|
``` ruby
|
44
|
-
class SamlIdpController
|
45
|
+
class SamlIdpController
|
46
|
+
include SamlIdp::IdpController
|
47
|
+
|
45
48
|
def idp_authenticate(email, password) # not using params intentionally
|
46
49
|
user = User.by_email(email).first
|
47
50
|
user && user.valid_password?(password) ? user : nil
|
@@ -49,9 +52,20 @@ class SamlIdpController < SamlIdp::IdpController
|
|
49
52
|
private :idp_authenticate
|
50
53
|
|
51
54
|
def idp_make_saml_response(found_user) # not using params intentionally
|
52
|
-
|
55
|
+
# NOTE encryption is optional
|
56
|
+
encode_response found_user, encryption: {
|
57
|
+
cert: saml_request.service_provider.cert,
|
58
|
+
block_encryption: 'aes256-cbc',
|
59
|
+
key_transport: 'rsa-oaep-mgf1p'
|
60
|
+
}
|
53
61
|
end
|
54
62
|
private :idp_make_saml_response
|
63
|
+
|
64
|
+
def idp_logout
|
65
|
+
user = User.by_email(saml_request.name_id)
|
66
|
+
user.logout
|
67
|
+
end
|
68
|
+
private :idp_logout
|
55
69
|
end
|
56
70
|
```
|
57
71
|
|
@@ -29,6 +29,17 @@ module SamlIdp
|
|
29
29
|
render :template => "saml_idp/idp/new"
|
30
30
|
end
|
31
31
|
|
32
|
+
def logout
|
33
|
+
idp_logout
|
34
|
+
@saml_response = idp_make_saml_response(nil)
|
35
|
+
render :template => "saml_idp/idp/saml_post", :layout => false
|
36
|
+
end
|
37
|
+
|
38
|
+
def idp_logout
|
39
|
+
raise NotImplementedError
|
40
|
+
end
|
41
|
+
private :idp_logout
|
42
|
+
|
32
43
|
def idp_authenticate(email, password)
|
33
44
|
raise NotImplementedError
|
34
45
|
end
|
data/lib/saml_idp.rb
CHANGED
@@ -71,11 +71,11 @@ module Saml
|
|
71
71
|
|
72
72
|
def valid_signature?(fingerprint)
|
73
73
|
signed? &&
|
74
|
-
signed_document.
|
74
|
+
signed_document.validate(fingerprint, :soft)
|
75
75
|
end
|
76
76
|
|
77
77
|
def signed_document
|
78
|
-
XMLSecurity::SignedDocument.new(to_xml)
|
78
|
+
SamlIdp::XMLSecurity::SignedDocument.new(to_xml)
|
79
79
|
end
|
80
80
|
|
81
81
|
def signature_namespace
|
@@ -14,10 +14,11 @@ module SamlIdp
|
|
14
14
|
attr_accessor :raw_algorithm
|
15
15
|
attr_accessor :authn_context_classref
|
16
16
|
attr_accessor :expiry
|
17
|
+
attr_accessor :encryption_opts
|
17
18
|
|
18
19
|
delegate :config, to: :SamlIdp
|
19
20
|
|
20
|
-
def initialize(reference_id, issuer_uri, principal, audience_uri, saml_request_id, saml_acs_url, raw_algorithm, authn_context_classref, expiry=60*60)
|
21
|
+
def initialize(reference_id, issuer_uri, principal, audience_uri, saml_request_id, saml_acs_url, raw_algorithm, authn_context_classref, expiry=60*60, encryption_opts=nil)
|
21
22
|
self.reference_id = reference_id
|
22
23
|
self.issuer_uri = issuer_uri
|
23
24
|
self.principal = principal
|
@@ -27,6 +28,7 @@ module SamlIdp
|
|
27
28
|
self.raw_algorithm = raw_algorithm
|
28
29
|
self.authn_context_classref = authn_context_classref
|
29
30
|
self.expiry = expiry
|
31
|
+
self.encryption_opts = encryption_opts
|
30
32
|
end
|
31
33
|
|
32
34
|
def fresh
|
@@ -73,6 +75,14 @@ module SamlIdp
|
|
73
75
|
alias_method :raw, :fresh
|
74
76
|
private :fresh
|
75
77
|
|
78
|
+
def encrypt(opts = {})
|
79
|
+
raise "Must set encryption_opts to encrypt" unless encryption_opts
|
80
|
+
raw_xml = opts[:sign] ? signed : raw
|
81
|
+
require 'saml_idp/encryptor'
|
82
|
+
encryptor = Encryptor.new encryption_opts
|
83
|
+
encryptor.encrypt(raw_xml)
|
84
|
+
end
|
85
|
+
|
76
86
|
def get_values_for(friendly_name, getter)
|
77
87
|
result = nil
|
78
88
|
if getter.present?
|
@@ -13,6 +13,7 @@ module SamlIdp
|
|
13
13
|
attr_accessor :reference_id_generator
|
14
14
|
attr_accessor :attribute_service_location
|
15
15
|
attr_accessor :single_service_post_location
|
16
|
+
attr_accessor :single_logout_service_post_location
|
16
17
|
attr_accessor :attributes
|
17
18
|
attr_accessor :service_provider
|
18
19
|
|
data/lib/saml_idp/controller.rb
CHANGED
@@ -4,6 +4,7 @@ require 'base64'
|
|
4
4
|
require 'time'
|
5
5
|
require 'uuid'
|
6
6
|
require 'saml_idp/request'
|
7
|
+
require 'saml_idp/logout_response_builder'
|
7
8
|
module SamlIdp
|
8
9
|
module Controller
|
9
10
|
extend ActiveSupport::Concern
|
@@ -30,10 +31,14 @@ module SamlIdp
|
|
30
31
|
Saml::XML::Namespaces::AuthnContext::ClassRef::PASSWORD
|
31
32
|
end
|
32
33
|
|
33
|
-
def
|
34
|
-
response_id
|
34
|
+
def encode_authn_response(principal, opts = {})
|
35
|
+
response_id = get_saml_response_id
|
36
|
+
reference_id = opts[:reference_id] || get_saml_reference_id
|
35
37
|
audience_uri = opts[:audience_uri] || saml_request.issuer || saml_acs_url[/^(.*?\/\/.*?\/)/, 1]
|
36
38
|
opt_issuer_uri = opts[:issuer_uri] || issuer_uri
|
39
|
+
my_authn_context_classref = opts[:authn_context_classref] || authn_context_classref
|
40
|
+
expiry = opts[:expiry] || 60*60
|
41
|
+
encryption_opts = opts[:encryption] || nil
|
37
42
|
|
38
43
|
SamlResponse.new(
|
39
44
|
reference_id,
|
@@ -43,11 +48,33 @@ module SamlIdp
|
|
43
48
|
audience_uri,
|
44
49
|
saml_request_id,
|
45
50
|
saml_acs_url,
|
46
|
-
algorithm,
|
47
|
-
|
51
|
+
(opts[:algorithm] || algorithm || default_algorithm),
|
52
|
+
my_authn_context_classref,
|
53
|
+
expiry,
|
54
|
+
encryption_opts
|
48
55
|
).build
|
49
56
|
end
|
50
57
|
|
58
|
+
def encode_logout_response(principal, opts = {})
|
59
|
+
SamlIdp::LogoutResponseBuilder.new(
|
60
|
+
get_saml_response_id,
|
61
|
+
(opts[:issuer_uri] || issuer_uri),
|
62
|
+
saml_logout_url,
|
63
|
+
saml_request_id,
|
64
|
+
(opts[:algorithm] || algorithm || default_algorithm)
|
65
|
+
).signed
|
66
|
+
end
|
67
|
+
|
68
|
+
def encode_response(principal, opts = {})
|
69
|
+
if saml_request.authn_request?
|
70
|
+
encode_authn_response(principal, opts)
|
71
|
+
elsif saml_request.logout_request?
|
72
|
+
encode_logout_response(principal, opts)
|
73
|
+
else
|
74
|
+
raise "Unknown request: #{saml_request}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
51
78
|
def issuer_uri
|
52
79
|
(SamlIdp.config.base_saml_location.present? && SamlIdp.config.base_saml_location) ||
|
53
80
|
(defined?(request) && request.url.to_s.split("?").first) ||
|
@@ -66,6 +93,10 @@ module SamlIdp
|
|
66
93
|
saml_request.acs_url
|
67
94
|
end
|
68
95
|
|
96
|
+
def saml_logout_url
|
97
|
+
saml_request.logout_url
|
98
|
+
end
|
99
|
+
|
69
100
|
def get_saml_response_id
|
70
101
|
UUID.generate
|
71
102
|
end
|
@@ -73,5 +104,9 @@ module SamlIdp
|
|
73
104
|
def get_saml_reference_id
|
74
105
|
UUID.generate
|
75
106
|
end
|
107
|
+
|
108
|
+
def default_algorithm
|
109
|
+
OpenSSL::Digest::SHA256
|
110
|
+
end
|
76
111
|
end
|
77
112
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'xmlenc'
|
2
|
+
module SamlIdp
|
3
|
+
class Encryptor
|
4
|
+
attr_accessor :encryption_key
|
5
|
+
attr_accessor :block_encryption
|
6
|
+
attr_accessor :key_transport
|
7
|
+
attr_accessor :cert
|
8
|
+
|
9
|
+
def initialize(opts)
|
10
|
+
self.block_encryption = opts[:block_encryption]
|
11
|
+
self.key_transport = opts[:key_transport]
|
12
|
+
self.cert = opts[:cert]
|
13
|
+
end
|
14
|
+
|
15
|
+
def encrypt(raw_xml)
|
16
|
+
encryption_template = Nokogiri::XML::Document.parse(build_encryption_template).root
|
17
|
+
encrypted_data = Xmlenc::EncryptedData.new(encryption_template)
|
18
|
+
@encryption_key = encrypted_data.encrypt(raw_xml)
|
19
|
+
encrypted_key_node = encrypted_data.node.at_xpath(
|
20
|
+
'//xenc:EncryptedData/ds:KeyInfo/xenc:EncryptedKey',
|
21
|
+
Xmlenc::NAMESPACES
|
22
|
+
)
|
23
|
+
encrypted_key = Xmlenc::EncryptedKey.new(encrypted_key_node)
|
24
|
+
encrypted_key.encrypt(openssl_cert.public_key, encryption_key)
|
25
|
+
xml = Builder::XmlMarkup.new
|
26
|
+
xml.EncryptedAssertion xmlns: Saml::XML::Namespaces::ASSERTION do |enc_assert|
|
27
|
+
enc_assert << encrypted_data.node.to_s
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def openssl_cert
|
32
|
+
if cert.is_a?(String)
|
33
|
+
@_openssl_cert ||= OpenSSL::X509::Certificate.new(Base64.decode64(cert))
|
34
|
+
else
|
35
|
+
@_openssl_cert ||= cert
|
36
|
+
end
|
37
|
+
end
|
38
|
+
private :openssl_cert
|
39
|
+
|
40
|
+
def block_encryption_ns
|
41
|
+
"http://www.w3.org/2001/04/xmlenc##{block_encryption}"
|
42
|
+
end
|
43
|
+
private :block_encryption_ns
|
44
|
+
|
45
|
+
def key_transport_ns
|
46
|
+
"http://www.w3.org/2001/04/xmlenc##{key_transport}"
|
47
|
+
end
|
48
|
+
private :key_transport_ns
|
49
|
+
|
50
|
+
def cipher_algorithm
|
51
|
+
Xmlenc::EncryptedData::ALGORITHMS[block_encryption_ns]
|
52
|
+
end
|
53
|
+
private :cipher_algorithm
|
54
|
+
|
55
|
+
def build_encryption_template
|
56
|
+
xml = Builder::XmlMarkup.new
|
57
|
+
xml.EncryptedData Id: 'ED', Type: 'http://www.w3.org/2001/04/xmlenc#Element',
|
58
|
+
xmlns: 'http://www.w3.org/2001/04/xmlenc#' do |enc_data|
|
59
|
+
enc_data.EncryptionMethod Algorithm: block_encryption_ns
|
60
|
+
enc_data.tag! 'ds:KeyInfo', 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#' do |key_info|
|
61
|
+
key_info.EncryptedKey Id: 'EK', xmlns: 'http://www.w3.org/2001/04/xmlenc#' do |enc_key|
|
62
|
+
enc_key.EncryptionMethod Algorithm: key_transport_ns
|
63
|
+
enc_key.tag! 'ds:KeyInfo', 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#' do |key_info2|
|
64
|
+
key_info2.tag! 'ds:KeyName'
|
65
|
+
key_info2.tag! 'ds:X509Data' do |x509_data|
|
66
|
+
x509_data.tag! 'ds:X509Certificate' do |x509_cert|
|
67
|
+
x509_cert << cert.to_s.gsub(/-+(BEGIN|END) CERTIFICATE-+/, '')
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
enc_key.CipherData do |cipher_data|
|
72
|
+
cipher_data.CipherValue
|
73
|
+
end
|
74
|
+
enc_key.ReferenceList do |ref_list|
|
75
|
+
ref_list.DataReference URI: 'ED'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
enc_data.CipherData do |cipher_data|
|
80
|
+
cipher_data.CipherValue
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
private :build_encryption_template
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'builder'
|
2
|
+
module SamlIdp
|
3
|
+
class LogoutBuilder
|
4
|
+
include Signable
|
5
|
+
|
6
|
+
# this is an abstract base class.
|
7
|
+
def build
|
8
|
+
raise "#{self.class} must implement build method"
|
9
|
+
end
|
10
|
+
|
11
|
+
def reference_id
|
12
|
+
UUID.generate
|
13
|
+
end
|
14
|
+
|
15
|
+
def digest
|
16
|
+
algorithm.hexdigest raw
|
17
|
+
end
|
18
|
+
|
19
|
+
def encoded
|
20
|
+
@encoded ||= encode
|
21
|
+
end
|
22
|
+
|
23
|
+
def raw
|
24
|
+
build
|
25
|
+
end
|
26
|
+
|
27
|
+
def encode
|
28
|
+
Base64.strict_encode64(raw)
|
29
|
+
end
|
30
|
+
private :encode
|
31
|
+
|
32
|
+
def response_id_string
|
33
|
+
"_#{response_id}"
|
34
|
+
end
|
35
|
+
private :response_id_string
|
36
|
+
|
37
|
+
def now_iso
|
38
|
+
Time.now.utc.iso8601
|
39
|
+
end
|
40
|
+
private :now_iso
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'saml_idp/logout_builder'
|
2
|
+
module SamlIdp
|
3
|
+
class LogoutRequestBuilder < LogoutBuilder
|
4
|
+
attr_accessor :response_id
|
5
|
+
attr_accessor :issuer_uri
|
6
|
+
attr_accessor :saml_slo_url
|
7
|
+
attr_accessor :name_id
|
8
|
+
attr_accessor :session_index
|
9
|
+
attr_accessor :algorithm
|
10
|
+
|
11
|
+
def initialize(response_id, issuer_uri, saml_slo_url, name_id, session_index, algorithm)
|
12
|
+
self.response_id = response_id
|
13
|
+
self.issuer_uri = issuer_uri
|
14
|
+
self.saml_slo_url = saml_slo_url
|
15
|
+
self.name_id = name_id
|
16
|
+
self.session_index = session_index
|
17
|
+
self.algorithm = algorithm
|
18
|
+
end
|
19
|
+
|
20
|
+
def build
|
21
|
+
builder = Builder::XmlMarkup.new
|
22
|
+
builder.LogoutRequest ID: response_id_string,
|
23
|
+
Version: "2.0",
|
24
|
+
IssueInstant: now_iso,
|
25
|
+
Destination: saml_slo_url,
|
26
|
+
"xmlns" => Saml::XML::Namespaces::PROTOCOL do |request|
|
27
|
+
request.Issuer issuer_uri, xmlns: Saml::XML::Namespaces::ASSERTION
|
28
|
+
sign request
|
29
|
+
request.NameID name_id, xmlns: Saml::XML::Namespaces::ASSERTION,
|
30
|
+
Format: Saml::XML::Namespaces::Formats::NameId::PERSISTENT
|
31
|
+
request.SessionIndex session_index
|
32
|
+
end
|
33
|
+
end
|
34
|
+
private :build
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'saml_idp/logout_builder'
|
2
|
+
module SamlIdp
|
3
|
+
class LogoutResponseBuilder < LogoutBuilder
|
4
|
+
attr_accessor :response_id
|
5
|
+
attr_accessor :issuer_uri
|
6
|
+
attr_accessor :saml_slo_url
|
7
|
+
attr_accessor :saml_request_id
|
8
|
+
attr_accessor :algorithm
|
9
|
+
|
10
|
+
def initialize(response_id, issuer_uri, saml_slo_url, saml_request_id, algorithm)
|
11
|
+
self.response_id = response_id
|
12
|
+
self.issuer_uri = issuer_uri
|
13
|
+
self.saml_slo_url = saml_slo_url
|
14
|
+
self.saml_request_id = saml_request_id
|
15
|
+
self.algorithm = algorithm
|
16
|
+
end
|
17
|
+
|
18
|
+
def build
|
19
|
+
builder = Builder::XmlMarkup.new
|
20
|
+
builder.LogoutResponse ID: response_id_string,
|
21
|
+
Version: "2.0",
|
22
|
+
IssueInstant: now_iso,
|
23
|
+
Destination: saml_slo_url,
|
24
|
+
InResponseTo: saml_request_id,
|
25
|
+
xmlns: Saml::XML::Namespaces::PROTOCOL do |response|
|
26
|
+
response.Issuer issuer_uri, xmlns: Saml::XML::Namespaces::ASSERTION
|
27
|
+
sign response
|
28
|
+
response.Status xmlns: Saml::XML::Namespaces::PROTOCOL do |status|
|
29
|
+
status.StatusCode Value: Saml::XML::Namespaces::Statuses::SUCCESS
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
private :build
|
34
|
+
end
|
35
|
+
end
|
@@ -25,6 +25,8 @@ module SamlIdp
|
|
25
25
|
entity.IDPSSODescriptor protocolSupportEnumeration: protocol_enumeration do |descriptor|
|
26
26
|
build_key_descriptor descriptor
|
27
27
|
build_name_id_formats descriptor
|
28
|
+
descriptor.SingleLogoutService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
29
|
+
Location: single_logout_service_post_location
|
28
30
|
descriptor.SingleSignOnService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
29
31
|
Location: single_service_post_location
|
30
32
|
build_attribute descriptor
|
@@ -146,6 +148,7 @@ module SamlIdp
|
|
146
148
|
organization_url
|
147
149
|
attribute_service_location
|
148
150
|
single_service_post_location
|
151
|
+
single_logout_service_post_location
|
149
152
|
technical_contact
|
150
153
|
].each do |delegatable|
|
151
154
|
define_method(delegatable) do
|
data/lib/saml_idp/request.rb
CHANGED
@@ -31,8 +31,32 @@ module SamlIdp
|
|
31
31
|
self.raw_xml = raw_xml
|
32
32
|
end
|
33
33
|
|
34
|
+
def logout_request?
|
35
|
+
logout_request.nil? ? false : true
|
36
|
+
end
|
37
|
+
|
38
|
+
def authn_request?
|
39
|
+
authn_request.nil? ? false : true
|
40
|
+
end
|
41
|
+
|
34
42
|
def request_id
|
35
|
-
|
43
|
+
request["ID"]
|
44
|
+
end
|
45
|
+
|
46
|
+
def request
|
47
|
+
if authn_request?
|
48
|
+
authn_request
|
49
|
+
elsif logout_request?
|
50
|
+
logout_request
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def requested_authn_context
|
55
|
+
if authn_request? && authn_context_node
|
56
|
+
authn_context_node.content
|
57
|
+
else
|
58
|
+
nil
|
59
|
+
end
|
36
60
|
end
|
37
61
|
|
38
62
|
def acs_url
|
@@ -40,14 +64,54 @@ module SamlIdp
|
|
40
64
|
authn_request["AssertionConsumerServiceURL"].to_s
|
41
65
|
end
|
42
66
|
|
67
|
+
def logout_url
|
68
|
+
service_provider.assertion_consumer_logout_service_url
|
69
|
+
end
|
70
|
+
|
71
|
+
def response_url
|
72
|
+
if authn_request?
|
73
|
+
acs_url
|
74
|
+
elsif logout_request?
|
75
|
+
logout_url
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def log(msg)
|
80
|
+
if Rails && Rails.logger
|
81
|
+
Rails.logger.info msg
|
82
|
+
else
|
83
|
+
puts msg
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
43
87
|
def valid?
|
44
|
-
service_provider?
|
45
|
-
|
46
|
-
|
88
|
+
unless service_provider?
|
89
|
+
log "Unable to find service provider for issuer #{issuer}"
|
90
|
+
return false
|
91
|
+
end
|
92
|
+
|
93
|
+
unless (authn_request? ^ logout_request?)
|
94
|
+
log "One and only one of authnrequest and logout request is required. authnrequest: #{authn_request?} logout_request: #{logout_request?} "
|
95
|
+
return false
|
96
|
+
end
|
97
|
+
|
98
|
+
unless valid_signature?
|
99
|
+
log "Signature is invalid in #{raw_xml}"
|
100
|
+
return false
|
101
|
+
end
|
102
|
+
|
103
|
+
if response_url.nil?
|
104
|
+
log "Unable to find response url for #{issuer}: #{raw_xml}"
|
105
|
+
return false
|
106
|
+
end
|
107
|
+
|
108
|
+
return true
|
47
109
|
end
|
48
110
|
|
49
111
|
def valid_signature?
|
50
|
-
|
112
|
+
# Force signatures for logout requests because there is no other
|
113
|
+
# protection against a cross-site DoS.
|
114
|
+
service_provider.valid_signature?(document, logout_request?)
|
51
115
|
end
|
52
116
|
|
53
117
|
def service_provider?
|
@@ -55,24 +119,44 @@ module SamlIdp
|
|
55
119
|
end
|
56
120
|
|
57
121
|
def service_provider
|
58
|
-
@
|
122
|
+
@_service_provider ||= ServiceProvider.new((service_provider_finder[issuer] || {}).merge(identifier: issuer))
|
59
123
|
end
|
60
124
|
|
61
125
|
def issuer
|
62
|
-
@
|
63
|
-
@
|
126
|
+
@_issuer ||= xpath("//saml:Issuer", saml: assertion).first.try(:content)
|
127
|
+
@_issuer if @_issuer.present?
|
128
|
+
end
|
129
|
+
|
130
|
+
def name_id
|
131
|
+
@_name_id ||= xpath("//saml:NameID", saml: assertion).first.try(:content)
|
132
|
+
end
|
133
|
+
|
134
|
+
def session_index
|
135
|
+
@_session_index ||= xpath("//samlp:SessionIndex", samlp: samlp).first.try(:content)
|
64
136
|
end
|
65
137
|
|
66
138
|
def document
|
67
|
-
@
|
139
|
+
@_document ||= Saml::XML::Document.parse(raw_xml)
|
68
140
|
end
|
69
141
|
private :document
|
70
142
|
|
143
|
+
def authn_context_node
|
144
|
+
@_authn_context_node ||= xpath("//samlp:AuthnRequest/samlp:RequestedAuthnContext/saml:AuthnContextClassRef",
|
145
|
+
samlp: samlp,
|
146
|
+
saml: assertion).first
|
147
|
+
end
|
148
|
+
private :authn_context_node
|
149
|
+
|
71
150
|
def authn_request
|
72
|
-
xpath("//samlp:AuthnRequest", samlp: samlp).first
|
151
|
+
@_authn_request ||= xpath("//samlp:AuthnRequest", samlp: samlp).first
|
73
152
|
end
|
74
153
|
private :authn_request
|
75
154
|
|
155
|
+
def logout_request
|
156
|
+
@_logout_request ||= xpath("//samlp:LogoutRequest", samlp: samlp).first
|
157
|
+
end
|
158
|
+
private :logout_request
|
159
|
+
|
76
160
|
def samlp
|
77
161
|
Saml::XML::Namespaces::PROTOCOL
|
78
162
|
end
|