MRBS 1.12.2 等保2.0二级整改完整提交
Docker image / push (push) Canceled after 0s

包含:登录失败锁定、90天密码有效期、30分钟会话超时、
强制改密、登录审计日志、屏幕水印、企业背景图、
备案信息固定底部、favicon、登录页JS修复等全部改动
This commit is contained in:
人事系统开发
2026-09-08 21:19:47 +08:00
commit 48092cab42
2221 changed files with 659586 additions and 0 deletions
+997
View File
@@ -0,0 +1,997 @@
<?php
namespace MRBS\Auth;
use MRBS\Errors\Errors;
use MRBS\Exception;
use MRBS\User;
use function MRBS\get_microtime;
use function MRBS\in_arrayi;
use function MRBS\session;
class AuthLdap extends Auth
{
// LDAP ERROR CODES
const LDAP_SUCCESS = 0x00;
const LDAP_OPERATIONS_ERROR = 0x01;
const LDAP_PROTOCOL_ERROR = 0x02;
const LDAP_TIMELIMIT_EXCEEDED = 0x03;
const LDAP_SIZELIMIT_EXCEEDED = 0x04;
const LDAP_COMPARE_FALSE = 0x05;
const LDAP_COMPARE_TRUE = 0x06;
const LDAP_AUTH_METHOD_NOT_SUPPORTED = 0x07;
const LDAP_STRONG_AUTH_REQUIRED = 0x08;
// Not used in LDAPv3
const LDAP_PARTIAL_RESULTS = 0x09;
// Next 5 new in LDAPv3
const LDAP_REFERRAL = 0x0a;
const LDAP_ADMINLIMIT_EXCEEDED = 0x0b;
const LDAP_UNAVAILABLE_CRITICAL_EXTENSION = 0x0c;
const LDAP_CONFIDENTIALITY_REQUIRED = 0x0d;
const LDAP_SASL_BIND_INPROGRESS = 0x0e;
const LDAP_NO_SUCH_ATTRIBUTE = 0x10;
const LDAP_UNDEFINED_TYPE = 0x11;
const LDAP_INAPPROPRIATE_MATCHING = 0x12;
const LDAP_CONSTRAINT_VIOLATION = 0x13;
const LDAP_TYPE_OR_VALUE_EXISTS = 0x14;
const LDAP_INVALID_SYNTAX = 0x15;
const LDAP_NO_SUCH_OBJECT = 0x20;
const LDAP_ALIAS_PROBLEM = 0x21;
const LDAP_INVALID_DN_SYNTAX = 0x22;
// Next two not used in LDAPv3 =
const LDAP_IS_LEAF = 0x23;
const LDAP_ALIAS_DEREF_PROBLEM = 0x24;
const LDAP_INAPPROPRIATE_AUTH = 0x30;
const LDAP_INVALID_CREDENTIALS = 0x31;
const LDAP_INSUFFICIENT_ACCESS = 0x32;
const LDAP_BUSY = 0x33;
const LDAP_UNAVAILABLE = 0x34;
const LDAP_UNWILLING_TO_PERFORM = 0x35;
const LDAP_LOOP_DETECT = 0x36;
const LDAP_SORT_CONTROL_MISSING = 0x3C;
const LDAP_INDEX_RANGE_ERROR = 0x3D;
const LDAP_NAMING_VIOLATION = 0x40;
const LDAP_OBJECT_CLASS_VIOLATION = 0x41;
const LDAP_NOT_ALLOWED_ON_NONLEAF = 0x42;
const LDAP_NOT_ALLOWED_ON_RDN = 0x43;
const LDAP_ALREADY_EXISTS = 0x44;
const LDAP_NO_OBJECT_CLASS_MODS = 0x45;
const LDAP_RESULTS_TOO_LARGE = 0x46;
// Next two for LDAPv3
const LDAP_AFFECTS_MULTIPLE_DSAS = 0x47;
const LDAP_OTHER = 0x50;
// Used by some APIs
const LDAP_SERVER_DOWN = 0x51;
const LDAP_LOCAL_ERROR = 0x52;
const LDAP_ENCODING_ERROR = 0x53;
const LDAP_DECODING_ERROR = 0x54;
const LDAP_TIMEOUT = 0x55;
const LDAP_AUTH_UNKNOWN = 0x56;
const LDAP_FILTER_ERROR = 0x57;
const LDAP_USER_CANCELLED = 0x58;
const LDAP_PARAM_ERROR = 0x59;
const LDAP_NO_MEMORY = 0x5a;
// Preliminary LDAPv3 codes
const LDAP_CONNECT_ERROR = 0x5b;
const LDAP_NOT_SUPPORTED = 0x5c;
const LDAP_CONTROL_NOT_FOUND = 0x5d;
const LDAP_NO_RESULTS_RETURNED = 0x5e;
const LDAP_MORE_RESULTS_TO_RETURN = 0x5f;
const LDAP_CLIENT_LOOP = 0x60;
const LDAP_REFERRAL_LIMIT_EXCEEDED = 0x61;
// Default ports
const DEFAULT_PORT_LDAP = 389;
const DEFAULT_PORT_LDAPS = 636;
private static $all_ldap_opts;
private static $config_items;
private static $profile_clock;
public function __construct()
{
global $ldap_host;
global $ldap_port;
global $ldap_v3;
global $ldap_tls;
global $ldap_base_dn;
global $ldap_user_attrib;
global $ldap_dn_search_attrib;
global $ldap_dn_search_dn;
global $ldap_dn_search_password;
global $ldap_filter;
global $ldap_group_member_attrib;
global $ldap_admin_group_dn;
global $ldap_email_attrib;
global $ldap_name_attrib;
global $ldap_disable_referrals;
global $ldap_deref;
global $ldap_filter_base_dn;
global $ldap_filter_user_attr;
// Check that ldap is installed
if (!function_exists('ldap_connect'))
{
die("<hr><p><b>ERROR: PHP's 'ldap' extension is not installed/enabled. ".
"Please check your web server configuration.</b></p><hr>\n");
}
// Transfer the values from the config variables into a local
// associative array, turning them all into arrays
self::$config_items = array('ldap_host',
'ldap_port',
'ldap_base_dn',
'ldap_user_attrib',
'ldap_dn_search_attrib',
'ldap_dn_search_dn',
'ldap_dn_search_password',
'ldap_filter',
'ldap_group_member_attrib',
'ldap_admin_group_dn',
'ldap_v3',
'ldap_tls',
'ldap_email_attrib',
'ldap_name_attrib',
'ldap_disable_referrals',
'ldap_deref',
'ldap_filter_base_dn',
'ldap_filter_user_attr',
'ldap_client_cert',
'ldap_client_key'
);
self::$all_ldap_opts = array();
// Get the array items (we'll handle the non-array items in a moment) and check
// that they all have the same length
$count = null;
foreach (self::$config_items as $item)
{
if (isset($$item) && is_array($$item))
{
self::$all_ldap_opts[$item] = $$item;
if (isset($count))
{
if (count($$item) != $count)
{
Errors::fatalError("MRBS configuration error: Count of LDAP array config variables doesn't match, aborting!");
}
}
else
{
$count = count($$item);
}
}
}
// Turn any non-array config items into arrays
if (!isset($count))
{
$count = 1;
}
foreach (self::$config_items as $item)
{
if (isset($$item) && !is_array($$item))
{
self::$all_ldap_opts[$item] = array_fill(0, $count, $$item);
}
}
}
public function validateUser(
#[\SensitiveParameter]
?string $user,
#[\SensitiveParameter]
?string $pass)
{
// Check if we do not have a username/password
// User can always bind to LDAP anonymously with empty password,
// therefore we need to block empty password here...
if (!isset($user) || !isset($pass) || strlen($pass)==0)
{
self::debug('empty username or password passed');
return false;
}
$object = array();
$object['pass'] = $pass;
return ($this->action('validateUserCallback', $user, $object)) ? $user : false;
}
/* validateUserCallback(&$ldap, $base_dn, $dn, $user_search,
$user, &$object)
*
* Checks if the specified username/password pair are valid
*
* &$ldap - Reference to the LDAP object
* $base_dn - The base DN
* $dn - The user's DN
* $user_search - The LDAP filter to find the user
* $user - The user name
* &$object - Reference to the generic object
*
* Returns:
* false - Didn't find a user
* true - Found a user
*/
private static function validateUserCallback(&$ldap, $base_dn, $dn, $user_search,
$user, &$object)
{
self::debug("base_dn '$base_dn' dn '$dn' user '$user'");
$pass = $object['pass'];
// try an authenticated bind
// use this to confirm that the user/password pair
if ($dn && self::ldapBind($ldap, $dn, $pass))
{
// however if there is a filter check that the
// user is part of the group defined by the filter
if (!isset($object['config']['ldap_filter']) || ($object['config']['ldap_filter'] === ''))
{
self::debug("successful authenticated bind with no \$ldap_filter");
return true;
}
else
{
// If we've got a search DN and password, then bind again using those credentials because
// it's possible that the user doesn't have read access in the directory, even for their own
// entry, in which case we'll get a "No such object" result.
if (isset($object['config']['ldap_dn_search_dn']) &&
isset($object['config']['ldap_dn_search_password']))
{
self::debug("rebinding as '" . $object['config']['ldap_dn_search_dn'] . "'");
if (!self::ldapBind($ldap, $object['config']['ldap_dn_search_dn'], $object['config']['ldap_dn_search_password']))
{
self::debug("rebinding failed: " . self::ldapError($ldap));
return false;
}
self::debug('rebinding successful');
}
$filter = $object['config']['ldap_filter'];
self::debug("successful authenticated bind checking '$filter'");
// If ldap_filter_base_dn is set, set the filter to search for the user
// in the given base_dn (OpenLDAP). If not, read from the user
// attribute (AD)
if (isset($object['config']['ldap_filter_base_dn']))
{
$f = "(&(".
$object['config']['ldap_filter_user_attr'].
"=$user)($filter))";
$filter_dn = $object['config']['ldap_filter_base_dn'];
$call = 'ldap_search';
}
else
{
$f = "($filter)";
$filter_dn = $dn;
$call = 'ldap_read';
}
self::debug("trying filter: $f: dn: $filter_dn: method: $call");
$res = $call(
$ldap,
$filter_dn,
$f,
array()
);
if (ldap_count_entries($ldap, $res) > 0)
{
self::debug('found entry with filter');
return true;
}
self::debug('no entry found with filter');
}
}
else
{
self::debug("bind to '$dn' failed: ". self::ldapError($ldap));
}
// return failure if no connection is established
return false;
}
protected function getUserFresh(string $username) : ?User
{
if (!isset($username) || ($username === ''))
{
return null;
}
$object = array();
$res = $this->action('getUserCallback', $username, $object);
if (!$res || !isset($object['user']))
{
return null;
}
// Use $object['user']['username'] rather than $username because they won't necessarily be
// the same. See https://sourceforge.net/p/mrbs/bugs/518/
$user = parent::getUserFresh($object['user']['username']);
$keys = array('display_name', 'email', 'level');
foreach ($keys as $key)
{
if (isset($object['user'][$key]))
{
$user->$key = $object['user'][$key];
}
}
return $user;
}
/* getUserCallback(&$ldap, $base_dn, $dn, $user_search,
$username, &$object)
*
* &$ldap - Reference to the LDAP object
* $base_dn - The base DN
* $dn - The user's DN
* $user_search - The LDAP filter to find the user
* $username - The user name
* &$object - Reference to the generic object
*
* Returns:
* false - Didn't find a user
* true - Found a user
*/
private static function getUserCallback(&$ldap, $base_dn, $dn, $user_search,
$user, &$object)
{
global $ldap_get_user_email, $ldap_debug_attributes, $max_level;
self::debug("base_dn '$base_dn' dn '$dn' user_search '$user_search' user '$user'");
if (!$ldap || !$base_dn || !$dn || !$user_search)
{
self::debug("invalid parameters, could not call ldap_read, returning false");
return false;
}
$attributes = self::getAttributes($object, $ldap_get_user_email, true);
self::resetProfileClock();
// We suppress the errors because it's possible to get a "No such object" error if
// the DN doesn't exist - which it won't if (a) we're searching an array of LDAP hosts
// or (b) the DN has been deleted since the booking was made. But check the error
// code afterwards and trigger an error if it was any other kind of error.
$res = @ldap_read(
$ldap,
$dn,
"(objectclass=*)",
array_values($attributes),
0,
1
);
$t = self::getProfileClock();
if ($res === false)
{
self::debug("ldap_read() failed: " . self::ldapError($ldap));
if (self::LDAP_NO_SUCH_OBJECT !== ($errno = ldap_errno($ldap)))
{
if ($errno === self::LDAP_SUCCESS)
{
// The errno is reporting success, but that's just the ldap errno. If ldap_read()
// doesn't get as far as interrogating the directory, it will return false,
// but ldap_errno() will return success. To get more details try temporarily
// removing the error suppression operator ('@').
$message = "ldap_read() failed, not due to an LDAP error but probably due " .
"to an initialization error such as 'Array initialization wrong'";
}
else
{
$message = ldap_err2str($errno);
}
trigger_error($message, E_USER_WARNING);
}
return false;
}
$n_entries = ldap_count_entries($ldap, $res);
if ($n_entries === false)
{
self::debug("No entries found - ldap_count_entries() error");
return false;
}
self::debug("$n_entries entries found");
if ($n_entries === 0)
{
return false;
}
self::debug("ldap_read() succeeded, taking $t seconds");
if ($ldap_debug_attributes)
{
// Repeat the read, this time fetching all the attributes and then write
// the attributes and their values to the debug log. Useful for discovering
// attribute names.
$res2 = @ldap_read($ldap, $dn, "(objectclass=*)", [], 0, 1);
$entry = ldap_first_entry($ldap, $res2);
$attribute = ldap_first_attribute($ldap, $entry);
while ($attribute)
{
$values = ldap_get_values($ldap, $entry, $attribute);
unset($values['count']); // We don't need this element
self::debug("Attribute: \"$attribute\"; Value(s): \"" . implode('", "', $values) . '"');
$attribute = ldap_next_attribute($ldap, $entry);
}
}
$entry = ldap_first_entry($ldap, $res);
$user = self::getResult($ldap, $entry, $attributes);
if (!isset($user['username']))
{
$message = 'No username found. Check the value of $ldap_user_attrib in the MRBS config file.';
$message .= " It is currently set to '" . $object['config']['ldap_user_attrib'] . "'.";
self::debug($message);
return false;
}
if (!isset($user['display_name']))
{
$user['display_name'] = $user['username'];
}
if (isset($user['groups']))
{
if (isset($object['config']['ldap_admin_group_dn']))
{
$user['level'] = in_arrayi($object['config']['ldap_admin_group_dn'], $user['groups']) ? $max_level : 1;
}
}
self::debug("User '" . $user['username'] . "' found");
$object['user'] = $user;
return true;
}
public function getUsernames()
{
$mrbs_user = session()->getCurrentUser();
if (!isset($mrbs_user))
{
return false;
}
$object = array();
$object['users'] = array();
$users = array();
$res = $this->action('getUsernamesCallback', $mrbs_user->username, $object, true);
if ($res === false)
{
trigger_error("MRBS: could not get LDAP usernames.", E_USER_WARNING);
return false;
}
if (isset($object['users']))
{
$users = $object['users'];
}
self::sortUsers($users);
return $users;
}
private static function getUsernamesCallback(&$ldap, $base_dn, $dn, $user_search,
$user, &$object)
{
self::debug("base_dn '$base_dn'");
if (!$ldap || !$base_dn || !isset($object['config']['ldap_user_attrib']))
{
self::debug("invalid parameters, could not call ldap_search, returning false");
return false;
}
if (isset($object['config']['ldap_filter']))
{
$filter = $object['config']['ldap_filter'];
}
else
{
$filter = 'objectclass=*';
}
$filter = "($filter)";
// Form the attributes
$username_attrib = mb_strtolower($object['config']['ldap_user_attrib']);
$attributes = array($username_attrib);
// The display name attribute might not have been set in the config file
if (isset($object['config']['ldap_name_attrib']))
{
$display_name_attrib = mb_strtolower($object['config']['ldap_name_attrib']);
$attributes[] = $display_name_attrib;
}
self::debug("searching with base_dn '$base_dn' and filter '$filter'");
self::resetProfileClock();
$res = ldap_search($ldap, $base_dn, $filter, $attributes);
$t = self::getProfileClock();
if ($res === false)
{
self::debug("ldap_search failed: " . self::ldapError($ldap));
return false;
}
self::debug(ldap_count_entries($ldap, $res) . " entries found in $t seconds");
$entry = ldap_first_entry($ldap, $res);
// Loop through the entries to get all the users
while ($entry)
{
// Initialise all keys in the user array to NULL, in case an attribute isn't present
$user = array('username' => null,
'display_name' => null);
$attribute = ldap_first_attribute($ldap, $entry);
// Loop through all the attributes for this user
while ($attribute)
{
$values = ldap_get_values($ldap, $entry, $attribute);
$attribute = mb_strtolower($attribute); // ready for the comparisons
if ($attribute == $username_attrib)
{
$user['username'] = $values[0];
}
elseif ($attribute == $display_name_attrib)
{
$user['display_name'] = $values[0];
}
$attribute = ldap_next_attribute($ldap, $entry);
}
if (isset($user['username']))
{
if (!isset($user['display_name']))
{
$user['display_name'] = $user['username'];
}
$object['users'][] = $user;
}
$entry = ldap_next_entry($ldap, $entry);
}
return true;
}
// Returns an array of attributes for use in an LDAP query
private static function getAttributes(array $object, bool $include_email=true, bool $include_groups=true) : array
{
$result = array();
// Username
$result['username'] = mb_strtolower($object['config']['ldap_user_attrib']);
// The display name attribute might not have been set in the config file
if (isset($object['config']['ldap_name_attrib']))
{
$result['display_name'] = mb_strtolower($object['config']['ldap_name_attrib']);
}
// The email address
if ($include_email && isset($object['config']['ldap_email_attrib']))
{
$result['email'] = mb_strtolower($object['config']['ldap_email_attrib']);
}
// The group name attribute might not have been set in the config file
if ($include_groups && isset($object['config']['ldap_group_member_attrib']))
{
$result['groups'] = mb_strtolower($object['config']['ldap_group_member_attrib']);
}
return $result;
}
// Returns an associative array from the result of an LDAP search
private static function getResult($ldap, $entry, array $attributes) : array
{
// Initialise all keys in the user array, in case an attribute isn't present
$attributes_keys = array_keys($attributes);
$user = array();
foreach ($attributes_keys as $key)
{
switch ($key)
{
case 'username':
case 'display_name':
case 'email':
$user[$key] = null;
break;
case 'groups':
$user[$key] = array();
break;
default:
throw new Exception("Unknown key '$key'");
}
}
$attribute = ldap_first_attribute($ldap, $entry);
// Loop through all the attributes for this user
while ($attribute)
{
$values = ldap_get_values($ldap, $entry, $attribute);
$attribute = mb_strtolower($attribute); // ready for the comparisons
if ($attribute == $attributes['username'])
{
$user['username'] = $values[0];
}
elseif (isset($attributes['display_name']) && ($attribute == $attributes['display_name']))
{
$user['display_name'] = $values[0];
}
elseif (isset($attributes['email']) && ($attribute == $attributes['email']))
{
$user['email'] = $values[0];
}
elseif (isset($attributes['groups']) && ($attribute == $attributes['groups']))
{
for ($i=0; $i<$values['count']; $i++)
{
$user['groups'][] = $values[$i];
}
}
$attribute = ldap_next_attribute($ldap, $entry);
}
return $user;
}
/* action($callback, $username, &$object)
*
* Connects/binds to all configured LDAP servers/base DNs and
* then performs a callback, passing the LDAP object, $base_dn,
* user DN (in $dn), $username and a generic object $object
*
* $callback - The callback function
* $username - The user name
* &$object - Reference to the generic object, type defined by caller
* $keep_going - Don't stop when a user has been found, but keep going through all the LDAP
* hosts. Useful, for example, when you want to get a list of all users.
*
* Returns:
* boolean - Whether the action was successful
*/
public function action(string $callback, string $username, &$object, bool $keep_going=false) : bool
{
global $ldap_unbind_between_attempts;
$result = false;
for ($idx=0; $idx < count(self::$all_ldap_opts['ldap_host']); $idx++)
{
// Establish LDAP connection
$uri = self::getUri($idx);
$ldap = ldap_connect($uri);
// Check that connection was established
if ($ldap)
{
self::debug("got LDAP connection using $uri");
// Set any applicable LDAP options
self::setOptions($ldap, $idx);
if (isset(self::$all_ldap_opts['ldap_dn_search_attrib'][$idx]))
{
if (isset(self::$all_ldap_opts['ldap_dn_search_dn'][$idx]) &&
isset(self::$all_ldap_opts['ldap_dn_search_password'][$idx]))
{
// Bind with DN and password
self::debug("binding with search_dn and search_password");
$res = self::ldapBind($ldap, self::$all_ldap_opts['ldap_dn_search_dn'][$idx],
self::$all_ldap_opts['ldap_dn_search_password'][$idx]);
}
else
{
// Anonymous bind
self::debug("binding anonymously");
$res = self::ldapBind($ldap);
}
if (!$res)
{
self::debug("initial bind failed: " . self::ldapError($ldap));
}
else
{
self::debug("initial bind was successful");
$base_dn = self::$all_ldap_opts['ldap_base_dn'][$idx];
$filter = "(" . self::$all_ldap_opts['ldap_dn_search_attrib'][$idx] . "=$username)";
self::debug("searching using base_dn '$base_dn' and filter '$filter'");
$res = ldap_search($ldap, $base_dn, $filter);
if ($res === false)
{
self::debug("ldap_search failed: ". self::ldapError($ldap));
}
else
{
if (ldap_count_entries($ldap, $res) == 1)
{
$entries = ldap_get_entries($ldap, $res);
$dn = $entries[0]["dn"];
$user_search = "distinguishedName=" . $dn;
self::debug("found one entry dn '$dn'");
}
else
{
self::debug(ldap_count_entries($ldap, $res) . " entries found, no unique dn");
}
}
}
}
else
{
// construct dn for user
$user_search = self::$all_ldap_opts['ldap_user_attrib'][$idx] . "=" . $username;
$dn = $user_search . "," . self::$all_ldap_opts['ldap_base_dn'][$idx];
self::debug("constructed dn '$dn' and " .
"user_search '$user_search' using '" .
self::$all_ldap_opts['ldap_user_attrib'][$idx] . "'");
}
foreach (self::$config_items as $item)
{
if (isset(self::$all_ldap_opts[$item][$idx]))
{
$object['config'][$item] = self::$all_ldap_opts[$item][$idx];
}
else
{
$object['config'][$item] = null;
}
}
if (empty($dn))
{
self::debug("no DN determined, not calling callback");
}
else
{
$res = self::$callback($ldap, self::$all_ldap_opts['ldap_base_dn'][$idx], $dn,
$user_search, $username, $object);
if ($res)
{
$result = true;
}
}
if ($ldap_unbind_between_attempts)
{
self::debug("unbinding from $uri");
ldap_unbind($ldap);
}
} // if ($ldap)
if ($result && !$keep_going)
{
return true;
}
} // for ()
return $result;
}
// A wrapper for ldap_bind() that optionally suppresses "invalid credentials" errors.
// The SensitiveParameter attribute needs to be on a separate line for PHP 7.
// The attribute is only recognised by PHP 8.2 and later.
private static function ldapBind (
$link_identifier,
?string $bind_rdn=null,
#[\SensitiveParameter]
?string $bind_password=null
) : bool
{
global $ldap_suppress_invalid_credentials;
// Suppress all errors and then look to see what the error was and then
// trigger the error again, depending on config settings.
$result = @ldap_bind($link_identifier, $bind_rdn, $bind_password);
if (!$result)
{
$errno = ldap_errno($link_identifier);
if (!$ldap_suppress_invalid_credentials || ($errno != self::LDAP_INVALID_CREDENTIALS))
{
trigger_error(ldap_err2str($errno), E_USER_WARNING);
}
}
return $result;
}
// Gets the full LDAP URI
private static function getUri(int $idx) : string
{
// First get the scheme and host
$host = self::$all_ldap_opts['ldap_host'][$idx];
$parsed_url = parse_url($host);
if (isset($parsed_url['scheme']))
{
$scheme = $parsed_url['scheme'];
$host = $parsed_url['host'];
}
// If we haven't got a scheme then make an educated guess based on the port.
if (!isset($scheme))
{
// And if there isn't a port defined either then use a sensible default
$port = self::$all_ldap_opts['ldap_port'][$idx] ?? self::DEFAULT_PORT_LDAP;
$scheme = ($port == self::DEFAULT_PORT_LDAPS) ? 'ldaps' : 'ldap';
}
// If we have got a scheme then get the port. If it has been defined explicitly in the
// config file, then use that. Otherwise make an educated guess based on the scheme.
else
{
if (isset(self::$all_ldap_opts['ldap_port'][$idx]))
{
$port = self::$all_ldap_opts['ldap_port'][$idx];
}
else
{
$port = ($scheme == 'ldaps') ? self::DEFAULT_PORT_LDAPS : self::DEFAULT_PORT_LDAP;
}
}
return "$scheme://$host:$port";
}
private static function setOptions($ldap, int $idx) : void
{
if (isset(self::$all_ldap_opts['ldap_deref'][$idx]))
{
ldap_set_option($ldap, LDAP_OPT_DEREF, self::$all_ldap_opts['ldap_deref'][$idx]);
}
if (isset(self::$all_ldap_opts['ldap_v3'][$idx]) &&
self::$all_ldap_opts['ldap_v3'][$idx])
{
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
}
if (isset(self::$all_ldap_opts['ldap_client_cert'][$idx]) &&
self::$all_ldap_opts['ldap_client_cert'][$idx])
{
// Requires PHP 7.1.0 or later
ldap_set_option($ldap, LDAP_OPT_X_TLS_CERTFILE, self::$all_ldap_opts['ldap_client_cert'][$idx]);
}
if (isset(self::$all_ldap_opts['ldap_client_key'][$idx]) &&
self::$all_ldap_opts['ldap_client_key'][$idx])
{
// Requires PHP 7.1.0 or later
ldap_set_option($ldap, LDAP_OPT_X_TLS_KEYFILE, self::$all_ldap_opts['ldap_client_key'][$idx]);
}
if (isset(self::$all_ldap_opts['ldap_tls'][$idx]) &&
self::$all_ldap_opts['ldap_tls'][$idx])
{
ldap_start_tls($ldap);
}
if (isset(self::$all_ldap_opts['ldap_disable_referrals'][$idx]) &&
self::$all_ldap_opts['ldap_disable_referrals'][$idx])
{
// Required to do a search on Active Directory for Win 2003+
ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
}
}
// Adds extra diagnostic information to ldap_error()
private static function ldapError ($link_identifier) : string
{
$result = ldap_error($link_identifier);
// LDAP_OPT_DIAGNOSTIC_MESSAGE is not supported by all LDAP libraries
if (defined('LDAP_OPT_DIAGNOSTIC_MESSAGE') &&
ldap_get_option($link_identifier, LDAP_OPT_DIAGNOSTIC_MESSAGE, $err) &&
isset($err) && ($err !== ''))
{
$result .= " [$err]";
}
return $result;
}
/* debug($message)
*
* Output LDAP debugging, if either of the configuration variables
* $ldap_debug or $ldap_debug_attributes is true.
*
*/
private static function debug(string $message) : void
{
global $ldap_debug, $ldap_debug_attributes;
if ($ldap_debug || $ldap_debug_attributes)
{
self::logDebugMessage($message);
}
}
private static function getProfileClock()
{
global $ldap_debug;
if ($ldap_debug)
{
return (get_microtime() - self::$profile_clock);
}
else
{
return null;
}
}
private static function resetProfileClock() : void
{
global $ldap_debug;
if ($ldap_debug)
{
self::$profile_clock = get_microtime();
}
}
}