knowledge-base/records/plugin-dev/KB-PLUGIN-044-glpi11-native-visibility-in-plugin-boards.md
2026-07-13 16:34:05 -03:00

3.8 KiB

id title domain tags status severity created_at updated_at applies_to related_records
KB-PLUGIN-044 GLPI 11 — Visibilidade nativa em listagens de plugin (tickets/projetos) e permissionamento espelhado: receita e armadilhas plugin-dev
glpi11
plugin
security
visibility
rights
profile
sqlprovider
ticket
project
hardening
mindscrum
active critical 2026-07-13 2026-07-13
GLPI 11.0.8 dev
plugin mindscrum 0.9.2
qualquer plugin que liste tickets/projetos fora do Search nativo
KB-PLUGIN-028
KB-PLUGIN-039
KB-PLUGIN-043

GLPI 11 — Visibilidade nativa em listagens de plugin e permissionamento espelhado

O problema (achado em auditoria do mindscrum)

Um plugin que consulta glpi_tickets/glpi_projects direto com $DB->request() e filtra só por getEntitiesRestrictCriteria() vaza dados: mostra a qualquer usuário com o right do plugin TODOS os itens da entidade — ignorando as regras nativas ("técnico vê só os seus/do grupo", "projeto visível a gerente/equipe"). O right do plugin NÃO substitui a visibilidade por item.

Receita — tickets (a mesma do kanban do core)

CommonITILObject::getDataToDisplayOnKanban faz (replicar):

use Glpi\Search\Provider\SQLProvider;

$where = ['glpi_tickets.is_deleted' => 0] + getEntitiesRestrictCriteria('glpi_tickets');
$vis = SQLProvider::getDefaultWhereCriteria(\Ticket::class);
if ($vis !== []) {
    $where[] = $vis;
}
$query = ['SELECT' => [/* campos PREFIXADOS glpi_tickets.x AS x */], 'FROM' => 'glpi_tickets', 'WHERE' => $where];

// ⚠️ o WHERE referencia JOINs com alias hasheado (glpi_tickets_users_<md5>...):
// SEM os joins correspondentes → "Unknown column ..._users_<hash>.users_id"
$linked = [];
$join = SQLProvider::getDefaultJoinCriteria(\Ticket::class, 'glpi_tickets', $linked);
if ($join !== []) {
    $query = array_merge_recursive($query, $join);
}
// joins de atores DUPLICAM linhas → deduplicar por id ($rows[$r['id']] = $r)

Armadilhas: (1) getDefaultWhereCriteria sem getDefaultJoinCriteria = SQL quebrado; (2) campos do SELECT precisam de prefixo de tabela (joins tornam id ambíguo); (3) dedup por id obrigatório.

Receita — projetos

$vis = \Project::getVisibilityCriteria();  // ['LEFT JOIN'=>[], 'WHERE'=>[]]
$vis['WHERE'] += getEntitiesRestrictCriteria('glpi_projects', '', '', 'auto');
// LEFT JOIN => (seus joins) + $vis['LEFT JOIN'];  WHERE => $vis['WHERE'] + (seus filtros)
// dedup por id (join com projectteams duplica)

Permissionamento espelhado (rights do plugin por perfil)

Conceder o right do plugin "para todos os perfis central" (mitigação C do KB-PLUGIN-028) dá UPDATE até a Observer/Read-Only. Correto: espelhar o nativo — UPDATE do plugin somente para perfis cujo right ticket tem UPDATE:

$value = READ + (($ticket_rights & UPDATE) ? UPDATE : 0);
addDefaultProfileInfos($profile_id, [RIGHT => $value], true); // drop_existing corrige base

Reinstalar via console aplica a correção (install idempotente).

Guardas de item-alvo (defesa em profundidade)

Right do plugin ≠ direito no item. TODA mutação deve carregar o alvo e delegar ao nativo: canUpdateItem() (criar/alterar tarefa, mover, campos), canViewItem() (comentar), Ticket::canCreate() + canViewItem() do projeto (spawn). Colocar as guardas NAS CLASSES de serviço (não só no endpoint) — valem para qualquer chamador e são testáveis em CLI.

Teste E2E que prova: sessão com ticket => READ (sem READALL) deve ver só os chamados onde é ator; sessão com rights zerados deve ter toda mutação negada.

Bônus — XSS de atributo em JS de plugin

div.textContent = s; return div.innerHTML escapa & < > mas não aspas — inseguro para title="..."/data-x="...". Usar replace de [&<>"'].