Moving authentication relevant things to its own class replacing checkpermission and old user.class.php, and a modern approach on validation without global variables

This commit is contained in:
ohartl 2016-02-18 17:44:27 +01:00
parent 838188174e
commit d0b38d17b1
12 changed files with 331 additions and 316 deletions

View file

@ -1,38 +0,0 @@
<?php
/*
* Checks, if the current user has the permission, which is required for an action.
* $role_req = required role [string]
*
* Returns:
* true: User has role $role_req
* false: User doesn't have role $role_req
*
* Possible roles: user, admin
*/
function user_has_permission($role_req){
global $user;
if($user->isLoggedIn() === true){
// User is logged in. Check permissions
// To be done. Load user role from database or better: save in SESSION
if($role_req === "user"){
if($user->getRole() == "user" || $user->getRole() == "admin"){
return true;
}
else{
return false;
}
}
else if($role_req === "admin"){
if($user->getRole() == "admin"){
return true;
}
}
}
else{
// User is not logged in => public user => no permissions
return false;
}
}
?>

View file

@ -0,0 +1,244 @@
<?php
class Auth
{
const SESSION_IDENTIFIER = 'uid';
/**
* @var User|null
*/
private static $loggedInUser = null;
/**
* Init Authentication
*/
public static function init()
{
static::loginUserViaSession();
}
/**
* Check whether the user is logged in or not.
*
* @return bool
*/
public static function isLoggedIn()
{
return !is_null(static::$loggedInUser);
}
/**
* Get the currently logged in user.
*
* @return null|User
*/
public static function getUser()
{
return static::$loggedInUser;
}
/**
* @param array $userData
*/
private static function loginUserByArray($userData)
{
static::$loggedInUser = new User($userData);
}
/**
* Checks session for logged in user, validates the login and finally logs him in.
*/
private static function loginUserViaSession()
{
global $_SESSION, $db;
if(isset($_SESSION[static::SESSION_IDENTIFIER])
&& !empty($_SESSION[static::SESSION_IDENTIFIER])
){
$userId = $_SESSION[static::SESSION_IDENTIFIER];
// check if user still exists in database
$sql = "SELECT * FROM `".DBT_USERS."` WHERE `".DBC_USERS_ID."` = '$userId' LIMIT 1;";
if(!$userExists = $db->query($sql)){
dbError($db->error);
}
// User exists,
if($userExists->num_rows === 1){
$userData = $userExists->fetch_assoc();
static::loginUserByArray($userData);
}
}
}
/**
* Login user with provided credentials and save login in session
*
* @param string $email
* @param string $password
*
* @return bool
*/
public static function login($email, $password)
{
global $db;
$email = $db->escape_string(strtolower($email));
$password = $db->escape_string($password);
$emailInParts = explode("@", $email);
if(count($emailInParts) !== 2) {
return false;
}
$username = $emailInParts[0];
$domain = $emailInParts[1];
// Check for user in database
$sql = "SELECT * FROM `".DBT_USERS."` WHERE `".DBC_USERS_USERNAME."` = '$username' AND `".DBC_USERS_DOMAIN."` = '$domain' LIMIT 1;";
if(!$result = $db->query($sql)){
dbError($db->error);
}
// Check if user exists
if($result->num_rows === 1){
$userData = $result->fetch_assoc();
if(static::checkPasswordByHash($password, $userData[DBC_USERS_PASSWORD])){
static::loginUserByArray($userData);
$_SESSION[static::SESSION_IDENTIFIER] = $userData[DBC_USERS_ID];
return true;
}
}
return false;
}
/**
* Check if current user has a certain role
*
* @param string $requiredRole
*
* @return bool
*/
public static function hasPermission($requiredRole)
{
if(static::isLoggedIn()) {
$user = static::getUser();
return $user->getRole() === $requiredRole || $user->getRole() === User::ROLE_ADMIN;
}
return false;
}
/**
* Checks the new password entered by user on certain criteria, and throws an Exception if its invalid.
*
* @param string $password
* @param string $passwordRepeated
*
* @throws Exception Codes explained below
* 2: One password field is empty
* 3: Passwords are not equal
* 4: Passwort is too snort
*/
public static function validateNewPassword($password, $passwordRepeated)
{
// Check if one passwort input is empty
if(empty($password)){
throw new Exception("First password field was'nt filled out.", 2);
}
elseif(empty($passwordRepeated)){
throw new Exception("Repeat password field was'nt filled out.", 2);
}
else {
// Check if password are equal
if($password !== $passwordRepeated){
throw new Exception("The repeated password must be equal to the first one.", 3);
}
else {
// Check if password length is okay
if(strlen($password) < MIN_PASS_LENGTH){
throw new Exception("Passwords must be at least ".MIN_PASS_LENGTH." characters long.", 4);
}
}
}
}
/**
* @param string $password
* @param string $hash
*
* @return bool
*/
public static function checkPasswordByHash($password, $hash)
{
return crypt($password, $hash) === $hash;
}
/**
* @return string
*/
private static function getPasswordSchemaPrefix()
{
switch(PASS_HASH_SCHEMA){
case "SHA-256":
return '$5$rounds=5000$';
case "BLOWFISH":
return '$2a$09$';
case "SHA-512":
default:
return '$6$rounds=5000$';
}
}
/**
* @param string $password
*
* @return string
*/
private static function generatePasswordHash($password)
{
$salt = base64_encode(rand(1, 1000000) + microtime());
$schemaPrefix = static::getPasswordSchemaPrefix();
$hash = crypt($password, $schemaPrefix.$salt.'$');
return $hash;
}
/**
* @param string $userId
* @param $password
*/
public static function changeUserPassword($userId, $password)
{
global $db;
$passwordHash = static::generatePasswordHash($password);
$userId = $db->escape_string($userId);
$passwordHash = $db->escape_string($passwordHash);
if(!$db->query("UPDATE `".DBT_USERS."` SET `".DBC_USERS_PASSWORD."` = '$passwordHash' WHERE `".DBC_USERS_ID."` = '$userId';")){
dbError($db->error);
}
}
}

