Skip to content

Commit 32419b1

Browse files
committed
Merge remote-tracking branch 'upstream/master' into feature/jruby_support_0.9_rebase
* upstream/master: (47 commits) Handle empty URI references as per http://www.w3.org/TR/xmldsig-core/#sec-Same-Document; thx to @sixto for resolving a test case failure. support nameid in attribute values first attempt at adding support for scoped attributes needs additional work and tests Add some documentation about the soft setting parameter Update readme.md for 1.0.0 release Update date of the 1.0.0 release Update Readme and changelog Security improvement: Avoid entity expansion (XEE attacks) According to the xsd, the issuer has to be before the status Update changelog Fix #244, related to PR #243. Fix bug on metadata. Reorder KeyDescriptors Add logging information to README Allow logging to be delegated to an arbitrary Logger. Add tests for existing Logging functionality no more silent failure fetching idp metadata fix schema validation errors in service provider metadata tests to validate service provider metadata xml against the schema ignore gemfile.lock files in the gemfiles directory Prepare 1.0.0 release Improve compatibility with namespaces ...
2 parents 126b120 + 35fc801 commit 32419b1

77 files changed

Lines changed: 3420 additions & 777 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ coverage
44
rdoc
55
pkg
66
Gemfile.lock
7+
gemfiles/*.lock
78
.idea/*
89
lib/Lib.iml
910
test/Test.iml

README.md

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
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)
2+
3+
4+
## Updating from 0.9.x to 1.0.X
5+
6+
Version `1.0` is a recommended update for all Ruby SAML users as it includes security fixes.
7+
8+
Version `1.0` adds security improvements like entity expansion limitation, more SAML message validations, and other important improvements like decrypt support.
9+
10+
For more details, please review [the changelog](changelog.md).
11+
12+
### Important Changes
13+
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.
214

315
## Updating from 0.8.x to 0.9.x
4-
Version `0.9` adds many new features and improvements. It is a recommended update for all Ruby SAML users. For more details, please review [the changelog](changelog.md)
16+
Version `0.9` adds many new features and improvements.
517

618
## Updating from 0.7.x to 0.8.x
719
Version `0.8.x` changes the namespace of the gem from `OneLogin::Saml` to `OneLogin::RubySaml`. Please update your implementations of the gem accordingly.
@@ -18,7 +30,7 @@ We created a demo project for Rails4 that uses the latest version of this librar
1830
* 1.8.7
1931
* 1.9.x
2032
* 2.1.x
21-
* 2.2.0
33+
* 2.2.x
2234
* JRuby 1.7.19
2335

2436
## Adding Features, Pull Requests
@@ -36,7 +48,7 @@ Using `Gemfile`
3648

3749
```ruby
3850
# latest stable
39-
gem 'ruby-saml', '~> 0.9'
51+
gem 'ruby-saml', '~> 1.0.0'
4052

4153
# or track master for bleeding-edge
4254
gem 'ruby-saml', :github => 'onelogin/ruby-saml'
@@ -75,6 +87,19 @@ Using RubyGems
7587
gem install nokogiri --version '~> 1.5.10'
7688
````
7789

90+
### Configuring Logging
91+
92+
When troubleshooting SAML integration issues, you will find it extremely helpful to examine the
93+
output of this gem's business logic. By default, log messages are emitted to RAILS_DEFAULT_LOGGER
94+
when the gem is used in a Rails context, and to STDOUT when the gem is used outside of Rails.
95+
96+
To override the default behavior and control the destination of log messages, provide
97+
a ruby Logger object to the gem's logging singleton:
98+
99+
```ruby
100+
OneLogin::RubySaml::Logging.logger = Logger.new(File.open('/var/log/ruby-saml.log', 'w')
101+
```
102+
78103
## The Initialization Phase
79104
80105
This is the first request you will get from the identity provider. It will hit your application at a specific URL (that you've announced as being your SAML initialization point). The response to this initialization, is a redirect back to the identity provider, which can look something like this (ignore the saml_settings method call for now):
@@ -90,27 +115,36 @@ Once you've redirected back to the identity provider, it will ensure that the us
90115
91116
```ruby
92117
def consume
93-
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
94-
response.settings = saml_settings
118+
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :settings => saml_settings)
95119
96120
# We validate the SAML Response and check if the user already exists in the system
97121
if response.is_valid?
98122
# authorize_success, log the user
99-
session[:userid] = response.name_id
123+
session[:userid] = response.nameid
100124
session[:attributes] = response.attributes
101125
else
102126
authorize_failure # This method shows an error message
103127
end
104128
end
105129
```
106130
107-
In the above there are a few assumptions in place, one being that the response.name_id is an email address. This is all handled with how you specify the settings that are in play via the saml_settings method. That could be implemented along the lines of this:
131+
In the above there are a few assumptions in place, one being that the response.nameid is an email address. This is all handled with how you specify the settings that are in play via the saml_settings method. That could be implemented along the lines of this:
132+
133+
If the assertion of the SAMLResponse is not encrypted, you can initialize the Response without the :settings parameter and set it later,
134+
135+
```
136+
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
137+
response.settings = saml_settings
138+
```
139+
but if the SAMLResponse contains an encrypted assertion, you need to provide the settings in the
140+
initialize method in order to be able to obtain the decrypted assertion, using the service provider private key in order to decrypt.
141+
If you don't know what expect, use always the first proposed way (always set the settings on the initialize method).
108142
109143
```ruby
110144
def saml_settings
111145
settings = OneLogin::RubySaml::Settings.new
112146
113-
settings.assertion_consumer_service_url = "http://#{request.host}/saml/finalize"
147+
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
114148
settings.issuer = "http://#{request.host}/saml/metadata"
115149
settings.idp_sso_target_url = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}"
116150
settings.idp_entity_id = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}"
@@ -148,7 +182,7 @@ class SamlController < ApplicationController
148182
# We validate the SAML Response and check if the user already exists in the system
149183
if response.is_valid?
150184
# authorize_success, log the user
151-
session[:userid] = response.name_id
185+
session[:userid] = response.nameid
152186
session[:attributes] = response.attributes
153187
else
154188
authorize_failure # This method shows an error message
@@ -331,8 +365,8 @@ The Ruby Toolkit supports 2 different kinds of signature: Embeded and as GET par
331365
In order to be able to sign we need first to define the private key and the public cert of the service provider
332366
333367
```ruby
334-
settings.certificate = "CERTIFICATE TEXT WITH HEADS"
335-
settings.private_key = "PRIVATE KEY TEXT WITH HEADS"
368+
settings.certificate = "CERTIFICATE TEXT WITH HEAD AND FOOT"
369+
settings.private_key = "PRIVATE KEY TEXT WITH HEAD AND FOOT"
336370
```
337371
338372
The settings related to sign are stored in the `security` attribute of the settings:
@@ -355,6 +389,29 @@ Notice that the RelayState parameter is used when creating the Signature on the
355389
remember to provide it to the Signature builder if you are sending a GET RelayState parameter or
356390
Signature validation process will fail at the Identity Provider.
357391
392+
The Service Provider will sign the request/responses with its private key.
393+
The Identity Provider will validate the sign of the received request/responses with the public x500 cert of the
394+
Service Provider.
395+
396+
Notice that this toolkit uses 'settings.certificate' and 'settings.private_key' for the sign and the decrypt process.
397+
398+
Enable/disable the soft mode by the settings.soft parameter. When is set false, the saml validations errors will raise an exception.
399+
400+
## Decrypting
401+
402+
The Ruby Toolkit supports EncryptedAssertion.
403+
404+
In order to be able to decrypt a SAML Response that contains a EncryptedAssertion we need first to define the private key and the public cert of the service provider, and share this with the Identity Provider.
405+
406+
```ruby
407+
settings.certificate = "CERTIFICATE TEXT WITH HEAD AND FOOT"
408+
settings.private_key = "PRIVATE KEY TEXT WITH HEAD AND FOOT"
409+
```
410+
411+
The Identity Provider will encrypt the Assertion with the public cert of the Service Provider.
412+
The Service Provider will decrypt the EncryptedAssertion with its private key.
413+
414+
Notice that this toolkit uses 'settings.certificate' and 'settings.private_key' for the sign and the decrypt process.
358415
359416
## Single Log Out
360417

changelog.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,21 @@
11
# RubySaml Changelog
2+
3+
### 1.0.0 (June 30, 2015)
4+
* [#247](https://github.com/onelogin/ruby-saml/pull/247) Avoid entity expansion (XEE attacks)
5+
* [#246](https://github.com/onelogin/ruby-saml/pull/246) Fix bug generating Logout Response (issuer was at wrong order)
6+
* [#243](https://github.com/onelogin/ruby-saml/issues/243) and [#244](https://github.com/onelogin/ruby-saml/issues/244) Fix metadata builder errors. Fix metadata xsd.
7+
* [#241](https://github.com/onelogin/ruby-saml/pull/241) Add decrypt support (EncryptID and EncryptedAssertion). Improve compatibility with namespaces.
8+
* [#240](https://github.com/onelogin/ruby-saml/pull/240) and [#238](https://github.com/onelogin/ruby-saml/pull/238) Improve test coverage and refactor.
9+
* [#239](https://github.com/onelogin/ruby-saml/pull/239) Improve security: Add more validations to SAMLResponse, LogoutRequest and LogoutResponse. Refactor code and improve tests coverage.
10+
* [#237](https://github.com/onelogin/ruby-saml/pull/237) Don't pretty print metadata by default.
11+
* [#235](https://github.com/onelogin/ruby-saml/pull/235) Remove the soft parameter from validation methods. Now can be configured on the settings and each class read it and store as an attribute of the class. Adding some validations and refactor old ones.
12+
* [#232](https://github.com/onelogin/ruby-saml/pull/232) Improve validations: Store the causes in the errors array, code refactor
13+
* [#231](https://github.com/onelogin/ruby-saml/pull/231) Refactor HTTP-Redirect Sign method, Move test data to right folder
14+
* [#226](https://github.com/onelogin/ruby-saml/pull/226) Ensure IdP certificate is formatted properly
15+
* [#225](https://github.com/onelogin/ruby-saml/pull/225) Add documentation to several methods. Fix xpath injection on xml_security.rb
16+
* [#223](https://github.com/onelogin/ruby-saml/pull/223) Allow logging to be delegated to an arbitrary Logger
17+
* [#222](https://github.com/onelogin/ruby-saml/pull/222) No more silent failure fetching idp metadata (OneLogin::RubySaml::HttpError raised).
18+
219
### 0.9.2 (Apr 28, 2015)
320
* [#216](https://github.com/onelogin/ruby-saml/pull/216) Add fingerprint algorithm support
421
* [#218](https://github.com/onelogin/ruby-saml/pull/218) Update README.md

lib/onelogin/ruby-saml.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
require 'onelogin/ruby-saml/response'
1010
require 'onelogin/ruby-saml/settings'
1111
require 'onelogin/ruby-saml/attribute_service'
12+
require 'onelogin/ruby-saml/http_error'
1213
require 'onelogin/ruby-saml/validation_error'
1314
require 'onelogin/ruby-saml/metadata'
1415
require 'onelogin/ruby-saml/idp_metadata_parser'
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module OneLogin
2+
module RubySaml
3+
class HttpError < StandardError
4+
end
5+
end
6+
end
7+

lib/onelogin/ruby-saml/idp_metadata_parser.rb

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@ class IdpMetadataParser
2020
DSIG = "http://www.w3.org/2000/09/xmldsig#"
2121

2222
attr_reader :document
23+
attr_reader :response
2324

24-
# Parse the Identity Provider metadata and update the settings with the IdP values
25-
# @param url [String] Url where the XML of the Identity Provider Metadata is published.
26-
# @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked.
25+
# Parse the Identity Provider metadata and update the settings with the
26+
# IdP values
2727
#
28+
# @param (see IdpMetadataParser#get_idp_metadata)
29+
# @return (see IdpMetadataParser#get_idp_metadata)
30+
# @raise (see IdpMetadataParser#get_idp_metadata)
2831
def parse_remote(url, validate_cert = true)
2932
idp_metadata = get_idp_metadata(url, validate_cert)
3033
parse(idp_metadata)
@@ -51,7 +54,7 @@ def parse(idp_metadata)
5154
# @param url [String] Url where the XML of the Identity Provider Metadata is published.
5255
# @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked.
5356
# @return [REXML::document] Parsed XML IdP metadata
54-
#
57+
# @raise [HttpError] Failure to fetch remote IdP metadata
5558
def get_idp_metadata(url, validate_cert)
5659
uri = URI.parse(url)
5760
if uri.scheme == "http"
@@ -75,7 +78,14 @@ def get_idp_metadata(url, validate_cert)
7578
get = Net::HTTP::Get.new(uri.request_uri)
7679
response = http.request(get)
7780
meta_text = response.body
81+
else
82+
raise ArgumentError.new("url must begin with http or https")
7883
end
84+
85+
unless response.is_a? Net::HTTPSuccess
86+
raise OneLogin::RubySaml::HttpError.new("Failed to fetch idp metadata")
87+
end
88+
7989
meta_text
8090
end
8191

lib/onelogin/ruby-saml/logging.rb

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
1+
require 'logger'
2+
13
# Simplistic log class when we're running in Rails
24
module OneLogin
35
module RubySaml
46
class Logging
7+
DEFAULT_LOGGER = ::Logger.new(STDOUT)
8+
9+
def self.logger
10+
@logger || (defined?(::Rails) && Rails.logger) || DEFAULT_LOGGER
11+
end
12+
13+
def self.logger=(logger)
14+
@logger = logger
15+
end
16+
517
def self.debug(message)
618
return if !!ENV["ruby-saml/testing"]
719

8-
if defined? Rails
9-
Rails.logger.debug message
10-
else
11-
puts message
12-
end
20+
logger.debug message
1321
end
1422

1523
def self.info(message)
1624
return if !!ENV["ruby-saml/testing"]
1725

18-
if defined? Rails
19-
Rails.logger.info message
20-
else
21-
puts message
22-
end
26+
logger.info message
2327
end
2428
end
2529
end

lib/onelogin/ruby-saml/logoutrequest.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,15 +101,15 @@ def create_logout_request_xml_doc(settings)
101101
issuer.text = settings.issuer
102102
end
103103

104-
name_id = root.add_element "saml:NameID"
104+
nameid = root.add_element "saml:NameID"
105105
if settings.name_identifier_value
106-
name_id.attributes['NameQualifier'] = settings.sp_name_qualifier if settings.sp_name_qualifier
107-
name_id.attributes['Format'] = settings.name_identifier_format if settings.name_identifier_format
108-
name_id.text = settings.name_identifier_value
106+
nameid.attributes['NameQualifier'] = settings.sp_name_qualifier if settings.sp_name_qualifier
107+
nameid.attributes['Format'] = settings.name_identifier_format if settings.name_identifier_format
108+
nameid.text = settings.name_identifier_value
109109
else
110110
# If no NameID is present in the settings we generate one
111-
name_id.text = "_" + UUID.new.generate
112-
name_id.attributes['Format'] = 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
111+
nameid.text = "_" + UUID.new.generate
112+
nameid.attributes['Format'] = 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
113113
end
114114

115115
if settings.sessionindex

0 commit comments

Comments
 (0)