View | Details | Raw Unified | Return to bug 38365
Collapse All | Expand All

(-)a/C4/Output.pm (+7 lines)
Lines 34-39 use C4::Auth qw( get_template_and_user ); Link Here
34
use C4::Context;
34
use C4::Context;
35
use C4::Templates;
35
use C4::Templates;
36
36
37
use Koha::ContentSecurityPolicy;
38
37
our ( @ISA, @EXPORT_OK );
39
our ( @ISA, @EXPORT_OK );
38
40
39
BEGIN {
41
BEGIN {
Lines 270-275 sub output_with_http_headers { Link Here
270
        $options->{'Content-Script-Type'} = 'text/javascript';
272
        $options->{'Content-Script-Type'} = 'text/javascript';
271
    }
273
    }
272
274
275
    my $csp = Koha::ContentSecurityPolicy->new;
276
    if ( $csp->is_enabled ) {
277
        $options->{ $csp->header_name } = $csp->header_value if $csp->header_value;
278
    }
279
273
    # We can't encode here, that will double encode our templates, and xslt
280
    # We can't encode here, that will double encode our templates, and xslt
274
    # We need to fix the encoding as it comes out of the database, or when we pass the variables to templates
281
    # We need to fix the encoding as it comes out of the database, or when we pass the variables to templates
275
282
(-)a/t/db_dependent/Output.t (-2 / +166 lines)
Lines 17-23 Link Here
17
17
18
use Modern::Perl;
18
use Modern::Perl;
19
19
20
use Test::More tests => 3;
20
use Test::More tests => 4;
21
use Test::MockModule;
21
use Test::MockModule;
22
use Test::NoWarnings;
22
use Test::NoWarnings;
23
use Test::Warn;
23
use Test::Warn;
Lines 160-162 subtest 'redirect_if_opac_suppressed() tests' => sub { Link Here
160
160
161
    $schema->storage->txn_rollback;
161
    $schema->storage->txn_rollback;
162
};
162
};
163
- 
163
164
subtest 'Content-Security-Policy tests' => sub {
165
    plan tests => 1;
166
167
    subtest 'OPAC' => sub {
168
169
        plan tests => 3;
170
171
        subtest 'turned off' => sub {
172
173
            plan tests => 2;
174
175
            $schema->storage->txn_begin;
176
177
            t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' );
178
179
            my $query = CGI->new();
180
181
            {
182
                local *STDOUT;
183
                my $stdout;
184
                open STDOUT, '>', \$stdout;
185
186
                my ( $template, $borrowernumber, $cookie ) = C4::Auth::get_template_and_user(
187
                    {
188
                        template_name   => "opac-main.tt",
189
                        type            => "opac",
190
                        query           => $query,
191
                        authnotrequired => 1,
192
                    }
193
                );
194
195
                C4::Output::output_html_with_http_headers( $query, $cookie, $template->output );
196
197
                unlike( $stdout, qr{Content-Security-Policy:}, 'No header present: Content-Security-Policy' );
198
                unlike(
199
                    $stdout, qr{Content-Security-Policy-Report-Only:},
200
                    'No header present: Content-Security-Policy-Report-Only'
201
                );
202
203
                close STDOUT;
204
            };
205
206
            $schema->storage->txn_rollback;
207
208
        };
209
210
        subtest 'report-only' => sub {
211
212
            plan tests => 3;
213
214
            $schema->storage->txn_begin;
215
216
            t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' );
217
218
            C4::Context->interface('opac');
219
220
            my $expected_csp_header = 'Content-security-policy-report-only';
221
            my $expected_csp_header_value =
222
                "default-src 'self'; script-src 'self' 'unsafe-eval' _CSP_NONCE_; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'";
223
            t::lib::Mocks::mock_config(
224
                'content_security_policy',
225
                { 'opac' => { csp_mode => 'report-only', csp_header_value => $expected_csp_header_value } }
226
            );
227
228
            my $query = CGI->new();
229
230
            {
231
                local *STDOUT;
232
                my $stdout;
233
                open STDOUT, '>', \$stdout;
234
235
                my ( $template, $borrowernumber, $cookie ) = C4::Auth::get_template_and_user(
236
                    {
237
                        template_name   => "opac-main.tt",
238
                        type            => "opac",
239
                        query           => $query,
240
                        authnotrequired => 1,
241
                    }
242
                );
243
244
                my $cache = Koha::Cache::Memory::Lite->get_instance;
245
                my $nonce = $cache->get_from_cache("csp_nonce");
246
                $expected_csp_header_value =~ s/_CSP_NONCE_/nonce-$nonce/g;
247
                C4::Output::output_html_with_http_headers( $query, $cookie, $template->output );
248
249
                $stdout =~ /(Content-Security-Policy[A-Za-z\-]*): (.*)\r/im;
250
                my ( $csp_header, $csp_header_value ) = ( $1, $2 );
251
                isnt(
252
                    $csp_header, 'Content-Security-Policy',
253
                    'No header present: Content-Security-Policy'
254
                );
255
                is( $csp_header, $expected_csp_header, 'Header present: Content-Security-Policy-Report-Only' );
256
                is(
257
                    $csp_header_value, $expected_csp_header_value,
258
                    'Content-Security-Policy-Report-Only value matches expected value'
259
                );
260
261
                close STDOUT;
262
            };
263
264
            $schema->storage->txn_rollback;
265
266
        };
267
268
        subtest 'enabled' => sub {
269
270
            plan tests => 3;
271
272
            $schema->storage->txn_begin;
273
274
            t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' );
275
276
            C4::Context->interface('opac');
277
278
            my $expected_csp_header = 'Content-security-policy';
279
            my $expected_csp_header_value =
280
                "default-src 'self'; script-src 'self' 'unsafe-eval' _CSP_NONCE_; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'";
281
            t::lib::Mocks::mock_config(
282
                'content_security_policy',
283
                { 'opac' => { csp_mode => 'enabled', csp_header_value => $expected_csp_header_value } }
284
            );
285
286
            my $query = CGI->new();
287
288
            {
289
                local *STDOUT;
290
                my $stdout;
291
                open STDOUT, '>', \$stdout;
292
293
                my ( $template, $borrowernumber, $cookie ) = C4::Auth::get_template_and_user(
294
                    {
295
                        template_name   => "opac-main.tt",
296
                        type            => "opac",
297
                        query           => $query,
298
                        authnotrequired => 1,
299
                    }
300
                );
301
302
                my $cache = Koha::Cache::Memory::Lite->get_instance;
303
                my $nonce = $cache->get_from_cache("csp_nonce");
304
                $expected_csp_header_value =~ s/_CSP_NONCE_/nonce-$nonce/g;
305
306
                C4::Output::output_html_with_http_headers( $query, $cookie, $template->output );
307
308
                $stdout =~ /(Content-Security-Policy[A-Za-z\-]*): (.*)\r/im;
309
                my ( $csp_header, $csp_header_value ) = ( $1, $2 );
310
                isnt(
311
                    $csp_header, 'Content-Security-Policy-Report-Only:',
312
                    'No header present: Content-Security-Policy-Report-Only'
313
                );
314
                is( $csp_header, $expected_csp_header, 'Header present: Content-Security-Policy' );
315
                is(
316
                    $csp_header_value, $expected_csp_header_value,
317
                    'Content-Security-Policy value matches expected value'
318
                );
319
320
                close STDOUT;
321
            };
322
323
            $schema->storage->txn_rollback;
324
325
        };
326
    };
327
};

Return to bug 38365