diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php index 59432ef..4bf2a8f 100644 --- a/lib/Auth/LDAP.php +++ b/lib/Auth/LDAP.php @@ -1,1495 +1,1495 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Jeroen van Meeuwen | +--------------------------------------------------------------------------+ */ /** * Kolab LDAP handling abstraction class. */ class LDAP extends Net_LDAP3 { private $conf; /** * Class constructor */ public function __construct($domain = null) { parent::__construct(); $this->conf = Conf::get_instance(); $this->config_set("debug", Log::mode() == Log::TRACE); $this->config_set("log_hook", array($this, "_log")); $this->config_set("config_root_dn", "cn=config"); $this->config_set("service_bind_dn", $this->conf->get("service_bind_dn")); $this->config_set("service_bind_pw", $this->conf->get("service_bind_pw")); $this->config_set("login_filter", $this->conf->get("kolab_wap", "login_filter")); $this->config_set("vlv", $this->conf->get("ldap", "vlv", Conf::AUTO)); // configure the cache $memcache_hosts = $this->conf->get('kolab_wap', 'memcache_hosts'); $this->config_set("memcache_pconnect", $this->conf->get('kolab_wap', 'memcache_pconnect', Conf::BOOL)); $this->config_set("memcache_hosts", $memcache_hosts); $this->config_set("cache", !empty($memcache_hosts)); $this->config_set("domain_base_dn", $this->conf->get('ldap', 'domain_base_dn')); $this->config_set("domain_filter", $this->conf->get('ldap', 'domain_filter')); $this->config_set("domain_name_attribute", $this->conf->get('ldap', 'domain_name_attribute')); // See if we are to connect to any domain explicitly defined. if (empty($domain)) { // If not, attempt to get the domain from the session. if (isset($_SESSION['user'])) { try { $domain = $_SESSION['user']->get_domain(); } catch (Exception $e) { $this->_log(LOG_WARNING, "LDAP: User not authenticated yet"); } } } else { $this->_log(LOG_DEBUG, "LDAP: __construct() using domain $domain"); } // Continue and default to the primary domain. $this->domain = $domain ? $domain : $this->conf->get('primary_domain'); $unique_attr = $this->conf->get($domain, 'unique_attribute'); if (empty($unique_attr)) { $unique_attr = $this->conf->get('ldap', 'unique_attribute'); } if (empty($unique_attr)) { $unique_attr = 'nsuniqueid'; } $this->config_set('unique_attribute', $unique_attr); $this->_ldap_uri = $this->conf->get('ldap_uri'); $this->_ldap_port = parse_url($this->_ldap_uri, PHP_URL_PORT); $this->_ldap_scheme = parse_url($this->_ldap_uri, PHP_URL_SCHEME); // Catch cases in which the ldap server port has not been explicitely defined if (!$this->_ldap_port) { if ($this->_ldap_scheme == "ldaps") { $this->_ldap_port = 636; } else { $this->_ldap_port = 389; } } $this->config_set("host", $this->_ldap_uri); $this->config_set("port", $this->_ldap_port); $this->config_set("use_tls", $this->_ldap_scheme == 'tls'); parent::connect(); // Attempt to get the root dn from the configuration file. $root_dn = $this->conf->get($this->domain, "base_dn"); if (empty($root_dn)) { // Fall back to a root dn from LDAP, or the standard root dn $root_dn = $this->domain_root_dn($this->domain); } $this->config_set("root_dn", $root_dn); } /********************************************************** *********** Public methods ************ **********************************************************/ /** * Authentication * * @param string $username User name (DN or mail) * @param string $password User password * * @return bool|string User ID or False on failure */ public function authenticate($username, $password, $domain = null, &$attributes = null) { $this->_log(LOG_DEBUG, "Auth::LDAP: authentication request for $username against domain $domain"); if (!$domain) { $domain = $this->domain; } $result = $this->login($username, $password, $domain, $attributes); if (!$result) { return false; } $_SESSION['user']->user_bind_dn = $result; $_SESSION['user']->user_bind_pw = $password; return $result; } public function domain_add($domain, $attributes = array()) { if (empty($domain)) { return false; } $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw); $domain_base_dn = $this->conf->get('ldap', 'domain_base_dn'); $domain_name_attribute = $this->conf->get('ldap', 'domain_name_attribute'); $service_bind_dn = $this->conf->get('ldap', 'service_bind_dn'); $primary_domain = $this->conf->get('kolab', 'primary_domain'); if (!empty($attributes['domainrelatedobject_only'])) { $domainrelatedobject_only = (bool)($attributes['domainrelatedobject_only']); unset($attributes['domainrelatedobject_only']); } else { $domainrelatedobject_only = false; } if (empty($service_bind_dn)) { $service_bind_dn = $this->conf->get('ldap', 'bind_dn'); } if (empty($domain_name_attribute)) { $domain_name_attribute = 'associateddomain'; } if (!is_array($attributes[$domain_name_attribute])) { $attributes[$domain_name_attribute] = (array) $attributes[$domain_name_attribute]; } if (!in_array($domain, $attributes[$domain_name_attribute])) { array_unshift($attributes[$domain_name_attribute], $domain); } $domain_dn = $domain_name_attribute . '=' . $domain . ',' . $domain_base_dn; if (!empty($attributes['inetdomainbasedn'])) { $inetdomainbasedn = $attributes['inetdomainbasedn']; } else { $inetdomainbasedn = $this->_standard_root_dn($domain); } if (empty($attributes['aci'])) { $attributes['aci'] = array( "(targetattr = \"*\") (version 3.0;acl \"Read Access for {$domain} Users\";allow (read,compare,search)(userdn = \"ldap:///{$inetdomainbasedn}??sub?(objectclass=*)\");)" ); } $result = $this->add_entry($domain_dn, $attributes); if (!$result) { return false; } // Return if the request specified to only create the domainrelatedobject if ($domainrelatedobject_only) { return true; } // Query the ACI for the primary domain if ($domain_entry = $this->find_domain($primary_domain)) { if (in_array('inetdomainbasedn', $domain_entry)) { $_base_dn = $domain_entry['inetdomainbasedn']; } } if (empty($_base_dn)) { $_base_dn = $this->_standard_root_dn($primary_domain); } $result = $this->_read($_base_dn, array('aci')); $result = $result[key($result)]; $acis = $result['aci']; // Skip one particular ACI foreach ($acis as $aci) { if (stristr($aci, "SIE Group") === false) { continue; } $_aci = $aci; } // @TODO: this list should be configurable or auto-created somehow $self_attrs = array( 'carLicense', 'description', 'displayName', 'facsimileTelephoneNumber', 'homePhone', 'homePostalAddress', 'initials', 'jpegPhoto', 'l', 'labeledURI', 'mobile', 'o', 'pager', 'photo', 'postOfficeBox', 'postalAddress', 'postalCode', 'preferredDeliveryMethod', 'preferredLanguage', 'registeredAddress', 'roomNumber', 'secretary', 'seeAlso', 'st', 'street', 'telephoneNumber', 'telexNumber', 'title', 'userCertificate', 'userPassword', 'userSMIMECertificate', 'x500UniqueIdentifier', ); if (in_array('kolabInetOrgPerson', $this->classes_allowed())) { $self_attrs = array_merge($self_attrs, array('kolabDelegate', 'kolabInvitationPolicy', 'kolabAllowSMTPSender')); } $_domain = str_replace('.', '_', $domain); $dn = $inetdomainbasedn; $cn = str_replace(array(',', '='), array('\2C', '\3D'), $dn); // Additional domain entries in various trees $entries = array( "cn={$cn},cn=mapping tree,cn=config" => array( 'objectclass' => array( 'top', 'extensibleObject', 'nsMappingTree', ), 'nsslapd-state' => 'backend', 'cn' => $inetdomainbasedn, 'nsslapd-backend' => $_domain, ), "cn={$_domain},cn=ldbm database,cn=plugins,cn=config" => array( 'objectclass' => array( 'top', 'extensibleobject', 'nsbackendinstance', ), 'cn' => $_domain, 'nsslapd-suffix' => $inetdomainbasedn, 'nsslapd-cachesize' => '-1', 'nsslapd-cachememsize' => '10485760', 'nsslapd-readonly' => 'off', 'nsslapd-require-index' => 'off', 'nsslapd-dncachememsize' => '10485760', 'nsslapd-directory' => true, // will be replaced below ), $inetdomainbasedn => array( // @TODO: Probably just use ldap_explode_dn() 'dc' => substr($dn, (strpos($dn, '=')+1), ((strpos($dn, ',')-strpos($dn, '='))-1)), 'objectclass' => array( 'top', 'domain', ), 'aci' => array( // Self-modification "(targetattr = \"" . implode(" || ", $self_attrs) . "\")(version 3.0; acl \"Enable self write for common attributes\"; allow (read,compare,search,write) userdn=\"ldap:///self\";)", // Directory Administrators "(targetattr = \"*\")(version 3.0; acl \"Directory Administrators Group\"; allow (all) (groupdn=\"ldap:///cn=Directory Administrators,{$inetdomainbasedn}\" or roledn=\"ldap:///cn=kolab-admin,{$inetdomainbasedn}\");)", // Configuration Administrators "(targetattr = \"*\")(version 3.0; acl \"Configuration Administrators Group\"; allow (all) groupdn=\"ldap:///cn=Configuration Administrators,ou=Groups,ou=TopologyManagement,o=NetscapeRoot\";)", // Administrator users "(targetattr = \"*\")(version 3.0; acl \"Configuration Administrator\"; allow (all) userdn=\"ldap:///uid=admin,ou=Administrators,ou=TopologyManagement,o=NetscapeRoot\";)", // SIE Group $_aci, // Search Access, "(targetattr != \"userPassword\") (version 3.0; acl \"Search Access\"; allow (read,compare,search) (userdn = \"ldap:///{$inetdomainbasedn}??sub?(objectclass=*)\");)", // Service Search Access "(targetattr = \"*\") (version 3.0; acl \"Service Search Access\"; allow (read,compare,search) (userdn = \"ldap:///{$service_bind_dn}\");)", ), ), ); $replica_hosts = $this->list_replicas(); if (!empty($replica_hosts)) { foreach ($replica_hosts as $replica_host) { $ldap = new Net_LDAP3($this->config); $ldap->config_set("log_hook", array($this, "_log")); $ldap->config_set('hosts', array($replica_host)); $ldap->connect(); $ldap->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw); foreach ($entries as $dn => $attrs) { if (isset($attrs['nsslapd-directory'])) { $attrs['nsslapd-directory'] = $this->nsslapd_directory($ldap, $domain); } if (!$ldap->add_entry($dn, $attrs)) { $this->_log(LOG_ERR, "Error adding $dn to $replica_host"); } } $ldap->close(); } } else { foreach ($entries as $dn => $attrs) { if (isset($attrs['nsslapd-directory'])) { $attrs['nsslapd-directory'] = $this->nsslapd_directory($this, $domain); } if (!$this->add_entry($dn, $attrs)) { $this->_log(LOG_ERR, "Error adding $dn"); } } } if (!empty($replica_hosts)) { $this->add_replication_agreements($inetdomainbasedn); } // add OUs, do this after adding replication agreements $entries = array( "cn=Directory Administrators,$inetdomainbasedn" => array( 'cn' => 'Directory Administrators', 'objectclass' => array('top', 'groupofuniquenames'), 'uniquemember' => array('cn=Directory Manager'), ), "cn=kolab-admin,$inetdomainbasedn" => array( 'cn' => 'kolab-admin', 'objectclass' => array( 'top', 'ldapsubentry', 'nsroledefinition', 'nssimpleroledefinition', 'nsmanagedroledefinition', ), ), // @TODO: these OUs DN should be read from config "ou=Groups,$inetdomainbasedn" => array( 'ou' => 'Groups', 'objectclass' => array('top', 'organizationalunit'), ), "ou=People,$inetdomainbasedn" => array( 'ou' => 'People', 'objectclass' => array('top', 'organizationalunit'), ), "ou=Special Users,$inetdomainbasedn" => array( 'ou' => 'Special Users', 'objectclass' => array('top', 'organizationalunit'), ), "ou=Resources,$inetdomainbasedn" => array( 'ou' => 'Resources', 'objectclass' => array('top', 'organizationalunit'), ), "ou=Shared Folders,$inetdomainbasedn" => array( 'ou' => 'Shared Folders', 'objectclass' => array('top', 'organizationalunit'), ), ); // create set of OUs and other domain entries foreach ($entries as $dn => $attrs) { $this->add_entry($dn, $attrs); } return $domain_dn; } public function domain_edit($domain, $attributes, $typeid = null) { $domain = $this->domain_info($domain, array_keys($attributes)); if (empty($domain)) { return false; } $domain_dn = key($domain); // We should start throwing stuff over the fence here. return $this->modify_entry($domain_dn, $domain[$domain_dn], $attributes); } public function domain_delete($domain) { $domain = $this->domain_info($domain); if (empty($domain)) { return false; } $domain_dn = key($domain); $attributes = array_merge($domain[$domain_dn], array('inetdomainstatus' => 'deleted')); // for performance reasons we set only domain status, // cronjob script should delete such domain later return $this->modify_entry($domain_dn, $domain[$domain_dn], $attributes); } public function domain_find_by_attribute($attribute) { $base_dn = $this->conf->get('ldap', 'domain_base_dn'); return $this->entry_find_by_attribute($attribute, $base_dn); } public function domain_info($domain, $attributes = array('*')) { $this->_log(LOG_DEBUG, "Auth::LDAP::domain_info() for domain " . var_export($domain, true)); if (empty($domain)) { return false; } $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw); $domain_base_dn = $this->conf->get('ldap', 'domain_base_dn'); $domain_dn = $this->entry_dn($domain, array(), $domain_base_dn); if (!$domain_dn) { if ($result = $this->find_domain($domain, $attributes)) { $result_dn = $result['dn']; unset($result['dn']); $result = array($result_dn => $result); } } else { $result = $this->_read($domain_dn, $attributes); } $this->_log(LOG_DEBUG, "Auth::LDAP::domain_info() result: " . var_export($result, true)); return $result ? $result : false; } /** * Checkes if specified domain is empty (no users assigned) * * @param string|array $domain Domain name or domain_info() result * * @return bool True if domain is empty, False otherwise */ public function domain_is_empty($domain) { $domain_name_attribute = $this->conf->get('ldap', 'domain_name_attribute'); if (empty($domain_name_attribute)) { $domain_name_attribute = 'associateddomain'; } if (!is_array($domain)) { $domain = $this->domain_info($domain); } if (!empty($domain)) { $domain_dn = key($domain); $domain_name = $domain[$domain_dn][$domain_name_attribute]; if (is_array($domain_name)) { $domain_name = $domain_name[0]; } } else { return false; } $result = $this->list_users(array('entrydn'), null, array('page_size' => 1), $domain_name); return is_array($result) && $result['count'] == 0; } /** * Proxy to parent function in order to enable us to insert our * configuration. */ public function effective_rights($subject) { $ckey = $_SESSION['user']->user_bind_dn . '#' . md5($this->domain . '::' . $subject . '::' . $_SESSION['user']->user_bind_pw); // use memcache if ($result = $this->get_cache_data($ckey)) { return $result; } // use internal cache else if (isset($this->icache[$ckey])) { return $this->icache[$ckey]; } // Ensure we are bound with the user's credentials $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw); $this->_log(LOG_DEBUG, "Auth::LDAP::effective_rights(\$subject = '" . $subject . "')"); switch ($subject) { case "domain": $result = parent::effective_rights($this->conf->get("ldap", "domain_base_dn")); break; case "group": case "ou": case "resource": case "role": case "sharedfolder": case "user": $result = parent::effective_rights($this->_subject_base_dn($subject)); break; default: $result = parent::effective_rights($subject); } if (!$result) { $result = $this->legacy_rights($subject); } if (!$this->set_cache_data($ckey, $result)) { $this->icache[$ckey] = $result; } return $result; } public function find_recipient($address) { if (strpos($address, '@') === false) { return false; } $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw); $root_dn = $this->config_get('root_dn'); $mail_attrs = $this->conf->get_list('mail_attributes') ?: array('mail', 'alias'); $search = array('operator' => 'OR'); foreach ($mail_attrs as $num => $attr) { $search['params'][$attr] = array( 'type' => 'exact', 'value' => $address, ); } $result = $this->search_entries($root_dn, '(objectclass=*)', 'sub', $mail_attrs, array('search' => $search)); if ($result && $result->count() > 0) { return $result->entries(true); } return false; } public function get_attributes($subject_dn, $attributes) { $this->_log(LOG_DEBUG, "Auth::LDAP::get_attributes() for $subject_dn"); $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw); return $this->get_entry_attributes($subject_dn, $attributes); } public function group_add($attrs, $typeid = null) { $base_dn = $this->entry_base_dn('group', $typeid, $attrs); // TODO: The rdn is configurable as well. // Use [$type_str . "_"]user_rdn_attr $dn = "cn=" . Net_LDAP3::quote_string($attrs['cn'], true) . "," . $base_dn; return $this->entry_add($dn, $attrs); } public function group_delete($group) { return $this->entry_delete($group); } public function group_edit($group, $attributes, $typeid = null) { $group = $this->group_info($group, array_keys($attributes)); if (empty($group)) { return false; } $group_dn = key($group); // We should start throwing stuff over the fence here. return $this->modify_entry($group_dn, $group[$group_dn], $attributes); } public function group_find_by_attribute($attribute) { return $this->entry_find_by_attribute($attribute); } public function group_info($group, $attributes = array('*')) { $this->_log(LOG_DEBUG, "Auth::LDAP::group_info() for group " . var_export($group, true)); $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw); $group_dn = $this->entry_dn($group); if (!$group_dn) { return false; } $this->read_prepare($attributes); return $this->_read($group_dn, $attributes); } public function group_members_list($group, $recurse = true) { $group_dn = $this->entry_dn($group); if (!$group_dn) { return false; } return $this->list_group_members($group_dn, null, $recurse); } public function list_domains($attributes = array(), $search = array(), $params = array()) { $this->_log(LOG_DEBUG, "Auth::LDAP::list_domains(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true)); $section = $this->conf->get('kolab', 'auth_mechanism'); $base_dn = $this->conf->get($section, 'domain_base_dn'); $filter = $this->conf->get($section, 'domain_filter'); $kolab_filter = $this->conf->get($section, 'kolab_domain_filter'); if (empty($filter) && !empty($kolab_filter)) { $filter = $kolab_filter; } if (!$filter) { $filter = "(associateddomain=*)"; } return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params); } public function list_groups($attributes = array(), $search = array(), $params = array()) { $this->_log(LOG_DEBUG, "Auth::LDAP::list_groups(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true)); $base_dn = $this->_subject_base_dn('group'); $filter = $this->conf->get('group_filter'); if (!$filter) { $filter = "(|(objectclass=groupofuniquenames)(objectclass=groupofurls))"; } return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params); } public function list_organizationalunits($attributes = array(), $search = array(), $params = array()) { $this->_log(LOG_DEBUG, "Auth::LDAP::list_organizationalunits(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true)); $base_dn = $this->_subject_base_dn($params['type'] ? $params['type'] . '_ou' : 'ou'); $filter = $this->conf->get('ou_filter'); if (!$filter) { $filter = "(objectclass=organizationalunit)"; } return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params); } public function list_resources($attributes = array(), $search = array(), $params = array()) { $this->_log(LOG_DEBUG, "Auth::LDAP::list_resources(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true)); $base_dn = $this->_subject_base_dn('resource'); $filter = $this->conf->get('resource_filter'); if (!$filter) { $filter = "(&(objectclass=*)(!(objectclass=organizationalunit)))"; } return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params); } public function list_roles($attributes = array(), $search = array(), $params = array()) { $this->_log(LOG_DEBUG, "Auth::LDAP::list_roles(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true)); $base_dn = $this->_subject_base_dn('role'); $filter = $this->conf->get('role_filter'); if (empty($filter)) { $filter = "(&(objectclass=ldapsubentry)(objectclass=nsroledefinition))"; } return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params); } public function list_sharedfolders($attributes = array(), $search = array(), $params = array()) { $this->_log(LOG_DEBUG, "Auth::LDAP::list_sharedfolders(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true)); $base_dn = $this->_subject_base_dn('sharedfolder'); $filter = $this->conf->get('sharedfolder_filter'); if (!$filter) { $filter = "(&(objectclass=*)(!(objectclass=organizationalunit)))"; } return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params); } public function list_users($attributes = array(), $search = array(), $params = array(), $domain = null) { $this->_log(LOG_DEBUG, "Auth::LDAP::list_users(" . var_export($attributes, true) . ", " . var_export($search, true) . ", " . var_export($params, true) . ", " . $domain . ")"); $base_dn = $this->_subject_base_dn('user', false, $domain); $filter = $this->conf->get('user_filter'); if (empty($filter)) { $filter = "(objectclass=kolabinetorgperson)"; } return $this->_list($base_dn, $filter, 'sub', $attributes, $search, $params); } public function organizationalunit_add($attrs, $typeid = null) { $base_dn = $this->entry_base_dn('ou', $typeid, $attrs); // TODO: The rdn is configurable as well. // Use [$type_str . "_"]ou_rdn_attr $dn = "ou=" . Net_LDAP3::quote_string($attrs['ou'], true) . "," . $base_dn; return $this->entry_add($dn, $attrs); } public function organizationalunit_edit($ou, $attributes, $typeid = null) { $ou = $this->organizationalunit_info($ou, array_keys($attributes)); if (empty($ou)) { return false; } $dn = key($ou); // We should start throwing stuff over the fence here. return $this->modify_entry($dn, $ou[$dn], $attributes); } public function organizationalunit_delete($ou) { return $this->entry_delete($ou, array('objectclass' => 'organizationalunit')); } public function organizationalunit_find_by_attribute($attribute) { $attribute['objectclass'] = 'organizationalunit'; return $this->entry_find_by_attribute($attribute); } public function organizationalunit_info($ou, $attributes = array('*')) { $this->_log(LOG_DEBUG, "Auth::LDAP::organizationalunit_info() for unit " . var_export($ou, true)); $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw); $dn = $this->entry_dn($ou, array('objectclass' => 'organizationalunit')); if (!$dn) { return false; } $this->read_prepare($attributes); return $this->_read($dn, $attributes); } public function resource_add($attrs, $typeid = null) { $base_dn = $this->entry_base_dn('resource', $typeid, $attrs); // TODO: The rdn is configurable as well. // Use [$type_str . "_"]resource_rdn_attr $dn = "cn=" . Net_LDAP3::quote_string($attrs['cn'], true) . "," . $base_dn; return $this->entry_add($dn, $attrs); } public function resource_delete($resource) { return $this->entry_delete($resource); } public function resource_edit($resource, $attributes, $typeid = null) { $resource = $this->resource_info($resource, array_keys($attributes)); if (empty($resource)) { return false; } $resource_dn = key($resource); // We should start throwing stuff over the fence here. return $this->modify_entry($resource_dn, $resource[$resource_dn], $attributes); } public function resource_find_by_attribute($attribute) { return $this->entry_find_by_attribute($attribute); } public function resource_info($resource, $attributes = array('*')) { $this->_log(LOG_DEBUG, "Auth::LDAP::resource_info() for resource " . var_export($resource, true)); $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw); $resource_dn = $this->entry_dn($resource); if (!$resource_dn) { return false; } $this->read_prepare($attributes); return $this->_read($resource_dn, $attributes); } public function role_add($attrs, $typeid = null) { $base_dn = $this->entry_base_dn('role', $typeid, $attrs); // TODO: The rdn is configurable as well. // Use [$type_str . "_"]role_rdn_attr $dn = "cn=" . Net_LDAP3::quote_string($attrs['cn'], true) . "," . $base_dn; return $this->entry_add($dn, $attrs); } public function role_edit($role, $attributes, $typeid = null) { $role = $this->role_info($role, array_keys($attributes)); if (empty($role)) { return false; } $role_dn = key($role); // We should start throwing stuff over the fence here. return $this->modify_entry($role_dn, $role[$role_dn], $attributes); } public function role_delete($role) { return $this->entry_delete($role, array('objectclass' => 'ldapsubentry')); } public function role_find_by_attribute($attribute) { $attribute['objectclass'] = 'ldapsubentry'; return $this->entry_find_by_attribute($attribute); } public function role_info($role, $attributes = array('*')) { $this->_log(LOG_DEBUG, "Auth::LDAP::role_info() for role " . var_export($role, true)); $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw); $role_dn = $this->entry_dn($role, array('objectclass' => 'ldapsubentry')); if (!$role_dn) { return false; } $this->read_prepare($attributes); return $this->_read($role_dn, $attributes); } public function sharedfolder_add($attrs, $typeid = null) { $base_dn = $this->entry_base_dn('sharedfolder', $typeid, $attrs); // TODO: The rdn is configurable as well. // Use [$type_str . "_"]user_rdn_attr $dn = "cn=" . Net_LDAP3::quote_string($attrs['cn'], true) . "," . $base_dn; return $this->entry_add($dn, $attrs); } public function sharedfolder_delete($sharedfolder) { return $this->entry_delete($sharedfolder); } public function sharedfolder_edit($sharedfolder, $attributes, $typeid = null) { $sharedfolder = $this->sharedfolder_info($sharedfolder, array_keys($attributes)); if (empty($sharedfolder)) { return false; } $sharedfolder_dn = key($sharedfolder); // We should start throwing stuff over the fence here. return $this->modify_entry($sharedfolder_dn, $sharedfolder[$sharedfolder_dn], $attributes); } public function sharedfolder_find_by_attribute($attribute) { return $this->entry_find_by_attribute($attribute); } public function sharedfolder_info($sharedfolder, $attributes = array('*')) { $this->_log(LOG_DEBUG, "Auth::LDAP::sharedfolder_info() for sharedfolder " . var_export($sharedfolder, true)); $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw); $sharedfolder_dn = $this->entry_dn($sharedfolder); if (!$sharedfolder_dn) { return false; } $this->read_prepare($attributes); return $this->_read($sharedfolder_dn, $attributes); } public function search($base_dn, $filter = '(objectclass=*)', $scope = 'sub', $attributes = null, $props = array(), $count_only = false) { if (isset($_SESSION['user']->user_bind_dn) && !empty($_SESSION['user']->user_bind_dn)) { $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw); } return parent::search($base_dn, $filter, $scope, $attributes ?: array('*'), $props, $count_only); } public function subject_base_dn($subject, $strict = false) { return $this->_subject_base_dn($subject, $strict); } public function user_add($attrs, $typeid = null) { $base_dn = $this->entry_base_dn('user', $typeid, $attrs); // TODO: The rdn is configurable as well. // Use [$type_str . "_"]user_rdn_attr $dn = "uid=" . Net_LDAP3::quote_string($attrs['uid'], true) . "," . $base_dn; return $this->entry_add($dn, $attrs); } public function user_edit($user, $attributes, $typeid = null) { $user = $this->user_info($user, array_keys($attributes)); if (empty($user)) { return false; } $user_dn = key($user); // We should start throwing stuff over the fence here. $result = $this->modify_entry($user_dn, $user[$user_dn], $attributes); // Handle modification of current user data if (!empty($result) && $user_dn == $_SESSION['user']->user_bind_dn) { // update session password if (!empty($result['replace']) && !empty($result['replace']['userpassword'])) { $pass = $result['replace']['userpassword']; $_SESSION['user']->user_bind_pw = is_array($pass) ? implode($pass) : $pass; } } return $result; } public function user_delete($user) { return $this->entry_delete($user); } public function user_info($user, $attributes = array('*')) { $this->_log(LOG_DEBUG, "Auth::LDAP::user_info() for user " . var_export($user, true)); $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw); $user_dn = $this->entry_dn($user); if (!$user_dn) { return false; } $this->read_prepare($attributes); return $this->_read($user_dn, $attributes); } public function user_find_by_attribute($attribute) { return $this->entry_find_by_attribute($attribute); } /** * Returns attributes available in specified object classes */ public function attributes_allowed($objectclasses = array()) { $attributes = parent::attributes_allowed($objectclasses); // additional special attributes that aren't in LDAP schema $additional_attributes = array( 'top' => array('nsRoleDN'), '*' => array('aci'), ); if (!empty($attributes)) { foreach ($additional_attributes as $class => $attrs) { if (in_array($class, $objectclasses)) { $attributes['may'] = array_merge($attributes['may'], $attrs); } } $attributes['may'] = array_merge($attributes['may'], $additional_attributes['*']); } return $attributes; } /** * Find domain by name * * @param string $domain Domain name * @param array $attributes Result attributes * * @return array|bool Domain attributes (+ 'dn' attribute) or False on error */ public function find_domain($domain, $attributes = array('*')) { if (empty($domain)) { return false; } $ckey = 'domain::' . $domain . '::' . md5(implode(',', $attributes)); if (isset($this->icache[$ckey])) { return $this->icache[$ckey]; } // connect and bind... if ($_SESSION['user'] && $_SESSION['user']->user_bind_dn) { $bind_dn = $_SESSION['user']->user_bind_dn; $bind_pw = $_SESSION['user']->user_bind_pw; } else { $bind_dn = $this->conf->get('service_bind_dn'); $bind_pw = $this->conf->get('service_bind_pw'); } if (!$this->bind($bind_dn, $bind_pw)) { return false; } return parent::find_domain($domain, $attributes); } /** * Wrapper for search_entries() */ protected function _list($base_dn, $filter, $scope, $attributes, $search, $params) { $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw); if (!empty($params['sort_by'])) { if (is_array($params['sort_by'])) { foreach ($params['sort_by'] as $attrib) { if (!in_array($attrib, $attributes)) { $attributes[] = $attrib; } } } else { if (!in_array($params['sort_by'], $attributes)) { $attributes[] = $params['sort_by']; } } } if (!empty($params['page_size'])) { $this->config_set('page_size', $params['page_size']); } else { $this->config_set('page_size', 15); } if (!empty($params['page'])) { $this->config_set('list_page', $params['page']); } else { $this->config_set('list_page', 1); } if (empty($attributes) || !is_array($attributes)) { $attributes = array('*'); } // LDAP3 search parameters $opts = array( 'search' => $search, 'sort' => $params['sort_by'], // for VLV ); $result = $this->search_entries($base_dn, $filter, $scope, $attributes, $opts); $entries = $this->sort_and_slice($result, $params); return array( 'list' => $entries, 'count' => is_object($result) ? $result->count() : 0, ); } /** * Prepare environment before _read() call */ protected function read_prepare(&$attributes) { // always return unique attribute $unique_attr = $this->conf->get('unique_attribute'); if (empty($unique_attr)) { $unique_attr = 'nsuniqueid'; } $this->_log(LOG_NOTICE, "Using unique_attribute " . var_export($unique_attr, TRUE) . " at " . __FILE__ . ":" . __LINE__); if (!in_array($unique_attr, $attributes)) { $attributes[] = $unique_attr; } } /** * delete_entry() wrapper with binding and DN resolving */ protected function entry_delete($entry, $attributes = array(), $base_dn = null) { $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw); $entry_dn = $this->entry_dn($entry, $attributes, $base_dn); // object not found or self deletion if (!$entry_dn) { return false; } return $this->delete_entry($entry_dn); } /** * add_entry() wrapper with binding */ protected function entry_add($entry_dn, $attrs) { $this->bind($_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw); if ($this->add_entry($entry_dn, $attrs)) { return $entry_dn; } return false; } /** * Return base DN for specified object type */ protected function entry_base_dn($type, $typeid = null, &$attrs = array()) { // check if base_dn already exists in object attributes if (!empty($attrs)) { if (!empty($attrs['base_dn'])) { $base_dn = $attrs['base_dn']; unset($attrs['base_dn']); } else if ($type != 'ou' && !empty($attrs['ou'])) { $base_dn = $attrs['ou']; unset($attrs['ou']); } } if (empty($base_dn) && $typeid) { $db = SQL::get_instance(); $query = $db->query("SELECT `key` FROM `{$type}_types` WHERE `id` = ?", array($typeid)); $sql = $db->fetch_assoc($query); // Check if the type has a specific base DN specified. $base_dn = $this->_subject_base_dn($sql['key'] . '_' . $type, true); } if (empty($base_dn)) { $base_dn = $this->_subject_base_dn($type); } return $base_dn; } public function _log($level, $msg) { if (strstr($_SERVER["REQUEST_URI"], "/api/")) { $str = "(api) "; } else { $str = ""; } if (is_array($msg)) { $msg = implode("\n", $msg); } switch ($level) { case LOG_DEBUG: Log::debug($str . $msg); break; case LOG_ERR: case LOG_ALERT: case LOG_CRIT: case LOG_EMERG: Log::error($str . $msg); break; case LOG_INFO: Log::info($str . $msg); break; case LOG_WARNING: Log::warning($str . $msg); break; case LOG_NOTICE: default: Log::trace($str . $msg); break; } } private function _subject_base_dn($subject, $strict = false, $domain = null) { if (empty($domain)) { $domain = $this->domain; } $subject_base_dn = $this->conf->get_raw($domain, $subject . "_base_dn"); if (empty($subject_base_dn)) { $subject_base_dn = $this->conf->get_raw("ldap", $subject . "_base_dn"); } // This could be "_", if so we'll try the name only now if (empty($subject_base_dn) && ($pos = strrpos($subject, '_'))) { $subject = substr($subject, $pos + 1); $subject_base_dn = $this->conf->get_raw($domain, $subject . "_base_dn"); if (empty($subject_base_dn)) { $subject_base_dn = $this->conf->get_raw("ldap", $subject . "_base_dn"); } } if (empty($subject_base_dn) && $strict) { $this->_log(LOG_DEBUG, "subject_base_dn for subject $subject not found"); return null; } // Attempt to get a configured base_dn $base_dn = $this->conf->get($domain, "base_dn"); if (empty($base_dn)) { $base_dn = $this->domain_root_dn($domain); } if (!empty($subject_base_dn)) { $base_dn = $this->conf->expand($subject_base_dn, array("base_dn" => $base_dn)); } $this->_log(LOG_DEBUG, "subject_base_dn for subject $subject is $base_dn"); return $base_dn; } private function legacy_rights($subject) { $subject_dn = $this->entry_dn($subject); $user_is_admin = false; $user_is_self = false; // List group memberships $user_groups = $this->find_user_groups($_SESSION['user']->user_bind_dn); foreach ($user_groups as $user_group_dn) { if ($user_is_admin) continue; $user_group_dn_components = ldap_explode_dn($user_group_dn, 1); unset($user_group_dn_components["count"]); $user_group_cn = array_shift($user_group_dn_components); if (in_array($user_group_cn, array('admin', 'maintainer', 'domain-maintainer'))) { // All rights default to write. $user_is_admin = true; } else { // The user is a regular user, see if the subject is the same has the // user session's bind_dn. if ($subject_dn == $_SESSION['user']->user_bind_dn) { $user_is_self = true; } } } if ($user_is_admin) { $standard_rights = array("add", "delete", "read", "write"); } elseif ($user_is_self) { $standard_rights = array("read", "write"); } else { $standard_rights = array("read"); } $rights = array( - 'entryLevelRights' => $standard_rights, - 'attributeLevelRights' => array(), + 'entrylevelrights' => $standard_rights, + 'attributelevelrights' => array(), ); $subject = $this->search($subject_dn); if (!$subject) { return $rights; } $subject = $subject->entries(true); $attributes = $this->attributes_allowed($subject[$subject_dn]['objectclass']); $attributes = array_merge((array)$attributes['may'], (array)$attributes['must']); foreach ($attributes as $attribute) { - $rights['attributeLevelRights'][$attribute] = $standard_rights; + $rights['attributelevelrights'][$attribute] = $standard_rights; } return $rights; } private function sort_and_slice(&$result, &$params) { if (!is_object($result)) { return array(); } $entries = $result->entries(true); if ($this->vlv_active) { return $entries; } if (!empty($params) && is_array($params)) { if (!empty($params['sort_by'])) { $this->sort_result_key = $params['sort_by']; uasort($entries, array($this, 'sort_result')); } if (!empty($params['sort_order']) && $params['sort_order'] == "DESC") { $entries = array_reverse($entries, true); } if (!empty($params['page_size']) && !empty($params['page'])) { if ($result->count() > $params['page_size']) { $entries = array_slice($entries, (($params['page'] - 1) * $params['page_size']), $params['page_size'], true); } } } return $entries; } /** * Result sorting callback for uasort() */ private function sort_result($a, $b) { if (is_array($this->sort_result_key)) { foreach ($this->sort_result_key as $attrib) { if (array_key_exists($attrib, $a) && !$str1) { $str1 = $a[$attrib]; } if (array_key_exists($attrib, $b) && !$str2) { $str2 = $b[$attrib]; } } } else { $str1 = $a[$this->sort_result_key]; $str2 = $b[$this->sort_result_key]; } if (is_array($str1)) { $str1 = array_shift($str1); } if (is_array($str2)) { $str2 = array_shift($str2); } return strcmp(mb_strtoupper($str1), mb_strtoupper($str2)); } /** * Qualify a username. * * Where username is 'kanarip@kanarip.com', the function will return an * array containing 'kanarip' and 'kanarip.com'. However, where the * username is 'kanarip', the domain name is to be assumed the * management domain name. */ private function _qualify_id($username) { $username_parts = explode('@', $username); if (count($username_parts) == 1) { $domain_name = $this->conf->get('primary_domain'); } else { $domain_name = array_pop($username_parts); } return array(implode('@', $username_parts), $domain_name); } /*********************************************************** ************ Shortcut functions **************** ***********************************************************/ protected function _read($entry_dn, $attributes = array('*')) { $result = $this->search($entry_dn, '(objectclass=*)', 'base', $attributes); if ($result) { $this->_log(LOG_DEBUG, "Auth::LDAP::_read() result: " . var_export($result->entries(true), true)); return $result->entries(true); } else { return false; } } /** * Finds nsslapd-directory for specified domain */ protected function nsslapd_directory($ldap, $domain) { $primary_domain = $this->conf->get('kolab', 'primary_domain'); $_primary_domain = str_replace('.', '_', $primary_domain); $_domain = str_replace('.', '_', $domain); $roots = array($_primary_domain, $primary_domain, 'userRoot'); foreach ($roots as $root) { if ($result = $ldap->get_entry("cn=$root,cn=ldbm database,cn=plugins,cn=config")) { break; } } $this->_log(LOG_DEBUG, "Primary domain ldbm database configuration entry: " . var_export($result, true)); $result = $result[key($result)]; $orig_directory = $result['nsslapd-directory']; $directory = $orig_directory; reset($roots); foreach ($roots as $root) { if ($directory == $orig_directory) { $directory = str_replace($root, $_domain, $result['nsslapd-directory']); } } $this->_log(LOG_DEBUG, "nsslapd-directory for domain $domain is $directory"); return $directory; } } diff --git a/lib/api/kolab_api_service_domain.php b/lib/api/kolab_api_service_domain.php index 9190e62..9781eeb 100644 --- a/lib/api/kolab_api_service_domain.php +++ b/lib/api/kolab_api_service_domain.php @@ -1,422 +1,422 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Jeroen van Meeuwen | +--------------------------------------------------------------------------+ */ /** * Service providing domain mutations */ class kolab_api_service_domain extends kolab_api_service { /** * Returns service capabilities. * * @param string $domain Domain name * * @return array Capabilities list */ public function capabilities($domain) { $auth = Auth::get_instance(); $conf = Conf::get_instance(); $domain_base_dn = $conf->get('domain_base_dn'); if (empty($domain_base_dn)) { return array(); } $effective_rights = $auth->list_rights($domain_base_dn); $rights = array(); - if (in_array('add', $effective_rights['entryLevelRights'])) { + if (in_array('add', $effective_rights['entrylevelrights'])) { $rights['add'] = "w"; } - if (in_array('delete', $effective_rights['entryLevelRights'])) { + if (in_array('delete', $effective_rights['entrylevelrights'])) { $rights['delete'] = "w"; } - if (in_array('modrdn', $effective_rights['entryLevelRights'])) { + if (in_array('modrdn', $effective_rights['entrylevelrights'])) { $rights['edit'] = "w"; } - if (in_array('read', $effective_rights['entryLevelRights'])) { + if (in_array('read', $effective_rights['entrylevelrights'])) { $rights['find'] = "r"; $rights['info'] = "r"; } $rights['effective_rights'] = "r"; return $rights; } public function domain_add($getdata, $postdata) { Log::trace("domain.add(\$getdata = " . var_export($getdata, TRUE) . ", \$postdata = " . var_export($postdata, TRUE) . ")"); $conf = Conf::get_instance(); $dna = $conf->get('domain_name_attribute'); if (empty($dna)) { $dna = 'associateddomain'; } if (empty($postdata[$dna])) { Log::error("domain.add called without '" . $dna . "' specified"); return false; } $auth = Auth::get_instance($conf->get('kolab', 'primary_domain')); // parse input attributes $attributes = $this->parse_input_attributes('domain', $postdata); $attributes[$dna] = (array) $attributes[$dna]; $domain = array_shift($attributes[$dna]); $this->_mod_domain_attrs($domain, $attributes); $result = $auth->domain_add($domain, $attributes); if ($result) { if ($id = $this->unique_attribute_value($result)) { $attributes['id'] = $id; } $this->_after_domain_created($attributes, $domain); return $attributes; } return false; } /** * Domain delete. * * @param array $get GET parameters * @param array $post POST parameters * * @return bool True on success, False on failure * @throws kolab_api_exception */ public function domain_delete($getdata, $postdata) { Log::trace("domain.delete(\$getdata = '" . var_export($getdata, TRUE) . "', \$postdata = '" . var_export($postdata, TRUE) . "')"); if (empty($postdata['id'])) { Log::error("domain.delete called without a Domain ID"); return false; } $auth = Auth::get_instance(); // check if domain is empty if (empty($postdata['force']) || strtolower($postdata['force']) == 'false') { if (!$auth->domain_is_empty($postdata['id'])) { throw new kolab_api_exception(kolab_api_exception::DOMAIN_NOT_EMPTY); } } $result = $auth->domain_delete($postdata['id']); if ($result) { return $result; } return false; } public function domain_edit($getdata, $postdata) { Log::trace("domain.edit(\$getdata = '" . var_export($getdata, TRUE) . "', \$postdata = '" . var_export($postdata, TRUE) . "')"); if (empty($postdata['id'])) { Log::error("domain.edit called without a Domain ID"); return false; } $auth = Auth::get_instance(); // check if domain is empty when changing status to deleted, as in domain.delete if ($postdata['inetdomainstatus'] == 'deleted' && (empty($postdata['force']) || strtolower($postdata['force']) == 'false') ) { $domain = $auth->domain_info($postdata['id']); if (!empty($domain) && $domain[key($domain)]['inetdomainstatus'] != 'deleted' && !$auth->domain_is_empty($domain) ) { throw new kolab_api_exception(kolab_api_exception::DOMAIN_NOT_EMPTY); } } $attributes = $this->parse_input_attributes('domain', $postdata, $postdata['type_id']); $this->_mod_domain_attrs(null, $attributes); $result = $auth->domain_edit($postdata['id'], $attributes, $postdata['type_id']); if ($result) { return $result; } return false; } public function domain_effective_rights($getdata, $postdata) { $auth = Auth::get_instance(); $conf = Conf::get_instance(); $dna = $conf->get('domain_name_attribute'); if (empty($dna)) { $dna = 'associateddomain'; } // TODO: Input validation if (!empty($getdata[$dna])) { $entry_dn = $getdata[$dna]; $unique_attr = self::unique_attribute(); $domain = $auth->domain_find_by_attribute(array($unique_attr => $entry_dn)); if (!empty($domain)) { $entry_dn = key($domain); } } else { $entry_dn = $conf->get('ldap', 'domain_base_dn'); } // TODO: Fix searching the correct base_dn... Perhaps find the entry // first. $effective_rights = $auth->list_rights($entry_dn); return $effective_rights; } public function domain_find($getdata, $postdata) { $conf = Conf::get_instance(); $dna = $conf->get('domain_name_attribute'); if (empty($dna)) { $dna = 'associateddomain'; } if (empty($getdata[$dna])) { Log::error("domain.find called without a '" . $dna . "' parameter"); return false; } $auth = Auth::get_instance(); $domain = $auth->domain_find_by_attribute(array($dna => $getdata[$dna])); if (!empty($domain)) { return $domain; } return false; } /** * Domain information. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool Domain attributes, False on error */ public function domain_info($getdata, $postdata) { Log::trace("domain.info(\$getdata = '" . var_export($getdata, TRUE) . "', \$postdata = '" . var_export($postdata, TRUE) . "')"); if (empty($getdata['id'])) { Log::error("domain.info called without a Domain ID"); return false; } $auth = Auth::get_instance(); $attrs = $this->object_attributes('domain'); $result = $auth->domain_info($getdata['id'], $attrs); // normalize result $result = $this->parse_result_attributes('domain', $result); if (empty($result['id'])) { $result['id'] = $getdata['id']; } if ($result) { return $result; } return false; } /** * Modify hosted domain attributes */ protected function _mod_domain_attrs($domain, &$attributes) { // Generate attributes (aci, inetdomainbasedn) for hosted domains $conf = Conf::get_instance(); if ($conf->get('kolab_wap', 'hosted_root_dn')) { $domain_name_attribute = $conf->get('ldap', 'domain_name_attribute'); $hosted_root_dn = $conf->get('kolab_wap', 'hosted_root_dn'); $mgmt_root_dn = $conf->get('kolab_wap', 'mgmt_root_dn'); if (empty($mgmt_root_dn)) { $mgmt_root_dn = $conf->get('root_dn'); } if (empty($domain_name_attribute)) { $domain_name_attribute = 'associateddomain'; } if (!is_array($attributes[$domain_name_attribute])) { $attributes[$domain_name_attribute] = (array) $attributes[$domain_name_attribute]; } if (empty($domain)) { $domain = $attributes[$domain_name_attribute][0]; } if (!in_array($domain, $attributes[$domain_name_attribute])) { array_unshift($attributes[$domain_name_attribute], $domain); } $domain_root_dn = 'ou=' . $domain . ',' . $hosted_root_dn; $aci = array( '(targetattr = "*")' . '(version 3.0; acl "Deny Unauthorized"; deny (all)' . '(userdn != "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . ' || ' . 'ldap:///ou=People,' . $domain_root_dn . '??sub?(objectclass=inetorgperson)") AND NOT ' . 'roledn = "ldap:///cn=kolab-admin,' . $mgmt_root_dn . '";)', '(targetattr != "userPassword")' . '(version 3.0;acl "Search Access";allow (read,compare,search)' . '(userdn = "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . ' || ' . 'ldap:///ou=People,' . $domain_root_dn . '??sub?(objectclass=inetorgperson)");)', '(targetattr = "*")' . '(version 3.0;acl "Kolab Administrators";allow (all)' . '(roledn = "ldap:///cn=kolab-admin,' . $domain_root_dn . ' || ' . 'ldap:///cn=kolab-admin,' . $mgmt_root_dn . '");)' ); $attributes['aci'] = $aci; $attributes['inetdomainbasedn'] = $domain_root_dn; $this->is_hosted = true; } } /** * Create LDAP object related to the new hosted domain */ protected function _after_domain_created($attributes, $domain) { if (!$this->is_hosted) { return; } $conf = Conf::get_instance(); $ou_service = $this->controller->get_service('ou'); $role_service = $this->controller->get_service('role'); $hosted_root_dn = $conf->get('kolab_wap', 'hosted_root_dn'); $mgmt_root_dn = $conf->get('kolab_wap', 'mgmt_root_dn'); $domain_root_dn = 'ou=' . $domain . ',' . $hosted_root_dn; if (empty($mgmt_root_dn)) { $mgmt_root_dn = $conf->get('root_dn'); } $ou_domain = array( 'ou' => $domain, 'base_dn' => $hosted_root_dn, 'description' => $domain, 'type_id' => 1, ); $ou_domain['aci'] = array( '(targetattr = "*")' . '(version 3.0;acl "Deny Unauthorized"; deny (all)' . '(userdn != "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . ' || ' . 'ldap:///ou=People,' . $domain_root_dn . '??sub?(objectclass=inetorgperson)") AND NOT ' . 'roledn = "ldap:///cn=kolab-admin,' . $mgmt_root_dn . '";)', '(targetattr != "userPassword")' . '(version 3.0;acl "Search Access";allow (read,compare,search,write)' . '(userdn = "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . ' || ' . 'ldap:///ou=People,' . $domain_root_dn . '??sub?(objectclass=inetorgperson)");)', '(targetattr = "*")' . '(version 3.0;acl "Kolab Administrators";allow (all)' . '(roledn = "ldap:///cn=kolab-admin,' . $domain_root_dn . ' || ' . 'ldap:///cn=kolab-admin,' . $mgmt_root_dn . '");)', '(target = "ldap:///ou=*,' . $domain_root_dn . '")(targetattr="objectclass || aci || ou")' . '(version 3.0;acl "Allow Domain sub-OU Registration"; allow (add)' . '(userdn = "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . '");)', '(target = "ldap:///uid=*,ou=People,' . $domain_root_dn . '")(targetattr="*")' . '(version 3.0;acl "Allow Domain First User Registration"; allow (add)' . '(userdn = "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . '");)', '(target = "ldap:///cn=*,' . $domain_root_dn . '")(targetattr="objectclass || cn")' . '(version 3.0;acl "Allow Domain Role Registration"; allow (add)' . '(userdn = "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . '");)', ); $ou_service->ou_add(null, $ou_domain); // Add OU trees foreach (array('Groups', 'People', 'Resources', 'Shared Folders') as $item) { $ou = array( 'ou' => $item, 'base_dn' => $domain_root_dn, 'type_id' => 1, 'description' => $item, ); $ou_service->ou_add(null, $ou); } // Add an admin role $role = array( 'cn' => 'kolab-admin', 'description' => 'Domain Administrator', 'type_id' => 1, 'base_dn' => $domain_root_dn, ); $role_service->role_add(null, $role); } } diff --git a/lib/api/kolab_api_service_domains.php b/lib/api/kolab_api_service_domains.php index 2373877..6b4c4c3 100644 --- a/lib/api/kolab_api_service_domains.php +++ b/lib/api/kolab_api_service_domains.php @@ -1,103 +1,103 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Jeroen van Meeuwen | +--------------------------------------------------------------------------+ */ /** * Service providing domains listing */ class kolab_api_service_domains extends kolab_api_service { public $list_attribs = array( 'associateddomain', 'inetdomainstatus', 'objectclass', 'entrydn', ); /** * Returns service capabilities. * * @param string $domain Domain name * * @return array Capabilities list */ public function capabilities($domain) { $auth = Auth::get_instance(); $conf = Conf::get_instance(); $domain_base_dn = $conf->get('domain_base_dn'); if (empty($domain_base_dn)) { return array(); } $effective_rights = $auth->list_rights($domain_base_dn); $rights = array(); - if (in_array('add', $effective_rights['entryLevelRights'])) { + if (in_array('add', $effective_rights['entrylevelrights'])) { $rights['list'] = "r"; } - if (in_array('delete', $effective_rights['entryLevelRights'])) { + if (in_array('delete', $effective_rights['entrylevelrights'])) { $rights['list'] = "r"; } - if (in_array('modrdn', $effective_rights['entryLevelRights'])) { + if (in_array('modrdn', $effective_rights['entrylevelrights'])) { $rights['list'] = "r"; } - if (in_array('read', $effective_rights['entryLevelRights'])) { + if (in_array('read', $effective_rights['entrylevelrights'])) { $rights['list'] = "r"; } $rights['effective_rights'] = "r"; return $rights; } /** * Users listing (with searching). * * @param array $get GET parameters * @param array $post POST parameters * * @return array List result with 'list' and 'count' items */ public function domains_list($get, $post) { $auth = Auth::get_instance(); $attributes = $this->parse_list_attributes($post); $params = $this->parse_list_params($post); $search = $this->parse_list_search($post); $domains = $auth->list_domains($attributes, $search, $params); $domains = $this->parse_list_result($domains); return $domains; } } diff --git a/lib/api/kolab_api_service_group.php b/lib/api/kolab_api_service_group.php index 3b3d547..5609cf1 100644 --- a/lib/api/kolab_api_service_group.php +++ b/lib/api/kolab_api_service_group.php @@ -1,238 +1,238 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Jeroen van Meeuwen | +--------------------------------------------------------------------------+ */ /** * */ class kolab_api_service_group extends kolab_api_service { /** * Returns service capabilities. * * @param string $domain Domain name * * @return array Capabilities list */ public function capabilities($domain) { //console("kolab_api_service_group::capabilities"); $auth = Auth::get_instance($domain); $effective_rights = $auth->list_rights('group'); $rights = array(); - if (in_array('add', $effective_rights['entryLevelRights'])) { + if (in_array('add', $effective_rights['entrylevelrights'])) { $rights['add'] = "w"; } - if (in_array('delete', $effective_rights['entryLevelRights'])) { + if (in_array('delete', $effective_rights['entrylevelrights'])) { $rights['delete'] = "w"; } - if (in_array('modrdn', $effective_rights['entryLevelRights'])) { + if (in_array('modrdn', $effective_rights['entrylevelrights'])) { $rights['edit'] = "w"; } - if (in_array('read', $effective_rights['entryLevelRights'])) { + if (in_array('read', $effective_rights['entrylevelrights'])) { $rights['info'] = "r"; $rights['find'] = "r"; $rights['members_list'] = "r"; } $rights['effective_rights'] = "r"; return $rights; } /** * Group create. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool Group attributes or False on failure */ public function group_add($getdata, $postdata) { $attributes = $this->parse_input_attributes('group', $postdata); $auth = Auth::get_instance(); $result = $auth->group_add($attributes, $postdata['type_id']); if ($result) { if ($id = $this->unique_attribute_value($result)) { $attributes['id'] = $id; } return $attributes; } return FALSE; } /** * Group delete. * * @param array $get GET parameters * @param array $post POST parameters * * @return bool True on success, False on failure */ public function group_delete($getdata, $postdata) { if (empty($postdata['id'])) { return FALSE; } // TODO: Input validation $auth = Auth::get_instance(); $result = $auth->group_delete($postdata['id']); if ($result) { return $result; } return FALSE; } public function group_edit($getdata, $postdata) { //console("group_edit \$postdata", $postdata); $group_attributes = $this->parse_input_attributes('group', $postdata); $auth = Auth::get_instance(); $result = $auth->group_edit($postdata['id'], $group_attributes, $postdata['type_id']); // @TODO: return unique attribute or all attributes as group_add() if ($result) { return true; } return false; } public function group_effective_rights($getdata, $postdata) { $auth = Auth::get_instance(); $effective_rights = $auth->list_rights(empty($getdata['id']) ? 'group' : $getdata['id']); return $effective_rights; } /** * Group information. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool Group attributes or False on failure */ public function group_info($getdata, $postdata) { if (empty($getdata['id'])) { return false; } $auth = Auth::get_instance(); $attrs = $this->object_attributes('group'); $result = $auth->group_info($getdata['id'], $attrs); // normalize result $result = $this->parse_result_attributes('group', $result); Log::trace("group_info() result: " . var_export($result, TRUE)); if ($result) { return $result; } return false; } /** * Find group and return its data. * It is a combination of group.info and groups.list with search capabilities * If the search returns only one record we'll return group data. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool Group attributes, False on error */ public function group_find($get, $post) { $auth = Auth::get_instance(); $attributes = array(''); $params = array('page_size' => 2); $search = $this->parse_list_search($post); // find group(s) $groups = $auth->list_groups(null, $attributes, $search, $params); if (empty($groups) || empty($groups['list']) || $groups['count'] > 1) { return false; } // get group data $attrs = $this->object_attributes('group'); $result = $auth->group_info(key($groups['list']), $attrs); // normalize result $result = $this->parse_result_attributes('group', $result); if ($result) { return $result; } return false; } /** * Group members listing. * * @param array $get GET parameters * @param array $post POST parameters * * @return array List of group members ('list' and 'count' items) */ public function group_members_list($getdata, $postdata) { Log::trace("group_members_list() for group " . $getdata['id']); $auth = Auth::get_instance(); if (empty($getdata['id'])) { //console("Empty \$getdata['id']"); return FALSE; } $result = $auth->group_members_list($getdata['id'], false); Log::trace("group_members_list() result: " . var_export($result, TRUE)); return array( 'list' => $result, 'count' => count($result), ); } } diff --git a/lib/api/kolab_api_service_ou.php b/lib/api/kolab_api_service_ou.php index 7d0c7d7..aa3c03c 100644 --- a/lib/api/kolab_api_service_ou.php +++ b/lib/api/kolab_api_service_ou.php @@ -1,222 +1,222 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Jeroen van Meeuwen | +--------------------------------------------------------------------------+ */ /** * */ class kolab_api_service_ou extends kolab_api_service { /** * Returns service capabilities. * * @param string $domain Domain name * * @return array Capabilities list */ public function capabilities($domain) { $auth = Auth::get_instance($domain); $effective_rights = $auth->list_rights('ou'); $rights = array(); - if (in_array('add', $effective_rights['entryLevelRights'])) { + if (in_array('add', $effective_rights['entrylevelrights'])) { $rights['add'] = "w"; } - if (in_array('delete', $effective_rights['entryLevelRights'])) { + if (in_array('delete', $effective_rights['entrylevelrights'])) { $rights['delete'] = "w"; } - if (in_array('modrdn', $effective_rights['entryLevelRights'])) { + if (in_array('modrdn', $effective_rights['entrylevelrights'])) { $rights['edit'] = "w"; } - if (in_array('read', $effective_rights['entryLevelRights'])) { + if (in_array('read', $effective_rights['entrylevelrights'])) { $rights['info'] = "r"; $rights['find'] = "r"; $rights['members_list'] = "r"; } $rights['effective_rights'] = "r"; return $rights; } /** * Create organizational unit. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool Unit attributes or False on failure */ public function ou_add($getdata, $postdata) { $attributes = $this->parse_input_attributes('ou', $postdata); $auth = Auth::get_instance(); $result = $auth->organizationalunit_add($attributes, $postdata['type_id']); if ($result) { if ($id = $this->unique_attribute_value($result)) { $attributes['id'] = $id; } return $attributes; } return FALSE; } /** * Delete organizational unit. * * @param array $get GET parameters * @param array $post POST parameters * * @return bool True on success, False on failure */ public function ou_delete($getdata, $postdata) { if (empty($postdata['id'])) { return FALSE; } // TODO: Input validation $auth = Auth::get_instance(); $result = $auth->organizationalunit_delete($postdata['id']); if ($result) { return $result; } return FALSE; } public function ou_edit($getdata, $postdata) { $unit_attributes = $this->parse_input_attributes('ou', $postdata); $auth = Auth::get_instance(); $result = $auth->organizationalunit_edit($postdata['id'], $unit_attributes, $postdata['type_id']); // @TODO: return unique attribute or all attributes as ou_add() if ($result) { return true; } return false; } public function ou_effective_rights($getdata, $postdata) { $auth = Auth::get_instance(); // Org. units are special in that they are ldapsubentries. if (!empty($getdata['id'])) { $unique_attr = self::unique_attribute(); $unit = $auth->organizationalunit_find_by_attribute(array($unique_attr => $getdata['id'])); if (is_array($unit) && count($unit) == 1) { $unit_dn = key($unit); } } $effective_rights = $auth->list_rights(empty($unit_dn) ? 'ou' : $unit_dn); return $effective_rights; } /** * Organizational unit information. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool Unit attributes or False on failure */ public function ou_info($getdata, $postdata) { if (empty($getdata['id'])) { return false; } $auth = Auth::get_instance(); $attrs = $this->object_attributes('ou'); $result = $auth->organizationalunit_info($getdata['id'], $attrs); // normalize result $result = $this->parse_result_attributes('ou', $result); if ($result) { // get base_dn "attribute" for the API client $dn = substr($result['entrydn'], strlen($result['ou']) + 4); if (strpos($dn, 'ou=') === 0) { $result['base_dn'] = $dn; } return $result; } return false; } /** * Find organizational unit and return its data. * It is a combination of ou.info and ous.list with search capabilities * If the search returns only one record we'll return unit data. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool Unit attributes, False on error */ public function ou_find($get, $post) { $auth = Auth::get_instance(); $attributes = array(''); $params = array('page_size' => 2); $search = $this->parse_list_search($post); // find OU(s) $units = $auth->list_organizationalunits(null, $attributes, $search, $params); if (empty($units) || empty($units['list']) || $units['count'] > 1) { return false; } // get OU data $attrs = $this->object_attributes('ou'); $result = $auth->organizationalunit_info(key($units['list']), $attrs); // normalize result $result = $this->parse_result_attributes('ou', $result); if ($result) { return $result; } return false; } } diff --git a/lib/api/kolab_api_service_resource.php b/lib/api/kolab_api_service_resource.php index 4e96070..7855aec 100644 --- a/lib/api/kolab_api_service_resource.php +++ b/lib/api/kolab_api_service_resource.php @@ -1,212 +1,212 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Jeroen van Meeuwen | +--------------------------------------------------------------------------+ */ /** * Service providing resource data management */ class kolab_api_service_resource extends kolab_api_service { /** * Returns service capabilities. * * @param string $domain Domain name * * @return array Capabilities list */ public function capabilities($domain) { //console("kolab_api_service_group::capabilities"); $auth = Auth::get_instance($domain); $effective_rights = $auth->list_rights('resource'); $rights = array(); - if (in_array('add', $effective_rights['entryLevelRights'])) { + if (in_array('add', $effective_rights['entrylevelrights'])) { $rights['add'] = "w"; } - if (in_array('delete', $effective_rights['entryLevelRights'])) { + if (in_array('delete', $effective_rights['entrylevelrights'])) { $rights['delete'] = "w"; } - if (in_array('modrdn', $effective_rights['entryLevelRights'])) { + if (in_array('modrdn', $effective_rights['entrylevelrights'])) { $rights['edit'] = "w"; } - if (in_array('read', $effective_rights['entryLevelRights'])) { + if (in_array('read', $effective_rights['entrylevelrights'])) { $rights['info'] = "r"; $rights['find'] = "r"; } $rights['effective_rights'] = "r"; return $rights; } /** * Create resource. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool User attributes or False on error. */ public function resource_add($getdata, $postdata) { $attributes = $this->parse_input_attributes('resource', $postdata); $auth = Auth::get_instance(); $result = $auth->resource_add($attributes, $postdata['type_id']); if ($result) { if ($id = $this->unique_attribute_value($result)) { $attributes['id'] = $id; } return $attributes; } return false; } /** * Detete resource. * * @param array $get GET parameters * @param array $post POST parameters * * @return bool True on success, False on failure */ public function resource_delete($getdata, $postdata) { //console("resource_delete()", $getdata, $postdata); if (!isset($postdata['id'])) { return false; } // TODO: Input validation $auth = Auth::get_instance(); $result = $auth->resource_delete($postdata['id']); if ($result) { return $result; } return false; } public function resource_edit($getdata, $postdata) { //console("\$postdata to resource_edit()", $postdata); $resource_attributes = $this->parse_input_attributes('resource', $postdata); //console("\$resource_attributes as result from parse_input_attributes", $resource_attributes); $auth = Auth::get_instance(); $result = $auth->resource_edit($postdata['id'], $resource_attributes, $postdata['type_id']); // Return the $mod_array if ($result) { return $result; } return false; } public function resource_effective_rights($getdata, $postdata) { $auth = Auth::get_instance(); $effective_rights = $auth->list_rights(empty($getdata['id']) ? 'resource' : $getdata['id']); return $effective_rights; } /** * Find resource and return its data. * It is a combination of resource.info and resources.list with search capabilities * If the search returns only one record we'll return resource data. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool Resource attributes, False on error */ public function resource_find($get, $post) { $auth = Auth::get_instance(); $attributes = array(''); $params = array('page_size' => 2); $search = $this->parse_list_search($post); // find resource(s) $resources = $auth->list_resources(null, $attributes, $search, $params); if (empty($resources) || empty($resources['list']) || $resources['count'] > 1) { return false; } // get resource data $attrs = $this->object_attributes('resource'); $result = $auth->resource_info(key($resources['list']), $attrs); // normalize result $result = $this->parse_result_attributes('resource', $result); if ($result) { return $result; } return false; } /** * User information. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool User attributes, False on error */ public function resource_info($getdata, $postdata) { if (!isset($getdata['id'])) { return false; } $auth = Auth::get_instance(); $attrs = $this->object_attributes('resource'); $result = $auth->resource_info($getdata['id'], $attrs); // normalize result $result = $this->parse_result_attributes('resource', $result); if ($result) { return $result; } return false; } } diff --git a/lib/api/kolab_api_service_role.php b/lib/api/kolab_api_service_role.php index f6353b0..2873a12 100644 --- a/lib/api/kolab_api_service_role.php +++ b/lib/api/kolab_api_service_role.php @@ -1,245 +1,245 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Jeroen van Meeuwen | +--------------------------------------------------------------------------+ */ /** * */ class kolab_api_service_role extends kolab_api_service { /** * Returns service capabilities. * * @param string $domain Domain name * * @return array Capabilities list */ public function capabilities($domain) { //console("kolab_api_service_role::capabilities"); $auth = Auth::get_instance($domain); $effective_rights = $auth->list_rights('role'); $rights = array(); - if (in_array('add', $effective_rights['entryLevelRights'])) { + if (in_array('add', $effective_rights['entrylevelrights'])) { $rights['add'] = "w"; } - if (in_array('delete', $effective_rights['entryLevelRights'])) { + if (in_array('delete', $effective_rights['entrylevelrights'])) { $rights['delete'] = "w"; } - if (in_array('modrdn', $effective_rights['entryLevelRights'])) { + if (in_array('modrdn', $effective_rights['entrylevelrights'])) { $rights['edit'] = "w"; } - if (in_array('read', $effective_rights['entryLevelRights'])) { + if (in_array('read', $effective_rights['entrylevelrights'])) { $rights['info'] = "r"; $rights['find'] = "r"; $rights['members_list'] = "r"; } $rights['effective_rights'] = "r"; return $rights; } /** * Group create. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool Group attributes or False on failure */ public function role_add($getdata, $postdata) { $attributes = $this->parse_input_attributes('role', $postdata); $auth = Auth::get_instance(); $result = $auth->role_add($attributes, $postdata['type_id']); if ($result) { if ($id = $this->unique_attribute_value($result)) { $attributes['id'] = $id; } return $attributes; } return false; } /** * Group delete. * * @param array $get GET parameters * @param array $post POST parameters * * @return bool True on success, False on failure */ public function role_delete($getdata, $postdata) { if (empty($postdata['id'])) { return false; } // TODO: Input validation $auth = Auth::get_instance(); $result = $auth->role_delete($postdata['id']); if ($result) { return $result; } return false; } public function role_edit($getdata, $postdata) { //console("role_edit \$postdata", $postdata); $role_attributes = $this->parse_input_attributes('role', $postdata); $auth = Auth::get_instance(); $result = $auth->role_edit($postdata['id'], $role_attributes, $postdata['type_id']); // @TODO: return unique attribute or all attributes as role_add() if ($result) { return true; } return false; } public function role_effective_rights($getdata, $postdata) { $auth = Auth::get_instance(); // Roles are special in that they are ldapsubentries. if (!empty($getdata['id'])) { $unique_attr = self::unique_attribute(); $role = $auth->role_find_by_attribute(array($unique_attr => $getdata['id'])); if (is_array($role) && count($role) == 1) { $role_dn = key($role); } } $effective_rights = $auth->list_rights(empty($role_dn) ? 'role' : $role_dn); return $effective_rights; } /** * Role information. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool Role attributes or False on failure */ public function role_info($getdata, $postdata) { //console("api::role.info \$getdata, \$postdata", $getdata, $postdata); if (empty($getdata['id'])) { return false; } $auth = Auth::get_instance(); $attrs = $this->object_attributes('role'); $result = $auth->role_info($getdata['id'], $attrs); // normalize result $result = $this->parse_result_attributes('role', $result); if ($result) { return $result; } return false; } /** * Find role and return its data. * It is a combination of role.info and roles.list with search capabilities * If the search returns only one record we'll return role data. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool Role attributes, False on error */ public function role_find($get, $post) { $auth = Auth::get_instance(); $attributes = array(''); $params = array('page_size' => 2); $search = $this->parse_list_search($post); // find role(s) $roles = $auth->list_roles(null, $attributes, $search, $params); if (empty($roles) || empty($roles['list']) || $roles['count'] > 1) { return false; } // get role data $attrs = $this->object_attributes('role'); $result = $auth->role_info(key($roles['list']), $attrs); // normalize result $result = $this->parse_result_attributes('role', $result); if ($result) { return $result; } return false; } /** * Group members listing. * * @param array $get GET parameters * @param array $post POST parameters * * @return array List of role members ('list' and 'count' items) */ public function role_members_list($getdata, $postdata) { if (empty($getdata['id'])) { return false; } $auth = Auth::get_instance(); $result = $auth->role_members_list($getdata['id'], false); return array( 'list' => $result, 'count' => count($result), ); } } diff --git a/lib/api/kolab_api_service_sharedfolder.php b/lib/api/kolab_api_service_sharedfolder.php index b828f81..2dcc33b 100644 --- a/lib/api/kolab_api_service_sharedfolder.php +++ b/lib/api/kolab_api_service_sharedfolder.php @@ -1,212 +1,212 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Jeroen van Meeuwen | +--------------------------------------------------------------------------+ */ /** * Service providing shared folder data management */ class kolab_api_service_sharedfolder extends kolab_api_service { /** * Returns service capabilities. * * @param string $domain Domain name * * @return array Capabilities list */ public function capabilities($domain) { //console("kolab_api_service_group::capabilities"); $auth = Auth::get_instance($domain); $effective_rights = $auth->list_rights('sharedfolder'); $rights = array(); - if (in_array('add', $effective_rights['entryLevelRights'])) { + if (in_array('add', $effective_rights['entrylevelrights'])) { $rights['add'] = "w"; } - if (in_array('delete', $effective_rights['entryLevelRights'])) { + if (in_array('delete', $effective_rights['entrylevelrights'])) { $rights['delete'] = "w"; } - if (in_array('modrdn', $effective_rights['entryLevelRights'])) { + if (in_array('modrdn', $effective_rights['entrylevelrights'])) { $rights['edit'] = "w"; } - if (in_array('read', $effective_rights['entryLevelRights'])) { + if (in_array('read', $effective_rights['entrylevelrights'])) { $rights['info'] = "r"; $rights['find'] = "r"; } $rights['effective_rights'] = "r"; return $rights; } /** * Create a shared folder. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool User attributes or False on error. */ public function sharedfolder_add($getdata, $postdata) { $attributes = $this->parse_input_attributes('sharedfolder', $postdata); $auth = Auth::get_instance(); $result = $auth->sharedfolder_add($attributes, $postdata['type_id']); if ($result) { if ($id = $this->unique_attribute_value($result)) { $attributes['id'] = $id; } return $attributes; } return false; } /** * Detete a shared folder. * * @param array $get GET parameters * @param array $post POST parameters * * @return bool True on success, False on failure */ public function sharedfolder_delete($getdata, $postdata) { //console("sharedfolder_delete()", $getdata, $postdata); if (!isset($postdata['id'])) { return false; } // TODO: Input validation $auth = Auth::get_instance(); $result = $auth->sharedfolder_delete($postdata['id']); if ($result) { return $result; } return false; } public function sharedfolder_edit($getdata, $postdata) { //console("\$postdata to sharedfolder_edit()", $postdata); $sharedfolder_attributes = $this->parse_input_attributes('sharedfolder', $postdata); //console("\$sharedfolder_attributes as result from parse_input_attributes", $sharedfolder_attributes); $auth = Auth::get_instance(); $result = $auth->sharedfolder_edit($postdata['id'], $sharedfolder_attributes, $postdata['type_id']); // Return the $mod_array if ($result) { return $result; } return false; } public function sharedfolder_effective_rights($getdata, $postdata) { $auth = Auth::get_instance(); $effective_rights = $auth->list_rights(empty($getdata['id']) ? 'sharedfolder' : $getdata['id']); return $effective_rights; } /** * User information. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool User attributes, False on error */ public function sharedfolder_info($getdata, $postdata) { if (!isset($getdata['id'])) { return false; } $auth = Auth::get_instance(); $attrs = $this->object_attributes('sharedfolder'); $result = $auth->sharedfolder_info($getdata['id'], $attrs); // normalize result $result = $this->parse_result_attributes('sharedfolder', $result); if ($result) { return $result; } return false; } /** * Find a shared folder and return its data. * It is a combination of sharedfolder.info and sharedfolders.list with search capabilities * If the search returns only one record we'll return sharedfolder data. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool Resource attributes, False on error */ public function sharedfolder_find($get, $post) { $auth = Auth::get_instance(); $attributes = array(''); $params = array('page_size' => 2); $search = $this->parse_list_search($post); // find shared folder(s) $sharedfolders = $auth->list_sharedfolders(null, $attributes, $search, $params); if (empty($sharedfolders) || empty($sharedfolders['list']) || $sharedfolders['count'] > 1) { return false; } // get shared folder data $attrs = $this->object_attributes('sharedfolder'); $result = $auth->sharedfolder_info(key($sharedfolders['list']), $attrs); // normalize result $result = $this->parse_result_attributes('sharedfolder', $result); if ($result) { return $result; } return false; } } diff --git a/lib/api/kolab_api_service_type.php b/lib/api/kolab_api_service_type.php index 0089589..65b60be 100644 --- a/lib/api/kolab_api_service_type.php +++ b/lib/api/kolab_api_service_type.php @@ -1,267 +1,267 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Jeroen van Meeuwen | +--------------------------------------------------------------------------+ */ /** * Service providing object types management */ class kolab_api_service_type extends kolab_api_service { /** * Returns service capabilities. * * @param string $domain Domain name * * @return array Capabilities list */ public function capabilities($domain) { $effective_rights = $this->type_effective_rights(); $rights = array(); - if (in_array('add', (array)$effective_rights['entryLevelRights'])) { + if (in_array('add', (array)$effective_rights['entrylevelrights'])) { $rights['add'] = "w"; } - if (in_array('delete', (array)$effective_rights['entryLevelRights'])) { + if (in_array('delete', (array)$effective_rights['entrylevelrights'])) { $rights['delete'] = "w"; } - if (in_array('modrdn', (array)$effective_rights['entryLevelRights'])) { + if (in_array('modrdn', (array)$effective_rights['entrylevelrights'])) { $rights['edit'] = "w"; } - if (in_array('read', (array)$effective_rights['entryLevelRights'])) { + if (in_array('read', (array)$effective_rights['entrylevelrights'])) { $rights['info'] = "r"; } $rights['effective_rights'] = "r"; return $rights; } /** * Create type. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool Type attributes or False on error. */ public function type_add($getdata, $postdata) { if (!in_array($postdata['type'], $this->supported_types_db)) { return false; } if (empty($postdata['name']) || empty($postdata['key'])) { return false; } if (empty($postdata['attributes']) || !is_array($postdata['attributes'])) { return false; } $effective_rights = $this->type_effective_rights(); - if (!in_array('add', (array)$effective_rights['entryLevelRights'])) { + if (!in_array('add', (array)$effective_rights['entrylevelrights'])) { return false; } $type = $postdata['type']; $query = array( 'key' => $postdata['key'], 'name' => $postdata['name'], 'description' => $postdata['description'] ? $postdata['description'] : '', 'attributes' => json_encode($postdata['attributes']), 'is_default' => $postdata['is_default'] ? 1 : 0, ); if ($postdata['type'] == 'user') { $query['used_for'] = $postdata['used_for'] == 'hosted' ? 'hosted' : null; } $query = array_map(array($this->db, 'quote'), $query); $columns = array_map(array($this->db, 'quote_identifier'), array_keys($query)); $this->db->query("INSERT INTO `{$type}_types`" . " (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $query) . ")"); if (!($id = $this->db->insert_id($type . '_types'))) { return false; } // there can be only one default if ($postdata['is_default']) { $this->db->query("UPDATE `{$type}_types` SET `is_default` = 0 WHERE `id` <> " . intval($id)); } // update cache $this->cache['object_types'][$type][$id] = $postdata; $postdata['id'] = $id; return $postdata; } /** * Detete type. * * @param array $get GET parameters * @param array $post POST parameters * * @return bool True on success, False on failure */ public function type_delete($getdata, $postdata) { if (empty($postdata['type']) || empty($postdata['id'])) { return false; } if (!in_array($postdata['type'], $this->supported_types_db)) { return false; } $object_name = $postdata['type']; $object_id = $postdata['id']; $effective_rights = $this->type_effective_rights(); - if (!in_array('delete', (array)$effective_rights['entryLevelRights'])) { + if (!in_array('delete', (array)$effective_rights['entrylevelrights'])) { return false; } $this->db->query("DELETE FROM `{$object_name}_types` WHERE `id` = " . intval($object_id)); return (bool) $this->db->affected_rows(); } /** * Update type. * * @param array $get GET parameters * @param array $post POST parameters * * @return bool True on success, False on failure */ public function type_edit($getdata, $postdata) { if (empty($postdata['type']) || empty($postdata['id'])) { return false; } if (empty($postdata['name']) || empty($postdata['key'])) { return false; } if (empty($postdata['attributes']) || !is_array($postdata['attributes'])) { return false; } $effective_rights = $this->type_effective_rights(); - if (!in_array('modrdn', (array)$effective_rights['entryLevelRights'])) { + if (!in_array('modrdn', (array)$effective_rights['entrylevelrights'])) { return false; } $type = $postdata['type']; $query = array( 'key' => $postdata['key'], 'name' => $postdata['name'], 'description' => $postdata['description'] ? $postdata['description'] : '', 'attributes' => json_encode($postdata['attributes']), 'is_default' => $postdata['is_default'] ? 1 : 0, ); if ($postdata['type'] == 'user') { $query['used_for'] = $postdata['used_for'] == 'hosted' ? 'hosted' : null; } foreach ($query as $idx => $value) { $query[$idx] = $this->db->quote_identifier($idx) . " = " . $this->db->quote($value); } $result = $this->db->query("UPDATE `{$type}_types` SET " . implode(', ', $query) . " WHERE `id` = " . intval($postdata['id'])); if (!$result) { return false; } // there can be only one default if ($postdata['is_default']) { $this->db->query("UPDATE `{$type}_types` SET `is_default` = 0 WHERE `id` <> " . intval($postdata['id'])); } // update cache $this->cache['object_types'][$type][$id] = $postdata; return $postdata; } public function type_effective_rights($getdata = null, $postdata = null) { $effective_rights = array(); // @TODO: set rights according to user group or sth if (strtolower($_SESSION['user']->get_userid()) == 'cn=directory manager') { $attr_acl = array('read', 'write', 'delete'); $effective_rights = array( - 'entryLevelRights' => array( + 'entrylevelrights' => array( 'read', 'add', 'delete', 'modrdn', ), - 'attributeLevelRights' => array( + 'attributelevelrights' => array( 'key' => $attr_acl, 'name' => $attr_acl, 'description' => $attr_acl, 'used_for' => $attr_acl, 'attributes' => $attr_acl, ), ); } return $effective_rights; } /** * Type information. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool Type data, False on error */ public function type_info($getdata, $postdata) { if (empty($getdata['type']) || empty($getdata['id'])) { return false; } if (!in_array($getdata['type'], $this->supported_types_db)) { return false; } $object_name = $getdata['type']; $object_id = $getdata['id']; $types = $this->object_types($object_name); return !empty($types[$object_id]) ? $types[$object_id] : false; } } diff --git a/lib/api/kolab_api_service_user.php b/lib/api/kolab_api_service_user.php index d63636d..ae3b442 100644 --- a/lib/api/kolab_api_service_user.php +++ b/lib/api/kolab_api_service_user.php @@ -1,269 +1,269 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Jeroen van Meeuwen | +--------------------------------------------------------------------------+ */ /** * Service providing user data management */ class kolab_api_service_user extends kolab_api_service { /** * Returns service capabilities. * * @param string $domain Domain name * * @return array Capabilities list */ public function capabilities($domain) { $auth = Auth::get_instance($domain); $effective_rights = $auth->list_rights('user'); $rights = array(); - if (in_array('add', $effective_rights['entryLevelRights'])) { + if (in_array('add', $effective_rights['entrylevelrights'])) { $rights['add'] = "w"; } - if (in_array('delete', $effective_rights['entryLevelRights'])) { + if (in_array('delete', $effective_rights['entrylevelrights'])) { $rights['delete'] = "w"; } - if (in_array('modrdn', $effective_rights['entryLevelRights'])) { + if (in_array('modrdn', $effective_rights['entrylevelrights'])) { $rights['edit'] = "w"; $rights['password'] = "w"; } - if (in_array('read', $effective_rights['entryLevelRights'])) { + if (in_array('read', $effective_rights['entrylevelrights'])) { $rights['info'] = "r"; $rights['find'] = "r"; } $rights['effective_rights'] = "r"; return $rights; } /** * Create user. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool User attributes or False on error. */ public function user_add($getdata, $postdata) { Log::trace("user_add()", $postdata); $attributes = $this->parse_input_attributes('user', $postdata); password_policy::validate_password($attributes['userpassword']); Log::trace("user_add()", $attributes); $auth = Auth::get_instance(); $result = $auth->user_add($attributes, $postdata['type_id']); if ($result) { if ($id = $this->unique_attribute_value($result)) { $attributes['id'] = $id; } return $attributes; } return false; } /** * Delete user. * * @param array $get GET parameters * @param array $post POST parameters * * @return bool True on success, False on failure */ public function user_delete($getdata, $postdata) { if (!isset($postdata['id'])) { return false; } // TODO: Input validation $auth = Auth::get_instance(); $result = $auth->user_delete($postdata['id']); return $result; } /** * Update user. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool User attributes or False on error. */ public function user_edit($getdata, $postdata) { Log::trace("\$postdata to user_edit()", $postdata); $user_attributes = $this->parse_input_attributes('user', $postdata); Log::trace("\$user_attributes as result from parse_input_attributes", $user_attributes); if (!empty($user_attributes['userpassword'])) { password_policy::validate_password($user_attributes['userpassword']); } $auth = Auth::get_instance(); $result = $auth->user_edit($postdata['id'], $user_attributes, $postdata['type_id']); // Return the $mod_array if ($result) { return $result; } return false; } /** * Update user password. * * @param array $get GET parameters * @param array $post POST parameters * * @return bool True on success, False on failure */ public function user_password($getdata, $postdata) { $password = $postdata['password']; $user_id = $postdata['id']; if (empty($user_id) || !is_string($password) || !strlen($password)) { return false; } if ($user_id === 'me') { $user_id = $_SESSION['user']->get_userid(); } password_policy::validate_password($password); $auth = Auth::get_instance(); $result = $auth->user_edit($user_id, array('userpassword' => $password)); return $result !== false; } /** * Effective rights on user record. * * @param array $get GET parameters * @param array $post POST parameters * * @return array Effective rights */ public function user_effective_rights($getdata, $postdata) { $auth = Auth::get_instance(); $effective_rights = $auth->list_rights(empty($getdata['id']) ? 'user' : $getdata['id']); return $effective_rights; } /** * User information. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool User attributes, False on error */ public function user_info($getdata, $postdata) { if (!isset($getdata['id'])) { return false; } $auth = Auth::get_instance(); $attrs = $this->object_attributes('user'); $result = $auth->user_info($getdata['id'], $attrs); // normalize result $result = $this->parse_result_attributes('user', $result); Log::trace("user.info on " . $getdata['id'] . " parsed result: " . var_export($result, TRUE)); if ($result) { return $result; } return false; } /** * Find user and return his data. * It is a combination of user.info and users.list with search capabilities * If the search returns only one record we'll return user data. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool User attributes, False on error */ public function user_find($get, $post) { $auth = Auth::get_instance(); $attributes = array(''); $params = array('page_size' => 2); $search = $this->parse_list_search($post); // find user(s) $users = $auth->list_users(null, $attributes, $search, $params); if (empty($users) || empty($users['list'])) { return Array(); } if ($users['count'] > 1) { throw new Exception("More than a single entry found.", 942); } // get user data $attrs = $this->object_attributes('user'); $result = $auth->user_info(key($users['list']), $attrs); // normalize result $result = $this->parse_result_attributes('user', $result); if ($result) { return $result; } return false; } } diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php index 46eecaf..1b3908e 100644 --- a/lib/kolab_client_task.php +++ b/lib/kolab_client_task.php @@ -1,1808 +1,1808 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Jeroen van Meeuwen | +--------------------------------------------------------------------------+ */ class kolab_client_task { /** * @var kolab_client_output */ protected $output; /** * @var kolab_client_api */ protected $api; /** * @var Conf */ protected $config; protected $ajax_only = false; protected $page_title = 'Kolab Web Admin Panel'; protected $menu = array(); protected $cache = array(); protected $devel_mode = false; protected $object_types = array('user', 'group', 'role', 'resource', 'sharedfolder', 'ou', 'domain'); protected $page_size = 20; protected $list_attribs = array(); protected $list_module; protected $search_attribs = array( 'name' => array('cn', 'displayname'), 'email' => array('mail', 'alias', 'mailalternateaddress'), 'uid' => array('uid'), ); protected static $translation = array(); /** * Class constructor. * * @param kolab_client_output $output Optional output object */ public function __construct($output = null) { $this->config_init(); $this->devel_mode = $this->config_get('devel_mode', false, Conf::BOOL); $this->output_init($output); $this->api_init(); ini_set('session.use_cookies', 'On'); session_start(); $this->auth(); } /** * Localization initialization. */ protected function locale_init() { if (!empty(self::$translation)) { return; } $language = $this->get_language(); $LANG = array(); if (!$language) { $language = 'en_US'; } @include INSTALL_PATH . '/locale/en_US.php'; if ($language != 'en_US') { @include INSTALL_PATH . "/locale/$language.php"; } setlocale(LC_ALL, $language . '.utf8', $language . 'UTF-8', 'en_US.utf8', 'en_US.UTF-8'); // workaround for http://bugs.php.net/bug.php?id=18556 if (PHP_VERSION_ID < 50500 && in_array($language, array('tr_TR', 'ku', 'az_AZ'))) { setlocale(LC_CTYPE, 'en_US.utf8', 'en_US.UTF-8'); } self::$translation = $LANG; } /** * Configuration initialization. */ private function config_init() { $this->config = Conf::get_instance(); } /** * Output initialization. */ private function output_init($output = null) { if ($output) { $this->output = $output; return; } $skin = $this->config_get('skin', 'default'); $this->output = new kolab_client_output($skin); // Assign self to template variable $this->output->assign('engine', $this); } /** * API initialization */ private function api_init() { $url = $this->config_get('api_url', ''); if (!$url) { $port = $_SERVER['SERVER_PORT']; $url = kolab_utils::https_check() ? 'https://' : 'http://'; $url .= $_SERVER['SERVER_NAME'] . ($port ? ":$port" : ''); $url .= preg_replace('/\/?\?.*$/', '', $_SERVER['REQUEST_URI']); $url .= '/api'; } // TODO: Debug logging //console($url); $this->api = new kolab_client_api($url); } /** * Returns system language (locale) setting. * * @return string Language code */ private function get_language() { $aliases = array( 'de' => 'de_DE', 'en' => 'en_US', 'pl' => 'pl_PL', ); // UI language $langs = !empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : ''; $langs = explode(',', $langs); if (!empty($_SESSION['user']) && !empty($_SESSION['user']['language'])) { array_unshift($langs, $_SESSION['user']['language']); } while ($lang = array_shift($langs)) { $lang = explode(';', $lang); $lang = $lang[0]; $lang = str_replace('-', '_', $lang); if (file_exists(INSTALL_PATH . "/locale/$lang.php")) { return $lang; } if (isset($aliases[$lang]) && ($alias = $aliases[$lang]) && file_exists(INSTALL_PATH . "/locale/$alias.php") ) { return $alias; } } return null; } /** * User authentication (and authorization). */ private function auth() { if (isset($_POST['login'])) { $login = $this->get_input('login', 'POST', true); if ($login['username']) { $login['username'] = trim($login['username']); $login['domain'] = trim($login['domain']); $result = $this->api->login($login['username'], $login['password'], $login['domain'], true); if ($token = $result->get('session_token')) { $user = array( 'token' => $token, 'id' => $result->get('userid'), 'domain' => $result->get('domain') ); $this->api->set_session_token($user['token']); // Find user settings // Don't call API user.info for non-existing users (#1025) if (preg_match('/^cn=([a-z ]+)/i', $login['username'], $m)) { $user['fullname'] = ucwords($m[1]); } else { $res = $result->get('info'); if (empty($res)) { $res = $this->api->get('user.info', array('id' => $user['id'])); $res = $res->get(); } if (is_array($res) && !empty($res)) { $user['language'] = $res['preferredlanguage']; $user['fullname'] = $res['cn']; // overwrite user id set in login request, which might be user base DN, // with unique attribute, which suits better to our needs $user['id'] = $res['id']; } } // Save user data $_SESSION['user'] = $user; if (($language = $this->get_language()) && $language != 'en_US') { $_SESSION['user']['language'] = $language; $session_config['language'] = $language; } // Configure API session if (!empty($session_config)) { $this->api->post('system.configure', null, $session_config); } header('Location: ?'); die; } else { $code = $result->get_error_code(); $str = $result->get_error_str(); $label = 'loginerror'; if ($code == kolab_client_api::ERROR_INTERNAL || $code == kolab_client_api::ERROR_CONNECTION ) { $label = 'internalerror'; $this->raise_error(500, 'Login failed. ' . $str); } $this->output->command('display_message', $label, 'error'); } } } else if (!empty($_SESSION['user']) && !empty($_SESSION['user']['token'])) { // Validate session $timeout = $this->config_get('session_timeout', 3600); if ($timeout && $_SESSION['time'] && $_SESSION['time'] < time() - $timeout) { $this->action_logout(true); } // update session time $_SESSION['time'] = time(); // Set API session key $this->api->set_session_token($_SESSION['user']['token']); } } /** * Main execution. */ public function run() { // Initialize locales $this->locale_init(); // Session check if (empty($_SESSION['user']) || empty($_SESSION['user']['token'])) { $this->action_logout(); } // Run security checks $this->input_checks(); $this->action = $this->get_input('action', 'GET'); if ($this->action) { $method = 'action_' . $this->action; if (method_exists($this, $method)) { $this->$method(); } } else if (method_exists($this, 'action_default')) { $this->action_default(); } } /** * Security checks and input validation. */ public function input_checks() { $ajax = $this->output->is_ajax(); // Check AJAX-only tasks if ($this->ajax_only && !$ajax) { $this->raise_error(500, 'Invalid request type!', null, true); } // CSRF prevention $token = $ajax ? kolab_utils::get_request_header('X-Session-Token') : $this->get_input('token'); $task = $this->get_task(); if ($task != 'main' && $token != $_SESSION['user']['token']) { $this->raise_error(403, 'Invalid request data!', null, true); } } /** * Logout action. */ private function action_logout($sess_expired = false, $stop_sess = true) { // Initialize locales $this->locale_init(); if (!empty($_SESSION['user']) && !empty($_SESSION['user']['token']) && $stop_sess) { $this->api->logout(); } $_SESSION = array(); if ($this->output->is_ajax()) { if ($sess_expired) { $args = array('error' => 'session.expired'); } $this->output->command('main_logout', $args); if ($sess_expired) { $this->output->send(); exit; } } else { $this->output->add_translation('loginerror', 'internalerror', 'session.expired'); } if ($sess_expired) { $error = 'session.expired'; } else { $error = $this->get_input('error', 'GET'); } if ($error) { $this->output->command('display_message', $error, 'error', 60000); } $this->send('login'); exit; } /** * Error action (with error logging). * * @param int $code Error code * @param string $msg Error message * @param array $args Optional arguments (type, file, line) * @param bool $output Enable to send output and finish */ public function raise_error($code, $msg, $args = array(), $output = false) { $log_line = sprintf("%s Error: %s (%s)", isset($args['type']) ? $args['type'] : 'PHP', $msg . (isset($args['file']) ? sprintf(' in %s on line %d', $args['file'], $args['line']) : ''), $_SERVER['REQUEST_METHOD']); write_log('errors', $log_line); if (!$output) { return; } if ($this->output->is_ajax()) { header("HTTP/1.0 $code $msg"); die; } $this->output->assign('error_code', $code); $this->output->assign('error_message', $msg); $this->send('error'); exit; } /** * Output sending. */ public function send($template = null) { if (!$template) { $template = $this->get_task(); } if ($this->page_title) { $this->output->assign('pagetitle', $this->page_title); } $this->output->send($template); exit; } /** * Returns name of the current task. * * @return string Task name */ public function get_task() { $class_name = get_class($this); if (preg_match('/^kolab_client_task_([a-z]+)$/', $class_name, $m)) { return $m[1]; } } /** * Returns output environment variable value * * @param string $name Variable name * * @return mixed Variable value */ public function get_env($name) { return $this->output->get_env($name); } /** * Returns configuration option value. * * @param string $name Option name * @param mixed $fallback Default value * @param int $type Value type (one of Conf class constants) * * @return mixed Option value */ public function config_get($name, $fallback = null, $type = null) { $value = $this->config->get('kolab_wap', $name, $type); return $value !== null ? $value : $fallback; } /** * Returns translation of defined label/message. * * @return string Translated string. */ public static function translate() { $args = func_get_args(); if (is_array($args[0])) { $args = $args[0]; } $label = $args[0]; if (isset(self::$translation[$label])) { $content = trim(self::$translation[$label]); } else { $content = $label; } for ($i = 1, $len = count($args); $i < $len; $i++) { $content = str_replace('$'.$i, $args[$i], $content); } return $content; } /** * Returns input parameter value. * * @param string $name Parameter name * @param string $type Parameter type (GET|POST|NULL) * @param bool $allow_html Disables stripping of insecure content (HTML tags) * * @see kolab_utils::get_input * @return mixed Input value. */ public static function get_input($name, $type = null, $allow_html = false) { if ($type == 'GET') { $type = kolab_utils::REQUEST_GET; } else if ($type == 'POST') { $type = kolab_utils::REQUEST_POST; } else { $type = kolab_utils::REQUEST_ANY; } return kolab_utils::get_input($name, $type, $allow_html); } /** * Returns task menu output. * * @return string HTML output */ protected function menu() { if (empty($this->menu)) { return ''; } $menu = array(); $task = $this->get_task(); $caps = (array) $this->get_capability('actions'); foreach ($this->menu as $idx => $label) { if (in_array($task, $this->object_types)) { if (!array_key_exists($task . "." . $idx, $caps)) { continue; } } if (strpos($idx, '.')) { $action = $idx; $class = preg_replace('/\.[a-z_-]+$/', '', $idx); } else { $action = $task . '.' . $idx; $class = $idx; } $menu[$idx] = sprintf('
  • ' .'%s
  • ', $class, $idx, $action, $this->translate($label)); } return '
      ' . implode("\n", $menu) . '
    '; } /** * Adds watermark page definition into main page. */ protected function watermark($name) { $this->output->command('set_watermark', $name); } /** * API GET request wrapper */ protected function api_get($action, $get = array()) { return $this->api_call('get', $action, $get); } /** * API POST request wrapper */ protected function api_post($action, $get = array(), $post = array()) { return $this->api_call('post', $action, $get, $post); } /** * API request wrapper with error handling */ protected function api_call($type, $action, $get = array(), $post = array()) { if ($type == 'post') { $result = $this->api->post($action, $get, $post); } else { $result = $this->api->get($action, $get); } // error handling if ($code = $result->get_error_code()) { // Invalid session, do logout if ($code == 403) { $this->action_logout(true, false); } // Log communication errors, other should be logged on API side if ($code < 400) { $this->raise_error($code, 'API Error: ' . $result->get_error_str()); } } return $result; } /** * Returns list of object types. * * @para string $type Object type name * @param string $used_for Used_for attribute of object type * * @return array List of user types */ protected function object_types($type, $used_for = null) { if (empty($type) || !in_array($type, $this->object_types)) { return array(); } $cache_idx = $type . '_types' . ($used_for ? ":$used_for" : ''); if (!array_key_exists($cache_idx, $this->cache)) { $result = $this->api_post($type . '_types.list'); $list = $result->get('list'); if (!empty($used_for) && is_array($list)) { foreach ($list as $type_id => $type_attrs) { if ($type_attrs['used_for'] != $used_for) { unset($list[$type_id]); } } } $this->cache[$cache_idx] = !empty($list) ? $list : array(); Log::trace("kolab_client_task::${type}_types() returns: " . var_export($list, true)); } return $this->cache[$cache_idx]; } /** * Returns user name. * * @param string $dn User DN attribute value * * @return string User name (displayname) */ protected function user_name($dn) { if (!$this->devel_mode) { if (!empty($this->cache['user_names']) && isset($this->cache['user_names'][$dn])) { return $this->cache['user_names'][$dn]; } } $result = $this->api_get('user.info', array('id' => $dn)); $username = $result->get('displayname'); if (empty($username)) { $username = $result->get('cn'); } if (empty($username)) { if (preg_match('/^cn=([a-zA=Z ]+)/', $dn, $m)) { $username = ucwords($m[1]); } } if (!$this->devel_mode) { $this->cache['user_names'][$dn] = $username; } return $username; } /** * Returns list of system capabilities. * * @param bool $all If enabled capabilities for all domains will be returned * @param bool $refresh Disable session cache * * @return array List of system capabilities */ protected function capabilities($all = false, $refresh = false) { if (!$refresh && isset($_SESSION['capabilities']) && !$this->devel_mode) { $list = $_SESSION['capabilities']; } else { $result = $this->api_post('system.capabilities'); $list = $result->get('list'); if (is_array($list) && !$this->devel_mode) { $_SESSION['capabilities'] = $list; } } $domain = $this->domain ? $this->domain : $_SESSION['user']['domain']; return !$all ? $list[$domain] : $list; } /** * Returns system capability * * @param string $name Capability (key) name * * @return array Capability value if supported, NULL otherwise */ protected function get_capability($name) { $caps = $this->capabilities(); return $caps[$name]; } /** * Returns domains list (based on capabilities response) * * @param bool $refresh Refresh session cache * * @return array List of domains */ protected function get_domains($refresh = false) { $caps = $this->capabilities(true, $refresh); return is_array($caps) ? array_keys($caps) : array(); } /** * Returns effective rights for the specified object * * @param string $type Object type * @param string $id Object identifier * * @return array Two element array with 'attribute' and 'entry' elements */ protected function effective_rights($type, $id = null) { $caps = $this->get_capability('actions'); if (empty($caps[$type . '.effective_rights'])) { return array( 'attribute' => array(), 'entry' => array(), ); } // Get the rights on the entry and attribute level $result = $this->api_get($type . '.effective_rights', array('id' => $id)); $result = array( - 'attribute' => $result->get('attributeLevelRights'), - 'entry' => $result->get('entryLevelRights'), + 'attribute' => $result->get('attributelevelrights'), + 'entry' => $result->get('entrylevelrights'), ); return $result; } /** * Returns execution time in seconds * * @param string Execution time */ public function gentime() { return sprintf('%.4f', microtime(true) - KADM_START); } /** * Returns HTML output of login form * * @param string HTML output */ public function login_form() { $post = $this->get_input('login', 'POST', true); $username = kolab_html::div(array( 'class' => 'form-group input-group input-group-lg', 'content' => kolab_html::label(array( 'for' => 'login_name', 'class' => 'font-icon login input-group-prepend input-group-text', 'content' => kolab_html::span(array( 'class' => 'sr-only', 'content' => $this->translate('login.username'), true)))) . kolab_html::input(array( 'type' => 'text', 'id' => 'login_name', 'name' => 'login[username]', 'value' => $post['username'], 'placeholder' => $this->translate('login.username'), 'autofocus' => true)) )); $password = kolab_html::div(array( 'class' => 'form-group input-group input-group-lg', 'content' => kolab_html::label(array( 'for' => 'login_pass', 'class' => 'font-icon password input-group-prepend input-group-text', 'content' => kolab_html::span(array( 'class' => 'sr-only', 'content' => $this->translate('login.password'), true)))) . kolab_html::input(array( 'type' => 'password', 'id' => 'login_pass', 'name' => 'login[password]', 'placeholder' => $this->translate('login.password'), 'value' => '')) )); $button = kolab_html::input(array( 'type' => 'submit', 'id' => 'login_submit', 'class' => 'btn btn-primary btn-lg text-uppercase w-100', 'value' => $this->translate('login.login'))); $form = kolab_html::form(array( 'id' => 'login_form', 'name' => 'login', 'method' => 'post', 'action' => '?'), kolab_html::div(array('content' => $username)) . kolab_html::div(array('content' => $password)) . $button ); return $form; } /** * Returns form element definition based on field attributes * * @param array $field Field attributes * @param array $data Attribute values * * @return array Field definition */ protected function form_element_type($field, $data = array()) { $result = array(); switch ($field['type']) { case 'select': case 'multiselect': $opts = $this->form_element_select_data($field, $data); $result['type'] = kolab_form::INPUT_SELECT; $result['options'] = $opts['options']; $result['value'] = $opts['default']; $result['default'] = $field['default']; if ($field['type'] == 'multiselect') { $result['multiple'] = true; } break; case 'list': $result['type'] = kolab_form::INPUT_TEXTAREA; $result['data-type'] = 'list'; if (!empty($field['maxlength'])) { $result['data-maxlength'] = $field['maxlength']; } if (!empty($field['maxcount'])) { $result['data-maxcount'] = $field['maxcount']; } if (!empty($field['autocomplete'])) { $result['data-autocomplete'] = true; } break; case 'checkbox': $result['type'] = kolab_form::INPUT_CHECKBOX; break; case 'password': $result['type'] = kolab_form::INPUT_PASSWORD; if (isset($field['maxlength'])) { $result['maxlength'] = $field['maxlength']; } break; case 'text-quota': $result['type'] = kolab_form::INPUT_TEXTQUOTA; $result['default'] = $field['default']; break; case 'aci': $result['type'] = kolab_form::INPUT_TEXTAREA; $result['data-type'] = 'aci'; $this->output->add_translation('aci.new', 'aci.edit', 'aci.remove', 'aci.users', 'aci.rights', 'aci.targets', 'aci.aciname', 'aci.read', 'aci.compare', 'aci.search', 'aci.write', 'aci.selfwrite', 'aci.delete', 'aci.add', 'aci.proxy', 'aci.all', 'aci.allow', 'aci.deny', 'aci.typeusers', 'aci.typegroups', 'aci.typeroles', 'aci.typeadmins', 'aci.typespecials', 'aci.ldap-all', 'aci.ldap-anyone', 'aci.ldap-self', 'aci.ldap-parent', 'aci.usersearch', 'aci.usersearchresult', 'aci.selected', 'aci.other', 'aci.userselected', 'aci.useradd', 'aci.userremove', 'aci.thisentry', 'aci.rights.target', 'aci.rights.filter', 'aci.rights.attrs', 'aci.checkall', 'aci.checknone', 'aci.error.noname', 'aci.error.exists', 'aci.error.nousers', 'button.cancel', 'button.ok' ); break; case 'imap_acl': $result['type'] = kolab_form::INPUT_TEXTAREA; $result['data-type'] = 'acl'; $result['default'] = $field['default']; $this->output->add_translation('aci.new', 'aci.edit', 'aci.remove', 'button.ok', 'button.cancel', 'acl.all', 'acl.custom', 'acl.read-only', 'acl.read-write', 'acl.full', 'acl.semi-full', 'acl.l', 'acl.r', 'acl.s', 'acl.w', 'acl.i', 'acl.p', 'acl.k', 'acl.a', 'acl.x', 'acl.t', 'acl.n', 'acl.e', 'acl.d', 'acl.anyone', 'acl.anonymous', 'acl.identifier', 'acl.rights', 'acl.expire', 'acl.user', 'acl.error.invaliddate', 'acl.error.nouser', 'acl.error.subjectexists', 'acl.error.norights' ); break; case 'text-separated': $result['type'] = kolab_form::INPUT_TEXTAREA; $result['data-type'] = 'separated'; break; default: if (!empty($field['autocomplete'])) { $result['type'] = kolab_form::INPUT_TEXTAREA; $result['data-type'] = 'list'; $result['data-maxcount'] = 1; $result['data-autocomplete'] = true; } else { $result['type'] = kolab_form::INPUT_TEXT; } if ($field['type'] && $field['type'] != 'text') { $result['data-type'] = $field['type']; if ($field['type'] == 'ldap_url') { $this->output->add_translation('ldap.one', 'ldap.sub', 'ldap.base', 'ldap.host', 'ldap.basedn','ldap.scope', 'ldap.conditions', 'ldap.filter_any', 'ldap.filter_both', 'ldap.filter_prefix', 'ldap.filter_suffix', 'ldap.filter_exact' ); } } else { $result['default'] = $field['default']; } if (isset($field['maxlength'])) { $result['maxlength'] = $field['maxlength']; } } $result['required'] = empty($field['optional']); return $result; } /** * Prepares options/value of select element * * @param array $field Field attributes * @param array $data Attribute values * @param bool $lc Convert option values to lower-case * * @return array Options/Default definition */ protected function form_element_select_data($field, $data = array(), $lc = false) { $options = array(); $default = null; if (!isset($field['values'])) { $data['attributes'] = array($field['name']); $resp = $this->api_post('form_value.select_options', null, $data); $resp = $resp->get($field['name']); unset($data['attributes']); $default = empty($data[$field['name']]) ? $resp['default'] : $data[$field['name']]; $field['values'] = $resp['list']; } if (!empty($field['values'])) { if ($lc) { $options = array_combine(array_map('strtolower', $field['values']), $field['values']); } else { $options = array_combine($field['values'], $field['values']); } // Exceptions if ($field['name'] == 'ou') { foreach ($options as $idx => $dn) { $options[$idx] = kolab_utils::dn2ufn($dn); } } } if (!empty($default)) { foreach ($options as $key => $value) { if (strtolower($default) == $key) { $default = strtolower($default); } } } return array( 'options' => $options, 'default' => $default, ); } /** * HTML Form elements preparation. * * @param string $name Object name (user, group, etc.) * @param array $data Object data * @param array $extra_fields Extra field names * @param string $used_for Object types filter * @param string $id_section Name of section for type_id field * * @return array Fields list, Object types list, Current type ID */ protected function form_prepare($name, &$data, $extra_fields = array(), $used_for = null, $id_section = null) { $types = (array) $this->object_types($name, $used_for); $add_mode = empty($data['id']); $event_fields = array(); $auto_fields = array(); $form_fields = array(); $fields = array(); $auto_attribs = array(); $extra_fields = array_flip($extra_fields); // Object type $data['object_type'] = $name; // Selected account type if (!empty($data['type_id'])) { $type = $data['type_id']; } else { // find default object type foreach ($types as $type_id => $type) { if ($type['is_default']) { $default = $type_id; break; } } reset($types); $data['type_id'] = $type = ($default !== null ? $default : key($types)); } if ($type) { $auto_fields = (array) $types[$type]['attributes']['auto_form_fields']; $form_fields = (array) $types[$type]['attributes']['form_fields']; } // Mark automatically generated fields as read-only, etc. foreach ($auto_fields as $idx => $field) { if (!is_array($field)) { unset($auto_fields[$idx]); continue; } // merge with field definition from if (isset($form_fields[$idx])) { $field = array_merge($field, $form_fields[$idx]); } // remove auto-generated value on type change, it will be re-generated else if ($add_mode) { unset($data[$idx]); } $field['name'] = $idx; $fields[$idx] = $this->form_element_type($field, $data); $fields[$idx]['readonly'] = true; $extra_fields[$idx] = true; // build event_fields list if (!empty($field['data'])) { foreach ($field['data'] as $fd) { $event_fields[$fd][] = $idx; } } } // Other fields foreach ($form_fields as $idx => $field) { if (!isset($fields[$idx])) { $field['name'] = $idx; $fields[$idx] = $this->form_element_type($field, $data); } else { unset($extra_fields[$idx]); } $fields[$idx]['readonly'] = false; // Attach on-change events to some fields, to update // auto-generated field values if (!empty($event_fields[$idx])) { $event = json_encode(array_unique($event_fields[$idx])); $fields[$idx]['onchange'] = "kadm.form_value_change($event)"; } } // Re-parse auto_fields again, to get attributes for auto-generation // Need to do this after form_fields have been initialized (#2980) foreach ($auto_fields as $idx => $field) { // build auto_attribs and event_fields lists if (!empty($field['data'])) { $is_data = 0; foreach ($field['data'] as $fd) { if (!isset($data[$fd]) && isset($fields[$fd]['value'])) { $data[$fd] = $fields[$fd]['value']; } if (isset($data[$fd])) { $is_data++; } } if (count($field['data']) == $is_data) { $auto_attribs[] = $idx; } } else { $auto_attribs[] = $idx; // Unset the $auto_fields array key to prevent the form field from // becoming disabled/readonly unset($auto_fields[$idx]); } } // Get the rights on the entry and attribute level $data['effective_rights'] = $this->effective_rights($name, $data['id']); $attribute_rights = (array) $data['effective_rights']['attribute']; $entry_rights = (array) $data['effective_rights']['entry']; // See if "administrators" (those who can delete and add back on the entry // level) may override the automatically generated contents of auto_form_fields. $admin_auto_fields_rw = $this->config_get('admin_auto_fields_rw', false, Conf::BOOL); foreach ($fields as $idx => $field) { $readonly = null; if (!array_key_exists($idx, $attribute_rights)) { // If the entry level rights contain 'add' and 'delete', well, you're an admin if (in_array('add', $entry_rights) && in_array('delete', $entry_rights)) { if ($admin_auto_fields_rw) { $fields[$idx]['readonly'] = false; } } else { $fields[$idx]['readonly'] = $readonly = true; } } else { if (in_array('add', $entry_rights) && in_array('delete', $entry_rights)) { if ($admin_auto_fields_rw) { $fields[$idx]['readonly'] = false; } } // Explicit attribute level rights, check for 'write' else if (!in_array('write', $attribute_rights[$idx])) { $fields[$idx]['readonly'] = $readonly = true; } } // disable auto-fields updates, user has no rights to modify them anyway if (is_bool($readonly) && $readonly) { if (($s_idx = array_search($idx, $auto_attribs)) !== false) { unset($auto_attribs[$s_idx]); } unset($auto_fields[$idx]); } } // Register list of auto-generated fields $this->output->set_env('auto_fields', $auto_fields); // Register list of disabled fields $this->output->set_env('extra_fields', array_keys($extra_fields)); // (Re-|Pre-)populate auto_form_fields if ($add_mode) { if (!empty($auto_attribs)) { $data['attributes'] = $auto_attribs; $resp = $this->api_post('form_value.generate', null, $data); $data = array_merge((array)$data, (array)$resp->get()); unset($data['attributes']); } } else { // Add common information fields $add_fields = array( 'creatorsname' => 'createtimestamp', 'modifiersname' => 'modifytimestamp', ); foreach ($add_fields as $idx => $val) { if (!empty($data[$idx])) { if ($value = $this->user_name($data[$idx])) { if ($data[$val]) { $value .= ' (' . strftime('%x %X', strtotime($data[$val])) . ')'; } $fields[$idx] = array( 'label' => $idx, 'section' => 'system', 'value' => $value, ); } } } // Add debug information if ($this->devel_mode) { ksort($data); $debug = kolab_html::escape(print_r($data, true)); $debug = preg_replace('/(^Array\n\(|\n*\)$|\t)/', '', $debug); $debug = str_replace("\n ", "\n", $debug); $debug = '
    ' . $debug . '
    '; $fields['debug'] = array( 'label' => 'debug', 'section' => 'system', 'value' => $debug, ); } } // Add object type hidden field $fields['object_type'] = array( 'section' => 'system', 'type' => kolab_form::INPUT_HIDDEN, 'value' => $name, ); // Get user-friendly names for lists foreach ($fields as $fname => $field) { if (!empty($field['data-autocomplete']) && !empty($data[$fname])) { if (!is_array($data[$fname])) { $data[$fname] = (array) $data[$fname]; } // request parameters $post = array( 'list' => $data[$fname], 'attribute' => $fname, 'object_type' => $name, 'type_id' => $data['type_id'], ); // get options list $result = $this->api_post('form_value.list_options', null, $post); $result = $result->get('list'); $data[$fname] = $result; } } // Add entry identifier if (!$add_mode) { $fields['id'] = array( 'section' => 'system', 'type' => kolab_form::INPUT_HIDDEN, 'value' => $data['id'] ); } // Add object type id selector if ($id_section) { $object_types = array(); foreach ($types as $idx => $elem) { $object_types[$idx] = array('value' => $idx, 'content' => $elem['name']); } // Add object type id selector $fields['type_id'] = array( 'section' => $id_section, 'type' => kolab_form::INPUT_SELECT, 'options' => $object_types, 'onchange' => "kadm.{$name}_save(true, '$id_section')", ); // Hide account type selector if there's only one type if (count($object_types) < 2 || !$add_mode) { $fields['type_id']['type'] = kolab_form::INPUT_HIDDEN; } // Add object type name if (!$add_mode && count($object_types) > 1) { $fields['type_id_name'] = array( 'label' => "$name.type_id", 'section' => $id_section, 'value' => $object_types[$type]['content'], ); } } $result = array($fields, $types, $type, $add_mode); return $result; } /** * HTML Form creation. * * @param string $name Object name (user, group, etc.) * @param array $attribs HTML attributes of the form * @param array $sections List of form sections * @param array $fields Fields list (from self::form_prepare()) * @param array $fields_map Fields map (used for sorting and sections assignment) * @param array $data Object data (with effective rights, see form_prepare()) * @param bool $add_mode Add mode enabled * @param string $title Page title * * @return kolab_form HTML Form object */ protected function form_create($name, $attribs, $sections, $fields, $fields_map, $data, $add_mode, $title = null) { // Assign sections to fields foreach ($fields as $idx => $field) { if (!$field['section']) { $fields[$idx]['section'] = isset($fields_map[$idx]) ? $fields_map[$idx] : 'other'; } } // Sort foreach ($fields_map as $idx => $val) { if (array_key_exists($idx, $fields)) { $fields_map[$idx] = $fields[$idx]; unset($fields[$idx]); } else { unset($fields_map[$idx]); } } if (!empty($fields)) { $fields_map = array_merge($fields_map, $fields); } $form = new kolab_form($attribs); $default_values = array(); $assoc_fields = array(); $req_fields = array(); $writeable = 0; $auto_fields = $this->output->get_env('auto_fields'); $conf_aliases = array('mailquota' => 'quota'); $domain = $this->domain ?: $_SESSION['user']['domain']; // Parse elements and add them to the form object foreach ($sections as $section_idx => $section) { $form->add_section($section_idx, kolab_html::escape($this->translate($section))); foreach ($fields_map as $idx => $field) { if ($field['section'] != $section_idx) { continue; } if (empty($field['label'])) { $field['label'] = "$name.$idx"; } $field['label'] = kolab_html::escape($this->translate($field['label'])); $field['description'] = "$name.$idx.desc"; $field['section'] = $section_idx; if (empty($field['value']) && $data[$idx] !== null && $data[$idx] !== '') { $value = $data[$idx]; // Convert data for the list field with autocompletion if ($field['data-type'] == 'list') { if (!is_array($value)) { if (!empty($field['data-autocomplete'])) { $value = array($value => $value); } else { $value = (array) $value; } } $value = !empty($field['data-autocomplete']) ? array_keys($value) : array_values($value); } if (is_array($value)) { $value = implode("\n", $value); } $field['value'] = $value; } else if ($add_mode && !isset($field['value'])) { // read default from config, e.g. default_quota if (!isset($field['default'])) { $conf_name = 'default_' . ($conf_aliases[$idx] ?: $idx); $field['default'] = $this->config->get($domain, $conf_name); } if (isset($field['default'])) { $field['value'] = $field['default']; $default_values[$idx] = $field['default']; unset($field['default']); } } // @TODO: We assume here that all autocompletion lists are associative // It's likely that we'll need autocompletion on ordinary lists if (!empty($field['data-autocomplete'])) { $assoc_fields[$idx] = !empty($data[$idx]) ? $data[$idx] : array(); } if ($field['type'] == kolab_form::INPUT_CHECKBOX && !isset($field['checked'])) { $field['checked'] = $field['value'] == 'TRUE'; $field['value'] = 'TRUE'; } /* if (!empty($field['suffix'])) { $field['suffix'] = kolab_html::escape($this->translate($field['suffix'])); } */ if (!empty($field['options']) && empty($field['escaped'])) { foreach ($field['options'] as $opt_idx => $option) { if (is_array($option)) { $field['options'][$opt_idx]['content'] = kolab_html::escape($this->translate($option['content'])); } else { $field['options'][$opt_idx] = kolab_html::escape($this->translate($option)); } } $field['escaped'] = true; } if (!empty($field['description'])) { $description = $this->translate($field['description']); if ($description != $field['description']) { $field['title'] = $description; } unset($field['description']); } if (empty($field['name'])) { $field['name'] = $idx; } if (empty($field['readonly']) && empty($field['disabled'])) { // count writeable fields if ($field['type'] && $field['type'] != kolab_form::INPUT_HIDDEN) { $writeable++; } if ($idx != "userpassword2") { if (!empty($field['required'])) { $req_fields[] = $idx; } } } $form->add_element($field); } } if (!empty($data['section'])) { $form->activate_section($data['section']); } if ($writeable) { $form->add_button(array( 'value' => kolab_html::escape($this->translate('button.submit')), 'onclick' => "kadm.command('{$name}.save')", 'class' => 'submit', )); } // add Delete button if ($this->is_deletable($data)) { $id = $data['id']; $form->add_button(array( 'value' => kolab_html::escape($this->translate('button.delete')), 'class' => 'btn btn-danger', 'onclick' => "kadm.command('{$name}.delete', '{$id}')", )); } // add Clone button if (!empty($attribs['clone-button'])) { $id = $data['id']; $form->add_button(array( 'value' => kolab_html::escape($this->translate('button.clone')), 'onclick' => "kadm.command('{$attribs['clone-button']}', '{$id}')", )); } $ac_min_len = $this->config_get('autocomplete_min_length', 1, Conf::INT); $this->output->set_env('form_id', $attribs['id']); $this->output->set_env('default_values', $default_values); $this->output->set_env('assoc_fields', $assoc_fields); $this->output->set_env('required_fields', $req_fields); $this->output->set_env('autocomplete_min_length', $ac_min_len); $this->output->set_env('entrydn', $data['entrydn']); $this->output->add_translation('form.required.empty', 'form.maxcount.exceeded', $name . '.add.success', $name . '.edit.success', $name . '.delete.success', $name . '.delete.confirm', $name . '.delete.force', 'add', 'edit', 'delete'); if (empty($title)) { $title = $add_mode ? $this->translate("$name.add") : $data['cn']; } $form->set_title(kolab_html::escape($title)); return $form; } /** * Check wether specified object can be deleted */ protected function is_deletable($data) { return !empty($data['id']) && in_array('delete', (array) $data['effective_rights']['entry']); } /** * Search form. * * @return string HTML output of the form */ public function search_form() { $attribs = $this->get_search_attribs(); if (empty($attribs)) { return ''; } $options = array(); foreach (array_keys($attribs) as $key) { $options[$key] = kolab_html::escape($this->translate('search.' . $key)); } $form = new kolab_form(array('id' => 'search-form')); $form->add_section('criteria', kolab_html::escape($this->translate('search.criteria'))); $form->add_element(array( 'section' => 'criteria', 'label' => $this->translate('search.field'), 'name' => 'field', 'type' => kolab_form::INPUT_SELECT, 'options' => $options, )); $form->add_element(array( 'section' => 'criteria', 'label' => $this->translate('search.method'), 'name' => 'method', 'type' => kolab_form::INPUT_SELECT, 'options' => array( 'both' => kolab_html::escape($this->translate('search.contains')), 'exact' => kolab_html::escape($this->translate('search.is')), 'prefix' => kolab_html::escape($this->translate('search.prefix')), ), )); return $form->output(); } /** * Returns list of attributes assigned to search field(s) * * @param string $name Optional search field name * * @return array List of attributes */ protected function get_search_attribs($name = null) { $task = $this->get_task(); $types = $this->object_types($task); $attribs = array(); foreach ($this->search_attribs as $key => $value) { foreach ((array)$value as $idx => $attr) { $found = false; foreach ($types as $type) { if (array_key_exists($attr, (array)$type['attributes']['auto_form_fields']) || array_key_exists($attr, (array)$type['attributes']['form_fields']) || array_key_exists($attr, (array)$type['attributes']['fields']) ) { $found = true; break; } } if (!$found) { unset($value[$idx]); } } if (!empty($value)) { $attribs[$key] = $value; } } return $name ? $attribs[$name] : $attribs; } /** * Object list (and search handler) */ public function action_list() { if (empty($this->list_attribs)) { return; } $task = $this->get_task(); $page = (int) self::get_input('page', 'POST'); if (!$page || $page < 1) { $page = 1; } // request parameters $post = array( 'attributes' => $this->list_attribs, 'sort_by' => $this->list_attribs, // 'sort_order' => 'ASC', 'page_size' => $this->page_size, 'page' => $page, ); // search parameters if (!empty($_POST['search'])) { $search = self::get_input('search', 'POST', true); $field = self::get_input('field', 'POST'); $method = self::get_input('method', 'POST'); $attrs = $this->get_search_attribs($field); $search_request = array(); // Support attribute=value searches if ($task != 'settings' && preg_match('/^([a-z0-9]+)([~<>]*=)(.+)$/i', $search, $m)) { $search_attr = $m[1]; $search_type = $m[2]; $search_value = $m[3]; if ($search_value === '*') { $search_type = 'exists'; $search_value = ''; } else if ($search_value[0] === '*' && $search_value[strlen($search_value)-1] === '*') { $search_type = 'both'; $search_value = substr($search_value, 1, -1); } else if ($search_value[0] === '*') { $search_type = 'suffix'; $search_value = substr($search_value, 1); } else if ($search_value[strlen($search_value)-1] === '*') { $search_type = 'prefix'; $search_value = substr($search_value, 0, -1); } $search_request[$search_attr] = array( 'value' => $search_value, 'type' => $search_type, ); } else { foreach ($attrs as $attr) { $search_request[$attr] = array( 'value' => $search, 'type' => $method, ); } } } else if (!empty($_POST['search_request'])) { $search_request = self::get_input('search_request', 'POST'); $search_request = @unserialize(base64_decode($search_request)); } if (!empty($search_request)) { $post['search'] = $search_request; $post['search_operator'] = 'OR'; } // get users list $module = $this->list_module ? $this->list_module : $task . 's'; $result = $this->api_post($module . '.list', null, $post); $count = $result->get('count'); $result = (array) $result->get('list'); // calculate records if ($count) { $start = 1 + max(0, $page - 1) * $this->page_size; $end = min($start + $this->page_size - 1, $count); } $rows = $head = $foot = array(); $cols = array('name'); $i = 0; $table_class = 'list'; // table header $head[0]['cells'][] = array('class' => 'name', 'body' => $this->translate($task . '.list')); // table footer (navigation) if ($count) { $pages = ceil($count / $this->page_size); $prev = max(0, $page - 1); $next = $page < $pages ? $page + 1 : 0; $count_str = kolab_html::span(array( 'content' => $this->translate('list.records', $start, $end, $count)), true); $prev = kolab_html::a(array( 'class' => 'prev font-icon' . ($prev ? '' : ' disabled'), 'href' => '#', 'onclick' => $prev ? "kadm.command('$task.list', {page: $prev})" : "return false", )); $next = kolab_html::a(array( 'class' => 'next font-icon' . ($next ? '' : ' disabled'), 'href' => '#', 'onclick' => $next ? "kadm.command('$task.list', {page: $next})" : "return false", )); $foot_body = kolab_html::span(array('content' => $prev . $count_str . $next)); } $foot[0]['cells'][] = array('class' => 'listnav', 'body' => $foot_body); // table body if (!empty($result)) { if (method_exists($this, 'list_result_handler')) { list($rows, $head, $foot, $table_class) = $this->list_result_handler($result, $head, $foot, $table_class); } else { foreach ($result as $idx => $item) { if (!is_array($item)) { continue; } $class = array('selectable'); if (method_exists($this, 'list_item_handler')) { $item = $this->list_item_handler($item, $class); } else { $item = array_shift($item); } if (empty($item)) { continue; } $i++; $cells = array(); $cells[] = array('class' => 'name', 'body' => kolab_html::escape($item), 'onclick' => "kadm.command('$task.info', '" . kolab_utils::js_escape($idx) . "')"); $rows[] = array('id' => $i, 'class' => implode(' ', $class), 'cells' => $cells); } } } else { $rows[] = array('cells' => array( 0 => array('class' => 'empty-body', 'body' => $this->translate($task . '.norecords') ))); } $table = kolab_html::table(array( 'id' => $task . 'list', 'class' => $table_class . ' table table-sm', 'head' => $head, 'body' => $rows, 'foot' => $foot, )); if ($this->action == 'list') { $this->output->command('set_watermark', 'taskcontent'); } $this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null); $this->output->set_env('list_page', $page); $this->output->set_env('list_count', $count); $this->output->set_env('list_size', count($result)); $this->output->set_object($task . 'list', $table); } }