ruby-saml 1.12.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.
Potentially problematic release.
This version of ruby-saml might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +25 -0
- data/{changelog.md → CHANGELOG.md} +26 -1
- data/README.md +321 -224
- data/UPGRADING.md +149 -0
- data/lib/onelogin/ruby-saml/attributes.rb +1 -1
- data/lib/onelogin/ruby-saml/authrequest.rb +3 -4
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +110 -77
- data/lib/onelogin/ruby-saml/logoutrequest.rb +8 -8
- data/lib/onelogin/ruby-saml/logoutresponse.rb +2 -2
- data/lib/onelogin/ruby-saml/metadata.rb +59 -22
- data/lib/onelogin/ruby-saml/response.rb +20 -17
- data/lib/onelogin/ruby-saml/saml_message.rb +6 -7
- data/lib/onelogin/ruby-saml/settings.rb +79 -69
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +13 -30
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +8 -7
- data/lib/onelogin/ruby-saml/utils.rb +50 -31
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +5 -7
- data/ruby-saml.gemspec +7 -2
- metadata +10 -9
- data/.travis.yml +0 -48
@@ -21,21 +21,50 @@ module OneLogin
|
|
21
21
|
#
|
22
22
|
def generate(settings, pretty_print=false, valid_until=nil, cache_duration=nil)
|
23
23
|
meta_doc = XMLSecurity::Document.new
|
24
|
+
add_xml_declaration(meta_doc)
|
25
|
+
root = add_root_element(meta_doc, settings, valid_until, cache_duration)
|
26
|
+
sp_sso = add_sp_sso_element(root, settings)
|
27
|
+
add_sp_certificates(sp_sso, settings)
|
28
|
+
add_sp_service_elements(sp_sso, settings)
|
29
|
+
add_extras(root, settings)
|
30
|
+
embed_signature(meta_doc, settings)
|
31
|
+
output_xml(meta_doc, pretty_print)
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def add_xml_declaration(meta_doc)
|
37
|
+
meta_doc << REXML::XMLDecl.new('1.0', 'UTF-8')
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_root_element(meta_doc, settings, valid_until, cache_duration)
|
24
41
|
namespaces = {
|
25
42
|
"xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata"
|
26
43
|
}
|
44
|
+
|
27
45
|
if settings.attribute_consuming_service.configured?
|
28
46
|
namespaces["xmlns:saml"] = "urn:oasis:names:tc:SAML:2.0:assertion"
|
29
47
|
end
|
30
|
-
|
31
|
-
|
48
|
+
|
49
|
+
root = meta_doc.add_element("md:EntityDescriptor", namespaces)
|
50
|
+
root.attributes["ID"] = OneLogin::RubySaml::Utils.uuid
|
51
|
+
root.attributes["entityID"] = settings.sp_entity_id if settings.sp_entity_id
|
52
|
+
root.attributes["validUntil"] = valid_until.strftime('%Y-%m-%dT%H:%M:%S%z') if valid_until
|
53
|
+
root.attributes["cacheDuration"] = "PT" + cache_duration.to_s + "S" if cache_duration
|
54
|
+
root
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_sp_sso_element(root, settings)
|
58
|
+
root.add_element "md:SPSSODescriptor", {
|
32
59
|
"protocolSupportEnumeration" => "urn:oasis:names:tc:SAML:2.0:protocol",
|
33
60
|
"AuthnRequestsSigned" => settings.security[:authn_requests_signed],
|
34
61
|
"WantAssertionsSigned" => settings.security[:want_assertions_signed],
|
35
62
|
}
|
63
|
+
end
|
36
64
|
|
37
|
-
|
38
|
-
|
65
|
+
# Add KeyDescriptor if messages will be signed / encrypted
|
66
|
+
# with SP certificate, and new SP certificate if any
|
67
|
+
def add_sp_certificates(sp_sso, settings)
|
39
68
|
cert = settings.get_sp_cert
|
40
69
|
cert_new = settings.get_sp_cert_new
|
41
70
|
|
@@ -58,16 +87,10 @@ module OneLogin
|
|
58
87
|
end
|
59
88
|
end
|
60
89
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
90
|
+
sp_sso
|
91
|
+
end
|
92
|
+
|
93
|
+
def add_sp_service_elements(sp_sso, settings)
|
71
94
|
if settings.single_logout_service_url
|
72
95
|
sp_sso.add_element "md:SingleLogoutService", {
|
73
96
|
"Binding" => settings.single_logout_service_binding,
|
@@ -75,10 +98,12 @@ module OneLogin
|
|
75
98
|
"ResponseLocation" => settings.single_logout_service_url
|
76
99
|
}
|
77
100
|
end
|
101
|
+
|
78
102
|
if settings.name_identifier_format
|
79
103
|
nameid = sp_sso.add_element "md:NameIDFormat"
|
80
104
|
nameid.text = settings.name_identifier_format
|
81
105
|
end
|
106
|
+
|
82
107
|
if settings.assertion_consumer_service_url
|
83
108
|
sp_sso.add_element "md:AssertionConsumerService", {
|
84
109
|
"Binding" => settings.assertion_consumer_service_binding,
|
@@ -117,15 +142,27 @@ module OneLogin
|
|
117
142
|
# <md:RoleDescriptor xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:query="urn:oasis:names:tc:SAML:metadata:ext:query" xsi:type="query:AttributeQueryDescriptorType" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"/>
|
118
143
|
# <md:XACMLAuthzDecisionQueryDescriptor WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"/>
|
119
144
|
|
120
|
-
|
145
|
+
sp_sso
|
146
|
+
end
|
121
147
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
148
|
+
# can be overridden in subclass
|
149
|
+
def add_extras(root, _settings)
|
150
|
+
root
|
151
|
+
end
|
152
|
+
|
153
|
+
def embed_signature(meta_doc, settings)
|
154
|
+
return unless settings.security[:metadata_signed]
|
155
|
+
|
156
|
+
private_key = settings.get_sp_key
|
157
|
+
cert = settings.get_sp_cert
|
158
|
+
return unless private_key && cert
|
159
|
+
|
160
|
+
meta_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
161
|
+
end
|
162
|
+
|
163
|
+
def output_xml(meta_doc, pretty_print)
|
164
|
+
ret = ''
|
127
165
|
|
128
|
-
ret = ""
|
129
166
|
# pretty print the XML so IdP administrators can easily see what the SP supports
|
130
167
|
if pretty_print
|
131
168
|
meta_doc.write(ret, 1)
|
@@ -133,7 +170,7 @@ module OneLogin
|
|
133
170
|
ret = meta_doc.to_s
|
134
171
|
end
|
135
172
|
|
136
|
-
|
173
|
+
ret
|
137
174
|
end
|
138
175
|
end
|
139
176
|
end
|
@@ -63,7 +63,7 @@ module OneLogin
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
-
@response = decode_raw_saml(response)
|
66
|
+
@response = decode_raw_saml(response, settings)
|
67
67
|
@document = XMLSecurity::SignedDocument.new(@response, @errors)
|
68
68
|
|
69
69
|
if assertion_encrypted?
|
@@ -227,11 +227,10 @@ module OneLogin
|
|
227
227
|
statuses = nodes.collect do |inner_node|
|
228
228
|
inner_node.attributes["Value"]
|
229
229
|
end
|
230
|
-
|
231
|
-
|
232
|
-
code = "#{code} | #{extra_code}"
|
233
|
-
end
|
230
|
+
|
231
|
+
code = [code, statuses].flatten.join(" | ")
|
234
232
|
end
|
233
|
+
|
235
234
|
code
|
236
235
|
end
|
237
236
|
end
|
@@ -338,9 +337,9 @@ module OneLogin
|
|
338
337
|
end
|
339
338
|
|
340
339
|
# returns the allowed clock drift on timing validation
|
341
|
-
# @return [
|
340
|
+
# @return [Float]
|
342
341
|
def allowed_clock_drift
|
343
|
-
|
342
|
+
options[:allowed_clock_drift].to_f.abs + Float::EPSILON
|
344
343
|
end
|
345
344
|
|
346
345
|
# Checks if the SAML Response contains or not an EncryptedAssertion element
|
@@ -377,7 +376,6 @@ module OneLogin
|
|
377
376
|
return false unless validate_response_state
|
378
377
|
|
379
378
|
validations = [
|
380
|
-
:validate_response_state,
|
381
379
|
:validate_version,
|
382
380
|
:validate_id,
|
383
381
|
:validate_success_status,
|
@@ -615,7 +613,12 @@ module OneLogin
|
|
615
613
|
#
|
616
614
|
def validate_audience
|
617
615
|
return true if options[:skip_audience]
|
618
|
-
return true if
|
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
|
619
622
|
|
620
623
|
unless audiences.include? settings.sp_entity_id
|
621
624
|
s = audiences.count > 1 ? 's' : '';
|
@@ -694,13 +697,13 @@ module OneLogin
|
|
694
697
|
|
695
698
|
now = Time.now.utc
|
696
699
|
|
697
|
-
if not_before &&
|
698
|
-
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})"
|
699
702
|
return append_error(error_msg)
|
700
703
|
end
|
701
704
|
|
702
|
-
if not_on_or_after && now >= (
|
703
|
-
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})"
|
704
707
|
return append_error(error_msg)
|
705
708
|
end
|
706
709
|
|
@@ -742,7 +745,7 @@ module OneLogin
|
|
742
745
|
return true if session_expires_at.nil?
|
743
746
|
|
744
747
|
now = Time.now.utc
|
745
|
-
unless (session_expires_at + allowed_clock_drift)
|
748
|
+
unless now < (session_expires_at + allowed_clock_drift)
|
746
749
|
error_msg = "The attributes have expired, based on the SessionNotOnOrAfter of the AuthnStatement of this Response"
|
747
750
|
return append_error(error_msg)
|
748
751
|
end
|
@@ -780,8 +783,8 @@ module OneLogin
|
|
780
783
|
|
781
784
|
attrs = confirmation_data_node.attributes
|
782
785
|
next if (attrs.include? "InResponseTo" and attrs['InResponseTo'] != in_response_to) ||
|
783
|
-
(attrs.include? "
|
784
|
-
(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)) ||
|
785
788
|
(attrs.include? "Recipient" and !options[:skip_recipient_check] and settings and attrs['Recipient'] != settings.assertion_consumer_service_url)
|
786
789
|
|
787
790
|
valid_subject_confirmation = true
|
@@ -828,7 +831,7 @@ module OneLogin
|
|
828
831
|
# otherwise, review if the decrypted assertion contains a signature
|
829
832
|
sig_elements = REXML::XPath.match(
|
830
833
|
document,
|
831
|
-
"/p:Response[@ID=$id]/ds:Signature
|
834
|
+
"/p:Response[@ID=$id]/ds:Signature",
|
832
835
|
{ "p" => PROTOCOL, "ds" => DSIG },
|
833
836
|
{ 'id' => document.signed_element_id }
|
834
837
|
)
|
@@ -16,14 +16,12 @@ 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
|
24
24
|
|
25
|
-
MAX_BYTE_SIZE = 250000
|
26
|
-
|
27
25
|
# @return [Nokogiri::XML::Schema] Gets the schema object of the SAML 2.0 Protocol schema
|
28
26
|
#
|
29
27
|
def self.schema
|
@@ -88,11 +86,12 @@ module OneLogin
|
|
88
86
|
# @param saml [String] The deflated and encoded SAML Message
|
89
87
|
# @return [String] The plain SAML Message
|
90
88
|
#
|
91
|
-
def decode_raw_saml(saml)
|
89
|
+
def decode_raw_saml(saml, settings = nil)
|
92
90
|
return saml unless base64_encoded?(saml)
|
93
91
|
|
94
|
-
|
95
|
-
|
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")
|
96
95
|
end
|
97
96
|
|
98
97
|
decoded = decode(saml)
|
@@ -31,9 +31,8 @@ module OneLogin
|
|
31
31
|
|
32
32
|
# IdP Data
|
33
33
|
attr_accessor :idp_entity_id
|
34
|
-
|
35
|
-
|
36
|
-
attr_accessor :idp_slo_service_url
|
34
|
+
attr_writer :idp_sso_service_url
|
35
|
+
attr_writer :idp_slo_service_url
|
37
36
|
attr_accessor :idp_slo_response_service_url
|
38
37
|
attr_accessor :idp_cert
|
39
38
|
attr_accessor :idp_cert_fingerprint
|
@@ -43,8 +42,10 @@ module OneLogin
|
|
43
42
|
attr_accessor :idp_name_qualifier
|
44
43
|
attr_accessor :valid_until
|
45
44
|
# SP Data
|
45
|
+
attr_writer :sp_entity_id
|
46
46
|
attr_accessor :assertion_consumer_service_url
|
47
|
-
|
47
|
+
attr_reader :assertion_consumer_service_binding
|
48
|
+
attr_writer :single_logout_service_url
|
48
49
|
attr_accessor :sp_name_qualifier
|
49
50
|
attr_accessor :name_identifier_format
|
50
51
|
attr_accessor :name_identifier_value
|
@@ -53,8 +54,9 @@ module OneLogin
|
|
53
54
|
attr_accessor :compress_request
|
54
55
|
attr_accessor :compress_response
|
55
56
|
attr_accessor :double_quote_xml_attribute_values
|
57
|
+
attr_accessor :message_max_bytesize
|
56
58
|
attr_accessor :passive
|
57
|
-
|
59
|
+
attr_reader :protocol_binding
|
58
60
|
attr_accessor :attributes_index
|
59
61
|
attr_accessor :force_authn
|
60
62
|
attr_accessor :certificate
|
@@ -67,9 +69,9 @@ module OneLogin
|
|
67
69
|
# Work-flow
|
68
70
|
attr_accessor :security
|
69
71
|
attr_accessor :soft
|
70
|
-
#
|
72
|
+
# Deprecated
|
71
73
|
attr_accessor :assertion_consumer_logout_service_url
|
72
|
-
|
74
|
+
attr_reader :assertion_consumer_logout_service_binding
|
73
75
|
attr_accessor :issuer
|
74
76
|
attr_accessor :idp_sso_target_url
|
75
77
|
attr_accessor :idp_slo_target_url
|
@@ -77,94 +79,89 @@ module OneLogin
|
|
77
79
|
# @return [String] IdP Single Sign On Service URL
|
78
80
|
#
|
79
81
|
def idp_sso_service_url
|
80
|
-
|
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
|
82
|
+
@idp_sso_service_url || @idp_sso_target_url
|
89
83
|
end
|
90
84
|
|
91
85
|
# @return [String] IdP Single Logout Service URL
|
92
86
|
#
|
93
87
|
def idp_slo_service_url
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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)
|
103
115
|
end
|
104
116
|
|
105
117
|
# @return [String] SP Entity ID
|
106
118
|
#
|
107
119
|
def sp_entity_id
|
108
|
-
|
109
|
-
if @sp_entity_id.nil?
|
110
|
-
if @issuer
|
111
|
-
val = @issuer
|
112
|
-
end
|
113
|
-
else
|
114
|
-
val = @sp_entity_id
|
115
|
-
end
|
116
|
-
val
|
120
|
+
@sp_entity_id || @issuer
|
117
121
|
end
|
118
122
|
|
119
|
-
# Setter for SP
|
120
|
-
# @param
|
123
|
+
# Setter for SP Protocol Binding
|
124
|
+
# @param value [String, Symbol].
|
121
125
|
#
|
122
|
-
def
|
123
|
-
@
|
126
|
+
def protocol_binding=(value)
|
127
|
+
@protocol_binding = get_binding(value)
|
124
128
|
end
|
125
129
|
|
126
|
-
#
|
130
|
+
# Setter for SP Assertion Consumer Service Binding
|
131
|
+
# @param value [String, Symbol].
|
127
132
|
#
|
128
|
-
def
|
129
|
-
|
130
|
-
if @single_logout_service_url.nil?
|
131
|
-
if @assertion_consumer_logout_service_url
|
132
|
-
val = @assertion_consumer_logout_service_url
|
133
|
-
end
|
134
|
-
else
|
135
|
-
val = @single_logout_service_url
|
136
|
-
end
|
137
|
-
val
|
133
|
+
def assertion_consumer_service_binding=(value)
|
134
|
+
@assertion_consumer_service_binding = get_binding(value)
|
138
135
|
end
|
139
136
|
|
140
|
-
#
|
141
|
-
# @param url [String].
|
137
|
+
# @return [String] Single Logout Service URL.
|
142
138
|
#
|
143
|
-
def single_logout_service_url
|
144
|
-
@single_logout_service_url
|
139
|
+
def single_logout_service_url
|
140
|
+
@single_logout_service_url || @assertion_consumer_logout_service_url
|
145
141
|
end
|
146
142
|
|
147
143
|
# @return [String] Single Logout Service Binding.
|
148
144
|
#
|
149
145
|
def single_logout_service_binding
|
150
|
-
|
151
|
-
if @single_logout_service_binding.nil?
|
152
|
-
if @assertion_consumer_logout_service_binding
|
153
|
-
val = @assertion_consumer_logout_service_binding
|
154
|
-
end
|
155
|
-
else
|
156
|
-
val = @single_logout_service_binding
|
157
|
-
end
|
158
|
-
val
|
146
|
+
@single_logout_service_binding || @assertion_consumer_logout_service_binding
|
159
147
|
end
|
160
148
|
|
161
149
|
# Setter for Single Logout Service Binding.
|
162
150
|
#
|
163
151
|
# (Currently we only support "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
|
164
|
-
# @param
|
152
|
+
# @param value [String, Symbol]
|
165
153
|
#
|
166
|
-
def single_logout_service_binding=(
|
167
|
-
@single_logout_service_binding =
|
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]
|
162
|
+
#
|
163
|
+
def assertion_consumer_logout_service_binding=(value)
|
164
|
+
@assertion_consumer_logout_service_binding = get_binding(value)
|
168
165
|
end
|
169
166
|
|
170
167
|
# Calculates the fingerprint of the IdP x509 certificate.
|
@@ -252,12 +249,23 @@ module OneLogin
|
|
252
249
|
|
253
250
|
private
|
254
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
|
+
|
255
262
|
DEFAULTS = {
|
256
|
-
:assertion_consumer_service_binding =>
|
257
|
-
:single_logout_service_binding =>
|
263
|
+
:assertion_consumer_service_binding => Utils::BINDINGS[:post],
|
264
|
+
:single_logout_service_binding => Utils::BINDINGS[:redirect],
|
258
265
|
:idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1,
|
259
266
|
:compress_request => true,
|
260
267
|
:compress_response => true,
|
268
|
+
:message_max_bytesize => 250000,
|
261
269
|
:soft => true,
|
262
270
|
:double_quote_xml_attribute_values => false,
|
263
271
|
:security => {
|
@@ -268,11 +276,13 @@ module OneLogin
|
|
268
276
|
:want_assertions_encrypted => false,
|
269
277
|
:want_name_id => false,
|
270
278
|
:metadata_signed => false,
|
271
|
-
:embed_sign => false,
|
279
|
+
:embed_sign => false, # Deprecated
|
272
280
|
:digest_method => XMLSecurity::Document::SHA1,
|
273
281
|
:signature_method => XMLSecurity::Document::RSA_SHA1,
|
274
282
|
:check_idp_cert_expiration => false,
|
275
|
-
:check_sp_cert_expiration => false
|
283
|
+
:check_sp_cert_expiration => false,
|
284
|
+
:strict_audience_validation => false,
|
285
|
+
:lowercase_url_encoding => false
|
276
286
|
}.freeze
|
277
287
|
}.freeze
|
278
288
|
end
|
@@ -43,7 +43,7 @@ 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
|
|
@@ -130,6 +130,12 @@ module OneLogin
|
|
130
130
|
|
131
131
|
private
|
132
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
|
+
|
133
139
|
# Hard aux function to validate the Logout Request
|
134
140
|
# @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true)
|
135
141
|
# @return [Boolean] TRUE if the Logout Request is valid
|
@@ -180,15 +186,17 @@ module OneLogin
|
|
180
186
|
true
|
181
187
|
end
|
182
188
|
|
183
|
-
# 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)
|
184
191
|
# If fails, the error is added to the errors array
|
185
192
|
# @return [Boolean] True if satisfies the conditions, otherwise False if soft=True
|
186
193
|
# @raise [ValidationError] if soft == false and validation fails
|
187
194
|
#
|
188
195
|
def validate_not_on_or_after
|
189
196
|
now = Time.now.utc
|
190
|
-
|
191
|
-
|
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})")
|
192
200
|
end
|
193
201
|
|
194
202
|
true
|
@@ -240,32 +248,8 @@ module OneLogin
|
|
240
248
|
return true unless options.has_key? :get_params
|
241
249
|
return true unless options[:get_params].has_key? 'Signature'
|
242
250
|
|
243
|
-
|
244
|
-
# of URI-encoded values _as sent by the IDP_:
|
245
|
-
#
|
246
|
-
# > Further, note that URL-encoding is not canonical; that is, there are multiple legal encodings for a given
|
247
|
-
# > value. The relying party MUST therefore perform the verification step using the original URL-encoded
|
248
|
-
# > values it received on the query string. It is not sufficient to re-encode the parameters after they have been
|
249
|
-
# > processed by software because the resulting encoding may not match the signer's encoding.
|
250
|
-
#
|
251
|
-
# <http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf>
|
252
|
-
#
|
253
|
-
# If we don't have the original parts (for backward compatibility) required to correctly verify the signature,
|
254
|
-
# then fabricate them by re-encoding the parsed URI parameters, and hope that we're lucky enough to use
|
255
|
-
# the exact same URI-encoding as the IDP. (This is not the case if the IDP is ADFS!)
|
256
|
-
options[:raw_get_params] ||= {}
|
257
|
-
if options[:raw_get_params]['SAMLRequest'].nil? && !options[:get_params]['SAMLRequest'].nil?
|
258
|
-
options[:raw_get_params]['SAMLRequest'] = CGI.escape(options[:get_params]['SAMLRequest'])
|
259
|
-
end
|
260
|
-
if options[:raw_get_params]['RelayState'].nil? && !options[:get_params]['RelayState'].nil?
|
261
|
-
options[:raw_get_params]['RelayState'] = CGI.escape(options[:get_params]['RelayState'])
|
262
|
-
end
|
263
|
-
if options[:raw_get_params]['SigAlg'].nil? && !options[:get_params]['SigAlg'].nil?
|
264
|
-
options[:raw_get_params]['SigAlg'] = CGI.escape(options[:get_params]['SigAlg'])
|
265
|
-
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])
|
266
252
|
|
267
|
-
# If we only received the raw version of SigAlg,
|
268
|
-
# then parse it back into the decoded params hash for convenience.
|
269
253
|
if options[:get_params]['SigAlg'].nil? && !options[:raw_get_params]['SigAlg'].nil?
|
270
254
|
options[:get_params]['SigAlg'] = CGI.unescape(options[:raw_get_params]['SigAlg'])
|
271
255
|
end
|
@@ -327,7 +311,6 @@ module OneLogin
|
|
327
311
|
|
328
312
|
true
|
329
313
|
end
|
330
|
-
|
331
314
|
end
|
332
315
|
end
|
333
316
|
end
|
@@ -13,7 +13,7 @@ module OneLogin
|
|
13
13
|
class SloLogoutresponse < SamlMessage
|
14
14
|
|
15
15
|
# Logout Response ID
|
16
|
-
|
16
|
+
attr_accessor :uuid
|
17
17
|
|
18
18
|
# Initializes the Logout Response. A SloLogoutresponse Object that is an extension of the SamlMessage class.
|
19
19
|
# Asigns an ID, a random uuid.
|
@@ -36,15 +36,15 @@ module OneLogin
|
|
36
36
|
#
|
37
37
|
def create(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil)
|
38
38
|
params = create_params(settings, request_id, logout_message, params, logout_status_code)
|
39
|
-
params_prefix = (settings.
|
40
|
-
url = settings.idp_slo_response_service_url || settings.
|
39
|
+
params_prefix = (settings.idp_slo_service_url =~ /\?/) ? '&' : '?'
|
40
|
+
url = settings.idp_slo_response_service_url || settings.idp_slo_service_url
|
41
41
|
saml_response = CGI.escape(params.delete("SAMLResponse"))
|
42
42
|
response_params = "#{params_prefix}SAMLResponse=#{saml_response}"
|
43
43
|
params.each_pair do |key, value|
|
44
44
|
response_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
45
45
|
end
|
46
46
|
|
47
|
-
raise SettingError.new "Invalid settings,
|
47
|
+
raise SettingError.new "Invalid settings, idp_slo_service_url is not set!" if url.nil? or url.empty?
|
48
48
|
@logout_url = url + response_params
|
49
49
|
end
|
50
50
|
|
@@ -79,7 +79,7 @@ module OneLogin
|
|
79
79
|
base64_response = encode(response)
|
80
80
|
response_params = {"SAMLResponse" => base64_response}
|
81
81
|
|
82
|
-
if settings.
|
82
|
+
if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_responses_signed] && settings.private_key
|
83
83
|
params['SigAlg'] = settings.security[:signature_method]
|
84
84
|
url_string = OneLogin::RubySaml::Utils.build_query(
|
85
85
|
:type => 'SAMLResponse',
|
@@ -117,7 +117,8 @@ module OneLogin
|
|
117
117
|
response_doc = XMLSecurity::Document.new
|
118
118
|
response_doc.uuid = uuid
|
119
119
|
|
120
|
-
destination = settings.idp_slo_response_service_url || settings.
|
120
|
+
destination = settings.idp_slo_response_service_url || settings.idp_slo_service_url
|
121
|
+
|
121
122
|
|
122
123
|
root = response_doc.add_element 'samlp:LogoutResponse', { 'xmlns:samlp' => 'urn:oasis:names:tc:SAML:2.0:protocol', "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
|
123
124
|
root.attributes['ID'] = uuid
|
@@ -149,7 +150,7 @@ module OneLogin
|
|
149
150
|
|
150
151
|
def sign_document(document, settings)
|
151
152
|
# embed signature
|
152
|
-
if settings.
|
153
|
+
if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && settings.private_key && settings.certificate
|
153
154
|
private_key = settings.get_sp_key
|
154
155
|
cert = settings.get_sp_cert
|
155
156
|
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|