ruby-saml 0.8.16 → 0.9
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.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.travis.yml +1 -6
- data/Gemfile +2 -12
- data/README.md +363 -35
- data/Rakefile +14 -0
- data/changelog.md +22 -9
- data/lib/onelogin/ruby-saml/attribute_service.rb +34 -0
- data/lib/onelogin/ruby-saml/attributes.rb +26 -64
- data/lib/onelogin/ruby-saml/authrequest.rb +47 -89
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +87 -0
- data/lib/onelogin/ruby-saml/logoutrequest.rb +34 -93
- data/lib/onelogin/ruby-saml/logoutresponse.rb +25 -24
- data/lib/onelogin/ruby-saml/metadata.rb +46 -16
- data/lib/onelogin/ruby-saml/response.rb +62 -322
- data/lib/onelogin/ruby-saml/saml_message.rb +78 -0
- data/lib/onelogin/ruby-saml/settings.rb +54 -121
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +26 -61
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +27 -84
- data/lib/onelogin/ruby-saml/utils.rb +32 -199
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/ruby-saml.rb +5 -2
- data/lib/schemas/{saml20assertion_schema.xsd → saml-schema-assertion-2.0.xsd} +283 -283
- data/lib/schemas/saml-schema-authn-context-2.0.xsd +23 -0
- data/lib/schemas/saml-schema-authn-context-types-2.0.xsd +821 -0
- data/lib/schemas/saml-schema-metadata-2.0.xsd +339 -0
- data/lib/schemas/{saml20protocol_schema.xsd → saml-schema-protocol-2.0.xsd} +302 -302
- data/lib/schemas/sstc-metadata-attr.xsd +35 -0
- data/lib/schemas/sstc-saml-attribute-ext.xsd +25 -0
- data/lib/schemas/sstc-saml-metadata-algsupport-v1.0.xsd +41 -0
- data/lib/schemas/sstc-saml-metadata-ui-v1.0.xsd +89 -0
- data/lib/schemas/{xenc_schema.xsd → xenc-schema.xsd} +1 -11
- data/lib/schemas/xml.xsd +287 -0
- data/lib/schemas/{xmldsig_schema.xsd → xmldsig-core-schema.xsd} +0 -9
- data/lib/xml_security.rb +83 -235
- data/ruby-saml.gemspec +1 -0
- data/test/idp_metadata_parser_test.rb +54 -0
- data/test/logoutrequest_test.rb +68 -144
- data/test/logoutresponse_test.rb +43 -25
- data/test/metadata_test.rb +87 -0
- data/test/request_test.rb +103 -90
- data/test/response_test.rb +181 -471
- data/test/responses/idp_descriptor.xml +3 -0
- data/test/responses/logoutresponse_fixtures.rb +5 -5
- data/test/responses/response_no_cert_and_encrypted_attrs.xml +29 -0
- data/test/responses/response_with_multiple_attribute_values.xml +1 -1
- data/test/responses/slo_request.xml +4 -0
- data/test/settings_test.rb +25 -112
- data/test/slo_logoutrequest_test.rb +41 -44
- data/test/slo_logoutresponse_test.rb +87 -167
- data/test/test_helper.rb +27 -102
- data/test/xml_security_test.rb +114 -337
- metadata +34 -84
- data/lib/onelogin/ruby-saml/setting_error.rb +0 -6
- data/test/certificates/certificate.der +0 -0
- data/test/certificates/formatted_certificate +0 -14
- data/test/certificates/formatted_chained_certificate +0 -42
- data/test/certificates/formatted_private_key +0 -12
- data/test/certificates/formatted_rsa_private_key +0 -12
- data/test/certificates/invalid_certificate1 +0 -1
- data/test/certificates/invalid_certificate2 +0 -1
- data/test/certificates/invalid_certificate3 +0 -12
- data/test/certificates/invalid_chained_certificate1 +0 -1
- data/test/certificates/invalid_private_key1 +0 -1
- data/test/certificates/invalid_private_key2 +0 -1
- data/test/certificates/invalid_private_key3 +0 -10
- data/test/certificates/invalid_rsa_private_key1 +0 -1
- data/test/certificates/invalid_rsa_private_key2 +0 -1
- data/test/certificates/invalid_rsa_private_key3 +0 -10
- data/test/certificates/ruby-saml-2.crt +0 -15
- data/test/requests/logoutrequest_fixtures.rb +0 -47
- data/test/responses/encrypted_new_attack.xml.base64 +0 -1
- data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
- data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
- data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
- data/test/responses/invalids/no_signature.xml.base64 +0 -1
- data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
- data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
- data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
- data/test/responses/response_node_text_attack.xml.base64 +0 -1
- data/test/responses/response_with_concealed_signed_assertion.xml +0 -51
- data/test/responses/response_with_doubled_signed_assertion.xml +0 -49
- data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
- data/test/responses/response_with_signed_assertion_3.xml +0 -30
- data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
- data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
- data/test/responses/response_wrapped.xml.base64 +0 -150
- data/test/responses/valid_response.xml.base64 +0 -1
- data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
- data/test/utils_test.rb +0 -231
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
require 'cgi'
|
|
2
|
+
require 'zlib'
|
|
3
|
+
require 'base64'
|
|
4
|
+
require "rexml/document"
|
|
5
|
+
require "rexml/xpath"
|
|
6
|
+
|
|
7
|
+
module OneLogin
|
|
8
|
+
module RubySaml
|
|
9
|
+
class SamlMessage
|
|
10
|
+
include REXML
|
|
11
|
+
|
|
12
|
+
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
|
13
|
+
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
|
|
14
|
+
|
|
15
|
+
def valid_saml?(document, soft = true)
|
|
16
|
+
Dir.chdir(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'schemas'))) do
|
|
17
|
+
@schema = Nokogiri::XML::Schema(IO.read('saml-schema-protocol-2.0.xsd'))
|
|
18
|
+
@xml = Nokogiri::XML(document.to_s)
|
|
19
|
+
end
|
|
20
|
+
if soft
|
|
21
|
+
@schema.validate(@xml).map{ return false }
|
|
22
|
+
else
|
|
23
|
+
@schema.validate(@xml).map{ |error| validation_error("#{error.message}\n\n#{@xml.to_s}") }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def validation_error(message)
|
|
28
|
+
raise ValidationError.new(message)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def decode_raw_saml(saml)
|
|
34
|
+
if saml =~ /^</
|
|
35
|
+
return saml
|
|
36
|
+
elsif (decoded = decode(saml)) =~ /^</
|
|
37
|
+
return decoded
|
|
38
|
+
elsif (inflated = inflate(decoded)) =~ /^</
|
|
39
|
+
return inflated
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
return nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def encode_raw_saml(saml, settings)
|
|
46
|
+
saml = Zlib::Deflate.deflate(saml, 9)[2..-5] if settings.compress_request
|
|
47
|
+
base64_saml = Base64.encode64(saml)
|
|
48
|
+
return CGI.escape(base64_saml)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def decode(encoded)
|
|
52
|
+
Base64.decode64(encoded)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def encode(encoded)
|
|
56
|
+
Base64.encode64(encoded).gsub(/\n/, "")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def escape(unescaped)
|
|
60
|
+
CGI.escape(unescaped)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def unescape(escaped)
|
|
64
|
+
CGI.unescape(escaped)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def inflate(deflated)
|
|
68
|
+
zlib = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
|
69
|
+
zlib.inflate(deflated)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def deflate(inflated)
|
|
73
|
+
Zlib::Deflate.deflate(inflated, 9)[2..-5]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -1,81 +1,48 @@
|
|
|
1
|
-
require "xml_security"
|
|
2
|
-
require "onelogin/ruby-saml/utils"
|
|
3
|
-
|
|
4
1
|
module OneLogin
|
|
5
2
|
module RubySaml
|
|
6
3
|
class Settings
|
|
7
|
-
def initialize(overrides = {}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
4
|
+
def initialize(overrides = {})
|
|
5
|
+
config = DEFAULTS.merge(overrides)
|
|
6
|
+
config.each do |k,v|
|
|
7
|
+
acc = "#{k.to_s}=".to_sym
|
|
8
|
+
self.send(acc, v) if self.respond_to? acc
|
|
9
|
+
end
|
|
10
|
+
@attribute_consuming_service = AttributeService.new
|
|
23
11
|
end
|
|
24
12
|
|
|
25
|
-
#
|
|
13
|
+
# IdP Data
|
|
14
|
+
attr_accessor :idp_entity_id
|
|
26
15
|
attr_accessor :idp_sso_target_url
|
|
27
|
-
attr_accessor :idp_cert_fingerprint
|
|
28
|
-
attr_accessor :idp_cert
|
|
29
16
|
attr_accessor :idp_slo_target_url
|
|
30
|
-
attr_accessor :
|
|
31
|
-
|
|
32
|
-
|
|
17
|
+
attr_accessor :idp_cert
|
|
18
|
+
attr_accessor :idp_cert_fingerprint
|
|
19
|
+
# SP Data
|
|
20
|
+
attr_accessor :issuer
|
|
33
21
|
attr_accessor :assertion_consumer_service_url
|
|
34
|
-
attr_accessor :
|
|
22
|
+
attr_accessor :assertion_consumer_service_binding
|
|
35
23
|
attr_accessor :sp_name_qualifier
|
|
36
24
|
attr_accessor :name_identifier_format
|
|
37
25
|
attr_accessor :name_identifier_value
|
|
38
|
-
attr_accessor :name_identifier_value_requested
|
|
39
26
|
attr_accessor :sessionindex
|
|
40
27
|
attr_accessor :compress_request
|
|
41
28
|
attr_accessor :compress_response
|
|
42
29
|
attr_accessor :double_quote_xml_attribute_values
|
|
43
|
-
attr_accessor :force_authn
|
|
44
30
|
attr_accessor :passive
|
|
45
31
|
attr_accessor :protocol_binding
|
|
32
|
+
attr_accessor :attributes_index
|
|
33
|
+
attr_accessor :force_authn
|
|
34
|
+
attr_accessor :security
|
|
46
35
|
attr_accessor :certificate
|
|
47
36
|
attr_accessor :private_key
|
|
48
|
-
|
|
49
|
-
attr_accessor :
|
|
37
|
+
attr_accessor :authn_context
|
|
38
|
+
attr_accessor :authn_context_comparison
|
|
39
|
+
attr_accessor :authn_context_decl_ref
|
|
40
|
+
attr_reader :attribute_consuming_service
|
|
50
41
|
# Compability
|
|
51
|
-
attr_accessor :issuer
|
|
52
42
|
attr_accessor :assertion_consumer_logout_service_url
|
|
53
43
|
attr_accessor :assertion_consumer_logout_service_binding
|
|
54
44
|
|
|
55
|
-
|
|
56
|
-
#
|
|
57
|
-
def sp_entity_id
|
|
58
|
-
val = nil
|
|
59
|
-
if @sp_entity_id.nil?
|
|
60
|
-
if @issuer
|
|
61
|
-
val = @issuer
|
|
62
|
-
end
|
|
63
|
-
else
|
|
64
|
-
val = @sp_entity_id
|
|
65
|
-
end
|
|
66
|
-
val
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# Setter for SP Entity ID.
|
|
70
|
-
# @param val [String].
|
|
71
|
-
#
|
|
72
|
-
def sp_entity_id=(val)
|
|
73
|
-
@sp_entity_id = val
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# @return [String] Single Logout Service URL.
|
|
77
|
-
#
|
|
78
|
-
def single_logout_service_url
|
|
45
|
+
def single_logout_service_url()
|
|
79
46
|
val = nil
|
|
80
47
|
if @single_logout_service_url.nil?
|
|
81
48
|
if @assertion_consumer_logout_service_url
|
|
@@ -87,16 +54,12 @@ module OneLogin
|
|
|
87
54
|
val
|
|
88
55
|
end
|
|
89
56
|
|
|
90
|
-
#
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def single_logout_service_url=(url)
|
|
94
|
-
@single_logout_service_url = url
|
|
57
|
+
# setter
|
|
58
|
+
def single_logout_service_url=(val)
|
|
59
|
+
@single_logout_service_url = val
|
|
95
60
|
end
|
|
96
61
|
|
|
97
|
-
|
|
98
|
-
#
|
|
99
|
-
def single_logout_service_binding
|
|
62
|
+
def single_logout_service_binding()
|
|
100
63
|
val = nil
|
|
101
64
|
if @single_logout_service_binding.nil?
|
|
102
65
|
if @assertion_consumer_logout_service_binding
|
|
@@ -108,76 +71,46 @@ module OneLogin
|
|
|
108
71
|
val
|
|
109
72
|
end
|
|
110
73
|
|
|
111
|
-
#
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
# @param url [String]
|
|
115
|
-
#
|
|
116
|
-
def single_logout_service_binding=(url)
|
|
117
|
-
@single_logout_service_binding = url
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Calculates the fingerprint of the IdP x509 certificate.
|
|
121
|
-
# @return [String] The fingerprint
|
|
122
|
-
#
|
|
123
|
-
def get_fingerprint
|
|
124
|
-
idp_cert_fingerprint || begin
|
|
125
|
-
idp_cert = get_idp_cert
|
|
126
|
-
if idp_cert
|
|
127
|
-
Digest::SHA1.hexdigest(idp_cert.to_der).upcase.scan(/../).join(":")
|
|
128
|
-
end
|
|
129
|
-
end
|
|
74
|
+
# setter
|
|
75
|
+
def single_logout_service_binding=(val)
|
|
76
|
+
@single_logout_service_binding = val
|
|
130
77
|
end
|
|
131
78
|
|
|
132
|
-
# @return [OpenSSL::X509::Certificate|nil] Build the IdP certificate from the settings (previously format it)
|
|
133
|
-
#
|
|
134
|
-
def get_idp_cert
|
|
135
|
-
return nil if idp_cert.nil?
|
|
136
|
-
|
|
137
|
-
if idp_cert.respond_to?(:to_pem)
|
|
138
|
-
idp_cert
|
|
139
|
-
else
|
|
140
|
-
return nil if idp_cert.empty?
|
|
141
|
-
formatted_cert = OneLogin::RubySaml::Utils.format_cert(idp_cert)
|
|
142
|
-
OpenSSL::X509::Certificate.new(formatted_cert)
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
# @return [OpenSSL::X509::Certificate|nil] Build the SP certificate from the settings (previously format it)
|
|
147
|
-
#
|
|
148
79
|
def get_sp_cert
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
80
|
+
cert = nil
|
|
81
|
+
if self.certificate
|
|
82
|
+
formated_cert = OneLogin::RubySaml::Utils.format_cert(self.certificate)
|
|
83
|
+
cert = OpenSSL::X509::Certificate.new(formated_cert)
|
|
84
|
+
end
|
|
85
|
+
cert
|
|
153
86
|
end
|
|
154
87
|
|
|
155
|
-
# @return [OpenSSL::PKey::RSA] Build the SP private from the settings (previously format it)
|
|
156
|
-
#
|
|
157
88
|
def get_sp_key
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
89
|
+
private_key = nil
|
|
90
|
+
if self.private_key
|
|
91
|
+
formated_private_key = OneLogin::RubySaml::Utils.format_private_key(self.private_key)
|
|
92
|
+
private_key = OpenSSL::PKey::RSA.new(formated_private_key)
|
|
93
|
+
end
|
|
94
|
+
private_key
|
|
162
95
|
end
|
|
163
96
|
|
|
164
97
|
private
|
|
165
98
|
|
|
166
99
|
DEFAULTS = {
|
|
167
|
-
:
|
|
168
|
-
:
|
|
169
|
-
:
|
|
170
|
-
:
|
|
171
|
-
:single_logout_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze,
|
|
100
|
+
:assertion_consumer_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
|
101
|
+
:single_logout_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
|
102
|
+
:compress_request => true,
|
|
103
|
+
:compress_response => true,
|
|
172
104
|
:security => {
|
|
173
|
-
:authn_requests_signed
|
|
174
|
-
:logout_requests_signed
|
|
175
|
-
:logout_responses_signed
|
|
176
|
-
:embed_sign
|
|
177
|
-
:digest_method
|
|
178
|
-
:signature_method
|
|
179
|
-
}
|
|
180
|
-
|
|
105
|
+
:authn_requests_signed => false,
|
|
106
|
+
:logout_requests_signed => false,
|
|
107
|
+
:logout_responses_signed => false,
|
|
108
|
+
:embed_sign => false,
|
|
109
|
+
:digest_method => XMLSecurity::Document::SHA1,
|
|
110
|
+
:signature_method => XMLSecurity::Document::SHA1
|
|
111
|
+
},
|
|
112
|
+
:double_quote_xml_attribute_values => false,
|
|
113
|
+
}
|
|
181
114
|
end
|
|
182
115
|
end
|
|
183
116
|
end
|
|
@@ -1,101 +1,66 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
1
|
+
require 'zlib'
|
|
2
|
+
require 'time'
|
|
3
|
+
require 'nokogiri'
|
|
3
4
|
|
|
4
5
|
# Only supports SAML 2.0
|
|
5
|
-
# SAML2 Logout Request (SLO IdP initiated, Parser)
|
|
6
6
|
module OneLogin
|
|
7
7
|
module RubySaml
|
|
8
|
-
class SloLogoutrequest
|
|
9
|
-
|
|
10
|
-
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
|
11
|
-
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
|
|
12
|
-
|
|
13
|
-
# OneLogin::RubySaml::Settings Toolkit settings
|
|
14
|
-
attr_accessor :settings
|
|
15
|
-
|
|
16
|
-
attr_reader :document
|
|
17
|
-
attr_reader :request
|
|
8
|
+
class SloLogoutrequest < SamlMessage
|
|
18
9
|
attr_reader :options
|
|
10
|
+
attr_reader :request
|
|
11
|
+
attr_reader :document
|
|
19
12
|
|
|
20
|
-
def initialize(request,
|
|
13
|
+
def initialize(request, options = {})
|
|
21
14
|
raise ArgumentError.new("Request cannot be nil") if request.nil?
|
|
22
|
-
|
|
15
|
+
@options = options
|
|
16
|
+
@request = decode_raw_saml(request)
|
|
17
|
+
@document = REXML::Document.new(@request)
|
|
18
|
+
end
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@document = XMLSecurity::SignedDocument.new(@request)
|
|
20
|
+
def is_valid?
|
|
21
|
+
validate
|
|
27
22
|
end
|
|
28
23
|
|
|
29
24
|
def validate!
|
|
30
25
|
validate(false)
|
|
31
26
|
end
|
|
32
27
|
|
|
33
|
-
|
|
34
|
-
return false unless validate_structure(soft)
|
|
35
|
-
|
|
36
|
-
valid_issuer?(soft)
|
|
37
|
-
end
|
|
38
|
-
|
|
28
|
+
# The value of the user identifier as designated by the initialization request response
|
|
39
29
|
def name_id
|
|
40
30
|
@name_id ||= begin
|
|
41
31
|
node = REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
|
|
42
|
-
|
|
32
|
+
node.nil? ? nil : node.text
|
|
43
33
|
end
|
|
44
34
|
end
|
|
45
35
|
|
|
46
|
-
alias_method :nameid, :name_id
|
|
47
|
-
|
|
48
|
-
def name_id_format
|
|
49
|
-
@name_id_node ||= REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
|
|
50
|
-
@name_id_format ||=
|
|
51
|
-
if @name_id_node && @name_id_node.attribute("Format")
|
|
52
|
-
@name_id_node.attribute("Format").value
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
alias_method :nameid_format, :name_id_format
|
|
57
|
-
|
|
58
36
|
def id
|
|
59
|
-
@id
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
37
|
+
return @id if @id
|
|
38
|
+
element = REXML::XPath.first(document, "/p:LogoutRequest", {
|
|
39
|
+
"p" => PROTOCOL} )
|
|
40
|
+
return nil if element.nil?
|
|
41
|
+
return element.attributes["ID"]
|
|
63
42
|
end
|
|
64
43
|
|
|
65
44
|
def issuer
|
|
66
45
|
@issuer ||= begin
|
|
67
46
|
node = REXML::XPath.first(document, "/p:LogoutRequest/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
|
|
68
|
-
|
|
47
|
+
node.nil? ? nil : node.text
|
|
69
48
|
end
|
|
70
49
|
end
|
|
71
50
|
|
|
72
51
|
private
|
|
73
52
|
|
|
74
|
-
def
|
|
75
|
-
|
|
76
|
-
@schema = Nokogiri::XML::Schema(IO.read('saml20protocol_schema.xsd'))
|
|
77
|
-
@xml = Nokogiri::XML(self.document.to_s)
|
|
78
|
-
end
|
|
79
|
-
if soft
|
|
80
|
-
@schema.validate(@xml).map{ return false }
|
|
81
|
-
else
|
|
82
|
-
@schema.validate(@xml).map{ |error| validation_error("#{error.message}\n\n#{@xml.to_s}") }
|
|
83
|
-
end
|
|
53
|
+
def validate(soft = true)
|
|
54
|
+
valid_saml?(document, soft) && validate_request_state(soft)
|
|
84
55
|
end
|
|
85
56
|
|
|
86
|
-
def
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
unless OneLogin::RubySaml::Utils.uri_match?(issuer, settings.idp_entity_id)
|
|
90
|
-
return soft ? false : validation_error("Doesn't match the issuer, expected: <#{self.settings.idp_entity_id}>, but was: <#{issuer}>")
|
|
57
|
+
def validate_request_state(soft = true)
|
|
58
|
+
if request.empty?
|
|
59
|
+
return soft ? false : validation_error("Blank request")
|
|
91
60
|
end
|
|
92
61
|
true
|
|
93
62
|
end
|
|
94
63
|
|
|
95
|
-
def validation_error(message)
|
|
96
|
-
raise ValidationError.new(message)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
64
|
end
|
|
100
65
|
end
|
|
101
66
|
end
|
|
@@ -1,33 +1,17 @@
|
|
|
1
|
-
require "
|
|
2
|
-
|
|
3
|
-
require "
|
|
4
|
-
require "onelogin/ruby-saml/utils"
|
|
5
|
-
require "onelogin/ruby-saml/setting_error"
|
|
1
|
+
require "uuid"
|
|
2
|
+
|
|
3
|
+
require "onelogin/ruby-saml/logging"
|
|
6
4
|
|
|
7
5
|
module OneLogin
|
|
8
6
|
module RubySaml
|
|
7
|
+
class SloLogoutresponse < SamlMessage
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
#
|
|
12
|
-
class SloLogoutresponse
|
|
13
|
-
|
|
14
|
-
# Logout Response ID
|
|
15
|
-
attr_reader :uuid
|
|
9
|
+
attr_reader :uuid # Can be obtained if neccessary
|
|
16
10
|
|
|
17
|
-
# Initializes the Logout Response. A SloLogoutresponse Object.
|
|
18
|
-
# Asigns an ID, a random uuid.
|
|
19
|
-
#
|
|
20
11
|
def initialize
|
|
21
|
-
@uuid =
|
|
12
|
+
@uuid = "_" + UUID.new.generate
|
|
22
13
|
end
|
|
23
14
|
|
|
24
|
-
# Creates the Logout Response string.
|
|
25
|
-
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
|
26
|
-
# @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
|
|
27
|
-
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
|
28
|
-
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
|
29
|
-
# @return [String] Logout Request string that includes the SAMLRequest
|
|
30
|
-
#
|
|
31
15
|
def create(settings, request_id = nil, logout_message = nil, params = {})
|
|
32
16
|
params = create_params(settings, request_id, logout_message, params)
|
|
33
17
|
params_prefix = (settings.idp_slo_target_url =~ /\?/) ? '&' : '?'
|
|
@@ -36,27 +20,12 @@ module OneLogin
|
|
|
36
20
|
params.each_pair do |key, value|
|
|
37
21
|
response_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
|
38
22
|
end
|
|
39
|
-
|
|
23
|
+
|
|
40
24
|
@logout_url = settings.idp_slo_target_url + response_params
|
|
41
25
|
end
|
|
42
26
|
|
|
43
|
-
# Creates the Get parameters for the logout response.
|
|
44
|
-
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
|
45
|
-
# @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
|
|
46
|
-
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
|
47
|
-
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
|
48
|
-
# @return [Hash] Parameters
|
|
49
|
-
#
|
|
50
27
|
def create_params(settings, request_id = nil, logout_message = nil, params = {})
|
|
51
|
-
|
|
52
|
-
# Based on the HashWithIndifferentAccess value in Rails we could experience
|
|
53
|
-
# conflicts so this line will solve them.
|
|
54
|
-
relay_state = params[:RelayState] || params['RelayState']
|
|
55
|
-
|
|
56
|
-
if relay_state.nil?
|
|
57
|
-
params.delete(:RelayState)
|
|
58
|
-
params.delete('RelayState')
|
|
59
|
-
end
|
|
28
|
+
params = {} if params.nil?
|
|
60
29
|
|
|
61
30
|
response_doc = create_logout_response_xml_doc(settings, request_id, logout_message)
|
|
62
31
|
response_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
|
|
@@ -66,29 +35,18 @@ module OneLogin
|
|
|
66
35
|
|
|
67
36
|
Logging.debug "Created SLO Logout Response: #{response}"
|
|
68
37
|
|
|
69
|
-
response =
|
|
70
|
-
|
|
71
|
-
base64_response = Base64.strict_encode64(response)
|
|
72
|
-
else
|
|
73
|
-
base64_response = Base64.encode64(response).gsub(/\n/, "")
|
|
74
|
-
end
|
|
38
|
+
response = deflate(response) if settings.compress_response
|
|
39
|
+
base64_response = encode(response)
|
|
75
40
|
response_params = {"SAMLResponse" => base64_response}
|
|
76
41
|
|
|
77
42
|
if settings.security[:logout_responses_signed] && !settings.security[:embed_sign] && settings.private_key
|
|
78
|
-
params['SigAlg'] =
|
|
79
|
-
url_string
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
)
|
|
85
|
-
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
|
|
86
|
-
signature = settings.get_sp_key.sign(sign_algorithm.new, url_string)
|
|
87
|
-
if Base64.respond_to?('strict_encode64')
|
|
88
|
-
params['Signature'] = Base64.strict_encode64(signature)
|
|
89
|
-
else
|
|
90
|
-
params['Signature'] = Base64.encode64(signature).gsub(/\n/, "")
|
|
91
|
-
end
|
|
43
|
+
params['SigAlg'] = XMLSecurity::Document::SHA1
|
|
44
|
+
url_string = "SAMLResponse=#{CGI.escape(base64_response)}"
|
|
45
|
+
url_string += "&RelayState=#{CGI.escape(params['RelayState'])}" if params['RelayState']
|
|
46
|
+
url_string += "&SigAlg=#{CGI.escape(params['SigAlg'])}"
|
|
47
|
+
private_key = settings.get_sp_key()
|
|
48
|
+
signature = private_key.sign(XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]).new, url_string)
|
|
49
|
+
params['Signature'] = encode(signature)
|
|
92
50
|
end
|
|
93
51
|
|
|
94
52
|
params.each_pair do |key, value|
|
|
@@ -98,18 +56,7 @@ module OneLogin
|
|
|
98
56
|
response_params
|
|
99
57
|
end
|
|
100
58
|
|
|
101
|
-
# Creates the SAMLResponse String.
|
|
102
|
-
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
|
103
|
-
# @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
|
|
104
|
-
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
|
105
|
-
# @return [String] The SAMLResponse String.
|
|
106
|
-
#
|
|
107
59
|
def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil)
|
|
108
|
-
document = create_xml_document(settings, request_id, logout_message)
|
|
109
|
-
sign_document(document, settings)
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def create_xml_document(settings, request_id = nil, logout_message = nil)
|
|
113
60
|
time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
114
61
|
|
|
115
62
|
response_doc = XMLSecurity::Document.new
|
|
@@ -120,12 +67,7 @@ module OneLogin
|
|
|
120
67
|
root.attributes['IssueInstant'] = time
|
|
121
68
|
root.attributes['Version'] = '2.0'
|
|
122
69
|
root.attributes['InResponseTo'] = request_id unless request_id.nil?
|
|
123
|
-
root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil?
|
|
124
|
-
|
|
125
|
-
if settings.sp_entity_id != nil
|
|
126
|
-
issuer = root.add_element "saml:Issuer"
|
|
127
|
-
issuer.text = settings.sp_entity_id
|
|
128
|
-
end
|
|
70
|
+
root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil?
|
|
129
71
|
|
|
130
72
|
# add success message
|
|
131
73
|
status = root.add_element 'samlp:Status'
|
|
@@ -139,18 +81,19 @@ module OneLogin
|
|
|
139
81
|
status_message = status.add_element 'samlp:StatusMessage'
|
|
140
82
|
status_message.text = logout_message
|
|
141
83
|
|
|
142
|
-
|
|
143
|
-
|
|
84
|
+
if settings.issuer != nil
|
|
85
|
+
issuer = root.add_element "saml:Issuer"
|
|
86
|
+
issuer.text = settings.issuer
|
|
87
|
+
end
|
|
144
88
|
|
|
145
|
-
|
|
146
|
-
# embed signature
|
|
89
|
+
# embebed sign
|
|
147
90
|
if settings.security[:logout_responses_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
|
|
148
|
-
private_key = settings.get_sp_key
|
|
149
|
-
cert = settings.get_sp_cert
|
|
150
|
-
|
|
91
|
+
private_key = settings.get_sp_key()
|
|
92
|
+
cert = settings.get_sp_cert()
|
|
93
|
+
response_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
|
151
94
|
end
|
|
152
95
|
|
|
153
|
-
|
|
96
|
+
response_doc
|
|
154
97
|
end
|
|
155
98
|
|
|
156
99
|
end
|