1
0
mirror of https://github.com/typemill/typemill.git synced 2025-07-31 11:20:15 +02:00

Version 2.1.0 CSP Middleware

This commit is contained in:
trendschau
2024-01-01 19:49:46 +01:00
parent c3517fde58
commit 79b3be3619
5 changed files with 67 additions and 29 deletions

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for the page rendering data.
*/
class OnCspLoaded extends BaseEvent
{
}

View File

@@ -11,28 +11,27 @@ class CspHeadersMiddleware implements MiddlewareInterface
{
protected $settings;
protected $urlinfo;
public function __construct($settings, $urlinfo)
protected $cspFromPlugins = false;
protected $cspFromTheme = false;
public function __construct($settings, $cspFromPlugins, $cspFromTheme)
{
$this->settings = $settings;
$this->urlinfo = $urlinfo;
$this->$cspFromPlugins = $cspFromPlugins;
$this->$cspFromTheme = $cspFromTheme;
}
public function process(Request $request, RequestHandler $handler) :response
{
$scheme = $request->getUri()->getScheme();
{
# add the custom headers to the response after everything is processed
$response = $handler->handle($request);
###################
# CSP HEADER #
###################
$whitelist = ["'unsafe-inline'", "'unsafe-eval'", "'self'", "*.youtube-nocookie.com", "*.youtube.com"];
$cspdomains = isset($this->settings['cspdomains']) ? trim($this->settings['cspdomains']) : false;
$defaultsrc = "default-src 'unsafe-inline' 'unsafe-eval' 'self'";
if($cspdomains && $cspdomains != '')
{
@@ -42,23 +41,29 @@ class CspHeadersMiddleware implements MiddlewareInterface
$cspdomain = trim($cspdomain);
if($cspdomain != '')
{
$defaultsrc .= ' ' . $cspdomain;
$whitelist[] = $cspdomain;
}
}
}
# dispatch to get from plugins
# get yaml from current theme
# add csp from plugins
if($this->cspFromPlugins && is_array($this->cspFromPlugins) && !empty($this->cspFromPlugins))
{
$whitelist = array_merge($whitelist, $this->cspFromPlugins);
}
# add csp from current theme
if($this->cspFromTheme && is_array($this->cspFromTheme) && !empty($this->cspFromTheme))
{
$whitelist = array_merge($whitelist, $this->cspFromTheme);
}
$whitelist = array_unique($whitelist);
$whitelist = implode(' ', $whitelist);
# Define the Content Security Policy header
$cspHeader = $defaultsrc . ";"; // Default source is restricted to 'self'
# $cspHeader .= "frame-src 'self' https://www.youtube.com;"; // Allowing embedding YouTube videos
# $cspHeader .= "img-src *";
# $cspHeader .= "media-src *";
# $cspHeader .= "script-src *";
# $cspHeader .= "style-src *";
# $cspHeader .= "object-src 'none'";
$cspHeader = "default-src " . $whitelist . ";";
# Add the Content Security Policy header to the response
$response = $response->withHeader('Content-Security-Policy', $cspHeader);

View File

@@ -92,7 +92,7 @@ $app->group('/api/v1', function (RouteCollectorProxy $group) use ($acl) {
if(isset($routes['api']) && !empty($routes['api']))
{
foreach($routes['api'] as $pluginRoute)
{
{
$method = $pluginRoute['httpMethod'] ?? false;
$route = $pluginRoute['route'] ?? false;
$class = $pluginRoute['class'] ?? false;

View File

@@ -4,6 +4,7 @@ use Slim\Routing\RouteCollectorProxy;
use Typemill\Middleware\WebRedirectIfAuthenticated;
use Typemill\Middleware\WebRedirectIfUnauthenticated;
use Typemill\Middleware\WebAuthorization;
use Typemill\Middleware\CspHeadersMiddleware;
use Typemill\Controllers\ControllerWebSetup;
use Typemill\Controllers\ControllerWebAuth;
use Typemill\Controllers\ControllerWebRecover;
@@ -31,7 +32,7 @@ $app->group('/tm', function (RouteCollectorProxy $group) use ($settings) {
$group->post('/reset', ControllerWebRecover::class . ':resetPassword')->setName('auth.reset');
}
})->add(new WebRedirectIfAuthenticated($routeParser, $settings));
})->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme))->add(new WebRedirectIfAuthenticated($routeParser, $settings));
# AUTHOR AREA (requires authentication)
$app->group('/tm', function (RouteCollectorProxy $group) use ($routeParser,$acl) {
@@ -51,7 +52,7 @@ $app->group('/tm', function (RouteCollectorProxy $group) use ($routeParser,$acl)
$group->get('/content/visual[/{route:.*}]', ControllerWebAuthor::class . ':showBlox')->setName('content.visual')->add(new WebAuthorization($routeParser, $acl, 'mycontent', 'view'));
$group->get('/content/raw[/{route:.*}]', ControllerWebAuthor::class . ':showRaw')->setName('content.raw')->add(new WebAuthorization($routeParser, $acl, 'mycontent', 'view'));
})->add(new WebRedirectIfUnauthenticated($routeParser));
})->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme))->add(new WebRedirectIfUnauthenticated($routeParser));
$app->redirect('/tm', $routeParser->urlFor('auth.show'), 302);
$app->redirect('/tm/', $routeParser->urlFor('auth.show'), 302);
@@ -74,11 +75,11 @@ if(isset($routes['web']) && !empty($routes['web']))
if($resources && $privilege)
{
$app->{$method}($route, $class)->setName($name)->add(new WebAuthorization($routeParser, $acl, $resource, $privilege))->add(new WebRedirectIfUnauthenticated($routeParser));
$app->{$method}($route, $class)->setName($name)->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme))->add(new WebAuthorization($routeParser, $acl, $resource, $privilege))->add(new WebRedirectIfUnauthenticated($routeParser));
}
else
{
$app->{$method}($route, $class)->setName($name);
$app->{$method}($route, $class)->setName($name)->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme));
}
}
}
@@ -86,10 +87,10 @@ if(isset($routes['web']) && !empty($routes['web']))
if(isset($settings['access']) && $settings['access'] != '')
{
# if access for website is restricted
$app->get('/[{route:.*}]', ControllerWebFrontend::class . ':index')->setName('home')->add(new WebAuthorization($routeParser, $acl, 'account', 'view'));
$app->get('/[{route:.*}]', ControllerWebFrontend::class . ':index')->setName('home')->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme))->add(new WebAuthorization($routeParser, $acl, 'account', 'view'));
}
else
{
# if access is not restricted
$app->get('/[{route:.*}]', ControllerWebFrontend::class . ':index')->setName('home');
$app->get('/[{route:.*}]', ControllerWebFrontend::class . ':index')->setName('home')->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme));
}

