ruby-saml 1.11.0 → 1.14.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 +5 -5
- data/.github/workflows/test.yml +25 -0
- data/{changelog.md → CHANGELOG.md} +49 -1
- data/README.md +363 -218
- data/UPGRADING.md +149 -0
- data/lib/onelogin/ruby-saml/attributes.rb +24 -1
- data/lib/onelogin/ruby-saml/authrequest.rb +12 -8
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +154 -83
- data/lib/onelogin/ruby-saml/logoutrequest.rb +13 -7
- data/lib/onelogin/ruby-saml/logoutresponse.rb +6 -2
- data/lib/onelogin/ruby-saml/metadata.rb +62 -17
- data/lib/onelogin/ruby-saml/response.rb +57 -32
- data/lib/onelogin/ruby-saml/saml_message.rb +8 -3
- data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
- data/lib/onelogin/ruby-saml/settings.rb +92 -50
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +17 -30
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +32 -18
- data/lib/onelogin/ruby-saml/utils.rb +83 -8
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +39 -13
- data/ruby-saml.gemspec +14 -5
- metadata +29 -288
- data/.travis.yml +0 -46
- 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
@@ -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
|
|
@@ -61,7 +63,7 @@ module OneLogin
|
|
61
63
|
end
|
62
64
|
end
|
63
65
|
|
64
|
-
@response = decode_raw_saml(response)
|
66
|
+
@response = decode_raw_saml(response, settings)
|
65
67
|
@document = XMLSecurity::SignedDocument.new(@response, @errors)
|
66
68
|
|
67
69
|
if assertion_encrypted?
|
@@ -225,11 +227,10 @@ module OneLogin
|
|
225
227
|
statuses = nodes.collect do |inner_node|
|
226
228
|
inner_node.attributes["Value"]
|
227
229
|
end
|
228
|
-
|
229
|
-
|
230
|
-
code = "#{code} | #{extra_code}"
|
231
|
-
end
|
230
|
+
|
231
|
+
code = [code, statuses].flatten.join(" | ")
|
232
232
|
end
|
233
|
+
|
233
234
|
code
|
234
235
|
end
|
235
236
|
end
|
@@ -336,9 +337,31 @@ module OneLogin
|
|
336
337
|
end
|
337
338
|
|
338
339
|
# returns the allowed clock drift on timing validation
|
339
|
-
# @return [
|
340
|
+
# @return [Float]
|
340
341
|
def allowed_clock_drift
|
341
|
-
|
342
|
+
options[:allowed_clock_drift].to_f.abs + Float::EPSILON
|
343
|
+
end
|
344
|
+
|
345
|
+
# Checks if the SAML Response contains or not an EncryptedAssertion element
|
346
|
+
# @return [Boolean] True if the SAML Response contains an EncryptedAssertion element
|
347
|
+
#
|
348
|
+
def assertion_encrypted?
|
349
|
+
! REXML::XPath.first(
|
350
|
+
document,
|
351
|
+
"(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
|
352
|
+
{ "p" => PROTOCOL, "a" => ASSERTION }
|
353
|
+
).nil?
|
354
|
+
end
|
355
|
+
|
356
|
+
def response_id
|
357
|
+
id(document)
|
358
|
+
end
|
359
|
+
|
360
|
+
def assertion_id
|
361
|
+
@assertion_id ||= begin
|
362
|
+
node = xpath_first_from_signed_assertion("")
|
363
|
+
node.nil? ? nil : node.attributes['ID']
|
364
|
+
end
|
342
365
|
end
|
343
366
|
|
344
367
|
private
|
@@ -353,7 +376,6 @@ module OneLogin
|
|
353
376
|
return false unless validate_response_state
|
354
377
|
|
355
378
|
validations = [
|
356
|
-
:validate_response_state,
|
357
379
|
:validate_version,
|
358
380
|
:validate_id,
|
359
381
|
:validate_success_status,
|
@@ -435,7 +457,7 @@ module OneLogin
|
|
435
457
|
# @return [Boolean] True if the SAML Response contains an ID, otherwise returns False
|
436
458
|
#
|
437
459
|
def validate_id
|
438
|
-
unless
|
460
|
+
unless response_id
|
439
461
|
return append_error("Missing ID attribute on SAML Response")
|
440
462
|
end
|
441
463
|
|
@@ -584,12 +606,19 @@ module OneLogin
|
|
584
606
|
end
|
585
607
|
|
586
608
|
# Validates the Audience, (If the Audience match the Service Provider EntityID)
|
609
|
+
# If the response was initialized with the :skip_audience option, this validation is skipped,
|
587
610
|
# If fails, the error is added to the errors array
|
588
611
|
# @return [Boolean] True if there is an Audience Element that match the Service Provider EntityID, otherwise False if soft=True
|
589
612
|
# @raise [ValidationError] if soft == false and validation fails
|
590
613
|
#
|
591
614
|
def validate_audience
|
592
|
-
return true if
|
615
|
+
return true if options[:skip_audience]
|
616
|
+
return true if settings.sp_entity_id.nil? || settings.sp_entity_id.empty?
|
617
|
+
|
618
|
+
if audiences.empty?
|
619
|
+
return true unless settings.security[:strict_audience_validation]
|
620
|
+
return append_error("Invalid Audiences. The <AudienceRestriction> element contained only empty <Audience> elements. Expected audience #{settings.sp_entity_id}.")
|
621
|
+
end
|
593
622
|
|
594
623
|
unless audiences.include? settings.sp_entity_id
|
595
624
|
s = audiences.count > 1 ? 's' : '';
|
@@ -668,13 +697,13 @@ module OneLogin
|
|
668
697
|
|
669
698
|
now = Time.now.utc
|
670
699
|
|
671
|
-
if not_before &&
|
672
|
-
error_msg = "Current time is earlier than NotBefore condition (#{
|
700
|
+
if not_before && now < (not_before - allowed_clock_drift)
|
701
|
+
error_msg = "Current time is earlier than NotBefore condition (#{now} < #{not_before}#{" - #{allowed_clock_drift.ceil}s" if allowed_clock_drift > 0})"
|
673
702
|
return append_error(error_msg)
|
674
703
|
end
|
675
704
|
|
676
|
-
if not_on_or_after && now >= (
|
677
|
-
error_msg = "Current time is on or after NotOnOrAfter condition (#{now} >= #{
|
705
|
+
if not_on_or_after && now >= (not_on_or_after + allowed_clock_drift)
|
706
|
+
error_msg = "Current time is on or after NotOnOrAfter condition (#{now} >= #{not_on_or_after}#{" + #{allowed_clock_drift.ceil}s" if allowed_clock_drift > 0})"
|
678
707
|
return append_error(error_msg)
|
679
708
|
end
|
680
709
|
|
@@ -716,7 +745,7 @@ module OneLogin
|
|
716
745
|
return true if session_expires_at.nil?
|
717
746
|
|
718
747
|
now = Time.now.utc
|
719
|
-
unless (session_expires_at + allowed_clock_drift)
|
748
|
+
unless now < (session_expires_at + allowed_clock_drift)
|
720
749
|
error_msg = "The attributes have expired, based on the SessionNotOnOrAfter of the AuthnStatement of this Response"
|
721
750
|
return append_error(error_msg)
|
722
751
|
end
|
@@ -754,8 +783,8 @@ module OneLogin
|
|
754
783
|
|
755
784
|
attrs = confirmation_data_node.attributes
|
756
785
|
next if (attrs.include? "InResponseTo" and attrs['InResponseTo'] != in_response_to) ||
|
757
|
-
(attrs.include? "
|
758
|
-
(attrs.include? "
|
786
|
+
(attrs.include? "NotBefore" and now < (parse_time(confirmation_data_node, "NotBefore") - allowed_clock_drift)) ||
|
787
|
+
(attrs.include? "NotOnOrAfter" and now >= (parse_time(confirmation_data_node, "NotOnOrAfter") + allowed_clock_drift)) ||
|
759
788
|
(attrs.include? "Recipient" and !options[:skip_recipient_check] and settings and attrs['Recipient'] != settings.assertion_consumer_service_url)
|
760
789
|
|
761
790
|
valid_subject_confirmation = true
|
@@ -802,7 +831,7 @@ module OneLogin
|
|
802
831
|
# otherwise, review if the decrypted assertion contains a signature
|
803
832
|
sig_elements = REXML::XPath.match(
|
804
833
|
document,
|
805
|
-
"/p:Response[@ID=$id]/ds:Signature
|
834
|
+
"/p:Response[@ID=$id]/ds:Signature",
|
806
835
|
{ "p" => PROTOCOL, "ds" => DSIG },
|
807
836
|
{ 'id' => document.signed_element_id }
|
808
837
|
)
|
@@ -821,7 +850,7 @@ module OneLogin
|
|
821
850
|
end
|
822
851
|
|
823
852
|
if sig_elements.size != 1
|
824
|
-
if
|
853
|
+
if sig_elements.size == 0
|
825
854
|
append_error("Signed element id ##{doc.signed_element_id} is not found")
|
826
855
|
else
|
827
856
|
append_error("Signed element id ##{doc.signed_element_id} is found more than once")
|
@@ -829,6 +858,7 @@ module OneLogin
|
|
829
858
|
return append_error(error_msg)
|
830
859
|
end
|
831
860
|
|
861
|
+
old_errors = @errors.clone
|
832
862
|
|
833
863
|
idp_certs = settings.get_idp_cert_multi
|
834
864
|
if idp_certs.nil? || idp_certs[:signing].empty?
|
@@ -852,21 +882,27 @@ module OneLogin
|
|
852
882
|
valid = false
|
853
883
|
expired = false
|
854
884
|
idp_certs[:signing].each do |idp_cert|
|
855
|
-
valid = doc.validate_document_with_cert(idp_cert)
|
885
|
+
valid = doc.validate_document_with_cert(idp_cert, true)
|
856
886
|
if valid
|
857
887
|
if settings.security[:check_idp_cert_expiration]
|
858
888
|
if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
|
859
889
|
expired = true
|
860
890
|
end
|
861
891
|
end
|
892
|
+
|
893
|
+
# At least one certificate is valid, restore the old accumulated errors
|
894
|
+
@errors = old_errors
|
862
895
|
break
|
863
896
|
end
|
897
|
+
|
864
898
|
end
|
865
899
|
if expired
|
866
900
|
error_msg = "IdP x509 certificate expired"
|
867
901
|
return append_error(error_msg)
|
868
902
|
end
|
869
903
|
unless valid
|
904
|
+
# Remove duplicated errors
|
905
|
+
@errors = @errors.uniq
|
870
906
|
return append_error(error_msg)
|
871
907
|
end
|
872
908
|
end
|
@@ -967,17 +1003,6 @@ module OneLogin
|
|
967
1003
|
XMLSecurity::SignedDocument.new(response_node.to_s)
|
968
1004
|
end
|
969
1005
|
|
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
1006
|
# Decrypts an EncryptedAssertion element
|
982
1007
|
# @param encrypted_assertion_node [REXML::Element] The EncryptedAssertion element
|
983
1008
|
# @return [REXML::Document] The decrypted EncryptedAssertion element
|
@@ -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
|
@@ -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)
|
@@ -31,8 +31,9 @@ module OneLogin
|
|
31
31
|
|
32
32
|
# IdP Data
|
33
33
|
attr_accessor :idp_entity_id
|
34
|
-
|
35
|
-
|
34
|
+
attr_writer :idp_sso_service_url
|
35
|
+
attr_writer :idp_slo_service_url
|
36
|
+
attr_accessor :idp_slo_response_service_url
|
36
37
|
attr_accessor :idp_cert
|
37
38
|
attr_accessor :idp_cert_fingerprint
|
38
39
|
attr_accessor :idp_cert_fingerprint_algorithm
|
@@ -41,8 +42,10 @@ module OneLogin
|
|
41
42
|
attr_accessor :idp_name_qualifier
|
42
43
|
attr_accessor :valid_until
|
43
44
|
# SP Data
|
45
|
+
attr_writer :sp_entity_id
|
44
46
|
attr_accessor :assertion_consumer_service_url
|
45
|
-
|
47
|
+
attr_reader :assertion_consumer_service_binding
|
48
|
+
attr_writer :single_logout_service_url
|
46
49
|
attr_accessor :sp_name_qualifier
|
47
50
|
attr_accessor :name_identifier_format
|
48
51
|
attr_accessor :name_identifier_value
|
@@ -51,8 +54,9 @@ module OneLogin
|
|
51
54
|
attr_accessor :compress_request
|
52
55
|
attr_accessor :compress_response
|
53
56
|
attr_accessor :double_quote_xml_attribute_values
|
57
|
+
attr_accessor :message_max_bytesize
|
54
58
|
attr_accessor :passive
|
55
|
-
|
59
|
+
attr_reader :protocol_binding
|
56
60
|
attr_accessor :attributes_index
|
57
61
|
attr_accessor :force_authn
|
58
62
|
attr_accessor :certificate
|
@@ -65,74 +69,99 @@ module OneLogin
|
|
65
69
|
# Work-flow
|
66
70
|
attr_accessor :security
|
67
71
|
attr_accessor :soft
|
68
|
-
#
|
72
|
+
# Deprecated
|
69
73
|
attr_accessor :assertion_consumer_logout_service_url
|
70
|
-
|
74
|
+
attr_reader :assertion_consumer_logout_service_binding
|
71
75
|
attr_accessor :issuer
|
76
|
+
attr_accessor :idp_sso_target_url
|
77
|
+
attr_accessor :idp_slo_target_url
|
78
|
+
|
79
|
+
# @return [String] IdP Single Sign On Service URL
|
80
|
+
#
|
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
|
72
116
|
|
73
117
|
# @return [String] SP Entity ID
|
74
118
|
#
|
75
119
|
def sp_entity_id
|
76
|
-
|
77
|
-
if @sp_entity_id.nil?
|
78
|
-
if @issuer
|
79
|
-
val = @issuer
|
80
|
-
end
|
81
|
-
else
|
82
|
-
val = @sp_entity_id
|
83
|
-
end
|
84
|
-
val
|
120
|
+
@sp_entity_id || @issuer
|
85
121
|
end
|
86
122
|
|
87
|
-
# Setter for SP
|
88
|
-
# @param
|
123
|
+
# Setter for SP Protocol Binding
|
124
|
+
# @param value [String, Symbol].
|
89
125
|
#
|
90
|
-
def
|
91
|
-
@
|
126
|
+
def protocol_binding=(value)
|
127
|
+
@protocol_binding = get_binding(value)
|
92
128
|
end
|
93
129
|
|
94
|
-
#
|
130
|
+
# Setter for SP Assertion Consumer Service Binding
|
131
|
+
# @param value [String, Symbol].
|
95
132
|
#
|
96
|
-
def
|
97
|
-
|
98
|
-
if @single_logout_service_url.nil?
|
99
|
-
if @assertion_consumer_logout_service_url
|
100
|
-
val = @assertion_consumer_logout_service_url
|
101
|
-
end
|
102
|
-
else
|
103
|
-
val = @single_logout_service_url
|
104
|
-
end
|
105
|
-
val
|
133
|
+
def assertion_consumer_service_binding=(value)
|
134
|
+
@assertion_consumer_service_binding = get_binding(value)
|
106
135
|
end
|
107
136
|
|
108
|
-
#
|
109
|
-
# @param url [String].
|
137
|
+
# @return [String] Single Logout Service URL.
|
110
138
|
#
|
111
|
-
def single_logout_service_url
|
112
|
-
@single_logout_service_url
|
139
|
+
def single_logout_service_url
|
140
|
+
@single_logout_service_url || @assertion_consumer_logout_service_url
|
113
141
|
end
|
114
142
|
|
115
143
|
# @return [String] Single Logout Service Binding.
|
116
144
|
#
|
117
145
|
def single_logout_service_binding
|
118
|
-
|
119
|
-
if @single_logout_service_binding.nil?
|
120
|
-
if @assertion_consumer_logout_service_binding
|
121
|
-
val = @assertion_consumer_logout_service_binding
|
122
|
-
end
|
123
|
-
else
|
124
|
-
val = @single_logout_service_binding
|
125
|
-
end
|
126
|
-
val
|
146
|
+
@single_logout_service_binding || @assertion_consumer_logout_service_binding
|
127
147
|
end
|
128
148
|
|
129
149
|
# Setter for Single Logout Service Binding.
|
130
150
|
#
|
131
151
|
# (Currently we only support "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
|
132
|
-
# @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]
|
133
162
|
#
|
134
|
-
def
|
135
|
-
@
|
163
|
+
def assertion_consumer_logout_service_binding=(value)
|
164
|
+
@assertion_consumer_logout_service_binding = get_binding(value)
|
136
165
|
end
|
137
166
|
|
138
167
|
# Calculates the fingerprint of the IdP x509 certificate.
|
@@ -220,12 +249,23 @@ module OneLogin
|
|
220
249
|
|
221
250
|
private
|
222
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
|
+
|
223
262
|
DEFAULTS = {
|
224
|
-
:assertion_consumer_service_binding =>
|
225
|
-
:single_logout_service_binding =>
|
263
|
+
:assertion_consumer_service_binding => Utils::BINDINGS[:post],
|
264
|
+
:single_logout_service_binding => Utils::BINDINGS[:redirect],
|
226
265
|
:idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1,
|
227
266
|
:compress_request => true,
|
228
267
|
:compress_response => true,
|
268
|
+
:message_max_bytesize => 250000,
|
229
269
|
:soft => true,
|
230
270
|
:double_quote_xml_attribute_values => false,
|
231
271
|
:security => {
|
@@ -236,11 +276,13 @@ module OneLogin
|
|
236
276
|
:want_assertions_encrypted => false,
|
237
277
|
:want_name_id => false,
|
238
278
|
:metadata_signed => false,
|
239
|
-
:embed_sign => false,
|
279
|
+
:embed_sign => false, # Deprecated
|
240
280
|
:digest_method => XMLSecurity::Document::SHA1,
|
241
281
|
:signature_method => XMLSecurity::Document::RSA_SHA1,
|
242
282
|
:check_idp_cert_expiration => false,
|
243
|
-
:check_sp_cert_expiration => false
|
283
|
+
:check_sp_cert_expiration => false,
|
284
|
+
:strict_audience_validation => false,
|
285
|
+
:lowercase_url_encoding => false
|
244
286
|
}.freeze
|
245
287
|
}.freeze
|
246
288
|
end
|
@@ -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
|
@@ -236,32 +248,8 @@ module OneLogin
|
|
236
248
|
return true unless options.has_key? :get_params
|
237
249
|
return true unless options[:get_params].has_key? 'Signature'
|
238
250
|
|
239
|
-
|
240
|
-
# of URI-encoded values _as sent by the IDP_:
|
241
|
-
#
|
242
|
-
# > Further, note that URL-encoding is not canonical; that is, there are multiple legal encodings for a given
|
243
|
-
# > value. The relying party MUST therefore perform the verification step using the original URL-encoded
|
244
|
-
# > values it received on the query string. It is not sufficient to re-encode the parameters after they have been
|
245
|
-
# > processed by software because the resulting encoding may not match the signer's encoding.
|
246
|
-
#
|
247
|
-
# <http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf>
|
248
|
-
#
|
249
|
-
# If we don't have the original parts (for backward compatibility) required to correctly verify the signature,
|
250
|
-
# then fabricate them by re-encoding the parsed URI parameters, and hope that we're lucky enough to use
|
251
|
-
# the exact same URI-encoding as the IDP. (This is not the case if the IDP is ADFS!)
|
252
|
-
options[:raw_get_params] ||= {}
|
253
|
-
if options[:raw_get_params]['SAMLRequest'].nil? && !options[:get_params]['SAMLRequest'].nil?
|
254
|
-
options[:raw_get_params]['SAMLRequest'] = CGI.escape(options[:get_params]['SAMLRequest'])
|
255
|
-
end
|
256
|
-
if options[:raw_get_params]['RelayState'].nil? && !options[:get_params]['RelayState'].nil?
|
257
|
-
options[:raw_get_params]['RelayState'] = CGI.escape(options[:get_params]['RelayState'])
|
258
|
-
end
|
259
|
-
if options[:raw_get_params]['SigAlg'].nil? && !options[:get_params]['SigAlg'].nil?
|
260
|
-
options[:raw_get_params]['SigAlg'] = CGI.escape(options[:get_params]['SigAlg'])
|
261
|
-
end
|
251
|
+
options[:raw_get_params] = OneLogin::RubySaml::Utils.prepare_raw_get_params(options[:raw_get_params], options[:get_params], settings.security[:lowercase_url_encoding])
|
262
252
|
|
263
|
-
# If we only received the raw version of SigAlg,
|
264
|
-
# then parse it back into the decoded params hash for convenience.
|
265
253
|
if options[:get_params]['SigAlg'].nil? && !options[:raw_get_params]['SigAlg'].nil?
|
266
254
|
options[:get_params]['SigAlg'] = CGI.unescape(options[:raw_get_params]['SigAlg'])
|
267
255
|
end
|
@@ -323,7 +311,6 @@ module OneLogin
|
|
323
311
|
|
324
312
|
true
|
325
313
|
end
|
326
|
-
|
327
314
|
end
|
328
315
|
end
|
329
316
|
end
|