Skip to content

Commit 1cf0d60

Browse files
committed
Merge remote-tracking branch 'origin/main' into feature/new-config-system-2-recursive
2 parents 0738b39 + d372434 commit 1cf0d60

File tree

18 files changed

+470
-214
lines changed

18 files changed

+470
-214
lines changed

Changes

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
{{$NEXT}}
22

33
[ BUG FIXES ]
4-
* None
4+
* GH #1701: Split cookie values on & only (Yanick Champoux)
55

66
[ ENHANCEMENTS ]
7+
* GH #530: Make data censoring configurable (Yanick Champoux, David
8+
Precious)
79
* GH #1723: Enable use of a different Template Toolkit base class
810
(Andy Beverley)
911
* PR #1727: Don't create CPAN package files when generating new apps
1012
(Jason A. Crome)
13+
* GH #1737: Add on_hook_exception for errors during hook processing
14+
(Andy Beverley)
1115

1216
[ DOCUMENTATION ]
13-
* None
17+
* GH #1342: Document skipping private methods in pod coverage tests
18+
(Jason A. Crome)
1419

1520
[ DEPRECATED ]
1621
* None

cpanfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ requires 'Attribute::Handlers';
33
requires 'Carp';
44
requires 'Clone';
55
requires 'Config::Any';
6+
requires 'Data::Censor' => '0.04';
67
requires 'Digest::SHA';
78
requires 'Encode';
89
requires 'Exporter', '5.57';

