ruby-saml 0.8.9 → 0.8.10
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of ruby-saml might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/Gemfile +9 -1
- data/lib/onelogin/ruby-saml/authrequest.rb +82 -17
- data/lib/onelogin/ruby-saml/logoutrequest.rb +90 -18
- data/lib/onelogin/ruby-saml/settings.rb +73 -12
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +157 -0
- data/lib/onelogin/ruby-saml/utils.rb +79 -0
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/ruby-saml.rb +2 -1
- data/lib/xml_security.rb +151 -28
- data/test/certificates/ruby-saml.crt +14 -0
- data/test/certificates/ruby-saml.key +15 -0
- data/test/logoutrequest_test.rb +176 -41
- data/test/logoutresponse_test.rb +2 -1
- data/test/request_test.rb +100 -37
- data/test/response_test.rb +1 -1
- data/test/slo_logoutresponse_test.rb +226 -0
- data/test/test_helper.rb +37 -1
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1a564ad5fdd002f4648b22638bf51dd7ad029ea633484289c7ce2e8759a3c5d6
|
4
|
+
data.tar.gz: 15d0f1c459006f4c65fc9cb916b29b55098e68d9e3c5123080737b41b4e5e449
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 943d65750b9895285e60b24d612c1cce77bef3a262387121ccf9b7f8536ebfea27874d3a68ff095dc1a36e2bc3c53f509de07d2e4e31c02a20437e18015af561
|
7
|
+
data.tar.gz: ccf1223639a43235641ba2533e61473e4ff248624571cd05177ccd1dd0611f7d8df16f405fd74af2987112a988f5fcb0f2f6c54fe3830f89d3688964780ea333
|
data/Gemfile
CHANGED
@@ -5,9 +5,16 @@ source 'http://rubygems.org'
|
|
5
5
|
|
6
6
|
gemspec
|
7
7
|
|
8
|
+
if RUBY_VERSION < '1.9'
|
9
|
+
gem 'nokogiri', '~> 1.5.0'
|
10
|
+
elsif RUBY_VERSION < '2.1'
|
11
|
+
gem 'nokogiri', '>= 1.5.0', '<= 1.6.8.1'
|
12
|
+
else
|
13
|
+
gem 'nokogiri', '>= 1.5.0'
|
14
|
+
end
|
15
|
+
|
8
16
|
group :test do
|
9
17
|
if RUBY_VERSION < '1.9'
|
10
|
-
gem 'nokogiri', '~> 1.5.0'
|
11
18
|
gem 'ruby-debug', '~> 0.10.4'
|
12
19
|
elsif RUBY_VERSION < '2.0'
|
13
20
|
gem 'debugger-linecache', '~> 1.2.0'
|
@@ -23,5 +30,6 @@ group :test do
|
|
23
30
|
gem 'shoulda', '~> 2.11'
|
24
31
|
gem 'systemu', '~> 2'
|
25
32
|
gem 'test-unit', '~> 3.0.9'
|
33
|
+
gem 'minitest', '~> 5.5'
|
26
34
|
gem 'timecop', '<= 0.6.0'
|
27
35
|
end
|
@@ -1,16 +1,49 @@
|
|
1
1
|
require "base64"
|
2
|
-
require "uuid"
|
3
2
|
require "zlib"
|
4
3
|
require "cgi"
|
5
|
-
require "
|
6
|
-
require "rexml/xpath"
|
4
|
+
require "onelogin/ruby-saml/utils"
|
7
5
|
|
8
6
|
module OneLogin
|
9
7
|
module RubySaml
|
10
|
-
|
8
|
+
|
11
9
|
class Authrequest
|
10
|
+
# AuthNRequest ID
|
11
|
+
attr_reader :uuid
|
12
|
+
|
13
|
+
# Initializes the AuthNRequest. An Authrequest Object.
|
14
|
+
# Asigns an ID, a random uuid.
|
15
|
+
#
|
16
|
+
def initialize
|
17
|
+
@uuid = OneLogin::RubySaml::Utils.uuid
|
18
|
+
end
|
19
|
+
|
12
20
|
def create(settings, params = {})
|
13
|
-
params =
|
21
|
+
params = create_params(settings, params)
|
22
|
+
params_prefix = (settings.idp_sso_target_url =~ /\?/) ? '&' : '?'
|
23
|
+
saml_request = CGI.escape(params.delete("SAMLRequest"))
|
24
|
+
request_params = "#{params_prefix}SAMLRequest=#{saml_request}"
|
25
|
+
params.each_pair do |key, value|
|
26
|
+
request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
27
|
+
end
|
28
|
+
raise "Invalid settings, idp_sso_target_url is not set!" if settings.idp_sso_target_url.nil?
|
29
|
+
@login_url = settings.idp_sso_target_url + request_params
|
30
|
+
end
|
31
|
+
|
32
|
+
# Creates the Get parameters for the request.
|
33
|
+
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
34
|
+
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
35
|
+
# @return [Hash] Parameters
|
36
|
+
#
|
37
|
+
def create_params(settings, params={})
|
38
|
+
# The method expects :RelayState but sometimes we get 'RelayState' instead.
|
39
|
+
# Based on the HashWithIndifferentAccess value in Rails we could experience
|
40
|
+
# conflicts so this line will solve them.
|
41
|
+
relay_state = params[:RelayState] || params['RelayState']
|
42
|
+
|
43
|
+
if relay_state.nil?
|
44
|
+
params.delete(:RelayState)
|
45
|
+
params.delete('RelayState')
|
46
|
+
end
|
14
47
|
|
15
48
|
request_doc = create_authentication_xml_doc(settings)
|
16
49
|
request_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
|
@@ -20,28 +53,49 @@ module OneLogin
|
|
20
53
|
|
21
54
|
Logging.debug "Created AuthnRequest: #{request}"
|
22
55
|
|
23
|
-
request
|
56
|
+
request = Zlib::Deflate.deflate(request, 9)[2..-5] if settings.compress_request
|
24
57
|
if Base64.respond_to?('strict_encode64')
|
25
|
-
|
58
|
+
base64_request = Base64.strict_encode64(request)
|
26
59
|
else
|
27
|
-
|
60
|
+
base64_request = Base64.encode64(request).gsub(/\n/, "")
|
61
|
+
end
|
62
|
+
|
63
|
+
request_params = {"SAMLRequest" => base64_request}
|
64
|
+
|
65
|
+
if settings.security[:authn_requests_signed] && !settings.security[:embed_sign] && settings.private_key
|
66
|
+
params['SigAlg'] = settings.security[:signature_method]
|
67
|
+
url_string = OneLogin::RubySaml::Utils.build_query(
|
68
|
+
:type => 'SAMLRequest',
|
69
|
+
:data => base64_request,
|
70
|
+
:relay_state => relay_state,
|
71
|
+
:sig_alg => params['SigAlg']
|
72
|
+
)
|
73
|
+
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
|
74
|
+
signature = settings.get_sp_key.sign(sign_algorithm.new, url_string)
|
75
|
+
if Base64.respond_to?('strict_encode64')
|
76
|
+
params['Signature'] = Base64.strict_encode64(signature)
|
77
|
+
else
|
78
|
+
params['Signature'] = Base64.encode64(signature).gsub(/\n/, "")
|
79
|
+
end
|
28
80
|
end
|
29
|
-
encoded_request = CGI.escape(base64_request)
|
30
|
-
params_prefix = (settings.idp_sso_target_url =~ /\?/) ? '&' : '?'
|
31
|
-
request_params = "#{params_prefix}SAMLRequest=#{encoded_request}"
|
32
81
|
|
33
82
|
params.each_pair do |key, value|
|
34
|
-
request_params
|
83
|
+
request_params[key] = value.to_s
|
35
84
|
end
|
36
85
|
|
37
|
-
|
86
|
+
request_params
|
38
87
|
end
|
39
88
|
|
40
89
|
def create_authentication_xml_doc(settings)
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
90
|
+
document = create_xml_document(settings)
|
91
|
+
sign_document(document, settings)
|
92
|
+
end
|
93
|
+
|
94
|
+
def create_xml_document(settings)
|
95
|
+
time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
96
|
+
|
97
|
+
request_doc = XMLSecurity::Document.new
|
98
|
+
request_doc.uuid = uuid
|
45
99
|
|
46
100
|
root = request_doc.add_element "samlp:AuthnRequest", { "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol", "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
|
47
101
|
root.attributes['ID'] = uuid
|
@@ -97,6 +151,17 @@ module OneLogin
|
|
97
151
|
request_doc
|
98
152
|
end
|
99
153
|
|
154
|
+
def sign_document(document, settings)
|
155
|
+
# embed signature
|
156
|
+
if settings.security[:authn_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
|
157
|
+
private_key = settings.get_sp_key
|
158
|
+
cert = settings.get_sp_cert
|
159
|
+
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
160
|
+
end
|
161
|
+
|
162
|
+
document
|
163
|
+
end
|
164
|
+
|
100
165
|
end
|
101
166
|
end
|
102
167
|
end
|
@@ -1,49 +1,106 @@
|
|
1
1
|
require "base64"
|
2
|
-
require "uuid"
|
3
2
|
require "zlib"
|
4
3
|
require "cgi"
|
4
|
+
require 'rexml/document'
|
5
|
+
require "onelogin/ruby-saml/utils"
|
5
6
|
|
6
7
|
module OneLogin
|
7
8
|
module RubySaml
|
8
|
-
|
9
|
+
|
9
10
|
class Logoutrequest
|
10
11
|
|
11
12
|
attr_reader :uuid # Can be obtained if neccessary
|
12
13
|
|
13
14
|
def initialize
|
14
|
-
@uuid =
|
15
|
+
@uuid = OneLogin::RubySaml::Utils.uuid
|
15
16
|
end
|
16
17
|
|
17
18
|
def create(settings, params={})
|
18
|
-
|
19
|
+
params = create_params(settings, params)
|
20
|
+
params_prefix = (settings.idp_slo_target_url =~ /\?/) ? '&' : '?'
|
21
|
+
saml_request = CGI.escape(params.delete("SAMLRequest"))
|
22
|
+
request_params = "#{params_prefix}SAMLRequest=#{saml_request}"
|
23
|
+
params.each_pair do |key, value|
|
24
|
+
request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
25
|
+
end
|
26
|
+
@logout_url = settings.idp_slo_target_url + request_params
|
27
|
+
end
|
28
|
+
|
29
|
+
# Creates the Get parameters for the logout request.
|
30
|
+
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
31
|
+
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
32
|
+
# @return [Hash] Parameters
|
33
|
+
#
|
34
|
+
def create_params(settings, params={})
|
35
|
+
# The method expects :RelayState but sometimes we get 'RelayState' instead.
|
36
|
+
# Based on the HashWithIndifferentAccess value in Rails we could experience
|
37
|
+
# conflicts so this line will solve them.
|
38
|
+
relay_state = params[:RelayState] || params['RelayState']
|
39
|
+
|
40
|
+
if relay_state.nil?
|
41
|
+
params.delete(:RelayState)
|
42
|
+
params.delete('RelayState')
|
43
|
+
end
|
44
|
+
|
45
|
+
request_doc = create_logout_request_xml_doc(settings)
|
46
|
+
request_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
|
47
|
+
|
19
48
|
request = ""
|
20
49
|
request_doc.write(request)
|
21
50
|
|
22
|
-
|
51
|
+
Logging.debug "Created SLO Logout Request: #{request}"
|
52
|
+
|
53
|
+
request = Zlib::Deflate.deflate(request, 9)[2..-5] if settings.compress_request
|
23
54
|
if Base64.respond_to?('strict_encode64')
|
24
|
-
|
55
|
+
base64_request = Base64.strict_encode64(request)
|
25
56
|
else
|
26
|
-
|
57
|
+
base64_request = Base64.encode64(request).gsub(/\n/, "")
|
27
58
|
end
|
28
|
-
|
59
|
+
request_params = {"SAMLRequest" => base64_request}
|
29
60
|
|
30
|
-
|
31
|
-
|
61
|
+
if settings.security[:logout_requests_signed] && !settings.security[:embed_sign] && settings.private_key
|
62
|
+
params['SigAlg'] = settings.security[:signature_method]
|
63
|
+
url_string = OneLogin::RubySaml::Utils.build_query(
|
64
|
+
:type => 'SAMLRequest',
|
65
|
+
:data => base64_request,
|
66
|
+
:relay_state => relay_state,
|
67
|
+
:sig_alg => params['SigAlg']
|
68
|
+
)
|
69
|
+
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
|
70
|
+
signature = settings.get_sp_key.sign(sign_algorithm.new, url_string)
|
71
|
+
if Base64.respond_to?('strict_encode64')
|
72
|
+
params['Signature'] = Base64.strict_encode64(signature)
|
73
|
+
else
|
74
|
+
params['Signature'] = Base64.encode64(signature).gsub(/\n/, "")
|
75
|
+
end
|
76
|
+
end
|
32
77
|
|
33
78
|
params.each_pair do |key, value|
|
34
|
-
request_params
|
79
|
+
request_params[key] = value.to_s
|
35
80
|
end
|
36
81
|
|
37
|
-
|
82
|
+
request_params
|
38
83
|
end
|
39
84
|
|
40
|
-
|
85
|
+
# Creates the SAMLRequest String.
|
86
|
+
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
87
|
+
# @return [String] The SAMLRequest String.
|
88
|
+
#
|
89
|
+
def create_logout_request_xml_doc(settings)
|
90
|
+
document = create_xml_document(settings)
|
91
|
+
sign_document(document, settings)
|
92
|
+
end
|
93
|
+
|
94
|
+
def create_xml_document(settings, request_doc=nil)
|
95
|
+
time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
41
96
|
|
42
|
-
|
97
|
+
if request_doc.nil?
|
98
|
+
request_doc = XMLSecurity::Document.new
|
99
|
+
request_doc.uuid = uuid
|
100
|
+
end
|
43
101
|
|
44
|
-
request_doc = REXML::Document.new
|
45
102
|
root = request_doc.add_element "samlp:LogoutRequest", { "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol" }
|
46
|
-
root.attributes['ID'] =
|
103
|
+
root.attributes['ID'] = uuid
|
47
104
|
root.attributes['IssueInstant'] = time
|
48
105
|
root.attributes['Version'] = "2.0"
|
49
106
|
|
@@ -57,8 +114,6 @@ module OneLogin
|
|
57
114
|
name_id.attributes['NameQualifier'] = settings.sp_name_qualifier if settings.sp_name_qualifier
|
58
115
|
name_id.attributes['Format'] = settings.name_identifier_format if settings.name_identifier_format
|
59
116
|
name_id.text = settings.name_identifier_value
|
60
|
-
else
|
61
|
-
raise ValidationError.new("Missing required name identifier")
|
62
117
|
end
|
63
118
|
|
64
119
|
if settings.sessionindex
|
@@ -81,6 +136,23 @@ module OneLogin
|
|
81
136
|
end
|
82
137
|
request_doc
|
83
138
|
end
|
139
|
+
|
140
|
+
def sign_document(document, settings)
|
141
|
+
# embed signature
|
142
|
+
if settings.security[:logout_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
|
143
|
+
private_key = settings.get_sp_key
|
144
|
+
cert = settings.get_sp_cert
|
145
|
+
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
146
|
+
end
|
147
|
+
|
148
|
+
document
|
149
|
+
end
|
150
|
+
|
151
|
+
# Leave due compatibility
|
152
|
+
def create_unauth_xml_doc(settings, params)
|
153
|
+
request_doc = ReXML::Document.new
|
154
|
+
create_xml_document(settings, request_doc)
|
155
|
+
end
|
84
156
|
end
|
85
157
|
end
|
86
158
|
end
|
@@ -1,27 +1,52 @@
|
|
1
|
+
require "xml_security"
|
2
|
+
require "onelogin/ruby-saml/utils"
|
3
|
+
|
1
4
|
module OneLogin
|
2
5
|
module RubySaml
|
3
6
|
class Settings
|
4
|
-
def initialize(overrides = {})
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
def initialize(overrides = {}, keep_security_attributes = false)
|
8
|
+
if keep_security_attributes
|
9
|
+
security_attributes = overrides.delete(:security) || {}
|
10
|
+
config = DEFAULTS.merge(overrides)
|
11
|
+
config[:security] = DEFAULTS[:security].merge(security_attributes)
|
12
|
+
else
|
13
|
+
config = DEFAULTS.merge(overrides)
|
14
|
+
end
|
15
|
+
|
16
|
+
config.each do |k,v|
|
17
|
+
acc = "#{k.to_s}=".to_sym
|
18
|
+
if respond_to? acc
|
19
|
+
value = v.is_a?(Hash) ? v.dup : v
|
20
|
+
send(acc, value)
|
21
|
+
end
|
22
|
+
end
|
10
23
|
end
|
11
|
-
|
12
|
-
|
13
|
-
attr_accessor :
|
24
|
+
|
25
|
+
#idp data
|
26
|
+
attr_accessor :idp_sso_target_url
|
27
|
+
attr_accessor :idp_cert_fingerprint
|
28
|
+
attr_accessor :idp_cert
|
14
29
|
attr_accessor :idp_slo_target_url
|
30
|
+
#sp data
|
31
|
+
attr_accessor :sp_entity_id
|
32
|
+
attr_accessor :assertion_consumer_service_url
|
33
|
+
attr_accessor :authn_context
|
34
|
+
attr_accessor :sp_name_qualifier
|
35
|
+
attr_accessor :name_identifier_format
|
15
36
|
attr_accessor :name_identifier_value
|
16
37
|
attr_accessor :name_identifier_value_requested
|
17
38
|
attr_accessor :sessionindex
|
18
39
|
attr_accessor :assertion_consumer_logout_service_url
|
19
40
|
attr_accessor :compress_request
|
41
|
+
attr_accessor :compress_response
|
20
42
|
attr_accessor :double_quote_xml_attribute_values
|
21
43
|
attr_accessor :force_authn
|
22
44
|
attr_accessor :passive
|
23
45
|
attr_accessor :protocol_binding
|
24
|
-
|
46
|
+
attr_accessor :certificate
|
47
|
+
attr_accessor :private_key
|
48
|
+
# Work-flow
|
49
|
+
attr_accessor :security
|
25
50
|
# Compability
|
26
51
|
attr_accessor :issuer
|
27
52
|
attr_accessor :assertion_consumer_logout_service_url
|
@@ -92,14 +117,50 @@ module OneLogin
|
|
92
117
|
@single_logout_service_binding = url
|
93
118
|
end
|
94
119
|
|
120
|
+
# @return [OpenSSL::X509::Certificate|nil] Build the SP certificate from the settings (previously format it)
|
121
|
+
#
|
122
|
+
def get_sp_cert
|
123
|
+
return nil if certificate.nil? || certificate.empty?
|
124
|
+
|
125
|
+
formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
|
126
|
+
OpenSSL::X509::Certificate.new(formatted_cert)
|
127
|
+
end
|
128
|
+
|
129
|
+
# @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings (previously format it)
|
130
|
+
#
|
131
|
+
def get_sp_cert_new
|
132
|
+
return nil if certificate_new.nil? || certificate_new.empty?
|
133
|
+
|
134
|
+
formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate_new)
|
135
|
+
OpenSSL::X509::Certificate.new(formatted_cert)
|
136
|
+
end
|
137
|
+
|
138
|
+
# @return [OpenSSL::PKey::RSA] Build the SP private from the settings (previously format it)
|
139
|
+
#
|
140
|
+
def get_sp_key
|
141
|
+
return nil if private_key.nil? || private_key.empty?
|
142
|
+
|
143
|
+
formatted_private_key = OneLogin::RubySaml::Utils.format_private_key(private_key)
|
144
|
+
OpenSSL::PKey::RSA.new(formatted_private_key)
|
145
|
+
end
|
146
|
+
|
95
147
|
private
|
96
148
|
|
97
149
|
DEFAULTS = {
|
98
150
|
:compress_request => true,
|
151
|
+
:compress_response => true,
|
99
152
|
:double_quote_xml_attribute_values => false,
|
100
153
|
:assertion_consumer_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze,
|
101
|
-
:single_logout_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze
|
102
|
-
|
154
|
+
:single_logout_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze,
|
155
|
+
:security => {
|
156
|
+
:authn_requests_signed => false,
|
157
|
+
:logout_requests_signed => false,
|
158
|
+
:logout_responses_signed => false,
|
159
|
+
:embed_sign => false,
|
160
|
+
:digest_method => XMLSecurity::Document::SHA1,
|
161
|
+
:signature_method => XMLSecurity::Document::RSA_SHA1
|
162
|
+
}.freeze
|
163
|
+
}.freeze
|
103
164
|
end
|
104
165
|
end
|
105
166
|
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require "base64"
|
2
|
+
require "zlib"
|
3
|
+
require "cgi"
|
4
|
+
require "onelogin/ruby-saml/utils"
|
5
|
+
|
6
|
+
module OneLogin
|
7
|
+
module RubySaml
|
8
|
+
|
9
|
+
# SAML2 Logout Response (SLO SP initiated)
|
10
|
+
#
|
11
|
+
class SloLogoutresponse
|
12
|
+
|
13
|
+
# Logout Response ID
|
14
|
+
attr_reader :uuid
|
15
|
+
|
16
|
+
# Initializes the Logout Response. A SloLogoutresponse Object.
|
17
|
+
# Asigns an ID, a random uuid.
|
18
|
+
#
|
19
|
+
def initialize
|
20
|
+
@uuid = OneLogin::RubySaml::Utils.uuid
|
21
|
+
end
|
22
|
+
|
23
|
+
# Creates the Logout Response string.
|
24
|
+
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
25
|
+
# @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
|
26
|
+
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
27
|
+
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
28
|
+
# @return [String] Logout Request string that includes the SAMLRequest
|
29
|
+
#
|
30
|
+
def create(settings, request_id = nil, logout_message = nil, params = {})
|
31
|
+
params = create_params(settings, request_id, logout_message, params)
|
32
|
+
params_prefix = (settings.idp_slo_target_url =~ /\?/) ? '&' : '?'
|
33
|
+
saml_response = CGI.escape(params.delete("SAMLResponse"))
|
34
|
+
response_params = "#{params_prefix}SAMLResponse=#{saml_response}"
|
35
|
+
params.each_pair do |key, value|
|
36
|
+
response_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
37
|
+
end
|
38
|
+
|
39
|
+
@logout_url = settings.idp_slo_target_url + response_params
|
40
|
+
end
|
41
|
+
|
42
|
+
# Creates the Get parameters for the logout response.
|
43
|
+
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
44
|
+
# @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
|
45
|
+
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
46
|
+
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
47
|
+
# @return [Hash] Parameters
|
48
|
+
#
|
49
|
+
def create_params(settings, request_id = nil, logout_message = nil, params = {})
|
50
|
+
# The method expects :RelayState but sometimes we get 'RelayState' instead.
|
51
|
+
# Based on the HashWithIndifferentAccess value in Rails we could experience
|
52
|
+
# conflicts so this line will solve them.
|
53
|
+
relay_state = params[:RelayState] || params['RelayState']
|
54
|
+
|
55
|
+
if relay_state.nil?
|
56
|
+
params.delete(:RelayState)
|
57
|
+
params.delete('RelayState')
|
58
|
+
end
|
59
|
+
|
60
|
+
response_doc = create_logout_response_xml_doc(settings, request_id, logout_message)
|
61
|
+
response_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
|
62
|
+
|
63
|
+
response = ""
|
64
|
+
response_doc.write(response)
|
65
|
+
|
66
|
+
Logging.debug "Created SLO Logout Response: #{response}"
|
67
|
+
|
68
|
+
response = Zlib::Deflate.deflate(response, 9)[2..-5] if settings.compress_response
|
69
|
+
if Base64.respond_to?('strict_encode64')
|
70
|
+
base64_response = Base64.strict_encode64(response)
|
71
|
+
else
|
72
|
+
base64_response = Base64.encode64(response).gsub(/\n/, "")
|
73
|
+
end
|
74
|
+
response_params = {"SAMLResponse" => base64_response}
|
75
|
+
|
76
|
+
if settings.security[:logout_responses_signed] && !settings.security[:embed_sign] && settings.private_key
|
77
|
+
params['SigAlg'] = settings.security[:signature_method]
|
78
|
+
url_string = OneLogin::RubySaml::Utils.build_query(
|
79
|
+
:type => 'SAMLResponse',
|
80
|
+
:data => base64_response,
|
81
|
+
:relay_state => relay_state,
|
82
|
+
:sig_alg => params['SigAlg']
|
83
|
+
)
|
84
|
+
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
|
85
|
+
signature = settings.get_sp_key.sign(sign_algorithm.new, url_string)
|
86
|
+
if Base64.respond_to?('strict_encode64')
|
87
|
+
params['Signature'] = Base64.strict_encode64(signature)
|
88
|
+
else
|
89
|
+
params['Signature'] = Base64.encode64(signature).gsub(/\n/, "")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
params.each_pair do |key, value|
|
94
|
+
response_params[key] = value.to_s
|
95
|
+
end
|
96
|
+
|
97
|
+
response_params
|
98
|
+
end
|
99
|
+
|
100
|
+
# Creates the SAMLResponse String.
|
101
|
+
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
102
|
+
# @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
|
103
|
+
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
104
|
+
# @return [String] The SAMLResponse String.
|
105
|
+
#
|
106
|
+
def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil)
|
107
|
+
document = create_xml_document(settings, request_id, logout_message)
|
108
|
+
sign_document(document, settings)
|
109
|
+
end
|
110
|
+
|
111
|
+
def create_xml_document(settings, request_id = nil, logout_message = nil)
|
112
|
+
time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
113
|
+
|
114
|
+
response_doc = XMLSecurity::Document.new
|
115
|
+
response_doc.uuid = uuid
|
116
|
+
|
117
|
+
root = response_doc.add_element 'samlp:LogoutResponse', { 'xmlns:samlp' => 'urn:oasis:names:tc:SAML:2.0:protocol', "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
|
118
|
+
root.attributes['ID'] = uuid
|
119
|
+
root.attributes['IssueInstant'] = time
|
120
|
+
root.attributes['Version'] = '2.0'
|
121
|
+
root.attributes['InResponseTo'] = request_id unless request_id.nil?
|
122
|
+
root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil?
|
123
|
+
|
124
|
+
if settings.sp_entity_id != nil
|
125
|
+
issuer = root.add_element "saml:Issuer"
|
126
|
+
issuer.text = settings.sp_entity_id
|
127
|
+
end
|
128
|
+
|
129
|
+
# add success message
|
130
|
+
status = root.add_element 'samlp:Status'
|
131
|
+
|
132
|
+
# success status code
|
133
|
+
status_code = status.add_element 'samlp:StatusCode'
|
134
|
+
status_code.attributes['Value'] = 'urn:oasis:names:tc:SAML:2.0:status:Success'
|
135
|
+
|
136
|
+
# success status message
|
137
|
+
logout_message ||= 'Successfully Signed Out'
|
138
|
+
status_message = status.add_element 'samlp:StatusMessage'
|
139
|
+
status_message.text = logout_message
|
140
|
+
|
141
|
+
response_doc
|
142
|
+
end
|
143
|
+
|
144
|
+
def sign_document(document, settings)
|
145
|
+
# embed signature
|
146
|
+
if settings.security[:logout_responses_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
|
147
|
+
private_key = settings.get_sp_key
|
148
|
+
cert = settings.get_sp_cert
|
149
|
+
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
150
|
+
end
|
151
|
+
|
152
|
+
document
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|