refactor: Refactored authorizers to classes (#7497)

* Refactored authorizers to classes

* Merge changes for #7335

* ! fix php 5.3 incompatibility

* Update ADAuthorizationAuthorizer.php

* Fix get_user -> getUser

* Rename AuthorizerFactory to Auth, fix interface missing functions

* Add phpdocs to all interface methods and normalize the names a bit.

* Re-work auth_test.php AD bind tests to work properly with the new class.
Reflection is not the nicest tool, but I think it is appropriate here.
Handle exceptions more nicely in auth_test.php

* Restore AD getUseList fix

Not sure how it got removed

* fix auth_test.php style
This commit is contained in:
mcq8
2017-11-18 11:33:03 +01:00
committed by Neil Lathwood
parent 38be66993f
commit c9728a1f71
35 changed files with 2423 additions and 2398 deletions

View File

@@ -0,0 +1,354 @@
<?php
namespace LibreNMS\Authentication;
use LibreNMS\Config;
use LibreNMS\Exceptions\AuthenticationException;
class ADAuthorizationAuthorizer extends AuthorizerBase
{
protected $ldap_connection;
public function __construct()
{
if (! isset($_SESSION['username'])) {
$_SESSION['username'] = '';
}
// Disable certificate checking before connect if required
if (Config::has('auth_ad_check_certificates') &&
Config::get('auth_ad_check_certificates') == 0) {
putenv('LDAPTLS_REQCERT=never');
};
// Set up connection to LDAP server
$this->ldap_connection = @ldap_connect(Config::get('auth_ad_url'));
if (! $this->ldap_connection) {
echo '<h2>Fatal error while connecting to AD url ' . Config::get('auth_ad_url') . ': ' . ldap_error($this->ldap_connection) . '</h2>';
exit;
}
// disable referrals and force ldap version to 3
ldap_set_option($this->ldap_connection, LDAP_OPT_REFERRALS, 0);
ldap_set_option($this->ldap_connection, LDAP_OPT_PROTOCOL_VERSION, 3);
// Bind to AD
if (Config::has('auth_ad_binduser') && Config::has('auth_ad_bindpassword')) {
// With specified bind user
if (! ldap_bind($this->ldap_connection, Config::get('auth_ad_binduser') . '@' . Config::get('auth_ad_domain'), Config::get('auth_ad_bindpassword'))) {
echo ldap_error($this->ldap_connection);
}
} else {
// Anonymous
if (! ldap_bind($this->ldap_connection)) {
echo ldap_error($this->ldap_connection);
}
}
}
public function authenticate($username, $password)
{
if (isset($_SERVER['REMOTE_USER'])) {
$_SESSION['username'] = mres($_SERVER['REMOTE_USER']);
if ($this->userExists($_SESSION['username'])) {
$this->addUser($username, null);
return true;
}
$_SESSION['username'] = Config::get('http_auth_guest');
return true;
}
throw new AuthenticationException();
}
public function addUser($username, $password, $level = 0, $email = '', $realname = '', $can_modify_passwd = 0, $description = '')
{
// Check to see if user is already added in the database
if (!$this->userExists($username)) {
$userid = dbInsert(array('username' => $username, 'realname' => $realname, 'email' => $email, 'descr' => $description, 'level' => $level, 'can_modify_passwd' => $can_modify_passwd, 'user_id' => $this->getUserid($username)), 'users');
if ($userid == false) {
return false;
} else {
foreach (dbFetchRows('select notifications.* from notifications where not exists( select 1 from notifications_attribs where notifications.notifications_id = notifications_attribs.notifications_id and notifications_attribs.user_id = ?) order by notifications.notifications_id desc', array($userid)) as $notif) {
dbInsert(array('notifications_id'=>$notif['notifications_id'],'user_id'=>$userid,'key'=>'read','value'=>1), 'notifications_attribs');
}
}
return $userid;
} else {
return false;
}
}
protected function userExistsInDb($username)
{
$return = dbFetchCell('SELECT COUNT(*) FROM users WHERE username = ?', array($username), true);
return $return;
}
public function userExists($username, $throw_exception = false)
{
if ($this->authLdapSessionCacheGet('user_exists')) {
return 1;
}
$search = ldap_search(
$this->ldap_connection,
Config::get('auth_ad_base_dn'),
get_auth_ad_user_filter($username),
array('samaccountname')
);
$entries = ldap_get_entries($this->ldap_connection, $search);
if ($entries['count']) {
/*
* Cache positiv result as this will result in more queries which we
* want to speed up.
*/
$this->authLdapSessionCacheSet('user_exists', 1);
return 1;
}
return 0;
}
public function getUserlevel($username)
{
$userlevel = $this->authLdapSessionCacheGet('userlevel');
if ($userlevel) {
return $userlevel;
} else {
$userlevel = 0;
}
// Find all defined groups $username is in
$search = ldap_search(
$this->ldap_connection,
Config::get('auth_ad_base_dn'),
get_auth_ad_user_filter($username),
array('memberOf')
);
$entries = ldap_get_entries($this->ldap_connection, $search);
// Loop the list and find the highest level
foreach ($entries[0]['memberof'] as $entry) {
$group_cn = $this->getCn($entry);
$auth_ad_groups = Config::get('auth_ad_groups');
if ($auth_ad_groups[$group_cn]['level'] > $userlevel) {
$userlevel = $auth_ad_groups[$group_cn]['level'];
}
}
$this->authLdapSessionCacheSet('userlevel', $userlevel);
return $userlevel;
}
public function getUserid($username)
{
$user_id = $this->authLdapSessionCacheGet('userid');
if (isset($user_id)) {
return $user_id;
} else {
$user_id = -1;
}
$attributes = array('objectsid');
$search = ldap_search(
$this->ldap_connection,
Config::get('auth_ad_base_dn'),
get_auth_ad_user_filter($username),
$attributes
);
$entries = ldap_get_entries($this->ldap_connection, $search);
if ($entries['count']) {
$user_id = preg_replace('/.*-(\d+)$/', '$1', $this->sidFromLdap($entries[0]['objectsid'][0]));
}
$this->authLdapSessionCacheSet('userid', $user_id);
return $user_id;
}
public function deleteUser($userid)
{
dbDelete('bill_perms', '`user_id` = ?', array($userid));
dbDelete('devices_perms', '`user_id` = ?', array($userid));
dbDelete('ports_perms', '`user_id` = ?', array($userid));
dbDelete('users_prefs', '`user_id` = ?', array($userid));
dbDelete('users', '`user_id` = ?', array($userid));
return dbDelete('users', '`user_id` = ?', array($userid));
}
public function getUserlist()
{
$userlist = array();
$userhash = array();
$ldap_groups = $this->getGroupList();
foreach ($ldap_groups as $ldap_group) {
$search_filter = "(memberOf=$ldap_group)";
if (Config::get('auth_ad_user_filter')) {
$search_filter = "(&{" . Config::get('auth_ad_user_filter') . $search_filter . ")";
}
$search = ldap_search($this->ldap_connection, Config::get('auth_ad_base_dn'), $search_filter, array('samaccountname','displayname','objectsid','mail'));
$results = ldap_get_entries($this->ldap_connection, $search);
foreach ($results as $result) {
if (isset($result['samaccountname'][0])) {
$userid = preg_replace(
'/.*-(\d+)$/',
'$1',
$this->sidFromLdap($result['objectsid'][0])
);
// don't make duplicates, user may be member of more than one group
$userhash[$result['samaccountname'][0]] = array(
'realname' => $result['displayName'][0],
'user_id' => $userid,
'email' => $result['mail'][0]
);
}
}
}
foreach (array_keys($userhash) as $key) {
$userlist[] = array(
'username' => $key,
'realname' => $userhash[$key]['realname'],
'user_id' => $userhash[$key]['user_id'],
'email' => $userhash[$key]['email']
);
}
return $userlist;
}
public function getUser($user_id)
{
// not supported so return 0
return dbFetchRow('SELECT * FROM `users` WHERE `user_id` = ?', array($user_id), true);
}
public function updateUser($user_id, $realname, $level, $can_modify_passwd, $email)
{
dbUpdate(array('realname' => $realname, 'can_modify_passwd' => $can_modify_passwd, 'email' => $email), 'users', '`user_id` = ?', array($user_id));
}
protected function getFullname($username)
{
$attributes = array('name');
$result = ldap_search(
$this->ldap_connection,
Config::get('auth_ad_base_dn'),
get_auth_ad_user_filter($username),
$attributes
);
$entries = ldap_get_entries($this->ldap_connection, $result);
if ($entries['count'] > 0) {
$membername = $entries[0]['name'][0];
} else {
$membername = $username;
}
return $membername;
}
public function getGroupList()
{
$ldap_groups = array();
// show all Active Directory Users by default
$default_group = 'Users';
if (Config::has('auth_ad_group')) {
if (Config::get('auth_ad_group') !== $default_group) {
$ldap_groups[] = Config::get('auth_ad_group');
}
}
if (!Config::has('auth_ad_groups') && !Config::has('auth_ad_group')) {
$ldap_groups[] = $this->getDn($default_group);
}
foreach (Config::get('auth_ad_groups') as $key => $value) {
$ldap_groups[] = $this->getDn($key);
}
return $ldap_groups;
}
protected function getDn($samaccountname)
{
$attributes = array('dn');
$result = ldap_search(
$this->ldap_connection,
Config::get('auth_ad_base_dn'),
get_auth_ad_group_filter($samaccountname),
$attributes
);
$entries = ldap_get_entries($this->ldap_connection, $result);
if ($entries['count'] > 0) {
return $entries[0]['dn'];
} else {
return '';
}
}
protected function getCn($dn)
{
preg_match('/[^,]*/', $dn, $matches, PREG_OFFSET_CAPTURE, 3);
return $matches[0][0];
}
protected function sidFromLdap($sid)
{
$sidHex = unpack('H*hex', $sid);
$subAuths = unpack('H2/H2/n/N/V*', $sid);
$revLevel = hexdec(substr($sidHex, 0, 2));
$authIdent = hexdec(substr($sidHex, 4, 12));
return 'S-'.$revLevel.'-'.$authIdent.'-'.implode('-', $subAuths);
}
protected function authLdapSessionCacheGet($attr)
{
$ttl = 300;
if (Config::get('auth_ldap_cache_ttl')) {
$ttl = Config::get('auth_ldap_cache_ttl');
}
// auth_ldap cache present in this session?
if (! isset($_SESSION['auth_ldap'])) {
return null;
}
$cache = $_SESSION['auth_ldap'];
// $attr present in cache?
if (! isset($cache[$attr])) {
return null;
}
// Value still valid?
if (time() - $cache[$attr]['last_updated'] >= $ttl) {
return null;
}
return $cache[$attr]['value'];
}
protected function authLdapSessionCacheSet($attr, $value)
{
$_SESSION['auth_ldap'][$attr]['value'] = $value;
$_SESSION['auth_ldap'][$attr]['last_updated'] = time();
}
}

View File

@@ -0,0 +1,451 @@
<?php
// easier to rewrite for Active Directory than to bash it into existing LDAP implementation
// disable certificate checking before connect if required
namespace LibreNMS\Authentication;
use LibreNMS\Config;
use LibreNMS\Exceptions\AuthenticationException;
class ActiveDirectoryAuthorizer extends AuthorizerBase
{
protected $ldap_connection;
protected $ad_init;
public function __construct()
{
if (Config::has('auth_ad_check_certificates') &&
!Config::get('auth_ad_check_certificates')) {
putenv('LDAPTLS_REQCERT=never');
};
if (Config::has('auth_ad_check_certificates') && Config::get('auth_ad_debug')) {
ldap_set_option(null, LDAP_OPT_DEBUG_LEVEL, 7);
}
$this->ad_init = false; // this variable tracks if bind has been called so we don't call it multiple times
$this->ldap_connection = @ldap_connect(Config::get('auth_ad_url'));
// disable referrals and force ldap version to 3
ldap_set_option($this->ldap_connection, LDAP_OPT_REFERRALS, 0);
ldap_set_option($this->ldap_connection, LDAP_OPT_PROTOCOL_VERSION, 3);
}
public function authenticate($username, $password)
{
if ($this->ldap_connection) {
// bind with sAMAccountName instead of full LDAP DN
if ($username && $password && ldap_bind($this->ldap_connection, $username . '@' . Config::get('auth_ad_domain'), $password)) {
$this->ad_init = true;
// group membership in one of the configured groups is required
if (Config::get('auth_ad_require_groupmembership', true)) {
// cycle through defined groups, test for memberOf-ship
foreach (Config::get('auth_ad_groups', array()) as $group => $level) {
if ($this->userInGroup($username, $group)) {
return true;
}
}
// failed to find user
if (Config::get('auth_ad_debug', false)) {
throw new AuthenticationException('User is not in one of the required groups or user/group is outside the base dn');
}
throw new AuthenticationException();
} else {
// group membership is not required and user is valid
return true;
}
}
}
if (!isset($password) || $password == '') {
throw new AuthenticationException('A password is required');
} elseif (Config::get('auth_ad_debug', false)) {
ldap_get_option($this->ldap_connection, LDAP_OPT_DIAGNOSTIC_MESSAGE, $extended_error);
throw new AuthenticationException(ldap_error($this->ldap_connection).'<br />'.$extended_error);
}
throw new AuthenticationException(ldap_error($this->ldap_connection));
}
public function reauthenticate($sess_id, $token)
{
if ($this->adBind(false, true)) {
$sess_id = clean($sess_id);
$token = clean($token);
list($username, $hash) = explode('|', $token);
if (!$this->userExists($username)) {
if (Config::get('auth_ad_debug', false)) {
throw new AuthenticationException("$username is not a valid AD user");
}
throw new AuthenticationException();
}
return $this->checkRememberMe($sess_id, $token);
}
return false;
}
protected function userInGroup($username, $groupname)
{
// check if user is member of the given group or nested groups
$search_filter = "(&(objectClass=group)(cn=$groupname))";
// get DN for auth_ad_group
$search = ldap_search(
$this->ldap_connection,
Config::get('auth_ad_base_dn'),
$search_filter,
array("cn")
);
$result = ldap_get_entries($this->ldap_connection, $search);
if ($result == false || $result['count'] !== 1) {
if (Config::get('auth_ad_debug', false)) {
if ($result == false) {
// FIXME: what went wrong?
throw new AuthenticationException("LDAP query failed for group '$groupname' using filter '$search_filter'");
} elseif ($result['count'] == 0) {
throw new AuthenticationException("Failed to find group matching '$groupname' using filter '$search_filter'");
} elseif ($result['count'] > 1) {
throw new AuthenticationException("Multiple groups returned for '$groupname' using filter '$search_filter'");
}
}
throw new AuthenticationException();
}
$group_dn = $result[0]["dn"];
$search = ldap_search(
$this->ldap_connection,
Config::get('auth_ad_base_dn'),
// add 'LDAP_MATCHING_RULE_IN_CHAIN to the user filter to search for $username in nested $group_dn
// limiting to "DN" for shorter array
"(&" . get_auth_ad_user_filter($username) . "(memberOf:1.2.840.113556.1.4.1941:=$group_dn))",
array("DN")
);
$entries = ldap_get_entries($this->ldap_connection, $search);
return ($entries["count"] > 0);
}
protected function userExistsInDb($username)
{
$return = dbFetchCell('SELECT COUNT(*) FROM users WHERE username = ?', array($username), true);
return $return;
}
public function userExists($username, $throw_exception = false)
{
$this->adBind(); // make sure we called bind
$search = ldap_search(
$this->ldap_connection,
Config::get('auth_ad_base_dn'),
get_auth_ad_user_filter($username),
array('samaccountname')
);
$entries = ldap_get_entries($this->ldap_connection, $search);
if ($entries['count']) {
return 1;
}
return 0;
}
public function getUserlevel($username)
{
$this->adBind(); // make sure we called bind
$userlevel = 0;
if (!Config::get('auth_ad_require_groupmembership', true)) {
if (Config::get('auth_ad_global_read', false)) {
$userlevel = 5;
}
}
// cycle through defined groups, test for memberOf-ship
foreach (Config::get('auth_ad_groups', array()) as $group => $level) {
try {
if ($this->userInGroup($username, $group)) {
$userlevel = max($userlevel, $level['level']);
}
} catch (AuthenticationException $e) {
}
}
return $userlevel;
}
public function getUserid($username)
{
$this->adBind(); // make sure we called bind
$attributes = array('objectsid');
$search = ldap_search(
$this->ldap_connection,
Config::get('auth_ad_base_dn'),
get_auth_ad_user_filter($username),
$attributes
);
$entries = ldap_get_entries($this->ldap_connection, $search);
if ($entries['count']) {
return $this->getUseridFromSid($this->sidFromLdap($entries[0]['objectsid'][0]));
}
return -1;
}
protected function getDomainSid()
{
$this->adBind(); // make sure we called bind
// Extract only the domain components
$dn_candidate = preg_replace('/^.*?DC=/i', 'DC=', Config::get('auth_ad_base_dn'));
$search = ldap_read(
$this->ldap_connection,
$dn_candidate,
'(objectClass=*)',
array('objectsid')
);
$entry = ldap_get_entries($this->ldap_connection, $search);
return substr($this->sidFromLdap($entry[0]['objectsid'][0]), 0, 41);
}
public function getUser($user_id)
{
$this->adBind(); // make sure we called bind
$domain_sid = $this->getDomainSid();
$search_filter = "(&(objectcategory=person)(objectclass=user)(objectsid=$domain_sid-$user_id))";
$attributes = array('samaccountname', 'displayname', 'objectsid', 'mail');
$search = ldap_search($this->ldap_connection, Config::get('auth_ad_base_dn'), $search_filter, $attributes);
$entry = ldap_get_entries($this->ldap_connection, $search);
if (isset($entry[0]['samaccountname'][0])) {
return $this->userFromAd($entry[0]);
}
return array();
}
public function deleteUser($userid)
{
dbDelete('bill_perms', '`user_id` = ?', array($userid));
dbDelete('devices_perms', '`user_id` = ?', array($userid));
dbDelete('ports_perms', '`user_id` = ?', array($userid));
dbDelete('users_prefs', '`user_id` = ?', array($userid));
return 0;
}
public function getUserlist()
{
$this->adBind(); // make sure we called bind
$userlist = array();
$ldap_groups = $this->getGroupList();
foreach ($ldap_groups as $ldap_group) {
$search_filter = "(memberOf=$ldap_group)";
if (Config::get('auth_ad_user_filter')) {
$search_filter = "(&" . Config::get('auth_ad_user_filter') . $search_filter .")";
}
$attributes = array('samaccountname', 'displayname', 'objectsid', 'mail');
$search = ldap_search($this->ldap_connection, Config::get('auth_ad_base_dn'), $search_filter, $attributes);
$results = ldap_get_entries($this->ldap_connection, $search);
foreach ($results as $result) {
if (isset($result['samaccountname'][0])) {
$userlist[$result['samaccountname'][0]] = $this->userFromAd($result);
}
}
}
return array_values($userlist);
}
/**
* Generate a user array from an AD LDAP entry
* Must have the attributes: objectsid, samaccountname, displayname, mail
* @internal
*
* @param $entry
* @return array
*/
protected function userFromAd($entry)
{
return array(
'user_id' => $this->getUseridFromSid($this->sidFromLdap($entry['objectsid'][0])),
'username' => $entry['samaccountname'][0],
'realname' => $entry['displayname'][0],
'email' => $entry['mail'][0],
'descr' => '',
'level' => $this->getUserlevel($entry['samaccountname'][0]),
'can_modify_passwd' => 0,
);
}
protected function getEmail($username)
{
$this->adBind(); // make sure we called bind
$attributes = array('mail');
$search = ldap_search(
$this->ldap_connection,
Config::get('auth_ad_base_dn'),
get_auth_ad_user_filter($username),
$attributes
);
$result = ldap_get_entries($this->ldap_connection, $search);
unset($result[0]['mail']['count']);
return current($result[0]['mail']);
}
protected function getFullname($username)
{
$this->adBind(); // make sure we called bind
$attributes = array('name');
$result = ldap_search(
$this->ldap_connection,
Config::get('auth_ad_base_dn'),
get_auth_ad_user_filter($username),
$attributes
);
$entries = ldap_get_entries($this->ldap_connection, $result);
if ($entries['count'] > 0) {
$membername = $entries[0]['name'][0];
} else {
$membername = $username;
}
return $membername;
}
public function getGroupList()
{
$ldap_groups = array();
// show all Active Directory Users by default
$default_group = 'Users';
if (Config::has('auth_ad_group')) {
if (Config::get('auth_ad_group') !== $default_group) {
$ldap_groups[] = Config::get('auth_ad_group');
}
}
if (!Config::has('auth_ad_groups') && !Config::has('auth_ad_group')) {
$ldap_groups[] = $this->getDn($default_group);
}
foreach (Config::get('auth_ad_groups') as $key => $value) {
$ldap_groups[] = $this->getDn($key);
}
return $ldap_groups;
}
protected function getDn($samaccountname)
{
$this->adBind(); // make sure we called bind
$attributes = array('dn');
$result = ldap_search(
$this->ldap_connection,
Config::get('auth_ad_base_dn'),
get_auth_ad_group_filter($samaccountname),
$attributes
);
$entries = ldap_get_entries($this->ldap_connection, $result);
if ($entries['count'] > 0) {
return $entries[0]['dn'];
} else {
return '';
}
}
protected function getCn($dn)
{
$dn = str_replace('\\,', '~C0mmA~', $dn);
preg_match('/[^,]*/', $dn, $matches, PREG_OFFSET_CAPTURE, 3);
return str_replace('~C0mmA~', ',', $matches[0][0]);
}
protected function getUseridFromSid($sid)
{
return preg_replace('/.*-(\d+)$/', '$1', $sid);
}
protected function sidFromLdap($sid)
{
$sidUnpacked = unpack('H*hex', $sid);
$sidHex = array_shift($sidUnpacked);
$subAuths = unpack('H2/H2/n/N/V*', $sid);
$revLevel = hexdec(substr($sidHex, 0, 2));
$authIdent = hexdec(substr($sidHex, 4, 12));
return 'S-'.$revLevel.'-'.$authIdent.'-'.implode('-', $subAuths);
}
/**
* Bind to AD with the bind user if available, otherwise anonymous bind
* @internal
*
* @param bool $allow_anonymous attempt anonymous bind if bind user isn't available
* @param bool $force force rebind
* @return bool success or failure
*/
protected function adBind($allow_anonymous = true, $force = false)
{
if ($this->ad_init && !$force) {
return true; // bind already attempted
}
// set timeout
ldap_set_option(
$this->ldap_connection,
LDAP_OPT_NETWORK_TIMEOUT,
Config::has('auth_ad_timeout') ? Config::has('auth_ad_timeout') : 5
);
// With specified bind user
if (Config::has('auth_ad_binduser') && Config::has('auth_ad_bindpassword')) {
$this->ad_init = true;
$bind = ldap_bind(
$this->ldap_connection,
Config::get('auth_ad_binduser') . '@' . Config::get('auth_ad_domain'),
Config::get('auth_ad_bindpassword')
);
ldap_set_option($this->ldap_connection, LDAP_OPT_NETWORK_TIMEOUT, -1); // restore timeout
return $bind;
}
$bind = false;
// Anonymous
if ($allow_anonymous) {
$this->ad_init = true;
$bind = ldap_bind($this->ldap_connection);
}
ldap_set_option($this->ldap_connection, LDAP_OPT_NETWORK_TIMEOUT, -1); // restore timeout
return $bind;
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace LibreNMS\Authentication;
use LibreNMS\Config;
use LibreNMS\Interfaces\Authentication\Authorizer;
class Auth
{
protected static $_instance;
/**
* Gets the authorizer based on the config
*
* @return Authorizer
*/
public static function get()
{
if (!static::$_instance) {
$configToClassMap = array(
'mysql' => 'LibreNMS\Authentication\MysqlAuthorizer',
'active_directory' => 'LibreNMS\Authentication\ActiveDirectoryAuthorizer',
'ldap' => 'LibreNMS\Authentication\LdapAuthorizer',
'radius' => 'LibreNMS\Authentication\RadiusAuthorizer',
'http-auth' => 'LibreNMS\Authentication\HttpAuthAuthorizer',
'ad-authorization' => 'LibreNMS\Authentication\ADAuthorizationAuthorizer',
'ldap-authorization' => 'LibreNMS\Authentication\LdapAuthorizationAuthorizer',
);
$auth_mechanism = Config::get('auth_mechanism');
if (!isset($configToClassMap[$auth_mechanism])) {
throw new \RuntimeException($auth_mechanism . ' not found as auth_mechanism');
}
static::$_instance = new $configToClassMap[$auth_mechanism]();
}
return static::$_instance;
}
}

View File

@@ -0,0 +1,260 @@
<?php
/**
* AuthorizerBase.php
*
* authentication functions
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2017 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Authentication;
use LibreNMS\Config;
use LibreNMS\Interfaces\Authentication\Authorizer;
use LibreNMS\Exceptions\AuthenticationException;
use Phpass\PasswordHash;
abstract class AuthorizerBase implements Authorizer
{
protected static $HAS_AUTH_USERMANAGEMENT = 0;
protected static $CAN_UPDATE_USER = 0;
/**
* Log out the user, unset cookies, destroy the session
*
* @param string $message The logout message.
*/
public function logOutUser($message = 'Logged Out')
{
global $auth_message;
dbInsert(array('user' => $_SESSION['username'], 'address' => get_client_ip(), 'result' => 'Logged Out'), 'authlog');
$this->clearRememberMe($_SESSION['username']);
unset($_SESSION['authenticated']);
session_destroy();
$auth_message = $message; // global variable used to display a message to the user
}
/**
* Log in the user and set up a few login tasks
* $_SESSION['username'] must be set prior to calling this function
* If twofactor authentication is enabled, it will be checked here.
*
* If everything goes well, $_SESSION['authenticated'] will be true after this function completes.
* @return bool If the user was successfully logged in.
* @throws AuthenticationException if anything failed why trying to log in
*/
public function logInUser()
{
// set up variables, but don't override existing ones (ad anonymous bind can only get user_id at login)
if (!isset($_SESSION['userlevel'])) {
$_SESSION['userlevel'] = $this->getUserlevel($_SESSION['username']);
}
if (!isset($_SESSION['user_id'])) {
$_SESSION['user_id'] = $this->getUserid($_SESSION['username']);
}
// check for valid user_id
if ($_SESSION['user_id'] === false || $_SESSION['user_id'] < 0) {
throw new AuthenticationException('Invalid Credentials');
}
if (!$this->sessionAuthenticated()) {
// check twofactor
if (Config::get('twofactor') === true && !isset($_SESSION['twofactor'])) {
if (TwoFactor::showForm()) {
return false; // not done yet, one more cycle to show the 2fa form
}
}
// if two factor isn't enabled or it has passed already ware are logged in
if (!Config::get('twofactor') || $_SESSION['twofactor']) {
$_SESSION['authenticated'] = true;
dbInsert(array('user' => $_SESSION['username'], 'address' => get_client_ip(), 'result' => 'Logged In'), 'authlog');
}
}
if ($this->sessionAuthenticated()) {
$this->setRememberMe();
}
return true;
}
/**
* Check if the session is authenticated
*
* @return bool
*/
public function sessionAuthenticated()
{
return isset($_SESSION['authenticated']) && $_SESSION['authenticated'];
}
/**
* Set or update the remember me cookie if $_SESSION['remember'] is set
* If setting a new cookie, $_SESSION['username'] must be set
*/
protected function setRememberMe()
{
if (!isset($_SESSION['remember'])) {
return;
}
unset($_SESSION['remember']);
$sess_id = session_id();
$expiration = time() + 60 * 60 * 24 * Config::get('auth_remember');
$db_entry = array(
'session_value' => $sess_id,
'session_expiry' => $expiration,
);
if (isset($_COOKIE['token'], $_COOKIE['auth'])) {
$token_id = $_COOKIE['token'];
$auth = $_COOKIE['auth'];
dbUpdate($db_entry, 'session', 'session_auth=?', array($_COOKIE['auth']));
} else {
$token = strgen();
$auth = strgen();
$hasher = new PasswordHash(8, false);
$token_id = $_SESSION['username'] . '|' . $hasher->HashPassword($_SESSION['username'] . $token);
$db_entry['session_username'] = $_SESSION['username'];
$db_entry['session_token'] = $token;
$db_entry['session_auth'] = $auth;
dbInsert($db_entry, 'session');
}\
setcookie('sess_id', $sess_id, $expiration, '/', null, Config::get('secure_cookies'), true);
setcookie('token', $token_id, $expiration, '/', null, Config::get('secure_cookies'), true);
setcookie('auth', $auth, $expiration, '/', null, Config::get('secure_cookies'), true);
}
/**
* Check the remember me cookie
* If the cookie is valid, $_SESSION['username'] will be set
*
* @param string $sess_id sess_id cookie value
* @param string $token token cookie value
* @return bool is the remember me token valid
* @throws AuthenticationException thrown if the cookie is invalid
*/
protected function checkRememberMe($sess_id, $token)
{
list($uname, $hash) = explode('|', $token);
$session = dbFetchRow(
"SELECT * FROM `session` WHERE `session_username`=? AND `session_value`=?",
array($uname, $sess_id),
true
);
$hasher = new PasswordHash(8, false);
if ($hasher->CheckPassword($uname . $session['session_token'], $hash)) {
$_SESSION['username'] = $uname;
return true;
}
$this->clearRememberMe($uname);
throw new AuthenticationException('Cookie invalid, please log in.');
}
/**
* Clear remember cookie and remove our database record
*
* @param $username
*/
protected function clearRememberMe($username)
{
dbDelete(
'session',
'`session_username` = ? AND `session_value` = ?',
array($username, $_COOKIE['sess_id'])
);
unset($_COOKIE);
$time = time() - 60 * 60 * 24 * Config::get('auth_remember'); // time in the past to make sure
setcookie('sess_id', '', $time, '/', null, Config::get('secure_cookies'));
setcookie('token', '', $time, '/', null, Config::get('secure_cookies'));
setcookie('auth', '', $time, '/', null, Config::get('secure_cookies'));
}
abstract public function authenticate($username, $password);
public function reauthenticate($sess_id, $token)
{
//not supported by default
return false;
}
public function canUpdatePasswords($username = '')
{
return 0;
}
public function changePassword($username, $newpassword)
{
//not supported by default
return 0;
}
public function canManageUsers()
{
return static::$HAS_AUTH_USERMANAGEMENT;
}
public function addUser($username, $password, $level = 0, $email = '', $realname = '', $can_modify_passwd = 0, $description = '')
{
//not supported by default
return 0;
}
abstract public function userExists($username, $throw_exception = false);
abstract public function getUserlevel($username);
abstract public function getUserid($username);
abstract public function getUser($user_id);
public function deleteUser($userid)
{
//not supported by default
return 0;
}
abstract public function getUserlist();
public function canUpdateUsers()
{
return static::$CAN_UPDATE_USER;
}
public function updateUser($user_id, $realname, $level, $can_modify_passwd, $email)
{
//not supported by default
return 0;
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace LibreNMS\Authentication;
use LibreNMS\Config;
use LibreNMS\Exceptions\AuthenticationException;
use Phpass\PasswordHash;
class HttpAuthAuthorizer extends AuthorizerBase
{
protected static $HAS_AUTH_USERMANAGEMENT = 1;
protected static $CAN_UPDATE_USER = 1;
public function authenticate($username, $password)
{
if ($this->userExists($username)) {
return true;
}
throw new AuthenticationException('No matching user found and http_auth_guest is not set');
}
public function addUser($username, $password, $level = 0, $email = '', $realname = '', $can_modify_passwd = 1, $description = '')
{
if (!$this->userExists($username)) {
$hasher = new PasswordHash(8, false);
$encrypted = $hasher->HashPassword($password);
$userid = dbInsert(array('username' => $username, 'password' => $encrypted, 'level' => $level, 'email' => $email, 'realname' => $realname, 'can_modify_passwd' => $can_modify_passwd, 'descr' => $description), 'users');
if ($userid == false) {
return false;
} else {
foreach (dbFetchRows('select notifications.* from notifications where not exists( select 1 from notifications_attribs where notifications.notifications_id = notifications_attribs.notifications_id and notifications_attribs.user_id = ?) order by notifications.notifications_id desc', array($userid)) as $notif) {
dbInsert(array('notifications_id' => $notif['notifications_id'], 'user_id' => $userid, 'key' => 'read', 'value' => 1), 'notifications_attribs');
}
}
return $userid;
} else {
return false;
}
}
public function userExists($username, $throw_exception = false)
{
$query = 'SELECT COUNT(*) FROM `users` WHERE `username`=?';
$params = array($username);
if (Config::has('http_auth_guest')) {
$query .= ' OR `username`=?';
$params[] = Config::get('http_auth_guest');
}
return dbFetchCell($query, $params) > 0;
}
public function getUserlevel($username)
{
$user_level = dbFetchCell('SELECT `level` FROM `users` WHERE `username`=?', array($username));
if ($user_level) {
return $user_level;
}
if (Config::has('http_auth_guest')) {
return dbFetchCell('SELECT `level` FROM `users` WHERE `username`=?', array(Config::get('http_auth_guest')));
}
return 0;
}
public function getUserid($username)
{
$user_id = dbFetchCell('SELECT `user_id` FROM `users` WHERE `username`=?', array($username));
if ($user_id) {
return $user_id;
}
if (Config::has('http_auth_guest')) {
return dbFetchCell('SELECT `user_id` FROM `users` WHERE `username`=?', array(Config::get('http_auth_guest')));
}
return -1;
}
public function getUserlist()
{
return dbFetchRows('SELECT * FROM `users`');
}
public function getUser($user_id)
{
return dbFetchRow('SELECT * FROM `users` WHERE `user_id` = ?', array($user_id));
}
public function updateUser($user_id, $realname, $level, $can_modify_passwd, $email)
{
dbUpdate(array('realname' => $realname, 'level' => $level, 'can_modify_passwd' => $can_modify_passwd, 'email' => $email), 'users', '`user_id` = ?', array($user_id));
}
}

View File

@@ -0,0 +1,290 @@
<?php
/*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
/**
* libreNMS HTTP-Authentication and LDAP Authorization Library
* @author Maximilian Wilhelm <max@rfc2324.org>
* @copyright 2016 LibreNMS, Barbarossa
* @license GPL
* @package LibreNMS
* @subpackage Authentication
*
* This Authentitation / Authorization module provides the ability to let
* the webserver (e.g. Apache) do the user Authentication (using Kerberos
* f.e.) and let libreNMS do the Authorization of the already known user.
* Authorization and setting of libreNMS user level is done by LDAP group
* names specified in the configuration file. The group configuration is
* basicly copied from the existing ldap Authentication module.
*
* Most of the code is copied from the http-auth and ldap Authentication
* modules already existing.
*
* To save lots of redundant queries to the LDAP server and speed up the
* libreNMS WebUI, all information is cached within the PHP $_SESSION as
* long as specified in $config['auth_ldap_cache_ttl'] (Default: 300s).
*/
namespace LibreNMS\Authentication;
use LibreNMS\Config;
use LibreNMS\Exceptions\AuthenticationException;
class LdapAuthorizationAuthorizer extends AuthorizerBase
{
protected $ldap_connection;
public function __construct()
{
if (! isset($_SESSION['username'])) {
$_SESSION['username'] = '';
}
/**
* Set up connection to LDAP server
*/
$this->ldap_connection = @ldap_connect(Config::get('auth_ldap_server'), Config::get('auth_ldap_port'));
if (! $this->ldap_connection) {
echo '<h2>Fatal error while connecting to LDAP server ' . Config::get('auth_ldap_server') . ':' . Config::get('auth_ldap_port') . ': ' . ldap_error($this->ldap_connection) . '</h2>';
exit;
}
if (Config::get('auth_ldap_version')) {
ldap_set_option($this->ldap_connection, LDAP_OPT_PROTOCOL_VERSION, Config::get('auth_ldap_version'));
}
if (Config::get('auth_ldap_starttls') && (Config::get('auth_ldap_starttls') == 'optional' || Config::get('auth_ldap_starttls') == 'require')) {
$tls = ldap_start_tls($this->ldap_connection);
if (Config::get('auth_ldap_starttls') == 'require' && $tls === false) {
echo '<h2>Fatal error: LDAP TLS required but not successfully negotiated:' . ldap_error($this->ldap_connection) . '</h2>';
exit;
}
}
}
public function authenticate($username, $password)
{
if (isset($_SERVER['REMOTE_USER'])) {
$_SESSION['username'] = mres($_SERVER['REMOTE_USER']);
if ($this->userExists($_SESSION['username'])) {
return true;
}
$_SESSION['username'] = Config::get('http_auth_guest');
return true;
}
throw new AuthenticationException();
}
public function userExists($username, $throw_exception = false)
{
if ($this->authLdapSessionCacheGet('user_exists')) {
return 1;
}
$filter = '(' . Config::get('auth_ldap_prefix') . $username . ')';
$search = ldap_search($this->ldap_connection, trim(Config::get('auth_ldap_suffix'), ','), $filter);
$entries = ldap_get_entries($this->ldap_connection, $search);
if ($entries['count']) {
/*
* Cache positiv result as this will result in more queries which we
* want to speed up.
*/
$this->authLdapSessionCacheSet('user_exists', 1);
return 1;
}
/*
* Don't cache that user doesn't exists as this might be a misconfiguration
* on some end and the user will be happy if it "just works" after the user
* has been added to LDAP.
*/
return 0;
}
public function getUserlevel($username)
{
$userlevel = $this->authLdapSessionCacheGet('userlevel');
if ($userlevel) {
return $userlevel;
} else {
$userlevel = 0;
}
// Find all defined groups $username is in
$filter = '(&(|(cn=' . join(')(cn=', array_keys(Config::get('auth_ldap_groups'))) . '))(' . Config::get('auth_ldap_groupmemberattr') .'=' . $this->getMembername($username) . '))';
$search = ldap_search($this->ldap_connection, Config::get('auth_ldap_groupbase'), $filter);
$entries = ldap_get_entries($this->ldap_connection, $search);
// Loop the list and find the highest level
foreach ($entries as $entry) {
$groupname = $entry['cn'][0];
$authLdapGroups = Config::get('auth_ldap_groups');
if ($authLdapGroups[$groupname]['level'] > $userlevel) {
$userlevel = $authLdapGroups[$groupname]['level'];
}
}
$this->authLdapSessionCacheSet('userlevel', $userlevel);
return $userlevel;
}
public function getUserid($username)
{
$user_id = $this->authLdapSessionCacheGet('userid');
if (isset($user_id)) {
return $user_id;
} else {
$user_id = -1;
}
$filter = '(' . Config::get('auth_ldap_prefix') . $username . ')';
$search = ldap_search($this->ldap_connection, trim(Config::get('auth_ldap_suffix'), ','), $filter);
$entries = ldap_get_entries($this->ldap_connection, $search);
if ($entries['count']) {
$user_id = $entries[0]['uidnumber'][0];
}
$this->authLdapSessionCacheSet('userid', $user_id);
return $user_id;
}
public function getUserlist()
{
$userlist = array ();
$filter = '(' . Config::get('auth_ldap_prefix') . '*)';
$search = ldap_search($this->ldap_connection, trim(Config::get('auth_ldap_suffix'), ','), $filter);
$entries = ldap_get_entries($this->ldap_connection, $search);
if ($entries['count']) {
foreach ($entries as $entry) {
$username = $entry['uid'][0];
$realname = $entry['cn'][0];
$user_id = $entry['uidnumber'][0];
$email = $entry[Config::get('auth_ldap_emailattr')][0];
$ldap_groups = $this->getGroupList();
foreach ($ldap_groups as $ldap_group) {
$ldap_comparison = ldap_compare(
$this->ldap_connection,
$ldap_group,
Config::get('auth_ldap_groupmemberattr'),
$this->getMembername($username)
);
if (! Config::has('auth_ldap_group') || $ldap_comparison === true) {
$userlist[] = array(
'username' => $username,
'realname' => $realname,
'user_id' => $user_id,
'email' => $email,
);
}
}
}
}
return $userlist;
}
public function getUser($user_id)
{
foreach ($this->getUserlist() as $users) {
if ($users['user_id'] === $user_id) {
return $users['username'];
}
}
return 0;
}
protected function getMembername($username)
{
if (Config::get('auth_ldap_groupmembertype') == 'fulldn') {
$membername = Config::get('auth_ldap_prefix') . $username . Config::get('auth_ldap_suffix');
} elseif (Config::get('auth_ldap_groupmembertype') == 'puredn') {
$filter = '(' . Config::get('auth_ldap_attr.uid') . '=' . $username . ')';
$search = ldap_search($this->ldap_connection, Config::get('auth_ldap_groupbase'), $filter);
$entries = ldap_get_entries($this->ldap_connection, $search);
$membername = $entries[0]['dn'];
} else {
$membername = $username;
}
return $membername;
}
protected function authLdapSessionCacheGet($attr)
{
$ttl = 300;
if (Config::get('auth_ldap_cache_ttl')) {
$ttl = Config::get('auth_ldap_cache_ttl');
}
// auth_ldap cache present in this session?
if (! isset($_SESSION['auth_ldap'])) {
return null;
}
$cache = $_SESSION['auth_ldap'];
// $attr present in cache?
if (! isset($cache[$attr])) {
return null;
}
// Value still valid?
if (time() - $cache[$attr]['last_updated'] >= $ttl) {
return null;
}
$cache[$attr]['value'];
}
protected function authLdapSessionCacheSet($attr, $value)
{
$_SESSION['auth_ldap'][$attr]['value'] = $value;
$_SESSION['auth_ldap'][$attr]['last_updated'] = time();
}
public function getGroupList()
{
$ldap_groups = array();
$default_group = 'cn=groupname,ou=groups,dc=example,dc=com';
if (Config::has('auth_ldap_group')) {
if (Config::get('auth_ldap_group') !== $default_group) {
$ldap_groups[] = Config::get('auth_ldap_group');
}
}
foreach (Config::get('auth_ldap_groups') as $key => $value) {
$dn = "cn=$key,".Config::get('auth_ldap_groupbase');
$ldap_groups[] = $dn;
}
return $ldap_groups;
}
}

View File

@@ -0,0 +1,302 @@
<?php
namespace LibreNMS\Authentication;
use LibreNMS\Config;
use LibreNMS\Exceptions\AuthenticationException;
class LdapAuthorizer extends AuthorizerBase
{
protected $ldap_connection;
public function authenticate($username, $password)
{
$connection = $this->getLdapConnection(true);
if ($username) {
if ($password && ldap_bind($connection, $this->getFullDn($username), $password)) {
if (!Config::has('auth_ldap_group')) {
return true;
} else {
$ldap_groups = $this->getGroupList();
foreach ($ldap_groups as $ldap_group) {
$ldap_comparison = ldap_compare(
$connection,
$ldap_group,
Config::get('auth_ldap_groupmemberattr', 'memberUid'),
$this->getMembername($username)
);
if ($ldap_comparison === true) {
return true;
}
}
}
}
if (!isset($password) || $password == '') {
throw new AuthenticationException('A password is required');
}
throw new AuthenticationException(ldap_error($connection));
}
throw new AuthenticationException();
}
public function reauthenticate($sess_id, $token)
{
$sess_id = clean($sess_id);
$token = clean($token);
list($username, $hash) = explode('|', $token);
if (!$this->userExists($username, true)) {
throw new AuthenticationException();
}
return $this->checkRememberMe($sess_id, $token);
}
public function userExists($username, $throw_exception = false)
{
try {
$connection = $this->getLdapConnection();
$filter = '(' . Config::get('auth_ldap_prefix') . $username . ')';
$search = ldap_search($connection, trim(Config::get('auth_ldap_suffix'), ','), $filter);
$entries = ldap_get_entries($connection, $search);
if ($entries['count']) {
return 1;
}
} catch (AuthenticationException $e) {
if ($throw_exception) {
throw $e;
} else {
echo $e->getMessage() . PHP_EOL;
}
}
return 0;
}
public function getUserlevel($username)
{
$userlevel = 0;
try {
$connection = $this->getLdapConnection();
$groups = Config::get('auth_ldap_groups');
// Find all defined groups $username is in
$filter = '(&(|(cn=' . join(')(cn=', array_keys($groups)) . '))(' . Config::get('auth_ldap_groupmemberattr', 'memberUid') . '=' . $this->getMembername($username) . '))';
$search = ldap_search($connection, Config::get('auth_ldap_groupbase'), $filter);
$entries = ldap_get_entries($connection, $search);
// Loop the list and find the highest level
foreach ($entries as $entry) {
$groupname = $entry['cn'][0];
if ($groups[$groupname]['level'] > $userlevel) {
$userlevel = $groups[$groupname]['level'];
}
}
} catch (AuthenticationException $e) {
echo $e->getMessage() . PHP_EOL;
}
return $userlevel;
}
public function getUserid($username)
{
try {
$connection = $this->getLdapConnection();
$filter = '(' . Config::get('auth_ldap_prefix') . $username . ')';
$search = ldap_search($connection, trim(Config::get('auth_ldap_suffix'), ','), $filter);
$entries = ldap_get_entries($connection, $search);
if ($entries['count']) {
$uid_attr = strtolower(Config::get('auth_ldap_uid_attribute', 'uidnumber'));
return $entries[0][$uid_attr][0];
}
} catch (AuthenticationException $e) {
echo $e->getMessage() . PHP_EOL;
}
return -1;
}
public function getUserlist()
{
$userlist = array();
try {
$connection = $this->getLdapConnection();
$filter = '(' . Config::get('auth_ldap_prefix') . '*)';
$search = ldap_search($connection, trim(Config::get('auth_ldap_suffix'), ','), $filter);
$entries = ldap_get_entries($connection, $search);
if ($entries['count']) {
foreach ($entries as $entry) {
$username = $entry['uid'][0];
$realname = $entry['cn'][0];
$uid_attr = strtolower(Config::get('auth_ldap_uid_attribute', 'uidnumber'));
$user_id = $entry[$uid_attr][0];
$email = $entry[Config::get('auth_ldap_emailattr', 'mail')][0];
$ldap_groups = $this->getGroupList();
foreach ($ldap_groups as $ldap_group) {
$ldap_comparison = $this->ldap_compare(
$connection,
$ldap_group,
Config::get('auth_ldap_groupmemberattr', 'memberUid'),
$this->getMembername($username)
);
if (!Config::has('auth_ldap_group') || $ldap_comparison === true) {
$userlist[$username] = array(
'username' => $username,
'realname' => $realname,
'user_id' => $user_id,
'email' => $email,
);
}
}
}
}
} catch (AuthenticationException $e) {
echo $e->getMessage() . PHP_EOL;
}
return $userlist;
}
public function getUser($user_id)
{
foreach ($this->getUserlist() as $user) {
if ($user['user_id'] === $user_id) {
return $user;
}
}
return 0;
}
protected function getMembername($username)
{
$type = Config::get('auth_ldap_groupmembertype');
if ($type == 'fulldn') {
return $this->getFullDn($username);
}
if ($type == 'puredn') {
try {
$connection = $this->getLdapConnection();
$filter = '(' . Config::get('auth_ldap_attr.uid') . '=' . $username . ')';
$search = ldap_search($connection, Config::get('auth_ldap_groupbase'), $filter);
$entries = ldap_get_entries($connection, $search);
return $entries[0]['dn'];
} catch (AuthenticationException $e) {
echo $e->getMessage() . PHP_EOL;
}
}
return $username;
}
public function getGroupList()
{
$ldap_groups = array();
$default_group = 'cn=groupname,ou=groups,dc=example,dc=com'; // in the documentation
if (Config::get('auth_ldap_group', $default_group) !== $default_group) {
$ldap_groups[] = Config::get('auth_ldap_group');
}
foreach (Config::get('auth_ldap_groups') as $key => $value) {
$ldap_groups[] = "cn=$key,".Config::get('auth_ldap_groupbase');
}
return $ldap_groups;
}
/**
* Get the full dn with auth_ldap_prefix and auth_ldap_suffix
* @internal
*
* @return string
*/
protected function getFullDn($username)
{
return Config::get('auth_ldap_prefix', '') . $username . Config::get('auth_ldap_suffix', '');
}
/**
* Get the ldap connection. If it hasn't been established yet, connect and try to bind.
* @internal
*
* @param bool $skip_bind do not attempt to bind on connection
* @return false|resource
* @throws AuthenticationException
*/
protected function getLdapConnection($skip_bind = false)
{
if ($this->ldap_connection) {
return $this->ldap_connection; // bind already attempted
}
$this->ldap_connection = @ldap_connect(Config::get('auth_ldap_server'), Config::get('auth_ldap_port', 389));
if (!$this->ldap_connection) {
throw new AuthenticationException('Unable to connect to ldap server');
}
ldap_set_option($this->ldap_connection, LDAP_OPT_PROTOCOL_VERSION, Config::get('auth_ldap_version', 2));
$use_tls = Config::get('auth_ldap_starttls');
if ($use_tls == 'optional'||$use_tls == 'require') {
$tls_success = ldap_start_tls($this->ldap_connection);
if ($use_tls == 'require' && $tls_success === false) {
$error = ldap_error($this->ldap_connection);
throw new AuthenticationException("Fatal error: LDAP TLS required but not successfully negotiated: $error");
}
}
if ($skip_bind) {
return $this->ldap_connection;
}
// set timeout
ldap_set_option($this->ldap_connection, LDAP_OPT_NETWORK_TIMEOUT, Config::get('auth_ldap_timeout', 5));
// With specified bind user
if ((Config::has('auth_ldap_binduser') || Config::has('auth_ldap_binddn'))
&& Config::has('auth_ldap_bindpassword')
) {
if (Config::has('auth_ldap_binddn')) {
$bind_dn = Config::get('auth_ldap_binddn');
} else {
$bind_dn = $this->getFullDn(Config::get('auth_ldap_binduser'));
}
if (ldap_bind(
$this->ldap_connection,
$bind_dn,
Config::get('auth_ldap_bindpassword')
)) {
ldap_set_option($this->ldap_connection, LDAP_OPT_NETWORK_TIMEOUT, -1); // restore timeout
return $this->ldap_connection;
}
}
// Anonymous
ldap_bind($this->ldap_connection);
ldap_set_option($this->ldap_connection, LDAP_OPT_NETWORK_TIMEOUT, -1); // restore timeout
return $this->ldap_connection;
}
}

View File

@@ -0,0 +1,161 @@
<?php
namespace LibreNMS\Authentication;
use LibreNMS\Exceptions\AuthenticationException;
use Phpass\PasswordHash;
class MysqlAuthorizer extends AuthorizerBase
{
protected static $HAS_AUTH_USERMANAGEMENT = 1;
protected static $CAN_UPDATE_USER = 1;
public function authenticate($username, $password)
{
$encrypted_old = md5($password);
$row = dbFetchRow('SELECT username,password FROM `users` WHERE `username`= ?', array($username), true);
if ($row['username'] && $row['username'] == $username) {
// Migrate from old, unhashed password
if ($row['password'] == $encrypted_old) {
$row_type = dbFetchRow('DESCRIBE users password');
if ($row_type['Type'] == 'varchar(34)') {
$this->changePassword($username, $password);
}
return true;
} elseif (substr($row['password'], 0, 3) == '$1$') {
$row_type = dbFetchRow('DESCRIBE users password');
if ($row_type['Type'] == 'varchar(60)') {
if ($row['password'] == crypt($password, $row['password'])) {
$this->changePassword($username, $password);
}
}
}
$hasher = new PasswordHash(8, false);
if ($hasher->CheckPassword($password, $row['password'])) {
return true;
}
}//end if
throw new AuthenticationException();
}//end authenticate()
public function reauthenticate($sess_id, $token)
{
return $this->checkRememberMe($sess_id, $token);
}//end reauthenticate()
public function canUpdatePasswords($username = '')
{
/*
* By default allow the password to be modified, unless the existing
* user is explicitly prohibited to do so.
*/
if (empty($username) || !$this->userExists($username)) {
return 1;
} else {
return dbFetchCell('SELECT can_modify_passwd FROM users WHERE username = ?', array($username), true);
}
}//end passwordscanchange()
/**
* From: http://code.activestate.com/recipes/576894-generate-a-salt/
* This public function generates a password salt as a string of x (default = 15) characters
* ranging from a-zA-Z0-9.
* @param $max integer The number of characters in the string
* @author AfroSoft <scripts@afrosoft.co.cc>
*/
public function generateSalt($max = 15)
{
$characterList = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$i = 0;
$salt = '';
do {
$salt .= $characterList{mt_rand(0, strlen($characterList))};
$i++;
} while ($i <= $max);
return $salt;
}//end generateSalt()
public function changePassword($username, $password)
{
$hasher = new PasswordHash(8, false);
$encrypted = $hasher->HashPassword($password);
return dbUpdate(array('password' => $encrypted), 'users', '`username` = ?', array($username));
}//end changepassword()
public function addUser($username, $password, $level = 0, $email = '', $realname = '', $can_modify_passwd = 1, $description = '')
{
if (!$this->userExists($username)) {
$hasher = new PasswordHash(8, false);
$encrypted = $hasher->HashPassword($password);
$userid = dbInsert(array('username' => $username, 'password' => $encrypted, 'level' => $level, 'email' => $email, 'realname' => $realname, 'can_modify_passwd' => $can_modify_passwd, 'descr' => $description), 'users');
if ($userid == false) {
return false;
} else {
foreach (dbFetchRows('select notifications.* from notifications where not exists( select 1 from notifications_attribs where notifications.notifications_id = notifications_attribs.notifications_id and notifications_attribs.user_id = ?) order by notifications.notifications_id desc', array($userid)) as $notif) {
dbInsert(array('notifications_id'=>$notif['notifications_id'],'user_id'=>$userid,'key'=>'read','value'=>1), 'notifications_attribs');
}
}
return $userid;
} else {
return false;
}
}//end adduser()
public function userExists($username, $throw_exception = false)
{
$return = @dbFetchCell('SELECT COUNT(*) FROM users WHERE username = ?', array($username), true);
return $return;
}//end userExists()
public function getUserlevel($username)
{
return dbFetchCell('SELECT `level` FROM `users` WHERE `username` = ?', array($username), true);
}//end getUserlevel()
public function getUserid($username)
{
return dbFetchCell('SELECT `user_id` FROM `users` WHERE `username` = ?', array($username), true);
}//end getUserid()
public function deleteUser($userid)
{
dbDelete('bill_perms', '`user_id` = ?', array($userid));
dbDelete('devices_perms', '`user_id` = ?', array($userid));
dbDelete('ports_perms', '`user_id` = ?', array($userid));
dbDelete('users_prefs', '`user_id` = ?', array($userid));
dbDelete('users', '`user_id` = ?', array($userid));
return dbDelete('users', '`user_id` = ?', array($userid));
}//end deluser()
public function getUserlist()
{
return dbFetchRows('SELECT * FROM `users` ORDER BY `username`');
}//end getUserlist()
public function getUser($user_id)
{
return dbFetchRow('SELECT * FROM `users` WHERE `user_id` = ?', array($user_id), true);
}//end getUser()
public function updateUser($user_id, $realname, $level, $can_modify_passwd, $email)
{
dbUpdate(array('realname' => $realname, 'level' => $level, 'can_modify_passwd' => $can_modify_passwd, 'email' => $email), 'users', '`user_id` = ?', array($user_id));
}//end updateUser()
}

View File

@@ -0,0 +1,111 @@
<?php
namespace LibreNMS\Authentication;
use Dapphp\Radius\Radius;
use LibreNMS\Config;
use LibreNMS\Exceptions\AuthenticationException;
use Phpass\PasswordHash;
class RadiusAuthorizer extends AuthorizerBase
{
protected static $HAS_AUTH_USERMANAGEMENT = 1;
protected static $CAN_UPDATE_USER = 1;
/** @var Radius $radius */
protected $radius;
public function __construct()
{
$this->radius = new Radius(Config::get('radius.hostname'), Config::get('radius.secret'), Config::get('radius.suffix'), Config::get('radius.timeout'), Config::get('radius.port'));
}
public function authenticate($username, $password)
{
global $debug;
if (empty($username)) {
throw new AuthenticationException('Username is required');
}
if ($debug) {
$this->radius->setDebug(true);
}
if ($this->radius->accessRequest($username, $password) === true) {
$this->addUser($username, $password);
return true;
}
throw new AuthenticationException();
}
public function addUser($username, $password, $level = 1, $email = '', $realname = '', $can_modify_passwd = 0, $description = '')
{
// Check to see if user is already added in the database
if (!$this->userExists($username)) {
$hasher = new PasswordHash(8, false);
$encrypted = $hasher->HashPassword($password);
if (Config::get('radius.default_level') > 0) {
$level = Config::get('radius.default_level');
}
$userid = dbInsert(array('username' => $username, 'password' => $encrypted, 'realname' => $realname, 'email' => $email, 'descr' => $description, 'level' => $level, 'can_modify_passwd' => $can_modify_passwd), 'users');
if ($userid == false) {
return false;
} else {
foreach (dbFetchRows('select notifications.* from notifications where not exists( select 1 from notifications_attribs where notifications.notifications_id = notifications_attribs.notifications_id and notifications_attribs.user_id = ?) order by notifications.notifications_id desc', array($userid)) as $notif) {
dbInsert(array('notifications_id'=>$notif['notifications_id'],'user_id'=>$userid,'key'=>'read','value'=>1), 'notifications_attribs');
}
}
return $userid;
} else {
return false;
}
}
public function userExists($username, $throw_exception = false)
{
return dbFetchCell('SELECT COUNT(*) FROM users WHERE username = ?', array($username), true);
}
public function getUserlevel($username)
{
return dbFetchCell('SELECT `level` FROM `users` WHERE `username` = ?', array($username), true);
}
public function getUserid($username)
{
return dbFetchCell('SELECT `user_id` FROM `users` WHERE `username` = ?', array($username), true);
}
public function deleteUser($userid)
{
dbDelete('bill_perms', '`user_id` = ?', array($userid));
dbDelete('devices_perms', '`user_id` = ?', array($userid));
dbDelete('ports_perms', '`user_id` = ?', array($userid));
dbDelete('users_prefs', '`user_id` = ?', array($userid));
dbDelete('users', '`user_id` = ?', array($userid));
return dbDelete('users', '`user_id` = ?', array($userid));
}
public function getUserlist()
{
return dbFetchRows('SELECT * FROM `users`');
}
public function getUser($user_id)
{
return dbFetchRow('SELECT * FROM `users` WHERE `user_id` = ?', array($user_id), true);
}
public function updateUser($user_id, $realname, $level, $can_modify_passwd, $email)
{
dbUpdate(array('realname' => $realname, 'level' => $level, 'can_modify_passwd' => $can_modify_passwd, 'email' => $email), 'users', '`user_id` = ?', array($user_id));
}
}

View File

@@ -28,6 +28,7 @@
namespace LibreNMS\Authentication;
use LibreNMS\Config;
use LibreNMS\Exceptions\AuthenticationException;
class TwoFactor
@@ -129,8 +130,6 @@ class TwoFactor
*/
public static function getForm($form_tags = true)
{
global $config;
$ret = '';
if ($form_tags) {
@@ -140,7 +139,7 @@ class TwoFactor
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<img src="' . $config['title_image'] . '">
<img src="' . Config::get('title_image') . '">
</h3>
</div>
<div class="panel-body">
@@ -179,7 +178,7 @@ class TwoFactor
*/
public static function showForm()
{
global $twofactorform, $config;
global $twofactorform;
$twofactor = get_user_pref('twofactor');
@@ -191,10 +190,10 @@ class TwoFactor
// lockout the user if there are too many failures
if ($twofactor['fails'] >= 3) {
if (!$config['twofactor_lock']) {
if (!Config::get('twofactor_lock')) {
throw new AuthenticationException('Too many two-factor failures, please contact administrator.');
} elseif ((time() - $twofactor['last']) < $config['twofactor_lock']) {
$msg = "Too many two-factor failures, please wait " . $config['twofactor_lock'] . " seconds";
} elseif ((time() - $twofactor['last']) < Config::get('twofactor_lock')) {
$msg = "Too many two-factor failures, please wait " . Config::get('twofactor_lock') . " seconds";
throw new AuthenticationException($msg);
}
}

View File

@@ -20,6 +20,7 @@
namespace LibreNMS;
use LibreNMS\Authentication\Auth;
use LibreNMS\Exceptions\DatabaseConnectException;
class IRCBot
@@ -539,15 +540,16 @@ class IRCBot
private function hostAuth()
{
global $authorizer;
foreach ($this->config['irc_auth'] as $nms_user => $hosts) {
foreach ($hosts as $host) {
$host = preg_replace("/\*/", ".*", $host);
if (preg_match("/$host/", $this->getUserHost($this->data))) {
$user_id = get_userid(mres($nms_user));
$user = get_user($user_id);
$user_id = Auth::get()->getUserid(mres($nms_user));
$user = Auth::get()->getUser($user_id);
$this->user['name'] = $user['username'];
$this->user['id'] = $user_id;
$this->user['level'] = get_userlevel($user['username']);
$this->user['level'] = Auth::get()->getUserlevel($user['username']);
$this->user['expire'] = (time() + ($this->config['irc_authtime'] * 3600));
if ($this->user['level'] < 5) {
foreach (dbFetchRows('SELECT device_id FROM devices_perms WHERE user_id = ?', array($this->user['id'])) as $tmp) {
@@ -577,12 +579,13 @@ class IRCBot
private function _auth($params)
{
global $authorizer;
$params = explode(' ', $params, 2);
if (strlen($params[0]) == 64) {
if ($this->tokens[$this->getUser($this->data)] == $params[0]) {
$this->user['expire'] = (time() + ($this->config['irc_authtime'] * 3600));
$tmp_user = get_user($this->user['id']);
$tmp = get_userlevel($tmp_user['username']);
$tmp_user = Auth::get()->getUser($this->user['id']);
$tmp = Auth::get()->getUserlevel($tmp_user['username']);
$this->user['level'] = $tmp;
if ($this->user['level'] < 5) {
foreach (dbFetchRows('SELECT device_id FROM devices_perms WHERE user_id = ?', array($this->user['id'])) as $tmp) {
@@ -599,8 +602,8 @@ class IRCBot
return $this->respond('Nope.');
}
} else {
$user_id = get_userid(mres($params[0]));
$user = get_user($user_id);
$user_id = Auth::get()->getUserid(mres($params[0]));
$user = Auth::get()->getUser($user_id);
if ($user['email'] && $user['username'] == $params[0]) {
$token = hash('gost', openssl_random_pseudo_bytes(1024));
$this->tokens[$this->getUser($this->data)] = $token;

View File

@@ -0,0 +1,170 @@
<?php
namespace LibreNMS\Interfaces\Authentication;
use LibreNMS\Exceptions\AuthenticationException;
interface Authorizer
{
/**
* Authenticate the user and password.
* Some Authorizer methods may only check username.
*
* @param $username
* @param $password
* @return true throws an Exception on failure
* @throws AuthenticationException thrown if the username or password is invalid
*/
public function authenticate($username, $password);
/**
* Check for cookie token to see if this is a valid saved session
* Authorizers should check if the user is still valid then return checkRememberMe()
*
* @param int $sess_id
* @param string $token
* @return bool
* @throws AuthenticationException thrown if the cookie or user is invalid
*/
public function reauthenticate($sess_id, $token);
/**
* Check if a $username exists.
*
* @param string $username
* @param bool $throw_exception If this is enabled instead of returning false, this will throw an exception.
* @return bool
*/
public function userExists($username, $throw_exception = false);
/**
* Get the userlevel of $username
*
* @param string $username The username to check
* @return int
*/
public function getUserlevel($username);
/**
* Get the user_id of $username
*
* @param string $username
* @return int
*/
public function getUserid($username);
/**
* Get an array describing this $user_id.
*
* It should contain the fields:
* user_id
* username
* realname
* email
* descr
* level
* can_modify_passwd
*
* @param int $user_id
* @return array
*/
public function getUser($user_id);
/**
* Add a new user.
*
* @param string $username
* @param string $password
* @param int $level
* @param string $email
* @param string $realname
* @param int $can_modify_passwd If this user is allowed to edit their password
* @param string $description
* @return int|false Returns the added user_id or false if adding failed
*/
public function addUser($username, $password, $level = 0, $email = '', $realname = '', $can_modify_passwd = 0, $description = '');
/**
* Update the some of the fields of a user
*
* @param int $user_id The user_id to update
* @param string $realname
* @param int $level
* @param int $can_modify_passwd
* @param string $email
* @return bool If the update was successful
*/
public function updateUser($user_id, $realname, $level, $can_modify_passwd, $email);
/**
* @param string $username The $username to update
* @param string $newpassword
* @return bool If the update was successful
*/
public function changePassword($username, $newpassword);
/**
* Delete a user.
*
* @param int $user_id
* @return bool If the deletion was successful
*/
public function deleteUser($user_id);
/**
* Get a list of all users in this Authorizer
* !Warning! this could be very slow for some Authorizer types or configurations
*
* @return array
*/
public function getUserlist();
/**
* Check if this Authorizer can add or remove users.
* You must also check canUpdateUsers() to see if it can edit users.
* You must check canUpdatePasswords() to see if it can set passwords.
*
* @return bool
*/
public function canManageUsers();
/**
* Check if this Authorizer can modify users.
*
* @return bool
*/
public function canUpdateUsers();
/**
* Check if this Authorizer can set new passwords.
*
* @param string $username Optionally, check if $username can set their own password
* @return bool
*/
public function canUpdatePasswords($username = '');
/**
* Log out the user, unset cookies, destroy the session
*
* @param string $message The logout message.
*/
public function logOutUser($message = 'Logged Out');
/**
* Log in the user and set up a few login tasks
* $_SESSION['username'] must be set prior to calling this function
* If twofactor authentication is enabled, it will be checked here.
*
* If everything goes well, $_SESSION['authenticated'] will be true after this function completes.
* @return bool If the user was successfully logged in.
* @throws AuthenticationException if anything failed why trying to log in
*/
public function logInUser();
/**
* Check if the session is authenticated
*
* @return bool
*/
public function sessionAuthenticated();
}