lib/Dancer2/ConfigReader.pm

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ sub _build_config_readers {
145145

146146
my @config_reader_names = $ENV{'DANCER_CONFIG_READERS'}
147147
? (split qr{,}msx, $ENV{'DANCER_CONFIG_READERS'})
148-
: ( q{Dancer2::ConfigReader::File::Simple} );
148+
: ( q{Dancer2::ConfigReader::Config::Any} );
149149

150150
warn "ConfigReaders to use: @config_reader_names\n" if $ENV{DANCER_CONFIG_VERBOSE};
151151
return [
@@ -164,7 +164,7 @@ __END__
164164
165165
This class provides a C<config> attribute that
166166
is populated by executing one or more B<ConfigReader> packages.
167-
The default ConfigReader used by default is C<Dancer2::ConfigReader::File::Simple>.
167+
The default ConfigReader used by default is C<Dancer2::ConfigReader::Config::Any>.
168168
169169
Also provides a C<setting()> method which is supposed to be used by externals to
170170
read/write config entries.
@@ -187,7 +187,7 @@ if the first config reader returns
187187
subitem1: subcontent1
188188
subitem2: subcontent2
189189
190-
and the second returns
190+
and the second returns
191191
192192
item2: content9
193193
item3:
@@ -213,14 +213,13 @@ then the final config is
213213
subsubitem5: subsubcontent5
214214
item4: content4
215215
216-
217216
=head2 Configuring the ConfigReaders via DANCER_CONFIG_READERS
218217
219218
You can control which B<ConfigReader>
220219
class or classes to use to create the config
221220
via the C<DANCER_CONFIG_READERS> environment.
222221
223-
DANCER_CONFIG_READERS='Dancer2::ConfigReader::File::Simple,Dancer2::ConfigReader::CustomConfig'
222+
DANCER_CONFIG_READERS='Dancer2::ConfigReader::Config::Any,Dancer2::ConfigReader::CustomConfig'
224223
225224
If you want several, separate them with a comma (",").
226225
@@ -234,22 +233,22 @@ instantiated and added to the list of configurations to merge. This way you can,
234233
path: /path/to/sqlite.db
235234
table: config
236235
237-
The default ConfigReader L<Dancer2::ConfigReader::File::Simple> will pick that file and proceed to instantiate C<Dancer2::ConfigReader::SQLite>
236+
The default ConfigReader L<Dancer2::ConfigReader::::Config::Any> will pick that file and proceed to instantiate C<Dancer2::ConfigReader::SQLite>
238237
with the provided parameters.
239238
240239
C<additional_config_readers> can take one or a list of reader configurations, which can be either the name of the ConfigReader's class, or the
241240
key/value pair of the class name and its constructor's arguments.
242241
243242
=head2 Creating your own custom B<ConfigReader> classes.
244243
245-
Here's an example extending class C<Dancer2::ConfigReader::File::Simple>.
244+
Here's an example extending class C<Dancer2::ConfigReader::Config::Any>.
246245
247-
package Dancer2::ConfigReader::FileExtended;
246+
package Dancer2::ConfigReader::Config::Any::Extended;
248247
use Moo;
249-
extends 'Dancer2::ConfigReader::File::Simple';
248+
extends 'Dancer2::ConfigReader::Config::Any';
250249
has name => (
251250
is => 'ro',
252-
default => sub {'FileExtended'},
251+
default => sub {'Config::Any::Extended'},
253252
);
254253
around read_config => sub {
255254
my ($orig, $self) = @_;
@@ -258,7 +257,7 @@ Here's an example extending class C<Dancer2::ConfigReader::File::Simple>.
258257
return $config;
259258
};
260259
261-
Another (more complex) example is in the file C<Dancer2::ConfigReader::File::Simple>.
260+
Another (more complex) example is in class C<Dancer2::ConfigReader::Config::Any>.
262261
263262
=head1 ATTRIBUTES
264263

lib/Dancer2/ConfigReader/File/Simple.pm renamed to lib/Dancer2/ConfigReader/Config/Any.pm

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# ABSTRACT: Config reader for files
2-
package Dancer2::ConfigReader::File::Simple;
2+
package Dancer2::ConfigReader::Config::Any;
33

44
use Moo;
55

@@ -20,7 +20,7 @@ has name => (
2020
is => 'ro',
2121
isa => Str,
2222
lazy => 0,
23-
default => sub {'File::Simple'},
23+
default => sub {'Config::Any'},
2424
);
2525

2626
has config_files => (
@@ -127,7 +127,7 @@ random parts of the file config with environmental variables:
127127
128128
use Carp 'croak';
129129
130-
extends 'Dancer2::ConfigReader::File::Simple';
130+
extends 'Dancer2::ConfigReader::Config::Any';
131131
132132
has name => (
133133
is => 'ro',
@@ -191,7 +191,7 @@ random parts of the file config with environmental variables:
191191
192192
=attr name
193193
194-
The name of the Config Reader class: C<File::Simple>.
194+
The name of the Config Reader class: C<Config::Any>.
195195
196196
=attr location
197197

lib/Dancer2/Core/App.pm

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,7 @@ sub supported_hooks {
800800
core.app.before_request
801801
core.app.after_request
802802
core.app.route_exception
803+
core.app.hook_exception
803804
core.app.before_file_render
804805
core.app.after_file_render
805806
core.error.before
@@ -819,6 +820,7 @@ sub hook_aliases {
819820
before_error => 'core.error.before',
820821
after_error => 'core.error.after',
821822
on_route_exception => 'core.app.route_exception',
823+
on_hook_exception => 'core.app.hook_exception',
822824

823825
before_file_render => 'core.app.before_file_render',
824826
after_file_render => 'core.app.after_file_render',
@@ -1235,7 +1237,26 @@ sub compile_hooks {
12351237
eval { $EVAL_SHIM->($hook,@_); 1; }
12361238
or do {
12371239
my $err = $@ || "Zombie Error";
1238-
$app->cleanup;
1240+
my $is_hook_exception = $position eq 'core.app.hook_exception';
1241+
# Don't execute the hook_exception hook if the exception
1242+
# has been generated from a hook exception handler itself,
1243+
# thus preventing potentially recursive code.
1244+
$app->execute_hook( 'core.app.hook_exception', $app, $err )
1245+
unless $is_hook_exception;
1246+
my $is_halted = $app->response->is_halted; # Capture before cleanup
1247+
# We can't cleanup if we're in the hook for a hook
1248+
# exception, as this would clear the custom response that
1249+
# may have been set by the hook. However, there is no need
1250+
# to do so, as the upper hook that called this hook
1251+
# exception will perform the cleanup instead anyway
1252+
$app->cleanup
1253+
unless $is_hook_exception;
1254+
# Allow the hook function to halt the response, thus
1255+
# retaining any response it may have set. Otherwise the
1256+
# croak from this function will overwrite any content that
1257+
# may have been set by the hook
1258+
return if $is_halted;
1259+
# Default behavior if nothing else defined
12391260
$app->log('error', "Exception caught in '$position' filter: $err");
12401261
croak "Exception caught in '$position' filter: $err";
12411262
};

lib/Dancer2/Core/Error.pm

Lines changed: 104 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use Dancer2::Core::HTTP;
88
use Data::Dumper;
99
use Dancer2::FileUtils qw/path open_file/;
1010
use Sub::Quote;
11-
use Module::Runtime 'require_module';
11+
use Module::Runtime qw/ require_module use_module /;
1212
use Ref::Util qw< is_hashref >;
1313
use Clone qw(clone);
1414

@@ -48,6 +48,52 @@ 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+
if( my $custom = $self->has_app && $self->app->setting('error_censor') ) {
59+
60+
if( is_hashref $custom ) {
61+
die "only one key can be set for the 'error_censor' setting\n"
62+
if 1 != keys %$custom;
63+
64+
my( $class, $args ) = %$custom;
65+
66+
my $censor = use_module($class)->new(%$args);
67+
68+
return sub {
69+
$censor->censor(@_);
70+
}
71+
}
72+
73+
my $coderef = eval '\&'.$custom;
74+
75+
# it's already defined? Nice! We're done
76+
return $coderef if $coderef;
77+
78+
my $module = $custom =~ s/::[^:]*?$//r;
79+
80+
require_module($module);
81+
82+
return eval '\&'.$custom;
83+
}
84+
85+
# reminder: update POD below if changing the config here
86+
my $data_censor = use_module('Data::Censor')->new(
87+
sensitive_fields => qr/pass|card.?num|pan|secret/i,
88+
replacement => "Hidden (looks potentially sensitive)",
89+
);
90+
91+
return sub {
92+
$data_censor->censor(@_);
93+
};
94+
}
95+
);
96+
5197
sub _build_title {
5298
my ($self) = @_;
5399
my $title = 'Error ' . $self->status;
@@ -367,11 +413,11 @@ sub backtrace {
367413
}
368414

369415
sub dumper {
370-
my $obj = shift;
416+
my ($self,$obj) = @_;
371417

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

376422
#use Data::Dumper;
377423
my $dd = Data::Dumper->new( [ $data ] );
@@ -399,7 +445,7 @@ sub environment {
399445
my $env = $self->has_app && $self->app->has_request && $self->app->request->env;
400446

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

404450
return <<"END_HTML";
405451
<div class="title">Stack</div><pre class="content">$stack</pre>
@@ -423,37 +469,6 @@ sub get_caller {
423469

424470
# private
425471

426-
# Given a hashref, censor anything that looks sensitive. Returns number of
427-
# items which were "censored".
428-
429-
sub _censor {
430-
my $hash = shift;
431-
my $visited = shift || {};
432-
433-
unless ( $hash && is_hashref($hash) ) {
434-
carp "_censor given incorrect input: $hash";
435-
return;
436-
}
437-
438-
my $censored = 0;
439-
for my $key ( keys %$hash ) {
440-
if ( is_hashref( $hash->{$key} ) ) {
441-
if (!$visited->{ $hash->{$key} }) {
442-
# mark the new ref as visited
443-
$visited->{ $hash->{$key} } = 1;
444-
445-
$censored += _censor( $hash->{$key}, $visited );
446-
}
447-
}
448-
elsif ( $key =~ /(pass|card?num|pan|secret)/i ) {
449-
$hash->{$key} = "Hidden (looks potentially sensitive)";
450-
$censored++;
451-
}
452-
}
453-
454-
return $censored;
455-
}
456-
457472
# Replaces the entities that are illegal in (X)HTML.
458473
sub _html_encode {
459474
my $value = shift;
@@ -523,6 +538,60 @@ This is only an attribute getter, you'll have to set it at C<new>.
523538
524539
The message of the error page.
525540
541+
=attr censor
542+
543+
The function to use to censor error messages. By default it uses the C<censor> method of L<Data::Censor>"
544+
545+
# default censor function used by `error_censor`
546+
# is equivalent to
547+
sub MyApp::censor {
548+
Data::Censor->new(
549+
sensitive_fields => qr/pass|card.?num|pan|secret/i,
550+
replacement => "Hidden (looks potentially sensitive)",
551+
)->censor(@_);
552+
}
553+
setting error_censor => 'MyApp::censor';
554+
555+
It can be configured via the app setting C<error_censor>. If provided,
556+
C<error_censor> has to be the fully qualified name of the censor
557+
function. That function is expected to take in the data as a hashref,
558+
modify it in place and return the number of items 'censored'.
559+
560+
For example, using L<Data::Censor>.
561+
562+
# in config.yml
563+
error_censor: MyApp::Censor::censor
564+
565+
# in MyApp::Censor
566+
package MyApp::Censor;
567+
568+
use Data::Censor;
569+
570+
my $data_censor = Data::Censor->new(
571+
sensitive_fields => [ qw(card_number password hush) ],
572+
replacement => '(Sensitive data hidden)',
573+
);
574+
575+
sub censor { $data_censor->censor(@_) }
576+
577+
1;
578+
579+
580+
As a shortcut, C<error_censor> can also be the key/value combo of
581+
a class and the arguments for its constructor. The created object
582+
is expected to have a method C<censor>. For example, the use of
583+
L<Data::Censor> above could also have been done via the config
584+
585+
error_censor:
586+
Data::Censor:
587+
sensitive_fields:
588+
- card_number
589+
- password
590+
- hush
591+
replacement: '(Sensitive data hidden)'
592+
593+
594+
526595
=method throw($response)
527596
528597
Populates the content of the response with the error's information.

lib/Dancer2/Core/Request.pm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,7 @@ sub _build_cookies {
579579
$cookies->{$name} = Dancer2::Core::Cookie->new(
580580
name => $name,
581581
# HTTP::XSCookies v0.17+ will do the split and return an arrayref
582-
value => (is_arrayref($value) ? $value : [split(/[&;]/, $value)])
582+
value => is_arrayref($value) ? $value : [split '&', $value ]
583583
);
584584
}
585585
return $cookies;

lib/Dancer2/Core/Role/ConfigReader.pm

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,7 @@ Config can be created by reading configuration
6464
files, from environment variables, by fetching
6565
it from a cloud service, or any other means.
6666
67-
By default, the config loader
68-
which is used, is C<Dancer2::ConfigReader::File::Simple>
67+
Default config reader is C<Dancer2::ConfigReader::Config::Any>
6968
but user can create his own config reader
7069
if he wants to replace or augment
7170
the default method of config creation.

0 commit comments

Comments
 (0)