7e2bc63f97b2bc31b02738332d0658df0526aa20
[atutor.git] / mods / wiki / plugins / auth-liveuser / liveuser_aux.php
1 <?php
2
3 /**
4  * Copyright (c) 2003, The Burgiss Group, LLC
5  * This source code is part of eWiki LiveUser Plugin.
6  *
7  * eWiki LiveUser Plugin is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU Lesser General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or (at your
10  * option) any later version.
11  *
12  * eWiki LiveUser Plugin is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
15  * for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with Wiki LiveUser Plugin; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21
22 /*
23  * note: this file should be included by all ewiki liveuser plugin scripts. it
24  * defines a number of utility functions, instantiates required liveuser
25  * objects used throughout the plugin, and ensures the inclusion of liveuser's
26  * configuration.
27  *
28  * note: The following abstraction functions treat the name value of entities as
29  * a unique id (although it does not have to be) along with the existing
30  * numerical id (unique by default). For language entities, these functions will
31  * return the 2-letter language code as the unique identifier.
32  */
33
34 require_once('LiveUser/LiveUser.php');
35 require_once('LiveUser/Admin/Auth/Container/DB.php');
36 require_once('LiveUser/Admin/Perm/Container/DB_Complex.php');
37 require_once(dirname(__FILE__).'/liveuser_conf.php');
38
39
40 define('LEVENSTIEN_MIN',6);
41
42 /* fetch auth/perm admin objects */
43 $liveuserAuthAdmin = &new LiveUser_Admin_Auth_Container_DB($liveuserConfig['authContainers'][0]);
44 $liveuserPermAdmin = &new LiveUser_Admin_Perm_Container_DB_Complex($liveuserConfig['permContainer']);
45
46 /**
47  * Translates a username or auth object user id into a perm object user id. The
48  * argument type may either be 'user' or 'user_id', to denote if the second
49  * id argument is a username or auth id.
50  *
51  * @param string type of second argument
52  * @param mixed id integer auth_id or username
53  * @return integer corresponding perm_id if user exists, false otherwise
54  */
55 function liveuser_getPermUserId($type, $id)
56 {
57     global $liveuserDB, $liveuserConfig;
58
59     $container_name = $liveuserConfig['authContainers'][0]['name'];
60     
61     if (($type == 'user' || $type == 'user_id') &&
62         ($auth_id = liveuser_checkEntity($type, $id)) !== false) {
63         $perm_user_id = $liveuserDB->getOne('
64             SELECT liveuser_perm_users.perm_user_id
65             FROM liveuser_perm_users, liveuser_users
66             WHERE liveuser_users.auth_user_id = ? AND
67                 liveuser_perm_users.auth_container_name = ? AND
68                 liveuser_users.auth_user_id = liveuser_perm_users.auth_user_id',
69             array($auth_id, $container_name));
70         return (is_numeric($perm_user_id) ? $perm_user_id : false);
71     } else {
72         return false;
73     }
74 }
75
76 /**
77  * Allows for duplicate-safe creation of LiveUser entities. The supplied entity
78  * name must be one of the following: language, application; area; right; group;
79  * or user. This function expects the following global variables:
80  *
81  *      $liveuserAuthAdmin (of type LiveUser_Admin_Auth_Container_DB)
82  *      $liveuserPermAdmin (of type LiveUser_Admin_Perm_Container_DB_Complex)
83  *      $liveuserConfig    (LiveUser configuration array)
84  *
85  * @param string type name of entity type to add
86  * @param array args array corresponding to argument list of the entity's add method
87  * @return mixed unique id (integer or string) of added entity on success, false on error
88  */
89 function liveuser_addEntity($type, $args)
90 {
91     global $liveuserAuthAdmin, $liveuserPermAdmin, $liveuserConfig;
92
93     switch ($type) {
94         // special case, add language, but return 2-letter code instead of numerical id
95         case 'language':
96             if (($languageId = liveuser_checkEntity($type, $args[0])) === false) {
97                 $languageId = call_user_func_array(array(&$liveuserPermAdmin,'addLanguage'), $args);
98                 // return 2-letter code for success, false otherwise
99                 return (is_numeric($languageId) ? $args[0] : false);
100             }
101             return $args[0];
102             break;
103         // special case, add user to both auth and perm containers
104         case 'user':
105             $perm_id = 0;
106             if (($auth_id = liveuser_checkEntity($type, $args[0])) === false) {
107                 $auth_id = call_user_func_array(array(&$liveuserAuthAdmin,'addUser'), $args);
108                 $perm_id = $liveuserPermAdmin->addUser($auth_id, LIVEUSER_USER_TYPE_ID, null, $liveuserConfig['authContainers'][0]['name']);
109             }
110             return (is_numeric($auth_id) && is_numeric($perm_id) ? $auth_id : false);
111             break;
112         // common cases, fetch unique name from arguments and proceed
113         case 'application':
114             $argName = $args[1];
115             break;
116         case 'area':
117             $argName = $args[2];
118             break;
119         case 'right':
120             $argName = $args[2];
121             break;
122         case 'group':
123            $argName = $args[1];
124             break;
125         // failure case, unknown type
126         default:
127             return false;
128             break;
129     }
130
131     // common check and create cases where type is: application, area, right, group
132     if (($entityId = liveuser_checkEntity($type, $argName)) === false) {
133         $entityId = call_user_func_array(array(&$liveuserPermAdmin,'add'.$type), $args);
134     }
135     return (is_numeric($entityId) ? $entityId : false);
136 }
137
138 /**
139  * Checks for existing LiveUser entities. The supplied entity name must be one
140  * of the following: application; area; right; group; user; or language. These
141  * types will denote that the id parameter is in the form of the entity name. To
142  * denote that the id parameter is in the form of the numeric id, the suffix
143  * '_id' should be appended to the type names listed above. Depending on the
144  * supplied id parameter, this function will search for entities matching the
145  * numerical id before matching against the string name. All name id values must
146  * be non-numeric strings. This function expects the following global variables:
147  *
148  *      $liveuserAuthAdmin (of type LiveUser_Admin_Auth_Container_DB)
149  *      $liveuserPermAdmin (of type LiveUser_Admin_Perm_Container_DB_Complex)
150  *
151  * @param string type name of entity type to add
152  * @param mixed id integer id or unique name of entity to check for depending on type
153  * @return mixed unique id (integer or string) of entity if it exists, false otherwise
154  */
155 function liveuser_checkEntity($type, $id)
156 {
157     global $liveuserDB, $liveuserConfig, $liveuserAuthAdmin, $liveuserPermAdmin;
158
159     switch ($type) {
160         // special case, check for existing language and handle 2-character name
161         case 'language':
162         case 'language_id':
163             $languages = $liveuserPermAdmin->getLanguages();
164             foreach ($languages as $code => $language) {
165                 if (($type == 'language' && $code == $id && strlen($id) == 2) ||
166                     ($type == 'language_id' && $language['language_id'] == $id)) {
167                     return $code;                    
168                 }
169             }
170             return false;
171             break;      
172             
173         // special case, check against user entity sets in both auth and perm containers
174         case 'user':
175         case 'user_id':
176             $users = $liveuserAuthAdmin->getUsers();
177             $auth_id = null;
178             foreach ($users as $user) {
179                 if (($type == 'user' && $user['handle'] == $id) ||
180                     ($type == 'user_id' && $user['auth_user_id'] == $id)) {
181                     $auth_id = $user['auth_user_id'];
182                     break;
183                 }
184             }
185             // no match if user_id is unset, otherwise proceed to check with perm container
186             if (!isset($auth_id)) {
187                 return false;
188             } else if ($liveuserDB->getOne('SELECT 1 FROM '.$liveuserConfig['permContainer']['prefix'].'perm_users WHERE auth_user_id = ?', array((int)$auth_id)) == 1) {
189                 return $auth_id;
190             } else {
191                 return false;
192             }
193             break;
194             
195         // common cases, check against entity selection
196         case 'application':
197         case 'area':
198         case 'right':
199         case 'group':
200             $entities = call_user_func_array(array(&$liveuserPermAdmin, 'get'.$type.'s'), array());
201             foreach ($entities as $entity) {
202                 if ($entity['name'] == $id) {
203                     return $entity[$type.'_id'];
204                 }
205             }
206             return false;
207             break;
208             
209         case 'application_id':
210         case 'area_id':
211         case 'right_id':
212         case 'group_id':
213             $entities = call_user_func_array(array(&$liveuserPermAdmin, 'get'.substr($type, 0, -3).'s'), array());
214             foreach ($entities as $entity) {
215                 if ($entity[$type] == $id) {
216                     return $entity[$type];
217                 }
218             }
219             return false;
220             break;
221             
222         // failure case, unknown type or no match for entity
223         default:
224             return false;
225             break;
226     }
227 }
228
229 /**
230  * Allows for removal of LiveUser entities. The supplied entity name must be one
231  * of the following: language, application; area; right; group; or user. These
232  * types imply that the id parameter is in the form of the entity name. To
233  * denote the id parameter as an entity numeric id, the suffix '_id' should be
234  * appended to the names. This function expects the following global variables:
235  *
236  *      $liveuserAuthAdmin (of type LiveUser_Admin_Auth_Container_DB)
237  *      $liveuserPermAdmin (of type LiveUser_Admin_Perm_Container_DB_Complex)
238  *
239  * @param string type name of entity type to remove
240  * @param mixed id integer id or unique name of entity to remove depending on type
241  * @return true if the entity existed and was removed, false otherwise
242  */
243 function liveuser_removeEntity($type, $id)
244 {
245     global $liveuserDB, $liveuserConfig, $liveuserAuthAdmin, $liveuserPermAdmin;
246
247     switch ($type) {
248         // special case, remove user to both auth and perm containers
249         case 'user':
250         case 'user_id':
251             // log removed users with ewiki_log
252             if (($auth_id = liveuser_checkEntity($type, $id)) !== false &&
253                 ($perm_id = liveuser_getPermUserId('user_id', $auth_id)) !== false) {
254                     // fetch authTable names
255                     $authTable = $liveuserConfig['authContainers'][0]['authTable'];
256                     
257                     // backup user preferences and groups of user being removed
258                     $backup['prefs'] = $liveuserDB->getAll('SELECT '.LW_PREFIX.'_prefs_fields.field_name, '.LW_PREFIX.'_prefs_data.field_value
259                         FROM '.LW_PREFIX.'_prefs_fields, '.LW_PREFIX.'_prefs_data
260                         WHERE '.LW_PREFIX.'_prefs_data.user_id = ? AND '.LW_PREFIX.'_prefs_data.field_id = '.LW_PREFIX.'_prefs_fields.field_id',
261                         array((int)$perm_id));
262                     
263                     // direct sql required to fetch group_define_name
264                     $backup['groups'] = $liveuserDB->getAll('SELECT liveuser_groups.group_define_name
265                         FROM liveuser_groups, liveuser_groupusers
266                         WHERE liveuser_groupusers.perm_user_id = ? AND liveuser_groupusers.group_id = liveuser_groups.group_id',
267                         array((int)$perm_id));
268                     
269                     // output serialized data to log file                    
270                     $handle = $liveuserDB->getOne('SELECT handle FROM ! WHERE auth_user_id = ?', 
271                         array($authTable, (int)$auth_id));                    
272                     ewiki_log('liveuser: removed user: '.$handle.'|'.serialize($backup), 1);                                    
273                 
274                 // remove records of user in auth/perm containers and the user's preferences
275                 if ($liveuserPermAdmin->removeUser($perm_id) !== true) {
276                     return false;
277                 }                
278                 if ($liveuserAuthAdmin->removeUser($auth_id) !== true) {
279                     return false;
280                 }
281                 return ($liveuserDB->query('DELETE FROM '.LW_PREFIX.'_prefs_data WHERE '.LW_PREFIX.'_prefs_data.user_id = ?',
282                     array((int)$perm_id)) == DB_OK);
283             }
284             return false;
285             break;
286             
287         // common cases, fetch unique name from arguments and proceed
288         case 'language':
289         case 'application':
290         case 'area':
291         case 'right':
292         case 'group':
293             if (($entityId = liveuser_checkEntity($type, $id)) !== false) {
294                 if (call_user_func_array(array(&$liveuserPermAdmin,'remove'.$type), $entityId) !== true) {
295                     return false;
296                 }
297                 return true;
298             }
299             return false;
300             break;
301
302         case 'language_id':
303         case 'application_id':
304         case 'area_id':
305         case 'right_id':
306         case 'group_id':
307             if (($entityId = liveuser_checkEntity($type, $id)) !== false) {
308                 if (call_user_func_array(array(&$liveuserPermAdmin,'remove'.substr($type, 0, -3)), $entityId) !== true) {
309                     return false;
310                 }
311                 return true;
312             }
313             return false;
314             break;
315
316         // failure case, unknown type or no match for entity
317         default:
318             return false;
319             break;
320     }   
321 }
322
323 /**
324  * Allows for duplicate-safe creation of page permissions. If the permissions
325  * already exists, the page/right record will be updated, rather than inserted.
326  * Permissions may be specified as a combination of the page name and right_id,
327  * or the integer id of the permission may be supplied as the first parameter
328  * and the right_id parameter omitted. The preserveHigher parameter may be set
329  * to ensure that existing permissions higher than the permission being set
330  * are not overwritten, thus making the update have no effect.
331  *
332  * @param string type name of entity type to remove
333  * @param mixed page_id ewiki page name as a string or integer id
334  * @param int ring ewiki ring level
335  * @param int right_id LiveUser right id
336  * @param boolean preserveHigher does not overwrite higher permissions if true
337  *
338  * @return boolean true if permission was successfully added or modified, false otherwise
339  */
340 function liveuser_addPerm($page_id, $ring, $right_id = null, $preserveHigher = false)
341 {
342     global $liveuserDB;
343
344     if (($perm_id = liveuser_checkPerm($page_id, $right_id)) !== false) {
345         if ($preserveHigher) {
346             return ($liveuserDB->query('UPDATE '.LW_PREFIX.'_perms SET ring = ? WHERE id = ? AND ring > ?',
347                 array((int)$ring, (int)$perm_id, (int)$ring)) == DB_OK);
348         } else {
349             return ($liveuserDB->query('UPDATE '.LW_PREFIX.'_perms SET ring = ? WHERE id = ?',
350                 array((int)$ring, (int)$perm_id)) == DB_OK);
351         }
352     } else {
353         return ($liveuserDB->query('INSERT INTO '.LW_PREFIX.'_perms (pagename, ring, right_id) VALUES (?, ?, ?)',
354             array($page_id, (int)$ring, (int)$right_id)) == DB_OK);
355     }
356     return false;
357 }
358
359 /**
360  * Checks if a page permission already exists in the database. Permissions
361  * may be specified as a combination of the page name and right_id, or the
362  * integer id of the permission may be supplied as the first and only parameter.
363  *
364  * @param mixed page_id ewiki page name as a string or integer id
365
366  * @param int right_id LiveUser right id
367  *
368  * @return mixed integer identifier if permission exists, false otherwise
369  */
370 function liveuser_checkPerm($page_id, $right_id = null)
371 {
372     global $liveuserDB;
373
374     $perm_id = null;
375
376     if (is_null($right_id)) {
377         if (is_numeric($page_id)) {
378             $perm_id = $liveuserDB->getOne('SELECT id FROM '.LW_PREFIX.'_perms WHERE id = ?',
379                 array($page_id));
380         } else {
381             // non-numeric page_id with null right_id, invalid input
382             return false;
383         }       
384     } else {
385         $perm_id = $liveuserDB->getOne('SELECT id FROM '.LW_PREFIX.'_perms WHERE pagename = ? AND right_id = ?',
386             array($page_id, (int)$right_id));
387     }
388     return (is_numeric($perm_id) ? $perm_id : false);
389 }
390
391 /**
392  * Allows for removal of page permissions. Permissions may be specified as a
393  * combination of the page name and right_id, or the integer id of the permission
394  * may be supplied as the first and only parameter. The ring_min parameter may
395  * be used to limit the permissions that will be removed by ensuring that their
396  * ring level is at least the value given. The default value (0: highest access
397  * level) would allow all permissions to be removed.
398  *
399  * @param mixed page_id ewiki page name as a string or integer id
400  * @param int right_id LiveUser right id
401  * @param int ring_min remove only if ring level is >= this value
402  *
403  * @return boolean true if permission was successfully removed, false otherwise
404  */
405 function liveuser_removePerm($page_id, $right_id = null, $ring_min = 0)
406 {
407     global $liveuserDB;
408
409     if (($perm_id = liveuser_checkPerm($page_id, $right_id)) !== false) {
410         return ($liveuserDB->query('DELETE FROM '.LW_PREFIX.'_perms WHERE id = ? AND ring >= ?',
411             array((int)$perm_id, (int)$ring_min)) == DB_OK);
412     }
413     return false;
414 }
415
416 /**
417  * Retrieves all permissions for a page. Permissions may be specified as a
418  * combination of the pagename and right_id, or the integer id of the permission
419  * may be supplied as the first and only parameter.
420  *
421  * @param string pagename ewiki page name as a string
422  * @param int ring_min retrieve only if ring level is >= this value
423  *
424  * @return mixed array of permissions for the page
425  */
426 function liveuser_getPerms($pagename, $ring_min = 0)
427 {
428     global $liveuserDB;
429
430     return $liveuserDB->getAll('SELECT * FROM '.LW_PREFIX.'_perms WHERE pagename = ? AND ring >= ?',
431         array($pagename, (int)$ring_min));
432 }
433
434 /**
435  * Checks if a group has a right.
436  *
437  * @param integer group_id group_id
438  * @param integer right_id right id
439  * @return boolean true if group has the right, false otherwise
440  */
441 function liveuser_checkGroupRight($group_id, $right_id)
442 {
443     global $liveuserPermAdmin;
444
445     $groups = $liveuserPermAdmin->getGroups(array('where_group_id' => $group_id, 'with_rights' => true));
446
447     if (!is_array($groups)) {
448         return false;
449     }
450     
451     // result of getGroups() is an array [result set] of arrays [groups]
452     foreach ($groups as $group) {
453         $rights = $group['rights'];
454         foreach($rights as $right) {
455             if ($right['right_id'] == $right_id) {
456                 return true;
457             }
458         }
459     }
460     return false;
461 }
462
463 /**
464  * Checks if a user is a member of a group.
465  *
466  * @param integer auth_id auth_id
467  * @param integer group_id group_id
468  * @return boolean true if user is in the group, false otherwise
469  */
470 function liveuser_checkGroupUser($group_id, $auth_id)
471 {
472     global $liveuserPermAdmin;
473
474     if (is_numeric($auth_id) && is_numeric($group_id) &&
475         is_numeric($perm_id = liveuser_getPermUserId('user_id', $auth_id))) {
476         $groups = $liveuserPermAdmin->getGroups(array('where_user_id' => $perm_id, 'where_group_id' => $group_id));
477         return (is_array($groups) && !empty($groups));
478     }
479     return false;
480 }
481 /* Generates passwords for liveuser_gui and liveuser_addusers_gui*/
482 function liveuser_generate_password(){
483     $pwd = '';  // to store generated password
484     $len = rand(LW_PASSWORD_LEN_MIN, LW_PASSWORD_LEN_MAX); // password length
485       
486     do{  
487       // generate random number sequence of ascii characters
488       for ($i = 0; $i < $len; $i++) {
489           $num = rand(48, 122);
490           
491           if ($num >= ord('a') && $num <= ord('z')) {   
492               $pwd .= chr($num);                    
493           } else if ($num >= ord('A') && $num <= ord('Z')) {
494               $pwd .= chr($num);
495           } else if ($num >= ord('0') && $num <= ord('9')) {
496               $pwd .= chr($num);
497           } else if ($num >= ord('#') && $num <= ord('&')) {
498               $pwd .= chr($num);
499           } else if ($num >= ord('?') && $num <= ord('@')) {
500               $pwd .= chr($num);                
501           } else {
502               $i--;
503           }
504       }
505     }while(ewiki_check_passwd($pwd)!="good passwd");
506
507     return($pwd);
508 }
509
510 function ewiki_calc_complexity($passlen, $groups){
511   //checks for password complexity
512   return intval(log10(pow($groups,$passlen))/log10(2));
513 }
514
515 /*Checks to see if the password meets or exceeds a given complexity. 
516   Checks to see if the password is based off of the user name.
517   Checks to see if the password is or contains a dictionary word.
518 */
519 function ewiki_check_passwd(&$password, $username=NULL, $skipdict=0){
520   $lcase_password=strtolower($password);
521   $username=strtolower($username);
522   $possibilities=0;
523   $a_complexity['[a-z]']=26; //lowercase letters
524   $a_complexity['[A-Z]']=26; //uppercase letters
525   $a_complexity['[0-9]']=10; //numbers
526   $a_complexity['[()<>[\]{}!-.?,\'";:]']=17; //punctuation
527   $a_complexity['[@#$%^&*_=+\\/|`~ ]']=16; //misc symbols
528
529   $passlen=strlen($password);
530   //determine the what sets are contained in the password
531   foreach($a_complexity as $regex => $count){
532     //echo("checking $regex ");
533     if(preg_match("/".$regex."/", $password)){
534       //echo("found $regex ");
535       $possibilities+=$count;
536     }
537   }
538   
539   $complexity=ewiki_calc_complexity($passlen, $possibilities);
540   if($complexity<EWIKI_PASSWORD_COMPLEXITY){
541     return("CHPW_BADNEW_COMPLEXITY");
542   }
543
544   if(strlen($username)){  
545     //checks to see if the password is based on the user name
546     $dist = levenshtein($username, $lcase_password); //check forward
547     $rdist = levenshtein(strrev($username), $lcase_password); //check backward
548     if($dist<=LEVENSTIEN_MIN || strstr($lcase_password, $username) || strstr($lcase_password, strrev($username)) || $rdist<=LEVENSTIEN_MIN){
549       return("CHPW_BADNEW_USERNAME");
550     }
551   }
552   
553   if(!$skipdict&&EWIKI_PASS_DICT_PATH){
554     //gets dictionary and dumps it into a string (fastest way to read a large file)
555     $dictionary=file_get_contents(EWIKI_PASS_DICT_PATH, 1);
556     
557     //checks that the file was read properly
558     if(!$dictionary){
559       return("read error");
560     }
561     
562     $chunk_length=EWIKI_MIN_DICT_WORD_LENGTH;
563
564     do{ //length of chunk
565     $position=0;
566     $chunks=$passlen-$chunk_length;
567       do{ //position of beginning of chunk
568         $subword=substr($password, $position, $chunk_length);
569         if(strpos($dictionary, "\n".strtolower($subword)."\n")){
570           $wordsfound[$subword]=strlen($subword);
571         }
572       } while(++$position<=$chunks);
573     } while(++$chunk_length<=$passlen);
574     
575     unset($dictionary); //the dictionary is huge, no need to keep it anymore
576        
577     if(isset($wordsfound)){
578       
579       arsort($wordsfound);
580     
581       foreach($wordsfound as $word => $cred){
582         //echo("found $word ");
583         $password = str_replace($word, $cred, $password);
584       }
585       //echo(" calculating complexity of $password");
586       if(ewiki_calc_complexity(strlen($password),$possibilities)<EWIKI_PASSWORD_COMPLEXITY){
587         return ('CHPW_BADNEW_DICTIONARY');
588       }
589     }
590   }
591
592   //the password passed all the checks...its good enough...for now
593   return("good passwd");
594 }
595
596
597
598 // initializes default language to 'EN'
599 $liveuserPermAdmin->setCurrentLanguage(liveuser_addEntity('language', array('EN', 'English', 'English')));
600
601 ?>