mirror of
https://github.com/doctrine/doctrine-website.git
synced 2026-03-23 22:32:11 +01:00
Ensure we have integrity and crossorigin params on all our css and js.
This commit is contained in:
52
lib/Assets/AssetIntegrityGenerator.php
Normal file
52
lib/Assets/AssetIntegrityGenerator.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Website\Assets;
|
||||
|
||||
use function assert;
|
||||
use function base64_encode;
|
||||
use function file_get_contents;
|
||||
use function hash;
|
||||
use function realpath;
|
||||
|
||||
class AssetIntegrityGenerator
|
||||
{
|
||||
/** @var string */
|
||||
private $sourcePath;
|
||||
|
||||
/** @var string[] */
|
||||
private $cache = [];
|
||||
|
||||
public function __construct(string $sourcePath)
|
||||
{
|
||||
$this->sourcePath = $sourcePath;
|
||||
}
|
||||
|
||||
public function getAssetIntegrity(string $path) : string
|
||||
{
|
||||
if (! isset($this->cache[$path])) {
|
||||
$contents = $this->getFileContents($path);
|
||||
|
||||
$this->cache[$path] = $this->buildAssetIntegrityString($contents);
|
||||
}
|
||||
|
||||
return $this->cache[$path];
|
||||
}
|
||||
|
||||
private function getFileContents(string $path) : string
|
||||
{
|
||||
$assetPath = realpath($this->sourcePath . '/' . $path);
|
||||
assert($assetPath !== false);
|
||||
|
||||
$contents = file_get_contents($assetPath);
|
||||
assert($contents !== false);
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
private function buildAssetIntegrityString(string $contents) : string
|
||||
{
|
||||
return 'sha384-' . base64_encode(hash('sha384', $contents, true));
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Website\Twig;
|
||||
|
||||
use Doctrine\Website\Assets\AssetIntegrityGenerator;
|
||||
use Doctrine\Website\Projects\Project;
|
||||
use Parsedown;
|
||||
use Twig_Extension;
|
||||
@@ -21,9 +22,17 @@ class MainExtension extends Twig_Extension
|
||||
/** @var Parsedown */
|
||||
private $parsedown;
|
||||
|
||||
public function __construct(Parsedown $parsedown)
|
||||
/** @var AssetIntegrityGenerator */
|
||||
private $assetIntegrityGenerator;
|
||||
|
||||
/** @var string */
|
||||
private $sourcePath;
|
||||
|
||||
public function __construct(Parsedown $parsedown, AssetIntegrityGenerator $assetIntegrityGenerator, string $sourcePath)
|
||||
{
|
||||
$this->parsedown = $parsedown;
|
||||
$this->parsedown = $parsedown;
|
||||
$this->assetIntegrityGenerator = $assetIntegrityGenerator;
|
||||
$this->sourcePath = $sourcePath;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,6 +43,7 @@ class MainExtension extends Twig_Extension
|
||||
return [
|
||||
new Twig_SimpleFunction('get_search_box_placeholder', [$this, 'getSearchBoxPlaceholder']),
|
||||
new Twig_SimpleFunction('get_asset_url', [$this, 'getAssetUrl']),
|
||||
new Twig_SimpleFunction('get_asset_integrity', [$this->assetIntegrityGenerator, 'getAssetIntegrity']),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -71,7 +81,7 @@ class MainExtension extends Twig_Extension
|
||||
|
||||
private function getAssetCacheBuster(string $path) : string
|
||||
{
|
||||
$assetPath = realpath(__DIR__ . '/../../source/' . $path);
|
||||
$assetPath = realpath($this->sourcePath . $path);
|
||||
assert(is_string($assetPath));
|
||||
|
||||
$contents = file_get_contents($assetPath);
|
||||
|
||||
@@ -146,7 +146,11 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ get_asset_url('/js/sidebar.js', site.url) }}"></script>
|
||||
<script
|
||||
src="{{ get_asset_url('/js/sidebar.js', site.url) }}"
|
||||
integrity="{{ get_asset_integrity('/js/sidebar.js') }}"
|
||||
crossorigin="anonymous">
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
new Sidebar();
|
||||
|
||||
@@ -20,16 +20,53 @@
|
||||
|
||||
{% block head_meta '' %}
|
||||
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
|
||||
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.8/css/all.css" integrity="sha384-3AB7yXWz4OeoZcPbieVW64vVXEwADiYyAEhwilzWsLw+9FgqpyjjStpPnpBO8o8S" crossorigin="anonymous">
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="https://use.fontawesome.com/releases/v5.0.8/css/all.css"
|
||||
integrity="sha384-3AB7yXWz4OeoZcPbieVW64vVXEwADiYyAEhwilzWsLw+9FgqpyjjStpPnpBO8o8S"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
|
||||
<link href="{{ get_asset_url('/css/style.css', site.url) }}" rel="stylesheet" type="text/css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="{{ get_asset_url('/css/style.css', site.url) }}"
|
||||
integrity="{{ get_asset_integrity('/css/style.css') }}"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/instantsearch.js@2.6.0/dist/instantsearch.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/instantsearch.js@2.6.0/dist/instantsearch-theme-algolia.min.css">
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="https://cdn.jsdelivr.net/npm/instantsearch.js@2.6.0/dist/instantsearch.min.css"
|
||||
integrity="sha384-3pqpjew6zqJsw9mdIpd6FVJvuFuacqOpSXwAifQ9iHE4keUyghetuuJDGFoTNhfn"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
|
||||
<link rel="stylesheet" href="{{ get_asset_url('/css/railscasts.css', site.url) }}" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="https://cdn.jsdelivr.net/npm/instantsearch.js@2.6.0/dist/instantsearch-theme-algolia.min.css"
|
||||
integrity="sha384-VzUGdSDk+HLoSg+ptudZxGZyaFVN7q7Eqh7QaUU19cWL2fK0kyz1belH38z7MbcZ"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="{{ get_asset_url('/css/railscasts.css', site.url) }}"
|
||||
integrity="sha384-K7nnw0wrUNTkBLTS8aN3AOx/e095lEJ1g60EJX7ash3cfsHTpQV6kWKoCPxPc9AJ"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
@@ -193,7 +230,7 @@
|
||||
|
||||
<script
|
||||
src="https://code.jquery.com/jquery-3.3.1.min.js"
|
||||
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
|
||||
integrity="sha384-tsQFqpEReu7ZLhBV2VZlAu7zcOV+rXbYlF2cqB8txI/8aZajjp4Bqd+V6D5IgvKT"
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<script
|
||||
@@ -204,22 +241,40 @@
|
||||
<script
|
||||
src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
|
||||
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
|
||||
crossorigin="anonymous"></script>
|
||||
crossorigin="anonymous">
|
||||
</script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/instantsearch.js@2.6.0"></script>
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/instantsearch.js@2.6.0"
|
||||
integrity="sha384-EtEQyqoKtrBSat8+V0eTyycfmdkEHHzKesnWrc2VPnKAcD8PJw8HKAWKsK/QnGT+"
|
||||
crossorigin="anonymous">
|
||||
</script>
|
||||
|
||||
<script id="instantsearch-template" type="text/template">
|
||||
{% include "search-results.html.twig" %}
|
||||
</script>
|
||||
|
||||
<script src="https://cdn.ravenjs.com/3.24.2/raven.min.js" crossorigin="anonymous"></script>
|
||||
<script
|
||||
src="https://cdn.ravenjs.com/3.24.2/raven.min.js"
|
||||
integrity="sha384-HwE7dFvrH8iD9R5VYUqNwjIhxDycip+e4DjP5nEhXrJtACG8AdNXjvP+AfJWxhJh"
|
||||
crossorigin="anonymous">
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
Raven.config('https://09ce137590054cfd8f0b7e9324d6ec14@sentry.io/1197701').install()
|
||||
</script>
|
||||
|
||||
<script src="{{ get_asset_url('/js/main.js', site.url) }}"></script>
|
||||
<script src="{{ get_asset_url('/js/search.js', site.url) }}"></script>
|
||||
<script
|
||||
src="{{ get_asset_url('/js/main.js', site.url) }}"
|
||||
integrity="{{ get_asset_integrity('/js/main.js') }}"
|
||||
crossorigin="anonymous">
|
||||
</script>
|
||||
|
||||
<script
|
||||
src="{{ get_asset_url('/js/search.js', site.url) }}"
|
||||
integrity="{{ get_asset_integrity('/js/search.js') }}"
|
||||
crossorigin="anonymous">
|
||||
</script>
|
||||
|
||||
{% set project = null %}
|
||||
|
||||
@@ -265,7 +320,11 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="text/javascript" src="https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit" async></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"
|
||||
async>
|
||||
</script>
|
||||
|
||||
{% block scripts '' %}
|
||||
|
||||
|
||||
31
tests/Assets/AssetIntegrityGeneratorTest.php
Normal file
31
tests/Assets/AssetIntegrityGeneratorTest.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Website\Tests\Assets;
|
||||
|
||||
use Doctrine\Website\Assets\AssetIntegrityGenerator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class AssetIntegrityGeneratorTest extends TestCase
|
||||
{
|
||||
/** @var string */
|
||||
private $sourcePath;
|
||||
|
||||
/** @var AssetIntegrityGenerator */
|
||||
private $assetIntegrityGenerator;
|
||||
|
||||
public function testGetAssetIntegrity() : void
|
||||
{
|
||||
$integrity = $this->assetIntegrityGenerator->getAssetIntegrity('/css/style.css');
|
||||
|
||||
self::assertSame('sha384-ypIyGShu7WBNc4JDDLBwHLtFojHsqgcjDrbRH9rt5hizlv05qzZgJKBSkJ0X6czC', $integrity);
|
||||
}
|
||||
|
||||
protected function setUp() : void
|
||||
{
|
||||
$this->sourcePath = __DIR__ . '/../source';
|
||||
|
||||
$this->assetIntegrityGenerator = new AssetIntegrityGenerator($this->sourcePath);
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ class SourceFileRepositoryTest extends TestCase
|
||||
{
|
||||
$files = $this->sortFiles($this->sourceFileRepository->getFiles(''));
|
||||
|
||||
self::assertCount(6, $files);
|
||||
self::assertCount(7, $files);
|
||||
|
||||
self::assertSame('html', $files[0]->getExtension());
|
||||
self::assertSame('/api/inflector.html', $files[0]->getWritePath());
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Website\Tests\Twig;
|
||||
|
||||
use Doctrine\Website\Assets\AssetIntegrityGenerator;
|
||||
use Doctrine\Website\Projects\Project;
|
||||
use Doctrine\Website\Tests\TestCase;
|
||||
use Doctrine\Website\Twig\MainExtension;
|
||||
@@ -14,15 +15,25 @@ class MainExtensionTest extends TestCase
|
||||
/** @var Parsedown */
|
||||
private $parsedown;
|
||||
|
||||
/** @var AssetIntegrityGenerator */
|
||||
private $assetIntegrityGenerator;
|
||||
|
||||
/** @var string */
|
||||
private $sourcePath;
|
||||
|
||||
/** @var MainExtension */
|
||||
private $mainExtension;
|
||||
|
||||
protected function setUp() : void
|
||||
{
|
||||
$this->parsedown = $this->createMock(Parsedown::class);
|
||||
$this->parsedown = $this->createMock(Parsedown::class);
|
||||
$this->assetIntegrityGenerator = $this->createMock(AssetIntegrityGenerator::class);
|
||||
$this->sourcePath = __DIR__ . '/../../source';
|
||||
|
||||
$this->mainExtension = new MainExtension(
|
||||
$this->parsedown
|
||||
$this->parsedown,
|
||||
$this->assetIntegrityGenerator,
|
||||
$this->sourcePath
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
1
tests/source/css/style.css
Normal file
1
tests/source/css/style.css
Normal file
@@ -0,0 +1 @@
|
||||
.test { color: black; }
|
||||
Reference in New Issue
Block a user