Skip to content
This repository was archived by the owner on Mar 12, 2026. It is now read-only.

Commit 6e8109a

Browse files
committed
merge v1.3.0
2 parents bdf6e0e + 171a41e commit 6e8109a

34 files changed

Lines changed: 799 additions & 270 deletions

README.md

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
# Ruby SAML [![Build Status](https://secure.travis-ci.org/onelogin/ruby-saml.png)](http://travis-ci.org/onelogin/ruby-saml) [![Coverage Status](https://coveralls.io/repos/onelogin/ruby-saml/badge.svg?branch=master%0A)](https://coveralls.io/r/onelogin/ruby-saml?branch=master%0A) [![Gem Version](https://badge.fury.io/rb/ruby-saml.svg)](http://badge.fury.io/rb/ruby-saml)
22

3+
## Updating from 1.2.x to 1.3.X
34

4-
## Updating from 1.0.x to 1.1.X
5+
Version `1.3.0` is a recommended update for all Ruby SAML users as it includes security fixes. It adds security improvements in order to prevent Signature wrapping attacks. [CVE-2016-5697](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-5697)
56

6-
Version `1.1` adds some improvements on signature validation and solves some namespace conflicts.
7+
## Updating from 1.1.x to 1.2.X
8+
9+
Version `1.2` adds IDP metadata parsing improvements, uuid deprecation in favour of SecureRandom, refactor error handling and some minor improvements
10+
11+
There is no compatibility issue detected.
712

813
For more details, please review [the changelog](changelog.md).
914

15+
## Updating from 1.0.x to 1.1.X
16+
17+
Version `1.1` adds some improvements on signature validation and solves some namespace conflicts.
18+
1019
## Updating from 0.9.x to 1.0.X
1120

1221
Version `1.0` is a recommended update for all Ruby SAML users as it includes security fixes.
@@ -33,6 +42,7 @@ We created a demo project for Rails4 that uses the latest version of this librar
3342
### Supported versions of Ruby
3443
* 1.8.7
3544
* 1.9.x
45+
* 2.0.x
3646
* 2.1.x
3747
* 2.2.x
3848
* JRuby 1.7.19
@@ -102,7 +112,7 @@ To override the default behavior and control the destination of log messages, pr
102112
a ruby Logger object to the gem's logging singleton:
103113

104114
```ruby
105-
OneLogin::RubySaml::Logging.logger = Logger.new(File.open('/var/log/ruby-saml.log', 'w')
115+
OneLogin::RubySaml::Logging.logger = Logger.new(File.open('/var/log/ruby-saml.log', 'w'))
106116
```
107117

108118
## The Initialization Phase
@@ -252,9 +262,9 @@ def saml_settings
252262
end
253263
```
254264
The following attributes are set:
255-
* id_sso_target_url
265+
* idp_sso_target_url
256266
* idp_slo_target_url
257-
* id_cert_fingerpint
267+
* idp_cert_fingerprint
258268
259269
If you are using saml:AttributeStatement to transfer metadata, like the user name, you can access all the attributes through response.attributes. It contains all the saml:AttributeStatement with its 'Name' as a indifferent key the one/more saml:AttributeValue as value. The value returned depends on the value of the
260270
`single_value_compatibility` (when activate, only one value returned, the first one)
@@ -386,7 +396,10 @@ The settings related to sign are stored in the `security` attribute of the setti
386396
```ruby
387397
settings.security[:authn_requests_signed] = true # Enable or not signature on AuthNRequest
388398
settings.security[:logout_requests_signed] = true # Enable or not signature on Logout Request
389-
settings.security[:logout_responses_signed] = true # Enable or not signature on Logout Response
399+
settings.security[:logout_responses_signed] = true # Enable or not
400+
signature on Logout Response
401+
settings.security[:want_assertions_signed] = true # Enable or not
402+
the requirement of signed assertion
390403
settings.security[:metadata_signed] = true # Enable or not signature on Metadata
391404
392405
settings.security[:digest_method] = XMLSecurity::Document::SHA1
@@ -466,8 +479,8 @@ and this method process the SAML Logout Response sent by the IdP as reply of the
466479
def process_logout_response
467480
settings = Account.get_saml_settings
468481
469-
if session.has_key? :transation_id
470-
logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings, :matches_request_id => session[:transation_id])
482+
if session.has_key? :transaction_id
483+
logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings, :matches_request_id => session[:transaction_id])
471484
else
472485
logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings)
473486
end

