CVE-2023-3095 (published 2023-06-04T11:15Z)

Improper Access Control in GitHub repository nilsteampassnet/teampass prior to 3.0.9.

CVSS v3 base score: 6.5, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N)

Weakness information:
cwe_id: CWE-284
cwe_name: Improper Access Control
description: The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor.
extended_description: 
            Access control involves the use of several protection mechanisms such as:
            
               Authentication (proving the identity of an actor)
               Authorization (ensuring that a given actor can access a resource), and
               Accountability (tracking of activities that were performed)
            
            When any mechanism is not applied or otherwise fails, attackers can compromise the security of the product by gaining privileges, reading sensitive information, executing commands, evading detection, etc.
            There are two distinct behaviors that can introduce access control weaknesses:
               
                  Specification: incorrect privileges, permissions, ownership, etc. are explicitly specified for either the user or the resource (for example, setting a password file to be world-writable, or giving administrator capabilities to a guest user). This action could be performed by the program or the administrator.
                  Enforcement: the mechanism contains errors that prevent it from properly enforcing the specified access control requirements (e.g., allowing the user to specify their own privileges, or allowing a syntactically-incorrect ACL to produce insecure settings). This problem occurs within the program itself, in that it does not actually enforce the intended security policy that the administrator specifies.
               
         
		 

Fix information:
repo_url: https://github.com/nilsteampassnet/teampass
commit_details: repo_url: https://github.com/nilsteampassnet/teampass
author: nilsteampassnet
author_date: 2023-06-04 11:20:08+02:00
msg: 3.0.9

