O framework PHP por trás do SitioYa

Documentação do Koshkil

Dados e segurança

Sessões, usuários e permissões

Sessões

Session::create()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 exibe login.tpl.
  • Com sessão, checkPermissions() verifica duas coisas. Se alguma falhar, exibe common/access_denied.tpl e 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 $roles ou alguma das $rules. Com $roles vazio, o papel aceito é Superusuario.

$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 use Koshkil::escapeString() ou intval(); 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.Level em 0 em produção e config/ fora de qualquer repositório público.