New upstream version 1.7.0

parent 72f5c6dd
......@@ -3,3 +3,4 @@ coverage/
spec/support/example_private_key.pem
/gemfiles/*.lock
.idea/
.bundle/
env:
global:
- JRUBY_OPTS="$JRUBY_OPTS --debug"
sudo: false
language: ruby
script: bundle exec rspec
before_install: gem update bundler
cache: bundler
before_install:
- gem update bundler
before_script:
- bundle update
script:
- bundle exec rspec
rvm:
- 1.9.3
- jruby-19mode
- 2.0.0
- 2.1.10
- 2.2.2
- 2.3.1
- jruby-9.0.5.0
- ruby-head
gemfile:
- gemfiles/rack_1.gemfile
- gemfiles/rack_2.gemfile
matrix:
include:
- rvm: 1.9.3
gemfile: Gemfile
- rvm: 2.0.0
gemfile: Gemfile
- rvm: 2.1
gemfile: Gemfile
- rvm: 2.2
gemfile: Gemfile
- rvm: 2.3.0
gemfile: Gemfile
- rvm: jruby-19mode
gemfile: Gemfile
- rvm: jruby-head
gemfile: Gemfile
- rvm: rbx-2
gemfile: Gemfile
- rvm: ruby-head
gemfile: Gemfile
allow_failures:
- rvm: jruby-head
- rvm: ruby-head
fast_finish: true
sudo: false
exclude:
- rvm: 1.9.3
gemfile: gemfiles/rack_2.gemfile
- rvm: jruby-19mode
gemfile: gemfiles/rack_2.gemfile
- rvm: 2.0.0
gemfile: gemfiles/rack_2.gemfile
- rvm: 2.1.10
gemfile: gemfiles/rack_2.gemfile
appraise 'rack-1' do
gem 'rack', '~> 1.x'
gem 'term-ansicolor', '1.3.2'
end
appraise 'rack-2' do
gem 'rack', '~> 2.x'
end
# OmniAuth SAML Version History
<a name="v1.7.0"></a>
### v1.7.0 (2016-09-18)
A generic SAML strategy for OmniAuth.
https://github.com/omniauth/omniauth-saml
#### Features
## 1.6.0 (2016-06-27)
* Support for Single Logout ([cd3fc43](/../../commit/cd3fc43))
* Add issuer information to the metadata endpoint, to allow IdPs to properly configure themselves. ([7bbbb67](/../../commit/7bbbb67))
* Added the response object to the extra['response_object'], so we can use the raw response object if we want to. ([76ed3d6](/../../commit/76ed3d6))
#### Chores
* Update `ruby-saml` to 1.4.0 to address security fixes. ([638212](/../../commit/638212))
<a name="v1.6.0"></a>
### v1.6.0 (2016-06-27)
* Ensure that subclasses of `OmniAuth::Stategies::SAML` are registered with OmniAuth as strategies (https://github.com/omniauth/omniauth-saml/pull/95)
* Update ruby-saml to 1.3 to address [CVE-2016-5697](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-5697) (Signature wrapping attacks)
## 1.5.0 (2016-02-25)
<a name="v1.5.0"></a>
### v1.5.0 (2016-02-25)
* Initialize OneLogin::RubySaml::Response instance with settings
* Adding "settings" to Response Class at initialization to handle signing verification
......@@ -18,56 +29,67 @@ https://github.com/omniauth/omniauth-saml
* Call validation earlier to get real error instead of 'response missing name_id'
* Avoid mutation of the options hash during requests and callbacks
## 1.4.2 (2016-02-09)
<a name="v1.4.2"></a>
### v1.4.2 (2016-02-09)
* update ruby-saml to 1.1
## 1.4.1 (2015-08-09)
<a name="v1.4.1"></a>
### v1.4.1 (2015-08-09)
* Configurable attribute_consuming_service
## 1.4.0 (2015-07-23)
<a name="v1.4.0"></a>
### v1.4.0 (2015-07-23)
* update ruby-saml to 1.0.0
## 1.3.1 (2015-02-26)
<a name="v1.3.1"></a>
### v1.3.1 (2015-02-26)
* Added missing fingerprint key check
* Expose fingerprint on the auth_hash
## 1.3.0 (2015-01-23)
<a name="v1.3.0"></a>
### v1.3.0 (2015-01-23)
* add `idp_cert_fingerprint_validator` option
## 1.2.0 (2014-03-19)
<a name="v1.2.0"></a>
### v1.2.0 (2014-03-19)
* provide SP metadata at `/auth/saml/metadata`
## 1.1.0 (2013-11-07)
<a name="v1.1.0"></a>
### v1.1.0 (2013-11-07)
* no longer set a default `name_identifier_format`
* pass strategy options to the underlying ruby-saml library
* fallback to omniauth callback url if `assertion_consumer_service_url` is not set
* add `idp_sso_target_url_runtime_params` option
## 1.0.0 (2012-11-12)
<a name="v1.0.0"></a>
### v1.0.0 (2012-11-12)
* remove SAML code and port to ruby-saml gem
* fix incompatibility with OmniAuth 1.1
## 0.9.2 (2012-03-30)
<a name="v0.9.2"></a>
### v0.9.2 (2012-03-30)
* validate the SAML response
* 100% test coverage
* now requires ruby 1.9.2+
## 0.9.1 (2012-02-23)
<a name="v0.9.1"></a>
### v0.9.1 (2012-02-23)
* return first and last name in the info hash
* no longer use LDAP OIDs for name and email selection
* return SAML attributes as the omniauth raw_info hash
## 0.9.0 (2012-02-14)
<a name="v0.9.0"></a>
### v0.9.0 (2012-02-14)
* initial release
* extracts commits from omniauth 0-3-stable branch
......
......@@ -66,4 +66,12 @@ feat: create initial CONTRIBUTING.md
This closes #73
```
> **NOTE:** [CHANGELOG.md](CHANGELOG.md) is generated based on the commits.
## Release process
Example for version `v1.7.0`
1. Bump the version in `lib/omniauth-saml/version.rb`
1. Update [CHANGELOG.md](CHANGELOG.md) with `bundle exec conventional-changelog version=v1.7.0 since_version=v1.6.0`
1. Commit all your changes
1. Tag the latest commit with `git tag v1.7.0`
1. Contact the maintainers
......@@ -2,7 +2,14 @@ source 'https://rubygems.org'
group :test do
gem 'coveralls', '~> 0.8', '>= 0.8.13', require: false
# Lock coveralls dependencies to lower versions to work with older rubies
gem 'json', '~> 1.8'
gem 'tins', '~> 1.6.0'
gem 'mime-types', '< 3'
end
gem 'appraisal'
gemspec
......@@ -68,6 +68,8 @@ end
For IdP-initiated SSO, users should directly access the IdP SSO target URL. Set the `href` of your application's login link to the value of `idp_sso_target_url`. For SP-initiated SSO, link to `/auth/saml`.
A `OneLogin::RubySaml::Response` object is added to the `env['omniauth.auth']` extra attribute, so we can use it in the controller via `env['omniauth.auth'].extra.response_object`
## Metadata
The service provider metadata used to ease configuration of the SAML SP in the IdP can be retrieved from `http://example.com/auth/saml/metadata`. Send this URL to the administrator of the IdP.
......@@ -84,6 +86,14 @@ The service provider metadata used to ease configuration of the SAML SP in the I
* `:idp_sso_target_url` - The URL to which the authentication request should be sent.
This would be on the identity provider. **Required**.
* `:idp_slo_target_url` - The URL to which the single logout request and response should
be sent. This would be on the identity provider. Optional.
* `:slo_default_relay_state` - The value to use as default `RelayState` for single log outs. The
value can be a string, or a `Proc` (or other object responding to `call`). The `request`
instance will be passed to this callable if it has an arity of 1. If the value is a string,
the string will be returned, when the `RelayState` is called. Optional.
* `:idp_sso_target_url_runtime_params` - A dynamic mapping of request params that exist
during the request phase of OmniAuth that should to be sent to the IdP after a specific
mapping. So for example, a param `original_request_param` with value `original_param_value`,
......@@ -143,6 +153,35 @@ end
Then follow Devise's general [OmniAuth tutorial](https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview), replacing references to `facebook` with `saml`.
## Single Logout
Single Logout can be Service Provider initiated or Identity Provider initiated.
When using Devise as an authentication solution, the SP initiated flow can be integrated
in the `SessionsController#destroy` action.
For this to work it is important to preserve the `saml_uid` value before Devise
clears the session and redirect to the `/spslo` sub-path to initiate the single logout.
Example `destroy` action in `sessions_controller.rb`:
```ruby
class SessionsController < Devise::SessionsController
# ...
def destroy
# Preserve the saml_uid in the session
saml_uid = session["saml_uid"]
super do
session["saml_uid"] = saml_uid
if SAML_SETTINGS.idp_slo_target_url
spslo_url = user_omniauth_authorize_url(:saml) + "/spslo"
redirect_to(spslo_url)
end
end
end
end
```
## Authors
Authored by [Rajiv Aaron Manglani](http://www.rajivmanglani.com/), Raecoo Cao, Todd W Saxton, Ryan Wilcox, Steven Anderson, Nikos Dimitrakopoulos, Rudolf Vriend and [Bruno Pedro](http://brunopedro.com/).
# This file was generated by Appraisal
source "https://rubygems.org"
gem "appraisal"
gem "rack", "~> 1.x"
gem "term-ansicolor", "1.3.2"
group :test do
gem "coveralls", "~> 0.8", ">= 0.8.13", :require => false
gem "json", "~> 1.8"
gem "tins", "~> 1.6.0"
gem "mime-types", "< 3"
end
gemspec :path => "../"
# This file was generated by Appraisal
source "https://rubygems.org"
gem "appraisal"
gem "rack", "~> 2.x"
group :test do
gem "coveralls", "~> 0.8", ">= 0.8.13", :require => false
gem "json", "~> 1.8"
gem "tins", "~> 1.6.0"
gem "mime-types", "< 3"
end
gemspec :path => "../"
module OmniAuth
module SAML
VERSION = '1.6.0'
VERSION = '1.7.0'
end
end
......@@ -27,15 +27,19 @@ module OmniAuth
first_name: ["first_name", "firstname", "firstName"],
last_name: ["last_name", "lastname", "lastName"]
}
option :slo_default_relay_state
def request_phase
options[:assertion_consumer_service_url] ||= callback_url
runtime_request_parameters = options.delete(:idp_sso_target_url_runtime_params)
additional_params = {}
runtime_request_parameters.each_pair do |request_param_key, mapped_param_key|
additional_params[mapped_param_key] = request.params[request_param_key.to_s] if request.params.has_key?(request_param_key.to_s)
end if runtime_request_parameters
if runtime_request_parameters
runtime_request_parameters.each_pair do |request_param_key, mapped_param_key|
additional_params[mapped_param_key] = request.params[request_param_key.to_s] if request.params.has_key?(request_param_key.to_s)
end
end
authn_request = OneLogin::RubySaml::Authrequest.new
settings = OneLogin::RubySaml::Settings.new(options)
......@@ -44,9 +48,7 @@ module OmniAuth
end
def callback_phase
unless request.params['SAMLResponse']
raise OmniAuth::Strategies::SAML::ValidationError.new("SAML response missing")
end
raise OmniAuth::Strategies::SAML::ValidationError.new("SAML response missing") unless request.params["SAMLResponse"]
# Call a fingerprint validation method if there's one
if options.idp_cert_fingerprint_validator
......@@ -59,29 +61,21 @@ module OmniAuth
end
settings = OneLogin::RubySaml::Settings.new(options)
# filter options to select only extra parameters
opts = options.select {|k,_| OTHER_REQUEST_OPTIONS.include?(k.to_sym)}
# symbolize keys without activeSupport/symbolize_keys (ruby-saml use symbols)
opts =
opts.inject({}) do |new_hash, (key, value)|
new_hash[key.to_sym] = value
new_hash
end
response = OneLogin::RubySaml::Response.new(request.params['SAMLResponse'], opts.merge(settings: settings))
response.attributes['fingerprint'] = options.idp_cert_fingerprint
# will raise an error since we are not in soft mode
response.soft = false
response.is_valid?
@name_id = response.name_id
@attributes = response.attributes
if @name_id.nil? || @name_id.empty?
raise OmniAuth::Strategies::SAML::ValidationError.new("SAML response missing 'name_id'")
handle_response(request.params["SAMLResponse"], opts, settings) do
super
end
super
rescue OmniAuth::Strategies::SAML::ValidationError
fail!(:invalid_ticket, $!)
rescue OneLogin::RubySaml::ValidationError
......@@ -90,7 +84,7 @@ module OmniAuth
# Obtain an idp certificate fingerprint from the response.
def response_fingerprint
response = request.params['SAMLResponse']
response = request.params["SAMLResponse"]
response = (response =~ /^</) ? response : Base64.decode64(response)
document = XMLSecurity::SignedDocument::new(response)
cert_element = REXML::XPath.first(document, "//ds:X509Certificate", { "ds"=> 'http://www.w3.org/2000/09/xmldsig#' })
......@@ -100,25 +94,43 @@ module OmniAuth
Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(':')
end
def on_metadata_path?
on_path?("#{request_path}/metadata")
end
def other_phase
if on_metadata_path?
# omniauth does not set the strategy on the other_phase
if current_path.start_with?(request_path)
@env['omniauth.strategy'] ||= self
setup_phase
response = OneLogin::RubySaml::Metadata.new
settings = OneLogin::RubySaml::Settings.new(options)
if options.request_attributes.length > 0
settings.attribute_consuming_service.service_name options.attribute_service_name
options.request_attributes.each do |attribute|
settings.attribute_consuming_service.add_attribute attribute
if on_subpath?(:metadata)
# omniauth does not set the strategy on the other_phase
response = OneLogin::RubySaml::Metadata.new
if options.request_attributes.length > 0
settings.attribute_consuming_service.service_name options.attribute_service_name
settings.issuer = options.issuer
options.request_attributes.each do |attribute|
settings.attribute_consuming_service.add_attribute attribute
end
end
Rack::Response.new(response.generate(settings), 200, { "Content-Type" => "application/xml" }).finish
elsif on_subpath?(:slo)
if request.params["SAMLResponse"]
handle_logout_response(request.params["SAMLResponse"], settings)
elsif request.params["SAMLRequest"]
handle_logout_request(request.params["SAMLRequest"], settings)
else
raise OmniAuth::Strategies::SAML::ValidationError.new("SAML logout response/request missing")
end
elsif on_subpath?(:spslo)
if options.idp_slo_target_url
redirect(generate_logout_request(settings))
else
Rack::Response.new("Not Implemented", 501, { "Content-Type" => "text/html" }).finish
end
else
call_app!
end
Rack::Response.new(response.generate(settings), 200, { "Content-Type" => "application/xml" }).finish
else
call_app!
end
......@@ -135,7 +147,7 @@ module OmniAuth
Hash[found_attributes]
end
extra { { :raw_info => @attributes } }
extra { { :raw_info => @attributes, :response_object => @response_object } }
def find_attribute_by(keys)
keys.each do |key|
......@@ -144,6 +156,94 @@ module OmniAuth
nil
end
private
def on_subpath?(subpath)
on_path?("#{request_path}/#{subpath}")
end
def handle_response(raw_response, opts, settings)
response = OneLogin::RubySaml::Response.new(raw_response, opts.merge(settings: settings))
response.attributes["fingerprint"] = options.idp_cert_fingerprint
response.soft = false
response.is_valid?
@name_id = response.name_id
@attributes = response.attributes
@response_object = response
if @name_id.nil? || @name_id.empty?
raise OmniAuth::Strategies::SAML::ValidationError.new("SAML response missing 'name_id'")
end
session["saml_uid"] = @name_id
yield
end
def slo_relay_state
if request.params.has_key?("RelayState") && request.params["RelayState"] != ""
request.params["RelayState"]
else
slo_default_relay_state = options.slo_default_relay_state
if slo_default_relay_state.respond_to?(:call)
if slo_default_relay_state.arity == 1
slo_default_relay_state.call(request)
else
slo_default_relay_state.call
end
else
slo_default_relay_state
end
end
end
def handle_logout_response(raw_response, settings)
# After sending an SP initiated LogoutRequest to the IdP, we need to accept
# the LogoutResponse, verify it, then actually delete our session.
logout_response = OneLogin::RubySaml::Logoutresponse.new(raw_response, settings, :matches_request_id => session["saml_transaction_id"])
logout_response.soft = false
logout_response.validate
session.delete("saml_uid")
session.delete("saml_transaction_id")
redirect(slo_relay_state)
end
def handle_logout_request(raw_request, settings)
logout_request = OneLogin::RubySaml::SloLogoutrequest.new(raw_request)
if logout_request.is_valid? &&
logout_request.name_id == session["saml_uid"]
# Actually log out this session
session.clear
# 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: slo_relay_state)
redirect(logout_response)
else
raise OmniAuth::Strategies::SAML::ValidationError.new("SAML failed to process LogoutRequest")
end
end
# Create a SP initiated SLO: https://github.com/onelogin/ruby-saml#single-log-out
def generate_logout_request(settings)
logout_request = OneLogin::RubySaml::Logoutrequest.new()
# Since we created a new SAML request, save the transaction_id
# to compare it with the response we get back
session["saml_transaction_id"] = logout_request.uuid
if settings.name_identifier_value.nil?
settings.name_identifier_value = session["saml_uid"]
end
logout_request.create(settings, RelayState: slo_relay_state)
end
end
end
end
......
......@@ -12,11 +12,13 @@ Gem::Specification.new do |gem|
gem.homepage = 'https://github.com/omniauth/omniauth-saml'
gem.add_runtime_dependency 'omniauth', '~> 1.3'
gem.add_runtime_dependency 'ruby-saml', '~> 1.3'
gem.add_runtime_dependency 'ruby-saml', '~> 1.4'
gem.add_development_dependency 'rake', '>= 10', '< 12'
gem.add_development_dependency 'rspec', '~>3.4'
gem.add_development_dependency 'simplecov', '~> 0.11'
gem.add_development_dependency 'rack-test', '~> 0.6', '>= 0.6.3'
gem.add_development_dependency 'conventional-changelog', '~> 1.2'
gem.files = ['README.md', 'CHANGELOG.md', 'LICENSE.md'] + Dir['lib/**/*.rb']
gem.test_files = Dir['spec/**/*.rb']
......
This diff is collapsed.
......@@ -15,6 +15,10 @@ require 'rexml/document'
require 'rexml/xpath'
require 'base64'
TEST_LOGGER = Logger.new(StringIO.new)
OneLogin::RubySaml::Logging.logger = TEST_LOGGER
OmniAuth.config.logger = TEST_LOGGER
RSpec.configure do |config|
config.include Rack::Test::Methods
end
......
<?xml version="1.0"?>
<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="_b6a69da0-04a2-0134-ea8a-0a2068490f7d" IssueInstant="2016-05-25T12:32:48" Version="2.0">
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://idp.sso.example.com/metadata/29490</saml:Issuer>
<saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">username@example.com</saml:NameID>
</samlp:LogoutRequest>
<?xml version="1.0" encoding="UTF-8"?>
<samlp:LogoutResponse xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" InResponseTo="_3fef1069-d0c6-418a-b68d-6f008a4787e9" Version="2.0" ID="_1c7fb9a0-04a0-0134-cd6f-068d68dd383d" IssueInstant="2016-05-25T12:14:10Z">
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://idp.sso.example.com/metadata/29490</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
<samlp:StatusMessage>Successfully logged out from service</samlp:StatusMessage>
</samlp:Status>
</samlp:LogoutResponse>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment