Skip to content

Commit

Permalink
Merge pull request #5 from flownative/feature/flow-7-compatibility
Browse files Browse the repository at this point in the history
Provide compatibility with Flow 7.0
  • Loading branch information
robertlemke authored Feb 26, 2021
2 parents ba468c8 + 8babf4b commit 45dc016
Show file tree
Hide file tree
Showing 9 changed files with 412 additions and 200 deletions.
81 changes: 81 additions & 0 deletions .github/workflows.inactive/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: CI

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
tests:
name: Tests

runs-on: ${{ matrix.os }}

env:
PHP_EXTENSIONS: json, libxml, mbstring
PHP_INI_VALUES: assert.exception=1, zend.assertions=1, pcov.directory=Classes

strategy:
matrix:
os:
- ubuntu-latest

php-version:
- "7.4"

compiler:
- default

dependencies:
- highest

steps:
- name: Checkout
uses: actions/checkout@v2

- name: Override PHP ini values for JIT compiler
if: matrix.compiler == 'jit'
run: echo "PHP_INI_VALUES=assert.exception=1, zend.assertions=1, opcache.enable=1, opcache.enable_cli=1, opcache.optimization_level=-1, opcache.jit=1205, opcache.jit_buffer_size=4096M" >> $GITHUB_ENV

- name: Install PHP with extensions
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
coverage: xdebug
extensions: ${{ env.PHP_EXTENSIONS }}
ini-values: ${{ env.PHP_INI_VALUES }}

- name: Determine composer cache directory on Linux
if: matrix.os == 'ubuntu-latest'
run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV

- name: Determine composer cache directory on Windows
if: matrix.os == 'windows-latest'
run: echo "COMPOSER_CACHE_DIR=~\AppData\Local\Composer" >> $GITHUB_ENV

- name: Cache dependencies installed with composer
uses: actions/cache@v1
with:
path: ${{ env.COMPOSER_CACHE_DIR }}
key: php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.json') }}
restore-keys: |
php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-
- name: Install lowest dependencies with composer
if: matrix.dependencies == 'lowest'
run: composer update --no-ansi --no-interaction --no-progress --prefer-lowest

- name: Install highest dependencies with composer
if: matrix.dependencies == 'highest'
run: composer update --no-ansi --no-interaction --no-progress

- name: Show modules
run: php -i; php -m

- name: Run tests with phpunit
run: vendor/bin/phpunit --coverage-clover=coverage.xml

# - name: Send code coverage report to Codecov.io
# uses: codecov/codecov-action@v1
# with:
# token: ${{ secrets.CODECOV_TOKEN }}
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,20 @@

use Flownative\Prometheus\CollectorRegistry;
use Flownative\Prometheus\Renderer;
use Neos\Flow\Http\Component\ComponentChain;
use Neos\Flow\Http\Component\ComponentContext;
use Neos\Flow\Http\Component\ComponentInterface;
use GuzzleHttp\Psr7\Response;
use Neos\Flow\Http\ContentStream;
use Neos\Flow\Log\Utility\LogEnvironment;
use Neos\Flow\Security\Exception\AccessDeniedException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface;

