chevereto-free/app/lib/classes/class.login.php

666 lines
21 KiB
PHP

<?php
/* --------------------------------------------------------------------
This file is part of Chevereto Free.
https://chevereto.com/free
(c) Rodolfo Berrios <rodolfo@chevereto.com>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
--------------------------------------------------------------------- */
namespace CHV;
use G;
use Exception;
/**
* PrePi suffix refers to functions before Chevereto 3.14.0 (pi number)
* @package CHV
*/
class Login
{
/** User::get */
protected static $logged_user;
/** Used when handling signup process */
protected static $signup;
/** The login "session" properties */
protected static $session;
protected static $social_services = [
'facebook' => 'Facebook',
'twitter' => 'Twitter',
'google' => 'Google',
'vk' => 'VK',
];
const COOKIE = 'KEEP_LOGIN';
protected static $cookies = [
self::COOKIE => 'cookie',
self::COOKIE . '_FACEBOOK' => 'cookie_facebook',
self::COOKIE . '_TWITTER' => 'cookie_twitter',
self::COOKIE . '_GOOGLE' => 'cookie_google',
self::COOKIE . '_VK' => 'cookie_vk',
];
/** @var bool */
protected static $isPi;
public static function isPi()
{
if (!isset(self::$isPi)) {
self::$isPi = version_compare(Settings::get('chevereto_version_installed'), '1.2.0', '>=');
}
return self::$isPi;
}
public static function getSocialCookieName($name)
{
$flip = array_flip(self::$cookies);
return $flip['cookie_' . $name];
}
public static function tryLogin()
{
if (self::isPi()) {
self::tryCookies();
} else {
try {
$login = false;
if ($_COOKIE['KEEP_LOGIN']) {
$login = self::loginCookiePrePi('internal');
} elseif ($_COOKIE['KEEP_LOGIN_SOCIAL']) {
$login = self::loginCookiePrePi('social');
}
if ($login == false && $_SESSION['login']) {
$login = self::login($_SESSION['login']['id']);
}
} catch (Exception $e) {
self::logoutPrePi();
throw new Exception($e->getMessage(), $e->getCode());
}
}
}
/**
* @return null|array|false Null if no cookies, array if cookie+login, false if cookie+error
*/
public static function tryCookies()
{
if ($_COOKIE['KEEP_LOGIN_SOCIAL']) {
self::updateSocialCookie();
}
$login = null;
foreach (self::$cookies as $cookieName => $type) {
if (!$_COOKIE[$cookieName]) {
continue;
}
$loginCookie = self::loginCookie($cookieName);
if ($loginCookie) {
$login = $loginCookie;
continue;
}
}
return $login;
}
/**
* @return array|false logged user if any
*/
protected static function loginCookie($cookieName = self::COOKIE)
{
if (!array_key_exists($cookieName, self::$cookies)) {
return;
}
try {
$validate = self::validateCookie($cookieName);
if ($validate['valid']) {
self::login($validate['user_id'], $validate['cookie']['type']);
self::$session['id'] = $validate['login_id'];
self::$session['login_cookies'][] = $validate['login_id'];
return self::$logged_user;
} else {
Requestlog::insert(array('result' => 'fail', 'type' => 'login', 'user_id' => $validate['user_id']));
static::unsetCookie($cookieName);
return false;
}
} catch (Exception $e) {
throw new LoginException($e->getMessage(), 400);
}
}
/**
* Login the target user $id
*
* Set and return array self::$logged_user
*/
public static function login($id, $cookieType = 'cookie')
{
$flip = array_flip(self::$cookies);
if (!array_key_exists($cookieType, $flip)) {
throw new Exception(sprintf('Invalid login $by %s', $cookieType));
}
$user = User::getSingle($id, 'id');
// Bind guest (session) content to logged user
if ($user) {
foreach (['albums', 'images'] as $t) {
$s = 'guest_' . $t;
if (is_array($_SESSION[$s]) == false) {
continue;
}
try {
$db = DB::getInstance();
$todoTable = DB::getTable($t); // images
$fieldPrefix = DB::getFieldPrefix($t); // image
$db->query('UPDATE ' . $todoTable . ' SET ' . $fieldPrefix . '_user_id=' . $id . ' WHERE ' . $fieldPrefix . '_id IN (' . implode(',', $_SESSION[$s]) . ')');
$db->exec();
if ($db->rowCount()) {
DB::increment('users', [$fieldPrefix . '_count' => '+' . $db->rowCount()], ['id' => $id]);
}
} catch (Exception $e) {
} // Silence
unset($_SESSION[$s]);
}
}
try {
// Wipe any bad login request
Requestlog::delete([
'user_id' => $id,
'result' => 'fail',
'type' => 'login',
'ip' => G\get_client_ip(),
]);
// User has logins?
if ($user['login']) {
if ($user['status'] == 'valid') {
self::unsetSignup();
self::$session = [
'user_id' => $id,
'type' => $cookieType,
];
} else {
self::setSingup([
'status' => $user['status'],
'email' => $user['email'],
]);
}
}
// Set the timezone for the logged user
if (self::getUser()['timezone'] !== Settings::get('default_timezone') and G\is_valid_timezone($user['timezone'])) {
date_default_timezone_set($user['timezone']);
}
// Translate logged user count labels
foreach (['image_count_label', 'album_count_label'] as $v) {
$user[$v] = _s(self::$logged_user[$v]);
}
self::$logged_user = $user;
return self::$logged_user;
} catch (Exception $e) {
throw new LoginException($e->getMessage(), 400);
}
}
/**
* Logout and remove all cookies (including DB)
* Beware when using this.
*/
public static function logout()
{
if (!self::isPi()) {
return self::logoutPrePi();
}
try {
self::$logged_user = null;
self::$session = null;
self::unsetSignup();
foreach (self::$cookies as $cookieName => $type) {
$validate = self::validateCookie($cookieName);
if ($validate['valid'] == null) {
continue;
}
if ($validate['valid']) {
self::delete(['id' => $validate['login_id']]);
}
static::unsetCookie($cookieName);
}
} catch (Exception $e) {
throw new LoginException($e->getMessage(), 400);
}
}
public static function insert($values)
{
if (!is_array($values)) {
throw new LoginException('Expecting array values, ' . gettype($values) . ' given in ' . __METHOD__, 100);
}
if (!$values['ip']) {
$values['ip'] = G\get_client_ip();
}
if (!$values['hostname']) {
$values['hostname'] = json_encode(array_merge(G\parse_user_agent($_SERVER['HTTP_USER_AGENT'])));
}
if (!$values['date']) {
$values['date'] = G\datetime();
}
if (!$values['date_gmt']) {
$values['date_gmt'] = G\datetimegmt();
}
try {
if (G\starts_with('cookie', $values['type'])) {
$cookieName = self::COOKIE;
if ($values['type'] != 'cookie') {
$cookieName .= '_' . G\str_replace_first('COOKIE_', '', strtoupper($values['type']));
}
$tokenize = generate_hashed_token($values['user_id']);
$values['secret'] = $tokenize['hash'];
$insert = DB::insert('logins', $values);
if ($insert) {
$d = \DateTime::createFromFormat('Y-m-d H:i:s', $values['date_gmt'], new \DateTimeZone('UTC'));
$cookie = $tokenize['public_token_format'] . ':' . $d->getTimestamp();
static::setCookie($cookieName, $cookie);
}
return $insert;
} else {
return DB::insert('logins', $values);
}
} catch (Exception $e) {
throw new LoginException($e->getMessage(), 400);
}
}
public static function loginCookiePrePi($type = 'internal')
{
if (!in_array($type, ['internal', 'social'])) {
return;
}
try {
$cookie = $_COOKIE[$type == 'internal' ? 'KEEP_LOGIN' : 'KEEP_LOGIN_SOCIAL'];
$explode = array_filter(explode(':', $cookie));
// CHV: 0->id | 1:token | 2:timestamp
// SOC: 0->id | 1:type | 2:hash | 3:timestamp
$count = $type == 'social' ? 4 : 3;
if (count($explode) !== $count) {
return false;
}
foreach ($explode as $exp) {
if ($exp == null) {
return false;
}
}
$user_id = decodeID($explode[0]);
$login_db_arr = [
'user_id' => $user_id,
'type' => $type == 'internal' ? 'cookie' : $explode[1],
'date_gmt' => gmdate('Y-m-d H:i:s', end($explode)),
];
$login_db = self::get($login_db_arr, null, 1);
$is_valid_token = $type == 'internal' ? check_hashed_token($login_db['secret'], $cookie) : password_verify($login_db['secret'] . $login_db['token_hash'], $explode[2]);
if ($is_valid_token) {
return self::login($login_db['user_id'], $type == 'internal' ? 'cookie' : $explode[1]);
} else {
Requestlog::insert(array('result' => 'fail', 'type' => 'login', 'user_id' => $user_id));
self::logoutPrePi();
return null;
}
} catch (Exception $e) {
throw new LoginException($e->getMessage(), 400);
}
}
public static function logoutPrePi()
{
try {
self::$logged_user = null;
$doing = $_SESSION['login']['type'];
if ($doing == 'session') {
self::delete([
'user_id' => $_SESSION['login']['id'],
'type' => 'session',
'date_gmt' => $_SESSION['login']['datetime'],
]);
}
session_unset();
// session_destroy();
// Unset the cookie from client and DB
$cookies = ['KEEP_LOGIN', 'KEEP_LOGIN_SOCIAL'];
foreach ($cookies as $cookie_name) {
static::unsetCookie($cookie_name);
if ($cookie_name == 'KEEP_LOGIN_SOCIAL') {
continue;
}
$cookie = $_COOKIE[$cookie_name];
$explode = array_filter(explode(':', $cookie));
if (count($explode) == 4) {
foreach ($explode as $exp) {
if ($exp == null) {
return false;
}
}
$user_id = decodeID($explode[0]);
self::delete([
'user_id' => $user_id,
'type' => 'cookie',
'date_gmt' => gmdate('Y-m-d H:i:s', $explode[3]),
]);
}
}
} catch (Exception $e) {
throw new LoginException($e->getMessage(), 400);
}
}
public static function updateSocialCookie()
{
$cookieName = self::COOKIE . '_SOCIAL';
$cookie = $_COOKIE[$cookieName];
// SOC: 0->user_id | 1:type | 2:hash | 3:timestamp
$explode = array_filter(explode(':', $cookie));
if (count($explode) != 4) {
self::unsetCookie($cookieName);
return;
}
$user_id = decodeID($explode[0]);
$type = $explode[1];
if (!in_array($type, self::getSocialServices(['flat' => true]))) {
self::unsetCookie($cookieName);
return;
}
$hash = $explode[2];
$login_arr = [
'user_id' => $user_id,
'type' => $type,
'date_gmt' => gmdate('Y-m-d H:i:s', $explode[3]),
];
$login = self::get($login_arr, null, 1);
$is_valid_token = password_verify($login['secret'] . $login['token_hash'], $hash);
if ($is_valid_token) {
unset($login_arr['date_gmt']);
$login_arr['type'] = 'cookie_' . $type;
self::insert($login_arr);
}
self::unsetCookie($cookieName);
}
public static function get($values, $sort = array(), $limit = null)
{
try {
$login_db = DB::get('logins', $values, 'AND', $sort, $limit);
return DB::formatRows($login_db);
} catch (Exception $e) {
throw new LoginException($e->getMessage(), 400);
}
}
public static function hasSingup()
{
return isset($_SESSION['signup']);
}
public static function getSingup()
{
return $_SESSION['signup'];
}
public static function setSingup($var)
{
$_SESSION['signup'] = $var;
}
public static function unsetSignup()
{
unset($_SESSION['signup']);
}
public static function hasSession()
{
return isset(self::$session);
}
public static function getSession()
{
return self::$session;
}
public static function getUser()
{
return self::isLoggedUser() ? self::$logged_user : null;
}
public static function setUser($key, $value)
{
if (self::$logged_user) {
self::$logged_user[$key] = $value;
}
}
public static function isLoggedUser()
{
return !is_null(self::$logged_user);
}
/**
* @return array
*/
public static function validateCookie($cookieName)
{
if (!$_COOKIE[$cookieName]) {
return [
'valid' => null,
];
}
$getCookie = static::getCookie($cookieName);
$login_arr = $getCookie;
unset($login_arr['raw']);
$login = self::get($login_arr, null, 1);
$is_valid = check_hashed_token($login['secret'], $getCookie['raw']);
return [
'valid' => $is_valid,
'cookie' => $getCookie,
'login_id' => $login['id'],
'user_id' => $getCookie['user_id']
];
}
public static function checkPassword($id, $password)
{
try {
$login = self::get(['user_id' => $id, 'type' => 'password'], null, 1);
return password_verify($password, $login['secret']);
} catch (Exception $e) {
throw new LoginException($e->getMessage(), 400);
}
}
public static function update($id, $values)
{
try {
return DB::update('logins', $values, ['id' => $id]);
} catch (Exception $e) {
throw new LoginException($e->getMessage(), 400);
}
}
protected static function setCookie($key, $value)
{
$args = [
$key, $value, time() + (60 * 60 * 24 * 30),
];
static::cookie(...$args);
}
public static function unsetCookie($key)
{
static::cookie($key, '', -1);
}
protected static function cookie($key, $value, $time)
{
if ($time == -1) {
unset($_COOKIE[$key]);
} else {
$_COOKIE[$key] = $value;
}
$args = func_get_args();
$args[] = G_ROOT_PATH_RELATIVE;
if ($time == -1) {
// PrePi
setcookie(...$args);
}
$args[] = G\get_host_domain();
$args[] = G_HTTP_PROTOCOL == 'https';
$args[] = true;
// setcookie(name,value,expire,path,domain,secure,httponly)
setcookie(...$args);
}
public static function addPassword($id, $password, $update_session = true)
{
return self::passwordDB('insert', $id, $password, $update_session);
}
public static function changePassword($id, $password, $update_session = true)
{
return self::passwordDB('update', $id, $password, $update_session);
}
public static function getCookie($cookieName)
{
$rawCookie = $_COOKIE[$cookieName];
$explode = explode(':', $rawCookie);
if (count($explode) !== 3) {
return [];
// throw new Exception('Invalid raw cookie format');
}
foreach ($explode as $exp) {
if ($exp == null) {
return [];
// throw new Exception('Invalid raw cookie format');
}
}
$return = [
'raw' => $rawCookie,
'user_id' => decodeID($explode[0]),
'type' => self::$cookies[$cookieName],
'date_gmt' => gmdate('Y-m-d H:i:s', $explode[2])
];
return $return;
}
protected static function passwordDB($action, $id, $password, $update_session)
{
$action = strtoupper($action);
if (!in_array($action, array('UPDATE', 'INSERT'))) {
throw new LoginException('Expecting UPDATE or INSERT statements in ' . __METHOD__, 200);
}
$hash = password_hash($password, PASSWORD_BCRYPT);
$array_values = array(
'ip' => G\get_client_ip(),
'date' => G\datetime(),
'date_gmt' => G\datetimegmt(),
'secret' => $hash,
);
try {
if ($action == 'UPDATE') {
$dbase = DB::update('logins', $array_values, array('type' => 'password', 'user_id' => $id));
} else {
$array_values['user_id'] = $id;
$array_values['type'] = 'password';
$dbase = DB::insert('logins', $array_values);
}
// Update logged user?
if (self::getUser()['id'] == $id and self::$session and $update_session) {
self::$session = [
'id' => $id,
'type' => 'password',
];
}
return $dbase;
} catch (Exception $e) {
throw new LoginException($e->getMessage(), 400);
}
}
public static function delete($values, $clause = 'AND')
{
try {
return DB::delete('logins', $values, $clause);
} catch (Exception $e) {
throw new LoginException($e->getMessage(), 400);
}
}
public static function getSocialServices($args = [])
{
$args = array_merge([
'get' => 'all',
'flat' => false,
], $args);
$return = [];
if ($args['get'] == 'all') {
if ($args['flat'] === true) {
$return = array_keys(self::$social_services);
} else {
$return = self::$social_services;
}
} else {
foreach (self::$social_services as $k => $v) {
if (($args['get'] == 'enabled' and !getSetting($k)) or ($args['get'] == 'disabled' and getSetting($k))) {
continue;
}
if ($args['flat'] === true) {
$return[] = $k;
} else {
$return[$k] = $v;
}
}
}
return $return;
}
public static function isAdmin()
{
return (bool) self::$logged_user['is_admin'];
}
public static function isManager()
{
return (bool) self::$logged_user['is_manager'];
}
}
class LoginException extends Exception
{
}