Skip to content

Commit

Permalink
Merge pull request #566 from onelogin/1.12.0-dev
Browse files Browse the repository at this point in the history
1.12.0 branch
  • Loading branch information
pitbulk authored Feb 18, 2021
2 parents b3f2191 + d49fde6 commit bbd954b
Show file tree
Hide file tree
Showing 36 changed files with 832 additions and 104 deletions.
11 changes: 5 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
language: ruby
rvm:
- 1.8.7
- 1.9.3
- 2.0.0
- 2.1.10
Expand All @@ -10,7 +9,7 @@ rvm:
- 2.5.8
- 2.6.6
- 2.7.2
- ree
- 3.0.0
- jruby-1.7.27
- jruby-9.1.17.0
- jruby-9.2.13.0
Expand All @@ -21,10 +20,6 @@ before_install:
- gem update bundler
matrix:
exclude:
- rvm: 1.8.7
gemfile: Gemfile
- rvm: ree
gemfile: Gemfile
- rvm: jruby-1.7.27
gemfile: gemfiles/nokogiri-1.5.gemfile
- rvm: jruby-9.1.17.0
Expand All @@ -33,6 +28,8 @@ matrix:
gemfile: gemfiles/nokogiri-1.5.gemfile
- rvm: 2.1.5
gemfile: gemfiles/nokogiri-1.5.gemfile
- rvm: 2.1.10
gemfile: gemfiles/nokogiri-1.5.gemfile
- rvm: 2.2.10
gemfile: gemfiles/nokogiri-1.5.gemfile
- rvm: 2.3.8
Expand All @@ -45,5 +42,7 @@ matrix:
gemfile: gemfiles/nokogiri-1.5.gemfile
- rvm: 2.7.2
gemfile: gemfiles/nokogiri-1.5.gemfile
- rvm: 3.0.0
gemfile: gemfiles/nokogiri-1.5.gemfile
env:
- JRUBY_OPTS="--debug"
60 changes: 48 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Ruby SAML [![Build Status](https://secure.travis-ci.org/onelogin/ruby-saml.svg)](http://travis-ci.org/onelogin/ruby-saml) [![Coverage Status](https://coveralls.io/repos/onelogin/ruby-saml/badge.svg?branch=master)](https://coveralls.io/r/onelogin/ruby-saml?branch=master) [![Gem Version](https://badge.fury.io/rb/ruby-saml.svg)](http://badge.fury.io/rb/ruby-saml)

## Updating from 1.11.x to 1.12.0
Version `1.12.0` adds support for gcm algorithm and
change/adds specific error messages for signature validations

## Updating from 1.10.x to 1.11.0
Version `1.11.0` deprecates the use of `settings.issuer` in favour of `settings.sp_entity_id`.
There are two new security settings: `settings.security[:check_idp_cert_expiration]` and `settings.security[:check_sp_cert_expiration]` (both false by default) that check if the IdP or SP X.509 certificate has expired, respectively.
Expand Down Expand Up @@ -261,8 +265,8 @@ def saml_settings
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_target_url = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}"
settings.idp_slo_target_url = "https://app.onelogin.com/trust/saml2/http-redirect/slo/#{OneLoginAppId}"
settings.idp_sso_service_url = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}"
settings.idp_slo_service_url = "https://app.onelogin.com/trust/saml2/http-redirect/slo/#{OneLoginAppId}"
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"
Expand Down Expand Up @@ -327,7 +331,7 @@ class SamlController < ApplicationController
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
settings.sp_entity_id = "http://#{request.host}/saml/metadata"
settings.idp_sso_target_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}"
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"
Expand Down Expand Up @@ -400,8 +404,8 @@ end
The following attributes are set:
* idp_entity_id
* name_identifier_format
* idp_sso_target_url
* idp_slo_target_url
* idp_sso_service_url
* idp_slo_service_url
* idp_attribute_names
* idp_cert
* idp_cert_fingerprint
Expand Down Expand Up @@ -467,6 +471,9 @@ Imagine this `saml:AttributeStatement`
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="1"/>
</saml:Attribute>
<saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname">
<saml:AttributeValue>usersName</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
```
Expand All @@ -477,7 +484,8 @@ pp(response.attributes) # is an OneLogin::RubySaml::Attributes object
"another_value"=>["value1", "value2"],
"role"=>["role1", "role2", "role3"],
"attribute_with_nil_value"=>[nil],
"attribute_with_nils_and_empty_strings"=>["", "valuePresent", nil, 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
Expand All @@ -494,6 +502,9 @@ pp(response.attributes.single(:role))
pp(response.attributes.multi(:role))
# => ["role1", "role2", "role3"]
pp(response.attributes.fetch(:role))
# => "role1"
pp(response.attributes[:attribute_with_nil_value])
# => nil
Expand All @@ -509,6 +520,9 @@ pp(response.attributes.single(:not_exists))
pp(response.attributes.multi(:not_exists))
# => nil
pp(response.attributes.fetch(/givenname/))
# => "usersName"
# Deactive single_value_compatibility
OneLogin::RubySaml::Attributes.single_value_compatibility = false
Expand All @@ -524,6 +538,9 @@ pp(response.attributes.single(:role))
pp(response.attributes.multi(:role))
# => ["role1", "role2", "role3"]
pp(response.attributes.fetch(:role))
# => ["role1", "role2", "role3"]
pp(response.attributes[:attribute_with_nil_value])
# => [nil]
Expand All @@ -538,6 +555,9 @@ pp(response.attributes.single(:not_exists))
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').
Expand Down Expand Up @@ -623,21 +643,27 @@ def sp_logout_request
# LogoutRequest accepts plain browser requests w/o paramters
settings = saml_settings
if settings.idp_slo_target_url.nil?
if settings.idp_slo_service_url.nil?
logger.info "SLO IdP Endpoint not found in settings, executing then a normal logout'"
delete_session
else
# Since we created a new SAML request, save the transaction_id
# to compare it with the response we get back
logout_request = OneLogin::RubySaml::Logoutrequest.new()
session[:transaction_id] = logout_request.uuid
logger.info "New SP SLO for userid '#{session[:userid]}' transactionid '#{session[:transaction_id]}'"
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
Expand Down Expand Up @@ -665,7 +691,7 @@ def process_logout_response
logger.error "The SAML Logout Response is invalid"
else
# Actually log out this session
logger.info "Delete session for '#{session[:userid]}'"
logger.info "SLO completed for '#{session[:logged_out_user]}'"
delete_session
end
end
Expand All @@ -674,6 +700,8 @@ end
def delete_session
session[:userid] = nil
session[:attributes] = nil
session[:transaction_id] = nil
session[:logged_out_user] = nil
end
```
Expand Down Expand Up @@ -741,6 +769,14 @@ class SamlController < ApplicationController
end
```
You can add ValidUntil and CacheDuration to the XML Metadata 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)
```
## Clock Drift
Expand Down
18 changes: 18 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# RubySaml Changelog

