Skip to content

Commit

Permalink
Added separate controller for CSP.
Browse files Browse the repository at this point in the history
  • Loading branch information
laurentmuller committed Jun 3, 2022
1 parent eba6695 commit d9abb97
Show file tree
Hide file tree
Showing 45 changed files with 648 additions and 433 deletions.
5 changes: 5 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ LOCK_DSN=flock
DATABASE_URL=sqlite:///%kernel.project_dir%/tests/Data/db_test.sqlite
###< doctrine/doctrine-bundle ###

###> symfony/mailer ###
MAILER_USER_NAME=Calculation
MAILER_USER_EMAIL=[email protected]
###< symfony/mailer ###

###> assets ###
ASSET_BASE=http://127.0.0.1:8000/
ASSET_LOCALE=http://127.0.0.1:8000/
Expand Down
12 changes: 6 additions & 6 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions config/packages/twig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ twig:
app_owner_city: '%app_owner_city%'
app_description: '%app_description%'
app_mode: '%app_mode%'
mailer_user_email: '%mailer_user_email%'
cookie_path: '%cookie_path%'
google_recaptcha_site_key: '%google_recaptcha_site_key%'
mailer_user_email: '%mailer_user_email%'
link_dev: '%link_dev%'
link_prod: '%link_prod%'

Expand Down
28 changes: 28 additions & 0 deletions public/css/calculation.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,31 @@
@import '../js/vendor/contextmenu/jquery.contextMenu.css';

/**! compression tag for ftp-deployment */

@media (min-width: 1200px) {
.col-xl-4 {
-ms-flex: initial;
flex: initial;
max-width: initial;
width: 100%;
}
.col-xl-8 {
-ms-flex: initial;
flex: initial;
max-width: initial;
width: 100%;
}
}

@media (min-width: 1400px) {
.col-xl-4 {
-ms-flex: 0 0 33.333333%;
flex: 0 0 33.333333%;
max-width: 33.333333%;
}
.col-xl-8 {
-ms-flex: 0 0 66.666667%;
flex: 0 0 66.666667%;
max-width: 66.666667%;
}
}
4 changes: 4 additions & 0 deletions public/css/email.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ table.body.wrapper {
border-right: 1px solid #f3f3f3;
border-bottom: 1px solid #f3f3f3;
}

