View | Details | Raw Unified | Return to bug 2960
Collapse All | Expand All

(-) (+389 lines)
Added Link Here 
1
<?php
2
3
/**
4
 * @file LDAPAuthPlugin.inc.php
5
 *
6
 * Copyright (c) 2000-2008 John Willinsky
7
 * Distributed under the GNU GPL v2. For full terms see the file docs/COPYING.
8
 *
9
 * @class LDAPAuthPlugin
10
 * @ingroup plugins_auth_ldap
11
 *
12
 * @brief LDAP authentication plugin.
13
 */
14
15
// $Id: LDAPAuthPlugin.inc.php,v 1.15 2008/07/01 01:16:12 asmecher Exp $
16
17
18
import('classes.plugins.AuthPlugin');
19
20
class LDAPAuthPlugin extends AuthPlugin {
21
	/**
22
	 * Called as a plugin is registered to the registry
23
	 * @param $category String Name of category plugin was registered to
24
	 * @return boolean True iff plugin initialized successfully; if false,
25
	 * 	the plugin will not be registered.
26
	 */
27
	function register($category, $path) {
28
		$success = parent::register($category, $path);
29
		$this->addLocaleData();
30
		return $success;
31
	}
32
33
	// LDAP-specific configuration settings:
34
	// - hostname
35
	// - port
36
	// - basedn
37
	// - managerdn
38
	// - managerpwd
39
	// - pwhash
40
	// - SASL: sasl, saslmech, saslrealm, saslauthzid, saslprop
41
42
	/** @var $conn resource the LDAP connection */
43
	var $conn;
44
45
	/**
46
	 * Return the name of this plugin.
47
	 * @return string
48
	 */
49
	function getName() {
50
		return 'ldap';
51
	}
52
53
	/**
54
	 * Return the localized name of this plugin.
55
	 * @return string
56
	 */
57
	function getDisplayName() {
58
		return Locale::translate('plugins.auth.ldap.displayName');
59
	}
60
61
	/**
62
	 * Return the localized description of this plugin.
63
	 * @return string
64
	 */
65
	function getDescription() {
66
		return Locale::translate('plugins.auth.ldap.description');
67
	}
68
69
70
	//
71
	// Core Plugin Functions
72
	// (Must be implemented by every authentication plugin)
73
	//
74
75
	/**
76
	 * Returns an instance of the authentication plugin
77
	 * @param $settings array settings specific to this instance.
78
	 * @param $authId int identifier for this instance
79
	 * @return LDAPuthPlugin
80
	 */
81
	function &getInstance($settings, $authId) {
82
		$returner =& new LDAPAuthPlugin($settings, $authId);
83
		return $returner;
84
	}
85
86
	/**
87
	 * Authenticate a username and password.
88
	 * @param $username string
89
	 * @param $password string
90
	 * @return boolean true if authentication is successful
91
	 */
92
	function authenticate($username, $password) {
93
		$valid = false;
94
		if ($this->open()) {
95
			if ($entry = $this->getUserEntry($username)) {
96
				$userdn = ldap_get_dn($this->conn, $entry);
97
				if ($this->bind($userdn, $password)) {
98
					$valid = true;
99
				}
100
			}
101
			$this->close();
102
		}
103
		return $valid;
104
	}
105
106
107
	//
108
	// Optional Plugin Functions
109
	//
110
111
	/**
112
	 * Check if a username exists.
113
	 * @param $username string
114
	 * @return boolean
115
	 */
116
	function userExists($username) {
117
		$exists = true;
118
		if ($this->open()) {
119
			if ($this->bind()) {
120
				$result = ldap_search($this->conn, $this->settings['basedn'], $this->settings['uid'] . '=' . $username);
121
				$exists = (ldap_count_entries($this->conn, $result) != 0);
122
			}
123
			$this->close();
124
		}
125
		return $exists;
126
	}
127
128
	/**
129
	 * Retrieve user profile information from the LDAP server.
130
	 * @param $user User to update
131
	 * @return boolean true if successful
132
	 */
133
	function getUserInfo(&$user) {
134
		$valid = false;
135
		if ($this->open()) {
136
			if ($entry = $this->getUserEntry($user->getUsername())) {
137
				$valid = true;
138
				$attr = ldap_get_attributes($this->conn, $entry);
139
				$this->userFromAttr($user, $attr);
140
			}
141
			$this->close();
142
		}
143
		return $valid;
144
	}
145
146
	/**
147
	 * Store user profile information on the LDAP server.
148
	 * @param $user User to store
149
	 * @return boolean true if successful
150
	 */
151
	function setUserInfo(&$user) {
152
		$valid = false;
153
		if ($this->open()) {
154
			if ($entry = $this->getUserEntry($user->getUsername())) {
155
				$userdn = ldap_get_dn($this->conn, $entry);
156
				if ($this->bind($this->settings['managerdn'], $this->settings['managerpwd'])) {
157
					$attr = array();
158
					$this->userToAttr($user, $attr);
159
					$valid = ldap_modify($this->conn, $userdn, $attr);
160
				}
161
			}
162
			$this->close();
163
		}
164
		return $valid;
165
	}
166
167
	/**
168
	 * Change a user's password on the LDAP server.
169
	 * @param $username string user to update
170
	 * @param $password string the new password
171
	 * @return boolean true if successful
172
	 */
173
	function setUserPassword($username, $password) {
174
		if ($this->open()) {
175
			if ($entry = $this->getUserEntry($username)) {
176
				$userdn = ldap_get_dn($this->conn, $entry);
177
				if ($this->bind($this->settings['managerdn'], $this->settings['managerpwd'])) {
178
					$attr = array('userPassword' => $this->encodePassword($password));
179
					$valid = ldap_modify($this->conn, $userdn, $attr);
180
				}
181
			}
182
			$this->close();
183
		}
184
	}
185
186
	/**
187
	 * Create a user on the LDAP server.
188
	 * @param $user User to create
189
	 * @return boolean true if successful
190
	 */
191
	function createUser(&$user) {
192
		$valid = false;
193
		if ($this->open()) {
194
			if (!($entry = $this->getUserEntry($user->getUsername()))) {
195
				if ($this->bind($this->settings['managerdn'], $this->settings['managerpwd'])) {
196
					$userdn = $this->settings['uid'] . '=' . $user->getUsername() . ',' . $this->settings['basedn'];
197
					$attr = array(
198
						'objectclass' => array('top', 'person', 'organizationalPerson', 'inetorgperson'),
199
						$this->settings['uid'] => $user->getUsername(),
200
						'userPassword' => $this->encodePassword($user->getPassword())
201
					);
202
					$this->userToAttr($user, $attr);
203
					$valid = ldap_add($this->conn, $userdn, $attr);
204
				}
205
			}
206
			$this->close();
207
		}
208
		return $valid;
209
	}
210
211
	/**
212
	 * Delete a user from the LDAP server.
213
	 * @param $username string user to delete
214
	 * @return boolean true if successful
215
	 */
216
	function deleteUser($username) {
217
		$valid = false;
218
		if ($this->open()) {
219
			if ($entry = $this->getUserEntry($username)) {
220
				$userdn = ldap_get_dn($this->conn, $entry);
221
				if ($this->bind($this->settings['managerdn'], $this->settings['managerpwd'])) {
222
					$valid = ldap_delete($this->conn, $userdn);
223
				}
224
			}
225
			$this->close();
226
		}
227
		return $valid;
228
	}
229
230
231
	//
232
	// LDAP Helper Functions
233
	//
234
235
	/**
236
	 * Open connection to the server.
237
	 */
238
	function open() {
239
		$this->conn = ldap_connect($this->settings['hostname'], (int)$this->settings['port']);
240
		ldap_set_option($this->conn, LDAP_OPT_PROTOCOL_VERSION, 3);
241
		return $this->conn;
242
	}
243
244
	/**
245
	 * Close connection.
246
	 */
247
	function close() {
248
		ldap_close($this->conn);
249
		$this->conn = null;
250
	}
251
252
	/**
253
	 * Bind to a directory.
254
	 * $binddn string directory to bind (optional)
255
	 * $password string (optional)
256
	 */
257
	function bind($binddn = null, $password = null) {
258
		if (isset($this->settings['sasl'])) {
259
			// FIXME ldap_sasl_bind requires PHP5, haven't tested this
260
			return @ldap_sasl_bind($this->conn, $binddn, $password, $this->settings['saslmech'], $this->settings['saslrealm'], $this->settings['saslauthzid'], $this->settings['saslprop']);
261
		}
262
		return @ldap_bind($this->conn, $binddn, $password);
263
	}
264
265
	/**
266
	 * Lookup a user entry in the directory.
267
	 * @param $username string
268
	 */
269
	function getUserEntry($username) {
270
		$entry = false;
271
		if ($this->bind($this->settings['managerdn'], $this->settings['managerpwd'])) {
272
			$result = ldap_search($this->conn, $this->settings['basedn'], $this->settings['uid'] . '=' . $username);
273
			if (ldap_count_entries($this->conn, $result) == 1) {
274
				$entry = ldap_first_entry($this->conn, $result);
275
			}
276
		}
277
		return $entry;
278
	}
279
280
	/**
281
	 * Update User object from entry attributes.
282
	 * TODO Abstract this to allow arbitrary LDAP <-> OJS schema mappings.
283
	 * For now must be subclassed for other schemas.
284
	 * TODO How to deal with deleted fields.
285
	 * @param $user User
286
	 * @param $uattr array
287
	 */
288
	function userFromAttr(&$user, &$uattr) {
289
		$attr = array_change_key_case($uattr, CASE_LOWER); // Note:  array_change_key_case requires PHP >= 4.2.0
290
		$firstName = @$attr['givenname'][0];
291
		$middleName = null;
292
		$initials = null;
293
		$lastName = @$attr['sn'][0];
294
		if (!isset($lastName))
295
			$lastName = @$attr['surname'][0];
296
		$affiliation = @$attr['o'][0];
297
		if (!isset($affiliation))
298
			$affiliation = @$attr['organizationname'][0];
299
		$email = @$attr['mail'][0];
300
		if (!isset($email))
301
			$email = @$attr['email'][0];
302
		$phone = @$attr['telephonenumber'][0];
303
		$fax = @$attr['facsimiletelephonenumber'][0];
304
		if (!isset($fax))
305
			$fax = @$attr['fax'][0];
306
		$mailingAddress = @$attr['postaladdress'][0];
307
		if (!isset($mailingAddress))
308
			$mailingAddress = @$attr['registeredAddress'][0];
309
		$biography = null;
310
		$interests = null;
311
312
		// Only update fields that exist
313
		if (isset($firstName))
314
			$user->setFirstName($firstName);
315
		if (isset($middleName))
316
			$user->setMiddleName($middleName);
317
		if (isset($initials))
318
			$user->setInitials($initials);
319
		if (isset($lastName))
320
			$user->setLastName($lastName);
321
		if (isset($affiliation))
322
			$user->setAffiliation($affiliation);
323
		if (isset($email))
324
			$user->setEmail($email);
325
		if (isset($phone))
326
			$user->setPhone($phone);
327
		if (isset($fax))
328
			$user->setFax($fax);
329
		if (isset($mailingAddress))
330
			$user->setMailingAddress($mailingAddress);
331
		if (isset($biography))
332
			$user->setBiography($biography, Locale::getLocale());
333
		if (isset($interests))
334
			$user->setInterests($interests, Locale::getLocale());
335
	}
336
337
	/**
338
	 * Update entry attributes from User object.
339
	 * TODO How to deal with deleted fields.
340
	 * @param $user User
341
	 * @param $attr array
342
	 */
343
	function userToAttr(&$user, &$attr) {
344
		// FIXME empty strings for unset fields?
345
		if ($user->getFullName())
346
			$attr['cn'] = $user->getFullName();
347
		if ($user->getFirstName())
348
			$attr['givenName'] = $user->getFirstName();
349
		if ($user->getLastName())
350
			$attr['sn'] = $user->getLastName();
351
		if ($user->getAffiliation())
352
			$attr['organizationName'] = $user->getAffiliation();
353
		if ($user->getEmail())
354
			$attr['mail'] = $user->getEmail();
355
		if ($user->getPhone())
356
			$attr['telephoneNumber'] = $user->getPhone();
357
		if ($user->getFax())
358
			$attr['facsimileTelephoneNumber'] = $user->getFax();
359
		if ($user->getMailingAddress())
360
			$attr['postalAddress'] = $user->getMailingAddress();
361
	}
362
363
	/**
364
	 * Encode password for the 'userPassword' field using the specified hash.
365
	 * @param $password string
366
	 * @return string hashed string (with prefix).
367
	 */
368
	function encodePassword($password) {
369
		switch ($this->settings['pwhash']) {
370
			case 'md5':
371
				return '{MD5}' . base64_encode(pack('H*', md5($password)));
372
			case 'smd5':
373
				$salt = pack('C*', mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand());
374
				return '{SMD5}' . base64_encode(pack('H*', md5($password . $salt)) . $salt);
375
			case 'sha':
376
				return '{SHA}' . base64_encode(pack('H*', sha1($password))); // Note: sha1 requres PHP >= 4.3.0
377
			case 'ssha':
378
				$salt = pack('C*', mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand());
379
				return '{SSHA}' . base64_encode(pack('H*', sha1($password . $salt)) . $salt);
380
			case 'crypt':
381
				return '{CRYPT}' . crypt($password);
382
			default:
383
				//return '{CLEARTEXT}'. $password;
384
				return $password;
385
		}
386
	}
387
}
388
389
?>
(-) (+25 lines)
Added Link Here 
1
<?php
2
3
/**
4
 * @defgroup plugins_auth_ldap
5
 */
