Commit ca8923b4 authored by Côme Chilliet's avatar Côme Chilliet
Browse files

Merge branch '5778-create-a-security-library-for-fusiondirectory' into '1.4-dev'

Resolve "Create a security library for fusiondirectory"

See merge request fusiondirectory/fd!163
Showing with 521 additions and 361 deletions
+521 -361
...@@ -220,368 +220,11 @@ if (isset($_REQUEST['message'])) { ...@@ -220,368 +220,11 @@ if (isset($_REQUEST['message'])) {
} }
} }
/* Class with a function for each login step $loginMethods = LoginMethod::getMethods();
* Each function can return a string to display an LDAP error, or FALSE to redirect to login foreach ($loginMethods as $method) {
* In this case it can set global $message and assign focusfield in smarty before hand */ if ($method::active()) {
class Index { $method::loginProcess();
static protected $username;
static protected $password;
static function init()
{
static::$username = NULL;
static::$password = NULL;
} }
/* Runs schemaCheck if activated in configuration */
static function runSchemaCheck()
{
global $config;
if ($config->get_cfg_value('schemaCheck') != 'TRUE') {
return TRUE;
}
$cfg = array();
$cfg['admin'] = $config->current['ADMINDN'];
$cfg['password'] = $config->current['ADMINPASSWORD'];
$cfg['connection'] = $config->current['SERVER'];
$cfg['tls'] = ($config->get_cfg_value('ldapTLS') == 'TRUE');
$str = check_schema($cfg);
foreach ($str as $tr) {
if (!$tr['STATUS']) {
if ($tr['IS_MUST_HAVE']) {
return _('LDAP schema check reported errors:').'<br/><br/><i>'.$tr['MSG'].'</i>';
} else {
msg_dialog::display(_('LDAP schema error'), $tr['MSG'], WARNING_DIALOG);
}
}
}
return TRUE;
}
/* Check locking LDAP branch is here or create it */
static function checkForLockingBranch()
{
global $config;
$ldap = $config->get_ldap_link();
$ldap->cat(get_ou('lockRDN').get_ou('fusiondirectoryRDN').$config->current['BASE'], array('dn'));
$attrs = $ldap->fetch();
if (!count($attrs)) {
$ldap->cd($config->current['BASE']);
$ldap->create_missing_trees(get_ou('lockRDN').get_ou('fusiondirectoryRDN').$config->current['BASE']);
}
}
/* Check username for invalid characters and check password is not empty
* Also trims username */
static function validateUserInput()
{
global $message, $smarty;
static::$username = trim(static::$username);
if (!preg_match('/^[@A-Za-z0-9_.-]+$/', static::$username)) {
$message = _('Please specify a valid username!');
return FALSE;
} elseif (mb_strlen(static::$password, 'UTF-8') == 0) {
$message = _('Please specify your password!');
$smarty->assign ('focusfield', 'password');
return FALSE;
}
return TRUE;
}
/* Performs an LDAP bind with $username and $password */
static function ldapLoginUser()
{
global $ui, $config, $message, $smarty;
/* Login as user, initialize user ACL's */
$ui = ldap_login_user(static::$username, static::$password);
if ($ui === NULL) {
if (isset($_SERVER['REMOTE_ADDR'])) {
logging::log('security', 'login', '', array(), 'Authentication failed for user "'.static::$username.'" [from '.$_SERVER['REMOTE_ADDR'].']');
} else {
logging::log('security', 'login', '', array(), 'Authentication failed for user "'.static::$username.'"');
}
$message = _('Please check the username/password combination.');
$smarty->assign ('focusfield', 'password');
return FALSE;
}
return TRUE;
}
/* Called after successful login, return FALSE if account is expired */
static function loginAndCheckExpired()
{
global $ui, $config, $plist, $message, $smarty;
/* Remove all locks of this user */
del_user_locks($ui->dn);
/* Save userinfo and plugin structure */
session::global_set('ui', $ui);
/* User might have its own language, re-run initLanguage */
$plistReloaded = Language::init();
/* We need a fully loaded plist and config to test account expiration */
if (!$plistReloaded) {
session::global_un_set('plist');
}
$plist = load_plist();
/* Check that newly installed plugins have their configuration in the LDAP (will reload plist if needed) */
$config->checkLdapConfig();
/* Check account expiration */
if ($config->get_cfg_value('handleExpiredAccounts') == 'TRUE') {
$expired = $ui->expired_status();
if ($expired == POSIX_ACCOUNT_EXPIRED) {
logging::log('security', 'login', '', array(), 'Account for user "'.static::$username.'" has expired');
$message = _('Account locked. Please contact your system administrator!');
$smarty->assign ('focusfield', 'username');
return FALSE;
}
}
return TRUE;
}
/* Final step of successful login: redirect to main.php */
static function redirect()
{
global $config;
/* Not account expired or password forced change go to main page */
logging::log('security', 'login', '', array(), 'User "'.static::$username.'" logged in successfully.');
session::global_set('connected', 1);
session::global_set('DEBUGLEVEL', $config->get_cfg_value('DEBUGLEVEL'));
header ('Location: main.php?global_check=1');
exit;
}
/* Return HTTP authentication header */
static function authenticateHeader($message = 'Authentication required')
{
header('WWW-Authenticate: Basic realm="FusionDirectory"');
header('HTTP/1.0 401 Unauthorized');
echo "$message\n";
exit;
}
/* Run each step in $steps, stop on errors */
static function runSteps($steps)
{
foreach ($steps as $step) {
$status = static::$step();
if (is_string($status)) {
msg_dialog::display(_('LDAP error'), $status, LDAP_ERROR);
return FALSE;
} elseif ($status === FALSE) {
return FALSE;
}
}
return TRUE;
}
/* All login steps in the right order for standard POST login */
static function fullLoginProcess()
{
global $config, $message;
static::init();
/* Reset error messages */
$message = '';
static::$username = $_POST['username'];
static::$password = $_POST['password'];
$success = static::runSteps(array(
'validateUserInput',
'ldapLoginUser',
'checkForLockingBranch',
'loginAndCheckExpired',
'runSchemaCheck',
));
if ($success) {
/* Everything went well, redirect to main.php */
static::redirect();
}
}
/* All login steps in the right order for HTTP auth login */
static function authLoginProcess()
{
global $config, $message;
static::init();
if (!isset($_SERVER['PHP_AUTH_USER'])) {
static::authenticateHeader();
}
static::$username = $_SERVER['PHP_AUTH_USER'];
static::$password = $_SERVER['PHP_AUTH_PW'];
$success = static::runSteps(array(
'validateUserInput',
'ldapLoginUser',
'checkForLockingBranch',
'loginAndCheckExpired',
'runSchemaCheck',
));
if ($success) {
/* Everything went well, redirect to main.php */
static::redirect();
} else {
static::authenticateHeader($message);
}
}
/* All login steps in the right order for HTTP Header login */
static function headerAuthLoginProcess()
{
global $config, $message, $ui;
static::init();
/* Reset error messages */
$message = '';
$header = $config->get_cfg_value('httpHeaderAuthHeaderName', 'AUTH_USER');
static::$username = $_SERVER['HTTP_'.$header];
if (!static::$username) {
msg_dialog::display(
_('Error'),
sprintf(
_('No value found in HTTP header "%s"'),
$header
),
FATAL_ERROR_DIALOG
);
exit();
}
$ui = ldap_get_user(static::$username);
if ($ui === FALSE) {
msg_dialog::display(
_('Error'),
sprintf(
_('Header user "%s" could not be found in the LDAP'),
static::$username
),
FATAL_ERROR_DIALOG
);
exit();
} elseif (is_string($ui)) {
msg_dialog::display(
_('Error'),
sprintf(
_('Login with user "%s" triggered error: %s'),
static::$username,
$ui
),
FATAL_ERROR_DIALOG
);
exit();
}
$ui->loadACL();
$success = static::runSteps(array(
'checkForLockingBranch',
'loginAndCheckExpired',
'runSchemaCheck',
));
if ($success) {
/* Everything went well, redirect to main.php */
static::redirect();
}
}
/* All login steps in the right order for CAS login */
static function casLoginProcess()
{
global $config, $message, $ui;
static::init();
/* Reset error messages */
$message = '';
//~ phpCAS::setDebug();
// Initialize phpCAS
phpCAS::client(
CAS_VERSION_2_0,
$config->get_cfg_value('casHost', 'localhost'),
(int)($config->get_cfg_value('casPort', 443)),
$config->get_cfg_value('casContext', '')
);
// Set the CA certificate that is the issuer of the cert
phpCAS::setCasServerCACert($config->get_cfg_value('casServerCaCertPath'));
//~ phpCAS::setNoCasServerValidation();
// force CAS authentication
phpCAS::forceAuthentication();
static::$username = phpCAS::getUser();
$ui = ldap_get_user(static::$username);
if ($ui === FALSE) {
msg_dialog::display(
_('Error'),
sprintf(
_('CAS user "%s" could not be found in the LDAP'),
static::$username
),
FATAL_ERROR_DIALOG
);
exit();
} elseif (is_string($ui)) {
msg_dialog::display(
_('Error'),
sprintf(
_('Login with user "%s" triggered error: %s'),
static::$username,
$ui
),
FATAL_ERROR_DIALOG
);
exit();
}
$ui->loadACL();
$success = static::runSteps(array(
'checkForLockingBranch',
'loginAndCheckExpired',
'runSchemaCheck',
));
if ($success) {
/* Everything went well, redirect to main.php */
static::redirect();
}
}
}
if ($config->get_cfg_value('httpAuthActivated') == 'TRUE') {
Index::authLoginProcess();
} elseif ($config->get_cfg_value('casActivated') == 'TRUE') {
require_once('CAS.php');
/* Move FD autoload after CAS autoload */
spl_autoload_unregister('__fusiondirectory_autoload');
spl_autoload_register('__fusiondirectory_autoload');
Index::casLoginProcess();
} elseif ($config->get_cfg_value('httpHeaderAuthActivated') == 'TRUE') {
Index::headerAuthLoginProcess();
} elseif ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['login'])) {
/* Got a formular answer, validate and try to log in */
Index::fullLoginProcess();
} }
/* Translation of cookie-warning. Whether to display it, is determined by JavaScript */ /* Translation of cookie-warning. Whether to display it, is determined by JavaScript */
......
<?php
/*
This code is part of FusionDirectory (http://www.fusiondirectory.org/)
Copyright (C) 2017-2018 FusionDirectory
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/*!
* \brief Login via CAS
*/
class LoginCAS extends LoginMethod
{
/*! \brief All login steps in the right order for CAS login */
static function loginProcess()
{
global $config, $message, $ui;
require_once('CAS.php');
/* Move FD autoload after CAS autoload */
spl_autoload_unregister('__fusiondirectory_autoload');
spl_autoload_register('__fusiondirectory_autoload');
static::init();
/* Reset error messages */
$message = '';
// Initialize phpCAS
phpCAS::client(
CAS_VERSION_2_0,
$config->get_cfg_value('casHost', 'localhost'),
(int)($config->get_cfg_value('casPort', 443)),
$config->get_cfg_value('casContext', '')
);
// Set the CA certificate that is the issuer of the cert
phpCAS::setCasServerCACert($config->get_cfg_value('casServerCaCertPath'));
// force CAS authentication
phpCAS::forceAuthentication();
static::$username = phpCAS::getUser();
$ui = ldap_get_user(static::$username);
if ($ui === FALSE) {
msg_dialog::display(
_('Error'),
sprintf(
_('CAS user "%s" could not be found in the LDAP'),
static::$username
),
FATAL_ERROR_DIALOG
);
exit();
} elseif (is_string($ui)) {
msg_dialog::display(
_('Error'),
sprintf(
_('Login with user "%s" triggered error: %s'),
static::$username,
$ui
),
FATAL_ERROR_DIALOG
);
exit();
}
$ui->loadACL();
$success = static::runSteps(array(
'checkForLockingBranch',
'loginAndCheckExpired',
'runSchemaCheck',
));
if ($success) {
/* Everything went well, redirect to main.php */
static::redirect();
}
}
static function active()
{
global $config;
return ($config->get_cfg_value('casActivated') == 'TRUE');
}
}
<?php
/*
This code is part of FusionDirectory (http://www.fusiondirectory.org/)
Copyright (C) 2017-2018 FusionDirectory
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/*!
* \brief Login via HTTP Basic Auth
*/
class LoginHTTPAuth extends LoginMethod
{
/*! \brief All login steps in the right order for HTTP auth login */
static function loginProcess()
{
global $config, $message;
static::init();
if (!isset($_SERVER['PHP_AUTH_USER'])) {
static::authenticateHeader();
}
static::$username = $_SERVER['PHP_AUTH_USER'];
static::$password = $_SERVER['PHP_AUTH_PW'];
$success = static::runSteps(array(
'validateUserInput',
'ldapLoginUser',
'checkForLockingBranch',
'loginAndCheckExpired',
'runSchemaCheck',
));
if ($success) {
/* Everything went well, redirect to main.php */
static::redirect();
} else {
static::authenticateHeader($message);
}
}
static function active()
{
global $config;
return ($config->get_cfg_value('httpAuthActivated') == 'TRUE');
}
}
<?php
/*
This code is part of FusionDirectory (http://www.fusiondirectory.org/)
Copyright (C) 2017-2018 FusionDirectory
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/*!
* \brief Login via HTTP Header
*/
class LoginHTTPHeader extends LoginMethod
{
/*! \brief All login steps in the right order for HTTP Header login */
static function loginProcess()
{
global $config, $message, $ui;
static::init();
/* Reset error messages */
$message = '';
$header = $config->get_cfg_value('httpHeaderAuthHeaderName', 'AUTH_USER');
static::$username = $_SERVER['HTTP_'.$header];
if (!static::$username) {
msg_dialog::display(
_('Error'),
sprintf(
_('No value found in HTTP header "%s"'),
$header
),
FATAL_ERROR_DIALOG
);
exit();
}
$ui = ldap_get_user(static::$username);
if ($ui === FALSE) {
msg_dialog::display(
_('Error'),
sprintf(
_('Header user "%s" could not be found in the LDAP'),
static::$username
),
FATAL_ERROR_DIALOG
);
exit();
} elseif (is_string($ui)) {
msg_dialog::display(
_('Error'),
sprintf(
_('Login with user "%s" triggered error: %s'),
static::$username,
$ui
),
FATAL_ERROR_DIALOG
);
exit();
}
$ui->loadACL();
$success = static::runSteps(array(
'checkForLockingBranch',
'loginAndCheckExpired',
'runSchemaCheck',
));
if ($success) {
/* Everything went well, redirect to main.php */
static::redirect();
}
}
static function active()
{
global $config;
return ($config->get_cfg_value('httpHeaderAuthActivated') == 'TRUE');
}
}
<?php
/*
This code is part of FusionDirectory (http://www.fusiondirectory.org/)
Copyright (C) 2003-2010 Cajus Pollmeier
Copyright (C) 2011-2018 FusionDirectory
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/*!
* \brief Base class for login methods
*
* See index.php
*/
class LoginMethod
{
static protected $username;
static protected $password;
static function init()
{
static::$username = NULL;
static::$password = NULL;
}
/*! \brief Runs schemaCheck if activated in configuration */
static function runSchemaCheck()
{
global $config;
if ($config->get_cfg_value('schemaCheck') != 'TRUE') {
return TRUE;
}
$cfg = array();
$cfg['admin'] = $config->current['ADMINDN'];
$cfg['password'] = $config->current['ADMINPASSWORD'];
$cfg['connection'] = $config->current['SERVER'];
$cfg['tls'] = ($config->get_cfg_value('ldapTLS') == 'TRUE');
$str = check_schema($cfg);
foreach ($str as $tr) {
if (!$tr['STATUS']) {
if ($tr['IS_MUST_HAVE']) {
return _('LDAP schema check reported errors:').'<br/><br/><i>'.$tr['MSG'].'</i>';
} else {
msg_dialog::display(_('LDAP schema error'), $tr['MSG'], WARNING_DIALOG);
}
}
}
return TRUE;
}
/*! \brief Check if locking LDAP branch is here or create it */
static function checkForLockingBranch()
{
global $config;
$ldap = $config->get_ldap_link();
$ldap->cat(get_ou('lockRDN').get_ou('fusiondirectoryRDN').$config->current['BASE'], array('dn'));
$attrs = $ldap->fetch();
if (!count($attrs)) {
$ldap->cd($config->current['BASE']);
$ldap->create_missing_trees(get_ou('lockRDN').get_ou('fusiondirectoryRDN').$config->current['BASE']);
}
}
/*! \brief Check username for invalid characters and check password is not empty
* Also trims username */
static function validateUserInput()
{
global $message, $smarty;
static::$username = trim(static::$username);
if (!preg_match('/^[@A-Za-z0-9_.-]+$/', static::$username)) {
$message = _('Please specify a valid username!');
return FALSE;
} elseif (mb_strlen(static::$password, 'UTF-8') == 0) {
$message = _('Please specify your password!');
$smarty->assign ('focusfield', 'password');
return FALSE;
}
return TRUE;
}
/*! \brief Performs an LDAP bind with $username and $password */
static function ldapLoginUser()
{
global $ui, $config, $message, $smarty;
/* Login as user, initialize user ACL's */
$ui = ldap_login_user(static::$username, static::$password);
if ($ui === NULL) {
if (isset($_SERVER['REMOTE_ADDR'])) {
logging::log('security', 'login', '', array(), 'Authentication failed for user "'.static::$username.'" [from '.$_SERVER['REMOTE_ADDR'].']');
} else {
logging::log('security', 'login', '', array(), 'Authentication failed for user "'.static::$username.'"');
}
$message = _('Please check the username/password combination.');
$smarty->assign ('focusfield', 'password');
return FALSE;
}
return TRUE;
}
/*! \brief Called after successful login, return FALSE if account is expired */
static function loginAndCheckExpired()
{
global $ui, $config, $plist, $message, $smarty;
/* Remove all locks of this user */
del_user_locks($ui->dn);
/* Save userinfo and plugin structure */
session::global_set('ui', $ui);
/* User might have its own language, re-run initLanguage */
$plistReloaded = Language::init();
/* We need a fully loaded plist and config to test account expiration */
if (!$plistReloaded) {
session::global_un_set('plist');
}
$plist = load_plist();
/* Check that newly installed plugins have their configuration in the LDAP (will reload plist if needed) */
$config->checkLdapConfig();
/* Check account expiration */
if ($config->get_cfg_value('handleExpiredAccounts') == 'TRUE') {
$expired = $ui->expired_status();
if ($expired == POSIX_ACCOUNT_EXPIRED) {
logging::log('security', 'login', '', array(), 'Account for user "'.static::$username.'" has expired');
$message = _('Account locked. Please contact your system administrator!');
$smarty->assign ('focusfield', 'username');
return FALSE;
}
}
return TRUE;
}
/*! \brief Final step of successful login: redirect to main.php */
static function redirect()
{
global $config;
/* Not account expired or password forced change go to main page */
logging::log('security', 'login', '', array(), 'User "'.static::$username.'" logged in successfully.');
session::global_set('connected', 1);
session::global_set('DEBUGLEVEL', $config->get_cfg_value('DEBUGLEVEL'));
header ('Location: main.php?global_check=1');
exit;
}
/*! \brief Return HTTP authentication header */
static function authenticateHeader($message = 'Authentication required')
{
header('WWW-Authenticate: Basic realm="FusionDirectory"');
header('HTTP/1.0 401 Unauthorized');
echo "$message\n";
exit;
}
/*! \brief Run each step in $steps, stop on errors */
static function runSteps($steps)
{
foreach ($steps as $step) {
$status = static::$step();
if (is_string($status)) {
msg_dialog::display(_('LDAP error'), $status, LDAP_ERROR);
return FALSE;
} elseif ($status === FALSE) {
return FALSE;
}
}
return TRUE;
}
/*! \brief All login steps in the right order */
static function loginProcess()
{
}
static function active()
{
return FALSE;
}
static function getMethods()
{
return array(
'LoginHTTPAuth',
'LoginCAS',
'LoginHTTPHeader',
'LoginPost',
);
}
}
<?php
/*
This code is part of FusionDirectory (http://www.fusiondirectory.org/)
Copyright (C) 2017-2018 FusionDirectory
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/*!
* \brief Login via POST
*/
class LoginPost extends LoginMethod
{
/*! \brief All login steps in the right order for standard POST login */
static function loginProcess()
{
global $config, $message;
static::init();
/* Reset error messages */
$message = '';
static::$username = $_POST['username'];
static::$password = $_POST['password'];
$success = static::runSteps(array(
'validateUserInput',
'ldapLoginUser',
'checkForLockingBranch',
'loginAndCheckExpired',
'runSchemaCheck',
));
if ($success) {
/* Everything went well, redirect to main.php */
static::redirect();
}
}
static function active()
{
return (($_SERVER['REQUEST_METHOD'] == 'POST') && isset($_POST['login']));
}
}
  • SonarQube analysis indicates that quality gate is failed.

    • Security Rating on New Code is passed: Actual value 1
    • Reliability Rating on New Code is passed: Actual value 1
    • Maintainability Rating on New Code is passed: Actual value 1
    • Duplicated Lines on New Code (%) is failed: Actual value 14.87603305785124 > 3

    SonarQube analysis reported no issues.

    By Ghost User on 2018-05-07T09:49:18 (imported from GitLab)

Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment