Source Viewer
Goto Line:
 
1 <?php
2  
3 /**
4 * LDAP Class Definition
5 *
6 * This file contains the LDAP class which provides a set of static
7 * methods for authenticating against an OpenDirectory server and discovering
8 * user/group infromation.
9 *
10 * @author Ian White <ian_white@hms.harvard.edu>
11 * @copyright Copyright (c) 2008, Ian White
12 * @version 0.8
13 *
14 */
15  
16  
17 class LDAP {
18
19 // This is a singleton class, the instance is stored here
20 static private $ldap;
21
22 // Various LDAP references
23 private $ds;
24 private $r;
25 private $base = "";
26
27 // An array of OpenDirectory Servers holding user information.
28 private $servers = array();
29
30
31 /*
32 * username_valid takes a username as an argument and returns true if
33 * the username meets the criteria for a valid username
34 *
35 * @access public
36 * @static
37 */
38 public static function username_valid($username) {
39 $username_pattern = '/^[a-zA-Z0-9]+$/';
40 $illegal_pattern = '/[&;`\'\\"|*?~<>^()[\]}{$\n\r]/';
41
42
43 return (preg_match($username_pattern, $username) && !preg_match($illegal_pattern, $username));
44 }
45
46
47 /*
48 * password_valid takes a password as an argument and returns true if the
49 * password is the correct length and contains no illegal characters. The
50 * criteria here are more stringent than those of the OpenDirectory server,
51 * but this has been useful for us in protecing against PHP/MySQL vulnerabilities.
52 *
53 * @access public
54 * @static
55 */
56 public static function password_valid($password) {
57
58 // This is more strict than the OpenDirectory requirements, but it has worked well for
59 // our projects.
60 $password_pattern = '/^[-!@#%_+=\ \/?a-zA-Z0-9]{8,}$/';
61
62 $illegal_pattern = '/[&;`\'\\"|*?~<>^()[\]}{$\n\r]/';
63
64 return (preg_match($password_pattern, $password) && !preg_match($illegal_pattern, $password));
65
66 }
67  
68 /*
69 * authenticate takes a username, password, and optional group. It verifies that the username
70 * and password are valid, checks to see if the user/pass combination successfully authenticates,
71 * and if the optional group is provided, ensures that the user is a member of the group
72 * specified. Note: this relies on the pam_auth module to be installed and configured. It also
73 * depends on the underlying server's PAM mechanism to actually perform the authenatication. This
74 * was used to enable kerberos to be the method of authentication. This function could be re-
75 * written to rely only on LDAP authentication if need be.
76 *
77 * @access public
78 * @static
79 */
80 public static function authenticate($user, $pass, $group=NULL) {
81
82 if(LDAP::username_valid($user) && LDAP::password_valid($pass)) {
83 if(pam_auth($user, $pass)) {
84
85 if($group) {
86 if(!LDAP::$ldap) LDAP::$ldap = new LDAP();
87
88 $sr=ldap_search(LDAP::$ldap->ds, "cn=Groups,{LDAP::$ldap->base}", "(&(memberUid={$user})(cn={$group}))");
89
90 return (ldap_count_entries(LDAP::$ldap->ds, $sr));
91 }
92 else {
93 return true;
94 }
95 }
96 else {
97 return false ;
98 }
99 }
100 else {
101 return false;
102 }
103
104 }
105
106 /*
107 * get_user_by_id takes an id and returns an array with user information for that user.
108 *
109 * @access public
110 * @static
111 */
112 public static function get_user_by_id($id) {
113 if(!LDAP::$ldap) LDAP::$ldap = new LDAP();
114
115 $sr=ldap_search(LDAP::$ldap->ds, "cn=Users,{LDAP::$ldap->base}", "uidNumber={$id}");
116
117 if(ldap_count_entries(LDAP::$ldap->ds, $sr)) {
118 $user_array = ldap_get_entries(LDAP::$ldap->ds, $sr);
119
120 return LDAP::convert_user($user_array[0]);
121 }
122 else {
123 throw new Exception("Invalid user or user not found.");
124 }
125 }
126
127 /*
128 * get_user_by_username takes an username and returns an array with user information for that user.
129 *
130 * @access public
131 * @static
132 */
133 public static function get_user_by_username($username) {
134 if(LDAP::username_valid($username) ) {
135 if(!LDAP::$ldap) LDAP::$ldap = new LDAP();
136
137 $sr=ldap_search(LDAP::$ldap->ds, "cn=Users,{LDAP::$ldap->base}", "uid={$username}");
138
139 if(ldap_count_entries(LDAP::$ldap->ds, $sr)) {
140 $user_array = ldap_get_entries(LDAP::$ldap->ds, $sr);
141
142 return LDAP::convert_user($user_array[0]);
143 }
144 }
145
146 throw new Exception("Invalid user or user not found.");
147
148 }
149
150
151 /*
152 * get_group_by_id takes a group id and returns an array with group information for that group.
153 *
154 * @access public
155 * @static
156 */
157 public static function get_group_by_id($id) {
158 if(!LDAP::$ldap) LDAP::$ldap = new LDAP();
159
160 $sr=ldap_search(LDAP::$ldap->ds, "cn=Groups,{LDAP::$ldap->base}", "gidNumber={$id}");
161
162 if(ldap_count_entries(LDAP::$ldap->ds, $sr)) {
163 $group_array = ldap_get_entries(LDAP::$ldap->ds, $sr);
164
165 return LDAP::convert_group($group_array[0]);
166 }
167 }
168
169
170 /*
171 * get_group_by_groupname takes a groupname and returns an array with group information for that group.
172 *
173 * @access public
174 * @static
175 */
176 public static function get_group_by_groupname($groupname) {
177 if(!LDAP::$ldap) LDAP::$ldap = new LDAP();
178
179 $sr=ldap_search(LDAP::$ldap->ds, "cn=Groups,{LDAP::$ldap->base}", "cn={$groupname}");
180
181 if(ldap_count_entries(LDAP::$ldap->ds, $sr)) {
182 $group_array = ldap_get_entries(LDAP::$ldap->ds, $sr);
183
184 return LDAP::convert_group($group_array[0]);
185 }
186 }
187
188
189 /*
190 * get_group_membership takes a groupname and returns an array user arrays representing the
191 * users who are a member of that group.
192 *
193 * @access public
194 * @static
195 */
196 public static function get_group_members($groupname) {
197 if(!LDAP::$ldap) LDAP::$ldap = new LDAP();
198
199 $new_users = array();
200
201 $group = LDAP::group_by_groupname($groupname);
202
203 // Search for secondary group members
204 if($group['members']) {
205 foreach($group['members'] as $username) {
206 try {
207 $tmp = LDAP::user_by_username($username);
208 $new_users[$tmp['uid']] = $tmp;
209 }
210 catch (Exception $e) { }
211 }
212
213 }
214
215 $id = $group['gid'];
216
217 // Search for users who have this group as their primary group id.
218 $sr=ldap_search(LDAP::$ldap->ds, "cn=Users,{LDAP::$ldap->base}", "gidNumber={$id}");
219
220 if(ldap_count_entries(LDAP::$ldap->ds, $sr)) {
221 $user_array = ldap_get_entries(LDAP::$ldap->ds, $sr);
222 $user_array = LDAP::clean_array($user_array);
223
224 foreach($user_array as $user) {
225 $tmp = LDAP::convert_user($user);
226 $new_users[$tmp['uid']] = $tmp;
227 }
228 }
229
230
231 return $new_users;
232
233 }
234
235
236 /*
237 * get_groups_for takes a user and an optional sort and primary argument. It returns
238 * an array of group arrays representing the groups that the specified user is a member
239 * of. If sort is not set, or set to true, the groups will be sorted by id. If primary
240 * is set to true, the primary group of the user will also be returned.
241 *
242 * @access public
243 * @static
244 */
245 public static function get_groups_for($username, $sort=true, $primary=false) {
246 if(LDAP::username_valid($username) ) {
247 if(!LDAP::$ldap) LDAP::$ldap = new LDAP();
248
249 $new_groups = array();
250
251 $user = LDAP::get_user_by_username($username);
252
253 if($primary) {
254 $new_groups[$user['primary_gid']] = LDAP::get_group_by_id($user['primary_gid']);
255 }
256
257 $sr=ldap_search(LDAP::$ldap->ds, "cn=Groups,{LDAP::$ldap->base}", "memberUid={$username}");
258
259 if(ldap_count_entries(LDAP::$ldap->ds, $sr)) {
260 $group_array = ldap_get_entries(LDAP::$ldap->ds, $sr);
261 $group_array = LDAP::clean_array($group_array);
262
263 foreach($group_array as $group) {
264 $tmp = LDAP::convert_group($group);
265 $new_groups[$tmp['gid']] = $tmp;
266 }
267 }
268
269 if($sort) {
270 $new_groups = array_reverse($new_groups, true);
271 }
272
273 return $new_groups;
274 }
275 else {
276 throw new Exception("Invalid user or user not found.");
277 }
278 }
279
280
281 /*
282 * is_member takes a username and groupname. It returns true if the user is a member of the
283 * group specified. This does not check the primary group, but could easily be modified to
284 * check the primary group.
285 *
286 * @access public
287 * @static
288 */
289 public static function is_member($username, $groupname) {
290 if(LDAP::username_valid($username) ) {
291 if(!LDAP::$ldap) LDAP::$ldap = new LDAP();
292
293 $sr=ldap_search(LDAP::$ldap->ds, "cn=Groups,{LDAP::$ldap->base}", "(&(memberUid={$username})(cn={$groupname}))");
294
295 return (ldap_count_entries(LDAP::$ldap->ds, $sr));
296 }
297 else {
298 throw new Exception("Invalid user or user not found.");
299 }
300
301 }
302
303
304 /*
305 * __construct calls reconnect to establish a connection to the OpenDirectory server.
306 *
307 * @access private
308 * @static
309 */
310 private function __construct() {
311 $this->reconnect();
312 }
313
314
315 /*
316 * reconnect opens the connection to the OpenDirectory server.
317 *
318 * @access private
319 * @static
320 */
321 private function reconnect() {
322
323 $index = mt_rand(0, count($this->servers) - 1);
324
325 $i = 0;
326
327 while( !$this->ds && $i < count($this->servers) ) {
328 $this->ds = ldap_connect($this->servers[($index % count($this->servers))]);
329 $index++;
330 $i++;
331 }
332
333 $server = $this->servers[($index % count($this->servers))];
334
335 if($this->ds) {
336 ldap_set_option($this->ds, LDAP_OPT_PROTOCOL_VERSION, 3);
337 $this->r = ldap_bind($this->ds);
338 }
339 else {
340 throw new Exception("Could not connect to any LDAP server.");
341 }
342
343 }
344
345
346 /*
347 * clear_array takes an array object returned by the OpenDirectory server
348 * to remove all of the cruft. It returns a new array with only named keys
349 * and empty attributes are removed.
350 *
351 * @access private
352 * @static
353 */
354 private static function clean_array($array) {
355 $return = array();
356 if($array) {
357 foreach($array as $key => $item) {
358 if($key === 'count') continue;
359
360 $return[$key] = $item;
361
362 }
363 }
364 return $return;
365 }
366
367
368 /*
369 * convert_user takes an OpenDirectory user array and converts it into a more
370 * user friendly array.
371 *
372 * @access private
373 * @static
374 */
375 private static function convert_user($user_array) {
376 $user = array();
377 $user['username' ] = $user_array['uid' ][0];
378 $user['uid' ] = $user_array['uidnumber' ][0];
379 $user['first_name' ] = $user_array['givenname' ][0];
380 $user['last_name' ] = $user_array['sn' ][0];
381 $user['primary_gid' ] = $user_array['gidnumber' ][0];
382 $user['room' ] = $user_array['street' ][0];
383 $user['lab_website' ] = $user_array['labeleduri' ][0];
384 $user['description' ] = $user_array['description' ][0];
385 $user['position' ] = $user_array['apple-keyword'][0];
386 $user['email' ] = LDAP::clean_array($user_array['mail' ]);
387 $user['phone']['work'] = LDAP::clean_array($user_array['telephonenumber' ]);
388 $user['phone']['cell'] = LDAP::clean_array($user_array['mobile' ]);
389 $user['phone']['fax' ] = LDAP::clean_array($user_array['facsimiletelephonenumber']);
390  
391 return $user;
392 }
393
394
395 /*
396 * convert_group takes an OpenDirectory group array and converts it into a more
397 * group friendly array.
398 *
399 * @access private
400 * @static
401 */
402 private static function convert_group($group_array) {
403  
404 $group = array();
405 $group['groupname' ] = $group_array['cn' ][0];
406 $group['gid' ] = $group_array['gidnumber' ][0];
407 $group['real_name' ] = $group_array['apple-group-realname'][0];
408 $group['members' ] = LDAP::clean_array($group_array['memberuid']);
409 $group['description'] = $group_array['description'][0];
410
411 return $group;
412 }
413
414 }
415  
416  
417  
418  
419  
420  
421 ?>