666 lines
21 KiB
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
|
|
{
|
|
}
|