55e5d640 by Jeff Balicki

wp-auth-ldap

Signed-off-by: Jeff <jeff@gotenzing.com>
1 parent f3f154e9
1 vendor
2 composer.lock
3 .svnAccess
...\ No newline at end of file ...\ No newline at end of file
1 # authLDAP
2
3 [![Join the chat at https://gitter.im/heiglandreas/authLdap](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/heiglandreas/authLdap?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4
5 Use your existing LDAP as authentication-backend for your wordpress!
6
7 [![Build Status](https://travis-ci.org/heiglandreas/authLdap.svg?branch=master)](https://travis-ci.org/heiglandreas/authLdap)
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/)
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)
12 [![Test Coverage](https://codeclimate.com/github/heiglandreas/authLdap/badges/coverage.svg)](https://codeclimate.com/github/heiglandreas/authLdap)
13
14 So what are the differences to other Wordpress-LDAP-Authentication-Plugins?
15
16 * **Flexible**: You are totaly free in which LDAP-backend to use. Due to the extensive configuration you can
17 freely decide how to do the authentication of your users. It simply depends on your
18 filters
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.
24
25 ## How does the plugin work?
26
27 Well, as a matter of fact it is rather simple. The plugin verifies, that the user
28 seeking authentification can bind to the LDAP using the provided password.
29
30 If that is so, the user is either created or updated in the wordpress-user-database.
31 This update includes the provided password (so the wordpress can authenticate users
32 even without the LDAP), the users name according to the authLDAP-preferences and
33 the status of the user depending on the groups-settings of the authLDAP-preferences
34
35 Writing this plugin would not have been as easy as it has been, without the
36 wonderfull plugin of Alistair Young from http://www.weblogs.uhi.ac.uk/sm00ay/?p=45
37
38 ## Configuration
39
40 ### Usage Settings
41
42 * **Enable Authentication via LDAP** Whether you want to enable authLdap for login or not
43 * **debug authLdap** When you have problems with authentication via LDAP you can enable a debugging mode here.
44 * **Save entered Password** Decide whether passwords will be cached in your wordpress-installation. **Attention:** Without the cache your users will not be able to log into your site when your LDAP is down!
45
46 ### Server Settings
47
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:
50
51 * **uid=%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’
53
54 This filter is rather powerfull if used wisely.
55
56 ### Creating Users
57
58 * **Name-Attribute** Which Attribute from the LDAP contains the Full or the First name of the user trying to log in. This defaults to name
59 * **Second Name Attribute** If the above Name-Attribute only contains the First Name of the user you can here specify an Attribute that contains the second name. This field is empty by default
60 * **User-ID Attribute** This field will be used as login-name for wordpress. Please give the Attribute, that is used to identify the user. This should be the same as you used in the above Filter-Option. This field defaults to uid
61 * **Mail Attribute** Which Attribute holds the eMail-Address of the user? If more than one eMail-Address are stored in the LDAP, only the first given is used. This field defaults to mail
62 * **Web-Attribute** If your users have a personal page (URI) stored in the LDAP, it can be provided here. This field is empty by default
63
64 ### User-Groups for Roles
65
66 * **Group-Attribute** This is the attribute that defines the Group-ID that can be matched against the Groups defined further down This field defaults to gidNumber.
67 * **Group-Filter** Here you can add the filter for selecting groups for the currentlly logged in user The Filter should contain the string %s which will be replaced by the login-name of the currently logged in
68
69
70 ## FAQ
71
72 <dl>
73 <dt>Can I change a users password with this plugin?</dt>
74 <dd>Short Answer: <strong>No</strong>!<br>Long Answer: As the users credentials are not
75 only used for a wordpress-site when you authenticate against an LDAP but for
76 many other services also chances are great that there is a centralized place
77 where password-changes shall be made. We'll later allow inclusion of a link
78 to such a place but currently it's not available. And as password-hashing and
79 where to store it requires deeper insight into the LDAP-Server then most users
80 have and admins are willing to give, password changes are out of scope of this
81 plugin. If you know exactyl what you do, you might want to have a look at
82 <a href="https://github.com/heiglandreas/authLdap/issues/54#issuecomment-125851029">
83 issue 54</a>
84 wherer a way of adding it is described!
85 </dd>
86 <dt>Can I add a user to the LDAP when she creates a user-account on wordpress?</dt>
87 <dd>Short Answer: <strong>No</strong>!<br>Long Answer: Even though that is technically possible
88 it's not in the scope of this plugin. As creating a user in an LDAP often involves
89 an administrative process that has already been implemented in your departments
90 administration it doesn't make sense to rebuild that - in most cases highly
91 individual - process in this plugin. If you know exactly what you do, have a look at
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.
94 </dd>
95 </dl>
...\ No newline at end of file ...\ No newline at end of file
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 === authLdap ===
2 Contributors: heiglandreas
3 Tags: ldap, auth
4 Requires at least: 2.5.0
5 Tested up to: 4.6.1
6 Stable tag: trunk
7
8 Use your existing LDAP flexible as authentication backend for WordPress
9
10 == Description ==
11
12 Use your existing LDAP as authentication-backend for your wordpress!
13
14 So what are the differences to other Wordpress-LDAP-Authentication-Plugins?
15
16 * Flexible: You are totaly free in which LDAP-backend to use. Due to the extensive configuration you can
17 freely decide how to do the authentication of your users. It simply depends on your
18 filters
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.
24
25 For more Information on the configuration have a look at https://github.com/heiglandreas/authLdap
26
27 == Installation ==
28
29 1. Upload the extracted folder `authLdap` to the `/wp-content/plugins/` directory
30 2. Activate the plugin through the 'Plugins' menu in WordPress
31 3. Configure the Plugin via the 'authLdap'-Configuration-Page.
32
33 == Frequently Asked Questions ==
34
35 = Where can I find more Informations about the plugin? =
36
37 Go to https://github.com/heiglandreas/authLdap
38
39 = Where can I report issues with the plugin? =
40
41 Please use the issuetracker at https://github.com/heiglandreas/authLdap/issues
42
43 == Changelog ==
44 = 1.4.20 =
45 * 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)
47
48 = 1.4.19 =
49 * Adds support for TLS
50
51 = 1.4.14 =
52 * Update to showing password-fields check (thanks to @chaplina)
53
54 = 1.4.13 =
55 * Removed generation of default email-address (thanks to @henryk)
56 * Fixes password-hashing when caching passwords (thanks to @litinoveweedle)
57 * Removes the possibility to reset a password for LDAP-based users (thanks to @chaplina)
58 * Removes the password-change-Email from 4.3 on (thanks to @litinoveweedle)
59 * Fixes double authentication-attempt (that resulted in failed authentication) (thanks to @litinoveweedle)
60
61 = 1.4.10 =
62 * Cleanup by removing deprecated code
63 * Fixes issues with undefined variables
64 * Enables internal option-versioning
65 * Setting users nickname initially to the realname instead of the uid
66 * Fixes display of password-change possibility in users profile-page
67 = 1.4.9 =
68 * Fixed an issue with changing display name on every login
69 * Use proper way of looking up user-roles in setups w/o DB-prefix
70 = 1.4.8 =
71 * Updated version string
72 = 1.4.7 =
73 * Use default user to retrieve group menberships and not logging in user.
74 * return the UID from the LDAP instead of the value given by the user
75 * remove unnecessary checkbox
76 * Adds a testsuite
77 * Fixes PSR2 violations
78
79 […]
80
81 = 1.2.1 =
82 * Fixed an issue with group-ids
83 * Moved the code to GitHub (https://github.com/heiglandreas/authLdap)
84 = 1.1.0 =
85 * Changed the login-process. Now users that are not allowed to login due to
86 missing group-memberships are not created within your blog as was the standard
87 until Version 1.0.3 - Thanks to alex@tayts.com
88 * Changed the default mail-address that is created when no mail-address can be
89 retrieved from the LDAP from me@example.com to $username@example.com so that
90 a new user can be created even though the mail address already exists in your
91 blog - Also thanks to alex@tayts.com
92 * Added support for WordPress-Table-prefixes as the capabilities of a user
93 are interlany stored in a field that is named "$tablePrefix_capabilities" -
94 again thanks to alex@tayts.com and also to sim0n of silicium.mine.nu
1 <?php
2 /**
3 * Copyright (c) Andreas Heigl<andreas@heigl.org>
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 * The above copyright notice and this permission notice shall be included in
11 * all copies or substantial portions of the Software.
12 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
18 * THE SOFTWARE.
19 *
20 * @author Andreas Heigl<andreas@heigl.org>
21 * @copyright Andreas Heigl
22 * @license http://www.opensource.org/licenses/mit-license.php MIT-License
23 * @since 07.07.2016
24 * @link http://github.com/heiglandreas/authLDAP
25 */
26
27 namespace Org_Heigl\AuthLdap;
28
29 class LdapList
30 {
31 /**
32 * @var \LDAP[]
33 */
34 protected $items = [];
35
36 public function addLdap(LDAP $ldap)
37 {
38 $this->items[] = $ldap;
39 }
40
41 public function authenticate($username, $password, $filter = '(uid=%s)')
42 {
43 foreach ($this->items as $key => $item) {
44 if (! $item->authenticate($username, $password, $filter)) {
45 unset ($this->items[$key]);
46 continue;
47 }
48 return true;
49 }
50
51 return false;
52 }
53
54 public function bind()
55 {
56 $allFailed = true;
57 foreach ($this->items as $key => $item) {
58 try {
59 $item->bind();
60 } catch (\Exception $e) {
61 unset($this->items[$key]);
62 continue;
63 }
64 $allFailed = false;
65 }
66
67 if ($allFailed) {
68 throw new AuthLDAP_Exception('No bind successfull');
69 }
70 }
71
72 public function search($filter, $attributes = array('uid'))
73 {
74 foreach ($this->items as $item) {
75 try {
76 $result = $item->search($filter, $attributes);
77 return $result;
78 } catch (Exception $e) {
79 throw $e;
80 }
81 }
82
83 throw new \AuthLDAP_Exception('No Results found');
84 }
85 }
...\ No newline at end of file ...\ No newline at end of file
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);