saml_idp 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|