View file

@ -1,129 +0,0 @@
<?php
class USER {
/*
* Class attributes
*/
private $uid;
private $email;
private $role;
private $loggedin = false;
/*
* Constructor
*
* Fills the user object up with anonymous data
*/
function __construct(){
global $admins;
// Start session
session_start();
session_regenerate_id();
if(isset($_SESSION['email']) && in_array($_SESSION['email'], $admins)){
$this->role = "admin";
}
else{
$this->role = "user";
}
if(isset($_SESSION['uid']) && $_SESSION['uid'] != ""){
// revive session ...
$this->uid = $_SESSION['uid'];
$this->loggedin = true;
}
}
/*
* Getter functions
*/
function getUID(){
return $this->uid;
}
function getRole(){
return $this->role;
}
function isLoggedIn(){
return $this->loggedin;
}
/*
* Login function. Checks login data and writes information to SESSION
*
* Returns:
* true: Login was successful
* false: Login was not successful
*/
function login($email, $password){
global $db;
// Prepare e-mail address
$email = $db->escape_string($email);
$email = strtolower($email);
$password = $db->escape_string($password);
$email_part = explode("@", $email);
$username = $email_part[0];
$domain = $email_part[1];
// Check e-mail address
$sql = "SELECT `".DBC_USERS_ID."`, `".DBC_USERS_PASSWORD."` FROM `".DBT_USERS."` WHERE `".DBC_USERS_USERNAME."` = '$username' AND `".DBC_USERS_DOMAIN."` = '$domain' LIMIT 1;";
if(!$result = $db->query($sql)){
dbError($db->error);
}
if($result->num_rows === 1){
$userdata = $result->fetch_array(MYSQLI_ASSOC);
$uid = $userdata[DBC_USERS_ID];
$password_hash = $userdata[DBC_USERS_PASSWORD];
// Check password
if (crypt($password, $password_hash) === $password_hash) {
// Password is valid, start a logged-in user session
$this->loggedin = true;
$_SESSION['uid'] = $uid;
$_SESSION['email'] = $email;
return true;
}
else {
// Password is invalid
return false;
}
}
else{
// User could not be found
return false;
}
}
/*
* Changes user password.
* Returns:
* true: Change success
* false: Error
*/
function change_password($newpass, $newpass_rep){
$pass_ok = check_new_pass($newpass, $newpass_rep);
if($pass_ok === true){
$pass_hash = gen_pass_hash($newpass);
write_pass_hash_to_db($pass_hash, $this->uid);
return true;
}
else{
return false;
}
}
}
?>