6
 
7
/**
8
 * @file plugins/auth/ldap/index.php
9
 *
10
 * Copyright (c) 2000-2008 John Willinsky
11
 * Distributed under the GNU GPL v2. For full terms see the file docs/COPYING.
12
 *
13
 * @ingroup plugins_auth_ldap
14
 * @brief Wrapper for loading the LDAP authentiation plugin.
15
 *
16
 */
17
18
// $Id: index.php,v 1.8 2008/07/01 01:16:12 asmecher Exp $
19
20
21
require_once('LDAPAuthPlugin.inc.php');
22
23
return new LDAPAuthPlugin();
24
25
?>
(-) (+118 lines)
Added Link Here 
1
{**
2
 * settings.tpl
3
 *
4
 * Copyright (c) 2000-2008 John Willinsky
5
 * Distributed under the GNU GPL v2. For full terms see the file docs/COPYING.
6
 *
7
 * LDAP authentication source settings.
8
 *
9
 * $Id: settings.tpl,v 1.6 2008/06/11 18:55:08 asmecher Exp $
10
 *}
11
<br />
12
13
<h3>{translate key="plugins.auth.ldap.settings"}</h3>
14
15
<table class="data" width="100%">
16
	<tr valign="top">
17
		<td width="20%" class="label">{fieldLabel name="hostname" key="plugins.auth.ldap.settings.hostname"}</td>
