mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
Gut dbFacile mres() now does nothing... could have some vulnerable queries, but most are ok TODO - [x] IRC client broke - [x] Install broke DO NOT DELETE THIS TEXT #### Please note > Please read this information carefully. You can run `./scripts/pre-commit.php` to check your code before submitting. - [x] Have you followed our [code guidelines?](http://docs.librenms.org/Developing/Code-Guidelines/) #### Testers If you would like to test this pull request then please run: `./scripts/github-apply <pr_id>`, i.e `./scripts/github-apply 5926`
513 lines
16 KiB
PHP
513 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* AuthSSO.php
|
|
*
|
|
* -Description-
|
|
*
|
|
* 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 https://librenms.org
|
|
* @copyright 2017 Adam Bishop
|
|
* @author Adam Bishop <adam@omega.org.uk>
|
|
*/
|
|
|
|
namespace LibreNMS\Tests;
|
|
|
|
use LibreNMS\Authentication\Auth;
|
|
use LibreNMS\Exceptions\AuthenticationException;
|
|
|
|
class AuthSSOTest extends DBTestCase
|
|
{
|
|
private $last_user = null;
|
|
private $original_auth_mech = null;
|
|
private $server;
|
|
|
|
public function setUp()
|
|
{
|
|
parent::setUp();
|
|
global $config;
|
|
|
|
$this->original_auth_mech = $config['auth_mechanism'];
|
|
$config['auth_mechanism'] = 'sso';
|
|
|
|
$this->server = $_SERVER;
|
|
}
|
|
|
|
// Set up an SSO config for tests
|
|
public function basicConfig()
|
|
{
|
|
global $config;
|
|
|
|
$config['sso']['mode'] = 'env';
|
|
$config['sso']['create_users'] = true;
|
|
$config['sso']['update_users'] = true;
|
|
$config['sso']['trusted_proxies'] = array('127.0.0.1', '::1');
|
|
$config['sso']['user_attr'] = 'REMOTE_USER';
|
|
$config['sso']['realname_attr'] = 'displayName';
|
|
$config['sso']['email_attr'] = 'mail';
|
|
$config['sso']['descr_attr'] = null;
|
|
$config['sso']['level_attr'] = null;
|
|
$config['sso']['group_strategy'] = 'static';
|
|
$config['sso']['group_attr'] = 'member';
|
|
$config['sso']['group_filter'] = '/(.*)/i';
|
|
$config['sso']['group_delimiter'] = ';';
|
|
$config['sso']['group_level_map'] = null;
|
|
$config['sso']['static_level'] = -1;
|
|
}
|
|
|
|
// Set up $_SERVER in env mode
|
|
public function basicEnvironmentEnv()
|
|
{
|
|
global $config;
|
|
unset($_SERVER);
|
|
|
|
$config['sso']['mode'] = 'env';
|
|
|
|
$_SERVER['REMOTE_ADDR'] = '::1';
|
|
$_SERVER['REMOTE_USER'] = 'test';
|
|
|
|
$_SERVER['mail'] = 'test@example.org';
|
|
$_SERVER['displayName'] = bin2hex(openssl_random_pseudo_bytes(16));
|
|
}
|
|
|
|
|
|
// Set up $_SERVER in header mode
|
|
public function basicEnvironmentHeader()
|
|
{
|
|
global $config;
|
|
unset($_SERVER);
|
|
|
|
$config['sso']['mode'] = 'header';
|
|
|
|
$_SERVER['REMOTE_ADDR'] = '::1';
|
|
$_SERVER['REMOTE_USER'] = bin2hex(openssl_random_pseudo_bytes(16));
|
|
|
|
$_SERVER['HTTP_MAIL'] = 'test@example.org';
|
|
$_SERVER['HTTP_DISPLAYNAME'] = 'Test User';
|
|
}
|
|
|
|
public function makeBreakUser()
|
|
{
|
|
$this->breakUser();
|
|
|
|
$u = bin2hex(openssl_random_pseudo_bytes(16));
|
|
$this->last_user = $u;
|
|
$_SERVER['REMOTE_USER'] = $u;
|
|
|
|
return $u;
|
|
}
|
|
|
|
public function breakUser()
|
|
{
|
|
$a = Auth::reset();
|
|
|
|
if ($this->last_user !== null) {
|
|
$r = $a->deleteUser($a->getUserid($this->last_user));
|
|
$this->last_user = null;
|
|
return $r;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Excercise general auth flow
|
|
public function testValidAuthNoCreateUpdate()
|
|
{
|
|
global $config;
|
|
|
|
$this->basicConfig();
|
|
$a = Auth::reset();
|
|
|
|
$config['sso']['create_users'] = false;
|
|
$config['sso']['update_users'] = false;
|
|
|
|
// Create a random username and store it with the defaults
|
|
$this->basicEnvironmentEnv();
|
|
$user = $this->makeBreakUser();
|
|
$this->assertTrue($a->authenticate($user, null));
|
|
|
|
// Retrieve it and validate
|
|
$dbuser = $a->getUser($a->getUserid($user));
|
|
$this->assertFalse($a->authSSOGetAttr($config['sso']['realname_attr']) === $dbuser['realname']);
|
|
$this->assertFalse($dbuser['level'] === "0");
|
|
$this->assertFalse($a->authSSOGetAttr($config['sso']['email_attr']) === $dbuser['email']);
|
|
}
|
|
|
|
// Excercise general auth flow with creation enabled
|
|
public function testValidAuthCreateOnly()
|
|
{
|
|
global $config;
|
|
|
|
$this->basicConfig();
|
|
$a = Auth::reset();
|
|
|
|
$config['sso']['create_users'] = true;
|
|
$config['sso']['update_users'] = false;
|
|
|
|
// Create a random username and store it with the defaults
|
|
$this->basicEnvironmentEnv();
|
|
$user = $this->makeBreakUser();
|
|
$this->assertTrue($a->authenticate($user, null));
|
|
|
|
// Retrieve it and validate
|
|
$dbuser = $a->getUser($a->getUserid($user));
|
|
$this->assertTrue($a->authSSOGetAttr($config['sso']['realname_attr']) === $dbuser['realname']);
|
|
$this->assertTrue($dbuser['level'] == -1);
|
|
$this->assertTrue($a->authSSOGetAttr($config['sso']['email_attr']) === $dbuser['email']);
|
|
|
|
// Change a few things and reauth
|
|
$_SERVER['mail'] = 'test@example.net';
|
|
$_SERVER['displayName'] = 'Testier User';
|
|
$config['sso']['static_level'] = 10;
|
|
$this->assertTrue($a->authenticate($user, null));
|
|
|
|
// Retrieve it and validate the update was not persisted
|
|
$dbuser = $a->getUser($a->getUserid($user));
|
|
$this->assertFalse($a->authSSOGetAttr($config['sso']['realname_attr']) === $dbuser['realname']);
|
|
$this->assertFalse($dbuser['level'] === "10");
|
|
$this->assertFalse($a->authSSOGetAttr($config['sso']['email_attr']) === $dbuser['email']);
|
|
}
|
|
|
|
// Excercise general auth flow with updates enabled
|
|
public function testValidAuthUpdate()
|
|
{
|
|
global $config;
|
|
|
|
$this->basicConfig();
|
|
$a = Auth::reset();
|
|
|
|
// Create a random username and store it with the defaults
|
|
$this->basicEnvironmentEnv();
|
|
$user = $this->makeBreakUser();
|
|
$this->assertTrue($a->authenticate($user, null));
|
|
|
|
// Change a few things and reauth
|
|
$_SERVER['mail'] = 'test@example.net';
|
|
$_SERVER['displayName'] = 'Testier User';
|
|
$config['sso']['static_level'] = 10;
|
|
$this->assertTrue($a->authenticate($user, null));
|
|
|
|
// Retrieve it and validate the update persisted
|
|
$dbuser = $a->getUser($a->getUserid($user));
|
|
$this->assertTrue($a->authSSOGetAttr($config['sso']['realname_attr']) === $dbuser['realname']);
|
|
$this->assertTrue($dbuser['level'] == 10);
|
|
$this->assertTrue($a->authSSOGetAttr($config['sso']['email_attr']) === $dbuser['email']);
|
|
}
|
|
|
|
// Check some invalid authentication modes
|
|
public function testBadAuth()
|
|
{
|
|
global $config;
|
|
|
|
$this->basicConfig();
|
|
$a = Auth::reset();
|
|
|
|
$this->basicEnvironmentEnv();
|
|
unset($_SERVER);
|
|
|
|
$this->setExpectedException('LibreNMS\Exceptions\AuthenticationException');
|
|
$a->authenticate(null, null);
|
|
|
|
$this->basicEnvironmentHeader();
|
|
unset($_SERVER);
|
|
|
|
$this->setExpectedException('LibreNMS\Exceptions\AuthenticationException');
|
|
$a->authenticate(null, null);
|
|
}
|
|
|
|
// Test some missing attributes
|
|
public function testNoAttribute()
|
|
{
|
|
global $config;
|
|
|
|
$this->basicConfig();
|
|
$a = Auth::reset();
|
|
|
|
$this->basicEnvironmentEnv();
|
|
unset($_SERVER['displayName']);
|
|
unset($_SERVER['mail']);
|
|
|
|
$this->assertTrue($a->authenticate($this->makeBreakUser(), null));
|
|
|
|
$this->basicEnvironmentHeader();
|
|
unset($_SERVER['HTTP_DISPLAYNAME']);
|
|
unset($_SERVER['HTTP_MAIL']);
|
|
|
|
$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->assertTrue($a->canUpdatePasswords() === 0);
|
|
$this->assertTrue($a->changePassword(null, null) === 0);
|
|
$this->assertTrue($a->canManageUsers() === 1);
|
|
$this->assertTrue($a->canUpdateUsers() === 1);
|
|
$this->assertTrue($a->authIsExternal() === 1);
|
|
}
|
|
|
|
/* Everything from here comprises of targeted tests to excercise single methods */
|
|
|
|
public function testGetExternalUserName()
|
|
{
|
|
global $config;
|
|
|
|
$this->basicConfig();
|
|
$a = Auth::reset();
|
|
|
|
$this->basicEnvironmentEnv();
|
|
$this->assertInternalType('string', $a->getExternalUsername());
|
|
|
|
// Missing
|
|
unset($_SERVER['REMOTE_USER']);
|
|
$this->assertNull($a->getExternalUsername());
|
|
$this->basicEnvironmentEnv();
|
|
|
|
// Missing pointer to attribute
|
|
unset($config['sso']['user_attr']);
|
|
$this->assertNull($a->getExternalUsername());
|
|
$this->basicEnvironmentEnv();
|
|
|
|
// Non-existant attribute
|
|
$config['sso']['user_attr'] = 'foobar';
|
|
$this->assertNull($a->getExternalUsername());
|
|
$this->basicEnvironmentEnv();
|
|
|
|
// null pointer to attribute
|
|
$config['sso']['user_attr'] = null;
|
|
$this->assertNull($a->getExternalUsername());
|
|
$this->basicEnvironmentEnv();
|
|
|
|
// null attribute
|
|
$config['sso']['user_attr'] = 'REMOTE_USER';
|
|
$_SERVER['REMOTE_USER'] = null;
|
|
$this->assertNull($a->getExternalUsername());
|
|
}
|
|
|
|
public function testGetAttr()
|
|
{
|
|
global $config;
|
|
$a = Auth::reset();
|
|
|
|
$_SERVER['HTTP_VALID_ATTR'] = 'string';
|
|
$_SERVER['alsoVALID-ATTR'] = 'otherstring';
|
|
|
|
$config['sso']['mode'] = 'env';
|
|
$this->assertNull($a->authSSOGetAttr('foobar'));
|
|
$this->assertNull($a->authSSOGetAttr(null));
|
|
$this->assertNull($a->authSSOGetAttr(1));
|
|
$this->assertInternalType('string', $a->authSSOGetAttr('alsoVALID-ATTR'));
|
|
$this->assertInternalType('string', $a->authSSOGetAttr('HTTP_VALID_ATTR'));
|
|
|
|
$config['sso']['mode'] = 'header';
|
|
$this->assertNull($a->authSSOGetAttr('foobar'));
|
|
$this->assertNull($a->authSSOGetAttr(null));
|
|
$this->assertNull($a->authSSOGetAttr(1));
|
|
$this->assertNull($a->authSSOGetAttr('alsoVALID-ATTR'));
|
|
$this->assertInternalType('string', $a->authSSOGetAttr('VALID-ATTR'));
|
|
}
|
|
|
|
public function testTrustedProxies()
|
|
{
|
|
global $config;
|
|
$a = Auth::reset();
|
|
|
|
$config['sso']['trusted_proxies'] = array('127.0.0.1', '::1', '2001:630:50::/48', '8.8.8.0/25');
|
|
|
|
// v4 valid CIDR
|
|
$_SERVER['REMOTE_ADDR'] = '8.8.8.8';
|
|
$this->assertTrue($a->authSSOProxyTrusted());
|
|
|
|
// v4 valid single
|
|
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
|
$this->assertTrue($a->authSSOProxyTrusted());
|
|
|
|
// v4 invalid CIDR
|
|
$_SERVER['REMOTE_ADDR'] = '9.8.8.8';
|
|
$this->assertFalse($a->authSSOProxyTrusted());
|
|
|
|
// v6 valid CIDR
|
|
$_SERVER['REMOTE_ADDR'] = '2001:630:50:baad:beef:feed:face:cafe';
|
|
$this->assertTrue($a->authSSOProxyTrusted());
|
|
|
|
// v6 valid single
|
|
$_SERVER['REMOTE_ADDR'] = '::1';
|
|
$this->assertTrue($a->authSSOProxyTrusted());
|
|
|
|
// v6 invalid CIDR
|
|
$_SERVER['REMOTE_ADDR'] = '2600::';
|
|
$this->assertFalse($a->authSSOProxyTrusted());
|
|
|
|
// Not an IP
|
|
$_SERVER['REMOTE_ADDR'] = 16;
|
|
$this->assertFalse($a->authSSOProxyTrusted());
|
|
|
|
//null
|
|
$_SERVER['REMOTE_ADDR'] = null;
|
|
$this->assertFalse($a->authSSOProxyTrusted());
|
|
|
|
// Invalid String
|
|
$_SERVER['REMOTE_ADDR'] = 'Not an IP address at all, but maybe PHP will end up type juggling somehow';
|
|
$this->assertFalse($a->authSSOProxyTrusted());
|
|
|
|
// Not a list
|
|
$config['sso']['trusted_proxies'] = '8.8.8.0/25';
|
|
$_SERVER['REMOTE_ADDR'] = '8.8.8.8';
|
|
$this->assertFalse($a->authSSOProxyTrusted());
|
|
|
|
// Unset
|
|
unset($_SERVER['REMOTE_ADDR']);
|
|
$this->assertFalse($a->authSSOProxyTrusted());
|
|
}
|
|
|
|
public function testLevelCaulculationFromAttr()
|
|
{
|
|
global $config;
|
|
$a = Auth::reset();
|
|
|
|
$config['sso']['mode'] = 'env';
|
|
$config['sso']['group_strategy'] = 'attribute';
|
|
|
|
//Integer
|
|
$config['sso']['level_attr'] = 'level';
|
|
$_SERVER['level'] = 9;
|
|
$this->assertTrue($a->authSSOCalculateLevel() === 9);
|
|
|
|
//String
|
|
$config['sso']['level_attr'] = 'level';
|
|
$_SERVER['level'] = "9";
|
|
$this->assertTrue($a->authSSOCalculateLevel() === 9);
|
|
|
|
//Invalid String
|
|
$config['sso']['level_attr'] = 'level';
|
|
$_SERVER['level'] = 'foobar';
|
|
$this->setExpectedException('LibreNMS\Exceptions\AuthenticationException');
|
|
$a->authSSOCalculateLevel();
|
|
|
|
//null
|
|
$config['sso']['level_attr'] = 'level';
|
|
$_SERVER['level'] = null;
|
|
$this->setExpectedException('LibreNMS\Exceptions\AuthenticationException');
|
|
$a->authSSOCalculateLevel();
|
|
|
|
//Unset pointer
|
|
unset($config['sso']['level_attr']);
|
|
$_SERVER['level'] = "9";
|
|
$this->setExpectedException('LibreNMS\Exceptions\AuthenticationException');
|
|
$a->authSSOCalculateLevel();
|
|
|
|
//Unset attr
|
|
$config['sso']['level_attr'] = 'level';
|
|
unset($_SERVER['level']);
|
|
$this->setExpectedException('LibreNMS\Exceptions\AuthenticationException');
|
|
$a->authSSOCalculateLevel();
|
|
}
|
|
|
|
public function testGroupParsing()
|
|
{
|
|
global $config;
|
|
|
|
$this->basicConfig();
|
|
$a = Auth::reset();
|
|
|
|
$this->basicEnvironmentEnv();
|
|
|
|
$config['sso']['group_strategy'] = 'map';
|
|
$config['sso']['group_delimiter'] = ';';
|
|
$config['sso']['group_attr'] = 'member';
|
|
$config['sso']['group_level_map'] = array('librenms-admins' => 10, 'librenms-readers' => 1, 'librenms-billingcontacts' => 5);
|
|
$_SERVER['member'] = "librenms-admins;librenms-readers;librenms-billingcontacts;unrelatedgroup;confluence-admins";
|
|
|
|
// Valid options
|
|
$this->assertTrue($a->authSSOParseGroups() === 10);
|
|
|
|
// No match
|
|
$_SERVER['member'] = "confluence-admins";
|
|
$this->assertTrue($a->authSSOParseGroups() === 0);
|
|
|
|
// Delimiter only
|
|
$_SERVER['member'] = ";;;;";
|
|
$this->assertTrue($a->authSSOParseGroups() === 0);
|
|
|
|
// Empty
|
|
$_SERVER['member'] = "";
|
|
$this->assertTrue($a->authSSOParseGroups() === 0);
|
|
|
|
// Null
|
|
$_SERVER['member'] = null;
|
|
$this->assertTrue($a->authSSOParseGroups() === 0);
|
|
|
|
// Unset
|
|
unset($_SERVER['member']);
|
|
$this->assertTrue($a->authSSOParseGroups() === 0);
|
|
|
|
$_SERVER['member'] = "librenms-admins;librenms-readers;librenms-billingcontacts;unrelatedgroup;confluence-admins";
|
|
|
|
// Empty
|
|
$config['sso']['group_level_map'] = array();
|
|
$this->assertTrue($a->authSSOParseGroups() === 0);
|
|
|
|
// Not associative
|
|
$config['sso']['group_level_map'] = array('foo', 'bar', 'librenms-admins');
|
|
$this->assertTrue($a->authSSOParseGroups() === 0);
|
|
|
|
// Null
|
|
$config['sso']['group_level_map'] = null;
|
|
$this->assertTrue($a->authSSOParseGroups() === 0);
|
|
|
|
// Unset
|
|
unset($config['sso']['group_level_map']);
|
|
$this->assertTrue($a->authSSOParseGroups() === 0);
|
|
|
|
// No delimiter
|
|
unset($config['sso']['group_delimiter']);
|
|
$this->assertTrue($a->authSSOParseGroups() === 0);
|
|
|
|
// Test group filtering by regex
|
|
$config['sso']['group_filter'] = "/confluence-(.*)/i";
|
|
$config['sso']['group_delimiter'] = ';';
|
|
$config['sso']['group_level_map'] = array('librenms-admins' => 10, 'librenms-readers' => 1, 'librenms-billingcontacts' => 5, 'confluence-admins' => 7);
|
|
$this->assertTrue($a->authSSOParseGroups() === 7);
|
|
|
|
// Test group filtering by empty regex
|
|
$config['sso']['group_filter'] = "";
|
|
$this->assertTrue($a->authSSOParseGroups() === 10);
|
|
|
|
// Test group filtering by null regex
|
|
$config['sso']['group_filter'] = null;
|
|
$this->assertTrue($a->authSSOParseGroups() === 10);
|
|
}
|
|
|
|
public function tearDown()
|
|
{
|
|
parent::tearDown();
|
|
global $config;
|
|
|
|
$config['auth_mechanism'] = $this->original_auth_mech;
|
|
unset($config['sso']);
|
|
$this->breakUser();
|
|
|
|
$_SERVER = $this->server;
|
|
}
|
|
}
|