View file

@ -1,4 +1,7 @@
<?php
// Start session
session_start();
session_regenerate_id();
// Include config
if(file_exists('config/config.inc.php')){
@ -19,17 +22,18 @@ function dbError($errorMessage){
// Establish database connection
$db = new mysqli(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE);
if($db->connect_errno > 0){
die('Unable to connect to database [' . $db->connect_error . ']');
}
/* Import classes */
require_once 'include/php/classes/user.class.php';
/* Import models */
require_once 'include/php/models/User.php';
$user = new USER();
/* Import classes */
require_once 'include/php/classes/Auth.php';
/* Initialize Authentication (Login User if in session) */
Auth::init();
require_once 'include/php/global.inc.php';
require_once 'include/php/checkpermissions.inc.php';
?>

View file

@ -33,88 +33,6 @@ function output_messages()
}
/*
* Function checks password input for new password
* Return codes:
* true: password is okay
* 2: One password field is empty
* 3: Passwords are not equal
* 4: Passwort is too snort
*/
function check_new_pass($pass1, $pass2)
{
global $PASS_ERR;
global $PASS_ERR_MSG;
// Check if one passwort input is empty
if($pass1 !== "" && $pass2 !== ""){
// Check if password are equal
if($pass1 === $pass2){
// Check if password length is okay
if(strlen($pass1) >= MIN_PASS_LENGTH){
// Password is okay.
return true;
}
else{
// Password is not long enough
$PASS_ERR = 4;
$PASS_ERR_MSG = "Password is not long enough. Please enter a password which has ".MIN_PASS_LENGTH." characters or more.";
return $PASS_ERR;
}
}
else{
// Passwords are not equal
$PASS_ERR = 3;
$PASS_ERR_MSG = "Passwords are not equal.";
return $PASS_ERR;
}
}
else{
// One password is empty.
$PASS_ERR = 2;
$PASS_ERR_MSG = "Not all password fields were filled out";
return $PASS_ERR;
}
}
function get_hash()
{
switch(PASS_HASH_SCHEMA){
case "SHA-512":
return '$6$rounds=5000$';
break;
case "SHA-256":
return '$5$rounds=5000$';
break;
case "BLOWFISH":
return '$2a$09$';
break;
}
}
function gen_pass_hash($pass)
{
$salt = base64_encode(rand(1, 1000000) + microtime());
$hash_schema = get_hash();
$pass_hash = crypt($pass, $hash_schema.$salt.'$');
return $pass_hash;
}
function write_pass_hash_to_db($pass_hash, $uid)
{
global $db;
$uid = $db->escape_string($uid);
$pass_hash = $db->escape_string($pass_hash);
$db->query("UPDATE `".DBT_USERS."` SET `".DBC_USERS_PASSWORD."` = '$pass_hash' WHERE `".DBC_USERS_ID."` = '$uid';");
}
/*
* Add message to logfile
*/
@ -232,4 +150,3 @@ function formatEmails($input, $glue)
}
?>

View file

@ -118,4 +118,20 @@ class User
return static::ROLE_USER;
}
/**
* Change this users password, throws Exception if password is invalid.
*
* @param string $password
* @param string $passwordRepeated
*
* @throws Exception
*/
public function changePassword($password, $passwordRepeated)
{
Auth::validateNewPassword($password, $passwordRepeated);
Auth::changeUserPassword($this->id, $password);
}
}

View file

