Make WordPress Core

Changeset 43095


Ignore:
Timestamp:
05/02/2018 02:32:57 AM (7 years ago)
Author:
SergeyBiryukov
Message:

Privacy: Add cron to delete expired export files to protect privacy.

The primary means of protecting the files is the CSPRN appended to the filename, but there is no reason to keep the files after the data subject has downloaded them, so deleting them provides an additional layer of protection. Previously this was done from wp_privacy_generate_personal_data_export_file(), but that does not guarantee that it will be run regularly, and on smaller sites that could result in export files being exposed for much longer than necessary.

wp_privacy_delete_old_export_files() was moved to a front end file, so that it can be called from cron.php.

This introduces the wp_privacy_export_expiration filter, which allows plugins to customize how long the exports are kept before being deleted.

index.html was added to the $exclusions parameter of list_files() to make sure that it isn't deleted. If it were, then poorly-configured servers would allow the directory to be traversed, exposing all of the exported files.

Props iandunn, desrosj.
Merges [43046] to the 4.9 branch.
See #43546.

Location:
branches/4.9
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • branches/4.9

  • branches/4.9/src/wp-admin/includes/file.php

    r43094 r43095  
    18631863 */
    18641864function wp_privacy_generate_personal_data_export_file( $request_id ) {
    1865     // Maybe make this a cron job instead.
    1866     wp_privacy_delete_old_export_files();
    1867 
    18681865    if ( ! class_exists( 'ZipArchive' ) ) {
    18691866        wp_send_json_error( __( 'Unable to generate export file. ZipArchive not available.' ) );
     
    20262023    }
    20272024
    2028 /* translators: Do not translate LINK, EMAIL, SITENAME, SITEURL: those are placeholders. */
     2025    /** This filter is documented in wp-admin/includes/file.php */
     2026    $expiration      = apply_filters( 'wp_privacy_export_expiration', 3 * DAY_IN_SECONDS );
     2027    $expiration_date = date_i18n( get_option( 'date_format' ), time() + $expiration );
     2028
     2029/* translators: Do not translate EXPIRATION, LINK, EMAIL, SITENAME, SITEURL: those are placeholders. */
    20292030$email_text = __(
    20302031'Howdy,
    20312032
    20322033Your request for an export of personal data has been completed. You may
    2033 download your personal data by clicking on the link below. This link is
    2034 good for the next 3 days.
     2034download your personal data by clicking on the link below. For privacy
     2035and security, we will automatically delete the file on ###EXPIRATION###,
     2036so please download it before then.
    20352037
    20362038###LINK###
     
    20472049     *
    20482050     * The following strings have a special meaning and will get replaced dynamically:
     2051     * ###EXPIRATION###         The date when the URL will be automatically deleted.
    20492052     * ###LINK###               URL of the personal data export file for the user.
    20502053     * ###EMAIL###              The email we are sending to.
     
    20642067    $site_url = network_home_url();
    20652068
     2069    $content = str_replace( '###EXPIRATION###', $expiration_date, $content );
    20662070    $content = str_replace( '###LINK###', esc_url_raw( $export_file_url ), $content );
    20672071    $content = str_replace( '###EMAIL###', $email_address, $content );
     
    22082212    return $response;
    22092213}
    2210 
    2211 /**
    2212  * Cleans up export files older than three days old.
    2213  *
    2214  * @since 4.9.6
    2215  */
    2216 function wp_privacy_delete_old_export_files() {
    2217     $upload_dir   = wp_upload_dir();
    2218     $exports_dir  = trailingslashit( $upload_dir['basedir'] . '/exports' );
    2219     $export_files = list_files( $exports_dir );
    2220 
    2221     foreach( (array) $export_files as $export_file ) {
    2222         $file_age_in_seconds = time() - filemtime( $export_file );
    2223 
    2224         if ( 3 * DAY_IN_SECONDS < $file_age_in_seconds ) {
    2225             @unlink( $export_file );
    2226         }
    2227     }
    2228 }
  • branches/4.9/src/wp-includes/default-filters.php

    r43083 r43095  
    326326add_filter( 'wp_privacy_personal_data_exporters', 'wp_register_comment_personal_data_exporter' );
    327327add_filter( 'wp_privacy_personal_data_erasers', 'wp_register_comment_personal_data_eraser' );
     328add_action( 'init', 'wp_schedule_delete_old_privacy_export_files' );
     329add_action( 'wp_privacy_delete_old_export_files', 'wp_privacy_delete_old_export_files' );
    328330
    329331// Cron tasks
  • branches/4.9/src/wp-includes/functions.php

    r43082 r43095  
    59355935    return apply_filters( 'wp_privacy_anonymize_data', $anonymous, $type, $data );
    59365936}
     5937
     5938/**
     5939 * Schedule a `WP_Cron` job to delete expired export files.
     5940 *
     5941 * @since 4.9.6
     5942 */
     5943function wp_schedule_delete_old_privacy_export_files() {
     5944    if ( ! wp_next_scheduled( 'wp_privacy_delete_old_export_files' ) ) {
     5945        wp_schedule_event( time(), 'hourly', 'wp_privacy_delete_old_export_files' );
     5946    }
     5947}
     5948
     5949/**
     5950 * Cleans up export files older than three days old.
     5951 *
     5952 * The export files are stored in `wp-content/uploads`, and are therefore publicly
     5953 * accessible. A CSPRN is appended to the filename to mitigate the risk of an
     5954 * unauthorized person downloading the file, but it is still possible. Deleting
     5955 * the file after the data subject has had a chance to delete it adds an additional
     5956 * layer of protection.
     5957 *
     5958 * @since 4.9.6
     5959 */
     5960function wp_privacy_delete_old_export_files() {
     5961    $upload_dir   = wp_upload_dir();
     5962    $exports_dir  = trailingslashit( $upload_dir['basedir'] . '/exports' );
     5963    $export_files = list_files( $exports_dir, 100, array( 'index.html' ) );
     5964
     5965    /**
     5966     * Filters the lifetime, in seconds, of a personal data export file.
     5967     *
     5968     * By default, the lifetime is 3 days. Once the file reaches that age, it will automatically
     5969     * be deleted by a cron job.
     5970     *
     5971     * @since 4.9.6
     5972     *
     5973     * @param int $expiration The expiration age of the export, in seconds.
     5974     */
     5975    $expiration = apply_filters( 'wp_privacy_export_expiration', 3 * DAY_IN_SECONDS );
     5976
     5977    foreach ( (array) $export_files as $export_file ) {
     5978        $file_age_in_seconds = time() - filemtime( $export_file );
     5979
     5980        if ( $expiration < $file_age_in_seconds ) {
     5981            unlink( $export_file );
     5982        }
     5983    }
     5984}
Note: See TracChangeset for help on using the changeset viewer.