updated plugin `AuthLDAP` version 2.5.9

This commit is contained in:
KawaiiPunk 2023-10-22 22:20:57 +00:00 committed by Gitium
parent 5a3dd4cf21
commit 052743ea8a
20 changed files with 1368 additions and 913 deletions

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<phive xmlns="https://phar.io/phive">
<phar name="phpunit" version="^9.5.21" installed="9.5.21" location="./tools/phpunit" copy="true"/>
<phar name="phpcs" version="^3.7.1" installed="3.7.1" location="./tools/phpcs" copy="true"/>
<phar name="phpcbf" version="^3.7.1" installed="3.7.1" location="./tools/phpcbf" copy="true"/>
</phive>

View File

@ -4,12 +4,12 @@
Use your existing LDAP as authentication-backend for your wordpress!
[![Build Status](https://travis-ci.org/heiglandreas/authLdap.svg?branch=master)](https://travis-ci.org/heiglandreas/authLdap)
[![Build Status](https://github.com/heiglandreas/authLdap/actions/workflows/tests.yml/badge.svg)](https://github.com/heiglandreas/authLdap/actions/workflows/tests.yml)
[![WordPress Stats](https://img.shields.io/wordpress/plugin/dt/authldap.svg)](https://wordpress.org/plugins/authldap/stats/)
[![WordPress Version](https://img.shields.io/wordpress/plugin/v/authldap.svg)](https://wordpress.org/plugins/authldap/)
[![WordPress testet](https://img.shields.io/wordpress/v/authldap.svg)](https://wordpress.org/plugins/authldap/)
[![Code Climate](https://codeclimate.com/github/heiglandreas/authLdap/badges/gpa.svg)](https://codeclimate.com/github/heiglandreas/authLdap)
[![Test Coverage](https://codeclimate.com/github/heiglandreas/authLdap/badges/coverage.svg)](https://codeclimate.com/github/heiglandreas/authLdap)
[![codecov](https://codecov.io/gh/heiglandreas/authLdap/branch/master/graph/badge.svg?token=AYAhEeWtRQ)](https://codecov.io/gh/heiglandreas/authLdap)
So what are the differences to other Wordpress-LDAP-Authentication-Plugins?

View File

@ -0,0 +1,18 @@
# Security-Policy
## Supported Versions
| Version | Supported |
| ------- |--------------------|
| 2.x | :white_check_mark: |
| 1.x | :x: |
## Reporting a Vulnerability
* Check our security.txt file for details on how to contact us
* Contact us before publicly disclosing the issue anywhere else
This plugin is developed as OpenSource under the MIT licence.
There is no money earned from it. Therefore we are not able to
provide any bug-bounties whatsoever. You will be mentioned in the
release notes of a fix-release though.

View File

@ -1,9 +1,10 @@
<?php
/*
Plugin Name: AuthLDAP
Plugin URI: https://github.com/heiglandreas/authLdap
Description: This plugin allows you to use your existing LDAP as authentication base for WordPress
Version: 2.5.2
Version: 2.5.9
Author: Andreas Heigl <andreas@heigl.org>
Author URI: http://andreas.heigl.org
License: MIT
@ -12,12 +13,25 @@ License URI: https://opensource.org/licenses/MIT
// phpcs:disable PSR1.Files.SideEffects
use Org_Heigl\AuthLdap\LdapList;
use Org_Heigl\AuthLdap\LdapUri;
use Org_Heigl\AuthLdap\Manager\Ldap;
use Org_Heigl\AuthLdap\UserRoleHandler;
use Org_Heigl\AuthLdap\Wrapper\LdapFactory;
require_once dirname(__FILE__) . '/ldap.php';
require_once __DIR__ . '/src/LdapUri.php';
require_once __DIR__ . '/src/Wrapper/LdapInterface.php';
require_once __DIR__ . '/src/Exception/Error.php';
require_once __DIR__ . '/src/Exception/InvalidLdapUri.php';
require_once __DIR__ . '/src/Exception/Error.php';
require_once __DIR__ . '/src/Exception/InvalidLdapUri.php';
require_once __DIR__ . '/src/Exception/MissingValidLdapConnection.php';
require_once __DIR__ . '/src/Exception/SearchUnsuccessfull.php';
require_once __DIR__ . '/src/Manager/Ldap.php';
require_once __DIR__ . '/src/Wrapper/Ldap.php';
require_once __DIR__ . '/src/Wrapper/LdapFactory.php';
require_once __DIR__ . '/src/LdapList.php';
require_once __DIR__ . '/src/LdapUri.php';
require_once __DIR__ . '/src/UserRoleHandler.php';
function authLdap_debug($message)
{
@ -60,7 +74,14 @@ function authLdap_options_panel()
wp_enqueue_style('authLdap-style', plugin_dir_url(__FILE__) . 'authLdap.css');
if (($_SERVER['REQUEST_METHOD'] == 'POST') && array_key_exists('ldapOptionsSave', $_POST)) {
$new_options = array(
if (!isset($_POST['authLdapNonce'])) {
die("Go away!");
}
if (!wp_verify_nonce($_POST['authLdapNonce'],'authLdapNonce')) {
die("Go away!");
}
$new_options = [
'Enabled' => authLdap_get_post('authLDAPAuth', false),
'CachePW' => authLdap_get_post('authLDAPCachePW', false),
'URI' => authLdap_get_post('authLDAPURI'),
@ -72,7 +93,7 @@ function authLdap_options_panel()
'UidAttr' => authLdap_get_post('authLDAPUidAttr'),
'MailAttr' => authLdap_get_post('authLDAPMailAttr'),
'WebAttr' => authLdap_get_post('authLDAPWebAttr'),
'Groups' => authLdap_get_post('authLDAPGroups', array()),
'Groups' => authLdap_get_post('authLDAPGroups', []),
'GroupSeparator' => authLdap_get_post('authLDAPGroupSeparator', ','),
'Debug' => authLdap_get_post('authLDAPDebug', false),
'GroupBase' => authLdap_get_post('authLDAPGroupBase'),
@ -83,7 +104,7 @@ function authLdap_options_panel()
'GroupOverUser' => authLdap_get_post('authLDAPGroupOverUser', false),
'DoNotOverwriteNonLdapUsers' => authLdap_get_post('authLDAPDoNotOverwriteNonLdapUsers', false),
'UserRead' => authLdap_get_post('authLDAPUseUserAccount', false),
);
];
if (authLdap_set_options($new_options)) {
echo "<div class='updated'><p>Saved Options!</p></div>";
} else {
@ -160,11 +181,11 @@ function authLdap_get_server()
//$authLDAPURI = 'ldap:/foo:bar@server/trallala';
authLdap_debug('connect to LDAP server');
require_once dirname(__FILE__) . '/src/LdapList.php';
$_ldapserver = new \Org_Heigl\AuthLdap\LdapList();
$_ldapserver = new LdapList();
foreach ($authLDAPURI as $uri) {
$_ldapserver->addLdap(new \Org_Heigl\AuthLdap\LDAP(
$_ldapserver->addLdap(new Ldap(
new LdapFactory(),
LdapUri::fromString($uri),
$authLDAPDebug,
$authLDAPStartTLS
));
}
@ -305,13 +326,13 @@ function authLdap_login($user, $username, $password, $already_md5 = false)
array_filter(
apply_filters(
'authLdap_filter_attributes',
array(
[
$authLDAPNameAttr,
$authLDAPSecName,
$authLDAPMailAttr,
$authLDAPWebAttr,
$authLDAPUidAttr
)
$authLDAPUidAttr,
]
)
)
);
@ -348,36 +369,53 @@ function authLdap_login($user, $username, $password, $already_md5 = false)
}
}
$role = '';
$roles = [];
// we only need this if either LDAP groups are disabled or
// if the WordPress role of the user overrides LDAP groups
if (!$authLDAPGroupEnable || !$authLDAPGroupOverUser) {
$role = authLdap_user_role($uid);
if ($role !== '') {
$roles[] = $role;
}
// TODO, this needs to be revised, it seems, like authldap is taking only the first role
// even if in WP there are assigned multiple.
}
// do LDAP group mapping if needed
// (if LDAP groups override worpress user role, $role is still empty)
if (empty($role) && $authLDAPGroupEnable) {
$role = authLdap_groupmap($realuid, $dn);
authLdap_debug('role from group mapping: ' . $role);
if (empty($roles) && $authLDAPGroupEnable) {
$mappedRoles = authLdap_groupmap($realuid, $dn);
if ($mappedRoles !== []) {
$roles = $mappedRoles;
authLdap_debug('role from group mapping: ' . json_encode($roles));
}
}
// if we don't have a role yet, use default role
if (empty($role) && !empty($authLDAPDefaultRole)) {
if (empty($roles) && !empty($authLDAPDefaultRole)) {
authLdap_debug('no role yet, set default role');
$role = $authLDAPDefaultRole;
$roles[] = $authLDAPDefaultRole;
}
if (empty($role)) {
if (empty($roles)) {
// Sorry, but you are not in any group that is allowed access
trigger_error('no group found');
authLdap_debug('user is not in any group that is allowed access');
return false;
} else {
$roles = new WP_Roles();
$wp_roles = new WP_Roles();
// not sure if this is needed, but it can't hurt
if (!$roles->is_role($role)) {
// Get rid of unexisting roles.
foreach ($roles as $k => $v) {
if (!$wp_roles->is_role($v)) {
unset($k);
}
}
// check if single role or an empty array provided
if (empty($roles)) {
trigger_error('no group found');
authLdap_debug('role is invalid');
return false;
@ -386,9 +424,8 @@ function authLdap_login($user, $username, $password, $already_md5 = false)
// from here on, the user has access!
// now, lets update some user details
$user_info = array();
$user_info = [];
$user_info['user_login'] = $realuid;
$user_info['role'] = $role;
$user_info['user_email'] = '';
$user_info['user_nicename'] = '';
@ -454,6 +491,17 @@ function authLdap_login($user, $username, $password, $already_md5 = false)
return $userid;
}
// Update user roles.
$user = new \WP_User($userid);
/**
* Add hook for custom User-Role assignment
*
* @param WP_User $user This user-object will be returned. Can be modified as necessary in the actions.
* @param array $roles
*/
do_action('authldap_user_roles', $user, $roles);
/**
* Add hook for custom updates
*
@ -468,7 +516,7 @@ function authLdap_login($user, $username, $password, $already_md5 = false)
update_user_meta($userid, 'authLDAP', true);
// return a user object upon positive authorization
return new WP_User($userid);
return $user;
} catch (Exception $e) {
authLdap_debug($e->getMessage() . '. Exception thrown in line ' . $e->getLine());
trigger_error($e->getMessage() . '. Exception thrown in line ' . $e->getLine());
@ -530,7 +578,7 @@ function authLdap_user_role($uid)
// By using this approach we are now using the order of the roles from the WP_Roles object
// and not from the capabilities any more.
$userroles = array_keys(array_intersect_key($editable_roles, $usercapabilities));
$role = $userroles[0];
$role = ($userroles !== []) ? $userroles[0] : '';
authLdap_debug("Existing user's role: {$role}");
return $role;
@ -541,7 +589,7 @@ function authLdap_user_role($uid)
*
* @param string $username
* @param string $dn
* @return string role, empty string if no mapping found, first found role otherwise
* @return array role, empty array if no mapping found, first or all role(s) found otherwise
* @conf array authLDAPGroups, associative array, role => ldap_group
* @conf string authLDAPGroupBase, base dn to look up groups
* @conf string authLDAPGroupAttr, ldap attribute that holds name of group
@ -568,7 +616,7 @@ function authLdap_groupmap($username, $dn)
if (!is_array($authLDAPGroups) || count(array_filter(array_values($authLDAPGroups))) == 0) {
authLdap_debug('No group names defined');
return '';
return [];
}
try {
@ -583,42 +631,52 @@ function authLdap_groupmap($username, $dn)
authLdap_debug('Group Base: ' . $authLDAPGroupBase);
$groups = authLdap_get_server()->search(
sprintf($authLDAPGroupFilter, ldap_escape($username, '', LDAP_ESCAPE_FILTER)),
array($authLDAPGroupAttr),
[$authLDAPGroupAttr],
$authLDAPGroupBase
);
} catch (Exception $e) {
authLdap_debug('Exception getting LDAP group attributes: ' . $e->getMessage());
return '';
return [];
}
$grp = array();
$grp = [];
for ($i = 0; $i < $groups ['count']; $i++) {
if ($authLDAPGroupAttr == "dn") {
$grp[] = $groups[$i]['dn'];
} else {
for ($k = 0; $k < $groups[$i][strtolower($authLDAPGroupAttr)]['count']; $k++) {
$grp[] = $groups[$i][strtolower($authLDAPGroupAttr)][$k];
}
}
}
authLdap_debug('LDAP groups: ' . json_encode($grp));
// Check whether the user is member of one of the groups that are
// allowed acces to the blog. If the user is not member of one of
// The groups throw her out! ;-)
// If the user is member of more than one group only the first one
// will be taken into account!
$role = '';
$roles = [];
foreach ($authLDAPGroups as $key => $val) {
$currentGroup = explode($authLDAPGroupSeparator, $val);
// Remove whitespaces around the group-ID
$currentGroup = array_map('trim', $currentGroup);
if (0 < count(array_intersect($currentGroup, $grp))) {
$role = $key;
break;
$roles[] = $key;
}
}
authLdap_debug("Role from LDAP group: {$role}");
return $role;
// Default: If the user is member of more than one group only the first one
// will be taken into account!
// This filter allows you to return multiple user roles. WordPress
// supports this functionality, but not natively via UI from Users
// overview (you need to use a plugin). However, it's still widely used,
// for example, by WooCommerce, etc. Use if you know what you're doing.
if (apply_filters('authLdap_allow_multiple_roles', false) === false && count($roles) > 1) {
$roles = array_slice($roles, 0, 1);
}
authLdap_debug("Roles from LDAP group: " . json_encode($roles));
return $roles;
}
/**
@ -689,7 +747,7 @@ function authLdap_sort_roles_by_capabilities($roles)
authLdap_debug(print_r($roles, true));
uasort($myRoles, 'authLdap_sortByCapabilitycount');
$return = array();
$return = [];
foreach ($myRoles as $key => $role) {
if (isset($roles[$key])) {
@ -736,13 +794,13 @@ function authLdap_load_options($reload = false)
$optionFunction = 'get_site_option';
}
if (is_null($options) || $reload) {
$options = $optionFunction('authLDAPOptions', array());
$options = $optionFunction('authLDAPOptions', []);
}
// check if option version has changed (or if it's there at all)
if (!isset($options['Version']) || ($options['Version'] != $option_version_plugin)) {
// defaults for all options
$options_default = array(
$options_default = [
'Enabled' => false,
'CachePW' => false,
'URI' => '',
@ -753,7 +811,7 @@ function authLdap_load_options($reload = false)
'UidAttr' => '', // 'uid'
'MailAttr' => '', // 'mail'
'WebAttr' => '',
'Groups' => array(),
'Groups' => [],
'Debug' => false,
'GroupAttr' => '', // 'gidNumber'
'GroupFilter' => '', // '(&(objectClass=posixGroup)(memberUid=%s))'
@ -762,13 +820,13 @@ function authLdap_load_options($reload = false)
'GroupOverUser' => true,
'Version' => $option_version_plugin,
'DoNotOverwriteNonLdapUsers' => false,
);
];
// check if we got a version
if (!isset($options['Version'])) {
// we just changed to the new option format
// read old options, then delete them
$old_option_new_option = array(
$old_option_new_option = [
'authLDAP' => 'Enabled',
'authLDAPCachePW' => 'CachePW',
'authLDAPURI' => 'URI',
@ -785,7 +843,7 @@ function authLdap_load_options($reload = false)
'authLDAPDefaultRole' => 'DefaultRole',
'authLDAPGroupEnable' => 'GroupEnable',
'authLDAPGroupOverUser' => 'GroupOverUser',
);
];
foreach ($old_option_new_option as $old_option => $new_option) {
$value = get_option($old_option, null);
if (!is_null($value)) {
@ -832,7 +890,7 @@ function authLdap_get_option($optionname, $default = null)
/**
* Set new options
*/
function authLdap_set_options($new_options = array())
function authLdap_set_options($new_options = [])
{
// initialize the options with what we currently have
$options = authLdap_load_options();
@ -884,3 +942,5 @@ add_filter('authenticate', 'authLdap_login', 10, 3);
/** This only works from WP 4.3.0 on */
add_filter('send_password_change_email', 'authLdap_send_change_email', 10, 3);
add_filter('send_email_change_email', 'authLdap_send_change_email', 10, 3);
$handler = new UserRoleHandler();
add_action('authldap_user_roles', [$handler, 'addRolesToUser'], 10, 2);

View File

@ -1,275 +0,0 @@
<?php
/**
* $Id: ldap.php 2676679 2022-02-10 18:26:37Z heiglandreas $
*
* authLdap - Authenticate Wordpress against an LDAP-Backend.
* Copyright (c) 2008 Andreas Heigl<andreas@heigl.org>
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* This file handles the basic LDAP-Tasks
*
* @author Andreas Heigl<andreas@heigl.org>
* @package authLdap
* @category authLdap
* @since 2008
*/
namespace Org_Heigl\AuthLdap;
use Exception;
use Org_Heigl\AuthLdap\Exception\Error;
use function ldap_escape;
class LDAP
{
private $server = '';
private $scheme = 'ldap';
private $port = 389;
private $baseDn = '';
private $debug = false;
/**
* This property contains the connection handle to the ldap-server
*
* @var Ressource|Connection|null
*/
private $ch = null;
private $username = '';
private $password = '';
private $starttls = false;
public function __construct(LdapUri $URI, $debug = false, $starttls = false)
{
$this->debug=$debug;
$array = parse_url($URI->toString());
if (! is_array($array)) {
throw new Exception($URI . ' seems not to be a valid URI');
}
$url = array_map(function ($item) {
return urldecode($item);
}, $array);
if (false === $url) {
throw new Exception($URI . ' is an invalid URL');
}
if (! isset($url['scheme'])) {
throw new Exception($URI . ' does not provide a scheme');
}
if (0 !== strpos($url['scheme'], 'ldap')) {
throw new Exception($URI . ' is an invalid LDAP-URI');
}
if (! isset($url['host'])) {
throw new Exception($URI . ' does not provide a server');
}
if (! isset($url['path'])) {
throw new Exception($URI . ' does not provide a search-base');
}
if (1 == strlen($url['path'])) {
throw new Exception($URI . ' does not provide a valid search-base');
}
$this -> server = $url['host'];
$this -> scheme = $url['scheme'];
$this -> baseDn = substr($url['path'], 1);
if (isset($url['user'])) {
$this -> username = $url['user'];
}
if ('' == trim($this -> username)) {
$this -> username = 'anonymous';
}
if (isset($url['pass'])) {
$this -> password = $url['pass'];
}
if (isset($url['port'])) {
$this -> port = $url['port'];
}
$this->starttls = $starttls;
}
/**
* Connect to the given LDAP-Server
*
* @return LDAP
* @throws Error
*/
public function connect()
{
$this -> disconnect();
if ('ldaps' == $this->scheme && 389 == $this->port) {
$this->port = 636;
}
$this->ch = @ldap_connect($this->scheme . '://' . $this->server . ':' . $this -> port);
if (false === $this->ch) {
$this->ch = null;
throw new Error('Could not connect to the server');
}
ldap_set_option($this->ch, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($this->ch, LDAP_OPT_REFERRALS, 0);
//if configured try to upgrade encryption to tls for ldap connections
if ($this->starttls) {
ldap_start_tls($this->ch);
}
return $this;
}
/**
* Disconnect from a resource if one is available
*
* @return LDAP
*/
public function disconnect()
{
if (null !== $this->ch ) {
@ldap_unbind($this->ch);
}
$this->ch = null;
return $this;
}
/**
* Bind to an LDAP-Server with the given credentials
*
* @return LDAP
* @throw AuthLdap_Exception
*/
public function bind()
{
if (! $this->ch) {
$this->connect();
}
if (null === $this->ch) {
throw new Error('No valid LDAP connection available');
}
$bind = false;
if (( ( $this->username )
&& ( $this->username != 'anonymous') )
&& ( $this->password != '' )) {
$bind = @ldap_bind($this->ch, $this->username, $this->password);
} else {
$bind = @ldap_bind($this->ch);
}
if (! $bind) {
throw new Error('bind was not successfull: ' . ldap_error($this->ch));
}
return $this;
}
public function getErrorNumber()
{
return @ldap_errno($this->ch);
}
public function getErrorText()
{
return @ldap_error($this->ch);
}
/**
* This method does the actual ldap-serch.
*
* This is using the filter <var>$filter</var> for retrieving the attributes
* <var>$attributes</var>
*
*
* @param string $filter
* @param array $attributes
* @param string $base
* @return array
*/
public function search($filter, $attributes = array('uid'), $base = '')
{
if (null === $this->ch) {
throw new Error('No resource handle avbailable');
}
if (! $base) {
$base = $this->baseDn;
}
$result = ldap_search($this->ch, $base, $filter, $attributes);
if ($result === false) {
throw new Error('no result found');
}
$this->_info = @ldap_get_entries($this->ch, $result);
if ($this->_info === false) {
throw new Error('invalid results found');
}
return $this -> _info;
}
/**
* This method sets debugging to ON
*/
public function debugOn()
{
$this->debug = true;
return $this;
}
/**
* This method sets debugging to OFF
*/
public function debugOff()
{
$this->debug = false;
return $this;
}
/**
* This method authenticates the user <var>$username</var> using the
* password <var>$password</var>
*
* @param string $username
* @param string $password
* @param string $filter OPTIONAL This parameter defines the Filter to be used
* when searchin for the username. This MUST contain the string '%s' which
* will be replaced by the vaue given in <var>$username</var>
* @return boolean true or false depending on successfull authentication or not
*/
public function authenticate($username, $password, $filter = '(uid=%s)')
{
//return true;
$this->connect();
$this->bind();
$res = $this->search(sprintf($filter, ldap_escape($username, '', LDAP_ESCAPE_FILTER)));
if (! $res || ! is_array($res) || ( $res ['count'] != 1 )) {
return false;
}
$dn = $res[0]['dn'];
if ($username && $password) {
if (@ldap_bind($this->ch, $dn, $password)) {
return true;
}
}
return false;
}
/**
* $this method loggs errors if debugging is set to ON
*/
public function logError()
{
if ($this->debug) {
$_v = debug_backtrace();
throw new Error(
'[LDAP_ERROR]' . ldap_errno($this->ch) . ':' . ldap_error($this->ch),
$_v[0]['line']
);
}
}
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<ruleset name="Custom Standard" namespace="MyProject\CS\Standard">
<description>authLdap codestyle</description>
<file>./src</file>
<file>./authLdap.php</file>
<file>./tests</file>
<arg name="colors"/>
<arg value="sp"/>
<autoload>./vendor/autoload.php</autoload>
<rule ref="PSR12">
<exclude name="Generic.WhiteSpace.DisallowTabIndent"/>
</rule>
<rule ref="Generic.WhiteSpace.ScopeIndent">
<properties>
<property name="tabIndent" value="true"/>
</properties>
</rule>
</ruleset>

View File

@ -2,8 +2,8 @@
Contributors: heiglandreas
Tags: ldap, auth, authentication, active directory, AD, openLDAP, Open Directory
Requires at least: 2.5.0
Tested up to: 5.9.0
Requires PHP: 7.2
Tested up to: 6.3.0
Requires PHP: 7.4
Stable tag: trunk
License: MIT
License URI: https://opensource.org/licenses/MIT
@ -39,8 +39,36 @@ Go to https://github.com/heiglandreas/authLdap
Please use the issuetracker at https://github.com/heiglandreas/authLdap/issues
= Where can I report sensitive security issues with the plugin? =
In essence: Report a security vulnerability at https://github.com/heiglandreas/authLdap/security/advisories/new
Please see https://github.com/heiglandreas/authLdap/blob/master/SECURITY.md for more details
== Changelog ==
= 2.5.9 =
* Adds information about security-contacts
* Addresses CVE-2023-41655
= 2.5.8 =
* Fix regression from 2.5.7
= 2.5.7 =
* Fix regressions from 2.5.4
* Fix CI system
= 2.5.4 =
* Update Tested up to
= 2.5.3 =
* Fix issue with broken role-assignement in combination with WooCommerce
* Fix spelling issue
* Allow DN as role-definition
= 2.5.0 =
* Ignore the order of capabilities to tell the role. In addition the filter `editable_roles` can be used to limit the roles

View File

@ -0,0 +1,6 @@
Contact: mailto://andreas@heigl.net
Contact: https://github.com/heiglandreas/authLdap/security/advisories/new
Expires: 2026-09-07T10:00:00.000Z
Encryption: https://andreas.heigl.org/publickey/
Encryption: https://heigl.org/.well-known/openpgpkey/hu/sfqdema7hgdj146cwzo4rxgsoujxis31
Preferred-Languages: en,de

View File

@ -1,13 +1,13 @@
<?php
declare(strict_types=1);
/**
* Copyright Andrea Heigl <andreas@heigl.org>
*
* Licenses under the MIT-license. For details see the included file LICENSE.md
*/
declare(strict_types=1);
namespace Org_Heigl\AuthLdap\Exception;
use Exception;

View File

@ -1,15 +1,74 @@
<?php
/**
* Copyright Andreas Heigl <andreas@heigl.org>
*
* Licenses under the MIT-license. For details see the included file LICENSE.md
*/
declare(strict_types=1);
namespace Org_Heigl\AuthLdap\Exception;
use RuntimeException;
use function sprintf;
class InvalidLdapUri extends RuntimeException
{
public static function fromLdapUriString(string $ldapUri): InvalidLdapUri
public static function cannotparse(string $ldapUri): self
{
return new self('"%s" is not a valid LDAP-URI.');
return new self(sprintf(
'%1$s seems not to be a valid URI',
$ldapUri
));
}
public static function wrongSchema(string $uri): self
{
return new self(sprintf(
'%1$s does not start with a valid schema',
$uri
));
}
public static function noSchema(string $uri): self
{
return new self(sprintf(
'%1$s does not provide a schema',
$uri
));
}
public static function noEnvironmentVariableSet(string $uri): self
{
return new self(sprintf(
'The environment variable %1$s does not provide a URI',
$uri
));
}
public static function noServerProvided(string $uri): self
{
return new self(sprintf(
'The LDAP-URI %1$s does not provide a server',
$uri
));
}
public static function noSearchBaseProvided(string $uri): self
{
return new self(sprintf(
'The LDAP-URI %1$s does not provide a search-base',
$uri
));
}
public static function invalidSearchBaseProvided(string $uri): self
{
return new self(sprintf(
'The LDAP-URI %1$s does not provide a valid search-base',
$uri
));
}
}

View File

@ -0,0 +1,23 @@
<?php
/**
* Copyright Andreas Heigl <andreas@heigl.org>
*
* Licenses under the MIT-license. For details see the included file LICENSE.md
*/
declare(strict_types=1);
namespace Org_Heigl\AuthLdap\Exception;
use RuntimeException;
class MissingValidLdapConnection extends Error
{
public static function get(): self
{
return new self(sprintf(
'No valid LDAP connection available'
));
}
}

View File

@ -0,0 +1,24 @@
<?php
/**
* Copyright Andreas Heigl <andreas@heigl.org>
*
* Licenses under the MIT-license. For details see the included file LICENSE.md
*/
declare(strict_types=1);
namespace Org_Heigl\AuthLdap\Exception;
use RuntimeException;
class SearchUnsuccessfull extends RuntimeException
{
public static function fromSearchFilter(string $filter): self
{
return new self(sprintf(
'Search for %1$s was not successfull',
$filter
));
}
}

View File

@ -1,4 +1,5 @@
<?php
/**
* Copyright (c) Andreas Heigl<andreas@heigl.org>
* Permission is hereby granted, free of charge, to any person obtaining a copy
@ -26,23 +27,26 @@
namespace Org_Heigl\AuthLdap;
use Exception;
use Org_Heigl\AuthLdap\Exception\Error;
use Org_Heigl\AuthLdap\Exception\SearchUnsuccessfull;
use Org_Heigl\AuthLdap\Manager\Ldap;
class LdapList
{
/**
* @var \LDAP[]
* @var Ldap[]
*/
protected $items = [];
public function addLdap(LDAP $ldap)
public function addLdap(Ldap $ldap)
{
$this->items[] = $ldap;
}
public function authenticate($username, $password, $filter = '(uid=%s)')
{
/** @var LDAP $item */
/** @var Ldap $item */
foreach ($this->items as $key => $item) {
if (! $item->authenticate($username, $password, $filter)) {
unset($this->items[$key]);
@ -81,10 +85,9 @@ class LdapList
$result = $item->search($filter, $attributes, $base);
return $result;
} catch (Exception $e) {
throw $e;
}
}
throw new \AuthLDAP_Exception('No Results found');
throw SearchUnsuccessfull::fromSearchFilter($filter);
}
}

View File

@ -1,7 +1,5 @@
<?php
declare(strict_types=1);
/**
* Copyright (c) Andreas Heigl<andreas@heigl.org>
* Permission is hereby granted, free of charge, to any person obtaining a copy
@ -27,23 +25,106 @@ declare(strict_types=1);
* @link http://github.com/heiglandreas/authLDAP
*/
declare(strict_types=1);
namespace Org_Heigl\AuthLdap;
use Org_Heigl\AuthLdap\Exception\InvalidLdapUri;
use function array_map;
use function error_get_last;
use function getenv;
use function preg_replace;
use function urlencode;
use function is_array;
use function is_string;
use function parse_url;
use function preg_replace_callback;
use function rawurlencode;
use function strlen;
use function strpos;
use function substr;
use function trim;
use function urldecode;
final class LdapUri
{
private $uri;
private $server;
private $scheme;
private $port = 389;
private string $baseDn;
private $username = '';
private $password = '';
private function __construct(string $uri)
{
if (!preg_match('/^(ldap|ldaps|env)/', $uri)) {
throw InvalidLdapUri::fromLdapUriString($uri);
throw InvalidLdapUri::wrongSchema($uri);
}
if (strpos($uri, 'env:') === 0) {
$newUri = getenv(substr($uri, 4));
if (false === $newUri) {
throw InvalidLdapUri::noEnvironmentVariableSet($uri);
}
$uri = (string) $newUri;
}
$uri = $this->injectEnvironmentVariables($uri);
$array = parse_url($uri);
if (!is_array($array)) {
throw InvalidLdapUri::cannotparse($uri);
}
$url = array_map(static function ($item) {
if (is_int($item)) {
return $item;
}
return urldecode($item);
}, $array);
if (!isset($url['scheme'])) {
throw InvalidLdapUri::noSchema($uri);
}
if (0 !== strpos($url['scheme'], 'ldap')) {
throw InvalidLdapUri::wrongSchema($uri);
}
if (!isset($url['host'])) {
throw InvalidLdapUri::noServerProvided($uri);
}
if (!isset($url['path'])) {
throw InvalidLdapUri::noSearchBaseProvided($uri);
}
if (1 === strlen($url['path'])) {
throw InvalidLdapUri::invalidSearchBaseProvided($uri);
}
$this->server = $url['host'];
$this->scheme = $url['scheme'];
$this->baseDn = substr($url['path'], 1);
if (isset($url['user'])) {
$this->username = $url['user'];
}
if ('' === trim($this->username)) {
$this->username = 'anonymous';
}
if (isset($url['pass'])) {
$this->password = $url['pass'];
}
if ($this->scheme === 'ldaps' && $this->port = 389) {
$this->port = 636;
}
// When someone sets the port in the URL we overwrite whatever is set.
// We have to assume they know what they are doing!
if (isset($url['port'])) {
$this->port = $url['port'];
}
$this->uri = $uri;
}
public static function fromString(string $uri): LdapUri
@ -51,22 +132,48 @@ final class LdapUri
return new LdapUri($uri);
}
public function toString(): string
private function injectEnvironmentVariables(string $base): string
{
$uri = $this->uri;
if (0 === strpos($uri, 'env:')) {
$uri = getenv(substr($this->uri, 4));
return preg_replace_callback('/%env:([^%]+)%/', static function (array $matches) {
return rawurlencode(getenv($matches[1]));
}, $base);
}
$uri = preg_replace_callback('/%env:([^%]+)%/', function (array $matches) {
return rawurlencode(getenv($matches[1]));
}, $uri);
return $uri;
public function toString(): string
{
return $this->scheme . '://' . $this->server . ':' . $this->port;
}
public function __toString()
{
return $this->toString();
}
public function getUsername(): string
{
return $this->username;
}
public function getPassword(): string
{
return $this->password;
}
public function getBaseDn(): string
{
return $this->baseDn;
}
public function isAnonymous(): bool
{
if ($this->password === '') {
return true;
}
if ($this->username === 'anonymous') {
return true;
}
return false;
}
}

View File

@ -0,0 +1,164 @@
<?php
/**
* $Id: ldap.php 381646 2011-05-06 09:37:31Z heiglandreas $
*
* authLdap - Authenticate Wordpress against an LDAP-Backend.
* Copyright (c) 2008 Andreas Heigl<andreas@heigl.org>
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* This file handles the basic LDAP-Tasks
*
* @author Andreas Heigl<andreas@heigl.org>
* @package authLdap
* @category authLdap
* @since 2008
*/
namespace Org_Heigl\AuthLdap\Manager;
use Org_Heigl\AuthLdap\Exception\Error;
use Org_Heigl\AuthLdap\Exception\MissingValidLdapConnection;
use Org_Heigl\AuthLdap\LdapUri;
use Org_Heigl\AuthLdap\Wrapper\LdapFactory;
use Org_Heigl\AuthLdap\Wrapper\LdapInterface;
class Ldap
{
/**
* This property contains the connection handle to the ldap-server
*
* @var LdapInterface|null
*/
private ?LdapInterface $connection;
private LdapUri $uri;
private LdapFactory $factory;
private $starttls;
public function __construct(LdapFactory $factory, LdapUri $uri, $starttls = false)
{
$this->starttls = $starttls;
$this->uri = $uri;
$this->factory = $factory;
$this->connection = null;
}
/**
* Connect to the given LDAP-Server
*/
public function connect(): self
{
$this->disconnect();
$this->connection = $this->factory->createFromLdapUri($this->uri->toString());
$this->connection->setOption(LDAP_OPT_PROTOCOL_VERSION, 3);
$this->connection->setOption(LDAP_OPT_REFERRALS, 0);
//if configured try to upgrade encryption to tls for ldap connections
if ($this->starttls) {
$this->connection->startTls();
}
return $this;
}
/**
* Disconnect from a resource if one is available
*/
public function disconnect(): self
{
if (null !== $this->connection) {
$this->connection->unbind();
}
$this->connection = null;
return $this;
}
/**
* Bind to an LDAP-Server with the given credentials
*
* @throws Error
*/
public function bind(): self
{
if (!$this->connection) {
$this->connect();
}
if (null === $this->connection) {
throw MissingValidLdapConnection::get();
}
if ($this->uri->isAnonymous()) {
$bind = $this->connection->bind();
} else {
$bind = $this->connection->bind($this->uri->getUsername(), $this->uri->getPassword());
}
if (!$bind) {
throw new Error('bind was not successfull: ' . $this->connection->error());
}
return $this;
}
/**
* This method does the actual ldap-serch.
*
* This is using the filter <var>$filter</var> for retrieving the attributes
* <var>$attributes</var>
*
* @return array<string|int, mixed>
* @throws Error
*/
public function search(string $filter, array $attributes = ['uid'], ?string $base = ''): array
{
if (null === $this->connection) {
throw new Error('No resource handle available');
}
if (!$base) {
$base = $this->uri->getBaseDn();
}
$result = $this->connection->search($base, $filter, $attributes);
if ($result === false) {
throw new Error('no result found');
}
$info = $this->connection->getEntries($result);
if ($info === false) {
throw new Error('invalid results found');
}
return $info;
}
/**
* This method authenticates the user <var>$username</var> using the
* password <var>$password</var>
*
* @param string $filter OPTIONAL This parameter defines the Filter to be used
* when searchin for the username. This MUST contain the string '%s' which
* will be replaced by the vaue given in <var>$username</var>
* @throws Error
*/
public function authenticate(string $username, string $password, string $filter = '(uid=%s)'): bool
{
$this->connect();
$this->bind();
$res = $this->search(sprintf($filter, $this->factory->escape($username, '', LDAP_ESCAPE_FILTER)));
if ($res ['count'] !== 1) {
return false;
}
$dn = $res[0]['dn'];
return $username && $password && $this->connection->bind($dn, $password);
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* Copyright Andreas Heigl <andreas@heigl.org>
*
* Licenses under the MIT-license. For details see the included file LICENSE.md
*/
declare(strict_types=1);
namespace Org_Heigl\AuthLdap;
use WP_User;
use function array_search;
use function in_array;
use function var_dump;
class UserRoleHandler
{
/**
* @param WP_User $user
* @param string[] $roles
* @return void
*/
public function addRolesToUser(WP_User $user, $roles) : void
{
if ($roles === []) {
return;
}
if ($user->roles == $roles) {
return;
}
// Remove unused roles from existing.
foreach ($user->roles as $role) {
if (!in_array($role, $roles)) {
// Remove unused roles.
$user->remove_role($role);
continue;
}
// Remove the existing role from roles.
if (($key = array_search($role, $roles)) !== false) {
unset($roles[$key]);
}
}
// Add new ones if not already assigned.
foreach ($roles as $role) {
$user->add_role($role);
}
}
}

View File

@ -0,0 +1,92 @@
<?php
/**
* Copyright Andreas Heigl <andreas@heigl.org>
*
* Licenses under the MIT-license. For details see the included file LICENSE.md
*/
declare(strict_types=1);
namespace Org_Heigl\AuthLdap\Wrapper;
use function ldap_bind;
use function ldap_connect;
use function ldap_error;
use function ldap_escape;
use function ldap_get_entries;
use function ldap_set_option;
use function ldap_start_tls;
use function ldap_unbind;
final class Ldap implements LdapInterface
{
private $connection;
public function __construct(string $ldapUri)
{
$this->connection = ldap_connect($ldapUri);
}
public function bind($dn = null, $password = null)
{
if (null === $dn && null === $password) {
return ldap_bind($this->connection);
}
return ldap_bind($this->connection, $dn, $password);
}
public function unbind()
{
return ldap_unbind($this->connection);
}
public function setOption($option, $value)
{
return ldap_set_option($this->connection, $option, $value);
}
public function startTls()
{
return ldap_start_tls($this->connection);
}
public function error()
{
return ldap_error($this->connection);
}
public function errno()
{
return ldap_errno($this->connection);
}
public function search(
$base,
$filter,
array $attributes = [],
$attributes_only = 0,
$sizelimit = -1,
$timelimit = -1
) {
return ldap_search(
$this->connection,
$base,
$filter,
$attributes,
$attributes_only,
$sizelimit,
$timelimit
);
}
public function getEntries($search_result)
{
return ldap_get_entries($this->connection, $search_result);
}
public static function escape(string $value, string $ignore = '', int $flags = 0): string
{
return ldap_escape($value, $ignore, $flags);
}
}

View File

@ -0,0 +1,24 @@
<?php
/**
* Copyright Andreas Heigl <andreas@heigl.org>
*
* Licenses under the MIT-license. For details see the included file LICENSE.md
*/
declare(strict_types=1);
namespace Org_Heigl\AuthLdap\Wrapper;
class LdapFactory
{
public function createFromLdapUri(string $ldapUri): LdapInterface
{
return new Ldap($ldapUri);
}
public function escape($value, $ignore = '', $flags = 0): string
{
return Ldap::escape($value, $ignore, $flags);
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Copyright Andreas Heigl <andreas@heigl.org>
*
* Licenses under the MIT-license. For details see the included file LICENSE.md
*/
declare(strict_types=1);
namespace Org_Heigl\AuthLdap\Wrapper;
interface LdapInterface
{
public function bind($dn = null, $password = null);
public function unbind();
public function setOption($option, $value);
public function startTls();
public function error();
public function errno();
public function search(
$base,
$filter,
array $attributes = [],
$attributes_only = 0,
$sizelimit = -1,
$timelimit = -1
);
public function getEntries($search_result);
public static function escape(string $value, string $ignore = '', int $flags = 0): string;
}

View File

@ -37,6 +37,7 @@
<?php endif ?>
<h2>AuthLDAP Options</h2>
<form class="authldap-options" method="post" id="authLDAP_options" action="<?php echo $action;?>">
<input name="authLdapNonce" type="hidden" value="<?php echo wp_create_nonce('authLdapNonce'); ?>" />
<h3 class="title">General Usage of authLDAP</h3>
<fieldset class="options">
<table class="form-table">