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);
56 protected $result_cache = [];
57 protected $ignoreACL = FALSE;
59 protected $ACLperPath = [];
70 function __construct ($userdn)
74 $this->ignoreACL = ($config->get_cfg_value(
'ignoreAcl') == $this->dn);
88 $ldap = $config->get_ldap_link();
89 $ldap->cat($this->dn, [
'*']);
90 $attrs = $ldap->fetch(TRUE);
91 if (!$ldap->success()) {
95 $this->uid = $attrs[
'uid'][0];
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];
102 $this->cn = $attrs[
'uid'][0];
104 if (isset($attrs[
'gidNumber'][0])) {
105 $this->gidNumber = $attrs[
'gidNumber'][0];
107 if (isset($attrs[
'sn'][0])) {
108 $this->sn = $attrs[
'sn'][0];
110 if (isset($attrs[
'givenName'][0])) {
111 $this->givenName = $attrs[
'givenName'][0];
113 if (isset($attrs[
'mail'][0])) {
114 $this->mail = $attrs[
'mail'][0];
118 if (isset($attrs[
'preferredLanguage'][0])) {
119 $this->language = $attrs[
'preferredLanguage'][0];
122 $this->cachedAttrs = $attrs;
139 global $config, $plist;
144 $this->result_cache = [];
146 $ldap = $config->get_ldap_link();
147 $ldap->cd($config->current[
'BASE']);
148 $targetFilterLimit = $config->get_cfg_value(
'AclTargetFilterLimit', 100);
151 $ldap->search(
'(&(objectClass=groupOfNames)(member='.ldap_escape_f($this->dn).
'))', [
'dn']);
152 while ($attrs = $ldap->fetch()) {
153 $this->groups[$attrs[
'dn']] = $attrs[
'dn'];
157 $ldap->search(
'(&(objectClass=posixGroup)(memberUid='.ldap_escape_f($this->uid).
'))', [
'dn']);
158 while ($attrs = $ldap->fetch()) {
159 $this->groups[$attrs[
'dn']] = $attrs[
'dn'];
163 $ldap->search(
'(&(objectClass=organizationalRole)(roleOccupant='.ldap_escape_f($this->dn).
'))', [
'dn']);
164 while ($attrs = $ldap->fetch()) {
165 $this->roles[$attrs[
'dn']] = $attrs[
'dn'];
169 $ldap->search(
'(objectClass=gosaACL)', [
'dn',
'gosaAclEntry']);
171 while ($attrs = $ldap->fetch()) {
175 for ($i = 0; $i < $attrs[
'gosaAclEntry'][
'count']; $i++) {
176 $mergedAcls = array_merge($mergedAcls,
acl::explodeACL($attrs[
'gosaAclEntry'][$i]));
178 $ACLsContent[$attrs[
'dn']] = $mergedAcls;
181 $ACLsContentResolved = [];
184 foreach ($ACLsContent as $dn => $ACLRules) {
185 foreach ($ACLRules as $ACLRule) {
186 $ldap->cat($ACLRule[
'acl'], [
'gosaAclTemplate']);
187 $attrs = $ldap->fetch();
189 if (!isset($attrs[
'gosaAclTemplate'])) {
193 $interesting = FALSE;
196 foreach (array_keys($ACLRule[
'members']) as $member) {
198 if ($member ===
'G:*') {
203 list($memberType, $memberDn) = explode(
':', $member, 2);
204 switch ($memberType) {
218 if (mb_strtolower($memberDn) === mb_strtolower($this->dn)) {
232 if (!empty($ACLRule[
'userfilter']) && !$ldap->object_match_filter($this->dn, $ACLRule[
'userfilter'])) {
237 if (!empty($ACLRule[
'targetfilter'])) {
239 $ldap->set_size_limit($targetFilterLimit);
241 $ldap->search($targetFilter, [
'dn']);
242 if ($ldap->hitSizeLimit()) {
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.'),
252 while ($targetAttrs = $ldap->fetch()) {
253 $targetDns[] = $targetAttrs[
'dn'];
255 $ldap->set_size_limit(0);
261 foreach ($roleAcls as $roleAcl) {
262 foreach ($targetDns as $targetDn) {
263 $ACLsContentResolved[$targetDn][] = [
265 'type' => $ACLRule[
'type'],
266 'members' => $ACLRule[
'members'],
275 $ACLsContentResolved,
276 function ($dn1, $dn2)
278 return substr_count($dn1,
',') <=> substr_count($dn2,
',');
283 foreach ($ACLsContentResolved as $dn => $ACLRules) {
284 foreach ($ACLRules as $idx => $ACLRule) {
285 if (!isset($this->ACL[$dn])) {
286 $this->ACL[$dn] = [];
288 $this->ACL[$dn][$idx] = $ACLRule;
307 foreach ($this->ACL as $dn => $acl) {
308 $all_acl[$dn][$dn] = $acl;
310 while (strpos($dn,
',') !== FALSE) {
311 $dn = preg_replace(
'/^[^,]*+,/',
'', $dn);
312 if (isset($this->ACL[$dn])) {
313 $all_acl[$sdn][$dn] = array_filter(
317 return ($ACLInfos[
'type'] ===
'subtree');
323 $this->ACLperPath = $all_acl;
327 while (strpos($dn,
',') && !isset($all_acl[$dn])) {
328 $dn = preg_replace(
'/^[^,]*+,/',
'', $dn);
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],
337 return ($ACLInfos[
'type'] ===
'subtree');
344 if (is_object($plist)) {
345 $plist->resetCache();
356 return array_keys($this->ACLperPath);
402 $remove = (strpos($this->
get_permissions($dn, $object.
'/'.$class),
'd') !== FALSE);
404 return ($remove && $read);
436 $permissions = $this->get_snapshot_permissions($dn, $categories);
437 return in_array(($deleted ?
'restore_deleted' :
'restore_over'), $permissions);
452 $permissions = $this->get_snapshot_permissions($dn, $categories);
453 return in_array(
'c', $permissions);
468 $permissions = $this->get_snapshot_permissions($dn, $categories);
469 return in_array(
'd', $permissions);
472 function get_snapshot_permissions ($dn, $categories)
474 if (!is_array($categories)) {
475 $categories = [$categories];
478 $objectPermissions = [
'r',
'c',
'd'];
479 $attributePermissions = [
'restore_over',
'restore_deleted'];
480 foreach ($categories as $category) {
482 foreach ($objectPermissions as $i => $perm) {
483 if (strpos($acl, $perm) === FALSE) {
484 unset($objectPermissions[$i]);
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]);
494 return array_merge($objectPermissions, $attributePermissions);
522 $attribute = static::sanitizeAttributeName($attribute);
526 if (isset($ACL_CACHE[
"$dn+$object+$attribute"])) {
527 $ret = $ACL_CACHE[
"$dn+$object+$attribute"];
529 $ret = str_replace([
'w',
'c',
'd',
'm'],
'', $ret);
536 while (!isset($this->ACLperPath[$parentACLdn]) && (strpos($parentACLdn,
',') !== FALSE)) {
537 $parentACLdn = preg_replace(
'/^[^,]*+,/',
'', $parentACLdn);
539 if (!isset($this->ACLperPath[$parentACLdn])) {
540 $ACL_CACHE[
"$dn+$object+$attribute"] =
'';
544 if (($parentACLdn !== $dn) && isset($ACL_CACHE[
"sub:$parentACLdn+$object+$attribute"])) {
546 $permissions = $ACL_CACHE[
"sub:$parentACLdn+$object+$attribute"];
551 foreach ($this->ACLperPath[$parentACLdn] as $parentdn => $ACLs) {
553 foreach ($ACLs as $subacl) {
554 if ($permissions->isFull()) {
559 if (($dn != $this->dn) && isset($subacl[
'acl'][$object][0]) && ($subacl[
'acl'][$object][0]->isSelf())) {
564 if (($subacl[
'type'] ===
'base') && ($parentdn !== $dn)) {
570 if (isset($subacl[
'acl'][
'all'][0])) {
571 $permissions->merge($subacl[
'acl'][
'all'][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())) {
587 foreach ($subacl[
'acl'][$ocs.
'/'.$oc] as $anyPermissions) {
588 $permissions->merge($anyPermissions);
599 if (($attribute ==
'') && isset($subacl[
'acl'][$object])) {
600 foreach ($subacl[
'acl'][$object] as $anyPermissions) {
601 $permissions->merge($anyPermissions);
607 if (isset($subacl[
'acl'][$object][$attribute])) {
608 $permissions->merge($subacl[
'acl'][$object][$attribute]);
612 if (isset($subacl[
'acl'][$object][0])) {
613 $permissions->merge($subacl[
'acl'][$object][0]);
619 if ($parentACLdn !== $dn) {
620 $ACL_CACHE[
"sub:$parentACLdn+$object+$attribute"] = $permissions;
622 $ACL_CACHE[
"$dn+$object+$attribute"] = $permissions;
625 return $permissions->toString($skip_write);
647 return array_values($config->getDepartmentList());
653 if (!is_array($module)) {
657 $departmentInfo = $config->getDepartmentInfo();
660 foreach ($module as $mod) {
661 if (isset($ACL_CACHE[
'MODULE_DEPARTMENTS'][$mod])) {
662 $res = array_merge($res, $ACL_CACHE[
'MODULE_DEPARTMENTS'][$mod]);
669 foreach ($this->ACL as $dn => $infos) {
670 foreach ($infos as $info) {
672 foreach ($info[
'acl'] as $cat => $data) {
674 if ($skip_self_acls && isset($data[0]) && $data[0]->isSelf()) {
677 if (preg_match(
'/^'.preg_quote($mod,
'/').
'/', $cat) || ($cat ===
'all')) {
684 if ($found && !isset($departmentInfo[$dn])) {
685 while (!isset($departmentInfo[$dn]) && strpos($dn,
',')) {
686 $dn = preg_replace(
"/^[^,]+,/",
"", $dn);
688 if (isset($departmentInfo[$dn])) {
696 $departments = $config->getDepartmentList();
697 foreach ($departments as $dn) {
698 if (isset($deps[$dn])) {
702 if (strpos($mod,
'/')) {
712 $ACL_CACHE[
'MODULE_DEPARTMENTS'][$mod] = $deps;
713 $res = array_merge($res, $deps);
716 return array_values($res);
739 if (!is_string($category)) {
740 trigger_error(
'category must be string');
743 if (isset($this->result_cache[
'get_complete_category_acls'][$dn][$category])) {
744 return $this->result_cache[
'get_complete_category_acls'][$dn][$category];
747 if (isset($config->data[
'CATEGORIES'][$category])) {
748 foreach ($config->data[
'CATEGORIES'][$category][
'classes'] as $oc) {
755 for ($i = 0, $l = strlen($types); $i < $l; $i++) {
756 if (strpos($tmp, $types[$i]) === FALSE) {
757 $acl = str_replace($types[$i],
'', $acl);
764 $this->result_cache[
'get_complete_category_acls'][$dn][$category] = $acl;
778 return $this->ignoreACL;
806 if ($this->forcePasswordChange) {
807 return POSIX_FORCE_PASSWORD_CHANGE;
811 if ($this->is_user_admin()) {
815 $ldap = $config->get_ldap_link();
819 list($policy, $attrs) = user::fetchPpolicy($this->dn);
821 isset($policy[
'pwdExpireWarning'][0]) &&
822 isset($policy[
'pwdMaxAge'][0]) &&
823 isset($attrs[
'pwdChangedTime'][0])
825 $now =
new DateTime(
'now', timezone::utc());
826 $pwdExpireWarningSeconds = intval($policy[
'pwdExpireWarning'][0]);
827 $maxAge = $policy[
'pwdMaxAge'][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;
841 if ($config->get_cfg_value(
'handleExpiredAccounts') !=
'TRUE') {
845 $ldap->cd($config->current[
'BASE']);
846 $ldap->cat($this->dn);
847 $attrs = $ldap->fetch();
848 $current = floor(date(
"U") / 60 / 60 / 24);
851 foreach ([
'shadowExpire',
'shadowLastChange',
'shadowMax',
'shadowMin',
852 'shadowInactive',
'shadowWarning',
'sambaKickoffTime'] as $attr) {
853 $$attr = (isset($attrs[$attr][0]) ? $attrs[$attr][0] : NULL);
859 if (($sambaKickoffTime !== NULL) && (time() >= $sambaKickoffTime)) {
860 return POSIX_ACCOUNT_EXPIRED;
870 if (($shadowExpire != NULL) && ($shadowExpire <= $current)
871 && (($shadowInactive == NULL) || ($current > $shadowExpire + $shadowInactive))) {
872 return POSIX_ACCOUNT_EXPIRED;
885 if (($shadowExpire != NULL) && ($shadowWarning != NULL)
886 && ($shadowExpire >= $current) && ($shadowExpire <= $current + $shadowWarning)) {
887 return POSIX_WARN_ABOUT_EXPIRATION;
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;
906 if (($shadowLastChange != NULL) && ($shadowMax != NULL)
907 && ($current >= $shadowLastChange + $shadowMax)) {
908 return POSIX_FORCE_PASSWORD_CHANGE;
917 if (($shadowLastChange != NULL) && ($shadowMin != NULL)
918 && ($shadowLastChange + $shadowMin >= $current)) {
919 return POSIX_DISALLOW_PASSWORD_CHANGE;
927 function is_user_admin ()
930 if (empty($this->ACLperPath)) {
933 return ($this->
get_permissions($config->current[
'BASE'],
'user/user') ==
'rwcdm');
938 function isBlacklisted ($plugin)
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))) {
956 function getAttributeCategory ($type, $attribute)
964 $attribute = static::sanitizeAttributeName($attribute);
966 if (is_array($type)) {
972 $infos = objects::infos($type);
973 $prefix = $infos[
'aclCategory'].
'/';
974 $tabs = $infos[
'tabClass']::getPotentialTabList($type, $infos);
976 foreach ($tabs as $tab) {
977 $acls = pluglist::pluginInfos($tab[
'CLASS'])[
'plProvidedAcls'];
978 if (isset($acls[$attribute])) {
979 return $prefix.$tab[
'CLASS'];
981 if (isset($tab[
'SUBTABS'])) {
982 $acl = $this->getAttributeCategory($config->data[
'TABS'][$tab[
'SUBTABS']], $attribute);
983 if ($acl !== FALSE) {
991 function getSizeLimitHandler ()
1005 function getCurrentBase ()
1007 if (!empty($this->currentBase)) {
1010 return $this->getBase();
1016 function setCurrentBase ($base)
1018 $this->currentBase = $base;
1023 public static function sanitizeAttributeName ($name)
1025 return preg_replace(
'/[\/\-,.#:;]/',
'_', $name);
1042 $ldap = $config->get_ldap_link();
1043 if (!$ldap->success()) {
1047 $allowed_attributes = [
'uid',
'mail'];
1049 $tmp = explode(
',', $config->get_cfg_value(
'loginAttribute'));
1050 foreach ($tmp as $attr) {
1051 if (in_array($attr, $allowed_attributes)) {
1052 $verify_attr[] = $attr;
1056 if (count($verify_attr) == 0) {
1057 $verify_attr = [
'uid'];
1059 $tmp = $verify_attr;
1062 foreach ($verify_attr as $attr) {
1063 $filter .=
'('.$attr.
'='.$username.
')';
1065 $filter =
'(&(|'.$filter.
')(objectClass=inetOrgPerson))';
1066 $ldap->cd($config->current[
'BASE']);
1067 $ldap->search($filter, $tmp);
1070 if ($ldap->count() == 0) {
1073 } elseif ($ldap->count() != 1) {
1075 return _(
'Login (uid) is not unique inside the LDAP tree. Please contact your administrator.');
1079 $attrs = $ldap->fetch();
1081 foreach ($verify_attr as $attr) {
1082 if (isset($attrs[$attr][0]) && $attrs[$attr][0] == $username) {
1086 $ldap->disconnect();
1110 $ui = static::getLdapUser($username);
1112 if ($ui === FALSE) {
1114 } elseif (is_string($ui)) {
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')
1124 if (!$ldap->success()) {
1125 if ($ldap->get_error(FALSE) ==
'changeAfterReset') {
1126 $ui->forcePasswordChange = TRUE;
htmlescape(string $str)
Escape string for HTML output.
in_array_ics($value, array $items)
Check if a value exists in an array (case-insensitive)
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.
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.
$forcePasswordChange
Password change should be forced.
$cachedAttrs
LDAP attributes of this user at login.
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.
Fatal error class. Does not extend FusionDirectoryError.
Class userinfo This class contains all informations and functions about user.
static explodeRole($role)
Explode a role.
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'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.
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.