18
		<td width="80%" class="value">
19
			<input type="text" id="hostname" name="settings[hostname]" value="{$settings.hostname|escape}" size="30" maxlength="255" class="textField" />
20
			<br />
21
			<span class="instruct">{translate key="plugins.auth.ldap.settings.hostname.description"}</span>
22
		</td>
23
	</tr>
24
	<tr valign="top">
25
		<td class="label">{fieldLabel name="port" key="plugins.auth.ldap.settings.port"}</td>
26
		<td class="value">
27
			<input type="text" id="port" name="settings[port]" value="{$settings.port|escape}" size="8" maxlength="5" class="textField" />
28
			<br />
29
			<span class="instruct">{translate key="plugins.auth.ldap.settings.port.description"}</span>
30
		</td>
31
	</tr>
32
	<tr valign="top">
33
		<td class="label">{fieldLabel name="basedn" key="plugins.auth.ldap.settings.basedn"}</td>
34
		<td class="value">
35
			<input type="text" id="basedn" name="settings[basedn]" value="{$settings.basedn|escape}" size="30" maxlength="255" class="textField" />
36
			<br />
37
			<span class="instruct">{translate key="plugins.auth.ldap.settings.basedn.description"}</span>
38
		</td>
39
	</tr>
40
	<tr valign="top">
