Skip to content

Commit bbd954b

Browse files
authored
Merge pull request #566 from onelogin/1.12.0-dev
1.12.0 branch
2 parents b3f2191 + d49fde6 commit bbd954b

36 files changed

+832
-104
lines changed

.travis.yml

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
language: ruby
22
rvm:
3-
- 1.8.7
43
- 1.9.3
54
- 2.0.0
65
- 2.1.10
@@ -10,7 +9,7 @@ rvm:
109
- 2.5.8
1110
- 2.6.6
1211
- 2.7.2
13-
- ree
12+
- 3.0.0
1413
- jruby-1.7.27
1514
- jruby-9.1.17.0
1615
- jruby-9.2.13.0
@@ -21,10 +20,6 @@ before_install:
2120
- gem update bundler
2221
matrix:
2322
exclude:
24-
- rvm: 1.8.7
25-
gemfile: Gemfile
26-
- rvm: ree
27-
gemfile: Gemfile
2823
- rvm: jruby-1.7.27
2924
gemfile: gemfiles/nokogiri-1.5.gemfile
3025
- rvm: jruby-9.1.17.0
@@ -33,6 +28,8 @@ matrix:
3328
gemfile: gemfiles/nokogiri-1.5.gemfile
3429
- rvm: 2.1.5
3530
gemfile: gemfiles/nokogiri-1.5.gemfile
31+
- rvm: 2.1.10
32+
gemfile: gemfiles/nokogiri-1.5.gemfile
3633
- rvm: 2.2.10
3734
gemfile: gemfiles/nokogiri-1.5.gemfile
3835
- rvm: 2.3.8
@@ -45,5 +42,7 @@ matrix:
4542
gemfile: gemfiles/nokogiri-1.5.gemfile
4643
- rvm: 2.7.2
4744
gemfile: gemfiles/nokogiri-1.5.gemfile
45+
- rvm: 3.0.0
46+
gemfile: gemfiles/nokogiri-1.5.gemfile
4847
env:
4948
- JRUBY_OPTS="--debug"

README.md

+48-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# 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)
22

