Datos y seguridad
Sesiones, usuarios y permisos
Sesiones
Session::create() lee config/sessions.php y cada controlador abre la sesión al construirse ($this->Session).
<?php
// config/sessions.php
$CONFIG = [
'Sessions' => [
'handler' => ['engine' => 'DatabaseSession'], // omitir para usar archivos de PHP
'defaults' => 'database',
'cookie' => 'misitio_session', // nombre de la cookie
'timeout' => 2040, // minutos (session.gc_maxlifetime)
'cookiePath' => '/', // opcional
'ini' => ['session.cookie_samesite' => 'Lax'], // directivas ini opcionales
],
];
Con DatabaseSession las sesiones se guardan en la tabla del modelo sessions, lo que permite ver y cerrar sesiones desde el panel. Koshkil activa por defecto session.use_strict_mode, session.cookie_httponly y, bajo HTTPS, session.cookie_secure.
$this->Session->write('carrito', [15 => 2]);
$this->Session->read('carrito');
$this->Session->check('carrito'); // ¿existe?
$this->Session->consume('aviso'); // lee y borra
$this->Session->delete('carrito');
$this->Session->renew(); // nuevo id (usar después del login)
$this->Session->destroy();
Usuarios
Los usuarios son el modelo usuarios del núcleo (TMUsuarios, tabla tbl_usuarios). Campos principales:
| Campo | Contenido |
|---|---|
usr_codigo |
Clave primaria. |
usr_parent |
Usuario "dueño" (0 = usuario principal). Los subusuarios pertenecen a un principal. |
usr_user, usr_email |
Identificadores para iniciar sesión. |
usr_pass |
Hash de la contraseña (passwordUtils::createHash()). |
usr_nombre, usr_apellido, usr_telefono… |
Datos personales. |
usr_estado |
1 = activo. |
usr_hash |
Token para activación y restablecimiento de contraseña. |
usr_registrado, usr_uvisita, usr_ulogout |
Fechas de alta, última visita y último cierre de sesión. |
usr_enable_mfa, usr_enabled_mfa |
Segundo factor (TOTP, ver totpUtils). |
Cada plugin activo puede extender el modelo con un comportamiento <plugin>.usuarios.
Sesión del sitio y del panel
El sitio y el panel usan variables de sesión distintas, así que se puede estar logueado en uno y no en el otro:
| Zona | Clase base | Variable de sesión |
|---|---|---|
| Sitio público | SuperController, FrontAjaxController |
Session.UserHandler.Frontend (por defecto frontend_user) |
| Panel | AdminSuperController, AdminAjaxController |
Session.UserHandler.Backend (por defecto backend_user) |
La variable guarda el usr_codigo. init() carga el usuario en $this->usuario y lo asigna a la vista como $usuario.
Contraseñas
Koshkil::Uses('sys.tools.utils.passwordUtils');
$hash = passwordUtils::createHash($clave); // para guardar
$ok = passwordUtils::createHash($clave, $usuario->usr_pass); // verifica: el hash si coincide, false si no
Warning
createHash() usa SHA-1 con sal, un esquema heredado. Para sistemas nuevos conviene evaluar password_hash() de PHP; el campo usr_pass tiene 40 caracteres, así que habría que ampliarlo en el modelo.
Proteger un controlador
class ReportesController extends AdminSuperController {
protected $openAccess = false; // exige login (ya es así en AdminSuperController)
protected $roles = 'Administrador|Contador'; // alguno de estos roles…
protected $rules = 'reportes|reportes.view'; // …o alguna de estas reglas
protected $strictRules = 'reportes.edit'; // y obligatoriamente esta regla
}
- Sin sesión, el controlador llama a
processLogin()y muestralogin.tpl. - Con sesión,
checkPermissions()verifica dos cosas. Si alguna falla, muestracommon/access_denied.tply no ejecuta la acción:- si hay
$strictRules, el usuario debe tener alguna de esas reglas; - si hay
$rules, el usuario debe tener alguno de los$roleso alguna de las$rules. Con$rolesvacío, el rol aceptado esSuperusuario.
- si hay
$roles solo no alcanza
$roles se evalúa únicamente junto con $rules. Un controlador que declara solo $roles no restringe el acceso por rol; en ese caso validá en init() con $this->usuario->hasRoles(...).
Roles y reglas
- Una regla es un permiso con nombre (
noticias,categorias…). - Un rol agrupa reglas (
Superusuario,Administrador,ABM Noticias…). - Cada regla asignada a un rol lleva 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) es acceso completo; 8, solo lectura.
Consultas de permisos
$u = $this->usuario;
$u->loadPermissions(); // los controladores ya lo hacen
$u->hasRoles('Administrador|Editor'); // alguno de los roles
$u->hasRules('noticias|categorias'); // alguna de las reglas
$u->hasRules('noticias&categorias'); // todas
$u->checkRule('noticias.edit'); // la regla con el atributo "edit"
$u->checkRule('noticias.delete', 'noticias'); // en el contexto de un plugin
$u->hasRoleOrRule('Administrador', 'noticias');
En las plantillas:
{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}
Los roles y reglas se administran en el panel, en Sistema → Roles / Reglas / Usuarios. Los plugins declaran los suyos en config/permissions.php (ver Plugins).
reCAPTCHA
Con claves configuradas, SuperController agrega el script de Google reCAPTCHA v3 a todas las páginas y recaptcha.js adjunta un token a cada petición AJAX. En el servidor:
<?php
// config/domains/<dominio>/google.php
$CONFIG = [
'Google' => [
'Recaptcha' => [
'SiteKey' => '…',
'SecretKey' => '…',
'MinScore' => 0.5, // opcional
],
],
];
// En un FrontAjaxController
if (!$this->recaptchaOk()) {
return $this->jsonize(['status' => 'error', 'message' => $this->recaptchaError()]);
}
Sin claves configuradas, o si Google no responde, RecaptchaVerifier::verify() deja pasar la petición.
Lista de control
- Pasá los datos del usuario como valores de
where()/having()(se escapan solos). En SQL escrito a mano usáKoshkil::escapeString()ointval(); ver Modelos. - Escapá la salida en las plantillas (
{$texto|escape}). - Nunca tomes el id del usuario del formulario: usá
$this->usuario->usr_codigo. - Renová el id de sesión después del login (
$this->Session->renew()). - Mantené
Debug.Levelen 0 en producción yconfig/fuera del repositorio público.