ruby-saml-1.13.0/ 0000755 0000041 0000041 00000000000 14136046347 013547 5 ustar www-data www-data ruby-saml-1.13.0/README.md 0000644 0000041 0000041 00000104445 14136046347 015036 0 ustar www-data www-data # Ruby SAML
[](https://github.com/onelogin/ruby-saml/actions/workflows/test.yml?query=branch%3Amaster)
[](https://coveralls.io/r/onelogin/ruby-saml?branch=master)
Ruby SAML minor and tiny versions may introduce breaking changes. Please read
[UPGRADING.md](UPGRADING.md) for guidance on upgrading to new Ruby SAML versions.
## Overview
The Ruby SAML library is for implementing the client side of a SAML authorization,
i.e. it provides a means for managing authorization initialization and confirmation
requests from identity providers.
SAML authorization is a two step process and you are expected to implement support for both.
We created a demo project for Rails 4 that uses the latest version of this library:
[ruby-saml-example](https://github.com/onelogin/ruby-saml-example)
### Supported Ruby Versions
The following Ruby versions are covered by CI testing:
* 2.1.x
* 2.2.x
* 2.3.x
* 2.4.x
* 2.5.x
* 2.6.x
* 2.7.x
* 3.0.x
* JRuby 9.1.x
* JRuby 9.2.x
* TruffleRuby (latest)
In addition, the following may work but are untested:
* 1.8.7
* 1.9.x
* 2.0.x
* JRuby 1.7.x
* JRuby 9.0.x
## Adding Features, Pull Requests
* Fork the repository
* Make your feature addition or bug fix
* Add tests for your new features. This is important so we don't break any features in a future version unintentionally.
* Ensure all tests pass by running `bundle exec rake test`.
* Do not change rakefile, version, or history.
* Open a pull request, following [this template](https://gist.github.com/Lordnibbler/11002759).
## Security Guidelines
If you believe you have discovered a security vulnerability in this gem, please report it
at https://www.onelogin.com/security with a description. We follow responsible disclosure
guidelines, and will work with you to quickly find a resolution.
### Security Warning
Some tools may incorrectly report ruby-saml is a potential security vulnerability.
ruby-saml depends on Nokogiri, and it's possible to use Nokogiri in a dangerous way
(by enabling its DTDLOAD option and disabling its NONET option).
This dangerous Nokogiri configuration, which is sometimes used by other components,
can create an XML External Entity (XXE) vulnerability if the XML data is not trusted.
However, ruby-saml never enables this dangerous Nokogiri configuration;
ruby-saml never enables DTDLOAD, and it never disables NONET.
The OneLogin::RubySaml::IdpMetadataParser class does not validate in any way the URL
that is introduced in order to be parsed.
Usually the same administrator that handles the Service Provider also sets the URL to
the IdP, which should be a trusted resource.
But there are other scenarios, like a SAAS app where the administrator of the app
delegates this functionality to other users. In this case, extra precaution should
be taken in order to validate such URL inputs and avoid attacks like SSRF.
## Getting Started
In order to use Ruby SAML you will need to install the gem (either manually or using Bundler),
and require the library in your Ruby application:
Using `Gemfile`
```ruby
# latest stable
gem 'ruby-saml', '~> 1.11.0'
# or track master for bleeding-edge
gem 'ruby-saml', :github => 'onelogin/ruby-saml'
```
Using RubyGems
```sh
gem install ruby-saml
```
You may require the entire Ruby SAML gem:
```ruby
require 'onelogin/ruby-saml'
```
or just the required components individually:
```ruby
require 'onelogin/ruby-saml/authrequest'
```
### Installation on Ruby 1.8.7
This gem uses Nokogiri as a dependency, which dropped support for Ruby 1.8.x in Nokogiri 1.6.
When installing this gem on Ruby 1.8.7, you will need to make sure a version of Nokogiri
prior to 1.6 is installed or specified if it hasn't been already.
Using `Gemfile`
```ruby
gem 'nokogiri', '~> 1.5.10'
```
Using RubyGems
```sh
gem install nokogiri --version '~> 1.5.10'
````
### Configuring Logging
When troubleshooting SAML integration issues, you will find it extremely helpful to examine the
output of this gem's business logic. By default, log messages are emitted to RAILS_DEFAULT_LOGGER
when the gem is used in a Rails context, and to STDOUT when the gem is used outside of Rails.
To override the default behavior and control the destination of log messages, provide
a ruby Logger object to the gem's logging singleton:
```ruby
OneLogin::RubySaml::Logging.logger = Logger.new('/var/log/ruby-saml.log')
```
## The Initialization Phase
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 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):
```ruby
def init
request = OneLogin::RubySaml::Authrequest.new
redirect_to(request.create(saml_settings))
end
```
If the SP knows who should be authenticated in the IdP, then can provide that info as follows:
```ruby
def init
request = OneLogin::RubySaml::Authrequest.new
saml_settings.name_identifier_value_requested = "testuser@example.com"
saml_settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
redirect_to(request.create(saml_settings))
end
```
Once you've redirected back to the identity provider, it will ensure that the user has been
authorized and redirect back to your application for final consumption.
This can look something like this (the `authorize_success` and `authorize_failure`
methods are specific to your application):
```ruby
def consume
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :settings => saml_settings)
# We validate the SAML Response and check if the user already exists in the system
if response.is_valid?
# authorize_success, log the user
session[:userid] = response.nameid
session[:attributes] = response.attributes
else
authorize_failure # This method shows an error message
# List of errors is available in response.errors array
end
end
```
In the above there are a few assumptions, one being that `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:
```
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
response.settings = saml_settings
```
If the assertion of the SAMLResponse is not encrypted, you can initialize the Response
without the `:settings` parameter and set it later. If the SAMLResponse contains an encrypted
assertion, you need to provide the settings in the initialize method in order to obtain the
decrypted assertion, using the service provider private key in order to decrypt.
If you don't know what expect, always use the former (set the settings on initialize).
```ruby
def saml_settings
settings = OneLogin::RubySaml::Settings.new
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
settings.sp_entity_id = "http://#{request.host}/saml/metadata"
settings.idp_entity_id = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}"
settings.idp_sso_service_url = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}"
settings.idp_sso_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" # or :post, :redirect
settings.idp_slo_service_url = "https://app.onelogin.com/trust/saml2/http-redirect/slo/#{OneLoginAppId}"
settings.idp_slo_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" # or :post, :redirect
settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
settings.idp_cert_fingerprint_algorithm = "http://www.w3.org/2000/09/xmldsig#sha1"
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
# Optional for most SAML IdPs
settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
# or as an array
settings.authn_context = [
"urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
"urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
]
# Optional bindings (defaults to Redirect for logout POST for ACS)
settings.single_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" # or :post, :redirect
settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" # or :post, :redirect
settings
end
```
The use of settings.issuer is deprecated in favour of settings.sp_entity_id since version 1.11.0
Some assertion validations can be skipped by passing parameters to `OneLogin::RubySaml::Response.new()`.
For example, you can skip the `AuthnStatement`, `Conditions`, `Recipient`, or the `SubjectConfirmation`
validations by initializing the response with different options:
```ruby
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_authnstatement: true}) # skips AuthnStatement
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_conditions: true}) # skips conditions
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_subject_confirmation: true}) # skips subject confirmation
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_recipient_check: true}) # doesn't skip subject confirmation, but skips the recipient check which is a sub check of the subject_confirmation check
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_audience: true}) # skips audience check
```
All that's left is to wrap everything in a controller and reference it in the initialization and
consumption URLs in OneLogin. A full controller example could look like this:
```ruby
# This controller expects you to use the URLs /saml/init and /saml/consume in your OneLogin application.
class SamlController < ApplicationController
def init
request = OneLogin::RubySaml::Authrequest.new
redirect_to(request.create(saml_settings))
end
def consume
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
response.settings = saml_settings
# We validate the SAML Response and check if the user already exists in the system
if response.is_valid?
# authorize_success, log the user
session[:userid] = response.nameid
session[:attributes] = response.attributes
else
authorize_failure # This method shows an error message
# List of errors is available in response.errors array
end
end
private
def saml_settings
settings = OneLogin::RubySaml::Settings.new
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
settings.sp_entity_id = "http://#{request.host}/saml/metadata"
settings.idp_sso_service_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}"
settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
# Optional for most SAML IdPs
settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
# Optional. Describe according to IdP specification (if supported) which attributes the SP desires to receive in SAMLResponse.
settings.attributes_index = 5
# Optional. Describe an attribute consuming service for support of additional attributes.
settings.attribute_consuming_service.configure do
service_name "Service"
service_index 5
add_attribute :name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name"
end
settings
end
end
```
## Signature Validation
Ruby SAML allows different ways to validate the signature of the SAMLResponse:
- You can provide the IdP X.509 public certificate at the `idp_cert` setting.
- You can provide the IdP X.509 public certificate in fingerprint format using the
`idp_cert_fingerprint` setting parameter and additionally the `idp_cert_fingerprint_algorithm` parameter.
When validating the signature of redirect binding, the fingerprint is useless and the certificate
of the IdP is required in order to execute the validation. You can pass the option
`:relax_signature_validation` to `SloLogoutrequest` and `Logoutresponse` if want to avoid signature
validation if no certificate of the IdP is provided.
In production also we highly recommend to register on the settings the IdP certificate instead
of using the fingerprint method. The fingerprint, is a hash, so at the end is open to a collision
attack that can end on a signature validation bypass. Other SAML toolkits deprecated that mechanism,
we maintain it for compatibility and also to be used on test environment.
## Handling Multiple IdP Certificates
If the IdP metadata XML includes multiple certificates, you may specify the `idp_cert_multi`
parameter. When used, the `idp_cert` and `idp_cert_fingerprint` parameters are ignored.
This is useful in the following scenarios:
* The IdP uses different certificates for signing versus encryption.
* The IdP is undergoing a key rollover and is publishing the old and new certificates in parallel.
The `idp_cert_multi` must be a `Hash` as follows. The `:signing` and `:encryption` arrays below,
add the IdP X.509 public certificates which were published in the IdP metadata.
```ruby
{
:signing => [],
:encryption => []
}
```
## Metadata Based Configuration
The method above requires a little extra work to manually specify attributes about both the IdP and your SP application.
There's an easier method: use a metadata exchange. Metadata is an XML file that defines the capabilities of both the IdP
and the SP application. It also contains the X.509 public key certificates which add to the trusted relationship.
The IdP administrator can also configure custom settings for an SP based on the metadata.
Using `IdpMetadataParser#parse_remote`, the IdP metadata will be added to the settings.
```ruby
def saml_settings
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
# Returns OneLogin::RubySaml::Settings pre-populated with IdP metadata
settings = idp_metadata_parser.parse_remote("https://example.com/auth/saml2/idp/metadata")
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
settings.sp_entity_id = "http://#{request.host}/saml/metadata"
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
# Optional for most SAML IdPs
settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
settings
end
```
The following attributes are set:
* idp_entity_id
* name_identifier_format
* idp_sso_service_url
* idp_slo_service_url
* idp_attribute_names
* idp_cert
* idp_cert_fingerprint
* idp_cert_multi
### Retrieve one Entity Descriptor when many exist in Metadata
If the Metadata contains several entities, the relevant Entity
Descriptor can be specified when retrieving the settings from the
IdpMetadataParser by its Entity Id value:
```ruby
validate_cert = true
settings = idp_metadata_parser.parse_remote(
"https://example.com/auth/saml2/idp/metadata",
validate_cert,
entity_id: "http//example.com/target/entity"
)
```
### Parsing Metadata into an Hash
The `OneLogin::RubySaml::IdpMetadataParser` also provides the methods `#parse_to_hash` and `#parse_remote_to_hash`.
Those return an Hash instead of a `Settings` object, which may be useful for configuring
[omniauth-saml](https://github.com/omniauth/omniauth-saml), for instance.
## Retrieving Attributes
If you are using `saml:AttributeStatement` to transfer data like the username, you can access all the attributes through `response.attributes`. It contains all the `saml:AttributeStatement`s with its 'Name' as an indifferent key and one or more `saml:AttributeValue`s as values. The value returned depends on the value of the
`single_value_compatibility` (when activated, only the first value is returned)
```ruby
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
response.settings = saml_settings
response.attributes[:username]
```
Imagine this `saml:AttributeStatement`
```xml
demovalue1value2role1role2role3valuePresentusersName
```
```ruby
pp(response.attributes) # is an OneLogin::RubySaml::Attributes object
# => @attributes=
{"uid"=>["demo"],
"another_value"=>["value1", "value2"],
"role"=>["role1", "role2", "role3"],
"attribute_with_nil_value"=>[nil],
"attribute_with_nils_and_empty_strings"=>["", "valuePresent", nil, nil]
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"=>["usersName"]}>
# Active single_value_compatibility
OneLogin::RubySaml::Attributes.single_value_compatibility = true
pp(response.attributes[:uid])
# => "demo"
pp(response.attributes[:role])
# => "role1"
pp(response.attributes.single(:role))
# => "role1"
pp(response.attributes.multi(:role))
# => ["role1", "role2", "role3"]
pp(response.attributes.fetch(:role))
# => "role1"
pp(response.attributes[:attribute_with_nil_value])
# => nil
pp(response.attributes[:attribute_with_nils_and_empty_strings])
# => ""
pp(response.attributes[:not_exists])
# => nil
pp(response.attributes.single(:not_exists))
# => nil
pp(response.attributes.multi(:not_exists))
# => nil
pp(response.attributes.fetch(/givenname/))
# => "usersName"
# Deprecated single_value_compatibility
OneLogin::RubySaml::Attributes.single_value_compatibility = false
pp(response.attributes[:uid])
# => ["demo"]
pp(response.attributes[:role])
# => ["role1", "role2", "role3"]
pp(response.attributes.single(:role))
# => "role1"
pp(response.attributes.multi(:role))
# => ["role1", "role2", "role3"]
pp(response.attributes.fetch(:role))
# => ["role1", "role2", "role3"]
pp(response.attributes[:attribute_with_nil_value])
# => [nil]
pp(response.attributes[:attribute_with_nils_and_empty_strings])
# => ["", "valuePresent", nil, nil]
pp(response.attributes[:not_exists])
# => nil
pp(response.attributes.single(:not_exists))
# => nil
pp(response.attributes.multi(:not_exists))
# => nil
pp(response.attributes.fetch(/givenname/))
# => ["usersName"]
```
The `saml:AuthnContextClassRef` of the AuthNRequest can be provided by `settings.authn_context`; possible values are described at [SAMLAuthnCxt]. The comparison method can be set using `settings.authn_context_comparison` parameter. Possible values include: 'exact', 'better', 'maximum' and 'minimum' (default value is 'exact').
To add a `saml:AuthnContextDeclRef`, define `settings.authn_context_decl_ref`.
In a SP-initiated flow, the SP can indicate to the IdP the subject that should be authenticated. This is done by defining the `settings.name_identifier_value_requested` before
building the authrequest object.
## Service Provider Metadata
To form a trusted pair relationship with the IdP, the SP (you) need to provide metadata XML
to the IdP for various good reasons. (Caching, certificate lookups, relaying party permissions, etc)
The class `OneLogin::RubySaml::Metadata` takes care of this by reading the Settings and returning XML. All you have to do is add a controller to return the data, then give this URL to the IdP administrator.
The metadata will be polled by the IdP every few minutes, so updating your settings should propagate
to the IdP settings.
```ruby
class SamlController < ApplicationController
# ... the rest of your controller definitions ...
def metadata
settings = Account.get_saml_settings
meta = OneLogin::RubySaml::Metadata.new
render :xml => meta.generate(settings), :content_type => "application/samlmetadata+xml"
end
end
```
You can add `ValidUntil` and `CacheDuration` to the SP Metadata XML using instead:
```ruby
# Valid until => 2 days from now
# Cache duration = 604800s = 1 week
valid_until = Time.now + 172800
cache_duration = 604800
meta.generate(settings, false, valid_until, cache_duration)
```
## Signing and Decryption
Ruby SAML supports the following functionality:
1. Signing your SP Metadata XML
2. Signing your SP SAML messages
3. Decrypting IdP Assertion messages upon receipt (EncryptedAssertion)
4. Verifying signatures on SAML messages and IdP Assertions
In order to use functions 1-3 above, you must first define your SP public certificate and private key:
```ruby
settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER"
settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER"
```
Note that the same certificate (and its associated private key) are used to perform
all decryption and signing-related functions (1-4) above. Ruby SAML does not currently allow
to specify different certificates for each function.
You may also globally set the SP signature and digest method, to be used in SP signing (functions 1 and 2 above):
```ruby
settings.security[:digest_method] = XMLSecurity::Document::SHA1
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
```
#### Signing SP Metadata
You may add a `` digital signature element to your SP Metadata XML using the following setting:
```ruby
settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER"
settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER"
settings.security[:metadata_signed] = true # Enable signature on Metadata
```
#### Signing SP SAML Messages
Ruby SAML supports SAML request signing. The Service Provider will sign the
request/responses with its private key. The Identity Provider will then validate the signature
of the received request/responses with the public X.509 cert of the Service Provider.
To enable, please first set your certificate and private key. This will add ``
to your SP Metadata XML, to be read by the IdP.
```ruby
settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER"
settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER"
```
Next, you may specify the specific SP SAML messages you would like to sign:
```ruby
settings.security[:authn_requests_signed] = true # Enable signature on AuthNRequest
settings.security[:logout_requests_signed] = true # Enable signature on Logout Request
settings.security[:logout_responses_signed] = true # Enable signature on Logout Response
```
Signatures will be handled automatically for both `HTTP-Redirect` and `HTTP-Redirect` Binding.
Note that the RelayState parameter is used when creating the Signature on the `HTTP-Redirect` Binding.
Remember to provide it to the Signature builder if you are sending a `GET RelayState` parameter or the
signature validation process will fail at the Identity Provider.
#### Decrypting IdP SAML Assertions
Ruby SAML supports EncryptedAssertion. The Identity Provider will encrypt the Assertion with the
public cert of the Service Provider. The Service Provider will decrypt the EncryptedAssertion with its private key.
You may enable EncryptedAssertion as follows. This will add `` to your
SP Metadata XML, to be read by the IdP.
```ruby
settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER"
settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER"
settings.security[:want_assertions_encrypted] = true # Invalidate SAML messages without an EncryptedAssertion
```
#### Verifying Signature on IdP Assertions
You may require the IdP to sign its SAML Assertions using the following setting.
With will add `` to your SP Metadata XML.
The signature will be checked against the `` element
present in the IdP's metadata.
```ruby
settings.security[:want_assertions_signed] = true # Require the IdP to sign its SAML Assertions
```
#### Certificate and Signature Validation
You may require SP and IdP certificates to be non-expired using the following settings:
```ruby
settings.security[:check_idp_cert_expiration] = true # Raise error if IdP X.509 cert is expired
settings.security[:check_sp_cert_expiration] = true # Raise error SP X.509 cert is expired
```
By default, Ruby SAML will raise a `OneLogin::RubySaml::ValidationError` if a signature or certificate
validation fails. You may disable such exceptions using the `settings.security[:soft]` parameter.
```ruby
settings.security[:soft] = true # Do not raise error on failed signature/certificate validations
```
#### Key Rollover
To update the SP X.509 certificate and private key without disruption of service, you may define the parameter
`settings.certificate_new`. This will publish the new SP certificate in your metadata so that your IdP counterparties
may cache it in preparation for rollover.
For example, if you to rollover from `CERT A` to `CERT B`. Before rollover, your settings should look as follows.
Both `CERT A` and `CERT B` will now appear in your SP metadata, however `CERT A` will still be used for signing
and encryption at this time.
```ruby
settings.certificate = "CERT A"
settings.private_key = "PRIVATE KEY FOR CERT A"
settings.certificate_new = "CERT B"
```
After the IdP has cached `CERT B`, you may then change your settings as follows:
```ruby
settings.certificate = "CERT B"
settings.private_key = "PRIVATE KEY FOR CERT B"
```
## Single Log Out
Ruby SAML supports SP-initiated Single Logout and IdP-Initiated Single Logout.
Here is an example that we could add to our previous controller to generate and send a SAML Logout Request to the IdP:
```ruby
# Create a SP initiated SLO
def sp_logout_request
# LogoutRequest accepts plain browser requests w/o paramters
settings = saml_settings
if settings.idp_slo_service_url.nil?
logger.info "SLO IdP Endpoint not found in settings, executing then a normal logout'"
delete_session
else
logout_request = OneLogin::RubySaml::Logoutrequest.new
logger.info "New SP SLO for userid '#{session[:userid]}' transactionid '#{logout_request.uuid}'"
if settings.name_identifier_value.nil?
settings.name_identifier_value = session[:userid]
end
# Ensure user is logged out before redirect to IdP, in case anything goes wrong during single logout process (as recommended by saml2int [SDP-SP34])
logged_user = session[:userid]
logger.info "Delete session for '#{session[:userid]}'"
delete_session
# Save the transaction_id to compare it with the response we get back
session[:transaction_id] = logout_request.uuid
session[:logged_out_user] = logged_user
relayState = url_for(controller: 'saml', action: 'index')
redirect_to(logout_request.create(settings, :RelayState => relayState))
end
end
```
This method processes the SAML Logout Response sent by the IdP as the reply of the SAML Logout Request:
```ruby
# After sending an SP initiated LogoutRequest to the IdP, we need to accept
# the LogoutResponse, verify it, then actually delete our session.
def process_logout_response
settings = Account.get_saml_settings
if session.has_key? :transaction_id
logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings, :matches_request_id => session[:transaction_id])
else
logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings)
end
logger.info "LogoutResponse is: #{logout_response.to_s}"
# Validate the SAML Logout Response
if not logout_response.validate
logger.error "The SAML Logout Response is invalid"
else
# Actually log out this session
logger.info "SLO completed for '#{session[:logged_out_user]}'"
delete_session
end
end
# Delete a user's session.
def delete_session
session[:userid] = nil
session[:attributes] = nil
session[:transaction_id] = nil
session[:logged_out_user] = nil
end
```
Here is an example that we could add to our previous controller to process a SAML Logout Request from the IdP and reply with a SAML Logout Response to the IdP:
```ruby
# Method to handle IdP initiated logouts
def idp_logout_request
settings = Account.get_saml_settings
logout_request = OneLogin::RubySaml::SloLogoutrequest.new(params[:SAMLRequest])
if !logout_request.is_valid?
logger.error "IdP initiated LogoutRequest was not valid!"
return render :inline => logger.error
end
logger.info "IdP initiated Logout for #{logout_request.name_id}"
# Actually log out this session
delete_session
# Generate a response to the IdP.
logout_request_id = logout_request.id
logout_response = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request_id, nil, :RelayState => params[:RelayState])
redirect_to logout_response
end
```
All the mentioned methods could be handled in a unique view:
```ruby
# Trigger SP and IdP initiated Logout requests
def logout
# If we're given a logout request, handle it in the IdP logout initiated method
if params[:SAMLRequest]
return idp_logout_request
# We've been given a response back from the IdP, process it
elsif params[:SAMLResponse]
return process_logout_response
# Initiate SLO (send Logout Request)
else
return sp_logout_request
end
end
```
## Clock Drift
Server clocks tend to drift naturally. If during validation of the response you get the error "Current time is earlier than NotBefore condition", this may be due to clock differences between your system and that of the Identity Provider.
First, ensure that both systems synchronize their clocks, using for example the industry standard [Network Time Protocol (NTP)](http://en.wikipedia.org/wiki/Network_Time_Protocol).
Even then you may experience intermittent issues, as the clock of the Identity Provider may drift slightly ahead of your system clocks. To allow for a small amount of clock drift, you can initialize the response by passing in an option named `:allowed_clock_drift`. Its value must be given in a number (and/or fraction) of seconds. The value given is added to the current time at which the response is validated before it's tested against the `NotBefore` assertion. For example:
```ruby
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :allowed_clock_drift => 1.second)
```
Make sure to keep the value as comfortably small as possible to keep security risks to a minimum.
## Deflation Limit
To protect against decompression bombs (a form of DoS attack), SAML messages are limited to 250,000 bytes by default.
Sometimes legitimate SAML messages will exceed this limit,
for example due to custom claims like including groups a user is a member of.
If you want to customize this limit, you need to provide a different setting when initializing the response object.
Example:
```ruby
def consume
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], { settings: saml_settings })
...
end
private
def saml_settings
OneLogin::RubySaml::Settings.new(message_max_bytesize: 500_000)
end
```
## Attribute Service
To request attributes from the IdP the SP needs to provide an attribute service within it's metadata and reference the index in the assertion.
```ruby
settings = OneLogin::RubySaml::Settings.new
settings.attributes_index = 5
settings.attribute_consuming_service.configure do
service_name "Service"
service_index 5
add_attribute :name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name"
add_attribute :name => "Another Attribute", :name_format => "Name Format", :friendly_name => "Friendly Name", :attribute_value => "Attribute Value"
end
```
The `attribute_value` option additionally accepts an array of possible values.
## Custom Metadata Fields
Some IdPs may require to add SPs to add additional fields (Organization, ContactPerson, etc.)
into the SP metadata. This can be achieved by extending the `OneLogin::RubySaml::Metadata`
class and overriding the `#add_extras` method as per the following example:
```ruby
class MyMetadata < OneLogin::RubySaml::Metadata
def add_extras(root, _settings)
org = root.add_element("md:Organization")
org.add_element("md:OrganizationName", 'xml:lang' => "en-US").text = 'ACME Inc.'
org.add_element("md:OrganizationDisplayName", 'xml:lang' => "en-US").text = 'ACME'
org.add_element("md:OrganizationURL", 'xml:lang' => "en-US").text = 'https://www.acme.com'
cp = root.add_element("md:ContactPerson", 'contactType' => 'technical')
cp.add_element("md:GivenName").text = 'ACME SAML Team'
cp.add_element("md:EmailAddress").text = 'saml@acme.com'
end
end
# Output XML with custom metadata
MyMetadata.new.generate(settings)
```
ruby-saml-1.13.0/gemfiles/ 0000755 0000041 0000041 00000000000 14136046347 015342 5 ustar www-data www-data ruby-saml-1.13.0/gemfiles/nokogiri-1.5.gemfile 0000644 0000041 0000041 00000000123 14136046347 021012 0 ustar www-data www-data source 'https://rubygems.org'
gem "nokogiri", "~> 1.5.10"
gemspec :path => "../"
ruby-saml-1.13.0/CHANGELOG.md 0000644 0000041 0000041 00000045570 14136046347 015373 0 ustar www-data www-data # Ruby SAML Changelog
### 1.13.0 (Sept 06, 2021)
* [#611](https://github.com/onelogin/ruby-saml/pull/601) Replace MAX_BYTE_SIZE constant with setting: message_max_bytesize
* [#605](https://github.com/onelogin/ruby-saml/pull/605) :allowed_clock_drift is now bidrectional
* [#614](https://github.com/onelogin/ruby-saml/pull/614) Support :name_id_format option for IdpMetadataParser
* [#611](https://github.com/onelogin/ruby-saml/pull/611) IdpMetadataParser should always set idp_cert_multi, even when there is only one cert
* [#610](https://github.com/onelogin/ruby-saml/pull/610) New IDP sso/slo binding params which deprecate :embed_sign
* [#602](https://github.com/onelogin/ruby-saml/pull/602) Refactor the OneLogin::RubySaml::Metadata class
* [#586](https://github.com/onelogin/ruby-saml/pull/586) Support milliseconds in cacheDuration parsing
* [#585](https://github.com/onelogin/ruby-saml/pull/585) Do not append " | " to StatusCode unnecessarily
* [#607](https://github.com/onelogin/ruby-saml/pull/607) Clean up
* Add warning about the use of IdpMetadataParser class and SSRF
* CI: Migrate from Travis to Github Actions
### 1.12.2 (Apr 08, 2021)
* [#575](https://github.com/onelogin/ruby-saml/pull/575) Fix SloLogoutresponse bug on LogoutRequest
### 1.12.1 (Apr 05, 2021)
* Fix XPath typo incompatible with Rexml 3.2.5
* Refactor GCM support
### 1.12.0 (Feb 18, 2021)
* Support AES-128-GCM, AES-192-GCM, and AES-256-GCM encryptions
* Parse & return SLO ResponseLocation in IDPMetadataParser & Settings
* Adding idp_sso_service_url and idp_slo_service_url settings
* [#536](https://github.com/onelogin/ruby-saml/pull/536) Adding feth method to be able retrieve attributes based on regex
* Reduce size of built gem by excluding the test folder
* Improve protection on Zlib deflate decompression bomb attack.
* Add ValidUntil and cacheDuration support on Metadata generator
* Add support for cacheDuration at the IdpMetadataParser
* Support customizable statusCode on generated LogoutResponse
* [#545](https://github.com/onelogin/ruby-saml/pull/545) More specific error messages for signature validation
* Support Process Transform
* Raise SettingError if invoking an action with no endpoint defined on the settings
* Made IdpMetadataParser more extensible for subclasses
*[#548](https://github.com/onelogin/ruby-saml/pull/548) Add :skip_audience option
* [#555](https://github.com/onelogin/ruby-saml/pull/555) Define 'soft' variable to prevent exception when doc cert is invalid
* Improve documentation
### 1.11.0 (Jul 24, 2019)
* Deprecate settings.issuer in favor of settings.sp_entity_id
* Add support for certification expiration
### 1.10.2 (Apr 29, 2019)
* Add valid until, accessor
* Fix Rubygem metadata that requested nokogiri <= 1.5.11
### 1.10.1 (Apr 08, 2019)
* Fix ruby 1.8.7 incompatibilities
### 1.10.0 (Mar 21, 2019)
* Add Subject support on AuthNRequest to allow SPs provide info to the IdP about the user to be authenticated
* Improves IdpMetadataParser to allow parse multiple IDPSSODescriptors
* Improves format_cert method to accept certs with /\x0d/
* Forces nokogiri >= 1.8.2 when possible
### 1.9.0 (Sept 03, 2018)
* [#458](https://github.com/onelogin/ruby-saml/pull/458) Remove ruby 2.4+ warnings
* Improve JRuby support
* [#465](https://github.com/onelogin/ruby-saml/pull/465) Extend Settings initialization with the new keep_security_attributes parameter
* Fix wrong message when SessionNotOnOrAfter expired
* [#471](https://github.com/onelogin/ruby-saml/pull/471) Allow for `allowed_clock_drift` to be set as a string
### 1.8.0 (April 23, 2018)
* [#437](https://github.com/onelogin/ruby-saml/issues/437) Creating AuthRequests/LogoutRequests/LogoutResponses with nil RelayState should not send empty RelayState URL param
* [#454](https://github.com/onelogin/ruby-saml/pull/454) Added Response available options
* [#453](https://github.com/onelogin/ruby-saml/pull/453) Raise a more descriptive exception if idp_sso_target_url is missing
* [#452](https://github.com/onelogin/ruby-saml/pull/452) Fix behavior of skip_conditions flag on Response
* [#449](https://github.com/onelogin/ruby-saml/pull/449) Add ability to skip authnstatement validation
* Clear cached values to be able to use IdpMetadataParser more than once
* Updated invalid audience error message
### 1.7.2 (Feb 28, 2018)
* [#446](https://github.com/onelogin/ruby-saml/pull/446) Normalize text returned by OneLogin::RubySaml::Utils.element_text
### 1.7.1 (Feb 28, 2018)
* [#444](https://github.com/onelogin/ruby-saml/pull/444) Fix audience validation for empty audience restriction
### 1.7.0 (Feb 27, 2018)
* Fix vulnerability CVE-2017-11428. Process text of nodes properly, ignoring comments
### 1.6.1 (January 15, 2018)
* [#428](https://github.com/onelogin/ruby-saml/issues/428) Fix a bug on IdPMetadataParser when parsing certificates
* [#426](https://github.com/onelogin/ruby-saml/pull/426) Ensure `Rails` responds to `logger`
### 1.6.0 (November 27, 2017)
* [#418](https://github.com/onelogin/ruby-saml/pull/418) Improve SAML message signature validation using original encoded parameters instead decoded in order to avoid conflicts (URL-encoding is not canonical, reported issues with ADFS)
* [#420](https://github.com/onelogin/ruby-saml/pull/420) Expose NameID Format on SloLogoutrequest
* [#423](https://github.com/onelogin/ruby-saml/pull/423) Allow format_cert to work with chained certificates
* [#422](https://github.com/onelogin/ruby-saml/pull/422) Use to_s for requested attribute value
### 1.5.0 (August 31, 2017)
* [#400](https://github.com/onelogin/ruby-saml/pull/400) When validating Signature use stored IdP certficate if Signature contains no info about Certificate
* [#402](https://github.com/onelogin/ruby-saml/pull/402) Fix validate_response_state method that rejected SAMLResponses when using idp_cert_multi and idp_cert and idp_cert_fingerprint were not provided.
* [#411](https://github.com/onelogin/ruby-saml/pull/411) Allow space in Base64 string
* [#407](https://github.com/onelogin/ruby-saml/issues/407) Improve IdpMetadataParser raising an ArgumentError when parser method receive a metadata string with no IDPSSODescriptor element.
* [#374](https://github.com/onelogin/ruby-saml/issues/374) Support more than one level of StatusCode
* [#405](https://github.com/onelogin/ruby-saml/pull/405) Support ADFS encrypted key (Accept KeyInfo nodes with no ds namespace)
### 1.4.3 (May 18, 2017)
* Added SubjectConfirmation Recipient validation
* [#393](https://github.com/onelogin/ruby-saml/pull/393) Implement IdpMetadataParser#parse_to_hash
* Adapt IdP XML metadata parser to take care of multiple IdP certificates and be able to inject the data obtained on the settings.
* Improve binding detection on idp metadata parser
* [#373](https://github.com/onelogin/ruby-saml/pull/373) Allow metadata to be retrieved from source containing data for multiple entities
* Be able to register future SP x509cert on the settings and publish it on SP metadata
* Be able to register more than 1 Identity Provider x509cert, linked with an specific use (signing or encryption.
* Improve regex to detect base64 encoded messages
* Fix binding configuration example in README.md
* Add Fix SLO request. Correct NameQualifier/SPNameQualifier values.
* Validate serial number as string to work around libxml2 limitation
* Propagate isRequired on md:RequestedAttribute when generating SP metadata
### 1.4.2 (January 11, 2017)
* Improve tests format
* Fix nokogiri requirements based on ruby version
* Only publish `KeyDescriptor[use="encryption"]` at SP metadata if `security[:want_assertions_encrypted]` is true
* Be able to skip destination validation
* Improved inResponse validation on SAMLResponses and LogoutResponses
* [#354](https://github.com/onelogin/ruby-saml/pull/354) Allow scheme and domain to match ignoring case
* [#363](https://github.com/onelogin/ruby-saml/pull/363) Add support for multiple requested attributes
### 1.4.1 (October 19, 2016)
* [#357](https://github.com/onelogin/ruby-saml/pull/357) Add EncryptedAttribute support. Improve decrypt method
* Allow multiple authn_context_decl_ref in settings
* Allow options[:settings] to be an hash for Settings overrides in IdpMetadataParser#parse
* Recover issuers method
### 1.4.0 (October 13, 2016)
* Several security improvements:
* Conditions element required and unique.
* AuthnStatement element required and unique.
* SPNameQualifier must math the SP EntityID
* Reject saml:Attribute element with same “Name” attribute
* Reject empty nameID
* Require Issuer element. (Must match IdP EntityID).
* Destination value can't be blank (if present must match ACS URL).
* Check that the EncryptedAssertion element only contains 1 Assertion element.
* [#335](https://github.com/onelogin/ruby-saml/pull/335) Explicitly parse as XML and fix setting of Nokogiri options.
* [#345](https://github.com/onelogin/ruby-saml/pull/345)Support multiple settings.auth_context
* More tests to prevent XML Signature Wrapping
* [#342](https://github.com/onelogin/ruby-saml/pull/342) Correct the usage of Mutex
* [352](https://github.com/onelogin/ruby-saml/pull/352) Support multiple AttributeStatement tags
### 1.3.1 (July 10, 2016)
* Fix response_test.rb of gem 1.3.0
* Add reference to Security Guidelines
* Update License
* [#334](https://github.com/onelogin/ruby-saml/pull/334) Keep API backward-compatibility on IdpMetadataParser fingerprint method.
### 1.3.0 (June 24, 2016)
* [Security Fix](https://github.com/onelogin/ruby-saml/commit/a571f52171e6bfd87db59822d1d9e8c38fb3b995) Add extra validations to prevent Signature wrapping attacks
* Fix XMLSecurity SHA256 and SHA512 uris
* [#326](https://github.com/onelogin/ruby-saml/pull/326) Fix Destination validation
### 1.2.0 (April 29, 2016)
* [#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)
* [#289](https://github.com/onelogin/ruby-saml/pull/289) Remove uuid gem in favor of SecureRandom
* [#297](https://github.com/onelogin/ruby-saml/pull/297) Implement EncryptedKey RetrievalMethod support
* [#298](https://github.com/onelogin/ruby-saml/pull/298) IDP metadata parsing improved: binding parsing, fingerprint_algorithm support)
* [#299](https://github.com/onelogin/ruby-saml/pull/299) Make 'signing' at KeyDescriptor optional
* [#308](https://github.com/onelogin/ruby-saml/pull/308) Support name_id_format on SAMLResponse
* [#315](https://github.com/onelogin/ruby-saml/pull/315) Support for canonicalization with comments
* [#316](https://github.com/onelogin/ruby-saml/pull/316) Fix Misspelling of transation_id to transaction_id
* [#321](https://github.com/onelogin/ruby-saml/pull/321) Support Attribute Names on IDPSSODescriptor parser
* Changes on empty URI of Signature reference management
* [#320](https://github.com/onelogin/ruby-saml/pull/320) Dont mutate document to fix lack of reference URI
* [#306](https://github.com/onelogin/ruby-saml/pull/306) Support WantAssertionsSigned
### 1.1.2 (February 15, 2016)
* Improve signature validation. Add tests.
[#302](https://github.com/onelogin/ruby-saml/pull/302) Add Destination validation.
* [#292](https://github.com/onelogin/ruby-saml/pull/292) Improve the error message when validating the audience.
* [#287](https://github.com/onelogin/ruby-saml/pull/287) Keep the extracted certificate when parsing IdP metadata.
### 1.1.1 (November 10, 2015)
* [#275](https://github.com/onelogin/ruby-saml/pull/275) Fix a bug on signature validations that invalidates valid SAML messages.
### 1.1.0 (October 27, 2015)
* [#273](https://github.com/onelogin/ruby-saml/pull/273) Support SAMLResponse without ds:x509certificate
* [#270](https://github.com/onelogin/ruby-saml/pull/270) Allow SAML elements to come from any namespace (at decryption process)
* [#261](https://github.com/onelogin/ruby-saml/pull/261) Allow validate_subject_confirmation Response validation to be skipped
* [#258](https://github.com/onelogin/ruby-saml/pull/258) Fix allowed_clock_drift on the validate_session_expiration test
* [#256](https://github.com/onelogin/ruby-saml/pull/256) Separate the create_authentication_xml_doc in two methods.
* [#255](https://github.com/onelogin/ruby-saml/pull/255) Refactor validate signature.
* [#254](https://github.com/onelogin/ruby-saml/pull/254) Handle empty URI references
* [#251](https://github.com/onelogin/ruby-saml/pull/251) Support qualified and unqualified NameID in attributes
* [#234](https://github.com/onelogin/ruby-saml/pull/234) Add explicit support for JRuby
### 1.0.0 (June 30, 2015)
* [#247](https://github.com/onelogin/ruby-saml/pull/247) Avoid entity expansion (XEE attacks)
* [#246](https://github.com/onelogin/ruby-saml/pull/246) Fix bug generating Logout Response (issuer was at wrong order)
* [#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.
* [#241](https://github.com/onelogin/ruby-saml/pull/241) Add decrypt support (EncryptID and EncryptedAssertion). Improve compatibility with namespaces.
* [#240](https://github.com/onelogin/ruby-saml/pull/240) and [#238](https://github.com/onelogin/ruby-saml/pull/238) Improve test coverage and refactor.
* [#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.
* [#237](https://github.com/onelogin/ruby-saml/pull/237) Don't pretty print metadata by default.
* [#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.
* [#232](https://github.com/onelogin/ruby-saml/pull/232) Improve validations: Store the causes in the errors array, code refactor
* [#231](https://github.com/onelogin/ruby-saml/pull/231) Refactor HTTP-Redirect Sign method, Move test data to right folder
* [#226](https://github.com/onelogin/ruby-saml/pull/226) Ensure IdP certificate is formatted properly
* [#225](https://github.com/onelogin/ruby-saml/pull/225) Add documentation to several methods. Fix xpath injection on xml_security.rb
* [#223](https://github.com/onelogin/ruby-saml/pull/223) Allow logging to be delegated to an arbitrary Logger
* [#222](https://github.com/onelogin/ruby-saml/pull/222) No more silent failure fetching idp metadata (OneLogin::RubySaml::HttpError raised).
### 0.9.2 (Apr 28, 2015)
* [#216](https://github.com/onelogin/ruby-saml/pull/216) Add fingerprint algorithm support
* [#218](https://github.com/onelogin/ruby-saml/pull/218) Update README.md
* [#214](https://github.com/onelogin/ruby-saml/pull/214) Cleanup `SamlMessage` class
* [#213](https://github.com/onelogin/ruby-saml/pull/213) Add ability to sign metadata. (Improved)
* [#212](https://github.com/onelogin/ruby-saml/pull/212) Rename library entry point
* [#210](https://github.com/onelogin/ruby-saml/pull/210) Call assert in tests
* [#208](https://github.com/onelogin/ruby-saml/pull/208) Update tests and CI for Ruby 2.2.0
* [#205](https://github.com/onelogin/ruby-saml/pull/205) Allow requirement of single files
* [#204](https://github.com/onelogin/ruby-saml/pull/204) Require ‘net/http’ library
* [#201](https://github.com/onelogin/ruby-saml/pull/201) Freeze and duplicate default security settings hash so that it doesn't get modified.
* [#200](https://github.com/onelogin/ruby-saml/pull/200) Set default SSL certificate store in Ruby 1.8.
* [#199](https://github.com/onelogin/ruby-saml/pull/199) Change Nokogiri's runtime dependency to fix support for Ruby 1.8.7.
* [#179](https://github.com/onelogin/ruby-saml/pull/179) Add support for setting the entity ID and name ID format when parsing metadata
* [#175](https://github.com/onelogin/ruby-saml/pull/175) Introduce thread safety to SAML schema validation
* [#171](https://github.com/onelogin/ruby-saml/pull/171) Fix inconsistent results with using regex matches in decode_raw_saml
### 0.9.1 (Feb 10, 2015)
* [#194](https://github.com/onelogin/ruby-saml/pull/194) Relax nokogiri gem requirements
* [#191](https://github.com/onelogin/ruby-saml/pull/191) Use Minitest instead of Test::Unit
### 0.9 (Jan 26, 2015)
* [#169](https://github.com/onelogin/ruby-saml/pull/169) WantAssertionSigned should be either true or false
* [#167](https://github.com/onelogin/ruby-saml/pull/167) (doc update) make unit of clock drift obvious
* [#160](https://github.com/onelogin/ruby-saml/pull/160) Extended solution for Attributes method [] can raise NoMethodError
* [#158](https://github.com/onelogin/ruby-saml/pull/1) Added ability to specify attribute services in metadata
* [#154](https://github.com/onelogin/ruby-saml/pull/154) Fix incorrect gem declaration statement
* [#152](https://github.com/onelogin/ruby-saml/pull/152) Fix the PR #99
* [#150](https://github.com/onelogin/ruby-saml/pull/150) Nokogiri already in gemspec
* [#147](https://github.com/onelogin/ruby-saml/pull/147) Fix LogoutResponse issuer validation and implement SAML Response issuer validation.
* [#144](https://github.com/onelogin/ruby-saml/pull/144) Fix DigestMethod lookup bug
* [#139](https://github.com/onelogin/ruby-saml/pull/139) Fixes handling of some soft and hard validation failures
* [#138](https://github.com/onelogin/ruby-saml/pull/138) Change logoutrequest.rb to UTC time
* [#136](https://github.com/onelogin/ruby-saml/pull/136) Remote idp metadata
* [#135](https://github.com/onelogin/ruby-saml/pull/135) Restored support for NIL as well as empty AttributeValues
* [#134](https://github.com/onelogin/ruby-saml/pull/134) explicitly require "onelogin/ruby-saml/logging"
* [#133](https://github.com/onelogin/ruby-saml/pull/133) Added license to gemspec
* [#132](https://github.com/onelogin/ruby-saml/pull/132) Support AttributeConsumingServiceIndex in AuthnRequest
* [#131](https://github.com/onelogin/ruby-saml/pull/131) Add ruby 2.1.1 to .travis.yml
* [#122](https://github.com/onelogin/ruby-saml/pull/122) Fixes #112 and #117 in a backwards compatible manner
* [#119](https://github.com/onelogin/ruby-saml/pull/119) Add support for extracting IdP details from metadata xml
### 0.8.2 (Jan 26, 2015)
* [#183](https://github.com/onelogin/ruby-saml/pull/183) Resolved a security vulnerability where string interpolation in a `REXML::XPath.first()` method call allowed for arbitrary code execution.
### 0.8.0 (Feb 21, 2014)
**IMPORTANT**: This release changed namespace of the gem from `OneLogin::Saml` to `OneLogin::RubySaml`. Please update your implementations of the gem accordingly.
* [#111](https://github.com/onelogin/ruby-saml/pull/111) `Onelogin::` is `OneLogin::`
* [#108](https://github.com/onelogin/ruby-saml/pull/108) Change namespacing from `Onelogin::Saml` to `Onelogin::Rubysaml`
### 0.7.3 (Feb 20, 2014)
Updated gem dependencies to be compatible with Ruby 1.8.7-p374 and 1.9.3-p448. Removed unnecessary `canonix` gem dependency.
* [#107](https://github.com/onelogin/ruby-saml/pull/107) Relax nokogiri version requirement to >= 1.5.0
* [#105](https://github.com/onelogin/ruby-saml/pull/105) Lock Gem versions, fix to resolve possible namespace collision
ruby-saml-1.13.0/.gitignore 0000644 0000041 0000041 00000000176 14136046347 015543 0 ustar www-data www-data *.sw?
.DS_Store
coverage
rdoc
pkg
Gemfile.lock
gemfiles/*.lock
.idea/*
lib/Lib.iml
test/Test.iml
.rvmrc
*.gem
.bundle
*.patch
ruby-saml-1.13.0/LICENSE 0000644 0000041 0000041 00000002050 14136046347 014551 0 ustar www-data www-data Copyright (c) 2010-2016 OneLogin, Inc.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
ruby-saml-1.13.0/.document 0000644 0000041 0000041 00000000074 14136046347 015367 0 ustar www-data www-data README.rdoc
lib/**/*.rb
bin/*
features/**/*.feature
LICENSE
ruby-saml-1.13.0/Rakefile 0000644 0000041 0000041 00000001041 14136046347 015210 0 ustar www-data www-data require 'rubygems'
require 'rake'
#not being used yet.
require 'rake/testtask'
Rake::TestTask.new(:test) do |test|
test.libs << 'lib' << 'test'
test.pattern = 'test/**/*_test.rb'
test.verbose = true
end
begin
require 'rcov/rcovtask'
Rcov::RcovTask.new do |test|
test.libs << 'test'
test.pattern = 'test/**/*_test.rb'
test.verbose = true
end
rescue LoadError
task :rcov do
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
end
end
task :test
task :default => :test
ruby-saml-1.13.0/lib/ 0000755 0000041 0000041 00000000000 14136046347 014315 5 ustar www-data www-data ruby-saml-1.13.0/lib/xml_security.rb 0000644 0000041 0000041 00000033646 14136046347 017405 0 ustar www-data www-data # The contents of this file are subject to the terms
# of the Common Development and Distribution License
# (the License). You may not use this file except in
# compliance with the License.
#
# You can obtain a copy of the License at
# https://opensso.dev.java.net/public/CDDLv1.0.html or
# opensso/legal/CDDLv1.0.txt
# See the License for the specific language governing
# permission and limitations under the License.
#
# When distributing Covered Code, include this CDDL
# Header Notice in each file and include the License file
# at opensso/legal/CDDLv1.0.txt.
# If applicable, add the following below the CDDL Header,
# with the fields enclosed by brackets [] replaced by
# your own identifying information:
# "Portions Copyrighted [year] [name of copyright owner]"
#
# $Id: xml_sec.rb,v 1.6 2007/10/24 00:28:41 todddd Exp $
#
# Copyright 2007 Sun Microsystems Inc. All Rights Reserved
# Portions Copyrighted 2007 Todd W Saxton.
require 'rubygems'
require "rexml/document"
require "rexml/xpath"
require "openssl"
require 'nokogiri'
require "digest/sha1"
require "digest/sha2"
require "onelogin/ruby-saml/utils"
require "onelogin/ruby-saml/error_handling"
module XMLSecurity
class BaseDocument < REXML::Document
REXML::Document::entity_expansion_limit = 0
C14N = "http://www.w3.org/2001/10/xml-exc-c14n#"
DSIG = "http://www.w3.org/2000/09/xmldsig#"
NOKOGIRI_OPTIONS = Nokogiri::XML::ParseOptions::STRICT |
Nokogiri::XML::ParseOptions::NONET
def canon_algorithm(element)
algorithm = element
if algorithm.is_a?(REXML::Element)
algorithm = element.attribute('Algorithm').value
end
case algorithm
when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
Nokogiri::XML::XML_C14N_1_0
when "http://www.w3.org/2006/12/xml-c14n11",
"http://www.w3.org/2006/12/xml-c14n11#WithComments"
Nokogiri::XML::XML_C14N_1_1
else
Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
end
end
def algorithm(element)
algorithm = element
if algorithm.is_a?(REXML::Element)
algorithm = element.attribute("Algorithm").value
end
algorithm = algorithm && algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i
case algorithm
when 256 then OpenSSL::Digest::SHA256
when 384 then OpenSSL::Digest::SHA384
when 512 then OpenSSL::Digest::SHA512
else
OpenSSL::Digest::SHA1
end
end
end
class Document < BaseDocument
RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256'
SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384"
SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512'
ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
INC_PREFIX_LIST = "#default samlp saml ds xs xsi md"
attr_writer :uuid
def uuid
@uuid ||= begin
document.root.nil? ? nil : document.root.attributes['ID']
end
end
#
#
#
#
#
#
#
#
#
# etc.
#
#
#
#
#
def sign_document(private_key, certificate, signature_method = RSA_SHA1, digest_method = SHA1)
noko = Nokogiri::XML(self.to_s) do |config|
config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
end
signature_element = REXML::Element.new("ds:Signature").add_namespace('ds', DSIG)
signed_info_element = signature_element.add_element("ds:SignedInfo")
signed_info_element.add_element("ds:CanonicalizationMethod", {"Algorithm" => C14N})
signed_info_element.add_element("ds:SignatureMethod", {"Algorithm"=>signature_method})
# Add Reference
reference_element = signed_info_element.add_element("ds:Reference", {"URI" => "##{uuid}"})
# Add Transforms
transforms_element = reference_element.add_element("ds:Transforms")
transforms_element.add_element("ds:Transform", {"Algorithm" => ENVELOPED_SIG})
c14element = transforms_element.add_element("ds:Transform", {"Algorithm" => C14N})
c14element.add_element("ec:InclusiveNamespaces", {"xmlns:ec" => C14N, "PrefixList" => INC_PREFIX_LIST})
digest_method_element = reference_element.add_element("ds:DigestMethod", {"Algorithm" => digest_method})
inclusive_namespaces = INC_PREFIX_LIST.split(" ")
canon_doc = noko.canonicalize(canon_algorithm(C14N), inclusive_namespaces)
reference_element.add_element("ds:DigestValue").text = compute_digest(canon_doc, algorithm(digest_method_element))
# add SignatureValue
noko_sig_element = Nokogiri::XML(signature_element.to_s) do |config|
config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
end
noko_signed_info_element = noko_sig_element.at_xpath('//ds:Signature/ds:SignedInfo', 'ds' => DSIG)
canon_string = noko_signed_info_element.canonicalize(canon_algorithm(C14N))
signature = compute_signature(private_key, algorithm(signature_method).new, canon_string)
signature_element.add_element("ds:SignatureValue").text = signature
# add KeyInfo
key_info_element = signature_element.add_element("ds:KeyInfo")
x509_element = key_info_element.add_element("ds:X509Data")
x509_cert_element = x509_element.add_element("ds:X509Certificate")
if certificate.is_a?(String)
certificate = OpenSSL::X509::Certificate.new(certificate)
end
x509_cert_element.text = Base64.encode64(certificate.to_der).gsub(/\n/, "")
# add the signature
issuer_element = elements["//saml:Issuer"]
if issuer_element
root.insert_after(issuer_element, signature_element)
elsif first_child = root.children[0]
root.insert_before(first_child, signature_element)
else
root.add_element(signature_element)
end
end
protected
def compute_signature(private_key, signature_algorithm, document)
Base64.encode64(private_key.sign(signature_algorithm, document)).gsub(/\n/, "")
end
def compute_digest(document, digest_algorithm)
digest = digest_algorithm.digest(document)
Base64.encode64(digest).strip!
end
end
class SignedDocument < BaseDocument
include OneLogin::RubySaml::ErrorHandling
attr_writer :signed_element_id
def initialize(response, errors = [])
super(response)
@errors = errors
end
def signed_element_id
@signed_element_id ||= extract_signed_element_id
end
def validate_document(idp_cert_fingerprint, soft = true, options = {})
# get cert from response
cert_element = REXML::XPath.first(
self,
"//ds:X509Certificate",
{ "ds"=>DSIG }
)
if cert_element
base64_cert = OneLogin::RubySaml::Utils.element_text(cert_element)
cert_text = Base64.decode64(base64_cert)
begin
cert = OpenSSL::X509::Certificate.new(cert_text)
rescue OpenSSL::X509::CertificateError => _e
return append_error("Document Certificate Error", soft)
end
if options[:fingerprint_alg]
fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(options[:fingerprint_alg]).new
else
fingerprint_alg = OpenSSL::Digest::SHA1.new
end
fingerprint = fingerprint_alg.hexdigest(cert.to_der)
# check cert matches registered idp cert
if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
return append_error("Fingerprint mismatch", soft)
end
else
if options[:cert]
base64_cert = Base64.encode64(options[:cert].to_pem)
else
if soft
return false
else
return append_error("Certificate element missing in response (ds:X509Certificate) and not cert provided at settings", soft)
end
end
end
validate_signature(base64_cert, soft)
end
def validate_document_with_cert(idp_cert, soft = true)
# get cert from response
cert_element = REXML::XPath.first(
self,
"//ds:X509Certificate",
{ "ds"=>DSIG }
)
if cert_element
base64_cert = OneLogin::RubySaml::Utils.element_text(cert_element)
cert_text = Base64.decode64(base64_cert)
begin
cert = OpenSSL::X509::Certificate.new(cert_text)
rescue OpenSSL::X509::CertificateError => _e
return append_error("Document Certificate Error", soft)
end
# check saml response cert matches provided idp cert
if idp_cert.to_pem != cert.to_pem
return append_error("Certificate of the Signature element does not match provided certificate", soft)
end
else
base64_cert = Base64.encode64(idp_cert.to_pem)
end
validate_signature(base64_cert, true)
end
def validate_signature(base64_cert, soft = true)
document = Nokogiri::XML(self.to_s) do |config|
config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
end
# create a rexml document
@working_copy ||= REXML::Document.new(self.to_s).root
# get signature node
sig_element = REXML::XPath.first(
@working_copy,
"//ds:Signature",
{"ds"=>DSIG}
)
# signature method
sig_alg_value = REXML::XPath.first(
sig_element,
"./ds:SignedInfo/ds:SignatureMethod",
{"ds"=>DSIG}
)
signature_algorithm = algorithm(sig_alg_value)
# get signature
base64_signature = REXML::XPath.first(
sig_element,
"./ds:SignatureValue",
{"ds" => DSIG}
)
signature = Base64.decode64(OneLogin::RubySaml::Utils.element_text(base64_signature))
# canonicalization method
canon_algorithm = canon_algorithm REXML::XPath.first(
sig_element,
'./ds:SignedInfo/ds:CanonicalizationMethod',
'ds' => DSIG
)
noko_sig_element = document.at_xpath('//ds:Signature', 'ds' => DSIG)
noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG)
canon_string = noko_signed_info_element.canonicalize(canon_algorithm)
noko_sig_element.remove
# get inclusive namespaces
inclusive_namespaces = extract_inclusive_namespaces
# check digests
ref = REXML::XPath.first(sig_element, "//ds:Reference", {"ds"=>DSIG})
hashed_element = document.at_xpath("//*[@ID=$id]", nil, { 'id' => extract_signed_element_id })
canon_algorithm = canon_algorithm REXML::XPath.first(
ref,
'//ds:CanonicalizationMethod',
{ "ds" => DSIG }
)
canon_algorithm = process_transforms(ref, canon_algorithm)
canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces)
digest_algorithm = algorithm(REXML::XPath.first(
ref,
"//ds:DigestMethod",
{ "ds" => DSIG }
))
hash = digest_algorithm.digest(canon_hashed_element)
encoded_digest_value = REXML::XPath.first(
ref,
"//ds:DigestValue",
{ "ds" => DSIG }
)
digest_value = Base64.decode64(OneLogin::RubySaml::Utils.element_text(encoded_digest_value))
unless digests_match?(hash, digest_value)
return append_error("Digest mismatch", soft)
end
# get certificate object
cert_text = Base64.decode64(base64_cert)
cert = OpenSSL::X509::Certificate.new(cert_text)
# verify signature
unless cert.public_key.verify(signature_algorithm.new, signature, canon_string)
return append_error("Key validation error", soft)
end
return true
end
private
def process_transforms(ref, canon_algorithm)
transforms = REXML::XPath.match(
ref,
"//ds:Transforms/ds:Transform",
{ "ds" => DSIG }
)
transforms.each do |transform_element|
if transform_element.attributes && transform_element.attributes["Algorithm"]
algorithm = transform_element.attributes["Algorithm"]
case algorithm
when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
canon_algorithm = Nokogiri::XML::XML_C14N_1_0
when "http://www.w3.org/2006/12/xml-c14n11",
"http://www.w3.org/2006/12/xml-c14n11#WithComments"
canon_algorithm = Nokogiri::XML::XML_C14N_1_1
when "http://www.w3.org/2001/10/xml-exc-c14n#",
"http://www.w3.org/2001/10/xml-exc-c14n#WithComments"
canon_algorithm = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
end
end
end
canon_algorithm
end
def digests_match?(hash, digest_value)
hash == digest_value
end
def extract_signed_element_id
reference_element = REXML::XPath.first(
self,
"//ds:Signature/ds:SignedInfo/ds:Reference",
{"ds"=>DSIG}
)
return nil if reference_element.nil?
sei = reference_element.attribute("URI").value[1..-1]
sei.nil? ? reference_element.parent.parent.parent.attribute("ID").value : sei
end
def extract_inclusive_namespaces
element = REXML::XPath.first(
self,
"//ec:InclusiveNamespaces",
{ "ec" => C14N }
)
if element
prefix_list = element.attributes.get_attribute("PrefixList").value
prefix_list.split(" ")
else
nil
end
end
end
end
ruby-saml-1.13.0/lib/schemas/ 0000755 0000041 0000041 00000000000 14136046347 015740 5 ustar www-data www-data ruby-saml-1.13.0/lib/schemas/saml-schema-authn-context-2.0.xsd 0000644 0000041 0000041 00000001372 14136046347 023751 0 ustar www-data www-data
Document identifier: saml-schema-authn-context-2.0
Location: http://docs.oasis-open.org/security/saml/v2.0/
Revision history:
V2.0 (March, 2005):
New core authentication context schema for SAML V2.0.
This is just an include of all types from the schema
referred to in the include statement below.