Code

Updated class userinfo.
[gosa.git] / gosa-core / include / class_userinfo.inc
1 <?php
2 /*
3  * This code is part of GOsa (http://www.gosa-project.org)
4  * Copyright (C) 2003-2008 GONICUS GmbH
5  *
6  * ID: $$Id$$
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
23 class userinfo
24 {
25   var $dn;
26   var $ip;
27   var $username;
28   var $cn;
29   var $uid;
30   var $gidNumber= -1;
31   var $language= "";
32   var $config;
33   var $gosaUnitTag= "";
34   var $subtreeACL= array();
35   var $ACL= array();
36   var $ocMapping= array();
37   var $groups= array();
38   var $result_cache =array();
39   var $ignoreACl = FALSE;
41   /* get acl's an put them into the userinfo object
42      attr subtreeACL (userdn:components, userdn:component1#sub1#sub2,component2,...) */
43   function userinfo(&$config, $userdn){
44     $this->config= &$config;
45     $ldap= $this->config->get_ldap_link();
46     $ldap->cat($userdn,array('sn', 'givenName', 'uid', 'gidNumber', 'preferredLanguage', 'gosaUnitTag'));
47     $attrs= $ldap->fetch();
49     if (isset($attrs['givenName'][0]) && isset($attrs['sn'][0])){
50       $this->cn= $attrs['givenName'][0]." ".$attrs['sn'][0];
51     } else {
52       $this->cn= $attrs['uid'][0];
53     }
54     if (isset($attrs['gidNumber'][0])){
55       $this->gidNumber= $attrs['gidNumber'][0];
56     }
58     /* Assign user language */
59     if (isset($attrs['preferredLanguage'][0])){
60       $this->language= $attrs['preferredLanguage'][0];
61     }
63     if (isset($attrs['gosaUnitTag'][0])){
64       $this->gosaUnitTag= $attrs['gosaUnitTag'][0];
65     }
67     $this->dn= $userdn;
68     $this->uid= $attrs['uid'][0];
69     $this->ip= $_SERVER['REMOTE_ADDR'];
71     $this->ignoreACL = ($this->config->get_cfg_value("ignoreAcl") == $this->dn);
73     /* Initialize ACL_CACHE */
74     session::set('ACL_CACHE',array());
75     $this->reset_acl_cache();
76   }
79   public function reset_acl_cache()
80   {
81     /* Initialize ACL_CACHE */
82     session::set('ACL_CACHE',array());
83   }
85   function loadACL()
86   {
87     $this->ACL= array();    
88     $this->groups= array();    
89     $this->result_cache =array();
90     $this->reset_acl_cache();
91     $ldap= $this->config->get_ldap_link();
92     $ldap->cd($this->config->current['BASE']);
94     /* Get member groups... */
95     $ldap->search("(&(objectClass=posixGroup)(memberUid=".$this->uid."))", array('dn'));
96     while ($attrs= $ldap->fetch()){
97       $this->groups[$attrs['dn']]= $attrs['dn'];
98     }
100     /* Crawl through ACLs and move relevant to the tree */
101     $ldap->search("(objectClass=gosaACL)", array('dn', 'gosaAclEntry'));
102     $aclp= array();
103     $aclc= array();
104     while ($attrs= $ldap->fetch()){
106       /* Insert links in ACL array */
107       $aclp[$attrs['dn']]= substr_count($attrs['dn'], ',');
108       $aclc[$attrs['dn']]= array();
109       $ol= array();
110       for($i= 0; $i<$attrs['gosaAclEntry']['count']; $i++){
111         $ol= array_merge($ol, @acl::explodeAcl($attrs['gosaAclEntry'][$i]));
112       }
113       $aclc[$attrs['dn']]= $ol;
114     }
116     /* Resolve roles here. 
117      */
118     foreach($aclc as $dn => $data){
119       foreach($data as $prio => $aclc_value)  {
120         if($aclc_value['type'] == "role"){
122           unset($aclc[$dn][$prio]);
124           $ldap->cat($aclc_value['acl'],array("gosaAclTemplate"));
125           $attrs = $ldap->fetch();
127           if(isset($attrs['gosaAclTemplate'])){
128             for($i= 0; $i<$attrs['gosaAclTemplate']['count']; $i++){
129               $tmp = @acl::explodeAcl($attrs['gosaAclTemplate'][$i]);  
131               foreach($tmp as $new_acl){
133                 /* Keep non role attributes here! */
134                 $new_acl['filter'] = $aclc_value['filter'];
135                 $new_acl['members'] = $aclc_value['members'];
136                 $aclc[$dn][] =$new_acl;
137               }
138             }      
139           }
140         }
141       }
142     }
144     /* ACL's read, sort for tree depth */
145     asort($aclp);
147     /* Sort in tree order */
148     foreach ($aclp as $dn => $acl){
149       /* Check if we need to keep this ACL */
150       foreach($aclc[$dn] as $idx => $type){
151         $interresting= FALSE;
152         
153         /* No members? This is good for all users... */
154         if (!count($type['members'])){
155           $interresting= TRUE;
156         } else {
158           /* Inspect members... */
159           foreach ($type['members'] as $grp => $grpdsc){
160             /* Some group inside the members that is relevant for us? */
161             if (in_array_ics(preg_replace('/^G:/', '', $grp), $this->groups)){
162               $interresting= TRUE;
163             }
165             /* User inside the members? */
166             if (preg_replace('/^U:/', '', $grp) == $this->dn){
167               $interresting= TRUE;
168             }
169           }
170         }
172         if ($interresting){
173           if (!isset($this->ACL[$dn])){
174             $this->ACL[$dn]= array();
175           }
176           $this->ACL[$dn][$idx]= $type;
177         }
178       }
180     }
181   }
184   function get_category_permissions($dn, $category, $any_acl = FALSE)
185   {
186     /* If we are forced to skip ACLs checks for the current user 
187         then return all permissions.
188      */
189     if($this->ignore_acl_for_current_user()){
190       return("rwcdm");
191     }
193     /* Get list of objectClasses and get the permissions for it */
194     $acl= "";
195     if (isset($this->ocMapping[$category])){
196       foreach($this->ocMapping[$category] as $oc){
197         $acl.= $this->get_permissions($dn, $category."/".$oc);
198         if($any_acl && !empty($acl)) return($acl);
199       }
200     }else{
201       trigger_error("ACL request for an invalid category (".$category.").");
202     }
204     return ($acl);
205   }
207   
208   /*! \brief Check if the given object (dn) is copyable
209       @param  String The object dn 
210       @param  String The acl  category (e.g. users) 
211       @param  String The acl  class (e.g. user) 
212       @return Boolean   TRUE if the given object is copyable else FALSE 
213   */
214   function is_copyable($dn, $object, $class)
215   {
216     return(preg_match("/r/",$this->has_complete_category_acls($dn, $object)));
217   }
220   /*! \brief Check if the given object (dn) is cutable
221       @param  String The object dn 
222       @param  String The acl  category (e.g. users) 
223       @param  String The acl  class (e.g. user) 
224       @return Boolean   TRUE if the given object is cutable else FALSE 
225   */
226   function is_cutable($dn, $object, $class)
227   {
228     $remove = preg_match("/d/",$this->get_permissions($dn,$object."/".$class));
229     $read   = preg_match("/r/",$this->has_complete_category_acls($dn, $object));
230     return($remove && $read);
231   }
234   /*! \brief  Checks if we are allowed to paste an object to the given destination ($dn)
235       @param  String The destination dn 
236       @param  String The acl  category (e.g. users) 
237       @param  String The acl  class (e.g. user) 
238       @return Boolean   TRUE if we are allowed to paste an object.
239   */
240   function is_pasteable($dn, $object)
241   {
242     return(preg_match("/w/",$this->has_complete_category_acls($dn, $object)));
243   }
246   /*! \brief  Checks if we are allowed to restore a snapshot for the given dn.
247       @param  String The destination dn 
248       @param  String The acl  category (e.g. users) 
249       @return Boolean   TRUE if we are allowed to restore a snapshot.
250   */
251   function allow_snapshot_restore($dn, $object)
252   {
253     if(!is_array($object)){
254       $object = array($object);
255     }
256     $r = $w = $c = TRUE;
257     foreach($object as $category){
258       $w &= preg_match("/w/",$this->has_complete_category_acls($dn, $category));
259       $c &= preg_match("/c/",$this->has_complete_category_acls($dn, $category));
260       $r &= preg_match("/r/",$this->has_complete_category_acls($dn, $category));
261 #     print_a(array($category => array($r.$w.$c)));
262     }
263     return($r && $w ); 
264   }  
267   /*! \brief  Checks if we are allowed to create a snapshot of the given dn.
268       @param  String The source dn 
269       @param  String The acl category (e.g. users) 
270       @return Boolean   TRUE if we are allowed to restore a snapshot.
271   */
272   function allow_snapshot_create($dn, $object)
273   {
274     if(!is_array($object)){
275       $object = array($object);
276     }
277     $r = $w = $c = TRUE;
278     foreach($object as $category){
279       $w &= preg_match("/w/",$this->has_complete_category_acls($dn, $category));
280       $c &= preg_match("/c/",$this->has_complete_category_acls($dn, $category));
281       $r &= preg_match("/r/",$this->has_complete_category_acls($dn, $category));
282 #      print_a(array($category => array($r.$w.$c)));
283     }
284     return($r) ; 
285   }  
288   function get_permissions($dn, $object, $attribute= "", $skip_write= FALSE)
289   {
290     /* If we are forced to skip ACLs checks for the current user 
291         then return all permissions.
292      */
293     if($this->ignore_acl_for_current_user()){
294       if($skip_write){
295         return("rcdm");
296       }
297       return("rwcdm");
298     }
300     /* Push cache answer? */
301     $ACL_CACHE = &session::get('ACL_CACHE');
302     if (isset($ACL_CACHE["$dn+$object+$attribute"])){
304       /* Remove write if needed */
305       if ($skip_write){
306         $ret = preg_replace('/w/', '', $ACL_CACHE["$dn+$object+$attribute"]);
307       }else{
308         $ret = $ACL_CACHE["$dn+$object+$attribute"];
309       } 
310       return($ret);
311     }
313     /* Get ldap object, for later filter checks 
314      */
315     $ldap = $this->config->get_ldap_link();
317     $acl= array("r" => "", "w" => "", "c" => "", "d" => "", "m" => "", "a" => "");
319     /* Build dn array */
320     $path= split(',', $dn);
321     $path= array_reverse($path);
323     /* Walk along the path to evaluate the acl */
324     $cpath= "";
325     foreach ($path as $element){
327       /* Clean potential ACLs for each level */
328                         if(isset($this->config->idepartments[$cpath])){
329         $acl= $this->cleanACL($acl);
330       }
332       if ($cpath == ""){
333         $cpath= $element;
334       } else {
335         $cpath= $element.','.$cpath;
336       }
338       if (isset($this->ACL[$cpath])){
340         /* Inspect this ACL, place the result into ACL */
341         foreach ($this->ACL[$cpath] as $subacl){
343           /* Reset? Just clean the ACL and turn over to the next one... */
344           if ($subacl['type'] == 'reset'){
345             $acl= $this->cleanACL($acl, TRUE);
346             continue;
347           }
349           if($subacl['type'] == "role") {
350             echo "role skipped";
351             continue;
352           }
354           /* With user filter */
355           if (isset($subacl['filter']) && !empty($subacl['filter'])){
356             $id = $dn."-".$subacl['filter'];
357             if(!isset($ACL_CACHE[$id])){
358               $ACL_CACHE[$id] = $ldap->object_match_filter($dn,$subacl['filter']);
359             }elseif(!$ACL_CACHE[$id]){
360               continue;
361             }
362           }
364           /* Self ACLs? 
365            */
366           if(isset($subacl['acl'][$object][0]) && preg_match("/s/",$subacl['acl'][$object][0]) && $dn != $this->dn){
367             continue;
368           }
370           /* If attribute is "", we want to know, if we've *any* permissions here... 
371               Merge global class ACLs [0] with attributes specific ACLs [attribute].
372            */
373           if ($attribute == "" && isset($subacl['acl'][$object])){
374             foreach($subacl['acl'][$object] as $attr => $dummy){
375               $acl= $this->mergeACL($acl, $subacl['type'], $subacl['acl'][$object][$attr]);
376             }
377             continue;
378           }
380           /* Per attribute ACL? */
381           if (isset($subacl['acl'][$object][$attribute])){
382             $acl= $this->mergeACL($acl, $subacl['type'], $subacl['acl'][$object][$attribute]);
383             continue;
384           }
386           /* Per object ACL? */
387           if (isset($subacl['acl'][$object][0])){
388             $acl= $this->mergeACL($acl, $subacl['type'], $subacl['acl'][$object][0]);
389             continue;
390           }
392           /* Global ACL? */
393           if (isset($subacl['acl']['all'][0])){
394             $acl= $this->mergeACL($acl, $subacl['type'], $subacl['acl']['all'][0]);
395             continue;
396           }
397         }
398       }
399     }
401     /* If the requested ACL is for a container object, then alter 
402         ACLs by applying cleanACL a last time.
403      */
404     if(isset($this->config->idepartments[$dn])){
405       $acl = $this->cleanACL($acl);
406     }
408     /* Assemble string */
409     $ret= "";
410     foreach ($acl as $key => $value){
411       if ($value !== ""){
412         $ret.= $key;
413       }
414     }
416     $ACL_CACHE["$dn+$object+$attribute"]= $ret;
418     /* Remove write if needed */
419     if ($skip_write){
420       $ret= preg_replace('/w/', '', $ret);
421     }
422     return ($ret);
423   }
426   /* Extract all departments that are accessible (direct or 'on the way' to an
427      accessible department) */
428   function get_module_departments($module, $skip_self_acls = FALSE )
429   {
430     /* If we are forced to skip ACLs checks for the current user 
431         then return all departments as valid.
432      */
433     if($this->ignore_acl_for_current_user()){
434       return(array_keys($this->config->idepartments));
435     }
437     /* Use cached results if possilbe */
438     $ACL_CACHE = session::get('ACL_CACHE');
440     if(!is_array($module)){
441       $module = array($module);
442     }
444     global $plist;
445     $res = array();
446     foreach($module as $mod){
447       if(isset($ACL_CACHE['MODULE_DEPARTMENTS'][$mod])){
448         $res = array_merge($res,$ACL_CACHE['MODULE_DEPARTMENTS'][$mod]);
449         continue;
450       }
452       $deps = array();
454       /* Search for per object ACLs */
455       foreach($this->ACL as $dn => $infos){
456         foreach($infos as $info){
457           $found = FALSE;
458           foreach($info['acl'] as $cat => $data){
460             /* Skip self acls? */
461             if($skip_self_acls && isset($data['0']) && strpos($data['0'], "s")) continue;
462             if(preg_match("/^".normalizePreg($mod)."/",$cat)){
463               $found =TRUE;
464               break;
465             }
466           } 
468           if($found && !isset($this->config->idepartments[$dn])){
469             while(!isset($this->config->idepartments[$dn]) && strpos($dn, ",")){
470               $dn = preg_replace("/^[^,]+,/","",$dn);
471             }
472             if(isset($this->config->idepartments[$dn])){
473               $deps[] = $dn;
474             }
475           }
476         }
477       }
479       /* For all gosaDepartments */
480       foreach ($this->config->departments as $dn){
481         if(in_array($dn,$deps)) continue;
482         $acl = "";
483         if(strpos($mod, '/')){
484           $acl.=  $this->get_permissions($dn,$mod);
485         }else{
486           $acl.=  $this->get_category_permissions($dn,$mod,TRUE);
487         }
488         if(!empty($acl)) {
489           $deps[] = $dn;
490         }
491       }
493       $ACL_CACHE['MODULE_DEPARTMENTS'][$mod] = $deps;
494       session::set('ACL_CACHE',$ACL_CACHE);
495       
496       $res = array_merge($res,$deps);
497     } 
498     return ($res);
499   }
502   function mergeACL($acl, $type, $newACL)
503   {
504                 $at= array("psub" => "p", "sub" => "s", "one" => "1");
506     if (strpos($newACL, 'w') !== FALSE && strpos($newACL, 'r') === FALSE){
507       $newACL .= "r";
508     }
510                 /* Ignore invalid characters */
511                 $newACL= preg_replace('/[^rwcdm]/', '', $newACL);
513     foreach(str_split($newACL) as $char){
515       /* Skip permanent and subtree entries */
516       if (preg_match('/[sp]/', $acl[$char])){
517         continue;
518       }
520                         if ($type == "base" && $acl[$char] != 1) {
521                                 $acl[$char]= 0;
522                         } else {
523         $acl[$char]= $at[$type];
524                         }
525     }
527     return ($acl);
528   }
531   function cleanACL($acl, $reset= FALSE)
532   {
533     foreach ($acl as $key => $value){
535       /* Continue, if value is empty or permanent */
536       if ($value == "" || $value == "p") {
537             continue;
538       }
540       /* Reset removes everything but 'p' */
541       if ($reset && $value != 'p'){
542         $acl[$key]= "";
543         continue;
544       }
546       /* Decrease tree level */
547       if (is_int($value)){
548         if ($value){
549           $acl[$key]--;
550         } else {
551           $acl[$key]= "";
552         }
553       }
554     }
556     return ($acl);
557   }
560   /* #FIXME This could be logical wrong or could be optimized in the future
561      Return combined acls for a given category. 
562      All acls will be combined like boolean AND 
563       As example ('rwcdm' + 'rcd' + 'wrm'= 'r') 
564     
565      Results will be cached in $this->result_cache.
566       $this->result_cache will be resetted if load_acls is called.
567   */
568   function has_complete_category_acls($dn,$category)
569   {
570     $acl    = "rwcdm";
571     $types  = "rwcdm";
573     if(!is_string($category)){
574       trigger_error("category must be string");   
575       $acl = "";
576     }else{
577       if(!isset($this->result_cache['has_complete_category_acls'][$dn][$category]))   {
578         if (isset($this->ocMapping[$category])){
579           foreach($this->ocMapping[$category] as $oc){
581             /* Skip objectClass '0' (e.g. users/0) get_permissions will ever return '' ??  */
582             if($oc == "0") continue;
583             $tmp =  $this->get_permissions($dn, $category."/".$oc);
584             for($i = 0 ; $i < strlen($types); $i++) {
585               if(!preg_match("/".$types[$i]."/",$tmp)){ 
586                 $acl = preg_replace("/".$types[$i]."/","",$acl);
587               }
588             }
589           }
590         }else{
591           trigger_error("Invalid type of category ".$category);
592           $acl = "";
593         }
594         $this->result_cache['has_complete_category_acls'][$dn][$category] = $acl;
595       }else{
596         $acl = $this->result_cache['has_complete_category_acls'][$dn][$category];
597       }
598     }
599     return($acl);
600   }
602  
603   /*! \brief  Returns TRUE if the current user is configured in IGNORE_ACL=".." in your gosa.conf 
604       @param  Return Boolean TRUE if we have to skip ACL checks else FALSE.
605    */ 
606   function ignore_acl_for_current_user()
607   {
608     return($this->ignoreACL);
609   }
613 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
614 ?>