Improve error handling, add password changing, add taskfloor tasks module

V2_Rewrite
Skylar Ittner 7 years ago
parent 1665b3f3e7
commit ee4899de80

3
.gitignore vendored

@ -1,4 +1,5 @@
/settings.php
/vendor
/database.mwb.bak
/nbproject/private
/nbproject/private
*.sync-conflict*

@ -19,7 +19,6 @@ if ($VARS['action'] == 'signout' && $_SESSION['loggedin'] != true) {
dieifnotloggedin();
require_once __DIR__ . "/lib/login.php";
require_once __DIR__ . "/lib/worst_passwords.php";
function returnToSender($msg, $arg = "") {
global $VARS;
@ -38,57 +37,18 @@ switch ($VARS['action']) {
header('Location: index.php');
die("Logged out.");
case "chpasswd":
if ($VARS['oldpass'] == $VARS['newpass']) {
returnToSender("passwords_same");
$error = [];
$result = change_password($VARS['oldpass'], $VARS['newpass'], $VARS['conpass'], $error);
if ($result === TRUE) {
returnToSender("password_updated");
}
if (authenticate_user($_SESSION['username'], $VARS['oldpass'])) {
if ($VARS['newpass'] == $VARS['conpass']) {
$passrank = checkWorst500List($VARS['newpass']);
if ($passrank !== FALSE) {
returnToSender("password_500", $passrank);
}
if (strlen($VARS['newpass']) < MIN_PASSWORD_LENGTH) {
returnToSender("weak_password");
}
$acctloc = account_location($_SESSION['username'], $_SESSION['password']);
if ($acctloc == "LOCAL") {
$database->update('accounts', ['password' => encryptPassword($VARS['newpass'])], ['uid' => $_SESSION['uid']]);
$_SESSION['password'] = $VARS['newpass'];
insertAuthLog(3, $_SESSION['uid']);
returnToSender("password_updated");
} else if ($acctloc == "LDAP") {
/* $ldap_config_domain
->setUsername($_SESSION['username'])
->setPassword($VARS['oldpass']); */
try {
//echo "0";
$ldapManager = new LdapManager($ldap_config);
//echo "1";
$repository = $ldapManager->getRepository(LdapObjectType::USER);
//echo "2";
$user = $repository->findOneByUsername($_SESSION['username']);
//echo "3";
$user->setPassword($VARS['newpass']);
//echo "4";
$ldapManager->persist($user);
//echo "5";
insertAuthLog(3, $_SESSION['uid']);
$_SESSION['password'] = $VARS['newpass'];
returnToSender("password_updated");
} catch (\Exception $e) {
echo $e->getMessage();
returnToSender("ldap_error", $e->getMessage());
}
} else {
returnToSender("account_state_error");
}
} else {
returnToSender("new_password_mismatch");
}
} else {
returnToSender("old_password_mismatch");
switch (count($error)) {
case 1:
returnToSender($error[0]);
case 2:
returnToSender($error[0], $error[1]);
default:
returnToSender("generic_op_error");
}
break;
case "add2fa":

@ -0,0 +1,41 @@
<?php
dieifnotloggedin();
addMultiLangStrings(["en_us" => [
"tasks" => "Tasks",
"no tasks found" => "No tasks found."
]
]);
$APPS["taskfloor_tasks"]["i18n"] = TRUE;
$APPS["taskfloor_tasks"]["title"] = "tasks";
$APPS["taskfloor_tasks"]["icon"] = "tasks";
try {
$client = new GuzzleHttp\Client();
$response = $client->request('POST', TASKFLOOR_API, ['form_params' => [
'action' => "gettasks",
'username' => $_SESSION['username'],
'password' => $_SESSION['password'],
'max' => 5
]]);
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
if (count($resp['tasks']) > 0) {
$content = '<div class="list-group">';
foreach ($resp['tasks'] as $task) {
$content .= '<div class="list-group-item">';
$content .= '<i class="fa fa-fw fa-' . $task['icon'] . '"></i> ' . $task['title'] . '';
$content .= '</div>';
}
$content .= "</div>";
} else {
$content = "<div class=\"alert alert-success\">" . lang("no tasks found", false) . "</div>";
}
}
} catch (Exception $e) {
$content = "<div class=\"alert alert-danger\">" . lang("error loading widget", false) . " " . $e->getMessage() . "</div>";
}
$content .= '<a href="' . TASKFLOOR_HOME . '" class="btn btn-primary btn-block">' . lang("open app", false) . ' &nbsp;<i class="fa fa-external-link-square"></i></a>';
$APPS["taskfloor_tasks"]["content"] = $content;
?>

@ -4,6 +4,9 @@ require_once __DIR__ . "/required.php";
if ($_SESSION['loggedin'] != true) {
header('Location: index.php');
die("Session expired. Log in again to continue.");
} else if (is_empty($_SESSION['password'])) {
header('Location: index.php');
die("You need to log in again.");
}
require_once __DIR__ . "/pages.php";
@ -145,7 +148,7 @@ END;
foreach (APPS[$pageid] as $app) {
if (file_exists(__DIR__ . "/apps/" . $app . ".php")) {
include_once __DIR__ . "/apps/" . $app . ".php";
$apptitle = $APPS[$app]['title'];
$apptitle = ($APPS[$app]['i18n'] === TRUE ? lang($APPS[$app]['title'], false) : $APPS[$app]['title']);
$appicon = (is_empty($APPS[$app]['icon']) ? "" : "fa fa-fw fa-" . $APPS[$app]['icon']);
$apptype = (is_empty($APPS[$app]['type']) ? "default" : $APPS[$app]['type']);
$appcontent = $APPS[$app]['content'];

@ -3,14 +3,19 @@ require_once __DIR__ . "/required.php";
require_once __DIR__ . "/lib/login.php";
// if we're logged in, we don't need to be here.
if ($_SESSION['loggedin']) {
// If we're logged in, we don't need to be here.
if ($_SESSION['loggedin'] && !is_empty($_SESSION['password'])) {
header('Location: home.php');
// This branch will likely run if the user signed in from a different app.
} else if ($_SESSION['loggedin'] && is_empty($_SESSION['password'])) {
$alert = lang("sign in again", false);
$alerttype = "info";
}
/* Authenticate user */
$userpass_ok = false;
$username_ok = false;
$multiauth = false;
$change_password = false;
if ($VARS['progress'] == "1") {
if (!RECAPTCHA_ENABLED || (RECAPTCHA_ENABLED && verifyReCaptcha($VARS['g-recaptcha-response']))) {
$autherror = "";
@ -25,13 +30,16 @@ if ($VARS['progress'] == "1") {
break;
case "CHANGE_PASSWORD":
$alert = lang("password expired", false);
$alerttype = "info";
$_SESSION['username'] = strtolower($VARS['username']);
$change_password = true;
break;
case "NORMAL":
$userpass_ok = true;
$username_ok = true;
break;
case "ALERT_ON_ACCESS":
sendLoginAlertEmail($VARS['username']);
$userpass_ok = true;
$username_ok = true;
break;
default:
if (!is_empty($error)) {
@ -41,7 +49,7 @@ if ($VARS['progress'] == "1") {
$alert = lang("login error", false);
break;
}
if ($userpass_ok) {
if ($username_ok) {
if (authenticate_user($VARS['username'], $VARS['password'], $autherror)) {
$_SESSION['passok'] = true; // stop logins using only username and authcode
if (userHasTOTP($VARS['username'])) {
@ -84,6 +92,32 @@ if ($VARS['progress'] == "1") {
$alert = lang("2fa incorrect", false);
insertAuthLog(6, null, "Username: " . $VARS['username']);
}
} else if ($VARS['progress'] == "chpasswd") {
if (!is_empty($_SESSION['username'])) {
$error = [];
$result = change_password($VARS['oldpass'], $VARS['newpass'], $VARS['conpass'], $error);
if ($result === TRUE) {
$alert = lang(MESSAGES["password_updated"]["string"], false);
$alerttype = MESSAGES["password_updated"]["type"];
}
switch (count($error)) {
case 1:
$alert = lang(MESSAGES[$error[0]]["string"], false);
$alerttype = MESSAGES[$error[0]]["type"];
break;
case 2:
$alert = lang2(MESSAGES[$error[0]]["string"], ["arg" => $error[1]], false);
$alerttype = MESSAGES[$error[0]]["type"];
break;
default:
$alert = lang(MESSAGES["generic_op_error"]["string"], false);
$alerttype = MESSAGES["generic_op_error"]["type"];
}
} else {
session_destroy();
header('Location: index.php');
die();
}
}
?>
<!DOCTYPE html>
@ -117,14 +151,33 @@ if ($VARS['progress'] == "1") {
<form action="" method="POST">
<?php
if (!is_empty($alert)) {
$alerttype = isset($alerttype) ? $alerttype : "danger";
?>
<div class="alert alert-danger">
<i class="fa fa-fw fa-exclamation-triangle"></i> <?php echo $alert; ?>
<div class="alert alert-<?php echo $alerttype ?>">
<?php
switch ($alerttype) {
case "danger":
$alerticon = "times";
break;
case "warning":
$alerticon = "exclamation-triangle";
break;
case "info":
$alerticon = "info-circle";
break;
case "success":
$alerticon = "check";
break;
default:
$alerticon = "square-o";
}
?>
<i class="fa fa-fw fa-<?php echo $alerticon ?>"></i> <?php echo $alert ?>
</div>
<?php
}
if ($multiauth != true) {
if (!$multiauth && !$change_password) {
?>
<input type="text" class="form-control" name="username" placeholder="<?php lang("username"); ?>" required="required" autofocus /><br />
<input type="password" class="form-control" name="password" placeholder="<?php lang("password"); ?>" required="required" /><br />
@ -143,6 +196,13 @@ if ($VARS['progress'] == "1") {
<input type="hidden" name="progress" value="2" />
<input type="hidden" name="username" value="<?php echo $VARS['username']; ?>" />
<?php
} else if ($change_password) {
?>
<input type="password" class="form-control" name="oldpass" placeholder="Current password" required="required" autocomplete="new-password" autofocus /><br />
<input type="password" class="form-control" name="newpass" placeholder="New password" required="required" autocomplete="off" /><br />
<input type="password" class="form-control" name="conpass" placeholder="New password (again)" required="required" autocomplete="off" /><br />
<input type="hidden" name="progress" value="chpasswd" />
<?php
}
?>
<button type="submit" class="btn btn-primary">

@ -1,6 +1,6 @@
<?php
define("STRINGS", [
$STRINGS = [
"sign in" => "Sign In",
"username" => "Username",
"password" => "Password",
@ -44,5 +44,10 @@ define("STRINGS", [
"captcha error" => "There was a problem with the CAPTCHA (robot test). Try again.",
"home" => "Home",
"ldap error" => "LDAP error: {error}",
"old and new passwords match" => "Your current and new passwords are the same."
]);
"old and new passwords match" => "Your current and new passwords are the same.",
"generic op error" => "An unknown error occurred. Try again later.",
"password complexity insufficent" => "The new password does not meet the minumum requirements defined by your system administrator.",
"error loading widget" => "There was a problem loading this app.",
"open app" => "Open App",
"sign in again" => "Please sign in again to continue."
];

@ -44,5 +44,13 @@ define("MESSAGES", [
"passwords_same" => [
"string" => "old and new passwords match",
"type" => "danger"
],
"password_complexity" => [
"string" => "password complexity insufficent",
"type" => "danger"
],
"generic_op_error" => [
"string" => "generic op error",
"type" => "danger"
]
]);

@ -7,7 +7,6 @@ use Base32\Base32;
use OTPHP\TOTP;
use LdapTools\LdapManager;
use LdapTools\Object\LdapObjectType;
use LdapTools\Connection\ADResponseCodes;
$ldap = new LdapManager($ldap_config);
@ -42,6 +41,83 @@ function adduser($username, $password, $realname, $email = null, $phone1 = "", $
return $database->id();
}
/**
* Change the password for the current user.
* @global $database $database
* @global LdapManager $ldap
* @param string $old The current password
* @param string $new The new password
* @param string $new2 New password again
* @param [string] $error If the function returns false, this will have an array
* with a message ID from `lang/messages.php` and (depending on the message) an
* extra string for that message.
* @return boolean true if the password is changed, else false
*/
function change_password($old, $new, $new2, &$error) {
global $database, $ldap;
// make sure the new password isn't the same as the current one
if ($old == $new) {
$error = ["passwords_same"];
return false;
}
// Make sure the new passwords are the same
if ($new != $new2) {
$error = ["new_password_mismatch"];
return false;
}
// check the current password
$login_ok = authenticate_user($_SESSION['username'], $old, $errmsg, $errcode);
// Allow login if the error is due to expired password
if (!$login_ok && ($errcode == LdapTools\Connection\ADResponseCodes::ACCOUNT_PASSWORD_EXPIRED || $errcode == LdapTools\Connection\ADResponseCodes::ACCOUNT_PASSWORD_MUST_CHANGE)) {
$login_ok = true;
}
if ($login_ok) {
// Check the new password and make sure it's not stupid
require_once __DIR__ . "/worst_passwords.php";
$passrank = checkWorst500List($new);
if ($passrank !== FALSE) {
$error = ["password_500", $passrank];
return false;
}
if (strlen($new) < MIN_PASSWORD_LENGTH) {
$error = ["weak_password"];
return false;
}
// Figure out how to change the password, then do it
$acctloc = account_location($_SESSION['username']);
if ($acctloc == "LOCAL") {
$database->update('accounts', ['password' => encryptPassword($VARS['newpass'])], ['uid' => $_SESSION['uid']]);
$_SESSION['password'] = $VARS['newpass'];
insertAuthLog(3, $_SESSION['uid']);
return true;
} else if ($acctloc == "LDAP") {
try {
$repository = $ldap->getRepository(LdapObjectType::USER);
$user = $repository->findOneByUsername($_SESSION['username']);
$user->setPassword($VARS['newpass']);
$user->setpasswordMustChange(false);
$ldap->persist($user);
insertAuthLog(3, $_SESSION['uid']);
$_SESSION['password'] = $VARS['newpass'];
return true;
} catch (\Exception $e) {
// Stupid password complexity BS error
if (strpos($e->getMessage(), "DSID-031A11E5") !== FALSE) {
$error = ["password_complexity"];
return false;
}
$error = ["ldap_error", $e->getMessage()];
return false;
}
}
$error = ["account_state_error"];
return false;
}
$error = ["old_password_mismatch"];
return false;
}
/**
* Get where a user's account actually is.
* @param string $username
@ -79,7 +155,7 @@ function account_location($username) {
* @param string $password
* @return boolean True if OK, else false
*/
function authenticate_user($username, $password, &$errormsg) {
function authenticate_user($username, $password, &$errormsg, &$errorcode) {
global $database;
global $ldap;
$username = strtolower($username);
@ -87,28 +163,28 @@ function authenticate_user($username, $password, &$errormsg) {
return false;
}
$loc = account_location($username, $password);
if ($loc == "NONE") {
return false;
} else if ($loc == "LOCAL") {
$hash = $database->select('accounts', ['password'], ['username' => $username, "LIMIT" => 1])[0]['password'];
return (comparePassword($password, $hash));
} else if ($loc == "LDAP") {
return authenticate_user_ldap($username, $password, $errormsg) === TRUE;
} else if ($loc == "LDAP_ONLY") {
try {
if (authenticate_user_ldap($username, $password, $errormsg) === TRUE) {
$user = $ldap->getRepository('user')->findOneByUsername($username);
//var_dump($user);
adduser($user->getUsername(), null, $user->getName(), ($user->hasEmailAddress() ? $user->getEmailAddress() : null), "", "", 2);
return true;
switch ($loc) {
case "LOCAL":
$hash = $database->select('accounts', ['password'], ['username' => $username, "LIMIT" => 1])[0]['password'];
return (comparePassword($password, $hash));
case "LDAP":
return authenticate_user_ldap($username, $password, $errormsg, $errorcode) === TRUE;
case "LDAP_ONLY":
// Authenticate with LDAP and create database account
try {
if (authenticate_user_ldap($username, $password, $errormsg, $errorcode) === TRUE) {
$user = $ldap->getRepository('user')->findOneByUsername($username);
adduser($user->getUsername(), null, $user->getName(), ($user->hasEmailAddress() ? $user->getEmailAddress() : null), "", "", 2);
return true;
}
return false;
} catch (Exception $e) {
$errormsg = $e->getMessage();
return false;
}
default:
return false;
} catch (Exception $e) {
$errormsg = $e->getMessage();
return false;
}
} else {
return false;
}
}
@ -176,7 +252,7 @@ function doLoginUser($username, $password) {
$_SESSION['uid'] = $userinfo['uid'];
$_SESSION['email'] = $userinfo['email'];
$_SESSION['realname'] = $userinfo['realname'];
$_SESSION['password'] = $password; // needed for things like EWS
$_SESSION['password'] = $password; // needed for accessing data in other apps
$_SESSION['loggedin'] = true;
}
@ -251,7 +327,7 @@ function verifyReCaptcha($response) {
* @param string $password
* @return mixed True if OK, else false or the error code from the server
*/
function authenticate_user_ldap($username, $password, &$errormsg) {
function authenticate_user_ldap($username, $password, &$errormsg, &$errorcode) {
global $ldap;
if (is_empty($username) || is_empty($password)) {
return false;
@ -262,9 +338,11 @@ function authenticate_user_ldap($username, $password, &$errormsg) {
$code = 0;
if ($ldap->authenticate($username, $password, $msg, $code) === TRUE) {
$errormsg = $msg;
$errorcode = $code;
return true;
} else {
$errormsg = $msg;
$errorcode = $code;
return $msg;
}
} catch (Exception $e) {

@ -16,6 +16,7 @@ define("PAGES", [
// Which apps to load on a given page
define("APPS", [
"home" => [
"taskfloor_tasks",
"sample_app"
],
"security" => [

@ -11,7 +11,7 @@ header('Content-Type: text/html; charset=utf-8');
header('X-Content-Type-Options: nosniff');
header('X-XSS-Protection: 1; mode=block');
header('X-Powered-By: Late-night coding frenzies (plz send caffeine, thx)');
header('X-Hacker: Why are you looking at HTTP headers? Get a life! </sarcasm>');
$session_length = 60 * 60; // 1 hour
session_set_cookie_params($session_length, "/", null, false, true);
@ -87,8 +87,8 @@ function is_empty($str) {
* @param boolean $echo whether to echo the result or return it (default echo)
*/
function lang($key, $echo = true) {
if (array_key_exists($key, STRINGS)) {
$str = STRINGS[$key];
if (array_key_exists($key, $GLOBALS['STRINGS'])) {
$str = $GLOBALS['STRINGS'][$key];
} else {
$str = $key;
}
@ -109,8 +109,8 @@ function lang($key, $echo = true) {
* @param boolean $echo whether to echo the result or return it (default echo)
*/
function lang2($key, $replace, $echo = true) {
if (array_key_exists($key, STRINGS)) {
$str = STRINGS[$key];
if (array_key_exists($key, $GLOBALS['STRINGS'])) {
$str = $GLOBALS['STRINGS'][$key];
} else {
$str = $key;
}
@ -126,6 +126,25 @@ function lang2($key, $replace, $echo = true) {
}
}
/**
* Add strings to the i18n global array.
* @param array $strings ['key' => 'value']
*/
function addLangStrings($strings) {
$GLOBALS['STRINGS'] = array_merge($GLOBALS['STRINGS'], $strings);
}
/**
* Add strings to the i18n global array. Accepts an array of language code
* keys, with the values a key-value array of strings.
* @param array $strings ['en_us' => ['key' => 'value']]
*/
function addMultiLangStrings($strings) {
if (!is_empty($strings[LANGUAGE])) {
$GLOBALS['STRINGS'] = array_merge($GLOBALS['STRINGS'], $strings[LANGUAGE]);
}
}
/**
* Checks if an email address is valid.
* @param string $email Email to check

@ -28,7 +28,7 @@ $ldap_config_domain = (new DomainConfiguration('example'))
->setDomainName("example.com")
->setServers(['192.168.25.131'])
->setLazyBind(TRUE)
->setUsername("readonly-bind")
->setUsername("admin-account")
->setPassword("password")
->setUseTls(TRUE);
$ldap_config->addDomain($ldap_config_domain);
@ -36,7 +36,7 @@ $ldap_config->addDomain($ldap_config_domain);
* End LDAP Configuration
*/
define("SITE_TITLE", "Netsyms Business Apps :: Single Sign On");
define("SITE_TITLE", "Portal");
// Used to identify the system in OTP and other places
define("SYSTEM_NAME", "Netsyms SSO Demo");
@ -53,6 +53,10 @@ define("RECAPTCHA_ENABLED", FALSE);
define('RECAPTCHA_SITE_KEY', '');
define('RECAPTCHA_SECRET_KEY', '');
// API URL and index URL for TaskFloor
define('TASKFLOOR_API', '');
define('TASKFLOOR_HOME', '');
// See lang folder for language options
define('LANGUAGE', "en_us");

@ -1,6 +1,8 @@
.banner-image {
max-height: 100px;
margin: 2em auto;
border: 1px solid grey;
border-radius: 12px;
}
.navbar-brand {

@ -1,4 +1,4 @@
@import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500,700");/*!
/*@import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500,700");/*!
* bootswatch v3.3.7
* Homepage: http://bootswatch.com
* Copyright 2012-2017 Thomas Park

Loading…
Cancel
Save