2017-08-04 16:28:16 +02:00
< ? php
/**
* 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 */
2017-08-24 10:39:32 +02:00
protected static $passwordOptions = [
2017-08-07 13:07:18 +02:00
'algorithm' => PASSWORD_BCRYPT ,
'options' => [],
];
2017-09-04 10:24:44 +02:00
/** @var int Opzioni per la protezione contro attacchi brute-force */
protected static $brute = [
'attemps' => 3 ,
'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 */
2017-08-24 10:39:32 +02:00
protected $infos ;
2017-08-29 12:42:42 +02:00
/** @var string 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
$id = $database -> fetchArray ( 'SELECT `id_utente` FROM `zz_tokens` WHERE `token` = ' . prepare ( $token ))[ 0 ][ 'id_utente' ];
}
// 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 = [];
$log [ 'username' ] = $username ;
$log [ 'ip' ] = get_client_ip ();
2017-08-07 13:07:18 +02:00
$log [ 'stato' ] = self :: $status [ 'failed' ][ 'code' ];
2017-08-04 16:28:16 +02:00
2017-09-04 10:24:44 +02:00
$users = $database -> fetchArray ( 'SELECT id AS id_utente, password, enabled FROM zz_users WHERE username = ' . prepare ( $username ) . ' LIMIT 1' );
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' ])) {
2017-09-01 18:13:25 +02:00
$this -> identifyUser ( $user [ 'id_utente' ]);
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 () &&
2017-09-01 18:13:25 +02:00
$this -> password_check ( $password , $user [ 'password' ], $user [ 'id_utente' ]) &&
2017-08-07 13:07:18 +02:00
! empty ( $module )
) {
2017-09-04 16:04:45 +02:00
// Accesso completato
2017-09-01 18:13:25 +02:00
$log [ 'id_utente' ] = $this -> infos [ 'id_utente' ];
2017-08-07 13:07:18 +02:00
$log [ 'stato' ] = self :: $status [ 'success' ][ 'code' ];
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 )) {
$log [ 'stato' ] = self :: $status [ 'unauthorized' ][ 'code' ];
}
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 {
$log [ 'stato' ] = self :: $status [ 'disabled' ][ 'code' ];
2017-08-04 16:28:16 +02:00
}
}
2017-09-04 16:04:45 +02:00
// Salvataggio dello stato nella sessione
2017-08-07 13:07:18 +02:00
if ( $log [ 'stato' ] != self :: $status [ 'success' ][ 'code' ]) {
foreach ( self :: $status as $key => $value ) {
if ( $log [ 'stato' ] == $value [ 'code' ]) {
$_SESSION [ 'errors' ][] = $value [ 'message' ];
break ;
}
}
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
/**
* Controlla la corrispondeza delle password ed eventalmente effettua un rehashing .
*
* @ param string $password
* @ param string $hash
* @ param int $user_id
*/
2017-08-07 13:07:18 +02:00
protected function password_check ( $password , $hash , $user_id = null )
2017-08-04 16:28:16 +02:00
{
2017-08-07 13:07:18 +02:00
$result = 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 )) {
2017-08-24 10:39:32 +02:00
$rehash = password_needs_rehash ( $hash , self :: $passwordOptions [ 'algorithm' ], self :: $passwordOptions [ '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 ()) {
2017-09-01 18:13:25 +02:00
foreach ( $this -> infos as $key => $value ) {
$_SESSION [ $key ] = $value ;
}
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 {
2017-09-04 10:43:22 +02:00
$results = $database -> fetchArray ( 'SELECT id AS id_utente, idanagrafica, username, (SELECT nome FROM zz_groups WHERE zz_groups.id=zz_users.idgruppo) AS gruppo FROM zz_users WHERE id = ' . prepare ( $user_id ) . ' AND enabled = 1 LIMIT 1' , false , [ 'session' => false ]);
2017-08-30 14:43:38 +02:00
2017-08-30 11:50:46 +02:00
if ( ! empty ( $results )) {
$results [ 0 ][ 'is_admin' ] = ( $results [ 0 ][ 'gruppo' ] == 'Amministratori' );
2017-08-04 16:28:16 +02:00
2017-08-30 11:50:46 +02:00
$this -> infos = $results [ 0 ];
}
} 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 ()
{
return ! empty ( $this -> infos );
}
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 ()
{
return $this -> isAuthenticated () && ! empty ( $this -> infos [ 'is_admin' ]);
}
2017-08-29 12:42:42 +02:00
/**
* Restituisce le informazioni riguardanti l ' utente autenticato .
*
* @ return array
*/
2017-08-07 13:07:18 +02:00
public function getUser ()
{
return $this -> infos ;
}
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' ])) {
2017-08-07 13:07:18 +02:00
$this -> infos = null ;
$this -> first_module = null ;
session_unset ();
session_destroy ();
session_start ();
session_regenerate_id ();
2017-08-04 16:28:16 +02:00
2017-08-07 13:07:18 +02:00
$_SESSION [ 'infos' ] = [];
$_SESSION [ 'warnings' ] = [];
$_SESSION [ 'errors' ] = [];
}
}
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 .
*
* @ return string
*/
2017-08-07 13:07:18 +02:00
public function getFirstModule ()
{
if ( empty ( $this -> first_module )) {
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 ()) {
2017-08-24 10:39:32 +02:00
$query .= ' AND id IN (SELECT idmodule FROM zz_permissions WHERE idgruppo = (SELECT id FROM zz_groups WHERE nome = ' . prepare ( $this -> getUser ()[ 'gruppo' ]) . " ) AND permessi IN ('r', 'rw')) " ;
2017-08-04 16:28:16 +02:00
}
2017-08-07 13:07:18 +02:00
$database = Database :: getConnection ();
$results = $database -> fetchArray ( $query . " AND options != '' AND options != 'menu' AND options IS NOT NULL ORDER BY `order` ASC " );
2017-08-04 16:28:16 +02:00
if ( ! empty ( $results )) {
$module = null ;
2017-08-07 13:07:18 +02:00
$first = Settings :: get ( 'Prima pagina' );
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
*
* @ return string
*/
2017-08-24 10:39:32 +02:00
public static function hashPassword ( $password )
{
return password_hash ( $password , self :: $passwordOptions [ 'algorithm' ], self :: $passwordOptions [ 'options' ]);
}
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 .
*
* @ return array
*/
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 ();
2017-09-05 17:31:58 +02:00
if ( $database -> isInstalled () || ! $database -> fetchNum ( " SHOW TABLES LIKE 'zz_logs' " )) {
2017-09-04 10:43:22 +02:00
return false ;
}
if ( ! isset ( self :: $is_brute )) {
$results = $database -> fetchArray ( 'SELECT COUNT(*) AS tot FROM zz_logs WHERE ip = ' . prepare ( get_client_ip ()) . ' AND stato = ' . prepare ( self :: getStatus ()[ 'failed' ][ 'code' ]) . ' AND DATE_ADD(created_at, INTERVAL ' . self :: $brute [ 'timeout' ] . ' SECOND) >= NOW()' );
2017-09-04 11:53:30 +02:00
2017-09-04 10:43:22 +02:00
self :: $is_brute = $results [ 0 ][ 'tot' ] > self :: $brute [ 'attemps' ];
}
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-04 10:43:22 +02:00
if ( self :: isBrute ()) {
return 0 ;
}
2017-09-04 10:24:44 +02:00
$database = Database :: getConnection ();
$results = $database -> fetchArray ( 'SELECT TIME_TO_SEC(TIMEDIFF(DATE_ADD(created_at, INTERVAL ' . self :: $brute [ 'timeout' ] . ' SECOND), NOW())) AS diff FROM zz_logs WHERE ip = ' . prepare ( get_client_ip ()) . ' AND stato = ' . prepare ( self :: getStatus ()[ 'failed' ][ 'code' ]) . ' AND DATE_ADD(created_at, INTERVAL ' . self :: $brute [ 'timeout' ] . ' SECOND) >= NOW() ORDER BY created_at DESC LIMIT 1' );
return intval ( $results [ 0 ][ 'diff' ]);
}
2017-08-04 16:28:16 +02:00
}