View File

@@ -21,6 +21,7 @@ use Typemill\Events\OnPluginsLoaded;
use Typemill\Events\OnSessionSegmentsLoaded;
use Typemill\Events\OnRolesPermissionsLoaded;
use Typemill\Events\OnResourcesLoaded;
use Typemill\Events\OnCspLoaded;
use Typemill\Middleware\RemoveCredentialsMiddleware;
use Typemill\Middleware\SessionMiddleware;
use Typemill\Middleware\OldInputMiddleware;
@@ -378,9 +379,25 @@ if(isset($settings['proxy']) && $settings['proxy'])
$timer['middleware'] = microtime(true);
/******************************
* GET CSP FROM PLUGINS/THEMES *
******************************/
# get additional csp headers from plugins
$cspFromPlugins = $dispatcher->dispatch(new OnCspLoaded([]), 'onCspLoaded')->getData();
# get additional csp headers from theme
$cspFromTheme = [];
$themeSettings = $settingsModel->getObjectSettings('themes', $settings['theme']);
if(isset($themeSettings['csp']) && is_array($themeSettings['csp']) && !empty($themeSettings['csp']) )
{
$cspFromTheme = $themeSettings['csp'];
}
/************************
* ADD ROUTES *
************************/
if(isset($settings['setup']) && $settings['setup'] == true)
{
require __DIR__ . '/routes/setup.php';
@@ -393,6 +410,7 @@ else
$timer['routes'] = microtime(true);
/************************
* RUN APP *
************************/