diff --git a/CHANGELOG b/CHANGELOG index d93ef0f7..53788c4e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ = master +* Support :filter plugin option in error_mail and error_email for filtering parameters, environment variables, and session values (jeremyevans) (#346) + * Set temporary name on Ruby 3.3 in middleware plugin for middleware class created (janko) (#344) * Add break plugin, for using break inside a routing block to return from the block and keep routing (jeremyevans) diff --git a/lib/roda/plugins/error_email.rb b/lib/roda/plugins/error_email.rb index bba16738..32a04d97 100644 --- a/lib/roda/plugins/error_email.rb +++ b/lib/roda/plugins/error_email.rb @@ -21,6 +21,9 @@ module RodaPlugins # # Options: # + # :filter :: Callable called with the key and value for each parameter, environment + # variable, and session value. If it returns true, the value of the + # parameter is filtered in the email. # :from :: The From address to use in the email (required) # :headers :: A hash of additional headers to use in the email (default: empty hash) # :host :: The SMTP server to use to send the email (default: localhost) @@ -38,6 +41,7 @@ module RodaPlugins # use an error reporting service instead of this plugin. module ErrorEmail DEFAULTS = { + :filter=>lambda{|k,v| false}, :headers=>OPTS, :host=>'localhost', # :nocov: @@ -52,7 +56,12 @@ module ErrorEmail {'From'=>h[:from], 'To'=>h[:to], 'Subject'=>"#{h[:prefix]}#{subject}"} end, :body=>lambda do |s, e| - format = lambda{|h| h.map{|k, v| "#{k.inspect} => #{v.inspect}"}.sort.join("\n")} + filter = s.opts[:error_email][:filter] + format = lambda do |h| + h = h.map{|k, v| "#{k.inspect} => #{filter.call(k, v) ? 'FILTERED' : v.inspect}"} + h.sort! + h.join("\n") + end begin params = s.request.params diff --git a/lib/roda/plugins/error_mail.rb b/lib/roda/plugins/error_mail.rb index a532db6e..163526ef 100644 --- a/lib/roda/plugins/error_mail.rb +++ b/lib/roda/plugins/error_mail.rb @@ -21,6 +21,9 @@ module RodaPlugins # # Options: # + # :filter :: Callable called with the key and value for each parameter, environment + # variable, and session value. If it returns true, the value of the + # parameter is filtered in the email. # :from :: The From address to use in the email (required) # :headers :: A hash of additional headers to use in the email (default: empty hash) # :prefix :: A prefix to use in the email's subject line (default: no prefix) @@ -36,9 +39,12 @@ module RodaPlugins # for low traffic web applications. For high traffic web applications, # use an error reporting service instead of this plugin. module ErrorMail + DEFAULT_FILTER = lambda{|k,v| false} + private_constant :DEFAULT_FILTER + # Set default opts for plugin. See ErrorEmail module RDoc for options. def self.configure(app, opts=OPTS) - app.opts[:error_mail] = email_opts = (app.opts[:error_mail] || OPTS).merge(opts).freeze + app.opts[:error_mail] = email_opts = (app.opts[:error_mail] || {:filter=>DEFAULT_FILTER}).merge(opts).freeze unless email_opts[:to] && email_opts[:from] raise RodaError, "must provide :to and :from options to error_mail plugin" end @@ -68,8 +74,13 @@ def _error_mail(e) e.to_s end subject = "#{email_opts[:prefix]}#{subject}" + filter = email_opts[:filter] - format = lambda{|h| h.map{|k, v| "#{k.inspect} => #{v.inspect}"}.sort.join("\n")} + format = lambda do |h| + h = h.map{|k, v| "#{k.inspect} => #{filter.call(k, v) ? 'FILTERED' : v.inspect}"} + h.sort! + h.join("\n") + end begin params = request.params diff --git a/spec/plugin/error_email_spec.rb b/spec/plugin/error_email_spec.rb index 9dfbd7b8..f7f28e94 100644 --- a/spec/plugin/error_email_spec.rb +++ b/spec/plugin/error_email_spec.rb @@ -48,6 +48,16 @@ def email b.must_match(/^Backtrace:$.+^ENV:$.+^"rack\.input" => .+^Params:$\s+^"b" => "c"$\s+^Session:$\s+^"d" => "e"$/m) end + it "supports :filter plugin option for filtering parameters, environment variables, and session values" do + app.route do |r| + raise ArgumentError, 'bad foo' rescue error_email_content($!) + end + app.plugin :error_email, :filter=>proc{|k, v| k == 'b' || k == 'd' || k == 'rack.input'} + b = body('rack.input'=>rack_input, 'QUERY_STRING'=>'b=c&f=g', 'rack.session'=>{'d'=>'e', 'h'=>'i'}) + b.must_match(/^Subject: ArgumentError: bad foo/) + b.must_match(/^Backtrace:.+^ENV:.+^"rack\.input" => FILTERED.+^Params:\s+^"b" => FILTERED\s+"f" => "g"\s+^Session:\s+^"d" => FILTERED\s+"h" => "i"/m) + end + it "handles invalid parameters in error_email_content" do app.route do |r| raise ArgumentError, 'bad foo' rescue error_email_content($!) diff --git a/spec/plugin/error_mail_spec.rb b/spec/plugin/error_mail_spec.rb index e1dd7ace..b821555a 100644 --- a/spec/plugin/error_mail_spec.rb +++ b/spec/plugin/error_mail_spec.rb @@ -59,6 +59,16 @@ def email b.must_match(/^Backtrace:.+^ENV:.+^"rack\.input" => .+^Params:\s+^"b" => "c"\s+^Session:\s+^"d" => "e"/m) end + it "supports :filter plugin option for filtering parameters, environment variables, and session values" do + app.route do |r| + raise ArgumentError, 'bad foo' rescue error_mail_content($!) + end + app.plugin :error_mail, :filter=>proc{|k, v| k == 'b' || k == 'd' || k == 'rack.input'} + b = body('rack.input'=>rack_input, 'QUERY_STRING'=>'b=c&f=g', 'rack.session'=>{'d'=>'e', 'h'=>'i'}) + b.must_match(/^Subject: ArgumentError: bad foo/) + b.must_match(/^Backtrace:.+^ENV:.+^"rack\.input" => FILTERED.+^Params:\s+^"b" => FILTERED\s+"f" => "g"\s+^Session:\s+^"d" => FILTERED\s+"h" => "i"/m) + end + it "handles invalid parameters in error_mail_content" do app.route do |r| raise ArgumentError, 'bad foo' rescue error_mail_content($!)