Contents
Introducción
Este artículo explica con todo lujo de detalles cómo crear un sistema de permisos granular en WordPress usando metacapabilities. Verás qué son las metacapabilities, cómo mapearlas a capacidades primitivas mediante el filtro map_meta_cap, cómo registrar roles y capacidades, cómo integrar comprobaciones en el REST API y en AJAX, y qué medidas de seguridad y rendimiento debes considerar. Incluyo ejemplos completos en PHP listos para insertar en tu plugin o tema.
Conceptos clave
Qué es una metacapability
En WordPress existen dos tipos de capacidades: las llamadas capabilities primitivas (por ejemplo, edit_posts, publish_posts) que representan permisos atómicos, y las metacapabilities (por ejemplo, edit_post, delete_post) que dependen del contexto del objeto (post, user, term…). Las metacapabilities se mapean a primitivas según la lógica de negocio (por ejemplo, si el usuario es el autor, permitir edit_post con la primitiva edit_posts o una capacidad específica como edit_others_posts).
Por qué usar metacapabilities
- Permiten lógica contextual (dueño, estado, grupo, meta del post).
- Fácil implementación de controles finos: un usuario X puede editar posts del tipo Y sólo si….
- Se integran con la API interna de current_user_can() y con checks automáticos de WordPress.
Fácil vs granular
Las capacidades primitivas sirven para roles simples las metacapabilities permiten reglas complejas (grupos, miembros, atributos de metadatos, expiraciones, etc.). Para sistemas multiusuario o contenido sensible, las metacapabilities son la mejor opción.
Arquitectura propuesta
Ejemplo práctico: construiremos un sistema de permisos para un Custom Post Type llamado document, donde:
- Cada documento tiene un autor (post_author).
- Además puede tener una lista de colaboradores guardada en post meta: _document_collaborators (array de IDs de usuario).
- Soportaremos roles administrativos: document_manager (capacidad manage_documents) y usuarios con capacidades granulares como edit_document, read_document, delete_document.
1) Registrar el Custom Post Type y capacidades primitivas
Cuando registres el CPT deberás declarar el parámetro capability_type y las capacidades personalizadas. Ejemplo de registro:
edit_document,
read_post => read_document,
delete_post => delete_document,
edit_posts => edit_documents,
edit_others_posts => edit_others_documents,
publish_posts => publish_documents,
read_private_posts => read_private_documents,
)
args = array(
label => Documents,
public => true,
supports => array(title, editor, author),
capability_type => array(document, documents),
map_meta_cap => true, // Crucial para usar map_meta_cap
capabilities => caps,
)
register_post_type(document, args)
})
?>
2) Crear roles y asignar capacidades
Crea un rol específico para gestores de documentos y asigna las capacidades primitivas necesarias.
true,
manage_documents => true,
// capacidades específicas
edit_documents => true,
edit_others_documents => true,
publish_documents => true,
read_private_documents => true,
))
// Opcional: otorgar capacidades a administrador
admin = get_role(administrator)
if (admin) {
admin->add_cap(manage_documents)
admin->add_cap(edit_documents)
admin->add_cap(edit_others_documents)
}
})
?>
3) Mapear metacapabilities usando map_meta_cap
El paso esencial: enganchar una función al filtro map_meta_cap. Esa función recibirá la metacapability solicitada, el ID del usuario, y argumentos adicionales (p. ej. ID del post). Debes retornar un array de capacidades primitivas requeridas para autorizar la operación. Aquí implementaremos reglas:
- El autor del documento puede leer/editar su documento si tiene edit_documents o read_documents según el caso.
- Los colaboradores listados en post meta _document_collaborators pueden editar si el post está en determinado estado.
- Los managers (capacidad manage_documents) pueden todo.
post_type !== document) {
return array(do_not_allow)
}
// Si el usuario es administrador o tiene manage_documents, dejar que WP valide con esa primitiva
if (user_can(user_id, manage_documents)) {
return array(manage_documents)
}
// Propietario del documento
if ((int)post->post_author === (int)user_id) {
switch (cap) {
case read_document:
return array(read_documents)
case edit_document:
return array(edit_documents)
case delete_document:
return array(delete_documents)
}
}
// Colaboradores almacenados en post meta (array de IDs)
collabs = get_post_meta(post_id, _document_collaborators, true)
if (!empty(collabs) is_array(collabs) in_array((int)user_id, array_map(intval, collabs), true)) {
// Los colaboradores pueden leer y editar, pero no eliminar, por ejemplo
if (cap === read_document) {
return array(read_documents)
} elseif (cap === edit_document) {
return array(edit_documents)
} else {
return array(do_not_allow)
}
}
// Por defecto, usuario necesita capacidades de others para editar/eliminar si no es autor
if (cap === edit_document) {
return array(edit_others_documents)
} elseif (cap === delete_document) {
return array(delete_others_documents)
} elseif (cap === read_document) {
// Si el post es público, permitir leer mediante capability read
if (post->post_status === publish) {
return array(read)
}
return array(do_not_allow)
}
return array(do_not_allow)
}, 10, 4)
?>
Notas sobre la implementación
- Usamos map_meta_cap para convertir una sola metacapability en una o más capacidades primitivas.
- Retornar do_not_allow es la forma correcta de denegar.
- Evitar llamadas costosas (consultas pesadas) dentro del hook cachea si es necesario.
4) Añadir/gestionar colaboradores en la UI (ejemplo simple)
Puedes usar campos personalizados en la pantalla de edición para añadir IDs de colaboradores, o crear una metabox para seleccionar usuarios. Aquí un ejemplo mínimo para guardar un array de IDs.
5) Verificaciones en REST API y endpoints personalizados
Si expones el CPT en el REST API o creas endpoints personalizados, debes usar la misma lógica de permisos en la propiedad permission_callback de tus rutas o al definir el endpoint del CPT. Ejemplo de endpoint REST personalizado que usa current_user_can con la metacapability:
d ), array(
methods => GET,
callback => function(request) {
id = (int) request[id]
post = get_post(id)
if (! post) {
return new WP_Error(not_found, Documento no encontrado, array(status => 404))
}
return rest_ensure_response(array(
id => post->ID,
title => post->post_title,
))
},
permission_callback => function(request) {
id = (int) request[id]
return current_user_can(read_document, id)
}
))
})
?>
6) Comprobaciones en frontend y en AJAX
En el frontend usa current_user_can(edit_document, post_id) antes de mostrar botones u operaciones sensibles. En AJAX asegúrate de sanitizar inputs y verificar nonces y capacidades en handlers.
7) Casos avanzados: grupos, expiraciones y capacidades temporales
Para escenarios más complejos (grupos de usuarios, permisos temporales, roles jerárquicos) sigue estas recomendaciones:
- Almacena relaciones en tablas o meta bien indexadas. Evita serializaciones grandes en opciones.
- Cuando verifiques pertenencia a grupos, usa transients para cachear comprobaciones costosas por unos segundos/minutos.
- Para permisos temporales, añade metadatos con fecha de expiración en map_meta_cap comprueba fecha y devuelve do_not_allow si expiró.
- Si necesitas capacidades calculadas a gran escala, considera almacenar capacidades agregadas por usuario (sincronizadas) para evitar cálculos on-the-fly en cada request.
8) Seguridad y buenas prácticas
- Principio de menor privilegio: otorga sólo las capacidades mínimas necesarias a roles.
- Evitar la escalada: no confíes en entradas del cliente siempre verifica capacidades en el servidor (REST/AJAX/Servidor).
- Saneamiento y validación: sanitiza IDs, cadenas y metas antes de guardarlas o procesarlas.
- Usa nonces en formularios y AJAX para evitar CSRF.
- Evita consultas dentro de map_meta_cap que sean muy costosas cachea resultados o precómputalos.
- Registro de auditoría: para acciones críticas (borrado, cambios de permisos), registra quién lo hizo y cuándo.
9) Rendimiento y caching
map_meta_cap se llama con frecuencia (cada current_user_can). Para evitar sobrecargar la base de datos:
- Cachea listas de colaboradores por post en un transient o en un objeto cache persistente con expiración corta.
- Si haces checks de pertenencia a grupos, precarga la información del usuario una vez por request.
- Usa object cache (Redis/Memcached) en entornos con alto tráfico.
10) Tests y validación
Implementa tests unitarios con WP_UnitTestCase para validar las reglas de map_meta_cap: casos de autor, colaborador, manager, usuario anónimo y expiraciones. Ejemplo de test que debes implementar en tu suite de pruebas (esquema):
factory->user->create()
wp_set_current_user(user_id)
post_id = this->factory->post->create(array(
post_type => document,
post_author => user_id,
))
this->assertTrue(current_user_can(edit_document, post_id))
}
}
?>
Tabla resumen: metacapabilities y primitivas (ejemplo)
| Metacapability | Posible mapping | Comentario |
|---|---|---|
| edit_document | edit_documents / edit_others_documents / do_not_allow | Autor usa edit_documents no autor requiere edit_others_documents o derecho especial |
| read_document | read / read_documents / do_not_allow | Documento publicado => read privado => read_documents o permiso especial |
| delete_document | delete_documents / delete_others_documents / do_not_allow | Similar a editar pero habitualmente más restrictivo |
Consejos finales
Implementar un sistema de permisos granular con metacapabilities te dará control y flexibilidad sobre quién puede hacer qué en tu aplicación WordPress. Las reglas deben ser explícitas, auditables y optimizadas. Empieza por modelar claramente los actores (autor, colaborador, manager), define capacidades primitivas coherentes y documenta el comportamiento de map_meta_cap. Siempre prueba exhaustivamente y aplica caching cuando detectes cuellos de botella.
Recursos útiles
|
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |
