Use style element to create css class for dynamic inline style

The `style` element with `nonce` attribute is used to create css classes for
inline styles that are not static. This prevents Content-Security-Policy violations.
This commit is contained in:
raviks789 2023-07-18 10:30:27 +02:00
parent 459f4198c3
commit 33a5f765b9
4 changed files with 192 additions and 51 deletions

View File

@ -6,7 +6,9 @@ namespace Icinga\Web\Widget\Chart;
use DateInterval; use DateInterval;
use DateTime; use DateTime;
use Icinga\Util\Color; use Icinga\Util\Color;
use Icinga\Util\Csp;
use Icinga\Web\Widget\AbstractWidget; use Icinga\Web\Widget\AbstractWidget;
use ipl\Web\Style;
/** /**
* Display a colored grid that visualizes a set of values for each day * Display a colored grid that visualizes a set of values for each day
@ -32,6 +34,9 @@ class HistoryColorGrid extends AbstractWidget
private $color; private $color;
public $opacity = 1.0; public $opacity = 1.0;
/** @var array<string, array<string, string>> History grid css rulesets */
protected $rulesets = [];
public function __construct($color = '#51e551', $start = null, $end = null) public function __construct($color = '#51e551', $start = null, $end = null)
{ {
$this->setColor($color); $this->setColor($color);
@ -123,18 +128,30 @@ class HistoryColorGrid extends AbstractWidget
{ {
if (array_key_exists($day, $this->data) && $this->data[$day]['value'] > 0) { if (array_key_exists($day, $this->data) && $this->data[$day]['value'] > 0) {
$entry = $this->data[$day]; $entry = $this->data[$day];
return '<a ' . $this->rulesets['.grid-day-with-entry-' . $entry['value']] = [
'style="background-color: ' . $this->calculateColor($entry['value']) . ';' 'background-color' => $this->calculateColor($entry['value']),
. ' opacity: ' . $this->opacity . ';" ' . 'opacity' => $this->opacity
'aria-label="' . $entry['caption'] . '" ' . ];
'title="' . $entry['caption'] . '" ' .
'href="' . $entry['url'] . '" ' . return '<a class="grid-day-with-entry-'
'></a>'; . $entry['value']
. '" '
. 'aria-label="' . $entry['caption']
. '" '
. 'title="' . $entry['caption']
. '" '
. 'href="' . $entry['url']
. '" '
. '"></a>';
} else { } else {
return '<span ' . if (! isset($this->rulesets['.grid-day-no-entry'])) {
'style="background-color: ' . $this->calculateColor(0) . '; opacity: ' . $this->opacity . ';" ' . $this->rulesets['.grid-day-no-entry'] = [
'title="No entries for ' . $day . '" ' . 'background-color' => $this->calculateColor(0),
'></span>'; 'opacity' => $this->opacity
];
}
return '<span class="grid-day-no-entry"' . ' title="No entries for ' . $day . '"></span>';
} }
} }
@ -366,8 +383,18 @@ class HistoryColorGrid extends AbstractWidget
} }
$grid = $this->createGrid(); $grid = $this->createGrid();
if ($this->orientation === self::ORIENTATION_HORIZONTAL) { if ($this->orientation === self::ORIENTATION_HORIZONTAL) {
return $this->renderHorizontal($grid); $html = $this->renderHorizontal($grid);
} else {
$html = $this->renderVertical($grid);
} }
return $this->renderVertical($grid);
$historyGridStyle = new Style();
$historyGridStyle->setNonce(Csp::getStyleNonce());
foreach ($this->rulesets as $selector => $properties) {
$historyGridStyle->add($selector, $properties);
}
return $html . $historyGridStyle;
} }
} }

View File

