Skip to content

Commit ae09439

Browse files
committed
Refactor to use SignedDocumentInfo. Remove REXML entirely in favor of Nokogiri.
1 parent 43b3f8c commit ae09439

19 files changed

+811
-886
lines changed

lib/ruby_saml/idp_metadata_parser.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def parse_to_array(idp_metadata, options = {})
161161
end
162162

163163
def parse_to_idp_metadata_array(idp_metadata, options = {})
164-
@document = Nokogiri::XML(idp_metadata)
164+
@document = Nokogiri::XML(idp_metadata) # TODO: RubySaml::XML.safe_load_nokogiri
165165
@options = options
166166

167167
idpsso_descriptors = self.class.get_idps(@document, options[:entity_id])
@@ -348,14 +348,14 @@ def certificates
348348
unless signing_nodes.empty?
349349
certs['signing'] = []
350350
signing_nodes.each do |cert_node|
351-
certs['signing'] << cert_node.content
351+
certs['signing'] << cert_node.text
352352
end
353353
end
354354

355355
unless encryption_nodes.empty?
356356
certs['encryption'] = []
357357
encryption_nodes.each do |cert_node|
358-
certs['encryption'] << cert_node.content
358+
certs['encryption'] << cert_node.text
359359
end
360360
end
361361
certs

lib/ruby_saml/logoutresponse.rb

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def initialize(response, settings = nil, options = {})
4141

4242
@options = options
4343
@response = RubySaml::XML::Decoder.decode_message(response, @settings&.message_max_bytesize)
44-
@document = RubySaml::XML::SignedDocument.new(@response)
44+
@document = RubySaml::XML.safe_load_nokogiri(@response)
4545
super()
4646
end
4747

@@ -60,47 +60,35 @@ def success?
6060
# @return [String|nil] Gets the InResponseTo attribute from the Logout Response if exists.
6161
#
6262
def in_response_to
63-
@in_response_to ||= begin
64-
node = REXML::XPath.first(
65-
document,
66-
"/p:LogoutResponse",
67-
{ "p" => RubySaml::XML::NS_PROTOCOL }
68-
)
69-
node.nil? ? nil : node.attributes['InResponseTo']
70-
end
63+
@in_response_to ||= document.at_xpath(
64+
"/p:LogoutResponse",
65+
{ "p" => RubySaml::XML::NS_PROTOCOL }
66+
)&.[]('InResponseTo')
7167
end
7268

7369
# @return [String] Gets the Issuer from the Logout Response.
7470
#
7571
def issuer
76-
@issuer ||= begin
77-
node = REXML::XPath.first(
78-
document,
79-
"/p:LogoutResponse/a:Issuer",
80-
{ "p" => RubySaml::XML::NS_PROTOCOL, "a" => RubySaml::XML::NS_ASSERTION }
81-
)
82-
Utils.element_text(node)
83-
end
72+
@issuer ||= document.at_xpath(
73+
"/p:LogoutResponse/a:Issuer",
74+
{ "p" => RubySaml::XML::NS_PROTOCOL, "a" => RubySaml::XML::NS_ASSERTION }
75+
)&.text
8476
end
8577

8678
# @return [String] Gets the StatusCode from a Logout Response.
8779
#
8880
def status_code
89-
@status_code ||= begin
90-
node = REXML::XPath.first(document, "/p:LogoutResponse/p:Status/p:StatusCode", { "p" => RubySaml::XML::NS_PROTOCOL })
91-
node.nil? ? nil : node.attributes["Value"]
92-
end
81+
@status_code ||= document.at_xpath(
82+
"/p:LogoutResponse/p:Status/p:StatusCode",
83+
{ "p" => RubySaml::XML::NS_PROTOCOL }
84+
)&.[]('Value')
9385
end
9486

9587
def status_message
96-
@status_message ||= begin
97-
node = REXML::XPath.first(
98-
document,
99-
"/p:LogoutResponse/p:Status/p:StatusMessage",
100-
{ "p" => RubySaml::XML::NS_PROTOCOL }
101-
)
102-
Utils.element_text(node)
103-
end
88+
@status_message ||= document.at_xpath(
89+
"/p:LogoutResponse/p:Status/p:StatusMessage",
90+
{ "p" => RubySaml::XML::NS_PROTOCOL }
91+
)&.text
10492
end
10593

10694
# Aux function to validate the Logout Response

lib/ruby_saml/memoizable.rb

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# frozen_string_literal: true
2+
3+
module RubySaml
4+
# Mixin for memoizing methods
5+
module Memoizable
6+
# Creates a memoized method
7+
#
8+
# @param method_name [Symbol] the name of the method to memoize
9+
# @param original_method [Symbol, nil] the original method to memoize (defaults to method_name)
10+
def self.included(base)
11+
base.extend(ClassMethods)
12+
end
13+
14+
private
15+
16+
# Memoizes the result of a block using the given name as the cache key
17+
#
18+
# @param cache_key [Symbol, String] the name to use as the cache key
19+
# @yield the block whose result will be cached
20+
# @return [Object] the cached result or the result of the block
21+
def memoize(cache_key)
22+
cache_key = "@#{cache_key.to_s.delete_prefix('@')}"
23+
return instance_variable_get(cache_key) if instance_variable_defined?(cache_key)
24+
25+
instance_variable_set(cache_key, yield)
26+
end
27+
28+
# Class methods for memoization
29+
module ClassMethods
30+
# Defines multiple memoized methods
31+
#
32+
# @param method_names [Array<Symbol>] the names of the methods to memoize
33+
# @raise [ArgumentError] if any method has an arity greater than 0
34+
def memoize_method(*method_names)
35+
method_names.each do |method_name|
36+
method_obj = instance_method(method_name)
37+
38+
# Check method arity
39+
if method_obj.arity > 0 # rubocop:disable Style/IfUnlessModifier
40+
raise ArgumentError.new("Cannot memoize method '#{method_name}' with arity > 0")
41+
end
42+
43+
# Store the original method
44+
original_method_name = "#{method_name}_without_memoization"
45+
alias_method original_method_name, method_name
46+
private original_method_name
47+
48+
# Define the memoized version
49+
define_method(method_name) do |&block|
50+
cache_key = "@memoized_#{method_name}"
51+
memoize(cache_key) do
52+
send(original_method_name, &block)
53+
end
54+
end
55+
56+
# Preserve method visibility
57+
if private_method_defined?(original_method_name)
58+
private method_name
59+
elsif protected_method_defined?(original_method_name)
60+
protected method_name
61+
end
62+
end
63+
end
64+
end
65+
end
66+
end

0 commit comments

Comments
 (0)