41
		<td class="label">{fieldLabel name="managerdn" key="plugins.auth.ldap.settings.managerdn"}</td>
42
		<td class="value">
43
			<input type="text" id="managerdn" name="settings[managerdn]" value="{$settings.managerdn|escape}" size="30" maxlength="255" class="textField" />
44
			<br />
45
			<span class="instruct">{translate key="plugins.auth.ldap.settings.managerdn.description"}</span>
46
		</td>
47
	</tr>
48
	<tr valign="top">
49
		<td class="label">{fieldLabel name="uid" key="plugins.auth.ldap.settings.uid"}</td>
50
		<td class="value">
51
			<input type="text" id="uid" name="settings[uid]" value="{$settings.uid|escape}" size="30" maxlength="255" class="textField" />
52
			<br />
53
			<span class="instruct">{translate key="plugins.auth.ldap.settings.uid.description"}</span>
54
		</td>
55
	</tr>
56
	<tr valign="top">
57
		<td class="label">{fieldLabel name="managerpwd" key="plugins.auth.ldap.settings.managerpwd"}</td>
58
		<td class="value">
59
			<input type="text" id="managerpwd" name="settings[managerpwd]" value="{$settings.managerpwd|escape}" size="30" maxlength="255" class="textField" />
60
			<br />
