librenms/LibreNMS/Authentication/ActiveDirectoryAuthorizer.php

251 lines
8.4 KiB
PHP
Raw Normal View History

<?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
{
use ActiveDirectoryCommon;
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
public function authenticate($username, $password)
{
$this->connect();
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->is_bound = 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));
}
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
"(&" . $this->userFilter($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);
}
public function userExists($username, $throw_exception = false)
{
$this->bind(); // make sure we called bind
$search = ldap_search(
$this->ldap_connection,
Config::get('auth_ad_base_dn'),
$this->userFilter($username),
array('samaccountname')
);
$entries = ldap_get_entries($this->ldap_connection, $search);
if ($entries['count']) {
return 1;
}
return 0;
}
public function getUserlevel($username)
{
$this->bind(); // 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->bind(); // make sure we called bind
$attributes = array('objectsid');
$search = ldap_search(
$this->ldap_connection,
Config::get('auth_ad_base_dn'),
$this->userFilter($username),
$attributes
);
$entries = ldap_get_entries($this->ldap_connection, $search);
if ($entries['count']) {
return $this->getUseridFromSid($this->sidFromLdap($entries[0]['objectsid'][0]));
}
return -1;
}
/**
* 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 bind($allow_anonymous = true, $force = false)
{
if ($this->is_bound && !$force) {
return true; // bind already attempted
}
$this->connect(); // make sure we are connected
// set timeout
ldap_set_option(
$this->ldap_connection,
LDAP_OPT_NETWORK_TIMEOUT,
Config::get('auth_ad_timeout', 5)
);
// With specified bind user
if (Config::has('auth_ad_binduser') && Config::has('auth_ad_bindpassword')) {
$this->is_bound = 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->is_bound = true;
$bind = ldap_bind($this->ldap_connection);
}
ldap_set_option($this->ldap_connection, LDAP_OPT_NETWORK_TIMEOUT, -1); // restore timeout
return $bind;
}
protected function connect()
{
if ($this->ldap_connection) {
// no need to re-connect
return;
}
if (!function_exists('ldap_connect')) {
throw new AuthenticationException("PHP does not support LDAP, please install or enable the PHP LDAP extension.");
}
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->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);
}
protected function getConnection()
{
$this->bind(); // make sure connected and bound
return $this->ldap_connection;
}
}