@ -54,26 +54,24 @@ if(isset($_POST['savemode'])){
}
// Is there a changed password?
if($_POST['password'] !== ""){
$pass_ok = check_new_pass($_POST['password'], $_POST['password_repeat']);
if($pass_ok === true){
if(empty($_POST['password']) && empty($_POST['password_repeat'])){
// Edit user successfull, redirect to overview
redirect("admin/listusers/?edited=1");
}
else {
try{
Auth::validateNewPassword($_POST['password'], $_POST['password_repeat']);
// Password is okay and can be set
$pass_hash = gen_pass_hash($_POST['password']);
write_pass_hash_to_db($pass_hash, $id);
Auth::changeUserPassword($id, $_POST['password']);
// Edit user password successfull, redirect to overview
redirect("admin/listusers/?edited=1");
}
else{
// Password is not okay
// $editsuccessful = 2;
add_message("fail", $PASS_ERR_MSG);
catch(Exception $passwordInvalidException){
add_message("fail", $passwordInvalidException->getMessage());
}
}
else{
// Edit user successfull, redirect to overview
redirect("admin/listusers/?edited=1");
}
}
else if($savemode === "create"){
@ -87,38 +85,36 @@ if(isset($_POST['savemode'])){
$mailbox_limit = 0;
}
$pass = $_POST['password'];
$pass_rep = $_POST['password_repeat'];
if(!empty($username) && !empty($domain) && !empty($mailbox_limit)){
if(!empty($username) && !empty($domain) && !empty($mailbox_limit) && !empty($_POST['password']) && !empty($_POST['password_repeat'])){
// Check if user already exists
$user_exists = $db->query("SELECT `".DBC_USERS_USERNAME."`, `".DBC_USERS_DOMAIN."` FROM `".DBT_USERS."` WHERE `".DBC_USERS_USERNAME."` = '$username' AND `".DBC_USERS_DOMAIN."` = '$domain';");
if($user_exists->num_rows == 0){
// All fields filled with content
// Check passwords
$pass_ok = check_new_pass($pass, $pass_rep);
if($pass_ok === true){
// Password is okay ... continue
$pass_hash = gen_pass_hash($pass);
try{
// Check password then go on an insert user first
Auth::validateNewPassword($_POST['password'], $_POST['password_repeat']);
// Differ between version with mailbox_limit and version without
// Optional mailbox_limit support
if(defined('DBC_USERS_MAILBOXLIMIT')){
$sql = "INSERT INTO `".DBT_USERS."` (`".DBC_USERS_USERNAME."`, `".DBC_USERS_DOMAIN."`, `".DBC_USERS_PASSWORD."`, `".DBC_USERS_MAILBOXLIMIT."`) VALUES ('$username', '$domain', '$pass_hash', '$mailbox_limit')";
$sql = "INSERT INTO `".DBT_USERS."` (`".DBC_USERS_USERNAME."`, `".DBC_USERS_DOMAIN."`, `".DBC_USERS_MAILBOXLIMIT."`) VALUES ('$username', '$domain', '$mailbox_limit')";
}
else{
$sql = "INSERT INTO `".DBT_USERS."` (`".DBC_USERS_USERNAME."`, `".DBC_USERS_DOMAIN."`, `".DBC_USERS_PASSWORD."`) VALUES ('$username', '$domain', '$pass_hash')";
$sql = "INSERT INTO `".DBT_USERS."` (`".DBC_USERS_USERNAME."`, `".DBC_USERS_DOMAIN."`) VALUES ('$username', '$domain')";
}
if(!$result = $db->query($sql)){
dbError($db->error);
}
$userId = $db->insert_id;
// Password is validated and user was created, we can insert the password now
Auth::changeUserPassword($userId, $_POST['password']);
// Redirect user to user list
redirect("admin/listusers/?created=1");
}
else{
// Password not okay
add_message("fail", $PASS_ERR_MSG);
catch(Exception $passwordInvalidException){
add_message("fail", $passwordInvalidException->getMessage());
}
}
else{

View file

@ -1,24 +1,28 @@
<?php
if(isset($_POST['email']) && isset($_POST['password'])){
// Start login
$login_success = $user->login($_POST['email'], $_POST['password']);
if($login_success){
redirect("private");
}
// If login is not successful
else{
//Log error message
writeLog("WebMUM login failed for IP ".$_SERVER['REMOTE_ADDR']);
add_message("fail", "Sorry, couldn't log you in :(");
}
}
<?php
// If user is already logged in, redirect to start.
if($user->isLoggedIn()){
if(Auth::isLoggedIn()){
redirect("private");
}
if(isset($_POST['email']) && isset($_POST['password'])){
if(empty($_POST['email']) || empty($_POST['password'])){
add_message('fail', 'Please fill out both email and password fields.');
}
else {
// Start login
if(Auth::login($_POST['email'], $_POST['password'])){
redirect("private");
}
// If login is not successful
else{
//Log error message
writeLog("WebMUM login failed for IP ".$_SERVER['REMOTE_ADDR']);
add_message("fail", "Sorry, but we cannot log you in with this combination of email and password, there might be a typo.");
}
}
}
?>
<h1>Login</h1>
@ -29,7 +33,7 @@ if($user->isLoggedIn()){
<div class="input-group">
<label>Email address</label>
<div class="input">
<input type="text" name="email" placeholder="Your email address" autofocus required/><br>
<input type="text" name="email" placeholder="Your email address" value="<?php echo isset($_POST['email']) ? $_POST['email'] : ''; ?>" autofocus required/><br>
</div>
</div>

View file

@ -1,14 +1,13 @@
<?php
if(isset($_POST['password']) && isset($_POST['password_repeat'])){
// User tries to change password
$change_pass_success = $user->change_password($_POST['password'], $_POST['password_repeat']);
if($change_pass_success === true){
try {
Auth::getUser()->changePassword($_POST['password'], $_POST['password_repeat']);
add_message("success", "Password changed successfully!");
}
else if($change_pass_success === false){
add_message("fail", "Error while changing password! ".$PASS_ERR_MSG);
catch(Exception $passwordInvalidException){
add_message("fail", $passwordInvalidException->getMessage());
}
}

View file

@ -1,5 +1,5 @@
<?php
if($user->isLoggedIn() === true){
if(Auth::isLoggedIn()){
redirect("private/");
}
?>

View file

@ -20,17 +20,19 @@
<div id="header">
<div class="title"><a href="<?php echo url('/'); ?>">WebMUM - Web Mailserver User Manager</a></div>
<div class="header-menu">
<?php if(user_has_permission("admin")): ?>
<?php if(Auth::hasPermission(User::ROLE_ADMIN)): ?>
<div class="header-button">
<a href="<?php echo url('admin'); ?>">[Admin Dashboard]</a>
</div>
<?php endif; ?>
<?php if(Auth::hasPermission(User::ROLE_USER)): ?>
<div class="header-button">
<a href="<?php echo url('private'); ?>">[Personal Dashboard]</a>
</div>
<?php endif; ?>
<?php if($user->isLoggedIn()): ?>
<?php if(Auth::hasPermission('user')): ?>
<div class="header-button">
Logged in as <?php echo $_SESSION['email']; ?>
Logged in as <?php echo Auth::getUser()->getEmail(); ?>
<a href="<?php echo url('logout'); ?>">[Logout]</a>
</div>
<?php endif; ?>

View file

@ -31,7 +31,7 @@ function load_page($p){
if(preg_match("/^\/private(.*)$/", $p) == 1){
// Page is user page
if(user_has_permission("user")){
if(Auth::hasPermission('user')){
switch($p){
case "/private/":
return "include/php/pages/private/start.php";
@ -45,10 +45,10 @@ function load_page($p){
}
else{ return "include/php/pages/not-allowed.php"; }
}
else if(preg_match("/^\/admin(.*)$/", $p) == 1){
// Page is admin page
if(user_has_permission("admin")){
if(Auth::hasPermission('admin')){
switch($p){
case "/admin/":
return "include/php/pages/admin/start.php";
@ -86,7 +86,7 @@ function load_page($p){
}
else{ return "include/php/pages/not-allowed.php"; }
}
else{
// Page is public accessible
switch($p){