¿Cómo proteger las subidas si el usuario no está logueado?
Uso WordPress para un sitio privado donde los usuarios suben archivos. Utilizo "Private WordPress" para evitar el acceso al sitio si el usuario no ha iniciado sesión.
Me gustaría hacer lo mismo con los archivos subidos en la carpeta uploads.
Así, si un usuario no está logueado no podrá acceder a: https://xxxxxxx.com/wp-content/uploads/2011/12/xxxxxxx.pdf si intentan acceder pero no están logueados, deberían ser redirigidos a la página de login, por ejemplo.
Encontré un plugin llamado private files pero su última actualización fue en 2009 y no parece funcionar en mi WordPress.
¿Alguien conoce algún método? ¿El método de hotlinking sería suficiente para proteger esto?
También encontré este método:
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_URI} ^.*uploads/private/.*
RewriteCond %{HTTP_COOKIE} !^.*wordpress_logged_in.*$ [NC]
RewriteRule . /index.php [R,L]
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
Pero entonces cualquier usuario que replique la cookie podría superar esto, ¿verdad? Saludos

Comprobar solamente si la cookie existe no es una protección muy estricta.
Para obtener una protección más fuerte, puedes pasar o "proxificar" todas las solicitudes a la carpeta de subidas (ejemplificada como uploads
en el siguiente ejemplo) a través de un script PHP:
RewriteCond %{REQUEST_FILENAME} -s
RewriteRule ^wp-content/uploads/(.*)$ dl-file.php?file=$1 [QSA,L]
Todas las solicitudes a archivos subidos (lo que incluye imágenes en las publicaciones) irían a dl-file.php
que luego puede verificar si el usuario está conectado o no.
Si el usuario no ha iniciado sesión, se mostrará el formulario de acceso de tu sitio. Después de que el usuario inicie sesión, será redirigido de vuelta al archivo y podrá descargarlo.
Algo similar se puede encontrar en \wp-includes\ms-files.php
en tu instalación de WordPress, pero ese es para multisitio y sin la verificación de inicio de sesión y redirecciones.
Dependiendo del tráfico que tengas, podría ser conveniente integrar esto mejor con tu servidor, por ejemplo con cabeceras X-Accel-Redirect
o X-Sendfile
.

¿cómo ajustas dl-file.php si quiero almacenar archivos en un subdirectorio como wp-content/uploads/secure?

Esta es la única solución realmente segura. Cualquier otra cosa que puedas encontrar en la web, como verificar el encabezado referer, verificar cookies, deshabilitar el listado de directorios, es una medida a medias ya que puedes falsificar fácilmente los encabezados de solicitudes HTTP para evitarlo.

Chicos... esta parecía la solución perfecta para mí... el problema es que estoy usando PDFJS de Mozilla para acceder a algunos PDFs desde la carpeta de uploads, y PDFJS usa encabezados partial-content para obtener solo las páginas que le interesan... así que esta solución no me sirve.
¿alguna sugerencia?

@OttoNascarella: Las solicitudes de contenido parcial a PHP se han resuelto a partir de hoy, esto es independiente de esta pregunta sobre WordPress. De hecho, la pregunta es bastante antigua ya: ¿Descargas resumibles al usar PHP para enviar el archivo?

@hakre ¿Qué pasa con algunas de esas imágenes utilizadas en la página principal del sitio web cuando cualquier usuario visita el sitio?
Me da un error 404 si no estoy logueado.

¿Podría alguien explicar esta línea de código list($basedir) = array_values(array_intersect_key(wp_upload_dir(), array('basedir' => 1)))+array(NULL);
y en qué se diferencia de $basedir = wp_upload_dir()['basedir']
?

@TomasEklund: El tuyo probablemente es menos complicado. Una razón para el original es muy probablemente la compatibilidad con versiones anteriores de PHP.

Genial. ¿Alguien podría por favor convertir esta solución en un plugin de WordPress?

En mi configuración Multisite, tuve problemas para completar la ubicación del archivo con el código proporcionado. Esto funcionó mejor: $file = rtrim( DIR, '/' ) . $_SERVER['REQUEST_URI'];