changelog.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,38 @@
11
# RubySaml Changelog
22

3+
### 1.3.0 (June 24, 2016)
4+
* [Security Fix](https://github.com/onelogin/ruby-saml/commit/a571f52171e6bfd87db59822d1d9e8c38fb3b995) Add extra validations to prevent Signature wrapping attacks
5+
* Fix XMLSecurity SHA256 and SHA512 uris
6+
* [#326](https://github.com/onelogin/ruby-saml/pull/326) Fix Destination validation
7+
8+
### 1.2.0 (April 29, 2016)
9+
* [#269](https://github.com/onelogin/ruby-saml/pull/269) Refactor error handling; allow collect error messages when soft=true (normal validation stop after find first error)
10+
* [#289](https://github.com/onelogin/ruby-saml/pull/289) Remove uuid gem in favor of SecureRandom
11+
* [#297](https://github.com/onelogin/ruby-saml/pull/297) Implement EncryptedKey RetrievalMethod support
12+
* [#298](https://github.com/onelogin/ruby-saml/pull/298) IDP metadata parsing improved: binding parsing, fingerprint_algorithm support)
13+
* [#299](https://github.com/onelogin/ruby-saml/pull/299) Make 'signing' at KeyDescriptor optional
14+
* [#308](https://github.com/onelogin/ruby-saml/pull/308) Support name_id_format on SAMLResponse
15+
* [#315](https://github.com/onelogin/ruby-saml/pull/315) Support for canonicalization with comments
16+
* [#316](https://github.com/onelogin/ruby-saml/pull/316) Fix Misspelling of transation_id to transaction_id
17+
* [#321](https://github.com/onelogin/ruby-saml/pull/321) Support Attribute Names on IDPSSODescriptor parser
18+
* Changes on empty URI of Signature reference management
19+
* [#320](https://github.com/onelogin/ruby-saml/pull/320) Dont mutate document to fix lack of reference URI
20+
* [#306](https://github.com/onelogin/ruby-saml/pull/306) Support WantAssertionsSigned
21+
22+
### 1.1.2 (February 15, 2016)
23+
* Improve signature validation. Add tests.
24+
[#302](https://github.com/onelogin/ruby-saml/pull/302) Add Destination validation.
25+
* [#292](https://github.com/onelogin/ruby-saml/pull/292) Improve the error message when validating the audience.
26+
* [#287](https://github.com/onelogin/ruby-saml/pull/287) Keep the extracted certificate when parsing IdP metadata.
27+
28+
### 1.1.1 (November 10, 2015)
29+
* [#275](https://github.com/onelogin/ruby-saml/pull/275) Fix a bug on signature validations that invalidates valid SAML messages.
30+
331
### 1.1.0 (October 27, 2015)
432
* [#273](https://github.com/onelogin/ruby-saml/pull/273) Support SAMLResponse without ds:x509certificate
533
* [#270](https://github.com/onelogin/ruby-saml/pull/270) Allow SAML elements to come from any namespace (at decryption process)
634
* [#261](https://github.com/onelogin/ruby-saml/pull/261) Allow validate_subject_confirmation Response validation to be skipped
7-
* [258](https://github.com/onelogin/ruby-saml/pull/258) Fix allowed_clock_drift on the validate_session_expiration test
35+
* [#258](https://github.com/onelogin/ruby-saml/pull/258) Fix allowed_clock_drift on the validate_session_expiration test
836
* [#256](https://github.com/onelogin/ruby-saml/pull/256) Separate the create_authentication_xml_doc in two methods.
937
* [#255](https://github.com/onelogin/ruby-saml/pull/255) Refactor validate signature.
1038
* [#254](https://github.com/onelogin/ruby-saml/pull/254) Handle empty URI references

lib/onelogin/ruby-saml/authrequest.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
require "uuid"
21
require "rexml/document"
32

43
require "onelogin/ruby-saml/logging"
54
require "onelogin/ruby-saml/saml_message"
5+
require "onelogin/ruby-saml/utils"
66

77
# Only supports SAML 2.0
88
module OneLogin
@@ -20,7 +20,7 @@ class Authrequest < SamlMessage
2020
# Asigns an ID, a random uuid.
2121
#
2222
def initialize
23-
@uuid = "_" + UUID.new.generate
23+
@uuid = OneLogin::RubySaml::Utils.uuid
2424
end
2525

2626
# Creates the AuthNRequest string.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
require "onelogin/ruby-saml/validation_error"
2+
3+
module OneLogin
4+
module RubySaml
5+
module ErrorHandling
6+
attr_accessor :errors
7+
8+
# Append the cause to the errors array, and based on the value of soft, return false or raise
9+
# an exception. soft_override is provided as a means of overriding the object's notion of
10+
# soft for just this invocation.
11+
def append_error(error_msg, soft_override = nil)
12+
@errors << error_msg
13+
14+
unless soft_override.nil? ? soft : soft_override
15+
raise ValidationError.new(error_msg)
16+
end
17+
18+
false
19+
end
20+
21+
# Reset the errors array
22+
def reset_errors!
23+
@errors = []
24+
end
25+
end
26+
end
27+
end

lib/onelogin/ruby-saml/idp_metadata_parser.rb

Lines changed: 96 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
require "base64"
2-
require "uuid"
32
require "zlib"
43
require "cgi"
54
require "net/http"
@@ -16,8 +15,10 @@ module RubySaml
1615
#
1716
class IdpMetadataParser
1817

19-
METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
20-
DSIG = "http://www.w3.org/2000/09/xmldsig#"
18+
METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
19+
DSIG = "http://www.w3.org/2000/09/xmldsig#"
20+
NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:*"
21+
SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
2122

2223
attr_reader :document
2324
attr_reader :response
@@ -26,25 +27,30 @@ class IdpMetadataParser
2627
# IdP values
2728
#
2829
# @param (see IdpMetadataParser#get_idp_metadata)
30+
# @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object
2931
# @return (see IdpMetadataParser#get_idp_metadata)
3032
# @raise (see IdpMetadataParser#get_idp_metadata)
31-
def parse_remote(url, validate_cert = true)
33+
def parse_remote(url, validate_cert = true, options = {})
3234
idp_metadata = get_idp_metadata(url, validate_cert)
33-
parse(idp_metadata)
35+
parse(idp_metadata, options)
3436
end
3537

3638
# Parse the Identity Provider metadata and update the settings with the IdP values
3739
# @param idp_metadata [String]
40+
# @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object
3841
#
39-
def parse(idp_metadata)
42+
def parse(idp_metadata, options = {})
4043
@document = REXML::Document.new(idp_metadata)
4144

42-
OneLogin::RubySaml::Settings.new.tap do |settings|
45+
(options[:settings] || OneLogin::RubySaml::Settings.new).tap do |settings|
4346
settings.idp_entity_id = idp_entity_id
4447
settings.name_identifier_format = idp_name_id_format
45-
settings.idp_sso_target_url = single_signon_service_url
46-
settings.idp_slo_target_url = single_logout_service_url
47-
settings.idp_cert_fingerprint = fingerprint
48+
settings.idp_sso_target_url = single_signon_service_url(options)
49+
settings.idp_slo_target_url = single_logout_service_url(options)
50+
settings.idp_cert = certificate_base64
51+
settings.idp_cert_fingerprint = fingerprint(settings.idp_cert_fingerprint_algorithm)
52+
settings.idp_attribute_names = attribute_names
53+
settings.idp_cert_fingerprint = fingerprint(settings.idp_cert_fingerprint_algorithm)
4854
end
4955
end
5056

@@ -111,51 +117,119 @@ def idp_name_id_format
111117
node.text if node
112118
end
113119

120+
# @param binding_priority [Array]
121+
# @return [String|nil] SingleSignOnService binding if exists
122+
#
123+
def single_signon_service_binding(binding_priority = nil)
124+
nodes = REXML::XPath.match(
125+
document,
126+
"/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleSignOnService/@Binding",
127+
{ "md" => METADATA }
128+
)
129+
if binding_priority
130+
values = nodes.map(&:value)
131+
binding_priority.detect{ |binding| values.include? binding }
132+
else
133+
nodes.first.value if nodes.any?
134+
end
135+
end
136+
137+
# @param options [Hash]
114138
# @return [String|nil] SingleSignOnService endpoint if exists
115139
#
116-
def single_signon_service_url
140+
def single_signon_service_url(options = {})
141+
binding = options[:sso_binding] || "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
117142
node = REXML::XPath.first(
118143
document,
119-
"/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleSignOnService/@Location",
144+
"/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleSignOnService[@Binding=\"#{binding}\"]/@Location",
120145
{ "md" => METADATA }
121146
)
122147
node.value if node
123148
end
124149

150+
# @param binding_priority [Array]
151+
# @return [String|nil] SingleLogoutService binding if exists
152+
#
153+
def single_logout_service_binding(binding_priority = nil)
154+
nodes = REXML::XPath.match(
155+
document,
156+
"/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleLogoutService/@Binding",
157+
{ "md" => METADATA }
158+
)
159+
if binding_priority
160+
values = nodes.map(&:value)
161+
binding_priority.detect{ |binding| values.include? binding }
162+
else
163+
nodes.first.value if nodes.any?
164+
end
165+
end
166+
167+
# @param options [Hash]
125168
# @return [String|nil] SingleLogoutService endpoint if exists
126169
#
127-
def single_logout_service_url
170+
def single_logout_service_url(options = {})
171+
binding = options[:slo_binding] || "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
128172
node = REXML::XPath.first(
129173
document,
130-
"/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleLogoutService/@Location",
174+
"/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleLogoutService[@Binding=\"#{binding}\"]/@Location",
131175
{ "md" => METADATA }
132176
)
133177
node.value if node
134178
end
135179

180+
# @return [String|nil] Unformatted Certificate if exists
181+
#
182+
def certificate_base64
183+
@certificate_base64 ||= begin
184+
node = REXML::XPath.first(
185+
document,
186+
"/md:EntityDescriptor/md:IDPSSODescriptor/md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
187+
{ "md" => METADATA, "ds" => DSIG }
188+
)
189+
190+
unless node
191+
node = REXML::XPath.first(
192+
document,
193+
"/md:EntityDescriptor/md:IDPSSODescriptor/md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
194+
{ "md" => METADATA, "ds" => DSIG }
195+
)
196+
end
197+
node.text if node
198+
end
199+
end
200+
136201
# @return [String|nil] X509Certificate if exists
137202
#
138203
def certificate
139204
@certificate ||= begin
140-
node = REXML::XPath.first(
141-
document,
142-
"/md:EntityDescriptor/md:IDPSSODescriptor/md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
143-
{ "md" => METADATA, "ds" => DSIG }
144-
)
145-
Base64.decode64(node.text) if node
205+
Base64.decode64(certificate_base64) if certificate_base64
146206
end
147207
end
148208

209+
149210
# @return [String|nil] the SHA-1 fingerpint of the X509Certificate if it exists
150211
#
151-
def fingerprint
212+
def fingerprint(fingerprint_algorithm)
152213
@fingerprint ||= begin
153214
if certificate
154215
cert = OpenSSL::X509::Certificate.new(certificate)
155-
Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":")
216+
217+
fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(fingerprint_algorithm).new
218+
fingerprint_alg.hexdigest(cert.to_der).upcase.scan(/../).join(":")
156219
end
157220
end
158221
end
222+
223+
# @return [Array] the names of all SAML attributes if any exist
224+
#
225+
def attribute_names
226+
nodes = REXML::XPath.match(
227+
document,
228+
"/md:EntityDescriptor/md:IDPSSODescriptor/saml:Attribute/@Name",
229+
{ "md" => METADATA, "NameFormat" => NAME_FORMAT, "saml" => SAML_ASSERTION }
230+
)
231+
nodes.map(&:value)
232+
end
159233
end
160234
end
161235
end

lib/onelogin/ruby-saml/logoutrequest.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
require "uuid"
2-
31
require "onelogin/ruby-saml/logging"
42
require "onelogin/ruby-saml/saml_message"
3+
require "onelogin/ruby-saml/utils"
54

65
# Only supports SAML 2.0
76
module OneLogin
@@ -18,7 +17,7 @@ class Logoutrequest < SamlMessage
1817
# Asigns an ID, a random uuid.
1918
#
2019
def initialize
21-
@uuid = "_" + UUID.new.generate
20+
@uuid = OneLogin::RubySaml::Utils.uuid
2221
end
2322

2423
# Creates the Logout Request string.
@@ -108,7 +107,7 @@ def create_logout_request_xml_doc(settings)
108107
nameid.text = settings.name_identifier_value
109108
else
110109
# If no NameID is present in the settings we generate one
111-
nameid.text = "_" + UUID.new.generate
110+
nameid.text = OneLogin::RubySaml::Utils.uuid
112111
nameid.attributes['Format'] = 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
113112
end
114113

0 commit comments

Comments
 (0)