KB-PLUGIN-034 + nota menu_toadd no 028 (itemtype de config de plugin)
- KB-PLUGIN-034: cifrar credenciais externas em itemtype via GLPIKey (padrão prepareInput + senha-vazia-mantem-atual). Validado no plugin sync. - KB-PLUGIN-028: seção sobre o mecanismo menu_toadd (seções válidas + estrutura). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
79c19c7425
commit
28dcfbca94
3 changed files with 141 additions and 0 deletions
19
index.json
19
index.json
|
|
@ -608,6 +608,25 @@
|
||||||
"severity": "high",
|
"severity": "high",
|
||||||
"path": "records/plugin-dev/KB-PLUGIN-033-glpi11-plugin-console-commands.md",
|
"path": "records/plugin-dev/KB-PLUGIN-033-glpi11-plugin-console-commands.md",
|
||||||
"summary": "id: KB-PLUGIN-033"
|
"summary": "id: KB-PLUGIN-033"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "KB-PLUGIN-034",
|
||||||
|
"title": "\"GLPI 11 — Cifrar credenciais de sistemas externos em itemtype de plugin (GLPIKey)\"",
|
||||||
|
"domain": "plugin-dev",
|
||||||
|
"tags": [
|
||||||
|
"glpi11",
|
||||||
|
"plugin",
|
||||||
|
"security",
|
||||||
|
"credentials",
|
||||||
|
"glpikey",
|
||||||
|
"encryption",
|
||||||
|
"commondbtm",
|
||||||
|
"itemtype"
|
||||||
|
],
|
||||||
|
"status": "active",
|
||||||
|
"severity": "high",
|
||||||
|
"path": "records/plugin-dev/KB-PLUGIN-034-glpi11-encrypt-external-credentials-itemtype.md",
|
||||||
|
"summary": "id: KB-PLUGIN-034"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,30 @@ function plugin_meuplugin_install(): bool
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Mecanismo do menu (menu_toadd) — como o itemtype entra na sidebar
|
||||||
|
|
||||||
|
Registrar o right faz o menu *poder* aparecer; para ele *de fato* aparecer, o
|
||||||
|
itemtype precisa ser injetado numa seção do menu via o hook `menu_toadd` (lido em
|
||||||
|
`Html::generateMenuSession()` / `Html.php`):
|
||||||
|
|
||||||
|
```php
|
||||||
|
// setup.php, dentro de plugin_init_<key>()
|
||||||
|
Plugin::registerClass('GlpiPlugin\\Meuplugin\\MeuItemtype');
|
||||||
|
$PLUGIN_HOOKS['menu_toadd']['meuplugin'] = [
|
||||||
|
'config' => 'GlpiPlugin\\Meuplugin\\MeuItemtype',
|
||||||
|
// ou vários: 'config' => ['Classe1', 'Classe2']
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
Seções válidas (chave do array): **`assets`, `helpdesk`, `management`, `tools`,
|
||||||
|
`admin`, `config`** (Setup), `plugins`. Itemtype administrativo de configuração
|
||||||
|
→ `config`; ver taxonomia em [KB-PLUGIN-029].
|
||||||
|
|
||||||
|
O `getMenuContent()` herdado de `CommonGLPI`/`CommonDBTM` já monta a entrada
|
||||||
|
(título, ícone, links search/add) a partir de `getTypeName()`, `getIcon()`,
|
||||||
|
`getSearchURL()` e `canCreate()` — **gated por `canView()`** (que depende do right).
|
||||||
|
Não precisa sobrescrever `getMenuContent()` para um CRUD simples.
|
||||||
|
|
||||||
## Gotcha — Instalação via CLI
|
## Gotcha — Instalação via CLI
|
||||||
|
|
||||||
`bin/console glpi:plugin:install -f <plugin>` roda **sem sessão GLPI**, então `$_SESSION['glpiactiveprofile']['id']` é `null` e `createFirstAccess()` não é executado. Resultado: rights são registrados na tabela mas com valor `0` (sem permissão real).
|
`bin/console glpi:plugin:install -f <plugin>` roda **sem sessão GLPI**, então `$_SESSION['glpiactiveprofile']['id']` é `null` e `createFirstAccess()` não é executado. Resultado: rights são registrados na tabela mas com valor `0` (sem permissão real).
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
---
|
||||||
|
id: KB-PLUGIN-034
|
||||||
|
title: "GLPI 11 — Cifrar credenciais de sistemas externos em itemtype de plugin (GLPIKey)"
|
||||||
|
domain: plugin-dev
|
||||||
|
tags:
|
||||||
|
- glpi11
|
||||||
|
- plugin
|
||||||
|
- security
|
||||||
|
- credentials
|
||||||
|
- glpikey
|
||||||
|
- encryption
|
||||||
|
- commondbtm
|
||||||
|
- itemtype
|
||||||
|
status: active
|
||||||
|
severity: high
|
||||||
|
created_at: 2026-06-16
|
||||||
|
updated_at: 2026-06-16
|
||||||
|
applies_to:
|
||||||
|
- plugins GLPI 11.x que guardam senha/token de sistema externo
|
||||||
|
- plugin Sync (conexão com o PROD legado)
|
||||||
|
related_records:
|
||||||
|
- KB-PLUGIN-028
|
||||||
|
- KB-PLUGIN-032
|
||||||
|
---
|
||||||
|
|
||||||
|
# GLPI 11 — Cifrar credenciais externas em itemtype de plugin
|
||||||
|
|
||||||
|
## Contexto
|
||||||
|
|
||||||
|
Plugins que se conectam a sistemas externos (banco do PROD, API de terceiros,
|
||||||
|
LDAP secundário) precisam guardar uma senha/token. Guardar em texto puro na
|
||||||
|
tabela é inaceitável. O GLPI já oferece a mesma cifra que usa para senhas de
|
||||||
|
e-mail/config: a classe `\GLPIKey` (chave em `GLPI_CONFIG_DIR/glpicrypt.key`).
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```php
|
||||||
|
$key = new \GLPIKey();
|
||||||
|
$cipher = $key->encrypt($plaintext); // string -> string cifrada
|
||||||
|
$plain = $key->decrypt($cipher); // string|null -> string|null
|
||||||
|
```
|
||||||
|
|
||||||
|
## Padrão no itemtype (CommonDBTM)
|
||||||
|
|
||||||
|
Cifrar no `prepareInputForAdd/Update`, decifrar sob demanda. **Senha vazia no
|
||||||
|
update = manter a atual** (não sobrescrever com vazio):
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function prepareInputForAdd($input) { return $this->encryptPasswordInput($input); }
|
||||||
|
public function prepareInputForUpdate($input) { return $this->encryptPasswordInput($input); }
|
||||||
|
|
||||||
|
private function encryptPasswordInput(array $input): array
|
||||||
|
{
|
||||||
|
if (!array_key_exists('db_password', $input)) {
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
if ($input['db_password'] === '' || $input['db_password'] === null) {
|
||||||
|
unset($input['db_password']); // mantém a senha atual
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
$input['db_password'] = (new \GLPIKey())->encrypt((string) $input['db_password']);
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDecryptedPassword(): string
|
||||||
|
{
|
||||||
|
$enc = (string) ($this->fields['db_password'] ?? '');
|
||||||
|
return $enc === '' ? '' : (string) (new \GLPIKey())->decrypt($enc);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
No form Twig, o campo de senha deve renderizar **sempre vazio** (nunca ecoar a
|
||||||
|
cifra) e avisar que vazio mantém a atual:
|
||||||
|
|
||||||
|
```twig
|
||||||
|
{{ fields.passwordField('db_password', '', __('Senha', 'sync'),
|
||||||
|
{'helper': __('Deixe em branco para manter a senha atual', 'sync')}) }}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- A coluna do banco guarda a **cifra** (use `TEXT`/`VARCHAR` amplo; a cifra é
|
||||||
|
maior que o texto puro).
|
||||||
|
- `decrypt()` pode retornar `null` — sempre fazer cast/checagem.
|
||||||
|
- Não logar nem expor a cifra em search options visíveis ao usuário comum.
|
||||||
|
- Cuidado se a `glpicrypt.key` mudar entre ambientes: cifra feita num GLPI não
|
||||||
|
decifra noutro com chave diferente (relevante em migração/cutover).
|
||||||
|
|
||||||
|
## Validação
|
||||||
|
|
||||||
|
Comprovado no plugin `sync` (2026-06-16): teste E2E criou Source com senha,
|
||||||
|
confirmou que `db_password` em repouso ≠ texto puro, que `getDecryptedPassword()`
|
||||||
|
devolve o original, e que update com senha vazia preserva a cifra anterior.
|
||||||
|
|
||||||
|
## Classificação
|
||||||
|
|
||||||
|
- Tipo: padrão de segurança reutilizável.
|
||||||
|
- Reutilização: qualquer plugin que persista credencial de sistema externo.
|
||||||
Loading…
Reference in a new issue