diff --git a/lib/api/kolab_api_service_form_value.php b/lib/api/kolab_api_service_form_value.php index 58799f6..2edee57 100644 --- a/lib/api/kolab_api_service_form_value.php +++ b/lib/api/kolab_api_service_form_value.php @@ -1,1691 +1,1696 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Jeroen van Meeuwen | +--------------------------------------------------------------------------+ */ /** * Service providing functionality related to HTML forms generation/validation. */ class kolab_api_service_form_value extends kolab_api_service { const VALIDATE_DEFAULT = 'default'; const VALIDATE_BASIC = 'basic'; const VALIDATE_EXTENDED = 'extended'; const VALIDATE_NONE = 'none'; /** * Returns service capabilities. * * @param string $domain Domain name * * @return array Capabilities list */ public function capabilities($domain) { return array( 'generate' => 'r', 'validate' => 'r', 'select_options' => 'r', 'list_options' => 'r', ); } /** * Generation of auto-filled field values. * * @param array $getdata GET parameters * @param array $postdata POST parameters. Required parameters: * - attributes: list of attribute names * - type_id: Type identifier * - object_type: Object type (user, group, etc.) * * @return array Response with attribute name as a key */ public function generate($getdata, $postdata) { $attribs = $this->object_type_attributes($postdata['object_type'], $postdata['type_id'], $type_key); $attributes = (array) $postdata['attributes']; $result = array(); $postdata['type_key'] = $type_key; foreach ($attributes as $attr_name) { if (empty($attr_name)) { continue; } $method_name = 'generate_' . str_replace('-', '_', strtolower($attr_name)) . '_' . strtolower($postdata['object_type']); if (!method_exists($this, $method_name)) { Log::trace("Method $method_name doesn't exist"); $method_name = 'generate_' . str_replace('-', '_', strtolower($attr_name)); if (!method_exists($this, $method_name)) { Log::trace("Method $method_name doesn't exist either"); continue; } } Log::trace("Executing method $method_name"); $result[$attr_name] = $this->{$method_name}($postdata, $attribs); } Log::trace("Returning result: " . var_export($result, TRUE)); return $result; } /** * Generation of values for fields of type LIST. * * @param array $getdata GET parameters * @param array $postdata POST parameters. Required parameters: * - attribute: attribute name * - type_id: Type identifier * - object_type: Object type (user, group, etc.) * * @return array Response with attribute name as a key */ public function list_options($getdata, $postdata) { //console($postdata); $attribs = $this->object_type_attributes($postdata['object_type'], $postdata['type_id'], $key_name); $attr_name = $postdata['attribute']; $result = array( // return search value, so client can match response to request 'search' => $postdata['search'], 'list' => array(), ); if (empty($attr_name)) { return $result; } if ($key_name) { $postdata['type_key'] = $key_name; } $method_name = 'list_options_' . strtolower($attr_name) . '_' . strtolower($postdata['object_type']); if (!method_exists($this, $method_name)) { Log::debug("Method $method_name doesn't exist"); $method_name = 'list_options_' . strtolower($attr_name); if (!method_exists($this, $method_name)) { Log::debug("Method $method_name doesn't exist"); return $result; } } //console($method_name); $result['list'] = $this->{$method_name}($postdata, $attribs); return $result; } /** * Generation of values for fields of type SELECT. * * @param array $getdata GET parameters * @param array $postdata POST parameters. Required parameters: * - attributes: list of attribute names * - type_id: Type identifier * - object_type: Object type (user, group, etc.) * * @return array Response with attribute name as a key */ public function select_options($getdata, $postdata) { //console("form_value.select_options postdata", $postdata); $attribs = $this->object_type_attributes($postdata['object_type'], $postdata['type_id'], $key_name); $attributes = (array) $postdata['attributes']; $result = array(); if ($key_name) { $postdata['type_key'] = $key_name; } foreach ($attributes as $attr_name) { if (empty($attr_name)) { continue; } $method_name = 'select_options_' . strtolower($attr_name); if (method_exists($this, $method_name)) { $res = $this->{$method_name}($postdata, $attribs); } else { $res = array(); } if (!is_array($res['list'])) { $res['list'] = array(); } $result[$attr_name] = $res; } return $result; } /** * Validation of field values. * * @param array $getdata GET parameters * @param array $postdata POST parameters. Required parameters: * - type_id: Type identifier * - object_type: Object type (user, group, etc.) * * @return array Response with attribute name as a key */ public function validate($getdata, $postdata) { $attribs = $this->object_type_attributes($postdata['object_type'], $postdata['type_id']); $result = array(); Log::trace("kolab_api_form_value::validate() \$postdata: " . var_export($postdata, TRUE)); foreach ((array)$postdata as $attr_name => $attr_value) { if (empty($attr_name) || $attr_name == 'type_id' || $attr_name == 'object_type') { continue; } $method_name = 'validate_' . strtolower($attr_name) . '_' . strtolower($postdata['object_type']); if (!method_exists($this, $method_name)) { //console("Method $method_name doesn't exist"); $method_name = 'validate_' . strtolower($attr_name); if (!method_exists($this, $method_name)) { $result[$attr_name] = 'OK'; continue; } } $validate = $attribs['form_fields'][$attr_name]['validate']; // deprecated value: false = VALIDATE_NONE if ($validate === false) { $validate = self::VALIDATE_NONE; } if ($validate == self::VALIDATE_NONE) { $result[$attr_name] = $attr_value; } else if ($attribs['form_fields'][$attr_name]['optional'] && $attr_value === '') { $result[$attr_name] = $attr_value; } else { $result[$attr_name] = $this->{$method_name}($attr_value, $postdata, $validate); } } Log::trace("kolab_api_form_value::validate() \$result: " . var_export($result, TRUE)); return $result; } private function generate_alias($postdata, $attribs = array()) { $rcpt_pol_aliases = $this->generate_secondary_mail($postdata, $attribs); $service = $this->controller->get_service('user'); $user_attrs = $service->user_info(array('id' => $postdata['id']), null); if (!empty($user_attrs['alias'])) { $cur_aliases = $user_attrs['alias']; } else { $cur_aliases = Array(); } if (!is_array($cur_aliases)) { $cur_aliases = (array)($cur_aliases); } sort($rcpt_pol_aliases); sort($cur_aliases); $form_aliases = array_unique(array_merge($rcpt_pol_aliases, $cur_aliases)); Log::trace("kolab_api_service_form_value::generate_alias() \$form_aliases: " . var_export($form_aliases, TRUE)); return array_values($form_aliases); } private function generate_apple_generateduid($postdata, $attribs = array()) { if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['apple-generateduid'])) { $uuid = exec("uuidgen | tr '[:lower:]' '[:upper:]'"); return $uuid; } } private function generate_astaccountcallerid($postdata, $attribs = array()) { if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['astaccountcallerid'])) { // Use Data Please foreach ($attribs['auto_form_fields']['astaccountcallerid']['data'] as $key) { if (!isset($postdata[$key])) { throw new Exception("Key not set: " . $key, 12356); } } // TODO: Correct this with what is in 'data'... return $this->generate_cn($postdata, $attribs); } } private function generate_astaccountdefaultuser($postdata, $attribs = array()) { if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['astaccountdefaultuser'])) { // Use Data Please foreach ($attribs['auto_form_fields']['astaccountdefaultuser']['data'] as $key) { if (!isset($postdata[$key])) { throw new Exception("Key not set: " . $key, 12356); } } return $this->generate_uid($postdata, $attribs); } } private function generate_astaccountmailbox($postdata, $attribs = array()) { if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['astaccountmailbox'])) { // Use Data Please foreach ($attribs['auto_form_fields']['astaccountmailbox']['data'] as $key) { if (!isset($postdata[$key])) { throw new Exception("Key not set: " . $key, 12356); } } return $this->generate_uid($postdata, $attribs); } } private function generate_astaccountregistrationcontext($postdata, $attribs = array()) { if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['astaccountregistrationcontext'])) { // Use Data Please foreach ($attribs['auto_form_fields']['astaccountregistrationcontext']['data'] as $key) { if (!isset($postdata[$key])) { throw new Exception("Key not set: " . $key, 12356); } } return $this->generate_uid($postdata, $attribs); } } private function generate_astaccountregistrationexten($postdata, $attribs = array()) { if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['astaccountregistrationexten'])) { $search = array( 'params' => array( 'objectclass' => array( 'type' => 'exact', 'value' => 'asterisksipuser', ), ), ); $auth = Auth::get_instance($_SESSION['user']->get_domain()); $conf = Conf::get_instance(); $users = $auth->list_users(NULL, Array('astaccountregistrationexten'), $search); $lower_astaccountregistrationexten = $conf->get('astaccountregistrationexten_lower_barrier'); if (!$lower_astaccountregistrationexten) { $lower_astaccountregistrationexten = 200; } // Start at the lower barrier + 1 $lower_astaccountregistrationexten = ($lower_astaccountregistrationexten + 1); $higher_astaccountregistrationexten = $conf->get('astaccountregistrationexten_higher_barrier'); if (!$higher_astaccountregistrationexten) { $higher_astaccountregistrationexten = 300; } $astaccountregistrationextens = Array(); foreach ($users['list'] as $dn => $attributes) { if (!array_key_exists('astaccountregistrationexten', $attributes)) { continue; } if ($attributes['astaccountregistrationexten'] > $highest_astaccountregistrationexten) { $astaccountregistrationextens[] = $attributes['astaccountregistrationexten']; } } for ($i = $lower_astaccountregistrationexten; $i < $higher_astaccountregistrationexten; $i++) { if (!in_array($i, $astaccountregistrationextens)) { $astaccountregistrationexten = $i; break; } } return $astaccountregistrationexten; } } private function generate_cn($postdata, $attribs = array()) { if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['cn'])) { // Use Data Please foreach ($attribs['auto_form_fields']['cn']['data'] as $key) { if (!isset($postdata[$key])) { throw new Exception("Key not set: " . $key, 12356); } } // TODO: Generate using policy from configuration $cn = trim(trim($postdata['givenname']) . " " . trim($postdata['sn'])); return $cn; } } private function generate_cn_resource($postdata, $attribs = array()) { if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['cn'])) { // Use Data Please foreach ($attribs['auto_form_fields']['cn']['data'] as $key) { if (!isset($postdata[$key])) { throw new Exception("Key not set: " . $key, 12356); } } $auth = Auth::get_instance($_SESSION['user']->get_domain()); $cn = trim($postdata['cn']); $x = 2; while (($resource_found = $auth->resource_find_by_attribute(array('cn' => $cn)))) { if (!empty($postdata['id'])) { $resource_found_dn = key($resource_found); $resource_found_unique_attr = $this->unique_attribute_value($resource_found_dn); //console("resource with mail $mail found", $resource_found_unique_attr); if ($resource_found_unique_attr == $postdata['id']) { //console("that's us."); break; } } $cn = trim($postdata['cn']) . ' #' . $x; $x++; } return $cn; } } private function generate_displayname($postdata, $attribs = array()) { if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['displayname'])) { // Use Data Please foreach ($attribs['auto_form_fields']['displayname']['data'] as $key) { if (!isset($postdata[$key])) { throw new Exception("Key not set: " . $key, 12356); } } // TODO: Generate using policy from configuration $displayname = trim($postdata['givenname']); $sn = trim($postdata['sn']); if ($sn) { $displayname = $sn . ", " . $displayname; } // TODO: Figure out what may be sent as an additional comment; // // Examples: // // - van Meeuwen, Jeroen (Kolab Systems) // - Doe, John (Contractor) // return $displayname; } } private function generate_gidnumber($postdata, $attribs = array()) { if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['gidnumber'])) { $auth = Auth::get_instance($_SESSION['user']->get_domain()); $conf = Conf::get_instance(); // TODO: Take a policy to use a known group ID, a known group (by name?) // and/or create user private groups. // groups search parameters $params = array('page_size' => 200); $search = array( 'operator' => 'AND', 'params' => array( 'objectclass' => array( 'type' => 'exact', 'value' => 'posixgroup', ), 'gidnumber' => array( 'type' => '>=', ) ), ); $highest_gidnumber = $conf->get('gidnumber_lower_barrier'); if (!$highest_gidnumber) { $highest_gidnumber = 999; } do { $search['params']['gidnumber']['value'] = $highest_gidnumber; $groups = $auth->list_groups(NULL, array('gidnumber'), $search, $params); foreach ($groups['list'] as $dn => $attributes) { if (!array_key_exists('gidnumber', $attributes)) { continue; } if ($attributes['gidnumber'] > $highest_gidnumber) { $highest_gidnumber = $attributes['gidnumber']; } } } while ($groups['count'] == $params['page_size']); $gidnumber = ($highest_gidnumber + 1); $postdata['gidnumber'] = $gidnumber; if (empty($postdata['uidnumber'])) { $uidnumber = $this->generate_uidnumber($postdata, $attribs); $gidnumber = max($uidnumber, $gidnumber); } return $gidnumber; } } private function generate_homedirectory($postdata, $attribs = array()) { if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['homedirectory'])) { // Use Data Please foreach ($attribs['auto_form_fields']['homedirectory']['data'] as $key) { if (!isset($postdata[$key])) { throw new Exception("Key not set: " . $key, 12356); } } // TODO: Home directory attribute to use $uid = $this->generate_uid($postdata, $attribs); // TODO: Home directory base path from configuration? $conf = Conf::get_instance(); $homedirectory_base = $conf->get($_SESSION['user']->get_domain(), 'homedirectory_base'); if (empty($homedirectory_base)) { $homedirectory_base = $conf->get('kolab', 'homedirectory_base'); } if (empty($homedirectory_base)) { $homedirectory_base = "/home/"; } if (substr($homedirectory_base, (strlen($homedirectory_base)-1), 1) == "/") { $homedirectory_base = substr($homedirectory_base, 0, (strlen($homedirectory_base)-1)); } return $homedirectory_base . '/' . $uid; } } private function generate_kolabtargetfolder_sharedfolder($postdata, $attribs = array()) { if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['kolabtargetfolder'])) { // Use Data Please foreach ($attribs['auto_form_fields']['kolabtargetfolder']['data'] as $key) { if (!isset($postdata[$key])) { throw new Exception("Key not set: " . $key, 12356); } } // no cn given then return empty if (!trim($postdata['cn'])) { return ''; } // check format (will throw exception) $this->validate_cn_sharedfolder($postdata['cn']); $cn = $postdata['cn']; $imap_hierarchysep = '/'; return $cn ? 'shared' . $imap_hierarchysep . $cn . '@' . $_SESSION['user']->get_domain() : ''; } } private function generate_kolabtargetfolder_resource($postdata, $attribs = array()) { if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['kolabtargetfolder'])) { // Use Data Please foreach ($attribs['auto_form_fields']['kolabtargetfolder']['data'] as $key) { if (!isset($postdata[$key])) { throw new Exception("Key not set: " . $key, 12356); } } // TODO: Detect or from config $imap_hierarchysep = '/'; $cn = $this->generate_cn_resource($postdata, $attribs); return 'shared' . $imap_hierarchysep . 'Resources' . $imap_hierarchysep . $cn . '@' . $_SESSION['user']->get_domain(); } } private function generate_mail($postdata, $attribs = array()) { return $this->generate_primary_mail($postdata, $attribs); } private function generate_mail_group($postdata, $attribs = array()) { return $this->generate_primary_mail_group($postdata, $attribs); } private function generate_mail_resource($postdata, $attribs = array()) { if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['mail'])) { // Use Data Please foreach ($attribs['auto_form_fields']['mail']['data'] as $key) { if (!isset($postdata[$key])) { throw new Exception("Key not set: " . $key, 12356); } } $resourcedata = kolab_recipient_policy::normalize_groupdata($postdata); //console("normalized resource data", $resourcedata); // TODO: Normalize $postdata $mail_local = 'resource-' . $postdata['type_key'] . '-' . strtolower(trim($resourcedata['cn'])); $mail_domain = $_SESSION['user']->get_domain(); $mail = $mail_local . '@' . $mail_domain; $auth = Auth::get_instance($_SESSION['user']->get_domain()); $x = 2; while (($resource_found = $auth->resource_find_by_attribute(array('mail' => $mail)))) { if (!empty($postdata['id'])) { $resource_found_dn = key($resource_found); $resource_found_unique_attr = $this->unique_attribute_value($resource_found_dn); //console("resource with mail $mail found", $resource_found_unique_attr); if ($resource_found_unique_attr == $postdata['id']) { //console("that's us."); break; } } $mail = $mail_local . '-' . $x . '@' . $mail_domain; $x++; } return $mail; } } private function generate_mailalternateaddress($postdata, $attribs = array()) { return $this->generate_secondary_mail($postdata, $attribs); } private function generate_mailhost($postdata, $attribs = array()) { if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['uidnumber'])) { // This value is determined by the Kolab Daemon return ''; } } private function generate_password($postdata, $attribs = array()) { return password_policy::generate_password(); } private function generate_userpassword($postdata, $attribs = array()) { return $this->generate_password($postdata, $attribs); } private function generate_primary_mail($postdata, $attribs = array()) { if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['mail'])) { // Use Data Please foreach ($attribs['auto_form_fields']['mail']['data'] as $key) { if (!isset($postdata[$key])) { throw new Exception("Key not set: " . $key, 12356); } } if (array_key_exists('uid', $attribs['auto_form_fields'])) { if (!array_key_exists('uid', $postdata)) { $postdata['uid'] = $this->generate_uid($postdata, $attribs); } } $primary_mail = kolab_recipient_policy::primary_mail($postdata); $auth = Auth::get_instance(); list($_local, $_domain) = explode("@", $primary_mail); $local = $_local; $x = 2; while (($user_found = $auth->find_recipient($local . "@" . $_domain))) { Log::trace(__FUNCTION__ . ": An entry with address " . $local . "@" . $_domain . " was found."); if (!empty($postdata['id'])) { $user_found_dn = key($user_found); $user_found_unique_attr = $this->unique_attribute_value($user_found_dn); if ($user_found_unique_attr == $postdata['id']) { Log::trace(__FUNCTION__ . ": Entry with address " . $local . "@" . $_domain . " is actually us."); break; } } // empty($postdata['id']) // Otherwise this is a new user and therefore the entry found with // this address is definitely not us $local = $_local . $x; $x++; } $primary_mail = $local . "@" . $_domain; return $primary_mail; } } private function generate_primary_mail_group($postdata, $attribs = array()) { if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['mail'])) { // Use Data Please foreach ($attribs['auto_form_fields']['mail']['data'] as $key) { if (!isset($postdata[$key])) { throw new Exception("Key not set: " . $key, 12356); } } $primary_mail = kolab_recipient_policy::primary_mail_group($postdata); return $primary_mail; } } private function generate_secondary_mail($postdata, $attribs = array()) { if (isset($attribs['auto_form_fields'])) { if (isset($attribs['auto_form_fields']['alias'])) { $secondary_mail_key = 'alias'; } elseif (isset($attribs['auto_form_fields']['mailalternateaddress'])) { $secondary_mail_key = 'mailalternateaddress'; } else { throw new Exception("No valid input for secondary mail address(es)", 478); } foreach ($attribs['auto_form_fields'][$secondary_mail_key]['data'] as $key) { if (!isset($postdata[$key])) { throw new Exception("Key not set: " . $key, 456789); } } if (array_key_exists('uid', $attribs['auto_form_fields'])) { if (!array_key_exists('uid', $postdata)) { $postdata['uid'] = $this->generate_uid($postdata, $attribs); } } if (array_key_exists('mail', $attribs['auto_form_fields'])) { if (!array_key_exists('mail', $postdata)) { $postdata['mail'] = $this->generate_primary_mail($postdata, $attribs); } } $auth = Auth::get_instance(); $_secondary_mail_addresses = kolab_recipient_policy::secondary_mail($postdata); $secondary_mail_addresses = array(); foreach ($_secondary_mail_addresses as $num => $alias) { list($_local, $_domain) = explode("@", $alias); $local = $_local; $x = 2; while (($user_found = $auth->find_recipient($local . "@" . $_domain))) { Log::trace(__FUNCTION__ . ": An entry with address " . $local . "@" . $_domain . " was found."); if (!empty($postdata['id'])) { $user_found_dn = key($user_found); $user_found_unique_attr = $this->unique_attribute_value($user_found_dn); if ($user_found_unique_attr == $postdata['id']) { Log::trace(__FUNCTION__ . ": Entry with address " . $local . "@" . $_domain . " is actually us."); break; } } // empty($postdata['id']) // Otherwise this is a new user and therefore the entry found with // this address is definitely not us $local = $_local . $x; $x++; } $secondary_mail_addresses[] = $local . "@" . $_domain; } if (($key = array_search($postdata['mail'], $secondary_mail_addresses)) !== false) { Log::trace("Found primary mail as part of the secondary mail addresses"); unset($secondary_mail_addresses[$key]); } return array_unique($secondary_mail_addresses); } } private function generate_uid($postdata, $attribs = array()) { if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['uid'])) { // Use Data Please foreach ($attribs['auto_form_fields']['uid']['data'] as $key) { if (!isset($postdata[$key])) { throw new Exception("Key not set: " . $key, 12356); } } if (empty($postdata['uid'])) { $postdata['uid'] = trim($postdata['sn']); } $userdata = kolab_recipient_policy::normalize_userdata($postdata); $uid = kolab_recipient_policy::uid($userdata); Log::debug("uid from recipient policy: " . var_export($uid, TRUE)); $orig_uid = $uid; $auth = Auth::get_instance($_SESSION['user']->get_domain()); $x = 2; while (($user_found = $auth->user_find_by_attribute(array('uid' => $uid)))) { if (!empty($postdata['id'])) { $user_found_dn = key($user_found); $user_found_unique_attr = $this->unique_attribute_value($user_found_dn); //console("user with uid $uid found", $user_found_unique_attr); if ($user_found_unique_attr == $postdata['id']) { //console("that's us."); break; } } $uid = $orig_uid . $x; $x++; } return $uid; } } private function generate_uidnumber($postdata, $attribs = array()) { if (isset($attribs['auto_form_fields']) && isset($attribs['auto_form_fields']['uidnumber'])) { $auth = Auth::get_instance($_SESSION['user']->get_domain()); $conf = Conf::get_instance(); // users search parameters $params = array('page_size' => 200); $search = array( 'operator' => 'AND', 'params' => array( 'objectclass' => array( 'type' => 'exact', 'value' => 'posixaccount', ), 'uidnumber' => array( 'type' => '>=', ), ), ); $highest_uidnumber = $conf->get('uidnumber_lower_barrier'); if (!$highest_uidnumber) { $highest_uidnumber = 999; } do { $search['params']['uidnumber']['value'] = $highest_uidnumber; $users = $auth->list_users(NULL, array('uidnumber'), $search, $params); foreach ($users['list'] as $dn => $attributes) { if (!array_key_exists('uidnumber', $attributes)) { continue; } if ($attributes['uidnumber'] > $highest_uidnumber) { $highest_uidnumber = $attributes['uidnumber']; } } } while ($users['count'] == $params['page_size']); $uidnumber = ($highest_uidnumber + 1); $postdata['uidnumber'] = $uidnumber; if (empty($postdata['gidnumber'])) { $gidnumber = $this->generate_gidnumber($postdata, $attribs); $uidnumber = max($uidnumber, $gidnumber); } return $uidnumber; } } private function list_options_kolabdelegate($postdata, $attribs = array()) { return $this->_list_options_users($postdata, $attribs); } private function list_options_member($postdata, $attribs = array()) { return $this->_list_options_members($postdata, $attribs); } private function list_options_nsrole($postdata, $attribs = array()) { //console("Listing options for attribute 'nsrole', while the expected attribute to use is 'nsroledn'"); return $this->list_options_nsroledn($postdata, $attribs); } private function list_options_nsroledn($postdata, $attribs = Array()) { // return specified records only, by exact DN attributes if (!empty($postdata['list'])) { $data['page_size'] = count($postdata['list']); $data['search'] = array( 'entrydn' => array( 'value' => $postdata['list'], 'type' => 'exact', ), ); } // return records with specified string else { $keyword = array('value' => $postdata['search']); $data['page_size'] = 15; $data['search'] = array( 'displayname' => $keyword, 'cn' => $keyword, 'mail' => $keyword, ); } $data['attributes'] = array('cn'); $service = $this->controller->get_service('roles'); $result = $service->roles_list(null, $data); $list = $result['list']; // convert to key=>value array foreach ($list as $idx => $value) { $list[$idx] = $value['cn']; } return $list; } private function list_options_ou($postdata, $attribs = array()) { // return specified records only, by exact DN attributes if (!empty($postdata['list'])) { $data['page_size'] = count($postdata['list']); $data['search'] = array( 'entrydn' => array( 'value' => $postdata['list'], 'type' => 'exact', ), ); } // return records with specified string else { $keyword = array('value' => $postdata['search']); $data['page_size'] = 15; $data['search'] = array( 'entrydn' => $keyword, 'description' => $keyword, ); } $data['attributes'] = array('ou', 'entrydn'); $service = $this->controller->get_service('ous'); $result = $service->ous_list(null, $data); $list = $result['list']; // convert to key=>value array foreach ($list as $idx => $value) { $list[$idx] = kolab_utils::dn2ufn($idx); } return $list; } private function list_options_owner($postdata, $attribs = array()) { return $this->_list_options_users($postdata, $attribs); } private function list_options_uniquemember($postdata, $attribs = array()) { $result = $this->_list_options_members($postdata, $attribs); return $result; } private function list_options_uniquemember_resource($postdata, $attribs = array()) { return $this->_list_options_resources($postdata, $attribs); } private function select_options_c($postdata, $attribs = array()) { return $this->_select_options_from_db('c'); } private function select_options_objectclass($postdata, $attribs = array()) { $auth = Auth::get_instance(); $list = $auth->schema_classes(); if (is_array($list)) { sort($list); } return array('list' => $list); } private function select_options_attribute($postdata, $attribs = array()) { // if objectClasses aren't specified we'll use all classes already in use // not all classes in LDAP if (empty($postdata['classes'])) { $postdata['classes'] = array(); foreach ($this->supported_types as $type) { foreach ($this->object_types($type) as $obj_type) { if ($obj_type['attributes'] && $obj_type['attributes']['fields']) { $postdata['classes'] = array_merge($postdata['classes'], (array) $obj_type['attributes']['fields']['objectclass']); } } } } $postdata['classes'] = array_unique($postdata['classes']); $auth = Auth::get_instance(); $list = $auth->schema_attributes($postdata['classes']); if (is_array($list['may'])) { // return required + optional if (is_array($list['must']) && !empty($list['must'])) { $list['may'] = array_unique(array_merge($list['may'], $list['must'])); } sort($list['may']); } return array( 'list' => $list['may'], 'required' => $list['must'] ); } private function select_options_ou($postdata, $attribs = array()) { $auth = Auth::get_instance(); $conf = Conf::get_instance(); $unique_attr = self::unique_attribute(); $object_type = $postdata['object_type']; $object_key = $postdata['type_key']; $base_dn = $auth->subject_base_dn($object_key, $object_type); if (!empty($postdata['id'])) { $subjects = $auth->search($base_dn, '(' . $unique_attr . '=' . $postdata['id'] . ')', 'sub', array('dn')); if ($subjects) { $subjects = $subjects->entries(true); $subject_dn = key($subjects); $subject_dn_components = kolab_utils::explode_dn($subject_dn); if ($subject_dn_components) { array_shift($subject_dn_components); $default = strtolower(implode(',', $subject_dn_components)); } } } if (empty($default)) { $default = $base_dn; } $result = array(); $params = array( 'type' => $object_key, 'page_size' => 99999, ); $ous = $auth->list_organizationalunits(null, array(''), null, $params); if ($ous['count']) { foreach ($ous['list'] as $ou_dn => $ou_attrs) { $result[] = strtolower($ou_dn); } sort($result); } return array( 'list' => $result, 'default' => strtolower($default), ); } private function select_options_preferredlanguage($postdata, $attribs = array()) { $options = $this->_select_options_from_db('preferredlanguage'); $conf = Conf::get_instance(); $default = $conf->get('default_locale'); if (!$default) { $default = 'en_US'; } if (!empty($postdata['preferredlanguage'])) { $default = $postdata['preferredlanguage']; } $options['default'] = $default; return $options; } /** * Checks if specified list of email addresses is already * in use by another user */ private function _email_addresses_in_use($addresses, $attr_name, $postdata) { $auth = Auth::get_instance(); foreach ($addresses as $addr) { if ($users = $auth->find_recipient($addr)) { Log::trace(__FUNCTION__ . ": An entry with address $addr was found."); if (!empty($postdata['id']) && count($users) == 1) { $user_found_dn = key($users); $user_found_unique_attr = $this->unique_attribute_value($user_found_dn); if ($user_found_unique_attr == $postdata['id']) { // check if the address is in another field, we prevent here // from e.g. adding primary mail address into aliases list $found = false; $user = $users[$user_found_dn]; $addr = mb_strtolower($addr); unset($user[$attr_name]); foreach ($user as $attr => $list) { $list = array_map('mb_strtolower', (array) $list); if (in_array($addr, $list)) { $found = true; break; } } if (!$found) { Log::trace(__FUNCTION__ . ": Entry with address $addr is actually us."); continue; } // @TODO: throw different exception? } } throw new Exception("Email address '$addr' is already in use", 694); } } } private function validate_alias($value, $postdata = null, $validation_type = null) { $conf = Conf::get_instance(); if (!is_array($value)) { $value = (array)($value); } foreach ($value as $mail_address) { if (!$this->_validate_email_address($mail_address)) { throw new Exception("Invalid email address '$mail_address'", 692); } if ($validation_type == self::VALIDATE_BASIC) { continue; } // Only validate the 'alias' attribute is in any of my domain name // spaces if indeed it is listed as a mail attribute. if (in_array('alias', $conf->get_list('mail_attributes'))) { if (!$this->_validate_email_address_in_any_of_my_domains($mail_address)) { throw new Exception("Email address '$mail_address' not in local domain", 693); } } } // Check if addresses are not already in use if ($validation_type == self::VALIDATE_EXTENDED) { $this->_email_addresses_in_use($value, 'alias', $postdata); } return 'OK'; } private function validate_associateddomain($value, $postdata = array(), $validation_type = null) { if (!is_array($value)) { $value = (array) $value; } return $value; } private function validate_astaccountrealmedpassword($value, $postdata = array(), $validation_type = null) { if (!array_key_exists('userpassword', $postdata) || empty($postdata['userpassword'])) { return $value; } if (!array_key_exists('uid', $postdata) || empty($postdata['uid'])) { $postdata['uid'] = $this->generate_uid($postdata); } $str = $postdata['uid'] . ":" . $_SESSION['user']->get_domain() . ":" . $postdata['userpassword']; Log::trace("Inserting astaccountrealmedpassword with value md5('" . $str . "');"); return md5($str); } private function validate_mail($value, $postdata = array(), $validation_type = null) { $conf = Conf::get_instance(); if (!is_array($value)) { $value = (array)($value); } foreach ($value as $mail_address) { if (!$this->_validate_email_address($mail_address)) { throw new Exception("Invalid email address '$mail_address'", 692); } if ($validation_type == self::VALIDATE_BASIC) { continue; } // Only validate the 'mail' attribute is in any of my domain name // spaces if indeed it is listed as a mail attribute. if (in_array('mail', $conf->get_list('mail_attributes'))) { if (!$this->_validate_email_address_in_any_of_my_domains($mail_address)) { throw new Exception("Email address '$mail_address' not in local domain", 693); } } } + // Check if addresses are not already in use + if ($validation_type == self::VALIDATE_EXTENDED) { + $this->_email_addresses_in_use($value, 'mail', $postdata); + } + return 'OK'; } private function validate_mailquota($value, $postdata = array(), $validation_type = null) { // convert MB/GB into KB if (preg_match('/^([0-9]+)\s*(KB|MB|GB)$/i', $value, $m)) { switch (strtoupper($m[2])) { case 'KB': $value = $m[1]; break; case 'MB': $value = $m[1] * 1024; break; case 'GB': $value = $m[1] * 1024 * 1024; break; } } return (string) intval($value); } private function validate_mailalternateaddress($value, $postdata = array(), $validation_type = null) { $conf = Conf::get_instance(); if (!is_array($value)) { $value = (array)($value); } foreach ($value as $mail_address) { if (!$this->_validate_email_address($mail_address)) { throw new Exception("Invalid email address '$mail_address'", 692); } if ($validation_type == self::VALIDATE_BASIC) { continue; } // Only validate the 'mailalternateaddress' attribute is in any of my domain name // spaces if indeed it is listed as a mail attribute. if (in_array('mailalternateaddress', $conf->get_list('mail_attributes'))) { if (!$this->_validate_email_address_in_any_of_my_domains($mail_address)) { throw new Exception("Email address '$mail_address' not in local domain", 693); } } } return 'OK'; } private function validate_cn_sharedfolder($value, $postdata = array(), $validation_type = null) { if (preg_match('/["\'@%+^]/',$value)) { throw new Exception("Folder name contains invalid characters: \" ' @ % + ^"); } return 'OK'; } private function validate_kolabtargetfolder_sharedfolder($value, $postdata = array(), $validation_type = null) { $domains = $this->_get_valid_domains(); if (!preg_match('#^shared/[^"\'\\+@%^]+@('.implode("|",$domains).')$#',$value)) { throw new Exception("Target IMAP Folder has to match the following format: 'shared/foldername@mydomain.org' and the foldername can't contain invalid characters: \" ' @ % + ^"); } // TODO: check for duplicate shared folder // same kolabTargetFolder && type = 'mail || type != mail && folderpart == cn (other) return 'OK'; } private function _list_options_members($postdata, $attribs = array()) { // return specified records only, by exact DN attributes if (!empty($postdata['list'])) { $data['page_size'] = count($postdata['list']); $data['search'] = array( 'params' => array( 'entrydn' => array( 'value' => $postdata['list'], 'type' => 'exact', ), ), 'operator' => 'OR' ); } // return records with specified string else { $keyword = array('value' => $postdata['search'], 'type' => 'both'); $data['page_size'] = 15; $data['search'] = array( 'params' => array( 'displayname' => $keyword, 'cn' => $keyword, 'mail' => $keyword, ), 'operator' => 'OR' ); } $data['attributes'] = array('displayname', 'cn', 'mail'); $service = $this->controller->get_service('users'); $result = $service->users_list(null, $data); $list = $result['list']; // skip groups listing if the list if already full, // and full list was requested (no paging) if (empty($keyword) && $result['count'] < $data['page_size']) { $data['attributes'] = array('cn', 'mail'); $service = $this->controller->get_service('groups'); $result = $service->groups_list(null, $data); $list = array_merge($list, $result['list']); } // convert to key=>value array foreach ($list as $idx => $value) { if (!empty($value['displayname'])) { $list[$idx] = $value['displayname']; } else if (!empty($value['cn'])) { $list[$idx] = $value['cn']; } else { $list[$idx] = ''; } if (!empty($value['mail'])) { $list[$idx] .= ' <' . $value['mail'] . '>'; } $list[$idx] = trim($list[$idx]); if ($list[$idx] === '') { unset($list[$idx]); } } // Sort and slice asort($list); if (!empty($data['page_size'])) { $list = array_slice($list, 0, $data['page_size']); } return $list; } private function _list_options_users($postdata, $attribs = array()) { // return specified records only, by exact DN attributes if (!empty($postdata['list'])) { $data['page_size'] = count($postdata['list']); $data['search'] = array( 'entrydn' => array( 'value' => $postdata['list'], 'type' => 'exact', ), ); } // return records with specified string else { $keyword = array('value' => $postdata['search']); $data['page_size'] = 15; $data['search'] = array( 'displayname' => $keyword, 'cn' => $keyword, 'mail' => $keyword, ); } $data['attributes'] = array('displayname', 'mail'); $service = $this->controller->get_service('users'); $result = $service->users_list(null, $data); $list = $result['list']; $result = array(); $result_key = $postdata['result_key'] ?: 'entrydn'; // convert to key=>value array foreach ($list as $idx => $value) { $ret = $value['displayname']; if (!empty($value['mail'])) { $ret .= ' <' . $value['mail'] . '>'; } if ($result_key != 'entrydn') { $idx = $value[$result_key]; } $ret = trim($ret); // sanity check (#3822) if ($ret !== '') { $result[$idx] = $ret; } } return $result; } private function _list_options_resources($postdata, $attribs = array()) { // return specified records only, by exact DN attributes if (!empty($postdata['list'])) { $data['page_size'] = count($postdata['list']); $data['search'] = array( 'entrydn' => array( 'value' => $postdata['list'], 'type' => 'exact', ), ); } // return records with specified string else { $keyword = array('value' => $postdata['search']); $data['page_size'] = 15; $data['search'] = array( 'cn' => $keyword, ); } $data['attributes'] = array('cn'); //console("api/form_value._list_options_resources() searching with data", $data); $service = $this->controller->get_service('resources'); $result = $service->resources_list(null, $data); $list = $result['list']; // convert to key=>value array foreach ($list as $idx => $value) { if (!empty($value['displayname'])) { $list[$idx] = $value['displayname']; } elseif (!empty($value['cn'])) { $list[$idx] = $value['cn']; } else { unset($list[$idx]); } } return $list; } private function _select_options_from_db($attribute) { if (empty($attribute)) { return false; } $db = SQL::get_instance(); $query = $db->query("SELECT `option_values` FROM `options` WHERE `attribute` = ?", array($attribute)); $result = $db->fetch_assoc($query); $result = json_decode($result['option_values']); return array('list' => $result); } private function _validate_email_address($mail_address) { $valid = true; $at_index = strrpos($mail_address, "@"); if (is_bool($at_index) && !$at_index) { $valid = false; } else { $domain = substr($mail_address, $at_index+1); $local = substr($mail_address, 0, $at_index); if (strlen($local) > 64) { // local part length exceeded //console("Local part of email address is longer than permitted"); $valid = false; } else if (strlen($domain) < 1 || strlen($domain) > 255) { // domain part length exceeded //console("Domain part of email address is longer than permitted"); $valid = false; } else if ($local[0] == '.' || $local[strlen($local)-1] == '.') { // local part starts or ends with '.' //console("Local part of email address starts or ends with '.'"); $valid = false; } else if (preg_match('/\\.\\./', $local)) { // local part has two consecutive dots //console("Local part contains two consecutive dots"); $valid = false; } else if (!preg_match('/^[A-Za-z0-9\\-\\.]+$/', $domain)) { // character not valid in domain part //console("Invalid character in domain part"); $valid = false; } else if (preg_match('/\\.\\./', $domain)) { // domain part has two consecutive dots //console("Domain part contains two consecutive dots"); $valid = false; } else if (strlen($local) > 0) { if (!preg_match('/^(\\\\.|[A-Za-z0-9!#%&`_=\\/$\'*+?^{}|~.-])+$/', str_replace("\\\\","",$local))) { // character not valid in local part unless // local part is quoted if (!preg_match('/^"(\\\\"|[^"])+"$/', str_replace("\\\\","",$local))) { //console("Unquoted invalid character in local part"); $valid = false; } } } // if ($valid && !(checkdnsrr($domain,"MX") || checkdnsrr($domain,"A"))) { // // domain not found in DNS // $valid = false; // } } return $valid; } /** * Lists the valid domains given a primary domain name space. * * Includes the parent domain and any alias or child domains. * Considers domains that point to the same base dn to be child domains. */ private function _get_valid_domains($my_primary_domain = null) { static $domains, $context; $my_primary_domain = $my_primary_domain ?: $_SESSION['user']->get_domain(); if (is_array($domains) && $context === $my_primary_domain) { Log::trace("_get_valid_domains(" . $my_primary_domain . "), use memory cache"); return $domains; } Log::trace("_get_valid_domains(" . $my_primary_domain . ")"); $auth = Auth::get_instance(); $conf = Conf::get_instance(); $all_domains = $auth->list_domains(array(), array(), array('page_size' => 99999)); $all_domains = $all_domains['list']; $all_basedns = array(); $dna = $conf->get('domain_name_attribute'); $dra = $conf->get('domain_rootdn_attribute'); $domains = $my_primary_domain ? array($my_primary_domain) : array(); $context = $my_primary_domain; foreach ($all_domains as $domain_id => $domain_attrs) { if (!is_array($domain_attrs[$dna])) { $domain_attrs[$dna] = (array) $domain_attrs[$dna]; } if (in_array($my_primary_domain, $domain_attrs[$dna])) { $domains = array_merge($domains, $domain_attrs[$dna]); } if (array_key_exists($dra, $domain_attrs)) { if (array_key_exists($domain_attrs[$dra], $all_basedns)) { $all_basedns[$domain_attrs[$dra]] = array_merge($all_basedns[$domain_attrs[$dra]], $domain_attrs[$dna]); } else { $all_basedns[$domain_attrs[$dra]] = $domain_attrs[$dna]; } } } foreach ($all_basedns as $base_dn => $_domains) { if (in_array($my_primary_domain, $_domains)) { $domains = array_merge($domains, $_domains); } } $domains = array_unique($domains); Log::trace("_get_valid_domains result: " . var_export($domains, true)); return $domains; } private function _validate_email_address_in_any_of_my_domains($mail_address) { $at_index = strrpos($mail_address, "@"); if (is_bool($at_index) && !$at_index) { throw new Exception("Invalid email address: No domain name space", 235); } else { $email_domain = substr($mail_address, $at_index+1); } $my_primary_domain = $_SESSION['user']->get_domain(); if ($email_domain == $my_primary_domain) { Log::trace("Found email address to be in one of my domains."); return true; } $valid = false; Log::trace("_validate_email_address_in_any_of_mydomains(\$mail_address = " . var_export($mail_address, TRUE) . ")"); if (in_array($email_domain, $this->_get_valid_domains($my_primary_domain))) { $valid = true; } if ($valid) { Log::trace("Found email address to be in one of my domains."); } else { Log::trace("Found email address to NOT be in one of my domains."); } return $valid; } }