2.9 KiB
| id | title | domain | tags | status | severity | created_at | updated_at | applies_to | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| KB-PLUGIN-010 | Padrao correto para chamadas AJAX autenticadas em plugins GLPI 11 | plugin-dev |
|
active | critical | 2026-05-03 | 2026-05-03 |
|
Padrao correto para chamadas AJAX autenticadas em plugins GLPI 11
Sintoma
Chamadas fetch POST feitas por um plugin retornam HTTP 403. O plugin está corretamente instalado e o usuário está autenticado com o direito necessário. O erro ocorre mesmo que Session::checkCSRF() não seja chamado no PHP do plugin.
Causa
O GLPI 11 utiliza o kernel Symfony com um listener dedicado (CheckCsrfListener) que intercepta todos os requests POST antes de o PHP do plugin executar. Para requests AJAX (identificados pelo header X-Requested-With: XMLHttpRequest), ele exige o CSRF token no header X-Glpi-Csrf-Token — não no body da requisição.
Fonte: /var/www/glpi/src/Glpi/Kernel/Listener/ControllerListener/CheckCsrfListener.php
if ($request->isXmlHttpRequest()) {
Session::checkCSRF(
['_glpi_csrf_token' => $request->server->get('HTTP_X_GLPI_CSRF_TOKEN') ?? ''],
preserve_token: true // token não é consumido — reutilizável em múltiplas chamadas
);
} else {
Session::checkCSRF($request->request->all());
}
Onde fica o token
O token é renderizado pelo layout do GLPI em uma meta tag:
<meta property="glpi:csrf_token" content="TOKEN_AQUI" />
Com preserve_token: true, o mesmo token permanece válido para todas as chamadas AJAX da sessão — não é consumido.
Solucao
JavaScript (plugin)
function csrfToken() {
var meta = document.querySelector('meta[property="glpi:csrf_token"]');
return meta ? meta.getAttribute('content') : '';
}
fetch('/plugins/meu-plugin/ajax/endpoint.php', {
method: 'POST',
credentials: 'same-origin', // envia o cookie de sessão
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest', // identifica como AJAX para o kernel
'X-Glpi-Csrf-Token': csrfToken() // token exigido pelo CheckCsrfListener
},
body: new URLSearchParams({ action: 'minha_action', key: 'valor' })
});
PHP (endpoint do plugin)
Não chamar Session::checkCSRF() — o kernel já fez a verificação. Apenas verificar o direito do usuário:
<?php
include('../../../inc/includes.php');
Session::checkRight('config', UPDATE); // suficiente — kernel já validou CSRF
// lógica do endpoint...
Comportamento esperado
Com o padrão acima:
- Kernel valida CSRF via header antes do PHP rodar
- PHP recebe a requisição autenticada normalmente
- Token não é consumido (
preserve_token: true), então múltiplos POSTs consecutivos funcionam sem gerar novo token