ruby-saml 1.11.0 → 1.14.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 +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
|