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 |
|
---|
40 | require_once(DOKU_INC.'inc/adLDAP.php');
|
---|
41 |
|
---|
42 | class 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 :
|
---|