1
0
Fork 0
mirror of https://github.com/ThomasGsp/HyperProxmox.git synced 2025-03-09 15:40:18 +00:00
HyperProxmox/code/web/backend/application/model/LoginModel.php
thomas.guiseppin 5352a2b94a first commit
2017-10-21 22:04:42 +02:00

270 lines
10 KiB
PHP

<?php
/**
* LoginModel
*
* The login part of the model: Handles the login / logout stuff
*/
class LoginModel
{
/**
* Login process (for DEFAULT user accounts).
*
* @param $user_name string The user's name
* @param $user_password string The user's password
* @param $set_remember_me_cookie mixed Marker for usage of remember-me cookie feature
*
* @return bool success state
*/
public static function login($user_name, $user_password, $set_remember_me_cookie = null)
{
// we do negative-first checks here, for simplicity empty username and empty password in one line
if (empty($user_name) OR empty($user_password)) {
Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_OR_PASSWORD_FIELD_EMPTY'));
return false;
}
// checks if user exists, if login is not blocked (due to failed logins) and if password fits the hash
$result = self::validateAndGetUser($user_name, $user_password);
if (!$result) {
return false;
}
// reset the failed login counter for that user (if necessary)
if ($result->user_last_failed_login > 0) {
self::resetFailedLoginCounterOfUser($result->user_name);
}
// save timestamp of this login in the database line of that user
self::saveTimestampOfLoginOfUser($result->user_name);
// if user has checked the "remember me" checkbox, then write token into database and into cookie
if ($set_remember_me_cookie) {
self::setRememberMeInDatabaseAndCookie($result->user_id);
}
// successfully logged in, so we write all necessary data into the session and set "user_logged_in" to true
self::setSuccessfulLoginIntoSession(
$result->user_id, $result->user_name, $result->user_email, $result->user_account_type
);
// return true to make clear the login was successful
// maybe do this in dependence of setSuccessfulLoginIntoSession ?
return true;
}
/**
* Validates the inputs of the users, checks if password is correct etc.
* If successful, user is returned
*
* @param $user_name
* @param $user_password
*
* @return bool|mixed
*/
private static function validateAndGetUser($user_name, $user_password)
{
// get all data of that user (to later check if password and password_hash fit)
$result = UserModel::getUserDataByUsername($user_name);
// Check if that user exists. We don't give back a cause in the feedback to avoid giving an attacker details.
if (!$result) {
Session::add('feedback_negative', Text::get('FEEDBACK_LOGIN_FAILED'));
return false;
}
// block login attempt if somebody has already failed 3 times and the last login attempt is less than 30sec ago
if (($result->user_failed_logins >= 3) AND ($result->user_last_failed_login > (time() - 30))) {
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_WRONG_3_TIMES'));
return false;
}
// if hash of provided password does NOT match the hash in the database: +1 failed-login counter
if (!password_verify($user_password, $result->user_password_hash)) {
self::incrementFailedLoginCounterOfUser($result->user_name);
// we say "password wrong" here, but less details like "login failed" would be better (= less information)
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_WRONG'));
return false;
}
// if user is not active (= has not verified account by verification mail)
if ($result->user_active != 1) {
Session::add('feedback_negative', Text::get('FEEDBACK_ACCOUNT_NOT_ACTIVATED_YET'));
return false;
}
return $result;
}
/**
* performs the login via cookie (for DEFAULT user account, FACEBOOK-accounts are handled differently)
* TODO add throttling here ?
*
* @param $cookie string The cookie "remember_me"
*
* @return bool success state
*/
public static function loginWithCookie($cookie)
{
if (!$cookie) {
Session::add('feedback_negative', Text::get('FEEDBACK_COOKIE_INVALID'));
return false;
}
// check cookie's contents, check if cookie contents belong together or token is empty
list ($user_id, $token, $hash) = explode(':', $cookie);
if ($hash !== hash('sha256', $user_id . ':' . $token) OR empty($token)) {
Session::add('feedback_negative', Text::get('FEEDBACK_COOKIE_INVALID'));
return false;
}
// get data of user that has this id and this token
$result = UserModel::getUserDataByUserIdAndToken($user_id, $token);
if ($result) {
// successfully logged in, so we write all necessary data into the session and set "user_logged_in" to true
self::setSuccessfulLoginIntoSession($result->user_id, $result->user_name, $result->user_email, $result->user_account_type);
// save timestamp of this login in the database line of that user
self::saveTimestampOfLoginOfUser($result->user_name);
Session::add('feedback_positive', Text::get('FEEDBACK_COOKIE_LOGIN_SUCCESSFUL'));
return true;
} else {
Session::add('feedback_negative', Text::get('FEEDBACK_COOKIE_INVALID'));
return false;
}
}
/**
* Log out process: delete cookie, delete session
*/
public static function logout()
{
self::deleteCookie();
Session::destroy();
}
/**
* The real login process: The user's data is written into the session.
* Cheesy name, maybe rename. Also maybe refactoring this, using an array.
*
* @param $user_id
* @param $user_name
* @param $user_email
* @param $user_account_type
*/
public static function setSuccessfulLoginIntoSession($user_id, $user_name, $user_email, $user_account_type)
{
Session::init();
Session::set('user_id', $user_id);
Session::set('user_name', $user_name);
Session::set('user_email', $user_email);
Session::set('user_account_type', $user_account_type);
Session::set('user_provider_type', 'DEFAULT');
// get and set avatars
Session::set('user_avatar_file', AvatarModel::getPublicUserAvatarFilePathByUserId($user_id));
Session::set('user_gravatar_image_url', AvatarModel::getGravatarLinkByEmail($user_email));
// finally, set user as logged-in
Session::set('user_logged_in', true);
}
/**
* Increments the failed-login counter of a user
*
* @param $user_name
*/
public static function incrementFailedLoginCounterOfUser($user_name)
{
$database = DatabaseFactory::getFactory()->getConnection();
$sql = "UPDATE users
SET user_failed_logins = user_failed_logins+1, user_last_failed_login = :user_last_failed_login
WHERE user_name = :user_name OR user_email = :user_name
LIMIT 1";
$sth = $database->prepare($sql);
$sth->execute(array(':user_name' => $user_name, ':user_last_failed_login' => time() ));
}
/**
* Resets the failed-login counter of a user back to 0
*
* @param $user_name
*/
public static function resetFailedLoginCounterOfUser($user_name)
{
$database = DatabaseFactory::getFactory()->getConnection();
$sql = "UPDATE users
SET user_failed_logins = 0, user_last_failed_login = NULL
WHERE user_name = :user_name AND user_failed_logins != 0
LIMIT 1";
$sth = $database->prepare($sql);
$sth->execute(array(':user_name' => $user_name));
}
/**
* Write timestamp of this login into database (we only write a "real" login via login form into the database,
* not the session-login on every page request
*
* @param $user_name
*/
public static function saveTimestampOfLoginOfUser($user_name)
{
$database = DatabaseFactory::getFactory()->getConnection();
$sql = "UPDATE users SET user_last_login_timestamp = :user_last_login_timestamp
WHERE user_name = :user_name LIMIT 1";
$sth = $database->prepare($sql);
$sth->execute(array(':user_name' => $user_name, ':user_last_login_timestamp' => time()));
}
/**
* Write remember-me token into database and into cookie
* Maybe splitting this into database and cookie part ?
*
* @param $user_id
*/
public static function setRememberMeInDatabaseAndCookie($user_id)
{
$database = DatabaseFactory::getFactory()->getConnection();
// generate 64 char random string
$random_token_string = hash('sha256', mt_rand());
// write that token into database
$sql = "UPDATE users SET user_remember_me_token = :user_remember_me_token WHERE user_id = :user_id LIMIT 1";
$sth = $database->prepare($sql);
$sth->execute(array(':user_remember_me_token' => $random_token_string, ':user_id' => $user_id));
// generate cookie string that consists of user id, random string and combined hash of both
$cookie_string_first_part = $user_id . ':' . $random_token_string;
$cookie_string_hash = hash('sha256', $cookie_string_first_part);
$cookie_string = $cookie_string_first_part . ':' . $cookie_string_hash;
// set cookie
setcookie('remember_me', $cookie_string, time() + Config::get('COOKIE_RUNTIME'), Config::get('COOKIE_PATH'));
}
/**
* Deletes the cookie
* It's necessary to split deleteCookie() and logout() as cookies are deleted without logging out too!
* Sets the remember-me-cookie to ten years ago (3600sec * 24 hours * 365 days * 10).
* that's obviously the best practice to kill a cookie @see http://stackoverflow.com/a/686166/1114320
*/
public static function deleteCookie()
{
setcookie('remember_me', false, time() - (3600 * 24 * 3650), Config::get('COOKIE_PATH'));
}
/**
* Returns the current state of the user's login
*
* @return bool user's login status
*/
public static function isUserLoggedIn()
{
return Session::userIsLoggedIn();
}
}