151a5505 by Jeff Balicki

authLdap

Signed-off-by: Jeff <jeff@gotenzing.com>
1 parent 55e5d640
Showing 43 changed files with 3546 additions and 983 deletions
1 dn: dc=test space,{{ LDAP_BASE_DN }}
2 changetype: add
3 dc: test space
4 description: LDAP Example with space
5 objectClass: dcObject
6 objectClass: organization
7 o: test space
8
9 dn: cn=Manager,{{ LDAP_BASE_DN }}
10 changetype: add
11 cn: Manager
12 objectClass: organizationalRole
13
14 dn: ou=test,{{ LDAP_BASE_DN }}
15 changetype: add
16 objectClass: organizationalUnit
17 ou: test
18
19 dn: uid=user1,{{ LDAP_BASE_DN }}
20 changetype: add
21 objectClass: account
22 objectClass: simpleSecurityObject
23 uid: user1
24 userPassword: user1
25
26 dn: cn=group1,{{ LDAP_BASE_DN }}
27 changetype: add
28 objectclass: groupOfUniqueNames
29 cn: group1
30 uniqueMember: uid=user1,{{ LDAP_BASE_DN }}
31
32 dn: uid=user2,{{ LDAP_BASE_DN }}
33 changetype: add
34 objectClass: account
35 objectClass: simpleSecurityObject
36 uid: user2
37 userPassword: user2
38
39 dn: cn=group2,{{ LDAP_BASE_DN }}
40 changetype: add
41 objectclass: groupOfUniqueNames
42 cn: group2
43 uniqueMember: uid=user2,{{ LDAP_BASE_DN }}
44
45 dn: uid=user3,{{ LDAP_BASE_DN }}
46 changetype: add
47 objectClass: account
48 objectClass: simpleSecurityObject
49 uid: user3
50 userPassword: user!"
51
52 dn: cn=group3,{{ LDAP_BASE_DN }}
53 changetype: add
54 objectclass: groupOfUniqueNames
55 cn: group3
56 uniqueMember: uid=user2,{{ LDAP_BASE_DN }}
57 uniqueMember: uid=user3,{{ LDAP_BASE_DN }}
58
59 dn: uid=user 4,{{ LDAP_BASE_DN }}
60 changetype: add
61 objectClass: account
62 objectClass: simpleSecurityObject
63 uid: user 4
64 userPassword: user!"
65
66 dn: cn=group4,{{ LDAP_BASE_DN }}
67 changetype: add
68 objectclass: groupOfUniqueNames
69 cn: group4
70 uniqueMember: uid=user 4,{{ LDAP_BASE_DN }}
71
72 dn: uid=user 5,dc=test space,{{ LDAP_BASE_DN }}
73 changetype: add
74 objectClass: account
75 objectClass: simpleSecurityObject
76 uid: user 5
77 userPassword: user!"
78
79 dn: cn=group5,{{ LDAP_BASE_DN }}
80 changetype: add
81 objectclass: groupOfUniqueNames
82 cn: group5
83 uniqueMember: uid=user 5,dc=test space,{{ LDAP_BASE_DN }}
1 .ci
2 .github
3 build
4 config
5 dockersetup
6 svn
7 tests
8 vendor
9 wp-app
10 wd-data
11 .distignore
12 .editorconfig
13 .gitignore
14 .phpunit.result.cache
15 .svnAccess
16 .svnAccess.dist
17 build.xml
18 composer.json
19 composer.lock
20 docker-compose.yml
21 GPG_KEY
22 phpunit.xml.dist
23 .git
1 root = true
2
3 [*]
4 charset = utf-8
5 tab_width = 4
6 trim_trailing_whitespace = true
7 indent_size = tab
8 indent_style = tab
9 insert_final_newline = true
10 end_of_line = lf
11
12 [*.yml]
13 tab_width = 2
14
15 [*.xml]
16 tab_width = 2
17
18 [*.json]
19 tab_width = 2
1 name: Deploy to WordPress.org
2
3 env:
4 SLUG: authldap
5 on:
6 release:
7 types: [published]
8 jobs:
9 tag:
10 name: New release
11 runs-on: ubuntu-latest
12 environment: deploy_on_release
13 steps:
14 - name: Checkout code
15 uses: actions/checkout@v2
16 - name: WordPress Plugin Deploy
17 id: deploy
18 uses: 10up/action-wordpress-plugin-deploy@stable
19 with:
20 generate-zip: true
21 env:
22 SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
23 SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
24 - name: Upload release asset
25 uses: actions/upload-release-asset@v1
26 env:
27 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28 with:
29 upload_url: ${{ github.event.release.upload_url }}
30 asset_path: ${{ steps.deploy.outputs.zip-path }}
31 asset_name: ${{ env.SLUG }}.zip
32 asset_content_type: application/zip
33
1 name: CI
2 on: [push, pull_request]
3 jobs:
4 test:
5 runs-on: ubuntu-latest
6 env:
7 PORT_LDAP: 3389
8 PORT_LDAPS: 6363
9 LDAP_ADMIN_PASSWORD: ${{ secrets.LDAP_ADMIN_PASSWORD }}
10 LDAP_LOG_LEVEL: 0
11 strategy:
12 matrix:
13 # operating-system: [ubuntu-latest, windows-latest, macos-latest]
14 php-versions: [ '7.4', '8.0', '8.1' ]
15 name: Test on ${{ matrix.php-versions }}
16 steps:
17 - uses: actions/checkout@v1
18 - name: Build the docker-compose stack
19 run: docker-compose -f docker-compose.yml up -d
20 - name: Check running containers
21 run: docker ps -a
22 - name: Check logs
23 run: docker-compose logs openldap
24 - name: Setup PHP
25 uses: shivammathur/setup-php@v2
26 with:
27 php-version: ${{ matrix.php-versions }}
28 extensions: ldap
29 tools: phive
30 - name: install dependencies
31 run: composer install
32 - name: install tools
33 run: phive install --trust-gpg-keys 4AA394086372C20A phpunit
34 - name: Run Unit-Tests
35 run: ./tools/phpunit --testdox
36 coverage:
37 needs: test
38 runs-on: ubuntu-latest
39 env:
40 PORT_LDAP: 3389
41 PORT_LDAPS: 6363
42 LDAP_ADMIN_PASSWORD: ${{ secrets.LDAP_ADMIN_PASSWORD }}
43 LDAP_LOG_LEVEL: 0
44 continue-on-error: false
45 steps:
46 - name: Checkout
47 uses: actions/checkout@v2
48 - name: Build the docker-compose stack
49 run: docker-compose -f docker-compose.yml up -d
50 - name: Check running containers
51 run: docker ps -a
52 - name: Setup PHP
53 uses: shivammathur/setup-php@v2
54 with:
55 php-version: "8.0"
56 coverage: xdebug
57 tools: phive
58 - name: install dependencies
59 run: composer install
60 - name: install tools
61 run: phive install --trust-gpg-keys 4AA394086372C20A phpunit
62 - name: run testsuite
63 run: ./tools/phpunit --testdox --colors=always --coverage-clover clover.xml
64 - name: upload to codecov
65 uses: codecov/codecov-action@v1
66 with:
67 #token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
68 files: ./clover.xml # optional
69 #flags: unittests # optional
70 #name: codecov-umbrella # optional
71 #fail_ci_if_error: true # optional (default = false)
72 #verbose: true # optional (default = false)
1 <?xml version="1.0" encoding="UTF-8"?>
2 <phive xmlns="https://phar.io/phive">
3 <phar name="phpunit" version="^9.5.21" installed="9.5.21" location="./tools/phpunit" copy="true"/>
4 <phar name="phpcs" version="^3.7.1" installed="3.7.1" location="./tools/phpcs" copy="true"/>
5 <phar name="phpcbf" version="^3.7.1" installed="3.7.1" location="./tools/phpcbf" copy="true"/>
6 </phive>
1 username = svnUsername
2 password = svnPassword
...\ No newline at end of file ...\ No newline at end of file
1 Copyright <YEAR> <COPYRIGHT HOLDER>
2
3 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
5 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
7 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
...\ No newline at end of file ...\ No newline at end of file
...@@ -4,12 +4,12 @@ ...@@ -4,12 +4,12 @@
4 4
5 Use your existing LDAP as authentication-backend for your wordpress! 5 Use your existing LDAP as authentication-backend for your wordpress!
6 6
7 [![Build Status](https://travis-ci.org/heiglandreas/authLdap.svg?branch=master)](https://travis-ci.org/heiglandreas/authLdap) 7 [![Build Status](https://github.com/heiglandreas/authLdap/workflows/CI/badge.svg)](https://github.com/heiglandreas/authLdap/workflows/CI)
8 [![WordPress Stats](https://img.shields.io/wordpress/plugin/dt/authldap.svg)](https://wordpress.org/plugins/authldap/stats/) 8 [![WordPress Stats](https://img.shields.io/wordpress/plugin/dt/authldap.svg)](https://wordpress.org/plugins/authldap/stats/)
9 [![WordPress Version](https://img.shields.io/wordpress/plugin/v/authldap.svg)](https://wordpress.org/plugins/authldap/) 9 [![WordPress Version](https://img.shields.io/wordpress/plugin/v/authldap.svg)](https://wordpress.org/plugins/authldap/)
10 [![WordPress testet](https://img.shields.io/wordpress/v/authldap.svg)](https://wordpress.org/plugins/authldap/) 10 [![WordPress testet](https://img.shields.io/wordpress/v/authldap.svg)](https://wordpress.org/plugins/authldap/)
11 [![Code Climate](https://codeclimate.com/github/heiglandreas/authLdap/badges/gpa.svg)](https://codeclimate.com/github/heiglandreas/authLdap) 11 [![Code Climate](https://codeclimate.com/github/heiglandreas/authLdap/badges/gpa.svg)](https://codeclimate.com/github/heiglandreas/authLdap)
12 [![Test Coverage](https://codeclimate.com/github/heiglandreas/authLdap/badges/coverage.svg)](https://codeclimate.com/github/heiglandreas/authLdap) 12 [![codecov](https://codecov.io/gh/heiglandreas/authLdap/branch/master/graph/badge.svg?token=AYAhEeWtRQ)](https://codecov.io/gh/heiglandreas/authLdap)
13 13
14 So what are the differences to other Wordpress-LDAP-Authentication-Plugins? 14 So what are the differences to other Wordpress-LDAP-Authentication-Plugins?
15 15
...@@ -48,8 +48,8 @@ wonderfull plugin of Alistair Young from http://www.weblogs.uhi.ac.uk/sm00ay/?p= ...@@ -48,8 +48,8 @@ wonderfull plugin of Alistair Young from http://www.weblogs.uhi.ac.uk/sm00ay/?p=
48 * **LDAP Uri** This is the URI where your ldap-backend can be reached. More information are actually on the Configuration page 48 * **LDAP Uri** This is the URI where your ldap-backend can be reached. More information are actually on the Configuration page
49 * **Filter** This is the real McCoy! The filter you define here specifies how a user will be found. Before applying the filter a %s will be replaced with the given username. This means, when a user logs in using ‘foobar’ as username the following happens: 49 * **Filter** This is the real McCoy! The filter you define here specifies how a user will be found. Before applying the filter a %s will be replaced with the given username. This means, when a user logs in using ‘foobar’ as username the following happens:
50 50
51 * **uid=%s** check for any LDAP-Entry that has an attribute ‘uid’ with value ‘foobar’ 51 * **uid=%1$s** check for any LDAP-Entry that has an attribute ‘uid’ with value ‘foobar’
52 * **(&(objectclass=posixAccount)((!(uid=%s)(mail=%s)))** check for any LDAP-Entry that has an attribute ‘objectclass’ with value ‘posixAccout’ and either a UID- or a mail-attribute with value ‘foobar’ 52 * **(&(objectclass=posixAccount)(|(uid=%1$s)(mail=%1$s)))** check for any LDAP-Entry that has an attribute ‘objectclass’ with value ‘posixAccout’ and either a UID- or a mail-attribute with value ‘foobar’
53 53
54 This filter is rather powerfull if used wisely. 54 This filter is rather powerfull if used wisely.
55 55
...@@ -92,4 +92,4 @@ wonderfull plugin of Alistair Young from http://www.weblogs.uhi.ac.uk/sm00ay/?p= ...@@ -92,4 +92,4 @@ wonderfull plugin of Alistair Young from http://www.weblogs.uhi.ac.uk/sm00ay/?p=
92 <a href="https://github.com/heiglandreas/authLdap/issues/65">issue 65</a> 92 <a href="https://github.com/heiglandreas/authLdap/issues/65">issue 65</a>
93 where <a href="https://github.com/wtfiwtz">wtfiwtz</a> shows how to implement that feature. 93 where <a href="https://github.com/wtfiwtz">wtfiwtz</a> shows how to implement that feature.
94 </dd> 94 </dd>
95 </dl>
...\ No newline at end of file ...\ No newline at end of file
95 </dl>
......
1 .row {
2 overflow: hidden;
3 padding-top: 10px;
4 }
5
6 .element {
7 float: right;
8 text-align: left;
9 }
10
11 .authldap-options input[type=text] {
12 width: 100%;
13 }
1 <?php
2
3 /*
4 Plugin Name: AuthLDAP
5 Plugin URI: https://github.com/heiglandreas/authLdap
6 Description: This plugin allows you to use your existing LDAP as authentication base for WordPress
7 Version: 2.5.2
8 Author: Andreas Heigl <andreas@heigl.org>
9 Author URI: http://andreas.heigl.org
10 License: MIT
11 License URI: https://opensource.org/licenses/MIT
12 */
13
14 // phpcs:disable PSR1.Files.SideEffects
15
16 use Org_Heigl\AuthLdap\LdapList;
17 use Org_Heigl\AuthLdap\LdapUri;
18 use Org_Heigl\AuthLdap\Manager\Ldap;
19 use Org_Heigl\AuthLdap\UserRoleHandler;
20 use Org_Heigl\AuthLdap\Wrapper\LdapFactory;
21
22 require_once __DIR__ . '/src/Wrapper/LdapInterface.php';
23 require_once __DIR__ . '/src/Exception/Error.php';
24 require_once __DIR__ . '/src/Exception/InvalidLdapUri.php';
25 require_once __DIR__ . '/src/Exception/Error.php';
26 require_once __DIR__ . '/src/Exception/InvalidLdapUri.php';
27 require_once __DIR__ . '/src/Exception/MissingValidLdapConnection.php';
28 require_once __DIR__ . '/src/Exception/SearchUnsuccessfull.php';
29 require_once __DIR__ . '/src/Manager/Ldap.php';
30 require_once __DIR__ . '/src/Wrapper/Ldap.php';
31 require_once __DIR__ . '/src/Wrapper/LdapFactory.php';
32 require_once __DIR__ . '/src/LdapList.php';
33 require_once __DIR__ . '/src/LdapUri.php';
34 require_once __DIR__ . '/src/UserRoleHandler.php';
35
36 function authLdap_debug($message)
37 {
38 if (authLdap_get_option('Debug')) {
39 error_log('[AuthLDAP] ' . $message, 0);
40 }
41 }
42
43
44 function authLdap_addmenu()
45 {
46 if (!is_multisite()) {
47 add_options_page(
48 'AuthLDAP',
49 'AuthLDAP',
50 'manage_options',
51 basename(__FILE__),
52 'authLdap_options_panel'
53 );
54 } else {
55 add_submenu_page(
56 'settings.php',
57 'AuthLDAP',
58 'AuthLDAP',
59 'manage_options',
60 'authldap',
61 'authLdap_options_panel'
62 );
63 }
64 }
65
66 function authLdap_get_post($name, $default = '')
67 {
68 return isset($_POST[$name]) ? $_POST[$name] : $default;
69 }
70
71 function authLdap_options_panel()
72 {
73 // inclusde style sheet
74 wp_enqueue_style('authLdap-style', plugin_dir_url(__FILE__) . 'authLdap.css');
75
76 if (($_SERVER['REQUEST_METHOD'] == 'POST') && array_key_exists('ldapOptionsSave', $_POST)) {
77 $new_options = [
78 'Enabled' => authLdap_get_post('authLDAPAuth', false),
79 'CachePW' => authLdap_get_post('authLDAPCachePW', false),
80 'URI' => authLdap_get_post('authLDAPURI'),
81 'URISeparator' => authLdap_get_post('authLDAPURISeparator'),
82 'StartTLS' => authLdap_get_post('authLDAPStartTLS', false),
83 'Filter' => authLdap_get_post('authLDAPFilter'),
84 'NameAttr' => authLdap_get_post('authLDAPNameAttr'),
85 'SecName' => authLdap_get_post('authLDAPSecName'),
86 'UidAttr' => authLdap_get_post('authLDAPUidAttr'),
87 'MailAttr' => authLdap_get_post('authLDAPMailAttr'),
88 'WebAttr' => authLdap_get_post('authLDAPWebAttr'),
89 'Groups' => authLdap_get_post('authLDAPGroups', []),
90 'GroupSeparator' => authLdap_get_post('authLDAPGroupSeparator', ','),
91 'Debug' => authLdap_get_post('authLDAPDebug', false),
92 'GroupBase' => authLdap_get_post('authLDAPGroupBase'),
93 'GroupAttr' => authLdap_get_post('authLDAPGroupAttr'),
94 'GroupFilter' => authLdap_get_post('authLDAPGroupFilter'),
95 'DefaultRole' => authLdap_get_post('authLDAPDefaultRole'),
96 'GroupEnable' => authLdap_get_post('authLDAPGroupEnable', false),
97 'GroupOverUser' => authLdap_get_post('authLDAPGroupOverUser', false),
98 'DoNotOverwriteNonLdapUsers' => authLdap_get_post('authLDAPDoNotOverwriteNonLdapUsers', false),
99 'UserRead' => authLdap_get_post('authLDAPUseUserAccount', false),
100 ];
101 if (authLdap_set_options($new_options)) {
102 echo "<div class='updated'><p>Saved Options!</p></div>";
103 } else {
104 echo "<div class='error'><p>Could not save Options!</p></div>";
105 }
106 }
107
108 // Do some initialization for the admin-view
109 $authLDAP = authLdap_get_option('Enabled');
110 $authLDAPCachePW = authLdap_get_option('CachePW');
111 $authLDAPURI = authLdap_get_option('URI');
112 $authLDAPURISeparator = authLdap_get_option('URISeparator');
113 $authLDAPStartTLS = authLdap_get_option('StartTLS');
114 $authLDAPFilter = authLdap_get_option('Filter');
115 $authLDAPNameAttr = authLdap_get_option('NameAttr');
116 $authLDAPSecName = authLdap_get_option('SecName');
117 $authLDAPMailAttr = authLdap_get_option('MailAttr');
118 $authLDAPUidAttr = authLdap_get_option('UidAttr');
119 $authLDAPWebAttr = authLdap_get_option('WebAttr');
120 $authLDAPGroups = authLdap_get_option('Groups');
121 $authLDAPGroupSeparator = authLdap_get_option('GroupSeparator');
122 $authLDAPDebug = authLdap_get_option('Debug');
123 $authLDAPGroupBase = authLdap_get_option('GroupBase');
124 $authLDAPGroupAttr = authLdap_get_option('GroupAttr');
125 $authLDAPGroupFilter = authLdap_get_option('GroupFilter');
126 $authLDAPDefaultRole = authLdap_get_option('DefaultRole');
127 $authLDAPGroupEnable = authLdap_get_option('GroupEnable');
128 $authLDAPGroupOverUser = authLdap_get_option('GroupOverUser');
129 $authLDAPDoNotOverwriteNonLdapUsers = authLdap_get_option('DoNotOverwriteNonLdapUsers');
130 $authLDAPUseUserAccount = authLdap_get_option('UserRead');
131
132 $tChecked = ($authLDAP) ? ' checked="checked"' : '';
133 $tDebugChecked = ($authLDAPDebug) ? ' checked="checked"' : '';
134 $tPWChecked = ($authLDAPCachePW) ? ' checked="checked"' : '';
135 $tGroupChecked = ($authLDAPGroupEnable) ? ' checked="checked"' : '';
136 $tGroupOverUserChecked = ($authLDAPGroupOverUser) ? ' checked="checked"' : '';
137 $tStartTLSChecked = ($authLDAPStartTLS) ? ' checked="checked"' : '';
138 $tDoNotOverwriteNonLdapUsers = ($authLDAPDoNotOverwriteNonLdapUsers) ? ' checked="checked"' : '';
139 $tUserRead = ($authLDAPUseUserAccount) ? ' checked="checked"' : '';
140
141 $roles = new WP_Roles();
142
143 $action = $_SERVER['REQUEST_URI'];
144 if (!extension_loaded('ldap')) {
145 echo '<div class="warning">The LDAP-Extension is not available on your '
146 . 'WebServer. Therefore Everything you can alter here does not '
147 . 'make any sense!</div>';
148 }
149
150 include dirname(__FILE__) . '/view/admin.phtml';
151 }
152
153 /**
154 * get a LDAP server object
155 *
156 * throws exception if there is a problem connecting
157 *
158 * @conf boolean authLDAPDebug true, if debugging should be turned on
159 * @conf string authLDAPURI LDAP server URI
160 *
161 * @return Org_Heigl\AuthLdap\LdapList LDAP server object
162 */
163 function authLdap_get_server()
164 {
165 static $_ldapserver = null;
166 if (is_null($_ldapserver)) {
167 $authLDAPDebug = authLdap_get_option('Debug');
168 $authLDAPURI = explode(
169 authLdap_get_option('URISeparator', ' '),
170 authLdap_get_option('URI')
171 );
172 $authLDAPStartTLS = authLdap_get_option('StartTLS');
173
174 //$authLDAPURI = 'ldap:/foo:bar@server/trallala';
175 authLdap_debug('connect to LDAP server');
176 require_once dirname(__FILE__) . '/src/LdapList.php';
177 $_ldapserver = new LdapList();
178 foreach ($authLDAPURI as $uri) {
179 $_ldapserver->addLdap(new Ldap(
180 new LdapFactory(),
181 LdapUri::fromString($uri),
182 $authLDAPStartTLS
183 ));
184 }
185 }
186 return $_ldapserver;
187 }
188
189
190 /**
191 * This method authenticates a user using either the LDAP or, if LDAP is not
192 * available, the local database
193 *
194 * For this we store the hashed passwords in the WP_Database to ensure working
195 * conditions even without an LDAP-Connection
196 *
197 * @param null|WP_User|WP_Error
198 * @param string $username
199 * @param string $password
200 * @param boolean $already_md5
201 * @return boolean true, if login was successfull or false, if it wasn't
202 * @conf boolean authLDAP true, if authLDAP should be used, false if not. Defaults to false
203 * @conf string authLDAPFilter LDAP filter to use to find correct user, defaults to '(uid=%s)'
204 * @conf string authLDAPNameAttr LDAP attribute containing user (display) name, defaults to 'name'
205 * @conf string authLDAPSecName LDAP attribute containing second name, defaults to ''
206 * @conf string authLDAPMailAttr LDAP attribute containing user e-mail, defaults to 'mail'
207 * @conf string authLDAPUidAttr LDAP attribute containing user id (the username we log on with), defaults to 'uid'
208 * @conf string authLDAPWebAttr LDAP attribute containing user website, defaults to ''
209 * @conf string authLDAPDefaultRole default role for authenticated user, defaults to ''
210 * @conf boolean authLDAPGroupEnable true, if we try to map LDAP groups to Wordpress roles
211 * @conf boolean authLDAPGroupOverUser true, if LDAP Groups have precedence over existing user roles
212 */
213 function authLdap_login($user, $username, $password, $already_md5 = false)
214 {
215 // don't do anything when authLDAP is disabled
216 if (!authLdap_get_option('Enabled')) {
217 authLdap_debug(
218 'LDAP disabled in AuthLDAP plugin options (use the first option in the AuthLDAP options to enable it)'
219 );
220 return $user;
221 }
222
223 // If the user has already been authenticated (only in that case we get a
224 // WP_User-Object as $user) we skip LDAP-authentication and simply return
225 // the existing user-object
226 if ($user instanceof WP_User) {
227 authLdap_debug(sprintf(
228 'User %s has already been authenticated - skipping LDAP-Authentication',
229 $user->get('nickname')
230 ));
231 return $user;
232 }
233
234 authLdap_debug("User '$username' logging in");
235
236 if ($username == 'admin') {
237 authLdap_debug('Doing nothing for possible local user admin');
238 return $user;
239 }
240
241 global $wpdb, $error;
242 try {
243 $authLDAP = authLdap_get_option('Enabled');
244 $authLDAPFilter = authLdap_get_option('Filter');
245 $authLDAPNameAttr = authLdap_get_option('NameAttr');
246 $authLDAPSecName = authLdap_get_option('SecName');
247 $authLDAPMailAttr = authLdap_get_option('MailAttr');
248 $authLDAPUidAttr = authLdap_get_option('UidAttr');
249 $authLDAPWebAttr = authLdap_get_option('WebAttr');
250 $authLDAPDefaultRole = authLdap_get_option('DefaultRole');
251 $authLDAPGroupEnable = authLdap_get_option('GroupEnable');
252 $authLDAPGroupOverUser = authLdap_get_option('GroupOverUser');
253 $authLDAPUseUserAccount = authLdap_get_option('UserRead');
254
255 if (!$username) {
256 authLdap_debug('Username not supplied: return false');
257 return false;
258 }
259
260 if (!$password) {
261 authLdap_debug('Password not supplied: return false');
262 $error = __('<strong>Error</strong>: The password field is empty.');
263 return false;
264 }
265 // First check for valid values and set appropriate defaults
266 if (!$authLDAPFilter) {
267 $authLDAPFilter = '(uid=%s)';
268 }
269 if (!$authLDAPNameAttr) {
270 $authLDAPNameAttr = 'name';
271 }
272 if (!$authLDAPMailAttr) {
273 $authLDAPMailAttr = 'mail';
274 }
275 if (!$authLDAPUidAttr) {
276 $authLDAPUidAttr = 'uid';
277 }
278
279 // If already_md5 is TRUE, then we're getting the user/password from the cookie. As we don't want
280 // to store LDAP passwords in any
281 // form, we've already replaced the password with the hashed username and LDAP_COOKIE_MARKER
282 if ($already_md5) {
283 if ($password == md5($username) . md5($ldapCookieMarker)) {
284 authLdap_debug('cookie authentication');
285 return true;
286 }
287 }
288
289 // Remove slashes as noted on https://github.com/heiglandreas/authLdap/issues/108
290 $password = stripslashes_deep($password);
291
292 // No cookie, so have to authenticate them via LDAP
293 $result = false;
294 try {
295 authLdap_debug('about to do LDAP authentication');
296 $result = authLdap_get_server()->Authenticate($username, $password, $authLDAPFilter);
297 } catch (Exception $e) {
298 authLdap_debug('LDAP authentication failed with exception: ' . $e->getMessage());
299 return false;
300 }
301
302 // Make optional querying from the admin account #213
303 if (!authLdap_get_option('UserRead')) {
304 // Rebind with the default credentials after the user has been loged in
305 // Otherwise the credentials of the user trying to login will be used
306 // This fixes #55
307 authLdap_get_server()->bind();
308 }
309
310 if (true !== $result) {
311 authLdap_debug('LDAP authentication failed');
312 // TODO what to return? WP_User object, true, false, even an WP_Error object...
313 // all seem to fall back to normal wp user authentication
314 return;
315 }
316
317 authLdap_debug('LDAP authentication successful');
318 $attributes = array_values(
319 array_filter(
320 apply_filters(
321 'authLdap_filter_attributes',
322 [
323 $authLDAPNameAttr,
324 $authLDAPSecName,
325 $authLDAPMailAttr,
326 $authLDAPWebAttr,
327 $authLDAPUidAttr,
328 ]
329 )
330 )
331 );
332
333 try {
334 $attribs = authLdap_get_server()->search(
335 sprintf($authLDAPFilter, $username),
336 $attributes
337 );
338 // First get all the relevant group informations so we can see if
339 // whether have been changes in group association of the user
340 if (!isset($attribs[0]['dn'])) {
341 authLdap_debug('could not get user attributes from LDAP');
342 throw new UnexpectedValueException('dn has not been returned');
343 }
344 if (!isset($attribs[0][strtolower($authLDAPUidAttr)][0])) {
345 authLdap_debug('could not get user attributes from LDAP');
346 throw new UnexpectedValueException('The user-ID attribute has not been returned');
347 }
348
349 $dn = $attribs[0]['dn'];
350 $realuid = $attribs[0][strtolower($authLDAPUidAttr)][0];
351 } catch (Exception $e) {
352 authLdap_debug('Exception getting LDAP user: ' . $e->getMessage());
353 return false;
354 }
355
356 $uid = authLdap_get_uid($realuid);
357
358 // This fixes #172
359 if (true == authLdap_get_option('DoNotOverwriteNonLdapUsers', false)) {
360 if (!get_user_meta($uid, 'authLDAP')) {
361 return null;
362 }
363 }
364
365 $roles = [];
366
367 // we only need this if either LDAP groups are disabled or
368 // if the WordPress role of the user overrides LDAP groups
369 if (!$authLDAPGroupEnable || !$authLDAPGroupOverUser) {
370 $roles[] = authLdap_user_role($uid);
371 // TODO, this needs to be revised, it seems, like authldap is taking only the first role
372 // even if in WP there are assigned multiple.
373 }
374
375 // do LDAP group mapping if needed
376 // (if LDAP groups override worpress user role, $role is still empty)
377 if (empty($roles) && $authLDAPGroupEnable) {
378 $roles = authLdap_groupmap($realuid, $dn);
379 authLdap_debug('role from group mapping: ' . json_encode($roles));
380 }
381
382 // if we don't have a role yet, use default role
383 if (empty($roles) && !empty($authLDAPDefaultRole)) {
384 authLdap_debug('no role yet, set default role');
385 $roles[] = $authLDAPDefaultRole;
386 }
387
388 if (empty($roles)) {
389 // Sorry, but you are not in any group that is allowed access
390 trigger_error('no group found');
391 authLdap_debug('user is not in any group that is allowed access');
392 return false;
393 } else {
394 $wp_roles = new WP_Roles();
395 // not sure if this is needed, but it can't hurt
396
397 // Get rid of unexisting roles.
398 foreach ($roles as $k => $v) {
399 if (!$wp_roles->is_role($v)) {
400 unset($k);
401 }
402 }
403
404 // check if single role or an empty array provided
405 if (empty($roles)) {
406 trigger_error('no group found');
407 authLdap_debug('role is invalid');
408 return false;
409 }
410 }
411
412 // from here on, the user has access!
413 // now, lets update some user details
414 $user_info = [];
415 $user_info['user_login'] = $realuid;
416 $user_info['user_email'] = '';
417 $user_info['user_nicename'] = '';
418
419 // first name
420 if (isset($attribs[0][strtolower($authLDAPNameAttr)][0])) {
421 $user_info['first_name'] = $attribs[0][strtolower($authLDAPNameAttr)][0];
422 }
423
424 // last name
425 if (isset($attribs[0][strtolower($authLDAPSecName)][0])) {
426 $user_info['last_name'] = $attribs[0][strtolower($authLDAPSecName)][0];
427 }
428
429 // mail address
430 if (isset($attribs[0][strtolower($authLDAPMailAttr)][0])) {
431 $user_info['user_email'] = $attribs[0][strtolower($authLDAPMailAttr)][0];
432 }
433
434 // website
435 if (isset($attribs[0][strtolower($authLDAPWebAttr)][0])) {
436 $user_info['user_url'] = $attribs[0][strtolower($authLDAPWebAttr)][0];
437 }
438 // display name, nickname, nicename
439 if (array_key_exists('first_name', $user_info)) {
440 $user_info['display_name'] = $user_info['first_name'];
441 $user_info['nickname'] = $user_info['first_name'];
442 $user_info['user_nicename'] = sanitize_title_with_dashes($user_info['first_name']);
443 if (array_key_exists('last_name', $user_info)) {
444 $user_info['display_name'] .= ' ' . $user_info['last_name'];
445 $user_info['nickname'] .= ' ' . $user_info['last_name'];
446 $user_info['user_nicename'] .= '_' . sanitize_title_with_dashes($user_info['last_name']);
447 }
448 }
449 $user_info['user_nicename'] = substr($user_info['user_nicename'], 0, 50);
450
451 // optionally store the password into the wordpress database
452 if (authLdap_get_option('CachePW')) {
453 // Password will be hashed inside wp_update_user or wp_insert_user
454 $user_info['user_pass'] = $password;
455 } else {
456 // clear the password
457 $user_info['user_pass'] = '';
458 }
459
460 // add uid if user exists
461 if ($uid) {
462 // found user in the database
463 authLdap_debug('The LDAP user has an entry in the WP-Database');
464 $user_info['ID'] = $uid;
465 unset($user_info['display_name'], $user_info['nickname']);
466 $userid = wp_update_user($user_info);
467 } else {
468 // new wordpress account will be created
469 authLdap_debug('The LDAP user does not have an entry in the WP-Database, a new WP account will be created');
470
471 $userid = wp_insert_user($user_info);
472 }
473
474 // if the user exists, wp_insert_user will update the existing user record
475 if (is_wp_error($userid)) {
476 authLdap_debug('Error creating user : ' . $userid->get_error_message());
477 trigger_error('Error creating user: ' . $userid->get_error_message());
478 return $userid;
479 }
480
481 // Update user roles.
482 $user = new \WP_User($userid);
483
484 /**
485 * Add hook for custom User-Role assignment
486 *
487 * @param WP_User $user This user-object will be returned. Can be modified as necessary in the actions.
488 * @param array $roles
489 */
490 do_action('authldap_user_roles', $user, $roles);
491
492 /**
493 * Add hook for custom updates
494 *
495 * @param int $userid User ID.
496 * @param array $attribs [0] Attributes retrieved from LDAP for the user.
497 */
498 do_action('authLdap_login_successful', $userid, $attribs[0]);
499
500 authLdap_debug('user id = ' . $userid);
501
502 // flag the user as an ldap user so we can hide the password fields in the user profile
503 update_user_meta($userid, 'authLDAP', true);
504
505 // return a user object upon positive authorization
506 return $user;
507 } catch (Exception $e) {
508 authLdap_debug($e->getMessage() . '. Exception thrown in line ' . $e->getLine());
509 trigger_error($e->getMessage() . '. Exception thrown in line ' . $e->getLine());
510 }
511 }
512
513 /**
514 * Get user's user id
515 *
516 * Returns null if username not found
517 *
518 * @param string $username username
519 * @param string user id, null if not found
520 */
521 function authLdap_get_uid($username)
522 {
523 global $wpdb;
524
525 // find out whether the user is already present in the database
526 $uid = $wpdb->get_var(
527 $wpdb->prepare(
528 "SELECT ID FROM {$wpdb->users} WHERE user_login = %s",
529 $username
530 )
531 );
532 if ($uid) {
533 authLdap_debug("Existing user, uid = {$uid}");
534 return $uid;
535 } else {
536 return null;
537 }
538 }
539
540 /**
541 * Get the user's current role
542 *
543 * Returns empty string if not found.
544 *
545 * @param int $uid wordpress user id
546 * @return string role, empty if none found
547 */
548 function authLdap_user_role($uid)
549 {
550 global $wpdb, $wp_roles;
551
552 if (!$uid) {
553 return '';
554 }
555
556 /** @var array<string, bool> $usercapabilities */
557 $usercapabilities = get_user_meta($uid, "{$wpdb->prefix}capabilities", true);
558 if (!is_array($usercapabilities)) {
559 return '';
560 }
561
562 /** @var array<string, array{name: string, capabilities: array<mixed>} $editable_roles */
563 $editable_roles = $wp_roles->roles;
564
565 // By using this approach we are now using the order of the roles from the WP_Roles object
566 // and not from the capabilities any more.
567 $userroles = array_keys(array_intersect_key($editable_roles, $usercapabilities));
568 $role = ($userroles !== []) ? $userroles[0] : '';
569
570 authLdap_debug("Existing user's role: {$role}");
571 return $role;
572 }
573
574 /**
575 * Get LDAP groups for user and map to role
576 *
577 * @param string $username
578 * @param string $dn
579 * @return string role, empty string if no mapping found, first found role otherwise
580 * @conf array authLDAPGroups, associative array, role => ldap_group
581 * @conf string authLDAPGroupBase, base dn to look up groups
582 * @conf string authLDAPGroupAttr, ldap attribute that holds name of group
583 * @conf string authLDAPGroupFilter, LDAP filter to find groups. can contain %s and %dn% placeholders
584 */
585 function authLdap_groupmap($username, $dn)
586 {
587 $authLDAPGroups = authLdap_sort_roles_by_capabilities(
588 authLdap_get_option('Groups')
589 );
590 $authLDAPGroupBase = authLdap_get_option('GroupBase');
591 $authLDAPGroupAttr = authLdap_get_option('GroupAttr');
592 $authLDAPGroupFilter = authLdap_get_option('GroupFilter');
593 $authLDAPGroupSeparator = authLdap_get_option('GroupSeparator');
594 if (!$authLDAPGroupAttr) {
595 $authLDAPGroupAttr = 'gidNumber';
596 }
597 if (!$authLDAPGroupFilter) {
598 $authLDAPGroupFilter = '(&(objectClass=posixGroup)(memberUid=%s))';
599 }
600 if (!$authLDAPGroupSeparator) {
601 $authLDAPGroupSeparator = ',';
602 }
603
604 if (!is_array($authLDAPGroups) || count(array_filter(array_values($authLDAPGroups))) == 0) {
605 authLdap_debug('No group names defined');
606 return '';
607 }
608
609 try {
610 // To allow searches based on the DN instead of the uid, we replace the
611 // string %dn% with the users DN.
612 $authLDAPGroupFilter = str_replace(
613 '%dn%',
614 ldap_escape($dn, '', LDAP_ESCAPE_FILTER),
615 $authLDAPGroupFilter
616 );
617 authLdap_debug('Group Filter: ' . json_encode($authLDAPGroupFilter));
618 authLdap_debug('Group Base: ' . $authLDAPGroupBase);
619 $groups = authLdap_get_server()->search(
620 sprintf($authLDAPGroupFilter, ldap_escape($username, '', LDAP_ESCAPE_FILTER)),
621 [$authLDAPGroupAttr],
622 $authLDAPGroupBase
623 );
624 } catch (Exception $e) {
625 authLdap_debug('Exception getting LDAP group attributes: ' . $e->getMessage());
626 return '';
627 }
628
629 $grp = [];
630 for ($i = 0; $i < $groups ['count']; $i++) {
631 if ($authLDAPGroupAttr == "dn") {
632 $grp[] = $groups[$i]['dn'];
633 } else {
634 for ($k = 0; $k < $groups[$i][strtolower($authLDAPGroupAttr)]['count']; $k++) {
635 $grp[] = $groups[$i][strtolower($authLDAPGroupAttr)][$k];
636 }
637 }
638 }
639
640 authLdap_debug('LDAP groups: ' . json_encode($grp));
641
642 // Check whether the user is member of one of the groups that are
643 // allowed acces to the blog. If the user is not member of one of
644 // The groups throw her out! ;-)
645 $roles = [];
646 foreach ($authLDAPGroups as $key => $val) {
647 $currentGroup = explode($authLDAPGroupSeparator, $val);
648 // Remove whitespaces around the group-ID
649 $currentGroup = array_map('trim', $currentGroup);
650 if (0 < count(array_intersect($currentGroup, $grp))) {
651 $roles[] = $key;
652 }
653 }
654
655 // Default: If the user is member of more than one group only the first one
656 // will be taken into account!
657 // This filter allows you to return multiple user roles. WordPress
658 // supports this functionality, but not natively via UI from Users
659 // overview (you need to use a plugin). However, it's still widely used,
660 // for example, by WooCommerce, etc. Use if you know what you're doing.
661 if (apply_filters('authLdap_allow_multiple_roles', false) === false && count($roles) > 1) {
662 $roles = array_slice($roles, 0, 1);
663 }
664
665 authLdap_debug("Roles from LDAP group: " . json_encode($roles));
666 return $roles;
667 }
668
669 /**
670 * This function disables the password-change fields in the users preferences.
671 *
672 * It does not make sense to authenticate via LDAP and then allow the user to
673 * change the password only in the wordpress database. And changing the password
674 * LDAP-wide can not be the scope of Wordpress!
675 *
676 * Whether the user is an LDAP-User or not is determined using the authLDAP-Flag
677 * of the users meta-informations
678 *
679 * @return false, if the user whose prefs are viewed is an LDAP-User, true if
680 * he isn't
681 * @conf boolean authLDAP
682 */
683 function authLdap_show_password_fields($return, $user)
684 {
685 if (!$user) {
686 return true;
687 }
688
689 if (get_user_meta($user->ID, 'authLDAP')) {
690 return false;
691 }
692
693 return $return;
694 }
695
696 /**
697 * This function disables the password reset for a user.
698 *
699 * It does not make sense to authenticate via LDAP and then allow the user to
700 * reset the password only in the wordpress database. And changing the password
701 * LDAP-wide can not be the scope of Wordpress!
702 *
703 * Whether the user is an LDAP-User or not is determined using the authLDAP-Flag
704 * of the users meta-informations
705 *
706 * @author chaplina (https://github.com/chaplina)
707 * @conf boolean authLDAP
708 * @return false, if the user is an LDAP-User, true if he isn't
709 */
710 function authLdap_allow_password_reset($return, $userid)
711 {
712 if (!(isset($userid))) {
713 return true;
714 }
715
716 if (get_user_meta($userid, 'authLDAP')) {
717 return false;
718 }
719 return $return;
720 }
721
722 /**
723 * Sort the given roles by number of capabilities
724 *
725 * @param array $roles
726 *
727 * @return array
728 */
729 function authLdap_sort_roles_by_capabilities($roles)
730 {
731 global $wpdb;
732 $myRoles = get_option($wpdb->get_blog_prefix() . 'user_roles');
733
734 authLdap_debug(print_r($roles, true));
735 uasort($myRoles, 'authLdap_sortByCapabilitycount');
736
737 $return = [];
738
739 foreach ($myRoles as $key => $role) {
740 if (isset($roles[$key])) {
741 $return[$key] = $roles[$key];
742 }
743 }
744
745 authLdap_debug(print_r($return, true));
746 return $return;
747 }
748
749 /**
750 * Sort according to the number of capabilities
751 *
752 * @param $a
753 * @param $b
754 */
755 function authLdap_sortByCapabilitycount($a, $b)
756 {
757 if (count($a['capabilities']) > count($b['capabilities'])) {
758 return -1;
759 }
760 if (count($a['capabilities']) < count($b['capabilities'])) {
761 return 1;
762 }
763
764 return 0;
765 }
766
767 /**
768 * Load AuthLDAP Options
769 *
770 * Sets and stores defaults if options are not up to date
771 */
772 function authLdap_load_options($reload = false)
773 {
774 static $options = null;
775
776 // the current version for options
777 $option_version_plugin = 1;
778
779 $optionFunction = 'get_option';
780 if (is_multisite()) {
781 $optionFunction = 'get_site_option';
782 }
783 if (is_null($options) || $reload) {
784 $options = $optionFunction('authLDAPOptions', []);
785 }
786
787 // check if option version has changed (or if it's there at all)
788 if (!isset($options['Version']) || ($options['Version'] != $option_version_plugin)) {
789 // defaults for all options
790 $options_default = [
791 'Enabled' => false,
792 'CachePW' => false,
793 'URI' => '',
794 'URISeparator' => ' ',
795 'Filter' => '', // '(uid=%s)'
796 'NameAttr' => '', // 'name'
797 'SecName' => '',
798 'UidAttr' => '', // 'uid'
799 'MailAttr' => '', // 'mail'
800 'WebAttr' => '',
801 'Groups' => [],
802 'Debug' => false,
803 'GroupAttr' => '', // 'gidNumber'
804 'GroupFilter' => '', // '(&(objectClass=posixGroup)(memberUid=%s))'
805 'DefaultRole' => '',
806 'GroupEnable' => true,
807 'GroupOverUser' => true,
808 'Version' => $option_version_plugin,
809 'DoNotOverwriteNonLdapUsers' => false,
810 ];
811
812 // check if we got a version
813 if (!isset($options['Version'])) {
814 // we just changed to the new option format
815 // read old options, then delete them
816 $old_option_new_option = [
817 'authLDAP' => 'Enabled',
818 'authLDAPCachePW' => 'CachePW',
819 'authLDAPURI' => 'URI',
820 'authLDAPFilter' => 'Filter',
821 'authLDAPNameAttr' => 'NameAttr',
822 'authLDAPSecName' => 'SecName',
823 'authLDAPUidAttr' => 'UidAttr',
824 'authLDAPMailAttr' => 'MailAttr',
825 'authLDAPWebAttr' => 'WebAttr',
826 'authLDAPGroups' => 'Groups',
827 'authLDAPDebug' => 'Debug',
828 'authLDAPGroupAttr' => 'GroupAttr',
829 'authLDAPGroupFilter' => 'GroupFilter',
830 'authLDAPDefaultRole' => 'DefaultRole',
831 'authLDAPGroupEnable' => 'GroupEnable',
832 'authLDAPGroupOverUser' => 'GroupOverUser',
833 ];
834 foreach ($old_option_new_option as $old_option => $new_option) {
835 $value = get_option($old_option, null);
836 if (!is_null($value)) {
837 $options[$new_option] = $value;
838 }
839 delete_option($old_option);
840 }
841 delete_option('authLDAPCookieMarker');
842 delete_option('authLDAPCookierMarker');
843 }
844
845 // set default for all options that are missing
846 foreach ($options_default as $key => $default) {
847 if (!isset($options[$key])) {
848 $options[$key] = $default;
849 }
850 }
851
852 // set new version and save
853 $options['Version'] = $option_version_plugin;
854 update_option('authLDAPOptions', $options);
855 }
856 return $options;
857 }
858
859 /**
860 * Get an individual option
861 */
862 function authLdap_get_option($optionname, $default = null)
863 {
864 $options = authLdap_load_options();
865 if (isset($options[$optionname]) && $options[$optionname]) {
866 return $options[$optionname];
867 }
868
869 if (null !== $default) {
870 return $default;
871 }
872
873 //authLdap_debug('option name invalid: ' . $optionname);
874 return null;
875 }
876
877 /**
878 * Set new options
879 */
880 function authLdap_set_options($new_options = [])
881 {
882 // initialize the options with what we currently have
883 $options = authLdap_load_options();
884
885 // set the new options supplied
886 foreach ($new_options as $key => $value) {
887 $options[$key] = $value;
888 }
889
890 // store options
891 $optionFunction = 'update_option';
892 if (is_multisite()) {
893 $optionFunction = 'update_site_option';
894 }
895 if ($optionFunction('authLDAPOptions', $options)) {
896 // reload the option cache
897 authLdap_load_options(true);
898
899 return true;
900 }
901
902 // could not set options
903 return false;
904 }
905
906 /**
907 * Do not send an email after changing the password or the email of the user!
908 *
909 * @param boolean $result The initial resturn value
910 * @param array $user The old userdata
911 * @param array $newUserData The changed userdata
912 *
913 * @return bool
914 */
915 function authLdap_send_change_email($result, $user, $newUserData)
916 {
917 if (get_user_meta($user['ID'], 'authLDAP')) {
918 return false;
919 }
920
921 return $result;
922 }
923
924 $hook = is_multisite() ? 'network_' : '';
925 add_action($hook . 'admin_menu', 'authLdap_addmenu');
926 add_filter('show_password_fields', 'authLdap_show_password_fields', 10, 2);
927 add_filter('allow_password_reset', 'authLdap_allow_password_reset', 10, 2);
928 add_filter('authenticate', 'authLdap_login', 10, 3);
929 /** This only works from WP 4.3.0 on */
930 add_filter('send_password_change_email', 'authLdap_send_change_email', 10, 3);
931 add_filter('send_email_change_email', 'authLdap_send_change_email', 10, 3);
932 $handler = new UserRoleHandler();
933 add_action('authldap_user_roles', [$handler, 'addRolesToUser'], 10, 2);
1 <?xml version="1.0" encoding="utf-8"?>
2 <project name="ajaxComments" default="build" basedir=".">
3 <php expression="include('vendor/autoload.php')"/>
4 <input propertyName="version">Which version shall be tagged?</input>
5 <!--loadfile property = "version" file = "VERSION">
6 <filterchain>
7 <striplinebreaks/>
8 </filterchain>
9 </loadfile-->
10 <target name="bumpversion">
11 <echo>Bumping version to ${version}</echo>
12 <reflexive>
13 <fileset dir=".">
14 <include name="index.php"/>
15 <include name="authLdap.php"/>
16 </fileset>
17 <filterchain>
18 <replaceregexp>
19 <regexp pattern="Version:.*" replace="Version: ${version}"/>
20 </replaceregexp>
21 </filterchain>
22 </reflexive>
23 </target>
24 <target name="build" depends="bumpversion,deploy.git,deploy.svn"/>
25 <target name="sync.svn">
26 <copy todir="${project.basedir}/svn/trunk">
27 <fileset dir="${project.basedir}">
28 <include name="src/**"/>
29 <include name="view/**"/>
30 <include name="authLdap.css"/>
31 <include name="authLdap.php"/>
32 <include name="ldap.php"/>
33 <include name="LICENSE.md"/>
34 <include name="README.md"/>
35 <include name="readme.txt"/>
36 </fileset>
37 </copy>
38 </target>
39 <target name="deploy.svn" depends="sync.svn">
40 <property override="true" file="${project.basedir}/.svnAccess" prefix="svnaccess" />
41 <foreach param="dirname" target="svn.addFile">
42 <fileset dir="${project.basedir}/svn/trunk">
43 <include name="**/*"/>
44 </fileset>
45 </foreach>
46 <echo message="${svnaccess.username}"/>
47 <exec outputProperty="committedrevision" executable="svn" dir="${project.basedir}/svn/trunk">
48 <arg value="commit"/>
49 <arg value="--username"/>
50 <arg value="${svnaccess.username}"/>
51 <arg value="--password"/>
52 <arg value="${svnaccess.password}"/>
53 <arg value="--message"/>
54 <arg value="Bumps version to ${version}"/>
55 <arg value="--no-auth-cache"/>
56 <arg value="--quiet"/>
57 </exec>
58 <!--svncommit
59 username="${svnaccess.username}"
60 password="${svnaccess.password}"
61 workingcopy="${project.basedir}/svn"
62 message="Bumps version to ${version}"
63 nocache="true"
64 /-->
65 <echo message="Committed revision: ${committedrevision}"/>
66 </target>
67
68 <target name="svn.addFile">
69 <trycatch>
70 <try>
71 <svninfo workingcopy="${project.basedir}/svn/trunk/${dirname}"/>
72 </try>
73 <catch>
74 <exec command="svn add ${project.basedir}/svn/trunk/${dirname}"/>
75 <echo>${dirname}</echo>
76 </catch>
77 <finally>
78
79 </finally>
80 </trycatch>
81 <echo>${svn.info}</echo>
82 </target>
83 <target name="deploy.git">
84 <exec executable="git" dir=".">
85 <arg value="add" />
86 <arg value="authLdap.php" />
87 </exec>
88 <exec executable="git" dir=".">
89 <arg value="commit" />
90 <arg value="-m"/>
91 <arg value="Bumps version to ${version}"/>
92 </exec>
93 <exec executable="git" dir=".">
94 <arg value="tag"/>
95 <arg value="-s"/>
96 <arg value="-m"/>
97 <arg value="Version ${version}"/>
98 <arg value="${version}"/>
99 </exec>
100 <exec executable="git" dir=".">
101 <arg value="push"/>
102 <arg value="--tags"/>
103 <arg value="origin"/>
104 <arg value="master" />
105 </exec>
106 </target>
107 </project>
...\ No newline at end of file ...\ No newline at end of file
1 {
2 "name" : "org_heigl/authldap",
3 "type" : "library",
4 "description": "Enables wordpress-authentication via LDAP",
5 "keywords": ["ldap","authenticate", "auth", "wordpress"],
6 "homepage": "http://github.com/heiglandreas/authLdap",
7 "license": "MIT",
8 "authors": [{
9 "name": "Andreas Heigl",
10 "email": "andreas@heigl.org",
11 "homepage": "http://andreas.heigl.org",
12 "role": "Developer"
13 }],
14 "require" : {
15 "php": ">=7.4",
16 "ext-ldap": "*"
17 },
18 "require-dev": {
19 "automattic/wordbless": "^0.3.1"
20 },
21 "autoload" : {
22 "classmap" : [
23 "authLdap.php"
24 ],
25 "psr-4" : {
26 "Org_Heigl\\AuthLdap\\" : "src/"
27 }
28 },
29 "autoload-dev" : {
30 "psr-4" : {
31 "Org_Heigl\\AuthLdapTest\\" : "tests/"
32 }
33 },
34 "scripts": {
35 "post-update-cmd": "php -r \"copy('vendor/automattic/wordbless/src/dbless-wpdb.php', 'wordpress/wp-content/db.php');\""
36 },
37 "config": {
38 "allow-plugins": {
39 "roots/wordpress-core-installer": true
40 }
41 }
42 }
1 version: "3.5"
2
3 services:
4 wp:
5 # image: authldap:latest
6 build:
7 context: dockersetup
8 dockerfile: Dockerfile_wordpress
9 ports:
10 - 80:80 # change ip if required
11 volumes:
12 - ./config/php.conf.ini:/usr/local/etc/php/conf.d/conf.ini
13 - ./wp-app:/var/www/html # Full wordpress project
14 - .:/var/www/html/wp-content/plugins/authldap # Plugin development
15 #- ./theme-name/trunk/:/var/www/html/wp-content/themes/theme-name # Theme development
16 environment:
17 WORDPRESS_DB_HOST: db
18 WORDPRESS_DB_NAME: "wordpress"
19 WORDPRESS_DB_USER: root
20 WORDPRESS_DB_PASSWORD: "wppasswd"
21 depends_on:
22 - db
23 links:
24 - db
25
26 wpcli:
27 image: wordpress:cli
28 volumes:
29 - ./config/php.conf.ini:/usr/local/etc/php/conf.d/conf.ini
30 - ./wp-app:/var/www/html
31 depends_on:
32 - db
33 - wp
34
35 db:
36 image: mysql:latest # https://hub.docker.com/_/mysql/ - or mariadb https://hub.docker.com/_/mariadb
37 ports:
38 - 3306:3306 # change ip if required
39 command: [
40 '--default_authentication_plugin=mysql_native_password',
41 '--character-set-server=utf8mb4',
42 '--collation-server=utf8mb4_unicode_ci'
43 ]
44 volumes:
45 - ./wp-data:/docker-entrypoint-initdb.d
46 - db_data:/var/lib/mysql
47 environment:
48 MYSQL_DATABASE: "wordpress"
49 MYSQL_ROOT_PASSWORD: "wppasswd"
50
51 openldap:
52 image: osixia/openldap:latest
53 # build:
54 # context: dockersetup
55 # dockerfile: Dockerfile_ldap
56 ports:
57 - 3389:389
58 volumes:
59 - ./.ci/50-init.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/50-bootstrap.ldif
60 command: "--copy-service --loglevel debug"
61 restart: always
62 environment:
63 LDAP_LOG_LEVEL: "0"
64 LDAP_TLS: "false"
65 LDAP_ADMIN_PASSWORD: "insecure"
66
67 volumes:
68 db_data:
1 FROM wordpress:latest
2
3 RUN set -x \
4 && apt-get update \
5 && apt-get install -y libldap2-dev \
6 && rm -rf /var/lib/apt/lists/* \
7 && docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu/ \
8 && docker-php-ext-install ldap \
9 && apt-get purge -y --auto-remove libldap2-dev
...\ No newline at end of file ...\ No newline at end of file
1 <?xml version="1.0"?>
2 <ruleset name="Custom Standard" namespace="MyProject\CS\Standard">
3 <description>authLdap codestyle</description>
4 <file>./src</file>
5 <file>./authLdap.php</file>
6 <file>./tests</file>
7
8 <arg name="colors"/>
9 <arg value="sp"/>
10
11 <autoload>./vendor/autoload.php</autoload>
12
13 <rule ref="PSR12">
14 <exclude name="Generic.WhiteSpace.DisallowTabIndent"/>
15 </rule>
16 <rule ref="Generic.WhiteSpace.ScopeIndent">
17 <properties>
18 <property name="tabIndent" value="true"/>
19 </properties>
20 </rule>
21
22 </ruleset>
1 <?xml version="1.0" encoding="UTF-8"?>
2 <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 bootstrap="tests/bootstrap.php"
4 testdox="true"
5 xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
6 >
7 <coverage>
8 <include>
9 <directory suffix=".php">src</directory>
10 <file>authLdap.php</file>
11 </include>
12 <exclude>
13 <directory>src/Wrapper</directory>
14 </exclude>
15 <report>
16 <html outputDirectory="build/coverage" lowUpperBound="35" highLowerBound="70"/>
17 </report>
18 </coverage>
19 <testsuite name="authLdap Test-Suite">
20 <directory>tests</directory>
21 </testsuite>
22 <groups>
23 <exclude>
24 <group>disable</group>
25 </exclude>
26 </groups>
27 <logging>
28 <!--log type="coverage-xml" target="../report/coverage.xml"/-->
29 <!--log type="graphviz" target="../report/logfile.dot"/-->
30 <!--log type="json" target="../report/logfile.json"/-->
31 <!--log type="metrics-xml" target="../report/metrics.xml"/-->
32 <!--log type="plain" target="../report/logfile.txt"/-->
33 <!--log type="pmd-xml" target="../report/pmd.xml" cpdMinLines="5" cpdMinMatches="70"/-->
34 <!--log type="tap" target="../report/logfile.tap"/-->
35 <!--log type="test-xml" target="../report/logfile.xml" logIncompleteSkipped="false"/-->
36 <!--log type="testdox-html" target="../report/testdox.html"/-->
37 <!--log type="testdox-text" target="../report/testdox.txt"/-->
38 </logging>
39 </phpunit>
1 === authLdap === 1 === authLdap ===
2 Contributors: heiglandreas 2 Contributors: heiglandreas
3 Tags: ldap, auth 3 Tags: ldap, auth, authentication, active directory, AD, openLDAP, Open Directory
4 Requires at least: 2.5.0 4 Requires at least: 2.5.0
5 Tested up to: 4.6.1 5 Tested up to: 5.9.0
6 Requires PHP: 7.4
6 Stable tag: trunk 7 Stable tag: trunk
8 License: MIT
9 License URI: https://opensource.org/licenses/MIT
7 10
8 Use your existing LDAP flexible as authentication backend for WordPress 11 Use your existing LDAP flexible as authentication backend for WordPress
9 12
...@@ -13,13 +16,9 @@ Use your existing LDAP as authentication-backend for your wordpress! ...@@ -13,13 +16,9 @@ Use your existing LDAP as authentication-backend for your wordpress!
13 16
14 So what are the differences to other Wordpress-LDAP-Authentication-Plugins? 17 So what are the differences to other Wordpress-LDAP-Authentication-Plugins?
15 18
16 * Flexible: You are totaly free in which LDAP-backend to use. Due to the extensive configuration you can 19 * Flexible: You are totaly free in which LDAP-backend to use. Due to the extensive configuration you can freely decide how to do the authentication of your users. It simply depends on your filters
17 freely decide how to do the authentication of your users. It simply depends on your 20 * Independent: As soon as a user logs in, it is added/updated to the Wordpress' user-database to allow wordpress to always use the correct data. You only have to administer your users once.
18 filters 21 * Failsafe: Due to the users being created in Wordpress' User-database they can also log in when the LDAP-backend currently is gone.
19 * Independent: As soon as a user logs in, it is added/updated to the Wordpress' user-database
20 to allow wordpress to always use the correct data. You only have to administer your users once.
21 * Failsafe: Due to the users being created in Wordpress' User-database they can
22 also log in when the LDAP-backend currently is gone.
23 * Role-Aware: You can map Wordpress' roles to values of an existing LDAP-attribute. 22 * Role-Aware: You can map Wordpress' roles to values of an existing LDAP-attribute.
24 23
25 For more Information on the configuration have a look at https://github.com/heiglandreas/authLdap 24 For more Information on the configuration have a look at https://github.com/heiglandreas/authLdap
...@@ -41,6 +40,51 @@ Go to https://github.com/heiglandreas/authLdap ...@@ -41,6 +40,51 @@ Go to https://github.com/heiglandreas/authLdap
41 Please use the issuetracker at https://github.com/heiglandreas/authLdap/issues 40 Please use the issuetracker at https://github.com/heiglandreas/authLdap/issues
42 41
43 == Changelog == 42 == Changelog ==
43
44 = 2.5.3 =
45 * Fix issue with broken role-assignement in combination with WooCommerce
46 * Fix spelling issue
47 * Allow DN as role-definition
48
49 = 2.5.0 =
50 * Ignore the order of capabilities to tell the role. In addition the filter `editable_roles` can be used to limit the roles
51
52 = 2.4.11 =
53 * Fix issue with running on PHP8.1
54
55 = 2.4.9 =
56 * Improve group-assignement UI
57
58 = 2.4.8 =
59 * Make textfields in settings-page wider
60
61 = 2.4.7 =
62 * Replace deprecated function
63 * Fix undefined index
64 * Add filter for retrieving other params at login (authLdap_filter_attributes)
65 * Add do_action after successfull login (authLdap_login_successful)
66
67 = 2.4.0 =
68 * Allow to use environment variables for LDAP-URI configuration
69
70 = 2.3.0 =
71 * Allow to not overwrite existing WordPress-Users with LDAP-Users as that can be a security issue.
72
73 = 2.1.0 =
74 * Add search-base for groups. This might come in handy for multisite-instances
75
76 = 2.0.0 =
77 * This new release adds Multi-Site support. It will no longer be possible to use this plugin just in one subsite of a multisite installation!
78 * Adds a warning screen to the config-section when no LDAPextension could be found
79 * Fixes an issue with the max-length of the username
80
81 = 1.5.1 =
82 * Fixes an issue with escaped backslashes and quotes
83
84 = 1.5.0 =
85 * Allows parts of the LDAP-URI to be URLEncoded
86 * Drops support for PHP 5.4
87
44 = 1.4.20 = 88 = 1.4.20 =
45 * Allows multiple LDAP-servers to be queried (given that they use the same attributes) 89 * Allows multiple LDAP-servers to be queried (given that they use the same attributes)
46 * Fixes issue with URL-Encoded informations (see https://github.com/heiglandreas/authLdap/issues/108) 90 * Fixes issue with URL-Encoded informations (see https://github.com/heiglandreas/authLdap/issues/108)
...@@ -77,7 +121,7 @@ Please use the issuetracker at https://github.com/heiglandreas/authLdap/issues ...@@ -77,7 +121,7 @@ Please use the issuetracker at https://github.com/heiglandreas/authLdap/issues
77 * Fixes PSR2 violations 121 * Fixes PSR2 violations
78 122
79 […] 123 […]
80 124
81 = 1.2.1 = 125 = 1.2.1 =
82 * Fixed an issue with group-ids 126 * Fixed an issue with group-ids
83 * Moved the code to GitHub (https://github.com/heiglandreas/authLdap) 127 * Moved the code to GitHub (https://github.com/heiglandreas/authLdap)
......
1 <?php
2
3 /**
4 * Copyright Andrea Heigl <andreas@heigl.org>
5 *
6 * Licenses under the MIT-license. For details see the included file LICENSE.md
7 */
8
9 declare(strict_types=1);
10
11 namespace Org_Heigl\AuthLdap\Exception;
12
13 use Exception;
14
15 class Error extends Exception
16 {
17 public function __construct($message, $line = null)
18 {
19 parent::__construct($message);
20 if ($line) {
21 $this -> line = $line;
22 }
23 }
24 }
1 <?php
2
3 /**
4 * Copyright Andreas Heigl <andreas@heigl.org>
5 *
6 * Licenses under the MIT-license. For details see the included file LICENSE.md
7 */
8
9 declare(strict_types=1);
10
11 namespace Org_Heigl\AuthLdap\Exception;
12
13 use RuntimeException;
14
15 use function sprintf;
16
17 class InvalidLdapUri extends RuntimeException
18 {
19 public static function cannotparse(string $ldapUri): self
20 {
21 return new self(sprintf(
22 '%1$s seems not to be a valid URI',
23 $ldapUri
24 ));
25 }
26
27 public static function wrongSchema(string $uri): self
28 {
29 return new self(sprintf(
30 '%1$s does not start with a valid schema',
31 $uri
32 ));
33 }
34
35 public static function noSchema(string $uri): self
36 {
37 return new self(sprintf(
38 '%1$s does not provide a schema',
39 $uri
40 ));
41 }
42
43 public static function noEnvironmentVariableSet(string $uri): self
44 {
45 return new self(sprintf(
46 'The environment variable %1$s does not provide a URI',
47 $uri
48 ));
49 }
50
51 public static function noServerProvided(string $uri): self
52 {
53 return new self(sprintf(
54 'The LDAP-URI %1$s does not provide a server',
55 $uri
56 ));
57 }
58
59 public static function noSearchBaseProvided(string $uri): self
60 {
61 return new self(sprintf(
62 'The LDAP-URI %1$s does not provide a search-base',
63 $uri
64 ));
65 }
66
67 public static function invalidSearchBaseProvided(string $uri): self
68 {
69 return new self(sprintf(
70 'The LDAP-URI %1$s does not provide a valid search-base',
71 $uri
72 ));
73 }
74 }
1 <?php
2
3 /**
4 * Copyright Andreas Heigl <andreas@heigl.org>
5 *
6 * Licenses under the MIT-license. For details see the included file LICENSE.md
7 */
8
9 declare(strict_types=1);
10
11 namespace Org_Heigl\AuthLdap\Exception;
12
13 use RuntimeException;
14
15 class MissingValidLdapConnection extends Error
16 {
17 public static function get(): self
18 {
19 return new self(sprintf(
20 'No valid LDAP connection available'
21 ));
22 }
23 }
1 <?php
2
3 /**
4 * Copyright Andreas Heigl <andreas@heigl.org>
5 *
6 * Licenses under the MIT-license. For details see the included file LICENSE.md
7 */
8
9 declare(strict_types=1);
10
11 namespace Org_Heigl\AuthLdap\Exception;
12
13 use RuntimeException;
14
15 class SearchUnsuccessfull extends RuntimeException
16 {
17 public static function fromSearchFilter(string $filter): self
18 {
19 return new self(sprintf(
20 'Search for %1$s was not successfull',
21 $filter
22 ));
23 }
24 }
1 <?php 1 <?php
2
2 /** 3 /**
3 * Copyright (c) Andreas Heigl<andreas@heigl.org> 4 * Copyright (c) Andreas Heigl<andreas@heigl.org>
4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * Permission is hereby granted, free of charge, to any person obtaining a copy
...@@ -26,23 +27,29 @@ ...@@ -26,23 +27,29 @@
26 27
27 namespace Org_Heigl\AuthLdap; 28 namespace Org_Heigl\AuthLdap;
28 29
30 use Exception;
31 use Org_Heigl\AuthLdap\Exception\Error;
32 use Org_Heigl\AuthLdap\Exception\SearchUnsuccessfull;
33 use Org_Heigl\AuthLdap\Manager\Ldap;
34
29 class LdapList 35 class LdapList
30 { 36 {
31 /** 37 /**
32 * @var \LDAP[] 38 * @var Ldap[]
33 */ 39 */
34 protected $items = []; 40 protected $items = [];
35 41
36 public function addLdap(LDAP $ldap) 42 public function addLdap(Ldap $ldap)
37 { 43 {
38 $this->items[] = $ldap; 44 $this->items[] = $ldap;
39 } 45 }
40 46
41 public function authenticate($username, $password, $filter = '(uid=%s)') 47 public function authenticate($username, $password, $filter = '(uid=%s)')
42 { 48 {
49 /** @var Ldap $item */
43 foreach ($this->items as $key => $item) { 50 foreach ($this->items as $key => $item) {
44 if (! $item->authenticate($username, $password, $filter)) { 51 if (! $item->authenticate($username, $password, $filter)) {
45 unset ($this->items[$key]); 52 unset($this->items[$key]);
46 continue; 53 continue;
47 } 54 }
48 return true; 55 return true;
...@@ -65,21 +72,22 @@ class LdapList ...@@ -65,21 +72,22 @@ class LdapList
65 } 72 }
66 73
67 if ($allFailed) { 74 if ($allFailed) {
68 throw new AuthLDAP_Exception('No bind successfull'); 75 throw new Error('No bind successfull');
69 } 76 }
77
78 return true;
70 } 79 }
71 80
72 public function search($filter, $attributes = array('uid')) 81 public function search($filter, $attributes = array('uid'), $base = '')
73 { 82 {
74 foreach ($this->items as $item) { 83 foreach ($this->items as $item) {
75 try { 84 try {
76 $result = $item->search($filter, $attributes); 85 $result = $item->search($filter, $attributes, $base);
77 return $result; 86 return $result;
78 } catch (Exception $e) { 87 } catch (Exception $e) {
79 throw $e;
80 } 88 }
81 } 89 }
82 90
83 throw new \AuthLDAP_Exception('No Results found'); 91 throw SearchUnsuccessfull::fromSearchFilter($filter);
84 } 92 }
85 }
...\ No newline at end of file ...\ No newline at end of file
93 }
......
1 <?php
2
3 /**
4 * Copyright (c) Andreas Heigl<andreas@heigl.org>
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 * THE SOFTWARE.
20 *
21 * @author Andreas Heigl<andreas@heigl.org>
22 * @copyright Andreas Heigl
23 * @license http://www.opensource.org/licenses/mit-license.php MIT-License
24 * @since 19.07.2020
25 * @link http://github.com/heiglandreas/authLDAP
26 */
27
28 declare(strict_types=1);
29
30 namespace Org_Heigl\AuthLdap;
31
32 use Org_Heigl\AuthLdap\Exception\InvalidLdapUri;
33
34 use function array_map;
35 use function error_get_last;
36 use function getenv;
37 use function is_array;
38 use function is_string;
39 use function parse_url;
40 use function preg_replace_callback;
41 use function rawurlencode;
42 use function strlen;
43 use function strpos;
44 use function substr;
45 use function trim;
46 use function urldecode;
47
48 final class LdapUri
49 {
50 private $server;
51
52 private $scheme;
53
54 private $port = 389;
55
56 private string $baseDn;
57
58 private $username = '';
59
60 private $password = '';
61
62 private function __construct(string $uri)
63 {
64 if (!preg_match('/^(ldap|ldaps|env)/', $uri)) {
65 throw InvalidLdapUri::wrongSchema($uri);
66 }
67
68 if (strpos($uri, 'env:') === 0) {
69 $newUri = getenv(substr($uri, 4));
70 if (false === $newUri) {
71 throw InvalidLdapUri::noEnvironmentVariableSet($uri);
72 }
73 $uri = (string) $newUri;
74 }
75
76 $uri = $this->injectEnvironmentVariables($uri);
77
78 $array = parse_url($uri);
79 if (!is_array($array)) {
80 throw InvalidLdapUri::cannotparse($uri);
81 }
82
83 $url = array_map(static function ($item) {
84 if (is_int($item)) {
85 return $item;
86 }
87 return urldecode($item);
88 }, $array);
89
90
91 if (!isset($url['scheme'])) {
92 throw InvalidLdapUri::noSchema($uri);
93 }
94 if (0 !== strpos($url['scheme'], 'ldap')) {
95 throw InvalidLdapUri::wrongSchema($uri);
96 }
97 if (!isset($url['host'])) {
98 throw InvalidLdapUri::noServerProvided($uri);
99 }
100 if (!isset($url['path'])) {
101 throw InvalidLdapUri::noSearchBaseProvided($uri);
102 }
103 if (1 === strlen($url['path'])) {
104 throw InvalidLdapUri::invalidSearchBaseProvided($uri);
105 }
106
107 $this->server = $url['host'];
108 $this->scheme = $url['scheme'];
109 $this->baseDn = substr($url['path'], 1);
110 if (isset($url['user'])) {
111 $this->username = $url['user'];
112 }
113 if ('' === trim($this->username)) {
114 $this->username = 'anonymous';
115 }
116 if (isset($url['pass'])) {
117 $this->password = $url['pass'];
118 }
119 if ($this->scheme === 'ldaps' && $this->port = 389) {
120 $this->port = 636;
121 }
122
123 // When someone sets the port in the URL we overwrite whatever is set.
124 // We have to assume they know what they are doing!
125 if (isset($url['port'])) {
126 $this->port = $url['port'];
127 }
128 }
129
130 public static function fromString(string $uri): LdapUri
131 {
132 return new LdapUri($uri);
133 }
134
135 private function injectEnvironmentVariables(string $base): string
136 {
137 return preg_replace_callback('/%env:([^%]+)%/', static function (array $matches) {
138 return rawurlencode(getenv($matches[1]));
139 }, $base);
140 }
141
142 public function toString(): string
143 {
144 return $this->scheme . '://' . $this->server . ':' . $this->port;
145 }
146
147 public function __toString()
148 {
149 return $this->toString();
150 }
151
152 public function getUsername(): string
153 {
154 return $this->username;
155 }
156
157 public function getPassword(): string
158 {
159 return $this->password;
160 }
161
162 public function getBaseDn(): string
163 {
164 return $this->baseDn;
165 }
166
167 public function isAnonymous(): bool
168 {
169 if ($this->password === '') {
170 return true;
171 }
172
173 if ($this->username === 'anonymous') {
174 return true;
175 }
176
177 return false;
178 }
179 }
1 <?php
2
3 /**
4 * $Id: ldap.php 381646 2011-05-06 09:37:31Z heiglandreas $
5 *
6 * authLdap - Authenticate Wordpress against an LDAP-Backend.
7 * Copyright (c) 2008 Andreas Heigl<andreas@heigl.org>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 *
23 * This file handles the basic LDAP-Tasks
24 *
25 * @author Andreas Heigl<andreas@heigl.org>
26 * @package authLdap
27 * @category authLdap
28 * @since 2008
29 */
30
31 namespace Org_Heigl\AuthLdap\Manager;
32
33 use Org_Heigl\AuthLdap\Exception\Error;
34 use Org_Heigl\AuthLdap\Exception\MissingValidLdapConnection;
35 use Org_Heigl\AuthLdap\LdapUri;
36 use Org_Heigl\AuthLdap\Wrapper\LdapFactory;
37 use Org_Heigl\AuthLdap\Wrapper\LdapInterface;
38
39 class Ldap
40 {
41 /**
42 * This property contains the connection handle to the ldap-server
43 *
44 * @var LdapInterface|null
45 */
46 private ?LdapInterface $connection;
47
48 private LdapUri $uri;
49
50 private LdapFactory $factory;
51
52 private $starttls;
53
54 public function __construct(LdapFactory $factory, LdapUri $uri, $starttls = false)
55 {
56 $this->starttls = $starttls;
57 $this->uri = $uri;
58 $this->factory = $factory;
59 $this->connection = null;
60 }
61
62 /**
63 * Connect to the given LDAP-Server
64 */
65 public function connect(): self
66 {
67 $this->disconnect();
68
69 $this->connection = $this->factory->createFromLdapUri($this->uri->toString());
70 $this->connection->setOption(LDAP_OPT_PROTOCOL_VERSION, 3);
71 $this->connection->setOption(LDAP_OPT_REFERRALS, 0);
72 //if configured try to upgrade encryption to tls for ldap connections
73 if ($this->starttls) {
74 $this->connection->startTls();
75 }
76 return $this;
77 }
78
79 /**
80 * Disconnect from a resource if one is available
81 */
82 public function disconnect(): self
83 {
84 if (null !== $this->connection) {
85 $this->connection->unbind();
86 }
87 $this->connection = null;
88 return $this;
89 }
90
91 /**
92 * Bind to an LDAP-Server with the given credentials
93 *
94 * @throws Error
95 */
96 public function bind(): self
97 {
98 if (!$this->connection) {
99 $this->connect();
100 }
101 if (null === $this->connection) {
102 throw MissingValidLdapConnection::get();
103 }
104 if ($this->uri->isAnonymous()) {
105 $bind = $this->connection->bind();
106 } else {
107 $bind = $this->connection->bind($this->uri->getUsername(), $this->uri->getPassword());
108 }
109 if (!$bind) {
110 throw new Error('bind was not successfull: ' . $this->connection->error());
111 }
112 return $this;
113 }
114
115 /**
116 * This method does the actual ldap-serch.
117 *
118 * This is using the filter <var>$filter</var> for retrieving the attributes
119 * <var>$attributes</var>
120 *
121 * @return array<string|int, mixed>
122 * @throws Error
123 */
124 public function search(string $filter, array $attributes = ['uid'], ?string $base = ''): array
125 {
126 if (null === $this->connection) {
127 throw new Error('No resource handle available');
128 }
129 if (!$base) {
130 $base = $this->uri->getBaseDn();
131 }
132 $result = $this->connection->search($base, $filter, $attributes);
133 if ($result === false) {
134 throw new Error('no result found');
135 }
136 $info = $this->connection->getEntries($result);
137 if ($info === false) {
138 throw new Error('invalid results found');
139 }
140 return $info;
141 }
142
143 /**
144 * This method authenticates the user <var>$username</var> using the
145 * password <var>$password</var>
146 *
147 * @param string $filter OPTIONAL This parameter defines the Filter to be used
148 * when searchin for the username. This MUST contain the string '%s' which
149 * will be replaced by the vaue given in <var>$username</var>
150 * @throws Error
151 */
152 public function authenticate(string $username, string $password, string $filter = '(uid=%s)'): bool
153 {
154 $this->connect();
155 $this->bind();
156 $res = $this->search(sprintf($filter, $this->factory->escape($username, '', LDAP_ESCAPE_FILTER)));
157 if ($res ['count'] !== 1) {
158 return false;
159 }
160
161 $dn = $res[0]['dn'];
162 return $username && $password && $this->connection->bind($dn, $password);
163 }
164 }
1 <?php
2
3 /**
4 * Copyright Andreas Heigl <andreas@heigl.org>
5 *
6 * Licenses under the MIT-license. For details see the included file LICENSE.md
7 */
8
9 declare(strict_types=1);
10
11 namespace Org_Heigl\AuthLdap;
12
13 use WP_User;
14
15 use function array_search;
16 use function in_array;
17 use function var_dump;
18
19 class UserRoleHandler
20 {
21 /**
22 * @param WP_User $user
23 * @param string[] $roles
24 * @return void
25 */
26 public function addRolesToUser(WP_User $user, $roles) : void
27 {
28 if ($roles === []) {
29 return;
30 }
31
32 if ($user->roles == $roles) {
33 return;
34 }
35
36 // Remove unused roles from existing.
37 foreach ($user->roles as $role) {
38 if (!in_array($role, $roles)) {
39 // Remove unused roles.
40 $user->remove_role($role);
41 continue;
42 }
43 // Remove the existing role from roles.
44 if (($key = array_search($role, $roles)) !== false) {
45 unset($roles[$key]);
46 }
47 }
48
49 // Add new ones if not already assigned.
50 foreach ($roles as $role) {
51 $user->add_role($role);
52 }
53 }
54 }
1 <?php
2
3 /**
4 * Copyright Andreas Heigl <andreas@heigl.org>
5 *
6 * Licenses under the MIT-license. For details see the included file LICENSE.md
7 */
8
9 declare(strict_types=1);
10
11 namespace Org_Heigl\AuthLdap\Wrapper;
12
13 use function ldap_bind;
14 use function ldap_connect;
15 use function ldap_error;
16 use function ldap_escape;
17 use function ldap_get_entries;
18 use function ldap_set_option;
19 use function ldap_start_tls;
20 use function ldap_unbind;
21
22 final class Ldap implements LdapInterface
23 {
24 private $connection;
25
26 public function __construct(string $ldapUri)
27 {
28 $this->connection = ldap_connect($ldapUri);
29 }
30
31 public function bind($dn = null, $password = null)
32 {
33 if (null === $dn && null === $password) {
34 return ldap_bind($this->connection);
35 }
36 return ldap_bind($this->connection, $dn, $password);
37 }
38
39 public function unbind()
40 {
41 return ldap_unbind($this->connection);
42 }
43
44 public function setOption($option, $value)
45 {
46 return ldap_set_option($this->connection, $option, $value);
47 }
48
49 public function startTls()
50 {
51 return ldap_start_tls($this->connection);
52 }
53
54 public function error()
55 {
56 return ldap_error($this->connection);
57 }
58
59 public function errno()
60 {
61 return ldap_errno($this->connection);
62 }
63
64 public function search(
65 $base,
66 $filter,
67 array $attributes = [],
68 $attributes_only = 0,
69 $sizelimit = -1,
70 $timelimit = -1
71 ) {
72 return ldap_search(
73 $this->connection,
74 $base,
75 $filter,
76 $attributes,
77 $attributes_only,
78 $sizelimit,
79 $timelimit
80 );
81 }
82
83 public function getEntries($search_result)
84 {
85 return ldap_get_entries($this->connection, $search_result);
86 }
87
88 public static function escape(string $value, string $ignore = '', int $flags = 0): string
89 {
90 return ldap_escape($value, $ignore, $flags);
91 }
92 }
1 <?php
2
3 /**
4 * Copyright Andreas Heigl <andreas@heigl.org>
5 *
6 * Licenses under the MIT-license. For details see the included file LICENSE.md
7 */
8
9 declare(strict_types=1);
10
11 namespace Org_Heigl\AuthLdap\Wrapper;
12
13 class LdapFactory
14 {
15 public function createFromLdapUri(string $ldapUri): LdapInterface
16 {
17 return new Ldap($ldapUri);
18 }
19
20 public function escape($value, $ignore = '', $flags = 0): string
21 {
22 return Ldap::escape($value, $ignore, $flags);
23 }
24 }
1 <?php
2
3 /**
4 * Copyright Andreas Heigl <andreas@heigl.org>
5 *
6 * Licenses under the MIT-license. For details see the included file LICENSE.md
7 */
8
9 declare(strict_types=1);
10
11 namespace Org_Heigl\AuthLdap\Wrapper;
12
13 interface LdapInterface
14 {
15 public function bind($dn = null, $password = null);
16
17 public function unbind();
18
19 public function setOption($option, $value);
20
21 public function startTls();
22
23 public function error();
24
25 public function errno();
26
27 public function search(
28 $base,
29 $filter,
30 array $attributes = [],
31 $attributes_only = 0,
32 $sizelimit = -1,
33 $timelimit = -1
34 );
35
36 public function getEntries($search_result);
37
38 public static function escape(string $value, string $ignore = '', int $flags = 0): string;
39 }
1 <?php
2
3 /**
4 * Copyright (c) 2016-2016} Andreas Heigl<andreas@heigl.org>
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 * THE SOFTWARE.
20 *
21 * @author Andreas Heigl<andreas@heigl.org>
22 * @copyright 2016-2016 Andreas Heigl
23 * @license http://www.opensource.org/licenses/mit-license.php MIT-License
24 * @version 0.0
25 * @since 07.06.2016
26 * @link http://github.com/heiglandreas/authLDAP
27 */
28
29 namespace Org_Heigl\AuthLdapTest;
30
31 use Closure;
32 use Org_Heigl\AuthLdap\Exception\Error;
33 use Org_Heigl\AuthLdap\Exception\SearchUnsuccessfull;
34 use Org_Heigl\AuthLdap\LdapList;
35 use Org_Heigl\AuthLdap\Manager\Ldap;
36 use Org_Heigl\AuthLdap\LdapUri;
37 use Org_Heigl\AuthLdap\Wrapper\LdapFactory;
38 use Org_Heigl\AuthLdap\Wrapper\LdapInterface;
39 use PHPUnit\Framework\Assert;
40 use PHPUnit\Framework\TestCase;
41
42 class LDAPListBaseTest extends TestCase
43 {
44 private $ldapA;
45
46 private $ldapB;
47
48 public function setUp(): void
49 {
50 $this->ldapA = $this->getMockBuilder(Ldap::class)->disableOriginalConstructor()->getMock();
51 $this->ldapB = $this->getMockBuilder(Ldap::class)->disableOriginalConstructor()->getMock();
52 Assert::assertNotSame($this->ldapA, $this->ldapB);
53 parent::setUp(); // TODO: Change the autogenerated stub
54 }
55
56 public function addingItemsWorks(): void
57 {
58 $list = new LdapList();
59
60 $list->addLdap($this->ldapA);
61 $list->addLdap($this->ldapB);
62
63 Assert::assertSame([$this->ldapA, $this->ldapB], $this->getLdaps($list));
64 }
65
66 public function testFailingBindRemovesItemsFromList(): void
67 {
68 $list = new LdapList();
69
70 $list->addLdap($this->ldapA);
71 $list->addLdap($this->ldapB);
72
73 $this->ldapA->method('bind')->willThrowException(new Error('throw'));
74 $this->ldapB->method('bind')->willReturn($this->ldapB);
75
76 $list->bind();
77
78 Assert::assertCount(1, $this->getLdaps($list));
79 }
80
81 public function testFailingBindInAllConnectorsThrows(): void
82 {
83 $list = new LdapList();
84
85 $list->addLdap($this->ldapA);
86 $list->addLdap($this->ldapB);
87
88 $this->ldapA->method('bind')->willThrowException(new Error('throw'));
89 $this->ldapB->method('bind')->willThrowException(new Error('throw'));
90
91 $this->expectException(Error::class);
92 $this->expectExceptionMessage('No bind successfull');
93
94 $list->bind();
95
96 Assert::assertCount(0, $this->getLdaps($list));
97 }
98
99 public function testAuthenticatingViaListWorks(): void
100 {
101 $list = new LdapList();
102
103 $list->addLdap($this->ldapA);
104 $list->addLdap($this->ldapB);
105
106 $this->ldapA->method('authenticate')->willReturn(false);
107 $this->ldapB->method('authenticate')->willReturn(true);
108
109 Assert::assertTrue($list->authenticate('foo', 'bar'));
110
111 Assert::assertCount(1, $this->getLdaps($list));
112 }
113
114 public function testAuthenticatingViaListFailsWhenNoConnectorAuthenticates(): void
115 {
116 $list = new LdapList();
117
118 $list->addLdap($this->ldapA);
119 $list->addLdap($this->ldapB);
120
121 $this->ldapA->method('authenticate')->willReturn(false);
122 $this->ldapB->method('authenticate')->willReturn(false);
123
124 Assert::assertFalse($list->authenticate('foo', 'bar'));
125
126 Assert::assertCount(0, $this->getLdaps($list));
127 }
128
129 public function testSuccessfullSearchInOneConnectorReturnsResult(): void
130 {
131 $list = new LdapList();
132
133 $list->addLdap($this->ldapA);
134 $list->addLdap($this->ldapB);
135
136 $this->ldapA->method('search')->willThrowException(new Error('Whoot'));
137 $this->ldapB->method('search')->willReturn(['count' => 1, ['dn' => 'foo']]);
138
139 Assert::assertEquals(['count' => 1, ['dn' => 'foo']], $list->search('uid=foo'));
140 }
141
142 public function testUnsuccessfullSearchWillThrow(): void
143 {
144 $list = new LdapList();
145
146 $list->addLdap($this->ldapA);
147 $list->addLdap($this->ldapB);
148
149 $this->ldapA->method('search')->willThrowException(new Error('Whoot'));
150 $this->ldapB->method('search')->willThrowException(new Error('Whoot2'));
151
152 $this->expectException(SearchUnsuccessfull::class);
153
154 $list->search('uid=foo');
155 }
156
157
158 private function getLdaps(LdapList $list): array
159 {
160
161 // Courtesy of Marco Pivetta
162 // https://ocramius.github.io/blog/accessing-private-php-class-members-without-reflection/
163 $sweetsThief = function (LdapList $kitchen) {
164 return $kitchen->items;
165 };
166
167 // Closure::bind() actually creates a new instance of the closure
168 $sweetsThief = Closure::bind($sweetsThief, null, $list);
169
170 return $sweetsThief($list);
171 }
172 }
1 <?php
2
3 /**
4 * $Id: LdapTest.php 292156 2010-09-21 19:32:01Z heiglandreas $
5 *
6 * authLdap - Authenticate Wordpress against an LDAP-Backend.
7 * Copyright (c) 2008 Andreas Heigl<andreas@heigl.org>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 *
23 * This file tests the basic LDAP-Tasks
24 *
25 * @category authLdap
26 * @package authLdap
27 * @subpackage UnitTests
28 * @author Andreas Heigl<andreas@heigl.org>
29 * @copyright 2010 Andreas Heigl<andreas@heigl.org>
30 * @license GPL
31 * @since 21.09.2010
32 */
33
34 namespace Org_Heigl\AuthLdapTest;
35
36 use Exception;
37 use Generator;
38 use Org_Heigl\AuthLdap\LdapUri;
39 use Org_Heigl\AuthLdap\Wrapper\LdapFactory;
40 use PHPUnit\Framework\TestCase;
41 use Org_Heigl\AuthLdap\Manager\Ldap;
42
43 class LdapTest extends TestCase
44 {
45 /**
46 *
47 * @dataProvider dpInstantiateLdapClass
48 * @param array $expected
49 * @param array $given
50 */
51 public function testInstantiateLdapClass($ldapUri, $debug, $startTls)
52 {
53 $ldap = new Ldap(new LdapFactory(), LdapUri::fromString($ldapUri), $debug, $startTls);
54 self::assertInstanceOf(Ldap::class, $ldap);
55 }
56
57 /**
58 * @dataProvider dpExceptionsWhenInstantiatingLdapClass
59 * @param string $expected
60 */
61 public function testExceptionsWhenInstantiatingLdapClass(string $expected)
62 {
63 self::expectException(Exception::class);
64 new Ldap(new LdapFactory(), LdapUri::fromString($expected));
65 }
66
67 public function dpInstantiateLdapClass(): Generator
68 {
69 yield [
70 'ldap://uid=jondoe,cn=users,cn=example,c=org:secret@ldap.example.org/cn=example,c=org',
71 true,
72 false,
73 [
74 'username' => 'uid=jondoe,cn=users,cn=example,c=org',
75 'password' => 'secret',
76 'server' => 'ldap.example.org',
77 'baseDn' => 'cn=example,c=org',
78 'debug' => true,
79 ],
80 ];
81 yield [
82 'ldap://uid=jondoe,cn=users,cn=example,c=org@ldap.example.org/cn=example,c=org',
83 true,
84 false,
85 [
86 'username' => 'uid=jondoe,cn=users,cn=example,c=org',
87 'password' => '',
88 'server' => 'ldap.example.org',
89 'baseDn' => 'cn=example,c=org',
90 'debug' => true,
91 ],
92 ];
93 yield [
94 'ldap://ldap.example.org/cn=example,c=org',
95 true,
96 false,
97 [
98 'username' => 'anonymous',
99 'password' => '',
100 'server' => 'ldap.example.org',
101 'baseDn' => 'cn=example,c=org',
102 'debug' => true,
103 ],
104 ];
105 // yield [
106 // 'ldap://ldap.example.org',
107 // true,
108 // false,
109 // [
110 // 'username' => 'anonymous',
111 // 'password' => '',
112 // 'server' => 'ldap.example.org',
113 // 'baseDn' => '',
114 // 'debug' => true
115 // ]
116 // ];
117 yield [
118 'ldap://uid=jondoe,cn=users,cn=example,c=org:secret@ldap.example.org/cn=example,c=org',
119 false,
120 false,
121 [
122 'username' => 'uid=jondoe,cn=users,cn=example,c=org',
123 'password' => 'secret',
124 'server' => 'ldap.example.org',
125 'baseDn' => 'cn=example,c=org',
126 'debug' => false,
127 ],
128 ];
129 yield [
130 'ldap://ldap.example.org/cn=test%20example,c=org',
131 false,
132 false,
133 [
134 'username' => 'anonymous',
135 'password' => '',
136 'server' => 'ldap.example.org',
137 'baseDn' => 'cn=test example,c=org',
138 'debug' => false,
139 ],
140 ];
141 }
142
143 public function dpExceptionsWhenInstantiatingLdapClass(): Generator
144 {
145 yield ['ldap://ldap.example.org'];
146 yield ['ldap://foo:bar@/cn=example,c=org'];
147 yield ['http://ldap.example.org'];
148 yield ['fooBar'];
149 yield ['ldap://ldap.example.org/'];
150 yield ['()123üäö'];
151 }
152
153 public function testThatGroupMappingWorks()
154 {
155 $groups = [
156 'count' => 1,
157 0 => [
158 'dn' => 'dn-1',
159 'count' => 1,
160 0 => 'group',
161 'group' => [
162 'count' => 2,
163 0 => 'angličtina@ff.cuni.cz',
164 1 => 'literatura@ff.cuni.cz',
165 ],
166 ],
167 ];
168
169 $grp = [];
170 for ($i = 0; $i < $groups ['count']; $i++) {
171 for ($k = 0; $k < $groups[$i][strtolower('group')]['count']; $k++) {
172 $grp[] = $groups[$i][strtolower('group')][$k];
173 }
174 }
175
176 $this->assertEquals([
177 'angličtina@ff.cuni.cz',
178 'literatura@ff.cuni.cz',
179 ], $grp);
180
181 $role = '';
182 foreach (
183 [
184 'testrole' => 'literatura@ff.cuni.cz,literatura@ff.cuni.cz',
185 ] as $key => $val
186 ) {
187 $currentGroup = explode(',', $val);
188 // Remove whitespaces around the group-ID
189 $currentGroup = array_map('trim', $currentGroup);
190 if (0 < count(array_intersect($currentGroup, $grp))) {
191 $role = $key;
192 break;
193 }
194 }
195
196 $this->assertEquals('testrole', $role);
197 }
198 }
1 <?php
2
3 namespace Org_Heigl\AuthLdapTest;
4
5 use Generator;
6 use Org_Heigl\AuthLdap\Exception\InvalidLdapUri;
7 use Org_Heigl\AuthLdap\LdapUri;
8 use PHPUnit\Framework\Assert;
9 use PHPUnit\Framework\TestCase;
10
11 use function getenv;
12 use function putenv;
13
14 class LdapUriTest extends TestCase
15 {
16 public function toStringProvider(): Generator
17 {
18 yield ['ldaps://foo:bar@foo.bar/baz', 'ldaps://foo.bar:636', 'foo', 'bar', 'baz'];
19 yield ['env:LDAP_URI', 'ldaps://foo.bar:636', 'foo', 'bar', 'baz', [
20 'LDAP_URI' => 'ldaps://foo:bar@foo.bar/baz',
21 ]];
22 yield ['ldaps://foo:%env:LDAP_PASSWORD%@foo.bar/baz', 'ldaps://foo.bar:636', 'foo', 'bar', 'baz', [
23 'LDAP_PASSWORD' => 'bar',
24 ]];
25 yield ['ldaps://foo:%env:LDAP_PASSWORD%@foo.bar/baz', 'ldaps://foo.bar:636', 'foo', 'ba r', 'baz', [
26 'LDAP_PASSWORD' => 'ba r',
27 ]];
28 }
29
30 public function fromStringProvider(): Generator
31 {
32 yield ['ldaps://foo:bar@foo.bar/baz', false];
33 yield ['env:LDAP_URI', false];
34 yield ['foo:MyLdapUri', true];
35 }
36
37 /**
38 * @dataProvider toStringProvider
39 */
40 public function testToString(string $uri, string $result, $user, $password, $baseDn, array $env = []): void
41 {
42 foreach ($env as $key => $value) {
43 putenv("$key=$value");
44 }
45 $ldapUri = LdapUri::fromString($uri);
46 Assert::assertSame($result, $ldapUri->toString());
47 Assert::assertSame($user, $ldapUri->getUsername());
48 Assert::assertSame($password, $ldapUri->getPassword());
49 Assert::assertSame($baseDn, $ldapUri->getBaseDn());
50 }
51
52 /** @dataProvider fromStringProvider */
53 public function testFromString(string $uri, bool $failure = false): void
54 {
55 if ($failure) {
56 self::expectException(InvalidLdapUri::class);
57 }
58 $ldapUri = LdapUri::fromString($uri);
59 self::assertInstanceOf(LdapUri::class, $ldapUri);
60 }
61
62 public function testSettingLdapsWillSetCorrectPort(): void
63 {
64 $uri = LdapUri::fromString('ldaps://example.org/foo');
65
66 Assert::assertSame('ldaps://example.org:636', $uri->toString());
67 }
68
69 public function testSettingLdapWillSetCorrectPort(): void
70 {
71 $uri = LdapUri::fromString('ldap://example.org/foo');
72
73 Assert::assertSame('ldap://example.org:389', $uri->toString());
74 }
75
76 /**
77 * @dataProvider anonymousProvider
78 */
79 public function testUriIsAnonymous(string $uri): void
80 {
81 $uri = LdapUri::fromString($uri);
82 Assert::assertTrue($uri->isAnonymous());
83 }
84
85 public function anonymousProvider(): Generator
86 {
87 yield ['ldaps://test.example.com/dc=com'];
88 yield ['ldaps://foo@test.example.com/dc=com'];
89 yield ['ldaps://%20:password@test.example.com/dc=com'];
90 yield ['ldaps://anonymous:password@test.example.com/dc=com'];
91 }
92
93 public function testMissingSchemaThrows(): void
94 {
95 $this->expectException(InvalidLdapUri::class);
96
97 LdapUri::fromString('ldaps.example.com');
98 }
99
100 public function testMWrongSchemaThrows(): void
101 {
102 $this->expectException(InvalidLdapUri::class);
103
104 LdapUri::fromString('environ://ldaps.example.com');
105 }
106
107 public function testMissingHostThrows(): void
108 {
109 $this->expectException(InvalidLdapUri::class);
110
111 LdapUri::fromString('ldaps:/foo=bar');
112 }
113
114 public function testgettingUriFromEnvironment(): void
115 {
116 putenv('URI=ldaps://example.com/foo');
117 $uri = LdapUri::fromString('env:URI');
118
119 Assert::assertSame('ldaps://example.com:636', (string) $uri);
120 Assert::assertSame('foo', $uri->getBaseDn());
121 }
122
123 public function testgettingUriFromEmptyEnvironment(): void
124 {
125 putenv('URI');
126 $this->expectException(InvalidLdapUri::class);
127 $uri = LdapUri::fromString('env:URI');
128 }
129 }
1 <?php
2
3 /**
4 * Copyright (c) 2016-2016} Andreas Heigl<andreas@heigl.org>
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 * THE SOFTWARE.
20 *
21 * @author Andreas Heigl<andreas@heigl.org>
22 * @copyright 2016-2016 Andreas Heigl
23 * @license http://www.opensource.org/licenses/mit-license.php MIT-License
24 * @version 0.0
25 * @since 07.06.2016
26 * @link http://github.com/heiglandreas/authLDAP
27 */
28
29 namespace Org_Heigl\AuthLdapTest\Manager;
30
31 use Org_Heigl\AuthLdap\Exception\Error;
32 use Org_Heigl\AuthLdap\LdapList;
33 use Org_Heigl\AuthLdap\LdapUri;
34 use Org_Heigl\AuthLdap\Manager\Ldap;
35 use Org_Heigl\AuthLdap\Wrapper\Ldap as LdapWrapper;
36 use Org_Heigl\AuthLdap\Wrapper\LdapFactory;
37 use Org_Heigl\AuthLdap\Wrapper\LdapInterface;
38 use PHPUnit\Framework\Assert;
39 use PHPUnit\Framework\TestCase;
40
41 class LDAPBaseTest extends TestCase
42 {
43 private LdapFactory $factory;
44
45 private LdapInterface $wrapper;
46
47 public function setUp(): void
48 {
49 $this->wrapper = $this->getMockBuilder(LdapInterface::class)->getMock();
50 $this->factory = $this->getMockBuilder(LdapFactory::class)->getMock();
51 $this->factory->method('createFromLdapUri')->willReturn($this->wrapper);
52 $this->factory->method('escape')->willReturnCallback(function ($value, $ignore, $flags) {
53 return \Org_Heigl\AuthLdap\Wrapper\Ldap::escape($value, $ignore, $flags);
54 });
55 }
56
57 /**
58 * @dataProvider bindingWithPasswordProvider
59 * @testdox Binding user $user with password $password using a filter $filter works
60 */
61 public function testThatBindingWithPasswordWorks($user, $password, $filter, $uri)
62 {
63 $uri = LdapUri::fromString($uri);
64 $this->wrapper
65 ->method('bind')
66 ->willReturn(true);
67
68 $this->wrapper
69 ->expects($this->once())
70 ->method('search')
71 ->with($uri->getBaseDn(), sprintf($filter, $user));
72
73 $this->wrapper
74 ->method('getEntries')
75 ->willReturn(['count' => 1, 0 => ['dn' => 'foo']]);
76
77 $ldap = new Ldap($this->factory, $uri);
78 $this->assertTrue($ldap->authenticate($user, $password, $filter));
79 }
80
81 public function bindingWithPasswordProvider()
82 {
83 return [
84 [
85 'user3',
86 'user!"',
87 'uid=%s',
88 'ldap://cn=admin,dc=example,dc=org:insecure@127.0.0.1:3389/dc=example,dc=org'
89 ], [
90 // 'admin',
91 // 'insecure',
92 // 'cn=%s',
93 // 'ldap://cn=admin,dc=example,dc=org:insecure@127.0.0.1:3389/dc=example,dc=org'
94 // ], [
95 'user1',
96 'user1',
97 'uid=%s',
98 'ldap://cn=admin,dc=example,dc=org:insecure@127.0.0.1:3389/dc=example,dc=org'
99 ], [
100 'user 4',
101 'user!"',
102 'uid=%s',
103 'ldap://cn=admin,dc=example,dc=org:insecure@127.0.0.1:3389/dc=example,dc=org'
104 ], [
105 'user 5',
106 'user!"',
107 'uid=%s',
108 'ldap://cn=admin,dc=example,dc=org:insecure@127.0.0.1:3389/dc=test%20space,dc=example,dc=org'
109 ],
110 ];
111 }
112
113 /**
114 * @param $uri
115 * @dataProvider initialBindingToLdapServerWorksProvider
116 */
117 public function testThatInitialBindingWorks($uri)
118 {
119 $this->wrapper
120 ->method('bind')
121 ->willReturn(true);
122
123 $ldap = new LDAP($this->factory, LdapUri::fromString($uri));
124 $this->assertInstanceof(Ldap::class, $ldap->bind());
125 }
126
127 /**
128 * @param $uri
129 * @dataProvider initialBindingToLdapServerWorksProvider
130 */
131 public function testThatInitialBindingToMultipleLdapsWorks($uri)
132 {
133 $this->wrapper->expects($this->once())
134 ->method('bind')
135 ->with('uid=user 5,dc=test space,dc=example,dc=org', 'user!"')
136 ->willReturn(true);
137
138 $list = new LdapList();
139 $list->addLDAP(new LDAP($this->factory, LdapUri::fromString($uri)));
140 $this->assertTrue($list->bind());
141 }
142
143 public function initialBindingToLdapServerWorksProvider()
144 {
145 return [
146 ['ldap://uid=user%205,dc=test%20space,dc=example,dc=org:user!"' .
147 '@127.0.0.1:3389/dc=test%20space,dc=example,dc=org'],
148 ];
149 }
150
151 /**
152 * @dataProvider provideUnescapedData
153 */
154 public function testThatPassedDataIsEscaped($unescaped, $escaped): void
155 {
156 $ldap = new LDAP($this->factory, LdapUri::fromString(
157 'ldap://cn=admin,dc=example,dc=org:insecure@127.0.0.1:3389/dc=example,dc=org'
158 ));
159
160 $this->wrapper->expects($this->exactly(2))
161 ->method('bind')
162 ->withConsecutive(
163 ['cn=admin,dc=example,dc=org', 'insecure'],
164 ['foo', 'password'],
165 )
166 ->willReturnOnConsecutiveCalls(true, true);
167 $this->wrapper->expects($this->once())->method('search')->with(
168 'dc=example,dc=org',
169 $escaped,
170 ['uid'],
171 );
172 $this->wrapper->method('getEntries')->willReturn(['count' => 1, 0 => ['dn' => 'foo']]);
173
174 $ldap->authenticate($unescaped, 'password');
175 }
176
177 public function provideUnescapedData(): array
178 {
179 return [
180 ['\’foobar', '(uid=\5c’foobar)'],
181 ['XXX;(&(uid=Admin)(userPassword=A*))', '(uid=XXX;\28&\28uid=Admin\29\28userPassword=A\2a\29\29)'],
182 ];
183 }
184
185
186 public function testSettingStartTls(): void
187 {
188 $ldap = new Ldap($this->factory, LdapUri::fromString('ldap://example.com/foo=bar'), true);
189
190 $this->wrapper->expects($this->once())->method('startTls');
191 $this->wrapper->method('bind')->willReturn(true);
192
193 $ldap->bind();
194 }
195
196
197 public function testUnsettingConnectionBeforeBinding(): void
198 {
199 $ldap = new Ldap($this->factory, LdapUri::fromString('ldap://example.com/foo=bar'), true);
200
201 $this->wrapper->method('bind')->willReturn(true);
202 $this->wrapper->expects($this->once())->method('unbind');
203
204 $ldap->connect();
205 $ldap->disconnect();
206 }
207
208 public function testErrorIsThrownOnUnsuccessfullBInd(): void
209 {
210 $ldap = new Ldap($this->factory, LdapUri::fromString('ldap://example.com/foo=bar'), true);
211
212 $this->wrapper->method('bind')->willReturn(false);
213 $this->expectException(Error::class);
214
215 $ldap->bind();
216 }
217
218 public function testFailingSearchThrowsError(): void
219 {
220 $ldap = new Ldap($this->factory, LdapUri::fromString('ldap://example.com/foo=bar'), true);
221
222 $this->wrapper->method('bind')->willReturn(true);
223 $this->wrapper->method('search')->willReturn(false);
224
225 $this->expectException(Error::class);
226 $this->expectExceptionMessage('no result found');
227
228 $ldap->bind();
229 $ldap->search('uid=foo');
230 }
231
232 public function testFailingSearchResultFetchingThrowsError(): void
233 {
234 $ldap = new Ldap($this->factory, LdapUri::fromString('ldap://example.com/foo=bar'), true);
235
236 $this->wrapper->method('bind')->willReturn(true);
237 $this->wrapper->method('search')->willReturn(true);
238 $this->wrapper->method('getEntries')->willReturn(false);
239
240 $this->expectException(Error::class);
241 $this->expectExceptionMessage('invalid results found');
242
243 $ldap->bind();
244 $ldap->search('uid=foo');
245 }
246
247 public function testSearchingWithoutBindingThrowsError(): void
248 {
249 $ldap = new Ldap($this->factory, LdapUri::fromString('ldap://example.com/foo=bar'), true);
250
251 $this->expectException(Error::class);
252 $this->expectExceptionMessage('No resource handle available');
253
254 $ldap->search('uid=foo');
255 }
256
257 public function testAuthenticatingFailsWithNoSearchResults(): void
258 {
259 $ldap = new Ldap($this->factory, LdapUri::fromString('ldap://example.com/foo=bar'), true);
260
261 $this->wrapper->method('bind')->willReturn(true);
262 $this->wrapper->method('search')->willReturn(true);
263 $this->wrapper->method('getEntries')->willReturn(['count' => 0]);
264
265 $ldap->bind();
266 Assert::assertFalse($ldap->authenticate('foo', 'bar'));
267 }
268 }
1 <?php
2
3 /**
4 * Copyright Andreas Heigl <andreas@heigl.org>
5 *
6 * Licenses under the MIT-license. For details see the included file LICENSE.md
7 */
8
9 namespace Org_Heigl\AuthLdapTest;
10
11 use Org_Heigl\AuthLdap\UserRoleHandler;
12 use WorDBless\BaseTestCase;
13 use WP_User;
14
15 class UserRoleHandlerTest extends BaseTestCase
16 {
17 public function testUserRolesAreAssignedAsExpected() : void
18 {
19 $user = new WP_User(1);
20
21 $handler = new UserRoleHandler();
22
23 $handler->addRolesToUser($user, ['author', 'user']);
24
25 self::assertEquals(['author'], $user->roles);
26 }
27
28 public function testEqualUserRolesAreEasy() : void
29 {
30 $user = new WP_User(1);
31 $user->add_role('administrator');
32 $user->add_role('author');
33
34 $handler = new UserRoleHandler();
35
36 $handler->addRolesToUser($user, ['administrator', 'author']);
37
38 self::assertEquals(['administrator', 'author'], $user->roles);
39 }
40
41 public function testUserRolesAreNotAssignedWhenUserAlreadyHasRole() : void
42 {
43 $user = new WP_User(1);
44 $user->add_role('administrator');
45 $user->add_role('author');
46
47 $handler = new UserRoleHandler();
48
49 $handler->addRolesToUser($user, ['author', 'editor']);
50
51 self::assertEquals(['author', 'editor'], $user->roles);
52 }
53
54 public function testEmptyRolesAreIgnored() : void
55 {
56 $user = new WP_User(1);
57 $user->add_role('administrator');
58
59 $handler = new UserRoleHandler();
60 $handler->addRolesToUser($user, []);
61
62 self::assertEquals(['administrator'], $user->roles);
63 }
64 }
1 <?php
2
3 /**
4 * Copyright Andreas Heigl <andreas@heigl.org>
5 *
6 * Licenses under the MIT-license. For details see the included file LICENSE.md
7 */
8
9 require_once __DIR__ . '/../vendor/autoload.php'; // adjust the path as needed
10
11 \WorDBless\Load::load();
1 <?php
2 /**
3 * Copyright (c)2014-2014 heiglandreas
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIBILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 * THE SOFTWARE.
22 *
23 * @category
24 * @author Andreas Heigl<andreas@heigl.org>
25 * @copyright ©2014-2014 Andreas Heigl
26 * @license http://www.opesource.org/licenses/mit-license.php MIT-License
27 * @version 0.0
28 * @since 19.12.14
29 * @link https://github.com/heiglandreas/authLdap
30 */
31 ?><div class="wrap">
32 <?php if (! extension_loaded('ldap')) : ?>
33 <div class="error"><strong>Caveat:</strong> The LDAP-extension is not loaded!
34 Without that extension it is not possible to query an LDAP-Server! Please have a look
35 at <a href="http://php.net/manual/install.php">the PHP-Installation page</a>
36 </div>
37 <?php endif ?>
38 <h2>AuthLDAP Options</h2>
39 <form class="authldap-options" method="post" id="authLDAP_options" action="<?php echo $action;?>">
40 <h3 class="title">General Usage of authLDAP</h3>
41 <fieldset class="options">
42 <table class="form-table">
43 <tr>
44 <th>
45 <label for="authLDAPAuth">Enable Authentication via LDAP?</label>
46 </th>
47 <td>
48 <input type="checkbox" name="authLDAPAuth" id="authLDAPAuth" value="1"<?php echo $tChecked; ?>/>
49 </td>
50 </tr>
51 <tr>
52 <th>
53 <label for="authLDAPDebug">Debug AuthLDAP?</label>
54 </th>
55 <td>
56 <input type="checkbox" name="authLDAPDebug" id="authLDAPDebug" value="1"<?php echo $tDebugChecked; ?>/>
57 </td>
58 </tr>
59 <tr>
60 <th>
61 <label for="authLDAPDoNotOverwriteNonLdapUsers">Do not authenticate existing WordPress-Users</label>
62 </th>
63 <td>
64 <input type="checkbox" name="authLDAPDoNotOverwriteNonLdapUsers" id="authLDAPDoNotOverwriteNonLdapUsers" value="1"<?php echo $tDoNotOverwriteNonLdapUsers; ?>/>
65 <p class="description">
66 Shall we prohibit authenticating already in WordPress created users using LDAP? If you enable this, LDAP-Users with the same user-ID
67 as existing WordPress-Users can no longer take over the WordPress-Users account. This also means that LDAP-Users with the same User-ID as existing
68 WordPress-Users will <strong>not</strong> be able to authenticate anymore! Accounts that have been taken over already will not be affected by this setting.
69 </p>
70 <p class="description">This should only be checked if you know what you are doing!</p>
71 </td>
72 </tr>
73 <tr>
74 <th>
75 <label for="authLDAPCachePW">Save entered passwords in the wordpress user table?</label>
76 </th>
77 <td>
78 <input type="checkbox" name="authLDAPCachePW" id="authLDAPCachePW" value="1"<?php echo $tPWChecked; ?>/>
79 </td>
80 </tr>
81 <tr>
82 <th>
83 <label for="authLDAPGroupEnable">Map LDAP Groups to wordpress Roles?</label>
84 </th>
85 <td>
86 <input type="checkbox" name="authLDAPGroupEnable" id="authLDAPGroupEnable" value="1"<?php echo $tGroupChecked; ?>/>
87 <p class="description">
88 Search LDAP for user's groups and map to Wordpress Roles.
89 </p>
90 </td>
91 </tr>
92 </table>
93 </fieldset>
94 <h3 class="title">General Server Settings</h3>
95 <fieldset class="options">
96 <table class="form-table">
97 <tr>
98 <th>
99 <label for="authLDAPURI">LDAP URI</label>
100 </th>
101 <td>
102 <input type="text" name="authLDAPURI" id="authLDAPURI" placeholder="LDAP-URI"
103 class="regular-text" value="<?php echo $authLDAPURI; ?>"/>
104 <p class="description">
105 The <abbr title="Uniform Ressource Identifier">URI</abbr>
106 for connecting to the LDAP-Server. This usualy takes the form
107 <var>&lt;scheme&gt;://&lt;user&gt;:&lt;password&gt;@&lt;server&gt;/&lt;path&gt;</var>
108 according to RFC 1738.</p>
109 <p class="description">
110 In this case it schould be something like
111 <var>ldap://uid=adminuser,dc=example,c=com:secret@ldap.example.com/dc=basePath,dc=example,c=com</var>.
112 </p>
113 <p class="description">
114 If your LDAP accepts anonymous login, you can ommit the user and
115 password-Part of the URI
116 </p>
117 <p class="description">
118 You can use the pseudo-schema <em>env</em> to provide your LDAP-URI from an environment-variable. So if you have your
119 LDAP-URI in a variable called <code>LDAP_URI</code> you can enter <code>env:LDAP_URI</code> in this field and at runtime the
120 appropriate value will be taken from the Environment-variable <code>LDAP_URI</code>. If the varialbe is not set, then the value will be empty.
121 </p>
122 <p class="description">
123 You can also provide different parts of the LDP-URI from environment variables by providing
124 <code>%env:[VARIABLENAME]%</code> within your LDAP-URI. So if you want to provide the
125 password from an Environment-variable <code>LDAP_PASSWORD</code> your LDAP-URI looks like
126 <code>ldap://uid=adminuser,dc=example,c=com:%env:LDAP_PASSWORD%@ldap.example.com/dc=basePath,dc=example,c=com</code>
127 </p>
128 <p class="description">
129 <strong>Caveat!</strong><br/>
130 If you are using Environment-variables for parts of the LDAP-URL then those <strong>must not</strong> be URL-Encoded!<br/>
131 Otherwise the different parts <strong>must</strong> be URL-Encoded!
132 </p>
133 </td>
134 </tr>
135 <tr>
136 <th>
137 <label for="authLDAPURISeparator">LDAP URI-Separator</label>
138 </th>
139 <td>
140 <input type="text" name="authLDAPURISeparator" id="authLDAPURISeparator" placeholder="LDAP-URI Separator"
141 class="regular-text" value="<?php echo $authLDAPURISeparator; ?>"/>
142 <p class="description">
143 A separator that separates multiple LDAP-URIs from one another.
144 You can use that feature to try to authenticate against multiple LDAP-Servers
145 as long as they all have the same attribute-settings. The first LDAP-Server the user can
146 authenticate against will be used to handle the user.
147 </td>
148 </tr>
149 <tr>
150 <th>
151 <label for="authLDAPStartTLS" class="description">StartTLS</label>
152 </th>
153 <td>
154 <input type="checkbox" name="authLDAPStartTLS" id="authLDAPStartTLS" value="1"<?php echo $tStartTLSChecked; ?>/>
155 <p class="description">
156 Use StartTLS for encryption of ldap connections. This setting is not to be used in combination with ldaps connections (ldap:// only).
157 </p>
158 </td>
159 <tr>
160 <th scope="row">
161 <label for="authLDAPFilter" class="description">Filter</label>
162 </th>
163 <td>
164 <input type="text" name="authLDAPFilter" id="authLDAPFilter" placeholder="(uid=%s)"
165 class="regular-text" value="<?php echo $authLDAPFilter; ?>"/>
166 <p class="description">
167 Please provide a valid filter that can be used for querying the
168 <abbr title="Lightweight Directory Access Protocol">LDAP</abbr>
169 for the correct user. For more information on this
170 feature have a look at <a href="http://andreas.heigl.org/cat/dev/wp/authldap">http://andreas.heigl.org/cat/dev/wp/authldap</a>
171 </p>
172 <p class="description">
173 This field <strong>should</strong> include the string <code>%s</code>
174 that will be replaced with the username provided during log-in
175 </p>
176 <p class="description">
177 If you leave this field empty it defaults to <strong>(uid=%s)</strong>
178 </p>
179 </td>
180 </tr>
181 </table>
182 </fieldset>
183
184 <h3 class="title">Settings for creating new Users</h3>
185 <fieldset class="options">
186 <table class="form-table">
187 <tr>
188 <th scope="row">
189 <label for="authLDAPUseUserAccount">User-Read</label>
190 </th>
191 <td>
192 <input type="checkbox" name="authLDAPUseUserAccount" id="authLDAPUseUserAccount" value="1"<?php echo $tUserRead; ?>/><br />
193 <p class="description">
194 If checked the plugin will use the user's account to query their own information. If not it will use the admin account.
195 </p>
196
197 </td>
198 </tr>
199 <tr>
200 <th scope="row">
201 <label for="authLDAPNameAttr">Name-Attribute</label>
202 </th>
203 <td>
204 <input type="text" name="authLDAPNameAttr" id="authLDAPNameAttr" placeholder="name"
205 class="regular-text" value="<?php echo $authLDAPNameAttr; ?>"/><br />
206 <p class="description">
207 Which Attribute from the LDAP contains the Full or the First name
208 of the user trying to log in.
209 </p>
210 <p class="description">
211 This defaults to <strong>name</strong>
212 </p>
213
214 </td>
215 </tr>
216 <tr>
217 <th scope="row">
218 <label for="authLDAPSecName">Second Name Attribute</label>
219 </th>
220 <td>
221 <input type="text" name="authLDAPSecName" id="authLDAPSecName" placeholder=""
222 class="regular-text" value="<?php echo $authLDAPSecName; ?>" />
223 <p class="description">
224 If the above Name-Attribute only contains the First Name of the
225 user you can here specify an Attribute that contains the second name.
226 </p>
227 <p class="description">
228 This field is empty by default
229 </p>
230 </td>
231 </tr>
232 <tr>
233 <th scope="row">
234 <label for="authLDAPUidAttr">User-ID Attribute</label>
235 </th>
236 <td>
237 <input type="text" name="authLDAPUidAttr" id="authLDAPUidAttr" placeholder="uid"
238 class="regular-text" value="<?php echo $authLDAPUidAttr; ?>" />
239 <p class="description">
240 Please give the Attribute, that is used to identify the user. This
241 should be the same as you used in the above <em>Filter</em>-Option
242 </p>
243 <p class="description">
244 This field defaults to <strong>uid</strong>
245 </p>
246 </td>
247 </tr>
248 <tr>
249 <th scope="row">
250 <label for="authLDAPMailAttr">Mail Attribute</label>
251 </th>
252 <td>
253 <input type="text" name="authLDAPMailAttr" id="authLDAPMailAttr" placeholder="mail"
254 class="regular-text" value="<?php echo $authLDAPMailAttr; ?>" />
255 <p class="description">
256 Which Attribute holds the eMail-Address of the user?
257 </p>
258 <p class="description">
259 If more than one eMail-Address are stored in the LDAP, only the first given is used
260 </p>
261 <p class="description">
262 This field defaults to <strong>mail</strong>
263 </p>
264 </td>
265 </tr>
266 <tr>
267 <th scope="row">
268 <label for="authLDAPWebAttr">Web-Attribute</label>
269 </th>
270 <td>
271 <input type="text" name="authLDAPWebAttr" id="authLDAPWebAttr" placeholder=""
272 class="regular-text" value="<?php echo $authLDAPWebAttr; ?>" />
273 <p class="description">
274 If your users have a personal page (URI) stored in the LDAP, it can
275 be provided here.
276 </p>
277 <p class="description">
278 This field is empty by default
279 </p>
280 </td>
281 </tr>
282 <tr>
283 <th scope="row">
284 <label for="authLDAPDefaultRole">Default Role</label>
285 </th>
286 <td>
287 <select name="authLDAPDefaultRole" id="authLDAPDefaultRole">
288 <option value="" <?php echo ( $authLDAPDefaultRole == '' ? 'selected="selected"' : '' ); ?>>
289 None (deny access)
290 </option>
291 <?php foreach ($roles->get_names() as $group => $vals) : ?>
292 <option value="<?php echo $group; ?>" <?php echo ( $authLDAPDefaultRole == $group ? 'selected="selected"' : '' ); ?>>
293 <?php echo $vals; ?>
294 </option>
295 <?php endforeach; ?>
296 </select>
297 <p class="description">
298 Here you can select the default role for users.
299 If you enable LDAP Groups below, they will take precedence over the Default Role.
300 </p>
301 <p class="description">
302 Existing users will retain their roles unless overriden by LDAP Groups below.
303 </p>
304 </td>
305 </tr>
306 </table>
307 </fieldset>
308
309
310 <div id="authldaprolemapping">
311 <h3 class="title">Groups for Roles</h3>
312 <fieldset class="options">
313 <table class="form-table">
314 <tr>
315 <th>
316 <label for="authLDAPGroupOverUser">LDAP Groups override role of existing users?</label>
317 </th>
318 <td>
319 <input type="checkbox" name="authLDAPGroupOverUser" id="authLDAPGroupOverUser" value="1"<?php echo $tGroupOverUserChecked; ?>/>
320 <p class="description">
321 If role determined by LDAP Group differs from existing Wordpress User's role, use LDAP Group.
322 </p>
323 </td>
324 </tr>
325 <tr>
326 <th scope="row">
327 <label for="authLDAPGroupBase">Group-Base</label>
328 </th>
329 <td>
330 <input type="text" name="authLDAPGroupBase" id="authLDAPGroupBase" placeholder=""
331 class="regular-text" value="<?php echo $authLDAPGroupBase; ?>" />
332 <p class="description">
333 This is the base dn to lookup groups.
334 </p>
335 <p class="description">
336 If empty the base dn of the LDAP URI will be used
337 </p>
338 </td>
339 </tr>
340 <tr>
341 <th scope="row">
342 <label for="authLDAPGroupAttr">Group-Attribute</label>
343 </th>
344 <td>
345 <input type="text" name="authLDAPGroupAttr" id="authLDAPGroupAttr" placeholder="gidNumber"
346 class="regular-text" value="<?php echo $authLDAPGroupAttr; ?>" />
347 <p class="description">
348 This is the attribute that defines the Group-ID that can be matched
349 against the Groups defined further down
350 </p>
351 <p class="description">
352 This field defaults to <strong>gidNumber</strong>
353 </p>
354 </td>
355 </tr>
356 <tr>
357 <th scope="row">
358 <label for="authLDAPGroupSeparator">Group-Separator</label>
359 </th>
360 <td>
361 <input type="text" name="authLDAPGroupSeparator" id="authLDAPGroupSeparator" placeholder=","
362 class="regular-text" value="<?php echo $authLDAPGroupSeparator; ?>" />
363 <p class="description">
364 This attribute defines the separator used for the Group-IDs listed in the
365 Groups defined further down. This is useful if the value of Group-Attribute
366 listed above can contain a comma (for example, when using the memberof attribute)
367 </p>
368 <p class="description">
369 This field defaults to <strong>, (comma)</strong>
370 </p>
371 </td>
372 </tr>
373 <tr>
374 <th scope="row">
375 <label for="authLDAPGroupFilter">Group-Filter</label>
376 </th>
377 <td>
378 <input type="text" name="authLDAPGroupFilter" id="authLDAPGroupFilter"
379 placeholder="(&amp;(objectClass=posixGroup)(memberUid=%s))"
380 class="regular-text" value="<?php echo $authLDAPGroupFilter; ?>" />
381 <p class="description">
382 Here you can add the filter for selecting groups for ther
383 currentlly logged in user
384 </p>
385 <p class="description">
386 The Filter should contain the string <code>%s</code> which will be replaced by
387 the login-name of the currently logged in user
388 </p>
389 <p class="description">
390 Alternatively the string <code>%dn%</code> will be replaced by the
391 DN of the currently logged in user. This can be helpfull if
392 group-memberships are defined with DNs rather than UIDs
393 </p>
394 <p class="description">This field defaults to
395 <strong>(&amp;(objectClass=posixGroup)(memberUid=%s))</strong>
396 </p>
397 </td>
398 </tr>
399 </table>
400 </fieldset>
401
402 <h3 class="title">Role - group mapping</h3>
403 <fieldset class="options">
404 <p class="description">You can set multiple values per role by separating them with a coma</p>
405 <p class="description">The values are empty by default</p>
406 <table class="form-table">
407 <thead>
408 <th scope="row">Assign this WordPress-Role</th>
409 <th style="width:auto;">to members of this/these LDAP-Groups</th>
410 </thead>
411 <tbody>
412 <?php
413 foreach ($roles->get_names() as $group => $vals) :
414 $aGroup=$authLDAPGroups[$group]; ?>
415 <tr>
416 <th scope="row" style="width:auto; min-width: 200px;">
417 <label for="authLDAPGroups[<?php echo $group; ?>]">
418 <?php echo $vals; ?>
419 </label>
420 </th>
421 <td>
422 <input type="text" name="authLDAPGroups[<?php echo $group; ?>]" id="authLDAPGroups[<?php echo $group; ?>]"
423 value="<?php echo $aGroup; ?>" />
424 </td>
425 </tr>
426 <?php endforeach; ?>
427 </tbody>
428 </table>
429 </fieldset>
430 </div>
431 <fieldset class="buttons">
432 <p class="submit">
433 <input type="submit" name="ldapOptionsSave" class="button button-primary" value="Save Changes" />
434 </p>
435 </fieldset>
436 </form>
437 </div>
438
439 <script type="text/javascript">
440 elem = document.getElementById('authLDAPGroupEnable');
441 if(! elem.checked) {
442 document.getElementById('authldaprolemapping').setAttribute('style', 'display:none;');
443 }
444
445 elem.addEventListener('change', function(e){
446 if(! e.target.checked) {
447 document.getElementById('authldaprolemapping').setAttribute('style', 'display:none;');
448 } else {
449 document.getElementById('authldaprolemapping').removeAttribute('style');
450 }
451 });
452
453 </script>
1 1.4.20
...\ No newline at end of file ...\ No newline at end of file
1 apiVersion: backstage.io/v1alpha1
2 kind: Component
3 metadata:
4 name: wp-auth-ldap
5 annotations:
6 github.com/project-slug: lampo/wp-auth-ldap
7 spec:
8 type: general
9 lifecycle: production
10 owner: B2C Developers
1 {
2 "name" : "lampo/wp-auth-ldap",
3 "type" : "wordpress-plugin",
4 "description": "Fork of http://github.com/heiglandreas/authLdap, moves settings to defined constants.",
5 "keywords": ["ldap","authenticate", "auth", "wordpress"],
6 "homepage": "http://github.com/lampo/wp-auth-ldap",
7 "license": "MIT",
8 "authors": [{
9 "name": "Andreas Heigl",
10 "email": "andreas@heigl.org",
11 "homepage": "http://andreas.heigl.org",
12 "role": "Developer"
13 },{
14 "name": "Micah Flatt",
15 "email": "mflatt@flattware.net",
16 "role": "Developer"
17 }],
18 "require" : {
19 "php": ">=5.4",
20 "composer/installers": "~1.0"
21 },
22 "autoload" : {
23 "psr-4" : {
24 "Org_Heigl\\AuthLdap\\" : "./"
25 }
26 }
27 }
1 <?php
2 /**
3 * $Id: ldap.php 381646 2011-05-06 09:37:31Z heiglandreas $
4 *
5 * authLdap - Authenticate Wordpress against an LDAP-Backend.
6 * Copyright (c) 2008 Andreas Heigl<andreas@heigl.org>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 *
22 * This file handles the basic LDAP-Tasks
23 *
24 * @author Andreas Heigl<andreas@heigl.org>
25 * @package authLdap
26 * @category authLdap
27 * @since 2008
28 */
29 namespace Org_Heigl\AuthLdap;
30
31 use Exception;
32
33 class LDAP
34 {
35 private $_server = '';
36
37 private $_scheme = 'ldap';
38
39 private $_port = 389;
40
41 private $_baseDn = '';
42
43 private $_debug = false;
44 /**
45 * This property contains the connection handle to the ldap-server
46 *
47 * @var Ressource
48 */
49 private $_ch = null;
50
51 private $_username = '';
52
53 private $_password = '';
54
55 private $_starttls = false;
56
57 public function __construct($URI, $debug = false, $starttls = false)
58 {
59 $this->_debug=$debug;
60 $array = parse_url($URI);
61 if (! is_array($array)) {
62 throw new Exception($URI . ' seems not to be a valid URI');
63 }
64 $url = array_map(function ($item) { return urldecode($item); }, $array);
65 if (false === $url) {
66 throw new Exception($URI . ' is an invalid URL');
67 }
68 if (! isset ( $url['scheme'] )) {
69 throw new Exception($URI . ' does not provide a scheme');
70 }
71 if (0 !== strpos($url['scheme'], 'ldap')) {
72 throw new Exception($URI . ' is an invalid LDAP-URI');
73 }
74 if (! isset ( $url['host'] )) {
75 throw new Exception($URI . ' does not provide a server');
76 }
77 if (! isset ( $url['path'] )) {
78 throw new Exception($URI . ' does not provide a search-base');
79 }
80 if (1 == strlen($url['path'])) {
81 throw new Exception($URI . ' does not provide a valid search-base');
82 }
83 $this -> _server = $url['host'];
84 $this -> _scheme = $url['scheme'];
85 $this -> _baseDn = substr($url['path'], 1);
86 if (isset ( $url['user'] )) {
87 $this -> _username = $url['user'];
88 }
89 if ('' == trim($this -> _username)) {
90 $this -> _username = 'anonymous';
91 }
92 if (isset ( $url['pass'] )) {
93 $this -> _password = $url['pass'];
94 }
95 if (isset ( $url['port'] )) {
96 $this -> _port = $url['port'];
97 }
98 $this->_starttls = $starttls;
99 }
100
101 /**
102 * Connect to the given LDAP-Server
103 *
104 * @return LDAP
105 * @throws AuthLdap_Exception
106 */
107 public function connect()
108 {
109 $this -> disconnect();
110 if ('ldaps' == $this->_scheme && 389 == $this->_port) {
111 $this->_port = 636;
112 }
113
114 $this->_ch = @ldap_connect($this->_scheme . '://' . $this->_server . ':' . $this -> _port);
115 if (! $this->_ch) {
116 throw new AuthLDAP_Exception('Could not connect to the server');
117 }
118 ldap_set_option($this->_ch, LDAP_OPT_PROTOCOL_VERSION, 3);
119 ldap_set_option($this->_ch, LDAP_OPT_REFERRALS, 0);
120 //if configured try to upgrade encryption to tls for ldap connections
121 if ($this->_starttls) {
122 ldap_start_tls($this->_ch);
123 }
124 return $this;
125 }
126
127 /**
128 * Disconnect from a resource if one is available
129 *
130 * @return LDAP
131 */
132 public function disconnect()
133 {
134 if (is_resource($this->_ch)) {
135 @ldap_unbind($this->_ch);
136 }
137 $this->_ch = null;
138 return $this;
139 }
140
141 /**
142 * Bind to an LDAP-Server with the given credentials
143 *
144 * @return LDAP
145 * @throw AuthLdap_Exception
146 */
147 public function bind()
148 {
149 if (! $this->_ch) {
150 $this->connect();
151 }
152 if (! is_resource($this->_ch)) {
153 throw new AuthLDAP_Exception('No Resource-handle given');
154 }
155 $bind = false;
156 if (( ( $this->_username )
157 && ( $this->_username != 'anonymous') )
158 && ( $this->_password != '' ) ) {
159 $bind = @ldap_bind($this->_ch, $this->_username, $this->_password);
160 } else {
161 $bind = @ldap_bind($this->_ch);
162 }
163 if (! $bind) {
164 throw new AuthLDAP_Exception('bind was not successfull: ' . ldap_error($this->_ch));
165 }
166 return $this;
167 }
168
169 public function getErrorNumber()
170 {
171 return @ldap_errno($this->_ch);
172 }
173
174 public function getErrorText()
175 {
176 return @ldap_error($this->_ch);
177 }
178
179 /**
180 * This method does the actual ldap-serch.
181 *
182 * This is using the filter <var>$filter</var> for retrieving the attributes
183 * <var>$attributes</var>
184 *
185 *
186 * @param string $filter
187 * @param array $attributes
188 * @return array
189 */
190 public function search($filter, $attributes = array('uid'))
191 {
192 if (! is_Resource($this->_ch)) {
193 throw new AuthLDAP_Exception('No resource handle avbailable');
194 }
195 $result = @ldap_search($this->_ch, $this->_baseDn, $filter, $attributes);
196 if ($result === false) {
197 throw new AuthLDAP_Exception('no result found');
198 }
199 $this->_info = @ldap_get_entries($this->_ch, $result);
200 if ($this->_info === false) {
201 throw new AuthLDAP_Exception('invalid results found');
202 }
203 return $this -> _info;
204 }
205
206 /**
207 * This method sets debugging to ON
208 */
209 public function debugOn()
210 {
211 $this->_debug = true;
212 return $this;
213 }
214
215 /**
216 * This method sets debugging to OFF
217 */
218 public function debugOff()
219 {
220 $this->_debug = false;
221 return $this;
222 }
223
224 /**
225 * This method authenticates the user <var>$username</var> using the
226 * password <var>$password</var>
227 *
228 * @param string $username
229 * @param string $password
230 * @param string $filter OPTIONAL This parameter defines the Filter to be used
231 * when searchin for the username. This MUST contain the string '%s' which
232 * will be replaced by the vaue given in <var>$username</var>
233 * @return boolean true or false depending on successfull authentication or not
234 */
235 public function authenticate($username, $password, $filter = '(uid=%s)')
236 {
237 //return true;
238 $this->connect();
239 $this->bind();
240 $res = $this->search(sprintf($filter, $username));
241 if (! $res || ! is_array($res) || ( $res ['count'] != 1 )) {
242 return false;
243 }
244 $dn = $res[0]['dn'];
245 if ($username && $password) {
246 if (@ldap_bind($this->_ch, $dn, $password)) {
247 return true;
248 }
249 }
250 return false;
251 }
252 /**
253 * $this method loggs errors if debugging is set to ON
254 */
255 public function logError()
256 {
257 if ($this->_debug) {
258 $_v = debug_backtrace();
259 throw new AuthLDAP_Exception('[LDAP_ERROR]' . ldap_errno($this->_ch) . ':' . ldap_error($this->_ch), $_v[0]['line']);
260 }
261 }
262 }
263
264 class AuthLDAP_Exception extends Exception
265 {
266 public function __construct($message, $line = null)
267 {
268 parent :: __construct($message);
269 if ($line) {
270 $this -> line = $line;
271 }
272 }
273 }
1 <?php
2 /*
3 Plugin Name: Lampo-AuthLDAP
4 Plugin URI: https://github.com/lampo/wp-auth-ldap
5 Description: This plugin allows you to use your existing LDAP as authentication base for WordPress
6 Version: 1.4.20
7 Author: Andreas Heigl <a.heigl@wdv.de>
8 Author URI: http://andreas.heigl.org
9 */
10
11 require_once dirname(__FILE__) . '/ldap.php';
12
13 function authLdap_debug($message)
14 {
15 if (authLdap_get_option('Debug')) {
16 error_log('[AuthLDAP] ' . $message, 0);
17 }
18 }
19
20
21 function authLdap_get_post($name, $default = '')
22 {
23 return isset($_POST[$name]) ? $_POST[$name] : $default;
24 }
25
26
27 /**
28 * get a LDAP server object
29 *
30 * throws exception if there is a problem connecting
31 *
32 * @conf boolean authLDAPDebug true, if debugging should be turned on
33 * @conf string authLDAPURI LDAP server URI
34 *
35 * @return LDAP LDAP server object
36 */
37 function authLdap_get_server()
38 {
39 static $_ldapserver = null;
40 if (is_null($_ldapserver)) {
41 $authLDAPDebug = authLdap_get_option('Debug');
42 $authLDAPURI = explode(
43 authLdap_get_option('URISeparator', ' '),
44 authLdap_get_option('URI')
45 );
46 $authLDAPStartTLS = authLdap_get_option('StartTLS');
47
48 //$authLDAPURI = 'ldap:/foo:bar@server/trallala';
49 authLdap_debug('connect to LDAP server');
50 require_once dirname(__FILE__) . '/src/LdapList.php';
51 $_ldapserver = new \Org_Heigl\AuthLdap\LdapList();
52 foreach ($authLDAPURI as $uri) {
53 $_ldapserver->addLdap(new \Org_Heigl\AuthLdap\LDAP($uri, $authLDAPDebug, $authLDAPStartTLS));
54 }
55 }
56 return $_ldapserver;
57 }
58
59
60 /**
61 * This method authenticates a user using either the LDAP or, if LDAP is not
62 * available, the local database
63 *
64 * For this we store the hashed passwords in the WP_Database to ensure working
65 * conditions even without an LDAP-Connection
66 *
67 * @param null|WP_User|WP_Error
68 * @param string $username
69 * @param string $password
70 * @param boolean $already_md5
71 * @return boolean true, if login was successfull or false, if it wasn't
72 * @conf boolean authLDAP true, if authLDAP should be used, false if not. Defaults to false
73 * @conf string authLDAPFilter LDAP filter to use to find correct user, defaults to '(uid=%s)'
74 * @conf string authLDAPNameAttr LDAP attribute containing user (display) name, defaults to 'name'
75 * @conf string authLDAPSecName LDAP attribute containing second name, defaults to ''
76 * @conf string authLDAPMailAttr LDAP attribute containing user e-mail, defaults to 'mail'
77 * @conf string authLDAPUidAttr LDAP attribute containing user id (the username we log on with), defaults to 'uid'
78 * @conf string authLDAPWebAttr LDAP attribute containing user website, defaults to ''
79 * @conf string authLDAPDefaultRole default role for authenticated user, defaults to ''
80 * @conf boolean authLDAPGroupEnable true, if we try to map LDAP groups to Wordpress roles
81 * @conf boolean authLDAPGroupOverUser true, if LDAP Groups have precedence over existing user roles
82 */
83 function authLdap_login($user, $username, $password, $already_md5 = false)
84 {
85 // don't do anything when authLDAP is disabled
86 if (! authLdap_get_option('Enabled')) {
87 authLdap_debug('LDAP disabled in AuthLDAP plugin options (use the first option in the AuthLDAP options to enable it)');
88 return $user;
89 }
90
91 // If the user has already been authenticated (only in that case we get a
92 // WP_User-Object as $user) we skip LDAP-authentication and simply return
93 // the existing user-object
94 if ($user instanceof WP_User) {
95 authLdap_debug(sprintf(
96 'User %s has already been authenticated - skipping LDAP-Authentication',
97 $user->get('nickname')));
98 return $user;
99 }
100
101 authLdap_debug("User '$username' logging in");
102
103 if ($username == 'admin') {
104 authLdap_debug('Doing nothing for possible local user admin');
105 return $user;
106 }
107
108 global $wpdb, $error;
109 try {
110 $authLDAP = authLdap_get_option('Enabled');
111 $authLDAPFilter = authLdap_get_option('Filter');
112 $authLDAPNameAttr = authLdap_get_option('NameAttr');
113 $authLDAPSecName = authLdap_get_option('SecName');
114 $authLDAPMailAttr = authLdap_get_option('MailAttr');
115 $authLDAPUidAttr = authLdap_get_option('UidAttr');
116 $authLDAPWebAttr = authLdap_get_option('WebAttr');
117 $authLDAPDefaultRole = authLdap_get_option('DefaultRole');
118 $authLDAPGroupEnable = authLdap_get_option('GroupEnable');
119 $authLDAPGroupOverUser = authLdap_get_option('GroupOverUser');
120
121 if (! $username) {
122 authLdap_debug('Username not supplied: return false');
123 return false;
124 }
125
126 if (! $password) {
127 authLdap_debug('Password not supplied: return false');
128 $error = __('<strong>Error</strong>: The password field is empty.');
129 return false;
130 }
131 // First check for valid values and set appropriate defaults
132 if (! $authLDAPFilter) {
133 $authLDAPFilter = '(uid=%s)';
134 }
135 if (! $authLDAPNameAttr) {
136 $authLDAPNameAttr = 'name';
137 }
138 if (! $authLDAPMailAttr) {
139 $authLDAPMailAttr = 'mail';
140 }
141 if (! $authLDAPUidAttr) {
142 $authLDAPUidAttr = 'uid';
143 }
144
145 // If already_md5 is TRUE, then we're getting the user/password from the cookie. As we don't want to store LDAP passwords in any
146 // form, we've already replaced the password with the hashed username and LDAP_COOKIE_MARKER
147 if ($already_md5) {
148 if ($password == md5($username).md5($ldapCookieMarker)) {
149 authLdap_debug('cookie authentication');
150 return true;
151 }
152 }
153
154 // No cookie, so have to authenticate them via LDAP
155 $result = false;
156 try {
157 authLdap_debug('about to do LDAP authentication');
158 $result = authLdap_get_server()->Authenticate($username, $password, $authLDAPFilter);
159 } catch (Exception $e) {
160 authLdap_debug('LDAP authentication failed with exception: ' . $e->getMessage());
161 return false;
162 }
163
164 // Rebind with the default credentials after the user has been loged in
165 // Otherwise the credentials of the user trying to login will be used
166 // This fixes #55
167 authLdap_get_server()->bind();
168
169 if (true !== $result) {
170 authLdap_debug('LDAP authentication failed');
171 // TODO what to return? WP_User object, true, false, even an WP_Error object... all seem to fall back to normal wp user authentication
172 return;
173 }
174
175 authLdap_debug('LDAP authentication successfull');
176 $attributes = array_values(
177 array_filter(
178 array(
179 $authLDAPNameAttr,
180 $authLDAPSecName,
181 $authLDAPMailAttr,
182 $authLDAPWebAttr,
183 $authLDAPUidAttr
184 )
185 )
186 );
187
188 try {
189 $attribs = authLdap_get_server()->search(
190 sprintf($authLDAPFilter, $username),
191 $attributes
192 );
193 // First get all the relevant group informations so we can see if
194 // whether have been changes in group association of the user
195 if (! isset($attribs[0]['dn'])) {
196 authLdap_debug('could not get user attributes from LDAP');
197 throw new UnexpectedValueException('dn has not been returned');
198 }
199 if (! isset($attribs[0][strtolower($authLDAPUidAttr)][0])) {
200 authLdap_debug('could not get user attributes from LDAP');
201 throw new UnexpectedValueException('The user-ID attribute has not been returned');
202
203 }
204
205 $dn = $attribs[0]['dn'];
206 $realuid = $attribs[0][strtolower($authLDAPUidAttr)][0];
207 } catch (Exception $e) {
208 authLdap_debug('Exception getting LDAP user: ' . $e->getMessage());
209 return false;
210 }
211
212 $uid = authLdap_get_uid($realuid);
213 $role = '';
214
215 // we only need this if either LDAP groups are disabled or
216 // if the WordPress role of the user overrides LDAP groups
217 if (!$authLDAPGroupEnable || !$authLDAPGroupOverUser) {
218 $role = authLdap_user_role($uid);
219 }
220
221 // do LDAP group mapping if needed
222 // (if LDAP groups override worpress user role, $role is still empty)
223 if (empty($role) && $authLDAPGroupEnable) {
224 $role = authLdap_groupmap($realuid, $dn);
225 authLdap_debug('role from group mapping: ' . $role);
226 }
227
228 // if we don't have a role yet, use default role
229 if (empty($role) && !empty($authLDAPDefaultRole)) {
230 authLdap_debug('no role yet, set default role');
231 $role = $authLDAPDefaultRole;
232 }
233
234 if (empty($role)) {
235 // Sorry, but you are not in any group that is allowed access
236 trigger_error('no group found');
237 authLdap_debug('user is not in any group that is allowed access');
238 return false;
239 } else {
240 $roles = new WP_Roles();
241 // not sure if this is needed, but it can't hurt
242 if (!$roles->is_role($role)) {
243 trigger_error('no group found');
244 authLdap_debug('role is invalid');
245 return false;
246 }
247 }
248
249 // from here on, the user has access!
250 // now, lets update some user details
251 $user_info = array();
252 $user_info['user_login'] = $realuid;
253 $user_info['role'] = $role;
254 $user_info['user_email'] = '';
255
256 // first name
257 if (isset($attribs[0][strtolower($authLDAPNameAttr)][0])) {
258 $user_info['first_name'] = $attribs[0][strtolower($authLDAPNameAttr)][0];
259 }
260
261 // last name
262 if (isset($attribs[0][strtolower($authLDAPSecName)][0])) {
263 $user_info['last_name'] = $attribs[0][strtolower($authLDAPSecName)][0];
264 }
265
266 // mail address
267 if (isset($attribs[0][strtolower($authLDAPMailAttr)][0])) {
268 $user_info['user_email'] = $attribs[0][strtolower($authLDAPMailAttr)][0];
269 }
270
271 // website
272 if (isset($attribs[0][strtolower($authLDAPWebAttr)][0])) {
273 $user_info['user_url'] = $attribs[0][strtolower($authLDAPWebAttr)][0];
274 }
275
276 // display name, nickname, nicename
277 if (array_key_exists('first_name', $user_info)) {
278 $user_info['display_name'] = $user_info['first_name'];
279 $user_info['nickname'] = $user_info['first_name'];
280 $user_info['user_nicename'] = sanitize_title_with_dashes($user_info['first_name']);
281 if (array_key_exists('last_name', $user_info)) {
282 $user_info['display_name'] .= ' ' . $user_info['last_name'];
283 $user_info['nickname'] .= ' ' . $user_info['last_name'];
284 $user_info['user_nicename'] .= '_' . sanitize_title_with_dashes($user_info['last_name']);
285 }
286 }
287
288 // optionally store the password into the wordpress database
289 if (authLdap_get_option('CachePW')) {
290 // Password will be hashed inside wp_update_user or wp_insert_user
291 $user_info['user_pass'] = $password;
292 } else {
293 // clear the password
294 $user_info['user_pass'] = '';
295 }
296
297 // add uid if user exists
298 if ($uid) {
299 // found user in the database
300 authLdap_debug('The LDAP user has an entry in the WP-Database');
301 $user_info['ID'] = $uid;
302 unset ($user_info['display_name'], $user_info['nickname']);
303 $userid = wp_update_user($user_info);
304 } else {
305 // new wordpress account will be created
306 authLdap_debug('The LDAP user does not have an entry in the WP-Database, a new WP account will be created');
307
308 $userid = wp_insert_user($user_info);
309 }
310
311 // if the user exists, wp_insert_user will update the existing user record
312 if (is_wp_error($userid)) {
313 authLdap_debug('Error creating user : ' . $userid->get_error_message());
314 trigger_error('Error creating user: ' . $userid->get_error_message());
315 return $userid;
316 }
317
318 authLdap_debug('user id = ' . $userid);
319
320 // flag the user as an ldap user so we can hide the password fields in the user profile
321 update_user_meta($userid, 'authLDAP', true);
322
323 // return a user object upon positive authorization
324 return new WP_User($userid);
325 } catch (Exception $e) {
326 authLdap_debug($e->getMessage() . '. Exception thrown in line ' . $e->getLine());
327 trigger_error($e->getMessage() . '. Exception thrown in line ' . $e->getLine());
328 }
329 }
330
331 /**
332 * Get user's user id
333 *
334 * Returns null if username not found
335 *
336 * @param string $username username
337 * @param string user id, null if not found
338 */
339 function authLdap_get_uid($username)
340 {
341 global $wpdb;
342
343 // find out whether the user is already present in the database
344 $uid = $wpdb->get_var(
345 $wpdb->prepare(
346 "SELECT ID FROM {$wpdb->users} WHERE user_login = %s",
347 $username
348 )
349 );
350 if ($uid) {
351 authLdap_debug("Existing user, uid = {$uid}");
352 return $uid;
353 } else {
354 return null;
355 }
356 }
357
358 /**
359 * Get the user's current role
360 *
361 * Returns empty string if not found.
362 *
363 * @param int $uid wordpress user id
364 * @return string role, empty if none found
365 */
366 function authLdap_user_role($uid)
367 {
368 global $wpdb;
369
370 if (!$uid) {
371 return '';
372 }
373
374 $meta_value = $wpdb->get_var("SELECT meta_value FROM {$wpdb->usermeta} WHERE meta_key = '{$wpdb->prefix}capabilities' AND user_id = {$uid}");
375
376 if (!$meta_value) {
377 return '';
378 }
379
380 $capabilities = unserialize($meta_value);
381 $roles = is_array($capabilities) ? array_keys($capabilities) : array('');
382 $role = $roles[0];
383
384 authLdap_debug("Existing user's role: {$role}");
385 return $role;
386 }
387
388 /**
389 * Get LDAP groups for user and map to role
390 *
391 * @param string $username
392 * @param string $dn
393 * @return string role, empty string if no mapping found, first found role otherwise
394 * @conf array authLDAPGroups, associative array, role => ldap_group
395 * @conf string authLDAPGroupAttr, ldap attribute that holds name of group
396 * @conf string authLDAPGroupFilter, LDAP filter to find groups. can contain %s and %dn% placeholders
397 */
398 function authLdap_groupmap($username, $dn)
399 {
400 $authLDAPGroups = authLdap_sort_roles_by_capabilities(
401 authLdap_get_option('Groups')
402 );
403 $authLDAPGroupAttr = authLdap_get_option('GroupAttr');
404 $authLDAPGroupFilter = authLdap_get_option('GroupFilter');
405 $authLDAPGroupSeparator = authLdap_get_option('GroupSeparator');
406 if (! $authLDAPGroupAttr) {
407 $authLDAPGroupAttr = 'gidNumber';
408 }
409 if (! $authLDAPGroupFilter) {
410 $authLDAPGroupFilter = '(&(objectClass=posixGroup)(memberUid=%s))';
411 }
412 if (! $authLDAPGroupSeparator) {
413 $authLDAPGroupSeparator = ',';
414 }
415
416 if (!is_array($authLDAPGroups) || count(array_filter(array_values($authLDAPGroups))) == 0) {
417 authLdap_debug('No group names defined');
418 return '';
419 }
420
421 try {
422 // To allow searches based on the DN instead of the uid, we replace the
423 // string %dn% with the users DN.
424 $authLDAPGroupFilter = str_replace('%dn%', $dn, $authLDAPGroupFilter);
425 authLdap_debug('Group Filter: ' . json_encode($authLDAPGroupFilter));
426 $groups = authLdap_get_server()->search(sprintf($authLDAPGroupFilter, $username), array($authLDAPGroupAttr));
427 } catch (Exception $e) {
428 authLdap_debug('Exception getting LDAP group attributes: ' . $e->getMessage());
429 return '';
430 }
431
432 $grp = array();
433 for ($i = 0; $i < $groups ['count']; $i++) {
434 for ($k = 0; $k < $groups[$i][strtolower($authLDAPGroupAttr)]['count']; $k++) {
435 $grp[] = $groups[$i][strtolower($authLDAPGroupAttr)][$k];
436 }
437 }
438
439 authLdap_debug('LDAP groups: ' . json_encode($grp));
440
441 // Check whether the user is member of one of the groups that are
442 // allowed acces to the blog. If the user is not member of one of
443 // The groups throw her out! ;-)
444 // If the user is member of more than one group only the first one
445 // will be taken into account!
446
447 $role = '';
448 foreach ($authLDAPGroups as $key => $val) {
449 $currentGroup = explode($authLDAPGroupSeparator, $val);
450 // Remove whitespaces around the group-ID
451 $currentGroup = array_map('trim', $currentGroup);
452 if (0 < count(array_intersect($currentGroup, $grp))) {
453 $role = $key;
454 break;
455 }
456 }
457
458 authLdap_debug("Role from LDAP group: {$role}");
459 return $role;
460 }
461
462 /**
463 * This function disables the password-change fields in the users preferences.
464 *
465 * It does not make sense to authenticate via LDAP and then allow the user to
466 * change the password only in the wordpress database. And changing the password
467 * LDAP-wide can not be the scope of Wordpress!
468 *
469 * Whether the user is an LDAP-User or not is determined using the authLDAP-Flag
470 * of the users meta-informations
471 *
472 * @return false, if the user whose prefs are viewed is an LDAP-User, true if
473 * he isn't
474 * @conf boolean authLDAP
475 */
476 function authLdap_show_password_fields($return, $user)
477 {
478 if (! $user) {
479 return true;
480 }
481
482 if (get_user_meta($user->ID, 'authLDAP')) {
483 return false;
484 }
485
486 return $return;
487 }
488
489 /**
490 * This function disables the password reset for a user.
491 *
492 * It does not make sense to authenticate via LDAP and then allow the user to
493 * reset the password only in the wordpress database. And changing the password
494 * LDAP-wide can not be the scope of Wordpress!
495 *
496 * Whether the user is an LDAP-User or not is determined using the authLDAP-Flag
497 * of the users meta-informations
498 *
499 * @author chaplina (https://github.com/chaplina)
500 * @conf boolean authLDAP
501 * @return false, if the user is an LDAP-User, true if he isn't
502 */
503 function authLdap_allow_password_reset($return, $userid)
504 {
505 if (!(isset($userid))) {
506 return true;
507 }
508
509 if (get_user_meta($userid, 'authLDAP')) {
510 return false;
511 }
512 return $return;
513 }
514
515 /**
516 * Sort the given roles by number of capabilities
517 *
518 * @param array $roles
519 *
520 * @return array
521 */
522 function authLdap_sort_roles_by_capabilities($roles)
523 {
524 global $wpdb;
525 $myRoles = get_option($wpdb->get_blog_prefix() . 'user_roles');
526
527 authLdap_debug(print_r($roles, true));
528 uasort($myRoles, 'authLdap_sortByCapabilitycount');
529
530 $return = array();
531
532 foreach ($myRoles as $key => $role) {
533 if (isset($roles[$key])) {
534 $return[$key] = $roles[$key];
535 }
536 }
537
538 authLdap_debug(print_r($return, true));
539 return $return;
540 }
541
542 /**
543 * Sort according to the number of capabilities
544 *
545 * @param $a
546 * @param $b
547 */
548 function authLdap_sortByCapabilitycount($a, $b)
549 {
550 if (count($a['capabilities']) > count($b['capabilities'])) {
551 return -1;
552 }
553 if (count($a['capabilities']) < count($b['capabilities'])) {
554 return 1;
555 }
556
557 return 0;
558 }
559
560 /**
561 * Load AuthLDAP Options
562 *
563 * Sets and stores defaults if options are not up to date
564 */
565 function authLdap_load_options($reload = false)
566 {
567 static $options = null;
568
569 if( empty($options) || $reload){
570 // the current version for options
571 $option_version_plugin = 1;
572 // defaults for all options
573 $options = array(
574 'Enabled' => false,
575 'CachePW' => false,
576 'URI' => '',
577 'URISeparator' => ' ',
578 'Filter' => '', // '(uid=%s)'
579 'NameAttr' => '', // 'name'
580 'SecName' => '',
581 'UidAttr' => '', // 'uid'
582 'MailAttr' => '', // 'mail'
583 'WebAttr' => '',
584 'Groups' => array(),
585 'Debug' => false,
586 'GroupAttr' => '', // 'gidNumber'
587 'GroupFilter' => '', // '(&(objectClass=posixGroup)(memberUid=%s))'
588 'DefaultRole' => '',
589 'GroupEnable' => true,
590 'GroupOverUser' => true,
591 'Version' => $option_version_plugin,
592 );
593
594 // 2016-12-23 MLF (why we forked) Set any options we can find via defined constants
595 foreach($options as $option_name => $option_value) {
596 $constant_name = 'AUTHLDAP_'.strtoupper($option_name);
597 if( defined($constant_name) ){
598 $options[$option_name] = constant($constant_name);
599 }
600 }
601 }
602
603 return $options;
604 }
605
606 /**
607 * Get an individual option
608 */
609 function authLdap_get_option($optionname, $default = null)
610 {
611 $options = authLdap_load_options();
612 if (isset($options[$optionname]) && $options[$optionname]) {
613 return $options[$optionname];
614 }
615
616 if (null !== $default) {
617 return $default;
618 }
619
620 //authLdap_debug('option name invalid: ' . $optionname);
621 return null;
622 }
623
624
625 /**
626 * Do not send an email after changing the password or the email of the user!
627 *
628 * @param boolean $result The initial resturn value
629 * @param array $user The old userdata
630 * @param array $newUserData The changed userdata
631 *
632 * @return bool
633 */
634 function authLdap_send_change_email($result, $user, $newUserData)
635 {
636 if (get_usermeta($user['ID'], 'authLDAP')) {
637 return false;
638 }
639
640 return $result;
641 }
642
643 add_filter('show_password_fields', 'authLdap_show_password_fields', 10, 2);
644 add_filter('allow_password_reset', 'authLdap_allow_password_reset', 10, 2);
645 add_filter('authenticate', 'authLdap_login', 10, 3);
646 /** This only works from WP 4.3.0 on */
647 add_filter('send_password_change_email', 'authLdap_send_change_email', 10, 3);
648 add_filter('send_email_change_email', 'authLdap_send_change_email', 10, 3);