Dados e segurança
Sessões, usuários e permissões
Sessões
Session::create() lê config/sessions.php, e cada controlador abre a sessão ao ser construído ($this->Session).
<?php
// config/sessions.php
$CONFIG = [
'Sessions' => [
'handler' => ['engine' => 'DatabaseSession'], // omita para usar os arquivos do PHP
'defaults' => 'database',
'cookie' => 'meusite_session', // nome do cookie
'timeout' => 2040, // minutos (session.gc_maxlifetime)
'cookiePath' => '/', // opcional
'ini' => ['session.cookie_samesite' => 'Lax'], // diretivas ini opcionais
],
];
Com DatabaseSession, as sessões ficam na tabela do modelo sessions, o que permite ver e encerrar sessões pelo painel. Por padrão, o Koshkil ativa session.use_strict_mode, session.cookie_httponly e, sob HTTPS, session.cookie_secure.
$this->Session->write('carrito', [15 => 2]);
$this->Session->read('carrito');
$this->Session->check('carrito'); // existe?
$this->Session->consume('aviso'); // lê e apaga
$this->Session->delete('carrito');
$this->Session->renew(); // novo id (use depois do login)
$this->Session->destroy();
Usuários
Os usuários são o modelo usuarios do núcleo (TMUsuarios, tabela tbl_usuarios). Campos principais:
| Campo | Conteúdo |
|---|---|
usr_codigo |
Chave primária. |
usr_parent |
Usuário "dono" (0 = usuário principal). Os subusuários pertencem a um principal. |
usr_user, usr_email |
Identificadores para login. |
usr_pass |
Hash da senha (passwordUtils::createHash()). |
usr_nombre, usr_apellido, usr_telefono… |
Dados pessoais. |
usr_estado |
1 = ativo. |
usr_hash |
Token de ativação e redefinição de senha. |
usr_registrado, usr_uvisita, usr_ulogout |
Datas de cadastro, última visita e último logout. |
usr_enable_mfa, usr_enabled_mfa |
Segundo fator (TOTP, veja totpUtils). |
Cada plugin ativo pode estender o modelo com um comportamento <plugin>.usuarios.
Sessão do site e do painel
O site e o painel usam variáveis de sessão diferentes, então é possível estar logado em um e não no outro:
| Área | Classe base | Variável de sessão |
|---|---|---|
| Site público | SuperController, FrontAjaxController |
Session.UserHandler.Frontend (padrão frontend_user) |
| Painel | AdminSuperController, AdminAjaxController |
Session.UserHandler.Backend (padrão backend_user) |
A variável guarda o usr_codigo. init() carrega o usuário em $this->usuario e o passa para a view como $usuario.
Senhas
Koshkil::Uses('sys.tools.utils.passwordUtils');
$hash = passwordUtils::createHash($senha); // para gravar
$ok = passwordUtils::createHash($senha, $usuario->usr_pass); // verifica: o hash se coincidir, false se não
Warning
createHash() usa SHA-1 com sal, um esquema legado. Para sistemas novos vale avaliar o password_hash() do PHP; o campo usr_pass tem 40 caracteres, então seria preciso ampliá-lo no modelo.
Proteger um controlador
class ReportesController extends AdminSuperController {
protected $openAccess = false; // exige login (já é assim no AdminSuperController)
protected $roles = 'Administrador|Contador'; // um destes papéis…
protected $rules = 'reportes|reportes.view'; // …ou uma destas regras
protected $strictRules = 'reportes.edit'; // e obrigatoriamente esta regra
}
- Sem sessão, o controlador chama
processLogin()e exibelogin.tpl. - Com sessão,
checkPermissions()verifica duas coisas. Se alguma falhar, exibecommon/access_denied.tple não executa a ação:- se houver
$strictRules, o usuário precisa ter alguma dessas regras; - se houver
$rules, o usuário precisa ter algum dos$rolesou alguma das$rules. Com$rolesvazio, o papel aceito éSuperusuario.
- se houver
$roles sozinho não basta
$roles só é avaliado junto com $rules. Um controlador que declara apenas $roles não restringe o acesso por papel; nesse caso, valide em init() com $this->usuario->hasRoles(...).
Papéis e regras
- Uma regra é uma permissão com nome (
noticias,categorias…). - Um papel (role) agrupa regras (
Superusuario,Administrador,ABM Noticias…). - Cada regra atribuída a um papel tem atributos por bits:
| Atributo | Constante | Valor |
|---|---|---|
add |
TMRules::ADD_RECORD |
1 |
edit |
TMRules::EDIT_RECORD |
2 |
delete |
TMRules::DELETE_RECORD |
4 |
view |
TMRules::VIEW_RECORD |
8 |
15 (1+2+4+8) é acesso total; 8, somente leitura.
Verificação de permissões
$u = $this->usuario;
$u->loadPermissions(); // os controladores já fazem isso
$u->hasRoles('Administrador|Editor'); // algum dos papéis
$u->hasRules('noticias|categorias'); // alguma das regras
$u->hasRules('noticias&categorias'); // todas
$u->checkRule('noticias.edit'); // a regra com o atributo "edit"
$u->checkRule('noticias.delete', 'noticias'); // no contexto de um plugin
$u->hasRoleOrRule('Administrador', 'noticias');
Nos templates:
{rule_or_role rules="noticias.edit"}
<a href="{Koshkil::getLink('/admin/noticias/editar/'|cat:$noticia->not_codigo)}">Editar</a>
{/rule_or_role}
{rule_or_role roles="Superusuario"}…{/rule_or_role}
Papéis e regras são administrados no painel, em Sistema → Roles / Reglas / Usuarios. Os plugins declaram os seus em config/permissions.php (veja Plugins).
reCAPTCHA
Com as chaves configuradas, o SuperController adiciona o script do Google reCAPTCHA v3 a todas as páginas e recaptcha.js anexa um token a cada requisição AJAX. No servidor:
<?php
// config/domains/<domínio>/google.php
$CONFIG = [
'Google' => [
'Recaptcha' => [
'SiteKey' => '…',
'SecretKey' => '…',
'MinScore' => 0.5, // opcional
],
],
];
// Em um FrontAjaxController
if (!$this->recaptchaOk()) {
return $this->jsonize(['status' => 'error', 'message' => $this->recaptchaError()]);
}
Sem chaves configuradas, ou se o Google não responder, RecaptchaVerifier::verify() deixa a requisição passar.
Lista de verificação
- Passe os dados do usuário como valores de
where()/having()(são escapados automaticamente). Em SQL escrito à mão useKoshkil::escapeString()ouintval(); veja Modelos. - Escape a saída nos templates (
{$texto|escape}). - Nunca pegue o id do usuário do formulário: use
$this->usuario->usr_codigo. - Renove o id da sessão depois do login (
$this->Session->renew()). - Mantenha
Debug.Levelem 0 em produção econfig/fora de qualquer repositório público.