Cacher son URL de connexion dans les emails RGPD de WordPress (et un peu plus)

Nous le savons, WordPress est un outil formidable qui propose des évolutions toujours appréciables (ou pas). La dernière en date, ne déroge pas à la règle surtout quand elle concerne la conformité RGPD 😃 
Si vous n’avez pas encore fait le tour du sujet, voici un article de monsieur Jb Audras : WordPress version 4.9.6/7 : de nouveaux outils pour l’application du RGPD, un autre article concernant WooCommerce : WooCommerce 3.4 GDPR featureset la liste des choses à venir : Proposed Privacy Roadmap 
C’est quand même cool tout ça !

Comme toutes nouvelles fonctionnalités, même si celle-ci facilite bien la vie aux administrateurs grâce à une interface soignée, l’envers du décor pour les développeurs est un peu moins réjouissant :

  1. WordPress ne fournit aucune aide pour enregistrer une demande venant de votre site. Pourquoi WP le fait-il pour les commentaires et pas avec ça finalement ? Je vous rassure, la communauté a déjà pensé à ce point : GDPR Data Request Form
  2. La page « Politique de confidentialité » est considérée comme uniquement éditable par les administrateurs : https://boiteaweb.fr/manage-privacy-options-roles-gdpr-105235.html
  3. Lorsqu’un utilisateur WP demande la suppression de ses données, son compte n’est ni supprimé, ni anonymisé et ses contenus lui sont toujours associés (vaste sujet hein).
  4. Tout le reste… Exemple le ticket 44354 et ceux listés dedans.

Mais le plus problématique pour moi à ce moment est la divulgation de l’URL de connexion dans un email « public »
Voici ce que WordPress envoye aux visiteurs (n’importe qui) qui vont effectuer une demande d’export :

Bonjour,

Une demande a été faite afin d’effectuer les actions suivantes sur votre compte :

Exporter les données

Pour confirmer cela, veuillez cliquer sur le lien suivant :
https://www.monsite.test/wp-admin/wp-login.php?action=confirmaction&request_id=289&confirm_key=8Gq7SGilB2blScR7owW4

Si vous ne souhaitez pas effectuer cette action, vous pouvez ignorer et supprimer cet e-mail en tout sécurité.

Cet e-mail a été envoyé à xavier@monsite.test.

Cordialement,
L’équipe de monsite_test
https://www.monsite.test

Quel malheur de voir apparaître son URL de connexion et cela même si on utilise une extension pour la cacher 😥 
En attendant que les extentions concernées (ex.: Move Login) se mettent à jour et pour ceux qui n’utilisent pas d’extension, voici comment j’ai abordé le sujet/problème :

Avant toutes choses, nous allons demander à WP de stocker les archives d’exports ailleurs que dans le répertoire par défaut wp-personal-data-exports. C’est un peu parano mais après tout ce sont des données bien plus sensibles que les images de notre site.

/**
 * Returns the directory used to store personal data export files.
 *
 * @return string Exports directory.
 */
function thistle_privacy_exports_dir() {
    $upload_dir  = wp_upload_dir();
    $exports_dir = trailingslashit( $upload_dir['basedir'] ) . 'gprd-exports/';

    return apply_filters( 'thistle_privacy_exports_dir', $exports_dir );
}

/**
 * Returns the URL of the directory used to store personal data export files.
 *
 * @return string Exports directory URL.
 */
function thistle_privacy_exports_url() {
    $upload_dir  = wp_upload_dir();
    $exports_url = trailingslashit( $upload_dir['baseurl'] ) . 'gprd-exports/';

    return apply_filters( 'thistle_privacy_exports_url', $exports_url );
}

/**
 * Overrides some default WP choices:
 * - the directory used to store personal data export files.
 * - the URL of the directory used to store personal data export files.
 */
add_filter( 'wp_privacy_exports_dir', 'thistle_privacy_exports_dir' );
add_filter( 'wp_privacy_exports_url', 'thistle_privacy_exports_url' );

Evidemment, vous pouvez remplacer gprd-exports par ce que vous voulez 😉

Maintenant, le plus important, nous allons demander à WP d’enregistrer deux alias :

  1. le premier pour cacher wp-login.php. Ce qui signifie qu’en nous rendant à cette adresse https://www.monsite.test/donnes-personnelles/, nous verrons notre page de confirmation d’action RGPD.
  2. le second pour cacher le dossier où sont stockées les archives des données exportées. Les archives stockées dans le répertoire gprd-exports seront donc aussi accessibles sur l’URL https://www.monsite.test/donnes-personnelles/export/*.zip.
/**
 *
 *
 * @return string
 */
function thistle_gprd_make_link_relative( $link ) {
  return ltrim( wp_make_link_relative( $link ), '/' );
}

/**
 * Returns the public URL which will be used by the users to confirm
 * their action about their personal data.
 *
 * @return string
 */
function thistle_privacy_confirm_public_url() {
  $confirm_url = home_url( 'donnees-personnelles/' );

  return apply_filters( 'thistle_privacy_confirm_public_url', $confirm_url );
}

/**
 * Returns the public URL which will be used by the users to download
 * their personal data export file.
 *
 * @return string
 */
function thistle_privacy_exports_public_url() {
  $exports_url = trailingslashit( thistle_privacy_confirm_public_url() ) . 'export/';

  return apply_filters( 'thistle_privacy_exports_public_url', $exports_url );
}

/**
 * Adds rewrite rules to hide :
 *  - the URL of login page
 *  - the URL of the directory used to store personal data export files.
 *
 * for non-registered users. It's a bit paranoiac, but if you have activated a plugin
 * to hide/move your login page, you will not want to display it in a public email ;)
 *
 * @param void
 * @return void
 */