#csp td {
padding-top: 5px;
}
2 changes: 0 additions & 2 deletions public/js/application/chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,11 @@ function onMonthsChange($months) {
*/
(function ($) {
'use strict';

// tooltip
$('#data').tooltip({
selector: '.has-tooltip',
customClass: 'tooltip-danger overall-datatable'
});

// handle months change
const $months = $('#months');
if ($months.length) {
Expand Down
1 change: 1 addition & 0 deletions public/js/chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* Vendor Files
*/
<!--#include file="vendor/highcharts/highcharts.js" -->
<!--#include file="vendor/highcharts/accessibility.js" -->

/**
* Plugin Files
Expand Down
2 changes: 1 addition & 1 deletion public/js/test/recaptcha.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ grecaptcha.ready(function () {
// execute
const $form = $('#edit-form');
const key = $form.data('key');
const action = $form.attr('action');
const action = $form.data('action');
grecaptcha.execute(key, {
action: action
}).then(function (token) {
Expand Down
11 changes: 11 additions & 0 deletions public/vendor.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@
"highcharts.src.js"
]
},
{
"name": "highcharts",
"version": "10.1.0",
"source": "https://cdn.jsdelivr.net/npm/",
"format": "{source}{name}@{version}/modules/{file}",
"target": "highcharts",
"files": [
"accessibility.js",
"accessibility.js.map"
]
},
{
"name": "html5sortable",
"version": "0.13.3",
Expand Down
2 changes: 2 additions & 0 deletions src/Chart/BaseChart.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ class BaseChart extends Highchart

/**
* Constructor.
*
* @throws \Psr\Cache\InvalidArgumentException
*/
public function __construct(protected ApplicationService $application, ThemeService $service, TranslatorInterface $translator)
{
Expand Down
117 changes: 117 additions & 0 deletions src/Controller/CspController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php
/*
* This file is part of the Calculation package.
*
* (c) bibi.nu <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace App\Controller;

use App\Mime\CspViolationEmail;
use App\Util\Utils;
use Psr\Log\LoggerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Bridge\Twig\Mime\NotificationEmail;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

/**
* Controller to handle CSP violation.
*/
#[AsController]
#[IsGranted('ROLE_USER')]
class CspController extends AbstractController
{
#[Route(path: '/csp', name: 'log_csp')]
public function invoke(LoggerInterface $logger, MailerInterface $mailer): Response
{
if (false !== $context = $this->getContext()) {
try {
$title = $this->trans('notification.csp_title');
$this->logContext($title, $context, $logger);
$this->sendNotification($title, $context, $mailer);
} catch (TransportExceptionInterface $e) {
$context = Utils::getExceptionContext($e);
$logger->error($e->getMessage(), $context);
}
}

return new Response('', Response::HTTP_NO_CONTENT);
}

private function explodeOriginalPolicy(string $value): array
{
$result = [];
$policies = \array_filter(\explode(';', $value));
foreach ($policies as $policy) {
$entries = \array_filter(\explode(' ', $policy));
if (\count($entries) > 1) {
$key = \reset($entries);
$values = \array_map(static fn (string $entry): string => \trim($entry, "'"), \array_slice($entries, 1));
\sort($values);
$result[$key] = \implode("\n", $values);
}
}

return $result;
}

private function getActionUrl(): string
{
return $this->generateUrl(self::HOME_PAGE, [], UrlGeneratorInterface::ABSOLUTE_URL);
}

private function getContext(): array|false
{
$content = (string) \file_get_contents('php://input');
/** @psalm-var bool|array{csp-report: string[]} $data */
$data = \json_decode($content, true);
if (\is_array($data)) {
$context = \array_filter($data['csp-report']);
if (isset($context['original-policy'])) {
$context['original-policy'] = $this->explodeOriginalPolicy($context['original-policy']);
}

return $context;
}

return false;
}

private function getFooterText(): string
{
return $this->trans('notification.footer', ['%name%' => $this->getApplicationName()]);
}

private function logContext(string $title, array $context, LoggerInterface $logger): void
{
$logger->error($title, $context);
}

/**
* @throws TransportExceptionInterface
*/
private function sendNotification(string $title, array $context, MailerInterface $mailer): void
{
$notification = new CspViolationEmail($this->getTranslator());
$notification->importance(NotificationEmail::IMPORTANCE_HIGH)
->subject($title)
->to($this->getAddressFrom())
->from($this->getAddressFrom())
->setFooterText($this->getFooterText())
->action($this->trans('index.title'), $this->getActionUrl())
->context([
'context' => $context,
]);
$mailer->send($notification);
}
}
50 changes: 10 additions & 40 deletions src/Controller/LogController.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use Symfony\Component\Routing\Annotation\Route;

/**
Expand All @@ -39,45 +36,10 @@ class LogController extends AbstractController
{
use TableTrait;

/**
* Logs a Content Security Policy report.
*/
#[IsGranted('ROLE_USER')]
#[Route(path: '/csp', name: 'log_csp')]
public function cspViolation(LoggerInterface $logger, MailerInterface $mailer): Response
{
$content = (string) \file_get_contents('php://input');
/** @psalm-var bool|array{csp-report: string[]} $data */
$data = \json_decode($content, true);
if (\is_array($data)) {
$title = 'CSP Violation';
$csp_report = $data['csp-report'];
$context = \array_filter($csp_report, 'strlen');
if (isset($context['document-uri'])) {
$title .= ': ' . $context['document-uri'];
} elseif (isset($context['source-file'])) {
$title .= ': ' . $context['source-file'];
}
$logger->error($title, $context);

try {
$text = (string) \json_encode($context, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES);
$email = (new Email())
->subject($title)
->text($text);
$mailer->send($email);
} catch (TransportExceptionInterface $e) {
$context = Utils::getExceptionContext($e);
$logger->error($e->getMessage(), $context);
}
}

// no content
return new Response('', Response::HTTP_NO_CONTENT);
}

/**
* Delete the content of the log file (if any).
*
* @throws \Psr\Cache\InvalidArgumentException
*/
#[Route(path: '/delete', name: 'log_delete')]
public function delete(Request $request, LogService $service, LoggerInterface $logger): Response
Expand Down Expand Up @@ -126,6 +88,8 @@ public function delete(Request $request, LogService $service, LoggerInterface $l

/**
* Export the logs to a Spreadsheet document.
*
* @throws \Psr\Cache\InvalidArgumentException
*/
#[Route(path: '/excel', name: 'log_excel')]
public function excel(LogService $service): Response
Expand All @@ -143,6 +107,8 @@ public function excel(LogService $service): Response

/**
* Export to PDF the content of the log file.
*
* @throws \Psr\Cache\InvalidArgumentException
*/
#[Route(path: '/pdf', name: 'log_pdf')]
public function pdf(LogService $service): Response
Expand All @@ -160,6 +126,8 @@ public function pdf(LogService $service): Response

/**
* Clear the log file cache.
*
* @throws \Psr\Cache\InvalidArgumentException
*/
#[Route(path: '/refresh', name: 'log_refresh')]
public function refresh(Request $request, LogService $service): Response
Expand All @@ -172,6 +140,8 @@ public function refresh(Request $request, LogService $service): Response

/**
* Show properties of a log entry.
*
* @throws \Psr\Cache\InvalidArgumentException
*/
#[Route(path: '/show/{id}', name: 'log_show', requirements: ['id' => self::DIGITS])]
public function show(Request $request, int $id, LogService $service): Response
Expand Down
Loading

0 comments on commit d9abb97

Please sign in to comment.