Skip to content

Commit

Permalink
Merge pull request #671 from SAML-Toolkits/slo_encrypted_nameid
Browse files Browse the repository at this point in the history
Add support on LogoutRequest with Encrypted NameID
  • Loading branch information
pitbulk committed Sep 30, 2023
2 parents 5193314 + 8f9976e commit 6d12c10
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 8 deletions.
45 changes: 38 additions & 7 deletions lib/onelogin/ruby-saml/slo_logoutrequest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,26 +62,57 @@ def is_valid?(collect_errors = false)
# @return [String] Gets the NameID of the Logout Request.
#
def name_id
@name_id ||= begin
node = REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
Utils.element_text(node)
end
@name_id ||= Utils.element_text(name_id_node)
end

alias_method :nameid, :name_id

# @return [String] Gets the NameID Format of the Logout Request.
#
def name_id_format
@name_id_node ||= REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
@name_id_format ||=
if @name_id_node && @name_id_node.attribute("Format")
@name_id_node.attribute("Format").value
if name_id_node && name_id_node.attribute("Format")
name_id_node.attribute("Format").value
end
end

alias_method :nameid_format, :name_id_format

def name_id_node
@name_id_node ||=
begin
encrypted_node = REXML::XPath.first(document, "/p:LogoutRequest/a:EncryptedID", { "p" => PROTOCOL, "a" => ASSERTION })
if encrypted_node
node = decrypt_nameid(encrypted_node)
else
node = REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
end
end
end

# Decrypts an EncryptedID element
# @param encryptedid_node [REXML::Element] The EncryptedID element
# @return [REXML::Document] The decrypted EncrypedtID element
#
def decrypt_nameid(encrypt_node)

if settings.nil? || !settings.get_sp_key
raise ValidationError.new('An ' + encrypt_node.name + ' found and no SP private key found on the settings to decrypt it')
end

elem_plaintext = OneLogin::RubySaml::Utils.decrypt_data(encrypt_node, settings.get_sp_key)
# If we get some problematic noise in the plaintext after decrypting.
# This quick regexp parse will grab only the Element and discard the noise.
elem_plaintext = elem_plaintext.match(/(.*<\/(\w+:)?NameID>)/m)[0]

# To avoid namespace errors if saml namespace is not defined
# create a parent node first with the namespace defined
node_header = '<node xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">'
elem_plaintext = node_header + elem_plaintext + '</node>'
doc = REXML::Document.new(elem_plaintext)
doc.root[0]
end

# @return [String|nil] Gets the ID attribute from the Logout Request. if exists.
#
def id
Expand Down
2 changes: 1 addition & 1 deletion ruby-saml.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Gem::Specification.new do |s|
s.add_development_dependency('simplecov-lcov', '>0.7.0')
end

s.add_development_dependency('minitest', '~> 5.5')
s.add_development_dependency('minitest', '~> 5.5', '<5.19.0')
s.add_development_dependency('mocha', '~> 0.14')

if RUBY_VERSION < '2.0'
Expand Down
6 changes: 6 additions & 0 deletions test/logout_requests/slo_request_encrypted_nameid.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Version="2.0" ID="_c0348950-935b-0131-1060-782bcb56fcaa" IssueInstant="2014-03-21T19:20:13">
<saml:Issuer>https://app.onelogin.com/saml/metadata/SOMEACCOUNT</saml:Issuer>
<saml:EncryptedID><xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" Type="http://www.w3.org/2001/04/xmlenc#Element"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/><dsig:KeyInfo xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"><xenc:EncryptedKey><xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/><xenc:CipherData><xenc:CipherValue>L99BsKQL2iq5chjY+wRj6AH3jYxv9L4tscPJaDdsPWvPG47toC903oxEhjd1p9EMWkSPqD/HclvRhjcNVmhfUa3clTMM5PpZS1+oin2cDNFgKDkEaCXsGRgfn44uUKbEfUHNaljC72qh0lBLnoJe7ZkJHbFMbsA8Cd4UBtHzp4Y=</xenc:CipherValue></xenc:CipherData></xenc:EncryptedKey></dsig:KeyInfo>
<xenc:CipherData><xenc:CipherValue>+dLZt52QiV39ltBeRNUev0jlD9ReI7lM3EDgfktPgKeIs6bvsLz9feWhlnydd+NjbwXTsBQjEhm80/O8szYZZZpQB3H+khA76HJoFeDdhDgnVMqeXVWVkeSjcDFHg6TPLPyydSNcsBPBOqP093xCF7je0PUgkK45cj6aN/hs7TckxCbeuOv/klz6jxc24TyNoGg3Z1TA/HlS2ePVY77LhQgqhsZIL52LTG3BjAHVvpzSXyuYbeR5OeiYIM028Xyl</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData></saml:EncryptedID></samlp:LogoutRequest>
25 changes: 25 additions & 0 deletions test/slo_logoutrequest_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class RubySamlTest < Minitest::Test

let(:settings) { OneLogin::RubySaml::Settings.new }
let(:logout_request) { OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document) }
let(:logout_request_encrypted_nameid) { OneLogin::RubySaml::SloLogoutrequest.new(logout_request_encrypted_nameid_document) }
let(:invalid_logout_request) { OneLogin::RubySaml::SloLogoutrequest.new(invalid_logout_request_document) }

before do
Expand Down Expand Up @@ -87,6 +88,18 @@ class RubySamlTest < Minitest::Test
it "extract the value of the name id element" do
assert_equal "someone@example.org", logout_request.nameid
end

it 'is not possible when encryptID but no private key' do
assert_raises(OneLogin::RubySaml::ValidationError, "An EncryptedID found and no SP private key found on the settings to decrypt it") do
assert_equal "someone@example.org", logout_request_encrypted_nameid.nameid
end
end

it "extract the value of the name id element inside an EncryptedId" do
settings.private_key = ruby_saml_key_text
logout_request_encrypted_nameid.settings = settings
assert_equal "someone@example.org", logout_request_encrypted_nameid.nameid
end
end

describe "#nameid_format" do
Expand All @@ -95,6 +108,18 @@ class RubySamlTest < Minitest::Test
it "extract the format attribute of the name id element" do
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", logout_request.nameid_format
end

it 'is not possible when encryptID but no private key' do
assert_raises(OneLogin::RubySaml::ValidationError, "An EncryptedID found and no SP private key found on the settings to decrypt it") do
assert_equal "someone@example.org", logout_request_encrypted_nameid.nameid
end
end

it "extract the format attribute of the name id element" do
settings.private_key = ruby_saml_key_text
logout_request_encrypted_nameid.settings = settings
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", logout_request_encrypted_nameid.nameid_format
end
end

describe "#issuer" do
Expand Down
9 changes: 9 additions & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,15 @@ def logout_request_document_with_name_id_format
@logout_request_document_with_name_id_format
end

def logout_request_encrypted_nameid_document
unless @logout_request_encrypted_nameid_document
xml = read_logout_request("slo_request_encrypted_nameid.xml")
deflated = Zlib::Deflate.deflate(xml, 9)[2..-5]
@logout_request_encrypted_nameid_document = Base64.encode64(deflated)
end
@logout_request_encrypted_nameid_document
end

def logout_request_xml_with_session_index
@logout_request_xml_with_session_index ||= File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'slo_request_with_session_index.xml'))
end
Expand Down

0 comments on commit 6d12c10

Please sign in to comment.