2017-08-04 16:28:16 +02:00
< ? php
2018-08-10 17:14:09 +02:00
use Models\User ;
2017-08-04 16:28:16 +02:00
/**
* Classe per la gestione delle utenze .
*
* @ since 2.3
*/
2017-08-07 13:07:18 +02:00
class Auth extends \Util\Singleton
2017-08-04 16:28:16 +02:00
{
2017-08-29 12:42:42 +02:00
/** @var array Stati previsti dal sistema di autenticazione */
2017-08-07 13:07:18 +02:00
protected static $status = [
'success' => [
'code' => 1 ,
'message' => 'Login riuscito!' ,
],
'failed' => [
'code' => 0 ,
'message' => 'Autenticazione fallita!' ,
],
'disabled' => [
'code' => 2 ,
'message' => 'Utente non abilitato!' ,
],
'unauthorized' => [
'code' => 3 ,
'message' => " L'utente non ha nessun permesso impostato! " ,
],
];
2017-08-29 12:42:42 +02:00
/** @var array Opzioni di sicurezza relative all'hashing delle password */
2018-07-08 16:18:44 +02:00
protected static $password_options = [
2017-08-07 13:07:18 +02:00
'algorithm' => PASSWORD_BCRYPT ,
'options' => [],
];
2018-06-26 16:33:15 +02:00
/** @var array Opzioni per la protezione contro attacchi brute-force */
2018-07-08 16:18:44 +02:00
protected static $brute_options = [
'attemps' => 3 ,
2017-09-04 10:24:44 +02:00
'timeout' => 180 ,
];
2017-09-04 10:43:22 +02:00
/** @var bool Informazioni riguardanti la condizione brute-force */
protected static $is_brute ;
2017-09-04 10:24:44 +02:00
2017-08-29 12:42:42 +02:00
/** @var array Informazioni riguardanti l'utente autenticato */
2018-08-10 17:14:09 +02:00
protected $user ;
2018-07-08 16:18:44 +02:00
/** @var string Stato del tentativo di accesso */
protected $current_status ;
2018-06-26 16:33:15 +02:00
/** @var string|null Nome del primo modulo su cui l'utente ha permessi di navigazione */
2017-08-24 10:39:32 +02:00
protected $first_module ;
2017-08-07 13:07:18 +02:00
protected function __construct ()
2017-08-04 16:28:16 +02:00
{
$database = Database :: getConnection ();
2017-08-07 13:07:18 +02:00
if ( $database -> isInstalled ()) {
2017-09-04 16:04:45 +02:00
// Controllo dell'accesso da API
2017-08-07 13:07:18 +02:00
if ( API :: isAPIRequest ()) {
2017-08-29 12:42:42 +02:00
$token = API :: getRequest ()[ 'token' ];
2017-08-07 13:07:18 +02:00
2018-07-14 11:41:42 +02:00
$user = $database -> fetchArray ( 'SELECT `id_utente` FROM `zz_tokens` WHERE `enabled` = 1 AND `token` = :token' , [
':token' => $token ,
]);
2018-05-03 17:29:10 +02:00
$id = ! empty ( $user ) ? $user [ 0 ][ 'id_utente' ] : null ;
2017-08-07 13:07:18 +02:00
}
// Controllo sulla sessione attiva
2017-09-01 18:13:25 +02:00
elseif ( ! empty ( $_SESSION [ 'id_utente' ])) {
$id = $_SESSION [ 'id_utente' ];
2017-08-07 13:07:18 +02:00
}
if ( ! empty ( $id )) {
$this -> identifyUser ( $id );
}
2017-09-01 18:13:25 +02:00
$this -> saveToSession ();
2017-08-04 16:28:16 +02:00
}
}
2017-08-29 12:42:42 +02:00
/**
* Effettua un tentativo di accesso con le credenziali fornite .
*
* @ param string $username
* @ param string $password
*
* @ return bool
*/
2017-08-04 16:28:16 +02:00
public function attempt ( $username , $password )
{
session_regenerate_id ();
2017-09-04 16:04:45 +02:00
// Controllo sulla disponibilità dell'accesso (brute-forcing non in corso)
2017-09-04 10:24:44 +02:00
if ( self :: isBrute ()) {
return false ;
}
2017-08-04 16:28:16 +02:00
$database = Database :: getConnection ();
$log = [];
2018-06-26 16:33:15 +02:00
$log [ 'username' ] = $username ;
2017-08-04 16:28:16 +02:00
$log [ 'ip' ] = get_client_ip ();
2018-07-08 16:18:44 +02:00
$status = 'failed' ;
2017-08-04 16:28:16 +02:00
2018-08-10 17:14:09 +02:00
$users = $database -> fetchArray ( 'SELECT id, password, enabled FROM zz_users WHERE username = :username LIMIT 1' , [
2018-07-14 11:41:42 +02:00
':username' => $username ,
]);
2017-08-04 16:28:16 +02:00
if ( ! empty ( $users )) {
$user = $users [ 0 ];
2017-08-07 13:07:18 +02:00
if ( ! empty ( $user [ 'enabled' ])) {
2018-08-10 17:14:09 +02:00
$this -> identifyUser ( $user [ 'id' ]);
2017-08-07 13:07:18 +02:00
$module = $this -> getFirstModule ();
2017-08-04 16:28:16 +02:00
2017-08-07 13:07:18 +02:00
if (
$this -> isAuthenticated () &&
2018-08-10 17:14:09 +02:00
$this -> password_check ( $password , $user [ 'password' ], $user [ 'id' ]) &&
2017-08-07 13:07:18 +02:00
! empty ( $module )
) {
2017-09-04 16:04:45 +02:00
// Accesso completato
2018-08-10 17:14:09 +02:00
$log [ 'id_utente' ] = $this -> user -> id ;
2018-07-08 16:18:44 +02:00
$status = 'success' ;
2017-08-04 16:28:16 +02:00
2017-09-04 16:04:45 +02:00
// Salvataggio nella sessione
2017-08-07 13:07:18 +02:00
$this -> saveToSession ();
2017-08-04 16:28:16 +02:00
} else {
2017-08-07 13:07:18 +02:00
if ( empty ( $module )) {
2018-07-08 16:18:44 +02:00
$status = 'unauthorized' ;
2017-08-07 13:07:18 +02:00
}
2017-09-04 16:04:45 +02:00
// Logout automatico
2017-09-01 18:13:25 +02:00
$this -> destory ();
2017-08-04 16:28:16 +02:00
}
2017-08-07 13:07:18 +02:00
} else {
2018-07-08 16:18:44 +02:00
$status = 'disabled' ;
2017-08-04 16:28:16 +02:00
}
}
2018-07-08 16:18:44 +02:00
// Salvataggio dello stato corrente
$log [ 'stato' ] = self :: getStatus ()[ $status ][ 'code' ];
$this -> current_status = $status ;
2017-08-04 16:28:16 +02:00
2017-09-04 16:04:45 +02:00
// Salvataggio del tentativo nel database
2017-08-07 13:07:18 +02:00
$database -> insert ( 'zz_logs' , $log );
2017-08-04 16:28:16 +02:00
2017-08-07 13:07:18 +02:00
return $this -> isAuthenticated ();
2017-08-04 16:28:16 +02:00
}
2017-08-29 12:42:42 +02:00
/**
2018-07-08 16:18:44 +02:00
* Controlla la corrispondenza delle password ed eventualmente effettua un rehashing .
2017-08-29 12:42:42 +02:00
*
* @ param string $password
* @ param string $hash
* @ param int $user_id
*/
2018-07-08 16:18:44 +02:00
protected function password_check ( $password , $hash , $user_id )
2017-08-04 16:28:16 +02:00
{
2017-08-07 13:07:18 +02:00
$result = false ;
2018-06-26 16:33:15 +02:00
$rehash = false ;
2017-08-04 16:28:16 +02:00
2017-08-07 13:07:18 +02:00
// Retrocompatibilità
if ( $hash == md5 ( $password )) {
$rehash = true ;
2017-08-04 16:28:16 +02:00
2017-08-07 13:07:18 +02:00
$result = true ;
2017-08-04 16:28:16 +02:00
}
2017-08-07 13:07:18 +02:00
// Nuova versione
2017-08-04 16:28:16 +02:00
if ( password_verify ( $password , $hash )) {
2018-07-08 16:18:44 +02:00
$rehash = password_needs_rehash ( $hash , self :: $password_options [ 'algorithm' ], self :: $password_options [ 'options' ]);
2017-08-07 13:07:18 +02:00
$result = true ;
2017-08-04 16:28:16 +02:00
}
2017-08-07 13:07:18 +02:00
// Controllo in automatico per futuri cambiamenti dell'algoritmo di password
if ( $rehash ) {
$database = Database :: getConnection ();
2017-09-04 10:43:22 +02:00
$database -> update ( 'zz_users' , [ 'password' => self :: hashPassword ( $password )], [ 'id' => $user_id ]);
2017-08-07 13:07:18 +02:00
}
return $result ;
2017-08-04 16:28:16 +02:00
}
2017-08-29 12:42:42 +02:00
/**
* Memorizza le informazioni riguardanti l 'utente all' interno della sessione .
*/
2017-08-07 13:07:18 +02:00
protected function saveToSession ()
2017-08-04 16:28:16 +02:00
{
2017-09-04 10:24:44 +02:00
if ( session_status () == PHP_SESSION_ACTIVE && $this -> isAuthenticated ()) {
2018-08-10 17:14:09 +02:00
// Retrocompatibilità
foreach ( $this -> user as $key => $value ) {
2017-09-01 18:13:25 +02:00
$_SESSION [ $key ] = $value ;
}
2018-08-10 17:14:09 +02:00
$_SESSION [ 'id_utente' ] = $this -> user -> id ;
2017-08-07 13:07:18 +02:00
2017-09-01 18:13:25 +02:00
$identifier = md5 ( $_SESSION [ 'id_utente' ] . $_SERVER [ 'HTTP_USER_AGENT' ]);
if (( empty ( $_SESSION [ 'last_active' ]) || time () < $_SESSION [ 'last_active' ] + ( 60 * 60 )) && ( empty ( $_SESSION [ 'identifier' ]) || $_SESSION [ 'identifier' ] == $identifier )) {
$_SESSION [ 'last_active' ] = time ();
$_SESSION [ 'identifier' ] = $identifier ;
}
2017-08-07 13:07:18 +02:00
}
2017-08-04 16:28:16 +02:00
}
2017-08-29 12:42:42 +02:00
/**
* Identifica l 'utente interessato dall' autenticazione .
*
* @ param int $user_id
*/
2017-08-07 13:07:18 +02:00
protected function identifyUser ( $user_id )
2017-08-04 16:28:16 +02:00
{
$database = Database :: getConnection ();
2017-08-30 11:50:46 +02:00
try {
2018-08-10 17:14:09 +02:00
$results = $database -> fetchArray ( 'SELECT id, idanagrafica, username, (SELECT nome FROM zz_groups WHERE zz_groups.id = zz_users.idgruppo) AS gruppo FROM zz_users WHERE id = :user_id AND enabled = 1 LIMIT 1' , [
2018-07-14 11:41:42 +02:00
':user_id' => $user_id ,
], false , [ 'session' => false ]);
2017-08-30 14:43:38 +02:00
2017-08-30 11:50:46 +02:00
if ( ! empty ( $results )) {
2018-08-10 17:14:09 +02:00
$this -> user = User :: with ( 'group' ) -> find ( $user_id );
2017-08-30 11:50:46 +02:00
}
} catch ( PDOException $e ) {
$this -> destory ();
2017-08-07 13:07:18 +02:00
}
}
2017-08-29 12:42:42 +02:00
/**
* Controlla se l ' utente è autenticato .
*
* @ return bool
*/
2017-08-07 13:07:18 +02:00
public function isAuthenticated ()
{
2018-08-10 17:14:09 +02:00
return ! empty ( $this -> user );
2017-08-07 13:07:18 +02:00
}
2017-08-29 12:42:42 +02:00
/**
* Controlla se l ' utente appartiene al gruppo degli Amministratori .
*
* @ return bool
*/
2017-08-07 13:07:18 +02:00
public function isAdmin ()
{
2018-08-10 17:14:09 +02:00
return $this -> isAuthenticated () && ! empty ( $this -> user -> is_admin );
2017-08-07 13:07:18 +02:00
}
2017-08-29 12:42:42 +02:00
/**
* Restituisce le informazioni riguardanti l ' utente autenticato .
*
2018-08-10 17:14:09 +02:00
* @ return User
2017-08-29 12:42:42 +02:00
*/
2017-08-07 13:07:18 +02:00
public function getUser ()
{
2018-08-10 17:14:09 +02:00
return $this -> user ;
2017-08-07 13:07:18 +02:00
}
2018-07-08 16:18:44 +02:00
/**
* Restituisce lo stato corrente .
*
* @ return string
*/
public function getCurrentStatus ()
{
return $this -> current_status ;
}
2018-02-20 14:23:00 +01:00
/**
* Restituisce il token di accesso all 'API per l' utente autenticato .
*
* @ return string
*/
public function getToken ()
{
$token = null ;
if ( $this -> isAuthenticated ()) {
$user = self :: user ();
$database = Database :: getConnection ();
2018-07-14 11:41:42 +02:00
$tokens = $database -> fetchArray ( 'SELECT `token` FROM `zz_tokens` WHERE `enabled` = 1 AND `id_utente` = :user_id' , [
2018-08-10 17:14:09 +02:00
':user_id' => $user -> id ,
2018-07-14 11:41:42 +02:00
]);
2018-02-20 14:23:00 +01:00
// Generazione del token per l'utente
if ( empty ( $tokens )) {
$token = secure_random_string ();
$database -> insert ( 'zz_tokens' , [
2018-08-10 17:14:09 +02:00
'id_utente' => $user -> id ,
2018-02-20 14:23:00 +01:00
'token' => $token ,
]);
} else {
$token = $tokens [ 0 ][ 'token' ];
}
}
return $token ;
}
2017-08-29 12:42:42 +02:00
/**
* Distrugge le informazioni riguardanti l ' utente autenticato , forzando il logout .
*/
2017-08-07 13:07:18 +02:00
public function destory ()
{
2017-09-01 18:13:25 +02:00
if ( $this -> isAuthenticated () || ! empty ( $_SESSION [ 'id_utente' ])) {
2018-08-10 17:14:09 +02:00
$this -> user = [];
2017-08-07 13:07:18 +02:00
$this -> first_module = null ;
session_unset ();
session_regenerate_id ();
2017-08-04 16:28:16 +02:00
2018-07-27 12:17:17 +02:00
if ( ! API :: isAPIRequest ()) {
flash () -> clearMessages ();
}
2017-08-07 13:07:18 +02:00
}
}
2017-08-04 16:28:16 +02:00
2017-08-29 12:42:42 +02:00
/**
* Restituisce il nome del primo modulo navigabile dall ' utente autenticato .
*
2018-06-26 16:33:15 +02:00
* @ return string | null
2017-08-29 12:42:42 +02:00
*/
2017-08-07 13:07:18 +02:00
public function getFirstModule ()
{
if ( empty ( $this -> first_module )) {
2018-08-08 19:32:20 +02:00
$parameters = [];
2017-08-04 16:28:16 +02:00
$query = 'SELECT id FROM zz_modules WHERE enabled = 1' ;
2017-08-07 13:07:18 +02:00
if ( ! $this -> isAdmin ()) {
2018-07-14 11:41:42 +02:00
$query .= " AND id IN (SELECT idmodule FROM zz_permissions WHERE idgruppo = (SELECT id FROM zz_groups WHERE nome = :group) AND permessi IN ('r', 'rw')) " ;
2018-08-08 19:32:20 +02:00
$parameters [ ':group' ] = $this -> getUser ()[ 'gruppo' ];
2017-08-04 16:28:16 +02:00
}
2017-08-07 13:07:18 +02:00
$database = Database :: getConnection ();
2018-08-08 19:32:20 +02:00
$results = $database -> fetchArray ( $query . " AND options != '' AND options != 'menu' AND options IS NOT NULL ORDER BY `order` ASC " , $parameters );
2017-08-04 16:28:16 +02:00
if ( ! empty ( $results )) {
$module = null ;
2018-07-09 10:44:54 +02:00
$first = setting ( 'Prima pagina' );
2017-08-07 13:07:18 +02:00
if ( ! in_array ( $first , array_column ( $results , 'id' ))) {
$module = $results [ 0 ][ 'id' ];
2017-08-04 16:28:16 +02:00
} else {
$module = $first ;
}
2017-08-07 13:07:18 +02:00
$this -> first_module = $module ;
2017-08-04 16:28:16 +02:00
}
}
2017-08-07 13:07:18 +02:00
return $this -> first_module ;
2017-08-04 16:28:16 +02:00
}
2017-08-29 12:42:42 +02:00
/**
* Restituisce l ' hashing della password per la relativa memorizzazione nel database .
*
* @ param string $password
*
2018-06-26 16:33:15 +02:00
* @ return string | bool
2017-08-29 12:42:42 +02:00
*/
2017-08-24 10:39:32 +02:00
public static function hashPassword ( $password )
{
2018-07-08 16:18:44 +02:00
return password_hash ( $password , self :: $password_options [ 'algorithm' ], self :: $password_options [ 'options' ]);
2017-08-24 10:39:32 +02:00
}
2017-08-31 11:32:49 +02:00
/**
* Restituisce l ' elenco degli stati del sistema di autenticazione .
*
* @ return array
*/
public static function getStatus ()
{
return self :: $status ;
}
/**
* Controlla se l ' utente è autenticato .
*
* @ return bool
*/
2017-08-07 13:07:18 +02:00
public static function check ()
2017-08-04 16:28:16 +02:00
{
2017-08-07 13:07:18 +02:00
return self :: getInstance () -> isAuthenticated ();
2017-08-04 16:28:16 +02:00
}
2017-08-31 11:32:49 +02:00
/**
* Controlla se l ' utente appartiene al gruppo degli Amministratori .
*
* @ return bool
*/
2017-08-07 13:07:18 +02:00
public static function admin ()
2017-08-04 16:28:16 +02:00
{
2017-08-07 13:07:18 +02:00
return self :: getInstance () -> isAdmin ();
2017-08-04 16:28:16 +02:00
}
2017-08-31 11:32:49 +02:00
/**
* Restituisce le informazioni riguardanti l ' utente autenticato .
*
2018-08-10 17:14:09 +02:00
* @ return User
2017-08-31 11:32:49 +02:00
*/
2017-08-07 13:07:18 +02:00
public static function user ()
2017-08-04 16:28:16 +02:00
{
2017-08-07 13:07:18 +02:00
return self :: getInstance () -> getUser ();
2017-08-04 16:28:16 +02:00
}
2017-08-31 11:32:49 +02:00
/**
* Distrugge le informazioni riguardanti l ' utente autenticato , forzando il logout .
*/
2017-08-04 16:28:16 +02:00
public static function logout ()
{
2017-08-07 13:07:18 +02:00
return self :: getInstance () -> destory ();
2017-08-04 16:28:16 +02:00
}
2017-08-29 12:42:42 +02:00
/**
2017-08-31 11:32:49 +02:00
* Restituisce il nome del primo modulo navigabile dall ' utente autenticato .
2017-08-29 12:42:42 +02:00
*
2017-08-31 11:32:49 +02:00
* @ return string
2017-08-29 12:42:42 +02:00
*/
2017-08-31 11:32:49 +02:00
public static function firstModule ()
2017-08-04 16:28:16 +02:00
{
2017-08-31 11:32:49 +02:00
return self :: getInstance () -> getFirstModule ();
2017-08-04 16:28:16 +02:00
}
2017-09-04 10:24:44 +02:00
/**
2017-09-04 10:43:22 +02:00
* Controlla se sono in corso molti tentativi di accesso ( possibile brute - forcing in corso ) .
2017-09-04 10:24:44 +02:00
*
* @ return bool
*/
public static function isBrute ()
{
$database = Database :: getConnection ();
2018-07-03 15:05:27 +02:00
if ( ! $database -> isInstalled () || ! $database -> tableExists ( 'zz_logs' ) || Update :: isUpdateAvailable ()) {
2017-09-04 10:43:22 +02:00
return false ;
}
if ( ! isset ( self :: $is_brute )) {
2018-07-14 11:41:42 +02:00
$results = $database -> fetchArray ( 'SELECT COUNT(*) AS tot FROM zz_logs WHERE ip = :ip AND stato = :state AND DATE_ADD(created_at, INTERVAL :timeout SECOND) >= NOW()' , [
':ip' => get_client_ip (),
':state' => self :: getStatus ()[ 'failed' ][ 'code' ],
':timeout' => self :: $brute_options [ 'timeout' ],
]);
2017-09-04 11:53:30 +02:00
2018-07-08 16:18:44 +02:00
self :: $is_brute = $results [ 0 ][ 'tot' ] > self :: $brute_options [ 'attemps' ];
2017-09-04 10:43:22 +02:00
}
2017-09-04 10:24:44 +02:00
2017-09-04 10:43:22 +02:00
return self :: $is_brute ;
2017-09-04 10:24:44 +02:00
}
/**
2017-09-04 10:43:22 +02:00
* Restituisce il tempo di attesa rimanente per lo sblocco automatico dellla protezione contro attacchi brute - force .
2017-09-04 10:24:44 +02:00
*
* @ return int
*/
public static function getBruteTimeout ()
{
2017-09-20 15:10:23 +02:00
if ( ! self :: isBrute ()) {
2017-09-04 10:43:22 +02:00
return 0 ;
}
2017-09-04 10:24:44 +02:00
$database = Database :: getConnection ();
2018-07-14 11:41:42 +02:00
$results = $database -> fetchArray ( 'SELECT TIME_TO_SEC(TIMEDIFF(DATE_ADD(created_at, INTERVAL ' . self :: $brute_options [ 'timeout' ] . ' SECOND), NOW())) AS diff FROM zz_logs WHERE ip = :ip AND stato = :state AND DATE_ADD(created_at, INTERVAL :timeout SECOND) >= NOW() ORDER BY created_at DESC LIMIT 1' , [
':ip' => get_client_ip (),
':state' => self :: getStatus ()[ 'failed' ][ 'code' ],
':timeout' => self :: $brute_options [ 'timeout' ],
]);
2017-09-04 10:24:44 +02:00
return intval ( $results [ 0 ][ 'diff' ]);
}
2017-08-04 16:28:16 +02:00
}