refactor: Share code between all mysql based authorizers (#8174)

* Share code between all mysql based authorizers
I plan to update the mysql password encryption and this will allow the code to be changed in a single location.
It also reduces a lot of duplication.

* Fix tests, I suspect reauthenticate will work for these...
Do not allow password updates for several authorizers
This commit is contained in:
Tony Murray
2018-02-06 15:20:34 -06:00
committed by Neil Lathwood
parent f706d0ab41
commit 5141fc4872
10 changed files with 55 additions and 279 deletions

View File

@@ -5,10 +5,12 @@ namespace LibreNMS\Authentication;
use LibreNMS\Config;
use LibreNMS\Exceptions\AuthenticationException;
class ADAuthorizationAuthorizer extends AuthorizerBase
class ADAuthorizationAuthorizer extends MysqlAuthorizer
{
protected $ldap_connection;
protected static $AUTH_IS_EXTERNAL = 1;
protected static $CAN_UPDATE_PASSWORDS = 0;
protected $ldap_connection;
public function __construct()
{
@@ -85,12 +87,6 @@ class ADAuthorizationAuthorizer extends AuthorizerBase
}
}
protected function userExistsInDb($username)
{
$return = dbFetchCell('SELECT COUNT(*) FROM users WHERE username = ?', array($username));
return $return;
}
public function userExists($username, $throw_exception = false)
{
if ($this->authLdapSessionCacheGet('user_exists')) {
@@ -176,18 +172,6 @@ class ADAuthorizationAuthorizer extends AuthorizerBase
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();
@@ -233,19 +217,6 @@ class ADAuthorizationAuthorizer extends AuthorizerBase
return $userlist;
}
public function getUser($user_id)
{
// not supported so return 0
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, 'can_modify_passwd' => $can_modify_passwd, 'email' => $email), 'users', '`user_id` = ?', array($user_id));
}
protected function getFullname($username)
{
$attributes = array('name');

View File

@@ -10,6 +10,8 @@ use LibreNMS\Exceptions\AuthenticationException;
class ActiveDirectoryAuthorizer extends AuthorizerBase
{
protected static $CAN_UPDATE_PASSWORDS = 0;
protected $ldap_connection;
protected $is_bound = false; // this variable tracks if bind has been called so we don't call it multiple times
@@ -120,12 +122,6 @@ class ActiveDirectoryAuthorizer extends AuthorizerBase
return ($entries["count"] > 0);
}
protected function userExistsInDb($username)
{
$return = dbFetchCell('SELECT COUNT(*) FROM users WHERE username = ?', array($username));
return $return;
}
public function userExists($username, $throw_exception = false)
{
$this->bind(); // make sure we called bind

View File

@@ -33,6 +33,7 @@ abstract class AuthorizerBase implements Authorizer
{
protected static $HAS_AUTH_USERMANAGEMENT = 0;
protected static $CAN_UPDATE_USER = 0;
protected static $CAN_UPDATE_PASSWORDS = 0;
protected static $AUTH_IS_EXTERNAL = 0;
/**
@@ -208,7 +209,7 @@ abstract class AuthorizerBase implements Authorizer
public function canUpdatePasswords($username = '')
{
return 0;
return static::$CAN_UPDATE_PASSWORDS;
}
public function changePassword($username, $newpassword)

View File

@@ -4,12 +4,12 @@ namespace LibreNMS\Authentication;
use LibreNMS\Config;
use LibreNMS\Exceptions\AuthenticationException;
use Phpass\PasswordHash;
class HttpAuthAuthorizer extends AuthorizerBase
class HttpAuthAuthorizer extends MysqlAuthorizer
{
protected static $HAS_AUTH_USERMANAGEMENT = 1;
protected static $CAN_UPDATE_USER = 1;
protected static $CAN_UPDATE_PASSWORDS = 0;
protected static $AUTH_IS_EXTERNAL = 1;
public function authenticate($username, $password)
@@ -21,51 +21,30 @@ class HttpAuthAuthorizer extends AuthorizerBase
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');
if (parent::userExists($username)) {
return true;
}
return dbFetchCell($query, $params) > 0;
if (Config::has('http_auth_guest') && parent::userExists(Config::get('http_auth_guest'))) {
return true;
}
return false;
}
public function getUserlevel($username)
{
$user_level = dbFetchCell('SELECT `level` FROM `users` WHERE `username`=?', array($username));
$user_level = parent::getUserlevel($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 parent::getUserlevel(Config::get('http_auth_guest'));
}
return 0;
@@ -74,34 +53,16 @@ class HttpAuthAuthorizer extends AuthorizerBase
public function getUserid($username)
{
$user_id = dbFetchCell('SELECT `user_id` FROM `users` WHERE `username`=?', array($username));
$user_id = parent::getUserid($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 parent::getUserid(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

@@ -9,6 +9,7 @@ class MysqlAuthorizer extends AuthorizerBase
{
protected static $HAS_AUTH_USERMANAGEMENT = 1;
protected static $CAN_UPDATE_USER = 1;
protected static $CAN_UPDATE_PASSWORDS = 1;
public function authenticate($username, $password)
{
@@ -55,37 +56,22 @@ class MysqlAuthorizer extends AuthorizerBase
* user is explicitly prohibited to do so.
*/
if (empty($username) || !$this->userExists($username)) {
if (!static::$CAN_UPDATE_PASSWORDS) {
return 0;
} elseif (empty($username) || !$this->userExists($username)) {
return 1;
} else {
return dbFetchCell('SELECT can_modify_passwd FROM users WHERE username = ?', array($username));
}
}//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)
{
// check if updating passwords is allowed (mostly for classes that extend this)
if (!static::$CAN_UPDATE_PASSWORDS) {
return 0;
}
$hasher = new PasswordHash(8, false);
$encrypted = $hasher->HashPassword($password);
return dbUpdate(array('password' => $encrypted), 'users', '`username` = ?', array($username));
@@ -113,10 +99,8 @@ class MysqlAuthorizer extends AuthorizerBase
public function userExists($username, $throw_exception = false)
{
$return = @dbFetchCell('SELECT COUNT(*) FROM users WHERE username = ?', array($username));
return $return;
}//end userExists()
return (bool)dbFetchCell('SELECT COUNT(*) FROM users WHERE username = ?', array($username));
}
public function getUserlevel($username)
{

View File

@@ -5,12 +5,12 @@ namespace LibreNMS\Authentication;
use Dapphp\Radius\Radius;
use LibreNMS\Config;
use LibreNMS\Exceptions\AuthenticationException;
use Phpass\PasswordHash;
class RadiusAuthorizer extends AuthorizerBase
class RadiusAuthorizer extends MysqlAuthorizer
{
protected static $HAS_AUTH_USERMANAGEMENT = 1;
protected static $CAN_UPDATE_USER = 1;
protected static $CAN_UPDATE_PASSWORDS = 0;
/** @var Radius $radius */
protected $radius;
@@ -33,79 +33,10 @@ class RadiusAuthorizer extends AuthorizerBase
}
if ($this->radius->accessRequest($username, $password) === true) {
$this->addUser($username, $password);
$this->addUser($username, $password, Config::get('radius.default_level', 1));
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));
}
public function getUserlevel($username)
{
return dbFetchCell('SELECT `level` FROM `users` WHERE `username` = ?', array($username));
}
public function getUserid($username)
{
return dbFetchCell('SELECT `user_id` FROM `users` WHERE `username` = ?', array($username));
}
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));
}
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