3+
## Updating from 1.11.x to 1.12.0
4+
Version `1.12.0` adds support for gcm algorithm and
5+
change/adds specific error messages for signature validations
6+
37
## Updating from 1.10.x to 1.11.0
48
Version `1.11.0` deprecates the use of `settings.issuer` in favour of `settings.sp_entity_id`.
59
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.
@@ -261,8 +265,8 @@ def saml_settings
261265
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
262266
settings.sp_entity_id = "http://#{request.host}/saml/metadata"
263267
settings.idp_entity_id = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}"
264-
settings.idp_sso_target_url = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}"
265-
settings.idp_slo_target_url = "https://app.onelogin.com/trust/saml2/http-redirect/slo/#{OneLoginAppId}"
268+
settings.idp_sso_service_url = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}"
269+
settings.idp_slo_service_url = "https://app.onelogin.com/trust/saml2/http-redirect/slo/#{OneLoginAppId}"
266270
settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
267271
settings.idp_cert_fingerprint_algorithm = "http://www.w3.org/2000/09/xmldsig#sha1"
268272
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
@@ -327,7 +331,7 @@ class SamlController < ApplicationController
327331
328332
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
329333
settings.sp_entity_id = "http://#{request.host}/saml/metadata"
330-
settings.idp_sso_target_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}"
334+
settings.idp_sso_service_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}"
331335
settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
332336
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
333337
@@ -400,8 +404,8 @@ end
400404
The following attributes are set:
401405
* idp_entity_id
402406
* name_identifier_format
403-
* idp_sso_target_url
404-
* idp_slo_target_url
407+
* idp_sso_service_url
408+
* idp_slo_service_url
405409
* idp_attribute_names
406410
* idp_cert
407411
* idp_cert_fingerprint
@@ -467,6 +471,9 @@ Imagine this `saml:AttributeStatement`
467471
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
468472
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="1"/>
469473
</saml:Attribute>
474+
<saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname">
475+
<saml:AttributeValue>usersName</saml:AttributeValue>
476+
</saml:Attribute>
470477
</saml:AttributeStatement>
471478
```
472479
@@ -477,7 +484,8 @@ pp(response.attributes) # is an OneLogin::RubySaml::Attributes object
477484
"another_value"=>["value1", "value2"],
478485
"role"=>["role1", "role2", "role3"],
479486
"attribute_with_nil_value"=>[nil],
480-
"attribute_with_nils_and_empty_strings"=>["", "valuePresent", nil, nil]}>
487+
"attribute_with_nils_and_empty_strings"=>["", "valuePresent", nil, nil]
488+
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"=>["usersName"]}>
481489
482490
# Active single_value_compatibility
483491
OneLogin::RubySaml::Attributes.single_value_compatibility = true
@@ -494,6 +502,9 @@ pp(response.attributes.single(:role))
494502
pp(response.attributes.multi(:role))
495503
# => ["role1", "role2", "role3"]
496504
505+
pp(response.attributes.fetch(:role))
506+
# => "role1"
507+
497508
pp(response.attributes[:attribute_with_nil_value])
498509
# => nil
499510
@@ -509,6 +520,9 @@ pp(response.attributes.single(:not_exists))
509520
pp(response.attributes.multi(:not_exists))
510521
# => nil
511522
523+
pp(response.attributes.fetch(/givenname/))
524+
# => "usersName"
525+
512526
# Deactive single_value_compatibility
513527
OneLogin::RubySaml::Attributes.single_value_compatibility = false
514528
@@ -524,6 +538,9 @@ pp(response.attributes.single(:role))
524538
pp(response.attributes.multi(:role))
525539
# => ["role1", "role2", "role3"]
526540
541+
pp(response.attributes.fetch(:role))
542+
# => ["role1", "role2", "role3"]
543+
527544
pp(response.attributes[:attribute_with_nil_value])
528545
# => [nil]
529546
@@ -538,6 +555,9 @@ pp(response.attributes.single(:not_exists))
538555
539556
pp(response.attributes.multi(:not_exists))
540557
# => nil
558+
559+
pp(response.attributes.fetch(/givenname/))
560+
# => ["usersName"]
541561
```
542562
543563
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').
@@ -623,21 +643,27 @@ def sp_logout_request
623643
# LogoutRequest accepts plain browser requests w/o paramters
624644
settings = saml_settings
625645
626-
if settings.idp_slo_target_url.nil?
646+
if settings.idp_slo_service_url.nil?
627647
logger.info "SLO IdP Endpoint not found in settings, executing then a normal logout'"
628648
delete_session
629649
else
630650
631-
# Since we created a new SAML request, save the transaction_id
632-
# to compare it with the response we get back
633651
logout_request = OneLogin::RubySaml::Logoutrequest.new()
634-
session[:transaction_id] = logout_request.uuid
635-
logger.info "New SP SLO for userid '#{session[:userid]}' transactionid '#{session[:transaction_id]}'"
652+
logger.info "New SP SLO for userid '#{session[:userid]}' transactionid '#{logout_request.uuid}'"
636653
637654
if settings.name_identifier_value.nil?
638655
settings.name_identifier_value = session[:userid]
639656
end
640657
658+
# Ensure user is logged out before redirect to IdP, in case anything goes wrong during single logout process (as recommended by saml2int [SDP-SP34])
659+
logged_user = session[:userid]
660+
logger.info "Delete session for '#{session[:userid]}'"
661+
delete_session
662+
663+
# Save the transaction_id to compare it with the response we get back
664+
session[:transaction_id] = logout_request.uuid
665+
session[:logged_out_user] = logged_user
666+
641667
relayState = url_for controller: 'saml', action: 'index'
642668
redirect_to(logout_request.create(settings, :RelayState => relayState))
643669
end
@@ -665,7 +691,7 @@ def process_logout_response
665691
logger.error "The SAML Logout Response is invalid"
666692
else
667693
# Actually log out this session
668-
logger.info "Delete session for '#{session[:userid]}'"
694+
logger.info "SLO completed for '#{session[:logged_out_user]}'"
669695
delete_session
670696
end
671697
end
@@ -674,6 +700,8 @@ end
674700
def delete_session
675701
session[:userid] = nil
676702
session[:attributes] = nil
703+
session[:transaction_id] = nil
704+
session[:logged_out_user] = nil
677705
end
678706
```
679707
@@ -741,6 +769,14 @@ class SamlController < ApplicationController
741769
end
742770
```
743771
772+
You can add ValidUntil and CacheDuration to the XML Metadata using instead
773+
```ruby
774+
# Valid until => 2 days from now
775+
# Cache duration = 604800s = 1 week
776+
valid_until = Time.now + 172800
777+
cache_duration = 604800
778+
meta.generate(settings, false, valid_until, cache_duration)
779+
```
744780
745781
## Clock Drift
746782

