El framework PHP detrás de SitioYa

Documentación de Koshkil

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 muestra login.tpl.
  • Con sesión, checkPermissions() verifica dos cosas. Si alguna falla, muestra common/access_denied.tpl y 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 $roles o alguna de las $rules. Con $roles vacío, el rol aceptado es Superusuario.

$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() o intval(); 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.Level en 0 en producción y config/ fuera del repositorio público.