Dos formas, la simple en 2. con ayuda de una regla de Apache o en 1. con ayuda de código personalizado en un plugin.
1. Plugin
Puedes escribir un plugin usando el hook init
y el valor GET $_GET[ 'file' ];
. Si el usuario tiene este valor GET, saltar a una función para verificar los permisos de acceso a los archivos: Por ejemplo, con un checkbox dentro de un Meta Box.
add_action( 'init', 'fb_init' );
function fb_init() {
// esto en una función para el hook init
if ( '' != $_GET[ 'file' ] ) {
fb_get_file( $_GET[ 'file' ] );
}
}
la función fb_get_file()
function fb_get_file( $file ) {
$upload = wp_upload_dir();
$the_file = $file;
$file = $upload[ 'basedir' ] . '/' . $file;
if ( !is_file( $file ) ) {
status_header( 404 );
die( '404 — Archivo no encontrado.' );
}
else {
$image = get_posts( array( 'post_type' => 'attachment', 'meta_query' => array( array( 'key' => '_wp_attached_file', 'value' => $the_file ) ) ) );
if ( 0 < count( $image ) && 0 < $image[0] -> post_parent ) { // adjunto encontrado y padre disponible
if ( post_password_required( $image[0] -> post_parent ) ) { // contraseña para el post no disponible
wp_die( get_the_password_form() );// mostrar el formulario de contraseña
}
$status = get_post_meta( $image[0] -> post_parent, '_inpsyde_protect_content', true );
if ( 1 == $status && !is_user_logged_in() ) {
wp_redirect( wp_login_url( $upload[ 'baseurl' ] . '/' . $the_file ) );
die();
}
}
else {
// no es un adjunto normal, verificar miniatura
$filename = pathinfo( $the_file );
$images = get_posts( array( 'post_type' => 'attachment', 'meta_query' => array( array( 'key' => '_wp_attachment_metadata', 'compare' => 'LIKE', 'value' => $filename[ 'filename' ] . '.' . $filename[ 'extension' ] ) ) ) );
if ( 0 < count( $images ) ) {
foreach ( $images as $SINGLEimage ) {
$meta = wp_get_attachment_metadata( $SINGLEimage -> ID );
if ( 0 < count( $meta[ 'sizes' ] ) ) {
$filepath = pathinfo( $meta[ 'file' ] );
if ( $filepath[ 'dirname' ] == $filename[ 'dirname' ] ) {// ruta actual de la miniatura
foreach ( $meta[ 'sizes' ] as $SINGLEsize ) {
if ( $filename[ 'filename' ] . '.' . $filename[ 'extension' ] == $SINGLEsize[ 'file' ] ) {
if ( post_password_required( $SINGLEimage -> post_parent ) ) { // contraseña para el post no disponible
wp_die( get_the_password_form() );// mostrar el formulario de contraseña
}
die('dD');
$status = get_post_meta( $SINGLEimage -> post_parent, '_inpsyde_protect_content', true );
if ( 1 == $status && !is_user_logged_in() ) {
wp_redirect( wp_login_url( $upload[ 'baseurl' ] . '/' . $the_file ) );
die();
}
}
}
}
}
}
}
}
}
$mime = wp_check_filetype( $file );
if( false === $mime[ 'type' ] && function_exists( 'mime_content_type' ) )
$mime[ 'type' ] = mime_content_type( $file );
if( $mime[ 'type' ] )
$mimetype = $mime[ 'type' ];
else
$mimetype = 'image/' . substr( $file, strrpos( $file, '.' ) + 1 );
header( 'Content-type: ' . $mimetype ); // siempre enviar esto
if ( false === strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS' ) )
header( 'Content-Length: ' . filesize( $file ) );
$last_modified = gmdate( 'D, d M Y H:i:s', filemtime( $file ) );
$etag = '"' . md5( $last_modified ) . '"';
header( "Last-Modified: $last_modified GMT" );
header( 'ETag: ' . $etag );
header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + 100000000 ) . ' GMT' );
// Soporte para GET condicional
$client_etag = isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ? stripslashes( $_SERVER['HTTP_IF_NONE_MATCH'] ) : false;
if( ! isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) )
$_SERVER['HTTP_IF_MODIFIED_SINCE'] = false;
$client_last_modified = trim( $_SERVER['HTTP_IF_MODIFIED_SINCE'] );
// Si la cadena está vacía, devolver 0. Si no, intentar convertir a timestamp
$client_modified_timestamp = $client_last_modified ? strtotime( $client_last_modified ) : 0;
// Crear un timestamp para nuestra modificación más reciente...
$modified_timestamp = strtotime($last_modified);
if ( ( $client_last_modified && $client_etag )
? ( ( $client_modified_timestamp >= $modified_timestamp) && ( $client_etag == $etag ) )
: ( ( $client_modified_timestamp >= $modified_timestamp) || ( $client_etag == $etag ) )
) {
status_header( 304 );
exit;
}
// Si llegamos hasta aquí, simplemente servir el archivo
readfile( $file );
die();
}
También puedes agregar una URL personalizada para archivos mediante el hook generate_rewrite_rules
add_filter( 'generate_rewrite_rules', 'fb_generate_rewrite_rules' );
function fb_generate_rewrite_rules( $wprewrite ) {
$upload = wp_upload_dir();
$path = str_replace( site_url( '/' ), '', $upload[ 'baseurl' ] );
$wprewrite -> non_wp_rules = array( $path . '/(.*)' => 'index.php?file=$1' );
return $wprewrite;
}
2. Verificación de Cookie con Apache
Deja un nuevo archivo .htaccess dentro del directorio /wp-content/uploads/
. U otro directorio definido para las subidas.
Cómo funciona
Dentro de los contenedores <IfModule>
, hay tres reglas que hacen lo siguiente:
- Verifica si la solicitud es para cualquier archivo
- Verifica la ausencia de una cookie que comience con
wordpress_logged_in_
- Si se cumplen estas condiciones, la solicitud del archivo será denegada mediante una respuesta 403 "Prohibido"
El truco aquí es el paso 2, verificar la ausencia de una cookie que comience con wordpress_logged_in_
. Cuando el usuario inicia sesión, WordPress agrega una cookie a tu navegador que luce como:
wordpress_logged_in_1234567890abcdefghijklmnopqrstuvwxyz
Ejemplo de regla con verificación de tipo de archivo
# requerir inicio de sesión para archivos multimedia
<IfModule mod_rewrite.c>
RewriteCond %{REQUEST_FILENAME} (.*)
RewriteCond %{HTTP_COOKIE} !wordpress_logged_in_([a-zA-Z0-9_]*) [NC]
RewriteRule .* - [F,L]
</IfModule>