@ -1,9 +1,15 @@
<?php <?php
use Icinga\Util\Csp;
use Icinga\Web\Url; use Icinga\Web\Url;
use Icinga\Util\Color; use Icinga\Util\Color;
use ipl\Web\Style;
$groupInfo = $timeline->getGroupInfo(); $groupInfo = $timeline->getGroupInfo();
$firstRow = ! $beingExtended; $firstRow = ! $beingExtended;
$timelineStyle = (new Style())
->setNonce(Csp::getStyleNonce())
->setModule('monitoring');
if (! $beingExtended && !$this->compact): ?> if (! $beingExtended && !$this->compact): ?>
<div class="controls"> <div class="controls">
@ -79,24 +85,72 @@ if (! $beingExtended && !$this->compact): ?>
<?php foreach ($groupInfo as $groupName => $labelAndColor): ?> <?php foreach ($groupInfo as $groupName => $labelAndColor): ?>
<?php if (array_key_exists($groupName, $timeInfo[1])): ?> <?php if (array_key_exists($groupName, $timeInfo[1])): ?>
<?php <?php
$styleId = uniqid();
$circleWidth = $timeline->calculateCircleWidth($timeInfo[1][$groupName], 2); $circleWidth = $timeline->calculateCircleWidth($timeInfo[1][$groupName], 2);
$extrapolatedCircleWidth = $timeline->getExtrapolatedCircleWidth($timeInfo[1][$groupName], 2); $extrapolatedCircleWidth = $timeline->getExtrapolatedCircleWidth($timeInfo[1][$groupName], 2);
?> ?>
<?php if ($firstRow && $extrapolatedCircleWidth !== $circleWidth): ?> <?php if ($firstRow && $extrapolatedCircleWidth !== $circleWidth): ?>
<div class="circle-box" style="width: <?= $extrapolatedCircleWidth; ?>;"> <?php
<div class="outer-circle extrapolated <?= $timeInfo[1][$groupName]->getClass() ?>" style="<?= sprintf( $timelineStyle->add(
'width: %2$s; height: %2$s; margin-top: -%1$Fem;', "#circle-box-$styleId",
(float) substr($extrapolatedCircleWidth, 0, -2) / 2, ['width' => $extrapolatedCircleWidth]
$extrapolatedCircleWidth );
); ?>">
$timelineStyle->add(
"#outer-circle-$styleId",
[
'width' => $extrapolatedCircleWidth,
'height' => $extrapolatedCircleWidth,
'margin-top' => sprintf(
'-%Fem',
(float)substr($extrapolatedCircleWidth, 0, -2) / 2
)
]
);
?>
<div id="circle-box-<?= $styleId ?>" class="circle-box">
<div id="outer-circle-<?= $styleId ?>" class="outer-circle extrapolated <?= $timeInfo[1][$groupName]->getClass() ?>">
<?php else: ?> <?php else: ?>
<div class="circle-box" style="width: <?= $circleWidth; ?>;"> <?php
<div class="outer-circle" style="<?= sprintf( $timelineStyle->add(
'width: %2$s; height: %2$s; margin-top: -%1$Fem;', "#circle-box-$styleId",
(float) substr($circleWidth, 0, -2) / 2, ['width' => $circleWidth]
$circleWidth );
); ?>">
$timelineStyle->add(
"#outer-circle-$styleId",
[
'width' => $circleWidth,
'height' => $circleWidth,
'margin-top' => sprintf(
'-%Fem',
(float)substr($circleWidth, 0, -2) / 2
)
]
);
?>
<div id="circle-box-<?= $styleId ?>" class="circle-box">
<div id="outer-circle-<?= $styleId ?>" class="outer-circle">
<?php endif ?> <?php endif ?>
<?php
$timelineStyle->add(
"#inner-circle-$styleId",
[
'width' => $circleWidth,
'height' => $circleWidth,
'margin-top' => sprintf(
'-%Fem',
(float)substr($circleWidth, 0, -2) / 2
),
'margin-left' => sprintf(
'-%Fem',
(float)substr($circleWidth, 0, -2) / 2
),
]
);
?>
<?= $this->qlink( <?= $this->qlink(
'', '',
$timeInfo[1][$groupName]->getDetailUrl(), $timeInfo[1][$groupName]->getDetailUrl(),
@ -112,12 +166,8 @@ $extrapolatedCircleWidth = $timeline->getExtrapolatedCircleWidth($timeInfo[1][$g
strtolower($labelAndColor['label']), strtolower($labelAndColor['label']),
$titleTime $titleTime
), ),
'class' => 'inner-circle ' . $timeInfo[1][$groupName]->getClass(), 'id' => "inner-circle-$styleId",
'style' => sprintf( 'class' => "inner-circle " . $timeInfo[1][$groupName]->getClass()
'width: %2$s; height: %2$s; margin-top: -%1$Fem; margin-left: -%1$Fem;',
(float) substr($circleWidth, 0, -2) / 2,
(string) $circleWidth
)
) )
); ?> ); ?>
</div> </div>
@ -143,3 +193,4 @@ $extrapolatedCircleWidth = $timeline->getExtrapolatedCircleWidth($timeInfo[1][$g
</div> </div>
</div> </div>
<?php endif ?> <?php endif ?>
<?= $timelineStyle; ?>

View File

@ -1,12 +1,17 @@
<?php <?php
use Icinga\Util\Csp;
use Icinga\Web\Notification; use Icinga\Web\Notification;
use ipl\Web\Style;
$pages = $wizard->getPages(); $pages = $wizard->getPages();
$finished = isset($success); $finished = isset($success);
$configPages = array_slice($pages, 3, count($pages) - 4, true); $configPages = array_slice($pages, 3, count($pages) - 4, true);
$currentPos = array_search($wizard->getCurrentPage(), $pages, true); $currentPos = array_search($wizard->getCurrentPage(), $pages, true);
list($configPagesLeft, $configPagesRight) = array_chunk($configPages, (int)(count($configPages) / 2), true); list($configPagesLeft, $configPagesRight) = array_chunk($configPages, (int)(count($configPages) / 2), true);
$setupStyle = (new Style())
->setSelector('.setup-header > .progress-bar')
->setNonce(Csp::getStyleNonce());
$visitedPages = array_keys($wizard->getPageData()); $visitedPages = array_keys($wizard->getPageData());
$maxProgress = max(array_merge([0], array_keys(array_filter( $maxProgress = max(array_merge([0], array_keys(array_filter(
@ -14,6 +19,13 @@ $maxProgress = max(array_merge([0], array_keys(array_filter(
function ($page) use ($visitedPages) { return in_array($page->getName(), $visitedPages); } function ($page) use ($visitedPages) { return in_array($page->getName(), $visitedPages); }
)))); ))));
$setupStyle->add(
'.width-percent-10',
['width' => '10%']
)->add(
'.width-percent-60',
['width' => '60%']
);
?> ?>
<div id="setup-content-wrapper" data-base-target="layout"> <div id="setup-content-wrapper" data-base-target="layout">
<div class="setup-header"> <div class="setup-header">
@ -76,14 +88,43 @@ $maxProgress = max(array_merge([0], array_keys(array_filter(
$pos < $maxProgress ? ' visited' : ($currentPos > 2 ? ' active' : '') $pos < $maxProgress ? ' visited' : ($currentPos > 2 ? ' active' : '')
); ?> ); ?>
<?php if ($page === $firstPage): ?> <?php if ($page === $firstPage): ?>
<div class="line left<?= $stateClass; ?>" style="float: left; width: <?= sprintf( <?php
'%.2F', $setupStyle->add(
'.step .left-line-' . $pos,
[
'float' => 'left',
'width' => sprintf(
'%.2F%%',
100 - (count($configPagesLeft) - 1) * $lineWidth 100 - (count($configPagesLeft) - 1) * $lineWidth
); ?>%; margin-right: 0"></div> ),
'margin-right' => 0
]
);
?>
<div class="line left<?= $stateClass; ?> left-line-<?= $pos; ?>"></div>
<?php elseif ($page === $lastPage): ?> <?php elseif ($page === $lastPage): ?>
<div class="line<?= $stateClass; ?>" style="float: left; width: <?= $lineWidth; ?>%; margin-right: -0.1em;"></div> <?php
$setupStyle->add(
'.step .left-line-' . $pos,
[
'float' => 'left',
'width' => $lineWidth . '%',
'margin-right' => '-0.1em'
]
);
?>
<div class="line<?= $stateClass; ?> left-line-<?= $pos; ?>"></div>
<?php else: ?> <?php else: ?>
<div class="line<?= $stateClass; ?>" style="float: left; width: <?= $lineWidth; ?>%;"></div> <?php
$setupStyle->add(
'.step .left-line-' . $pos,
[
'float' => 'left',
'width' => $lineWidth . '%'
]
);
?>
<div class="line<?= $stateClass; ?> left-line-<?= $pos; ?>"></div>
<?php endif ?> <?php endif ?>
<?php endforeach ?> <?php endforeach ?>
</td> </td>
@ -106,14 +147,43 @@ $maxProgress = max(array_merge([0], array_keys(array_filter(
$pos < $maxProgress ? ' visited' : ($currentPos > 2 ? ' active' : '') $pos < $maxProgress ? ' visited' : ($currentPos > 2 ? ' active' : '')
); ?> ); ?>
<?php if ($page === $firstPage): ?> <?php if ($page === $firstPage): ?>
<div class="line<?= $stateClass; ?>" style="float: left; width: <?= $lineWidth; ?>%; margin-left: -0.1em;"></div> <?php
$setupStyle->add(
'.step .right-line-' . $pos,
[
'float' => 'left',
'width' => $lineWidth . '%',
'margin-right' => '-0.1em'
]
);
?>
<div class="line<?= $stateClass; ?> right-line-<?= $pos; ?>"></div>
<?php elseif ($page === $lastPage): ?> <?php elseif ($page === $lastPage): ?>
<div class="line right<?= $stateClass; ?>" style="float: left; width: <?= sprintf( <?php
'%.2F', $setupStyle->add(
'.step .right-line-' . $pos,
[
'float' => 'left',
'width' => sprintf(
'%.2F%%',
100 - (count($configPagesRight) - 1) * $lineWidth 100 - (count($configPagesRight) - 1) * $lineWidth
); ?>%; margin-left: 0;"></div> ),
'margin-right' => 0
]
);
?>
<div class="line right<?= $stateClass; ?> right-line-<?= $pos; ?>"></div>
<?php else: ?> <?php else: ?>
<div class="line<?= $stateClass; ?>" style="float: left; width: <?= $lineWidth; ?>%;"></div> <?php
$setupStyle->add(
'.step .right-line-' . $pos,
[
'float' => 'left',
'width' => $lineWidth . '%'
]
);
?>
<div class="line<?= $stateClass; ?> right-line-<?= $pos; ?>"></div>
<?php endif ?> <?php endif ?>
<?php endforeach ?> <?php endforeach ?>
</td> </td>
@ -151,3 +221,4 @@ $maxProgress = max(array_merge([0], array_keys(array_filter(
} }
?></ul> ?></ul>
</div> </div>
<?= $setupStyle; ?>

View File

@ -114,14 +114,6 @@
} }
} }
.width-percent-10 {
width: 10%;
}
.width-percent-60 {
width: 60%;
}
.setup-content { .setup-content {
padding: 1.5em 10em 0 10em; padding: 1.5em 10em 0 10em;