Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ test/Test.iml
*.gem
.bundle
*.patch
/vendor
40 changes: 31 additions & 9 deletions lib/onelogin/ruby-saml/authrequest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
require "onelogin/ruby-saml/saml_message"
require "onelogin/ruby-saml/utils"
require "onelogin/ruby-saml/setting_error"
require "onelogin/ruby-saml/requested_attribute"

# Only supports SAML 2.0
module OneLogin
module RubySaml
include REXML
include REXML

# SAML2 Authentication. AuthNRequest (SSO SP initiated, Builder)
#
Expand Down Expand Up @@ -46,7 +47,7 @@ def create(settings, params = {})
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
# @return [Hash] Parameters
#
def create_params(settings, params={})
def create_params(settings, params = {})
# The method expects :RelayState but sometimes we get 'RelayState' instead.
# Based on the HashWithIndifferentAccess value in Rails we could experience
# conflicts so this line will solve them.
Expand All @@ -70,12 +71,12 @@ def create_params(settings, params={})
request_params = {"SAMLRequest" => base64_request}

if settings.security[:authn_requests_signed] && !settings.security[:embed_sign] && settings.private_key
params['SigAlg'] = settings.security[:signature_method]
params['SigAlg'] = settings.security[:signature_method]
url_string = OneLogin::RubySaml::Utils.build_query(
:type => 'SAMLRequest',
:data => base64_request,
:relay_state => relay_state,
:sig_alg => params['SigAlg']
:type => 'SAMLRequest',
:data => base64_request,
:relay_state => relay_state,
:sig_alg => params['SigAlg']
)
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
signature = settings.get_sp_key.sign(sign_algorithm.new, url_string)
Expand Down Expand Up @@ -104,7 +105,7 @@ def create_xml_document(settings)
request_doc = XMLSecurity::Document.new
request_doc.uuid = uuid

