mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 11:09:18 +02:00
Implement TFE file access restriction feature (complete)
Requirements: - parametres.php toggle: 'restricted_files_enabled' enables/disables the feature - Public TFE page: when enabled + access_type=Interne, hides files, shows French restriction message + access request form (metadata/synopsis still visible) - ERG emails (@erg.school / @erg.be): auto-approve, send 24h access link immediately - External emails: show justification textarea, create pending request, notify admin - Admin panel /admin/file-access.php: approve/reject requests with optional notes, sends access email on approval (linked from admin nav with pending count badge) Security: - One-time 24h email tokens (used_at + is_valid=0 on first click) - Token redeemed via POST /validate-access (GET shows confirmation page only) - Long-lived 30-day browser session in file_access_sessions table - Cookie: HttpOnly + Secure + SameSite=Strict - CSRF on all mutations, rate limiting on request submission - Audit trail: IP, UA, event, timestamp in file_access_audit Bug fixes: - admin/file-access.php: $vars never extract()ed → page was blank - Template had self-contained head/footer includes (double-include) - Admin approval URL used $requestId instead of $request['thesis_id'] - App::boot() now starts session so CSRF token works on public pages - Dispatcher routes /validate-access and /request-access through front controller
This commit is contained in:
124
app/public/admin/actions/access-request.php
Normal file
124
app/public/admin/actions/access-request.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/**
|
||||
* Access Request Action Handler
|
||||
*
|
||||
* Approve or reject file access requests.
|
||||
*/
|
||||
require_once __DIR__ . '/../../../bootstrap.php';
|
||||
require_once __DIR__ . '/../../../src/AdminAuth.php';
|
||||
AdminAuth::requireLogin();
|
||||
|
||||
if (!isset($_POST['csrf_token'], $_SESSION['csrf_token'])
|
||||
|| !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'message' => 'Token de sécurité invalide']);
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once APP_ROOT . '/src/Database.php';
|
||||
require_once APP_ROOT . '/src/SmtpRelay.php';
|
||||
|
||||
$db = Database::getInstance();
|
||||
|
||||
$requestId = isset($_POST['request_id']) ? (int)$_POST['request_id'] : 0;
|
||||
$action = $_POST['action'] ?? '';
|
||||
$notes = $_POST['admin_notes'] ?? null;
|
||||
|
||||
if ($requestId <= 0 || !in_array($action, ['approve', 'reject'], true)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Données invalides']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$request = $db->getAccessRequestById($requestId);
|
||||
|
||||
if (!$request) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['success' => false, 'message' => 'Demande non trouvée']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'approve') {
|
||||
// Generate token
|
||||
$token = $db->approveAccessRequest($requestId);
|
||||
|
||||
// Send access email to user
|
||||
$thesisTitle = $request['title'];
|
||||
$thesisAuthors = $request['authors'] ?? '';
|
||||
$accessUrl = "https://{$_SERVER['HTTP_HOST']}/validate-access?token={$token}&thesis={$request['thesis_id']}";
|
||||
|
||||
$subject = "Accès accordé - TFE: {$thesisTitle}";
|
||||
$body = buildApprovalEmail($thesisTitle, $thesisAuthors, $accessUrl, $notes);
|
||||
$plain = strip_tags($body);
|
||||
|
||||
SmtpRelay::send($db, $request['email'], $subject, $body, $plain);
|
||||
|
||||
App::flash('success', "Demande approuvée. Email envoyé à {$request['email']}.");
|
||||
|
||||
} elseif ($action === 'reject') {
|
||||
$db->rejectAccessRequest($requestId, $notes);
|
||||
|
||||
// Optionally send rejection email (not implemented for now)
|
||||
|
||||
App::flash('success', "Demande rejetée.");
|
||||
}
|
||||
|
||||
header('Location: /admin/file-access.php');
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('Access request action failed: ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => 'Erreur lors du traitement']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build approval notification email HTML
|
||||
*/
|
||||
function buildApprovalEmail(string $title, string $authors, string $accessUrl, ?string $adminNotes): string {
|
||||
$notesHtml = '';
|
||||
if (!empty($adminNotes)) {
|
||||
$notesHtml = "<p><strong>Note de l'administrateur :</strong><br>" . htmlspecialchars($adminNotes) . "</p>";
|
||||
}
|
||||
|
||||
return <<<HTML
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><meta charset="UTF-8"></head>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
|
||||
<h2 style="color: #2c5282;">Accès accordé au TFE</h2>
|
||||
|
||||
<p>Bonjour,</p>
|
||||
|
||||
<p>Votre demande d'accès au TFE suivant a été approuvée par un administrateur :</p>
|
||||
|
||||
<div style="background: #f7fafc; padding: 15px; border-left: 4px solid #2c5282; margin: 20px 0;">
|
||||
<strong>Titre :</strong> {$title}<br>
|
||||
<strong>Auteur(s) :</strong> {$authors}
|
||||
</div>
|
||||
|
||||
{$notesHtml}
|
||||
|
||||
<p>Pour accéder aux fichiers attachés, veuillez cliquer sur le lien ci-dessous :</p>
|
||||
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href="{$accessUrl}"
|
||||
style="display: inline-block; padding: 12px 30px; background-color: #2c5282;
|
||||
color: white; text-decoration: none; border-radius: 5px; font-weight: bold;">
|
||||
Accéder au TFE
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p><strong>Ce lien est valable 24 heures et à usage unique.</strong> L'accès sur votre appareil sera ensuite conservé 30 jours.</p>
|
||||
|
||||
<p style="margin-top: 30px; color: #666; font-size: 0.9em;">
|
||||
Cordialement,<br>
|
||||
L'équipe XAMXAM - ERG
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
HTML;
|
||||
}
|
||||
@@ -17,7 +17,12 @@ $db = new Database();
|
||||
$section = $_POST['section'] ?? '';
|
||||
|
||||
if ($section === 'formulaire') {
|
||||
$allowed = ['access_type_libre_enabled', 'access_type_interne_enabled', 'access_type_interdit_enabled'];
|
||||
$allowed = [
|
||||
'access_type_libre_enabled',
|
||||
'access_type_interne_enabled',
|
||||
'access_type_interdit_enabled',
|
||||
'restricted_files_enabled'
|
||||
];
|
||||
foreach ($allowed as $key) {
|
||||
$value = isset($_POST[$key]) ? '1' : '0';
|
||||
$db->setSetting($key, $value);
|
||||
|
||||
20
app/public/admin/file-access.php
Normal file
20
app/public/admin/file-access.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../bootstrap.php';
|
||||
require_once __DIR__ . '/../../src/AdminAuth.php';
|
||||
AdminAuth::requireLogin();
|
||||
|
||||
require_once APP_ROOT . '/src/Controllers/FileAccessController.php';
|
||||
|
||||
$controller = FileAccessController::create();
|
||||
$vars = $controller->handle();
|
||||
extract($vars);
|
||||
|
||||
$pageTitle = 'Demandes d\'accès aux fichiers';
|
||||
$isAdmin = true;
|
||||
$bodyClass = 'admin-body';
|
||||
|
||||
require_once APP_ROOT . '/templates/head.php';
|
||||
include APP_ROOT . '/templates/header.php';
|
||||
echo '<link rel="stylesheet" href="/assets/css/file-access.css">';
|
||||
include APP_ROOT . '/templates/admin/file-access.php';
|
||||
require_once APP_ROOT . '/templates/admin/footer.php';
|
||||
Reference in New Issue
Block a user