FusionDirectory
class_userinfo.inc
Go to the documentation of this file.
1 <?php
2 /*
3  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
4 
5  Copyright (C) 2003-2010 Cajus Pollmeier
6  Copyright (C) 2011-2020 FusionDirectory
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
21 */
22 
28 /* Define shadow states */
29 define('POSIX_ACCOUNT_EXPIRED', 1);
30 define('POSIX_WARN_ABOUT_EXPIRATION', 2);
31 define('POSIX_FORCE_PASSWORD_CHANGE', 4);
32 define('POSIX_DISALLOW_PASSWORD_CHANGE', 8);
33 
40 class userinfo
41 {
42  var $dn;
43  var $cn;
44  var $uid;
45  var $sn = '';
46  var $givenName = '';
47  var $gidNumber = -1;
48  var $language = "";
49  var $groups = [];
50  var $roles = [];
51  var $mail = '';
52 
54  protected $cachedAttrs = [];
55 
56  protected $result_cache = [];
57  protected $ignoreACL = FALSE;
58  protected $ACL = [];
59  protected $ACLperPath = [];
60 
62  protected $sizeLimitHandler;
63 
65  protected $currentBase;
66 
68  protected $forcePasswordChange = FALSE;
69 
70  function __construct ($userdn)
71  {
72  global $config;
73  $this->dn = $userdn;
74  $this->ignoreACL = ($config->get_cfg_value('ignoreAcl') == $this->dn);
75 
76  $this->loadLDAPInfo();
77 
78  /* Initialize ACL_CACHE */
79  $this->reset_acl_cache();
80 
81  $this->sizeLimitHandler = new ldapSizeLimit();
82  }
83 
85  function loadLDAPInfo ()
86  {
87  global $config;
88  $ldap = $config->get_ldap_link();
89  $ldap->cat($this->dn, ['*']);
90  $attrs = $ldap->fetch(TRUE);
91  if (!$ldap->success()) {
92  throw new FusionDirectoryLdapError($this->dn, LDAP_SEARCH, $ldap->get_error(), $ldap->get_errno());
93  }
94 
95  $this->uid = $attrs['uid'][0];
96 
97  if (isset($attrs['cn'][0])) {
98  $this->cn = $attrs['cn'][0];
99  } elseif (isset($attrs['givenName'][0]) && isset($attrs['sn'][0])) {
100  $this->cn = $attrs['givenName'][0].' '.$attrs['sn'][0];
101  } else {
102  $this->cn = $attrs['uid'][0];
103  }
104  if (isset($attrs['gidNumber'][0])) {
105  $this->gidNumber = $attrs['gidNumber'][0];
106  }
107  if (isset($attrs['sn'][0])) {
108  $this->sn = $attrs['sn'][0];
109  }
110  if (isset($attrs['givenName'][0])) {
111  $this->givenName = $attrs['givenName'][0];
112  }
113  if (isset($attrs['mail'][0])) {
114  $this->mail = $attrs['mail'][0];
115  }
116 
117  /* Assign user language */
118  if (isset($attrs['preferredLanguage'][0])) {
119  $this->language = $attrs['preferredLanguage'][0];
120  }
121 
122  $this->cachedAttrs = $attrs;
123  }
124 
128  public function reset_acl_cache ()
129  {
130  /* Initialize ACL_CACHE */
131  session::set('ACL_CACHE', []);
132  }
133 
137  function loadACL ()
138  {
139  global $config, $plist;
140 
141  $this->ACL = [];
142  $this->groups = [];
143  $this->roles = [];
144  $this->result_cache = [];
145  $this->reset_acl_cache();
146  $ldap = $config->get_ldap_link();
147  $ldap->cd($config->current['BASE']);
148  $targetFilterLimit = $config->get_cfg_value('AclTargetFilterLimit', 100);
149 
150  /* Get member groups... */
151  $ldap->search('(&(objectClass=groupOfNames)(member='.ldap_escape_f($this->dn).'))', ['dn']);
152  while ($attrs = $ldap->fetch()) {
153  $this->groups[$attrs['dn']] = $attrs['dn'];
154  }
155 
156  /* Get member POSIX groups... */
157  $ldap->search('(&(objectClass=posixGroup)(memberUid='.ldap_escape_f($this->uid).'))', ['dn']);
158  while ($attrs = $ldap->fetch()) {
159  $this->groups[$attrs['dn']] = $attrs['dn'];
160  }
161 
162  /* Get member roles... */
163  $ldap->search('(&(objectClass=organizationalRole)(roleOccupant='.ldap_escape_f($this->dn).'))', ['dn']);
164  while ($attrs = $ldap->fetch()) {
165  $this->roles[$attrs['dn']] = $attrs['dn'];
166  }
167 
168  /* Crawl through ACLs and move relevant to the tree */
169  $ldap->search('(objectClass=gosaACL)', ['dn', 'gosaAclEntry']);
170  $ACLsContent = [];
171  while ($attrs = $ldap->fetch()) {
172 
173  /* Insert links in ACL array */
174  $mergedAcls = [];
175  for ($i = 0; $i < $attrs['gosaAclEntry']['count']; $i++) {
176  $mergedAcls = array_merge($mergedAcls, acl::explodeACL($attrs['gosaAclEntry'][$i]));
177  }
178  $ACLsContent[$attrs['dn']] = $mergedAcls;
179  }
180 
181  $ACLsContentResolved = [];
182 
183  /* Resolve roles here */
184  foreach ($ACLsContent as $dn => $ACLRules) {
185  foreach ($ACLRules as $ACLRule) {
186  $ldap->cat($ACLRule['acl'], ['gosaAclTemplate']);
187  $attrs = $ldap->fetch();
188 
189  if (!isset($attrs['gosaAclTemplate'])) {
190  continue;
191  }
192 
193  $interesting = FALSE;
194 
195  /* Inspect members... */
196  foreach (array_keys($ACLRule['members']) as $member) {
197  /* Wildcard? */
198  if ($member === 'G:*') {
199  $interesting = TRUE;
200  break;
201  }
202 
203  list($memberType, $memberDn) = explode(':', $member, 2);
204  switch ($memberType) {
205  case 'G':
206  if (in_array_ics($memberDn, $this->groups)) {
207  $interesting = TRUE;
208  break 2;
209  }
210  break;
211  case 'R':
212  if (in_array_ics($memberDn, $this->roles)) {
213  $interesting = TRUE;
214  break 2;
215  }
216  break;
217  case 'U':
218  if (mb_strtolower($memberDn) === mb_strtolower($this->dn)) {
219  $interesting = TRUE;
220  break 2;
221  }
222  break;
223  default:
224  throw new FusionDirectoryException('Unknown ACL member type '.$memberType);
225  }
226  }
227 
228  if (!$interesting) {
229  continue;
230  }
231 
232  if (!empty($ACLRule['userfilter']) && !$ldap->object_match_filter($this->dn, $ACLRule['userfilter'])) {
233  /* We do not match the user filter */
234  continue;
235  }
236 
237  if (!empty($ACLRule['targetfilter'])) {
238  $ldap->cd($dn);
239  $ldap->set_size_limit($targetFilterLimit);
240  $targetFilter = templateHandling::parseString($ACLRule['targetfilter'], $this->cachedAttrs, 'ldap_escape_f');
241  $ldap->search($targetFilter, ['dn']);
242  if ($ldap->hitSizeLimit()) {
243  $error = new FusionDirectoryError(
244  htmlescape(sprintf(
245  _('An ACL assignment for the connected user matched more than than the %d objects limit. This user will not have the ACL rights he should.'),
246  $targetFilterLimit
247  ))
248  );
249  $error->display();
250  }
251  $targetDns = [];
252  while ($targetAttrs = $ldap->fetch()) {
253  $targetDns[] = $targetAttrs['dn'];
254  }
255  $ldap->set_size_limit(0);
256  } else {
257  $targetDns = [$dn];
258  }
259 
260  $roleAcls = acl::explodeRole($attrs['gosaAclTemplate']);
261  foreach ($roleAcls as $roleAcl) {
262  foreach ($targetDns as $targetDn) {
263  $ACLsContentResolved[$targetDn][] = [
264  'acl' => $roleAcl,
265  'type' => $ACLRule['type'],
266  'members' => $ACLRule['members'],
267  ];
268  }
269  }
270  }
271  }
272 
273  /* Sort by tree depth */
274  uksort(
275  $ACLsContentResolved,
276  function ($dn1, $dn2)
277  {
278  return substr_count($dn1, ',') <=> substr_count($dn2, ',');
279  }
280  );
281 
282  /* Insert in $this->ACL */
283  foreach ($ACLsContentResolved as $dn => $ACLRules) {
284  foreach ($ACLRules as $idx => $ACLRule) {
285  if (!isset($this->ACL[$dn])) {
286  $this->ACL[$dn] = [];
287  }
288  $this->ACL[$dn][$idx] = $ACLRule;
289  }
290  }
291 
292  /* Create an array which represent all relevant permissions settings
293  per dn.
294 
295  The array will look like this:
296 
297  . ['ou=base'] ['ou=base'] = array(ACLs);
298  .
299  . ['ou=dep1,ou=base']['ou=dep1,ou=base'] = array(ACLs);
300  . ['ou=base'] = array(ACLs);
301 
302 
303  For objects located in 'ou=dep1,ou=base' we have to apply both ACLs,
304  for objects in 'ou=base' we only have to apply one ACL.
305  */
306  $all_acl = [];
307  foreach ($this->ACL as $dn => $acl) {
308  $all_acl[$dn][$dn] = $acl;
309  $sdn = $dn;
310  while (strpos($dn, ',') !== FALSE) {
311  $dn = preg_replace('/^[^,]*+,/', '', $dn);
312  if (isset($this->ACL[$dn])) {
313  $all_acl[$sdn][$dn] = array_filter(
314  $this->ACL[$dn],
315  function ($ACLInfos)
316  {
317  return ($ACLInfos['type'] === 'subtree');
318  }
319  );
320  }
321  }
322  }
323  $this->ACLperPath = $all_acl;
324 
325  /* Append Self entry */
326  $dn = $this->dn;
327  while (strpos($dn, ',') && !isset($all_acl[$dn])) {
328  $dn = preg_replace('/^[^,]*+,/', '', $dn);
329  }
330  if (isset($all_acl[$dn])) {
331  $this->ACLperPath[$this->dn] = $all_acl[$dn];
332  if ($dn !== $this->dn) {
333  $this->ACLperPath[$this->dn][$dn] = array_filter(
334  $this->ACLperPath[$this->dn][$dn],
335  function ($ACLInfos)
336  {
337  return ($ACLInfos['type'] === 'subtree');
338  }
339  );
340  }
341  }
342 
343  /* Reset plist menu and ACL cache if needed */
344  if (is_object($plist)) {
345  $plist->resetCache();
346  }
347  }
348 
355  {
356  return array_keys($this->ACLperPath);
357  }
358 
368  function get_category_permissions ($dn, $category)
369  {
370  return $this->get_permissions($dn, $category.'/0', '');
371  }
372 
373 
383  function is_copyable ($dn, $object): bool
384  {
385  return (strpos($this->get_complete_category_acls($dn, $object), 'r') !== FALSE);
386  }
387 
388 
400  function is_cutable ($dn, $object, $class): bool
401  {
402  $remove = (strpos($this->get_permissions($dn, $object.'/'.$class), 'd') !== FALSE);
403  $read = (strpos($this->get_complete_category_acls($dn, $object), 'r') !== FALSE);
404  return ($remove && $read);
405  }
406 
407 
417  function is_pasteable ($dn, $object): bool
418  {
419  return (strpos($this->get_complete_category_acls($dn, $object), 'w') !== FALSE);
420  }
421 
422 
434  function allow_snapshot_restore ($dn, $categories, $deleted): bool
435  {
436  $permissions = $this->get_snapshot_permissions($dn, $categories);
437  return in_array(($deleted ? 'restore_deleted' : 'restore_over'), $permissions);
438  }
439 
440 
450  function allow_snapshot_create ($dn, $categories): bool
451  {
452  $permissions = $this->get_snapshot_permissions($dn, $categories);
453  return in_array('c', $permissions);
454  }
455 
456 
466  function allow_snapshot_delete ($dn, $categories): bool
467  {
468  $permissions = $this->get_snapshot_permissions($dn, $categories);
469  return in_array('d', $permissions);
470  }
471 
472  function get_snapshot_permissions ($dn, $categories)
473  {
474  if (!is_array($categories)) {
475  $categories = [$categories];
476  }
477  /* Possible permissions for snapshots */
478  $objectPermissions = ['r', 'c', 'd'];
479  $attributePermissions = ['restore_over', 'restore_deleted'];
480  foreach ($categories as $category) {
481  $acl = $this->get_permissions($dn, $category.'/SnapshotHandler');
482  foreach ($objectPermissions as $i => $perm) {
483  if (strpos($acl, $perm) === FALSE) {
484  unset($objectPermissions[$i]);
485  }
486  }
487  foreach ($attributePermissions as $i => $attribute) {
488  $acl = $this->get_permissions($dn, $category.'/SnapshotHandler', $attribute);
489  if (strpos($acl, 'w') === FALSE) {
490  unset($attributePermissions[$i]);
491  }
492  }
493  }
494  return array_merge($objectPermissions, $attributePermissions);
495  }
496 
509  function get_permissions ($dn, $object, $attribute = '', $skip_write = FALSE)
510  {
511  global $config;
512  /* If we are forced to skip ACLs checks for the current user
513  then return all permissions.
514  */
515  if ($this->ignore_acl_for_current_user()) {
516  if ($skip_write) {
517  return 'r';
518  }
519  return 'rwcdm';
520  }
521 
522  $attribute = static::sanitizeAttributeName($attribute);
523 
524  /* Push cache answer? */
525  $ACL_CACHE = &session::get_ref('ACL_CACHE');
526  if (isset($ACL_CACHE["$dn+$object+$attribute"])) {
527  $ret = $ACL_CACHE["$dn+$object+$attribute"];
528  if ($skip_write) {
529  $ret = str_replace(['w','c','d','m'], '', $ret);
530  }
531  return $ret;
532  }
533 
534  /* Detect the set of ACLs we have to check for this object */
535  $parentACLdn = $dn;
536  while (!isset($this->ACLperPath[$parentACLdn]) && (strpos($parentACLdn, ',') !== FALSE)) {
537  $parentACLdn = preg_replace('/^[^,]*+,/', '', $parentACLdn);
538  }
539  if (!isset($this->ACLperPath[$parentACLdn])) {
540  $ACL_CACHE["$dn+$object+$attribute"] = '';
541  return '';
542  }
543 
544  if (($parentACLdn !== $dn) && isset($ACL_CACHE["sub:$parentACLdn+$object+$attribute"])) {
545  /* Load parent subtree ACLs from cache */
546  $permissions = $ACL_CACHE["sub:$parentACLdn+$object+$attribute"];
547  } else {
548  $permissions = new ACLPermissions();
549 
550  /* Merge relevent permissions from parent ACLs */
551  foreach ($this->ACLperPath[$parentACLdn] as $parentdn => $ACLs) {
552  /* Inspect this ACL, place the result into permissions */
553  foreach ($ACLs as $subacl) {
554  if ($permissions->isFull()) {
555  /* Stop merging if we have all rights already */
556  break 2;
557  }
558 
559  if (($dn != $this->dn) && isset($subacl['acl'][$object][0]) && ($subacl['acl'][$object][0]->isSelf())) {
560  /* Self ACL */
561  continue;
562  }
563 
564  if (($subacl['type'] === 'base') && ($parentdn !== $dn)) {
565  /* Base assignment on another dn */
566  continue;
567  }
568 
569  /* Special global ACL */
570  if (isset($subacl['acl']['all'][0])) {
571  $permissions->merge($subacl['acl']['all'][0]);
572  }
573 
574  /* Category ACLs (e.g. $object = "user/0") */
575  if (strstr($object, '/0')) {
576  $ocs = preg_replace("/\/0$/", '', $object);
577  if (isset($config->data['CATEGORIES'][$ocs]) && ($attribute == '')) {
578  foreach ($config->data['CATEGORIES'][$ocs]['classes'] as $oc) {
579  if (isset($subacl['acl'][$ocs.'/'.$oc])) {
580  if (($dn != $this->dn) &&
581  isset($subacl['acl'][$ocs.'/'.$oc][0]) &&
582  ($subacl['acl'][$ocs.'/'.$oc][0]->isSelf())) {
583  /* Skip self ACL */
584  continue;
585  }
586 
587  foreach ($subacl['acl'][$ocs.'/'.$oc] as $anyPermissions) {
588  $permissions->merge($anyPermissions);
589  }
590  }
591  }
592  }
593  continue;
594  }
595 
596  /* If attribute is "", we want to know, if we've *any* permissions here...
597  Merge global class ACLs [0] with attributes specific ACLs [attribute].
598  */
599  if (($attribute == '') && isset($subacl['acl'][$object])) {
600  foreach ($subacl['acl'][$object] as $anyPermissions) {
601  $permissions->merge($anyPermissions);
602  }
603  continue;
604  }
605 
606  /* Per attribute ACL? */
607  if (isset($subacl['acl'][$object][$attribute])) {
608  $permissions->merge($subacl['acl'][$object][$attribute]);
609  }
610 
611  /* Per object ACL? */
612  if (isset($subacl['acl'][$object][0])) {
613  $permissions->merge($subacl['acl'][$object][0]);
614  }
615  }
616  }
617  }
618 
619  if ($parentACLdn !== $dn) {
620  $ACL_CACHE["sub:$parentACLdn+$object+$attribute"] = $permissions;
621  }
622  $ACL_CACHE["$dn+$object+$attribute"] = $permissions;
623 
624  /* Remove write if needed */
625  return $permissions->toString($skip_write);
626  }
627 
640  function get_module_departments ($module, bool $skip_self_acls = FALSE): array
641  {
642  global $config;
643  /* If we are forced to skip ACLs checks for the current user
644  then return all departments as valid.
645  */
646  if ($this->ignore_acl_for_current_user()) {
647  return array_values($config->getDepartmentList());
648  }
649 
650  /* Use cached results if possilbe */
651  $ACL_CACHE = &session::get_ref('ACL_CACHE');
652 
653  if (!is_array($module)) {
654  $module = [$module];
655  }
656 
657  $departmentInfo = $config->getDepartmentInfo();
658 
659  $res = [];
660  foreach ($module as $mod) {
661  if (isset($ACL_CACHE['MODULE_DEPARTMENTS'][$mod])) {
662  $res = array_merge($res, $ACL_CACHE['MODULE_DEPARTMENTS'][$mod]);
663  continue;
664  }
665 
666  $deps = [];
667 
668  /* Search for per object ACLs */
669  foreach ($this->ACL as $dn => $infos) {
670  foreach ($infos as $info) {
671  $found = FALSE;
672  foreach ($info['acl'] as $cat => $data) {
673  /* Skip self acls? */
674  if ($skip_self_acls && isset($data[0]) && $data[0]->isSelf()) {
675  continue;
676  }
677  if (preg_match('/^'.preg_quote($mod, '/').'/', $cat) || ($cat === 'all')) {
678  /* $cat starts with $mod (example: cat=user/user and mod=user) or cat is 'all' */
679  $found = TRUE;
680  break;
681  }
682  }
683 
684  if ($found && !isset($departmentInfo[$dn])) {
685  while (!isset($departmentInfo[$dn]) && strpos($dn, ',')) {
686  $dn = preg_replace("/^[^,]+,/", "", $dn);
687  }
688  if (isset($departmentInfo[$dn])) {
689  $deps[$dn] = $dn;
690  }
691  }
692  }
693  }
694 
695  /* For all departments */
696  $departments = $config->getDepartmentList();
697  foreach ($departments as $dn) {
698  if (isset($deps[$dn])) {
699  continue;
700  }
701  $acl = '';
702  if (strpos($mod, '/')) {
703  $acl .= $this->get_permissions($dn, $mod);
704  } else {
705  $acl .= $this->get_category_permissions($dn, $mod);
706  }
707  if (!empty($acl)) {
708  $deps[$dn] = $dn;
709  }
710  }
711 
712  $ACL_CACHE['MODULE_DEPARTMENTS'][$mod] = $deps;
713  $res = array_merge($res, $deps);
714  }
715 
716  return array_values($res);
717  }
718 
735  function get_complete_category_acls ($dn, $category)
736  {
737  global $config;
738 
739  if (!is_string($category)) {
740  trigger_error('category must be string');
741  return '';
742  } else {
743  if (isset($this->result_cache['get_complete_category_acls'][$dn][$category])) {
744  return $this->result_cache['get_complete_category_acls'][$dn][$category];
745  }
746  $acl = 'rwcdm';
747  if (isset($config->data['CATEGORIES'][$category])) {
748  foreach ($config->data['CATEGORIES'][$category]['classes'] as $oc) {
749  if ($oc == '0') {
750  /* Skip objectClass '0' (e.g. user/0) */
751  continue;
752  }
753  $tmp = $this->get_permissions($dn, $category.'/'.$oc);
754  $types = $acl;
755  for ($i = 0, $l = strlen($types); $i < $l; $i++) {
756  if (strpos($tmp, $types[$i]) === FALSE) {
757  $acl = str_replace($types[$i], '', $acl);
758  }
759  }
760  }
761  } else {
762  $acl = '';
763  }
764  $this->result_cache['get_complete_category_acls'][$dn][$category] = $acl;
765  return $acl;
766  }
767  }
768 
769 
777  {
778  return $this->ignoreACL;
779  }
780 
802  function expired_status ()
803  {
804  global $config;
805 
806  if ($this->forcePasswordChange) {
807  return POSIX_FORCE_PASSWORD_CHANGE;
808  }
809 
810  // Skip this for the admin account, we do not want to lock him out.
811  if ($this->is_user_admin()) {
812  return 0;
813  }
814 
815  $ldap = $config->get_ldap_link();
816 
817  if (class_available('ppolicyAccount')) {
818  try {
819  list($policy, $attrs) = user::fetchPpolicy($this->dn);
820  if (
821  isset($policy['pwdExpireWarning'][0]) &&
822  isset($policy['pwdMaxAge'][0]) &&
823  isset($attrs['pwdChangedTime'][0])
824  ) {
825  $now = new DateTime('now', timezone::utc());
826  $pwdExpireWarningSeconds = intval($policy['pwdExpireWarning'][0]);
827  $maxAge = $policy['pwdMaxAge'][0];
828  /* Build expiration date from pwdChangedTime and max age */
829  $expDate = LdapGeneralizedTime::fromString($attrs['pwdChangedTime'][0]);
830  $expDate->setTimezone(timezone::utc());
831  $expDate->add(new DateInterval('PT'.$maxAge.'S'));
832  if ($expDate->getTimeStamp() < ($now->getTimeStamp() + $pwdExpireWarningSeconds)) {
833  return POSIX_WARN_ABOUT_EXPIRATION;
834  }
835  }
836  } catch (NonExistingLdapNodeException $e) {
837  /* ppolicy not found in LDAP */
838  }
839  }
840 
841  if ($config->get_cfg_value('handleExpiredAccounts') != 'TRUE') {
842  return 0;
843  }
844 
845  $ldap->cd($config->current['BASE']);
846  $ldap->cat($this->dn);
847  $attrs = $ldap->fetch();
848  $current = floor(date("U") / 60 / 60 / 24);
849 
850  // Fetch required attributes
851  foreach (['shadowExpire','shadowLastChange','shadowMax','shadowMin',
852  'shadowInactive','shadowWarning','sambaKickoffTime'] as $attr) {
853  $$attr = (isset($attrs[$attr][0]) ? $attrs[$attr][0] : NULL);
854  }
855 
856  // Check if the account has reached its kick off limitations.
857  // ----------------------------------------------------------
858  // Once the accout reaches the kick off limit it has expired.
859  if (($sambaKickoffTime !== NULL) && (time() >= $sambaKickoffTime)) {
860  return POSIX_ACCOUNT_EXPIRED;
861  }
862 
863  // Check if the account has expired.
864  // ---------------------------------
865  // An account is locked/expired once its expiration date was reached (shadowExpire).
866  // If the optional attribute (shadowInactive) is set, we've to postpone
867  // the account expiration by the amount of days specified in (shadowInactive).
868  // ShadowInactive specifies an amount of days we've to reprieve the user.
869  // It some kind of x days' grace.
870  if (($shadowExpire != NULL) && ($shadowExpire <= $current)
871  && (($shadowInactive == NULL) || ($current > $shadowExpire + $shadowInactive))) {
872  return POSIX_ACCOUNT_EXPIRED;
873  }
874 
875  // The users password is going to expire.
876  // --------------------------------------
877  // We've to warn the user in the case of an expiring account.
878  // An account is going to expire when it reaches its expiration date (shadowExpire).
879  // The user has to be warned, if the days left till expiration, match the
880  // configured warning period (shadowWarning)
881  // --> shadowWarning: Warn x days before account expiration.
882  // Check if the account is still active and not already expired.
883  // Check if we've to warn the user by comparing the remaining
884  // number of days till expiration with the configured amount of days in shadowWarning.
885  if (($shadowExpire != NULL) && ($shadowWarning != NULL)
886  && ($shadowExpire >= $current) && ($shadowExpire <= $current + $shadowWarning)) {
887  return POSIX_WARN_ABOUT_EXPIRATION;
888  }
889 
890  // -- I guess this is the correct detection, isn't it?
891  if (($shadowLastChange != NULL) && ($shadowWarning != NULL) && ($shadowMax != NULL)) {
892  $daysRemaining = ($shadowLastChange + $shadowMax) - $current;
893  if ($daysRemaining > 0 && $daysRemaining <= $shadowWarning) {
894  return POSIX_WARN_ABOUT_EXPIRATION;
895  }
896  }
897 
898  // Check if we've to force the user to change his password.
899  // --------------------------------------------------------
900  // A password change is enforced when the password is older than
901  // the configured amount of days (shadowMax).
902  // The age of the current password (shadowLastChange) plus the maximum
903  // amount amount of days (shadowMax) has to be smaller than the
904  // current timestamp.
905  // Check if we've an outdated password.
906  if (($shadowLastChange != NULL) && ($shadowMax != NULL)
907  && ($current >= $shadowLastChange + $shadowMax)) {
908  return POSIX_FORCE_PASSWORD_CHANGE;
909  }
910 
911  // Check if we've to freeze the users password.
912  // --------------------------------------------
913  // Once a user has changed his password, he cannot change it again
914  // for a given amount of days (shadowMin).
915  // We should not allow to change the password within FusionDirectory too.
916  // Check if we've an outdated password.
917  if (($shadowLastChange != NULL) && ($shadowMin != NULL)
918  && ($shadowLastChange + $shadowMin >= $current)) {
919  return POSIX_DISALLOW_PASSWORD_CHANGE;
920  }
921 
922  return 0;
923  }
924 
925  /* \brief Check if a user is a 'user admin'
926  */
927  function is_user_admin ()
928  {
929  global $config;
930  if (empty($this->ACLperPath)) {
931  $this->loadACL();
932  }
933  return ($this->get_permissions($config->current['BASE'], 'user/user') == 'rwcdm');
934  }
935 
936  /* \brief Test if a plugin is blacklisted for this user (does not show up in the menu)
937  */
938  function isBlacklisted ($plugin)
939  {
940  global $config;
941  $blacklist = $config->get_cfg_value('PluginsMenuBlacklist', []);
942  foreach ($blacklist as $item) {
943  list ($group, $p) = explode('|', $item, 2);
944  if (($plugin == $p) && (in_array($group, $this->groups) || in_array($group, $this->roles))) {
945  return TRUE;
946  }
947  }
948 
949  return FALSE;
950  }
951 
952  /* \brief Search which ACL category should be used for this attribute and this object type, if any
953  *
954  * \return The ACL category, or FALSE if not found, or TRUE if acl check should be bypassed
955  */
956  function getAttributeCategory ($type, $attribute)
957  {
958  global $config;
959 
960  if (in_array_ics($attribute, ['objectClass', 'dn'])) {
961  return TRUE;
962  }
963 
964  $attribute = static::sanitizeAttributeName($attribute);
965 
966  if (is_array($type)) {
967  /* Used for recursion through subtabs */
968  $prefix = '';
969  $tabs = $type;
970  } else {
971  /* Usual workflow */
972  $infos = objects::infos($type);
973  $prefix = $infos['aclCategory'].'/';
974  $tabs = $infos['tabClass']::getPotentialTabList($type, $infos);
975  }
976  foreach ($tabs as $tab) {
977  $acls = pluglist::pluginInfos($tab['CLASS'])['plProvidedAcls'];
978  if (isset($acls[$attribute])) {
979  return $prefix.$tab['CLASS'];
980  }
981  if (isset($tab['SUBTABS'])) {
982  $acl = $this->getAttributeCategory($config->data['TABS'][$tab['SUBTABS']], $attribute);
983  if ($acl !== FALSE) {
984  return $prefix.$acl;
985  }
986  }
987  }
988  return FALSE;
989  }
990 
991  function getSizeLimitHandler ()
992  {
994  }
995 
996  /* \brief Returns the base this user is stored in
997  */
998  function getBase ()
999  {
1000  return get_base_from_people($this->dn);
1001  }
1002 
1003  /* \brief Returns the current base the user went to in management classes
1004  */
1005  function getCurrentBase ()
1006  {
1007  if (!empty($this->currentBase)) {
1008  return $this->currentBase;
1009  } else {
1010  return $this->getBase();
1011  }
1012  }
1013 
1014  /* \brief Sets the current base the user went to in management classes
1015  */
1016  function setCurrentBase ($base)
1017  {
1018  $this->currentBase = $base;
1019  }
1020 
1021  /* \brief Get ACL name or HTML id from attribute name
1022  */
1023  public static function sanitizeAttributeName ($name)
1024  {
1025  return preg_replace('/[\/\-,.#:;]/', '_', $name);
1026  }
1027 
1037  public static function getLdapUser (string $username)
1038  {
1039  global $config;
1040 
1041  /* look through the entire ldap */
1042  $ldap = $config->get_ldap_link();
1043  if (!$ldap->success()) {
1044  throw new FatalError(msgPool::ldaperror($ldap->get_error(FALSE), '', LDAP_AUTH));
1045  }
1046 
1047  $allowed_attributes = ['uid','mail'];
1048  $verify_attr = [];
1049  $tmp = explode(',', $config->get_cfg_value('loginAttribute'));
1050  foreach ($tmp as $attr) {
1051  if (in_array($attr, $allowed_attributes)) {
1052  $verify_attr[] = $attr;
1053  }
1054  }
1055 
1056  if (count($verify_attr) == 0) {
1057  $verify_attr = ['uid'];
1058  }
1059  $tmp = $verify_attr;
1060  $tmp[] = 'uid';
1061  $filter = '';
1062  foreach ($verify_attr as $attr) {
1063  $filter .= '('.$attr.'='.$username.')';
1064  }
1065  $filter = '(&(|'.$filter.')(objectClass=inetOrgPerson))';
1066  $ldap->cd($config->current['BASE']);
1067  $ldap->search($filter, $tmp);
1068 
1069  /* get results, only a count of 1 is valid */
1070  if ($ldap->count() == 0) {
1071  /* user not found */
1072  return FALSE;
1073  } elseif ($ldap->count() != 1) {
1074  /* found more than one matching id */
1075  return _('Login (uid) is not unique inside the LDAP tree. Please contact your administrator.');
1076  }
1077 
1078  /* LDAP schema is not case sensitive. Perform additional check. */
1079  $attrs = $ldap->fetch();
1080  $success = FALSE;
1081  foreach ($verify_attr as $attr) {
1082  if (isset($attrs[$attr][0]) && $attrs[$attr][0] == $username) {
1083  $success = TRUE;
1084  }
1085  }
1086  $ldap->disconnect();
1087  if (!$success) {
1088  return FALSE;
1089  }
1090 
1091  return new userinfo($attrs['dn']);
1092  }
1093 
1106  public static function loginUser (string $username, string $password): userinfo
1107  {
1108  global $config;
1109 
1110  $ui = static::getLdapUser($username);
1111 
1112  if ($ui === FALSE) {
1114  } elseif (is_string($ui)) {
1115  throw new LoginFailureException($ui);
1116  }
1117 
1118  /* password check, bind as user with supplied password */
1119  $ldapObj = new LDAP($ui->dn, $password, $config->current['SERVER'],
1120  isset($config->current['LDAPFOLLOWREFERRALS']) && ($config->current['LDAPFOLLOWREFERRALS'] == 'TRUE'),
1121  isset($config->current['LDAPTLS']) && ($config->current['LDAPTLS'] == 'TRUE')
1122  );
1123  $ldap = new ldapMultiplexer($ldapObj);
1124  if (!$ldap->success()) {
1125  if ($ldap->get_error(FALSE) == 'changeAfterReset') {
1126  $ui->forcePasswordChange = TRUE;
1127  } else {
1128  throw new LoginFailureException($ldap->get_error(FALSE));
1129  }
1130  }
1131 
1132  /* Username is set, load ACLs now */
1133  $ui->loadACL();
1134 
1135  return $ui;
1136  }
1137 }
htmlescape(string $str)
Escape string for HTML output.
Definition: php_setup.inc:32
in_array_ics($value, array $items)
Check if a value exists in an array (case-insensitive)
Definition: functions.inc:814
static getLdapUser(string $username)
Get user from LDAP directory.
get_category_permissions($dn, $category)
Get permissions by category.
get_complete_category_acls($dn, $category)
Return combined acls for a given category.
reset_acl_cache()
Reset acl cache.
ignore_acl_for_current_user()
Ignore acl for the current user.
get_module_departments($module, bool $skip_self_acls=FALSE)
Extract all departments that are accessible.
is_copyable($dn, $object)
Check if the given object (dn) is copyable.
$currentBase
Current management base.
loadLDAPInfo()
Loads user information from LDAP.
expired_status()
Checks the posixAccount status by comparing the shadow attributes.
static set($name, $value)
Set a value in a session.
Class ldapSizeLimit This class contains all informations and functions to handle the LDAP size limit ...
get_base_from_people($dn)
Return a base from a given user DN.
Definition: functions.inc:500
Error returned by an LDAP operation called from FusionDirectory.
static & get_ref($name)
Accessor of a session var by reference.
allow_snapshot_delete($dn, $categories)
Checks if we are allowed to delete a snapshot of the given dn.
static invalidCredentialsError()
Error text that must be returned for invalid user or password.
Definition: class_ldap.inc:174
$forcePasswordChange
Password change should be forced.
$cachedAttrs
LDAP attributes of this user at login.
loadACL()
Load an acl.
static loginUser(string $username, string $password)
Verify user login against LDAP directory.
is_cutable($dn, $object, $class)
Check if the given object (dn) is cutable.
allow_snapshot_restore($dn, $categories, $deleted)
Checks if we are allowed to restore a snapshot for the given dn.
Parent class for all exceptions thrown in FusionDirectory.
This class contains all ldap function needed to make ldap operations easy.
Definition: class_ldap.inc:34
Fatal error class. Does not extend FusionDirectoryError.
Class userinfo This class contains all informations and functions about user.
static explodeRole($role)
Explode a role.
Definition: class_acl.inc:54
static ldaperror($error, $dn='', $type=0, $plugin='')
Display LDAP error.
get_permissions($dn, $object, $attribute='', $skip_write=FALSE)
Get the permissions for a specified dn.
$sizeLimitHandler
LDAP size limit handler.
get_acl_target_objects()
Returns an array containing all target objects we&#39;ve permissions on.
static fromString($string, $useException=TRUE)
Convert from LDAP GeneralizedTime formatted string to DateTime object.
allow_snapshot_create($dn, $categories)
Checks if we are allowed to create a snapshot of the given dn.
Parent class for all errors in FusionDirectory.
static explodeACL($acl)
Explode an acl.
Definition: class_acl.inc:74
is_pasteable($dn, $object)
Checks if we are allowed to paste an object to the given destination ($dn)
static parseString(string $string, array $attrs, $escapeMethod=NULL, string $unique=NULL, string $target=NULL)
Parse template masks in a single string.
class_available($name)
Checks if a class is available.
Definition: functions.inc:92