Fix Broken Access Control On Item via ID
repository: repo_url: https://github.com/nilsteampassnet/teampass
repo_name: nilsteampassnet/TeamPass
description: Collaborative Passwords Manager
repo_language: PHP
owner: nilsteampassnet
file_changes: filename: install.queries.php
change_type: ModificationType.MODIFY
diff: @@ -430,8 +430,9 @@ if (null !== $post_type) {
                             `auto_update_pwd_next_date` varchar(100) NOT null DEFAULT '0',
                             `encryption_type` VARCHAR(20) NOT NULL DEFAULT 'not_set',
                             `fa_icon` varchar(100) DEFAULT NULL,
+                            `item_key` varchar(500) NOT NULL DEFAULT '1',
                             PRIMARY KEY (`id`),
-                            KEY    `restricted_inactif_idx` (`restricted_to`,`inactif`)
+                            KEY `restricted_inactif_idx` (`restricted_to`,`inactif`)
                             ) CHARSET=utf8;"
                         );
                     } elseif ($task === 'log_items') {

programming_language: PHP
filename: upgrade.php
change_type: ModificationType.MODIFY
diff: @@ -1090,11 +1090,17 @@ function sendPwdToUsers(usersList, init, cpt, total)
 function runUpdate (script_file, type_parameter, start_at, noitems_by_loop, loop_number, file_number)
 {
     var d = new Date(),
-        info = '';
+        info = '',
+        text_action_in_progress = script_file;
     loop_number ++;
     var rand_number = createRandomId();
 
-    $("#step4_progress").html("<div>" + getTime() +" - <i>"+script_file+"</i> - Loop #"+loop_number+" <span id='span_"+rand_number+"'>is now running ... <i class=\"fas fa-cog fa-spin\" style=\"color:orange\"></i></span></div>"+ $("#step4_progress").html());
+    // Inform
+    if (script_file === 'upgrade_operations.php') {
+        text_action_in_progress = 'Performing operation ' + type_parameter;
+    }
+
+    $("#step4_progress").html("<div>" + getTime() +" - <i>"+text_action_in_progress+"</i> - Loop #"+loop_number+" <span id='span_"+rand_number+"'>is now running ... <i class=\"fas fa-cog fa-spin\" style=\"color:orange\"></i></span></div>"+ $("#step4_progress").html());
 
     if (type_parameter === 'user_id') {
         info = $("#infotmp").val();

programming_language: PHP
filename: upgrade_operations.php
change_type: ModificationType.ADD
diff: @@ -0,0 +1,141 @@
+<?php
+/**
+ * Teampass - a collaborative passwords manager.
+ * ---
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * ---
+ * @project   Teampass
+ * @file      upgrade_operations.php
+ * ---
+ * @author    Nils Laumaillé (nils@teampass.net)
+ * @copyright 2009-2023 Teampass.net
+ * @license   https://spdx.org/licenses/GPL-3.0-only.html#licenseText GPL-3.0
+ * ---
+ * @see       https://www.teampass.net
+ */
+
+set_time_limit(600);
+
+
+require_once '../sources/SecureHandler.php';
+session_name('teampass_session');
+session_start();
+error_reporting(E_ERROR | E_PARSE);
+$_SESSION['CPM'] = 1;
+
+//include librairies
+require_once '../includes/language/english.php';
+require_once '../includes/config/include.php';
+require_once '../includes/config/settings.php';
+require_once '../sources/main.functions.php';
+require_once '../includes/libraries/Tree/NestedTree/NestedTree.php';
+require_once 'tp.functions.php';
+require_once 'libs/aesctr.php';
+require_once '../includes/config/tp.config.php';
+
+// Prepare POST variables
+$post_nb = filter_input(INPUT_POST, 'nb', FILTER_SANITIZE_NUMBER_INT);
+$post_start = filter_input(INPUT_POST, 'start', FILTER_SANITIZE_NUMBER_INT);
+
+// Load libraries
+require_once '../includes/libraries/protect/SuperGlobal/SuperGlobal.php';
+$superGlobal = new protect\SuperGlobal\SuperGlobal();
+
+// Some init
+$_SESSION['settings']['loaded'] = '';
+$finish = true;
+
+// Get the encrypted password
+define('DB_PASSWD_CLEAR', defuse_return_decrypted(DB_PASSWD));
+
+// DataBase
+// Test DB connexion
+$pass = DB_PASSWD_CLEAR;
+$server = DB_HOST;
+$pre = DB_PREFIX;
+$database = DB_NAME;
+$port = DB_PORT;
+$user = DB_USER;
+
+if (mysqli_connect(
+    $server,
+    $user,
+    $pass,
+    $database,
+    $port
+)) {
+    $db_link = mysqli_connect(
+        $server,
+        $user,
+        $pass,
+        $database,
+        $port
+    );
+} else {
+    $res = 'Impossible to get connected to server. Error is: ' . addslashes(mysqli_connect_error());
+    echo '[{"finish":"1", "msg":"", "error":"Impossible to get connected to server. Error is: ' . addslashes(mysqli_connect_error()) . '!"}]';
+    mysqli_close($db_link);
+    exit();
+}
+
+// Load libraries
+require_once '../includes/libraries/protect/SuperGlobal/SuperGlobal.php';
+$superGlobal = new protect\SuperGlobal\SuperGlobal();
+
+// Get POST with operation to perform
+$post_operation = filter_input(INPUT_POST, 'type', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
+if (isset($post_operation) === true && empty($post_operation) === false) {
+    // ---->
+    // OPERATION - 20230604_1 - generate key for item_key
+
+    // Get items to treat
+    $rows = mysqli_query(
+        $db_link,
+        "SELECT id FROM ".$pre."items
+        WHERE item_key = '-1'
+        ORDER BY id
+        LIMIT ".$post_nb.";"
+    );
+    // Handle error on query
+    if (!$rows) {
+        echo '[{"finish":"1" , "error":"'.mysqli_error($db_link).'"}]';
+        exit();
+    }
+
+    // Get total of items to treat
+    $total = mysqli_num_rows($rows);
+
+    // Loop on items and update for requested ones
+    if ((int) $total > 0) {
+        while ($row = mysqli_fetch_array($rows, MYSQLI_ASSOC)) {
+            // Gererate a key and update
+            mysqli_query(
+                $db_link,
+                "UPDATE `".$pre."items`
+                SET `item_key` = '".uniqidReal(50)."'
+                WHERE `id` = ".$row['id'].";"
+            );
+            if (mysqli_error($db_link)) {
+                echo '[{"finish":"1", "next":"", "error":"MySQL Error! '.addslashes(mysqli_error($db_link)).'"}]';
+                exit();
+            }
+        }
+    }
+
+    // Manage end of operation
+    if ($total === 0) {
+        $finish = 1;
+    } else {
+        $finish = 0;
+    }
+    // ----<
+}
+
+// Close connection
+mysqli_close($db_link);
+
+
+// Return back
+echo '[{"finish":"'.$finish.'" , "next":"", "error":"", "total":"'.$total.'"}]';
\ No newline at end of file

programming_language: PHP
filename: upgrade_run_3.0.php
change_type: ModificationType.MODIFY
diff: @@ -7,7 +7,7 @@
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  * ---
  * @project   Teampass
- * @file      upgrade_run_3.0.php
+ * @file      upgrade.php
  * ---
  * @author    Nils Laumaillé (nils@teampass.net)
  * @copyright 2009-2023 Teampass.net
@@ -16,250 +16,1224 @@
  * @see       https://www.teampass.net
  */
 
-set_time_limit(600);
 
+header('X-XSS-Protection: 1; mode=block');
+header('X-Frame-Options: SameOrigin');
+
+// **PREVENTING SESSION HIJACKING**
+// Prevents javascript XSS attacks aimed to steal the session ID
+ini_set('session.cookie_httponly', 1);
+
+// **PREVENTING SESSION FIXATION**
+// Session ID cannot be passed through URLs
+ini_set('session.use_only_cookies', 1);
+
+// Uses a secure connection (HTTPS) if possible
+ini_set('session.cookie_secure', 0);
 
 require_once '../sources/SecureHandler.php';
 session_name('teampass_session');
 session_start();
-error_reporting(E_ERROR | E_PARSE);
+//Session teampass tag
 $_SESSION['CPM'] = 1;
+define('MIN_PHP_VERSION', 7.4);
+define('MIN_MYSQL_VERSION', '8.0.13');
+define('MIN_MARIADB_VERSION', '10.2.1');
+
+// Prepare POST variables
+$post_root_url = filter_input(INPUT_POST, 'root_url', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
+$post_step = filter_input(INPUT_POST, 'step', FILTER_SANITIZE_NUMBER_INT);
+$post_actual_cpm_version = filter_input(INPUT_POST, 'actual_cpm_version', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
+$post_cpm_isUTF8 = filter_input(INPUT_POST, 'cpm_isUTF8', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
+$post_user_granted = filter_input(INPUT_POST, 'user_granted', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
+$post_session_salt = filter_input(INPUT_POST, 'session_salt', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
+$post_url_path = filter_input(INPUT_POST, 'url_path', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
+$post_infotmp = filter_input(INPUT_POST, 'infotmp', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
+
+//###############
+//# Function permits to get the value from a line
+//###############
+/**
+ * @param string $val
+ */
+function getSettingValue($val)
+{
+    $val = trim(strstr($val, '='));
 
-//include librairies
+    return trim(str_replace('"', '', substr($val, 1, strpos($val, ';') - 1)));
+}
+
+//get infos from SETTINGS.PHP file
+$filename = '../includes/config/settings.php';
+$events = '';
+if (file_exists($filename)) {
+    include_once $filename;
+}
+
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+    <head>
+        <title>TeamPass Upgrade</title>
+        
+        <meta http-equiv='Content-Type' content='text/html;charset=utf-8' />
+        <meta name="viewport" content="width=device-width, initial-scale=1"/>
+        <meta http-equiv="x-ua-compatible" content="ie=edge"/>
+
+        <link rel="stylesheet" href="css/install.css" type="text/css" />
+        <link rel="stylesheet" href="../plugins/fontawesome-free-6/css/all.css">
+        
+        <!-- Theme style -->
+        <link rel="stylesheet" href="../plugins/adminlte/css/adminlte.min.css">
+        <link rel="stylesheet" href="../plugins/alertifyjs/css/alertify.min.css"/>
+        <link rel="stylesheet" href="../plugins/alertifyjs/css/themes/bootstrap.min.css"/>
+        
+        
+    </head>
+    
+    <body>
+<?php
 require_once '../includes/language/english.php';
 require_once '../includes/config/include.php';
-require_once '../includes/config/settings.php';
-require_once '../sources/main.functions.php';
-require_once '../includes/libraries/Tree/NestedTree/NestedTree.php';
-require_once 'tp.functions.php';
-require_once 'libs/aesctr.php';
-require_once '../includes/config/tp.config.php';
-
-// Get the encrypted password
-define('DB_PASSWD_CLEAR', defuse_return_decrypted(DB_PASSWD));
-
-/*
-//Build tree
-$tree = new Tree\NestedTree\NestedTree(
-    $pre . 'nested_tree',
-    'id',
-    'parent_id',
-    'title'
+
+if (empty($post_root_url) === false) {
+    $_SESSION['fullurl'] = $post_root_url;
+}
+
+//define root path
+$abs_path = rtrim(
+    filter_var($_SERVER['DOCUMENT_ROOT'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
+    '/'
+).substr(
+    filter_var($_SERVER['PHP_SELF'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
+    0,
+    strlen(filter_var($_SERVER['PHP_SELF'], FILTER_SANITIZE_FULL_SPECIAL_CHARS)) - 20
 );
-*/
-
-// DataBase
-// Test DB connexion
-$pass = DB_PASSWD_CLEAR;
-$server = DB_HOST;
-$pre = DB_PREFIX;
-$database = DB_NAME;
-$port = DB_PORT;
-$user = DB_USER;
-
-if (mysqli_connect(
-    $server,
-    $user,
-    $pass,
-    $database,
-    $port
-)) {
-    $db_link = mysqli_connect(
-        $server,
-        $user,
-        $pass,
-        $database,
-        $port
-    );
+if (isset($_SERVER['HTTPS'])) {
+    $protocol = 'https://';
 } else {
-    $res = 'Impossible to get connected to server. Error is: ' . addslashes(mysqli_connect_error());
-    echo '[{"finish":"1", "msg":"", "error":"Impossible to get connected to server. Error is: ' . addslashes(mysqli_connect_error()) . '!"}]';
-    mysqli_close($db_link);
-    exit();
+    $protocol = 'http://';
 }
 
-// Load libraries
-require_once '../includes/libraries/protect/SuperGlobal/SuperGlobal.php';
-$superGlobal = new protect\SuperGlobal\SuperGlobal();
 
+// HEADER
+echo '
+    <div id="top" class="center-screen">
+        <div id="logo" class="lcol"><img src="../includes/images/teampass-logo2-home.png" /></div>
+        <div class="lcol">
+            <span class="header-title">'.strtoupper(TP_TOOL_NAME).'</span>
+            <!--<span class="header-title-small"> v'.TP_VERSION.'</span>-->
+        </div>
+    <div id="content">
+        <form name="install" method="post" action="">
+        <div class="card card-default color-palette-box">
+            <div class="card-header">
+                <h3 class="card-title">
+                    <i class="fas fa-people-carry mr-2"></i>Teampass upgrade
+                </h3>
+            </div>
+            <div class="card-body">';
 
-//--->BEGIN 3.0.1
+//HIDDEN THINGS
+echo '
+                <input type="hidden" id="step" name="step" value="', isset($post_step) ? $post_step : '', '" />
+                <input type="hidden" id="actual_cpm_version" name="actual_cpm_version" value="', isset($post_actual_cpm_version) ? $post_actual_cpm_version : '', '" />
+                <input type="hidden" id="cpm_isUTF8" name="cpm_isUTF8" value="', isset($post_cpm_isUTF8) ? $post_cpm_isUTF8 : '', '" />
+                <input type="hidden" name="menu_action" id="menu_action" value="" />
+                <input type="hidden" name="user_granted" id="user_granted" value="" />
+                <input type="hidden" name="infotmp" id="infotmp" value="', isset($post_infotmp) ? $post_infotmp : '', '" />
+                <input type="hidden" name="url_path" id="url_path" value="'.$protocol.$_SERVER['HTTP_HOST'].substr($_SERVER['PHP_SELF'], 0, strrpos($_SERVER['PHP_SELF'], '/') - 8).'" />
+                <input type="hidden" name="session_salt" id="session_salt" value="', (isset($post_session_salt) && !empty($post_session_salt)) ? $post_session_salt : @$_SESSION['encrypt_key'], '" />';
 
-// Ensure admin user is ready
-mysqli_query(
-    $db_link,
-    "UPDATE ".$pre."users 
-    SET is_ready_for_usage = 1, otp_provided = 1 
-    WHERE id = 1"
-);
+if (!isset($_GET['step']) && !isset($post_step)) {
+    //ETAPE O
+    echo '
+                <div class="row">
+                    <div class="callout callout-warning col-12">
+                        <h5>Information</h5>
+    
+                        <p>Upgrade process is about to start. This will upgrade Teampass database to version '.TP_VERSION.'.</p>
+                        <p>Version 3 comes with a new secured encryption strategy getting rid of any Saltkey. It relies on public and private keys generated for each user. As an impact, this upgrade will automatically generate a One-Time-Code for each user and send by email. It will be requested on first login. Please ensure your users have filled in their email with a valid value.</p>
+                    </div>
 
-//---<END 3.0.1
+                    <div class="callout callout-info col-12">
+                        <h5>Before starting, take a couple of minutes to perform backup of current Teampass instance:</h5>
+    
+                        <p>
+                        <ul>
+                            <li><i class="fas fa-exclamation-circle mr-2 text-danger"></i>Ensure to clear your browser cache (keyboard: <i>CTRL + F5</i>)</li>
+                            <li><i class="fas fa-exclamation-triangle mr-2 text-warning"></i>Create a dump of your database</li>
+                            <li><i class="fas fa-exclamation-triangle mr-2 text-warning"></i>Perform a zip of the current Teampass folder</li>
+                            <li><i class="fas fa-info-circle mr-2 text-success"></i>Refer to <a href="https://teampass.readthedocs.io/en/latest/install/upgrade/" target="_blank" class="text-info">upgrade documentation</a>.</li>
+                        </ul>
+                        </p>
+                    </div>
+                </div>
 
+                <div class="row card card-primary">
+                    <div class="card-body col-12">
+                        <div class="form-group">
+                            <label>Administrator Login</label>
+                            <input type="text" class="form-control" id="user_login" placeholder="Enter admin login">
+                        </div>
+                        <div class="form-group">
+                            <label>Password</label>
+                            <input type="password" class="form-control" id="user_pwd" placeholder="Enter admin password">
+                        </div>
+                        <div class="alert alert-warning hidden" id="res_step0"></div>
+                    </div>
+                </div>
 
-//--->BEGIN 3.0.5
 
-// Add the INDEX process_id_idx to the processes_tasks table
-$res = checkIndexExist(
-    $pre . 'processes_tasks',
-    'process_id_idx',
-    "ADD KEY `process_id_idx` (`process_id`)"
-);
-if (!$res) {
-    echo '[{"finish":"1", "msg":"", "error":"An error appears when adding the INDEX process_id_idx to the processes_tasks table! ' . mysqli_error($db_link) . '!"}]';
-    mysqli_close($db_link);
-    exit();
-}
+                <input type="hidden" id="step0" name="step0" value="" />
 
-//---<END 3.0.5
+            </div>';
+// STEP1
+} elseif ((isset($post_step) && $post_step == 1)
+    || (isset($_GET['step']) && $_GET['step'] == 1)
+    && $post_user_granted === '1'
+) {
+    //ETAPE 1
+    $_SESSION['user_granted'] = $post_user_granted;
+    echo '
+            <div class="row">
+                <div class="col-12">
+                    <div class="card card-primary">
+                        <div class="card-header">
+                            <h5>Server information</h5>
+                        </div>
+                        <div class="card-body">
+                            <div class="form-group">
+                                <label>Absolute path to TeamPass folder</label>
+                                <input type="text" class="form-control" id="root_path" value="'.$abs_path.'">
+                            </div>
+                            <div class="form-group">
+                                <label>Full URL to TeamPass</label>
+                                <input type="text" class="form-control" id="root_url" value="'.$protocol.$_SERVER['HTTP_HOST'].substr($_SERVER['PHP_SELF'], 0, strrpos($_SERVER['PHP_SELF'], '/') - 8).'">
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
 
+            <div class="row">
+                <div class="col-12">
+                    <div class="card card-primary">
+                        <div class="card-header">
+                            <h5>Next elements will be checked</h5>
+                        </div>
+                        <div class="card-body">
 
-//--->BEGIN 3.0.6
-// Add new setting 'sending_emails_job_frequency'
-$tmp = mysqli_num_rows(mysqli_query($db_link, "SELECT * FROM `" . $pre . "misc` WHERE type = 'admin' AND intitule = 'sending_emails_job_frequency'"));
-if (intval($tmp) === 0) {
-    mysqli_query(
-        $db_link,
-        "INSERT INTO `" . $pre . "misc` (`type`, `intitule`, `valeur`) VALUES ('admin', 'sending_emails_job_frequency', '2')"
-    );
-}
-// Add new setting 'user_keys_job_frequency'
-$tmp = mysqli_num_rows(mysqli_query($db_link, "SELECT * FROM `" . $pre . "misc` WHERE type = 'admin' AND intitule = 'user_keys_job_frequency'"));
-if (intval($tmp) === 0) {
-    mysqli_query(
-        $db_link,
-        "INSERT INTO `" . $pre . "misc` (`type`, `intitule`, `valeur`) VALUES ('admin', 'user_keys_job_frequency', '1')"
-    );
+                            <div id="res_step1">
+                            <span>File "settings.php" is writable</span><br />
+                            <span>Directory "/install/" is writable</span><br />
+                            <span>Directory "/includes/" is writable</span><br />
+                            <span>Directory "/includes/config/" is writable</span><br />
+                            <span>Directory "/includes/avatars/" is writable</span><br />
+                            <span>Directory "/files/" is writable</span><br />
+                            <span>Directory "/upload/" is writable</span><br />
+                            <span>PHP extension "openssl" is loaded</span><br />
+                            <span>PHP extension "gd" is loaded</span><br />
+                            <span>PHP extension "mbstring" is loaded</span><br />
+                            <span>PHP extension "bcmath" is loaded</span><br />
+                            <span>PHP extension "iconv" is loaded</span><br />
+                            <span>PHP extension "xml" is loaded</span><br />
+                            <span>PHP extension "curl" is loaded</span><br />
+                            <span>PHP extension "gmp" is loaded</span><br />
+                            <span>PHP extension "mcrypt" is loaded</span><br />
+                            <span>PHP version is greater or equal to '.MIN_PHP_VERSION.'</span><br />
+                            <span>SQL version is greater or equal to MySQL '.MIN_MYSQL_VERSION.' or MariaDB '.MIN_MARIADB_VERSION.'</span><br />
+                            </div>
+                        
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <input type="hidden" id="step1" name="step1" value="" />';
+// STEP2
+} elseif ((isset($post_step) && $post_step == 2)
+    || (isset($_GET['step']) && $_GET['step'] == 2)
+    && $_SESSION['user_granted'] === '1'
+) {
+    // Do we have all database settings
+    if (null !== DB_HOST
+        && null !== DB_USER
+        && null !== DB_PASSWD
+        && null !== DB_NAME
+        && null !== DB_PREFIX
+        && null !== DB_PORT
+    ) {
+        $dbSettings = true;
+    } else {
+        $dbSettings = false;
+    }
+    //ETAPE 2
+    echo '
+        <div class="row">
+            <div class="col-12">
+                <div class="card card-',$dbSettings === true ? 'primary' : 'warning','">
+                    <div class="card-header">
+                        <h5>DataBase Informations</h5>
+                    </div>
+                    <div class="card-body">
+                        <!--<div class="form-group">
+                            <label>Host</label>
+                            <input type="text" class="form-control" name="db_host" id="db_host" class="ui-widget" value="'.DB_HOST.'">
+                        </div>
+                        
+                        <div class="form-group">
+                            <label>Database name</label>
+                            <input type="text" class="form-control" name="db_name" id="db_name" class="ui-widget" value="'.DB_NAME.'">
+                        </div>
+                        
+                        <div class="form-group">
+                            <label>Login</label>
+                            <input type="text" class="form-control" name="db_user" id="db_user" class="ui-widget" value="'.DB_USER.'">
+                        </div>
+                        
+                        <div class="form-group">
+                            <label>Password</label>
+                            <input type="text" class="form-control" name="db_pw" id="db_pw" class="ui-widget" value="'.$visible_pwd.'">
+                        </div>
+                        
+                        <div class="form-group">
+                            <label>Prefix</label>
+                            <input type="text" class="form-control" name="db_prefix" id="db_prefix" class="ui-widget" value="'.DB_PREFIX.'">
+                        </div>
+                        
+                        <div class="form-group">
+                            <label>Port</label>
+                            <input type="text" class="form-control" name="db_port" id="db_port" class="ui-widget" value="'.DB_PORT.'">
+                        </div>-->';
+
+    // check if all database  info are available
+    if ($dbSettings === true) {
+        echo '
+                        <div>
+                        Database settings has been retreived.<br>
+                        If you need to change them, please edit file `/includes/config/settings.php` and relaunch the upgrade process.
+                        </div>';
+    } else {
+        echo '
+                        <div>
+                        The database information has not been retreived from the settings file.<br>
+                        You need to adapt the file `/includes/config/settings.php` and relaunch the upgrade process.
+                        </div>';
+    }
+
+    echo '
+                        <a href="'.$protocol.$_SERVER['HTTP_HOST'].substr($_SERVER['PHP_SELF'], 0, strrpos($_SERVER['PHP_SELF'], '/') - 8).'/install/upgrade.php">Restart upgrade process</a>
+                    </div>
+                </div>
+
+                <div class="card card-primary">
+                    <div class="card-header">
+                        <h5>Maintenance Mode</h5>
+                    </div>
+                    <div class="card-body">
+                        <div>
+                            <input type="checkbox" class="mr-2" id="no_maintenance_mode">
+                            <label for="no_maintenance_mode">Don\'t activate the Maintenance mode</label>
+                        </div>
+                        <small class="form-text text-muted">
+                            By default, the maintenance mode is enabled when an Update is performed. This prevents the use of TeamPass while the scripts are running.<br />
+                            However, some administrators may prefer to warn the users in another way. Nevertheless, keep in mind that the update process may fail or even be corrupted due to parallel queries.
+                        </small>
+                    </div>
+                </div>
+
+                <div class="card card-primary">
+                    <div class="card-header">
+                        <h5>Database dump</h5>
+                    </div>
+                    <div class="card-body">
+                        <div>
+                            If you have NOT performed a dump of your database, please considere to create one now.
+                        </div>
+                        <div>
+                            <a href="#" onclick="launch_database_dump(); return false;">Launch a new database dump</a>
+                        </div>
+                        <div>
+                            <span id="dump_result" style="margin-top:4px;" class="card card-info"></span>
+                        </div>
+                    </div>
+                </div>
+
+                <!-- teampass_version = 2.1.27 and no encrypt_key in db -->
+                <div class="callout card-warning hidden" id="no_encrypt_key">
+                    <div class="card-header">
+                        <h5>Database Origine</h5>
+                    </div>
+                    <div class="card-body">
+                        <div>
+                            Please select:&nbsp;<select id="no_key_selection">
+                            <option value="false">-- select --</option>
+                            <option value="no_previous_sk_sel">We have never used Teampass in an older version than 2.1.27(.x)</option>
+                            <option value="previous_sk_sel">We have used Teampass in an older version (example: 2.1.26)</option>
+                        </select>
+                        <div id="previous_sk_div" style="display:none;">
+                            <p>Please use the next field to enter the saltkey you used in previous version of Teampass. It can be retrieved by editing sk.php file (in case you are upgrading from a version older than 2.1.27) or a sk.php backup file (in case you are upgrading from 2.1.27).<br>
+                            </p>
+                            <label for="previous_sk">Previous SaltKey:&nbsp</label>
+                            <input type="text" id="previous_sk" size="100px" value="'.@$_SESSION['encrypt_key'].'" />
+                        </div>
+                        </div>
+                    </div>
+                </div>
+
+                <div style="margin-top:20px;font-weight:bold;text-align:center;height:27px;" id="res_step2"></div>
+                <input type="hidden" id="step2" name="step2" value="">
+
+            </div>
+        </div>';
+
+
+// STEP3
+} elseif ((isset($post_step) && $post_step == 3 || isset($_GET['step']) && $_GET['step'] == 3)
+    && isset($post_actual_cpm_version)
+    && intVal($_SESSION['user_granted']) === 1
+) {
+    if (version_compare($post_actual_cpm_version, '2.1.26', '<')) {
+        $conversion_utf8 = true;
+    } else {
+        $conversion_utf8 = false;
+    }
+    echo '
+        <div class="card card-primary">
+            <div class="card-header">
+                <h5>Converting database to UTF-8</h5>
+            </div>
+            <div class="card-body">
+                <div>
+                ', $conversion_utf8 === true ?
+                'Notice that TeamPass is now only using UTF-8 charset.
+                This step will convert the database to this charset.
+                <div>
+                <input type="checkbox" id="prefix_before_convert" class="mr-2"><label for="prefix_before_convert">Save previous tables before converting (prefix "old_" will be used)</label>
+                </div>' :
+                'The database is currently using UTF-8 charset <i class="fas fa-check-circle fa-lg text-success ml-3"></i>', '
+                </div>
+            </div>
+        </div>';
+// STEP4
+} elseif ((isset($post_step) && $post_step == 4) || (isset($_GET['step'])
+    && $_GET['step'] == 4)
+    && $_SESSION['user_granted'] === '1'
+) {
+    echo '
+        <div class="card card-primary">
+            <div class="card-header">
+                <h5>Database updates</h5>
+            </div>
+            <div class="card-body">
+                <small class="form-text text-muted">
+                    The database needs to be adapted. This step can take a very long time depending on the data volume and server performance.
+                </small>
+                <div class="row card card-primary mt-2">
+                    <div class="card-body col-12 font-weight-light" id="step4_progress" style="overflow-y: scroll; height:400px;">
+                        Please click the button START.
+                    </div>
+                </div>
+
+                <div class="hidden" id="change_pw_encryption">
+                    <br />
+                    <p><b>Encryption protocol of existing passwords now has to be started. It may take a very long time depending on the data volume and server performance.</b></p>
+                    <p>
+                        <div style="display:none;" id="change_pw_encryption_progress"></div>
+                    </p>
+                    <input type="button" value="Click to continue" id="but_encrypt_continu" onclick="newEncryptPw(0);" />
+                    <input type="hidden" id="change_pw_encryption_start" value="" />
+                    <input type="hidden" id="change_pw_encryption_total" value="" />
+                </div>
+
+                <div style="margin-top:20px;font-weight:bold;text-align:center;height:27px;" id="res_step4"></div>
+                <input type="hidden" id="step4" name="step4" value="">
+            </div>
+        </div>';
+// STEP5
+} elseif ((isset($post_step) && $post_step == 5)
+    || (isset($_GET['step']) && $_GET['step'] == 5)
+    && $_SESSION['user_granted'] === '1'
+) {
+    //ETAPE 5
+    echo '
+        <h4>Finalization</h4>
+        <div>
+            <ul>
+            <li>Regenerate settings.php file <span id="step5_settingFile"></span></li>
+            <li>Anonymize saltkey file <span id="step5_saltkeyFile"></span></li>
+            <li>Generate config file <span id="step5_configFile"></span></li>
+            <li>Generate CSRFP config file <span id="step5_csrfpFile"></span></li>
+            <li>Add new cron job <span id="step5_cronJob"></span></li>	
+            </ul>
+        </div>';
+
+    echo '
+        <div class="card card-primary">
+            <div class="card-header">
+                <h5>Absolute path to SaltKey</h5>
+            </div>
+            <div class="card-body">
+                <small class="form-text text-muted">
+                The SaltKey is stored in a file stored in a folder outside the www folder of your server.
+                </small>
+                <div class="mt-4">
+                    <input type="text" class="form-control" id="sk_path" value="', defined('SECUREPATH') === true ? SECUREPATH : '', '" placeholder="Path to folder">
+                </div>
+            </div>
+        </div>';
+
+    echo '
+        <div class="alert alert-info mt-4 hidden" id="res_step5"></div>';
+} elseif ((isset($post_step) && $post_step == 6)
+    || (isset($_GET['step']) && $_GET['step'] == 6)
+    && $_SESSION['user_granted'] === '1'
+) {
+    //ETAPE 5
+    echo '
+        <h4>Upgrade is now completed</h4>
+        <div class="callout callout-info mt-4">
+            <div>
+                For news, help and information, visit <a href="https://teampass.net" target="_blank" class="text-info">TeamPass website</a>
+            </div>
+        </div>
+        <div class="alert alert-primary mt-4">
+            <i class="far fa-lightbulb text-warning mr-2 fa-lg"></i>It is recommended to clean the cache of your Web Browser before trying to log in.
+        </div>';
+
+    if (version_compare($post_actual_cpm_version, '2.1.27', '<=')) {
+        echo '
+        <div class="alert alert-warning mt-4">
+            <i class="fa fa-exclamation-circle text-danger mr-2 fa-lg"></i>This upgrade was a heavy one. Indeed we have changed the encryption of your data to make them safer now as they don\'t rely anymore on a key.<br>
+            This forced us to encode your users data with a One-Time-Code that they did receive by email. For any reason, they did not received it, you as an admin, can change it from the users management page.
+        </div>';
+    }
+
+    echo '
+        <div class="mt-5">
+        <a href="#" class="btn btn-primary" onclick="javascript:window.location.href=\'', (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] == 1) || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') ? 'https' : 'http', '://'.$_SERVER['HTTP_HOST'].substr($_SERVER['PHP_SELF'], 0, strrpos($_SERVER['PHP_SELF'], '/') - 8).'\';"><b>Open TeamPass</b></a>
+        </div>';
 }
-// Add new setting 'items_statistics_job_frequency'
-$tmp = mysqli_num_rows(mysqli_query($db_link, "SELECT * FROM `" . $pre . "misc` WHERE type = 'admin' AND intitule = 'enable_tasks_items_statistics_job_frequencymanager'"));
-if (intval($tmp) === 0) {
-    mysqli_query(
-        $db_link,
-        "INSERT INTO `" . $pre . "misc` (`type`, `intitule`, `valeur`) VALUES ('admin', 'items_statistics_job_frequency', '5')"
-    );
+
+echo '
+            <div class="card-footer">';
+//buttons
+if (!isset($post_step)) {
+    echo '
+            <input type="button" id="but_launch" data-step="step0" class="btn btn-primary" value="START">
+            <input type="button" id="but_next" data-target="1" style="" class="btn btn-primary" value="NEXT" disabled="disabled">';
+} elseif (intVal($post_step) === 3 && $conversion_utf8 === false && $_SESSION['user_granted'] === '1') {
+    echo '
+            <input type="button" id="but_next" target_id="'.(intval($post_step) + 1).'" class="btn btn-primary" value="NEXT">';
+} elseif (intVal($post_step) === 3 && $conversion_utf8 === true && $_SESSION['user_granted'] === '1') {
+    echo '
+            <input type="button" id="but_launch" data-step="step'.$post_step.'" class="btn btn-primary" value="START">
+            <input type="button" id="but_next" data-target="'.(intval($post_step) + 1).'" class="btn btn-primary" value="NEXT" disabled="disabled">';
+} elseif (intVal($post_step) === 6 && $_SESSION['user_granted'] === '1') {
+    // Nothong to do
+} else {
+    echo '
+            <input type="button" id="but_launch" data-step="step'.$post_step.'" class="btn btn-primary" value="START" />
+            <input type="button" id="but_next" data-target="'.(intval($post_step) + 1).'" class="btn btn-primary" value="NEXT" disabled="disabled">';
 }
 
-// Add field ongoing_process_id to USERS table
-$res = addColumnIfNotExist(
-    $pre . 'users',
-    'ongoing_process_id',
-    "varchar(100) NULL;"
-);
-if ($res === false) {
-    echo '[{"finish":"1", "msg":"", "error":"An error appears when adding field ongoing_process_id to table USERS! ' . mysqli_error($db_link) . '!"}]';
-    mysqli_close($db_link);
-    exit();
+echo '
+            </div>
+        </div>
+        </form>
+    </div>';
+//FOOTER
+echo '
+    <div id="footer">
+        <div style="width:500px; font-size:16px;">
+            '.TP_TOOL_NAME.' '.TP_VERSION.' &#169; copyright 2009-'.date("Y").'
+        </div>
+        <div style="float:right;margin-top:-15px;">
+        </div>
+    </div>';
+?>
+    </div>
+
+</body>
+</html>
+
+
+<script type="text/javascript" src="js/aes.min.js"></script>
+<!-- jQuery -->
+<script src="../plugins/jquery/jquery.min.js"></script>
+<!-- jQuery -->
+<script src="../plugins/jqueryUI/jquery-ui.min.js"></script>
+<!-- Popper -->
+<script src="../plugins/popper/umd/popper.min.js"></script>
+<!-- Bootstrap -->
+<script src="../plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
+<!-- AdminLTE -->
+<script src="../plugins/adminlte/js/adminlte.min.js"></script>
+<!-- Altertify -->
+<script type="text/javascript" src="../plugins/alertifyjs/alertify.min.js"></script>
+
+
+
+
+<script type="text/javascript">
+var timeTaken = '';
+
+
+$(function(){
+    // click on button NEXT
+    $("#but_next").click(function(event) {
+        $("#step").val($(this).data("target"));
+        document.install.submit();
+    });
+
+    $("#dump_done").click(function(event) {
+        if($("#dump_done").is(':checked')) {
+            $("#but_next").prop("disabled", false);
+        } else {
+            $("#but_next").prop("disabled", true);
+        }
+    });
+
+    $("#no_key_selection").change(function() {
+        if ($("#no_key_selection").val() === "no_previous_sk_sel") {
+            $("#previous_sk_div").hide();
+            $("#previous_sk").val("");
+        } else if ($("#no_key_selection").val() === "previous_sk_sel") {
+            $("#previous_sk_div").show();
+        }
+    });
+
+    // click on button START
+    $('#but_launch').click(function(event) {
+        var currentStep = $(this).data('step'),
+            postData = '';
+        // STEP 0
+        if (currentStep === 'step0') {
+            if ($("#user_login").val() === "" || $("#user_pwd").val() === "") {
+                alertify
+                    .error('<i class="fas fa-ban mr-2">[ERROR] You must provide credentials</i>', 10)
+                    .dismissOthers();
+                return false;
+            }
+
+            postData = {
+                type : currentStep,
+                login : $("#user_login").val(),
+                pwd : window.btoa(aesEncrypt($("#user_pwd").val()))
+            }
+            
+        } else if (currentStep === 'step1') {
+            postData = {
+                type : currentStep,
+                abspath : $("#root_path").val(),
+                fullurl : $("#root_url").val()
+            }
+        } else if (currentStep === 'step2') {
+            postData = {
+                type : currentStep,
+                abspath : $("#root_path").val(),
+                fullurl : $("#root_url").val()
+            }
+        } else if (currentStep === 'step3') {
+            postData = {
+                type : currentStep,
+                abspath : $("#root_path").val(),
+                fullurl : $("#root_url").val(),
+                prefix_before_convert : $('#prefix_before_convert').prop("checked")
+            }
+
+        } else if (currentStep === "step4") {
+            upgrade_file = "";
+            timeTaken = getTime();
+            manageUpgradeScripts("0");
+            return false;
+
+        } else if (currentStep === "step5") {
+            postData = {
+                type : currentStep,
+                url_path : $("#url_path").val()
+            }
+        }
+
+        alertify
+            .message('<i class="fas fa-cog fa-spin fa-2x"></i>', 0)
+            .dismissOthers();
+        $("#res_"+currentStep).html("").addClass("hidden");
+
+        // EXECUTE AJAX REQUEST
+        return $.ajax({
+            url: "upgrade_ajax.php",
+            type : "POST",
+            dataType : "json",
+            async: false,
+            data : postData,
+            complete : function(result, status){
+                //console.log(result.responseText)
+                data = $.parseJSON(result.responseText)[0];
+                console.log(data)
+                // manage error
+                if (data.error !== "") {
+                    $("#user_granted").val("0");
+                    $('#but_next').attr('disabled');
+
+                    if (data.info !== "") {
+                        $('#res_'+currentStep).html(data.info).removeClass("hidden");
+                    }
+                    alertify
+                        .error('<i class="fas fa-exclamation-triangle mr-2"></i>  '+data.error+'</i>', 5)
+                        .dismissOthers();
+                } else {
+                    $("#step").val(data.index);
+                    $("#user_granted").val("1");
+                    $('#but_next').removeAttr('disabled');
+
+                    // Special
+                    if (currentStep === 'step0') {
+                        $("#infotmp").val(data.info);
+                    } else if (currentStep === 'step1') {
+                        $('#res_step1').html(data.info).removeClass('hidden');
+                    } else if (currentStep === 'step2') {
+                        
+                        $('#res_step2').html(data.info).removeClass('hidden');
+                        $('#cpm_isUTF8').val(data.isUtf8);
+                        if (parseInt(data.isUtf8) === 1) {
+                            $('#step').val(4);
+                            $('#but_next').data('target', "4");
+                        }
+                    } else if (currentStep === 'step5') {
+                        $("#res_step5").html("Operations are successfully completed.").removeClass("hidden");
+                        var res = $.parseJSON(atob(data.info));
+                        
+                        $.each(res, function(index, value) {
+                            $('#'+value.id).html(value.html);
+                        });
+                    }
+
+                    // Display
+                    alertify
+                        .success('<i class="fas fa-thumbs-up mr-2">  Done</i>', 5)
+                        .dismissOthers();
+                }
+            }
+        });
+    });
+
+});
+
+function aesEncrypt(text)
+{
+    return Aes.Ctr.encrypt(text, "cpm", 128);
 }
-//---<END 3.0.6
 
 
-//--->BEGIN 3.0.7
-// Alter
-try {
-    mysqli_query(
-        $db_link,
-        'ALTER TABLE `' . $pre . 'cache_tree` CHANGE `data` `data` LONGTEXT DEFAULT NULL;'
+function manageUpgradeScripts(file_number)
+{
+    var start_at = 0;
+    var noitems_by_loop = 100;
+    var loop_number = 0;
+
+    if (file_number == 0) {
+        $("#step4_progress").html("");
+        alertify
+            .success('Done', 1)
+            .dismissOthers();
+    }
+
+    request = $.post("upgrade_scripts_manager.php",
+        {
+            file_number : parseInt(file_number)
+        },
+        function(data) {
+            console.log(data[0])
+            // work not finished
+            if (data[0].finish !== "1") {
+                // loop
+                runUpdate(data[0].scriptname, data[0].parameter, start_at, noitems_by_loop, loop_number, file_number);
+            }
+            // work finished
+            else {
+                // refresh categories cache
+                rand_number = createRandomId();
+                $("#step4_progress")
+                    .html(
+                        "<div>" + getTime() + " - <span id='user_"+rand_number+"'>Performing database update operations ... <i class=\"fas fa-cog fa-spin\" style=\"color:orange\"></i></span></div>"+ 
+                        $("#step4_progress").html()
+                    );
+                $.ajax({
+                    url: "upgrade_ajax.php",
+                    type : "POST",
+                    dataType : "json",
+                    async: false,
+                    data : {
+                        type : "perform_nestedtree_categories_population_3.0.0.18"
+                    },
+                    complete : function(result, status) {
+                        console.log("Tash perform_nestedtree_categories_population_3.0.0.18 DONE");
+                        $("#user_"+rand_number)
+                            .html('Database update operations done <i class="fas fa-thumbs-up" style="color:green"></i>'); 
+                        migrateUsersToV3('step1', '', 'init', createRandomId(), 0, false, 0);
+
+                        alertify
+                            .success('Done with initialization phase', 3)
+                            .dismissOthers();
+                    }
+                });
+            }
+        },
+        "json"
     );
-} catch (Exception $e) {
-    // Do nothing
 }
 
-// Fix for #3679
-mysqli_query(
-    $db_link,
-    "UPDATE `" . $pre . "users` SET `treeloadstrategy` = 'full' WHERE treeloadstrategy NOT IN ('full','sequential');"
-);
+var usersList = [],
+    previousStep = '',
+    usersRestList = [];
+/**
+    * We need to migrate all users
+    * This will change the users encryption and generate a OTC
+    *
+    * @return void
+    */
+function migrateUsersToV3(step, data, number, rand_number, loop_start, loop_finished, tp_user_done)
+{
+    console.log("> "+step+" - Number: "+number + " - Previous step: "+previousStep);
+    console.log(usersList);
+    var d = new Date(),
+        count_in_loop = <?php echo (int) NUMBER_ITEMS_IN_BATCH;?>,
+        userSteps = {};
 
-//---<END 3.0.7
 
+    // Decode data
+    var newData = '';
+    if (data !== '' && step === 'step1') {
+        newData = JSON.parse(window.atob(data));
+    }
+    console.log("TO DO : "+step+" -- "+number+" -- "+loop_start+" -- "+loop_finished+" -- "+newData.id)
 
-//--->BEGIN 3.0.8
-// Add field mfa_disabled to USERS table
-$res = addColumnIfNotExist(
-    $pre . 'users',
-    'mfa_enabled',
-    "tinyint(1) NOT null DEFAULT '1';"
-);
-if ($res === false) {
-    echo '[{"finish":"1", "msg":"", "error":"An error appears when adding field mfa_disabled to table USERS! ' . mysqli_error($db_link) . '!"}]';
-    mysqli_close($db_link);
-    exit();
-}
+    if (step === 'step1') {
+        userInfo = '';
+        // Show progress to user
+        $("#user_"+rand_number).html("User id " + newData.id + " done <i class=\"fas fa-thumbs-up\" style=\"color:green\"></i>");
+
+        if (newData !== '') {
+            usersList.push(newData);
+        }
+        console.log('List of users');
+        console.log(JSON.stringify(usersList))
+        
+        if (usersRestList.length > 0) {
+            number = usersRestList[0];
+            usersRestList.shift();
+        } else if (number !== 'init') {
+            number = 'end';
+        }
+        //console.log('List of next users');
+        //console.log(JSON.stringify(usersRestList) + " ; Number = "+number)
+        
+        rand_number = createRandomId();
+        $("#step4_progress").html("<div>" + getTime() + " - <span id='user_"+rand_number+"'>Treating User account ... <i class=\"fas fa-cog fa-spin\" style=\"color:orange\"></i></span></div>"+ $("#step4_progress").html());
+        // --
+        // --
+    } else {
+        // Prepare array                
+        if (usersList[number] !== undefined) {
+            userSteps = {
+                'step1' : {
+                    'text' : 'Users public / private keys were generated if needed',
+                    'number' : 0,
+                    'start' : 0
+                },
+                'step2' : {
+                    'text' : 'User '+usersList[number].id+' - All ITEMS keys generated',
+                    'number' : 0,
+                    'start' : 0
+                },
+                'step3' : {
+                    'text' : 'User '+usersList[number].id+' - All LOGS keys generated',
+                    'number' : number,
+                    'start' : 0
+                },
+                'step4' : {
+                    'text' : 'User '+usersList[number].id+' - All FIELDS keys generated',
+                    'number' : number,
+                    'start' : 0
+                },
+                'step5' : {
+                    'text' : 'User '+usersList[number].id+' - All SUGGESTIONS keys generated',
+                    'number' : number,
+                    'start' : 0
+                },
+                'step6' : {
+                    'text' : 'User '+usersList[number].id+' - All FILES keys generated',
+                    'number' : number,
+                    'start' : 0
+                },
+                'nextUser' : {
+                    'number' : parseInt(number) + 1,
+                    'start' : 0
+                }
+            };
+        }
+
+        // What strategy to have for next treatment
+        if (previousStep === 'step1') {
+            $("#user_"+rand_number).html("Users public / private keys were generated if needed <i class=\"fas fa-thumbs-up\" style=\"color:green\"></i>");
+
+            number = 0;
+            loop_start = 0;
+            // --
+        } else if (previousStep === step) {
+            // IF we are in the same step and we continue
+            $("#user_"+rand_number).html(userSteps[previousStep].text + " until " + (loop_start + count_in_loop) + " done <i class=\"fas fa-thumbs-up\" style=\"color:green\"></i>");
+
+            loop_start += count_in_loop;
+            // --
+        } else if (step !== 'nextUser' && loop_finished === "true") {
+            // The loop on current topic is finished but still with same user
+            $("#user_"+rand_number).html(userSteps[previousStep].text + " <i class=\"fas fa-thumbs-up\" style=\"color:green\"></i>");
+
+            number = userSteps[step].number;
+            loop_start = userSteps[step].start;
+            // --
+        } else if (step === 'nextUser') {
+            // The treatment of current user is finished
+            // Select next user
+            $("#user_"+rand_number)
+                .html(userSteps[previousStep].text + " <i class=\"fas fa-thumbs-up\" style=\"color:green\"></i>");
+
+            $("#user_"+rand_number).parent()
+                .prepend('<div>' + getTime() +' - User ' + usersList[number].id + ' fully treated <i class="fas fa-thumbs-up" style="color:green"></i></div>');                    
+
+            // Have we handled all users
+            // If not then restart at step2 for next user
+            if ((parseInt(number)+1) < usersList.length) {
+                number = userSteps[step].number;
+                loop_start = userSteps[step].start;
+                step = 'step2';
+            } else {
+                // Prepare list of users to be displayed
+                if (usersList.length === 1 && usersList[0].login === 'TP') {
+                    // do nothing and finish
+                    sendPwdToUsers(usersList, true, 0, usersList.length);
+                } else {
+                    var htmlUsersList = '<i class="fas fa-info-circle mr-2"></i>You could provide those unique codes to users by your own.<br><ul>';
+                    $.each(usersList, function(index, user) {
+                        htmlUsersList += '<li>User: '+user.name+' '+user.lastname+' (login: '+user.login+') ; OneTime code: '+user.otp+'</li>'
+                    });
+                    htmlUsersList += '</ul>';
+
+                    // Done
+                    $("#user_"+rand_number).parent()
+                        .prepend(
+                            '<div>' + getTime() +' - All keys have been generated for users <i class="fas fa-thumbs-up" style="color:green"></i>'+
+                            '<br>'+
+                            '<div type="button" class="btn btn-primary btn-sm" id="buttonListOfUsers">Show/Hide list of users</div>'+
+                            '<div class="hidden alert alert-secondary mt-2" id="htmlListOfUsers" role="alert">'+htmlUsersList+'</div>'+
+                            '</div>'
+                        );
+
+                    // Act on button click
+                    $(document).on('click', '#buttonListOfUsers', function() { 
+                        if ($('#htmlListOfUsers').hasClass('hidden') === true) {
+                            $('#htmlListOfUsers').removeClass('hidden');
+                        } else {
+                            $('#htmlListOfUsers').addClass('hidden');
+                        }
+                    });
+
+                    console.log(usersList);
+                    // Now send passwords
+                    console.log("Send emails to users from now");
+                    sendPwdToUsers(usersList, true, 0, usersList.length);
+                }
 
-//---<END 3.0.8
+                return;
+            }
+        }
+        
+        if (usersList.length === 0 && step === 'step2' && loop_finished === 'true' && number === 0 && loop_start === 0) {
+            /* Unlock this step */
+            document.getElementById("but_next").disabled = "";
+            document.getElementById("but_launch").disabled = "disabled";
+            
+            $("#user_"+rand_number).parent()
+                .prepend('<div>' + getTime() +' - All steps have been successfully performed <i class="fas fa-thumbs-up" style="color:green"></i></div>'); 
+            return;
+        } else {
+            // Show progress
+            rand_number = createRandomId();
+            $("#step4_progress").html("<div>" + getTime() +" - <span id='user_"+rand_number+"'>User "+usersList[number].id+" - Creating keys until "+(loop_start + count_in_loop)+" ... <i class=\"fas fa-cog fa-spin\" style=\"color:orange\"></i></span></div>"+ $("#step4_progress").html());
 
+            // Prepare user information                
+            userInfo = {
+                'id' : usersList[number].id,
+                'otp' : usersList[number].otp,
+                'public_key' : usersList[number].public_key,
+                'private_key' : usersList[number].private_key,
+                'login' : usersList[number].login,
+                'name' : encode_utf8(usersList[number].name),
+                'lastname' : encode_utf8(usersList[number].lastname),
+            };
+        }
+    }
+    // Migrate if needed all account to new AES encryption
+    //console.log('Posting number = '+number);
+    $.post(
+        "upgrade_run_3.0.0_users.php",
+        {
+            step : step,
+            number : number === 'init' ? '' : number,
+            userInfo : window.btoa(unescape(encodeURIComponent(JSON.stringify(userInfo)))),
+            start : loop_start,
+            count_in_loop : count_in_loop,
+            info : $('#infotmp').val(),
+            extra : number === 'end' ? 'all_users_created' : '',
+            tp_user_done : tp_user_done,
+        },
+        function(data) {
+            console.log(data[0]);
+            //console.log(JSON.parse(window.atob(data[0].rest)));
+            //console.log(JSON.parse(window.atob(data[0].data)));
+            previousStep = step;
 
-//--->BEGIN 3.0.9
-// Add new setting 'reload_cache_table_task'
-$tmp = mysqli_num_rows(mysqli_query($db_link, "SELECT * FROM `" . $pre . "misc` WHERE type = 'admin' AND intitule = 'reload_cache_table_task'"));
-if (intval($tmp) === 0) {
-    mysqli_query(
-        $db_link,
-        "INSERT INTO `" . $pre . "misc` (`type`, `intitule`, `valeur`) VALUES ('admin', 'reload_cache_table_task', '')"
+            if (data[0].finish !== "1") {
+                // Manage list of users that is provide on number = 0
+                if (number === 'init' && step === 'step1') {
+                    if (data[0].rest !== '') {
+                        usersRestList = JSON.parse(window.atob(data[0].rest));
+                    }else {
+                        usersRestList = '';
+                    }
+                    console.log("USERLIST = "+usersRestList);
+                }
+
+                // loop
+                migrateUsersToV3(
+                    data[0].next,
+                    data[0].data,
+                    data[0].number,
+                    rand_number,
+                    loop_start,
+                    data[0].loop_finished,
+                    1
+                );
+            } else {
+                // Done
+                /* Unlock this step */
+                document.getElementById("but_next").disabled = "";
+                document.getElementById("but_launch").disabled = "disabled";
+            }
+        },
+        "json"
     );
 }
-// Add new setting 'rebuild_config_file'
-$tmp = mysqli_num_rows(mysqli_query($db_link, "SELECT * FROM `" . $pre . "misc` WHERE type = 'admin' AND intitule = 'rebuild_config_file'"));
-if (intval($tmp) === 0) {
-    mysqli_query(
-        $db_link,
-        "INSERT INTO `" . $pre . "misc` (`type`, `intitule`, `valeur`) VALUES ('admin', 'rebuild_config_file', '')"
-    );
-}// Add new setting 'purge_temporary_files_task'
-$tmp = mysqli_num_rows(mysqli_query($db_link, "SELECT * FROM `" . $pre . "misc` WHERE type = 'admin' AND intitule = 'purge_temporary_files_task'"));
-if (intval($tmp) === 0) {
-    mysqli_query(
-        $db_link,
-        "INSERT INTO `" . $pre . "misc` (`type`, `intitule`, `valeur`) VALUES ('admin', 'purge_temporary_files_task', '')"
+
+/**
+* 
+ */
+function sendPwdToUsers(usersList, init, cpt, total)
+{
+    var d = new Date();
+
+    // Inform
+    if (init === true) {
+        rand_number = createRandomId();
+        $("#step4_progress").html("<div>" + getTime() +" - <span id='user_"+rand_number+"'>Now sending new users password by email ... " +
+            "<i class=\"fas fa-cog fa-spin\" style=\"color:orange\"></i><span id='sending_emails_pct' class='ml-3'>0%</span></div>"+ $("#step4_progress").html());
+    }
+    
+    // Prepare user data to be sent
+    userInfo = {
+        'id' : usersList[0].id,
+        'otp' : usersList[0].otp,
+    };
+    //console.log('Envoi email pour : ');
+    //console.log(userInfo);
+
+    // Remove user from list
+    usersList.shift();
+
+    // Send email for each user
+    $.post(
+        "upgrade_run_3.0.0_users.php",
+        {
+            step : 'send_pwd_by_email',
+            userInfo : window.btoa(JSON.stringify(userInfo))
+        },
+        function(data) {
+            //console.log(data);
+            //console.log('----');
+            // Done with sending the user email
+            if (usersList.length === 0) {
+                // all users have received their email
+                $("#user_"+rand_number)
+                    .html("Emails were sent <i class=\"fas fa-thumbs-up\" style=\"color:green\"></i>");
+
+                /* Unlock this step */
+                document.getElementById("but_next").disabled = "";
+                document.getElementById("but_launch").disabled = "disabled";
+                
+                $("#user_"+rand_number).parent()
+                .prepend('<div>' + getTime() +' - All steps have been successfully performed <i class="fas fa-thumbs-up" style="color:green"></i></div>'); 
+                
+            } else {
+                // current user received his email
+                // Send to the next one
+                $("#sending_emails_pct").text(Math.round((cpt / total) * 100) + "%");
+
+                sendPwdToUsers(usersList, false, cpt++, total);
+            }
+        },
+        "json"
     );
 }
-// Add new setting 'clean_orphan_objects_task'
-$tmp = mysqli_num_rows(mysqli_query($db_link, "SELECT * FROM `" . $pre . "misc` WHERE type = 'admin' AND intitule = 'clean_orphan_objects_task'"));
-if (intval($tmp) === 0) {
-    mysqli_query(
-        $db_link,
-        "INSERT INTO `" . $pre . "misc` (`type`, `intitule`, `valeur`) VALUES ('admin', 'clean_orphan_objects_task', '')"
+
+function runUpdate (script_file, type_parameter, start_at, noitems_by_loop, loop_number, file_number)
+{
+    var d = new Date(),
+        info = '',
+        text_action_in_progress = script_file;
+    loop_number ++;
+    var rand_number = createRandomId();
+
+    // Inform
+    if (script_file === 'upgrade_operations.php') {
+        text_action_in_progress = 'Performing operation ' + type_parameter;
+    }
+
+    $("#step4_progress").html("<div>" + getTime() +" - <i>"+text_action_in_progress+"</i> - Loop #"+loop_number+" <span id='span_"+rand_number+"'>is now running ... <i class=\"fas fa-cog fa-spin\" style=\"color:orange\"></i></span></div>"+ $("#step4_progress").html());
+
+    if (type_parameter === 'user_id') {
+        info = $("#infotmp").val();
+    }
+
+    request = $.post(
+        script_file,
+        {
+            type        : type_parameter,
+            start       : start_at,
+            total       : start_at,
+            nb          : noitems_by_loop,
+            session_salt: $("#session_salt").val(),
+            info        : info,
+        },
+        function(data) {
+            console.info(type_parameter)
+            console.log(data)
+            // work not finished
+            if (data[0].finish !== "1") {
+                $("#span_"+rand_number).html("<i class=\"fas fa-thumbs-up\" style=\"color:green\"></i>")
+                // loop
+                runUpdate(script_file, type_parameter, data[0].next, noitems_by_loop, loop_number, file_number);
+            // is there an error
+            } else if (data[0].finish === "1" && data[0].error !== "") {
+                $("#span_"+rand_number).html("<i class=\"fas fa-thumbs-down\" style=\"color:red\"></i>");
+                $("#step4_progress").html("<div style=\"margin:15px 0 15px 0; font-style:italic;\">"+getTime() +" - <b>ERROR</b>: "+data[0].error+"</div>"+ $("#step4_progress").html());
+                $("#step4_progress").html("<div>An error occurred. Please check and relaunch.</div>"+ $("#step4_progress").html());
+            // work finished
+            } else {
+                $("#span_"+rand_number).html("<i class=\"fas fa-thumbs-up\" style=\"color:green\"></i>")
+                // continue with next script file
+                file_number ++;
+                manageUpgradeScripts(file_number);
+            }
+        },
+        "json"
     );
 }
-// Add new setting 'users_personal_folder_task'
-$tmp = mysqli_num_rows(mysqli_query($db_link, "SELECT * FROM `" . $pre . "misc` WHERE type = 'admin' AND intitule = 'users_personal_folder_task'"));
-if (intval($tmp) === 0) {
-    mysqli_query(
-        $db_link,
-        "INSERT INTO `" . $pre . "misc` (`type`, `intitule`, `valeur`) VALUES ('admin', 'users_personal_folder_task', '')"
+
+function newEncryptPw(suggestion)
+{
+    var nb = 20;
+    var start = 0;
+
+    if ($("#change_pw_encryption_start").val() != "") {
+        start = $("#change_pw_encryption_start").val();
+    } else {
+        $("#change_pw_encryption_progress").html('Progress: 0% <i class="fas fa-cog fa-spin fa-2x"></i>');
+    }
+    var request = $.post("upgrade_ajax.php",
+        {
+            type        : "new_encryption_of_pw",
+            start       : start,
+            total       : $("#change_pw_encryption_total").val(),
+            suggestion  : suggestion,
+            nb          : nb
+        },
+        function(data) {
+            if (data[0].finish != 1 && data[0].finish != "suggestion") {
+                // handle re-encryption of passwords in Items table
+                $("#change_pw_encryption_start").val(data[0].next);
+                $("#change_pw_encryption_progress").html('Progress: '+data[0].progress+'% <i class="fas fa-cog fa-spin fa-2x"></i>');
+                if (parseInt(start) < parseInt($("#change_pw_encryption_total").val())) {
+                    newEncryptPw("0");
+                }
+            } else if (data[0].finish == "suggestion") {
+                // handle the re-encryption of passwords in suggestion table
+                newEncryptPw("1");
+            } else {
+                // handle finishing
+                $("#change_pw_encryption_progress").html("Done");
+                $("#but_encrypt_continu").hide();
+                /* Unlock this step */
+                document.getElementById("but_next").disabled = "";
+                document.getElementById("but_launch").disabled = "disabled";
+                document.getElementById("res_step4").innerHTML = "dataBase has been populated";
+                document.getElementById("loader").style.display = "none";
+            }
+        },
+        "json"
     );
+
 }
 
-// Remove unused settings
-mysqli_query(
-    $db_link,
-    "DELETE FROM `" . $pre . "misc` WHERE `intitule`='maintenance_job_tasks';"
-);
-mysqli_query(
-    $db_link,
-    "DELETE FROM `" . $pre . "misc` WHERE `intitule`='maintenance_job_frequency';"
-);
+function launch_database_dump() {
+    alertify
+        .message('<i class="fas fa-cog fa-spin fa-2x"></i>', 0)
+        .dismissOthers();
 
-//---<END 3.0.9
+    request = $.post(
+        "upgrade_ajax.php",
+        {
+            type      : "perform_database_dump"
+        },
+        function(data) {
+            console.log(data)
+            if (data[0].error !== "") {
+                // ERROR
+                $("#dump_result").html(data[0].error);
 
-// Save timestamp
-$tmp = mysqli_num_rows(mysqli_query($db_link, "SELECT * FROM `" . $pre . "misc` WHERE type = 'admin' AND intitule = 'upgrade_timestamp'"));
-if (intval($tmp) === 0) {
-    mysqli_query(
-        $db_link,
-        "INSERT INTO `" . $pre . "misc` (`type`, `intitule`, `valeur`) VALUES ('admin', 'upgrade_timestamp', ".time().")"
-    );
-} else {
-    mysqli_query(
-        $db_link,
-        "UPDATE `" . $pre . "misc` SET valeur = ".time()." WHERE type = 'admin' AND intitule = 'upgrade_timestamp'"
+                alertify
+                    .Error('Error', 1)
+                    .dismissOthers();
+            } else {
+                // DONE
+                $("#dump_result").html('<div class="alert alert-info mt-2">Dump is successfull. File stored in file <b>' + data[0].filename + '</b></div>');
+                $('#but_next').attr("disabled", false);
+                alertify
+                    .success('Success', 1)
+                    .dismissOthers();
+            }
+        },
+        "json"
     );
 }
 
-//---< END 3.0.X upgrade steps
+function createRandomId()
+{
+    var randLetter = String.fromCharCode(65 + Math.floor(Math.random() * 26));
+    return randLetter + Date.now();
+}
+
+function getTime()
+{
+    var d = new Date();
+    return ("0" +d.getHours()).slice(-2)+":"+("0" + d.getMinutes()).slice(-2)+":"+("0" + d.getSeconds()).slice(-2)
+}
+
+function encode_utf8( s )
+{
+  return unescape( encodeURIComponent( s ) );
+}
 
-// Close connection
-mysqli_close($db_link);
+</script>
 
-// Finished
-echo '[{"finish":"1" , "next":"", "error":""}]';
\ No newline at end of file

programming_language: PHP
filename: upgrade_scripts_manager.php
change_type: ModificationType.MODIFY
diff: @@ -38,6 +38,7 @@ $scripts_list = array(
     array('upgrade_run_3.0.0_suggestions.php', 'user_id'),
     array('upgrade_run_3.0.0_files.php', 'user_id'),
     array('upgrade_run_3.0.php', 'user_id'),
+    array('upgrade_operations.php', '20230604_1'),
 );
 $param = '';
 

programming_language: PHP
filename: items.js.php
change_type: ModificationType.MODIFY
diff: @@ -3688,7 +3688,7 @@ $var['hidden_asterisk'] = '<i class="fas fa-asterisk mr-2"></i><i class="fas fa-
                                     type: "POST",
                                     async: false,
                                     url: 'sources/items.queries.php',
-                                    data: 'type=show_item_password&item_id=' + trigger.getAttribute('data-item-id') +
+                                    data: 'type=show_item_password&item_key=' + trigger.getAttribute('data-item-key') +
                                         '&key=<?php echo $_SESSION['key']; ?>',
                                     dataType: "",
                                     success: function(data) {
@@ -3967,7 +3967,7 @@ $var['hidden_asterisk'] = '<i class="fas fa-asterisk mr-2"></i><i class="fas fa-
                     }
                     // Pwd icon
                     if (value.pw_status !== 'pw_is_empty' && value.pw_status !== 'encryption_error') {
-                        icon_pwd = '<span class="fa-stack fa-clickable fa-clickable-password pointer infotip mr-2" title="<?php echo langHdl('item_menu_copy_pw'); ?>" data-item-id="' + value.item_id + '" data-item-label="' + value.label + '"><i class="fas fa-circle fa-stack-2x"></i><i class="fas fa-key fa-stack-1x fa-inverse"></i></span>';
+                        icon_pwd = '<span class="fa-stack fa-clickable fa-clickable-password pointer infotip mr-2" title="<?php echo langHdl('item_menu_copy_pw'); ?>" data-item-key="' + value.item_key + '" data-item-label="' + value.label + '"><i class="fas fa-circle fa-stack-2x"></i><i class="fas fa-key fa-stack-1x fa-inverse"></i></span>';
                     }
 
                     // Now check if pwd is empty. If it is then warn user

programming_language: JavaScript
filename: items.queries.php
change_type: ModificationType.MODIFY
diff: @@ -448,6 +448,7 @@ if (is_null($inputData['type']) === false) {
                             'complexity_level' => $post_complexity_level,
                             'encryption_type' => 'teampass_aes',
                             'fa_icon' => $post_fa_icon,
+                            'unique' => uniqidReal(50),
                         )
                     );
                     $newID = DB::insertId();
@@ -2609,6 +2610,7 @@ if (is_null($inputData['type']) === false) {
                 $arrData['tags'] = $tags;
                 $arrData['folder'] = (int) $dataItem['id_tree'];
                 $arrData['fa_icon'] = $dataItem['fa_icon'];
+                $arrData['item_key'] = $dataItem['item_key'];
 
                 if (
                     isset($SETTINGS['enable_server_password_change'])
@@ -3815,7 +3817,7 @@ if (is_null($inputData['type']) === false) {
                         $post_nb_items_to_display_once;
                     //db::debugmode(true);
                     $rows = DB::query(
-                        'SELECT i.id AS id, MIN(i.restricted_to) AS restricted_to, MIN(i.perso) AS perso,
+                        'SELECT i.id AS id, i.item_key AS item_key, MIN(i.restricted_to) AS restricted_to, MIN(i.perso) AS perso,
                         MIN(i.label) AS label, MIN(i.description) AS description, MIN(i.pw) AS pw, MIN(i.login) AS login,
                         MIN(i.anyone_can_modify) AS anyone_can_modify, l.date AS date, i.id_tree AS tree_id, i.fa_icon AS fa_icon,
                         MIN(n.renewal_period) AS renewal_period,
@@ -3836,7 +3838,7 @@ if (is_null($inputData['type']) === false) {
                     $where->add('i.inactif=%i', 0);
 
                     $rows = DB::query(
-                        'SELECT i.id AS id, MIN(i.restricted_to) AS restricted_to, MIN(i.perso) AS perso,
+                        'SELECT i.id AS id, i.item_key AS item_key, MIN(i.restricted_to) AS restricted_to, MIN(i.perso) AS perso,
                         MIN(i.label) AS label, MIN(i.description) AS description, MIN(i.pw) AS pw, MIN(i.login) AS login,
                         MIN(i.anyone_can_modify) AS anyone_can_modify,l.date AS date, i.id_tree AS tree_id, i.fa_icon AS fa_icon,
                         MIN(n.renewal_period) AS renewal_period,
@@ -3923,6 +3925,7 @@ if (is_null($inputData['type']) === false) {
                         // Init
                         $html_json[$record['id']]['expired'] = (int) $expired_item;
                         $html_json[$record['id']]['item_id'] = (int) $record['id'];
+                        $html_json[$record['id']]['item_key'] = (string) $record['item_key'];
                         $html_json[$record['id']]['tree_id'] = (int) $record['tree_id'];
                         $html_json[$record['id']]['label'] = strip_tags($record['label']);
                         if (isset($SETTINGS['show_description']) === true && (int) $SETTINGS['show_description'] === 1) {
@@ -4162,9 +4165,9 @@ if (is_null($inputData['type']) === false) {
                 'SELECT i.pw AS pw, s.share_key AS share_key
                 FROM ' . prefixTable('items') . ' AS i
                 INNER JOIN ' . prefixTable('sharekeys_items') . ' AS s ON (s.object_id = i.id)
-                WHERE user_id = %i AND i.id = %i',
+                WHERE user_id = %i AND i.item_key = %i',
                 $_SESSION['user_id'],
-                $inputData['itemId']
+                $inputData['item_key']
             );
 
             // Uncrypt PW

programming_language: PHP