ruby-saml 1.12.1 → 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 +4 -4
- data/.github/workflows/test.yml +25 -0
- data/{changelog.md → CHANGELOG.md} +24 -3
- data/README.md +318 -227
- data/UPGRADING.md +149 -0
- 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 +19 -16
- 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 +47 -28
- 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
|
@@ -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])
|