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'] = ['*'];