Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry, access denied, unauthenticated access attempts) - Add UrlHelper for secure URL generation with host validation against configurable ALLOWED_HOSTS whitelist - Add OutputHelper with consistent XSS-safe escaping functions (h, attr, json, url, css, truncate, date, cssClass) - Add validation to AuditLogModel query parameters (pagination limits, date format validation, action/entity type validation, IP sanitization) - Add APP_DOMAIN and ALLOWED_HOSTS configuration options Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
99
helpers/UrlHelper.php
Normal file
99
helpers/UrlHelper.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/**
|
||||
* UrlHelper - URL and domain utilities
|
||||
*
|
||||
* Provides secure URL generation with host validation.
|
||||
*/
|
||||
class UrlHelper {
|
||||
/**
|
||||
* Get the application base URL with validated host
|
||||
*
|
||||
* Uses APP_DOMAIN from config if set, otherwise validates HTTP_HOST
|
||||
* against ALLOWED_HOSTS whitelist.
|
||||
*
|
||||
* @return string Base URL (e.g., "https://example.com")
|
||||
*/
|
||||
public static function getBaseUrl(): string {
|
||||
$protocol = self::getProtocol();
|
||||
$host = self::getValidatedHost();
|
||||
|
||||
return "{$protocol}://{$host}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current protocol (http or https)
|
||||
*
|
||||
* @return string 'https' or 'http'
|
||||
*/
|
||||
public static function getProtocol(): string {
|
||||
if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
|
||||
return 'https';
|
||||
}
|
||||
if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
|
||||
return 'https';
|
||||
}
|
||||
if (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) {
|
||||
return 'https';
|
||||
}
|
||||
return 'http';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validated hostname
|
||||
*
|
||||
* Priority:
|
||||
* 1. APP_DOMAIN from config (if set)
|
||||
* 2. HTTP_HOST if it passes validation
|
||||
* 3. First allowed host as fallback
|
||||
*
|
||||
* @return string Validated hostname
|
||||
*/
|
||||
public static function getValidatedHost(): string {
|
||||
$config = $GLOBALS['config'] ?? [];
|
||||
|
||||
// Use configured APP_DOMAIN if available
|
||||
if (!empty($config['APP_DOMAIN'])) {
|
||||
return $config['APP_DOMAIN'];
|
||||
}
|
||||
|
||||
// Get allowed hosts
|
||||
$allowedHosts = $config['ALLOWED_HOSTS'] ?? ['localhost'];
|
||||
|
||||
// Validate HTTP_HOST against whitelist
|
||||
$httpHost = $_SERVER['HTTP_HOST'] ?? '';
|
||||
|
||||
// Strip port if present for comparison
|
||||
$hostWithoutPort = preg_replace('/:\d+$/', '', $httpHost);
|
||||
|
||||
if (in_array($hostWithoutPort, $allowedHosts, true)) {
|
||||
return $httpHost;
|
||||
}
|
||||
|
||||
// Log suspicious host header
|
||||
if (!empty($httpHost) && $httpHost !== 'localhost') {
|
||||
error_log("UrlHelper: Rejected HTTP_HOST '{$httpHost}' - not in allowed hosts");
|
||||
}
|
||||
|
||||
// Return first allowed host as fallback
|
||||
return $allowedHosts[0] ?? 'localhost';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a full URL for a ticket
|
||||
*
|
||||
* @param string $ticketId Ticket ID
|
||||
* @return string Full ticket URL
|
||||
*/
|
||||
public static function ticketUrl(string $ticketId): string {
|
||||
return self::getBaseUrl() . '/ticket/' . urlencode($ticketId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current request is using HTTPS
|
||||
*
|
||||
* @return bool True if HTTPS
|
||||
*/
|
||||
public static function isSecure(): bool {
|
||||
return self::getProtocol() === 'https';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user