@ -1,147 +1,98 @@
<?php
<?php
/**
/**
* Authentication and account functions
* Authentication and account functions. Connects to a Portal instance.
*/
*/
use Base32\Base32;
use OTPHP\TOTP;
use LdapTools\LdapManager;
use LdapTools\Connection\ADResponseCodes;
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Account handling //
// Account handling //
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/**
/**
* Add a user to the system. /!\ Assumes input is OK /!\
* Checks the given credentials against the API.
* @param string $username Username, saved in lowercase.
* @param string $password Password, will be hashed before saving.
* @param string $realname User's real legal name
* @param string $email User's email address.
* @return int The new user's ID number in the database.
*/
function adduser($username, $password, $realname, $email = null, $phone1 = "", $phone2 = "") {
global $database;
$database->debug()->insert('accounts', [
'username' => strtolower($username),
'password' => (is_null($password) ? null : encryptPassword($password)),
'realname' => $realname,
'email' => $email,
'phone1' => $phone1,
'phone2' => $phone2,
'acctstatus' => 1
]);
return $database->id();
}
/**
* Get where a user's account actually is.
* @param string $username
* @return string "LDAP", "LOCAL", "LDAP_ONLY", or "NONE".
*/
function account_location($username, $password) {
global $database;
$user_exists = user_exists($username);
if (!$user_exists & & !LDAP_ENABLED) {
return false;
}
if ($user_exists) {
$userinfo = $database->select('accounts', ['password'], ['username' => $username])[0];
// if password empty, it's an LDAP user
if (is_empty($userinfo['password']) & & LDAP_ENABLED) {
return "LDAP";
} else if (is_empty($userinfo['password']) & & !LDAP_ENABLED) {
return "NONE";
} else {
return "LOCAL";
}
} else {
if (user_exists_ldap($username, $password)) {
return "LDAP_ONLY";
} else {
return "NONE";
}
}
}
/**
* Checks the given credentials against the database.
* @param string $username
* @param string $username
* @param string $password
* @param string $password
* @return boolean True if OK, else false
* @return boolean True if OK, else false
*/
*/
function authenticate_user($username, $password) {
function authenticate_user($username, $password) {
global $database;
$client = new GuzzleHttp\Client();
global $ldap_config;
if (is_empty($username) || is_empty($password)) {
$response = $client
return false;
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "auth",
'username' => $username,
'password' => $password
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
}
$loc = account_location($username, $password);
if ($loc == "NONE") {
$resp = json_decode($response->getBody(), TRUE);
return false;
if ($resp['status'] == "OK") {
} 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);
} else if ($loc == "LDAP_ONLY") {
if (authenticate_user_ldap($username, $password) === TRUE) {
try {
$user = (new LdapManager($ldap_config))->getRepository('user')->findOneByUsername($username);
var_dump($user);
adduser($user->getUsername(), null, $user->getName(), ($user->hasEmailAddress() ? $user->getEmailAddress() : null));
return true;
return true;
} catch (Exception $e) {
sendError("LDAP error: " . $e->getMessage());
}
} else {
return false;
}
} else {
} else {
return false;
return false;
}
}
}
}
/**
/**
* Check if a username exists in the local database .
* Check if a username exists.
* @param String $username
* @param String $username
*/
*/
function user_exists($username) {
function user_exists($username) {
global $database;
$client = new GuzzleHttp\Client();
return $database->has('accounts', ['username' => $username, "LIMIT" => QUERY_LIMIT]);
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "userexists",
'username' => $username
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK" & & $resp['exists'] === true) {
return true;
} else {
return false;
}
}
}
/**
/**
* Get the account status: NORMAL, TERMINATED, LOCKED_OR_DISABLED,
* Get the account status: NORMAL, TERMINATED, LOCKED_OR_DISABLED,
* CHANGE_PASSWORD, or ALERT_ON_ACCESS
* CHANGE_PASSWORD, or ALERT_ON_ACCESS
* @global $database $database
* @param string $username
* @param string $username
* @return string
* @return string
*/
*/
function get_account_status($username) {
function get_account_status($username) {
global $database;
$client = new GuzzleHttp\Client();
$loc = account_location($username);
if ($loc == "LOCAL") {
$response = $client
$statuscode = $database->select('accounts', [
->request('POST', PORTAL_API, [
'[>]acctstatus' => [
'form_params' => [
'acctstatus' => 'statusid'
'key' => PORTAL_KEY,
]
'action' => "acctstatus",
], [
'username' => $username
'accounts.acctstatus',
'acctstatus.statuscode'
], [
'username' => $username,
"LIMIT" => 1
]
]
)[0]['statuscode'];
]);
return $statuscode;
} else if ($loc == "LDAP") {
if ($response->getStatusCode() > 299) {
// TODO: Read actual account status from AD servers
sendError("Login server error: " . $response->getBody());
return "NORMAL";
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return $resp['account'];
} else {
} else {
// account isn't setup properly
return false;
return "LOCKED_OR_DISABLED";
}
}
}
}
@ -150,94 +101,65 @@ function get_account_status($username) {
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/**
/**
* Setup $_SESSION values to log in a user
* Setup $_SESSION values with user data and set loggedin flag to true
* @param string $username
* @param string $username
*/
*/
function doLoginUser($username, $password) {
function doLoginUser($username) {
global $database;
$client = new GuzzleHttp\Client();
$userinfo = $database->select('accounts', ['email', 'uid', 'realname'], ['username' => $username])[0];
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "userinfo",
'username' => $username
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
var_dump($resp);
if ($resp['status'] == "OK") {
$userinfo = $resp['data'];
$_SESSION['username'] = $username;
$_SESSION['username'] = $username;
$_SESSION['uid'] = $userinfo['uid'];
$_SESSION['uid'] = $userinfo['uid'];
$_SESSION['email'] = $userinfo['email'];
$_SESSION['email'] = $userinfo['email'];
$_SESSION['realname'] = $userinfo['realname'];
$_SESSION['realname'] = $userinfo['name'];
$_SESSION['password'] = $password; // needed for things like EWS
$_SESSION['password'] = $password;
$_SESSION['loggedin'] = true;
$_SESSION['loggedin'] = true;
return true;
} else {
return false;
}
}
/**
* Send an alert email to the system admin
*
* Used when an account with the status ALERT_ON_ACCESS logs in
* @param String $username the account username
*/
function sendLoginAlertEmail($username) {
// TODO: add email code
}
}
////////////////////////////////////////////////////////////////////////////////
function simLogin($username, $password) {
// LDAP handling //
$client = new GuzzleHttp\Client();
////////////////////////////////////////////////////////////////////////////////
/**
$response = $client
* Checks the given credentials against the LDAP server.
->request('POST', PORTAL_API, [
* @param string $username
'form_params' => [
* @param string $password
'key' => PORTAL_KEY,
* @return mixed True if OK, else false or the error code from the server
'action' => "login",
*/
'username' => $username,
function authenticate_user_ldap($username, $password) {
'password' => $password
global $ldap_config;
]
if (is_empty($username) || is_empty($password)) {
]);
return false;
}
if ($response->getStatusCode() > 299) {
$ldapManager = new LdapManager($ldap_config);
sendError("Login server error: " . $response->getBody());
$msg = "";
$code = 0;
if ($ldapManager->authenticate($username, $password, $msg, $code)) {
return true;
} else {
return $code;
}
}
}
/**
$resp = json_decode($response->getBody(), TRUE);
* Check if a username exists on the LDAP server.
if ($resp['status'] == "OK") {
* @global type $ldap_config
* @param type $username
* @return boolean true if yes, else false
*/
function user_exists_ldap($username, $password) {
global $ldap_config;
$ldap = new LdapManager($ldap_config);
if (!$ldap->authenticate($username, $password, $message, $code)) {
switch ($code) {
case ADResponseCodes::ACCOUNT_INVALID:
return false;
case ADResponseCodes::ACCOUNT_CREDENTIALS_INVALID:
return true;
case ADResponseCodes::ACCOUNT_RESTRICTIONS:
return true;
case ADResponseCodes::ACCOUNT_RESTRICTIONS_TIME:
return true;
case ADResponseCodes::ACCOUNT_RESTRICTIONS_DEVICE:
return true;
case ADResponseCodes::ACCOUNT_PASSWORD_EXPIRED:
return true;
case ADResponseCodes::ACCOUNT_DISABLED:
return true;
case ADResponseCodes::ACCOUNT_CONTEXT_IDS:
return true;
case ADResponseCodes::ACCOUNT_EXPIRED:
return false;
case ADResponseCodes::ACCOUNT_PASSWORD_MUST_CHANGE:
return true;
return true;
case ADResponseCodes::ACCOUNT_LOCKED:
} else {
return true;
return $resp['msg'];
default:
return false;
}
}
}
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// 2-factor authentication //
// 2-factor authentication //
@ -245,43 +167,31 @@ function user_exists_ldap($username, $password) {
/**
/**
* Check if a user has TOTP setup
* Check if a user has TOTP setup
* @global $database $database
* @param string $username
* @param string $username
* @return boolean true if TOTP secret exists, else false
* @return boolean true if TOTP secret exists, else false
*/
*/
function userHasTOTP($username) {
function userHasTOTP($username) {
global $database;
$client = new GuzzleHttp\Client();
$secret = $database->select('accounts', 'authsecret', ['username' => $username])[0];
if (is_empty($secret)) {
$response = $client
return false;
->request('POST', PORTAL_API, [
}
'form_params' => [
return true;
'key' => PORTAL_KEY,
}
'action' => "hastotp",
'username' => $username
]
]);
/**
if ($response->getStatusCode() > 299) {
* Generate a TOTP secret for the given user.
sendError("Login server error: " . $response->getBody());
* @param string $username
* @return string OTP provisioning URI (for generating a QR code)
*/
function newTOTP($username) {
global $database;
$secret = random_bytes(20);
$encoded_secret = Base32::encode($secret);
$userdata = $database->select('accounts', ['email', 'authsecret', 'realname'], ['username' => $username])[0];
$totp = new TOTP((is_null($userdata['email']) ? $userdata['realname'] : $userdata['email']), $encoded_secret);
$totp->setIssuer(SYSTEM_NAME);
return $totp->getProvisioningUri();
}
}
/**
$resp = json_decode($response->getBody(), TRUE);
* Save a TOTP secret for the user.
if ($resp['status'] == "OK") {
* @global $database $database
return $resp['otp'];
* @param string $username
} else {
* @param string $secret
return false;
*/
}
function saveTOTP($username, $secret) {
global $database;
$database->update('accounts', ['authsecret' => $secret], ['username' => $username]);
}
}
/**
/**
@ -292,11 +202,26 @@ function saveTOTP($username, $secret) {
* @return boolean true if it's legit, else false
* @return boolean true if it's legit, else false
*/
*/
function verifyTOTP($username, $code) {
function verifyTOTP($username, $code) {
global $database;
$client = new GuzzleHttp\Client();
$userdata = $database->select('accounts', ['email', 'authsecret'], ['username' => $username])[0];
if (is_empty($userdata['authsecret'])) {
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "verifytotp",
'username' => $username,
'code' => $code
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return $resp['valid'];
} else {
return false;
return false;
}
}
$totp = new TOTP(null, $userdata['authsecret']);
return $totp->verify($code);
}
}