Files
wp-prometheus/src/Endpoint/MetricsEndpoint.php

149 lines
4.0 KiB
PHP
Raw Normal View History

<?php
/**
* Metrics endpoint class.
*
* @package WP_Prometheus
*/
namespace Magdev\WpPrometheus\Endpoint;
use Magdev\WpPrometheus\Metrics\Collector;
// Prevent direct file access.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* MetricsEndpoint class.
*
* Provides the /metrics endpoint for Prometheus scraping.
*/
class MetricsEndpoint {
/**
* Metrics collector instance.
*
* @var Collector
*/
private Collector $collector;
/**
* Constructor.
*
* @param Collector $collector Metrics collector instance.
*/
public function __construct( Collector $collector ) {
$this->collector = $collector;
$this->init_hooks();
}
/**
* Initialize WordPress hooks.
*
* @return void
*/
private function init_hooks(): void {
add_action( 'init', array( $this, 'register_endpoint' ) );
add_action( 'template_redirect', array( $this, 'handle_request' ) );
}
/**
* Register the metrics endpoint rewrite rule.
*
* @return void
*/
public function register_endpoint(): void {
add_rewrite_rule(
'^metrics/?$',
'index.php?wp_prometheus_metrics=1',
'top'
);
add_rewrite_tag( '%wp_prometheus_metrics%', '([^&]+)' );
}
/**
* Handle the metrics endpoint request.
*
* @return void
*/
public function handle_request(): void {
if ( ! get_query_var( 'wp_prometheus_metrics' ) ) {
return;
}
// Authenticate the request.
if ( ! $this->authenticate() ) {
status_header( 401 );
header( 'WWW-Authenticate: Bearer realm="WP Prometheus Metrics"' );
header( 'Content-Type: text/plain; charset=utf-8' );
echo 'Unauthorized';
exit;
}
// Output metrics.
status_header( 200 );
header( 'Content-Type: text/plain; version=0.0.4; charset=utf-8' );
header( 'Cache-Control: no-cache, no-store, must-revalidate' );
header( 'Pragma: no-cache' );
header( 'Expires: 0' );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Prometheus format.
echo $this->collector->render();
exit;
}
/**
* Authenticate the metrics request.
*
* @return bool
*/
private function authenticate(): bool {
$auth_token = get_option( 'wp_prometheus_auth_token', '' );
// If no token is set, deny access.
if ( empty( $auth_token ) ) {
return false;
}
// Check for Bearer token in Authorization header.
$auth_header = $this->get_authorization_header();
if ( ! empty( $auth_header ) && preg_match( '/Bearer\s+(.*)$/i', $auth_header, $matches ) ) {
return hash_equals( $auth_token, $matches[1] );
}
// Check for token in query parameter (less secure but useful for testing).
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Auth token check.
if ( isset( $_GET['token'] ) && hash_equals( $auth_token, sanitize_text_field( wp_unslash( $_GET['token'] ) ) ) ) {
return true;
}
return false;
}
/**
* Get the Authorization header from the request.
*
* @return string
*/
private function get_authorization_header(): string {
if ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) ) {
return sanitize_text_field( wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ) );
}
if ( isset( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ) {
return sanitize_text_field( wp_unslash( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) );
}
if ( function_exists( 'apache_request_headers' ) ) {
$headers = apache_request_headers();
if ( isset( $headers['Authorization'] ) ) {
return sanitize_text_field( $headers['Authorization'] );
}
}
return '';
}
}