### 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
Expand Down
23 changes: 23 additions & 0 deletions lib/onelogin/ruby-saml/attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,29 @@ def ==(other)
end
end

# Fetch attribute value using name or regex
# @param name [String|Regexp] The attribute name
# @return [String|Array] Depending on the single value compatibility status this returns:
# - First value if single_value_compatibility = true
# response.attributes['mail'] # => '[email protected]'
# - All values if single_value_compatibility = false
# response.attributes['mail'] # => ['[email protected]','[email protected]']
#
def fetch(name)
attributes.each_key do |attribute_key|
if name.is_a?(Regexp)
if name.method_exists? :match?
return self[attribute_key] if name.match?(attribute_key)
else
return self[attribute_key] if name.match(attribute_key)
end
elsif canonize_name(name) == canonize_name(attribute_key)
return self[attribute_key]
end
end
nil
end

protected

# stringifies all names so both 'email' and :email return the same result
Expand Down
12 changes: 8 additions & 4 deletions lib/onelogin/ruby-saml/authrequest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,25 @@ def initialize
@uuid = OneLogin::RubySaml::Utils.uuid
end

def request_id
@uuid
end

# Creates the AuthNRequest string.
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
# @return [String] AuthNRequest string that includes the SAMLRequest
#
def create(settings, params = {})
params = create_params(settings, params)
params_prefix = (settings.idp_sso_target_url =~ /\?/) ? '&' : '?'
params_prefix = (settings.idp_sso_service_url =~ /\?/) ? '&' : '?'
saml_request = CGI.escape(params.delete("SAMLRequest"))
request_params = "#{params_prefix}SAMLRequest=#{saml_request}"
params.each_pair do |key, value|
request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
end
raise SettingError.new "Invalid settings, idp_sso_target_url is not set!" if settings.idp_sso_target_url.nil? or settings.idp_sso_target_url.empty?
@login_url = settings.idp_sso_target_url + request_params
raise SettingError.new "Invalid settings, idp_sso_service_url is not set!" if settings.idp_sso_service_url.nil? or settings.idp_sso_service_url.empty?
@login_url = settings.idp_sso_service_url + request_params
end

