[FIX] Fix some securiry issues

This commit is contained in:
NADAL Jean-Baptiste
2026-02-18 15:27:55 +01:00
parent 3abc6f6371
commit 039cecc4a6
15 changed files with 2179 additions and 200 deletions

View File

@@ -1,14 +1,20 @@
<?php
class Auth {
private const JWT_SECRET = 'ohmj_secret_key_change_in_production';
private const JWT_ALGO = 'HS256';
private const JWT_EXPIRY = 3600; // 1 hour
private string $usersFile;
private string $jwtSecret;
public function __construct(string $usersFile = null) {
$this->usersFile = $usersFile ?? __DIR__ . '/../config/users.json';
// Load JWT secret from environment variable
$this->jwtSecret = $_ENV['JWT_SECRET'] ?? getenv('JWT_SECRET');
if (empty($this->jwtSecret)) {
throw new Exception('JWT_SECRET environment variable is not configured');
}
}
public function login(string $username, string $password): array {
@@ -45,7 +51,7 @@ class Auth {
// Verify signature
$expectedSignature = base64_encode(
hash_hmac('sha256', "$header.$payload", self::JWT_SECRET, true)
hash_hmac('sha256', "$header.$payload", $this->jwtSecret, true)
);
if (!hash_equals($expectedSignature, $signature)) {
@@ -101,7 +107,7 @@ class Auth {
]));
$signature = base64_encode(
hash_hmac('sha256', "$header.$payload", self::JWT_SECRET, true)
hash_hmac('sha256', "$header.$payload", $this->jwtSecret, true)
);
return "$header.$payload.$signature";

View File

@@ -376,7 +376,7 @@ class ScoreScanner {
return $instruments;
}
public function createScore(string $id, string $name, string $compositor): array {
public function createScore(string $id, string $name, string $compositor, array $pieces = []): array {
$scoreDir = $this->scoresPath . $id;
if (is_dir($scoreDir)) {
@@ -387,7 +387,19 @@ class ScoreScanner {
return ['success' => false, 'error' => 'Failed to create directory'];
}
$iniContent = "[info]\nname = $name\ncompositor = $compositor\n\n[pieces]\ncount = 1\n";
// Security: Sanitize inputs to prevent INI injection
$name = $this->sanitizeIniValue($name);
$compositor = $this->sanitizeIniValue($compositor);
$pieceCount = count($pieces) > 0 ? count($pieces) : 1;
$iniContent = "[info]\nname = $name\ncompositor = $compositor\n\n[pieces]\ncount = $pieceCount\n";
// Add piece names if provided
foreach ($pieces as $piece) {
$num = $piece['number'];
$pieceName = $this->sanitizeIniValue($piece['name']);
$iniContent .= "{$num} = $pieceName\n";
}
if (file_put_contents($scoreDir . '/score.ini', $iniContent) === false) {
return ['success' => false, 'error' => 'Failed to create score.ini'];
@@ -403,6 +415,11 @@ class ScoreScanner {
];
}
private function sanitizeIniValue(string $value): string {
// Remove newlines and carriage returns to prevent INI injection
return str_replace(["\n", "\r", "\x00"], '', $value);
}
public function updateScore(string $scoreId, ?string $name, ?string $compositor): array {
$scoreDir = $this->scoresPath . $scoreId;
$iniFile = $scoreDir . '/score.ini';
@@ -481,6 +498,47 @@ class ScoreScanner {
return ['success' => false, 'error' => 'Score not found'];
}
// Security: Validate file upload
if (!isset($file['tmp_name']) || !isset($file['name'])) {
return ['success' => false, 'error' => 'No file uploaded'];
}
// Security: Check file upload errors
if ($file['error'] !== UPLOAD_ERR_OK) {
return ['success' => false, 'error' => 'Upload error: ' . $file['error']];
}
// Security: Validate MIME type
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if ($mimeType !== 'application/pdf') {
return ['success' => false, 'error' => 'Invalid file type. Only PDF files are allowed'];
}
// Security: Validate extension
$originalExtension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if ($originalExtension !== 'pdf') {
return ['success' => false, 'error' => 'Invalid file extension. Only .pdf files are allowed'];
}
// Security: Check magic bytes (PDF starts with %PDF)
$handle = fopen($file['tmp_name'], 'rb');
if ($handle) {
$header = fread($handle, 4);
fclose($handle);
if ($header !== '%PDF') {
return ['success' => false, 'error' => 'Invalid PDF file header'];
}
}
// Security: Check file size (max 20MB)
$maxSize = 20 * 1024 * 1024; // 20MB
if ($file['size'] > $maxSize) {
return ['success' => false, 'error' => 'File too large. Maximum size is 20MB'];
}
// Create directory structure: scoreId/piece/instrument/version
$targetDir = $scoreDir . '/' . $piece . '/' . $instrument . '/' . $version;
@@ -532,4 +590,84 @@ class ScoreScanner {
return ['success' => false, 'error' => 'Failed to move uploaded file'];
}
public function getScoreFiles(string $scoreId): array {
$scoreDir = $this->scoresPath . $scoreId;
if (!is_dir($scoreDir)) {
return ['success' => false, 'error' => 'Score not found'];
}
$tree = $this->buildFileTree($scoreDir, $scoreId);
return ['success' => true, 'files' => $tree];
}
private function buildFileTree(string $dir, string $scoreId, string $relativePath = ''): array {
$items = [];
$directories = scandir($dir);
foreach ($directories as $item) {
if ($item === '.' || $item === '..') continue;
if ($item === 'score.ini') continue;
$fullPath = $dir . '/' . $item;
$itemRelativePath = $relativePath ? $relativePath . '/' . $item : $item;
if (is_dir($fullPath)) {
$children = $this->buildFileTree($fullPath, $scoreId, $itemRelativePath);
$items[] = [
'name' => $item,
'path' => $itemRelativePath,
'type' => 'folder',
'children' => $children
];
} else {
$items[] = [
'name' => $item,
'path' => $itemRelativePath,
'type' => 'file'
];
}
}
return $items;
}
public function deleteScoreFile(string $scoreId, string $filePath): array {
$scoreDir = $this->scoresPath . $scoreId;
$fullPath = $scoreDir . '/' . $filePath;
if (!file_exists($fullPath)) {
return ['success' => false, 'error' => 'File not found'];
}
if (is_dir($fullPath)) {
return ['success' => false, 'error' => 'Cannot delete directory'];
}
if (unlink($fullPath)) {
$this->cleanupEmptyDirectories($scoreDir, dirname($filePath));
return ['success' => true];
}
return ['success' => false, 'error' => 'Failed to delete file'];
}
private function cleanupEmptyDirectories(string $scoreDir, string $dirPath): void {
while ($dirPath && $dirPath !== '.') {
$fullPath = $scoreDir . '/' . $dirPath;
if (!is_dir($fullPath)) break;
$files = scandir($fullPath);
$files = array_diff($files, ['.', '..']);
if (empty($files)) {
rmdir($fullPath);
$dirPath = dirname($dirPath);
} else {
break;
}
}
}
}