61
			<span class="instruct">{translate key="plugins.auth.ldap.settings.managerpwd.description"}</span>
62
		</td>
63
	</tr>
64
	<tr valign="top">
65
		<td class="label">{fieldLabel name="pwhash" key="plugins.auth.ldap.settings.pwhash"}</td>
66
		<td class="value">
67
			<select name="settings[pwhash]" id="pwhash" size="1" class="selectMenu">
68
				<option value="">CLEARTEXT</option>
69
				<option value="ssha"{if $settings.pwhash == 'ssha'} selected="selected"{/if}>SSHA</option>
70
				<option value="sha"{if $settings.pwhash == 'sha'} selected="selected"{/if}>SHA</option>
71
				<option value="smd5"{if $settings.pwhash == 'smd5'} selected="selected"{/if}>SMD5</option>
72
				<option value="md5"{if $settings.pwhash == 'md5'} selected="selected"{/if}>MD5</option>
73
				<option value="crypt"{if $settings.pwhash == 'crypt'} selected="selected"{/if}>CRYPT</option>
74
			</select>
75
			<br />
76
			<span class="instruct">{translate key="plugins.auth.ldap.settings.pwhash.description"}</span>
77
		</td>
78
	</tr>
79
	<tr valign="top">
80
		<td class="label" colspan="2">
81
			<h4>{translate key="plugins.auth.ldap.settings.saslopt"}</h4>
82
		</td>
83
	</tr>
84
	<tr valign="top">
85
		<td class="label" align="right">
86
			<input type="checkbox" name="settings[sasl]" id="sasl" value="1"{if $settings.sasl} checked="checked"{/if} />
87
		</td>
88
		<td class="value">
89
			<label for="sasl">{translate key="plugins.auth.ldap.settings.sasl"}</label>
90
		</td>
91
	</tr>
92
	<tr valign="top">
93
		<td class="label">{fieldLabel name="saslmech" key="plugins.auth.ldap.settings.saslmech"}</td>
94
		<td class="value">
95
			<input type="text" id="saslmech" name="settings[saslmech]" value="{$settings.saslmech|escape}" size="30" maxlength="255" class="textField" />
96
			<br />
97
			<span class="instruct">{translate key="plugins.auth.ldap.settings.saslmech.description"}</span>
98
		</td>
99
	</tr>
100
	<tr valign="top">
101
		<td class="label">{fieldLabel name="saslrealm" key="plugins.auth.ldap.settings.saslrealm"}</td>
102
		<td class="value">
103
			<input type="text" id="saslrealm" name="settings[saslrealm]" value="{$settings.saslrealm|escape}" size="30" maxlength="255" class="textField" />
104
		</td>
105
	</tr>
106
	<tr valign="top">
107
		<td class="label">{fieldLabel name="saslauthzid" key="plugins.auth.ldap.settings.saslauthzid"}</td>
