3 commits - lib/api lib/Auth lib/Auth.php lib/client lib/ext lib/kolab_api_exception.php lib/kolab_client_task.php lib/locale public_html/js public_html/skins

Aleksander Machniak machniak at kolabsys.com
Fri Jan 17 11:57:48 CET 2014


 lib/Auth.php                               |    5 
 lib/Auth/LDAP.php                          |  428 +++++++++++++----------------
 lib/api/kolab_api_service_domain.php       |   30 +-
 lib/api/kolab_api_service_domain_types.php |    2 
 lib/api/kolab_api_service_domains.php      |    1 
 lib/client/kolab_client_task_domain.php    |   19 +
 lib/client/kolab_client_task_user.php      |    7 
 lib/ext/Net/LDAP3.php                      |   28 +
 lib/kolab_api_exception.php                |   71 ++++
 lib/kolab_client_task.php                  |   32 +-
 lib/locale/en_US.php                       |   11 
 public_html/js/kolab_admin.js              |   29 +
 public_html/skins/default/style.css        |    4 
 13 files changed, 420 insertions(+), 247 deletions(-)

New commits:
commit 11efdc8b637e3ebebf9bcb5fdc1fafe1c05f1a11
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Jan 17 11:57:22 2014 +0100

    Refactored domain_add()

diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php
index 5958166..a2e951f 100644
--- a/lib/Auth/LDAP.php
+++ b/lib/Auth/LDAP.php
@@ -159,9 +159,12 @@ class LDAP extends Net_LDAP3 {
 
         $domain_base_dn        = $this->conf->get('ldap', 'domain_base_dn');
         $domain_name_attribute = $this->conf->get('ldap', 'domain_name_attribute');
+        $service_bind_dn       = $this->conf->get('ldap', 'service_bind_dn');
         $primary_domain        = $this->conf->get('kolab', 'primary_domain');
-        $_primary_domain       = str_replace('.', '_', $primary_domain);
-        $_domain               = str_replace('.', '_', $domain);
+
+        if (empty($service_bind_dn)) {
+            $service_bind_dn = $this->conf->get('ldap', 'bind_dn');
+        }
 
         if (empty($domain_name_attribute)) {
             $domain_name_attribute = 'associateddomain';
@@ -190,124 +193,6 @@ class LDAP extends Net_LDAP3 {
             $inetdomainbasedn = $this->_standard_root_dn($domain);
         }
 
-        $cn = str_replace(array(',', '='), array('\2C', '\3D'), $inetdomainbasedn);
-
-        $dn = "cn=" . $cn . ",cn=mapping tree,cn=config";
-        $attrs = array(
-            'objectclass' => array(
-                'top',
-                'extensibleObject',
-                'nsMappingTree',
-            ),
-            'nsslapd-state'   => 'backend',
-            'cn'              => $inetdomainbasedn,
-            'nsslapd-backend' => $_domain,
-        );
-
-        $replica_hosts = $this->list_replicas();
-
-        if (!empty($replica_hosts)) {
-            foreach ($replica_hosts as $replica_host) {
-                Log::trace("Iterating over replication partners (now: $replica_host)");
-                $ldap = new Net_LDAP3($this->config);
-                $ldap->config_set("log_hook", array($this, "_log"));
-                $ldap->config_set('host', $replica_host);
-                $ldap->config_set('hosts', array($replica_host));
-                $ldap->connect();
-                $ldap->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
-                $result = $ldap->add_entry($dn, $attrs);
-
-                if (!$result) {
-                    Log::error("Error adding $dn to $replica_host");
-                }
-
-                $ldap->close();
-            }
-        } else {
-            $this->add_entry($dn, $attrs);
-        }
-
-        $result = $this->_read("cn=" . $_primary_domain . ",cn=ldbm database,cn=plugins,cn=config", array('nsslapd-directory'));
-        if (!$result) {
-            $result = $this->_read("cn=" . $primary_domain . ",cn=ldbm database,cn=plugins,cn=config", array('nsslapd-directory'));
-        }
-
-        if (!$result) {
-            $result = $this->_read("cn=userRoot,cn=ldbm database,cn=plugins,cn=config", array('nsslapd-directory'));
-        }
-
-        $this->_log(LOG_DEBUG, "Primary domain ldbm database configuration entry: " . var_export($result, true));
-
-        $result         = $result[key($result)];
-        $orig_directory = $result['nsslapd-directory'];
-        $directory      = str_replace($_primary_domain, $_domain, $result['nsslapd-directory']);
-
-        if ($directory == $orig_directory) {
-            $directory = str_replace($primary_domain, $_domain, $result['nsslapd-directory']);
-        }
-
-        if ($directory == $orig_directory) {
-            $directory = str_replace("userRoot", $_domain, $result['nsslapd-directory']);
-        }
-
-        $dn = "cn=" . $_domain . ",cn=ldbm database,cn=plugins,cn=config";
-        $attrs = array(
-            'objectclass' => array(
-                'top',
-                'extensibleobject',
-                'nsbackendinstance',
-             ),
-            'cn'                     => $_domain,
-            'nsslapd-suffix'         => $inetdomainbasedn,
-            'nsslapd-cachesize'      => '-1',
-            'nsslapd-cachememsize'   => '10485760',
-            'nsslapd-readonly'       => 'off',
-            'nsslapd-require-index'  => 'off',
-            'nsslapd-dncachememsize' => '10485760'
-        );
-
-        if (!empty($replica_hosts)) {
-            foreach ($replica_hosts as $replica_host) {
-                $ldap = new Net_LDAP3($this->config);
-                $ldap->config_set("log_hook", array($this, "_log"));
-                $ldap->config_set('host', $replica_host);
-                $ldap->config_set('hosts', array($replica_host));
-                $ldap->connect();
-                $ldap->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
-
-                $ldap->config_set('return_attributes', array('nsslapd-directory'));
-                $result = $ldap->get_entry("cn=" . $_primary_domain . ",cn=ldbm database,cn=plugins,cn=config");
-                if (!$result) {
-                    $result = $ldap->get_entry("cn=" . $primary_domain . ",cn=ldbm database,cn=plugins,cn=config");
-                }
-
-                if (!$result) {
-                    $result = $ldap->get_entry("cn=userRoot,cn=ldbm database,cn=plugins,cn=config");
-                }
-
-                $this->_log(LOG_DEBUG, "Primary domain ldbm database configuration entry: " . var_export($result, true));
-
-                $result         = $result[key($result)];
-                $orig_directory = $result['nsslapd-directory'];
-                $directory      = str_replace($_primary_domain, $_domain, $result['nsslapd-directory']);
-
-                if ($directory == $orig_directory) {
-                    $directory = str_replace($primary_domain, $_domain, $result['nsslapd-directory']);
-                }
-
-                if ($directory == $orig_directory) {
-                    $directory = str_replace("userRoot", $_domain, $result['nsslapd-directory']);
-                }
-
-                $attrs['nsslapd-directory'] = $directory;
-
-                $ldap->add_entry($dn, $attrs);
-                $ldap->close();
-            }
-        } else {
-            $this->add_entry($dn, $attrs);
-        }
-
         // Query the ACI for the primary domain
         if ($domain_entry = $this->_find_domain($primary_domain)) {
             $domain_entry = array_shift($domain_entry);
@@ -332,13 +217,6 @@ class LDAP extends Net_LDAP3 {
             $_aci = $aci;
         }
 
-        $service_bind_dn = $this->conf->get('ldap', 'service_bind_dn');
-        if (empty($service_bind_dn)) {
-            $service_bind_dn = $this->conf->get('ldap', 'bind_dn');
-        }
-
-        $dn = $inetdomainbasedn;
-
         // @TODO: this list should be configurable or auto-created somehow
         $self_attrs = array(
             'carLicense', 'description', 'displayName', 'facsimileTelephoneNumber', 'homePhone',
@@ -352,31 +230,65 @@ class LDAP extends Net_LDAP3 {
              $self_attrs = array_merge($self_attrs, array('kolabDelegate', 'kolabInvitationPolicy', 'kolabAllowSMTPSender'));
         }
 
-        $attrs = array(
-            // @TODO: Probably just use ldap_explode_dn()
-            'dc' => substr($dn, (strpos($dn, '=')+1), ((strpos($dn, ',')-strpos($dn, '='))-1)),
-            'objectclass' => array(
-                'top',
-                'domain',
+        $_domain = str_replace('.', '_', $domain);
+        $dn      = $inetdomainbasedn;
+        $cn      = str_replace(array(',', '='), array('\2C', '\3D'), $dn);
+
+        // Additional domain entries in various trees
+        $entries = array(
+            "cn={$cn},cn=mapping tree,cn=config" => array(
+                'objectclass' => array(
+                    'top',
+                    'extensibleObject',
+                    'nsMappingTree',
+                ),
+                'nsslapd-state'   => 'backend',
+                'cn'              => $inetdomainbasedn,
+                'nsslapd-backend' => $_domain,
             ),
-            'aci' => array(
-                // Self-modification
-                "(targetattr = \"" . implode(" || ", $self_attrs) . "\")(version 3.0; acl \"Enable self write for common attributes\"; allow (read,compare,search,write) userdn=\"ldap:///self\";)",
-                // Directory Administrators
-                "(targetattr = \"*\")(version 3.0; acl \"Directory Administrators Group\"; allow (all) (groupdn=\"ldap:///cn=Directory Administrators," . $inetdomainbasedn . "\" or roledn=\"ldap:///cn=kolab-admin," . $inetdomainbasedn . "\");)",
-                // Configuration Administrators
-                "(targetattr = \"*\")(version 3.0; acl \"Configuration Administrators Group\"; allow (all) groupdn=\"ldap:///cn=Configuration Administrators,ou=Groups,ou=TopologyManagement,o=NetscapeRoot\";)",
-                // Administrator users
-                "(targetattr = \"*\")(version 3.0; acl \"Configuration Administrator\"; allow (all) userdn=\"ldap:///uid=admin,ou=Administrators,ou=TopologyManagement,o=NetscapeRoot\";)",
-                // SIE Group
-                $_aci,
-                // Search Access,
-                "(targetattr != \"userPassword\") (version 3.0; acl \"Search Access\"; allow (read,compare,search) (userdn = \"ldap:///" . $inetdomainbasedn . "??sub?(objectclass=*)\");)",
-                // Service Search Access
-                "(targetattr = \"*\") (version 3.0; acl \"Service Search Access\"; allow (read,compare,search) (userdn = \"ldap:///" . $service_bind_dn . "\");)",
+            "cn={$_domain},cn=ldbm database,cn=plugins,cn=config" => array(
+                'objectclass' => array(
+                    'top',
+                    'extensibleobject',
+                    'nsbackendinstance',
+                ),
+                'cn'                     => $_domain,
+                'nsslapd-suffix'         => $inetdomainbasedn,
+                'nsslapd-cachesize'      => '-1',
+                'nsslapd-cachememsize'   => '10485760',
+                'nsslapd-readonly'       => 'off',
+                'nsslapd-require-index'  => 'off',
+                'nsslapd-dncachememsize' => '10485760',
+                'nsslapd-directory'      => true, // will be replaced below
+            ),
+            $inetdomainbasedn => array(
+                // @TODO: Probably just use ldap_explode_dn()
+                'dc'          => substr($dn, (strpos($dn, '=')+1), ((strpos($dn, ',')-strpos($dn, '='))-1)),
+                'objectclass' => array(
+                    'top',
+                    'domain',
+                ),
+                'aci' => array(
+                    // Self-modification
+                    "(targetattr = \"" . implode(" || ", $self_attrs) . "\")(version 3.0; acl \"Enable self write for common attributes\"; allow (read,compare,search,write) userdn=\"ldap:///self\";)",
+                    // Directory Administrators
+                    "(targetattr = \"*\")(version 3.0; acl \"Directory Administrators Group\"; allow (all) (groupdn=\"ldap:///cn=Directory Administrators,{$inetdomainbasedn}\" or roledn=\"ldap:///cn=kolab-admin,{$inetdomainbasedn}\");)",
+                    // Configuration Administrators
+                    "(targetattr = \"*\")(version 3.0; acl \"Configuration Administrators Group\"; allow (all) groupdn=\"ldap:///cn=Configuration Administrators,ou=Groups,ou=TopologyManagement,o=NetscapeRoot\";)",
+                    // Administrator users
+                    "(targetattr = \"*\")(version 3.0; acl \"Configuration Administrator\"; allow (all) userdn=\"ldap:///uid=admin,ou=Administrators,ou=TopologyManagement,o=NetscapeRoot\";)",
+                    // SIE Group
+                    $_aci,
+                    // Search Access,
+                    "(targetattr != \"userPassword\") (version 3.0; acl \"Search Access\"; allow (read,compare,search) (userdn = \"ldap:///{$inetdomainbasedn}??sub?(objectclass=*)\");)",
+                    // Service Search Access
+                    "(targetattr = \"*\") (version 3.0; acl \"Service Search Access\"; allow (read,compare,search) (userdn = \"ldap:///{$service_bind_dn}\");)",
+                ),
             ),
         );
 
+        $replica_hosts = $this->list_replicas();
+
         if (!empty($replica_hosts)) {
             foreach ($replica_hosts as $replica_host) {
                 $ldap = new Net_LDAP3($this->config);
@@ -385,84 +297,77 @@ class LDAP extends Net_LDAP3 {
                 $ldap->config_set('hosts', array($replica_host));
                 $ldap->connect();
                 $ldap->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw);
-                $ldap->add_entry($dn, $attrs);
+
+                foreach ($entries as $dn => $attrs) {
+                    if (isset($attrs['nsslapd-directory'])) {
+                        $attrs['nsslapd-directory'] = $this->nsslapd_directory($ldap, $domain);
+                    }
+                    if (!$ldap->add_entry($dn, $attrs)) {
+                        Log::error("Error adding $dn to $replica_host");
+                    }
+                }
                 $ldap->close();
             }
-        } else {
-            $this->add_entry($dn, $attrs);
+        }
+        else {
+            foreach ($entries as $dn => $attrs) {
+                if (isset($attrs['nsslapd-directory'])) {
+                    $attrs['nsslapd-directory'] = $this->nsslapd_directory($this, $domain);
+                }
+                if (!$this->add_entry($dn, $attrs)) {
+                    Log::error("Error adding $dn");
+                }
+            }
         }
 
         if (!empty($replica_hosts)) {
             $this->add_replication_agreements($inetdomainbasedn);
         }
 
-        $dn = "cn=Directory Administrators," . $inetdomainbasedn;
-        $attrs = array(
-            'objectclass' => array(
-                'top',
-                'groupofuniquenames',
+        // add OUs, do this after adding replication agreements
+        $entries = array(
+            "cn=Directory Administrators,$inetdomainbasedn" => array(
+                'cn'           => 'Directory Administrators',
+                'objectclass'  => array('top', 'groupofuniquenames'),
+                'uniquemember' => array('cn=Directory Manager'),
             ),
-            'cn' => 'Directory Administrators',
-            'uniquemember' => array(
-                'cn=Directory Manager'
+            "cn=kolab-admin,$inetdomainbasedn" => array(
+                'cn'          => 'kolab-admin',
+                'objectclass' => array(
+                    'top',
+                    'ldapsubentry',
+                    'nsroledefinition',
+                    'nssimpleroledefinition',
+                    'nsmanagedroledefinition',
+                ),
             ),
-        );
-
-        $this->add_entry($dn, $attrs);
-
-        $dn = "ou=Groups," . $inetdomainbasedn;
-        $attrs = array(
-            'objectclass' => array('top', 'organizationalunit'),
-            'ou' => 'Groups',
-        );
-
-        $this->add_entry($dn, $attrs);
-
-        $dn = "ou=People," . $inetdomainbasedn;
-        $attrs = array(
-            'objectclass' => array('top', 'organizationalunit'),
-            'ou' => 'People',
-        );
-
-        $this->add_entry($dn, $attrs);
-
-        $dn = "ou=Special Users," . $inetdomainbasedn;
-        $attrs = array(
-            'objectclass' => array('top', 'organizationalunit'),
-            'ou' => 'Special Users',
-        );
-
-        $this->add_entry($dn, $attrs);
-
-        $dn = "ou=Resources," . $inetdomainbasedn;
-        $attrs = array(
-            'objectclass' => array('top', 'organizationalunit'),
-            'ou' => 'Resources',
-        );
-
-        $this->add_entry($dn, $attrs);
-
-        $dn = "ou=Shared Folders," . $inetdomainbasedn;
-        $attrs = array(
-            'objectclass' => array('top', 'organizationalunit'),
-            'ou' => 'Shared Folders',
-        );
-
-        $this->add_entry($dn, $attrs);
-
-        $dn = 'cn=kolab-admin,' . $inetdomainbasedn;
-        $attrs = array(
-            'objectclass' => array(
-                'top',
-                'ldapsubentry',
-                'nsroledefinition',
-                'nssimpleroledefinition',
-                'nsmanagedroledefinition',
+            // @TODO: these OUs DN should be read from config
+            "ou=Groups,$inetdomainbasedn" => array(
+                'ou'          => 'Groups',
+                'objectclass' => array('top', 'organizationalunit'),
+            ),
+            "ou=People,$inetdomainbasedn" => array(
+                'ou'          => 'People',
+                'objectclass' => array('top', 'organizationalunit'),
+            ),
+            "ou=Special Users,$inetdomainbasedn" => array(
+                'ou'          => 'Special Users',
+                'objectclass' => array('top', 'organizationalunit'),
+            ),
+            "ou=Resources,$inetdomainbasedn" => array(
+                'ou'          => 'Resources',
+                'objectclass' => array('top', 'organizationalunit'),
+            ),
+            "ou=Shared Folders,$inetdomainbasedn" => array(
+                'ou'          => 'Shared Folders',
+                'objectclass' => array('top', 'organizationalunit'),
             ),
-            'cn' => 'kolab-admin'
         );
 
-        $this->add_entry($dn, $attrs);
+        // create set of OUs and other domain entries
+        foreach ($entries as $dn => $attrs) {
+            $this->add_entry($dn, $attrs);
+        }
 
         return true;
     }
@@ -1595,6 +1500,40 @@ class LDAP extends Net_LDAP3 {
     }
 
     /**
+     * Finds nsslapd-directory for specified domain
+     */
+    protected function nsslapd_directory($ldap, $domain)
+    {
+        $primary_domain  = $this->conf->get('kolab', 'primary_domain');
+        $_primary_domain = str_replace('.', '_', $primary_domain);
+        $_domain         = str_replace('.', '_', $domain);
+        $roots           = array($_primary_domain, $primary_domain, 'userRoot');
+
+        foreach ($roots as $root) {
+            if ($result = $ldap->get_entry("cn=$root,cn=ldbm database,cn=plugins,cn=config")) {
+                break;
+            }
+        }
+
+        $this->_log(LOG_DEBUG, "Primary domain ldbm database configuration entry: " . var_export($result, true));
+
+        $result         = $result[key($result)];
+        $orig_directory = $result['nsslapd-directory'];
+        $directory      = $orig_directory;
+
+        reset($roots);
+        foreach ($roots as $root) {
+            if ($directory == $orig_directory) {
+                $directory = str_replace($root, $_domain, $result['nsslapd-directory']);
+            }
+        }
+
+        $this->_log(LOG_DEBUG, "nsslapd-directory for domain $domain is $directory");
+
+        return $directory;
+    }
+
+    /**
      * Get global handle for memcache access
      *
      * @return object Memcache


commit f434be6ad70510471bf1ccada311c869a5bc6f62
Author: Aleksander Machniak <alec at alec.pl>
Date:   Fri Jan 17 11:02:18 2014 +0100

    Delete domains by status change and related improvements e.g.
    check if domain is empty and warn the user before setting status to deleted
    TODO: shell script for real domains deletion

diff --git a/lib/Auth.php b/lib/Auth.php
index 40f3049..4aee7ad 100644
--- a/lib/Auth.php
+++ b/lib/Auth.php
@@ -234,6 +234,11 @@ class Auth {
         return $this->auth_instance()->domain_info($domaindata);
     }
 
+    public function domain_is_empty($domain)
+    {
+        return $this->auth_instance()->domain_is_empty($domain);
+    }
+
     public function find_recipient($address)
     {
         return $this->auth_instance()->find_recipient($address);
diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php
index 0075166..5958166 100644
--- a/lib/Auth/LDAP.php
+++ b/lib/Auth/LDAP.php
@@ -483,9 +483,18 @@ class LDAP extends Net_LDAP3 {
 
     public function domain_delete($domain)
     {
-        $base_dn = $this->conf->get('ldap', 'domain_base_dn');
+        $domain = $this->domain_info($domain);
+
+        if (empty($domain)) {
+            return false;
+        }
+
+        $domain_dn  = key($domain);
+        $attributes = array_merge($domain[$domain_dn], array('inetdomainstatus' => 'deleted'));
 
-        return $this->entry_delete($domain, array(), $base_dn);
+        // for performance reasons we set only domain status,
+        // cronjob script should delete such domain later
+        return $this->modify_entry($domain_dn, $domain[$domain_dn], $attributes);
     }
 
     public function domain_find_by_attribute($attribute)
@@ -521,6 +530,38 @@ class LDAP extends Net_LDAP3 {
     }
 
     /**
+     * Checkes if specified domain is empty (no users assigned)
+     *
+     * @param string $domain Domain name
+     *
+     * @return bool True if domain is empty, False otherwise
+     */
+    public function domain_is_empty($domain)
+    {
+        $this->_log(LOG_DEBUG, "Auth::LDAP::domain_is_empty($domain)");
+
+        $domain_name_attribute = $this->conf->get('ldap', 'domain_name_attribute');
+
+        if (empty($domain_name_attribute)) {
+            $domain_name_attribute = 'associateddomain';
+        }
+
+        $domain = $this->domain_info($domain);
+
+        if (!empty($domain)) {
+            $domain_dn   = key($domain);
+            $domain_name = $domain[$domain_dn][$domain_name_attribute];
+        }
+        else {
+            return false;
+        }
+
+        $result = $this->list_users(array('entrydn'), null, array('page_size' => 1), $domain_name);
+
+        return is_array($result) && $result['count'] == 0;
+    }
+
+    /**
      * Proxy to parent function in order to enable us to insert our
      * configuration.
      */
@@ -746,11 +787,11 @@ class LDAP extends Net_LDAP3 {
         return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params);
     }
 
-    public function list_users($attributes = array(), $search = array(), $params = array())
+    public function list_users($attributes = array(), $search = array(), $params = array(), $domain = null)
     {
-        $this->_log(LOG_DEBUG, "Auth::LDAP::list_users(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true));
+        $this->_log(LOG_DEBUG, "Auth::LDAP::list_users(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true) . ", " . $domain . ")");
 
-        $base_dn = $this->_subject_base_dn('user');
+        $base_dn = $this->_subject_base_dn('user', false, $domain);
         $filter  = $this->conf->get('user_filter');
 
         if (empty($filter)) {
@@ -1189,9 +1230,13 @@ class LDAP extends Net_LDAP3 {
         }
     }
 
-    private function _subject_base_dn($subject, $strict = false)
+    private function _subject_base_dn($subject, $strict = false, $domain = null)
     {
-        $subject_base_dn = $this->conf->get_raw($this->domain, $subject . "_base_dn");
+        if (empty($domain)) {
+            $domain = $this->domain;
+        }
+
+        $subject_base_dn = $this->conf->get_raw($domain, $subject . "_base_dn");
 
         if (empty($subject_base_dn)) {
             $subject_base_dn = $this->conf->get_raw("ldap", $subject . "_base_dn");
@@ -1203,10 +1248,10 @@ class LDAP extends Net_LDAP3 {
         }
 
         // Attempt to get a configured base_dn
-        $base_dn = $this->conf->get($this->domain, "base_dn");
+        $base_dn = $this->conf->get($domain, "base_dn");
 
         if (empty($base_dn)) {
-            $base_dn = $this->domain_root_dn($this->domain);
+            $base_dn = $this->domain_root_dn($domain);
         }
 
         if (!empty($subject_base_dn)) {
diff --git a/lib/api/kolab_api_service_domain.php b/lib/api/kolab_api_service_domain.php
index 4d5fcaa..711b37b 100644
--- a/lib/api/kolab_api_service_domain.php
+++ b/lib/api/kolab_api_service_domain.php
@@ -105,6 +105,7 @@ class kolab_api_service_domain extends kolab_api_service
      * @param array $post POST parameters
      *
      * @return bool True on success, False on failure
+     * @throws kolab_api_exception
      */
     public function domain_delete($getdata, $postdata)
     {
@@ -115,8 +116,15 @@ class kolab_api_service_domain extends kolab_api_service
             return false;
         }
 
-        // TODO: Input validation
-        $auth   = Auth::get_instance();
+        $auth = Auth::get_instance();
+
+        // check if domain is empty
+        if (empty($postdata['force']) || strtolower($postdata['force']) == 'false') {
+            if (!$auth->domain_is_empty($postdata['id'])) {
+                throw new kolab_api_exception(kolab_api_exception::DOMAIN_NOT_EMPTY);
+            }
+        }
+
         $result = $auth->domain_delete($postdata['id']);
 
         if ($result) {
@@ -135,13 +143,25 @@ class kolab_api_service_domain extends kolab_api_service
             return false;
         }
 
-        $auth       = Auth::get_instance();
+        $auth = Auth::get_instance();
+
+        // check if domain is empty when changing status to deleted, as in domain.delete
+        if ($postdata['inetdomainstatus'] == 'deleted'
+            && (empty($postdata['force']) || strtolower($postdata['force']) == 'false')
+        ) {
+            $domain = $auth->domain_info($postdata['id']);
+
+            if ($domain['inetdomainstatus'] != 'deleted' && !$auth->domain_is_empty($postdata['id'])) {
+                throw new kolab_api_exception(kolab_api_exception::DOMAIN_NOT_EMPTY);
+            }
+        }
+
+
         $attributes = $this->parse_input_attributes('domain', $postdata);
         $result     = $auth->domain_edit($postdata['id'], $attributes, $postdata['type_id']);
 
-        // @TODO: return unique attribute or all attributes as domain_add()
         if ($result) {
-            return true;
+            return $result;
         }
 
         return false;
diff --git a/lib/api/kolab_api_service_domain_types.php b/lib/api/kolab_api_service_domain_types.php
index 6829047..189b39f 100644
--- a/lib/api/kolab_api_service_domain_types.php
+++ b/lib/api/kolab_api_service_domain_types.php
@@ -41,7 +41,7 @@ class kolab_api_service_domain_types extends kolab_api_service
                 'optional' => true,
                 'type'     => 'select',
                 'values'   => array(
-                    '', 'active', 'suspended',
+                    '', 'active', 'suspended', 'deleted',
                 ),
             ),
         ),
diff --git a/lib/api/kolab_api_service_domains.php b/lib/api/kolab_api_service_domains.php
index ca9ea01..2373877 100644
--- a/lib/api/kolab_api_service_domains.php
+++ b/lib/api/kolab_api_service_domains.php
@@ -31,6 +31,7 @@ class kolab_api_service_domains extends kolab_api_service
 
     public $list_attribs = array(
             'associateddomain',
+            'inetdomainstatus',
             'objectclass',
             'entrydn',
         );
diff --git a/lib/client/kolab_client_task_domain.php b/lib/client/kolab_client_task_domain.php
index 4a1190a..5c23e8a 100644
--- a/lib/client/kolab_client_task_domain.php
+++ b/lib/client/kolab_client_task_domain.php
@@ -34,7 +34,7 @@ class kolab_client_task_domain extends kolab_client_task
         'name' => array('associateddomain'),
     );
 
-    protected $list_attribs = array('associateddomain');
+    protected $list_attribs = array('associateddomain', 'inetdomainstatus');
 
     /**
      * Default action.
@@ -122,6 +122,7 @@ class kolab_client_task_domain extends kolab_client_task
             'type_id'           => 'system',
             'type_id_name'      => 'system',
             'associateddomain'  => 'system',
+            'inetdomainstatus'  => 'system',
         );
 
         //console("domain_form() \$data", $data);
@@ -190,12 +191,26 @@ class kolab_client_task_domain extends kolab_client_task
     /**
      * List item handler
      */
-    protected function list_item_handler($item)
+    protected function list_item_handler($item, &$class)
     {
+        if ($item['inetdomainstatus'] == 'deleted') {
+            $class[] = 'deleted';
+        }
+
         if (is_array($item['associateddomain'])) {
             return $item['associateddomain'][0];
         }
 
         return $item['associateddomain'];
     }
+
+    /**
+     * Returns false when object matches current domain
+     */
+    protected function is_deletable($data)
+    {
+        // domain delete is done by setting inetdomainstatus to 'deleted'
+        return false;
+    }
+
 }
diff --git a/lib/client/kolab_client_task_user.php b/lib/client/kolab_client_task_user.php
index 40c2fcc..6d51754 100644
--- a/lib/client/kolab_client_task_user.php
+++ b/lib/client/kolab_client_task_user.php
@@ -255,4 +255,11 @@ class kolab_client_task_user extends kolab_client_task
         return empty($item['displayname']) ? $item['cn'] : $item['displayname'];
     }
 
+    /**
+     * Returns true when current object matches logged user
+     */
+    protected function is_deletable($data)
+    {
+        return parent::is_deletable($data) && $data['id'] != $_SESSION['user']['id'];
+    }
 }
diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php
index 9a4ecb2..3f69613 100644
--- a/lib/ext/Net/LDAP3.php
+++ b/lib/ext/Net/LDAP3.php
@@ -677,7 +677,6 @@ class Net_LDAP3
 
         if (ldap_delete($this->conn, $entry_dn) === false) {
             $this->_debug("LDAP: S: " . ldap_error($this->conn));
-            $this->_debug("LDAP: Delete failed. " . ldap_error($this->conn));
             return false;
         }
 
@@ -686,6 +685,32 @@ class Net_LDAP3
         return true;
     }
 
+    /**
+     * Deletes specified entry and all entries in the tree
+     */
+    public function delete_entry_recursive($entry_dn)
+    {
+        // searching for sub entries, but not scope sub, just one level
+        $this->config_set('return_attributes', array('entrydn'));
+        $result = $this->search($entry_dn, '(objectclass=*)', 'one');
+
+        if ($result) {
+            $entries = $result->entries(true);
+
+            foreach (array_keys($entries) as $sub_dn) {
+                if (!$this->delete_entry_recursive($sub_dn)) {
+                    return false;
+                }
+            }
+        }
+
+        if (!$this->delete_entry($entry_dn)) {
+            return false;
+        }
+
+        return true;
+    }
+
     public function effective_rights($subject)
     {
         $effective_rights_control_oid = "1.3.6.1.4.1.42.2.27.9.5.2";
@@ -1411,7 +1436,6 @@ class Net_LDAP3
         if ($result) {
             return $mod_array;
         }
-
     }
 
     /**
diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index 20c4bc0..8cac7e0 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -1358,16 +1358,13 @@ class kolab_client_task
             ));
         }
 
-        if (!empty($data['id']) && in_array('delete', (array) $data['effective_rights']['entry'])) {
+        // add delete button
+        if ($this->is_deletable($data)) {
             $id = $data['id'];
-
-            // disable delete for self
-            if ($id != $_SESSION['user']['id']) {
-                $form->add_button(array(
-                    'value'   => kolab_html::escape($this->translate('button.delete')),
-                    'onclick' => "kadm.{$name}_delete('{$id}')",
-                ));
-            }
+            $form->add_button(array(
+                'value'   => kolab_html::escape($this->translate('button.delete')),
+                'onclick' => "kadm.{$name}_delete('{$id}')",
+            ));
         }
 
         $ac_min_len = $this->config_get('autocomplete_min_length', 1, Conf::INT);
@@ -1378,12 +1375,21 @@ class kolab_client_task
         $this->output->set_env('autocomplete_min_length', $ac_min_len);
         $this->output->add_translation('form.required.empty', 'form.maxcount.exceeded',
             $name . '.add.success', $name . '.edit.success', $name . '.delete.success',
-            $name . '.delete.confirm', 'add', 'edit', 'delete');
+            $name . '.delete.confirm', $name . '.delete.force',
+            'add', 'edit', 'delete');
 
         return $form;
     }
 
     /**
+     * Check wether specified object can be deleted
+     */
+    protected function is_deletable($data)
+    {
+        return !empty($data['id']) && in_array('delete', (array) $data['effective_rights']['entry']);
+    }
+
+    /**
      * Search form.
      *
      * @return string HTML output of the form
@@ -1564,8 +1570,10 @@ class kolab_client_task
                     continue;
                 }
 
+                $class = array('selectable');
+
                 if (method_exists($this, 'list_item_handler')) {
-                    $item = $this->list_item_handler($item);
+                    $item = $this->list_item_handler($item, $class);
                 }
                 else {
                     $item = array_shift($item);
@@ -1579,7 +1587,7 @@ class kolab_client_task
                 $cells = array();
                 $cells[] = array('class' => 'name', 'body' => kolab_html::escape($item),
                     'onclick' => "kadm.command('$task.info', '$idx')");
-                $rows[] = array('id' => $i, 'class' => 'selectable', 'cells' => $cells);
+                $rows[] = array('id' => $i, 'class' => implode(' ', $class), 'cells' => $cells);
             }
         }
         else {
diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php
index 5e288e9..7f59947 100644
--- a/lib/locale/en_US.php
+++ b/lib/locale/en_US.php
@@ -49,10 +49,11 @@ $LANG['domain.add'] = 'Add Domain';
 $LANG['domain.add.success'] = 'Domain created successfully.';
 $LANG['domain.associateddomain'] = 'Domain name(s)';
 $LANG['domain.delete.confirm'] = 'Are you sure, you want to delete this domain?';
+$LANG['domain.delete.force'] = "There are users assigned to this domain.\nAre you sure, you want to delete this domain and all assigned objects?";
 $LANG['domain.delete.success'] = 'Domain deleted successfully.';
 $LANG['domain.edit'] = 'Edit domain';
 $LANG['domain.edit.success'] = 'Domain updated successfully.';
-$LANG['domain.inetdomainbasedn'] = 'Custom Root DN(s)';
+$LANG['domain.inetdomainbasedn'] = 'Custom Root DN';
 $LANG['domain.inetdomainstatus'] = 'Status';
 $LANG['domain.list'] = 'Domains List';
 $LANG['domain.norecords'] = 'No domain records found!';
diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js
index e487688..87f355b 100644
--- a/public_html/js/kolab_admin.js
+++ b/public_html/js/kolab_admin.js
@@ -1406,6 +1406,7 @@ function kolab_admin()
 
   this.domain_delete = function(id)
   {
+    this.env.delete_domain_id = id;
     this.delete_handler(id, 'domain');
   };
 
@@ -1433,7 +1434,17 @@ function kolab_admin()
 
   this.domain_delete_response = function(response)
   {
-    this.response_handler(response, 'domain.delete', 'domain.list', {refresh: 1});
+    // domain is not empty
+    if (response && response.code == 450) {
+      this.set_busy(false);
+
+      if (confirm(this.t('domain.delete.force'))) {
+        this.set_busy(true, 'deleting');
+        this.api_post('domain.delete', {'id': this.env.delete_domain_id, force: 1}, 'domain_delete_response');
+      }
+    }
+    else
+      this.response_handler(response, 'domain.delete', 'domain.list', {refresh: 1});
   };
 
   this.domain_add_response = function(response)
@@ -1443,7 +1454,21 @@ function kolab_admin()
 
   this.domain_edit_response = function(response)
   {
-    this.response_handler(response, 'domain.edit', 'domain.list', {refresh: 1});
+    // domain is not empty and delete was requested
+    if (response && response.code == 450) {
+      this.set_busy(false);
+
+      // warn the user and re-send the request
+      if (confirm(this.t('domain.delete.force'))) {
+        var data = this.serialize_form('#'+this.env.form_id);
+        data.force = 1;
+
+        this.set_busy(true, 'saving');
+        this.api_post('domain.edit', data, 'domain_edit_response');
+      }
+    }
+    else
+      this.response_handler(response, 'domain.edit', 'domain.list', {refresh: 1});
   };
 
   this.user_info = function(id)
diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css
index 6647caa..c6af986 100644
--- a/public_html/skins/default/style.css
+++ b/public_html/skins/default/style.css
@@ -95,6 +95,10 @@ table.list tbody tr td.empty-body {
   text-align: center;
 }
 
+table.list tbody tr.deleted td {
+  color: #999;
+}
+
 fieldset {
   border: 1px solid #d0d0d0;
   border-radius: 3px;


commit 955f4186c5e3681226d01e5f4e77ff0af99f09d6
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Jan 16 12:19:38 2014 +0100

    Add kolab_api_exception object for API responses

diff --git a/lib/kolab_api_exception.php b/lib/kolab_api_exception.php
new file mode 100644
index 0000000..c8d763d
--- /dev/null
+++ b/lib/kolab_api_exception.php
@@ -0,0 +1,71 @@
+<?php
+/*
+ +--------------------------------------------------------------------------+
+ | This file is part of the Kolab Web Admin Panel                           |
+ |                                                                          |
+ | Copyright (C) 2011-2014, Kolab Systems AG                                |
+ |                                                                          |
+ | This program is free software: you can redistribute it and/or modify     |
+ | it under the terms of the GNU Affero General Public License as published |
+ | by the Free Software Foundation, either version 3 of the License, or     |
+ | (at your option) any later version.                                      |
+ |                                                                          |
+ | This program is distributed in the hope that it will be useful,          |
+ | but WITHOUT ANY WARRANTY; without even the implied warranty of           |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the             |
+ | GNU Affero General Public License for more details.                      |
+ |                                                                          |
+ | You should have received a copy of the GNU Affero General Public License |
+ | along with this program. If not, see <http://www.gnu.org/licenses/>      |
+ +--------------------------------------------------------------------------+
+ | Author: Aleksander Machniak <machniak at kolabsys.com>                      |
+ +--------------------------------------------------------------------------+
+*/
+
+/**
+ * Main exception class for Kolab Admin API responses
+ */
+class kolab_api_exception extends Exception
+{
+    const UNAUTHORIZED     = 401;
+    const FORBIDDEN        = 403;
+    const NOT_FOUND        = 404;
+    const TIMEOUT          = 408;
+    const DOMAIN_NOT_EMPTY = 450;
+    const SERVER_ERROR     = 500;
+    const TEMP_ERROR       = 503;
+
+    /**
+     * Constructor
+     */
+    function __construct()
+    {
+        $args = func_get_args();
+
+        if (isset($args[1])) {
+            $code    = $args[1];
+            $message = $args[0];
+        }
+        else if (is_int($args[0])) {
+            $code    = $args[0];
+            $message = null;
+        }
+        else {
+            $message = $args[0];
+        }
+
+        if (!$code) {
+            $code = self::SERVER_ERROR;
+        }
+
+        if (!$message) {
+            $message = kolab_api_controller::translate("error.$code");
+
+            if (!$message) {
+                $message = "Server error.";
+            }
+        }
+
+        parent::__construct($message, $code);
+    }
+}
diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php
index bc42754..5e288e9 100644
--- a/lib/locale/en_US.php
+++ b/lib/locale/en_US.php
@@ -64,6 +64,14 @@ $LANG['domain.type_id'] = 'Standard Domain';
 $LANG['edit'] = 'Edit';
 $LANG['error'] = 'Error';
 
+$LANG['error.401'] = 'Unauthorized.';
+$LANG['error.403'] = 'Access forbidden.';
+$LANG['error.404'] = 'Object not found.';
+$LANG['error.408'] = 'Request timeout.';
+$LANG['error.450'] = 'Domain is not empty.';
+$LANG['error.500'] = 'Internal server error.';
+$LANG['error.503'] = 'Service unavailable. Try again later.';
+
 $LANG['form.required.empty'] = 'Some of the required fields are empty!';
 $LANG['form.maxcount.exceeded'] = 'Maximum count of items exceeded!';
 





More information about the commits mailing list