ruby-saml 1.11.0 → 1.12.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.
Potentially problematic release.
This version of ruby-saml might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/.travis.yml +14 -12
- data/README.md +67 -19
- data/changelog.md +23 -0
- data/lib/onelogin/ruby-saml/attributes.rb +24 -1
- data/lib/onelogin/ruby-saml/authrequest.rb +9 -4
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +62 -24
- data/lib/onelogin/ruby-saml/logoutrequest.rb +7 -1
- data/lib/onelogin/ruby-saml/logoutresponse.rb +4 -0
- data/lib/onelogin/ruby-saml/metadata.rb +9 -1
- data/lib/onelogin/ruby-saml/response.rb +37 -15
- data/lib/onelogin/ruby-saml/saml_message.rb +6 -0
- data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
- data/lib/onelogin/ruby-saml/settings.rb +34 -2
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +4 -0
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +27 -14
- data/lib/onelogin/ruby-saml/utils.rb +56 -0
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +34 -6
- data/ruby-saml.gemspec +8 -4
- metadata +22 -282
- data/test/certificates/certificate.der +0 -0
- 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 -594
- 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 -86
- data/test/logoutrequest_test.rb +0 -260
- data/test/logoutresponse_test.rb +0 -427
- 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 -59
- data/test/metadata/idp_multiple_descriptors_2.xml +0 -59
- data/test/metadata/no_idp_descriptor.xml +0 -21
- data/test/metadata_test.rb +0 -331
- data/test/request_test.rb +0 -340
- data/test/response_test.rb +0 -1629
- 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_with_formatted_x509certificate.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 -338
- data/test/slo_logoutrequest_test.rb +0 -467
- data/test/slo_logoutresponse_test.rb +0 -233
- data/test/test_helper.rb +0 -333
- data/test/utils_test.rb +0 -259
- data/test/xml_security_test.rb +0 -421
@@ -1,6 +1,7 @@
|
|
1
1
|
require "onelogin/ruby-saml/logging"
|
2
2
|
require "onelogin/ruby-saml/saml_message"
|
3
3
|
require "onelogin/ruby-saml/utils"
|
4
|
+
require "onelogin/ruby-saml/setting_error"
|
4
5
|
|
5
6
|
# Only supports SAML 2.0
|
6
7
|
module OneLogin
|
@@ -20,6 +21,10 @@ module OneLogin
|
|
20
21
|
@uuid = OneLogin::RubySaml::Utils.uuid
|
21
22
|
end
|
22
23
|
|
24
|
+
def request_id
|
25
|
+
@uuid
|
26
|
+
end
|
27
|
+
|
23
28
|
# Creates the Logout Request string.
|
24
29
|
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
25
30
|
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
@@ -33,6 +38,7 @@ module OneLogin
|
|
33
38
|
params.each_pair do |key, value|
|
34
39
|
request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
35
40
|
end
|
41
|
+
raise SettingError.new "Invalid settings, idp_slo_target_url is not set!" if settings.idp_slo_target_url.nil? or settings.idp_slo_target_url.empty?
|
36
42
|
@logout_url = settings.idp_slo_target_url + request_params
|
37
43
|
end
|
38
44
|
|
@@ -103,7 +109,7 @@ module OneLogin
|
|
103
109
|
root.attributes['ID'] = uuid
|
104
110
|
root.attributes['IssueInstant'] = time
|
105
111
|
root.attributes['Version'] = "2.0"
|
106
|
-
root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil?
|
112
|
+
root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil? or settings.idp_slo_target_url.empty?
|
107
113
|
|
108
114
|
if settings.sp_entity_id
|
109
115
|
issuer = root.add_element "saml:Issuer"
|
@@ -47,6 +47,10 @@ module OneLogin
|
|
47
47
|
@document = XMLSecurity::SignedDocument.new(@response)
|
48
48
|
end
|
49
49
|
|
50
|
+
def response_id
|
51
|
+
id(document)
|
52
|
+
end
|
53
|
+
|
50
54
|
# Checks if the Status has the "Success" code
|
51
55
|
# @return [Boolean] True if the StatusCode is Sucess
|
52
56
|
# @raise [ValidationError] if soft == false and validation fails
|
@@ -15,9 +15,11 @@ module OneLogin
|
|
15
15
|
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
16
16
|
# @param pretty_print [Boolean] Pretty print or not the response
|
17
17
|
# (No pretty print if you gonna validate the signature)
|
18
|
+
# @param valid_until [DateTime] Metadata's valid time
|
19
|
+
# @param cache_duration [Integer] Duration of the cache in seconds
|
18
20
|
# @return [String] XML Metadata of the Service Provider
|
19
21
|
#
|
20
|
-
def generate(settings, pretty_print=false)
|
22
|
+
def generate(settings, pretty_print=false, valid_until=nil, cache_duration=nil)
|
21
23
|
meta_doc = XMLSecurity::Document.new
|
22
24
|
namespaces = {
|
23
25
|
"xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata"
|
@@ -60,6 +62,12 @@ module OneLogin
|
|
60
62
|
if settings.sp_entity_id
|
61
63
|
root.attributes["entityID"] = settings.sp_entity_id
|
62
64
|
end
|
65
|
+
if valid_until
|
66
|
+
root.attributes["validUntil"] = valid_until.strftime('%Y-%m-%dT%H:%M:%S%z')
|
67
|
+
end
|
68
|
+
if cache_duration
|
69
|
+
root.attributes["cacheDuration"] = "PT" + cache_duration.to_s + "S"
|
70
|
+
end
|
63
71
|
if settings.single_logout_service_url
|
64
72
|
sp_sso.add_element "md:SingleLogoutService", {
|
65
73
|
"Binding" => settings.single_logout_service_binding,
|
@@ -34,7 +34,7 @@ module OneLogin
|
|
34
34
|
# This is not a whitelist to allow people extending OneLogin::RubySaml:Response
|
35
35
|
# and pass custom options
|
36
36
|
AVAILABLE_OPTIONS = [
|
37
|
-
:allowed_clock_drift, :check_duplicated_attributes, :matches_request_id, :settings, :skip_authnstatement, :skip_conditions,
|
37
|
+
:allowed_clock_drift, :check_duplicated_attributes, :matches_request_id, :settings, :skip_audience, :skip_authnstatement, :skip_conditions,
|
38
38
|
:skip_destination, :skip_recipient_check, :skip_subject_confirmation
|
39
39
|
]
|
40
40
|
# TODO: Update the comment on initialize to describe every option
|
@@ -47,6 +47,8 @@ module OneLogin
|
|
47
47
|
# or :matches_request_id that will validate that the response matches the ID of the request,
|
48
48
|
# or skip the subject confirmation validation with the :skip_subject_confirmation option
|
49
49
|
# or skip the recipient validation of the subject confirmation element with :skip_recipient_check option
|
50
|
+
# or skip the audience validation with :skip_audience option
|
51
|
+
#
|
50
52
|
def initialize(response, options = {})
|
51
53
|
raise ArgumentError.new("Response cannot be nil") if response.nil?
|
52
54
|
|
@@ -341,6 +343,28 @@ module OneLogin
|
|
341
343
|
return options[:allowed_clock_drift].to_f
|
342
344
|
end
|
343
345
|
|
346
|
+
# Checks if the SAML Response contains or not an EncryptedAssertion element
|
347
|
+
# @return [Boolean] True if the SAML Response contains an EncryptedAssertion element
|
348
|
+
#
|
349
|
+
def assertion_encrypted?
|
350
|
+
! REXML::XPath.first(
|
351
|
+
document,
|
352
|
+
"(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
|
353
|
+
{ "p" => PROTOCOL, "a" => ASSERTION }
|
354
|
+
).nil?
|
355
|
+
end
|
356
|
+
|
357
|
+
def response_id
|
358
|
+
id(document)
|
359
|
+
end
|
360
|
+
|
361
|
+
def assertion_id
|
362
|
+
@assertion_id ||= begin
|
363
|
+
node = xpath_first_from_signed_assertion("")
|
364
|
+
node.nil? ? nil : node.attributes['ID']
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
344
368
|
private
|
345
369
|
|
346
370
|
# Validates the SAML Response (calls several validation methods)
|
@@ -435,7 +459,7 @@ module OneLogin
|
|
435
459
|
# @return [Boolean] True if the SAML Response contains an ID, otherwise returns False
|
436
460
|
#
|
437
461
|
def validate_id
|
438
|
-
unless
|
462
|
+
unless response_id
|
439
463
|
return append_error("Missing ID attribute on SAML Response")
|
440
464
|
end
|
441
465
|
|
@@ -584,11 +608,13 @@ module OneLogin
|
|
584
608
|
end
|
585
609
|
|
586
610
|
# Validates the Audience, (If the Audience match the Service Provider EntityID)
|
611
|
+
# If the response was initialized with the :skip_audience option, this validation is skipped,
|
587
612
|
# If fails, the error is added to the errors array
|
588
613
|
# @return [Boolean] True if there is an Audience Element that match the Service Provider EntityID, otherwise False if soft=True
|
589
614
|
# @raise [ValidationError] if soft == false and validation fails
|
590
615
|
#
|
591
616
|
def validate_audience
|
617
|
+
return true if options[:skip_audience]
|
592
618
|
return true if audiences.empty? || settings.sp_entity_id.nil? || settings.sp_entity_id.empty?
|
593
619
|
|
594
620
|
unless audiences.include? settings.sp_entity_id
|
@@ -821,7 +847,7 @@ module OneLogin
|
|
821
847
|
end
|
822
848
|
|
823
849
|
if sig_elements.size != 1
|
824
|
-
if
|
850
|
+
if sig_elements.size == 0
|
825
851
|
append_error("Signed element id ##{doc.signed_element_id} is not found")
|
826
852
|
else
|
827
853
|
append_error("Signed element id ##{doc.signed_element_id} is found more than once")
|
@@ -829,6 +855,7 @@ module OneLogin
|
|
829
855
|
return append_error(error_msg)
|
830
856
|
end
|
831
857
|
|
858
|
+
old_errors = @errors.clone
|
832
859
|
|
833
860
|
idp_certs = settings.get_idp_cert_multi
|
834
861
|
if idp_certs.nil? || idp_certs[:signing].empty?
|
@@ -852,21 +879,27 @@ module OneLogin
|
|
852
879
|
valid = false
|
853
880
|
expired = false
|
854
881
|
idp_certs[:signing].each do |idp_cert|
|
855
|
-
valid = doc.validate_document_with_cert(idp_cert)
|
882
|
+
valid = doc.validate_document_with_cert(idp_cert, true)
|
856
883
|
if valid
|
857
884
|
if settings.security[:check_idp_cert_expiration]
|
858
885
|
if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
|
859
886
|
expired = true
|
860
887
|
end
|
861
888
|
end
|
889
|
+
|
890
|
+
# At least one certificate is valid, restore the old accumulated errors
|
891
|
+
@errors = old_errors
|
862
892
|
break
|
863
893
|
end
|
894
|
+
|
864
895
|
end
|
865
896
|
if expired
|
866
897
|
error_msg = "IdP x509 certificate expired"
|
867
898
|
return append_error(error_msg)
|
868
899
|
end
|
869
900
|
unless valid
|
901
|
+
# Remove duplicated errors
|
902
|
+
@errors = @errors.uniq
|
870
903
|
return append_error(error_msg)
|
871
904
|
end
|
872
905
|
end
|
@@ -967,17 +1000,6 @@ module OneLogin
|
|
967
1000
|
XMLSecurity::SignedDocument.new(response_node.to_s)
|
968
1001
|
end
|
969
1002
|
|
970
|
-
# Checks if the SAML Response contains or not an EncryptedAssertion element
|
971
|
-
# @return [Boolean] True if the SAML Response contains an EncryptedAssertion element
|
972
|
-
#
|
973
|
-
def assertion_encrypted?
|
974
|
-
! REXML::XPath.first(
|
975
|
-
document,
|
976
|
-
"(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
|
977
|
-
{ "p" => PROTOCOL, "a" => ASSERTION }
|
978
|
-
).nil?
|
979
|
-
end
|
980
|
-
|
981
1003
|
# Decrypts an EncryptedAssertion element
|
982
1004
|
# @param encrypted_assertion_node [REXML::Element] The EncryptedAssertion element
|
983
1005
|
# @return [REXML::Document] The decrypted EncryptedAssertion element
|
@@ -22,6 +22,8 @@ module OneLogin
|
|
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
|
24
24
|
|
25
|
+
MAX_BYTE_SIZE = 250000
|
26
|
+
|
25
27
|
# @return [Nokogiri::XML::Schema] Gets the schema object of the SAML 2.0 Protocol schema
|
26
28
|
#
|
27
29
|
def self.schema
|
@@ -89,6 +91,10 @@ module OneLogin
|
|
89
91
|
def decode_raw_saml(saml)
|
90
92
|
return saml unless base64_encoded?(saml)
|
91
93
|
|
94
|
+
if saml.bytesize > MAX_BYTE_SIZE
|
95
|
+
raise ValidationError.new("Encoded SAML Message exceeds " + MAX_BYTE_SIZE.to_s + " bytes, so was rejected")
|
96
|
+
end
|
97
|
+
|
92
98
|
decoded = decode(saml)
|
93
99
|
begin
|
94
100
|
inflate(decoded)
|
@@ -31,8 +31,10 @@ module OneLogin
|
|
31
31
|
|
32
32
|
# IdP Data
|
33
33
|
attr_accessor :idp_entity_id
|
34
|
-
|
35
|
-
attr_accessor :
|
34
|
+
|
35
|
+
attr_accessor :idp_sso_service_url
|
36
|
+
attr_accessor :idp_slo_service_url
|
37
|
+
attr_accessor :idp_slo_response_service_url
|
36
38
|
attr_accessor :idp_cert
|
37
39
|
attr_accessor :idp_cert_fingerprint
|
38
40
|
attr_accessor :idp_cert_fingerprint_algorithm
|
@@ -69,6 +71,36 @@ module OneLogin
|
|
69
71
|
attr_accessor :assertion_consumer_logout_service_url
|
70
72
|
attr_accessor :assertion_consumer_logout_service_binding
|
71
73
|
attr_accessor :issuer
|
74
|
+
attr_accessor :idp_sso_target_url
|
75
|
+
attr_accessor :idp_slo_target_url
|
76
|
+
|
77
|
+
# @return [String] IdP Single Sign On Service URL
|
78
|
+
#
|
79
|
+
def idp_sso_service_url
|
80
|
+
val = nil
|
81
|
+
if @idp_sso_service_url.nil?
|
82
|
+
if @idp_sso_target_url
|
83
|
+
val = @idp_sso_target_url
|
84
|
+
end
|
85
|
+
else
|
86
|
+
val = @idp_sso_service_url
|
87
|
+
end
|
88
|
+
val
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [String] IdP Single Logout Service URL
|
92
|
+
#
|
93
|
+
def idp_slo_service_url
|
94
|
+
val = nil
|
95
|
+
if @idp_slo_service_url.nil?
|
96
|
+
if @idp_slo_target_url
|
97
|
+
val = @idp_slo_target_url
|
98
|
+
end
|
99
|
+
else
|
100
|
+
val = @idp_slo_service_url
|
101
|
+
end
|
102
|
+
val
|
103
|
+
end
|
72
104
|
|
73
105
|
# @return [String] SP Entity ID
|
74
106
|
#
|
@@ -47,6 +47,10 @@ module OneLogin
|
|
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
|
@@ -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)
|
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)
|
33
39
|
params_prefix = (settings.idp_slo_target_url =~ /\?/) ? '&' : '?'
|
40
|
+
url = settings.idp_slo_response_service_url || settings.idp_slo_target_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_target_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 = ""
|
@@ -94,39 +103,43 @@ 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)
|
100
|
-
document = create_xml_document(settings, request_id, logout_message)
|
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)
|
101
111
|
sign_document(document, settings)
|
102
112
|
end
|
103
113
|
|
104
|
-
def create_xml_document(settings, request_id = nil, logout_message = nil)
|
114
|
+
def create_xml_document(settings, request_id = nil, logout_message = nil, status_code = nil)
|
105
115
|
time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
106
116
|
|
107
117
|
response_doc = XMLSecurity::Document.new
|
108
118
|
response_doc.uuid = uuid
|
109
119
|
|
120
|
+
destination = settings.idp_slo_response_service_url || settings.idp_slo_target_url
|
121
|
+
|
110
122
|
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" }
|
111
123
|
root.attributes['ID'] = uuid
|
112
124
|
root.attributes['IssueInstant'] = time
|
113
125
|
root.attributes['Version'] = '2.0'
|
114
126
|
root.attributes['InResponseTo'] = request_id unless request_id.nil?
|
115
|
-
root.attributes['Destination'] =
|
127
|
+
root.attributes['Destination'] = destination unless destination.nil? or destination.empty?
|
116
128
|
|
117
129
|
if settings.sp_entity_id != nil
|
118
130
|
issuer = root.add_element "saml:Issuer"
|
119
131
|
issuer.text = settings.sp_entity_id
|
120
132
|
end
|
121
133
|
|
122
|
-
# add
|
134
|
+
# add status
|
123
135
|
status = root.add_element 'samlp:Status'
|
124
136
|
|
125
|
-
#
|
126
|
-
status_code
|
127
|
-
|
137
|
+
# status code
|
138
|
+
status_code ||= 'urn:oasis:names:tc:SAML:2.0:status:Success'
|
139
|
+
status_code_elem = status.add_element 'samlp:StatusCode'
|
140
|
+
status_code_elem.attributes['Value'] = status_code
|
128
141
|
|
129
|
-
#
|
142
|
+
# status message
|
130
143
|
logout_message ||= 'Successfully Signed Out'
|
131
144
|
status_message = status.add_element 'samlp:StatusMessage'
|
132
145
|
status_message.text = logout_message
|
@@ -15,6 +15,7 @@ module OneLogin
|
|
15
15
|
|
16
16
|
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
17
17
|
XENC = "http://www.w3.org/2001/04/xmlenc#"
|
18
|
+
DURATION_FORMAT = %r(^(-?)P(?:(?:(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?)|(?:(\d+)W))$)
|
18
19
|
|
19
20
|
# Checks if the x509 cert provided is expired
|
20
21
|
#
|
@@ -28,6 +29,48 @@ module OneLogin
|
|
28
29
|
return cert.not_after < Time.now
|
29
30
|
end
|
30
31
|
|
32
|
+
# Interprets a ISO8601 duration value relative to a given timestamp.
|
33
|
+
#
|
34
|
+
# @param duration [String] The duration, as a string.
|
35
|
+
# @param timestamp [Integer] The unix timestamp we should apply the
|
36
|
+
# duration to. Optional, default to the
|
37
|
+
# current time.
|
38
|
+
#
|
39
|
+
# @return [Integer] The new timestamp, after the duration is applied.
|
40
|
+
#
|
41
|
+
def self.parse_duration(duration, timestamp=Time.now.utc)
|
42
|
+
matches = duration.match(DURATION_FORMAT)
|
43
|
+
|
44
|
+
if matches.nil?
|
45
|
+
raise Exception.new("Invalid ISO 8601 duration")
|
46
|
+
end
|
47
|
+
|
48
|
+
durYears = matches[2].to_i
|
49
|
+
durMonths = matches[3].to_i
|
50
|
+
durDays = matches[4].to_i
|
51
|
+
durHours = matches[5].to_i
|
52
|
+
durMinutes = matches[6].to_i
|
53
|
+
durSeconds = matches[7].to_f
|
54
|
+
durWeeks = matches[8].to_i
|
55
|
+
|
56
|
+
if matches[1] == "-"
|
57
|
+
durYears = -durYears
|
58
|
+
durMonths = -durMonths
|
59
|
+
durDays = -durDays
|
60
|
+
durHours = -durHours
|
61
|
+
durMinutes = -durMinutes
|
62
|
+
durSeconds = -durSeconds
|
63
|
+
durWeeks = -durWeeks
|
64
|
+
end
|
65
|
+
|
66
|
+
initial_datetime = Time.at(timestamp).utc.to_datetime
|
67
|
+
final_datetime = initial_datetime.next_year(durYears)
|
68
|
+
final_datetime = final_datetime.next_month(durMonths)
|
69
|
+
final_datetime = final_datetime.next_day((7*durWeeks) + durDays)
|
70
|
+
final_timestamp = final_datetime.to_time.utc.to_i + (durHours * 3600) + (durMinutes * 60) + durSeconds
|
71
|
+
return final_timestamp
|
72
|
+
end
|
73
|
+
|
31
74
|
# Return a properly formatted x509 certificate
|
32
75
|
#
|
33
76
|
# @param cert [String] The original certificate
|
@@ -253,6 +296,9 @@ module OneLogin
|
|
253
296
|
when 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' then cipher = OpenSSL::Cipher.new('AES-128-CBC').decrypt
|
254
297
|
when 'http://www.w3.org/2001/04/xmlenc#aes192-cbc' then cipher = OpenSSL::Cipher.new('AES-192-CBC').decrypt
|
255
298
|
when 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' then cipher = OpenSSL::Cipher.new('AES-256-CBC').decrypt
|
299
|
+
when 'http://www.w3.org/2009/xmlenc11#aes128-gcm' then auth_cipher = OpenSSL::Cipher.new('AES-128-GCM').decrypt
|
300
|
+
when 'http://www.w3.org/2009/xmlenc11#aes192-gcm' then auth_cipher = OpenSSL::Cipher.new('AES-192-GCM').decrypt
|
301
|
+
when 'http://www.w3.org/2009/xmlenc11#aes256-gcm' then auth_cipher = OpenSSL::Cipher.new('AES-256-GCM').decrypt
|
256
302
|
when 'http://www.w3.org/2001/04/xmlenc#rsa-1_5' then rsa = symmetric_key
|
257
303
|
when 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' then oaep = symmetric_key
|
258
304
|
end
|
@@ -263,6 +309,16 @@ module OneLogin
|
|
263
309
|
cipher.padding, cipher.key, cipher.iv = 0, symmetric_key, cipher_text[0..iv_len-1]
|
264
310
|
assertion_plaintext = cipher.update(data)
|
265
311
|
assertion_plaintext << cipher.final
|
312
|
+
elsif auth_cipher
|
313
|
+
iv_len, text_len, tag_len = auth_cipher.iv_len, cipher_text.length, 16
|
314
|
+
data = cipher_text[iv_len..text_len-1-tag_len]
|
315
|
+
auth_cipher.padding = 0
|
316
|
+
auth_cipher.key = symmetric_key
|
317
|
+
auth_cipher.iv = cipher_text[0..iv_len-1]
|
318
|
+
auth_cipher.auth_data = ''
|
319
|
+
auth_cipher.auth_tag = cipher_text[text_len-tag_len..-1]
|
320
|
+
assertion_plaintext = auth_cipher.update(data)
|
321
|
+
assertion_plaintext << auth_cipher.final
|
266
322
|
elsif rsa
|
267
323
|
rsa.private_decrypt(cipher_text)
|
268
324
|
elsif oaep
|