changelog.md

+18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
# RubySaml Changelog
22

3+
### 1.12.0 (Feb 18, 2021)
4+
* Support AES-128-GCM, AES-192-GCM, and AES-256-GCM encryptions
5+
* Parse & return SLO ResponseLocation in IDPMetadataParser & Settings
6+
* Adding idp_sso_service_url and idp_slo_service_url settings
7+
* [#536](https://github.com/onelogin/ruby-saml/pull/536) Adding feth method to be able retrieve attributes based on regex
8+
* Reduce size of built gem by excluding the test folder
9+
* Improve protection on Zlib deflate decompression bomb attack.
10+
* Add ValidUntil and cacheDuration support on Metadata generator
11+
* Add support for cacheDuration at the IdpMetadataParser
12+
* Support customizable statusCode on generated LogoutResponse
13+
* [#545](https://github.com/onelogin/ruby-saml/pull/545) More specific error messages for signature validation
14+
* Support Process Transform
15+
* Raise SettingError if invoking an action with no endpoint defined on the settings
16+
* Made IdpMetadataParser more extensible for subclasses
17+
*[#548](https://github.com/onelogin/ruby-saml/pull/548) Add :skip_audience option
18+
* [#555](https://github.com/onelogin/ruby-saml/pull/555) Define 'soft' variable to prevent exception when doc cert is invalid
19+
* Improve documentation
20+
321
### 1.11.0 (Jul 24, 2019)
422

523
* Deprecate settings.issuer in favor of settings.sp_entity_id

lib/onelogin/ruby-saml/attributes.rb

+23
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,29 @@ def ==(other)
113113
end
114114
end
115115

116+
# Fetch attribute value using name or regex
117+
# @param name [String|Regexp] The attribute name
118+
# @return [String|Array] Depending on the single value compatibility status this returns:
119+
# - First value if single_value_compatibility = true
120+
# response.attributes['mail'] # => '[email protected]'
121+
# - All values if single_value_compatibility = false
122+
# response.attributes['mail'] # => ['[email protected]','[email protected]']
123+
#
124+
def fetch(name)
125+
attributes.each_key do |attribute_key|
126+
if name.is_a?(Regexp)
127+
if name.method_exists? :match?
128+
return self[attribute_key] if name.match?(attribute_key)
129+
else
130+
return self[attribute_key] if name.match(attribute_key)
131+
end
132+
elsif canonize_name(name) == canonize_name(attribute_key)
133+
return self[attribute_key]
134+
end
135+
end
136+
nil
137+
end
138+
116139
protected
117140

118141
# stringifies all names so both 'email' and :email return the same result

lib/onelogin/ruby-saml/authrequest.rb

+8-4
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,25 @@ def initialize
2424
@uuid = OneLogin::RubySaml::Utils.uuid
2525
end
2626

27+
def request_id
28+
@uuid
29+
end
30+
2731
# Creates the AuthNRequest string.
2832
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
2933
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
3034
# @return [String] AuthNRequest string that includes the SAMLRequest
3135
#
3236
def create(settings, params = {})
3337
params = create_params(settings, params)
34-
params_prefix = (settings.idp_sso_target_url =~ /\?/) ? '&' : '?'
38+
params_prefix = (settings.idp_sso_service_url =~ /\?/) ? '&' : '?'
3539
saml_request = CGI.escape(params.delete("SAMLRequest"))
3640
request_params = "#{params_prefix}SAMLRequest=#{saml_request}"
3741
params.each_pair do |key, value|
3842
request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
3943
end
40-
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?
41-
@login_url = settings.idp_sso_target_url + request_params
44+
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?
45+
@login_url = settings.idp_sso_service_url + request_params
4246
end
4347

4448
# Creates the Get parameters for the request.
@@ -108,7 +112,7 @@ def create_xml_document(settings)
108112
root.attributes['ID'] = uuid
109113
root.attributes['IssueInstant'] = time
110114
root.attributes['Version'] = "2.0"
111-
root.attributes['Destination'] = settings.idp_sso_target_url unless settings.idp_sso_target_url.nil? or settings.idp_sso_target_url.empty?
115+
root.attributes['Destination'] = settings.idp_sso_service_url unless settings.idp_sso_service_url.nil? or settings.idp_sso_service_url.empty?
112116
root.attributes['IsPassive'] = settings.passive unless settings.passive.nil?
113117
root.attributes['ProtocolBinding'] = settings.protocol_binding unless settings.protocol_binding.nil?
114118
root.attributes["AttributeConsumingServiceIndex"] = settings.attributes_index unless settings.attributes_index.nil?

lib/onelogin/ruby-saml/idp_metadata_parser.rb

+37-3
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,16 @@ def parse_remote_to_array(url, validate_cert = true, options = {})
113113
def parse(idp_metadata, options = {})
114114
parsed_metadata = parse_to_hash(idp_metadata, options)
115115

116+
unless parsed_metadata[:cache_duration].nil?
117+
cache_valid_until_timestamp = OneLogin::RubySaml::Utils.parse_duration(parsed_metadata[:cache_duration])
118+
if parsed_metadata[:valid_until].nil? || cache_valid_until_timestamp < Time.parse(parsed_metadata[:valid_until], Time.now.utc).to_i
119+
parsed_metadata[:valid_until] = Time.at(cache_valid_until_timestamp).utc.strftime("%Y-%m-%dT%H:%M:%SZ")
120+
end
121+
end
122+
# Remove the cache_duration because on the settings
123+
# we only gonna suppot valid_until
124+
parsed_metadata.delete(:cache_duration)
125+
116126
settings = options[:settings]
117127

118128
if settings.nil?
@@ -210,13 +220,15 @@ def to_hash(options = {})
210220
{
211221
:idp_entity_id => @entity_id,
212222
:name_identifier_format => idp_name_id_format,
213-
:idp_sso_target_url => single_signon_service_url(options),
214-
:idp_slo_target_url => single_logout_service_url(options),
223+
:idp_sso_service_url => single_signon_service_url(options),
224+
:idp_slo_service_url => single_logout_service_url(options),
225+
:idp_slo_response_service_url => single_logout_response_service_url(options),
215226
:idp_attribute_names => attribute_names,
216227
:idp_cert => nil,
217228
:idp_cert_fingerprint => nil,
218229
:idp_cert_multi => nil,
219-
:valid_until => valid_until
230+
:valid_until => valid_until,
231+
:cache_duration => cache_duration,
220232
}.tap do |response_hash|
221233
merge_certificates_into(response_hash) unless certificates.nil?
222234
end
@@ -240,6 +252,13 @@ def valid_until
240252
root.attributes['validUntil'] if root && root.attributes
241253
end
242254

255+
# @return [String|nil] 'cacheDuration' attribute of metadata
256+
#
257+
def cache_duration
258+
root = @idpsso_descriptor.root
259+
root.attributes['cacheDuration'] if root && root.attributes
260+
end
261+
243262
# @param binding_priority [Array]
244263
# @return [String|nil] SingleSignOnService binding if exists
245264
#
@@ -304,6 +323,21 @@ def single_logout_service_url(options = {})
304323
return node.value if node
305324
end
306325

326+
# @param options [Hash]
327+
# @return [String|nil] SingleLogoutService response url if exists
328+
#
329+
def single_logout_response_service_url(options = {})
330+
binding = single_logout_service_binding(options[:slo_binding])
331+
return if binding.nil?
332+
333+
node = REXML::XPath.first(
334+
@idpsso_descriptor,
335+
"md:SingleLogoutService[@Binding=\"#{binding}\"]/@ResponseLocation",
336+
SamlMetadata::NAMESPACE
337+
)
338+
return node.value if node
339+
end
340+
307341
# @return [String|nil] Unformatted Certificate if exists
308342
#
309343
def certificates

lib/onelogin/ruby-saml/logoutrequest.rb

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ def initialize
2121
@uuid = OneLogin::RubySaml::Utils.uuid
2222
end
2323

24+
def request_id
25+
@uuid
26+
end
27+
2428
# Creates the Logout Request string.
2529
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
2630
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState

lib/onelogin/ruby-saml/logoutresponse.rb

+4
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ def initialize(response, settings = nil, options = {})
4747
@document = XMLSecurity::SignedDocument.new(@response)
4848
end
4949

50+
def response_id
51+
id(document)
52+
end
53+
5054
# Checks if the Status has the "Success" code
5155
# @return [Boolean] True if the StatusCode is Sucess
5256
# @raise [ValidationError] if soft == false and validation fails

0 commit comments

Comments
 (0)