ruby-saml 1.12.1 → 1.13.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} +19 -3
- data/README.md +288 -226
- data/UPGRADING.md +149 -0
- data/lib/onelogin/ruby-saml/authrequest.rb +2 -3
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +110 -77
- data/lib/onelogin/ruby-saml/logoutrequest.rb +7 -7
- data/lib/onelogin/ruby-saml/logoutresponse.rb +1 -1
- data/lib/onelogin/ruby-saml/metadata.rb +59 -22
- data/lib/onelogin/ruby-saml/response.rb +13 -15
- data/lib/onelogin/ruby-saml/saml_message.rb +6 -7
- data/lib/onelogin/ruby-saml/settings.rb +76 -68
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +12 -4
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +7 -6
- data/lib/onelogin/ruby-saml/utils.rb +27 -22
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +5 -7
- metadata +7 -5
- 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,
|
@@ -694,13 +692,13 @@ module OneLogin
|
|
694
692
|
|
695
693
|
now = Time.now.utc
|
696
694
|
|
697
|
-
if not_before &&
|
698
|
-
error_msg = "Current time is earlier than NotBefore condition (#{
|
695
|
+
if not_before && now < (not_before - allowed_clock_drift)
|
696
|
+
error_msg = "Current time is earlier than NotBefore condition (#{now} < #{not_before}#{" - #{allowed_clock_drift.ceil}s" if allowed_clock_drift > 0})"
|
699
697
|
return append_error(error_msg)
|
700
698
|
end
|
701
699
|
|
702
|
-
if not_on_or_after && now >= (
|
703
|
-
error_msg = "Current time is on or after NotOnOrAfter condition (#{now} >= #{
|
700
|
+
if not_on_or_after && now >= (not_on_or_after + allowed_clock_drift)
|
701
|
+
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
702
|
return append_error(error_msg)
|
705
703
|
end
|
706
704
|
|
@@ -742,7 +740,7 @@ module OneLogin
|
|
742
740
|
return true if session_expires_at.nil?
|
743
741
|
|
744
742
|
now = Time.now.utc
|
745
|
-
unless (session_expires_at + allowed_clock_drift)
|
743
|
+
unless now < (session_expires_at + allowed_clock_drift)
|
746
744
|
error_msg = "The attributes have expired, based on the SessionNotOnOrAfter of the AuthnStatement of this Response"
|
747
745
|
return append_error(error_msg)
|
748
746
|
end
|
@@ -780,8 +778,8 @@ module OneLogin
|
|
780
778
|
|
781
779
|
attrs = confirmation_data_node.attributes
|
782
780
|
next if (attrs.include? "InResponseTo" and attrs['InResponseTo'] != in_response_to) ||
|
783
|
-
(attrs.include? "
|
784
|
-
(attrs.include? "
|
781
|
+
(attrs.include? "NotBefore" and now < (parse_time(confirmation_data_node, "NotBefore") - allowed_clock_drift)) ||
|
782
|
+
(attrs.include? "NotOnOrAfter" and now >= (parse_time(confirmation_data_node, "NotOnOrAfter") + allowed_clock_drift)) ||
|
785
783
|
(attrs.include? "Recipient" and !options[:skip_recipient_check] and settings and attrs['Recipient'] != settings.assertion_consumer_service_url)
|
786
784
|
|
787
785
|
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,7 +276,7 @@ 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,
|
@@ -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
|
@@ -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])
|
@@ -13,9 +13,25 @@ module OneLogin
|
|
13
13
|
class Utils
|
14
14
|
@@uuid_generator = UUID.new if RUBY_VERSION < '1.9'
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
BINDINGS = { :post => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze,
|
17
|
+
:redirect => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze }.freeze
|
18
|
+
DSIG = "http://www.w3.org/2000/09/xmldsig#".freeze
|
19
|
+
XENC = "http://www.w3.org/2001/04/xmlenc#".freeze
|
20
|
+
DURATION_FORMAT = %r(^
|
21
|
+
(-?)P # 1: Duration sign
|
22
|
+
(?:
|
23
|
+
(?:(\d+)Y)? # 2: Years
|
24
|
+
(?:(\d+)M)? # 3: Months
|
25
|
+
(?:(\d+)D)? # 4: Days
|
26
|
+
(?:T
|
27
|
+
(?:(\d+)H)? # 5: Hours
|
28
|
+
(?:(\d+)M)? # 6: Minutes
|
29
|
+
(?:(\d+(?:[.,]\d+)?)S)? # 7: Seconds
|
30
|
+
)?
|
31
|
+
|
|
32
|
+
(\d+)W # 8: Weeks
|
33
|
+
)
|
34
|
+
$)x.freeze
|
19
35
|
|
20
36
|
# Checks if the x509 cert provided is expired
|
21
37
|
#
|
@@ -37,31 +53,20 @@ module OneLogin
|
|
37
53
|
# current time.
|
38
54
|
#
|
39
55
|
# @return [Integer] The new timestamp, after the duration is applied.
|
40
|
-
#
|
56
|
+
#
|
41
57
|
def self.parse_duration(duration, timestamp=Time.now.utc)
|
58
|
+
return nil if RUBY_VERSION < '1.9' # 1.8.7 not supported
|
59
|
+
|
42
60
|
matches = duration.match(DURATION_FORMAT)
|
43
|
-
|
61
|
+
|
44
62
|
if matches.nil?
|
45
63
|
raise Exception.new("Invalid ISO 8601 duration")
|
46
64
|
end
|
47
65
|
|
48
|
-
|
49
|
-
|
50
|
-
durDays =
|
51
|
-
|
52
|
-
durMinutes = matches[6].to_i
|
53
|
-
durSeconds = matches[7].to_f
|
54
|
-
durWeeks = matches[8].to_i
|
55
|
-
|
56
|
-
if matches[1] == "-"
|
57
|
-
durYears = -durYears
|
58
|
-
durMonths = -durMonths
|
59
|
-
durDays = -durDays
|
60
|
-
durHours = -durHours
|
61
|
-
durMinutes = -durMinutes
|
62
|
-
durSeconds = -durSeconds
|
63
|
-
durWeeks = -durWeeks
|
64
|
-
end
|
66
|
+
sign = matches[1] == '-' ? -1 : 1
|
67
|
+
|
68
|
+
durYears, durMonths, durDays, durHours, durMinutes, durSeconds, durWeeks =
|
69
|
+
matches[2..8].map { |match| match ? sign * match.tr(',', '.').to_f : 0.0 }
|
65
70
|
|
66
71
|
initial_datetime = Time.at(timestamp).utc.to_datetime
|
67
72
|
final_datetime = initial_datetime.next_year(durYears)
|
data/lib/xml_security.rb
CHANGED
@@ -159,15 +159,13 @@ module XMLSecurity
|
|
159
159
|
x509_cert_element.text = Base64.encode64(certificate.to_der).gsub(/\n/, "")
|
160
160
|
|
161
161
|
# add the signature
|
162
|
-
issuer_element =
|
162
|
+
issuer_element = elements["//saml:Issuer"]
|
163
163
|
if issuer_element
|
164
|
-
|
164
|
+
root.insert_after(issuer_element, signature_element)
|
165
|
+
elsif first_child = root.children[0]
|
166
|
+
root.insert_before(first_child, signature_element)
|
165
167
|
else
|
166
|
-
|
167
|
-
self.root.insert_before sp_sso_descriptor, signature_element
|
168
|
-
else
|
169
|
-
self.root.add_element(signature_element)
|
170
|
-
end
|
168
|
+
root.add_element(signature_element)
|
171
169
|
end
|
172
170
|
end
|
173
171
|
|