@@ -35,10 +35,11 @@ use LibreNMS\Exceptions\InvalidIpException;
*
*/
class SSOAuthorizer extends AuthorizerBase
class SSOAuthorizer extends MysqlAuthorizer
{
protected static $HAS_AUTH_USERMANAGEMENT = 1;
protected static $CAN_UPDATE_USER = 1;
protected static $CAN_UPDATE_PASSWORDS = 0;
protected static $AUTH_IS_EXTERNAL = 1;
public function authenticate($username, $password)
@@ -57,7 +58,7 @@ class SSOAuthorizer extends AuthorizerBase
// User has already been approved by the authenicator so if automatic user create/update is enabled, do it
if (Config::get('sso.create_users') && !$this->userExists($username)) {
$this->addUser($username, $password, $level, $email, $realname, $can_modify_passwd, $description ? $description : 'SSO User');
$this->addUser($username, null, $level, $email, $realname, $can_modify_passwd, $description ? $description : 'SSO User');
} elseif (Config::get('sso.update_users') && $this->userExists($username)) {
$this->updateUser($this->getUserid($username), $realname, $level, $can_modify_passwd, $email);
}
@@ -65,87 +66,6 @@ class SSOAuthorizer extends AuthorizerBase
return true;
}
public function addUser($username, $password, $level = 1, $email = '', $realname = '', $can_modify_passwd = 0, $description = 'SSO User')
{
// Check to see if user is already added in the database
if (!$this->userExists($username)) {
$userid = dbInsert(array('username' => $username, 'password' => null, '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)
{
// A local user is always created, so we query this, as in the event of an admin choosing to manually administer user creation we should return false.
$user = dbFetchCell('SELECT COUNT(*) FROM users WHERE username = ?', array($username));
if ($throw_exception) {
// Hopefully the caller knows what they're doing
throw new AuthenticationException('User not found and invocation requested an exception be thrown');
}
return $user;
}
public function getUserLevel($username)
{
// The user level should always be persisted to the database (and may be managed there) so we again query the database.
return dbFetchCell('SELECT `level` FROM `users` WHERE `username` = ?', array($username));
}
public function getUserid($username)
{
// User ID is obviously unique to LibreNMS, this must be resolved via the database
return dbFetchCell('SELECT `user_id` FROM `users` WHERE `username` = ?', array($username));
}
public function deleteUser($userid)
{
// Clean up the entries persisted to the database
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` ORDER BY `username`');
}
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)
{
// This could, in the worse case, occur on every single pageload, so try and do a bit of optimisation
$user = $this->getUser($user_id);
if ($realname !== $user['realname'] || $level !== $user['level'] || $can_modify_passwd !== $user['can_modify_passwd'] || $email !== $user['email']) {
dbUpdate(array('realname' => $realname, 'level' => $level, 'can_modify_passwd' => $can_modify_passwd, 'email' => $email), 'users', '`user_id` = ?', array($user_id));
}
}
public function getExternalUsername()
{
return $this->authSSOGetAttr(Config::get('sso.user_attr'), '');

View File

@@ -32,7 +32,7 @@ interface Authorizer
* 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.
* @param bool $throw_exception Allows for a message to be sent to callers in case the user does not exist
* @return bool
*/
public function userExists($username, $throw_exception = false);

View File

@@ -26,10 +26,17 @@
namespace LibreNMS\Tests;
use LibreNMS\Authentication\Auth;
use LibreNMS\Exceptions\AuthenticationException;
// Note that as this test set depends on mres(), it is a DBTestCase even though the database is unused
class AuthHTTPTest extends DBTestCase
{
public function testReauthenticate()
{
$this->setExpectedException(AuthenticationException::class);
Auth::reset()->reauthenticate(null, null);
}
// Document the modules current behaviour, so that changes trigger test failures
public function testCapabilityFunctions()
{
@@ -38,7 +45,6 @@ class AuthHTTPTest extends DBTestCase
$a = Auth::reset();
$this->assertFalse($a->reauthenticate(null, null));
$this->assertTrue($a->canUpdatePasswords() === 0);
$this->assertTrue($a->changePassword(null, null) === 0);
$this->assertTrue($a->canManageUsers() === 1);
@@ -49,7 +55,7 @@ class AuthHTTPTest extends DBTestCase
public function testOldBehaviourAgainstCurrent()
{
global $config;
$old_username = null;
$new_username = null;

View File

@@ -26,6 +26,7 @@
namespace LibreNMS\Tests;
use LibreNMS\Authentication\Auth;
use LibreNMS\Exceptions\AuthenticationException;
class AuthSSOTest extends DBTestCase
{
@@ -244,12 +245,17 @@ class AuthSSOTest extends DBTestCase
$this->assertTrue($a->authenticate($this->makeBreakUser(), null));
}
public function testReauthenticate()
{
$this->setExpectedException(AuthenticationException::class);
Auth::reset()->reauthenticate(null, null);
}
// Document the modules current behaviour, so that changes trigger test failures
public function testCapabilityFunctions()
{
$a = Auth::reset();
$this->assertFalse($a->reauthenticate(null, null));
$this->assertTrue($a->canUpdatePasswords() === 0);
$this->assertTrue($a->changePassword(null, null) === 0);
$this->assertTrue($a->canManageUsers() === 1);