Sfoglia il codice sorgente

Implement password hashing and verification for admin users; update legacy plaintext handling and improve password management

szisz 2 mesi fa
parent
commit
9befdc4515

+ 25 - 0
application/helpers/admin_utils.php

@@ -2,6 +2,31 @@
 
 class admin_utils {
 
+    // Bcrypt hash for an admin password.
+    static function hashPassword($plain) {
+        return password_hash($plain, PASSWORD_DEFAULT);
+    }
+
+    // Verify a plain password against a stored value.
+    // Returns true on match. Handles legacy plaintext passwords:
+    // if the stored value is not a recognised hash, it falls back to a
+    // direct comparison so existing accounts keep working until rehashed.
+    static function verifyPassword($plain, $stored) {
+        $info = password_get_info($stored);
+        if ($info['algo'] !== null && $info['algo'] !== 0) {
+            return password_verify($plain, $stored);
+        }
+        // Legacy plaintext fallback.
+        return hash_equals((string)$stored, (string)$plain);
+    }
+
+    // Whether a stored password value is still legacy plaintext and should
+    // be upgraded to a proper hash after a successful login.
+    static function passwordNeedsRehash($stored) {
+        $info = password_get_info($stored);
+        return ($info['algo'] === null || $info['algo'] === 0);
+    }
+
     static function dbconnect() {
         global $config;
         global $conn;

+ 6 - 5
application/models/admin_admins_model.php

@@ -53,7 +53,8 @@ class admin_admins_model extends Model {
                     . "admin_real_name='".$data['admin_real_name']."' where admin_id='".$data['admin_id']."';");
             
             if ($data['admin_pass']!='' && $data['admin_pass']==$data['admin_pass_repeat']) {
-                $this->execute("update azonics_admin_users set admin_pass='".$data['admin_pass']."' where admin_id='".$data['admin_id']."';");
+                $hash = admin_utils::hashPassword($data['admin_pass']);
+                $this->execute("update azonics_admin_users set admin_pass='".$this->escapeString($hash)."' where admin_id='".$data['admin_id']."';");
             }
             
             if ($data['tempContainer']!='') {
@@ -67,9 +68,8 @@ class admin_admins_model extends Model {
             $checkAlredy = $this->query("select * from azonics_admin_users where "
                     . "admin_name='".$data['admin_name']."' and "
                     . "admin_email='".$data['admin_email']."' and "
-                    . "admin_pass='".$data['admin_pass']."' and "
                     . "admin_status='1';");
-            
+
             if (sizeof($checkAlredy)>0) {
                 return false;
             }
@@ -77,11 +77,12 @@ class admin_admins_model extends Model {
                 if ($data['tempContainer']!='') {
                     $fname = Image_helper::saveImageToFile('avatars',$data['tempContainer']);
                 }
-                
+
+                $hash = admin_utils::hashPassword($data['admin_pass']);
                 $this->execute("insert into azonics_admin_users set "
                     . "admin_name='".$data['admin_name']."', "
                     . "admin_email='".$data['admin_email']."', "
-                    . "admin_pass='".$data['admin_pass']."', "
+                    . "admin_pass='".$this->escapeString($hash)."', "
                     . "admin_avatar='".$fname."', "
                     . "admin_status='1', "
                     . "admin_access_level='".$data['admin_access_level']."', "

+ 18 - 4
application/models/admin_login_model.php

@@ -6,12 +6,19 @@ class admin_login_model extends Model {
     public function logmein() {
         $data = $this->escapeArray($_REQUEST);
         if ($_REQUEST['email']!='' && $_REQUEST['pass']!='') {
+            // Look the user up by identity only; the password is verified in PHP
+            // so we can support hashed passwords (and legacy plaintext during migration).
             $result = $this->query("select * from azonics_admin_users where "
                     . "admin_status='1' and "
-                    . "(admin_email='".$data['email']."' or admin_name='".$data['email']."') and "
-                    . "admin_pass='".$data['pass']."';");
-            
-            if (sizeof($result)>0) {
+                    . "(admin_email='".$data['email']."' or admin_name='".$data['email']."');");
+
+            if (sizeof($result)>0 && admin_utils::verifyPassword($_REQUEST['pass'], $result[0]->admin_pass)) {
+                // Upgrade legacy plaintext passwords to a proper hash on first valid login.
+                if (admin_utils::passwordNeedsRehash($result[0]->admin_pass)) {
+                    $newHash = admin_utils::hashPassword($_REQUEST['pass']);
+                    $this->execute("update azonics_admin_users set admin_pass='".$this->escapeString($newHash)."' where admin_id='".$result[0]->admin_id."';");
+                    $result[0]->admin_pass = $newHash;
+                }
                 $this->execute("update azonics_admin_users set admin_last_ip='".$_SERVER['REMOTE_ADDR']."' where admin_id='".$result[0]->admin_id."';");
                 $_SESSION['admin_user'] = $result[0];
                 $_SESSION['access_level'] = $result[0]->admin_access_level;
@@ -37,6 +44,13 @@ class admin_login_model extends Model {
         $data = $this->escapeArray($_REQUEST);
         $result = $this->query("select * from azonics_admin_users where admin_email='".$data['email']."' and admin_status='1';");
         if (sizeof($result)>0) {
+            // Passwords are stored hashed and can no longer be recovered, so we
+            // generate a new temporary password, store it hashed, and return the
+            // plaintext value so the caller can e-mail it to the user.
+            $tempPass = bin2hex(random_bytes(6));
+            $hash = admin_utils::hashPassword($tempPass);
+            $this->execute("update azonics_admin_users set admin_pass='".$this->escapeString($hash)."' where admin_id='".$result[0]->admin_id."';");
+            $result[0]->admin_pass = $tempPass;
             return $result[0];
         }
         else {

+ 14 - 5
application/models/api_model.php

@@ -3,9 +3,10 @@
 class api_model extends Model {
 
   //private $API = 'https://hoponticket.com/api/index.php';
-  private $API = 'https://preprod2.bbus.umsbox.hu/api/index.php';
+  //private $API = 'https://preprod2.bbus.umsbox.hu/api/index.php';
   //private $API = 'https://php82fpm.umsbox.hu/api/index.php';
   //private $API = 'https://szollosil.bbus.umsbox.hu/api/index.php';
+  private $API = 'https://preprod.bbus.umsbox.hu/api/index.php';
 
   private $API_KEY = '92JxvN5Zeti4E1FDwKg0QPEl3md4vY63';
 
@@ -946,14 +947,22 @@ class api_model extends Model {
   }
 
   public function authenticateUser($username, $password) {
-    $row = $this->query("SELECT * FROM `azonics_admin_users` WHERE admin_pass='".$password."' AND (admin_email='".$username."' OR admin_name='".$username."') AND admin_status='1';");
-    if (count($row) == 0) {
+    // Look the user up by identity only; the password is verified in PHP
+    // so we can support hashed passwords (and legacy plaintext during migration).
+    $username = $this->escapeString($username);
+    $row = $this->query("SELECT * FROM `azonics_admin_users` WHERE (admin_email='".$username."' OR admin_name='".$username."') AND admin_status='1';");
+    if (count($row) == 0 || !admin_utils::verifyPassword($password, $row[0]->admin_pass)) {
       return ['success' => false];
     }
     else {
+      // Upgrade legacy plaintext passwords to a proper hash on first valid login.
+      if (admin_utils::passwordNeedsRehash($row[0]->admin_pass)) {
+        $newHash = admin_utils::hashPassword($password);
+        $this->execute("UPDATE `azonics_admin_users` SET admin_pass='".$this->escapeString($newHash)."' WHERE admin_id='".$row[0]->admin_id."';");
+      }
       return [
-        'success' => true, 
-        'name' => $row[0]->admin_name, 
+        'success' => true,
+        'name' => $row[0]->admin_name,
         'hotel' => $row[0]->admin_dashboard
       ];
     }

+ 2 - 2
application/views/admin_admins.php

@@ -125,11 +125,11 @@
                             </div>
                             <div class="form-group">
                                 <label for="admin_pass"><?=lang::_('Password')?>: </label>
-                                <input type="password" name="admin_pass" autocomplete="new-password" value="<?=$active->admin_pass?>" class="form-control" id="admin_pass" data-validate="true" />
+                                <input type="password" name="admin_pass" autocomplete="new-password" value="" class="form-control" id="admin_pass"<?php if ($active->admin_id=='') echo ' data-validate="true"'; ?> />
                             </div>
                             <div class="form-group">
                                 <label for="admin_pass_repeat"><?=lang::_('Retype password')?>: </label>
-                                <input type="password" name="admin_pass_repeat" value="<?=$active->admin_pass?>" class="form-control" id="admin_pass_repeat" data-validate="true" />
+                                <input type="password" name="admin_pass_repeat" value="" class="form-control" id="admin_pass_repeat"<?php if ($active->admin_id=='') echo ' data-validate="true"'; ?> />
                             </div>
                             <div class="form-group">
                                 <label for="admin_access_level"><?=lang::_('Access level')?>: </label>

+ 2 - 2
application/views/admin_myprofile.php

@@ -39,11 +39,11 @@
                             </div>
                             <div class="form-group">
                                 <label for="admin_pass"><?=lang::_('Password')?>: </label>
-                                <input type="password" name="admin_pass" value="<?=$active->admin_pass?>" class="form-control" id="admin_pass" data-validate="true" />
+                                <input type="password" name="admin_pass" autocomplete="new-password" value="" placeholder="<?=lang::_('Leave empty to keep current')?>" class="form-control" id="admin_pass" />
                             </div>
                             <div class="form-group">
                                 <label for="admin_pass_repeat"><?=lang::_('Retype password')?>: </label>
-                                <input type="password" name="admin_pass_repeat" value="<?=$active->admin_pass?>" class="form-control" id="admin_pass_repeat" data-validate="true" />
+                                <input type="password" name="admin_pass_repeat" value="" class="form-control" id="admin_pass_repeat" />
                             </div>
                             <input type="hidden" name="admin_access_level" value="<?=$active->admin_access_level?>" />
                         </div>

+ 1 - 1
application/views/monitoring_view.php

@@ -34,7 +34,7 @@
                         </thead>
                         <tbody>
                             <?php
-                                if ($_SESSION['admin_user']->admin_dashboard != '' && $_SESSION['admin_user']->admin_dashboard != 'N;') {
+                                if ($_SESSION['admin_user']->admin_dashboard != '' && $_SESSION['admin_user']->admin_dashboard != 'N;' && $_SESSION['admin_user']->admin_access_level != '11') {
                                     $avaiableHotels = unserialize($_SESSION['admin_user']->admin_dashboard);
                                     if ($avaiableHotels === false) {
                                         $avaiableHotels = [];