source: documentation/trunk/packages/dokuwiki-2011-05-25a/inc/auth/ad.class.php@ 25027

Last change on this file since 25027 was 25027, checked in by jmt12, 12 years ago

Adding the packages directory, and within it a configured version of dokuwiki all ready to run

File size: 11.2 KB
Line 
1<?php
2/**
3 * Active Directory authentication backend for DokuWiki
4 *
5 * This makes authentication with a Active Directory server much easier
6 * than when using the normal LDAP backend by utilizing the adLDAP library
7 *
8 * Usage:
9 * Set DokuWiki's local.protected.php auth setting to read
10 *
11 * $conf['useacl'] = 1;
12 * $conf['disableactions'] = 'register';
13 * $conf['autopasswd'] = 0;
14 * $conf['authtype'] = 'ad';
15 * $conf['passcrypt'] = 'ssha';
16 *
17 * $conf['auth']['ad']['account_suffix'] = '@my.domain.org';
18 * $conf['auth']['ad']['base_dn'] = 'DC=my,DC=domain,DC=org';
19 * $conf['auth']['ad']['domain_controllers'] = 'srv1.domain.org,srv2.domain.org';
20 *
21 * //optional:
22 * $conf['auth']['ad']['sso'] = 1;
23 * $conf['auth']['ad']['ad_username'] = 'root';
24 * $conf['auth']['ad']['ad_password'] = 'pass';
25 * $conf['auth']['ad']['real_primarygroup'] = 1;
26 * $conf['auth']['ad']['use_ssl'] = 1;
27 * $conf['auth']['ad']['use_tls'] = 1;
28 * $conf['auth']['ad']['debug'] = 1;
29 *
30 * // get additional information to the userinfo array
31 * // add a list of comma separated ldap contact fields.
32 * $conf['auth']['ad']['additional'] = 'field1,field2';
33 *
34 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
35 * @author James Van Lommel <[email protected]>
36 * @link http://www.nosq.com/blog/2005/08/ldap-activedirectory-and-dokuwiki/
37 * @author Andreas Gohr <[email protected]>
38 */
39
40require_once(DOKU_INC.'inc/adLDAP.php');
41
42class auth_ad extends auth_basic {
43 var $cnf = null;
44 var $opts = null;
45 var $adldap = null;
46 var $users = null;
47
48 /**
49 * Constructor
50 */
51 function auth_ad() {
52 global $conf;
53 $this->cnf = $conf['auth']['ad'];
54
55
56 // additional information fields
57 if (isset($this->cnf['additional'])) {
58 $this->cnf['additional'] = str_replace(' ', '', $this->cnf['additional']);
59 $this->cnf['additional'] = explode(',', $this->cnf['additional']);
60 } else $this->cnf['additional'] = array();
61
62 // ldap extension is needed
63 if (!function_exists('ldap_connect')) {
64 if ($this->cnf['debug'])
65 msg("AD Auth: PHP LDAP extension not found.",-1);
66 $this->success = false;
67 return;
68 }
69
70 // Prepare SSO
71 if($_SERVER['REMOTE_USER'] && $this->cnf['sso']){
72 // remove possible NTLM domain
73 list($dom,$usr) = explode('\\',$_SERVER['REMOTE_USER'],2);
74 if(!$usr) $usr = $dom;
75
76 // remove possible Kerberos domain
77 list($usr,$dom) = explode('@',$usr);
78
79 $dom = strtolower($dom);
80 $_SERVER['REMOTE_USER'] = $usr;
81
82 // we need to simulate a login
83 if(empty($_COOKIE[DOKU_COOKIE])){
84 $_REQUEST['u'] = $_SERVER['REMOTE_USER'];
85 $_REQUEST['p'] = 'sso_only';
86 }
87 }
88
89 // prepare adLDAP standard configuration
90 $this->opts = $this->cnf;
91
92 // add possible domain specific configuration
93 if($dom && is_array($this->cnf[$dom])) foreach($this->cnf[$dom] as $key => $val){
94 $this->opts[$key] = $val;
95 }
96
97 // handle multiple AD servers
98 $this->opts['domain_controllers'] = explode(',',$this->opts['domain_controllers']);
99 $this->opts['domain_controllers'] = array_map('trim',$this->opts['domain_controllers']);
100 $this->opts['domain_controllers'] = array_filter($this->opts['domain_controllers']);
101
102 // we can change the password if SSL is set
103 if($this->opts['use_ssl'] || $this->opts['use_tls']){
104 $this->cando['modPass'] = true;
105 }
106 $this->cando['modName'] = true;
107 $this->cando['modMail'] = true;
108 }
109
110 /**
111 * Check user+password [required auth function]
112 *
113 * Checks if the given user exists and the given
114 * plaintext password is correct by trying to bind
115 * to the LDAP server
116 *
117 * @author James Van Lommel <[email protected]>
118 * @return bool
119 */
120 function checkPass($user, $pass){
121 if($_SERVER['REMOTE_USER'] &&
122 $_SERVER['REMOTE_USER'] == $user &&
123 $this->cnf['sso']) return true;
124
125 if(!$this->_init()) return false;
126 return $this->adldap->authenticate($user, $pass);
127 }
128
129 /**
130 * Return user info [required auth function]
131 *
132 * Returns info about the given user needs to contain
133 * at least these fields:
134 *
135 * name string full name of the user
136 * mail string email address of the user
137 * grps array list of groups the user is in
138 *
139 * This LDAP specific function returns the following
140 * addional fields:
141 *
142 * dn string distinguished name (DN)
143 * uid string Posix User ID
144 *
145 * @author James Van Lommel <[email protected]>
146 */
147 function getUserData($user){
148 global $conf;
149 if(!$this->_init()) return false;
150
151 $fields = array('mail','displayname','samaccountname');
152
153 // add additional fields to read
154 $fields = array_merge($fields, $this->cnf['additional']);
155 $fields = array_unique($fields);
156
157 //get info for given user
158 $result = $this->adldap->user_info($user, $fields);
159 //general user info
160 $info['name'] = $result[0]['displayname'][0];
161 $info['mail'] = $result[0]['mail'][0];
162 $info['uid'] = $result[0]['samaccountname'][0];
163 $info['dn'] = $result[0]['dn'];
164
165 // additional information
166 foreach ($this->cnf['additional'] as $field) {
167 if (isset($result[0][strtolower($field)])) {
168 $info[$field] = $result[0][strtolower($field)][0];
169 }
170 }
171
172 // handle ActiveDirectory memberOf
173 $info['grps'] = $this->adldap->user_groups($user,(bool) $this->opts['recursive_groups']);
174
175 if (is_array($info['grps'])) {
176 foreach ($info['grps'] as $ndx => $group) {
177 $info['grps'][$ndx] = $this->cleanGroup($group);
178 }
179 }
180
181 // always add the default group to the list of groups
182 if(!is_array($info['grps']) || !in_array($conf['defaultgroup'],$info['grps'])){
183 $info['grps'][] = $conf['defaultgroup'];
184 }
185
186 return $info;
187 }
188
189 /**
190 * Make AD group names usable by DokuWiki.
191 *
192 * Removes backslashes ('\'), pound signs ('#'), and converts spaces to underscores.
193 *
194 * @author James Van Lommel ([email protected])
195 */
196 function cleanGroup($name) {
197 $sName = str_replace('\\', '', $name);
198 $sName = str_replace('#', '', $sName);
199 $sName = preg_replace('[\s]', '_', $sName);
200 return $sName;
201 }
202
203 /**
204 * Sanitize user names
205 */
206 function cleanUser($name) {
207 return $this->cleanGroup($name);
208 }
209
210 /**
211 * Most values in LDAP are case-insensitive
212 */
213 function isCaseSensitive(){
214 return false;
215 }
216
217 /**
218 * Bulk retrieval of user data
219 *
220 * @author Dominik Eckelmann <[email protected]>
221 * @param start index of first user to be returned
222 * @param limit max number of users to be returned
223 * @param filter array of field/pattern pairs, null for no filter
224 * @return array of userinfo (refer getUserData for internal userinfo details)
225 */
226 function retrieveUsers($start=0,$limit=-1,$filter=array()) {
227 if(!$this->_init()) return false;
228
229 if ($this->users === null) {
230 //get info for given user
231 $result = $this->adldap->all_users();
232 if (!$result) return array();
233 $this->users = array_fill_keys($result, false);
234 }
235
236 $i = 0;
237 $count = 0;
238 $this->_constructPattern($filter);
239 $result = array();
240
241 foreach ($this->users as $user => &$info) {
242 if ($i++ < $start) {
243 continue;
244 }
245 if ($info === false) {
246 $info = $this->getUserData($user);
247 }
248 if ($this->_filter($user, $info)) {
249 $result[$user] = $info;
250 if (($limit >= 0) && (++$count >= $limit)) break;
251 }
252 }
253 return $result;
254 }
255
256 /**
257 * Modify user data
258 *
259 * @param $user nick of the user to be changed
260 * @param $changes array of field/value pairs to be changed
261 * @return bool
262 */
263 function modifyUser($user, $changes) {
264 $return = true;
265
266 // password changing
267 if(isset($changes['pass'])){
268 try {
269 $return = $this->adldap->user_password($user,$changes['pass']);
270 } catch (adLDAPException $e) {
271 if ($this->cnf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
272 $return = false;
273 }
274 if(!$return) msg('AD Auth: failed to change the password. Maybe the password policy was not met?',-1);
275 }
276
277 // changing user data
278 $adchanges = array();
279 if(isset($changes['name'])){
280 // get first and last name
281 $parts = explode(' ',$changes['name']);
282 $adchanges['surname'] = array_pop($parts);
283 $adchanges['firstname'] = join(' ',$parts);
284 $adchanges['display_name'] = $changes['name'];
285 }
286 if(isset($changes['mail'])){
287 $adchanges['email'] = $changes['mail'];
288 }
289 try {
290 $return = $return & $this->adldap->user_modify($user,$adchanges);
291 } catch (adLDAPException $e) {
292 if ($this->cnf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
293 $return = false;
294 }
295
296 return $return;
297 }
298
299 /**
300 * Initialize the AdLDAP library and connect to the server
301 */
302 function _init(){
303 if(!is_null($this->adldap)) return true;
304
305 // connect
306 try {
307 $this->adldap = new adLDAP($this->opts);
308 if (isset($this->opts['ad_username']) && isset($this->opts['ad_password'])) {
309 $this->canDo['getUsers'] = true;
310 }
311 return true;
312 } catch (adLDAPException $e) {
313 if ($this->cnf['debug']) {
314 msg('AD Auth: '.$e->getMessage(), -1);
315 }
316 $this->success = false;
317 $this->adldap = null;
318 }
319 return false;
320 }
321
322 /**
323 * return 1 if $user + $info match $filter criteria, 0 otherwise
324 *
325 * @author Chris Smith <[email protected]>
326 */
327 function _filter($user, $info) {
328 foreach ($this->_pattern as $item => $pattern) {
329 if ($item == 'user') {
330 if (!preg_match($pattern, $user)) return 0;
331 } else if ($item == 'grps') {
332 if (!count(preg_grep($pattern, $info['grps']))) return 0;
333 } else {
334 if (!preg_match($pattern, $info[$item])) return 0;
335 }
336 }
337 return 1;
338 }
339
340 function _constructPattern($filter) {
341 $this->_pattern = array();
342 foreach ($filter as $item => $pattern) {
343// $this->_pattern[$item] = '/'.preg_quote($pattern,"/").'/i'; // don't allow regex characters
344 $this->_pattern[$item] = '/'.str_replace('/','\/',$pattern).'/i'; // allow regex characters
345 }
346 }
347}
348
349//Setup VIM: ex: et ts=4 :
Note: See TracBrowser for help on using the repository browser.