Contents
Introducción
Este artículo explica en detalle cómo restringir endpoints del REST API de WordPress mediante capacidades (capabilities) usando PHP. Verás las diferencias entre usar permission_callback al registrar rutas nuevas y cómo modificar endpoints ya existentes del núcleo o de plugins para forzar comprobaciones de capacidad. Incluiré ejemplos prácticos listos para usar en un plugin o en functions.php de tu tema (mejor en un plugin), y también recomendaciones de seguridad y pruebas.
Conceptos clave
Antes de entrar en el código conviene repasar conceptos básicos:
- REST endpoint: una ruta registrada en el REST API (por ejemplo /wp/v2/posts).
- permission_callback: función que se ejecuta cuando se solicita un endpoint. Debe devolver true (permitir) o un WP_Error (o false) para denegar.
- Capabilities: permisos del sistema de roles de WordPress (por ejemplo: edit_posts, delete_posts, manage_options). Se comprueban con current_user_can( capability, args ).
- Hooks relevantes: register_rest_route() para endpoints nuevos y el filtro rest_endpoints para modificar endpoints ya registrados.
Por qué usar permission_callback y no filtrar la respuesta
- permission_callback evita que el endpoint llegue a ejecutarse, por lo que no se gaste CPU en generar respuestas para usuarios no autorizados.
- Es la forma recomendada por la API REST de WP para controlar acceso por petición.
- Si un endpoint ya existe (core o plugin) y no tiene permission_callback o su callback no encaja con tus necesidades, puedes modificarlo con el filtro rest_endpoints antes de que se sirva.
Ejemplo 1 — Registrar un endpoint personalizado restringido por capability
Ejemplo: crear un endpoint que devuelva datos administrativos solo si el usuario tiene la capability manage_options (normalmente administradores).
GET, callback => function( WP_REST_Request request ) { // Código para recuperar las estadísticas return rest_ensure_response( array( users => count_users(), site_url => get_site_url(), ) ) }, permission_callback => function( WP_REST_Request request ) { // Permitir solo a usuarios con manage_options if ( ! current_user_can( manage_options ) ) { return new WP_Error( rest_forbidden, __( No autorizado. ), array( status => 403 ) ) } return true }, ) ) } ) ?>
Este ejemplo es claro: si el current_user no tiene la capability, el resultado será un error 403. Usar WP_Error permite devolver un mensaje y código HTTP correcto.
Ejemplo 2 — Restringir endpoints existentes (core o plugins) usando rest_endpoints
Si necesitas cambiar el comportamiento de endpoints ya registrados (por ejemplo /wp/v2/posts), el filtro rest_endpoints te permite interceptar y ajustar los permission_callback. Debes engancharte con prioridad alta o al menos tras el registro de las rutas.
[d] ) if ( isset( endpoints[/wp/v2/posts] ) ) { foreach ( endpoints[/wp/v2/posts] as route ) { // Reemplazamos o envolvemos el permission_callback existente route[permission_callback] = function( request ) { // Solo usuarios con capacidad edit_posts podrán usar la colección posts if ( ! current_user_can( edit_posts ) ) { return new WP_Error( rest_forbidden, __( Acceso restringido a /wp/v2/posts ), array( status => 403 ) ) } return true } } } // Ejemplo para rutas con ID: if ( isset( endpoints[/wp/v2/posts/(?P[d] )] ) ) { foreach ( endpoints[/wp/v2/posts/(?P [d] )] as route ) { route[permission_callback] = function( request ) { post_id = (int) request[id] // Permitir si puede editar el post concreto if ( current_user_can( edit_post, post_id ) current_user_can( edit_others_posts ) ) { return true } return new WP_Error( rest_forbidden, __( No tienes permiso para este post. ), array( status => 403 ) ) } } } return endpoints } ) ?>
Notas:
- Las claves del array endpoints corresponden a las rutas registradas. Algunas rutas tienen parámetros (regex) y aparecen con esa notación.
- Al sobrescribir permission_callback te aseguras de imponer la política que necesites, pero debes ser cuidadoso con rutas utilizadas por otros plugins o por el core.
Ejemplo 3 — Comprobar capabilities dinámicas (por parámetro)
Puede que quieras que el endpoint reciba una capability requerida en su propia definición o que varíe según parámetros. Aquí un ejemplo donde se dice qué capability se requiere según un parámetro del request:
[d] ), array( methods => GET, callback => function( request ) { id = (int) request[resource_id] // Devolver los datos del recurso return rest_ensure_response( array( id => id, foo => bar ) ) }, permission_callback => function( request ) { resource_id = (int) request[resource_id] // Ejemplo: si el recurso es privado requerimos read_private_posts, si es público lo permitimos is_private = get_post_meta( resource_id, _is_private, true ) if ( is_private ) { if ( ! current_user_can( read_private_posts ) ) { return new WP_Error( rest_forbidden, __( No autorizado para recursos privados. ), array( status => 403 ) ) } } return true } ) ) } ) ?>
Buenas prácticas y consideraciones
- Usa capability en lugar de rol: comprobar capabilities es más flexible (roles pueden cambiar).
- Devuelve WP_Error con códigos HTTP claros: facilita debugeo y control en clientes front-end.
- No confíes en front-end JS para seguridad: las comprobaciones deben hacerse en servidor.
- Caching y performance: permission_callback se ejecuta por petición. Las comprobaciones con current_user_can son baratas, pero evita consultas innecesarias si puedes.
- Autenticación: current_user_can depende del usuario autenticado. Para peticiones desde apps externas, asegúrate que la autenticación (cookie, Application Passwords, OAuth) esté funcionando.
- Orden de carga: rest_endpoints se debe usar con cuidado en plugins que se cargan antes o después del registro de endpoints objetivo. Hooks en rest_api_init o filtros pueden necesitar prioridade ajustada.
Errores comunes
- No devolver un WP_Error en el permission_callback: si devuelves false WordPress puede responder con un 401 o 403 inesperado WP_Error permite personalizar el mensaje y el status.
- Modificar rutas del core sin probar a fondo: podrías bloquear funciones necesarias para el editor o el front.
- Asumir que current_user_can admite cualquier string: algunas capabilities requieren mapa meta (por ejemplo capabilities dinámicas tipo edit_post con ID).
Comprobaciones más específicas: capabilities con argumentos (ej. edit_post)
Para capacidades que aceptan argumentos, pasa el ID correcto a current_user_can. Ejemplo para permitir edición sólo si el usuario puede editar ese post:
403 ) ) } ?>
Cómo probar tus cambios
- Usa Insomnia, Postman o curl y autentica con cookies (via navegador) o Application Passwords para representar distintos usuarios.
- Prueba usuarios con y sin la capability esperada para confirmar el comportamiento.
- Revisar respuestas HTTP: 200 (ok), 401/403 (no autorizado) según el caso.
Enlaces útiles
Resumen
La forma correcta de restringir endpoints por capability en WordPress es usar permission_callback cuando registras nuevas rutas y, para endpoints ya existentes del core o de terceros, usar el filtro rest_endpoints para sobreescribir o envolver permission_callback. Utiliza current_user_can para comprobar capabilities (incluyendo las que aceptan argumentos, como edit_post) y devuelve WP_Error con códigos HTTP correctos para un comportamiento claro en el cliente. Todo esto mantiene tu API segura y eficiente.
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |