ruby-saml 1.8.0 → 1.13.0
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 +4 -4
- data/.github/workflows/test.yml +25 -0
- data/{changelog.md → CHANGELOG.md} +66 -1
- data/README.md +365 -209
- data/UPGRADING.md +149 -0
- data/lib/onelogin/ruby-saml/attribute_service.rb +1 -1
- data/lib/onelogin/ruby-saml/attributes.rb +24 -1
- data/lib/onelogin/ruby-saml/authrequest.rb +25 -9
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +285 -184
- data/lib/onelogin/ruby-saml/logging.rb +4 -1
- data/lib/onelogin/ruby-saml/logoutrequest.rb +25 -10
- data/lib/onelogin/ruby-saml/logoutresponse.rb +33 -17
- data/lib/onelogin/ruby-saml/metadata.rb +62 -17
- data/lib/onelogin/ruby-saml/response.rb +89 -45
- data/lib/onelogin/ruby-saml/saml_message.rb +17 -8
- data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
- data/lib/onelogin/ruby-saml/settings.rb +124 -43
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +38 -11
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +42 -19
- data/lib/onelogin/ruby-saml/utils.rb +94 -12
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +44 -19
- data/ruby-saml.gemspec +16 -8
- metadata +44 -282
- data/.travis.yml +0 -26
- data/test/certificates/certificate1 +0 -12
- data/test/certificates/certificate_without_head_foot +0 -1
- 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/certificates/ruby-saml.crt +0 -14
- data/test/certificates/ruby-saml.key +0 -15
- data/test/idp_metadata_parser_test.rb +0 -579
- data/test/logging_test.rb +0 -62
- data/test/logout_requests/invalid_slo_request.xml +0 -6
- data/test/logout_requests/slo_request.xml +0 -4
- data/test/logout_requests/slo_request.xml.base64 +0 -1
- data/test/logout_requests/slo_request_deflated.xml.base64 +0 -1
- data/test/logout_requests/slo_request_with_name_id_format.xml +0 -4
- data/test/logout_requests/slo_request_with_session_index.xml +0 -5
- data/test/logout_responses/logoutresponse_fixtures.rb +0 -67
- data/test/logoutrequest_test.rb +0 -226
- data/test/logoutresponse_test.rb +0 -402
- data/test/metadata/idp_descriptor.xml +0 -26
- data/test/metadata/idp_descriptor_2.xml +0 -56
- data/test/metadata/idp_descriptor_3.xml +0 -14
- data/test/metadata/idp_descriptor_4.xml +0 -72
- data/test/metadata/idp_metadata_different_sign_and_encrypt_cert.xml +0 -72
- data/test/metadata/idp_metadata_multi_certs.xml +0 -75
- data/test/metadata/idp_metadata_multi_signing_certs.xml +0 -52
- data/test/metadata/idp_metadata_same_sign_and_encrypt_cert.xml +0 -71
- data/test/metadata/idp_multiple_descriptors.xml +0 -53
- data/test/metadata/no_idp_descriptor.xml +0 -21
- data/test/metadata_test.rb +0 -331
- data/test/request_test.rb +0 -323
- data/test/response_test.rb +0 -1586
- data/test/responses/adfs_response_sha1.xml +0 -46
- data/test/responses/adfs_response_sha256.xml +0 -46
- data/test/responses/adfs_response_sha384.xml +0 -46
- data/test/responses/adfs_response_sha512.xml +0 -46
- data/test/responses/adfs_response_xmlns.xml +0 -45
- data/test/responses/attackxee.xml +0 -13
- data/test/responses/invalids/duplicated_attributes.xml.base64 +0 -1
- data/test/responses/invalids/empty_destination.xml.base64 +0 -1
- data/test/responses/invalids/empty_nameid.xml.base64 +0 -1
- data/test/responses/invalids/encrypted_new_attack.xml.base64 +0 -1
- data/test/responses/invalids/invalid_audience.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/invalid_signature_position.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +0 -1
- data/test/responses/invalids/multiple_assertions.xml.base64 +0 -2
- data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
- data/test/responses/invalids/no_authnstatement.xml.base64 +0 -1
- data/test/responses/invalids/no_conditions.xml.base64 +0 -1
- data/test/responses/invalids/no_id.xml.base64 +0 -1
- data/test/responses/invalids/no_issuer_assertion.xml.base64 +0 -1
- data/test/responses/invalids/no_issuer_response.xml.base64 +0 -1
- data/test/responses/invalids/no_nameid.xml.base64 +0 -1
- data/test/responses/invalids/no_saml2.xml.base64 +0 -1
- data/test/responses/invalids/no_signature.xml.base64 +0 -1
- data/test/responses/invalids/no_status.xml.base64 +0 -1
- data/test/responses/invalids/no_status_code.xml.base64 +0 -1
- data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +0 -1
- data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +0 -1
- data/test/responses/invalids/response_invalid_signed_element.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/invalids/status_code_responder.xml.base64 +0 -1
- data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +0 -1
- data/test/responses/invalids/wrong_spnamequalifier.xml.base64 +0 -1
- data/test/responses/no_signature_ns.xml +0 -48
- data/test/responses/open_saml_response.xml +0 -56
- data/test/responses/response_assertion_wrapped.xml.base64 +0 -93
- data/test/responses/response_audience_self_closed_tag.xml.base64 +0 -1
- data/test/responses/response_double_status_code.xml.base64 +0 -1
- data/test/responses/response_encrypted_attrs.xml.base64 +0 -1
- data/test/responses/response_encrypted_nameid.xml.base64 +0 -1
- data/test/responses/response_eval.xml +0 -7
- data/test/responses/response_no_cert_and_encrypted_attrs.xml +0 -29
- data/test/responses/response_node_text_attack.xml.base64 +0 -1
- data/test/responses/response_node_text_attack2.xml.base64 +0 -1
- data/test/responses/response_node_text_attack3.xml.base64 +0 -1
- data/test/responses/response_unsigned_xml_base64 +0 -1
- data/test/responses/response_with_ampersands.xml +0 -139
- data/test/responses/response_with_ampersands.xml.base64 +0 -93
- data/test/responses/response_with_ds_namespace_at_the_root.xml.base64 +0 -1
- data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
- data/test/responses/response_with_multiple_attribute_values.xml +0 -67
- data/test/responses/response_with_retrieval_method.xml +0 -26
- data/test/responses/response_with_saml2_namespace.xml.base64 +0 -102
- data/test/responses/response_with_signed_assertion.xml.base64 +0 -66
- data/test/responses/response_with_signed_assertion_2.xml.base64 +0 -1
- 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_without_attributes.xml.base64 +0 -79
- data/test/responses/response_without_reference_uri.xml.base64 +0 -1
- data/test/responses/response_wrapped.xml.base64 +0 -150
- data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +0 -1
- data/test/responses/signed_nameid_in_atts.xml +0 -47
- data/test/responses/signed_unqual_nameid_in_atts.xml +0 -47
- data/test/responses/simple_saml_php.xml +0 -71
- data/test/responses/starfield_response.xml.base64 +0 -1
- data/test/responses/test_sign.xml +0 -43
- data/test/responses/unsigned_encrypted_adfs.xml +0 -23
- data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +0 -1
- data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +0 -1
- data/test/responses/valid_response.xml.base64 +0 -1
- data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
- data/test/saml_message_test.rb +0 -56
- data/test/settings_test.rb +0 -301
- data/test/slo_logoutrequest_test.rb +0 -448
- data/test/slo_logoutresponse_test.rb +0 -199
- data/test/test_helper.rb +0 -327
- data/test/utils_test.rb +0 -254
- data/test/xml_security_test.rb +0 -421
@@ -16,8 +16,8 @@ module OneLogin
|
|
16
16
|
class SamlMessage
|
17
17
|
include REXML
|
18
18
|
|
19
|
-
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
20
|
-
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
|
19
|
+
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion".freeze
|
20
|
+
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol".freeze
|
21
21
|
|
22
22
|
BASE64_FORMAT = %r(\A([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z)
|
23
23
|
@@mutex = Mutex.new
|
@@ -62,7 +62,7 @@ module OneLogin
|
|
62
62
|
# @param document [REXML::Document] The message that will be validated
|
63
63
|
# @param soft [Boolean] soft Enable or Disable the soft mode (In order to raise exceptions when the message is invalid or not)
|
64
64
|
# @return [Boolean] True if the XML is valid, otherwise False, if soft=True
|
65
|
-
# @raise [ValidationError] if soft == false and validation fails
|
65
|
+
# @raise [ValidationError] if soft == false and validation fails
|
66
66
|
#
|
67
67
|
def valid_saml?(document, soft = true)
|
68
68
|
begin
|
@@ -74,9 +74,9 @@ module OneLogin
|
|
74
74
|
raise ValidationError.new("XML load failed: #{error.message}")
|
75
75
|
end
|
76
76
|
|
77
|
-
SamlMessage.schema.validate(xml).map do |
|
77
|
+
SamlMessage.schema.validate(xml).map do |schema_error|
|
78
78
|
return false if soft
|
79
|
-
raise ValidationError.new("#{
|
79
|
+
raise ValidationError.new("#{schema_error.message}\n\n#{xml.to_s}")
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
@@ -86,9 +86,14 @@ module OneLogin
|
|
86
86
|
# @param saml [String] The deflated and encoded SAML Message
|
87
87
|
# @return [String] The plain SAML Message
|
88
88
|
#
|
89
|
-
def decode_raw_saml(saml)
|
89
|
+
def decode_raw_saml(saml, settings = nil)
|
90
90
|
return saml unless base64_encoded?(saml)
|
91
91
|
|
92
|
+
settings = OneLogin::RubySaml::Settings.new if settings.nil?
|
93
|
+
if saml.bytesize > settings.message_max_bytesize
|
94
|
+
raise ValidationError.new("Encoded SAML Message exceeds " + settings.message_max_bytesize.to_s + " bytes, so was rejected")
|
95
|
+
end
|
96
|
+
|
92
97
|
decoded = decode(saml)
|
93
98
|
begin
|
94
99
|
inflate(decoded)
|
@@ -105,7 +110,7 @@ module OneLogin
|
|
105
110
|
def encode_raw_saml(saml, settings)
|
106
111
|
saml = deflate(saml) if settings.compress_request
|
107
112
|
|
108
|
-
CGI.escape(
|
113
|
+
CGI.escape(encode(saml))
|
109
114
|
end
|
110
115
|
|
111
116
|
# Base 64 decode method
|
@@ -121,7 +126,11 @@ module OneLogin
|
|
121
126
|
# @return [String] The encoded string
|
122
127
|
#
|
123
128
|
def encode(string)
|
124
|
-
Base64.
|
129
|
+
if Base64.respond_to?('strict_encode64')
|
130
|
+
Base64.strict_encode64(string)
|
131
|
+
else
|
132
|
+
Base64.encode64(string).gsub(/\n/, "")
|
133
|
+
end
|
125
134
|
end
|
126
135
|
|
127
136
|
# Check if a string is base64 encoded
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require "xml_security"
|
2
2
|
require "onelogin/ruby-saml/attribute_service"
|
3
3
|
require "onelogin/ruby-saml/utils"
|
4
|
+
require "onelogin/ruby-saml/validation_error"
|
4
5
|
|
5
6
|
# Only supports SAML 2.0
|
6
7
|
module OneLogin
|
@@ -9,8 +10,15 @@ module OneLogin
|
|
9
10
|
# SAML2 Toolkit Settings
|
10
11
|
#
|
11
12
|
class Settings
|
12
|
-
def initialize(overrides = {})
|
13
|
-
|
13
|
+
def initialize(overrides = {}, keep_security_attributes = false)
|
14
|
+
if keep_security_attributes
|
15
|
+
security_attributes = overrides.delete(:security) || {}
|
16
|
+
config = DEFAULTS.merge(overrides)
|
17
|
+
config[:security] = DEFAULTS[:security].merge(security_attributes)
|
18
|
+
else
|
19
|
+
config = DEFAULTS.merge(overrides)
|
20
|
+
end
|
21
|
+
|
14
22
|
config.each do |k,v|
|
15
23
|
acc = "#{k.to_s}=".to_sym
|
16
24
|
if respond_to? acc
|
@@ -23,27 +31,32 @@ module OneLogin
|
|
23
31
|
|
24
32
|
# IdP Data
|
25
33
|
attr_accessor :idp_entity_id
|
26
|
-
|
27
|
-
|
34
|
+
attr_writer :idp_sso_service_url
|
35
|
+
attr_writer :idp_slo_service_url
|
36
|
+
attr_accessor :idp_slo_response_service_url
|
28
37
|
attr_accessor :idp_cert
|
29
38
|
attr_accessor :idp_cert_fingerprint
|
30
39
|
attr_accessor :idp_cert_fingerprint_algorithm
|
31
40
|
attr_accessor :idp_cert_multi
|
32
41
|
attr_accessor :idp_attribute_names
|
33
42
|
attr_accessor :idp_name_qualifier
|
43
|
+
attr_accessor :valid_until
|
34
44
|
# SP Data
|
35
|
-
|
45
|
+
attr_writer :sp_entity_id
|
36
46
|
attr_accessor :assertion_consumer_service_url
|
37
|
-
|
47
|
+
attr_reader :assertion_consumer_service_binding
|
48
|
+
attr_writer :single_logout_service_url
|
38
49
|
attr_accessor :sp_name_qualifier
|
39
50
|
attr_accessor :name_identifier_format
|
40
51
|
attr_accessor :name_identifier_value
|
52
|
+
attr_accessor :name_identifier_value_requested
|
41
53
|
attr_accessor :sessionindex
|
42
54
|
attr_accessor :compress_request
|
43
55
|
attr_accessor :compress_response
|
44
56
|
attr_accessor :double_quote_xml_attribute_values
|
57
|
+
attr_accessor :message_max_bytesize
|
45
58
|
attr_accessor :passive
|
46
|
-
|
59
|
+
attr_reader :protocol_binding
|
47
60
|
attr_accessor :attributes_index
|
48
61
|
attr_accessor :force_authn
|
49
62
|
attr_accessor :certificate
|
@@ -56,52 +69,99 @@ module OneLogin
|
|
56
69
|
# Work-flow
|
57
70
|
attr_accessor :security
|
58
71
|
attr_accessor :soft
|
59
|
-
#
|
72
|
+
# Deprecated
|
60
73
|
attr_accessor :assertion_consumer_logout_service_url
|
61
|
-
|
74
|
+
attr_reader :assertion_consumer_logout_service_binding
|
75
|
+
attr_accessor :issuer
|
76
|
+
attr_accessor :idp_sso_target_url
|
77
|
+
attr_accessor :idp_slo_target_url
|
62
78
|
|
63
|
-
# @return [String] Single
|
79
|
+
# @return [String] IdP Single Sign On Service URL
|
64
80
|
#
|
65
|
-
def
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
81
|
+
def idp_sso_service_url
|
82
|
+
@idp_sso_service_url || @idp_sso_target_url
|
83
|
+
end
|
84
|
+
|
85
|
+
# @return [String] IdP Single Logout Service URL
|
86
|
+
#
|
87
|
+
def idp_slo_service_url
|
88
|
+
@idp_slo_service_url || @idp_slo_target_url
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [String] IdP Single Sign On Service Binding
|
92
|
+
#
|
93
|
+
def idp_sso_service_binding
|
94
|
+
@idp_sso_service_binding || idp_binding_from_embed_sign
|
95
|
+
end
|
96
|
+
|
97
|
+
# Setter for IdP Single Sign On Service Binding
|
98
|
+
# @param value [String, Symbol].
|
99
|
+
#
|
100
|
+
def idp_sso_service_binding=(value)
|
101
|
+
@idp_sso_service_binding = get_binding(value)
|
102
|
+
end
|
103
|
+
|
104
|
+
# @return [String] IdP Single Logout Service Binding
|
105
|
+
#
|
106
|
+
def idp_slo_service_binding
|
107
|
+
@idp_slo_service_binding || idp_binding_from_embed_sign
|
108
|
+
end
|
109
|
+
|
110
|
+
# Setter for IdP Single Logout Service Binding
|
111
|
+
# @param value [String, Symbol].
|
112
|
+
#
|
113
|
+
def idp_slo_service_binding=(value)
|
114
|
+
@idp_slo_service_binding = get_binding(value)
|
115
|
+
end
|
116
|
+
|
117
|
+
# @return [String] SP Entity ID
|
118
|
+
#
|
119
|
+
def sp_entity_id
|
120
|
+
@sp_entity_id || @issuer
|
121
|
+
end
|
122
|
+
|
123
|
+
# Setter for SP Protocol Binding
|
124
|
+
# @param value [String, Symbol].
|
125
|
+
#
|
126
|
+
def protocol_binding=(value)
|
127
|
+
@protocol_binding = get_binding(value)
|
75
128
|
end
|
76
129
|
|
77
|
-
# Setter for
|
78
|
-
# @param
|
130
|
+
# Setter for SP Assertion Consumer Service Binding
|
131
|
+
# @param value [String, Symbol].
|
79
132
|
#
|
80
|
-
def
|
81
|
-
@
|
133
|
+
def assertion_consumer_service_binding=(value)
|
134
|
+
@assertion_consumer_service_binding = get_binding(value)
|
135
|
+
end
|
136
|
+
|
137
|
+
# @return [String] Single Logout Service URL.
|
138
|
+
#
|
139
|
+
def single_logout_service_url
|
140
|
+
@single_logout_service_url || @assertion_consumer_logout_service_url
|
82
141
|
end
|
83
142
|
|
84
143
|
# @return [String] Single Logout Service Binding.
|
85
144
|
#
|
86
145
|
def single_logout_service_binding
|
87
|
-
|
88
|
-
if @single_logout_service_binding.nil?
|
89
|
-
if @assertion_consumer_logout_service_binding
|
90
|
-
val = @assertion_consumer_logout_service_binding
|
91
|
-
end
|
92
|
-
else
|
93
|
-
val = @single_logout_service_binding
|
94
|
-
end
|
95
|
-
val
|
146
|
+
@single_logout_service_binding || @assertion_consumer_logout_service_binding
|
96
147
|
end
|
97
148
|
|
98
149
|
# Setter for Single Logout Service Binding.
|
99
150
|
#
|
100
151
|
# (Currently we only support "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
|
101
|
-
# @param
|
152
|
+
# @param value [String, Symbol]
|
153
|
+
#
|
154
|
+
def single_logout_service_binding=(value)
|
155
|
+
@single_logout_service_binding = get_binding(value)
|
156
|
+
end
|
157
|
+
|
158
|
+
# @deprecated Setter for legacy Single Logout Service Binding parameter.
|
159
|
+
#
|
160
|
+
# (Currently we only support "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
|
161
|
+
# @param value [String, Symbol]
|
102
162
|
#
|
103
|
-
def
|
104
|
-
@
|
163
|
+
def assertion_consumer_logout_service_binding=(value)
|
164
|
+
@assertion_consumer_logout_service_binding = get_binding(value)
|
105
165
|
end
|
106
166
|
|
107
167
|
# Calculates the fingerprint of the IdP x509 certificate.
|
@@ -158,7 +218,15 @@ module OneLogin
|
|
158
218
|
return nil if certificate.nil? || certificate.empty?
|
159
219
|
|
160
220
|
formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
|
161
|
-
OpenSSL::X509::Certificate.new(formatted_cert)
|
221
|
+
cert = OpenSSL::X509::Certificate.new(formatted_cert)
|
222
|
+
|
223
|
+
if security[:check_sp_cert_expiration]
|
224
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(cert)
|
225
|
+
raise OneLogin::RubySaml::ValidationError.new("The SP certificate expired.")
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
cert
|
162
230
|
end
|
163
231
|
|
164
232
|
# @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings (previously format it)
|
@@ -181,13 +249,25 @@ module OneLogin
|
|
181
249
|
|
182
250
|
private
|
183
251
|
|
252
|
+
def idp_binding_from_embed_sign
|
253
|
+
security[:embed_sign] ? Utils::BINDINGS[:post] : Utils::BINDINGS[:redirect]
|
254
|
+
end
|
255
|
+
|
256
|
+
def get_binding(value)
|
257
|
+
return unless value
|
258
|
+
|
259
|
+
Utils::BINDINGS[value.to_sym] || value
|
260
|
+
end
|
261
|
+
|
184
262
|
DEFAULTS = {
|
185
|
-
:assertion_consumer_service_binding =>
|
186
|
-
:single_logout_service_binding =>
|
263
|
+
:assertion_consumer_service_binding => Utils::BINDINGS[:post],
|
264
|
+
:single_logout_service_binding => Utils::BINDINGS[:redirect],
|
187
265
|
:idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1,
|
188
266
|
:compress_request => true,
|
189
267
|
:compress_response => true,
|
268
|
+
:message_max_bytesize => 250000,
|
190
269
|
:soft => true,
|
270
|
+
:double_quote_xml_attribute_values => false,
|
191
271
|
:security => {
|
192
272
|
:authn_requests_signed => false,
|
193
273
|
:logout_requests_signed => false,
|
@@ -196,11 +276,12 @@ module OneLogin
|
|
196
276
|
:want_assertions_encrypted => false,
|
197
277
|
:want_name_id => false,
|
198
278
|
:metadata_signed => false,
|
199
|
-
:embed_sign => false,
|
279
|
+
:embed_sign => false, # Deprecated
|
200
280
|
:digest_method => XMLSecurity::Document::SHA1,
|
201
|
-
:signature_method => XMLSecurity::Document::RSA_SHA1
|
202
|
-
|
203
|
-
|
281
|
+
:signature_method => XMLSecurity::Document::RSA_SHA1,
|
282
|
+
:check_idp_cert_expiration => false,
|
283
|
+
:check_sp_cert_expiration => false
|
284
|
+
}.freeze
|
204
285
|
}.freeze
|
205
286
|
end
|
206
287
|
end
|
@@ -24,9 +24,9 @@ module OneLogin
|
|
24
24
|
|
25
25
|
# Constructs the Logout Request. A Logout Request Object that is an extension of the SamlMessage class.
|
26
26
|
# @param request [String] A UUEncoded Logout Request from the IdP.
|
27
|
-
# @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object
|
27
|
+
# @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object
|
28
28
|
# Or :allowed_clock_drift for the logout request validation process to allow a clock drift when checking dates with
|
29
|
-
# Or :relax_signature_validation to accept signatures if no idp certificate registered on settings
|
29
|
+
# Or :relax_signature_validation to accept signatures if no idp certificate registered on settings
|
30
30
|
#
|
31
31
|
# @raise [ArgumentError] If Request is nil
|
32
32
|
#
|
@@ -43,10 +43,14 @@ module OneLogin
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
@request = decode_raw_saml(request)
|
46
|
+
@request = decode_raw_saml(request, settings)
|
47
47
|
@document = REXML::Document.new(@request)
|
48
48
|
end
|
49
49
|
|
50
|
+
def request_id
|
51
|
+
id(document)
|
52
|
+
end
|
53
|
+
|
50
54
|
# Validates the Logout Request with the default values (soft = true)
|
51
55
|
# @param collect_errors [Boolean] Stop validation when first error appears or keep validating.
|
52
56
|
# @return [Boolean] TRUE if the Logout Request is valid
|
@@ -126,6 +130,12 @@ module OneLogin
|
|
126
130
|
|
127
131
|
private
|
128
132
|
|
133
|
+
# returns the allowed clock drift on timing validation
|
134
|
+
# @return [Float]
|
135
|
+
def allowed_clock_drift
|
136
|
+
options[:allowed_clock_drift].to_f.abs + Float::EPSILON
|
137
|
+
end
|
138
|
+
|
129
139
|
# Hard aux function to validate the Logout Request
|
130
140
|
# @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true)
|
131
141
|
# @return [Boolean] TRUE if the Logout Request is valid
|
@@ -176,15 +186,17 @@ module OneLogin
|
|
176
186
|
true
|
177
187
|
end
|
178
188
|
|
179
|
-
# Validates the time. (If the logout request was initialized with the :allowed_clock_drift
|
189
|
+
# Validates the time. (If the logout request was initialized with the :allowed_clock_drift
|
190
|
+
# option, the timing validations are relaxed by the allowed_clock_drift value)
|
180
191
|
# If fails, the error is added to the errors array
|
181
192
|
# @return [Boolean] True if satisfies the conditions, otherwise False if soft=True
|
182
193
|
# @raise [ValidationError] if soft == false and validation fails
|
183
194
|
#
|
184
195
|
def validate_not_on_or_after
|
185
196
|
now = Time.now.utc
|
186
|
-
|
187
|
-
|
197
|
+
|
198
|
+
if not_on_or_after && now >= (not_on_or_after + allowed_clock_drift)
|
199
|
+
return append_error("Current time is on or after NotOnOrAfter (#{now} >= #{not_on_or_after}#{" + #{allowed_clock_drift.ceil}s" if allowed_clock_drift > 0})")
|
188
200
|
end
|
189
201
|
|
190
202
|
true
|
@@ -192,7 +204,7 @@ module OneLogin
|
|
192
204
|
|
193
205
|
# Validates the Logout Request against the specified schema.
|
194
206
|
# @return [Boolean] True if the XML is valid, otherwise False if soft=True
|
195
|
-
# @raise [ValidationError] if soft == false and validation fails
|
207
|
+
# @raise [ValidationError] if soft == false and validation fails
|
196
208
|
#
|
197
209
|
def validate_structure
|
198
210
|
unless valid_saml?(document, soft)
|
@@ -202,7 +214,7 @@ module OneLogin
|
|
202
214
|
true
|
203
215
|
end
|
204
216
|
|
205
|
-
# Validates that the Logout Request provided in the initialization is not empty,
|
217
|
+
# Validates that the Logout Request provided in the initialization is not empty,
|
206
218
|
# @return [Boolean] True if the required info is found, otherwise False if soft=True
|
207
219
|
# @raise [ValidationError] if soft == false and validation fails
|
208
220
|
#
|
@@ -280,28 +292,43 @@ module OneLogin
|
|
280
292
|
:raw_sig_alg => options[:raw_get_params]['SigAlg']
|
281
293
|
)
|
282
294
|
|
295
|
+
expired = false
|
283
296
|
if idp_certs.nil? || idp_certs[:signing].empty?
|
284
297
|
valid = OneLogin::RubySaml::Utils.verify_signature(
|
285
|
-
:cert =>
|
298
|
+
:cert => idp_cert,
|
286
299
|
:sig_alg => options[:get_params]['SigAlg'],
|
287
300
|
:signature => options[:get_params]['Signature'],
|
288
301
|
:query_string => query_string
|
289
302
|
)
|
303
|
+
if valid && settings.security[:check_idp_cert_expiration]
|
304
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
|
305
|
+
expired = true
|
306
|
+
end
|
307
|
+
end
|
290
308
|
else
|
291
309
|
valid = false
|
292
|
-
idp_certs[:signing].each do |
|
310
|
+
idp_certs[:signing].each do |signing_idp_cert|
|
293
311
|
valid = OneLogin::RubySaml::Utils.verify_signature(
|
294
|
-
:cert =>
|
312
|
+
:cert => signing_idp_cert,
|
295
313
|
:sig_alg => options[:get_params]['SigAlg'],
|
296
314
|
:signature => options[:get_params]['Signature'],
|
297
315
|
:query_string => query_string
|
298
316
|
)
|
299
317
|
if valid
|
318
|
+
if settings.security[:check_idp_cert_expiration]
|
319
|
+
if OneLogin::RubySaml::Utils.is_cert_expired(signing_idp_cert)
|
320
|
+
expired = true
|
321
|
+
end
|
322
|
+
end
|
300
323
|
break
|
301
324
|
end
|
302
325
|
end
|
303
326
|
end
|
304
327
|
|
328
|
+
if expired
|
329
|
+
error_msg = "IdP x509 certificate expired"
|
330
|
+
return append_error(error_msg)
|
331
|
+
end
|
305
332
|
unless valid
|
306
333
|
return append_error("Invalid Signature on Logout Request")
|
307
334
|
end
|
@@ -2,6 +2,7 @@ require "onelogin/ruby-saml/logging"
|
|
2
2
|
|
3
3
|
require "onelogin/ruby-saml/saml_message"
|
4
4
|
require "onelogin/ruby-saml/utils"
|
5
|
+
require "onelogin/ruby-saml/setting_error"
|
5
6
|
|
6
7
|
# Only supports SAML 2.0
|
7
8
|
module OneLogin
|
@@ -21,23 +22,30 @@ module OneLogin
|
|
21
22
|
@uuid = OneLogin::RubySaml::Utils.uuid
|
22
23
|
end
|
23
24
|
|
25
|
+
def response_id
|
26
|
+
@uuid
|
27
|
+
end
|
28
|
+
|
24
29
|
# Creates the Logout Response string.
|
25
30
|
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
26
31
|
# @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
32
|
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
28
33
|
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
34
|
+
# @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
|
29
35
|
# @return [String] Logout Request string that includes the SAMLRequest
|
30
36
|
#
|
31
|
-
def create(settings, request_id = nil, logout_message = nil, params = {})
|
32
|
-
params = create_params(settings, request_id, logout_message, params)
|
33
|
-
params_prefix = (settings.
|
37
|
+
def create(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil)
|
38
|
+
params = create_params(settings, request_id, logout_message, params, logout_status_code)
|
39
|
+
params_prefix = (settings.idp_slo_service_url =~ /\?/) ? '&' : '?'
|
40
|
+
url = settings.idp_slo_response_service_url || settings.idp_slo_service_url
|
34
41
|
saml_response = CGI.escape(params.delete("SAMLResponse"))
|
35
42
|
response_params = "#{params_prefix}SAMLResponse=#{saml_response}"
|
36
43
|
params.each_pair do |key, value|
|
37
44
|
response_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
38
45
|
end
|
39
46
|
|
40
|
-
|
47
|
+
raise SettingError.new "Invalid settings, idp_slo_service_url is not set!" if url.nil? or url.empty?
|
48
|
+
@logout_url = url + response_params
|
41
49
|
end
|
42
50
|
|
43
51
|
# Creates the Get parameters for the logout response.
|
@@ -45,9 +53,10 @@ module OneLogin
|
|
45
53
|
# @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
54
|
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
47
55
|
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
56
|
+
# @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
|
48
57
|
# @return [Hash] Parameters
|
49
58
|
#
|
50
|
-
def create_params(settings, request_id = nil, logout_message = nil, params = {})
|
59
|
+
def create_params(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil)
|
51
60
|
# The method expects :RelayState but sometimes we get 'RelayState' instead.
|
52
61
|
# Based on the HashWithIndifferentAccess value in Rails we could experience
|
53
62
|
# conflicts so this line will solve them.
|
@@ -58,7 +67,7 @@ module OneLogin
|
|
58
67
|
params.delete('RelayState')
|
59
68
|
end
|
60
69
|
|
61
|
-
response_doc = create_logout_response_xml_doc(settings, request_id, logout_message)
|
70
|
+
response_doc = create_logout_response_xml_doc(settings, request_id, logout_message, logout_status_code)
|
62
71
|
response_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
|
63
72
|
|
64
73
|
response = ""
|
@@ -70,7 +79,7 @@ module OneLogin
|
|
70
79
|
base64_response = encode(response)
|
71
80
|
response_params = {"SAMLResponse" => base64_response}
|
72
81
|
|
73
|
-
if settings.
|
82
|
+
if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_responses_signed] && settings.private_key
|
74
83
|
params['SigAlg'] = settings.security[:signature_method]
|
75
84
|
url_string = OneLogin::RubySaml::Utils.build_query(
|
76
85
|
:type => 'SAMLResponse',
|
@@ -94,46 +103,60 @@ module OneLogin
|
|
94
103
|
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
95
104
|
# @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
|
96
105
|
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
106
|
+
# @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
|
97
107
|
# @return [String] The SAMLResponse String.
|
98
108
|
#
|
99
|
-
def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil)
|
109
|
+
def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil, logout_status_code = nil)
|
110
|
+
document = create_xml_document(settings, request_id, logout_message, logout_status_code)
|
111
|
+
sign_document(document, settings)
|
112
|
+
end
|
113
|
+
|
114
|
+
def create_xml_document(settings, request_id = nil, logout_message = nil, status_code = nil)
|
100
115
|
time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
101
116
|
|
102
117
|
response_doc = XMLSecurity::Document.new
|
103
118
|
response_doc.uuid = uuid
|
104
119
|
|
120
|
+
destination = settings.idp_slo_response_service_url || settings.idp_slo_service_url
|
121
|
+
|
122
|
+
|
105
123
|
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" }
|
106
124
|
root.attributes['ID'] = uuid
|
107
125
|
root.attributes['IssueInstant'] = time
|
108
126
|
root.attributes['Version'] = '2.0'
|
109
127
|
root.attributes['InResponseTo'] = request_id unless request_id.nil?
|
110
|
-
root.attributes['Destination'] =
|
128
|
+
root.attributes['Destination'] = destination unless destination.nil? or destination.empty?
|
111
129
|
|
112
|
-
if settings.
|
130
|
+
if settings.sp_entity_id != nil
|
113
131
|
issuer = root.add_element "saml:Issuer"
|
114
|
-
issuer.text = settings.
|
132
|
+
issuer.text = settings.sp_entity_id
|
115
133
|
end
|
116
134
|
|
117
|
-
# add
|
135
|
+
# add status
|
118
136
|
status = root.add_element 'samlp:Status'
|
119
137
|
|
120
|
-
#
|
121
|
-
status_code
|
122
|
-
|
138
|
+
# status code
|
139
|
+
status_code ||= 'urn:oasis:names:tc:SAML:2.0:status:Success'
|
140
|
+
status_code_elem = status.add_element 'samlp:StatusCode'
|
141
|
+
status_code_elem.attributes['Value'] = status_code
|
123
142
|
|
124
|
-
#
|
143
|
+
# status message
|
125
144
|
logout_message ||= 'Successfully Signed Out'
|
126
145
|
status_message = status.add_element 'samlp:StatusMessage'
|
127
146
|
status_message.text = logout_message
|
128
147
|
|
148
|
+
response_doc
|
149
|
+
end
|
150
|
+
|
151
|
+
def sign_document(document, settings)
|
129
152
|
# embed signature
|
130
|
-
if settings.
|
153
|
+
if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && settings.private_key && settings.certificate
|
131
154
|
private_key = settings.get_sp_key
|
132
155
|
cert = settings.get_sp_cert
|
133
|
-
|
156
|
+
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
134
157
|
end
|
135
158
|
|
136
|
-
|
159
|
+
document
|
137
160
|
end
|
138
161
|
|
139
162
|
end
|