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

Commit bdf6e0e

Browse files
committed
Merge tag 'v1.1.0' into upgrade
2 parents 9d419b8 + 12c9f9c commit bdf6e0e

18 files changed

Lines changed: 382 additions & 107 deletions

.travis.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
sudo: false
12
language: ruby
23
rvm:
34
- 1.8.7
@@ -6,6 +7,8 @@ rvm:
67
- 2.1.5
78
- 2.2.0
89
- ree
10+
- jruby-1.7.21
11+
- jruby-9.0.0.0
912
gemfile:
1013
- Gemfile
1114
- gemfiles/nokogiri-1.5.gemfile
@@ -15,3 +18,7 @@ matrix:
1518
gemfile: Gemfile
1619
- rvm: ree
1720
gemfile: Gemfile
21+
- rvm: jruby-9.0.0.0
22+
gemfile: gemfiles/nokogiri-1.5.gemfile
23+
- rvm: jruby-1.7.21
24+
gemfile: gemfiles/nokogiri-1.5.gemfile

README.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1-
# Ruby SAML [![Build Status](https://secure.travis-ci.org/onelogin/ruby-saml.png)](http://travis-ci.org/onelogin/ruby-saml)
1+
# 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

33

4+
## Updating from 1.0.x to 1.1.X
5+
6+
Version `1.1` adds some improvements on signature validation and solves some namespace conflicts.
7+
8+
For more details, please review [the changelog](changelog.md).
9+
410
## Updating from 0.9.x to 1.0.X
511

612
Version `1.0` is a recommended update for all Ruby SAML users as it includes security fixes.
713

814
Version `1.0` adds security improvements like entity expansion limitation, more SAML message validations, and other important improvements like decrypt support.
915

10-
For more details, please review [the changelog](changelog.md).
11-
1216
### Important Changes
1317
Please note the `get_idp_metadata` method raises an exception when it is not able to fetch the idp metadata, so review your integration if you are using this functionality.
1418

@@ -31,6 +35,8 @@ We created a demo project for Rails4 that uses the latest version of this librar
3135
* 1.9.x
3236
* 2.1.x
3337
* 2.2.x
38+
* JRuby 1.7.19
39+
* JRuby 9.0.0.0
3440

3541
## Adding Features, Pull Requests
3642
* Fork the repository
@@ -164,6 +170,13 @@ def saml_settings
164170
end
165171
```
166172
173+
Some assertion validations can be skipped by passing parameters to OneLogin::RubySaml::Response.new(). For example, you can skip the Conditions validation or the SubjectConfirmation validations by initializing the response with different options:
174+
175+
```ruby
176+
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_conditions: true}) # skips conditions
177+
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_subject_confirmation: true}) # skips subject confirmation
178+
```
179+
167180
What's left at this point, is to wrap it all up in a controller and point the initialization and consumption URLs in OneLogin at that. A full controller example could look like this:
168181
169182
```ruby
@@ -394,6 +407,7 @@ Service Provider.
394407
395408
Notice that this toolkit uses 'settings.certificate' and 'settings.private_key' for the sign and the decrypt process.
396409
410+
Enable/disable the soft mode by the settings.soft parameter. When is set false, the saml validations errors will raise an exception.
397411
398412
## Decrypting
399413

Rakefile

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,3 @@ end
2525
task :test
2626

2727
task :default => :test
28-
29-
# require 'rake/rdoctask'
30-
# Rake::RDocTask.new do |rdoc|
31-
# if File.exist?('VERSION')
32-
# version = File.read('VERSION')
33-
# else
34-
# version = ""
35-
# end
36-
37-
# rdoc.rdoc_dir = 'rdoc'
38-
# rdoc.title = "ruby-saml #{version}"
39-
# rdoc.rdoc_files.include('README*')
40-
# rdoc.rdoc_files.include('lib/**/*.rb')
41-
#end

changelog.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# RubySaml Changelog
22