Esto no funcionó en mi caso, ¿alguien sabe por qué? Lo copié exactamente igual.

La protección solo funciona con PDF. No funciona con otras extensiones de archivo como: doc, docx, jpg, etc...

La opción 2 funcionó perfectamente para todos los tipos de archivo, gracias.

Si prefieres un enfoque basado en plugins para resolver este problema, aquí hay una solución bastante buena que he encontrado (finalmente):
- Instala el plugin 'Download Monitor', disponible en:
https://wordpress.org/plugins/download-monitor/ - En el Escritorio de WordPress, ve al nuevo elemento de menú 'Descargas' y añade una nueva 'Descarga', como se describe en la documentación del plugin aquí: https://www.download-monitor.com/kb/adding-downloads/.
Toma nota del shortcode de 'Descarga' que se te proporciona (por ejemplo, guárdalo en el Bloc de notas). Ten en cuenta que el archivo se guarda en
/wp-content/uploads/dlm_uploads/
- En el metabox 'Opciones de descarga', especifica 'Solo para miembros' (como se documenta aquí https://www.download-monitor.com/kb/download-options/), y haz clic en 'Publicar'.
- En la página donde quieras que aparezca la descarga solo para miembros, añade el shortcode que anotaste en el paso #2, y 'Publica/Actualiza' la página, como se documenta aquí: https://www.download-monitor.com/kb/shortcode-download/. Puedes cambiar la plantilla del enlace de descarga como se describe aquí https://www.download-monitor.com/kb/content-templates/, o crear tu propia plantilla (por ejemplo, para eliminar el 'contador' de descargas)
- Navega a tu página, deberías ver un enlace de descarga (pero que no revela la URL del archivo). Si navegas a la misma página en una nueva ventana del navegador (o en modo Incógnito), deberías ver que la descarga ya no funciona.
Esto significa que cualquier persona que no haya iniciado sesión no puede descargar el archivo ni ver la URL real del archivo. En el caso de que alguien no autorizado descubra la URL del archivo, el plugin también evita que los usuarios accedan a la URL real del archivo bloqueando el acceso a la carpeta /wp-content/uploads/dlm_uploads/
.
Bonus: si estás haciendo esto para un sitio donde necesitas que los usuarios puedan iniciar sesión solo como 'Miembros' (pero sin permisos de WordPress como editar páginas o ser Administrador), instala el plugin 'Members' https://wordpress.org/plugins/members/, crea un nuevo rol de usuario llamado 'Miembro', y asígnale la única capacidad de 'leer', crea un nuevo Usuario en WordPress, y asegúrate de asignarle el rol de 'Miembro'.
Si quieres proteger el contenido de las páginas, el plugin 'Members' proporciona algunas opciones, o hay otros plugins disponibles. Si quieres personalizar la página de inicio de sesión para que los Miembros tengan una apariencia mejor que el formulario de inicio de sesión predeterminado de WordPress, usa algo como 'Theme My Login': https://wordpress.org/plugins/theme-my-login/

El proceso que he descrito anteriormente también se explica aquí, aunque como puedes ver no tiene que ser específico solo para PDFs: http://www.thedigitalcrowd.com/website-development/wordpress/wordpress-restrict-access-to-pdf-downloads-with-private-members-login-details/

¿Qué tal un enfoque basado en plugins para resolver este problema? He encontrado un plugin de WordPress creado para abordar esto:
Prevenir acceso a archivos/carpetas: https://wordpress.org/plugins/prevent-file-access/