# Creates the Get parameters for the request.
Expand Down Expand Up @@ -108,7 +112,7 @@ def create_xml_document(settings)
root.attributes['ID'] = uuid
root.attributes['IssueInstant'] = time
root.attributes['Version'] = "2.0"
root.attributes['Destination'] = settings.idp_sso_target_url unless settings.idp_sso_target_url.nil? or settings.idp_sso_target_url.empty?
root.attributes['Destination'] = settings.idp_sso_service_url unless settings.idp_sso_service_url.nil? or settings.idp_sso_service_url.empty?
root.attributes['IsPassive'] = settings.passive unless settings.passive.nil?
root.attributes['ProtocolBinding'] = settings.protocol_binding unless settings.protocol_binding.nil?
root.attributes["AttributeConsumingServiceIndex"] = settings.attributes_index unless settings.attributes_index.nil?
Expand Down
40 changes: 37 additions & 3 deletions lib/onelogin/ruby-saml/idp_metadata_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ def parse_remote_to_array(url, validate_cert = true, options = {})
def parse(idp_metadata, options = {})
parsed_metadata = parse_to_hash(idp_metadata, options)

unless parsed_metadata[:cache_duration].nil?
cache_valid_until_timestamp = OneLogin::RubySaml::Utils.parse_duration(parsed_metadata[:cache_duration])
if parsed_metadata[:valid_until].nil? || cache_valid_until_timestamp < Time.parse(parsed_metadata[:valid_until], Time.now.utc).to_i
parsed_metadata[:valid_until] = Time.at(cache_valid_until_timestamp).utc.strftime("%Y-%m-%dT%H:%M:%SZ")
end
end
# Remove the cache_duration because on the settings
# we only gonna suppot valid_until
parsed_metadata.delete(:cache_duration)

settings = options[:settings]

if settings.nil?
Expand Down Expand Up @@ -210,13 +220,15 @@ def to_hash(options = {})
{
:idp_entity_id => @entity_id,
:name_identifier_format => idp_name_id_format,
:idp_sso_target_url => single_signon_service_url(options),
:idp_slo_target_url => single_logout_service_url(options),
:idp_sso_service_url => single_signon_service_url(options),
:idp_slo_service_url => single_logout_service_url(options),
:idp_slo_response_service_url => single_logout_response_service_url(options),
:idp_attribute_names => attribute_names,
:idp_cert => nil,
:idp_cert_fingerprint => nil,
:idp_cert_multi => nil,
:valid_until => valid_until
:valid_until => valid_until,
:cache_duration => cache_duration,
}.tap do |response_hash|
merge_certificates_into(response_hash) unless certificates.nil?
end
Expand All @@ -240,6 +252,13 @@ def valid_until
root.attributes['validUntil'] if root && root.attributes
end

# @return [String|nil] 'cacheDuration' attribute of metadata
#
def cache_duration
root = @idpsso_descriptor.root
root.attributes['cacheDuration'] if root && root.attributes
end

# @param binding_priority [Array]
# @return [String|nil] SingleSignOnService binding if exists
#
Expand Down Expand Up @@ -304,6 +323,21 @@ def single_logout_service_url(options = {})
return node.value if node
end

# @param options [Hash]
# @return [String|nil] SingleLogoutService response url if exists
#
def single_logout_response_service_url(options = {})
binding = single_logout_service_binding(options[:slo_binding])
return if binding.nil?

node = REXML::XPath.first(
@idpsso_descriptor,
"md:SingleLogoutService[@Binding=\"#{binding}\"]/@ResponseLocation",
SamlMetadata::NAMESPACE
)
return node.value if node
end

# @return [String|nil] Unformatted Certificate if exists
#
def certificates
Expand Down
4 changes: 4 additions & 0 deletions lib/onelogin/ruby-saml/logoutrequest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ def initialize
@uuid = OneLogin::RubySaml::Utils.uuid
end

def request_id
@uuid
end

# Creates the Logout Request string.
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
Expand Down
4 changes: 4 additions & 0 deletions lib/onelogin/ruby-saml/logoutresponse.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ def initialize(response, settings = nil, options = {})
@document = XMLSecurity::SignedDocument.new(@response)
end

def response_id
id(document)
end

# Checks if the Status has the "Success" code
# @return [Boolean] True if the StatusCode is Sucess
# @raise [ValidationError] if soft == false and validation fails
Expand Down
Loading

0 comments on commit bbd954b

Please sign in to comment.