108
		<td class="value">
109
			<input type="text" id="saslauthzid" name="settings[saslauthzid]" value="{$settings.saslauthzid|escape}" size="30" maxlength="255" class="textField" />
110
		</td>
111
	</tr>
112
	<tr valign="top">
113
		<td class="label">{fieldLabel name="saslprop" key="plugins.auth.ldap.settings.saslprop"}</td>
114
		<td class="value">
115
			<input type="text" id="saslprop" name="settings[saslprop]" value="{$settings.saslprop|escape}" size="30" maxlength="255" class="textField" />
116
		</td>
117
	</tr>
118
</table>
(-) (+41 lines)
Added Link Here 
1
<?xml version="1.0" encoding="UTF-8"?>
2
<!DOCTYPE locale SYSTEM "../../../../../locale/locale.dtd">
3
4
<!--
5
  * locale.xml
6
  *
7
  * Copyright (c) 2000-2008 John Willinsky
8
  * Distributed under the GNU GPL v2. For full terms see the file docs/COPYING.
9
  *
10
  * LDAP authentication plugin localization strings for the en_US (U.S. English) locale.
11
  *
12
  * $Id: locale.xml,v 1.6 2008/06/11 18:55:08 asmecher Exp $
13
  -->
14
 
15
<locale name="en_US" full_name="U.S. English">
16
	<message key="plugins.auth.ldap.displayName">LDAP</message>
17
	<message key="plugins.auth.ldap.description">This plugin allows for authentication and synchronization of user accounts against an external LDAP data source.</message>
18
	
19
	<message key="plugins.auth.ldap.settings">LDAP Settings</message>
20
	<message key="plugins.auth.ldap.settings.hostname">Server hostname</message>
21
	<message key="plugins.auth.ldap.settings.hostname.description">E.g., "ldap.example.com", or "ldaps://ldap.example.com" (to use SSL)</message>
22
	<message key="plugins.auth.ldap.settings.port">Server port</message>
23
	<message key="plugins.auth.ldap.settings.port.description">Optional. Defaults to 389 (LDAP) or 636 (LDAP over SSL)</message>
24
	<message key="plugins.auth.ldap.settings.basedn">Base DN</message>
25
	<message key="plugins.auth.ldap.settings.basedn.description">E.g., "ou=people,dc=example,dc=com"</message>
26
	<message key="plugins.auth.ldap.settings.managerdn">Manager DN</message>
27
	<message key="plugins.auth.ldap.settings.managerdn.description">E.g., "cn=Manager,dc=example,dc=com"</message>
28
	<message key="plugins.auth.ldap.settings.managerpwd">Manager password</message>
29
	<message key="plugins.auth.ldap.settings.managerpwd.description">The manager DN and password are only required if the user profile/password synchronization or user creation options are enabled. If LDAP will be used solely for authentication then these options can be omitted.</message>
30
	<message key="plugins.auth.ldap.settings.uid">Account name attribute</message>
31
	<message key="plugins.auth.ldap.settings.uid.description">The attribute whose value uniquely identifies a user object, such as uid or cn or sAMAccountName.</message>
32
	<message key="plugins.auth.ldap.settings.pwhash">Password encryption</message>
33
	<message key="plugins.auth.ldap.settings.pwhash.description">Hash format for passwords stored to the server. SSHA is recommended (requires PHP >= 4.3.0).</message>
34
	<message key="plugins.auth.ldap.settings.saslopt">SASL Settings (optional)</message>
35
	<message key="plugins.auth.ldap.settings.sasl">Use SASL instead of simple authentication (requires PHP >= 5)</message>
36
	<message key="plugins.auth.ldap.settings.saslmech">SASL mechanism</message>
37
	<message key="plugins.auth.ldap.settings.saslmech.description">E.g., "DIGEST-MD5"</message>
38
	<message key="plugins.auth.ldap.settings.saslrealm">Realm</message>
39
	<message key="plugins.auth.ldap.settings.saslauthzid">Requested authorization ID</message>
40
	<message key="plugins.auth.ldap.settings.saslprop">SASL security properties</message>
41
</locale>

Return to bug 2960