diff --git a/include/class_acl.inc b/include/class_acl.inc index 67fb4925c5e4785bdf940c38bf120d9242b1da51..2935a02f192d6f035d7b3663716632741a34476e 100644 --- a/include/class_acl.inc +++ b/include/class_acl.inc @@ -74,20 +74,29 @@ class acl static function explodeACL ($acl) { $list = explode(':', $acl); - if (count($list) == 5) { - list($index, $type, $role, $members, $filter) = $list; - $filter = base64_decode($filter); + if (count($list) == 6) { + list($index, $type, $role, $members, $userfilter, $targetfilter) = $list; + $userfilter = base64_decode($userfilter); + $targetfilter = base64_decode($targetfilter); + } elseif (count($list) == 5) { + list($index, $type, $role, $members, $userfilter) = $list; + $userfilter = base64_decode($userfilter); + $targetfilter = ''; } else { - $filter = ""; list($index, $type, $role, $members) = $list; + $userfilter = ''; + $targetfilter = ''; } - $a = [ $index => [ - 'type' => $type, - 'filter' => $filter, - 'members' => acl::extractMembers($members), - 'acl' => base64_decode($role), - ]]; + $a = [ + $index => [ + 'type' => $type, + 'userfilter' => $userfilter, + 'targetfilter' => $targetfilter, + 'members' => acl::extractMembers($members), + 'acl' => base64_decode($role), + ] + ]; /* Handle unknown types */ if (!in_array($type, ['subtree', 'base'])) { @@ -104,7 +113,7 @@ class acl * * \return an array with members */ - static function extractMembers ($ms) + static function extractMembers (string $ms) { global $config; $a = []; @@ -158,7 +167,7 @@ class acl * * \param string $acl The acl to be extracted */ - static function extractACL ($acl) + static function extractACL (string $acl) { /* Rip acl off the string, seperate by ',' and place it in an array */ $as = preg_replace('/^[^:]+:[^:]+:[^:]*:([^:]*).*$/', '\1', $acl); diff --git a/include/class_userinfo.inc b/include/class_userinfo.inc index 428f27de448a38352c8045a99b79ac9ad765d435..e4291b5269fd93ba099703b85e88a32553cb9f3b 100644 --- a/include/class_userinfo.inc +++ b/include/class_userinfo.inc @@ -46,14 +46,16 @@ class userinfo var $givenName = ''; var $gidNumber = -1; var $language = ""; - var $subtreeACL = []; - var $ACL = []; var $groups = []; var $roles = []; - var $result_cache = []; - var $ignoreACL = FALSE; - var $ACLperPath = []; + /*! \brief LDAP attributes of this user at login */ + protected $cachedAttrs = []; + + protected $result_cache = []; + protected $ignoreACL = FALSE; + protected $ACL = []; + protected $ACLperPath = []; /*! \brief LDAP size limit handler */ protected $sizeLimitHandler; @@ -64,8 +66,6 @@ class userinfo /*! \brief Password change should be forced */ protected $forcePasswordChange = FALSE; - /* get acl's an put them into the userinfo object - attr subtreeACL (userdn:components, userdn:component1#sub1#sub2,component2,...) */ function __construct ($userdn) { global $config; @@ -85,7 +85,7 @@ class userinfo { global $config; $ldap = $config->get_ldap_link(); - $ldap->cat($this->dn, ['cn', 'sn', 'givenName', 'uid', 'gidNumber', 'preferredLanguage']); + $ldap->cat($this->dn, ['*']); $attrs = $ldap->fetch(); $this->uid = $attrs['uid'][0]; @@ -111,6 +111,8 @@ class userinfo if (isset($attrs['preferredLanguage'][0])) { $this->language = $attrs['preferredLanguage'][0]; } + + $this->cachedAttrs = $attrs; } /*! @@ -155,88 +157,117 @@ class userinfo } /* Crawl through ACLs and move relevant to the tree */ - $ldap->search("(objectClass=gosaACL)", ['dn', 'gosaAclEntry']); - $aclp = []; - $aclc = []; + $ldap->search('(objectClass=gosaACL)', ['dn', 'gosaAclEntry']); + $ACLsContent = []; while ($attrs = $ldap->fetch()) { /* Insert links in ACL array */ - $aclp[$attrs['dn']] = substr_count($attrs['dn'], ','); - $ol = []; + $mergedAcls = []; for ($i = 0; $i < $attrs['gosaAclEntry']['count']; $i++) { - $ol = array_merge($ol, acl::explodeAcl($attrs['gosaAclEntry'][$i])); + $mergedAcls = array_merge($mergedAcls, acl::explodeACL($attrs['gosaAclEntry'][$i])); } - $aclc[$attrs['dn']] = $ol; + $ACLsContent[$attrs['dn']] = $mergedAcls; } + $ACLsContentResolved = []; /* Resolve roles here */ - foreach ($aclc as $dn => $data) { - foreach ($data as $prio => $aclc_value) { - unset($aclc[$dn][$prio]); - - $ldap->cat($aclc_value['acl'], ["gosaAclTemplate"]); + foreach ($ACLsContent as $dn => $ACLRules) { + foreach ($ACLRules as $ACLRule) { + $ldap->cat($ACLRule['acl'], ['gosaAclTemplate']); $attrs = $ldap->fetch(); - if (isset($attrs['gosaAclTemplate'])) { - $roleAcls = acl::explodeRole($attrs['gosaAclTemplate']); - foreach ($roleAcls as $roleAcl) { - $aclc[$dn][] = [ - 'acl' => $roleAcl, - 'type' => $aclc_value['type'], - 'members' => $aclc_value['members'], - 'filter' => $aclc_value['filter'] - ]; - } + if (!isset($attrs['gosaAclTemplate'])) { + continue; } - } - } - - /* ACL's read, sort for tree depth */ - asort($aclp); - /* Sort in tree order */ - foreach ($aclp as $dn => $acl) { - /* Check if we need to keep this ACL */ - foreach ($aclc[$dn] as $idx => $type) { $interesting = FALSE; - /* No members? This ACL rule is deactivated ... */ - if (count($type['members'])) { - /* Inspect members... */ - foreach (array_keys($type['members']) as $grp) { - /* Some group inside the members that is relevant for us? */ - if (in_array_ics(preg_replace('/^G:/', '', $grp), $this->groups)) { - $interesting = TRUE; - break; - } + /* Inspect members... */ + foreach (array_keys($ACLRule['members']) as $member) { + /* Wildcard? */ + if ($member === 'G:*') { + $interesting = TRUE; + break; + } - /* Some role inside the members that is relevant for us? */ - if (in_array_ics(preg_replace('/^R:/', '', $grp), $this->roles)) { - $interesting = TRUE; + list($memberType, $memberDn) = explode(':', $member, 2); + switch ($memberType) { + case 'G': + if (in_array_ics($memberDn, $this->groups)) { + $interesting = TRUE; + break 2; + } break; - } - - /* User inside the members? */ - if (mb_strtoupper(preg_replace('/^U:/', '', $grp)) == mb_strtoupper($this->dn)) { - $interesting = TRUE; + case 'R': + if (in_array_ics($memberDn, $this->roles)) { + $interesting = TRUE; + break 2; + } break; - } - - /* Wildcard? */ - if (preg_match('/^G:\*/', $grp)) { - $interesting = TRUE; + case 'U': + if (mb_strtolower($memberDn) === mb_strtolower($this->dn)) { + $interesting = TRUE; + break 2; + } break; - } + default: + throw FusionDirectoryException('Unknown ACL member type '.$memberType); + } + } + + if (!$interesting) { + continue; + } + + if (!empty($ACLRule['userfilter'])) { + /* Check if we match the user filter */ + if (!$ldap->object_match_filter($this->dn, $ACLRule['userfilter'])) { + continue; } } - if ($interesting) { - if (!isset($this->ACL[$dn])) { - $this->ACL[$dn] = []; + if (!empty($ACLRule['targetfilter'])) { + $ldap->cd($dn); + $targetFilter = templateHandling::parseString($ACLRule['targetfilter'], $this->cachedAttrs, 'ldap_escape_f'); + $ldap->search($targetFilter, ['dn']); + $targetDns = []; + while ($targetAttrs = $ldap->fetch()) { + $targetDns[] = $targetAttrs['dn']; } - $this->ACL[$dn][$idx] = $type; + } else { + $targetDns = [$dn]; + } + + $roleAcls = acl::explodeRole($attrs['gosaAclTemplate']); + foreach ($roleAcls as $roleAcl) { + foreach ($targetDns as $targetDn) { + $ACLsContentResolved[$targetDn][] = [ + 'acl' => $roleAcl, + 'type' => $ACLRule['type'], + 'members' => $ACLRule['members'], + ]; + } + } + } + } + + /* Sort by tree depth */ + uksort( + $ACLsContentResolved, + function ($dn1, $dn2) + { + return substr_count($dn1, ',') <=> substr_count($dn2, ','); + } + ); + + /* Insert in $this->ACL */ + foreach ($ACLsContentResolved as $dn => $ACLRules) { + foreach ($ACLRules as $idx => $ACLRule) { + if (!isset($this->ACL[$dn])) { + $this->ACL[$dn] = []; } + $this->ACL[$dn][$idx] = $ACLRule; } } @@ -539,17 +570,6 @@ class userinfo continue; } - /* With user filter */ - if (!empty($subacl['filter'])) { - $id = $dn."-".$subacl['filter']; - if (!isset($ACL_CACHE['FILTER'][$id])) { - $ACL_CACHE['FILTER'][$id] = $ldap->object_match_filter($dn, $subacl['filter']); - } - if (!$ACL_CACHE['FILTER'][$id]) { - continue; - } - } - /* Self ACLs? */ if (($dn != $this->dn) && isset($subacl['acl'][$object][0]) && (strpos($subacl['acl'][$object][0], "s") !== FALSE)) { continue; @@ -747,7 +767,7 @@ class userinfo * * \param $newACL The new ACL */ - function mergeACL ($acl, $type, $newACL) + function mergeACL (array $acl, $type, $newACL) { $at = ["subtree" => "s", "one" => "1"]; @@ -1240,7 +1260,7 @@ class userinfo } } - /* Username is set, load subtreeACL's now */ + /* Username is set, load ACLs now */ $ui->loadACL(); return $ui; diff --git a/plugins/admin/acl/class_ACLsAssignmentAttribute.inc b/plugins/admin/acl/class_ACLsAssignmentAttribute.inc index 03a18f01dda95d03d13ec338af8592f0694b36f0..09d7913012f0950fb34e67ee8a7603caa2ff48b7 100644 --- a/plugins/admin/acl/class_ACLsAssignmentAttribute.inc +++ b/plugins/admin/acl/class_ACLsAssignmentAttribute.inc @@ -41,10 +41,11 @@ class ACLsAssignmentAttribute extends DialogOrderedArrayAttribute { $acl = explode(':', $value); return [$acl[0], [ - 'scope' => $acl[1], - 'role' => base64_decode($acl[2]), - 'members' => array_map('base64_decode', explode(',', $acl[3])), - 'userfilter' => (isset($acl[4]) ? base64_decode($acl[4]) : ''), + 'scope' => $acl[1], + 'role' => base64_decode($acl[2]), + 'members' => array_map('base64_decode', explode(',', $acl[3])), + 'userfilter' => (isset($acl[4]) ? base64_decode($acl[4]) : ''), + 'targetfilter' => (isset($acl[5]) ? base64_decode($acl[5]) : ''), ]]; } @@ -54,7 +55,8 @@ class ACLsAssignmentAttribute extends DialogOrderedArrayAttribute ':'.$value['scope']. ':'.base64_encode($value['role']). ':'.join(',', array_map('base64_encode', $value['members'])). - ':'.base64_encode($value['userfilter']); + ':'.base64_encode($value['userfilter']). + ':'.base64_encode($value['targetfilter']); } function foreignKeyUpdate ($oldvalue, $newvalue, array $source) diff --git a/plugins/admin/acl/class_aclAssignmentDialogWindow.inc b/plugins/admin/acl/class_aclAssignmentDialogWindow.inc index e0fadc2bb49bddf184f138548c268371f1bfe810..f1f77a5496977e6e3959236f247c393e8899ac4c 100644 --- a/plugins/admin/acl/class_aclAssignmentDialogWindow.inc +++ b/plugins/admin/acl/class_aclAssignmentDialogWindow.inc @@ -65,6 +65,10 @@ class aclAssignmentDialogWindow extends simplePlugin _('Restrict users with filter'), _('LDAP filter which a member must match to actually get the rights'), 'aclUserFilter', FALSE ), + new StringAttribute( + _('Restrict targets with filter'), _('LDAP filter which a dn must match to actually be concerned. May use %dn% mask for user dn. Example: (manager=%dn%).'), + 'aclTargetFilter', FALSE + ), ] ], ]; @@ -98,7 +102,8 @@ class aclAssignmentDialogWindow extends simplePlugin if ($value['members'][0] == '*') { $this->allUsers = TRUE; } - $this->aclUserFilter = $value['userfilter']; + $this->aclUserFilter = $value['userfilter']; + $this->aclTargetFilter = $value['targetfilter']; } } @@ -120,10 +125,11 @@ class aclAssignmentDialogWindow extends simplePlugin function getAclEntry () { $entry = [ - 'scope' => $this->aclMode, - 'role' => $this->aclRole, - 'members' => $this->aclMembers, - 'userfilter' => $this->aclUserFilter, + 'scope' => $this->aclMode, + 'role' => $this->aclRole, + 'members' => $this->aclMembers, + 'userfilter' => $this->aclUserFilter, + 'targetfilter' => $this->aclTargetFilter, ]; if ($this->allUsers) { $entry['members'] = ['*'];