diff --git a/wp-content/plugins/authldap/.phive/phars.xml b/wp-content/plugins/authldap/.phive/phars.xml index 2825dbbc..34099137 100644 --- a/wp-content/plugins/authldap/.phive/phars.xml +++ b/wp-content/plugins/authldap/.phive/phars.xml @@ -3,4 +3,6 @@ + + diff --git a/wp-content/plugins/authldap/authLdap.php b/wp-content/plugins/authldap/authLdap.php index 542ea85e..cbe009d3 100644 --- a/wp-content/plugins/authldap/authLdap.php +++ b/wp-content/plugins/authldap/authLdap.php @@ -4,7 +4,7 @@ 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.9 +Version: 2.6.0 Author: Andreas Heigl Author URI: http://andreas.heigl.org License: MIT @@ -373,10 +373,10 @@ function authLdap_login($user, $username, $password, $already_md5 = false) // 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; + if (!$authLDAPGroupEnable || $authLDAPGroupOverUser) { + $userRoles = authLdap_user_role($uid); + if ($userRoles !== []) { + $roles = array_merge($roles, $userRoles); } // TODO, this needs to be revised, it seems, like authldap is taking only the first role // even if in WP there are assigned multiple. @@ -430,23 +430,23 @@ function authLdap_login($user, $username, $password, $already_md5 = false) $user_info['user_nicename'] = ''; // first name - if (isset($attribs[0][strtolower($authLDAPNameAttr)][0])) { - $user_info['first_name'] = $attribs[0][strtolower($authLDAPNameAttr)][0]; + if (isset($attribs[0][strtolower((string) $authLDAPNameAttr)][0])) { + $user_info['first_name'] = $attribs[0][strtolower((string) $authLDAPNameAttr)][0]; } // last name - if (isset($attribs[0][strtolower($authLDAPSecName)][0])) { - $user_info['last_name'] = $attribs[0][strtolower($authLDAPSecName)][0]; + if (isset($attribs[0][strtolower((string) $authLDAPSecName)][0])) { + $user_info['last_name'] = $attribs[0][strtolower((string) $authLDAPSecName)][0]; } // mail address - if (isset($attribs[0][strtolower($authLDAPMailAttr)][0])) { - $user_info['user_email'] = $attribs[0][strtolower($authLDAPMailAttr)][0]; + if (isset($attribs[0][strtolower((string) $authLDAPMailAttr)][0])) { + $user_info['user_email'] = $attribs[0][strtolower((string) $authLDAPMailAttr)][0]; } // website - if (isset($attribs[0][strtolower($authLDAPWebAttr)][0])) { - $user_info['user_url'] = $attribs[0][strtolower($authLDAPWebAttr)][0]; + if (isset($attribs[0][strtolower((string) $authLDAPWebAttr)][0])) { + $user_info['user_url'] = $attribs[0][strtolower((string) $authLDAPWebAttr)][0]; } // display name, nickname, nicename if (array_key_exists('first_name', $user_info)) { @@ -556,20 +556,20 @@ function authLdap_get_uid($username) * Returns empty string if not found. * * @param int $uid wordpress user id - * @return string role, empty if none found + * @return array roles, empty if none found */ function authLdap_user_role($uid) { global $wpdb, $wp_roles; if (!$uid) { - return ''; + return []; } /** @var array $usercapabilities */ $usercapabilities = get_user_meta($uid, "{$wpdb->prefix}capabilities", true); if (!is_array($usercapabilities)) { - return ''; + return []; } /** @var array} $editable_roles */ @@ -578,10 +578,10 @@ 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 !== []) ? $userroles[0] : ''; - authLdap_debug("Existing user's role: {$role}"); - return $role; + authLdap_debug(sprintf("Existing user's roles: %s", implode(', ', $userroles))); + + return $userroles; } /** diff --git a/wp-content/plugins/authldap/behat.yml.dist b/wp-content/plugins/authldap/behat.yml.dist new file mode 100644 index 00000000..0f63330a --- /dev/null +++ b/wp-content/plugins/authldap/behat.yml.dist @@ -0,0 +1 @@ +default: diff --git a/wp-content/plugins/authldap/features/bootstrap/FeatureContext.php b/wp-content/plugins/authldap/features/bootstrap/FeatureContext.php new file mode 100644 index 00000000..1e495b2c --- /dev/null +++ b/wp-content/plugins/authldap/features/bootstrap/FeatureContext.php @@ -0,0 +1,289 @@ +set(Options::URI, 'ldap://cn=admin,dc=example,dc=org:insecure@openldap:389/dc=example,dc=org'); + $options->set(Options::ENABLED, true); + $options->set(Options::FILTER, 'uid=%1$s'); + $options->set(Options::DEFAULT_ROLE, 'subscriber'); + $options->set(Options::DEBUG, true); + $options->set(Options::NAME_ATTR, 'cn'); + + exec(sprintf( + 'wp --allow-root option update --format=json authLDAPOptions \'%1$s\'', + json_encode($options->toArray()) + )); + } + + /** + * @Given configuration value :arg1 is set to :arg2 + */ + public function configurationValueIsSetTo($arg1, $arg2) + { + exec(sprintf( + 'wp --allow-root option patch update authLDAPOptions %1$s %2$s --format=json', + $arg1, + "'" . json_encode($arg2) . "'" + )); + } + + /** + * @Given an LDAP user :arg1 with name :arg2, password :arg3 and email :arg4 exists + */ + public function anLdapUserWithNamePasswordAndEmailExists($arg1, $arg2, $arg3, $arg4) + { + exec(sprintf( + 'ldapadd -x -H %1$s -D "%2$s" -w %3$s <res = $client->post('http://wp/wp-login.php', [ + 'cookies' => CookieJar::fromArray([ + 'wordpress_test_cookie' => 'test', + 'XDEBUG_SESSION' => 'PHPSTORM', + ], 'http://wp'), + 'form_params' => [ + 'log' => $arg1, + 'pwd' => $arg2, + ], + 'allow_redirects' => false + ]); + } + + /** + * @Then the login suceeds + */ + public function theLoginSuceeds() + { + Assert::isInstanceOf($this->res, Response::class); + Assert::eq( $this->res->getStatusCode(), 302); + Assert::startsWith($this->res->getHeader('Location')[0], 'http://localhost/wp-admin'); + } + + /** + * @Then a new WordPress user :arg1 was created with name :arg2 and email :arg3 + */ + public function aNewWordpressUserWasCreatedWithNameAndEmail($arg1, $arg2, $arg3) + { + exec(sprintf( + 'wp --allow-root user get %1$s --format=json 2> /dev/null', + $arg1, + ), $output, $result); + Assert::eq(0, $result); + $user = json_decode($output[0], true); + Assert::eq($user['user_email'], $arg3); + Assert::eq($user['display_name'], $arg2); + Assert::greaterThan( + new DateTimeImmutable($user['user_registered']), + (new DateTimeImmutable())->sub(new DateInterval('PT1M')), + ); + } + + /** + * @Then the WordPress user :arg1 is member of role :arg2 + */ + public function theWordpressUserIsMemberOfRole($arg1, $arg2) + { + exec(sprintf( + 'wp --allow-root user get %1$s --format=json 2> /dev/null', + $arg1, + ), $output, $result); + Assert::eq(0, $result); + $user = json_decode($output[0], true); + $roles = array_map(function($item): string { + return trim($item); + }, explode(',', $user['roles'])); + Assert::inArray($arg2, $roles); + } + + /** + * @Given LDAP user :arg1 is member of LDAP group :arg2 + */ + public function ldapUserIsMemberOfLdapGroup($arg1, $arg2) + { + exec(sprintf( + 'ldapmodify -x -H %1$s -D "%2$s" -w %3$s 2>&1 < /dev/null', + $arg1, + ), $output, $result); + Assert::eq(0, $result); + $user = json_decode($output[0], true); + $roles = array_map(function($item): string { + return trim($item); + }, explode(',', $user['roles'])); + Assert::false(in_array($arg2, $roles)); + + } +} diff --git a/wp-content/plugins/authldap/features/log in using no groups at all.feature b/wp-content/plugins/authldap/features/log in using no groups at all.feature new file mode 100644 index 00000000..b8515c84 --- /dev/null +++ b/wp-content/plugins/authldap/features/log in using no groups at all.feature @@ -0,0 +1,63 @@ +Feature: Log in without group assignment + Scenario: Login without group assignment with + Given a default configuration + And configuration value "GroupEnable" is set to "false" + And configuration value "DefaultRole" is set to "subscriber" + And an LDAP user "ldapuser" with name "LDAP User", password "P@ssw0rd" and email "ldapuser@example.com" exists + And an LDAP group "ldapgroup" exists + And LDAP user "ldapuser" is member of LDAP group "ldapgroup" + And a WordPress user "wordpressuser" with name "WordPress_User" and email "wordpressuser@example.com" exists + And a WordPress role "wordpressrole" exists + And WordPress user "wordpressuser" has role "wordpressrole" + And a WordPress user "ldapuser" does not exist + When LDAP user "ldapuser" logs in with password "P@ssw0rd" + Then the login suceeds + And a new WordPress user "ldapuser" was created with name "LDAP User" and email "ldapuser@example.com" + And the WordPress user "ldapuser" is member of role "subscriber" + + Scenario: Login with group assignment to multiple groups where only first wordpress group is used + Given a default configuration + And configuration value "GroupEnable" is set to "true" + And configuration value "DefaultRole" is set to "subscriber" + And configuration value "Groups" is set to "administrator=ldapgroup" and "editor=ldapgroup" + And configuration value "GroupAttr" is set to "cn" + And configuration value "GroupFilter" is set to "uniquemember=%dn%" + And configuration value "GroupOverUser" is set to "true" + And an LDAP user "ldapuser" with name "LDAP User", password "P@ssw0rd" and email "ldapuser@example.com" exists + And an LDAP group "ldapgroup" exists + And LDAP user "ldapuser" is member of LDAP group "ldapgroup" + And a WordPress user "wordpressuser" with name "WordPress_User" and email "wordpressuser@example.com" exists + And a WordPress role "wordpressrole" exists + And WordPress user "wordpressuser" has role "wordpressrole" + And a WordPress user "ldapuser" does not exist + When LDAP user "ldapuser" logs in with password "P@ssw0rd" + Then the login suceeds + And a new WordPress user "ldapuser" was created with name "LDAP User" and email "ldapuser@example.com" + And the WordPress user "ldapuser" is member of role "administrator" + And the WordPress user "ldapuser" is not member of role "editor" + And the WordPress user "ldapuser" is not member of role "subscriber" + + Scenario: Second Login with group assignment to multiple groups where only first wordpress group is used. + Given a default configuration + And configuration value "GroupEnable" is set to "true" + And configuration value "DefaultRole" is set to "subscriber" + And configuration value "Groups" is set to "administrator=ldapgroup" and "editor=ldapgroup" + And configuration value "GroupAttr" is set to "cn" + And configuration value "GroupFilter" is set to "uniquemember=%dn%" + And configuration value "GroupOverUser" is set to "false" + And an LDAP user "ldapuser" with name "LDAP User", password "P@ssw0rd" and email "ldapuser@example.com" exists + And an LDAP group "ldapgroup" exists + And LDAP user "ldapuser" is member of LDAP group "ldapgroup" + And a WordPress user "wordpressuser" with name "WordPress_User" and email "wordpressuser@example.com" exists + And a WordPress role "wordpressrole" exists + And WordPress user "wordpressuser" has role "wordpressrole" + And a WordPress user "ldapuser" does not exist + And LDAP user "ldapuser" logs in with password "P@ssw0rd" + And WordPress user "ldapuser" has role "wordpressrole" + And the WordPress user "ldapuser" is member of role "wordpressrole" + When LDAP user "ldapuser" logs in with password "P@ssw0rd" + Then the login suceeds + And the WordPress user "ldapuser" is member of role "administrator" + And the WordPress user "ldapuser" is member of role "wordpressrole" + And the WordPress user "ldapuser" is not member of role "editor" + And the WordPress user "ldapuser" is not member of role "subscriber" diff --git a/wp-content/plugins/authldap/readme.txt b/wp-content/plugins/authldap/readme.txt index 1c6084da..00476fe6 100644 --- a/wp-content/plugins/authldap/readme.txt +++ b/wp-content/plugins/authldap/readme.txt @@ -2,7 +2,7 @@ Contributors: heiglandreas Tags: ldap, auth, authentication, active directory, AD, openLDAP, Open Directory Requires at least: 2.5.0 -Tested up to: 6.3.0 +Tested up to: 6.4.0 Requires PHP: 7.4 Stable tag: trunk License: MIT @@ -47,6 +47,11 @@ Please see https://github.com/heiglandreas/authLdap/blob/master/SECURITY.md for == Changelog == += 2.6.0 = + +* Fix reducing assigned WordPress roles to single role on login when WordPress roles shall be kept +* Add Behavioural testing and first 3 scenarios + = 2.5.9 = * Adds information about security-contacts diff --git a/wp-content/plugins/authldap/src/Exception/UnknownOption.php b/wp-content/plugins/authldap/src/Exception/UnknownOption.php new file mode 100644 index 00000000..b2737b19 --- /dev/null +++ b/wp-content/plugins/authldap/src/Exception/UnknownOption.php @@ -0,0 +1,24 @@ + + * + * Licensed under the MIT-license. For details see the included file LICENSE.md + */ + +namespace Org_Heigl\AuthLdap\Exception; + +use RuntimeException; + +class UnknownOption extends RuntimeException +{ + public static function withKey(string $key): self + { + return new self(sprintf( + 'An option "%1$s" is not known', + $key + )); + } +} diff --git a/wp-content/plugins/authldap/src/LdapUri.php b/wp-content/plugins/authldap/src/LdapUri.php index 2d2169fc..265ec96e 100644 --- a/wp-content/plugins/authldap/src/LdapUri.php +++ b/wp-content/plugins/authldap/src/LdapUri.php @@ -116,7 +116,7 @@ final class LdapUri if (isset($url['pass'])) { $this->password = $url['pass']; } - if ($this->scheme === 'ldaps' && $this->port = 389) { + if ($this->scheme === 'ldaps' && $this->port === 389) { $this->port = 636; } diff --git a/wp-content/plugins/authldap/src/OptionFactory.php b/wp-content/plugins/authldap/src/OptionFactory.php new file mode 100644 index 00000000..aa2955e4 --- /dev/null +++ b/wp-content/plugins/authldap/src/OptionFactory.php @@ -0,0 +1,27 @@ + + * + * Licensed under the MIT-license. For details see the included file LICENSE.md + */ + +namespace Org_Heigl\AuthLdap; + +use function json_decode; + +class OptionFactory +{ + public function fromJson(string $json): Options + { + $option = new Options(); + $content = json_decode($json, true); + foreach ($content as $key => $value) { + $option->set($key, $value); + } + + return $option; + } +} diff --git a/wp-content/plugins/authldap/src/Options.php b/wp-content/plugins/authldap/src/Options.php new file mode 100644 index 00000000..ee7b969c --- /dev/null +++ b/wp-content/plugins/authldap/src/Options.php @@ -0,0 +1,90 @@ + + * + * Licensed under the MIT-license. For details see the included file LICENSE.md + */ + +namespace Org_Heigl\AuthLdap; + +use Org_Heigl\AuthLdap\Exception\UnknownOption; +use function array_key_exists; + +class Options +{ + public const ENABLED = 'Enabled'; + public const CACHE_PW = 'CachePW'; + public const URI = 'URI'; + public const URI_SEPARATOR = 'URISeparator'; + public const FILTER = 'Filter'; + public const NAME_ATTR = 'NameAttr'; + public const SEC_NAME = 'SecName'; + public const UID_ATTR = 'UidAttr'; + public const MAIL_ATTR = 'MailAttr'; + public const WEB_ATTR = 'WebAttr'; + public const GROUPS = 'Groups'; + public const DEBUG = 'Debug'; + public const GROUP_ATTR = 'GroupAttr'; + public const GROUP_FILTER = 'GroupFilter'; + public const DEFAULT_ROLE = 'DefaultRole'; + public const GROUP_ENABLE = 'GroupEnable'; + public const GROUP_OVER_USER = 'GroupOverUser'; + public const VERSION = 'Version'; + public const DO_NOT_OVERWRITE_NON_LDAP_USERS = 'DoNotOverwriteNonLdapUsers'; + + private array $settings = [ + 'Enabled' => false, + 'CachePW' => false, + 'URI' => '', + 'URISeparator' => ' ', + 'Filter' => '', // '(uid=%s)' + 'NameAttr' => '', // 'name' + 'SecName' => '', + 'UidAttr' => '', // 'uid' + 'MailAttr' => '', // 'mail' + 'WebAttr' => '', + 'Groups' => [], + 'Debug' => false, + 'GroupAttr' => '', // 'gidNumber' + 'GroupFilter' => '', // '(&(objectClass=posixGroup)(memberUid=%s))' + 'DefaultRole' => '', + 'GroupEnable' => true, + 'GroupOverUser' => true, + 'Version' => 1, + 'DoNotOverwriteNonLdapUsers' => false, + ]; + + public function get(string $key) + { + if (! array_key_exists($key, $this->settings)) { + throw UnknownOption::withKey($key); + } + + return $this->settings[$key]; + } + + public function has(string $key): bool + { + return array_key_exists($key, $this->settings); + } + + /** + * @param mixed $value + */ + public function set(string $key, $value): void + { + if (! array_key_exists($key, $this->settings)) { + throw UnknownOption::withKey($key); + } + + $this->settings[$key] = $value; + } + + public function toArray(): array + { + return $this->settings; + } +} diff --git a/wp-content/plugins/authldap/src/Wrapper/Ldap.php b/wp-content/plugins/authldap/src/Wrapper/Ldap.php index 530de8e8..48b01d34 100644 --- a/wp-content/plugins/authldap/src/Wrapper/Ldap.php +++ b/wp-content/plugins/authldap/src/Wrapper/Ldap.php @@ -18,6 +18,7 @@ use function ldap_get_entries; use function ldap_set_option; use function ldap_start_tls; use function ldap_unbind; +use function var_dump; final class Ldap implements LdapInterface {