function thistle_rewrite_privacy_urls() {
    // Export URL
    $exports_rel_url = thistle_gprd_make_link_relative( thistle_privacy_exports_public_url() );
    $wp_privacy_exports_rel_url = thistle_gprd_make_link_relative( wp_privacy_exports_url() );

    add_rewrite_rule( $exports_rel_url . '([^/]+)\.zip$', $wp_privacy_exports_rel_url . '$1.zip', 'top' );

    // Confirm URL
    $confirm_rel_url = thistle_gprd_make_link_relative( thistle_privacy_confirm_public_url() );
    $wp_privacy_confirm_rel_url = thistle_gprd_make_link_relative( site_url() . '/wp-login.php' );

    add_rewrite_rule( $confirm_rel_url . '?$', $wp_privacy_confirm_rel_url, 'top' );
}
add_action( 'init', 'thistle_rewrite_privacy_urls' );

Ces règles concidérées comme non_wp_rules (car elles ne concernent pas index.php) iront s’écrire dans votre fichier .htaccess entre # BEGIN WordPress et # END WordPress comme ci-dessous :

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteRule ^donnees-personnelles/export/([^/]+)\.zip$ /wp-content/uploads/gprd-exports/$1.zip [QSA,L]
RewriteRule ^donnees-personnelles/?$ /wp-admin/wp-login.php [QSA,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

# END WordPress

C’est bien joli d’avoir cette nouvelle URL https://www.monsite.test/donnes-personnelles/ mais il va falloir imposer quelques règles pour que les gens mal-intentionnés ne puissent pas y accéder directement et voir notre boite de connexion. De toute façon, il n’y a aucun intérêt à accéder à cette page si ce n’est pas pour confirmer une action RGPD.

/**
 * Handles access to the new public URL which receives confirm actions from users for their personal data.
 *
 * Forbids direct or wrong formatted requests.
 *
 * @param void
 * @return void
 */
function thistle_handle_privacy_confirm_action() {
  $confirm_url_pattern = '/' . trim( thistle_gprd_make_link_relative( thistle_privacy_confirm_public_url() ), '/' );
  if ( mb_strpos( $_SERVER['REQUEST_URI'], $confirm_url_pattern ) !== 0 ) {
    return;
  }

  $wp_privacy_action = isset( $_REQUEST['action'], $_REQUEST['request_id'], $_REQUEST['confirm_key'] ) && ! empty( $_REQUEST['action'] ) && ! empty( $_REQUEST['request_id'] ) && ! empty( $_REQUEST['confirm_key'] );
  if ( ! $wp_privacy_action ) {
    wp_die( __( 'Invalid request.' ) );
  }

  if ( $_REQUEST['action'] != 'confirmaction' || ! intval( $_REQUEST['request_id'] ) ) {
    wp_die( __( 'Invalid request.' ) );
  }
}
add_filter( 'init', 'thistle_handle_privacy_confirm_action', 9 );

Avec ces quelques lignes, si quelqu’un arrive sur notre nouvelle URL sans faire une demande de confirmation sur une action RGPD valide, celui-ci sera en tête à tête avec un message d’erreur.

Après tout ça, il ne reste plus qu’à gérer l’essentiel : remplacer les URLs dans les emails envoyés par WordPress.

  1. l’URL de confirmation d’une action RGPD via le filtre user_request_action_email_content.
  2. l’URL de téléchargement de l’archive contenant nos données personnelles via le filtre user_request_action_email_content.
/**
 * Overrides the confirmation link into the email sent to the user
 * when an account action is attempted.
 *
 * @param string $email_text Text in the email.
 * @param array  $email_data {
 *     Data relating to the account action email.
 *
 *     @type WP_User_Request $request     User request object.
 *     @type string          $email       The email address this is being sent to.
 *     @type string          $description Description of the action being performed so the user knows what the email is for.
 *     @type string          $confirm_url The link to click on to confirm the account action.
 *     @type string          $sitename    The site name sending the mail.
 *     @type string          $siteurl     The site URL sending the mail.
 * }
 * @return string Text in the email.
 */
function thistle_override_privacy_confirm_url( $email_text, $email_data ) {
    $confirm_url_query = parse_url( $email_data['confirm_url'], PHP_URL_QUERY );
    $confirm_url = thistle_privacy_confirm_public_url() . '?' . $confirm_url_query;

    $email_text = str_replace( '###CONFIRM_URL###', esc_url_raw( $confirm_url ), $email_text );

    return $email_text;
}
add_filter( 'user_request_action_email_content', 'thistle_override_privacy_confirm_url', 10, 2 );

/**
 * Overrides the URL of the personal data export file into the email sent to the user.
 *
 * @param string $email_text Text in the email.
 * @param int    $request_id The request ID for this personal data export.
 * @return string
 */
function thistle_override_privacy_file_url( $email_text, $request_id ) {
    $export_file_url = get_post_meta( $request_id, '_export_file_url', true );
    $export_file_url = str_replace( wp_privacy_exports_url(), thistle_privacy_exports_public_url(), $export_file_url );

    $email_text = str_replace( '###LINK###', esc_url_raw( $export_file_url ), $email_text );

    return $email_text;
}
add_filter( 'wp_privacy_personal_data_email_content', 'thistle_override_privacy_file_url', 10, 2 );

Je pensais qu’avec ce ticket 44353 nous accèderions à une façon plus « simple » de procéder au remplacement de l’URL de confirmation mais non. Et les tickets 44376 et 44235 n’ont pas été plus chanceux…

Pour finir, si vous avez envie d’aller plus loin avec la partie RGPD de WordPress pour rajouter vos données personnalisées aux exports par exemple, pensez à https://developer.wordpress.org/plugins/privacy/.

Voila, c’est fini 😁