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