/**
* HTTP component which renders Prometheus metrics
* PSR-15 middleware which renders Prometheus metrics
*/
class MetricsExporterComponent implements ComponentInterface
class MetricsExporterMiddleware implements MiddlewareInterface
{
/**
* @var CollectorRegistry
Expand Down Expand Up @@ -71,35 +74,35 @@ public function injectLogger(LoggerInterface $logger): void
}

/**
* @param ComponentContext $componentContext
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
* @throws AccessDeniedException
*/
public function handle(ComponentContext $componentContext): void
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if (getenv('FLOWNATIVE_PROMETHEUS_ENABLE') !== 'true') {
return;
return $handler->handle($request);
}

if ($componentContext->getHttpRequest()->getUri()->getPath() !== $this->options['telemetryPath']) {
return;
if ($request->getUri()->getPath() !== $this->options['telemetryPath']) {
return $handler->handle($request);
}

$componentContext->setParameter(ComponentChain::class, 'cancel', true);

if ($this->options['basicAuth']['username'] !== '' && $this->options['basicAuth']['password'] !== '' && $this->authenticateWithBasicAuth($componentContext) === false) {
$response = $this->createResponseWithAuthenticateHeader($componentContext->getHttpResponse());
$componentContext->replaceHttpResponse($response);
return;
if ($this->options['basicAuth']['username'] !== '' && $this->options['basicAuth']['password'] !== '') {
$authenticated = $this->authenticateWithBasicAuth($request);
if (!$authenticated) {
return $this->createResponseWithAuthenticateHeader();
}
}

$response = $this->createResponseWithRenderedMetrics($componentContext->getHttpResponse());
$componentContext->replaceHttpResponse($response);
return $this->createResponseWithRenderedMetrics();
}

/**
* @param ResponseInterface $existingResponse
* @return ResponseInterface
*/
private function createResponseWithRenderedMetrics(ResponseInterface $existingResponse): ResponseInterface
private function createResponseWithRenderedMetrics(): ResponseInterface
{
$renderer = new Renderer();
if ($this->collectorRegistry->hasCollectors()) {
Expand All @@ -111,39 +114,32 @@ private function createResponseWithRenderedMetrics(ResponseInterface $existingRe
$output = "# Flownative Prometheus Metrics Exporter: There are no collectors registered at the registry.\n";
}

return $existingResponse
->withBody(ContentStream::fromContents($output))
->withHeader('Content-Type', 'text/plain; version=' . $renderer->getFormatVersion() . '; charset=UTF-8');
return new Response(
200,
['Content-Type' => 'text/plain; version=' . $renderer->getFormatVersion() . '; charset=UTF-8'],
ContentStream::fromContents($output)
);
}

/**
* @param ResponseInterface $existingResponse
* @return ResponseInterface
*/
private function createResponseWithAuthenticateHeader(ResponseInterface $existingResponse): ResponseInterface
private function createResponseWithAuthenticateHeader(): ResponseInterface
{
return $existingResponse
->withHeader('WWW-Authenticate', 'Basic realm="' . $this->options['basicAuth']['realm'] . '", charset="UTF-8"');
return new Response(200, ['WWW-Authenticate' => 'Basic realm="' . $this->options['basicAuth']['realm'] . '", charset="UTF-8"']);
}

/**
* @param ComponentContext $componentContext
* @param ServerRequestInterface $request
* @return bool
* @throws AccessDeniedException
*/
private function authenticateWithBasicAuth(ComponentContext $componentContext): bool
private function authenticateWithBasicAuth(ServerRequestInterface $request): bool
{
$authorizationHeaders = $componentContext->getHttpRequest()->getHeader('Authorization');

// For backwards-compatibility with Flow < 6.x:
if ($authorizationHeaders === null) {
$authorizationHeaders = [];
} elseif (is_string($authorizationHeaders)) {
$authorizationHeaders = [$authorizationHeaders];
}

$authorizationHeaders = $request->getHeader('Authorization');
if ($authorizationHeaders === []) {
if ($this->logger) {
$this->logger->info('No authorization header found, asking for authentication for Prometheus telemetry endpoint');
$this->logger->info('No authorization header found, asking for authentication for Prometheus telemetry endpoint', LogEnvironment::fromMethodName(__METHOD__));
}
return false;
}
Expand All @@ -157,7 +153,7 @@ private function authenticateWithBasicAuth(ComponentContext $componentContext):

if (!isset($authorizationHeader)) {
if ($this->logger) {
$this->logger->warning('Failed authenticating for Prometheus telemetry endpoint, no "Basic" authorization header found');
$this->logger->warning('Failed authenticating for Prometheus telemetry endpoint, no "Basic" authorization header found', LogEnvironment::fromMethodName(__METHOD__));
}
return false;
}
Expand All @@ -169,13 +165,11 @@ private function authenticateWithBasicAuth(ComponentContext $componentContext):
$givenUsername !== $this->options['basicAuth']['username'] ||
$givenPassword !== $this->options['basicAuth']['password']
) {
$componentContext->replaceHttpResponse($componentContext->getHttpResponse()->withStatus(403));
if ($this->logger) {
$this->logger->warning('Failed authenticating for Prometheus telemetry endpoint: wrong username or password');
}
return false;
throw new AccessDeniedException('Failed authenticating for Prometheus telemetry endpoint: wrong username or password', 1614338257);
}

return true;
}
}
14 changes: 12 additions & 2 deletions Configuration/Objects.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
Flownative\Prometheus\Http\MetricsExporterComponent:
Flownative\Prometheus\Http\MetricsExporterMiddleware:
arguments:
1:
value:
## Path at which metrics are published for scraping
telemetryPath: '/metrics'

basicAuth:
username: ''
password: ''
realm: 'Flownative Prometheus Plugin'

properties:
collectorRegistry:
object: Flownative\Prometheus\DefaultCollectorRegistry


Flownative\Prometheus\DefaultCollectorRegistry:
arguments:
1:
Expand Down
27 changes: 13 additions & 14 deletions Configuration/Settings.Http.yaml
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
Neos:
Flow:
http:
chain:
'process':
chain:
'Flownative.Prometheus:metricsExporter':
'position': 'before routing'
component: 'Flownative\Prometheus\Http\MetricsExporterComponent'
componentOptions:
middlewares:
'Flownative.Prometheus:metricsExporter':
'position': 'before routing'
middleware: 'Flownative\Prometheus\Http\MetricsExporterMiddleware'

## Path at which metrics are published for scraping
telemetryPath: '/metrics'

basicAuth:
username: ''
password: ''
realm: 'Flownative Prometheus Plugin'
Flownative:
OpenIdConnect:
Client:
middleware:
authenticationProviderName: 'Flownative.OpenIdConnect.Client:OidcProvider'
cookie:
name: 'flownative_oidc_jwt'
secure: true
sameSite: 'strict'
Loading

0 comments on commit 45dc016

Please sign in to comment.