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": {
"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();
$this->load_config();
$this->require_plugin('libkolab');
$this->add_hook('authenticate', array($this, 'authenticate'));
$this->add_hook('startup', array($this, 'startup'));
@ -796,28 +797,12 @@ class kolab_auth extends rcube_plugin
*/
public static function ldap()
{
self::$ldap = kolab_storage::ldap('kolab_auth_addressbook');
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;
}
@ -834,7 +819,7 @@ class kolab_auth extends rcube_plugin
/**
* 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
*

View file

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

View file

@ -79,7 +79,7 @@ class kolab_delegation_engine
// add delegate to the list
$list[] = $dn;
$list = array_map(array('kolab_auth_ldap', 'dn_decode'), $list);
$list = array_map(array('kolab_ldap', 'dn_decode'), $list);
// update user record
$result = $this->user_update_delegates($list);
@ -149,7 +149,7 @@ class kolab_delegation_engine
// remove delegate from the list
unset($list[$dn]);
$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;
// update user record
@ -181,7 +181,7 @@ class kolab_delegation_engine
}
// Get delegate
$user = $ldap->get_record(kolab_auth_ldap::dn_decode($dn));
$user = $ldap->get_record(kolab_ldap::dn_decode($dn));
if (empty($user)) {
return array();
@ -230,27 +230,9 @@ class kolab_delegation_engine
return $this->ldap;
}
if ($addressbook = $this->rc->config->get('kolab_delegation_addressbook')) {
if (!is_array($addressbook)) {
$ldap_config = (array) $this->rc->config->get('ldap_public');
$addressbook = $ldap_config[$addressbook];
}
$this->ldap = kolab_storage::ldap('kolab_delegation_addressbook');
if (!empty($addressbook)) {
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) {
if (!$this->ldap || !$this->ldap->ready) {
return null;
}
@ -270,10 +252,10 @@ class kolab_delegation_engine
// 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'));
$ldap->set_filter($this->ldap_filter);
$ldap->extend_fieldmap(array($this->ldap_delegate_field => $this->ldap_delegate_field));
$this->ldap->set_filter($this->ldap_filter);
$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(
'ID' => kolab_auth_ldap::dn_encode($dn),
'ID' => kolab_ldap::dn_encode($dn),
'uid' => $uid,
'name' => $name,
'realname' => $realname,

View file

@ -1,12 +1,11 @@
<?php
/**
* Kolab Authentication
* Kolab Authentication and User Base
*
* @version @package_version@
* @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
* it under the terms of the GNU Affero General Public License as
@ -25,10 +24,11 @@
/**
* 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 $fieldmap = array();
private $rcache;
function __construct($p)
@ -44,6 +44,11 @@ class kolab_auth_ldap extends rcube_ldap_generic
$p['attributes'] = array_values($this->fieldmap);
$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)
parent::__construct($p);
$this->_connect();
@ -65,19 +70,145 @@ class kolab_auth_ldap extends rcube_ldap_generic
continue;
}
$bind_pass = $this->config['bind_pass'];
$bind_user = $this->config['bind_user'];
$bind_dn = $this->config['bind_dn'];
$bind_pass = $this->config['bind_pass'];
$bind_user = $this->config['bind_user'];
$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)) {
$this->ready = true;
}
else {
if (!empty($bind_dn)) {
$this->ready = $this->bind($bind_dn, $bind_pass);
if (!empty($this->config['auth_cid'])) {
$this->ready = $this->sasl_bind($this->config['auth_cid'], $bind_pass, $bind_dn);
}
else if (!empty($this->config['auth_cid'])) {
$this->ready = $this->sasl_bind($this->config['auth_cid'], $bind_pass, $bind_user);
else if (!empty($bind_dn)) {
$this->ready = $this->bind($bind_dn, $bind_pass);
}
else {
$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 $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)
{

View file

@ -48,10 +48,10 @@ class kolab_storage
private static $subscriptions;
private static $ldapcache = array();
private static $typedata = array();
private static $ldap = array();
private static $states;
private static $config;
private static $imap;
private static $ldap;
// Default folder names
@ -96,7 +96,7 @@ class kolab_storage
'message' => "required kolabformat module not found"
), true);
}
else {
else if (self::$imap->get_error_code()) {
rcube::raise_error(array(
'code' => 900, 'type' => 'php', 'message' => "IMAP error"
), true);
@ -116,16 +116,23 @@ class kolab_storage
/**
* 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();
$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)) {
$ldap_config = (array)self::$config->get('ldap_public');
@ -136,17 +143,21 @@ class kolab_storage
return null;
}
$ldap = new kolab_ldap($config);
// overwrite filter option
if ($filter = self::$config->get('kolab_users_filter')) {
self::$config->set('kolab_auth_filter', $filter);
}
// re-use the LDAP wrapper class from kolab_auth plugin
require_once rtrim(RCUBE_PLUGINS_DIR, '/') . '/kolab_auth/kolab_auth_ldap.php';
$user_attrib = self::$config->get('kolab_users_id_attrib', self::$config->get('kolab_auth_login', 'mail'));
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 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 $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)
{
$query = str_replace('*', '', $query);
// requires a working LDAP setup
if (!self::ldap() || strlen($query) == 0) {
if (!strlen($query) || !($ldap = self::ldap())) {
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
$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
if ($_SESSION['kolab_dn']) {
@ -1512,9 +1528,6 @@ class kolab_storage
}
// 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) {
list($localpart, ) = explode('@', $user[$user_attrib]);
$user['kolabtargetfolder'] = $root . $localpart;
@ -1652,7 +1665,6 @@ class kolab_storage
/**
* 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 bool $as_string Return configured display name attribute value
@ -1692,7 +1704,7 @@ class kolab_storage
$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']);
if (!empty($user)) {