Un plan technique accessible par simple URL directe, sans le moindre contrôle d’accès : c’est ce qu’une entreprise de charpente-couverture a découvert après qu’un client concurrent eut mis la main sur un document censé rester réservé aux clients ayant réellement un projet engagé. Le fichier PDF était référencé par erreur dans un moteur de recherche d’images, sans qu’aucune condition n’en limite la diffusion.
Le vrai problème n’était pas la protection du fichier lui-même — cela reste un sujet distinct, souvent traité par un filigrane ou une restriction serveur — mais l’absence totale de condition d’accès. La solution retenue : un lien de téléchargement qui n’existe tout simplement pas tant qu’un rendez-vous n’a pas été confirmé par l’équipe commerciale.
Générer un jeton d’accès à la confirmation du rendez-vous
Le rendez-vous est un type de contenu personnalisé rendez_vous, avec un champ statut qui passe de en_attente à confirme lorsque l’équipe valide manuellement la demande depuis l’administration. C’est précisément ce changement de statut qui déclenche la génération du jeton de téléchargement.
add_action( 'ch_rendez_vous_confirme', function ( $rdv_id ) {
$plan_id = get_post_meta( $rdv_id, 'plan_associe', true );
if ( ! $plan_id ) {
return;
}
$jeton = wp_generate_password( 32, false );
$expiration = time() + ( 48 * HOUR_IN_SECONDS );
update_post_meta( $rdv_id, 'jeton_telechargement', $jeton );
update_post_meta( $rdv_id, 'jeton_expiration', $expiration );
$lien = add_query_arg( array(
'ch_plan' => $plan_id,
'jeton' => $jeton,
), home_url( '/telecharger-plan/' ) );
// Envoi du lien par e-mail au client, dans une fonction séparée.
ch_envoyer_lien_plan( $rdv_id, $lien );
} );

Vérifier le jeton avant de servir le fichier
La page /telecharger-plan/ ne sert jamais directement un fichier stocké dans la médiathèque publique. Elle vérifie le jeton, contrôle l’expiration, puis transmet le fichier en flux depuis un dossier hors de portée d’un accès direct par URL.
add_action( 'template_redirect', function () {
if ( ! is_page( 'telecharger-plan' ) ) {
return;
}
$jeton_recu = isset( $_GET['jeton'] ) ? sanitize_text_field( $_GET['jeton'] ) : '';
$rdv = get_posts( array(
'post_type' => 'rendez_vous',
'meta_key' => 'jeton_telechargement',
'meta_value' => $jeton_recu,
'numberposts' => 1,
) );
if ( empty( $rdv ) ) {
wp_die( 'Lien invalide ou expiré.', 403 );
}
$expiration = get_post_meta( $rdv[0]->ID, 'jeton_expiration', true );
if ( time() > $expiration ) {
wp_die( 'Ce lien a expiré. Contactez votre conseiller pour un nouvel envoi.', 403 );
}
$chemin_fichier = get_attached_file( get_post_meta( $rdv[0]->ID, 'plan_associe', true ) );
header( 'Content-Type: application/pdf' );
header( 'Content-Disposition: attachment; filename="plan-technique.pdf"' );
readfile( $chemin_fichier );
exit;
} );
Tracer les consultations sans excès
L’entreprise souhaitait garder une trace des téléchargements, sans mettre en place un outil d’analyse complexe. Une simple ligne enregistrée à chaque accès réussi suffit, stockée comme métadonnée du rendez-vous concerné.
- Date et heure du téléchargement effectif, différente de la date de confirmation du rendez-vous.
- Adresse IP tronquée, conservée uniquement à des fins de diagnostic technique en cas de contestation.
- Nombre de tentatives avec un jeton expiré, un indicateur utile pour repérer un lien qui circule au-delà du client concerné.
update_post_meta( $rdv[0]->ID, 'derniere_consultation', current_time( 'mysql' ) );
Pourquoi un jeton plutôt qu’une simple case protégée par mot de passe
Une protection par mot de passe partagé aurait été plus rapide à développer, mais elle repose sur la discrétion du client, qui n’a aucune obligation de garder l’information confidentielle. Le jeton, lui, est unique par rendez-vous, expire de lui-même, et ne dépend d’aucune vigilance humaine pour rester efficace après quarante-huit heures.
Un lien qui expire protège mieux qu’un mot de passe qu’on espère garder secret : la sécurité par défaut vaut toujours mieux que la sécurité par confiance.
En résumé
Un champ de statut, un jeton généré à la confirmation et une vérification stricte avant l’envoi du fichier : ce mécanisme a suffi à fermer une fuite qui aurait pu, à terme, coûter bien plus cher qu’un après-midi de développement. Il ne remplace pas une protection anti-copie du document une fois téléchargé, qui reste un enjeu distinct, mais il évite déjà l’essentiel des diffusions accidentelles.