Ver código fonte

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

ohartl 9 anos atrás
pai
commit
d0b38d17b1

+ 0 - 38
include/php/checkpermissions.inc.php

@@ -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;
-	}
-}
-
-?>

+ 244 - 0
include/php/classes/Auth.php

@@ -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);
+		}
+	}
+}

+ 0 - 129
include/php/classes/user.class.php

@@ -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;
-		}
-	}
-}
-?>

+ 9 - 5
include/php/default.inc.php

@@ -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 models */
+require_once 'include/php/models/User.php';
+
 /* Import classes */
-require_once 'include/php/classes/user.class.php';
+require_once 'include/php/classes/Auth.php';
 
-$user = new USER();
+/* Initialize Authentication (Login User if in session) */
+Auth::init();
 
 require_once 'include/php/global.inc.php';
-require_once 'include/php/checkpermissions.inc.php';
 
-?>

+ 0 - 83
include/php/global.inc.php

@@ -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)
 }
 
 
-?>

+ 16 - 0
include/php/models/User.php

@@ -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);
+	}
 }

+ 26 - 30
include/php/pages/admin/edituser.php

@@ -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);
-
-					// Differ between version with mailbox_limit and version without
+				try{
+					// Check password then go on an insert user first
+					Auth::validateNewPassword($_POST['password'], $_POST['password_repeat']);
+
+					// 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{

+ 20 - 16
include/php/pages/login.php

@@ -1,24 +1,28 @@
-<?php 
+<?php
+
+// If user is already logged in, redirect to start.
+if(Auth::isLoggedIn()){
+	redirect("private");
+}
 
 if(isset($_POST['email']) && isset($_POST['password'])){
-	// Start login
-	$login_success = $user->login($_POST['email'], $_POST['password']);
-	if($login_success){
-		redirect("private");
+	if(empty($_POST['email']) || empty($_POST['password'])){
+		add_message('fail', 'Please fill out both email and password fields.');
 	}
-	// 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 :(");
+	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.");
+		}
 	}
 }
 
-// If user is already logged in, redirect to start.
-if($user->isLoggedIn()){
-	redirect("private");
-}
-
 ?>
 
 <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>
 

+ 5 - 6
include/php/pages/private/changepass.php

@@ -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());
 	}
 }
 

+ 1 - 1
include/php/pages/start.php

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

+ 5 - 3
include/php/template/header.php

@@ -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; ?>

+ 4 - 4
index.php

@@ -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){