Support user_specific source in kolab_users_directory (Bifrost#T236416)

Move kolab_auth/kolab_auth_ldap to libkolab/lib/kolab_ldap.
It ends up much simpler to add user_specific support and unify some
code than replace use of kolab_auth_ldap with rcube_ldap.

This means that libkolab plugin does not depend on kolab_auth plugin
anymore, but kolab_auth depends on libkolab, which is better situation.
This commit is contained in:
Aleksander Machniak 2019-08-19 14:06:11 +00:00
parent 14e1a98d8c
commit 80a5241a9d
6 changed files with 193 additions and 82 deletions

View file

@ -25,6 +25,7 @@
], ],
"require": { "require": {
"php": ">=5.3.0", "php": ">=5.3.0",
"roundcube/plugin-installer": ">=0.1.3" "roundcube/plugin-installer": ">=0.1.3",
"kolab/libkolab": ">=3.5.1"
} }
} }

View file

@ -39,6 +39,7 @@ class kolab_auth extends rcube_plugin
$rcmail = rcube::get_instance(); $rcmail = rcube::get_instance();
$this->load_config(); $this->load_config();
$this->require_plugin('libkolab');
$this->add_hook('authenticate', array($this, 'authenticate')); $this->add_hook('authenticate', array($this, 'authenticate'));
$this->add_hook('startup', array($this, 'startup')); $this->add_hook('startup', array($this, 'startup'));
@ -796,28 +797,12 @@ class kolab_auth extends rcube_plugin
*/ */
public static function ldap() public static function ldap()
{ {
self::$ldap = kolab_storage::ldap('kolab_auth_addressbook');
if (self::$ldap) { if (self::$ldap) {
return self::$ldap; self::$ldap->extend_fieldmap(array('uniqueid' => 'nsuniqueid'));
} }
$rcmail = rcube::get_instance();
$addressbook = $rcmail->config->get('kolab_auth_addressbook');
if (!is_array($addressbook)) {
$ldap_config = (array)$rcmail->config->get('ldap_public');
$addressbook = $ldap_config[$addressbook];
}
if (empty($addressbook)) {
return null;
}
$addressbook['fieldmap']['uniqueid'] = 'nsuniqueid';
require_once __DIR__ . '/kolab_auth_ldap.php';
self::$ldap = new kolab_auth_ldap($addressbook);
return self::$ldap; return self::$ldap;
} }
@ -834,7 +819,7 @@ class kolab_auth extends rcube_plugin
/** /**
* Parses LDAP DN string with replacing supported variables. * Parses LDAP DN string with replacing supported variables.
* See kolab_auth_ldap::parse_vars() * See kolab_ldap::parse_vars()
* *
* @param string $str LDAP DN string * @param string $str LDAP DN string
* *

View file

@ -21,7 +21,7 @@
"require": { "require": {
"php": ">=5.3.0", "php": ">=5.3.0",
"roundcube/plugin-installer": ">=0.1.3", "roundcube/plugin-installer": ">=0.1.3",
"kolab/libkolab": ">=3.4.0", "kolab/libkolab": ">=3.5.1",
"kolab/kolab_auth": ">=3.4.0" "kolab/kolab_auth": ">=3.5.1"
} }
} }

View file

@ -79,7 +79,7 @@ class kolab_delegation_engine
// add delegate to the list // add delegate to the list
$list[] = $dn; $list[] = $dn;
$list = array_map(array('kolab_auth_ldap', 'dn_decode'), $list); $list = array_map(array('kolab_ldap', 'dn_decode'), $list);
// update user record // update user record
$result = $this->user_update_delegates($list); $result = $this->user_update_delegates($list);
@ -149,7 +149,7 @@ class kolab_delegation_engine
// remove delegate from the list // remove delegate from the list
unset($list[$dn]); unset($list[$dn]);
$list = array_keys($list); $list = array_keys($list);
$list = array_map(array('kolab_auth_ldap', 'dn_decode'), $list); $list = array_map(array('kolab_ldap', 'dn_decode'), $list);
$user[$this->ldap_delegate_field] = $list; $user[$this->ldap_delegate_field] = $list;
// update user record // update user record
@ -181,7 +181,7 @@ class kolab_delegation_engine
} }
// Get delegate // Get delegate
$user = $ldap->get_record(kolab_auth_ldap::dn_decode($dn)); $user = $ldap->get_record(kolab_ldap::dn_decode($dn));
if (empty($user)) { if (empty($user)) {
return array(); return array();
@ -230,27 +230,9 @@ class kolab_delegation_engine
return $this->ldap; return $this->ldap;
} }
if ($addressbook = $this->rc->config->get('kolab_delegation_addressbook')) { $this->ldap = kolab_storage::ldap('kolab_delegation_addressbook');
if (!is_array($addressbook)) {
$ldap_config = (array) $this->rc->config->get('ldap_public');
$addressbook = $ldap_config[$addressbook];
}
if (!empty($addressbook)) { if (!$this->ldap || !$this->ldap->ready) {
require_once __DIR__ . '/../kolab_auth/kolab_auth_ldap.php';
$ldap = new kolab_auth_ldap($addressbook);
}
}
// Fallback to kolab_auth plugin's addressbook
if (!$ldap) {
$ldap = kolab_auth::ldap();
}
$this->ldap = $ldap;
if (!$ldap || !$ldap->ready) {
return null; return null;
} }
@ -270,10 +252,10 @@ class kolab_delegation_engine
// Name of the LDAP field with organization name for identities // Name of the LDAP field with organization name for identities
$this->ldap_org_field = $this->rc->config->get('kolab_delegation_organization_field', $this->rc->config->get('kolab_auth_organization')); $this->ldap_org_field = $this->rc->config->get('kolab_delegation_organization_field', $this->rc->config->get('kolab_auth_organization'));
$ldap->set_filter($this->ldap_filter); $this->ldap->set_filter($this->ldap_filter);
$ldap->extend_fieldmap(array($this->ldap_delegate_field => $this->ldap_delegate_field)); $this->ldap->extend_fieldmap(array($this->ldap_delegate_field => $this->ldap_delegate_field));
return $ldap; return $this->ldap;
} }
/** /**
@ -540,7 +522,7 @@ class kolab_delegation_engine
} }
return array( return array(
'ID' => kolab_auth_ldap::dn_encode($dn), 'ID' => kolab_ldap::dn_encode($dn),
'uid' => $uid, 'uid' => $uid,
'name' => $name, 'name' => $name,
'realname' => $realname, 'realname' => $realname,

View file

@ -1,12 +1,11 @@
<?php <?php
/** /**
* Kolab Authentication * Kolab Authentication and User Base
* *
* @version @package_version@
* @author Aleksander Machniak <machniak@kolabsys.com> * @author Aleksander Machniak <machniak@kolabsys.com>
* *
* Copyright (C) 2011-2013, Kolab Systems AG <contact@kolabsys.com> * Copyright (C) 2011-2019, Kolab Systems AG <contact@kolabsys.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@ -25,10 +24,11 @@
/** /**
* Wrapper class for rcube_ldap_generic * Wrapper class for rcube_ldap_generic
*/ */
class kolab_auth_ldap extends rcube_ldap_generic class kolab_ldap extends rcube_ldap_generic
{ {
private $conf = array(); private $conf = array();
private $fieldmap = array(); private $fieldmap = array();
private $rcache;
function __construct($p) function __construct($p)
@ -44,6 +44,11 @@ class kolab_auth_ldap extends rcube_ldap_generic
$p['attributes'] = array_values($this->fieldmap); $p['attributes'] = array_values($this->fieldmap);
$p['debug'] = (bool) $rcmail->config->get('ldap_debug'); $p['debug'] = (bool) $rcmail->config->get('ldap_debug');
if ($cache_type = $rcmail->config->get('ldap_cache', 'db')) {
$cache_ttl = $rcmail->config->get('ldap_cache_ttl', '10m');
$this->cache = $rcmail->get_cache('LDAP.kolab_cache', $cache_type, $cache_ttl);
}
// Connect to the server (with bind) // Connect to the server (with bind)
parent::__construct($p); parent::__construct($p);
$this->_connect(); $this->_connect();
@ -65,19 +70,145 @@ class kolab_auth_ldap extends rcube_ldap_generic
continue; continue;
} }
$bind_pass = $this->config['bind_pass']; $bind_pass = $this->config['bind_pass'];
$bind_user = $this->config['bind_user']; $bind_user = $this->config['bind_user'];
$bind_dn = $this->config['bind_dn']; $bind_dn = $this->config['bind_dn'];
$base_dn = $this->config['base_dn'];
$groups_base_dn = $this->config['groups']['base_dn'] ?: $base_dn;
// User specific access, generate the proper values to use.
if ($this->config['user_specific']) {
$rcube = rcube::get_instance();
// No password set, use the session password
if (empty($bind_pass)) {
$bind_pass = $rcube->get_user_password();
}
// Get the pieces needed for variable replacement.
if ($fu = ($rcube->get_user_email() ?: $this->config['username'])) {
list($u, $d) = explode('@', $fu);
}
else {
$d = $this->config['mail_domain'];
}
$dc = 'dc=' . strtr($d, array('.' => ',dc=')); // hierarchal domain string
// resolve $dc through LDAP
if (!empty($this->config['domain_filter']) && !empty($this->config['search_bind_dn'])) {
$this->bind($this->config['search_bind_dn'], $this->config['search_bind_pw']);
$dc = $this->domain_root_dn($d);
}
$replaces = array('%dn' => '', '%dc' => $dc, '%d' => $d, '%fu' => $fu, '%u' => $u);
// Search for the dn to use to authenticate
if ($this->config['search_base_dn'] && $this->config['search_filter']
&& (strstr($bind_dn, '%dn') || strstr($base_dn, '%dn') || strstr($groups_base_dn, '%dn'))
) {
$search_attribs = array('uid');
if ($search_bind_attrib = (array) $this->config['search_bind_attrib']) {
foreach ($search_bind_attrib as $r => $attr) {
$search_attribs[] = $attr;
$replaces[$r] = '';
}
}
$search_bind_dn = strtr($this->config['search_bind_dn'], $replaces);
$search_base_dn = strtr($this->config['search_base_dn'], $replaces);
$search_filter = strtr($this->config['search_filter'], $replaces);
$cache_key = 'DN.' . md5("$host:$search_bind_dn:$search_base_dn:$search_filter:" . $this->config['search_bind_pw']);
if ($this->cache && ($dn = $this->cache->get($cache_key))) {
$replaces['%dn'] = $dn;
}
else {
$ldap = $this;
if (!empty($search_bind_dn) && !empty($this->config['search_bind_pw'])) {
// To protect from "Critical extension is unavailable" error
// we need to use a separate LDAP connection
if (!empty($this->config['vlv'])) {
$ldap = new rcube_ldap_generic($this->config);
$ldap->config_set(array('cache' => $this->cache, 'debug' => $this->debug));
if (!$ldap->connect($host)) {
continue;
}
}
if (!$ldap->bind($search_bind_dn, $this->config['search_bind_pw'])) {
continue; // bind failed, try next host
}
}
$res = $ldap->search($search_base_dn, $search_filter, 'sub', $search_attribs);
if ($res) {
$res->rewind();
$replaces['%dn'] = key($res->entries(true));
// add more replacements from 'search_bind_attrib' config
if ($search_bind_attrib) {
$res = $res->current();
foreach ($search_bind_attrib as $r => $attr) {
$replaces[$r] = $res[$attr][0];
}
}
}
if ($ldap != $this) {
$ldap->close();
}
}
// DN not found
if (empty($replaces['%dn'])) {
if (!empty($this->config['search_dn_default']))
$replaces['%dn'] = $this->config['search_dn_default'];
else {
rcube::raise_error(array(
'code' => 100, 'type' => 'ldap',
'file' => __FILE__, 'line' => __LINE__,
'message' => "DN not found using LDAP search."), true);
continue;
}
}
if ($this->cache && !empty($replaces['%dn'])) {
$this->cache->set($cache_key, $replaces['%dn']);
}
}
// Replace the bind_dn and base_dn variables.
$bind_dn = strtr($bind_dn, $replaces);
$base_dn = strtr($base_dn, $replaces);
$groups_base_dn = strtr($groups_base_dn, $replaces);
// replace placeholders in filter settings
if (!empty($this->config['filter'])) {
$this->config['filter'] = strtr($this->config['filter'], $replaces);
}
foreach (array('base_dn', 'filter', 'member_filter') as $k) {
if (!empty($this->config['groups'][$k])) {
$this->config['groups'][$k] = strtr($this->config['groups'][$k], $replaces);
}
}
if (empty($bind_user)) {
$bind_user = $u;
}
}
if (empty($bind_pass)) { if (empty($bind_pass)) {
$this->ready = true; $this->ready = true;
} }
else { else {
if (!empty($bind_dn)) { if (!empty($this->config['auth_cid'])) {
$this->ready = $this->bind($bind_dn, $bind_pass); $this->ready = $this->sasl_bind($this->config['auth_cid'], $bind_pass, $bind_dn);
} }
else if (!empty($this->config['auth_cid'])) { else if (!empty($bind_dn)) {
$this->ready = $this->sasl_bind($this->config['auth_cid'], $bind_pass, $bind_user); $this->ready = $this->bind($bind_dn, $bind_pass);
} }
else { else {
$this->ready = $this->sasl_bind($bind_user, $bind_pass); $this->ready = $this->sasl_bind($bind_user, $bind_pass);
@ -220,7 +351,7 @@ class kolab_auth_ldap extends rcube_ldap_generic
* @param int $limit Number of records * @param int $limit Number of records
* @param int $count Returns the number of records found * @param int $count Returns the number of records found
* *
* @return array List or false on error * @return array List of LDAP records found
*/ */
function dosearch($fields, $value, $mode=1, $required = array(), $limit = 0, &$count = 0) function dosearch($fields, $value, $mode=1, $required = array(), $limit = 0, &$count = 0)
{ {

View file

@ -48,10 +48,10 @@ class kolab_storage
private static $subscriptions; private static $subscriptions;
private static $ldapcache = array(); private static $ldapcache = array();
private static $typedata = array(); private static $typedata = array();
private static $ldap = array();
private static $states; private static $states;
private static $config; private static $config;
private static $imap; private static $imap;
private static $ldap;
// Default folder names // Default folder names
@ -96,7 +96,7 @@ class kolab_storage
'message' => "required kolabformat module not found" 'message' => "required kolabformat module not found"
), true); ), true);
} }
else { else if (self::$imap->get_error_code()) {
rcube::raise_error(array( rcube::raise_error(array(
'code' => 900, 'type' => 'php', 'message' => "IMAP error" 'code' => 900, 'type' => 'php', 'message' => "IMAP error"
), true); ), true);
@ -116,16 +116,23 @@ class kolab_storage
/** /**
* Initializes LDAP object to resolve Kolab users * Initializes LDAP object to resolve Kolab users
*
* @param string $name Name of the configuration option with LDAP config
*/ */
public static function ldap() public static function ldap($name = 'kolab_users_directory')
{ {
if (self::$ldap) {
return self::$ldap;
}
self::setup(); self::setup();
$config = self::$config->get('kolab_users_directory', self::$config->get('kolab_auth_addressbook')); $config = self::$config->get($name);
if (empty($config)) {
$name = 'kolab_auth_addressbook';
$config = self::$config->get($name);
}
if (self::$ldap[$name]) {
return self::$ldap[$name];
}
if (!is_array($config)) { if (!is_array($config)) {
$ldap_config = (array)self::$config->get('ldap_public'); $ldap_config = (array)self::$config->get('ldap_public');
@ -136,17 +143,21 @@ class kolab_storage
return null; return null;
} }
$ldap = new kolab_ldap($config);
// overwrite filter option // overwrite filter option
if ($filter = self::$config->get('kolab_users_filter')) { if ($filter = self::$config->get('kolab_users_filter')) {
self::$config->set('kolab_auth_filter', $filter); self::$config->set('kolab_auth_filter', $filter);
} }
// re-use the LDAP wrapper class from kolab_auth plugin $user_attrib = self::$config->get('kolab_users_id_attrib', self::$config->get('kolab_auth_login', 'mail'));
require_once rtrim(RCUBE_PLUGINS_DIR, '/') . '/kolab_auth/kolab_auth_ldap.php';
self::$ldap = new kolab_auth_ldap($config); //$ldap->set_filter($this->ldap_filter);
$ldap->extend_fieldmap(array($user_attrib => $user_attrib));
return self::$ldap; self::$ldap[$name] = $ldap;
return $ldap;
} }
/** /**
@ -1485,6 +1496,7 @@ class kolab_storage
} }
/** /**
* Search users in Kolab LDAP storage
* *
* @param mixed $query Search value (or array of field => value pairs) * @param mixed $query Search value (or array of field => value pairs)
* @param int $mode Matching mode: 0 - partial (*abc*), 1 - strict (=), 2 - prefix (abc*) * @param int $mode Matching mode: 0 - partial (*abc*), 1 - strict (=), 2 - prefix (abc*)
@ -1492,19 +1504,23 @@ class kolab_storage
* @param int $limit Maximum number of records * @param int $limit Maximum number of records
* @param int $count Returns the number of records found * @param int $count Returns the number of records found
* *
* @return array List or false on error * @return array List of users
*/ */
public static function search_users($query, $mode = 1, $required = array(), $limit = 0, &$count = 0) public static function search_users($query, $mode = 1, $required = array(), $limit = 0, &$count = 0)
{ {
$query = str_replace('*', '', $query); $query = str_replace('*', '', $query);
// requires a working LDAP setup // requires a working LDAP setup
if (!self::ldap() || strlen($query) == 0) { if (!strlen($query) || !($ldap = self::ldap())) {
return array(); return array();
} }
$root = self::namespace_root('other');
$user_attrib = self::$config->get('kolab_users_id_attrib', self::$config->get('kolab_auth_login', 'mail'));
$search_attrib = self::$config->get('kolab_users_search_attrib', array('cn','mail','alias'));
// search users using the configured attributes // search users using the configured attributes
$results = self::$ldap->dosearch(self::$config->get('kolab_users_search_attrib', array('cn','mail','alias')), $query, $mode, $required, $limit, $count); $results = $ldap->dosearch($search_attrib, $query, $mode, $required, $limit, $count);
// exclude myself // exclude myself
if ($_SESSION['kolab_dn']) { if ($_SESSION['kolab_dn']) {
@ -1512,9 +1528,6 @@ class kolab_storage
} }
// resolve to IMAP folder name // resolve to IMAP folder name
$root = self::namespace_root('other');
$user_attrib = self::$config->get('kolab_users_id_attrib', self::$config->get('kolab_auth_login', 'mail'));
array_walk($results, function(&$user, $dn) use ($root, $user_attrib) { array_walk($results, function(&$user, $dn) use ($root, $user_attrib) {
list($localpart, ) = explode('@', $user[$user_attrib]); list($localpart, ) = explode('@', $user[$user_attrib]);
$user['kolabtargetfolder'] = $root . $localpart; $user['kolabtargetfolder'] = $root . $localpart;
@ -1652,7 +1665,6 @@ class kolab_storage
/** /**
* Get user attributes for specified other user (imap) folder identifier. * Get user attributes for specified other user (imap) folder identifier.
* Note: This uses LDAP config/code from kolab_auth.
* *
* @param string $folder_id Folder name w/o path (imap user identifier) * @param string $folder_id Folder name w/o path (imap user identifier)
* @param bool $as_string Return configured display name attribute value * @param bool $as_string Return configured display name attribute value
@ -1692,7 +1704,7 @@ class kolab_storage
$user = $cache->get($token); $user = $cache->get($token);
} }
if (empty($user) && ($ldap = kolab_storage::ldap())) { if (empty($user) && ($ldap = self::ldap())) {
$user = $ldap->get_user_record($token, $_SESSION['imap_host']); $user = $ldap->get_user_record($token, $_SESSION['imap_host']);
if (!empty($user)) { if (!empty($user)) {