From 36d4183828de3842e1672121797533c82dc93f61 Mon Sep 17 00:00:00 2001 From: Gunnar Wrobel Date: Wed, 11 Feb 2009 19:53:25 +0000 Subject: [PATCH] Initial import of Kolab_Sever from Horde cvs HEAD. --- .../Kolab_Server/doc/Horde/Kolab/Server/usage.txt | 113 +++ .../examples/Horde/Kolab/Server/server.php | 35 + framework/Kolab_Server/lib/Horde/Kolab/IMAP.php | 139 +++ .../Kolab_Server/lib/Horde/Kolab/IMAP/cclient.php | 778 ++++++++++++++++ .../Kolab_Server/lib/Horde/Kolab/IMAP/pear.php | 520 +++++++++++ .../Kolab_Server/lib/Horde/Kolab/IMAP/test.php | 728 +++++++++++++++ framework/Kolab_Server/lib/Horde/Kolab/Server.php | 673 ++++++++++++++ .../Kolab_Server/lib/Horde/Kolab/Server/Object.php | 505 +++++++++++ .../lib/Horde/Kolab/Server/Object/address.php | 105 +++ .../Horde/Kolab/Server/Object/administrator.php | 45 + .../lib/Horde/Kolab/Server/Object/adminrole.php | 157 ++++ .../lib/Horde/Kolab/Server/Object/distlist.php | 52 ++ .../Horde/Kolab/Server/Object/domainmaintainer.php | 124 +++ .../lib/Horde/Kolab/Server/Object/group.php | 239 +++++ .../lib/Horde/Kolab/Server/Object/maintainer.php | 45 + .../lib/Horde/Kolab/Server/Object/server.php | 50 ++ .../lib/Horde/Kolab/Server/Object/sharedfolder.php | 109 +++ .../lib/Horde/Kolab/Server/Object/user.php | 312 +++++++ .../Kolab_Server/lib/Horde/Kolab/Server/ldap.php | 987 +++++++++++++++++++++ .../Kolab_Server/lib/Horde/Kolab/Server/test.php | 623 +++++++++++++ framework/Kolab_Server/lib/Horde/Kolab/Session.php | 384 ++++++++ .../Kolab_Server/lib/Horde/Kolab/Test/Server.php | 746 ++++++++++++++++ framework/Kolab_Server/package.xml | 255 ++++++ .../test/Horde/Kolab/Server/AddingObjectsTest.php | 73 ++ .../test/Horde/Kolab/Server/AdminTest.php | 138 +++ .../test/Horde/Kolab/Server/AllTests.php | 84 ++ .../Horde/Kolab/Server/DistListHandlingTest.php | 56 ++ .../test/Horde/Kolab/Server/GroupHandlingTest.php | 456 ++++++++++ .../test/Horde/Kolab/Server/GroupTest.php | 137 +++ .../test/Horde/Kolab/Server/ObjectTest.php | 167 ++++ .../test/Horde/Kolab/Server/ServerTest.php | 72 ++ .../test/Horde/Kolab/Server/SessionTest.php | 209 +++++ .../test/Horde/Kolab/Server/UserHandlingTest.php | 681 ++++++++++++++ .../test/Horde/Kolab/Server/UserTest.php | 125 +++ .../test/Horde/Kolab/Server/ldapTest.php | 232 +++++ .../test/Horde/Kolab/Server/testTest.php | 604 +++++++++++++ 36 files changed, 10758 insertions(+) create mode 100644 framework/Kolab_Server/doc/Horde/Kolab/Server/usage.txt create mode 100644 framework/Kolab_Server/examples/Horde/Kolab/Server/server.php create mode 100644 framework/Kolab_Server/lib/Horde/Kolab/IMAP.php create mode 100644 framework/Kolab_Server/lib/Horde/Kolab/IMAP/cclient.php create mode 100644 framework/Kolab_Server/lib/Horde/Kolab/IMAP/pear.php create mode 100644 framework/Kolab_Server/lib/Horde/Kolab/IMAP/test.php create mode 100644 framework/Kolab_Server/lib/Horde/Kolab/Server.php create mode 100644 framework/Kolab_Server/lib/Horde/Kolab/Server/Object.php create mode 100644 framework/Kolab_Server/lib/Horde/Kolab/Server/Object/address.php create mode 100644 framework/Kolab_Server/lib/Horde/Kolab/Server/Object/administrator.php create mode 100644 framework/Kolab_Server/lib/Horde/Kolab/Server/Object/adminrole.php create mode 100644 framework/Kolab_Server/lib/Horde/Kolab/Server/Object/distlist.php create mode 100644 framework/Kolab_Server/lib/Horde/Kolab/Server/Object/domainmaintainer.php create mode 100644 framework/Kolab_Server/lib/Horde/Kolab/Server/Object/group.php create mode 100644 framework/Kolab_Server/lib/Horde/Kolab/Server/Object/maintainer.php create mode 100644 framework/Kolab_Server/lib/Horde/Kolab/Server/Object/server.php create mode 100644 framework/Kolab_Server/lib/Horde/Kolab/Server/Object/sharedfolder.php create mode 100644 framework/Kolab_Server/lib/Horde/Kolab/Server/Object/user.php create mode 100644 framework/Kolab_Server/lib/Horde/Kolab/Server/ldap.php create mode 100644 framework/Kolab_Server/lib/Horde/Kolab/Server/test.php create mode 100644 framework/Kolab_Server/lib/Horde/Kolab/Session.php create mode 100644 framework/Kolab_Server/lib/Horde/Kolab/Test/Server.php create mode 100644 framework/Kolab_Server/package.xml create mode 100644 framework/Kolab_Server/test/Horde/Kolab/Server/AddingObjectsTest.php create mode 100644 framework/Kolab_Server/test/Horde/Kolab/Server/AdminTest.php create mode 100644 framework/Kolab_Server/test/Horde/Kolab/Server/AllTests.php create mode 100644 framework/Kolab_Server/test/Horde/Kolab/Server/DistListHandlingTest.php create mode 100644 framework/Kolab_Server/test/Horde/Kolab/Server/GroupHandlingTest.php create mode 100644 framework/Kolab_Server/test/Horde/Kolab/Server/GroupTest.php create mode 100644 framework/Kolab_Server/test/Horde/Kolab/Server/ObjectTest.php create mode 100644 framework/Kolab_Server/test/Horde/Kolab/Server/ServerTest.php create mode 100644 framework/Kolab_Server/test/Horde/Kolab/Server/SessionTest.php create mode 100644 framework/Kolab_Server/test/Horde/Kolab/Server/UserHandlingTest.php create mode 100644 framework/Kolab_Server/test/Horde/Kolab/Server/UserTest.php create mode 100644 framework/Kolab_Server/test/Horde/Kolab/Server/ldapTest.php create mode 100644 framework/Kolab_Server/test/Horde/Kolab/Server/testTest.php diff --git a/framework/Kolab_Server/doc/Horde/Kolab/Server/usage.txt b/framework/Kolab_Server/doc/Horde/Kolab/Server/usage.txt new file mode 100644 index 000000000..6980b2715 --- /dev/null +++ b/framework/Kolab_Server/doc/Horde/Kolab/Server/usage.txt @@ -0,0 +1,113 @@ +The "Horde_Kolab_Server" package allows you to access the Kolab +Server user database. + + +Installation of the package +=========================== + +The package is being distributed as a standard PEAR package by the +Horde project. As long as you have PEAR installed, installation should +be straight forward. + + pear channel-discover pear.horde.org + pear install --force channel://pear.horde.org/Horde_Kolab_Server + +"pear" will probably complain about the library (and its dependencies) +not being marked stable yet but the "--force" option allows to ignore +these warnings. + + +Using the package +=========================== + +This section will present some examples on how to fetch information +from the Kolab Server database. + +Currently the package only provides a LDAP and a test driver as +possible backends. The test driver is for internal testing purposes +only and emulates a LDAP system without requiring access to a real +LDAP database. So most developers will use the LDAP back end. In order +to enable the driver some basic configuration is required. + +Configurations for Horde are always set in the global "$conf" +variable. A possible configuration for "Horde_Kolab_Server" could look +like this: + + global $conf; + $conf['kolab']['server']['driver'] = 'ldap'; + $conf['kolab']['server']['params']['server'] = 'example.com'; + $conf['kolab']['server']['params']['base_dn'] = 'dc=example,dc=com'; + $conf['kolab']['server']['params']['bind_dn'] = 'cn=nobody,cn=internal,dc=example,dc=com'; + $conf['kolab']['server']['params']['bind_pw'] = 'MY_VERY_SECRET_PASSWORD'; + +With this base configuration the developer can load the code and +initialize the server object: + + require_once 'Horde/Kolab/Server.php'; + $server = Horde_Kolab_Server::singleton(); + +This server handler serves two primary purposes: + + * Identifying the ID of an object on basis of a given attribute + * Fetching an object from the database based on the ID + +A common request to the Kolab LDAP database is to return the ID of a +user given a mail address. The "$server" object will return the "DN" +("distinguished name") of the object in the LDAP database: + + $dn = $server->dnForMailAddress('wrobel@example.com'); + +The returned "DN" could look like this: + + var_dump($dn); + + string(32) "cn=Gunnar Wrobel,dc=example,dc=com" + +For alternative methods of retrieving a particular "DN" you should +consult the detailed package documentation (see below). + +This "DN" can now be used to fetch the object from the database: + + $object = $server->fetch($dn); + +The "$server" object will always return an object of class +"Horde_Kolab_Server_Object". But you will receive a sub class of this +parent class that matches the type of the object. For the example +given above the server handler returns an object of type +"Horde_Kolab_Server_Object_user": + + var_dump(get_class($object)); + + string(30) "Horde_Kolab_Server_Object_user" + +Depending on the sub class such an object presents different +features. One primary method is common to all the objects though: +Fetching attribute values with "get()". + + $attr = $object->get(KOLAB_ATTR_CN); + +"KOLAB_ATTR_CN" is being used to retrieve the "common name" of the +object: + + var_dump($attr); + + string(13) "Gunnar Wrobel" + +For additional details about the different objects and attribute types +you should consult the detailed package documentation (see below). + + +Detailed package documentation +============================== + +A detailed documentation based on the code comments and extracted via +phpDocumentor can be found at +http://dev.horde.org/api/framework/. Simply select the package +"Kolab_Server" in the package selection box in the upper right +corner. + + +ToDo +==== + +Currently the package only implements read access. At some point it should allow write access, too. This is required to convert the Kolab webadmin to use this package. diff --git a/framework/Kolab_Server/examples/Horde/Kolab/Server/server.php b/framework/Kolab_Server/examples/Horde/Kolab/Server/server.php new file mode 100644 index 000000000..55c7c3029 --- /dev/null +++ b/framework/Kolab_Server/examples/Horde/Kolab/Server/server.php @@ -0,0 +1,35 @@ +dnForMailAddress('wrobel@example.com'); +var_dump($dn); + +/** Fetch the corresponding object */ +$object = $server->fetch($dn); +var_dump(get_class($object)); + +/** Display object attributes */ +var_dump($object->get(KOLAB_ATTR_CN)); diff --git a/framework/Kolab_Server/lib/Horde/Kolab/IMAP.php b/framework/Kolab_Server/lib/Horde/Kolab/IMAP.php new file mode 100644 index 000000000..9c4bcc52b --- /dev/null +++ b/framework/Kolab_Server/lib/Horde/Kolab/IMAP.php @@ -0,0 +1,139 @@ + + * @author Thomas Jarosch + * @package Kolab_Storage + */ +class Horde_Kolab_IMAP { + + /** + * IMAP server to connect to. + * + * @var string + */ + var $_server; + + /** + * IMAP server port to connect to. + * + * @var int + */ + var $_port; + + /** + * IMAP connection. + * + * @var mixed + */ + var $_imap; + + /** + * Connection reuse detection signature. + * + * @var string + */ + var $_reuse_detection; + + /** + * Constructor. + * + * @param string $server Server to connect to + * @param int $port Port to connect to + */ + function Horde_Kolab_IMAP($server, $port) + { + $this->_server = $server; + $this->_port = $port; + } + + /** + * Attempts to return a reference to a concrete Horde_Kolab_IMAP instance. + * It will only create a new instance if no Horde_Kolab_IMAP instance + * exists. + * + * @static + * + * @param string $server Server name + * @param int $port Port + * @param boolean $annotation_required Do we actually need + * the annotation calls? + * + * @return Horde_Kolab_IMAP|PEAR_Error The concrete reference. + */ + function &singleton($server, $port, $annotation_required = true) + { + static $instances = array(); + + /** + * There are Kolab specific PHP functions available that make the IMAP + * access more efficient. If these are detected, or if they are not + * required for the current operation, the PHP IMAP implementation + * should be used. + * + * The c-client Kolab driver provides quicker IMAP routines so is + * preferable whenever possible. + */ + if ($annotation_required) { + if (function_exists('imap_status_current') + && function_exists('imap_getannotation')) { + $driver = 'cclient'; + } else { + $driver = 'pear'; + } + } else { + $driver = 'cclient'; + } + + if (isset($GLOBALS['KOLAB_TESTING'])) { + $driver = 'test'; + } + + $signature = "$server|$port|$driver"; + if (!isset($instances[$signature])) { + $instances[$signature] = &Horde_Kolab_IMAP::factory($server, $port, $driver); + } + + return $instances[$signature]; + } + + /** + * Attempts to return a concrete Horde_Kolab_IMAP instance based on the + * available PHP functionality. + * + * @param string $server Server name. + * @param int $port Server port. + * @param string $driver Which driver should we use? + * + * @return Horde_Kolab_IMAP|PEAR_Error The newly created concrete + * Horde_Kolab_IMAP instance. + */ + function &factory($server, $port, $driver = 'cclient') + { + @include_once dirname(__FILE__) . '/IMAP/' . $driver . '.php'; + + $class = 'Horde_Kolab_IMAP_' . $driver; + if (class_exists($class)) { + $driver = &new $class($server, $port); + } else { + return PEAR::raiseError(sprintf(_("Failed to load Kolab IMAP driver %s"), $driver)); + } + + return $driver; + } +} diff --git a/framework/Kolab_Server/lib/Horde/Kolab/IMAP/cclient.php b/framework/Kolab_Server/lib/Horde/Kolab/IMAP/cclient.php new file mode 100644 index 000000000..88d3e9ffe --- /dev/null +++ b/framework/Kolab_Server/lib/Horde/Kolab/IMAP/cclient.php @@ -0,0 +1,778 @@ + + * @author Thomas Jarosch + * @package Kolab_Storage + */ +class Horde_Kolab_IMAP_cclient extends Horde_Kolab_IMAP { + + /** + * Basic IMAP connection string. + * + * @var string + */ + var $_base_mbox; + + /** + * IMAP connection string that includes the folder. + * + * @var string + */ + var $_mbox; + + /** + * The signature of the current connection. + * + * @var string + */ + var $_signature; + + /** + * IMAP user name. + * + * @var string + */ + var $_login; + + /** + * IMAP password. + * + * @var string + */ + var $_password; + + /** + * Connects to the IMAP server. + * + * @param string $login The user account name. + * @param string $password The user password. + * @param boolean $tls Should TLS be used for the connection? + * + * @return boolean|PEAR_Error True in case the connection was opened + * successfully. + */ + function connect($login, $password, $tls = false) + { + $options = ''; + if (!$tls) { + $options = '/notls'; + } + + $mbox = '{' . $this->_server . ':' . $this->_port + . $options . '}'; + + $this->_signature = "$mbox|$login|$password"; + if ($this->_signature == $this->_reuse_detection) { + return true; + } + + $this->_mbox = $this->_base_mbox = $mbox; + $this->_login = $login; + $this->_password = $password; + $this->_imap = null; + + $this->_reuse_detection = $this->_signature; + + return true; + } + + /** + * Lazy connect to the IMAP server. + * + * @return mixed True in case the connection was opened successfully, a + * PEAR error otherwise. + */ + function _connect() + { + $result = @imap_open($this->_base_mbox, $this->_login, $this->_password, OP_HALFOPEN); + if (!$result) { + return PEAR::raiseError(sprintf(_("IMAP error. Server: %s. Error: %s"), $this->_server, @imap_last_error())); + } + $this->_imap = $result; + return true; + } + + /** + * Disconnects from the IMAP server. If not really necessary this + * should not be called. Once the page got served the connections + * should be closed anyhow and if there is a chance to reuse the + * connection it should be used. + * + * @return mixed True in case the connection was closed successfully, a + * PEAR error otherwise. + */ + function disconnect() + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $this->_reuse_detection = null; + + $result = @imap_close($this->_imap); + if (!$result) { + return PEAR::raiseError(sprintf(_("IMAP error. Server: %s. Error: %s"), $this->_server, @imap_last_error())); + } + return $result; + } + + /** + * Opens the given folder. + * + * @param string $folder The folder to open. + * + * @return mixed True in case the folder was opened successfully, a PEAR + * error otherwise. + */ + function select($folder) + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $this->_mbox = $this->_base_mbox . $folder; + + $result = @imap_reopen($this->_imap, $this->_mbox); + if (!$result) { + return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $folder, @imap_last_error())); + } + return $result; + } + + /** + * Does the given folder exist? + * + * @param string $folder The folder to check. + * + * @return mixed True in case the folder exists, false otherwise + */ + function exists($folder) + { + $folders = $this->getMailboxes(); + if (is_a($folders, 'PEAR_Error')) { + return $folders; + } + return in_array($folder, $folders); + } + + /** + * Create the specified folder. + * + * @param string $folder The folder to create. + * + * @return mixed True in case the operation was successfull, a + * PEAR error otherwise. + */ + function create($folder) + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $mbox = $this->_base_mbox . $folder; + $result = @imap_createmailbox($this->_imap, $mbox); + if (!$result) { + return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $folder, @imap_last_error())); + } + return $result; + } + + /** + * Delete the specified folder. + * + * @param string $folder The folder to delete. + * + * @return mixed True in case the operation was successfull, a + * PEAR error otherwise. + */ + function delete($folder) + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $mbox = $this->_base_mbox . $folder; + $result = @imap_deletemailbox($this->_imap, $mbox); + if (!$result) { + return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $folder, @imap_last_error())); + } + return $result; + } + + /** + * Rename the specified folder. + * + * @param string $old The folder to rename. + * @param string $new The new name of the folder. + * + * @return mixed True in case the operation was successfull, a + * PEAR error otherwise. + */ + function rename($old, $new) + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $result = @imap_renamemailbox($this->_imap, + $this->_base_mbox . $old, + $this->_base_mbox . $new); + if (!$result) { + return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $old, @imap_last_error())); + } + return $result; + } + + /** + * Returns the status of the current folder. + * + * @return array An array that contains 'uidvalidity' and 'uidnext'. + */ + function status() + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $status = @imap_status_current($this->_imap, SA_MESSAGES | SA_UIDVALIDITY | SA_UIDNEXT); + if (!$status) { + return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $this->_mbox, @imap_last_error())); + } + + return array('uidvalidity' => $status->uidvalidity, + 'uidnext' => $status->uidnext); + } + + /** + * Returns the uids of the messages in this folder. + * + * @return mixed The message ids or a PEAR error in case of an error. + */ + function getUids() + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $uids = @imap_search($this->_imap, 'UNDELETED', SE_UID); + if (!is_array($uids)) { + $uids = array(); + } + + return $uids; + } + + /** + * Searches the current folder using the given list of search criteria. + * + * @param string $search_list A list of search criteria. + * + * @return mixed The list of matching message ids or a PEAR error in case + * of an error. + */ + function search($search_list) + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $result = @imap_search($this->_imap, $search_list, SE_UID); + if (!$result) { + return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $this->_mbox, @imap_last_error())); + } + return $result; + } + + /** + * Searches the headers of the messages. c-client does not allow using + * "HEADER" as it is possible with Net/IMAP, so we need a workaround. + * + * @param string $field The name of the header field. + * @param string $value The value that field should match. + * + * @return mixed The list of matching message ids or a PEAR error in case + * of an error. + */ + function searchHeaders($field, $value) + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $uids = $this->getUids(); + if (is_a($uids, 'PEAR_Error')) { + return $uids; + } + + $result = array(); + foreach ($uids as $uid) { + $header = $this->getMessageHeader($uid, false); + if (is_a($header, 'PEAR_Error')) { + return $header; + } + $header_array = MIME_Headers::parseHeaders($header); + if (isset($header_array[$field]) && $header_array[$field] == $value) { + $result[] = $uid; + } + } + + return $result; + } + + /** + * Retrieves the message headers for a given message id. + * + * @param integer $uid The message id. + * @param boolean $peek_for_body Prefetch the body. + * + * @return mixed The message header or a PEAR error in case of an error. + */ + function getMessageHeader($uid, $peek_for_body = true) + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $flags = FT_UID; + if ($peek_for_body) { + $flags |= FT_PREFETCHTEXT; + } + + $result = @imap_fetchheader($this->_imap, $uid, $flags); + if (!$result) { + return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), $uid, @imap_last_error())); + } + + return $result; + } + + /** + * Retrieves the message body for a given message id. + * + * @param integer $uid The message id. + * + * @return mixed The message body or a PEAR error in case of an error. + */ + function getMessageBody($uid) + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $result = @imap_body($this->_imap, $uid, FT_UID); + if (!$result) { + return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), $uid, @imap_last_error())); + } + + return $result; + } + + /** + * Retrieves the full message text for a given message id. + * + * @param integer $uid The message id. + * + * @return mixed The message text or a PEAR error in case of an error. + */ + function getMessage($uid) + { + $header = $this->getMessageHeader($uid); + if (is_a($header, 'PEAR_Error')) { + return $header; + } + + $body = $this->getMessageBody($uid); + if (is_a($body, 'PEAR_Error')) { + return $body; + } + + return $header . $body; + } + + /** + * Retrieves a list of mailboxes on the server. + * + * @return mixed The list of mailboxes or a PEAR error in case of an + * error. + */ + function getMailboxes() + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $folders = array(); + + $result = @imap_list($this->_imap, $this->_base_mbox, '*'); + if (!$result) { + return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $this->_base_mbox, @imap_last_error())); + } + + $server_len = strlen($this->_base_mbox); + foreach ($result as $folder) { + if (substr($folder, 0, $server_len) == $this->_base_mbox) { + $folders[] = substr($folder, $server_len); + } + } + + return $folders; + } + + /** + * Fetches the annotation on a folder. + * + * @param string $entries The entry to fetch. + * @param string $value The specific value to fetch. + * @param string $mailbox_name The name of the folder. + * + * @return mixed The annotation value or a PEAR error in case of an error. + */ + function getAnnotation($entries, $value, $mailbox_name) + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + static $annotations = array(); + + $signature = "$this->_signature|$entries|$value|$mailbox_name"; + + if (!isset($annotations[$signature])) { + $result = @imap_getannotation($this->_imap, $mailbox_name, $entries, $value); + if (isset($result[$value])) { + $annotations[$signature] = $result[$value]; + } else { + $annotations[$signature] = ''; + } + } + + return $annotations[$signature]; + } + + /** + * Sets the annotation on a folder. + * + * @param string $entries The entry to set. + * @param array $values The values to set + * @param string $mailbox_name The name of the folder. + * + * @return mixed True if successfull, a PEAR error otherwise. + */ + function setAnnotation($entries, $values, $mailbox_name) + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + foreach ($values as $key => $value) { + $result = @imap_setannotation($this->_imap, $mailbox_name, $entries, $key, $value); + if (!$result) { + return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $mailbox_name, @imap_last_error())); + } + } + return true; + } + + /** + * Retrieve the access rights from a folder + * + * @param string $folder The folder to retrieve the ACLs from. + * + * @return mixed An array of rights if successfull, a PEAR error + * otherwise. + */ + function getACL($folder) + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $result = @imap_getacl($this->_imap, $folder); + if (!$result) { + return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $folder, @imap_last_error())); + } + return $result; + } + + /** + * Retrieve the access rights from a folder not owned by the current user + * + * @param string $folder The folder to retrieve the ACLs from. + * + * @return mixed An array of rights if successfull, a PEAR error + * otherwise. + */ + function getMyRights($folder) + { + if (!function_exists('imap_myrights')) { + return PEAR::raiseError(sprintf(_("PHP does not support imap_myrights."), $folder, @imap_last_error())); + } + + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $result = @imap_myrights($this->_imap, $folder); + if (!$result) { + return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $folder, @imap_last_error())); + } + return $result; + + } + + /** + * Set the access rights for a folder + * + * @param string $folder The folder to retrieve the ACLs from. + * @param string $user The user to set the ACLs for + * @param string $acl The ACLs + * + * @return mixed True if successfull, a PEAR error otherwise. + */ + function setACL($folder, $user, $acl) + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $result = @imap_setacl($this->_imap, $folder, $user, $acl); + if (!$result) { + return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $folder, @imap_last_error())); + } + return $result; + } + + /** + * Delete the access rights for a user. + * + * @param string $folder The folder that should be modified. + * @param string $user The user that should get the ACLs removed + * + * @return mixed True if successfull, a PEAR error otherwise. + */ + function deleteACL($folder, $user) + { + return $this->setACL($folder, $user, ''); + } + + /** + * Appends a message to the current folder. + * + * @param string $msg The message to append. + * + * @return mixed True or a PEAR error in case of an error. + */ + function appendMessage($msg) + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $result = @imap_append($this->_imap, $this->_mbox, $msg); + if (!$result) { + return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $this->_mbox, @imap_last_error())); + } + return $result; + } + + /** + * Copies a message to a new folder. + * + * @param integer $uid IMAP message id. + * @param string $new_folder Target folder. + * + * @return mixed True or a PEAR error in case of an error. + */ + function copyMessage($uid, $new_folder) + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $result = @imap_mail_copy($this->_imap, $uid, $new_folder, CP_UID); + if (!$result) { + return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $new_folder, @imap_last_error())); + } + return $result; + } + + /** + * Moves a message to a new folder. + * + * @param integer $uid IMAP message id. + * @param string $new_folder Target folder. + * + * @return mixed True or a PEAR error in case of an error. + */ + function moveMessage($uid, $new_folder) + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $result = @imap_mail_move($this->_imap, $uid, $new_folder, CP_UID); + if (!$result) { + return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $new_folder, @imap_last_error())); + } + return $result; + } + + /** + * Deletes messages from the current folder. + * + * @param integer $uids IMAP message ids. + * + * @return mixed True or a PEAR error in case of an error. + */ + function deleteMessages($uids) + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + if (!is_array($uids)) { + $uids = array($uids); + } + + foreach($uids as $uid) { + $result = @imap_delete($this->_imap, $uid, FT_UID); + if (!$result) { + return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), $uid, @imap_last_error())); + } + } + return true; + } + + /** + * Undeletes a message in the current folder. + * + * @param integer $uid IMAP message id. + * + * @return mixed True or a PEAR error in case of an error. + */ + function undeleteMessages($uid) + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $result = @imap_undelete($this->_imap, $uid, FT_UID); + if (!$result) { + return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), $uid, @imap_last_error())); + } + return $result; + } + + /** + * Expunges messages in the current folder. + * + * @return mixed True or a PEAR error in case of an error. + */ + function expunge() + { + if (!isset($this->_imap)) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $result = @imap_expunge($this->_imap); + if (!$result) { + return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), $this->_mbox, @imap_last_error())); + } + return $result; + } + + /** + * Return the currently selected mailbox + * + * @return string The mailbox currently selected + */ + function current() + { + return $this->_mbox; + } +} diff --git a/framework/Kolab_Server/lib/Horde/Kolab/IMAP/pear.php b/framework/Kolab_Server/lib/Horde/Kolab/IMAP/pear.php new file mode 100644 index 000000000..49b911ece --- /dev/null +++ b/framework/Kolab_Server/lib/Horde/Kolab/IMAP/pear.php @@ -0,0 +1,520 @@ += 1.0.3 of Net_IMAP (i.e. a + * version that includes support for the ANNOTATEMORE IMAP extension). The + * latest version of Net_IMAP can be obtained from + * http://pear.php.net/get/Net_IMAP + */ +require_once 'Net/IMAP.php'; + +/** + * The Horde_Kolab_IMAP_Connection_pear class connects to an IMAP server using the + * Net_IMAP PEAR package. + * + * $Horde: framework/Kolab_Server/lib/Horde/Kolab/IMAP/pear.php,v 1.2 2009/01/06 17:49:25 jan Exp $ + * + * Copyright 2007-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Gunnar Wrobel + * @author Thomas Jarosch + * @package Kolab_Storage + */ +class Horde_Kolab_IMAP_pear extends Horde_Kolab_IMAP { + + /** + * The signature of the current connection + * + * @var string + */ + var $_signature; + + /** + * Connects to the IMAP server. + * + * @param string $login The user account name. + * @param string $password The user password. + * @param boolean $tls Should TLS be used for the connection? + * + * @return mixed True in case the connection was opened successfully, a + * PEAR error otherwise. + */ + function connect($login, $password, $tls = false) + { + $this->_signature = $this->_server . '|' . $this->_port . "|$login|$password|$tls"; + + // Reuse existing connection? + if ($this->_signature == $this->_reuse_detection) { + return true; + } + + $this->_imap = &new Net_IMAP($this->_server, $this->_port); + $result = $this->_imap->login($login, $password, true, false); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + $this->_reuse_detection = $this->_signature; + + return true; + } + + /** + * Disconnects from the IMAP server. + * + * @return mixed True in case the connection was closed successfully, a + * PEAR error otherwise. + */ + function disconnect() + { + $this->_reuse_detection = null; + return $this->_imap->disconnect(); + } + + /** + * Opens the given folder. + * + * @param string $folder The folder to open + * + * @return mixed True in case the folder was opened successfully, a PEAR + * error otherwise. + */ + function select($folder) + { + return $this->_imap->selectMailbox($folder); + } + + /** + * Does the given folder exist? + * + * @param string $folder The folder to check. + * + * @return mixed True in case the folder exists, false otherwise + */ + function exists($folder) + { + return $this->_imap->mailboxExist($folder); + } + + /** + * Create the specified folder. + * + * @param string $folder The folder to create. + * + * @return mixed True in case the operation was successfull, a + * PEAR error otherwise. + */ + function create($folder) + { + return $this->_imap->createMailbox($folder); + } + + /** + * Delete the specified folder. + * + * @param string $folder The folder to delete. + * + * @return mixed True in case the operation was successfull, a + * PEAR error otherwise. + */ + function delete($folder) + { + return $this->_imap->deleteMailbox($folder); + } + + /** + * Rename the specified folder. + * + * @param string $old The folder to rename. + * @param string $new The new name of the folder. + * + * @return mixed True in case the operation was successfull, a + * PEAR error otherwise. + */ + function rename($old, $new) + { + return $this->_imap->renameMailbox($old, $new); + } + + /** + * Returns the status of the current folder. + * + * @return array An array that contains 'uidvalidity' and 'uidnext'. + */ + function status() + { + $result = array(); + + $mailbox = $this->_imap->getCurrentMailbox(); + + // Net_IMAP is not very efficent here + $ret = $this->_imap->cmdStatus($mailbox, 'UIDVALIDITY'); + $result['uidvalidity'] = $ret['PARSED']['STATUS']['ATTRIBUTES']['UIDVALIDITY']; + + $ret = $this->_imap->cmdStatus($mailbox, 'UIDNEXT'); + $result['uidnext'] = $ret['PARSED']['STATUS']['ATTRIBUTES']['UIDNEXT']; + + return $result; + } + + /** + * Returns the message ids of the messages in this folder. + * + * @return array The message ids. + */ + function getUids() + { + $uids = $this->_imap->search('UNDELETED', true); + if (!is_array($uids)) { + $uids = array(); + } + return $uids; + } + + /** + * Searches the current folder using the given list of search criteria. + * + * @param string $search_list A list of search criteria. + * + * @return mixed The list of matching message ids or a PEAR error in case + * of an error. + */ + function search($search_list, $uidSearch = true) + { + return $this->_imap->search($search_list, $uidSearch); + } + + /** + * Searches the headers of the messages. + * + * @param string $field The name of the header field. + * @param string $value The value that field should match. + * + * @return mixed The list of matching message ids or a PEAR error in case + * of an error. + */ + function searchHeaders($field, $value) + { + return $this->_imap->search('HEADER "' . $field . '" "' . $value . '"', true); + } + + /** + * Retrieves the message headers for a given message id. + * + * @param int $uid The message id. + * @param boolean $peek_for_body Prefetch the body. + * + * @return mixed The message header or a PEAR error in case of an error. + */ + function getMessageHeader($uid, $peek_for_body = true) + { + $ret = $this->_imap->cmdUidFetch($uid, 'BODY[HEADER]'); + if (String::upper($ret['RESPONSE']['CODE']) != 'OK') { + return PEAR::raiseError(sprintf(_("Failed fetching headers of IMAP message %s. Error was %s"), + $uid, + $ret['RESPONSE']['CODE'] . ', ' . $ret['RESPONSE']['STR_CODE'])); + } + + if (isset($ret['PARSED'])) { + foreach ($ret['PARSED'] as $msg) { + if (isset($msg['EXT']['BODY[HEADER]']['CONTENT'])) { + return $msg['EXT']['BODY[HEADER]']['CONTENT']; + } + } + } + + return ''; + } + + /** + * Retrieves the message body for a given message id. + * + * @param integet $uid The message id. + * + * @return mixed The message body or a PEAR error in case of an error. + */ + function getMessageBody($uid) + { + $ret = $this->_imap->cmdUidFetch($uid, 'BODY[TEXT]'); + if (String::upper($ret['RESPONSE']['CODE']) != 'OK') { + return PEAR::raiseError(sprintf(_("Failed fetching body of IMAP message %s. Error was %s"), + $uid, + $ret['RESPONSE']['CODE'] . ', ' . $ret['RESPONSE']['STR_CODE'])); + } + + if (isset($ret['PARSED'])) { + foreach ($ret['PARSED'] as $msg) { + if (isset($msg['EXT']['BODY[TEXT]']['CONTENT'])) { + return $msg['EXT']['BODY[TEXT]']['CONTENT']; + } + } + } + + return ''; + } + + /** + * Retrieves the full message text for a given message id. + * + * @param integer $uid The message id. + * + * @return mixed The message text or a PEAR error in case of an error. + */ + function getMessage($uid) + { + $ret = $this->_imap->cmdUidFetch($uid, 'RFC822'); + if (String::upper($ret['RESPONSE']['CODE']) != 'OK') { + return PEAR::raiseError(sprintf(_("Failed fetching IMAP message %s. Error was %s"), + $uid, + $ret['RESPONSE']['CODE'] . ', ' . $ret['RESPONSE']['STR_CODE'])); + } + + if (isset($ret['PARSED'])) { + foreach ($ret['PARSED'] as $msg) { + if (isset($msg['EXT']['RFC822']['CONTENT'])) { + return $msg['EXT']['RFC822']['CONTENT']; + } + } + } + + return ''; + } + + /** + * Retrieves a list of mailboxes on the server. + * + * @return mixed The list of mailboxes or a PEAR error in case of an + * error. + */ + function getMailboxes() + { + return $this->_imap->getMailboxes(); + } + + /** + * Fetches the annotation on a folder. + * + * @param string $entries The entry to fetch. + * @param string $value The specific value to fetch. + * @param string $mailbox_name The name of the folder. + * + * @return mixed The annotation value or a PEAR error in case of an error. + */ + function getAnnotation($entries, $value, $mailbox_name) + { + static $annotations = array(); + + $signature = "$this->_signature|$entries|$value|$mailbox_name"; + + if (!isset($annotations[$signature])) { + $annotations[$signature] = $this->_imap->getAnnotation($entries, $value, $mailbox_name); + } + + return $annotations[$signature]; + } + + /** + * Sets the annotation on a folder. + * + * @param string $entries The entry to set. + * @param array $values The values to set + * @param string $mailbox_name The name of the folder. + * + * @return mixed True if successfull, a PEAR error otherwise. + */ + function setAnnotation($entries, $values, $mailbox_name) + { + return $this->_imap->setAnnotation($entries, $values, $mailbox_name); + } + + /** + * Retrieve the access rights from a folder + * + * @param string $folder The folder to retrieve the ACLs from. + * + * @return mixed An array of rights if successfull, a PEAR error + * otherwise. + */ + function getACL($folder) + { + $result = $this->_imap->getACL($folder); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + $acl = array(); + foreach ($result as $user) { + $acl[$user['USER']] = $user['RIGHTS']; + } + return $acl; + } + + /** + * Retrieve the access rights on a folder not owned by the current user + * + * @param string $folder The folder to retrieve the ACLs from. + * + * @return mixed An array of rights if successfull, a PEAR error + * otherwise. + */ + function getMyRights($folder) + { + $result = $this->_imap->getMyRights($folder); + return $result; + } + + /** + * Set the access rights for a folder + * + * @param string $folder The folder to retrieve the ACLs from. + * @param string $user The user to set the ACLs for + * @param string $acl The ACLs + * + * @return mixed True if successfull, a PEAR error otherwise. + */ + function setACL($folder, $user, $acl) + { + return $this->_imap->setACL($folder, $user, $acl); + } + + /** + * Delete the access rights for a user. + * + * @param string $folder The folder that should be modified. + * @param string $user The user that should get the ACLs removed + * + * @return mixed True if successfull, a PEAR error otherwise. + */ + function deleteACL($folder, $user) + { + return $this->_imap->deleteACL($folder, $user); + } + + /** + * Appends a message to the current folder. + * + * @param string $msg The message to append. + * + * @return mixed True or a PEAR error in case of an error. + */ + function appendMessage($msg) + { + return $this->_imap->appendMessage($msg); + } + + /** + * Copies a message to a new folder. + * + * @param integer $uid IMAP message id. + * @param string $new_folder Target folder. + * + * @return mixed True or a PEAR error in case of an error. + */ + function copyMessage($uid, $new_folder) + { + $ret = $this->_imap->cmdUidCopy($uid, $new_folder); + if (String::upper($ret['RESPONSE']['CODE']) != 'OK') { + return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), + $uid, + $ret['RESPONSE']['CODE'] . ', ' . $ret['RESPONSE']['STR_CODE'])); + } + return true; + } + + /** + * Moves a message to a new folder. + * + * @param integer $uid IMAP message id. + * @param string $new_folder Target folder. + * + * @return mixed True or a PEAR error in case of an error. + */ + function moveMessage($uid, $new_folder) + { + $result = $this->copyMessage($uid, $new_folder); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + $result = $this->deleteMessages($uid); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + $result = $this->expunge(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + return true; + } + + /** + * Deletes messages from the current folder. + * + * @param integer $uids IMAP message ids. + * + * @return mixed True or a PEAR error in case of an error. + */ + function deleteMessages($uids) + { + if (!is_array($uids)) { + $uids = array($uids); + } + + foreach ($uids as $uid) { + $ret = $this->_imap->cmdUidStore($uid, '+FLAGS.SILENT', '\Deleted'); + if (String::upper($ret['RESPONSE']['CODE']) != 'OK') { + return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), + $uid, + $ret['RESPONSE']['CODE'] . ', ' . $ret['RESPONSE']['STR_CODE'])); + } + } + + return true; + } + + /** + * Undeletes a message in the current folder. + * + * @param integer $uid IMAP message id. + * + * @return mixed True or a PEAR error in case of an error. + */ + function undeleteMessages($uid) + { + $ret = $this->_imap->cmdUidStore($uid, '-FLAGS.SILENT', '\Deleted'); + if (String::upper($ret['RESPONSE']['CODE']) != 'OK') { + return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), + $uid, + $ret['RESPONSE']['CODE'] . ', ' . $ret['RESPONSE']['STR_CODE'])); + } + return true; + } + + /** + * Expunges messages in the current folder. + * + * @return mixed True or a PEAR error in case of an error. + */ + function expunge() + { + return $this->_imap->expunge(); + } + + /** + * Return the currently selected mailbox + * + * @return string The mailbox currently selected + */ + function current() + { + return $this->_imap->getCurrentMailbox(); + } +} diff --git a/framework/Kolab_Server/lib/Horde/Kolab/IMAP/test.php b/framework/Kolab_Server/lib/Horde/Kolab/IMAP/test.php new file mode 100644 index 000000000..ee8d0c423 --- /dev/null +++ b/framework/Kolab_Server/lib/Horde/Kolab/IMAP/test.php @@ -0,0 +1,728 @@ + + * @package Kolab_Storage + */ +class Horde_Kolab_IMAP_test extends Horde_Kolab_IMAP { + + /** + * If we are supposed to be connected this holds the user + * credentials and some connection details. + * + * @var string + */ + var $_connected; + + /** + * Login of the current user + * + * @var string + */ + var $_user; + + /** + * The data of the mailbox currently opened + * + * @var array + */ + var $_mbox = null; + + /** + * The name of the mailbox currently opened + * + * @var array + */ + var $_mboxname = null; + + /** + * Prepare the dummy server. + * + * @param string $login The user account name. + * @param string $password The user password. + * @param boolean $tls Should TLS be used for the connection? + * + * @return mixed True in case the connection was opened successfully, a + * PEAR error otherwise. + */ + function connect($login, $password, $tls = false) + { + if (!is_array($GLOBALS['KOLAB_TESTING'])) { + /* Simulate an empty IMAP server */ + $GLOBALS['KOLAB_TESTING'] = array(); + } + + $tls = ($tls) ? 'tls' : 'notls'; + $this->_connected = $login . ':' . $password . ':' . $tls; + $this->_user = $login; + $this->_mbox = null; + $this->_mboxname = null; + } + + /** + * Disconnects from the IMAP server. + * + * @return mixed True in case the connection was closed successfully, a + * PEAR error otherwise. + */ + function disconnect() + { + $this->_connected = null; + } + + function _parseFolder($folder) + { + if (substr($folder, 0, 5) == 'INBOX') { + $user = split('@', $this->_user); + return 'user/' . $user[0] . substr($folder, 5); + } + return $folder; + } + + /** + * Opens the given folder. + * + * @param string $folder The folder to open + * + * @return mixed True in case the folder was opened successfully, a PEAR + * error otherwise. + */ + function select($folder) + { + $folder = $this->_parseFolder($folder); + if (!isset($GLOBALS['KOLAB_TESTING'][$folder])) { + return PEAR::raiseError(sprintf("IMAP folder %s does not exist!", $folder)); + } + $this->_mbox = &$GLOBALS['KOLAB_TESTING'][$folder]; + $this->_mboxname = $folder; + return true; + } + + /** + * Does the given folder exist? + * + * @param string $folder The folder to check. + * + * @return mixed True in case the folder exists, false otherwise + */ + function exists($folder) + { + $folder = $this->_parseFolder($folder); + if (!isset($GLOBALS['KOLAB_TESTING'][$folder])) { + return false; + } + return true; + } + + /** + * Create the specified folder. + * + * @param string $folder The folder to create. + * + * @return mixed True in case the operation was successfull, a + * PEAR error otherwise. + */ + function create($folder) + { + $folder = $this->_parseFolder($folder); + if (isset($GLOBALS['KOLAB_TESTING'][$folder])) { + return PEAR::raiseError(sprintf("IMAP folder %s does already exist!", $folder)); + } + $GLOBALS['KOLAB_TESTING'][$folder] = array( + 'status' => array( + 'uidvalidity' => time(), + 'uidnext' => 1), + 'mails' => array(), + 'permissions' => array(), + 'annotations' => array(), + ); + return true; + } + + /** + * Delete the specified folder. + * + * @param string $folder The folder to delete. + * + * @return mixed True in case the operation was successfull, a + * PEAR error otherwise. + */ + function delete($folder) + { + $folder = $this->_parseFolder($folder); + if (!isset($GLOBALS['KOLAB_TESTING'][$folder])) { + return PEAR::raiseError(sprintf("IMAP folder %s does not exist!", $folder)); + } + unset($GLOBALS['KOLAB_TESTING'][$folder]); + return true; + } + + /** + * Rename the specified folder. + * + * @param string $old The folder to rename. + * @param string $new The new name of the folder. + * + * @return mixed True in case the operation was successfull, a + * PEAR error otherwise. + */ + function rename($old, $new) + { + $old = $this->_parseFolder($old); + $new = $this->_parseFolder($new); + + if (!isset($GLOBALS['KOLAB_TESTING'][$old])) { + return PEAR::raiseError(sprintf("IMAP folder %s does not exist!", $old)); + } + if (isset($GLOBALS['KOLAB_TESTING'][$new])) { + return PEAR::raiseError(sprintf("IMAP folder %s does already exist!", $new)); + } + $GLOBALS['KOLAB_TESTING'][$new] = $GLOBALS['KOLAB_TESTING'][$old]; + unset($GLOBALS['KOLAB_TESTING'][$old]); + return true; + } + + /** + * Returns the status of the current folder. + * + * @return array An array that contains 'uidvalidity' and 'uidnext'. + */ + function status() + { + if (!$this->_mbox) { + return PEAR::raiseError("No IMAP folder selected!"); + } + return $this->_mbox['status']; + } + + /** + * Returns the message ids of the messages in this folder. + * + * @return array The message ids. + */ + function getUids() + { + if (!$this->_mbox) { + return PEAR::raiseError("No IMAP folder selected!"); + } + $uids = array(); + foreach ($this->_mbox['mails'] as $uid => $mail) { + if (!($mail['flags'] & KOLAB_IMAP_FLAG_DELETED)) { + $uids[] = $uid; + } + } + return $uids; + } + + /** + * Searches the current folder using the given list of search criteria. + * + * @param string $search_list A list of search criteria. + * + * @return mixed The list of matching message ids or a PEAR error in case + * of an error. + */ + function search($search_list, $uidSearch = true) + { + if (!$this->_mbox) { + return PEAR::raiseError("No IMAP folder selected!"); + } + $uids = array(); + if (substr($search_list, 0, 7) == 'SUBJECT') { + $needle = '^Subject: ' . substr($search_list, 8); + foreach ($this->_mbox['mails'] as $uid => $mail) { + if (preg_match($needle, $mail['header'])) { + $uids[] = $uid; + } + } + } else if (substr($search_list, 0, 6) == 'HEADER') { + preg_match('([^ ]*) ([^ ]*)', substr($search_list, 7), $matches); + $needle = '^' . $matches[0] . ': ' . $matches[1]; + foreach ($this->_mbox['mails'] as $uid => $mail) { + if (preg_match($needle, $mail['header'])) { + $uids[] = $uid; + } + } + + } + return $uids; + } + + /** + * Searches the headers of the messages. + * + * @param string $field The name of the header field. + * @param string $value The value that field should match. + * + * @return mixed The list of matching message ids or a PEAR error in case + * of an error. + */ + function searchHeaders($field, $value) + { + return $this->search('HEADER "' . $field . '" "' . $value . '"', true); + } + + /** + * Retrieves the message headers for a given message id. + * + * @param int $uid The message id. + * @param boolean $peek_for_body Prefetch the body. + * + * @return mixed The message header or a PEAR error in case of an error. + */ + function getMessageHeader($uid, $peek_for_body = true) + { + if (!$this->_mbox) { + return PEAR::raiseError("No IMAP folder selected!"); + } + if (!isset($this->_mbox['mails'][$uid])) { + return PEAR::raiseError(sprintf("No IMAP message %s!", $uid)); + } + return $this->_mbox['mails'][$uid]['header']; + } + + /** + * Retrieves the message body for a given message id. + * + * @param integet $uid The message id. + * + * @return mixed The message body or a PEAR error in case of an error. + */ + function getMessageBody($uid) + { + if (!$this->_mbox) { + return PEAR::raiseError("No IMAP folder selected!"); + } + if (!isset($this->_mbox['mails'][$uid])) { + return PEAR::raiseError(sprintf("No IMAP message %s!", $uid)); + } + return $this->_mbox['mails'][$uid]['body']; + } + + /** + * Retrieves the full message text for a given message id. + * + * @param integer $uid The message id. + * + * @return mixed The message text or a PEAR error in case of an error. + */ + function getMessage($uid) + { + if (!$this->_mbox) { + return PEAR::raiseError("No IMAP folder selected!"); + } + if (!isset($this->_mbox['mails'][$uid])) { + return PEAR::raiseError(sprintf("No IMAP message %s!", $uid)); + } + return $this->_mbox['mails'][$uid]['header'] . $this->_mbox['mails'][$uid]['body']; + } + + /** + * Retrieves a list of mailboxes on the server. + * + * @return mixed The list of mailboxes or a PEAR error in case of an + * error. + */ + function getMailboxes() + { + $mboxes = array_keys($GLOBALS['KOLAB_TESTING']); + $user = split('@', $this->_user); + $pattern = '#^user/' . $user[0] . '#'; + $result = array(); + foreach ($mboxes as $mbox) { + $result[] = preg_replace($pattern, 'INBOX', $mbox); + } + return $result; + } + + /** + * Fetches the annotation on a folder. + * + * @param string $entries The entry to fetch. + * @param string $value The specific value to fetch. + * @param string $mailbox_name The name of the folder. + * + * @return mixed The annotation value or a PEAR error in case of an error. + */ + function getAnnotation($entries, $value, $mailbox_name) + { + $mailbox_name = $this->_parseFolder($mailbox_name); + $old_mbox = null; + if ($mailbox_name != $this->_mboxname) { + $old_mbox = $this->_mboxname; + $result = $this->select($mailbox_name); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + if (!isset($this->_mbox['annotations'][$entries]) + || !isset($this->_mbox['annotations'][$entries][$value])) { + return false; + } + $annotation = $this->_mbox['annotations'][$entries][$value]; + if ($old_mbox) { + $this->select($old_mbox); + } + return $annotation; + } + + /** + * Sets the annotation on a folder. + * + * @param string $entries The entry to set. + * @param array $values The values to set + * @param string $mailbox_name The name of the folder. + * + * @return mixed True if successfull, a PEAR error otherwise. + */ + function setAnnotation($entries, $values, $mailbox_name) + { + $mailbox_name = $this->_parseFolder($mailbox_name); + $old_mbox = null; + if ($mailbox_name != $this->_mboxname) { + $old_mbox = $this->_mboxname; + $result = $this->select($mailbox_name); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + if (!isset($this->_mbox['annotations'][$entries])) { + $this->_mbox['annotations'][$entries] = array(); + } + foreach ($values as $key => $value) { + $this->_mbox['annotations'][$entries][$key] = $value; + } + if ($old_mbox) { + $result = $this->select($old_mbox); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + return true; + } + + /** + * Retrieve the access rights from a folder + * + * @param string $folder The folder to retrieve the ACLs from. + * + * @return mixed An array of rights if successfull, a PEAR error + * otherwise. + */ + function getACL($folder) + { + $folder = $this->_parseFolder($folder); + $old_mbox = null; + if ($folder != $this->_mboxname) { + $old_mbox = $this->_mboxname; + $result = $this->select($folder); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + $acl = $this->_mbox['permissions']; + if ($old_mbox) { + $result = $this->select($old_mbox); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + return $acl; + } + + /** + * Retrieve the access rights on a folder not owned by the current user + * + * @param string $folder The folder to retrieve the ACLs from. + * + * @return mixed An array of rights if successfull, a PEAR error + * otherwise. + */ + function getMyRights($folder) + { + $folder = $this->_parseFolder($folder); + $old_mbox = null; + if ($folder != $this->_mboxname) { + $old_mbox = $this->_mboxname; + $result = $this->select($folder); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + $acl = ''; + if (isset($this->_mbox['permissions'][$this->_user])) { + $acl = $this->_mbox['permissions'][$this->_user]; + } + if ($old_mbox) { + $result = $this->select($old_mbox); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + return $acl; + } + + /** + * Set the access rights for a folder + * + * @param string $folder The folder to retrieve the ACLs from. + * @param string $user The user to set the ACLs for + * @param string $acl The ACLs + * + * @return mixed True if successfull, a PEAR error otherwise. + */ + function setACL($folder, $user, $acl) + { + $folder = $this->_parseFolder($folder); + $old_mbox = null; + if ($folder != $this->_mboxname) { + $old_mbox = $this->_mboxname; + $result = $this->select($folder); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + $this->_mbox['permissions'][$user] = $acl; + if ($old_mbox) { + $result = $this->select($old_mbox); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + return true; + } + + /** + * Delete the access rights for a user. + * + * @param string $folder The folder that should be modified. + * @param string $user The user that should get the ACLs removed + * + * @return mixed True if successfull, a PEAR error otherwise. + */ + function deleteACL($folder, $user) + { + $folder = $this->_parseFolder($folder); + $old_mbox = null; + if ($folder != $this->_mboxname) { + $old_mbox = $this->_mboxname; + $result = $this->select($folder); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + unset($this->_mbox['permissions'][$user]); + if ($old_mbox) { + $result = $this->select($old_mbox); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + return true; + } + + /** + * Appends a message to the current folder. + * + * @param string $msg The message to append. + * + * @return mixed True or a PEAR error in case of an error. + */ + function appendMessage($msg) + { + $split = strpos($msg, "\r\n\r\n"); + $mail = array('header' => substr($msg, 0, $split + 2), + 'body' => substr($msg, $split + 3)); + return $this->_appendMessage($mail); + } + + /** + * Appends a message to the current folder. + * + * @param array $msg The message to append. + * + * @return mixed True or a PEAR error in case of an error. + */ + function _appendMessage($msg) + { + if (!$this->_mbox) { + return PEAR::raiseError("No IMAP folder selected!"); + } + $mail = array(); + $mail['flags'] = 0; + $mail['header'] = $msg['header']; + $mail['body'] = $msg['body']; + + + $this->_mbox['mails'][$this->_mbox['status']['uidnext']] = $mail; + $this->_mbox['status']['uidnext']++; + return true; + } + + /** + * Copies a message to a new folder. + * + * @param integer $uid IMAP message id. + * @param string $new_folder Target folder. + * + * @return mixed True or a PEAR error in case of an error. + */ + function copyMessage($uid, $new_folder) + { + $new_folder = $this->_parseFolder($new_folder); + if (!$this->_mbox) { + return PEAR::raiseError("No IMAP folder selected!"); + } + if (!isset($this->_mbox['mails'][$uid])) { + return PEAR::raiseError(sprintf("No IMAP message %s!", $uid)); + } + $mail = $this->_mbox['mails'][$uid]; + + $old_mbox = null; + $result = $this->select($new_folder); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + $this->_appendMessage($mail); + if ($old_mbox) { + $result = $this->select($old_mbox); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + return true; + } + + /** + * Moves a message to a new folder. + * + * @param integer $uid IMAP message id. + * @param string $new_folder Target folder. + * + * @return mixed True or a PEAR error in case of an error. + */ + function moveMessage($uid, $new_folder) + { + $new_folder = $this->_parseFolder($new_folder); + if (!$this->_mbox) { + return PEAR::raiseError("No IMAP folder selected!"); + } + if (!isset($this->_mbox['mails'][$uid])) { + return PEAR::raiseError(sprintf("No IMAP message %s!", $uid)); + } + $mail = $this->_mbox['mails'][$uid]; + unset($this->_mbox['mails'][$uid]); + + $old_mbox = null; + $result = $this->select($new_folder); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + $this->_appendMessage($mail); + if ($old_mbox) { + $result = $this->select($old_mbox); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + return true; + } + + /** + * Deletes messages from the current folder. + * + * @param integer $uids IMAP message ids. + * + * @return mixed True or a PEAR error in case of an error. + */ + function deleteMessages($uids) + { + if (!$this->_mbox) { + return PEAR::raiseError("No IMAP folder selected!"); + } + + if (!is_array($uids)) { + $uids = array($uids); + } + + foreach ($uids as $uid) { + + if (!isset($this->_mbox['mails'][$uid])) { + return PEAR::raiseError(sprintf("No IMAP message %s!", $uid)); + } + $this->_mbox['mails'][$uid]['flags'] |= KOLAB_IMAP_FLAG_DELETED; + } + return true; + } + + /** + * Undeletes a message in the current folder. + * + * @param integer $uid IMAP message id. + * + * @return mixed True or a PEAR error in case of an error. + */ + function undeleteMessages($uid) + { + if (!$this->_mbox) { + return PEAR::raiseError("No IMAP folder selected!"); + } + + if (!isset($this->_mbox['mails'][$uid])) { + return PEAR::raiseError(sprintf("No IMAP message %s!", $uid)); + } + $this->_mbox['mails'][$uid]['flags'] &= ~KOLAB_IMAP_FLAG_DELETED; + return true; + } + + /** + * Expunges messages in the current folder. + * + * @return mixed True or a PEAR error in case of an error. + */ + function expunge() + { + if (!$this->_mbox) { + return PEAR::raiseError("No IMAP folder selected!"); + } + + $remaining = array(); + foreach ($this->_mbox['mails'] as $uid => $mail) { + if (!($mail['flags'] & KOLAB_IMAP_FLAG_DELETED)) { + $remaining[$uid] = $mail; + } + } + $this->_mbox['mails'] = $remaining; + return true; + } + + /** + * Return the currently selected mailbox + * + * @return string The mailbox currently selected + */ + function current() + { + return $this->_mboxname; + } +} diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server.php b/framework/Kolab_Server/lib/Horde/Kolab/Server.php new file mode 100644 index 000000000..d8089b7e1 --- /dev/null +++ b/framework/Kolab_Server/lib/Horde/Kolab/Server.php @@ -0,0 +1,673 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** We need PEAR */ +require_once 'PEAR.php'; + +/** Provide access to the Kolab specific objects. */ +require_once 'Horde/Kolab/Server/Object.php'; + +/** Define types of return values. */ +define('KOLAB_SERVER_RESULT_SINGLE', 1); +define('KOLAB_SERVER_RESULT_STRICT', 2); +define('KOLAB_SERVER_RESULT_MANY', 3); + +/** + * This class provides methods to deal with Kolab objects stored in + * the Kolab object db. + * + * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server.php,v 1.9 2009/01/08 07:12:43 wrobel Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server { + + /** + * Server parameters. + * + * @var array + */ + var $_params = array(); + + /** + * The UID of the current user. + * + * @var string + */ + var $uid; + + /** + * Valid Kolab object types + * + * @var array + */ + var $valid_types = array( + KOLAB_OBJECT_ADDRESS, + KOLAB_OBJECT_ADMINISTRATOR, + KOLAB_OBJECT_DISTLIST, + KOLAB_OBJECT_DOMAINMAINTAINER, + KOLAB_OBJECT_GROUP, + KOLAB_OBJECT_MAINTAINER, + KOLAB_OBJECT_SERVER, + KOLAB_OBJECT_SHAREDFOLDER, + KOLAB_OBJECT_USER, + ); + + /** + * Construct a new Horde_Kolab_Server object. + * + * @param array $params Parameter array. + */ + function Horde_Kolab_Server($params = array()) + { + $this->_params = $params; + if (isset($params['uid'])) { + $this->uid = $params['uid']; + } + } + + /** + * Attempts to return a concrete Horde_Kolab_Server instance based + * on $driver. + * + * @param mixed $driver The type of concrete Horde_Kolab_Server subclass to + * return. + * @param array $params A hash containing any additional + * configuration or connection parameters a subclass + * might need. + * + * @return Horde_Kolab_Server|PEAR_Error The newly created concrete + * Horde_Kolab_Server instance. + */ + function &factory($driver, $params = array()) + { + $driver = basename($driver); + if (empty($driver) || $driver == 'none') { + $db = new Horde_Kolab_Server($params); + return $db; + } + + if (file_exists(dirname(__FILE__) . '/Server/' . $driver . '.php')) { + include_once dirname(__FILE__) . '/Server/' . $driver . '.php'; + } + + $class = 'Horde_Kolab_Server_' . $driver; + if (class_exists($class)) { + $db = new $class($params); + } else { + $db = PEAR::raiseError('Class definition of ' . $class . ' not found.'); + } + + return $db; + } + + /** + * Attempts to return a reference to a concrete Horde_Kolab_Server + * instance based on $driver. It will only create a new instance + * if no Horde_Kolab_Server instance with the same parameters currently + * exists. + * + * This method must be invoked as: + * $var = &Horde_Kolab_Server::singleton() + * + * @param array $params An array of optional login parameters. May + * contain "uid" (for the login uid), "user" + * (if the uid is not yet known), and "pass" + * (for a password). + * + * @return Horde_Kolab_Server|PEAR_Error The concrete Horde_Kolab_Server + * reference. + */ + function &singleton($params = null) + { + global $conf; + + static $instances = array(); + + if (isset($conf['kolab']['server']['driver'])) { + $driver = $conf['kolab']['server']['driver']; + if (isset($conf['kolab']['server']['params'])) { + $server_params = $conf['kolab']['server']['params']; + } else { + $server_params = array(); + } + } else if (isset($conf['kolab']['ldap']['server']) + && isset($conf['kolab']['ldap']['basedn']) + && isset($conf['kolab']['ldap']['phpdn']) + && isset($conf['kolab']['ldap']['phppw'])) { + $driver = 'ldap'; + + $server_params = array('server' => $conf['kolab']['ldap']['server'], + 'base_dn' => $conf['kolab']['ldap']['basedn'], + 'uid' => $conf['kolab']['ldap']['phpdn'], + 'pass' => $conf['kolab']['ldap']['phppw']); + } else { + $driver = null; + $server_params = array(); + } + + if (!empty($params)) { + if (isset($params['user'])) { + $tmp_server = &Horde_Kolab_Server::factory($driver, $server_params); + + $uid = $tmp_server->uidForIdOrMail($params['user']); + if (is_a($uid, 'PEAR_Error')) { + return PEAR::raiseError(sprintf(_("Failed identifying the UID of the Kolab user %s. Error was: %s"), + $params['user'], + $uid->getMessage())); + } + $server_params['uid'] = $uid; + } + if (isset($params['pass'])) { + if (isset($server_params['pass'])) { + $server_params['search_pass'] = $server_params['pass']; + } + $server_params['pass'] = $params['pass']; + } + if (isset($params['uid'])) { + if (isset($server_params['uid'])) { + $server_params['search_uid'] = $server_params['pass']; + } + $server_params['uid'] = $params['uid']; + } + } + + $sparam = $server_params; + $sparam['pass'] = isset($sparam['pass']) ? md5($sparam['pass']) : ''; + $signature = serialize(array($driver, $sparam)); + if (empty($instances[$signature])) { + $instances[$signature] = &Horde_Kolab_Server::factory($driver, + $server_params); + } + + return $instances[$signature]; + } + + /** + * Return the root of the UID values on this server. + * + * @return string The base UID on this server (base DN on ldap). + */ + function getBaseUid() + { + return ''; + } + + /** + * Fetch a Kolab object. + * + * @param string $uid The UID of the object to fetch. + * @param string $type The type of the object to fetch. + * + * @return Kolab_Object|PEAR_Error The corresponding Kolab object. + */ + function &fetch($uid = null, $type = null) + { + if (!isset($uid)) { + $uid = $this->uid; + } + if (empty($type)) { + $type = $this->_determineType($uid); + if (is_a($type, 'PEAR_Error')) { + return $type; + } + } else { + if (!in_array($type, $this->valid_types)) { + return PEAR::raiseError(sprintf(_("Invalid Kolab object type \"%s\"."), + $type)); + } + } + + $object = &Horde_Kolab_Server_Object::factory($type, $uid, $this); + return $object; + } + + /** + * Add a Kolab object. + * + * @param array $info The object to store. + * + * @return Kolab_Object|PEAR_Error The newly created Kolab object. + */ + function &add($info) + { + if (!isset($info['type'])) { + return PEAR::raiseError('The type of a new object must be specified!'); + } + if (!in_array($info['type'], $this->valid_types)) { + return PEAR::raiseError(sprintf(_("Invalid Kolab object type \"%s\"."), + $type)); + } + + $uid = $this->generateUid($info['type'], $info); + if (is_a($uid, 'PEAR_Error')) { + return $uid; + } + + $object = &Horde_Kolab_Server_Object::factory($info['type'], $uid, $this); + if (is_a($object, 'PEAR_Error')) { + return $object; + } + + if ($object->exists()) { + return PEAR::raiseError('The object does already exist!'); + } + + $result = $object->save($info); + if (is_a($result, 'PEAR_Error')) { + return PEAR::raiseError(sprintf('Adding object failed: %s', + $result->getMessage())); + } + return $object; + } + + /** + * Update or create a Kolab object. + * + * @param string $type The type of the object to store. + * @param array $info Any additional information about the object to store. + * @param string $uid The unique id of the object to store. + * + * @return Kolab_Object|PEAR_Error The updated Kolab object. + */ + function &store($type, $info, $uid = null) + { + if (!in_array($type, $this->valid_types)) { + return PEAR::raiseError(sprintf(_("Invalid Kolab object type \"%s\"."), + $type)); + } + if (empty($uid)) { + $uid = $this->generateUid($type, $info); + } + + $object = &Horde_Kolab_Server_Object::factory($type, $uid, $this); + $result = $object->save($info); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + return $object; + } + + /** + * Get the groups for this object + * + * @param string $uid The UID of the object to fetch. + * + * @return array|PEAR_Error An array of group ids. + */ + function getGroups($uid) + { + return array(); + } + + /** + * Read object data. + * + * @param string $uid The object to retrieve. + * @param string $attrs Restrict to these attributes. + * + * @return array|PEAR_Error An array of attributes. + */ + function read($uid, $attrs = null) + { + return $this->_read($uid, $attrs); + } + + /** + * Stub for reading object data. + * + * @param string $uid The object to retrieve. + * @param string $attrs Restrict to these attributes. + * + * @return array|PEAR_Error An array of attributes. + */ + function _read($uid, $attrs = null) + { + return PEAR::raiseError(_("Not implemented!")); + } + + /** + * Stub for saving object data. + * + * @param string $uid The object to save. + * @param string $data The data of the object. + * + * @return array|PEAR_Error An array of attributes. + */ + function save($uid, $data) + { + return PEAR::raiseError(_("Not implemented!")); + } + + /** + * Determine the type of a Kolab object. + * + * @param string $uid The UID of the object to examine. + * + * @return string The corresponding Kolab object type. + */ + function _determineType($uid) + { + return KOLAB_OBJECT_USER; + } + + /** + * Identify the primary mail attribute for the first object found + * with the given ID or mail. + * + * @param string $id Search for objects with this ID/mail. + * + * @return mixed|PEAR_Error The mail address or false if there was + * no result. + */ + function mailForIdOrMail($id) + { + /* In the default class we just return the id */ + return $id; + } + + /** + * Returns a list of allowed email addresses for the given user. + * + * @param string $user The user name. + * + * @return array|PEAR_Error An array of allowed mail addresses. + */ + function addrsForIdOrMail($user) + { + /* In the default class we just return the user name */ + return $user; + } + + /** + * Return the UID for a given primary mail, uid, or alias. + * + * @param string $mail A valid mail address for the user. + * + * @return mixed|PEAR_Error The UID or false if there was no result. + */ + function uidForMailAddress($mail) + { + /* In the default class we just return the mail address */ + return $mail; + } + + /** + * Identify the UID for the first user found using a specified + * attribute value. + * + * @param string $attr The name of the attribute used for searching. + * @param string $value The desired value of the attribute. + * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. + * + * @return mixed|PEAR_Error The UID or false if there was no result. + */ + function uidForAttr($attr, $value, + $restrict = KOLAB_SERVER_RESULT_SINGLE) + { + /* In the default class we just return false */ + return false; + } + + /** + * Identify the GID for the first group found using a specified + * attribute value. + * + * @param string $attr The name of the attribute used for searching. + * @param string $value The desired value of the attribute. + * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. + * + * @return mixed|PEAR_Error The GID or false if there was no result. + */ + function gidForAttr($attr, $value, + $restrict = KOLAB_SERVER_RESULT_SINGLE) + { + /* In the default class we just return false */ + return false; + } + + /** + * Is the given UID member of the group with the given mail address? + * + * @param string $uid UID of the user. + * @param string $mail Search the group with this mail address. + * + * @return boolean|PEAR_Error True in case the user is in the + * group, false otherwise. + */ + function memberOfGroupAddress($uid, $mail) + { + /* No groups in the default class */ + return false; + } + + /** + * Identify the UID for the first object found with the given ID. + * + * @param string $id Search for objects with this ID. + * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. + * + * @return mixed|PEAR_Error The UID or false if there was no result. + */ + function uidForId($id, + $restrict = KOLAB_SERVER_RESULT_SINGLE) + { + return $this->uidForAttr('uid', $id); + } + + /** + * Identify the UID for the first user found with the given mail. + * + * @param string $mail Search for users with this mail address. + * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. + * + * @return mixed|PEAR_Error The UID or false if there was no result. + */ + function uidForMail($mail, + $restrict = KOLAB_SERVER_RESULT_SINGLE) + { + return $this->uidForAttr('mail', $mail); + } + + /** + * Identify the GID for the first group found with the given mail. + * + * @param string $mail Search for groups with this mail address. + * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. + * + * @return mixed|PEAR_Error The GID or false if there was no result. + */ + function gidForMail($mail, + $restrict = KOLAB_SERVER_RESULT_SINGLE) + { + return $this->gidForAttr('mail', $mail); + } + + /** + * Identify the UID for the first object found with the given ID or mail. + * + * @param string $id Search for objects with this uid/mail. + * + * @return mixed|PEAR_Error The UID or false if there was no result. + */ + function uidForIdOrMail($id) + { + $uid = $this->uidForAttr('uid', $id); + if (!$uid) { + $uid = $this->uidForAttr('mail', $id); + } + return $uid; + } + + /** + * Identify the UID for the first object found with the given alias. + * + * @param string $mail Search for objects with this mail alias. + * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. + * + * @return mixed|PEAR_Error The UID or false if there was no result. + */ + function uidForAlias($mail, + $restrict = KOLAB_SERVER_RESULT_SINGLE) + { + return $this->uidForAttr('alias', $mail); + } + + /** + * Identify the UID for the first object found with the given mail + * address or alias. + * + * @param string $mail Search for objects with this mail address + * or alias. + * + * @return mixed|PEAR_Error The UID or false if there was no result. + */ + function uidForMailOrAlias($mail) + { + $uid = $this->uidForAttr('alias', $mail); + if (!$uid) { + $uid = $this->uidForAttr('mail', $mail); + } + return $uid; + } + + /** + * Identify the UID for the first object found with the given ID, + * mail or alias. + * + * @param string $id Search for objects with this ID/mail/alias. + * + * @return mixed|PEAR_Error The UID or false if there was no result. + */ + function uidForMailOrIdOrAlias($id) + { + $uid = $this->uidForAttr('uid', $id); + if (!$uid) { + $uid = $this->uidForAttr('mail', $id); + if (!$uid) { + $uid = $this->uidForAttr('alias', $id); + } + } + return $uid; + } + + /** + * Generate a hash representation for a list of objects. + * + * @param string $type The type of the objects to be listed + * @param array $params Additional parameters. + * + * @return array|PEAR_Error An array of Kolab objects. + */ + function listHash($type, $params = null) + { + $list = $this->_listObjects($type, $params); + if (is_a($list, 'PEAR_Error')) { + return $list; + } + + if (isset($params['attributes'])) { + $attributes = $params['attributes']; + } else { + $attributes = null; + } + + $hash = array(); + foreach ($list as $entry) { + $hash[] = $entry->toHash($attributes); + } + + return $hash; + } + + /** + * List all objects of a specific type + * + * @param string $type The type of the objects to be listed + * @param array $params Additional parameters. + * + * @return array|PEAR_Error An array of Kolab objects. + */ + function listObjects($type, $params = null) + { + if (!in_array($type, $this->valid_types)) { + return PEAR::raiseError(sprintf(_("Invalid Kolab object type \"%s\"."), + $type)); + } + + return $this->_listObjects($type, $params); + } + + /** + * List all objects of a specific type + * + * @param string $type The type of the objects to be listed + * @param array $params Additional parameters. + * + * @return array|PEAR_Error An array of Kolab objects. + */ + function _listObjects($type, $params = null) + { + return array(); + } + + /** + * Generates a unique ID for the given information. + * + * @param string $type The type of the object to create. + * @param array $info Any additional information about the object to create. + * + * @return string|PEAR_Error The UID. + */ + function generateUid($type, $info) + { + if (!class_exists($type)) { + $result = Horde_Kolab_Server_Object::loadClass($type); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $id = call_user_func(array($type, 'generateId'), $info); + if (is_a($id, 'PEAR_Error')) { + return $id; + } + return $this->_generateUid($type, $id, $info); + } + + /** + * Generates a UID for the given information. + * + * @param string $type The type of the object to create. + * @param string $id The id of the object. + * @param array $info Any additional information about the object to create. + * + * @return string|PEAR_Error The UID. + */ + function _generateUid($type, $id, $info) + { + return $id; + } + +} diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object.php new file mode 100644 index 000000000..ceb1300bf --- /dev/null +++ b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object.php @@ -0,0 +1,505 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** Define the different Kolab object types */ +define('KOLAB_OBJECT_ADDRESS', 'Horde_Kolab_Server_Object_address'); +define('KOLAB_OBJECT_ADMINISTRATOR', 'Horde_Kolab_Server_Object_administrator'); +define('KOLAB_OBJECT_DOMAINMAINTAINER', 'Horde_Kolab_Server_Object_domainmaintainer'); +define('KOLAB_OBJECT_GROUP', 'Horde_Kolab_Server_Object_group'); +define('KOLAB_OBJECT_DISTLIST', 'Horde_Kolab_Server_Object_distlist'); +define('KOLAB_OBJECT_MAINTAINER', 'Horde_Kolab_Server_Object_maintainer'); +define('KOLAB_OBJECT_SHAREDFOLDER', 'Horde_Kolab_Server_Object_sharedfolder'); +define('KOLAB_OBJECT_USER', 'Horde_Kolab_Server_Object_user'); +define('KOLAB_OBJECT_SERVER', 'Horde_Kolab_Server_Object_server'); + +/** Define the possible Kolab object attributes */ +define('KOLAB_ATTR_UID', 'dn'); +define('KOLAB_ATTR_ID', 'id'); +define('KOLAB_ATTR_SN', 'sn'); +define('KOLAB_ATTR_CN', 'cn'); +define('KOLAB_ATTR_GIVENNAME', 'givenName'); +define('KOLAB_ATTR_FN', 'fn'); +define('KOLAB_ATTR_LNFN', 'lnfn'); +define('KOLAB_ATTR_FNLN', 'fnln'); +define('KOLAB_ATTR_MAIL', 'mail'); +define('KOLAB_ATTR_SID', 'uid'); +define('KOLAB_ATTR_ACL', 'acl'); +define('KOLAB_ATTR_MEMBER', 'member'); +define('KOLAB_ATTR_USERTYPE', 'usertype'); +define('KOLAB_ATTR_DOMAIN', 'domain'); +define('KOLAB_ATTR_FOLDERTYPE', 'kolabFolderType'); +define('KOLAB_ATTR_USERPASSWORD', 'userPassword'); +define('KOLAB_ATTR_DELETED', 'kolabDeleteFlag'); +define('KOLAB_ATTR_FREEBUSYHOST', 'kolabFreeBusyServer'); +define('KOLAB_ATTR_IMAPHOST', 'kolabImapServer'); +define('KOLAB_ATTR_HOMESERVER', 'kolabHomeServer'); +define('KOLAB_ATTR_KOLABDELEGATE','kolabDelegate'); +define('KOLAB_ATTR_IPOLICY', 'kolabInvitationPolicy'); +define('KOLAB_ATTR_QUOTA', 'cyrus-userquota'); +define('KOLAB_ATTR_FBPAST', 'kolabFreeBusyPast'); +define('KOLAB_ATTR_FBFUTURE', 'kolabFreeBusyFuture'); +define('KOLAB_ATTR_VISIBILITY', 'visible'); + +/** Define the possible Kolab object classes */ +define('KOLAB_OC_TOP', 'top'); +define('KOLAB_OC_INETORGPERSON', 'inetOrgPerson'); +define('KOLAB_OC_KOLABINETORGPERSON', 'kolabInetOrgPerson'); +define('KOLAB_OC_HORDEPERSON', 'hordePerson'); +define('KOLAB_OC_KOLABGROUPOFNAMES', 'kolabGroupOfNames'); +define('KOLAB_OC_KOLABSHAREDFOLDER', 'kolabSharedFolder'); + +/** Define the possible Kolab user types */ +define('KOLAB_UT_STANDARD', 0); +define('KOLAB_UT_INTERNAL', 1); +define('KOLAB_UT_GROUP', 2); +define('KOLAB_UT_RESOURCE', 3); + +/** + * This class provides methods to deal with Kolab objects stored in + * the Kolab db. + * + * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object.php,v 1.9 2009/01/08 21:00:07 wrobel Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_Object { + + /** + * Link into the Kolab server. + * + * @var Kolab_Server + */ + var $_db; + + /** + * UID of this object on the Kolab server. + * + * @var string + */ + var $_uid; + + /** + * The cached LDAP result + * + * FIXME: Include _ldap here + * + * @var mixed + */ + var $_cache = false; + + /** FIXME: Add an attribute cache for the get() function */ + + /** + * The LDAP filter to retrieve this object type. + * + * @var string + */ + var $filter = ''; + + /** + * The group the UID must be member of so that this object really + * matches this class type. This may not include the root UID. + * + * @var string + */ + var $required_group; + + /** + * The LDAP attributes supported by this class. + * + * @var array + */ + var $_supported_attributes = array(); + + /** + * Attributes derived from the LDAP values. + * + * @var array + */ + var $_derived_attributes = array( + KOLAB_ATTR_ID, + ); + + /** + * The attributes required when creating an object of this class. + * + * @var array + */ + var $_required_attributes = array(); + + /** + * The ldap classes for this type of object. + * + * @var array + */ + var $_object_classes = array(); + + /** + * Sort by this attributes (must be a LDAP attribute). + * + * @var string + */ + var $sort_by = KOLAB_ATTR_SN; + + /** + * Initialize the Kolab Object. Provide either the UID or a + * LDAP search result. + * + * @param Horde_Kolab_Server &$db The link into the Kolab db. + * @param string $uid UID of the object. + * @param array $data A possible array of data for the object + */ + function Horde_Kolab_Server_Object(&$db, $uid = null, $data = null) + { + $this->_db = &$db; + if (empty($uid)) { + if (empty($data) || !isset($data[KOLAB_ATTR_UID])) { + $this->_cache = PEAR::raiseError(_('Specify either the UID or a search result!')); + return; + } + if (is_array($data[KOLAB_ATTR_UID])) { + $this->_uid = $data[KOLAB_ATTR_UID][0]; + } else { + $this->_uid = $data[KOLAB_ATTR_UID]; + } + $this->_cache = $data; + } else { + $this->_uid = $uid; + } + } + + /** + * Attempts to return a concrete Horde_Kolab_Server_Object instance based on + * $type. + * + * @param mixed $type The type of the Horde_Kolab_Server_Object subclass + * to return. + * @param string $uid UID of the object + * @param array &$storage A link to the Kolab_Server class handling read/write. + * @param array $data A possible array of data for the object + * + * @return Horde_Kolab_Server_Object|PEAR_Error The newly created concrete + * Horde_Kolab_Server_Object instance. + */ + function &factory($type, $uid, &$storage, $data = null) + { + $result = Horde_Kolab_Server_Object::loadClass($type); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + if (class_exists($type)) { + $object = &new $type($storage, $uid, $data); + } else { + $object = PEAR::raiseError('Class definition of ' . $type . ' not found.'); + } + + return $object; + } + + /** + * Attempts to load the concrete Horde_Kolab_Server_Object class based on + * $type. + * + * @param mixed $type The type of the Horde_Kolab_Server_Object subclass. + * + * @static + * + * @return true|PEAR_Error True if successfull. + */ + function loadClass($type) + { + if (in_array($type, array(KOLAB_OBJECT_ADDRESS, KOLAB_OBJECT_ADMINISTRATOR, + KOLAB_OBJECT_DISTLIST, KOLAB_OBJECT_DOMAINMAINTAINER, + KOLAB_OBJECT_GROUP, KOLAB_OBJECT_MAINTAINER, + KOLAB_OBJECT_SHAREDFOLDER, KOLAB_OBJECT_USER, + KOLAB_OBJECT_SERVER))) { + $name = substr($type, 26); + } else { + return PEAR::raiseError(sprintf('Object type "%s" not supported.', + $type)); + } + + $name = basename($name); + + if (file_exists(dirname(__FILE__) . '/Object/' . $name . '.php')) { + include_once dirname(__FILE__) . '/Object/' . $name . '.php'; + } + } + + /** + * Does the object exist? + * + * @return NULL + */ + function exists() + { + $this->_read(); + if (!$this->_cache || is_a($this->_cache, 'PEAR_Error')) { + return false; + } + return true; + } + + /** + * Read the object into the cache + * + * @return NULL + */ + function _read() + { + $this->_cache = $this->_db->read($this->_uid, + $this->_supported_attributes); + } + + /** + * Get the specified attribute of this object + * + * @param string $attr The attribute to read + * + * @return string the value of this attribute + */ + function get($attr) + { + if ($attr != KOLAB_ATTR_UID) { + if (!in_array($attr, $this->_supported_attributes) + && !in_array($attr, $this->_derived_attributes)) { + return PEAR::raiseError(sprintf(_("Attribute \"%s\" not supported!"), + $attr)); + } + if (!$this->_cache) { + $this->_read(); + } + if (is_a($this->_cache, 'PEAR_Error')) { + return $this->_cache; + } + } + + if (in_array($attr, $this->_derived_attributes)) { + return $this->_derive($attr); + } + + switch ($attr) { + case KOLAB_ATTR_UID: + return $this->_getUID(); + case KOLAB_ATTR_FN: + return $this->_getFn(); + case KOLAB_ATTR_SN: + case KOLAB_ATTR_CN: + case KOLAB_ATTR_GIVENNAME: + case KOLAB_ATTR_MAIL: + case KOLAB_ATTR_SID: + case KOLAB_ATTR_USERPASSWORD: + case KOLAB_ATTR_DELETED: + case KOLAB_ATTR_IMAPHOST: + case KOLAB_ATTR_FREEBUSYHOST: + case KOLAB_ATTR_HOMESERVER: + case KOLAB_ATTR_FBPAST: + case KOLAB_ATTR_FBFUTURE: + case KOLAB_ATTR_FOLDERTYPE: + return $this->_get($attr, true); + default: + return $this->_get($attr, false); + } + } + + /** + * Get the specified attribute of this object + * + * @param string $attr The attribute to read + * @param boolean $single Should a single value be returned + * or are multiple values allowed? + * + * @return string the value of this attribute + */ + function _get($attr, $single = true) + { + if (isset($this->_cache[$attr])) { + if ($single && is_array($this->_cache[$attr])) { + return $this->_cache[$attr][0]; + } else { + return $this->_cache[$attr]; + } + } + return false; + } + + /** + * Derive an attribute value. + * + * @param string $attr The attribute to derive. + * + * @return mixed The value of the attribute. + */ + function _derive($attr) + { + switch ($attr) { + case KOLAB_ATTR_ID: + $result = split(',', $this->_uid); + if (substr($result[0], 0, 3) == 'cn=') { + return substr($result[0], 3); + } else { + return $result[0]; + } + case KOLAB_ATTR_LNFN: + $gn = $this->_get(KOLAB_ATTR_GIVENNAME, true); + $sn = $this->_get(KOLAB_ATTR_SN, true); + return sprintf('%s, %s', $sn, $gn); + case KOLAB_ATTR_FNLN: + $gn = $this->_get(KOLAB_ATTR_GIVENNAME, true); + $sn = $this->_get(KOLAB_ATTR_SN, true); + return sprintf('%s %s', $gn, $sn); + default: + return false; + } + } + + /** + * Convert the object attributes to a hash. + * + * @param string $attrs The attributes to return. + * + * @return array|PEAR_Error The hash representing this object. + */ + function toHash($attrs = null) + { + if (!isset($attrs)) { + $attrs = array(); + } + $result = array(); + foreach ($attrs as $key) { + $value = $this->get($key); + if (is_a($value, 'PEAR_Error')) { + return $value; + } + $result[$key] = $value; + } + + return $result; + } + + /** + * Get the UID of this object + * + * @return string the UID of this object + */ + function _getUid() + { + return $this->_uid; + } + + /** + * Get the "first name" attribute of this object + * + * FIXME: This should get refactored to be combined with the Id value. + * + * @return string the "first name" of this object + */ + function _getFn() + { + $sn = $this->_get(KOLAB_ATTR_SN, true); + $cn = $this->_get(KOLAB_ATTR_CN, true); + return trim(substr($cn, 0, strlen($cn) - strlen($sn))); + } + + /** + * Get the groups for this object + * + * @return mixed An array of group ids or a PEAR Error in case of + * an error. + */ + function getGroups() + { + return array(); + } + + /** + * Returns the server url of the given type for this user. + * + * This method can be used to encapsulate multidomain support. + * + * @param string $server_type The type of server URL that should be returned. + * + * @return string|PEAR_Error The server url or empty. + */ + function getServer($server_type) + { + return PEAR::raiseError('Not implemented!'); + } + + /** + * Generates an ID for the given information. + * + * @param array $info The data of the object. + * + * @static + * + * @return string|PEAR_Error The ID. + */ + function generateId($info) + { + $id_mapfields = array('givenName', 'sn'); + $id_format = '%s %s'; + + $fieldarray = array(); + foreach ($id_mapfields as $mapfield) { + if (isset($info[$mapfield])) { + $fieldarray[] = $info[$mapfield]; + } else { + $fieldarray[] = ''; + } + } + + return trim(vsprintf($id_format, $fieldarray), " \t\n\r\0\x0B,"); + } + + /** + * Saves object information. + * + * @param array $info The information about the object. + * + * @return boolean|PEAR_Error True on success. + */ + function save($info) + { + foreach ($this->_required_attributes as $attribute) { + if (!isset($info[$attribute])) { + return PEAR::raiseError(sprintf('The value for "%s" is missing!', + $attribute)); + } + } + + $info['objectClass'] = $this->_object_classes; + + $result = $this->_db->save($this->_uid, $info); + if ($result === false || is_a($result, 'PEAR_Error')) { + return $result; + } + + $this->_cache = $info; + + return $result; + } +}; diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/address.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/address.php new file mode 100644 index 000000000..ca81da10a --- /dev/null +++ b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/address.php @@ -0,0 +1,105 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * This class provides methods to deal with global address book + * entries for Kolab. + * + * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/address.php,v 1.6 2009/01/08 21:00:08 wrobel Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_Object_address extends Horde_Kolab_Server_Object { + + /** + * The LDAP filter to retrieve this object type + * + * @var string + */ + var $filter = '(&(objectclass=inetOrgPerson)(!(uid=*))(sn=*))'; + + /** + * The attributes supported by this class + * + * @var array + */ + var $_supported_attributes = array( + KOLAB_ATTR_SN, + KOLAB_ATTR_CN, + KOLAB_ATTR_GIVENNAME, + KOLAB_ATTR_FN, + KOLAB_ATTR_LNFN, + KOLAB_ATTR_MAIL, + KOLAB_ATTR_DELETED, + ); + + /** + * Attributes derived from the LDAP values. + * + * @var array + */ + var $_derived_attributes = array( + KOLAB_ATTR_LNFN, + KOLAB_ATTR_FNLN, + ); + + /** + * The attributes required when creating an object of this class. + * + * @var array + */ + var $_required_attributes = array( + KOLAB_ATTR_SN, + KOLAB_ATTR_GIVENNAME, + ); + + /** + * The ldap classes for this type of object. + * + * @var array + */ + var $_object_classes = array( + KOLAB_OC_TOP, + KOLAB_OC_INETORGPERSON, + KOLAB_OC_KOLABINETORGPERSON, + ); + + /** + * Convert the object attributes to a hash. + * + * @param string $attrs The attributes to return. + * + * @return array|PEAR_Error The hash representing this object. + */ + function toHash($attrs = null) + { + if (!isset($attrs)) { + $attrs = array( + KOLAB_ATTR_LNFN, + ); + } + return parent::toHash($attrs); + } + +} diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/administrator.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/administrator.php new file mode 100644 index 000000000..33b075015 --- /dev/null +++ b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/administrator.php @@ -0,0 +1,45 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +require_once 'Horde/Kolab/Server/Object/adminrole.php'; + +/** + * This class provides methods to deal with administrator + * entries for Kolab. + * + * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/administrator.php,v 1.5 2009/01/06 17:49:26 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_Object_administrator extends Horde_Kolab_Server_Object_adminrole { + + /** + * The group the UID must be member of so that this object really + * matches this class type. This may not include the root UID. + * + * @var string + */ + var $required_group = 'cn=admin,cn=internal'; + +} diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/adminrole.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/adminrole.php new file mode 100644 index 000000000..d87aa7267 --- /dev/null +++ b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/adminrole.php @@ -0,0 +1,157 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * This class provides methods to deal with administrator object types. + * + * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/adminrole.php,v 1.3 2009/01/06 17:49:26 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_Object_adminrole extends Horde_Kolab_Server_Object { + + /** + * The LDAP filter to retrieve this object type + * + * @var string + */ + var $filter = '(&(cn=*)(objectClass=inetOrgPerson)(!(uid=manager))(sn=*))'; + + /** + * The attributes supported by this class + * + * @var array + */ + var $_supported_attributes = array( + KOLAB_ATTR_SN, + KOLAB_ATTR_CN, + KOLAB_ATTR_GIVENNAME, + KOLAB_ATTR_FN, + KOLAB_ATTR_SID, + KOLAB_ATTR_USERPASSWORD, + KOLAB_ATTR_DELETED, + ); + + /** + * The attributes required when creating an object of this class. + * + * @var array + */ + var $_required_attributes = array( + KOLAB_ATTR_SN, + KOLAB_ATTR_GIVENNAME, + KOLAB_ATTR_USERPASSWORD, + KOLAB_ATTR_SID, + ); + + /** + * Attributes derived from the LDAP values. + * + * @var array + */ + var $_derived_attributes = array( + KOLAB_ATTR_ID, + KOLAB_ATTR_LNFN, + ); + + /** + * The ldap classes for this type of object. + * + * @var array + */ + var $_object_classes = array( + KOLAB_OC_TOP, + KOLAB_OC_INETORGPERSON, + KOLAB_OC_KOLABINETORGPERSON, + ); + + /** + * Convert the object attributes to a hash. + * + * @param string $attrs The attributes to return. + * + * @return array|PEAR_Error The hash representing this object. + */ + function toHash($attrs = null) + { + if (!isset($attrs)) { + $attrs = array( + KOLAB_ATTR_SID, + KOLAB_ATTR_LNFN, + ); + } + return parent::toHash($attrs); + } + + /** + * Saves object information. + * + * @param array $info The information about the object. + * + * @return boolean|PEAR_Error True on success. + */ + function save($info) + { + if (!isset($info['cn'])) { + if (!isset($info['sn']) || !isset($info['givenName'])) { + return PEAR::raiseError('Either the last name or the given name is missing!'); + } else { + $info['cn'] = $this->generateId($info); + } + } + + $admins_uid = sprintf('%s,%s', $this->required_group, + $this->_db->getBaseUid()); + + $admin_group = $this->_db->fetch($admins_uid, KOLAB_OBJECT_GROUP); + if (is_a($admin_group, 'PEAR_Error') || !$admin_group->exists()) { + + $members = array($this->_uid); + + //FIXME: This is not okay and also contains too much LDAP knowledge + $parts = split(',', $this->required_group); + list($groupname) = sscanf($parts[0], 'cn=%s'); + + $result = $this->_db->add(array(KOLAB_ATTR_CN => $groupname, + 'type' => KOLAB_OBJECT_GROUP, + KOLAB_ATTR_MEMBER => $members, + KOLAB_ATTR_VISIBILITY => false)); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } else { + $result = $admin_group->isMember($this->_uid); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + if ($result === false) { + $members = $admin_group->getMembers(); + $members[] = $this->_uid; + $admin_group->save(array(KOLAB_ATTR_MEMBER => $members)); + } + } + return parent::save($info); + } + +} diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/distlist.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/distlist.php new file mode 100644 index 000000000..d41164b61 --- /dev/null +++ b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/distlist.php @@ -0,0 +1,52 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +require_once 'Horde/Kolab/Server/Object/group.php'; + +/** + * This class provides methods to deal with distribution lists for Kolab. + * + * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/distlist.php,v 1.2 2009/01/06 17:49:26 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_Object_distlist extends Horde_Kolab_Server_Object_group { + + /** + * The LDAP filter to retrieve this object type + * + * @var string + */ + var $filter = '(&(objectClass=kolabGroupOfNames)(mail=*))'; + + + /** + * The attributes required when creating an object of this class. + * + * @var array + */ + var $_required_attributes = array( + KOLAB_ATTR_MAIL, + ); +}; diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/domainmaintainer.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/domainmaintainer.php new file mode 100644 index 000000000..c4517b4a9 --- /dev/null +++ b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/domainmaintainer.php @@ -0,0 +1,124 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +require_once 'Horde/Kolab/Server/Object/adminrole.php'; + +/** + * This class provides methods associated to Kolab domain maintainers. + * + * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/domainmaintainer.php,v 1.6 2009/01/06 17:49:26 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_Object_domainmaintainer extends Horde_Kolab_Server_Object_adminrole { + + /** + * The attributes required when creating an object of this class. + * + * @var array + */ + var $_required_attributes = array( + KOLAB_ATTR_SN, + KOLAB_ATTR_GIVENNAME, + KOLAB_ATTR_USERPASSWORD, + KOLAB_ATTR_SID, + KOLAB_ATTR_DOMAIN, + ); + + /** + * Attributes derived from the LDAP values. + * + * @var array + */ + var $_derived_attributes = array( + KOLAB_ATTR_ID, + KOLAB_ATTR_LNFN, + KOLAB_ATTR_DOMAIN, + ); + + /** + * The group the UID must be member of so that this object really + * matches this class type. This may not include the root UID. + * + * @var string + */ + var $required_group = 'cn=domain-maintainer,cn=internal'; + + /** + * Convert the object attributes to a hash. + * + * @param string $attrs The attributes to return. + * + * @return array|PEAR_Error The hash representing this object. + */ + function toHash($attrs = null) + { + if (!isset($attrs)) { + $attrs = array( + KOLAB_ATTR_SID, + KOLAB_ATTR_LNFN, + KOLAB_ATTR_DOMAIN, + ); + } + return parent::toHash($attrs); + } + + /** + * Saves object information. + * + * @param array $info The information about the object. + * + * @return boolean|PEAR_Error True on success. + */ + function save($info) + { + foreach ($info[KOLAB_ATTR_DOMAIN] as $domain) { + $domain_uid = sprintf('cn=%s,cn=domain,cn=internal,%s', + $domain, $this->_db->getBaseUid()); + + //FIXME: This should be made easier by the group object + + $domain_group = $this->_db->fetch($domain_uid, KOLAB_OBJECT_GROUP); + if (is_a($domain_group, 'PEAR_Error')) { + return $domain_group; + } + if (!$domain_group->exists()) { + $members = array($this->_uid); + $domain_group->save(array(KOLAB_ATTR_MEMBER => $members)); + } else { + $result = $domain_group->isMember($this->_uid); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + if ($result === false) { + $members = $domain_group->getMembers(); + $members[] = $this->_uid; + $domain_group->save(array(KOLAB_ATTR_MEMBER => $members)); + } + } + } + return parent::save($info); + } + +} diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/group.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/group.php new file mode 100644 index 000000000..76e47365d --- /dev/null +++ b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/group.php @@ -0,0 +1,239 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * This class provides methods to deal with groups for Kolab. + * + * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/group.php,v 1.5 2009/01/06 17:49:26 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_Object_group extends Horde_Kolab_Server_Object { + + /** + * The LDAP filter to retrieve this object type + * + * @var string + */ + var $filter = '(objectClass=kolabGroupOfNames)'; + + /** + * The attributes supported by this class + * + * @var array + */ + var $_supported_attributes = array( + KOLAB_ATTR_CN, + KOLAB_ATTR_MAIL, + KOLAB_ATTR_MEMBER, + KOLAB_ATTR_DELETED, + ); + + /** + * Attributes derived from the LDAP values. + * + * @var array + */ + var $_derived_attributes = array( + KOLAB_ATTR_ID, + KOLAB_ATTR_VISIBILITY, + ); + + /** + * The attributes required when creating an object of this class. + * + * @var array + */ + var $_required_attributes = array( + KOLAB_ATTR_CN, + ); + + /** + * The ldap classes for this type of object. + * + * @var array + */ + var $_object_classes = array( + KOLAB_OC_TOP, + KOLAB_OC_KOLABGROUPOFNAMES, + ); + + /** + * Sort by this attributes (must be a LDAP attribute). + * + * @var string + */ + var $sort_by = KOLAB_ATTR_MAIL; + + /** + * Derive an attribute value. + * + * @param string $attr The attribute to derive. + * + * @return mixed The value of the attribute. + */ + function _derive($attr) + { + switch ($attr) { + case KOLAB_ATTR_VISIBILITY: + return strpos($this->_uid, 'cn=internal') === false; + default: + return parent::_derive($attr); + } + } + + /** + * Convert the object attributes to a hash. + * + * @param string $attrs The attributes to return. + * + * @return array|PEAR_Error The hash representing this object. + */ + function toHash($attrs = null) + { + if (!isset($attrs)) { + $attrs = array( + KOLAB_ATTR_ID, + KOLAB_ATTR_MAIL, + KOLAB_ATTR_VISIBILITY, + ); + } + return parent::toHash($attrs); + } + + /** + * Generates an ID for the given information. + * + * @param array $info The data of the object. + * + * @static + * + * @return string|PEAR_Error The ID. + */ + function generateId($info) + { + if (isset($info['mail'])) { + return trim($info['mail'], " \t\n\r\0\x0B,"); + } else { + return trim($info['cn'], " \t\n\r\0\x0B,"); + } + } + + /** + * Saves object information. + * + * @param array $info The information about the object. + * + * @return boolean|PEAR_Error True on success. + */ + function save($info) + { + if (!isset($info['cn'])) { + if (!isset($info['mail'])) { + return PEAR::raiseError('Either the mail address or the common name has to be specified for a group object!'); + } else { + $info['cn'] = $info['mail']; + } + } + return parent::save($info); + } + + /** + * Retrieve the member list for this group. + * + * @return array|PEAR_Error The list of members in this group. + */ + function getMembers() + { + return $this->_get(KOLAB_ATTR_MEMBER, false); + } + + /** + * Add a member to this group. + * + * @param string $member The UID of the member to add. + * + * @return array|PEAR_Error True if successful. + */ + function addMember($member) + { + $members = $this->getMembers(); + if (is_a($members, 'PEAR_Error')) { + return $members; + } + if (!in_array($member, $members)) { + $this->_cache[KOLAB_ATTR_MEMBER][] = $member; + } else { + return PEAR::raiseError(_("The UID %s is already a member of the group %s!"), + $member, $this->_uid); + } + return $this->save($this->_cache); + } + + /** + * Delete a member from this group. + * + * @param string $member The UID of the member to delete. + * + * @return array|PEAR_Error True if successful. + */ + function deleteMember($member) + { + $members = $this->getMembers(); + if (is_a($members, 'PEAR_Error')) { + return $members; + } + if (in_array($member, $members)) { + $this->_cache[KOLAB_ATTR_MEMBER] = array_diff($this->_cache[KOLAB_ATTR_MEMBER], + array($member)); + } else { + return PEAR::raiseError(_("The UID %s is no member of the group %s!"), + $member, $this->_uid); + + } + return $this->save($this->_cache); + } + + /** + * Is the specified UID member of this group? + * + * @param string $member The UID of the member to check. + * + * @return boolean|PEAR_Error True if the UID is a member of the group, + * false otherwise. + */ + function isMember($member) + { + $members = $this->getMembers(); + if (is_a($members, 'PEAR_Error') || !is_array($members)) { + return $members; + } + if (!in_array($member, $members)) { + return false; + } else { + return true; + } + } + +} diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/maintainer.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/maintainer.php new file mode 100644 index 000000000..342bd7bbd --- /dev/null +++ b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/maintainer.php @@ -0,0 +1,45 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +require_once 'Horde/Kolab/Server/Object/adminrole.php'; + +/** + * This class provides methods to deal with maintainer + * entries for Kolab. + * + * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/maintainer.php,v 1.5 2009/01/06 17:49:26 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_Object_maintainer extends Horde_Kolab_Server_Object_adminrole { + + /** + * The group the UID must be member of so that this object really + * matches this class type. This may not include the root UID. + * + * @var string + */ + var $required_group = 'cn=maintainer,cn=internal'; + +} diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/server.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/server.php new file mode 100644 index 000000000..c10dcccc3 --- /dev/null +++ b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/server.php @@ -0,0 +1,50 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * This class provides methods to deal with Kolab server configuration. + * + * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/server.php,v 1.5 2009/01/06 17:49:26 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_Object_server extends Horde_Kolab_Server_Object { + + /** + * The LDAP filter to retrieve this object type + * + * @var string + */ + var $filter = '(&((k=kolab))(objectclass=kolab))'; + + /** + * The attributes supported by this class + * + * @var array + */ + var $_supported_attributes = array( + KOLAB_ATTR_FBPAST, + ); + +} \ No newline at end of file diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/sharedfolder.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/sharedfolder.php new file mode 100644 index 000000000..05800bc50 --- /dev/null +++ b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/sharedfolder.php @@ -0,0 +1,109 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * This class provides methods to deal with shared folders + * entries for Kolab. + * + * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/sharedfolder.php,v 1.5 2009/01/06 17:49:26 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_Object_sharedfolder extends Horde_Kolab_Server_Object { + + /** + * The LDAP filter to retrieve this object type + * + * @var string + */ + var $filter = '(objectClass=kolabSharedFolder)'; + + /** + * The attributes supported by this class + * + * @var array + */ + var $_supported_attributes = array( + KOLAB_ATTR_CN, + KOLAB_ATTR_DELETED, + KOLAB_ATTR_FOLDERTYPE, + KOLAB_ATTR_HOMESERVER, + KOLAB_ATTR_IMAPHOST, + KOLAB_ATTR_QUOTA, + KOLAB_ATTR_ACL, + ); + + /** + * The attributes required when creating an object of this class. + * + * @var array + */ + var $_required_attributes = array( + KOLAB_ATTR_CN, + KOLAB_ATTR_HOMESERVER, + ); + + /** + * The ldap classes for this type of object. + * + * @var array + */ + var $_object_classes = array( + KOLAB_OC_TOP, + KOLAB_OC_KOLABSHAREDFOLDER, + ); + + /** + * Generates an ID for the given information. + * + * @param array $info The data of the object. + * + * @static + * + * @return string|PEAR_Error The ID. + */ + function generateId($info) + { + return trim($info['cn'], " \t\n\r\0\x0B,"); + } + + /** + * Convert the object attributes to a hash. + * + * @param string $attrs The attributes to return. + * + * @return array|PEAR_Error The hash representing this object. + */ + function toHash($attrs = null) + { + if (!isset($attrs)) { + $attrs = array( + KOLAB_ATTR_CN, + KOLAB_ATTR_HOMESERVER, + KOLAB_ATTR_FOLDERTYPE, + ); + } + return parent::toHash($attrs); + } +} diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/user.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/user.php new file mode 100644 index 000000000..8b47e9026 --- /dev/null +++ b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/user.php @@ -0,0 +1,312 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * This class provides methods to deal with Kolab users stored in + * the Kolab db. + * + * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/user.php,v 1.11 2009/01/08 21:00:08 wrobel Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_Object_user extends Horde_Kolab_Server_Object { + + /** + * The LDAP filter to retrieve this object type + * + * @var string + */ + var $filter = '(&(objectClass=kolabInetOrgPerson)(uid=*)(mail=*)(sn=*))'; + + /** + * The attributes supported by this class + * + * @var array + */ + var $_supported_attributes = array( + KOLAB_ATTR_SN, + KOLAB_ATTR_CN, + KOLAB_ATTR_GIVENNAME, + KOLAB_ATTR_FN, + KOLAB_ATTR_SID, + KOLAB_ATTR_USERPASSWORD, + KOLAB_ATTR_MAIL, + KOLAB_ATTR_DELETED, + KOLAB_ATTR_IMAPHOST, + KOLAB_ATTR_FREEBUSYHOST, + KOLAB_ATTR_HOMESERVER, + KOLAB_ATTR_KOLABDELEGATE, + KOLAB_ATTR_IPOLICY, + KOLAB_ATTR_FBFUTURE, + ); + + /** + * Attributes derived from the LDAP values. + * + * @var array + */ + var $_derived_attributes = array( + KOLAB_ATTR_ID, + KOLAB_ATTR_USERTYPE, + KOLAB_ATTR_LNFN, + KOLAB_ATTR_FNLN, + ); + + /** + * The attributes required when creating an object of this class. + * + * @var array + */ + var $_required_attributes = array( + KOLAB_ATTR_SN, + KOLAB_ATTR_GIVENNAME, + KOLAB_ATTR_USERPASSWORD, + KOLAB_ATTR_MAIL, + KOLAB_ATTR_HOMESERVER, + ); + + /** + * The ldap classes for this type of object. + * + * @var array + */ + var $_object_classes = array( + KOLAB_OC_TOP, + KOLAB_OC_INETORGPERSON, + KOLAB_OC_KOLABINETORGPERSON, + KOLAB_OC_HORDEPERSON, + ); + + /** + * Initialize the Kolab Object. Provide either the UID or a + * LDAP search result. + * + * @param Horde_Kolab_Server &$db The link into the Kolab db. + * @param string $dn UID of the object. + * @param array $data A possible array of data for the object + */ + function Horde_Kolab_Server_Object_user(&$db, $dn = null, $data = null) + { + global $conf; + + /** Allows to customize the supported user attributes. */ + if (isset($conf['kolab']['server']['user_supported_attrs'])) { + $this->_supported_attributes = $conf['kolab']['server']['user_supported_attrs']; + } + + /** Allows to customize the required user attributes. */ + if (isset($conf['kolab']['server']['user_required_attrs'])) { + $this->_required_attributes = $conf['kolab']['server']['user_required_attrs']; + } + + /** Allows to customize the user object classes. */ + if (isset($conf['kolab']['server']['user_objectclasses'])) { + $this->_object_classes = $conf['kolab']['server']['user_object_classes']; + } + + Horde_Kolab_Server_Object::Horde_Kolab_Server_Object($db, $dn, $data); + } + + /** + * Derive an attribute value. + * + * @param string $attr The attribute to derive. + * + * @return mixed The value of the attribute. + */ + function _derive($attr) + { + switch ($attr) { + case KOLAB_ATTR_USERTYPE: + if (strpos($this->_uid, 'cn=internal')) { + return KOLAB_UT_INTERNAL; + } else if (strpos($this->_uid, 'cn=group')) { + return KOLAB_UT_GROUP; + } else if (strpos($this->_uid, 'cn=resource')) { + return KOLAB_UT_RESOURCE; + } else { + return KOLAB_UT_STANDARD; + } + default: + return parent::_derive($attr); + } + } + + /** + * Convert the object attributes to a hash. + * + * @param string $attrs The attributes to return. + * + * @return array|PEAR_Error The hash representing this object. + */ + function toHash($attrs = null) + { + if (!isset($attrs)) { + $attrs = array( + KOLAB_ATTR_SID, + KOLAB_ATTR_FN, + KOLAB_ATTR_MAIL, + KOLAB_ATTR_USERTYPE, + ); + } + return parent::toHash($attrs); + } + + /** + * Get the groups for this object + * + * @return mixed|PEAR_Error An array of group ids, false if no groups were + * found. + */ + function getGroups() + { + return $this->_db->getGroups($this->_uid); + } + + /** + * Returns the server url of the given type for this user. + * + * This method is used to encapsulate multidomain support. + * + * @param string $server_type The type of server URL that should be returned. + * + * @return string The server url or empty on error. + */ + function getServer($server_type) + { + global $conf; + + switch ($server_type) { + case 'freebusy': + $server = $this->get(KOLAB_ATTR_FREEBUSYHOST); + if (!is_a($server, 'PEAR_Error') && !empty($server)) { + return $server; + } + $server = $this->getServer('homeserver'); + if (is_a($server, 'PEAR_Error')) { + return $server; + } + if (empty($server)) { + $server = $_SERVER['SERVER_NAME']; + } + if (isset($conf['kolab']['freebusy']['server'])) { + return $conf['kolab']['freebusy']['server']; + } + if (isset($conf['kolab']['server']['freebusy_url_format'])) { + return sprintf($conf['kolab']['server']['freebusy_url_format'], + $server); + } else { + return 'https://' . $server . '/freebusy'; + } + case 'imap': + $server = $this->get(KOLAB_ATTR_IMAPHOST); + if (!is_a($server, 'PEAR_Error') && !empty($server)) { + return $server; + } + case 'homeserver': + default: + $server = $this->get(KOLAB_ATTR_HOMESERVER); + if (empty($server)) { + $server = $_SERVER['SERVER_NAME']; + } + return $server; + } + } + + /** + * Generates an ID for the given information. + * + * @param array $info The data of the object. + * + * @static + * + * @return string|PEAR_Error The ID. + */ + function generateId($info) + { + global $conf; + + /** The fields that should get mapped into the user ID. */ + if (isset($conf['kolab']['server']['user_id_mapfields'])) { + $id_mapfields = $conf['kolab']['server']['user_id_mapfields']; + } else { + $id_mapfields = array('givenName', 'sn'); + } + + /** The user ID format. */ + if (isset($conf['kolab']['server']['user_id_format'])) { + $id_format = $conf['kolab']['server']['user_id_format']; + } else { + $id_format = '%s %s'; + } + + $fieldarray = array(); + foreach ($id_mapfields as $mapfield) { + if (isset($info[$mapfield])) { + $fieldarray[] = $info[$mapfield]; + } else { + $fieldarray[] = ''; + } + } + return trim(vsprintf($id_format, $fieldarray), " \t\n\r\0\x0B,"); + } + + /** + * Saves object information. + * + * @param array $info The information about the object. + * + * @return boolean|PEAR_Error True on success. + */ + function save($info) + { + if (!isset($info['cn'])) { + if (!isset($info['sn']) || !isset($info['givenName'])) { + return PEAR::raiseError('Either the last name or the given name is missing!'); + } else { + $info['cn'] = $this->generateId($info); + } + } + + if (isset($conf['kolab']['server']['user_mapping'])) { + $mapped = array(); + $map = $conf['kolab']['server']['user_mapping']; + foreach ($map as $key => $val) { + $mapped[$val] = $info[$key]; + } + $info = $mapped; + } + + if (isset($conf['kolab']['server']['user_mapping'])) { + $mapped = array(); + $map = $conf['kolab']['server']['user_mapping']; + foreach ($map as $key => $val) { + $mapped[$val] = $info[$key]; + } + $info = $mapped; + } + + return parent::save($info); + } +}; diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/ldap.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/ldap.php new file mode 100644 index 000000000..3e9d52981 --- /dev/null +++ b/framework/Kolab_Server/lib/Horde/Kolab/Server/ldap.php @@ -0,0 +1,987 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** We need the Horde LDAP tools for this class **/ +require_once 'Horde/LDAP.php'; + +/** + * This class provides methods to deal with Kolab objects stored in + * the standard Kolab LDAP db. + * + * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/ldap.php,v 1.9 2009/01/08 07:12:44 wrobel Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_ldap extends Horde_Kolab_Server { + + /** + * LDAP connection handle. + * + * @var resource + */ + var $_connection; + + /** + * Flag that indicates bound state for the LDAP connection. + * + * @var boolean + */ + var $_bound; + + /** + * The base dn . + * + * @var boolean + */ + var $_base_dn; + + /** + * Connects to the LDAP server. + * + * @param string $server LDAP server URL. + * @param string $base_dn LDAP server base DN. + * + * @return boolean|PEAR_Error True if the connection succeeded. + */ + function _connect($server = null, $base_dn = null) + { + if (!function_exists('ldap_connect')) { + return PEAR::raiseError(_("Cannot connect to the Kolab LDAP server. PHP does not support LDAP!")); + } + + if (!$server) { + if (isset($this->_params['server'])) { + $server = $this->_params['server']; + } else { + return PEAR::raiseError(_("Horde_Kolab_Server_ldap needs a server parameter!")); + } + } + if (!$base_dn) { + if (isset($this->_params['base_dn'])) { + $this->_base_dn = $this->_params['base_dn']; + } else { + return PEAR::raiseError(_("Horde_Kolab_Server_ldap needs a base_dn parameter!")); + } + } else { + $this->_base_dn = $base_dn; + } + + $this->_connection = @ldap_connect($server); + if (!$this->_connection) { + return PEAR::raiseError(sprintf(_("Error connecting to LDAP server %s!"), + $server)); + } + + /* We need version 3 for Kolab */ + if (!ldap_set_option($this->_connection, LDAP_OPT_PROTOCOL_VERSION, 3)) { + return PEAR::raiseError(sprintf(_("Error setting LDAP protocol on server %s to v3: %s"), + $server, + ldap_error($this->_connection))); + } + + return true; + } + + /** + * Binds the LDAP connection with a specific user and pass. + * + * @param string $dn DN to bind with + * @param string $pw Password associated to this DN. + * + * @return boolean|PEAR_Error Whether or not the binding succeeded. + */ + function _bind($dn = false, $pw = '') + { + if (!$this->_connection) { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + if (!$dn) { + if (isset($this->_params['uid'])) { + $dn = $this->_params['uid']; + } else { + $dn = ''; + } + } + if (!$pw) { + if (isset($this->_params['pass'])) { + $pw = $this->_params['pass']; + } + } + + $this->_bound = @ldap_bind($this->_connection, $dn, $pw); + + if (!$this->_bound) { + return PEAR::raiseError(sprintf(_("Unable to bind to the LDAP server as %s!"), + $dn)); + } + return true; + } + + /** + * Disconnect from LDAP. + * + * @return NULL + */ + function unbind() + { + $result = @ldap_unbind($this->_connection); + if (!$result) { + return PEAR::raiseError("Failed to unbind from the LDAP server!"); + } + + $this->_bound = false; + } + + /** + * Search for an object. + * + * @param string $filter Filter criteria. + * @param array $attributes Restrict the search result to + * these attributes. + * @param string $base The base location for searching. + * + * @return array|PEAR_Error A LDAP search result. + */ + function _search($filter, $attributes = null, $base = null) + { + if (!$this->_bound) { + $result = $this->_bind(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + if (empty($base)) { + $base = $this->_base_dn; + } + + if (isset($attributes)) { + $result = @ldap_search($this->_connection, $base, $filter, $attributes); + } else { + $result = @ldap_search($this->_connection, $base, $filter); + } + if (!$result && $this->_errno()) { + return PEAR::raiseError(sprintf(_("LDAP Error: Failed to search using filter %s. Error was: %s"), + $filter, $this->_error())); + } + return $result; + } + + /** + * Read object data. + * + * @param string $dn The object to retrieve. + * @param string $attrs Restrict to these attributes. + * + * @return array|PEAR_Error An array of attributes. + */ + function _read($dn, $attrs = null) + { + if (!$this->_bound) { + $result = $this->_bind(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + if (isset($attrs)) { + $result = @ldap_read($this->_connection, $dn, '(objectclass=*)', $attrs); + } else { + $result = @ldap_read($this->_connection, $dn, '(objectclass=*)'); + } + if (!$result && $this->_errno()) { + return PEAR::raiseError(sprintf(_("LDAP Error: No such object: %s: %s"), + $dn, $this->_error())); + } + $entry = $this->_firstEntry($result); + if (!$entry) { + ldap_free_result($result); + return PEAR::raiseError(sprintf(_("LDAP Error: Empty result for: %s."), + $dn)); + } + $object = $this->_getAttributes($entry); + if (!$object && $this->_errno()) { + return PEAR::raiseError(sprintf(_("LDAP Error: No such dn: %s: %s"), + $dn, $this->_error())); + } + ldap_free_result($result); + return $object; + } + + /** + * Add a new object + * + * @param string $dn The DN of the object to be added. + * @param array $data The attributes of the object to be added. + * + * @return boolean True if adding succeeded. + */ + function _add($dn, $data) + { + if (!$this->_bound) { + $result = $this->_bind(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + return @ldap_add($this->_connection, $dn, $data); + } + + /** + * Count the number of results. + * + * @param string $result The LDAP search result. + * + * @return int The number of records found. + */ + function _count($result) + { + return @ldap_count_entries($this->_connection, $result); + } + + /** + * Return the dn of an entry. + * + * @param resource $entry The LDAP entry. + * + * @return string The DN of the entry. + */ + function _getDn($entry) + { + return @ldap_get_dn($this->_connection, $entry); + } + + /** + * Return the attributes of an entry. + * + * @param resource $entry The LDAP entry. + * + * @return array The attributes of the entry. + */ + function _getAttributes($entry) + { + return @ldap_get_attributes($this->_connection, $entry); + } + + /** + * Return the first entry of a result. + * + * @param resource $result The LDAP search result. + * + * @return resource The first entry of the result. + */ + function _firstEntry($result) + { + return @ldap_first_entry($this->_connection, $result); + } + + /** + * Return the next entry of a result. + * + * @param resource $entry The current LDAP entry. + * + * @return resource The next entry of the result. + */ + function _nextEntry($entry) + { + return @ldap_next_entry($this->_connection, $entry); + } + + /** + * Return the entries of a result. + * + * @param resource $result The LDAP search result. + * @param int $from Only return results after this position. + * @param int $to Only return results until this position. + * + * @return array The entries of the result. + */ + function _getEntries($result, $from = -1, $to = -1) + { + if ($from >= 0 || $to >= 0) { + $result = array(); + + $i = 0; + for ($entry = $this->_firstEntry($result); + $entry != false; + $entry = $this->_nextEntry($entry)) { + if (!$entry && $this->_errno()) { + return false; + } + if ($i > $from && ($i <= $to || $to == -1)) { + $attributes = $this->_getAttributes($entry); + if (!$attributes && $this->_errno()) { + return false; + } + $result[] = $attributes; + } + $i++; + } + return $result; + } + return @ldap_get_entries($this->_connection, $result); + } + + /** + * Sort the entries of a result. + * + * @param resource $result The LDAP search result. + * @param string $attribute The attribute used for sorting. + * + * @return boolean True if sorting succeeded. + */ + function _sort($result, $attribute) + { + return @ldap_sort($this->_connection, $result, $attribute); + } + + /** + * Return the current LDAP error number. + * + * @return int The current LDAP error number. + */ + function _errno() + { + return @ldap_errno($this->_connection); + } + + /** + * Return the current LDAP error description. + * + * @return string The current LDAP error description. + */ + function _error() + { + return @ldap_error($this->_connection); + } + + /* + * ------------------------------------------------------------------ + * The functions defined below do not call ldap_* functions directly. + * ------------------------------------------------------------------ + */ + + /** + * Return the root of the UID values on this server. + * + * @return string The base UID on this server (base DN on ldap). + */ + function getBaseUid() + { + return $this->_base_dn; + } + + /** + * Return the DNs of a result. + * + * @param resource $result The LDAP search result. + * @param int $from Only return results after this position. + * @param int $to Only return results until this position. + * + * @return array The DNs of the result. + */ + function _getDns($result, $from = -1, $to = -1) + { + $dns = array(); + $entry = $this->_firstEntry($result); + + $i = 0; + for ($entry = $this->_firstEntry($result); + $entry != false; + $entry = $this->_nextEntry($entry)) { + if ($i > $from && ($i <= $to || $to == -1)) { + $dn = $this->_getDn($entry); + if (!$dn && $this->_errno()) { + return false; + } + $dns[] = $dn; + } + $i++; + } + if ($this->_errno()) { + return false; + } + return $dns; + } + + /** + * Identify the DN of the first result entry. + * + * @param array $result The LDAP search result. + * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. + * + * @return string|PEAR_Error The DN. + */ + function _dnFromResult($result, $restrict = KOLAB_SERVER_RESULT_SINGLE) + { + switch ($restrict) { + case KOLAB_SERVER_RESULT_STRICT: + $count = $this->_count($result); + if (!$count) { + return false; + } else if ($count > 1) { + return PEAR::raiseError(sprintf(_("Found %s results when expecting only one!"), + $count)); + } + case KOLAB_SERVER_RESULT_SINGLE: + $entry = $this->_firstEntry($result); + if (!$entry && $this->_errno()) { + return PEAR::raiseError(sprintf(_("Search failed. Error was: %s"), + $this->_error())); + } + if (!$entry) { + return false; + } + $dn = $this->_getDn($entry); + if (!$dn && $this->_errno()) { + return PEAR::raiseError(sprintf(_("Retrieving DN failed. Error was: %s"), + $this->_error())); + } + return $dn; + case KOLAB_SERVER_RESULT_MANY: + $entries = $this->_getDns($result); + if (!$entries && $this->_errno()) { + return PEAR::raiseError(sprintf(_("Search failed. Error was: %s"), + $this->_error())); + } + if (!$entries) { + return false; + } + return $entries; + } + return false; + } + + /** + * Get the attributes of the first result entry. + * + * @param array $result The LDAP search result. + * @param array $attrs The attributes to retrieve. + * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. + * + * @return mixed|PEAR_Error The attributes or false if there were + * no results. + */ + function _attrsFromResult($result, $attrs, + $restrict = KOLAB_SERVER_RESULT_SINGLE) + { + $entries = array(); + + switch ($restrict) { + case KOLAB_SERVER_RESULT_STRICT: + $count = $this->_count($result); + if (!$count) { + return false; + } else if ($count > 1) { + return PEAR::raiseError(sprintf(_("Found %s results when expecting only one!"), + $count)); + } + case KOLAB_SERVER_RESULT_SINGLE: + $first = $this->_firstEntry($result); + if (!$first && $this->_errno()) { + return PEAR::raiseError(sprintf(_("Search failed. Error was: %s"), + $this->_error())); + } + if (!$first) { + return false; + } + $entry = $this->_getAttributes($first); + if (!$entry && $this->_errno()) { + return PEAR::raiseError(sprintf(_("Retrieving attributes failed. Error was: %s"), + $this->_error())); + } + + $result = array(); + foreach ($attrs as $attr) { + if ($entry[$attr]['count'] > 0) { + unset($entry[$attr]['count']); + $result[$attr] = $entry[$attr]; + } + } + return $result; + case KOLAB_SERVER_RESULT_MANY: + $entries = $this->_getEntries($result); + if (!$entries && $this->_errno()) { + return PEAR::raiseError(sprintf(_("Search failed. Error was: %s"), + $this->_error())); + } + if (!$entries) { + return false; + } + unset($entries['count']); + $result = array(); + + $i = 0; + foreach ($entries as $entry) { + $result[$i] = array(); + foreach ($attrs as $attr) { + if (isset($entry[$attr])) { + if ($entry[$attr]['count'] > 0) { + unset($entry[$attr]['count']); + $result[$i][$attr] = $entry[$attr]; + } + } + } + $i++; + } + return $result; + } + return false; + } + + /** + * Determine the type of a Kolab object. + * + * @param string $dn The DN of the object to examine. + * + * @return int The corresponding Kolab object type. + */ + function _determineType($dn) + { + $oc = $this->_getObjectClasses($dn); + if (is_a($oc, 'PEAR_Error')) { + return $oc; + } + + // Not a user type? + if (!in_array('kolabinetorgperson', $oc)) { + // Is it a group? + if (in_array('kolabgroupofnames', $oc)) { + return KOLAB_OBJECT_GROUP; + } + // Is it a shared Folder? + if (in_array('kolabsharedfolder', $oc)) { + return KOLAB_OBJECT_SHAREDFOLDER; + } + return PEAR::raiseError(sprintf(_("Unkown Kolab object type for DN %s."), + $dn)); + } + + $groups = $this->getGroups($dn); + if (is_a($groups, 'PEAR_Error')) { + return $groups; + } + if (!empty($groups)) { + if (in_array('cn=admin,cn=internal,' . $this->_base_dn, $groups)) { + return KOLAB_OBJECT_ADMINISTRATOR; + } + if (in_array('cn=maintainer,cn=internal,' . $this->_base_dn, + $groups)) { + return KOLAB_OBJECT_MAINTAINER; + } + if (in_array('cn=domain-maintainer,cn=internal,' . $this->_base_dn, + $groups)) { + return KOLAB_OBJECT_DOMAINMAINTAINER; + } + } + + if (strpos($dn, 'cn=external') !== false) { + return KOLAB_OBJECT_ADDRESS; + } + + return KOLAB_OBJECT_USER; + } + + /** + * Get the LDAP object classes for the given DN. + * + * @param string $dn DN of the object. + * + * @return array|PEAR_Error An array of object classes. + */ + function _getObjectClasses($dn) + { + $object = $this->_read($dn, array('objectClass')); + if (is_a($object, 'PEAR_Error')) { + return $object; + } + if (!isset($object['objectClass'])) { + return PEAR::raiseError('The result has no object classes!'); + } + unset($object['count']); + unset($object['objectClass']['count']); + $result = array_map('strtolower', $object['objectClass']); + return $result; + } + + /** + * Identify the DN for the first object found using a filter. + * + * @param string $filter The LDAP filter to use. + * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. + * + * @return mixed|PEAR_Error The DN or false if there was no result. + */ + function _dnForFilter($filter, + $restrict = KOLAB_SERVER_RESULT_SINGLE) + { + $result = $this->_search($filter, array()); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + if (!$this->_count($result)) { + return false; + } + return $this->_dnFromResult($result, $restrict); + } + + /** + * Identify attributes for the first object found using a filter. + * + * @param string $filter The LDAP filter to use. + * @param array $attrs The attributes to retrieve. + * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. + * + * @return mixed|PEAR_Error The DN or false if there was no result. + */ + function _attrsForFilter($filter, $attrs, + $restrict = KOLAB_SERVER_RESULT_SINGLE) + { + $result = $this->_search($filter, $attrs); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + return $this->_attrsFromResult($result, $attrs, $restrict); + } + + /** + * Identify the primary mail attribute for the first object found + * with the given ID or mail. + * + * @param string $id Search for objects with this ID/mail. + * + * @return mixed|PEAR_Error The mail address or false if there was + * no result. + */ + function mailForIdOrMail($id) + { + $filter = '(&(objectClass=kolabInetOrgPerson)(|(uid='. + Horde_LDAP::quote($id) . ')(mail=' . + Horde_LDAP::quote($id) . ')))'; + $result = $this->_attrsForFilter($filter, array('mail'), + KOLAB_SERVER_RESULT_STRICT); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + return $result['mail'][0]; + } + + /** + * Identify the UID for the first object found with the given ID + * or mail. + * + * @param string $id Search for objects with this ID/mail. + * + * @return mixed|PEAR_Error The UID or false if there was no result. + */ + function uidForIdOrMail($id) + { + $filter = '(&(objectClass=kolabInetOrgPerson)(|(uid='. + Horde_LDAP::quote($id) . ')(mail=' . + Horde_LDAP::quote($id) . ')))'; + return $this->_dnForFilter($filter, KOLAB_SERVER_RESULT_STRICT); + } + + /** + * Returns a list of allowed email addresses for the given user. + * + * @param string $id The users primary mail address or ID. + * + * @return array|PEAR_Error An array of allowed mail addresses + */ + function addrsForIdOrMail($id) + { + $filter = '(&(objectClass=kolabInetOrgPerson)(|(mail=' + . Horde_LDAP::quote($id) . ')(uid=' + . Horde_LDAP::quote($id) . ')))'; + $result = $this->_attrsForFilter($filter, array('mail', 'alias'), + KOLAB_SERVER_RESULT_STRICT); + if (empty($result) || is_a($result, 'PEAR_Error')) { + return $result; + } + $addrs = array_merge((array) $result['mail'], (array) $result['alias']); + $mail = $result['mail'][0]; + + $filter = '(&(objectClass=kolabInetOrgPerson)(kolabDelegate=' + . Horde_LDAP::quote($mail) . '))'; + $result = $this->_attrsForFilter($filter, array('mail', 'alias'), + KOLAB_SERVER_RESULT_MANY); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + if (!empty($result)) { + foreach ($result as $adr) { + if (isset($adr['mail'])) { + $addrs = array_merge((array) $addrs, (array) $adr['mail']); + } + if (isset($adr['alias'])) { + $addrs = array_merge((array) $addrs, (array) $adr['alias']); + } + } + } + + return $addrs; + } + + /** + * Return the UID for a given primary mail, ID, or alias. + * + * @param string $mail A valid mail address for the user. + * + * @return mixed|PEAR_Error The UID or false if there was no result. + */ + function uidForMailAddress($mail) + { + $filter = '(&(objectClass=kolabInetOrgPerson)(|(uid='. + Horde_LDAP::quote($mail) . ')(mail=' . + Horde_LDAP::quote($mail) . ')(alias=' . + Horde_LDAP::quote($mail) . ')))'; + return $this->_dnForFilter($filter); + } + + /** + * Identify the UID for the first object found using a specified + * attribute value. + * + * @param string $attr The name of the attribute used for searching. + * @param string $value The desired value of the attribute. + * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. + * + * @return mixed|PEAR_Error The UID or false if there was no result. + */ + function uidForAttr($attr, $value, + $restrict = KOLAB_SERVER_RESULT_SINGLE) + { + $filter = '(&(objectClass=kolabInetOrgPerson)(' . $attr . + '=' . Horde_LDAP::quote($value) . '))'; + return $this->_dnForFilter($filter, $restrict); + } + + /** + * Identify the GID for the first group found using a specified + * attribute value. + * + * @param string $attr The name of the attribute used for searching. + * @param string $value The desired value of the attribute. + * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. + * + * @return mixed|PEAR_Error The GID or false if there was no result. + */ + function gidForAttr($attr, $value, + $restrict = KOLAB_SERVER_RESULT_SINGLE) + { + $filter = '(&(objectClass=kolabGroupOfNames)(' . $attr . + '=' . Horde_LDAP::quote($value) . '))'; + return $this->_dnForFilter($filter, $restrict); + } + + /** + * Is the given UID member of the group with the given mail address? + * + * @param string $uid UID of the user. + * @param string $mail Search the group with this mail address. + * + * @return boolen|PEAR_Error True in case the user is in the + * group, false otherwise. + */ + function memberOfGroupAddress($uid, $mail) + { + $filter = '(&(objectClass=kolabGroupOfNames)(mail=' + . Horde_LDAP::quote($mail) . ')(member=' + . Horde_LDAP::quote($uid) . '))'; + $result = $this->_dnForFilter($filter, KOLAB_SERVER_RESULT_STRICT); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + if (empty($result)) { + return false; + } + return true; + } + + /** + * Get the groups for this object. + * + * @param string $uid The UID of the object to fetch. + * + * @return array|PEAR_Error An array of group ids. + */ + function getGroups($uid) + { + $filter = '(&(objectClass=kolabGroupOfNames)(member=' + . Horde_LDAP::quote($uid) . '))'; + $result = $this->_dnForFilter($filter, KOLAB_SERVER_RESULT_MANY); + if (empty($result)) { + return array(); + } + return $result; + } + + /** + * List all objects of a specific type + * + * @param string $type The type of the objects to be listed + * @param array $params Additional parameters. + * + * @return array|PEAR_Error An array of Kolab objects. + */ + function _listObjects($type, $params = null) + { + if (empty($params['base_dn'])) { + $base = $this->_base_dn; + } else { + $base = $params['base_dn']; + } + + $result = Horde_Kolab_Server_Object::loadClass($type); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + $vars = get_class_vars($type); + $filter = $vars['filter']; + $sort = $vars['sort_by']; + + if (isset($params['sort'])) { + $sort = $params['sort']; + } + + $result = $this->_search($filter, null, $base); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + if (empty($result)) { + return array(); + } + + if ($sort) { + $this->_sort($result, $sort); + } + + if (isset($params['from'])) { + $from = $params['from']; + } else { + $from = -1; + } + + if (isset($params['to'])) { + $sort = $params['to']; + } else { + $to = -1; + } + + $entries = $this->_getDns($result, $from, $to); + if (!$entries && $this->_errno()) { + return PEAR::raiseError(sprintf(_("Search failed. Error was: %s"), + $this->_error())); + } + if (!$entries) { + return false; + } + + if (!empty($vars['required_group'])) { + $required_group = $this->fetch($vars['required_group'], + KOLAB_OBJECT_GROUP); + } + + $objects = array(); + foreach ($entries as $dn) { + if (!empty($vars['required_group']) && $required_group->isMember($dn)) { + continue; + } + $result = $this->fetch($dn, $type); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + $objects[] = $result; + } + return $objects; + } + + /** + * Generates a UID for the given information. + * + * @param string $type The type of the object to create. + * @param string $id The id of the object. + * @param array $info Any additional information about the object to create. + * + * @return string|PEAR_Error The DN. + */ + function _generateUid($type, $id, $info) + { + switch ($type) { + case KOLAB_OBJECT_USER: + if (!isset($info['user_type']) || $info['user_type'] == 0) { + return sprintf('cn=%s,%s', $id, $this->_base_dn); + } else if ($info['user_type'] == KOLAB_UT_INTERNAL) { + return sprintf('cn=%s,cn=internal,%s', $id, $this->_base_dn); + } else if ($info['user_type'] == KOLAB_UT_GROUP) { + return sprintf('cn=%s,cn=groups,%s', $id, $this->_base_dn); + } else if ($info['user_type'] == KOLAB_UT_RESOURCE) { + return sprintf('cn=%s,cn=resources,%s', $id, $this->_base_dn); + } else { + return sprintf('cn=%s,%s', $id, $this->_base_dn); + } + case KOLAB_OBJECT_ADDRESS: + return sprintf('cn=%s,cn=external,%s', $id, $this->_base_dn); + case KOLAB_OBJECT_SHAREDFOLDER: + case KOLAB_OBJECT_ADMINISTRATOR: + case KOLAB_OBJECT_MAINTAINER: + case KOLAB_OBJECT_DOMAINMAINTAINER: + return sprintf('cn=%s,%s', $id, $this->_base_dn); + case KOLAB_OBJECT_GROUP: + case KOLAB_OBJECT_DISTLIST: + if (!isset($info['visible']) || !empty($info['visible'])) { + return sprintf('cn=%s,%s', $id, $this->_base_dn); + } else { + return sprintf('cn=%s,cn=internal,%s', $id, $this->_base_dn); + } + default: + return PEAR::raiseError(_("Not implemented!")); + } + } + + /** + * Save an object. + * + * @param string $dn The DN of the object. + * @param array $data The data for the object. + * + * @return boolean|PEAR_Error True if successfull. + */ + function save($dn, $data) + { + $result = $this->_add($dn, $data); + if (!$result && $this->_errno()) { + return PEAR::raiseError(sprintf(_("Failed saving object. Error was: %s"), + $this->_error())); + } + } + +} diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/test.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/test.php new file mode 100644 index 000000000..65c7820b0 --- /dev/null +++ b/framework/Kolab_Server/lib/Horde/Kolab/Server/test.php @@ -0,0 +1,623 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** Require the LDAP based class as our base class */ +require_once 'Horde/Kolab/Server/ldap.php'; + +/** + * This class provides a class for testing the Kolab Server DB. + * + * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/test.php,v 1.8 2009/01/06 17:49:26 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_test extends Horde_Kolab_Server_ldap { + + /** + * Array holding the current result set. + * + * @var array + */ + var $_result; + + /** + * Buffer for error numbers. + * + * @var int + */ + var $_errno = 0; + + /** + * Buffer for error descriptions. + * + * @var int + */ + var $_error = ''; + + /** + * Attribute used for sorting. + * + * @var string + */ + var $_sort_by; + + /** + * A result cache for iterating over the result. + * + * @var array + */ + var $_current_result; + + /** + * An index into the current result for iterating. + * + * @var int + */ + var $_current_index; + + /** + * Construct a new Horde_Kolab_Server object. + * + * @param array $params Parameter array. + */ + function Horde_Kolab_Server_test($params = array()) + { + if (isset($params['data'])) { + $GLOBALS['KOLAB_SERVER_TEST_DATA'] = $params['data']; + } else { + if (!isset($GLOBALS['KOLAB_SERVER_TEST_DATA'])) { + $GLOBALS['KOLAB_SERVER_TEST_DATA'] = array(); + } + } + Horde_Kolab_Server::Horde_Kolab_Server($params); + } + + /** + * Binds the LDAP connection with a specific user and pass. + * + * @param string $dn DN to bind with + * @param string $pw Password associated to this DN. + * + * @return boolean|PEAR_Error Whether or not the binding succeeded. + */ + function _bind($dn = false, $pw = '') + { + if (!$dn) { + if (isset($this->_params['uid'])) { + $dn = $this->_params['uid']; + } else { + $dn = ''; + } + } + if (!$pw) { + if (isset($this->_params['pass'])) { + $pw = $this->_params['pass']; + } + } + + if (!empty($dn)) { + if (!isset($GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn])) { + return PEAR::raiseError('User does not exist!'); + } + + $this->_bound = true; + + $data = $this->_read($dn, $attrs = array('userPassword')); + if (is_a($data, 'PEAR_Error')) { + $this->_bound = false; + return $data; + } + if (!isset($data['userPassword'])) { + $this->_bound = false; + return PEAR::raiseError('User has no password entry!'); + } + $this->_bound = $data['userPassword'][0] == $pw; + if (!$this->_bound) { + return PEAR::raiseError('Incorrect password!'); + } + } else if (!empty($this->_params['no_anonymous_bind'])) { + $this->_bound = false; + return PEAR::raiseError('Anonymous bind is not allowed!'); + } else { + $this->_bound = true; + } + return $this->_bound; + } + + /** + * Disconnect from LDAP. + * + * @return NULL + */ + function unbind() + { + $this->_bound = false; + } + + /** + * Parse LDAP filter. + * Partially derived from Net_LDAP_Filter. + * + * @param string $filter The filter string. + * + * @return array|PEAR_Error An array of the parsed filter. + */ + function _parse($filter) + { + $result = array(); + if (preg_match('/^\((.+?)\)$/', $filter, $matches)) { + if (in_array(substr($matches[1], 0, 1), array('!', '|', '&'))) { + $result['op'] = substr($matches[1], 0, 1); + $result['sub'] = $this->_parseSub(substr($matches[1], 1)); + return $result; + } else { + if (stristr($matches[1], ')(')) { + return PEAR::raiseError("Filter parsing error: invalid filter syntax - multiple leaf components detected!"); + } else { + $filter_parts = preg_split('/(?|<|>=|<=)/', + $matches[1], 2, + PREG_SPLIT_DELIM_CAPTURE); + if (count($filter_parts) != 3) { + return PEAR::raiseError("Filter parsing error: invalid filter syntax - unknown matching rule used"); + } else { + $result['att'] = $filter_parts[0]; + $result['log'] = $filter_parts[1]; + $result['val'] = $filter_parts[2]; + return $result; + } + } + } + } else { + return PEAR::raiseError(sprintf("Filter parsing error: %s - filter components must be enclosed in round brackets", + $filter)); + } + } + + /** + * Parse a LDAP subfilter. + * + * @param string $filter The subfilter string. + * + * @return array|PEAR_Error An array of the parsed subfilter. + */ + function _parseSub($filter) + { + $result = array(); + $level = 0; + $collect = ''; + while (preg_match('/^(\(.+?\))(.*)/', $filter, $matches)) { + if (in_array(substr($matches[1], 0, 2), array('(!', '(|', '(&'))) { + $level++; + } + if ($level) { + $collect .= $matches[1]; + if (substr($matches[2], 0, 1) == ')') { + $collect .= ')'; + $matches[2] = substr($matches[2], 1); + $level--; + if (!$level) { + $result[] = $this->_parse($collect); + } + } + } else { + $result[] = $this->_parse($matches[1]); + } + $filter = $matches[2]; + } + return $result; + } + + /** + * Search for an object. + * + * @param string $filter Filter criteria. + * @param array $attributes Restrict the search result to + * these attributes. + * @param string $base DN of the search base. + * + * @return array|PEAR_Error A LDAP serach result. + */ + function _search($filter, $attributes = null, $base = null) + { + if (!$this->_bound) { + $result = $this->_bind(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $filter = $this->_parse($filter); + if (is_a($filter, 'PEAR_Error')) { + return $filter; + } + $result = $this->_doSearch($filter, $attributes); + if (empty($result)) { + return null; + } + if ($base) { + $subtree = array(); + foreach ($result as $entry) { + if (strpos($entry['dn'], $base)) { + $subtree[] = $entry; + } + } + $result = $subtree; + } + + return $result; + } + + /** + * Perform the search. + * + * @param array $filter Filter criteria- + * @param array $attributes Restrict the search result to + * these attributes. + * + * @return array|PEAR_Error A LDAP serach result. + */ + function _doSearch($filter, $attributes = null) + { + if (isset($filter['log'])) { + $result = array(); + foreach ($GLOBALS['KOLAB_SERVER_TEST_DATA'] as $element) { + if (isset($element['data'][$filter['att']])) { + switch ($filter['log']) { + case '=': + $value = $element['data'][$filter['att']]; + if (($filter['val'] == '*' && !empty($value)) + || $value == $filter['val'] + || (is_array($value) + && in_array($filter['val'], $value))) { + if (empty($attributes)) { + $result[] = $element; + } else { + $selection = $element; + foreach ($element['data'] as $attr => $value) { + if (!in_array($attr, $attributes)) { + unset($selection['data'][$attr]); + } + } + $result[] = $selection; + } + } + break; + default: + return PEAR::raiseError(_("Not implemented!")); + } + } + } + return $result; + } else { + $subresult = array(); + $filtercount = count($filter['sub']); + foreach ($filter['sub'] as $subfilter) { + $subresult = array_merge($subresult, + $this->_doSearch($subfilter, + $attributes)); + } + $result = array(); + $dns = array(); + foreach ($subresult as $element) { + $dns[] = $element['dn']; + + $result[$element['dn']] = $element; + } + switch ($filter['op']) { + case '&': + $count = array_count_values($dns); + $selection = array(); + foreach ($count as $dn => $value) { + if ($value == $filtercount) { + $selection[] = $result[$dn]; + } + } + return $selection; + case '|': + return array_values($result); + case '!': + $dns = array(); + foreach ($result as $entry) { + if (!in_array($entry['dn'], $dns) ) { + $dns[] = $entry['dn']; + } + } + $all_dns = array_keys($GLOBALS['KOLAB_SERVER_TEST_DATA']); + $diff = array_diff($all_dns, $dns); + + $result = array(); + foreach ($diff as $dn) { + if (empty($attributes)) { + $result[] = $GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn]; + } else { + $selection = $GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn]; + foreach ($GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn]['data'] + as $attr => $value) { + if (!in_array($attr, $attributes)) { + unset($selection['data'][$attr]); + } + } + $result[] = $selection; + } + } + return $result; + default: + return PEAR::raiseError(_("Not implemented!")); + } + } + } + + /** + * Read object data. + * + * @param string $dn The object to retrieve. + * @param string $attrs Restrict to these attributes + * + * @return array|PEAR_Error An array of attributes. + */ + function _read($dn, $attrs = null) + { + if (!$this->_bound) { + $result = $this->_bind(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + if (!isset($GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn])) { + return PEAR::raiseError(sprintf("LDAP Error: No such object: %s: No such object", + $dn)); + } + if (empty($attrs)) { + return $GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn]['data']; + } else { + $result = array(); + $data = $GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn]['data']; + + foreach ($attrs as $attr) { + if (isset($data[$attr])) { + $result[$attr] = $data[$attr]; + array_push($result, $attr); + } + } + $result['count'] = 1; + return $result; + } + } + + /** + * Add a new object + * + * @param string $dn The DN of the object to be added. + * @param array $data The attributes of the object to be added. + * + * @return boolean True if adding succeeded. + */ + function _add($dn, $data) + { + if (!$this->_bound) { + $result = $this->_bind(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + $ldap_data = array(); + foreach ($data as $key => $val) { + if (!is_array($val)) { + $val = array($val); + } + $ldap_data[$key] = array_merge(array('count' => count($val)), $val); + } + + $GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn] = array( + 'dn' => $dn, + 'data' => $ldap_data + ); + } + + /** + * Count the number of results. + * + * @param array $result The LDAP search result. + * + * @return int The number of records found. + */ + function _count($result) + { + if (is_array($result)) { + return count($result); + } else { + return false; + } + } + + /** + * Return the dn of an entry. + * + * @param array $entry The LDAP entry. + * + * @return string The DN of the entry. + */ + function _getDn($entry) + { + if (is_array($entry) && isset($entry['dn'])) { + if (isset($entry['count'])) { + return $entry['dn'][0]; + } else { + return $entry['dn']; + } + } + return false; + } + + /** + * Return the attributes of an entry. + * + * @param array $entry The LDAP entry. + * + * @return array The attributes of the entry. + */ + function _getAttributes($entry) + { + if (is_array($entry)) { + return $entry; + } + return false; + } + + /** + * Return the current entry of a result. + * + * @return mixe The current entry of the result or false. + */ + function _fetchEntry() + { + if (is_array($this->_current_result) + && $this->_current_index < count($this->_current_result)) { + + $data = array_keys($this->_current_result[$this->_current_index]['data']); + + $data['count'] = 1; + $data['dn'] = array($this->_current_result[$this->_current_index]['dn']); + $data['dn']['count'] = 1; + + foreach ($this->_current_result[$this->_current_index]['data'] + as $attr => $value) { + if (!is_array($value)) { + $value = array($value); + } + $data[$attr] = $value; + $data[$attr]['count'] = count($value); + } + $this->_current_index++; + return $data; + } + return false; + } + + /** + * Return the first entry of a result. + * + * @param array $result The LDAP search result. + * + * @return mixed The first entry of the result or false. + */ + function _firstEntry($result) + { + $this->_current_result = $result; + $this->_current_index = 0; + return $this->_fetchEntry(); + } + + /** + * Return the next entry of a result. + * + * @param resource $entry The current LDAP entry. + * + * @return resource The next entry of the result. + */ + function _nextEntry($entry) + { + return $this->_fetchEntry(); + } + + /** + * Return the entries of a result. + * + * @param array $result The LDAP search result. + * + * @return mixed The entries of the result or false. + */ + function _getEntries($result) + { + if (is_array($result)) { + $data = array(); + $data['count'] = count($result); + foreach ($result as $entry) { + $t = $entry['data']; + $t['dn'] = $entry['dn']; + $data[] = $t; + } + return $data; + } + return false; + } + + /** + * Sort the entries of a result. + * + * @param resource &$result The LDAP search result. + * @param string $attribute The attribute used for sorting. + * + * @return boolean True if sorting succeeded. + */ + function _sort(&$result, $attribute) + { + if (empty($result)) { + return $result; + } + + $this->_sort_by = $attribute; + usort($result, array($this, '_resultSort')); + return false; + } + + /** + * Sort two entries. + * + * @param array $a First entry. + * @param array $b Second entry. + * + * @return int Comparison result. + */ + function _resultSort($a, $b) + { + $x = isset($a['data'][$this->_sort_by][0])?$a['data'][$this->_sort_by][0]:''; + $y = isset($b['data'][$this->_sort_by][0])?$b['data'][$this->_sort_by][0]:''; + return strcasecmp($x, $y); + } + + + /** + * Return the current LDAP error number. + * + * @return int The current LDAP error number. + */ + function _errno() + { + return $this->_errno; + } + + /** + * Return the current LDAP error description. + * + * @return string The current LDAP error description. + */ + function _error() + { + return $this->_error; + } + +} diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Session.php b/framework/Kolab_Server/lib/Horde/Kolab/Session.php new file mode 100644 index 000000000..d72437279 --- /dev/null +++ b/framework/Kolab_Server/lib/Horde/Kolab/Session.php @@ -0,0 +1,384 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** We need the Auth library */ +require_once 'Horde/Auth.php'; + +/** + * The Horde_Kolab_Session class holds additional user details for the current + * session. + * + * The core user credentials (login, pass) are kept within the Auth module and + * can be retrieved using Auth::getAuth() respectively + * Auth::getCredential('password'). Any additional Kolab user data + * relevant for the user session should be accessed via the Horde_Kolab_Session + * class. + * + * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Session.php,v 1.11 2009/02/07 14:03:32 wrobel Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Session { + + /** + * User ID. + * + * @var string + */ + var $user_id; + + /** + * User UID. + * + * @var string + */ + var $user_uid; + + /** + * Primary user mail address. + * + * @var string + */ + var $user_mail; + + /** + * Full name. + * + * @var string + */ + var $user_name = ''; + + /** + * True if the Kolab_Server login was successfull. + * + * @var boolean|PEAR_Error + */ + var $auth; + + /** + * The connection parameters for the IMAP server. + * + * @var array|PEAR_Error + */ + var $_imap_params; + + /** + * Our IMAP connection. + * + * @var Horde_Kolab_IMAP + */ + var $_imap; + + /** + * The free/busy server for the current user. + * + * @var array|PEAR_Error + */ + var $freebusy_server; + + /** + * Constructor. + * + * @param string $user The session will be setup for the user with + * this ID. + * @param array $credentials An array of login credentials. For Kolab, + * this must contain a "password" entry. + */ + function Horde_Kolab_Session($user = null, $credentials = null) + { + global $conf; + + if (empty($user)) { + $user = Auth::getAuth(); + if (empty($user)) { + $user = 'anonymous'; + } else if (!strpos($user, '@')) { + $user = $user . '@' . (!empty($_SERVER['SERVER_NAME']) ? + $_SERVER['SERVER_NAME'] : 'localhost'); + } + } + + $this->user_id = $user; + $this->_imap_params = array(); + + $user_object = null; + + if ($user != 'anonymous') { + $server = $this->getServer($user, $credentials); + if (is_a($server, 'PEAR_Error')) { + $this->auth = $server; + } else { + $this->user_uid = $server->uid; + $user_object = $server->fetch(); + + if (is_a($user_object, 'PEAR_Error')) { + $this->auth = $user_object; + } else { + if (empty($conf['kolab']['imap']['allow_special_users']) + && !is_a($user_object, 'Horde_Kolab_Server_Object_user')) { + $this->auth = PEAR::raiseError(_('Access to special Kolab users is denied.')); + } else if (isset($conf['kolab']['server']['deny_group'])) { + $dn = $server->gidForMail($conf['kolab']['server']['deny_group']); + if (is_a($dn, 'PEAR_Error')) { + $this->auth = $dn; + } else if (empty($dn)) { + Horde::logMessage('The Kolab configuratin setting $conf[\'kolab\'][\'server\'][\'deny_group\'] holds a non-existing group!', + __FILE__, __LINE__, PEAR_LOG_WARNING); + $this->auth = true; + } else if (in_array($dn, $user_object->getGroups())) { + $this->auth = PEAR::raiseError(_('You are member of a group that may not login on this server.')); + } else { + $this->auth = true; + } + } else if (isset($conf['kolab']['server']['allow_group'])) { + $dn = $server->gidForMail($conf['kolab']['server']['allow_group']); + if (is_a($dn, 'PEAR_Error')) { + $this->auth = $dn; + } else if (empty($dn)) { + Horde::logMessage('The Kolab configuratin setting $conf[\'kolab\'][\'server\'][\'allow_group\'] holds a non-existing group!', + __FILE__, __LINE__, PEAR_LOG_WARNING); + $this->auth = true; + } else if (!in_array($dn, $user_object->getGroups())) { + $this->auth = PEAR::raiseError(_('You are no member of a group that may login on this server.')); + } else { + $this->auth = true; + } + } else { + /** + * At this point we can be certain the user is an + * authenticated Kolab user. + */ + $this->auth = true; + } + + if (empty($this->auth) || is_a($this->auth, 'PEAR_Error')) { + return; + } + + $result = $user_object->get(KOLAB_ATTR_MAIL); + if (!empty($result) && !is_a($result, 'PEAR_Error')) { + $this->user_mail = $result; + } + + $result = $user_object->get(KOLAB_ATTR_SID); + if (!empty($result) && !is_a($result, 'PEAR_Error')) { + $this->user_id = $result; + } + + $result = $user_object->get(KOLAB_ATTR_FNLN); + if (!empty($result) && !is_a($result, 'PEAR_Error')) { + $this->user_name = $result; + } + + $result = $user_object->getServer('imap'); + if (!empty($result) && !is_a($result, 'PEAR_Error')) { + $server = explode(':', $result, 2); + if (!empty($server[0])) { + $this->_imap_params['hostspec'] = $server[0]; + } + if (!empty($server[1])) { + $this->_imap_params['port'] = $server[1]; + } + } + + $result = $user_object->getServer('freebusy'); + if (!empty($result) && !is_a($result, 'PEAR_Error')) { + $this->freebusy_server = $result; + } + } + } + } + + if (empty($this->user_mail)) { + $this->user_mail = $user; + } + + if (!isset($this->_imap_params['hostspec'])) { + if (isset($conf['kolab']['imap']['server'])) { + $this->_imap_params['hostspec'] = $conf['kolab']['imap']['server']; + } else { + $this->_imap_params['hostspec'] = 'localhost'; + } + } + + if (!isset($this->_imap_params['port'])) { + if (isset($conf['kolab']['imap']['port'])) { + $this->_imap_params['port'] = $conf['kolab']['imap']['port']; + } else { + $this->_imap_params['port'] = 143; + } + } + + $this->_imap_params['protocol'] = 'imap/notls/novalidate-cert'; + } + + /** + * Returns the properties that need to be serialized. + * + * @return array List of serializable properties. + */ + function __sleep() + { + $properties = get_object_vars($this); + unset($properties['_imap']); + $properties = array_keys($properties); + return $properties; + } + + /** + * Get the Kolab Server connection. + * + * @param string $user The session will be setup for the user with + * this ID. + * @param array $credentials An array of login credentials. For Kolab, + * this must contain a "password" entry. + * + * @return Horde_Kolab_Server|PEAR_Error The Kolab Server connection. + */ + function &getServer($user = null, $credentials = null) + { + /** We need the Kolab Server access. */ + require_once 'Horde/Kolab/Server.php'; + + $params = array(); + if ($this->user_uid) { + $params['uid'] = $this->user_uid; + $params['pass'] = Auth::getCredential('password'); + } else if (isset($user)) { + $params['user'] = $user; + if (isset($credentials['password'])) { + $params['pass'] = $credentials['password']; + } else { + $params['pass'] = Auth::getCredential('password'); + } + } + return Horde_Kolab_Server::singleton($params); + } + + /** + * Get the IMAP connection parameters. + * + * @return array|PEAR_Error The IMAP connection parameters. + */ + function &getImapParams() + { + return $this->_imap_params; + } + + /** + * Create an IMAP connection. + * + * @return Horde_Kolab_IMAP|PEAR_Error The IMAP connection. + */ + function &getImap() + { + if (!isset($this->_imap)) { + + $params = $this->getImapParams(); + if (is_a($params, 'PEAR_Error')) { + return $params; + } + + /** We need the Kolab IMAP library now. */ + require_once 'Horde/Kolab/IMAP.php'; + + $imap = &Horde_Kolab_IMAP::singleton($params['hostspec'], + $params['port'], true, false); + if (is_a($imap, 'PEAR_Error')) { + return $imap; + } + + $result = $imap->connect(Auth::getAuth(), + Auth::getCredential('password')); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + $this->_imap = &$imap; + } + return $this->_imap; + } + + /** + * Attempts to return a reference to a concrete Horde_Kolab_Session instance. + * + * It will only create a new instance if no Horde_Kolab_Session instance + * currently exists or if a user ID has been specified that does not match the + * user ID/user mail of the current session. + * + * This method must be invoked as: + * $var = &Horde_Kolab_Session::singleton(); + * + * @param string $user The session will be setup for the user with + * this ID. + * @param array $credentials An array of login credentials. For Kolab, + * this must contain a "password" entry. + * + * @static + * + * @return Horde_Kolab_Session The concrete Session reference. + */ + function &singleton($user = null, $credentials = null, $destruct = false) + { + static $session; + + if (!isset($session)) { + /** + * Horde_Kolab_Server currently has no caching so we mainly + * cache some user information here as reading this data + * may be expensive when running in a multi-host + * environment. + */ + require_once 'Horde/SessionObjects.php'; + $hs = &Horde_SessionObjects::singleton(); + $session = $hs->query('kolab_session'); + } + + if (empty($user)) { + $user = Auth::getAuth(); + } + + if ($destruct || empty($session) + || ($user != $session->user_mail && $user != $session->user_id)) { + $session = new Horde_Kolab_Session($user, $credentials); + } + + register_shutdown_function(array(&$session, 'shutdown')); + + return $session; + } + + /** + * Stores the object in the session cache. + * + * @return NULL + */ + function shutdown() + { + require_once 'Horde/SessionObjects.php'; + $session = &Horde_SessionObjects::singleton(); + $session->overwrite('kolab_session', $this, false); + } + +} diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Test/Server.php b/framework/Kolab_Server/lib/Horde/Kolab/Test/Server.php new file mode 100644 index 000000000..d45ddd3db --- /dev/null +++ b/framework/Kolab_Server/lib/Horde/Kolab/Test/Server.php @@ -0,0 +1,746 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Share + */ + +/** + * We need the unit test framework + */ +require_once 'PHPUnit/Framework.php'; +require_once 'PHPUnit/Extensions/Story/TestCase.php'; + +/** + * We need the classes to be tested + */ +require_once 'Horde.php'; +require_once 'Horde/Kolab/Server.php'; + +/** + * Base for PHPUnit scenarios. + * + * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Test/Server.php,v 1.3 2009/01/06 17:49:26 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Test + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Share + */ +class Horde_Kolab_Test_Server extends PHPUnit_Extensions_Story_TestCase +{ + /** + * Handle a "given" step. + * + * @param array &$world Joined "world" of variables. + * @param string $action The description of the step. + * @param array $arguments Additional arguments to the step. + * + * @return mixed The outcome of the step. + */ + public function runGiven(&$world, $action, $arguments) + { + switch($action) { + case 'an empty Kolab server': + $world['server'] = &$this->prepareEmptyKolabServer(); + break; + case 'a basic Kolab server': + $world['server'] = &$this->prepareBasicKolabServer(); + break; + case 'the Kolab auth driver has been selected': + $world['auth'] = &$this->prepareKolabAuthDriver(); + break; + default: + return $this->notImplemented($action); + } + } + + /** + * Handle a "when" step. + * + * @param array &$world Joined "world" of variables. + * @param string $action The description of the step. + * @param array $arguments Additional arguments to the step. + * + * @return mixed The outcome of the step. + */ + public function runWhen(&$world, $action, $arguments) + { + switch($action) { + case 'adding a Kolab server object': + $world['result']['add'] = $world['server']->add($arguments[0]); + break; + case 'logging in as a user with a password': + $world['login'] = $world['auth']->authenticate($arguments[0], + array('password' => $arguments[1])); + break; + case 'adding an object list': + foreach ($arguments[0] as $object) { + $result = $world['server']->add($object); + if (is_a($result, 'PEAR_Error')) { + $world['result']['add'] = $result; + return; + } + } + $world['result']['add'] = true; + break; + case 'adding a user without first name': + $world['result']['add'] = $world['server']->add($this->provideInvalidUserWithoutGivenName()); + break; + case 'adding a user without last name': + $world['result']['add'] = $world['server']->add($this->provideInvalidUserWithoutLastName()); + break; + case 'adding a user without password': + $world['result']['add'] = $world['server']->add($this->provideInvalidUserWithoutPassword()); + break; + case 'adding a user without primary mail': + $world['result']['add'] = $world['server']->add($this->provideInvalidUserWithoutMail()); + break; + case 'adding a distribution list': + $world['result']['add'] = $world['server']->add($this->provideDistributionList()); + break; + case 'listing all users': + $world['list'] = $world['server']->listObjects(KOLAB_OBJECT_USER); + break; + case 'listing all groups': + $world['list'] = $world['server']->listObjects(KOLAB_OBJECT_GROUP); + break; + case 'listing all objects of type': + $world['list'] = $world['server']->listObjects($arguments[0]); + break; + case 'retrieving a hash list with all objects of type': + $world['list'] = $world['server']->listHash($arguments[0]); + break; + default: + return $this->notImplemented($action); + } + } + + /** + * Handle a "then" step. + * + * @param array &$world Joined "world" of variables. + * @param string $action The description of the step. + * @param array $arguments Additional arguments to the step. + * + * @return mixed The outcome of the step. + */ + public function runThen(&$world, $action, $arguments) + { + switch($action) { + case 'the result should be an object of type': + if (!isset($world['result'])) { + $this->fail('Did not receive a result!'); + } + foreach ($world['result'] as $result) { + if ($result instanceOf PEAR_Error) { + $this->assertEquals('', $result->getMessage()); + } else { + $this->assertEquals($arguments[0], get_class($result)); + } + } + break; + case 'the result indicates success.': + if (!isset($world['result'])) { + $this->fail('Did not receive a result!'); + } + foreach ($world['result'] as $result) { + if ($result instanceOf PEAR_Error) { + $this->assertEquals('', $result->getMessage()); + } else { + $this->assertTrue($result); + } + } + break; + case 'the result should indicate an error with': + if (!isset($world['result'])) { + $this->fail('Did not receive a result!'); + } + foreach ($world['result'] as $result) { + if ($result instanceOf PEAR_Error) { + $this->assertEquals($arguments[0], $result->getMessage()); + } else { + $this->assertEquals($arguments[0], 'Action succeeded without an error.'); + } + } + break; + case 'the list has a number of entries equal to': + if ($world['list'] instanceOf PEAR_Error) { + $this->assertEquals('', $world['list']->getMessage()); + } else { + $this->assertEquals($arguments[0], count($world['list'])); + } + break; + case 'the list is an empty array': + if ($world['list'] instanceOf PEAR_Error) { + $this->assertEquals('', $world['list']->getMessage()); + } else { + $this->assertEquals(array(), $world['list']); + } + break; + case 'the list is an empty array': + if ($world['list'] instanceOf PEAR_Error) { + $this->assertEquals('', $world['list']->getMessage()); + } else { + $this->assertEquals(array(), $world['list']); + } + break; + case 'the provided list and the result list match with regard to these attributes': + if ($world['list'] instanceOf PEAR_Error) { + $this->assertEquals('', $world['list']->getMessage()); + } else { + $provided_vals = array(); + foreach ($arguments[2] as $provided_element) { + if (isset($provided_element[$arguments[0]])) { + $provided_vals[] = $provided_element[$arguments[0]]; + } else { + $this->fail(sprintf('The provided element %s does have no value for %s.', + print_r($provided_element, true), + print_r($arguments[0]))); + } + } + $result_vals = array(); + foreach ($world['list'] as $result_element) { + if (isset($result_element[$arguments[1]])) { + $result_vals[] = $result_element[$arguments[1]]; + } else { + $this->fail(sprintf('The result element %s does have no value for %s.', + print_r($result_element, true), + print_r($arguments[1]))); + } + } + $this->assertEquals(array(), + array_diff($provided_vals, $result_vals)); + } + break; + case 'each element in the result list has an attribute': + if ($world['list'] instanceOf PEAR_Error) { + $this->assertEquals('', $world['list']->getMessage()); + } else { + $result_vals = array(); + foreach ($world['list'] as $result_element) { + if (!isset($result_element[$arguments[0]])) { + $this->fail(sprintf('The result element %s does have no value for %s.', + print_r($result_element, true), + print_r($arguments[0]))); + } + } + } + break; + case 'each element in the result list has an attribute set to a given value': + if ($world['list'] instanceOf PEAR_Error) { + $this->assertEquals('', $world['list']->getMessage()); + } else { + $result_vals = array(); + foreach ($world['list'] as $result_element) { + if (!isset($result_element[$arguments[0]])) { + $this->fail(sprintf('The result element %s does have no value for %s.', + print_r($result_element, true), + print_r($arguments[0], true))); + } + if ($result_element[$arguments[0]] != $arguments[1]) { + $this->fail(sprintf('The result element %s has an unexpected value %s for %s.', + print_r($result_element, true), + print_r($result_element[$arguments[0]], true), + print_r($arguments[0], true))); + } + } + } + break; + case 'the login was successful': + $this->assertNoError($world['login']); + $this->assertTrue($world['login']); + break; + case 'the list contains a number of elements equal to': + $this->assertEquals($arguments[0], count($world['list'])); + break; + default: + return $this->notImplemented($action); + } + } + + + /** + * Prepare an empty Kolab server. + * + * @return Horde_Kolab_Server The empty server. + */ + public function &prepareEmptyKolabServer() + { + global $conf; + + include_once 'Horde/Kolab/Server.php'; + + $GLOBALS['KOLAB_SERVER_TEST_DATA'] = array(); + + /** Prepare a Kolab test server */ + $conf['kolab']['server']['driver'] = 'test'; + + $server = Horde_Kolab_Server::singleton(); + + /** Ensure we don't use a connection from older tests */ + $server->unbind(); + + /** Set base DN */ + $server->_base_dn = 'dc=example,dc=org'; + + /** Clean the server data */ + + return $server; + } + + /** + * Prepare a Kolab server with some basic entries. + * + * @return Horde_Kolab_Server The empty server. + */ + public function &prepareBasicServer() + { + $server = $this->prepareEmptyKolabServer(); + $this->prepareUsers($server); + return $server; + } + + /** + * Fill a Kolab Server with test users. + * + * @param Kolab_Server &$server The server to populate. + * + * @return Horde_Kolab_Server The empty server. + */ + public function prepareUsers(&$server) + { + $result = $server->add($this->provideBasicUserOne()); + $this->assertNoError($result); + $result = $server->add($this->provideBasicUserTwo()); + $this->assertNoError($result); + $result = $server->add($this->provideBasicAddress()); + $this->assertNoError($result); + $result = $server->add($this->provideBasicAdmin()); + $this->assertNoError($result); + $result = $server->add($this->provideBasicDomainMaintainer()); + $this->assertNoError($result); + $result = $server->add($this->provideBasicGroupOne()); + $this->assertNoError($result); + $result = $server->add($this->provideBasicGroupTwo()); + $this->assertNoError($result); + $result = $server->add($this->provideBasicMaintainer()); + $this->assertNoError($result); + $result = $server->add($this->provideBasicSharedFolder()); + $this->assertNoError($result); + } + + /** + * Prepare a Kolab Auth Driver. + * + * @return Auth The auth driver. + */ + public function &prepareKolabAuthDriver() + { + include_once 'Horde/Auth.php'; + + $auth = Auth::singleton('kolab'); + return $auth; + } + + /** + * Return a test user. + * + * @return array The test user. + */ + public function provideBasicUserOne() + { + return array('givenName' => 'Gunnar', + 'sn' => 'Wrobel', + 'type' => KOLAB_OBJECT_USER, + 'mail' => 'wrobel@example.org', + 'uid' => 'wrobel', + 'userPassword' => 'none', + 'kolabHomeServer' => 'home.example.org', + 'kolabImapServer' => 'imap.example.org', + 'kolabFreeBusyServer' => 'https://fb.example.org/freebusy', + KOLAB_ATTR_IPOLICY => array('ACT_REJECT_IF_CONFLICTS'), + 'alias' => array('gunnar@example.org', + 'g.wrobel@example.org'), + ); + } + + /** + * Return a test user. + * + * @return array The test user. + */ + public function provideBasicUserTwo() + { + return array('givenName' => 'Test', + 'sn' => 'Test', + 'type' => KOLAB_OBJECT_USER, + 'mail' => 'test@example.org', + 'uid' => 'test', + 'userPassword' => 'test', + 'kolabHomeServer' => 'home.example.org', + 'kolabImapServer' => 'home.example.org', + 'kolabFreeBusyServer' => 'https://fb.example.org/freebusy', + 'alias' => array('t.test@example.org'), + KOLAB_ATTR_KOLABDELEGATE => 'wrobel@example.org',); + } + + /** + * Return a test address. + * + * @return array The test address. + */ + public function provideBasicAddress() + { + return array('givenName' => 'Test', + 'sn' => 'Address', + 'type' => KOLAB_OBJECT_ADDRESS, + 'mail' => 'address@example.org'); + } + + /** + * Return a test administrator. + * + * @return array The test administrator. + */ + public function provideBasicAdmin() + { + return array('sn' => 'Administrator', + 'givenName' => 'The', + 'uid' => 'admin', + 'type' => KOLAB_OBJECT_ADMINISTRATOR, + 'userPassword' => 'none'); + } + + /** + * Return a test maintainer. + * + * @return array The test maintainer. + */ + public function provideBasicMaintainer() + { + return array('sn' => 'Tainer', + 'givenName' => 'Main', + 'uid' => 'maintainer', + 'type' => KOLAB_OBJECT_MAINTAINER, + 'userPassword' => 'none', + ); + } + + /** + * Return a test domain maintainer. + * + * @return array The test domain maintainer. + */ + public function provideBasicDomainMaintainer() + { + return array('sn' => 'Maintainer', + 'givenName' => 'Domain', + 'uid' => 'domainmaintainer', + 'type' => KOLAB_OBJECT_DOMAINMAINTAINER, + 'userPassword' => 'none', + 'domain' => array('example.com'), + ); + } + + /** + * Return a test shared folder. + * + * @return array The test shared folder. + */ + public function provideBasicSharedFolder() + { + return array('cn' => 'shared@example.org', + 'kolabHomeServer' => 'example.org', + 'type' => KOLAB_OBJECT_SHAREDFOLDER); + } + + /** + * Return a test group. + * + * @return array The test group. + */ + public function provideBasicGroupOne() + { + return array('mail' => 'empty.group@example.org', + 'type' => KOLAB_OBJECT_GROUP); + } + + /** + * Return a test group. + * + * @return array The test group. + */ + public function provideBasicGroupTwo() + { + return array('mail' => 'group@example.org', + 'type' => KOLAB_OBJECT_GROUP, + 'member' => array('cn=Test Test,dc=example,dc=org', + 'cn=Gunnar Wrobel,dc=example,dc=org')); + } + + public function provideDistributionList() + { + return array('mail' => 'distlist@example.org', + 'type' => KOLAB_OBJECT_DISTLIST, + 'member' => array('cn=Test Test,dc=example,dc=org', + 'cn=Gunnar Wrobel,dc=example,dc=org')); + } + + public function provideInvalidUserWithoutPassword() + { + return array('givenName' => 'Test', + 'sn' => 'Test', + 'type' => KOLAB_OBJECT_USER, + 'mail' => 'test@example.org'); + } + + public function provideInvalidUserWithoutGivenName() + { + return array('sn' => 'Test', + 'userPassword' => 'none', + 'type' => KOLAB_OBJECT_USER, + 'mail' => 'test@example.org'); + } + + public function provideInvalidUserWithoutLastName() + { + return array('givenName' => 'Test', + 'userPassword' => 'none', + 'type' => KOLAB_OBJECT_USER, + 'mail' => 'test@example.org'); + } + + public function provideInvalidUserWithoutMail() + { + return array('givenName' => 'Test', + 'sn' => 'Test', + 'userPassword' => 'none', + 'type' => KOLAB_OBJECT_USER); + } + + public function provideInvalidUsers() + { + return array( + array( + $this->provideInvalidUserWithoutPassword(), + 'Adding object failed: The value for "userPassword" is missing!' + ), + array( + $this->provideInvalidUserWithoutGivenName(), + 'Adding object failed: Either the last name or the given name is missing!' + ), + array( + $this->provideInvalidUserWithoutLastName(), + 'Adding object failed: Either the last name or the given name is missing!' + ), + array( + $this->provideInvalidUserWithoutMail(), + 'Adding object failed: The value for "mail" is missing!' + ), + ); + } + + /** FIXME: Prefix the stuff bewlow with provide...() */ + + public function validUsers() + { + return array( + array( + $this->provideBasicUserOne(), + ), + array( + $this->provideBasicUserTwo(), + ), + ); + } + + public function validAddresses() + { + return array( + array( + $this->provideBasicAddress(), + ), + ); + } + + public function validAdmins() + { + return array( + array( + $this->provideBasicAdmin(), + ), + ); + } + + public function validMaintainers() + { + return array( + array( + $this->provideBasicMaintainer(), + ) + ); + } + + public function validDomainMaintainers() + { + return array( + array( + $this->provideBasicDomainMaintainer(), + ) + ); + } + + public function validGroups() + { + return array( + array( + $this->validGroupWithoutMembers(), + ), + array( + array('mail' => 'group@example.org', + 'type' => KOLAB_OBJECT_GROUP, + 'member' => array('cn=Test Test,dc=example,dc=org', + 'cn=Gunnar Wrobel,dc=example,dc=org') + ), + ), + array( + array('mail' => 'group2@example.org', + 'type' => KOLAB_OBJECT_GROUP, + 'member' => array('cn=Gunnar Wrobel,dc=example,dc=org') + ), + ), + ); + } + + public function validSharedFolders() + { + return array( + array('cn' => 'Shared', + 'type' => KOLAB_OBJECT_SHAREDFOLDER + ), + ); + } + + public function validGroupWithoutMembers() + { + return array('mail' => 'empty.group@example.org', + 'type' => KOLAB_OBJECT_GROUP, + ); + } + + public function userLists() + { + return array( + ); + } + + public function groupLists() + { + return array( + array( + array( + array('type' => KOLAB_OBJECT_GROUP, + 'mail' => 'empty.group@example.org', + ), + ) + ), + array( + array( + array('mail' => 'empty.group@example.org', + 'type' => KOLAB_OBJECT_GROUP, + ), + ), + array( + array('mail' => 'group@example.org', + 'type' => KOLAB_OBJECT_GROUP, + 'member' => array('cn=Test Test,dc=example,dc=org', + 'cn=Gunnar Wrobel,dc=example,dc=org') + ), + ), + array( + array('mail' => 'group2@example.org', + 'type' => KOLAB_OBJECT_GROUP, + 'member' => array('cn=Gunnar Wrobel,dc=example,dc=org') + ), + ), + ) + ); + } + + public function userListByLetter() + { + return array( + ); + } + + public function userListByAttribute() + { + return array( + ); + } + + public function userAdd() + { + return array( + ); + } + + public function invalidMails() + { + return array( + ); + } + + public function largeList() + { + return array( + ); + } + + /** + * Ensure that the variable contains no PEAR_Error and fail if it does. + * + * @param mixed $var The variable to check. + * + * @return NULL. + */ + public function assertNoError($var) + { + if (is_a($var, 'PEAR_Error')) { + $this->assertEquals('', $var->getMessage()); + } + } + + /** + * Ensure that the variable contains a PEAR_Error and fail if it does + * not. Optionally compare the error message with the provided message and + * fail if both do not match. + * + * @param mixed $var The variable to check. + * @param string $msg The expected error message. + * + * @return NULL. + */ + public function assertError($var, $msg = null) + { + $this->assertEquals('PEAR_Error', get_class($var)); + if (isset($msg)) { + $this->assertEquals($msg, $var->getMessage()); + } + } +} diff --git a/framework/Kolab_Server/package.xml b/framework/Kolab_Server/package.xml new file mode 100644 index 000000000..4ce4ea70f --- /dev/null +++ b/framework/Kolab_Server/package.xml @@ -0,0 +1,255 @@ + + + Kolab_Server + pear.horde.org + A package for manipulating the Kolab user database. + This package allows to read/write entries in the Kolab user + database stored in LDAP. + + + Gunnar Wrobel + wrobel + p@rdus.de + yes + + + Thomas Jarosch + jarosch + thomas.jarosch@intra2net.com + yes + + + Chuck Hagenbuch + chuck + chuck@horde.org + yes + + + Jan Schneider + jan + jan@horde.org + yes + + 2008-12-16 + + 0.4.0 + 0.2.0 + + + alpha + alpha + + LGPL + + * Fixed objectClass evaluation to respect case-insensitivity (Bug: #7694) + * kolab/issue2207 (Make it possible to enable and disable users to be able to + use the webclient) + * Added fullname attribute as support for kolab/issue2546 (Horde + should use name and email from ldap as defaults) + * Fixed initialization of parameters retrieved from LDAP. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.3.0 + + + 1.4.0b1 + + + Auth + pear.horde.org + + + + + Horde_LDAP + pear.horde.org + + + Horde_SessionObjects + pear.horde.org + + + PHPUnit + pear.phpunit.de + + + ldap + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2008-12-16 + + 0.3.0 + 0.2.0 + + + alpha + alpha + + LGPL + + * Fixed the fallback for a missing freebusy_server value. + * Fixed identification of external addresses. + + + + 2008-10-29 + + 0.2.0 + 0.2.0 + + + alpha + alpha + + LGPL + + * Fixed retrieval of the users IMAP home server. + * Allowed retrieving a DN for an id matching either mail, uid or alias. + (Kolab issue 2587, https://www.intevation.de/roundup/kolab/issue2587) + * Moved Kolab session handler from Kolab_Storage to Kolab_Server. + * Enabled retrieval of the users free/busy server. (Enhancement: #6699) + * Added capability to list objects. + * Added write capabilities to the package. + * Moved the IMAP drivers from Kolab_Storage to Kolab_Server as the + IMAP connection must be handled by the Kolab session. + * Added a test class for simplified PHPUnit testing. + + + + + 0.1.1 + 0.1.0 + + + alpha + alpha + + LGPL + + * Renamed package to Kolab_Server. + * Removed an unnecessary translation. + * Added dnForMailOrAlias function to Horde_Kolab_Server. + * Fixed experimental KOLAB_ATTR_IMAPHOST attribute. + + + + + 0.1.0 + 0.1.0 + + + alpha + alpha + + 2008-07-29 + LGPL + + * Initial release. + + + + diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/AddingObjectsTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/AddingObjectsTest.php new file mode 100644 index 000000000..2a9f60b56 --- /dev/null +++ b/framework/Kolab_Server/test/Horde/Kolab/Server/AddingObjectsTest.php @@ -0,0 +1,73 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * We need the base class + */ +require_once 'Horde/Kolab/Test/Server.php'; + +/** + * Adding objects to the server. + * + * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/AddingObjectsTest.php,v 1.3 2009/01/06 17:49:27 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_AddingObjectsTest extends Horde_Kolab_Test_Server { + /** + * Test adding valid users. + * + * @param array $user The user to add. + * + * @scenario + * @dataProvider validUsers + * + * @return NULL + */ + public function addingValidUser($user) + { + $this->given('an empty Kolab server') + ->when('adding a Kolab server object', $user) + ->then('the result should be an object of type', KOLAB_OBJECT_USER); + } + + /** + * Test adding invalid users. + * + * @param array $user The user to add. + * @param string $error The error to expect. + * + * @scenario + * @dataProvider provideInvalidUsers + * + * @return NULL + */ + public function addingInvalidUser($user, $error) + { + $this->given('an empty Kolab server') + ->when('adding a Kolab server object', $user) + ->then('the result should indicate an error with', $error); + } + +} diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/AdminTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/AdminTest.php new file mode 100644 index 000000000..cf730f210 --- /dev/null +++ b/framework/Kolab_Server/test/Horde/Kolab/Server/AdminTest.php @@ -0,0 +1,138 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * We need the base class + */ +require_once 'Horde/Kolab/Test/Server.php'; + +/** + * Test the admin object. + * + * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/AdminTest.php,v 1.3 2009/01/06 17:49:27 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_AdminTest extends Horde_Kolab_Test_Server { + + /** + * Set up testing. + * + * @return NULL + */ + protected function setUp() + { + $this->ldap = $this->prepareEmptyKolabServer(); + } + + /** + * Add an administrator object. + * + * @return NULL + */ + private function _addValidAdmin() + { + $admin = $this->provideBasicAdmin(); + $result = $this->ldap->add($admin); + $this->assertNoError($result); + } + + /** + * Test ID generation for an admin. + * + * @return NULL + */ + public function testGenerateId() + { + $admin = $this->provideBasicAdmin(); + $this->assertNoError($admin); + $uid = $this->ldap->generateUid(KOLAB_OBJECT_ADMINISTRATOR, $admin); + $this->assertNoError($uid); + $this->assertEquals('cn=The Administrator,dc=example,dc=org', $uid); + } + + /** + * Test fetching an admin. + * + * @return NULL + */ + public function testFetchAdmin() + { + $this->_addValidAdmin(); + + $this->assertEquals(2, count($GLOBALS['KOLAB_SERVER_TEST_DATA'])); + $this->assertContains('cn=admin,cn=internal,dc=example,dc=org', + array_keys($GLOBALS['KOLAB_SERVER_TEST_DATA'])); + + $administrators = $this->ldap->getGroups('cn=The Administrator,dc=example,dc=org'); + $this->assertNoError($administrators); + + $admin_group = $this->ldap->fetch('cn=admin,cn=internal,dc=example,dc=org'); + $this->assertNoError($admin_group); + $this->assertTrue($admin_group->exists()); + + $admin = $this->ldap->fetch('cn=The Administrator,dc=example,dc=org'); + $this->assertNoError($admin); + $this->assertEquals('Horde_Kolab_Server_Object_administrator', + get_class($admin)); + } + + /** + * Test listing the admins. + * + * @return NULL + */ + public function testToHash() + { + $this->_addValidAdmin(); + + $admin = $this->ldap->fetch('cn=The Administrator,dc=example,dc=org'); + $this->assertNoError($admin); + + $hash = $admin->toHash(); + $this->assertNoError($hash); + $this->assertContains('uid', array_keys($hash)); + $this->assertContains('lnfn', array_keys($hash)); + $this->assertEquals('admin', $hash['uid']); + } + + /** + * Test listing admins. + * + * @return NULL + */ + public function testListingGroups() + { + $this->_addValidAdmin(); + + $entries = $this->ldap->_search('(&(cn=*)(objectClass=inetOrgPerson)(!(uid=manager))(sn=*))'); + $this->assertNoError($entries); + $this->assertEquals(1, count($entries)); + + $list = $this->ldap->listObjects(KOLAB_OBJECT_ADMINISTRATOR); + $this->assertNoError($list); + $this->assertEquals(1, count($list)); + } + +} diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/AllTests.php b/framework/Kolab_Server/test/Horde/Kolab/Server/AllTests.php new file mode 100644 index 000000000..2463bbbd6 --- /dev/null +++ b/framework/Kolab_Server/test/Horde/Kolab/Server/AllTests.php @@ -0,0 +1,84 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * Define the main method + */ +if (!defined('PHPUnit_MAIN_METHOD')) { + define('PHPUnit_MAIN_METHOD', 'Horde_Kolab_Server_AllTests::main'); +} + +require_once 'PHPUnit/Framework/TestSuite.php'; +require_once 'PHPUnit/TextUI/TestRunner.php'; + +/** + * Combine the tests for this package. + * + * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/AllTests.php,v 1.5 2009/01/06 17:49:27 jan Exp $ + * + * Copyright 2007-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_AllTests { + + /** + * Main entry point for running the suite. + * + * @return NULL + */ + public static function main() + { + PHPUnit_TextUI_TestRunner::run(self::suite()); + } + + /** + * Collect the unit tests of this directory into a new suite. + * + * @return PHPUnit_Framework_TestSuite The test suite. + */ + public static function suite() + { + $suite = new PHPUnit_Framework_TestSuite('Horde Framework - Kolab_Server'); + + $basedir = dirname(__FILE__); + $baseregexp = preg_quote($basedir . DIRECTORY_SEPARATOR, '/'); + + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basedir)) as $file) { + if ($file->isFile() && preg_match('/Test.php$/', $file->getFilename())) { + $pathname = $file->getPathname(); + require $pathname; + + $class = str_replace(DIRECTORY_SEPARATOR, '_', + preg_replace("/^$baseregexp(.*)\.php/", '\\1', $pathname)); + $suite->addTestSuite('Horde_Kolab_Server_' . $class); + } + } + + return $suite; + } + +} + +if (PHPUnit_MAIN_METHOD == 'Horde_Kolab_Server_AllTests::main') { + Horde_Kolab_Server_AllTests::main(); +} diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/DistListHandlingTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/DistListHandlingTest.php new file mode 100644 index 000000000..6f3611cb4 --- /dev/null +++ b/framework/Kolab_Server/test/Horde/Kolab/Server/DistListHandlingTest.php @@ -0,0 +1,56 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * We need the base class + */ +require_once 'Horde/Kolab/Test/Server.php'; + +require_once 'Horde/Kolab/Server.php'; + +/** + * Handling distribution lists. + * + * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/DistListHandlingTest.php,v 1.3 2009/01/06 17:49:27 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_DistListHandlingTest extends Horde_Kolab_Test_Server { + + /** + * Test adding a distribution list. + * + * @scenario + * + * @return NULL + */ + public function creatingDistributionList() + { + $this->given('an empty Kolab server') + ->when('adding a distribution list') + ->then('the result should be an object of type', + KOLAB_OBJECT_DISTLIST); + } + +} diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/GroupHandlingTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/GroupHandlingTest.php new file mode 100644 index 000000000..ae959cb80 --- /dev/null +++ b/framework/Kolab_Server/test/Horde/Kolab/Server/GroupHandlingTest.php @@ -0,0 +1,456 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * We need the base class + */ +require_once 'Horde/Kolab/Test/Server.php'; + +require_once 'Horde/Kolab/Server.php'; + +/** + * Handling groups. + * + * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/GroupHandlingTest.php,v 1.3 2009/01/06 17:49:27 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_GroupHandlingTest extends Horde_Kolab_Test_Server { + /** + * Test listing groups if there are no groups. + * + * @scenario + * + * @return NULL + */ + public function listingGroupsOnEmptyServer() + { + $this->given('an empty Kolab server') + ->when('retrieving a hash list with all objects of type', + KOLAB_OBJECT_GROUP) + ->then('the list is an empty array'); + } + + /** + * Test listing groups after adding some groups. + * + * @param array $group_list The groups to add. + * + * @scenario + * @dataProvider groupLists + * + * @return NULL + */ + public function listingGroups($group_list) + { + $this->given('an empty Kolab server') + ->when('adding an object list', $group_list) + ->and('retrieving a hash list with all objects of type', + KOLAB_OBJECT_GROUP) + ->then('the result indicates success.') + ->and('the list has a number of entries equal to', + count($group_list)); + } + + /** + * Test the list of groups for the group id. + * + * @param array $group_list The groups to add. + * + * @scenario + * @dataProvider groupLists + * + * @return NULL + */ + public function listingGroupsHasAttributeId($group_list) + { + $this->given('an empty Kolab server') + ->when('adding an object list', $group_list) + ->and('retrieving a hash list with all objects of type', + KOLAB_OBJECT_GROUP) + ->then('the provided list and the result list match with regard to these attributes', + 'mail', 'id', $group_list); + } + + /** + * Test the list of groups for the group mail address. + * + * @param array $group_list The groups to add. + * + * @scenario + * @dataProvider groupLists + * + * @return NULL + */ + public function listingGroupsHasAttributeMail($group_list) + { + $this->given('an empty Kolab server') + ->when('adding an object list', $group_list) + ->and('retrieving a hash list with all objects of type', + KOLAB_OBJECT_GROUP) + ->then('the provided list and the result list match with regard to these attributes', + 'mail', 'mail', $group_list); + } + + /** + * Test the list of groups for the group visibility. + * + * @param array $group_list The groups to add. + * + * @scenario + * @dataProvider groupLists + * + * @return NULL + */ + public function listingGroupsHasAttributeVisibility($group_list) + { + $this->given('an empty Kolab server') + ->when('adding an object list', $group_list) + ->and('retrieving a hash list with all objects of type', + KOLAB_OBJECT_GROUP) + ->then('each element in the result list has an attribute', + 'visible'); + } + + /** + * Test adding an invalid group. + * + * @scenario + * + * @return NULL + */ + public function creatingGroupsWithoutMailAddressFails() + { + $this->given('an empty Kolab server') + ->when('adding a group without a mail address') + ->then('the result should indicate an error with', + 'Adding object failed: The value for "mail" is missing!'); + } + + /** + * Test adding a group without setting the visibility. + * + * @scenario + * + * @return NULL + */ + public function creatingGroupWithoutVisibilityCreatesVisibleGroup() + { + $this->given('an empty Kolab server') + ->when('adding an object', $this->validGroupWithoutMembers()) + ->and('retrieving a hash list with all objects of type', + KOLAB_OBJECT_GROUP) + ->then('each element in the result list has an attribute set to a given value', + 'visible', true); + } + + /** + * Test modifying a group mail address. + * + * @scenario + * + * @return NULL + */ + public function modifyingGroupMailAddressIsNotAllowed() + { + $this->given('an empty Kolab server') + ->when('adding a group with the mail address "test@example.org"') + ->and('modifying the mail address to "new@example.org"') + ->then('the result should indicate an error with', + 'The group cannot be modified: Changing the mail address from "test@example.org" to "new@example.org" is not allowed!'); + } + + /** + * Test modifying a group mail address. + * + * @scenario + */ + public function conflictBetweenGroupMailAndUserMailIsNotAllowed() + { + $this->given('an empty Kolab server') + ->when('adding a group with the mail address "test@example.org"') + ->and('adding a user "Test Test" with the mail address "test@example.org"') + ->then('the result should indicate an error with', + 'The user cannot be added: Mail address "test@example.org" is already the mail address for the group "test@example.org"!'); + } + + /** + * @scenario + */ + public function conflictBetweenUserMailAndGroupMailIsNotAllowed() + { + $this->given('an empty Kolab server') + ->when('adding a user "Test Test" with the mail address "test@example.org"') + ->and('adding a group with the mail address "test@example.org"') + ->then('the result should indicate an error with', + 'The group cannot be added: Mail address "test@example.org" is already the mail address of the user "Test Test"!'); + } + + /** + * @scenario + */ + public function conflictBetweenGroupMailAndUserAliasIsNotAllowed() + { + $this->given('an empty Kolab server') + ->when('adding a group with the mail address "test@example.org"') + ->and('adding a user with the alias address "test@example.org"') + ->then('the result should indicate an error with', + 'The user cannot be added: Alias address "test@example.org" is already the mail address of the group "test@example.org"!'); + } + + /** + * @scenario + */ + public function conflictBetweenUserAliasAndGroupMailIsNotAllowed() + { + $this->given('an empty Kolab server') + ->when('adding a user "Test Test" with the alias address "test@example.org"') + ->and('adding a group with the mail address "test@example.org"') + ->then('the result should indicate an error with', + 'The group cannot be added: Mail address "test@example.org" is already the alias address of the user "Test Test"!'); + } + + /** + * kolab/issue890 (Assigning multiple Distribution Lists to user during creation and modification) + * + * @scenario + */ + public function showGroupsWhenFetchingTheUser() + { + $this->given('an empty Kolab server') + ->when('adding a user "cn=Test Test" with the mail address "test@example.org"') + ->and('adding a group with the mail address "testgroup@example.org" and the member "cn=Test Test"') + ->and('fetching the user "test@example.org"') + ->and('listing the groups of this user') + ->then('the list should contain "testgroup@example.org"'); + } + + /** + * @scenario + */ + public function allowAddingUserToGroup() + { + $this->given('an empty Kolab server') + ->when('adding a group with the mail address "testgroup@example.org"') + ->and('adding a user "cn=Test Test" with the mail address "test@example.org"') + ->and('modifying group with the mail address "testgroup@example.org" to contain the member "cn=Test Test".') + ->and('fetching the groups "group@example.org"') + ->and('listing the members of this group') + ->then('the list should contain "test@example.org"'); + } + + /** + * @scenario + */ + public function allowAddingUserToGroupWhenCreatingUser() + { + $this->given('an empty Kolab server') + ->when('adding a group with the mail address "testgroup@example.org"') + ->and('adding a user "cn=Test Test" with the mail address "test@example.org" and member of "testgroup@example.org"') + ->and('fetching the groups "group@example.org"') + ->and('listing the members of this group') + ->then('the list should contain "test@example.org"'); + } + + /** + * @scenario + */ + public function allowRemovingUserFromGroup() + { + $this->given('an empty Kolab server') + ->when('adding a user "cn=Test Test" with the mail address "test@example.org"') + ->and('adding a group with the mail address "testgroup@example.org" and the member "cn=Test Test"') + ->and('modifying group with the mail address "testgroup@example.org" to contain no members.') + ->and('fetching the groups "group@example.org"') + ->and('listing the members of this group') + ->then('the list is empty'); + } + + /** + * @scenario + */ + public function deletingUserRemovesUserFromAllDistributionLists() + { + $this->given('an empty Kolab server') + ->when('adding a user "cn=Test Test" with the mail address "test@example.org"') + ->and('adding a group with the mail address "testgroup@example.org" and the member "cn=Test Test"') + ->and('adding a group with the mail address "testgroup2@example.org" and the member "cn=Test Test"') + ->and('deleting user "cn=Test Test"') + ->and('listing the members of group "testgroup@example.org"') + ->and('listing the members of group "testgroup2@example.org"') + ->then('the list of group "testgroup@example.org" is empty') + ->and('the list of group "testgroup2@example.org" is empty'); + } + + /** + * @scenario + */ + public function modifyingUserIDDoesNotChangeGroupMembership() + { + $this->given('an empty Kolab server') + ->when('adding a user "cn=Test Test" with the mail address "test@example.org"') + ->and('adding a group with the mail address "testgroup@example.org" and the member "cn=Test Test"') + ->and('modifying user "cn=Test Test" to ID "cn=Test2 Test"') + ->and('listing the members of group "testgroup@example.org"') + ->then('the list of group "testgroup@example.org" contains "cn=Test2 Test"'); + } + + /** + * @scenario + */ + public function addingGroupInUndefinedDomainIsNotAllowed() + { + $this->given('an empty Kolab server') + ->and('the only served mail domain is "example.org"') + ->when('adding a group with the mail address "test@doesnotexist.org"') + ->then('the result should indicate an error with', 'The group cannot be added: Domain "doesnotexist.org" is not being handled by this server!'); + } + + /** + * kolab/issue1368 (Webinterface allows to create email addresses with slash that cyrus cannot handle) + * + * @scenario + * @dataProvider invalidMails + */ + public function disallowInvalidMailAddresses($address) + { + $this->given('an empty Kolab server') + ->when('adding a group with an invalid mail address', $address) + ->then('the result should indicate an error with', "The group cannot be added: Address \"$address\" is not a valid mail address!"); + } + + /** + * @scenario + */ + public function objectAttributeDescriptionsCanBeRetrieved() + { + $this->given('an empty Kolab server') + ->when('retrieving the supported attributes by the object type "group"') + ->then('the result is an array of Horde attribute descriptions') + ->and('contains the description of "members"'); + } + + /** + * @scenario + */ + public function removingGroupFailsIfGroupDoesNotExist() + { + $this->given('an empty Kolab server') + ->when('adding a group with the mail address "group@example.org"') + ->and('deleting the group with the mail address "group@example.org"') + ->then('the result should indicate an error with', 'The group cannot be deleted: Group "group@example.org" does not exist!'); + } + + /** + * @scenario + */ + public function removingGroupByMailSucceeds() + { + $this->given('an empty Kolab server') + ->when('adding a group with the mail address "test@example.org"') + ->and('deleting the group with mail address "test@example.org"') + ->then('the result indicates success') + ->and('listing all groups returns an empty list'); + } + + /** + * kolab/issue1189 (IMAP login fails on some specific uids) + * + * @scenario + */ + public function userUidsShouldNotResembleTheLocalPartOfMailAddresses() + { + $this->given('an empty Kolab server') + ->when('adding a group with the mail address "test@example.org"') + ->and('adding a user with the uid "test"') + ->then('the result should indicate an error with', 'The user cannot be added: The uid "test" matches the local part of the mail address "test@example.org" assigned to group "test@example.org"!'); + } + + /** + * kolab/issue2207 (Make it possible to enable and disable users to be able to use the webclient.) + * + * @scenario + */ + public function addedUserCanLoginIfInAllowedGroup() + { + $this->given('an empty Kolab server') + ->and('Horde uses the Kolab auth driver') + ->and('only members of group "testgroup@example.org" are allowed') + ->when('adding a user "cn=Test Test" with the mail address "test@example.org" and password "test"') + ->and('adding a group with the mail address "testgroup@example.org" and the member "cn=Test Test"') + ->and('trying to login to Horde with "test@example.org" and passowrd "test"') + ->then('the result indicates success') + ->and('the session shows "test@example.org" as the current user'); + } + + /** + * kolab/issue2207 (Make it possible to enable and disable users to be able to use the webclient.) + * + * @scenario + */ + public function addedUserCannotLoginIfInNotInAllowedGroup() + { + $this->given('an empty Kolab server') + ->and('Horde uses the Kolab auth driver') + ->and('only members of group "testgroup@example.org" are allowed') + ->when('adding a user "cn=Test Test" with the mail address "test@example.org" and password "test"') + ->and('adding a group with the mail address "testgroup@example.org" and no members') + ->and('trying to login to Horde with "test@example.org" and passowrd "test"') + ->then('the user may not login'); + } + + /** + * kolab/issue2207 (Make it possible to enable and disable users to be able to use the webclient.) + * + * @scenario + */ + public function addedUserCanLoginIfInNotInDisallowedGroup() + { + $this->given('an empty Kolab server') + ->and('Horde uses the Kolab auth driver') + ->and('members of group "testgroup@example.org" may not login') + ->when('adding a user "cn=Test Test" with the mail address "test@example.org" and password "test"') + ->and('adding a group with the mail address "testgroup@example.org" and no members') + ->and('trying to login to Horde with "test@example.org" and passowrd "test"') + ->then('the result indicates success') + ->and('the session shows "test@example.org" as the current user'); + } + + /** + * kolab/issue2207 (Make it possible to enable and disable users to be able to use the webclient.) + * + * @scenario + */ + public function addedUserCannotLoginIfInDisallowedGroup() + { + $this->given('an empty Kolab server') + ->and('Horde uses the Kolab auth driver') + ->and('members of group "testgroup@example.org" may not login') + ->when('adding a user "cn=Test Test" with the mail address "test@example.org" and password "test"') + ->and('adding a group with the mail address "testgroup@example.org" and the member "cn=Test Test"') + ->and('trying to login to Horde with "test@example.org" and passowrd "test"') + ->then('the user may not login'); + } + +} diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/GroupTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/GroupTest.php new file mode 100644 index 000000000..b62c6ac82 --- /dev/null +++ b/framework/Kolab_Server/test/Horde/Kolab/Server/GroupTest.php @@ -0,0 +1,137 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * We need the base class + */ +require_once 'Horde/Kolab/Test/Server.php'; + +/** + * Test the group object. + * + * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/GroupTest.php,v 1.3 2009/01/06 17:49:27 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_GroupTest extends Horde_Kolab_Test_Server { + + /** + * Set up testing. + * + * @return NULL + */ + protected function setUp() + { + $this->ldap = $this->prepareEmptyKolabServer(); + } + + /** + * Add a group object. + * + * @return NULL + */ + private function _addValidGroups() + { + $groups = $this->validGroups(); + foreach ($groups as $group) { + $result = $this->ldap->add($group[0]); + $this->assertNoError($result); + } + } + + /** + * Test ID generation for a group. + * + * @return NULL + */ + public function testGenerateId() + { + $groups = $this->validGroups(); + $this->assertEquals('cn=empty.group@example.org,dc=example,dc=org', + $this->ldap->generateUid(KOLAB_OBJECT_GROUP, $groups[0][0])); + } + + /** + * Test fetching a group. + * + * @return NULL + */ + public function testFetchGroup() + { + $this->_addValidGroups(); + + $group = $this->ldap->fetch('cn=empty.group@example.org,dc=example,dc=org'); + $this->assertNoError($group); + $this->assertEquals('Horde_Kolab_Server_Object_group', get_class($group)); + } + + /** + * Test fetching a group. + * + * @return NULL + */ + public function testToHash() + { + $this->_addValidGroups(); + + $group = $this->ldap->fetch('cn=empty.group@example.org,dc=example,dc=org'); + $this->assertNoError($group); + + $hash = $group->toHash(); + $this->assertNoError($hash); + $this->assertContains('mail', array_keys($hash)); + $this->assertContains('id', array_keys($hash)); + $this->assertContains('visible', array_keys($hash)); + $this->assertEquals('empty.group@example.org', $hash['mail']); + $this->assertEquals('empty.group@example.org', $hash['id']); + $this->assertTrue($hash['visible']); + } + + /** + * Test listing groups. + * + * @return NULL + */ + public function testListingGroups() + { + $this->assertEquals(0, count($GLOBALS['KOLAB_SERVER_TEST_DATA'])); + $this->assertEquals(0, + count($this->ldap->_search('(&(!(cn=domains))(objectClass=kolabGroupOfNames))', + array(), + $this->ldap->_base_dn))); + + $this->_addValidGroups(); + + $this->assertEquals(3, count($GLOBALS['KOLAB_SERVER_TEST_DATA'])); + $this->assertEquals(3, + count($this->ldap->_search('(&(!(cn=domains))(objectClass=kolabGroupOfNames))', + array(), + $this->ldap->_base_dn))); + + $list = $this->ldap->listObjects(KOLAB_OBJECT_GROUP); + $this->assertNoError($list); + $this->assertEquals(3, count($list)); + } + +} diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/ObjectTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/ObjectTest.php new file mode 100644 index 000000000..e35a65d4f --- /dev/null +++ b/framework/Kolab_Server/test/Horde/Kolab/Server/ObjectTest.php @@ -0,0 +1,167 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * We need the base class + */ +require_once 'Horde/Kolab/Test/Server.php'; + +require_once 'PEAR.php'; +require_once 'Horde/Kolab/Server/Object.php'; + +/** + * The the handling of objects. + * + * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/ObjectTest.php,v 1.7 2009/01/06 17:49:27 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_ObjectTest extends Horde_Kolab_Test_Server { + + /** + * Set up a dummy db object that will not be used during the + * tests. We just need it so that PHP does not complain about the + * inability to refernce the storage class. + * + * @return NULL + */ + protected function setUp() + { + $this->_dummydb = &new DummyDB(); + } + + /** + * Test construction of the class. + * + * @return NULL + */ + public function testConstruct() + { + $ko = &new Horde_Kolab_Server_Object($this->_dummydb); + /** Not enough information provided */ + $this->assertEquals('Specify either the UID or a search result!', + $ko->_cache->message); + $attr = $ko->get(KOLAB_ATTR_CN); + /** The base object supports nothing */ + $this->assertError($attr, 'Attribute "cn" not supported!'); + $ko2 = &new Horde_Kolab_Server_Object($this->_dummydb); + $ko = &new Horde_Kolab_Server_Object($ko2); + $this->assertNoError($ko); + $this->assertNoError($ko2); + /** Ensure that referencing works */ + $this->assertSame($ko->_db, $ko2); + } + + /** + * Provide test data for the ConstructDn test. + * + * @return array The test object data. + */ + public static function provideConstructDn() + { + return array( + array('test', null, 'test'), + array(false, array('dn' => 'test'), 'test'), + array(false, array('dn' => array('test')), 'test'), + array('test2', array('dn' => array('test')), 'test2'), + ); + } + + /** + * Check a few DN values when constructing the object. + * + * @param string $dn The uid for the object. + * @param string $data Object data. + * @param string $expect Expect this uid. + * + * @dataProvider provideConstructDn + * + * @return NULL + */ + public function testConstructDn($dn, $data, $expect) + { + $ko = &new Horde_Kolab_Server_Object($this->_dummydb, $dn, $data); + $ndn = $ko->get(KOLAB_ATTR_UID); + $this->assertNoError($ndn); + $this->assertEquals($expect, $ndn); + } + + /** + * Provide test data for the GetFn test. + * + * @return array The test object data. + */ + public static function provideGetFn() + { + return array( + array( + array( + 'dn' => 'test', + 'cn' => 'Frank Mustermann', + 'sn' => 'Mustermann'), + 'Frank')); + } + + /** + * Check the generating of the "First Name" attribute. + * + * @param string $data Object data. + * @param string $expect Expect this full name. + * + * @dataProvider provideGetFn + * + * @return NULL + */ + public function testGetFn($data, $expect) + { + $ko = &Horde_Kolab_Server_Object::factory(KOLAB_OBJECT_USER, + null, $this->_dummydb, $data); + $this->assertNoError($ko); + $ndn = $ko->get(KOLAB_ATTR_FN); + $this->assertNoError($ndn); + $this->assertEquals($expect, $ndn); + } + +} + +/** + * A dummy class for testing. + * + * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/ObjectTest.php,v 1.7 2009/01/06 17:49:27 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class DummyDB { + + +} diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/ServerTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/ServerTest.php new file mode 100644 index 000000000..9959a9078 --- /dev/null +++ b/framework/Kolab_Server/test/Horde/Kolab/Server/ServerTest.php @@ -0,0 +1,72 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * We need the unit test framework + */ +require_once 'PHPUnit/Framework.php'; + +require_once 'Horde/Kolab/Server.php'; + +/** + * Tests for the main server class. + * + * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/ServerTest.php,v 1.6 2009/01/06 17:49:27 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_ServerTest extends PHPUnit_Framework_TestCase { + /** + * The base class provides no abilities for reading data. So it + * should mainly return error. But it should be capable of + * returning a dummy Kolab user object. + * + * @return NULL + */ + public function testFetch() + { + $ks = &Horde_Kolab_Server::factory('none'); + $user = $ks->fetch('test'); + $this->assertEquals(KOLAB_OBJECT_USER, get_class($user)); + $cn = $user->get(KOLAB_ATTR_CN); + $this->assertEquals('Not implemented!', $cn->message); + } + + /** + * The base class returns no valid data. The DN request should + * just return false and the search for a mail address returns the + * provided argument. + * + * @return NULL + */ + public function testDnFor() + { + $ks = &Horde_Kolab_Server::factory('none'); + $dn = $ks->uidForIdOrMail('test'); + $this->assertEquals(false, $dn); + $dn = $ks->uidForMailAddress('test'); + $this->assertEquals('test', $dn); + } + +} diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/SessionTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/SessionTest.php new file mode 100644 index 000000000..16fbdecac --- /dev/null +++ b/framework/Kolab_Server/test/Horde/Kolab/Server/SessionTest.php @@ -0,0 +1,209 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * We need the base class + */ +require_once 'Horde/Kolab/Test/Server.php'; + +require_once 'Horde/Kolab/Session.php'; + +/** + * Test the Kolab session handler. + * + * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/SessionTest.php,v 1.10 2009/01/14 21:46:54 wrobel Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_SessionTest extends Horde_Kolab_Test_Server { + + /** + * Test class construction. + * + * @return NULL + */ + public function testConstructEmpty() + { + global $conf; + $conf['kolab']['imap']['allow_special_users'] = true; + + $session = &Horde_Kolab_Session::singleton(); + + $this->assertEquals('anonymous', $session->user_mail); + + $params = $session->getImapParams(); + $this->assertNoError($params); + $this->assertEquals('localhost', $params['hostspec']); + $this->assertEquals(143, $params['port']); + } + + /** + * Test old style class construction. + * + * @return NULL + */ + public function testConstructSimple() + { + global $conf; + $conf['kolab']['imap']['server'] = 'example.com'; + $conf['kolab']['imap']['port'] = 200; + $conf['kolab']['freebusy']['server'] = 'fb.example.com'; + + $session = &new Horde_Kolab_Session(); + $params = $session->getImapParams(); + if (is_a($params, 'PEAR_Error')) { + $this->assertEquals('', $params->getMessage()); + } + $this->assertEquals('example.com', $params['hostspec']); + $this->assertEquals(200, $params['port']); + } + + /** + * Test IMAP server retrieval. + * + * @return NULL + */ + public function testGetServer() + { + $server = &$this->prepareEmptyKolabServer(); + $result = $server->add($this->provideBasicUserTwo()); + $this->assertNoError($result); + $this->assertEquals(1, count($GLOBALS['KOLAB_SERVER_TEST_DATA'])); + + $session = &Horde_Kolab_Session::singleton('test', + array('password' => 'test')); + + $this->assertNoError($session->auth); + $this->assertEquals('test@example.org', $session->user_mail); + + $params = $session->getImapParams(); + $this->assertNoError($params); + $this->assertEquals('home.example.org', $params['hostspec']); + $this->assertEquals(143, $params['port']); + $this->assertEquals('test@example.org', $session->user_mail); + + $session->shutdown(); + + $hs = &Horde_SessionObjects::singleton(); + + $recovered_session = &$hs->query('kolab_session'); + $params = $recovered_session->getImapParams(); + $this->assertNoError($params); + $this->assertEquals('home.example.org', $params['hostspec']); + $this->assertEquals(143, $params['port']); + $this->assertEquals('test@example.org', $session->user_mail); + + $this->assertEquals('https://fb.example.org/freebusy', $session->freebusy_server); + } + + /** + * Test retrieving the FreeBusy server for the unauthenticated state. + * + * @return NULL + */ + public function testGetFreeBusyServer() + { + $server = &$this->prepareEmptyKolabServer(); + $result = $server->add($this->provideBasicUserTwo()); + $this->assertNoError($result); + $session = &Horde_Kolab_Session::singleton(); + $this->assertEquals('', $session->freebusy_server); + } + + /** + * Test group based login allow implemention. + * + * @return NULL + */ + public function testLoginAllow() + { + global $conf; + $conf['kolab']['server']['allow_group'] = 'group2@example.org'; + $conf['kolab']['server']['deny_group'] = null; + + $server = &$this->prepareEmptyKolabServer(); + $result = $server->add($this->provideBasicUserOne()); + $this->assertNoError($result); + $result = $server->add($this->provideBasicUserTwo()); + $this->assertNoError($result); + $groups = $this->validGroups(); + foreach ($groups as $group) { + $result = $server->add($group[0]); + $this->assertNoError($result); + } + + $session = &Horde_Kolab_Session::singleton('wrobel', + array('password' => 'none'), + true); + + $this->assertNoError($session->auth); + $this->assertEquals('wrobel@example.org', $session->user_mail); + + $session = &Horde_Kolab_Session::singleton('test', + array('password' => 'test'), + true); + + $this->assertError($session->auth, 'You are no member of a group that may login on this server.'); + $this->assertTrue(empty($session->user_mail)); + } + + /** + * Test group based login deny implemention. + * + * @return NULL + */ + public function testLoginDeny() + { + global $conf; + $conf['kolab']['server']['deny_group'] = 'group2@example.org'; + unset($conf['kolab']['server']['allow_group']); + + $server = &$this->prepareEmptyKolabServer(); + $result = $server->add($this->provideBasicUserOne()); + $this->assertNoError($result); + $result = $server->add($this->provideBasicUserTwo()); + $this->assertNoError($result); + $groups = $this->validGroups(); + foreach ($groups as $group) { + $result = $server->add($group[0]); + $this->assertNoError($result); + } + + $session = &Horde_Kolab_Session::singleton('test', + array('password' => 'test'), + true); + + $this->assertNoError($session->auth); + $this->assertEquals('test@example.org', $session->user_mail); + + $session = &Horde_Kolab_Session::singleton('wrobel', + array('password' => 'none'), + true); + + $this->assertError($session->auth, 'You are member of a group that may not login on this server.'); + $this->assertTrue(empty($session->user_mail)); + + } + +} diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/UserHandlingTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/UserHandlingTest.php new file mode 100644 index 000000000..76c2cef8c --- /dev/null +++ b/framework/Kolab_Server/test/Horde/Kolab/Server/UserHandlingTest.php @@ -0,0 +1,681 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * We need the base class + */ +require_once 'Horde/Kolab/Test/Server.php'; + +/** + * Handling users. + * + * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/UserHandlingTest.php,v 1.3 2009/01/06 17:49:27 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_UserHandlingTest extends Horde_Kolab_Test_Server { + + /** + * Test listing userss if there are no users. + * + * @scenario + * + * @return NULL + */ + public function listingUsersOnEmptyServer() + { + $this->given('an empty Kolab server') + ->when('listing all users') + ->then('the list is an empty array'); + } + + /** + * Test listing users after adding some users. + * + * @param array $user_list The users to add. + * + * @scenario + * @dataProvider userLists + * + * @return NULL + */ + public function listingUsersAfterAddingUsers($user_list) + { + $this->given('an empty Kolab server') + ->when('adding an object list', $user_list) + ->and('listing all users') + ->then('the list has a number of entries equal to', count($user_list)); + } + + /** + * Test listing users after adding some users. + * + * @param array $user_list The users to add. + * + * @scenario + * @dataProvider userLists + * + * @return NULL + */ + public function listingUserCount($user_list) + { + $this->given('an empty Kolab server') + ->when('adding an object list', $user_list) + ->and('retriving the result count') + ->then('the count equals to', count($user_list)); + } + + /** + * Test the list of users for the user id. + * + * @param array $user_list The users to add. + * + * @scenario + * @dataProvider userLists + * + * @return NULL + */ + public function listingUsersHasAttributeId($user_list) + { + $this->given('an empty Kolab server') + ->when('adding a user list', $user_list) + ->then('the user list contains the unique ID for each user'); + } + + /** + * Test the list of users for the user type. + * + * @param array $user_list The users to add. + * + * @scenario + * @dataProvider userLists + * + * @return NULL + */ + public function listingUsersHasAttributeType($user_list) + { + $this->given('an empty Kolab server') + ->when('adding a user list', $user_list) + ->then('the user list contains the user type for each user'); + } + + /** + * Test the list of users for the user full name. + * + * @param array $user_list The users to add. + * + * @scenario + * @dataProvider userLists + * + * @return NULL + */ + public function listingUsersHasAttributeFullName($user_list) + { + $this->given('an empty Kolab server') + ->when('adding a user list', $user_list) + ->then('the user list contains the full name for each user'); + } + + /** + * Test the list of users for the user mail. + * + * @param array $user_list The users to add. + * + * @scenario + * @dataProvider userLists + * + * @return NULL + */ + public function listingUsersHasAttributeEmail($user_list) + { + $this->given('an empty Kolab server') + ->when('adding a user list', $user_list) + ->then('the user list contains the email for each user'); + } + + /** + * Test the list of users for the user uid. + * + * @param array $user_list The users to add. + * + * @scenario + * @dataProvider userLists + * + * @return NULL + */ + public function listingUsersHasAttributeUid($user_list) + { + $this->given('an empty Kolab server') + ->when('adding a user list', $user_list) + ->then('the list contains the uid for each user'); + } + + /** + * @scenario + * @dataProvider userListByLetter + */ + public function listingUsersCanBeRestrictedByStartLetterOfTheLastName($letter, $count) + { + $this->given('an empty Kolab server') + ->when('adding user list', $this->largeList()) + ->and('retrieving the result count of a list restricted by the start letter of the last name', $letter) + ->then('the list contains a correct amount of results', $count); + } + + /** + * @scenario + * @dataProvider userListByLetter + */ + public function countingUsersCanBeRestrictedByStartLetterOfTheLastName($letter, $count) + { + $this->given('an empty Kolab server') + ->when('adding user list', $this->largeList()) + ->and('retrieving the result count of a list restricted by the start letter of the last name', $letter) + ->then('the count contains a correct number', $count); + } + + /** + * @scenario + * @dataProvider userListByAttribute + */ + public function countingUsersCanBeRestrictedByContentsInAnAttribute($attribute, $content, $count) + { + $this->given('an empty Kolab server') + ->when('adding user list', $this->largeList()) + ->and('retrieving the result count of a list restricted by content in an attribute', $attribute, $content) + ->then('the count contains a correct number', $count); + } + + /** + * @scenario + */ + public function creatingUserWithoutGivenName() + { + $this->given('an empty Kolab server') + ->when('adding a user without first name') + ->then('the result should indicate an error with', 'Adding object failed: Either the last name or the given name is missing!'); + } + + /** + * @scenario + */ + public function creatingUserWithoutLastName() + { + $this->given('an empty Kolab server') + ->when('adding a user without last name') + ->then('the result should indicate an error with', 'Adding object failed: Either the last name or the given name is missing!'); + } + + /** + * @scenario + */ + public function creatingUserWithoutPassword() + { + $this->given('an empty Kolab server') + ->when('adding a user without password') + ->then('the result should indicate an error with', 'Adding object failed: The value for "userPassword" is missing!'); + } + + /** + * @scenario + */ + public function creatingUserWithoutPrimaryMail() + { + $this->given('an empty Kolab server') + ->when('adding a user without primary mail') + ->then('the result should indicate an error with', 'Adding object failed: The value for "mail" is missing!'); + } + + /** + * @scenario + */ + public function creatingUserWithoutTypeCreatesStandardUser() + { + $this->given('an empty Kolab server') + ->when('adding a user without user type') + ->then('a standard user has been created'); + } + + /** + * @scenario + */ + public function creatingUserWithoutInvitationPolicySetsManualPolicy() + { + $this->given('an empty Kolab server') + ->when('adding a user without an invitation policy') + ->then('the added user has a manual policy'); + } + + /** + * @scenario + */ + public function creatingUserWithoutHomeServerFails() + { + $this->given('an empty Kolab server') + ->when('adding a user without a home server') + ->then('the result should indicate an error with', 'The user cannot be added: The home Kolab server (or network) has not been specified!'); + } + + /** + * @scenario + */ + public function creatingUserForDistributedKolabWithoutImapServerFails() + { + $this->given('an empty Kolab server') + ->and('distributed Kolab') + ->when('adding a user without an imap server') + ->then('the result should indicate an error with', 'The user cannot be added: The home imap server has not been specified!'); + } + + /** + * @scenario + */ + public function creatingUserWithImapServerFailsOnNonDistributedKolab() + { + $this->given('an empty Kolab server') + ->and('monolithic Kolab') + ->when('adding a user with an imap server') + ->then('the result should indicate an error with', 'The user cannot be added: A home imap server is only supported with a distributed Kolab setup!'); + } + + /** + * @scenario + */ + public function creatingUserWithFreeBusyServerFailsOnNonDistributedKolab() + { + $this->given('an empty Kolab server') + ->and('monolithic Kolab') + ->when('adding a user with a free/busy server') + ->then('the result should indicate an error with', 'The user cannot be added: A seperate free/busy server is only supported with a distributed Kolab setup!'); + } + + /** + * @scenario + */ + public function modifyingUserMailAddressIsNotAllowed() + { + $this->given('an empty Kolab server') + ->when('adding a user with the mail address "test@example.org"') + ->and('modifying the mail address to "new@example.org"') + ->then('the result should indicate an error with', 'The user cannot be modified: Changing the mail address from "test@example.org" to "new@example.org" is not allowed!'); + } + + /** + * @scenario + */ + public function modifyingUserHomeServerIsNotAllowd() + { + $this->given('an empty Kolab server') + ->when('adding a user with the home server "test.example.org"') + ->and('modifying the home server to "new.example.org"') + ->then('the result should indicate an error with', 'The user cannot be modified: Changing the home server from "test.example.org" to "new.example.org" is not allowed!'); + } + + /** + * @scenario + */ + public function modifyingUserImapServerIsNotAllowd() + { + $this->given('an empty Kolab server') + ->and('distributed Kolab') + ->when('adding a user with the imap server "test.example.org"') + ->and('modifying the imap server to "new.example.org"') + ->then('the result should indicate an error with', 'The user cannot be modified: Changing the imap server from "test.example.org" to "new.example.org" is not allowed!'); + } + + /** + * @scenario + */ + public function conflictBetweenMailAndMailIsNotAllowed() + { + $this->given('an empty Kolab server') + ->when('adding a user "Test Test" with the mail address "test@example.org"') + ->and('adding a user "Test2 Test2" with the mail address "test@example.org"') + ->then('the result should indicate an error with', 'The user cannot be added: Mail address "test@example.org" is already the mail address of user "Test Test"!'); + } + + /** + * @scenario + */ + public function conflictBetweenMailAndAliasIsNotAllowed() + { + $this->given('an empty Kolab server') + ->when('adding a user "Test Test" with the mail address "test@example.org"') + ->and('adding a user with the alias address "test@example.org"') + ->then('the result should indicate an error with', 'The user cannot be added: Alias address "test@example.org" is already the mail address of user "Test Test"!'); + } + + /** + * @scenario + */ + public function conflictBetweenAliasAndAliasIsNotAllowed() + { + $this->given('an empty Kolab server') + ->when('adding a user "Test Test" with the alias address "test@example.org"') + ->and('adding a user with the alias address "test@example.org"') + ->then('the result should indicate an error with', 'The user cannot be added: Alias address "test@example.org" is already the alias address of user "Test Test"!'); + } + + /** + * @scenario + */ + public function conflictBetweenMailAndUidIsNotAllowed() + { + $this->given('an empty Kolab server') + ->when('adding a user "Test Test" with the mail address "test@example.org"') + ->and('adding a user with the uid "test@example.org"') + ->then('the result should indicate an error with', 'The user cannot be added: Uid "test@example.org" is already the mail address of user "Test Test"!'); + } + + /** + * @scenario + */ + public function conflictBetweenUidAndUidIsNotAllowed() + { + $this->given('an empty Kolab server') + ->when('adding a user "Test Test" with the uid "test"') + ->and('adding a user with the uid "test"') + ->then('the result should indicate an error with', 'The user cannot be added: Uid "test" is already the uid of user "Test Test"!'); + } + + /** + * @scenario + */ + public function nonExistingDelegateIsNotAllowed() + { + $this->given('an empty Kolab server') + ->when('adding a user with the delegate address "test@example.org"') + ->then('the result should indicate an error with', 'The user cannot be added: Delegate address "test@example.org" does not exist!'); + } + + /** + * @scenario + */ + public function addingUserInUndefinedDomainIsNotAllowed() + { + $this->given('an empty Kolab server') + ->and('the only served mail domain is "example.org"') + ->when('adding a user with the mail address "test@doesnotexist.org"') + ->then('the result should indicate an error with', 'The user cannot be added: Domain "doesnotexist.org" is not being handled by this server!'); + } + + /** + * kolab/issue444 (a kolab user may delegate to an external user which should not be possible) + * + * @scenario + */ + public function addingUserWithDelegateInUndefinedDomainIsNotAllowed() + { + $this->given('an empty Kolab server') + ->and('the only served mail domain is "example.org"') + ->when('adding a user with the delegate mail address "test@doesnotexist.org"') + ->then('the result should indicate an error with', 'The user cannot be added: Domain "doesnotexist.org" is not being handled by this server!'); + } + + /** + * kolab/issue1368 (Webinterface allows to create email addresses with slash that cyrus cannot handle) + * + * @scenario + * @dataProvider invalidMails + */ + public function disallowInvalidMailAddresses($address) + { + $this->given('an empty Kolab server') + ->when('adding a user with an invalid mail address', $address) + ->then('the result should indicate an error with', "The user cannot be added: Address \"$address\" is not a valid mail address!"); + } + + /** + * @scenario + */ + public function addingUserOnUndefinedHomeServer() + { + $this->given('an empty Kolab server') + ->and('the only home server in the network is "example.org"') + ->when('adding a user with the home server "doesnotexist.org"') + ->then('the result should indicate an error with', 'The user cannot be added: Host "doesnotexist.org" is not part of the Kolab network!'); + } + + /** + * @scenario + */ + public function addingUserOnUndefinedImapServer() + { + $this->given('an empty Kolab server') + ->and('distributed Kolab') + ->and('the only imap server in the network is "example.org"') + ->when('adding a user with the imap server "doesnotexist.org"') + ->then('the result should indicate an error with', 'The user cannot be added: Imap server "doesnotexist.org" is not part of the Kolab network!'); + } + + /** + * @scenario + */ + public function userAttributesCanBeExtended() + { + $this->given('an empty Kolab server') + ->and('an extended attribute "test" has been defined') + ->when('adding a user with the attribute "test" set to "FIND ME"') + ->then('the result indicates success') + ->and('the user can be found using the "test" attribute with the value "FIND ME"'); + } + + /** + * @scenario + */ + public function extendedObjectAttributeDescriptionsCanBeRetrieved() + { + $this->given('an empty Kolab server') + ->and('an extended attribute "test" has been defined') + ->when('retrieving the supported attributes by the object type "user"') + ->then('the result is an array of Horde attribute descriptions') + ->and('contains the description of "test"'); + } + + /** + * @scenario + */ + public function removingUserFailsIfUserDoesNotExist() + { + $this->given('an empty Kolab server') + ->when('adding a user with the ID "cn=Test Test"') + ->and('deleting the user with the ID "cn=Dummy Dummy"') + ->then('the result should indicate an error with', 'The user cannot be deleted: User "cn=Dummy Dummy" does not exist!'); + } + + /** + * @scenario + */ + public function removingUserByMailSucceeds() + { + $this->given('an empty Kolab server') + ->when('adding a user with the mail address "test@example.org"') + ->and('deleting the user with mail address "test@example.org"') + ->then('the result indicates success') + ->and('listing all users returns an empty list'); + } + + /** + * @scenario + */ + public function removingUserByIdSucceeds() + { + $this->given('an empty Kolab server') + ->when('adding a user with the ID "cn=Test Test"') + ->and('deleting the user with the ID "cn=Test Test"') + ->then('the result indicates success') + ->and('listing all users returns an empty list'); + } + + /** + * @scenario + */ + public function addedUserCanLogin() + { + $this->given('an empty Kolab server') + ->and('Horde uses the Kolab auth driver') + ->when('adding a user with the mail address "test@example.org" and password "test"') + ->and('trying to login to Horde with "test@example.org" and passowrd "test"') + ->then('the result indicates success') + ->and('the session shows "test@example.org" as the current user'); + } + + /** + * @scenario + */ + public function allowUserWithExtendedObjectClasses() + { + $this->given('an empty Kolab server') + ->and('an extended set of objectclasses') + ->when('adding a user with the mail address "test@example.org"') + ->and('fetching user "test@example.org"') + ->then('has the additional object classes set'); + } + + /** + * @scenario + */ + public function allowToCheckUserPasswords() + { + $this->given('an empty Kolab server') + ->and('password check enabled') + ->when('adding a user with the mail address "test@example.org" and password "tosimple"') + ->then('the result should indicate an error with', 'The user cannot be added: The chosen password is not complex enough!'); + } + + /** + * @scenario + */ + public function allowToSetAttributeDefaults() + { + $this->given('an empty Kolab server') + ->and('an extended attribute "test" with the default value "test" has been defined') + ->when('adding a user with the mail address "test@example.org" and an empty attribute "test"') + ->and('fetching user "test@example.org"') + ->then('the user object has the attribute "test" set to "test"'); + } + + /** + * kolab/issue2742 (Have a default quota value when creating new users via the web interface) + * + * @scenario + */ + public function allowToSetDomainSpecificAttributeDefaults() + { + $this->given('an empty Kolab server') + ->and('domain "example.org" is served by the Kolab server') + ->and('domain "example2.org" is served by the Kolab server') + ->and('an extended attribute "test" with the default value "test" has been defined') + ->and('an extended attribute "test" with the default value "test2" has been defined for domain example2.org') + ->when('adding a user with the mail address "test@example.org" and an empty attribute "test"') + ->and('adding a user with the mail address "test@example2.org" and an empty attribute "test"') + ->and('fetching user "test@example.org" and "test@example2.org"') + ->then('the user "test@example.org" has the attribute "test" set to "test"') + ->and('the user "test@example2.org" has the attribute "test" set to "test2"'); + } + + /** + * kolab/issue3035 (Initialise internal Horde parameters when creating a user) + * + * @scenario + * @dataProvider userAdd + */ + public function addedUserHasPreferencesInitialized() + { + $this->given('an empty Kolab server') + ->and('Horde uses the Kolab auth driver') + ->when('adding a user', $user) + ->and('trying to login to Horde with "test@example.org" and passowrd "test"') + ->then('the preferences are automatically set to the user information', $user); + } + + /** + * kolab/issue1189 (IMAP login fails on some specific uids) + * + * @scenario + */ + public function userUidsShouldNotResembleTheLocalPartOfMailAddresses() + { + $this->given('an empty Kolab server') + ->when('adding a user "cn=Test Test" with the mail address "test@example.org"') + ->and('adding a user with the uid "test"') + ->then('the result should indicate an error with', 'The user cannot be added: The uid "test" matches the local part of the mail address "test@example.org" assigned to user "cn=Test Test"!'); + } + + /** + * kolab/issue606 (It is not possible to register people with middlename correctly) + * + * @scenario + */ + public function allowToSetTheMiddleName() + { + $this->given('an empty Kolab server') + ->and('an extended attribute "middleName" has been defined') + ->when('adding a user with the mail address "test@example.org" and the middle name "Middle"') + ->and('fetching user "test@example.org"') + ->then('the user object has the attribute "middleName" set to "Middle"'); + } + + /** + * kolab/issue1880 (Poor handling of apostrophes in ldap and admin webpages) + * + * @scenario + */ + public function correctlyEscapeApostrophesInNames() + { + $this->given('an empty Kolab server') + ->when('adding a user with the mail address "test@example.org" and the last name "O\'Donnell"') + ->and('fetching user "test@example.org"') + ->then('the user name has the attribute "sn" set to "O\'Donnell"'); + } + + /** + * kolab/issue1677 (Allow a user to use an external address as sender) + * + * @scenario + */ + public function allowUserToUseExternalAddressAsSender() + { + $this->given('an empty Kolab server') + ->when('adding a user with the mail address "test@example.org" and the external address "other@doesnotexist.org"') + ->and('fetching user "test@example.org"') + ->then('the user has the attribute external address "other@doesnotexist.org"'); + } + + /** + * kolab/issue3036 (cn = "givenName sn" ?) + * + * @scenario + */ + public function allowCustomFullnameHandling() + { + $this->given('an empty Kolab server') + ->and('an extended attribute "middleName" has been defined') + ->and('custom full name handling has been set to "lastname, firstname middlename"') + ->when('adding a user with the mail address "test@example.org", the last name "Test", the first name "Test", and the middle name "Middle"') + ->and('fetching user "test@example.org"') + ->then('the user has the attribute full name "Test, Test Middle"'); + } + +} diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/UserTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/UserTest.php new file mode 100644 index 000000000..cd918239b --- /dev/null +++ b/framework/Kolab_Server/test/Horde/Kolab/Server/UserTest.php @@ -0,0 +1,125 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * We need the base class + */ +require_once 'Horde/Kolab/Test/Server.php'; + +/** + * Test the user object. + * + * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/UserTest.php,v 1.5 2009/01/06 17:49:27 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_UserTest extends Horde_Kolab_Test_Server { + + /** + * Set up testing. + * + * @return NULL + */ + protected function setUp() + { + $this->server = $this->prepareEmptyKolabServer(); + $users = $this->validUsers(); + foreach ($users as $user) { + $result = $this->server->add($user[0]); + $this->assertNoError($result); + } + } + + /** + * Test ID generation for a user. + * + * @return NULL + */ + public function testGenerateId() + { + $users = $this->validUsers(); + $this->assertEquals('Gunnar Wrobel', + Horde_Kolab_Server_Object_user::generateId($users[0][0])); + + $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', + $this->server->generateUid(KOLAB_OBJECT_USER, + $users[0][0])); + } + + /** + * Test adding invalid user. + * + * @return NULL + */ + public function testAddInvalidUser() + { + $user = $this->provideInvalidUserWithoutGivenName(); + + $result = $this->server->add($user); + + $this->assertError($result, + 'Adding object failed: Either the last name or the given name is missing!'); + } + + /** + * Test fetching a user. + * + * @return NULL + */ + public function testFetchUser() + { + $user = $this->server->fetch('cn=Gunnar Wrobel,dc=example,dc=org'); + $this->assertNoError($user); + $this->assertEquals('Horde_Kolab_Server_Object_user', get_class($user)); + } + + /** + * Test fetching server information. + * + * @return NULL + */ + public function testGetServer() + { + $user = $this->server->fetch('cn=Gunnar Wrobel,dc=example,dc=org'); + $this->assertNoError($user); + $imap = $user->getServer('imap'); + $this->assertEquals('imap.example.org', $imap); + + $user = $this->server->fetch('cn=Test Test,dc=example,dc=org'); + $imap = $user->getServer('imap'); + $this->assertEquals('home.example.org', $imap); + + $user = $this->server->fetch('cn=Gunnar Wrobel,dc=example,dc=org'); + $attr = $user->get(KOLAB_ATTR_FREEBUSYHOST); + if (is_a($attr, 'PEAR_Error')) { + $this->assertEquals('', $attr->getMessage()); + } + $this->assertEquals('https://fb.example.org/freebusy', $attr); + + $imap = $user->getServer('freebusy'); + $this->assertEquals('https://fb.example.org/freebusy', $imap); + + } + +} diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/ldapTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/ldapTest.php new file mode 100644 index 000000000..3aebcb879 --- /dev/null +++ b/framework/Kolab_Server/test/Horde/Kolab/Server/ldapTest.php @@ -0,0 +1,232 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * We need the unit test framework + */ +require_once 'PHPUnit/Framework.php'; + +require_once 'Horde/Kolab/Server.php'; +require_once 'Horde/Kolab/Server/ldap.php'; + +/** + * Test the LDAP backend. + * + * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/ldapTest.php,v 1.7 2009/01/06 17:49:27 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_ldapTest extends PHPUnit_Framework_TestCase { + + /** + * Test handling of object classes. + * + * @return NULL + */ + public function testGetObjectClasses() + { + $ldap = $this->getMock('Horde_Kolab_Server_ldap', array('_read')); + $ldap->expects($this->any()) + ->method('_read') + ->will($this->returnValue(array ( + 'objectClass' => + array ( + 'count' => 4, + 0 => 'top', + 1 => 'inetOrgPerson', + 2 => 'kolabInetOrgPerson', + 3 => 'hordePerson', + ), + 0 => 'objectClass', + 'count' => 1))); + + $classes = $ldap->_getObjectClasses('cn=Gunnar Wrobel,dc=example,dc=org'); + $this->assertContains('top', $classes); + $this->assertContains('kolabinetorgperson', $classes); + $this->assertContains('hordeperson', $classes); + + $ldap = $this->getMock('Horde_Kolab_Server_ldap', array('_read')); + $ldap->expects($this->any()) + ->method('_read') + ->will($this->returnValue(PEAR::raiseError('LDAP Error: No such object: cn=DOES NOT EXIST,dc=example,dc=org: No such object'))); + + $classes = $ldap->_getObjectClasses('cn=DOES NOT EXIST,dc=example,dc=org'); + $this->assertEquals('LDAP Error: No such object: cn=DOES NOT EXIST,dc=example,dc=org: No such object', + $classes->message); + } + + /** + * Test retrieving a primary mail for a mail or uid. + * + * @return NULL + */ + public function testMailForUidOrMail() + { + $ldap = $this->getMock('Horde_Kolab_Server_ldap', array('_getAttributes', + '_search', '_count', + '_firstEntry')); + $ldap->expects($this->any()) + ->method('_getAttributes') + ->will($this->returnValue(array ( + 'mail' => + array ( + 'count' => 1, + 0 => 'wrobel@example.org', + ), + 0 => 'mail', + 'count' => 1))); + $ldap->expects($this->any()) + ->method('_search') + ->will($this->returnValue('cn=Gunnar Wrobel,dc=example,dc=org')); + $ldap->expects($this->any()) + ->method('_count') + ->will($this->returnValue(1)); + $ldap->expects($this->any()) + ->method('_firstEntry') + ->will($this->returnValue(1)); + + $mail = $ldap->mailForIdOrMail('wrobel'); + $this->assertEquals('wrobel@example.org', $mail); + + $ldap = $this->getMock('Horde_Kolab_Server_ldap', array('_getAttributes', + '_search', + '_count', + '_firstEntry', + '_errno', + '_error')); + $ldap->expects($this->any()) + ->method('_getAttributes') + ->will($this->returnValue(false)); + $ldap->expects($this->any()) + ->method('_search') + ->will($this->returnValue('cn=Gunnar Wrobel,dc=example,dc=org')); + $ldap->expects($this->any()) + ->method('_count') + ->will($this->returnValue(1)); + $ldap->expects($this->any()) + ->method('_firstEntry') + ->will($this->returnValue(1)); + $ldap->expects($this->any()) + ->method('_errno') + ->will($this->returnValue(1)); + $ldap->expects($this->any()) + ->method('_error') + ->will($this->returnValue('cn=DOES NOT EXIST,dc=example,dc=org: No such object')); + + $mail = $ldap->mailForIdOrMail('wrobel'); + $this->assertEquals('Retrieving attributes failed. Error was: cn=DOES NOT EXIST,dc=example,dc=org: No such object', + $mail->message); + + $ldap = $this->getMock('Horde_Kolab_Server_ldap', array('_getAttributes', + '_search', + '_count')); + $ldap->expects($this->any()) + ->method('_getAttributes') + ->will($this->returnValue(false)); + $ldap->expects($this->any()) + ->method('_search') + ->will($this->returnValue('cn=Gunnar Wrobel,dc=example,dc=org')); + $ldap->expects($this->any()) + ->method('_count') + ->will($this->returnValue(4)); + + $mail = $ldap->mailForIdOrMail('wrobel'); + $this->assertEquals('Found 4 results when expecting only one!', + $mail->message); + } + + /** + * Test retrieving a DN for a mail or uid. + * + * @return NULL + */ + public function testDnForUidOrMail() + { + $ldap = $this->getMock('Horde_Kolab_Server_ldap', array('_getDn', + '_search', '_count', + '_firstEntry')); + $ldap->expects($this->any()) + ->method('_getDn') + ->will($this->returnValue('cn=Gunnar Wrobel,dc=example,dc=org')); + $ldap->expects($this->any()) + ->method('_search') + ->will($this->returnValue('cn=Gunnar Wrobel,dc=example,dc=org')); + $ldap->expects($this->any()) + ->method('_count') + ->will($this->returnValue(1)); + $ldap->expects($this->any()) + ->method('_firstEntry') + ->will($this->returnValue(1)); + + $dn = $ldap->uidForIdOrMail('wrobel'); + $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $dn); + + $ldap = $this->getMock('Horde_Kolab_Server_ldap', array('_getDn', + '_search', + '_count', + '_firstEntry', + '_errno', + '_error')); + $ldap->expects($this->any()) + ->method('_getDn') + ->will($this->returnValue(false)); + $ldap->expects($this->any()) + ->method('_search') + ->will($this->returnValue('cn=Gunnar Wrobel,dc=example,dc=org')); + $ldap->expects($this->any()) + ->method('_count') + ->will($this->returnValue(1)); + $ldap->expects($this->any()) + ->method('_firstEntry') + ->will($this->returnValue(1)); + $ldap->expects($this->any()) + ->method('_errno') + ->will($this->returnValue(1)); + $ldap->expects($this->any()) + ->method('_error') + ->will($this->returnValue('cn=DOES NOT EXIST,dc=example,dc=org: No such object')); + + $dn = $ldap->uidForIdOrMail('wrobel'); + $this->assertEquals('Retrieving DN failed. Error was: cn=DOES NOT EXIST,dc=example,dc=org: No such object', + $dn->message); + + $ldap = $this->getMock('Horde_Kolab_Server_ldap', array('_getDn', + '_search', + '_count')); + $ldap->expects($this->any()) + ->method('_getDn') + ->will($this->returnValue(false)); + $ldap->expects($this->any()) + ->method('_search') + ->will($this->returnValue('cn=Gunnar Wrobel,dc=example,dc=org')); + $ldap->expects($this->any()) + ->method('_count') + ->will($this->returnValue(4)); + + $dn = $ldap->uidForIdOrMail('wrobel'); + $this->assertEquals('Found 4 results when expecting only one!', + $dn->message); + } + +} diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/testTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/testTest.php new file mode 100644 index 000000000..992fe282b --- /dev/null +++ b/framework/Kolab_Server/test/Horde/Kolab/Server/testTest.php @@ -0,0 +1,604 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * We need the base class + */ +require_once 'Horde/Kolab/Test/Server.php'; + +/** + * Test the test backend. + * + * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/testTest.php,v 1.12 2009/01/06 17:49:27 jan Exp $ + * + * Copyright 2008-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Server + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ +class Horde_Kolab_Server_testTest extends Horde_Kolab_Test_Server { + + /** + * Set up testing. + * + * @return NULL + */ + protected function setUp() + { + $this->ldap = &$this->prepareBasicServer(); + } + + /** + * Test search base. + * + * @return NULL + */ + public function testSearchBase() + { + $result = $this->ldap->_search('(objectClass=top)', array('objectClass')); + $this->assertNoError($result); + $this->assertEquals(12, count($result)); + + $result = $this->ldap->_search('(objectClass=top)', array('objectClass'), + 'cn=internal,dc=example,dc=org'); + $this->assertNoError($result); + $this->assertEquals(3, count($result)); + } + + /** + * Test sorting. + * + * @return NULL + */ + public function testSorting() + { + $result = $this->ldap->_search('(mail=*)', array('mail')); + $this->assertNoError($result); + $this->assertEquals(5, count($result)); + $this->ldap->_sort($result, 'mail'); + $this->assertEquals('address@example.org', $result[0]['data']['mail'][0]); + $this->assertEquals('wrobel@example.org', + $result[count($result) - 1]['data']['mail'][0]); + } + + /** + * Test listing objects. + * + * @return NULL + */ + public function testListObjects() + { + $filter = '(&(objectClass=kolabInetOrgPerson)(uid=*)(mail=*)(sn=*))'; + $attributes = array( + KOLAB_ATTR_SN, + KOLAB_ATTR_CN, + KOLAB_ATTR_UID, + KOLAB_ATTR_MAIL, + KOLAB_ATTR_DELETED, + ); + + $sort = KOLAB_ATTR_SN; + $result = $this->ldap->_search($filter); + $this->assertNoError($result); + $this->assertEquals(2, count($result)); + + $result = $this->ldap->listObjects(KOLAB_OBJECT_USER); + $this->assertNoError($result); + $this->assertEquals(2, count($result)); + $this->assertEquals(KOLAB_OBJECT_USER, get_class($result[0])); + + $result = $this->ldap->listObjects(KOLAB_OBJECT_SHAREDFOLDER); + $this->assertNoError($result); + $this->assertEquals(1, count($result)); + $this->assertEquals(KOLAB_OBJECT_SHAREDFOLDER, get_class($result[0])); + } + + /** + * Test handling of object classes. + * + * @return NULL + */ + public function testGetObjectClasses() + { + $classes = $this->ldap->_getObjectClasses('cn=Gunnar Wrobel,dc=example,dc=org'); + $this->assertNoError($classes); + $this->assertContains('top', $classes); + $this->assertContains('kolabinetorgperson', $classes); + $this->assertContains('hordeperson', $classes); + + $classes = $this->ldap->_getObjectClasses('cn=DOES NOT EXIST,dc=example,dc=org'); + $this->assertError($classes, + 'LDAP Error: No such object: cn=DOES NOT EXIST,dc=example,dc=org: No such object'); + + $classes = $this->ldap->_getObjectClasses('cn=The Administrator,dc=example,dc=org'); + $this->assertNoError($classes); + $this->assertContains('kolabinetorgperson', $classes); + } + + /** + * Test handling of object types. + * + * @return NULL + */ + public function testDetermineType() + { + $type = $this->ldap->_determineType('cn=empty.group@example.org,dc=example,dc=org'); + $this->assertNoError($type); + $this->assertEquals(KOLAB_OBJECT_GROUP, $type); + + $type = $this->ldap->_determineType('cn=shared@example.org,dc=example,dc=org'); + $this->assertNoError($type); + $this->assertEquals(KOLAB_OBJECT_SHAREDFOLDER, $type); + + $type = $this->ldap->_determineType('cn=The Administrator,dc=example,dc=org'); + $this->assertNoError($type); + $this->assertEquals(KOLAB_OBJECT_ADMINISTRATOR, $type); + + $type = $this->ldap->_determineType('cn=Main Tainer,dc=example,dc=org'); + $this->assertNoError($type); + $this->assertEquals(KOLAB_OBJECT_MAINTAINER, $type); + + $type = $this->ldap->_determineType('cn=Domain Maintainer,dc=example,dc=org'); + $this->assertNoError($type); + $this->assertEquals(KOLAB_OBJECT_DOMAINMAINTAINER, $type); + + $type = $this->ldap->_determineType('cn=Test Address,cn=external,dc=example,dc=org'); + $this->assertNoError($type); + $this->assertEquals(KOLAB_OBJECT_ADDRESS, $type); + + $type = $this->ldap->_determineType('cn=Gunnar Wrobel,dc=example,dc=org'); + $this->assertNoError($type); + $this->assertEquals(KOLAB_OBJECT_USER, $type); + } + + /** + * Test retrieving a primary mail for a mail or id. + * + * @return NULL + */ + public function testMailForIdOrMail() + { + $mail = $this->ldap->mailForIdOrMail('wrobel'); + $this->assertNoError($mail); + $this->assertEquals('wrobel@example.org', $mail); + + $mail = $this->ldap->mailForIdOrMail('wrobel@example.org'); + $this->assertNoError($mail); + $this->assertEquals('wrobel@example.org', $mail); + + $mail = $this->ldap->mailForIdOrMail('DOES NOT EXIST'); + $this->assertNoError($mail); + $this->assertSame(null, $mail); + } + + /** + * Test retrieving a UID for a mail or id. + * + * @return NULL + */ + public function testUidForIdOrMail() + { + $uid = $this->ldap->uidForIdOrMail('wrobel'); + $this->assertNoError($uid); + $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $uid); + + $uid = $this->ldap->uidForIdOrMail('wrobel@example.org'); + $this->assertNoError($uid); + $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $uid); + + $uid = $this->ldap->uidForIdOrMail('DOES NOT EXIST'); + $this->assertNoError($uid); + $this->assertSame(false, $uid); + } + + /** + * Test retrieving a UID for a mail or id. + * + * @return NULL + */ + public function testUidForMailOrIdOrAlias() + { + $uid = $this->ldap->uidForMailOrIdOrAlias('g.wrobel@example.org'); + $this->assertNoError($uid); + $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $uid); + + $uid = $this->ldap->uidForMailOrIdOrAlias('wrobel@example.org'); + $this->assertNoError($uid); + $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $uid); + + $uid = $this->ldap->uidForMailOrIdOrAlias('wrobel'); + $this->assertNoError($uid); + $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $uid); + + $uid = $this->ldap->uidForMailOrIdOrAlias('DOES NOT EXIST'); + $this->assertNoError($uid); + $this->assertSame(false, $uid); + } + + /** + * Test retrieving all addresses for a mail or id. + * + * @return NULL + */ + public function testAddrsForIdOrMail() + { + $addrs = $this->ldap->addrsForIdOrMail('wrobel'); + + $testuser = $this->ldap->fetch('cn=Test Test,dc=example,dc=org'); + $this->assertNoError($testuser); + $this->assertContains('wrobel@example.org', + $testuser->get(KOLAB_ATTR_KOLABDELEGATE, false)); + + $this->assertNoError($addrs); + $this->assertContains('wrobel@example.org', $addrs); + $this->assertContains('test@example.org', $addrs); + $this->assertContains('t.test@example.org', $addrs); + $this->assertContains('g.wrobel@example.org', $addrs); + $this->assertContains('gunnar@example.org', $addrs); + + $addrs = $this->ldap->addrsForIdOrMail('test@example.org'); + $this->assertNoError($addrs); + $this->assertContains('test@example.org', $addrs); + $this->assertContains('t.test@example.org', $addrs); + } + + /** + * Test retrieving a UID for a primary mail. + * + * @return NULL + */ + public function testUidForMailAddress() + { + $uid = $this->ldap->uidForMailAddress('wrobel@example.org'); + $this->assertNoError($uid); + $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $uid); + + $uid = $this->ldap->uidForMailAddress('test@example.org'); + $this->assertNoError($uid); + $this->assertEquals('cn=Test Test,dc=example,dc=org', $uid); + + $uid = $this->ldap->uidForMailAddress('gunnar@example.org'); + $this->assertNoError($uid); + $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $uid); + + $uid = $this->ldap->uidForMailAddress('wrobel'); + $this->assertNoError($uid); + $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $uid); + } + + /** + * Test retrieving a UID for an attribute. + * + * @return NULL + */ + public function testUidForAttr() + { + $uid = $this->ldap->uidForAttr('alias', 'g.wrobel@example.org'); + $this->assertNoError($uid); + $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $uid); + } + + /** + * Test group membership testing. + * + * @return NULL + */ + public function testMemberOfGroupAddress() + { + $uid = $this->ldap->uidForMailAddress('g.wrobel@example.org'); + $this->assertNoError($uid); + $member = $this->ldap->memberOfGroupAddress($uid, 'group@example.org'); + $this->assertNoError($member); + $this->assertTrue($member); + + $member = $this->ldap->memberOfGroupAddress( + $this->ldap->uidForMailAddress('test@example.org'), + 'group@example.org'); + $this->assertNoError($member); + $this->assertTrue($member); + + $member = $this->ldap->memberOfGroupAddress( + $this->ldap->uidForMailAddress('somebody@example.org'), + 'group@example.org'); + $this->assertNoError($member); + $this->assertFalse($member); + } + + /** + * Test group fetching. + * + * @return NULL + */ + public function testGetGroups() + { + $filter = '(&(objectClass=kolabGroupOfNames)(member=' + . Horde_LDAP::quote('cn=The Administrator,dc=example,dc=org') . '))'; + $result = $this->ldap->_search($filter, array()); + $this->assertNoError($result); + $this->assertTrue(!empty($result)); + + $entry = $this->ldap->_firstEntry($result); + $this->assertNoError($entry); + $this->assertTrue(!empty($entry)); + + $uid = $this->ldap->_getDn($entry); + $this->assertNoError($uid); + $this->assertTrue(!empty($uid)); + + $entry = $this->ldap->_nextEntry($entry); + $this->assertNoError($entry); + $this->assertTrue(empty($entry)); + + $entries = $this->ldap->_getDns($result); + $this->assertNoError($entries); + $this->assertTrue(!empty($entries)); + + $groups = $this->ldap->getGroups('cn=The Administrator,dc=example,dc=org'); + $this->assertNoError($groups); + $this->assertTrue(!empty($groups)); + + $groups = $this->ldap->getGroups($this->ldap->uidForMailAddress('g.wrobel@example.org')); + $this->assertNoError($groups); + $this->assertContains('cn=group@example.org,dc=example,dc=org', $groups); + + $groups = $this->ldap->getGroups($this->ldap->uidForMailAddress('test@example.org')); + $this->assertNoError($groups); + $this->assertContains('cn=group@example.org,dc=example,dc=org', $groups); + + $groups = $this->ldap->getGroups('nobody'); + $this->assertNoError($groups); + $this->assertTrue(empty($groups)); + + } + + /** + * Test parsing of LDAP filters. + * + * @return NULL + */ + public function testFilterParse() + { + $db = &Horde_Kolab_Server::factory('test', array()); + + $a = $db->_parse('(a=b)'); + $this->assertNoError($a); + $this->assertEquals(array('att' => 'a', 'log' => '=', 'val' => 'b'), + $a); + + $a = $db->_parse('(&(a=b)(c=d))'); + $this->assertNoError($a); + $this->assertEquals(array('op' => '&', 'sub' => array( + array('att' => 'a', 'log' => '=', 'val' => 'b'), + array('att' => 'c', 'log' => '=', 'val' => 'd'), + )), $a); + + $a = $db->_parse('(&(a=1)(|(b=2)(c=3)))'); + $this->assertNoError($a); + $this->assertEquals(array('op' => '&', 'sub' => array( + array('att' => 'a', 'log' => '=', 'val' => '1'), + array('op' => '|', 'sub' => + array( + array('att' => 'b', 'log' => '=', 'val' => '2'), + array('att' => 'c', 'log' => '=', 'val' => '3'), + )))), $a); + + $a = $db->_parseSub('(!(x=2))(b=1)'); + $this->assertNoError($a); + $this->assertEquals(array(array('op' => '!', 'sub' => + array( + array('att' => 'x', 'log' => '=', 'val' => '2'), + ) + ), + array('att' => 'b', 'log' => '=', 'val' => '1'), + ), $a); + + $a = $db->_parse('(&(!(x=2))(b=1))'); + $this->assertNoError($a); + $this->assertEquals(array('op' => '&', 'sub' => array( + array('op' => '!', 'sub' => + array( + array('att' => 'x', 'log' => '=', 'val' => '2'), + ) + ), + array('att' => 'b', 'log' => '=', 'val' => '1'), + )), $a); + + } + + /** + * Test searching in the simulated LDAP data. + * + * @return NULL + */ + public function testSearch() + { + $db = &Horde_Kolab_Server::factory('test', + array('data' => + array( + 'cn=a' => array( + 'dn' => 'cn=a', + 'data' => array( + 'a' => '1', + 'b' => '1', + 'c' => '1', + ) + ), + 'cn=b' => array( + 'dn' => 'cn=b', + 'data' => array( + 'a' => '1', + 'b' => '2', + 'c' => '2', + ) + ), + 'cn=c' => array( + 'dn' => 'cn=c', + 'data' => array( + 'a' => '1', + 'b' => '2', + 'c' => '3', + ) + ), + 'cn=d' => array( + 'dn' => 'cn=d', + 'data' => array( + 'a' => '2', + 'b' => '2', + 'c' => '1', + ) + ), + ) + ) + ); + + $a = $db->_search('(c=1)'); + $this->assertNoError($a); + $this->assertEquals( + array( + array( + 'dn' => 'cn=a', + 'data' => array( + 'a' => '1', + 'b' => '1', + 'c' => '1', + ) + ), + array( + 'dn' => 'cn=d', + 'data' => array( + 'a' => '2', + 'b' => '2', + 'c' => '1', + ) + ), + ), + $a + ); + + $a = $db->_search('(c=3)'); + $this->assertNoError($a); + $this->assertEquals( + array( + array( + 'dn' => 'cn=c', + 'data' => array( + 'a' => '1', + 'b' => '2', + 'c' => '3', + ) + ), + ), + $a + ); + + $a = $db->_search('(c=3)', array('a')); + $this->assertNoError($a); + $this->assertEquals( + array( + array( + 'dn' => 'cn=c', + 'data' => array( + 'a' => '1', + ) + ), + ), + $a + ); + + $a = $db->_search('(&(a=1)(b=2))', array('a', 'b')); + $this->assertNoError($a); + $this->assertEquals( + array( + array( + 'dn' => 'cn=b', + 'data' => array( + 'a' => '1', + 'b' => '2', + ) + ), + array( + 'dn' => 'cn=c', + 'data' => array( + 'a' => '1', + 'b' => '2', + ) + ), + ), + $a + ); + + $a = $db->_search('(&(b=2))', array('b')); + $this->assertNoError($a); + $this->assertEquals( + array( + array( + 'dn' => 'cn=b', + 'data' => array( + 'b' => '2', + ) + ), + array( + 'dn' => 'cn=c', + 'data' => array( + 'b' => '2', + ) + ), + array( + 'dn' => 'cn=d', + 'data' => array( + 'b' => '2', + ) + ), + ), + $a + ); + + $a = $db->_search('(!(b=2))', array('a', 'b')); + $this->assertNoError($a); + $this->assertEquals( + array( + array( + 'dn' => 'cn=a', + 'data' => array( + 'a' => '1', + 'b' => '1', + ) + ), + ), + $a + ); + + $a = $db->_search('(&(!(x=2))(b=1))', array('b')); + $this->assertNoError($a); + $this->assertEquals( + array( + array( + 'dn' => 'cn=a', + 'data' => array( + 'b' => '1', + ) + ), + ), + $a + ); + } + +} -- 2.11.0