function searchUsers($get) { /*** * ***/ global $udb, $login_status; $q = $udb->sanitize($get['q']); $response = array('search' => $q); $search = array('username' => $q, 'name' => $q, 'dblink' => $q); $cols = array('username', 'name', 'dblink', "email_verified", "alternate_email_verified", "admin_flag", "alternate_email"); if (!empty($get['cols'])) { if (checkUserColumnExists($get['cols'], false)) { # Replace the defaults $colList = explode(',', $get['cols']); $search = array(); foreach ($colList as $col) { $col = trim($col); # If the column exists, we don't have to sanitize it # $col = $db->sanitize($col); $search[$col] = $q; $cols[] = $col; } } else { $response['notice'] = 'Invalid columns; defaults used'; $response["detail"] = checkUserColumnExists($get["cols"], false, true); } } $response['status'] = true; $result = $udb->getQueryResults($search, $cols, 'OR', true, true); $suFlag = $login_status['detail']['userdata']['su_flag']; $isSu = boolstr($suFlag); $adminFlag = $login_status['detail']['userdata']['admin_flag']; $isAdmin = boolstr($adminFlag); foreach ($result as $k => $entry) { $clean = array('email' => $entry['username'], 'uid' => $entry['dblink'], "has_verified_email" => boolstr($entry["email_verified"]) || boolstr($entry["alternate_email_verified"])); if ($isAdmin) { $clean["is_admin"] = boolstr($entry["admin_flag"]); $clean["alternate_email"] = $entry["alternate_email"]; $tmpUser = new UserFunctions($clean["email"]); $clean["unrestricted"] = $tmpUser->meetsRestrictionCriteria(); } $nameXml = $entry['name']; $xml = new Xml(); $xml->setXml($nameXml); $clean['first_name'] = htmlspecialchars_decode($xml->getTagContents('fname')); $clean['last_name'] = htmlspecialchars_decode($xml->getTagContents('lname')); $clean['full_name'] = htmlspecialchars_decode($xml->getTagContents('name')); $clean['handle'] = $xml->getTagContents('dname'); $result[$k] = $clean; } $response['result'] = $result; $response['count'] = sizeof($result); returnAjax($response); }
$deferredJS .= "console.log('Needs phone? '," . strbool($needPhone) . "," . DBHelper::staticSanitize($user->getPhone()) . ");\n"; $altPhone = "<p>Congratulations! Your phone number is verified.</p>"; } catch (Exception $e) { $needPhone = false; $deferredJS .= "console.warn('An exception was thrown checking for SMS-ability:','" . $e->getMessage() . "');\n"; $altPhone = "<p>You don't have a phone number registered with us. Please go to account settings and add a phone number.</p>"; } $verifyphone_link = $needPhone ? "<li><a href='?q=verify'>Verify Phone</a></li>" : null; $phone_verify_form = $needPhone ? $phone_verify_template : $altPhone; } catch (Exception $e) { # There have been no cookies set. $logged_in = false; $twofactor = "Please log in."; } if ($logged_in) { $xml->setXml($_COOKIE[$cookieperson]); $full_name = $xml->getTagContents("<name>"); $first_name = $xml->getTagContents("<fname>"); $display_name = $xml->getTagContents("<dname>"); if (empty($first_name)) { $first_name = $_COOKIE[$cookieperson]; } } else { if ($captive_login) { header("Refresh: 0; url={$baseurl}"); $deferredJS .= "\nwindow.location.href=\"{$baseurl}\";"; } } // $random = "<li><a href='#' id='totp_help'>Help with Two-Factor Authentication</a></li>"; try { $has2fa = strbool($user->has2FA());
private function changeUserPassword($oldPassword, $newPassword = null, $isResetPassword = false) { /*** * Replace the password stored. * If there are any encrypted fields, decrypt them and re-encrypt them in the process. * Trash the encrypted fields if we're resetting. * Update the cookies. * * @param string|array $oldPassword If the password is being * reset, then $oldPassword * should be an array with the * keys "key" and * "verify". Otherwise, it should * be the plain-text string of * the old password. * @param string $newPassword If $isResetPassword is true, * this field can be "true" to * email the new password. * * @param bool $isResetPassword Is the password being reset? ***/ try { if ($isResetPassword === true) { $userdata = $this->getUser(); if (empty($userdata)) { throw new Exception('Base user not set'); } $doEmailPassword = $newPassword === true; # We can't verify the old password, so we have to verify the # reset token provided under $oldPassword instead if (!is_array($oldPassword)) { throw new Exception('Bad credential format'); } $key = $oldPassword['key']; $verify = $oldPassword['verify']; if (empty($key) or empty($verify)) { throw new Exception('Not all required credentials were provided'); } # Now, we verify the supplied credentials $pw_data = json_decode($userdata[$this->pwColumn], true); $salt = $pw_data['salt']; # Pull the secret from the temp column $secret = $this->getSecret(true); $string = self::decryptThis($key, $secret, $this->getIV()); $test_string = $salt . $string; $match_token = substr(hash('md5', $test_string), 0, 8); if ($match_token != $verify) { # The computed token doesn't match the provided one // $testPass = "******"; // $method = self::getPreferredCipherMethod(); // $iv = self::getIV(); // $testPass = md5($testPass); // $foo = openssl_encrypt("FooBar", $method, $testPass, 0, $iv); // $foo64 = base64_encode($foo); // $bar64 = openssl_decrypt(base64_decode($foo64), $method, $testPass, 0, $iv); // $bar = openssl_decrypt($foo, $method, $testPass, 0, $iv); // $barTrim = rtrim($bar, "\0"); // $barTrim64 = rtrim($bar64, "\0"); // $testPass = substr($testPass, 0, 8); // $faz = self::encryptThis("FooBar", $testPass, $this->getIV()); // $baz = self::decryptThis($faz, $testPass); #throw( new Exception('Invalid reset tokens (got '.$string.' and match '.$match_token.' from '.$salt.' and '.$secret.' [input->'.$key.':'.$verify.' with iv '.$this->getIV().']). Tested '.$foo.' decoding to '.$bar.' with '.$method. " (64: $foo64 to $bar64 to $barTrim64 vs ".$barTrim.") Also $faz -> $baz and " . openssl_error_string() ) ); throw new Exception('Invalid reset tokens'); } # The token matches -- let's make them a new password and # provide it. if (!class_exists('Stronghash')) { require_once dirname(__FILE__) . '/../core/stronghash/php-stronghash.php'; } $newPassword = self::createRandomUserPassword(); $hash = new Stronghash(); $pw1 = $hash->hasher($newPassword); $pwStore = json_encode($pw1); # We don't need or want to recalculate a hardlink. The old # salt isn't used anywhere where the old value is relevant. $algo = $pw1['algo']; $rounds = $pw1['rounds']; # We need to update the "data" column with the $algo and # $rounds data $data = $userdata['data']; $xml = new Xml(); $xml->setXml($data); $data = $xml->updateTag('<rounds>', $this->sanitize($rounds)); $data = $xml->updateTag('<algo>', $this->sanitize($algo)); /* * We can't use writeToUser() since it requires user * validation, which we don't have by definition, so we're * going to manually construct the query here. */ $query = 'UPDATE `' . $this->getTable() . '` SET `' . $this->pwColumn . '`="' . $this->sanitize($pwStore, true) . '", `data`="' . $data . '" WHERE `' . $this->userColumn . "`='" . $this->getUsername() . "'"; $l = $this->openDB(); mysqli_query($l, 'BEGIN'); $r = mysqli_query($l, $query); $finish_query = $r ? 'COMMIT' : 'ROLLBACK'; $callback = array('status' => $r, 'action' => $finish_query, 'new_password' => $newPassword, 'new_password_length' => strlen($newPassword), 'minimum_length' => $this->getMinPasswordLength(), 'maximum_length' => 8192); if ($finish_query == 'ROLLBACK') { $callback['error'] = mysqli_error($l); $callback['new_password'] = null; } $r2 = mysqli_query($l, $finish_query); $callback['status'] = $r && $r2; if ($r2 !== true) { $callback['error'] = 'Unable to commit your password reset'; } if ($callback['status'] === true) { $verifySetPw = $this->lookupUser($this->getUsername(), $newPassword, false); $callback['verification_data'] = $verifySetPw['status']; # It all worked, remove the secret $this->setTempSecret(); if ($doEmailPassword === true) { $mail = $this->getMailObject(); $mail->Subject = '[' . $this->getDomain() . '] New Password'; $mail->Body = "<p>You've successfully reset the password to " . $this->getDomain() . ". Here is your new password:.</p><pre>{$newPassword}</pre><p>If you didn't request a password change, please log in and change your password IMMEDIATELY, and we suggest adding two-factor authentication.</p>."; $mail->addAddress($this->getUsername()); $status = $mail->send(); $mailDetail = array('status' => $status, 'method' => 'email'); if (!$status) { $mailDetail['error'] = $mail->ErrorInfo; } $callback['mail_details'] = $mailDetail; } } $callback['mail_requested'] = $doEmailPassword; return $callback; } else { # We want to look at the current user, and make sure it's OK # before re-assigning the password $userLookup = $this->lookupUser($this->getUsername(), $oldPassword); # If this user checks out, now we can overwrite their old password if ($userLookup['status'] === false) { # Bad user throw new Exception('Invalid original credentials'); } $currentUser = $userLookup['data']; if (strlen($newPassword) < $this->getMinPasswordLength()) { throw new Exception('New password is too short. It should be at least ' . $this->getMinPasswordLenght() . ' characters'); } $currentUser = $userLookup['data']; if (strlen($newPassword) < $this->getMinPasswordLength()) { throw new Exception('New password is too short. It should be at least ' . $this->getMinPasswordLength() . ' characters'); } if (strlen($newPassword) > 8192) { throw new Exception('New password is too long. It should be less than 8192 characters'); } if (!class_exists('Stronghash')) { require_once dirname(__FILE__) . '/../core/stronghash/php-stronghash.php'; } $hash = new Stronghash(); $hashedPw = $hash->hasher($newPassword); $pwStore = json_encode($hashedPw); # We don't need or want to recalculate a hardlink. The old # salt isn't used anywhere where the old value is relevant. $algo = $hashedPw['algo']; $rounds = $hashedPw['rounds']; # We need to update the "data" column with the $algo and # $rounds data $xml = new Xml(); $xml->setXml($data); $data = $currentUser['data']; $backupData = $data; $backupPassword = $currentUser[$this->pwColumn]; $data = $xml->updateTag('<rounds>', $this->sanitize($rounds)); $data = $xml->updateTag('<algo>', $this->sanitize($algo)); /* * We can't use writeToUser() since it requires user * validation, and the second part of the update will always fail. * So, we're going to manually construct the query here. */ $l = $this->openDB(); $query = 'UPDATE `' . $this->getTable() . '` SET `' . $this->pwColumn . '`="' . mysqli_real_escape_string($l, $pwStore) . '", `data`="' . mysqli_real_escape_string($l, $data) . '" WHERE `' . $this->userColumn . "`='" . $this->getUsername() . "'"; mysqli_query($l, 'BEGIN'); $r = mysqli_query($l, $query); $finish_query = $r ? 'COMMIT' : 'ROLLBACK'; $callback = array('status' => $r, 'action' => $finish_query, 'new_password' => $newPassword); if ($finish_query == 'ROLLBACK') { $callback['error'] = mysqli_error($l); } $r2 = mysqli_query($l, $finish_query); $callback['status'] = $r && $r2; if ($callback['status']) { $verifySetPw = $this->lookupUser($this->getUsername(), $newPassword, false); $callback['verification_data'] = $verifySetPw['status']; if (!$verifySetPw['status']) { # verify setting with a lookup # if bad, revert to old # if reset, try again $revert = array(); $query2 = 'UPDATE `' . $this->getTable() . '` SET `' . $this->pwColumn . '`="' . $backupPassword . '", `data`="' . $backupData . '" WHERE `' . $this->userColumn . "`='" . $this->getUsername() . "'"; mysqli_query($l, 'BEGIN'); $r = mysqli_query($l, $query2); $finish_query = $r ? 'COMMIT' : 'ROLLBACK'; $revert['action'] = $finish_query; if ($finish_query == 'ROLLBACK') { $revert['error'] = mysqli_error($l); } $r2 = mysqli_query($l, $finish_query); $revert['status'] = $r && $r2; $revert['debug'] = array('new_data' => $data, 'new_password' => $pwStore, 'original_query' => $query, 'restored_to' => $backupPassword, 'old_data' => $backupData); $callback['revert_status'] = $revert; $callback['original_status'] = $callback['status']; $callback['status'] = false; } } return $callback; } } catch (Exception $e) { # Bad user throw new Exception('Invalid user credentials - ' . $e->getMessage()); } }