Files
librenms-librenms/LibreNMS/Authentication/LdapAuthorizer.php
Tony Murray 32a7c50189 Use Laravel authentication (#8702)
* Use Laravel for authentication
Support legacy auth methods
Always create DB entry for users (segregate by auth method)

Port api auth to Laravel

restrict poller errors to devices the user has access to

Run checks on every page load.  But set a 5 minute (configurable) timer.
Only run some checks if the user is an admin

Move toastr down a few pixels so it isn't as annoying.

Fix menu not loaded on laravel pages when twofactor is enabled for the system, but disabled for the user.
Add two missing menu entries in the laravel menu

Rewrite 2FA code
Simplify some and verify code before applying

Get http-auth working
Handle legacy $_SESSION differently.  Allows Auth::once(), etc to work.

* Fix tests and mysqli extension check

* remove duplicate Toastr messages

* Fix new items

* Rename 266.sql to 267.sql
2018-09-11 07:51:35 -05:00

352 lines
12 KiB
PHP

<?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)) {
$ldap_groups = $this->getGroupList();
if (empty($ldap_groups)) {
// no groups, don't check membership
return true;
} else {
foreach ($ldap_groups as $ldap_group) {
if (Config::get('auth_ldap_userdn') === true) {
$ldap_comparison = ldap_compare(
$connection,
$ldap_group,
Config::get('auth_ldap_groupmemberattr', 'memberUid'),
$this->getFullDn($username)
);
} else {
$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 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
$group_names = array_keys($groups);
$ldap_group_filter = '';
foreach ($group_names as $group_name) {
$ldap_group_filter .= "(cn=" . trim($group_name) . ")";
}
if (count($group_names) > 1) {
$ldap_group_filter = "(|{$ldap_group_filter})";
}
if (Config::get('auth_ldap_userdn') === true) {
$filter = "(&{$ldap_group_filter}(" . trim(Config::get('auth_ldap_groupmemberattr', 'memberUid')) . "=" . $this->getFullDn($username) . "))";
} else {
$filter = "(&{$ldap_group_filter}(" . trim(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 = [];
try {
$connection = $this->getLdapConnection();
$ldap_groups = $this->getGroupList();
if (empty($ldap_groups)) {
d_echo('No groups defined. Cannot search for users.');
return [];
}
$filter = '(' . Config::get('auth_ldap_prefix') . '*)';
// build group filter
$group_filter = '';
foreach ($ldap_groups as $group) {
$group_filter .= '(memberOf=' . trim($group) . ')';
}
if (count($ldap_groups) > 1) {
$group_filter = "(|$group_filter)";
}
// search using memberOf
$search = ldap_search($connection, trim(Config::get('auth_ldap_suffix'), ','), "(&$filter$group_filter)");
if (ldap_count_entries($connection, $search)) {
foreach (ldap_get_entries($connection, $search) as $entry) {
$user = $this->ldapToUser($entry);
$userlist[$user['username']] = $user;
}
} else {
// probably doesn't support memberOf, go through all users, this could be slow
$search = ldap_search($connection, trim(Config::get('auth_ldap_suffix'), ','), $filter);
foreach (ldap_get_entries($connection, $search) as $entry) {
foreach ($ldap_groups as $ldap_group) {
if (ldap_compare(
$connection,
$ldap_group,
Config::get('auth_ldap_groupmemberattr', 'memberUid'),
$this->getMembername($entry['uid'][0])
)) {
$user = $this->ldapToUser($entry);
$userlist[$user['username']] = $user;
}
}
}
}
} 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
}
if (!function_exists('ldap_connect')) {
throw new AuthenticationException("PHP does not support LDAP, please install or enable the PHP LDAP extension.");
}
$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', 3));
$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;
}
if (Config::get('auth_ldap_debug')) {
ldap_set_option(null, LDAP_OPT_DEBUG_LEVEL, 7);
}
// 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'));
}
$bind_result = ldap_bind($this->ldap_connection, $bind_dn, Config::get('auth_ldap_bindpassword'));
if (Config::get('auth_ldap_debug')) {
echo "Bind result: " . ldap_error($this->ldap_connection) . PHP_EOL;
}
if ($bind_result) {
ldap_set_option($this->ldap_connection, LDAP_OPT_NETWORK_TIMEOUT, -1); // restore timeout
return $this->ldap_connection;
}
}
// Anonymous
ldap_bind($this->ldap_connection);
if (Config::get('auth_ldap_debug')) {
echo "Anonymous bind result: " . ldap_error($this->ldap_connection) . PHP_EOL;
}
ldap_set_option($this->ldap_connection, LDAP_OPT_NETWORK_TIMEOUT, -1); // restore timeout
return $this->ldap_connection;
}
/**
* @param array $entry ldap entry array
* @return array
*/
private function ldapToUser($entry)
{
$uid_attr = strtolower(Config::get('auth_ldap_uid_attribute', 'uidnumber'));
return [
'username' => $entry['uid'][0],
'realname' => $entry['cn'][0],
'user_id' => (int)$entry[$uid_attr][0],
'email' => $entry[Config::get('auth_ldap_emailattr', 'mail')][0],
'level' => $this->getUserlevel($entry['uid'][0]),
];
}
}