ruby-saml 0.8.9 → 0.8.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of 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
|