root = request_doc.add_element "samlp:AuthnRequest", { "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol", "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
root = request_doc.add_element "samlp:AuthnRequest", {"xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol", "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion", "xmlns:eidas" => "http://eidas.europa.eu/saml-extensions"}
root.attributes['ID'] = uuid
root.attributes['IssueInstant'] = time
root.attributes['Version'] = "2.0"
Expand Down Expand Up @@ -151,7 +152,7 @@ def create_xml_document(settings)
end

requested_context = root.add_element "samlp:RequestedAuthnContext", {
"Comparison" => comparison,
"Comparison" => comparison,
}

if settings.authn_context != nil
Expand All @@ -171,6 +172,27 @@ def create_xml_document(settings)
end
end

if settings.extensions[:sptype] != false || settings.extensions[:requested_attributes] != false
req_extensions = root.add_element "samlp:Extensions"

sptype_value = settings.extensions[:sptype] != nil ? settings.extensions[:sptype] : 'public'
eidas_sptype = req_extensions.add_element 'eidas:SPType'
eidas_sptype.text = sptype_value

if settings.extensions[:requested_attributes].respond_to? :each
req_attributes = req_extensions.add_element 'eidas:RequestedAttributes'
settings.extensions[:requested_attributes].each do |requested_attr|
next unless requested_attr.is_a? RequestedAttribute

el_attr = req_attributes.add_element 'eidas:RequestedAttribute', requested_attr.stringify_attribute_keys
next unless requested_attr.value

el_attr_val = el_attr.add_element 'eidas:AttributeValue'
el_attr_val.text = requested_attr.value.to_s
end
end
end

request_doc
end

Expand Down
42 changes: 42 additions & 0 deletions lib/onelogin/ruby-saml/requested_attribute.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module OneLogin
module RubySaml

# Class simplifying management of eidas:RequestedAttribute from eIDAS saml-extensions
# It's implementation is intentionally vague to allow custom attributes of RequestedAttribute element and accept any kind of value
class RequestedAttribute

attr_accessor :attributes
attr_accessor :value

DEFAULTS = {
:NameFormat => 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri'.freeze,
:isRequired => false,
:FriendlyName => false
}.freeze

# @param attrs [Hash] The +attrs+ must be Hash of known attributes, ie:
# RequestedAttribute.new({
# :Name => "http://eidas.europa.eu/attributes/naturalperson/DateOfBirth",
# :FriendlyName => "DoB",
# :isRequired => false,
# :NameFormat => "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
# })
# or if above mentioned defaults suit your needs, you can provide only RequestedAttribute Name
# RequestedAttribute.new({
# :Name = "http://www.stork.gov.eu/1.0/isAgeOver"
# }, 18)
# @param [String|Object] val value of eidas:AttributeValue or nil if you don't want the element to be provided
def initialize(attrs = {}, val = nil)
@attributes = DEFAULTS.merge(attrs)
@value = val
end

# @return [Hash]
def stringify_attribute_keys
Hash[attributes.collect { |k, v| [k.to_s, v] }]
end

end

end
end
62 changes: 35 additions & 27 deletions lib/onelogin/ruby-saml/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@ module RubySaml
# SAML2 Toolkit Settings
#
class Settings
def initialize(overrides = {}, keep_security_attributes = false)
def initialize(overrides = {}, keep_security_attributes = false, keep_extensions_attributes = true)
config = DEFAULTS.merge(overrides)
if keep_security_attributes
security_attributes = overrides.delete(:security) || {}
config = DEFAULTS.merge(overrides)
config[:security] = DEFAULTS[:security].merge(security_attributes)
else
config = DEFAULTS.merge(overrides)
end
if keep_extensions_attributes
extensions_attributes = overrides.delete(:extensions) || {}
config[:extensions] = DEFAULTS[:extensions].merge(extensions_attributes)
end

config.each do |k,v|
config.each do |k, v|
acc = "#{k.to_s}=".to_sym
if respond_to? acc
value = v.is_a?(Hash) ? v.dup : v
Expand Down Expand Up @@ -69,6 +71,8 @@ def initialize(overrides = {}, keep_security_attributes = false)
attr_accessor :assertion_consumer_logout_service_url
attr_accessor :assertion_consumer_logout_service_binding
attr_accessor :issuer
# EIDAS / samlp:Extensions
attr_accessor :extensions

# @return [String] SP Entity ID
#
Expand Down Expand Up @@ -164,7 +168,7 @@ def get_idp_cert_multi

raise ArgumentError.new("Invalid value for idp_cert_multi") if not idp_cert_multi.is_a?(Hash)

certs = {:signing => [], :encryption => [] }
certs = {:signing => [], :encryption => []}

if idp_cert_multi.key?(:signing) and not idp_cert_multi[:signing].empty?
idp_cert_multi[:signing].each do |idp_cert|
Expand Down Expand Up @@ -221,27 +225,31 @@ def get_sp_key
private

DEFAULTS = {
:assertion_consumer_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze,
:single_logout_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze,
:idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1,
:compress_request => true,
:compress_response => true,
:soft => true,
:double_quote_xml_attribute_values => false,
:security => {
:authn_requests_signed => false,
:logout_requests_signed => false,
:logout_responses_signed => false,
:want_assertions_signed => false,
:want_assertions_encrypted => false,
:want_name_id => false,
:metadata_signed => false,
:embed_sign => false,
:digest_method => XMLSecurity::Document::SHA1,
:signature_method => XMLSecurity::Document::RSA_SHA1,
:check_idp_cert_expiration => false,
:check_sp_cert_expiration => false
}.freeze
:assertion_consumer_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze,
:single_logout_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze,
:idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1,
:compress_request => true,
:compress_response => true,
:soft => true,
:double_quote_xml_attribute_values => false,
:extensions => {
:sptype => false,
:requested_attributes => false
}.freeze,
:security => {
:authn_requests_signed => false,
:logout_requests_signed => false,
:logout_responses_signed => false,
:want_assertions_signed => false,
:want_assertions_encrypted => false,
:want_name_id => false,
:metadata_signed => false,
:embed_sign => false,
:digest_method => XMLSecurity::Document::SHA1,
:signature_method => XMLSecurity::Document::RSA_SHA1,
:check_idp_cert_expiration => false,
:check_sp_cert_expiration => false
}.freeze
}.freeze
end
end
Expand Down
Loading