/php/user.php
PHP | 420 lines | 351 code | 23 blank | 46 comment | 88 complexity | dbfcec451428a2c3b2deb17fdf00954e MD5 | raw file
Possible License(s): AGPL-3.0, LGPL-2.1
- <?php
- // Dobrado Content Management System
- // Copyright (C) 2019 Malcolm Blaney
- //
- // This program is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Affero General Public License as
- // published by the Free Software Foundation, either version 3 of the
- // License, or (at your option) any later version.
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Affero General Public License for more details.
- //
- // You should have received a copy of the GNU Affero General Public License
- // along with this program. If not, see <http://www.gnu.org/licenses/>.
- class User {
-
- public $name = '';
- public $group = '';
- public $page = '';
- public $config = NULL;
- public $settings = [];
- public $active = true;
- public $loggedIn = false;
- public $loginFailed = false;
- public $loginStatus = '';
- public $emailReset = false;
- public $defaultPage = false;
- public $canEditSite = false;
- public $canEditPage = false;
- public $canCopyPage = false;
- public $canViewPage = false;
- public function __construct($user = '', $group = '', $timezone = '') {
- $this->config = new Config();
- if ($user !== '') {
- $this->name = $user;
- if ($group === '') {
- $this->SetGroup();
- }
- else {
- $this->group = $group;
- }
- $this->Settings();
- $this->config->SetUser($user, $timezone);
- return;
- }
- $from_admin = false;
- // Check if a (possibly different) user is trying to log in.
- if (isset($_POST['user'])) {
- $single_user = substitute('account-single-user') === 'true';
- $mysqli = connect_db();
- $this->name = $mysqli->escape_string(strtolower(trim($_POST['user'])));
- if ($single_user) {
- $query = 'SELECT value FROM settings WHERE user = "admin" AND ' .
- 'label = "account" AND name = "username"';
- if ($mysqli_result = $mysqli->query($query)) {
- if ($settings = $mysqli_result->fetch_assoc()) {
- if ($this->name === $settings['value']) $this->name = 'admin';
- }
- }
- else {
- log_db('User: ' . $mysqli->error);
- }
- }
- $mysqli->close();
- if (!$single_user &&
- $this->name === 'guest' && $this->config->GuestAllowed()) {
- $this->Guest();
- return;
- }
- // Check if the user is requesting a password reset.
- if (isset($_POST['email'])) {
- $this->emailReset = true;
- }
- // If the admin user is currently logged in, let them log in as anyone.
- else if (!$single_user &&
- isset($_SESSION['user']) && $_SESSION['user'] === 'admin') {
- $this->loggedIn = true;
- $this->defaultPage = true;
- if ($this->name !== 'admin') $from_admin = true;
- }
- else if ($this->Valid()) {
- $this->loggedIn = true;
- // Go to the user's default page when they first log in.
- $this->defaultPage = true;
- // Check if the user wants a persistent login.
- if (isset($_POST['remember']) && $_POST['remember'] === 'true') {
- $this->RememberLogin();
- }
- else {
- $this->ForgetLogin();
- }
- }
- else {
- $this->name = '';
- $this->group = '';
- $this->loginFailed = true;
- }
- // Restart the session in case the username has changed.
- session_destroy();
- session_start();
- // new-login is used by init.php to clear local storage.
- if ($this->defaultPage) $_SESSION['new-login'] = true;
- // Store from_admin in the new session.
- if ($from_admin) $_SESSION['from-admin'] = true;
- }
- // Check if a session already exists.
- else if (isset($_SESSION['user'])) {
- $this->name = $_SESSION['user'];
- $this->loggedIn = true;
- // Indieauth users are given persistent login by default.
- if (isset($_SESSION['indieauth-login'])) {
- unset($_SESSION['indieauth-login']);
- $this->RememberLogin();
- }
- }
- // Check if a persistent login cookie is being used.
- else if (isset($_COOKIE['token']) && isset($_COOKIE['user'])) {
- $this->CheckLogin();
- }
- // Check if a login code is being used last so that it can be removed.
- else if (isset($_GET['code'])) {
- $this->CheckCode();
- }
- if ($this->loggedIn) {
- // Don't update last visit for this user when logging in from admin.
- if (!isset($_SESSION['from-admin'])) $this->SetLastVisit();
- $this->SetGroup();
- $this->Settings();
- $this->config->SetUser($this->name);
- setcookie('user', $this->name, time() + 3600 * 24 * 7, '/');
- }
- }
- public function SetPermission($page, $owner = '') {
- $this->page = $page;
- $name = $owner === '' ? $page : $owner . '/' . $page;
- $this->canEditSite = can_edit_site();
- $this->canEditPage = can_edit_page($name);
- $this->canCopyPage = can_copy_page($name);
- $this->canViewPage = can_view_page($name);
- }
- // Private functions below here ////////////////////////////////////////////
- private function CheckCode() {
- // If a value isn't found set the login status as expired.
- $this->loginStatus = 'code expired';
- $mysqli = connect_db();
- // A login code can be sent to an email address in deploy.php after a
- // verification code was previously sent to make sure the email address
- // matches the requested domain. If a verification code is set here, make
- // sure it matches in DNS before checking the login code.
- $verification = '';
- $query = 'SELECT value FROM settings WHERE user = "admin" AND ' .
- 'label = "login" AND name = "verification"';
- if ($mysqli_result = $mysqli->query($query)) {
- if ($settings = $mysqli_result->fetch_assoc()) {
- $verification = $settings['value'];
- }
- $mysqli_result->close();
- }
- else {
- log_db('User->CheckCode 1: ' . $mysqli->error);
- }
- $verification_set = false;
- if ($verification !== '') {
- if ($result_list = dns_get_record($this->config->ServerName(), DNS_TXT)) {
- foreach ($result_list as $result) {
- if ($result['type'] === 'TXT' &&
- strtolower($result['txt']) === $verification) {
- $verification_set = true;
- break;
- }
- }
- }
- if (!$verification_set) {
- $this->loginStatus = 'verification not set';
- $this->loginFailed = true;
- }
- }
- if ($verification === '' || $verification_set) {
- $query = 'SELECT value FROM settings WHERE user = "admin" AND ' .
- 'label = "login" AND name = "code"';
- if ($mysqli_result = $mysqli->query($query)) {
- if ($settings = $mysqli_result->fetch_assoc()) {
- if ($settings['value'] === $_GET['code']) $this->loggedIn = true;
- else $this->loginStatus = 'code failed';
- }
- $mysqli_result->close();
- }
- else {
- log_db('User->CheckCode 2: ' . $mysqli->error);
- }
- if ($this->loggedIn) {
- $this->name = 'admin';
- $this->loginStatus = 'valid';
- $this->RememberLogin();
- // If the code worked then remove it now that the user is logged in.
- $query = 'DELETE FROM settings WHERE user = "admin" AND ' .
- 'label = "login" AND name = "code"';
- if (!$mysqli->query($query)) {
- log_db('User->CheckCode 3: ' . $mysqli->error);
- }
- }
- else {
- $this->loginFailed = true;
- }
- }
- $mysqli->close();
- }
- private function CheckLogin() {
- $mysqli = connect_db();
- $user = $mysqli->escape_string($_COOKIE['user']);
- list($series, $token) =
- explode(':', $mysqli->escape_string($_COOKIE['token']));
- $query = 'SELECT token FROM session WHERE user = "' . $user . '" AND ' .
- 'series = "' . $series . '"';
- if ($mysqli_result = $mysqli->query($query)) {
- if ($session = $mysqli_result->fetch_assoc()) {
- if ($session['token'] === $token) {
- $this->name = $user;
- $this->loggedIn = true;
- $_SESSION['limited-login'] = true;
- // Replace the token.
- $token = bin2hex(openssl_random_pseudo_bytes(16));
- $query = 'UPDATE session SET token = "' . $token . '" WHERE ' .
- 'user = "' . $user . '" AND series = "' . $series . '"';
- if (!$mysqli->query($query)) {
- log_db('User->CheckLogin 1: ' . $mysqli->error);
- }
- setcookie('token', $series . ':' . $token,
- time() + 3600 * 24 * 7, '/');
- }
- else {
- // When a token is provided for a series that doesn't match the
- // database, it means the token was stolen. Need to remove all
- // tokens in this series to prevent it being used again.
- $query = 'DELETE FROM session WHERE user = "' . $user . '" AND ' .
- 'series = "' . $series . '"';
- if (!$mysqli->query($query)) {
- log_db('User->CheckLogin 2: ' . $mysqli->error);
- }
- setcookie('user', '', time() - 3600, '/');
- setcookie('token', '', time() - 3600, '/');
- session_destroy();
- }
- }
- $mysqli_result->close();
- }
- else {
- log_db('User->CheckLogin 3: ' . $mysqli->error);
- }
- $mysqli->close();
- }
- private function ForgetLogin() {
- $mysqli = connect_db();
- $query = 'DELETE FROM session WHERE user = "' . $this->name . '"';
- if (!$mysqli->query($query)) {
- log_db('User->ForgetLogin: ' . $mysqli->error);
- }
- $mysqli->close();
- }
- private function Guest() {
- $this->name = 'guest' . time();
- $chars = 'bcdfghjklmnpqrstvwxyz1234567890';
- $length = strlen($chars) - 1;
- for ($i = 0; $i < 5; $i++) {
- $this->name .= substr($chars, mt_rand(0, $length), 1);
- }
- if (new_user($this, 'admin') === true) {
- $this->loggedIn = true;
- if (isset($_POST['remember']) && $_POST['remember'] === 'true') {
- $this->RememberLogin();
- }
- else {
- $this->ForgetLogin();
- }
- setcookie('user', $this->name, time() + 3600 * 24 * 7, '/');
- }
- else {
- $this->name = '';
- $this->loggedIn = false;
- }
- }
- private function RememberLogin() {
- $mysqli = connect_db();
- // When a user logs in and a persistent login cookie already exists,
- // remove the existing one from the database first.
- if (isset($_COOKIE['user']) && isset($_COOKIE['token'])) {
- $user = $mysqli->escape_string($_COOKIE['user']);
- list($series, $token) =
- explode(':', $mysqli->escape_string($_COOKIE['token']));
- $query = 'DELETE FROM session WHERE user = "' . $user . '" AND ' .
- 'series = "' . $series . '" AND token = "' . $token . '"';
- if (!$mysqli->query($query)) {
- log_db('User->RememberLogin 1: ' . $mysqli->error);
- }
- }
- $series = bin2hex(openssl_random_pseudo_bytes(16));
- $token = bin2hex(openssl_random_pseudo_bytes(16));
- $query = 'INSERT INTO session VALUES ("' . $this->name . '", ' .
- '"' . $series . '", "' . $token . '")';
- if (!$mysqli->query($query)) {
- log_db('User->RememberLogin 2: ' . $mysqli->error);
- }
- $mysqli->close();
- setcookie('token', $series . ':' . $token, time() + 3600 * 24 * 7, '/');
- }
- private function SetGroup() {
- $mysqli = connect_db();
- $query = 'SELECT system_group, active FROM users WHERE ' .
- 'user = "' . $this->name . '"';
- if ($mysqli_result = $mysqli->query($query)) {
- if ($users = $mysqli_result->fetch_assoc()) {
- $this->group = $users['system_group'];
- $this->active = $users['active'] === '1';
- }
- $mysqli_result->close();
- }
- else {
- log_db('User->SetGroup: ' . $mysqli->error);
- }
- $mysqli->close();
- }
- private function SetLastVisit() {
- $mysqli = connect_db();
- $query = 'INSERT INTO settings VALUES ("' . $this->name . '", ' .
- '"account", "lastVisit", "' . time() . '") ON DUPLICATE KEY UPDATE ' .
- 'value = "' . time() . '"';
- if (!$mysqli->query($query)) {
- log_db('User->SetLastVisit: ' . $mysqli->error);
- }
- $mysqli->close();
- }
- private function Settings() {
- // The start module provides some default settings if installed. It is
- // possible that module.php cannot be included before user.php, so need
- // to check that the Module class exits here.
- if (class_exists('Module')) {
- $start = new Module($this, $this->name, 'start');
- if ($start->IsInstalled()) {
- $this->settings = $start->Factory('Settings');
- }
- }
- $mysqli = connect_db();
- $query = 'SELECT label, name, value FROM settings WHERE ' .
- 'user = "' . $this->name . '"';
- if ($mysqli_result = $mysqli->query($query)) {
- while ($settings = $mysqli_result->fetch_assoc()) {
- $label = $settings['label'];
- if (isset($this->settings[$label])) {
- $this->settings[$label][$settings['name']] = $settings['value'];
- }
- else {
- $this->settings[$label] = [$settings['name'] => $settings['value']];
- }
- }
- $mysqli_result->close();
- }
- else {
- log_db('User->Settings: ' . $mysqli->error);
- }
- $mysqli->close();
- }
- private function Valid() {
- $valid = false;
- $us_password = isset($_POST['password']) ? trim($_POST['password']) : '';
- $mysqli = connect_db();
- $query = 'SELECT password, confirmed FROM users WHERE ' .
- 'user = "' . $this->name . '"';
- if ($mysqli_result = $mysqli->query($query)) {
- if ($users = $mysqli_result->fetch_assoc()) {
- if (password_verify($us_password, $users['password'])) {
- // Set loginStatus so that the Login module can display a better
- // error message if login failed.
- if ($users['confirmed'] === '1') {
- $valid = true;
- $this->loginStatus = 'valid';
- }
- else {
- $this->loginStatus = 'unconfirmed';
- }
- }
- else {
- $this->loginStatus = 'password failed';
- }
- }
- else {
- $this->loginStatus = 'user not found';
- }
- $mysqli_result->close();
- }
- else {
- log_db('User->Valid: ' . $mysqli->error);
- }
- $mysqli->close();
- return $valid;
- }
- }