3+
### 1.1.0 (October 27, 2015)
4+
* [#273](https://github.com/onelogin/ruby-saml/pull/273) Support SAMLResponse without ds:x509certificate
5+
* [#270](https://github.com/onelogin/ruby-saml/pull/270) Allow SAML elements to come from any namespace (at decryption process)
6+
* [#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
8+
* [#256](https://github.com/onelogin/ruby-saml/pull/256) Separate the create_authentication_xml_doc in two methods.
9+
* [#255](https://github.com/onelogin/ruby-saml/pull/255) Refactor validate signature.
10+
* [#254](https://github.com/onelogin/ruby-saml/pull/254) Handle empty URI references
11+
* [#251](https://github.com/onelogin/ruby-saml/pull/251) Support qualified and unqualified NameID in attributes
12+
* [#234](https://github.com/onelogin/ruby-saml/pull/234) Add explicit support for JRuby
13+
314
### 1.0.0 (June 30, 2015)
415
* [#247](https://github.com/onelogin/ruby-saml/pull/247) Avoid entity expansion (XEE attacks)
516
* [#246](https://github.com/onelogin/ruby-saml/pull/246) Fix bug generating Logout Response (issuer was at wrong order)

lib/onelogin/ruby-saml/authrequest.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ def create_params(settings, params={})
8787
# @return [String] The SAMLRequest String.
8888
#
8989
def create_authentication_xml_doc(settings)
90+
document = create_xml_document(settings)
91+
sign_document(document, settings)
92+
end
93+
94+
def create_xml_document(settings)
9095
time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
9196

9297
request_doc = XMLSecurity::Document.new
@@ -153,14 +158,18 @@ def create_authentication_xml_doc(settings)
153158
end
154159
end
155160

161+
request_doc
162+
end
163+
164+
def sign_document(document, settings)
156165
# embed signature
157166
if settings.security[:authn_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
158167
private_key = settings.get_sp_key
159168
cert = settings.get_sp_cert
160-
request_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
169+
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
161170
end
162171

163-
request_doc
172+
document
164173
end
165174

166175
end

lib/onelogin/ruby-saml/response.rb

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ class Response < SamlMessage
3636
# @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object
3737
# Or some options for the response validation process like skip the conditions validation
3838
# with the :skip_conditions, or allow a clock_drift when checking dates with :allowed_clock_drift
39-
# or :matches_request_id that will validate that the response matches the ID of the request.
39+
# or :matches_request_id that will validate that the response matches the ID of the request,
40+
# or skip the subject confirmation validation with the :skip_subject_confirmation option
4041
def initialize(response, options = {})
4142
@errors = []
4243

@@ -94,6 +95,7 @@ def name_id
9495

9596
alias_method :nameid, :name_id
9697

98+
9799
# Gets the SessionIndex from the AuthnStatement.
98100
# Could be used to be stored in the local session in order
99101
# to be used in a future Logout Request that the SP could
@@ -131,12 +133,22 @@ def attributes
131133
stmt_element.elements.each do |attr_element|
132134
name = attr_element.attributes["Name"]
133135
values = attr_element.elements.collect{|e|
134-
# SAMLCore requires that nil AttributeValues MUST contain xsi:nil XML attribute set to "true" or "1"
135-
# otherwise the value is to be regarded as empty.
136-
["true", "1"].include?(e.attributes['xsi:nil']) ? nil : e.text.to_s
136+
if (e.elements.nil? || e.elements.size == 0)
137+
# SAMLCore requires that nil AttributeValues MUST contain xsi:nil XML attribute set to "true" or "1"
138+
# otherwise the value is to be regarded as empty.
139+
["true", "1"].include?(e.attributes['xsi:nil']) ? nil : e.text.to_s
140+
# explicitly support saml2:NameID with saml2:NameQualifier if supplied in attributes
141+
# this is useful for allowing eduPersonTargetedId to be passed as an opaque identifier to use to
142+
# identify the subject in an SP rather than email or other less opaque attributes
143+
# NameQualifier, if present is prefixed with a "/" to the value
144+
else
145+
REXML::XPath.match(e,'a:NameID', { "a" => ASSERTION }).collect{|n|
146+
(n.attributes['NameQualifier'] ? n.attributes['NameQualifier'] +"/" : '') + n.text.to_s
147+
}
148+
end
137149
}
138150

139-
attributes.add(name, values)
151+
attributes.add(name, values.flatten)
140152
end
141153

142154
attributes
@@ -316,11 +328,9 @@ def validate_structure
316328

317329
# Validates that the SAML Response provided in the initialization is not empty,
318330
# also check that the setting and the IdP cert were also provided
319-
# @param soft [Boolean] soft Enable or Disable the soft mode (In order to raise exceptions when the response is invalid or not)
320-
# @return [Boolean] True if the required info is found, otherwise False if soft=True
321-
# @raise [ValidationError] if soft == false and validation fails
331+
# @return [Boolean] True if the required info is found, false otherwise
322332
#
323-
def validate_response_state(soft = true)
333+
def validate_response_state
324334
return append_error("Blank response") if response.nil? || response.empty?
325335

326336
return append_error("No settings on response") if settings.nil?
@@ -504,7 +514,7 @@ def validate_session_expiration(soft = true)
504514
return true if session_expires_at.nil?
505515

506516
now = Time.now.utc
507-
unless session_expires_at > (now + allowed_clock_drift)
517+
unless (session_expires_at + allowed_clock_drift) > now
508518
error_msg = "The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response"
509519
return append_error(error_msg)
510520
end
@@ -513,12 +523,14 @@ def validate_session_expiration(soft = true)
513523
end
514524

515525
# Validates if exists valid SubjectConfirmation (If the response was initialized with the :allowed_clock_drift option,
516-
# timimg validation are relaxed by the allowed_clock_drift value)
526+
# timimg validation are relaxed by the allowed_clock_drift value. If the response was initialized with the
527+
# :skip_subject_confirmation option, this validation is skipped)
517528
# If fails, the error is added to the errors array
518529
# @return [Boolean] True if exists a valid SubjectConfirmation, otherwise False if soft=True
519530
# @raise [ValidationError] if soft == false and validation fails
520531
#
521532
def validate_subject_confirmation
533+
return true if options[:skip_subject_confirmation]
522534
valid_subject_confirmation = false
523535

524536
subject_confirmation_nodes = xpath_from_signed_assertion('/a:Subject/a:SubjectConfirmation')
@@ -561,6 +573,7 @@ def validate_subject_confirmation
561573
def validate_signature
562574
return true if settings.do_not_verify_cert
563575
fingerprint = settings.get_fingerprint
576+
idp_cert = settings.get_idp_cert
564577

565578
# If the response contains the signature, and the assertion was encrypted, validate the original SAML Response
566579
# otherwise, review if the decrypted assertion contains a signature
@@ -572,7 +585,11 @@ def validate_signature
572585
)
573586
doc = (response_signed || decrypted_document.nil?) ? document : decrypted_document
574587

575-
unless fingerprint && doc.validate_document(fingerprint, :fingerprint_alg => settings.idp_cert_fingerprint_algorithm)
588+
opts = {}
589+
opts[:fingerprint_alg] = settings.idp_cert_fingerprint_algorithm
590+
opts[:cert] = idp_cert
591+
592+
unless fingerprint && doc.validate_document(fingerprint, @soft, opts)
576593
error_msg = "Invalid Signature on SAML Response"
577594
return append_error(error_msg)
578595
end
@@ -677,15 +694,15 @@ def assertion_encrypted?
677694
# @return [REXML::Document] The decrypted EncryptedAssertion element
678695
#
679696
def decrypt_assertion(encrypted_assertion_node)
680-
decrypt_element(encrypted_assertion_node, /(.*<\/(saml2*:|)Assertion>)/m)
697+
decrypt_element(encrypted_assertion_node, /(.*<\/(\w+:)?Assertion>)/m)
681698
end
682699

683700
# Decrypts an EncryptedID element
684701
# @param encryptedid_node [REXML::Element] The EncryptedID element
685702
# @return [REXML::Document] The decrypted EncrypedtID element
686703
#
687704
def decrypt_nameid(encryptedid_node)
688-
decrypt_element(encryptedid_node, /(.*<\/(saml2*:|)NameID>)/m)
705+
decrypt_element(encryptedid_node, /(.*<\/(\w+:)?NameID>)/m)
689706
end
690707

691708
# Decrypt an element

lib/onelogin/ruby-saml/saml_message.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class SamlMessage
2323
# @return [Nokogiri::XML::Schema] Gets the schema object of the SAML 2.0 Protocol schema
2424
#
2525
def self.schema
26-
@schema ||= Mutex.new.synchronize do
26+
Mutex.new.synchronize do
2727
Dir.chdir(File.expand_path("../../../schemas", __FILE__)) do
2828
::Nokogiri::XML::Schema(File.read("saml-schema-protocol-2.0.xsd"))
2929
end

lib/onelogin/ruby-saml/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module OneLogin
22
module RubySaml
3-
VERSION = '1.0.0'
3+
VERSION = '1.1.0'
44
end
55
end

0 commit comments

Comments
 (0)