Skip to content

Commit f70456a

Browse files
committed
make the censor function customizable
related to #530
1 parent 5448bc5 commit f70456a

File tree

3 files changed

+108
-4
lines changed

3 files changed

+108
-4
lines changed

lib/Dancer2/Core/Error.pm

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,27 @@ has title => (
4848
builder => '_build_title',
4949
);
5050

51+
has censor => (
52+
is => 'ro',
53+
isa => CodeRef,
54+
lazy => 1,
55+
default => sub {
56+
my $self = shift;
57+
58+
my $custom = $self->has_app && $self->app->setting('error_censor')
59+
or return \&_censor;
60+
61+
my $module = $custom =~ s/::[^:]*?$//r;
62+
my $function = $custom =~ s/^.*:://r;
63+
64+
require_module($module) unless eval {
65+
$module->can($function)
66+
};
67+
68+
return $module->can($function);
69+
}
70+
);
71+
5172
sub _build_title {
5273
my ($self) = @_;
5374
my $title = 'Error ' . $self->status;
@@ -367,11 +388,11 @@ sub backtrace {
367388
}
368389

369390
sub dumper {
370-
my $obj = shift;
391+
my ($self,$obj) = @_;
371392

372393
# Take a copy of the data, so we can mask sensitive-looking stuff:
373394
my $data = clone($obj);
374-
my $censored = _censor( $data );
395+
my $censored = $self->censor->( $data );
375396

376397
#use Data::Dumper;
377398
my $dd = Data::Dumper->new( [ $data ] );
@@ -399,7 +420,7 @@ sub environment {
399420
my $env = $self->has_app && $self->app->has_request && $self->app->request->env;
400421

401422
# Get a sanitised dump of the settings, session and environment
402-
$_ = $_ ? dumper($_) : '<i>undefined</i>' for $settings, $session, $env;
423+
$_ = $_ ? $self->dumper($_) : '<i>undefined</i>' for $settings, $session, $env;
403424

404425
return <<"END_HTML";
405426
<div class="title">Stack</div><pre class="content">$stack</pre>
@@ -523,6 +544,35 @@ This is only an attribute getter, you'll have to set it at C<new>.
523544
524545
The message of the error page.
525546
547+
=attr censor
548+
549+
The function to use to censor error messages. By default it uses the
550+
C<_censor> function of this package, but it can be configured via the
551+
app setting 'error_censor'. If provided, C<error_censor> has to be
552+
the fully qualified name of the censor function to use. That function is
553+
expected to take in the data as a hashref, modify it in place and return
554+
the number of items 'censored'.
555+
556+
For example, using L<Data::Censor>.
557+
558+
# in config.yml
559+
error_censor: MyApp::Censor::censor
560+
561+
# in MyApp::Censor
562+
package MyApp::Censor;
563+
564+
use Data::Censor;
565+
566+
my $data_censor = Data::Censor->new(
567+
sensitive_fields => [ qw(card_number password hush) ],
568+
replacement => '(Sensitive data hidden)',
569+
);
570+
571+
sub censor { $data_censor->censor(@_) }
572+
573+
1;
574+
575+
526576
=method throw($response)
527577
528578
Populates the content of the response with the error's information.

t/error.t

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use strict;
22
use warnings;
3+
4+
use lib 't/lib';
5+
36
use Test::More import => ['!pass'];
47
use Plack::Test;
58
use HTTP::Request::Common;
@@ -37,6 +40,7 @@ my $request = $app->build_request($env);
3740

3841
$app->set_request($request);
3942

43+
4044
subtest 'basic defaults of Error object' => sub {
4145
my $err = Dancer2::Core::Error->new( app => $app );
4246
is $err->status, 500, 'code';
@@ -240,8 +244,47 @@ subtest 'Errors with show_stacktrace and circular references' => sub {
240244
'Values for other keys (non-sensitive) appear in the stacktrace');
241245
};
242246

243-
done_testing;
247+
subtest censor => sub {
248+
sub MyApp::Censor::censor { $_[0]->{hush} = 'NOT TELLING'; return 1; }
249+
250+
my $app = Dancer2::Core::App->new( name => 'main' );
251+
252+
$app->setting( password => 'potato' ); # oh my, we're leaking a password
253+
254+
subtest 'core censor()' => sub {
255+
my $error = Dancer2::Core::Error->new( app => $app );
256+
257+
unlike $error->environment => qr/potato/, 'the password is censored';
258+
like $error->environment => qr/^.*password.*Hidden.*$/m, 'we say it is hidden';
259+
};
260+
261+
subtest 'custom censor()' => sub {
262+
my $app = Dancer2::Core::App->new( name => 'main' );
263+
my $error = Dancer2::Core::Error->new( app => $app );
264+
265+
$app->setting( hush => 'potato' );
244266

267+
$app->setting( error_censor => 'MyApp::Censor::censor' );
268+
269+
unlike $error->environment => qr/potato/, 'the password is censored';
270+
like $error->environment => qr/^ .* hush .* NOT \s TELLING .* $/xm, 'we say it is hidden';
271+
};
272+
273+
subtest 'custom imported censor()' => sub {
274+
my $app = Dancer2::Core::App->new( name => 'main' );
275+
my $error = Dancer2::Core::Error->new( app => $app );
276+
277+
$app->setting( personal => 'potato' );
278+
279+
$app->setting( error_censor => 'CustomCensor::censor' );
280+
281+
unlike $error->environment => qr/potato/, 'the password is censored';
282+
like $error->environment => qr/^ .* personal .* for \s my \s eyes \s only .* $/xm, 'we say it is hidden';
283+
};
284+
285+
};
286+
287+
done_testing;
245288

246289
{ # Simple test exception class
247290
package MyTestException;

t/lib/CustomCensor.pm

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package CustomCensor;
2+
3+
sub censor {
4+
my $data = shift;
5+
6+
$data->{personal} = 'for my eyes only';
7+
8+
return 1;
9+
}
10+
11+
1;

0 commit comments

Comments
 (0)