0
0
mirror of https://github.com/postfixadmin/postfixadmin.git synced 2024-09-19 19:22:14 +02:00
postfixadmin/model/Login.php

188 lines
5.7 KiB
PHP
Raw Normal View History

<?php
2021-03-22 10:28:28 +01:00
class Login
{
private $key_table;
private $table;
2021-04-13 22:19:16 +02:00
public function __construct(string $tableName)
{
$ok = ['mailbox', 'admin'];
2020-09-26 10:58:16 +02:00
if (!in_array($tableName, $ok)) {
throw new \InvalidArgumentException("Unsupported tableName for login: " . $tableName);
}
$this->table = $tableName;
$this->key_table = table_by_key($tableName);
}
/**
* Attempt to log a user in.
* @param string $tablename
* @param string $username
* @param string $password
* @return boolean true on successful login (i.e. password matches etc)
*/
2021-04-13 22:19:16 +02:00
public function login($username, $password): bool
{
$active = db_get_boolean(true);
$query = "SELECT password FROM {$this->key_table} WHERE username = :username AND active = :active";
$values = array('username' => $username, 'active' => $active);
$result = db_query_all($query, $values);
2021-01-19 17:50:56 +01:00
if (sizeof($result) == 1 && strlen($password) > 0) {
$row = $result[0];
try {
$crypt_password = pacrypt($password, $row['password']);
} catch (\Exception $e) {
error_log("Error while trying to call pacrypt()");
2021-02-17 22:29:18 +01:00
error_log("" . $e);
2021-01-14 12:16:31 +01:00
hash_equals("not", "comparable");
2021-01-14 12:26:04 +01:00
return false; // just refuse to login?
}
return hash_equals($row['password'], $crypt_password);
}
// try and be near constant time regardless of whether the db user exists or not
try {
2021-01-25 18:00:16 +01:00
// this causes errors with e.g. dovecot as there is no prefix.
$x = pacrypt('abc', 'def');
2021-01-14 12:26:04 +01:00
} catch (\Exception $e) {
error_log("Error trying to call pacrypt()");
2021-02-17 22:29:18 +01:00
error_log("" . $e);
}
return hash_equals('not', 'comparable');
}
/**
* Updates db with password recovery code, and returns it.
* @param string $username
* @return false|string
* @throws Exception
*/
2021-04-13 22:19:16 +02:00
public function generatePasswordRecoveryCode(string $username)
{
$sql = "SELECT count(1) FROM {$this->key_table} WHERE username = :username AND active = :active";
$active = db_get_boolean(true);
$values = [
'username' => $username,
'active' => $active,
];
$result = db_query_one($sql, $values);
if ($result) {
$token = generate_password();
$updatedRows = db_update($this->table, 'username', $username, array(
'token' => pacrypt($token),
'token_validity' => date("Y-m-d H:i:s", strtotime('+ 1 hour')),
));
if ($updatedRows == 1) {
return $token;
}
}
return false;
}
/**
* returns user's domain name
* @param $username
* @return mixed|null
* @throws Exception
*/
protected function getUserDomain($username)
{
$sql = "SELECT domain FROM {$this->table} WHERE username = :username AND active = :active";
$active = db_get_boolean(true);
$values = [
'username' => $username,
'active' => $active,
];
// Fetch the domain
$result = db_query_one($sql, $values);
if (is_array($result) && isset($result['domain'])) {
return $result['domain'];
} else {
return NULL;
}
}
/**
* @param string $username
* @param string $new_password
* @param string $old_password
*
* All passwords need to be plain text; they'll be hashed appropriately
* as per the configuration in config.inc.php
*
* @return boolean true on success; false on failure
* @throws \Exception if invalid user, or db update fails.
*/
2021-04-13 22:19:16 +02:00
public function changePassword($username, $new_password, $old_password): bool
{
list(/*NULL*/, $domain) = explode('@', $username);
if (!$this->login($username, $old_password)) {
throw new \Exception(Config::Lang('pPassword_password_current_text_error'));
}
$set = array(
'password' => pacrypt($new_password),
);
if (Config::bool('password_expiration')) {
$domain = $this->getUserDomain($username);
if (!is_null($domain)) {
$password_expiration_value = (int)get_password_expiration_value($domain);
$set['password_expiry'] = date('Y-m-d H:i', strtotime("+$password_expiration_value day"));
}
}
$result = db_update($this->table, 'username', $username, $set);
if ($result != 1) {
db_log($domain, 'edit_password', "FAILURE: " . $username);
throw new \Exception(Config::lang('pEdit_mailbox_result_error'));
}
db_log($domain, 'edit_password', $username);
$cmd_pw = Config::read('mailbox_postpassword_script');
if (empty($cmd_pw)) {
return true;
}
$warnmsg_pw = Config::Lang('mailbox_postpassword_failed');
// If we have a mailbox_postpassword_script (dovecot only?)
$cmdarg1=escapeshellarg($username);
$cmdarg2=escapeshellarg($domain);
$cmdarg3=escapeshellarg($old_password);
$cmdarg4=escapeshellarg($new_password);
$command= "$cmd_pw $cmdarg1 $cmdarg2 $cmdarg3 $cmdarg4";
$retval=0;
$output=array();
$firstline='';
$firstline=exec($command, $output, $retval);
if (0 != $retval) {
error_log("Running $command yielded return value=$retval, output was: " . json_encode($output));
throw new \Exception($warnmsg_pw);
}
return true;
}
2020-09-25 22:33:26 +02:00
}