Initial Drupal 11 with DDEV setup
This commit is contained in:
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\system\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Routing\RouteBuildEvent;
|
||||
use Drupal\Core\Routing\RoutingEvents;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Alters routes to add necessary requirements.
|
||||
*
|
||||
* @see \Drupal\system\Access\SystemAdminMenuBlockAccessCheck
|
||||
* @see \Drupal\system\Controller\SystemController::systemAdminMenuBlockPage()
|
||||
*/
|
||||
class AccessRouteAlterSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents(): array {
|
||||
$events[RoutingEvents::ALTER][] = 'accessAdminMenuBlockPage';
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds requirements to some System Controller routes.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteBuildEvent $event
|
||||
* The event to process.
|
||||
*/
|
||||
public function accessAdminMenuBlockPage(RouteBuildEvent $event) {
|
||||
$routes = $event->getRouteCollection();
|
||||
foreach ($routes as $route) {
|
||||
// Do not use a leading slash when comparing to the _controller string
|
||||
// because the leading slash in a fully-qualified method name is optional.
|
||||
if ($route->hasDefault('_controller')) {
|
||||
switch (ltrim($route->getDefault('_controller'), '\\')) {
|
||||
case 'Drupal\system\Controller\SystemController::systemAdminMenuBlockPage':
|
||||
$route->setRequirement('_access_admin_menu_block_page', 'TRUE');
|
||||
break;
|
||||
|
||||
case 'Drupal\system\Controller\SystemController::overview':
|
||||
$route->setRequirement('_access_admin_overview_page', 'TRUE');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\system\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Routing\RouteSubscriberBase;
|
||||
use Drupal\Core\Routing\RoutingEvents;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Adds the _admin_route option to each admin HTML route.
|
||||
*/
|
||||
class AdminRouteSubscriber extends RouteSubscriberBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function alterRoutes(RouteCollection $collection) {
|
||||
foreach ($collection->all() as $route) {
|
||||
$path = $route->getPath();
|
||||
if (($path == '/admin' || str_starts_with($path, '/admin/')) && !$route->hasOption('_admin_route') && static::isHtmlRoute($route)) {
|
||||
$route->setOption('_admin_route', TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents(): array {
|
||||
$events = parent::getSubscribedEvents();
|
||||
|
||||
// Use a lower priority than \Drupal\field_ui\Routing\RouteSubscriber or
|
||||
// \Drupal\views\EventSubscriber\RouteSubscriber to ensure we add the option
|
||||
// to their routes.
|
||||
$events[RoutingEvents::ALTER] = ['onAlterRoutes', -200];
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given route is an HTML route.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route to analyze.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if HTML is a valid format for this route.
|
||||
*/
|
||||
protected static function isHtmlRoute(Route $route) {
|
||||
// If a route has no explicit format, then HTML is valid.
|
||||
$format = $route->hasRequirement('_format') ? explode('|', $route->getRequirement('_format')) : ['html'];
|
||||
return in_array('html', $format, TRUE);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\system\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Config\ConfigCrudEvent;
|
||||
use Drupal\Core\Config\ConfigEvents;
|
||||
use Drupal\system\SecurityAdvisories\SecurityAdvisoriesFetcher;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Defines a config subscriber for changes to 'system.advisories'.
|
||||
*/
|
||||
class AdvisoriesConfigSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The security advisory fetcher service.
|
||||
*
|
||||
* @var \Drupal\system\SecurityAdvisories\SecurityAdvisoriesFetcher
|
||||
*/
|
||||
protected $securityAdvisoriesFetcher;
|
||||
|
||||
/**
|
||||
* Constructs a new ConfigSubscriber object.
|
||||
*
|
||||
* @param \Drupal\system\SecurityAdvisories\SecurityAdvisoriesFetcher $security_advisories_fetcher
|
||||
* The security advisory fetcher service.
|
||||
*/
|
||||
public function __construct(SecurityAdvisoriesFetcher $security_advisories_fetcher) {
|
||||
$this->securityAdvisoriesFetcher = $security_advisories_fetcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the stored response from the security advisories feed, if needed.
|
||||
*
|
||||
* The stored response will only be deleted if the 'interval_hours' config
|
||||
* setting is reduced from the previous value.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigCrudEvent $event
|
||||
* The configuration event.
|
||||
*/
|
||||
public function onConfigSave(ConfigCrudEvent $event): void {
|
||||
$saved_config = $event->getConfig();
|
||||
if ($saved_config->getName() === 'system.advisories' && $event->isChanged('interval_hours')) {
|
||||
$original_interval = $saved_config->getOriginal('interval_hours');
|
||||
if ($original_interval && $saved_config->get('interval_hours') < $original_interval) {
|
||||
// If the new interval is less than the original interval, delete the
|
||||
// stored results.
|
||||
$this->securityAdvisoriesFetcher->deleteStoredResponse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents(): array {
|
||||
$events[ConfigEvents::SAVE][] = ['onConfigSave'];
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\system\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
|
||||
use Drupal\Core\Config\ConfigCrudEvent;
|
||||
use Drupal\Core\Config\ConfigEvents;
|
||||
use Drupal\Core\Extension\ThemeHandlerInterface;
|
||||
use Drupal\Core\Theme\Registry;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* A subscriber invalidating cache tags when system config objects are saved.
|
||||
*/
|
||||
class ConfigCacheTag implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* Constructs a ConfigCacheTag object.
|
||||
*/
|
||||
public function __construct(
|
||||
protected ThemeHandlerInterface $themeHandler,
|
||||
protected CacheTagsInvalidatorInterface $cacheTagsInvalidator,
|
||||
protected Registry $themeRegistry,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate cache tags when particular system config objects are saved.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigCrudEvent $event
|
||||
* The Event to process.
|
||||
*/
|
||||
public function onSave(ConfigCrudEvent $event) {
|
||||
$config_name = $event->getConfig()->getName();
|
||||
// Changing the site settings may mean a different route is selected for the
|
||||
// front page. Additionally a change to the site name or similar must
|
||||
// invalidate the render cache since this could be used anywhere.
|
||||
if ($config_name === 'system.site') {
|
||||
$this->cacheTagsInvalidator->invalidateTags(['route_match', 'rendered']);
|
||||
}
|
||||
|
||||
// Theme configuration and global theme settings.
|
||||
if (in_array($config_name, ['system.theme', 'system.theme.global'], TRUE)) {
|
||||
$this->cacheTagsInvalidator->invalidateTags(['rendered']);
|
||||
}
|
||||
|
||||
// Library and template overrides potentially change for the default theme
|
||||
// when the admin theme is changed.
|
||||
if ($config_name === 'system.theme' && $event->isChanged('admin')) {
|
||||
$this->themeRegistry->reset();
|
||||
$this->cacheTagsInvalidator->invalidateTags(['library_info']);
|
||||
}
|
||||
|
||||
// Theme-specific settings, check if this matches a theme settings
|
||||
// configuration object (THEME_NAME.settings), in that case, clear the
|
||||
// rendered cache tag.
|
||||
if (preg_match('/^([^\.]*)\.settings$/', $config_name, $matches)) {
|
||||
if ($this->themeHandler->themeExists($matches[1])) {
|
||||
$this->cacheTagsInvalidator->invalidateTags(['rendered']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents(): array {
|
||||
$events[ConfigEvents::SAVE][] = ['onSave'];
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\system\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\File\Event\FileUploadSanitizeNameEvent;
|
||||
use Drupal\Core\File\FileSystemInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* The final subscriber to 'file.upload.sanitize.name'.
|
||||
*
|
||||
* This prevents insecure filenames.
|
||||
*/
|
||||
class SecurityFileUploadEventSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* Constructs a new file event listener.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
|
||||
* The config factory.
|
||||
*/
|
||||
public function __construct(
|
||||
protected ConfigFactoryInterface $configFactory,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents(): array {
|
||||
// This event must be run last to ensure the filename obeys the security
|
||||
// rules.
|
||||
$events[FileUploadSanitizeNameEvent::class][] = [
|
||||
'sanitizeName',
|
||||
PHP_INT_MIN,
|
||||
];
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the upload's filename to make it secure.
|
||||
*
|
||||
* @param \Drupal\Core\File\Event\FileUploadSanitizeNameEvent $event
|
||||
* File upload sanitize name event.
|
||||
*/
|
||||
public function sanitizeName(FileUploadSanitizeNameEvent $event): void {
|
||||
$filename = $event->getFilename();
|
||||
// Dot files are renamed regardless of security settings.
|
||||
$filename = trim($filename, '.');
|
||||
|
||||
// Remove any null bytes. See
|
||||
// http://php.net/manual/security.filesystem.nullbytes.php
|
||||
$filename = str_replace(chr(0), '', $filename);
|
||||
|
||||
// Split up the filename by periods. The first part becomes the basename,
|
||||
// the last part the final extension.
|
||||
$filename_parts = explode('.', $filename);
|
||||
// Remove file basename.
|
||||
$filename = array_shift($filename_parts);
|
||||
// Remove final extension.
|
||||
$final_extension = (string) array_pop($filename_parts);
|
||||
// Check if we're dealing with a dot file that is also an insecure extension
|
||||
// e.g. .htaccess. In this scenario there is only one 'part' and the
|
||||
// extension becomes the filename. We use the original filename from the
|
||||
// event rather than the trimmed version above.
|
||||
$insecure_uploads = $this->configFactory->get('system.file')->get('allow_insecure_uploads');
|
||||
if (!$insecure_uploads && $final_extension === '' && str_contains($event->getFilename(), '.') && in_array(strtolower($filename), FileSystemInterface::INSECURE_EXTENSIONS, TRUE)) {
|
||||
$final_extension = $filename;
|
||||
$filename = '';
|
||||
}
|
||||
|
||||
$extensions = $event->getAllowedExtensions();
|
||||
if (!empty($extensions) && !in_array(strtolower($final_extension), $extensions, TRUE)) {
|
||||
// This upload will be rejected by FileExtension constraint anyway so do
|
||||
// not make any alterations to the filename. This prevents a file named
|
||||
// 'example.php' being renamed to 'example.php_.txt' and uploaded if the
|
||||
// .txt extension is allowed but .php is not. It is the responsibility of
|
||||
// the function that dispatched the event to ensure
|
||||
// FileValidator::validate() is called with 'FileExtension' in the list of
|
||||
// validators if $extensions is not empty.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$insecure_uploads && in_array(strtolower($final_extension), FileSystemInterface::INSECURE_EXTENSIONS, TRUE)) {
|
||||
if (empty($extensions) || in_array('txt', $extensions, TRUE)) {
|
||||
// Add .txt to potentially executable files prior to munging to help
|
||||
// prevent exploits. This results in a filenames like filename.php being
|
||||
// changed to filename.php.txt prior to munging.
|
||||
$filename_parts[] = $final_extension;
|
||||
$final_extension = 'txt';
|
||||
}
|
||||
else {
|
||||
// Since .txt is not an allowed extension do not rename the file. The
|
||||
// file will be rejected by FileValidator::validate().
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If there are any insecure extensions in the filename munge all the
|
||||
// internal extensions.
|
||||
$munge_everything = !empty(array_intersect(array_map('strtolower', $filename_parts), FileSystemInterface::INSECURE_EXTENSIONS));
|
||||
|
||||
// Munge the filename to protect against possible malicious extension hiding
|
||||
// within an unknown file type (i.e. filename.html.foo). This was introduced
|
||||
// as part of SA-2006-006 to fix Apache's risky fallback behavior.
|
||||
|
||||
// Loop through the middle parts of the name and add an underscore to the
|
||||
// end of each section that could be a file extension but isn't in the
|
||||
// list of allowed extensions.
|
||||
foreach ($filename_parts as $filename_part) {
|
||||
$filename .= '.' . $filename_part;
|
||||
if ($munge_everything) {
|
||||
$filename .= '_';
|
||||
}
|
||||
elseif (!empty($extensions) && !in_array(strtolower($filename_part), $extensions) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) {
|
||||
$filename .= '_';
|
||||
}
|
||||
}
|
||||
if ($final_extension !== '') {
|
||||
$filename .= '.' . $final_extension;
|
||||
}
|
||||
if ($filename !== $event->getFilename()) {
|
||||
$event->setFilename($filename)->setSecurityRename();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user