summaryrefslogtreecommitdiff
path: root/plugins/kolab_auth
diff options
context:
space:
mode:
authorAleksander Machniak <machniak@kolabsys.com>2013-06-25 10:27:26 (GMT)
committerAleksander Machniak <machniak@kolabsys.com>2013-06-25 10:27:26 (GMT)
commite69e9b90ae6d187d4038897f007d2f786c1132a3 (patch)
tree5eb391e4af8f2b7137d9d8398105fd548e270141 /plugins/kolab_auth
parent65dfbee60170b3da6ba7658a717b8b8c9ead8caf (diff)
downloadroundcubemail-plugins-kolab-e69e9b90ae6d187d4038897f007d2f786c1132a3.tar.gz
Make kolab_auth's LDAP class be based on new rcube_ldap_generic class.
Move kolab_auth_ldap into separate file. Some improvements, including performance improvement in kolab_delegate
Diffstat (limited to 'plugins/kolab_auth')
-rw-r--r--plugins/kolab_auth/kolab_auth.php119
-rw-r--r--plugins/kolab_auth/kolab_auth_ldap.php403
-rw-r--r--plugins/kolab_auth/package.xml10
3 files changed, 431 insertions, 101 deletions
diff --git a/plugins/kolab_auth/kolab_auth.php b/plugins/kolab_auth/kolab_auth.php
index f511949..5579743 100644
--- a/plugins/kolab_auth/kolab_auth.php
+++ b/plugins/kolab_auth/kolab_auth.php
@@ -61,12 +61,12 @@ class kolab_auth extends rcube_plugin
$rcmail->config->set('imap_debug', true);
$rcmail->config->set('ldap_debug', true);
$rcmail->config->set('smtp_debug', true);
-
}
}
- public function startup($args) {
+ public function startup($args)
+ {
// Arguments are task / action, not interested
if (!empty($_SESSION['user_roledns'])) {
$this->load_user_role_plugins_and_settings($_SESSION['user_roledns']);
@@ -75,7 +75,8 @@ class kolab_auth extends rcube_plugin
return $args;
}
- public function load_user_role_plugins_and_settings($role_dns) {
+ public function load_user_role_plugins_and_settings($role_dns)
+ {
$rcmail = rcube::get_instance();
$this->load_config();
@@ -151,7 +152,8 @@ class kolab_auth extends rcube_plugin
}
}
- public function write_log($args) {
+ public function write_log($args)
+ {
$rcmail = rcube::get_instance();
if (!$rcmail->config->get('kolab_auth_auditlog', false)) {
@@ -286,7 +288,7 @@ class kolab_auth extends rcube_plugin
}
// Find user record in LDAP
- $record = $this->get_user_record($user, $host);
+ $record = $ldap->get_user_record($user, $host);
if (empty($record)) {
$args['abort'] = true;
@@ -309,8 +311,7 @@ class kolab_auth extends rcube_plugin
// Login As...
if (!empty($loginas) && $admin_login) {
// Authenticate to LDAP
- $dn = rcube_ldap::dn_decode($record['ID']);
- $result = $ldap->bind($dn, $pass);
+ $result = $ldap->bind($record['dn'], $pass);
if (!$result) {
$args['abort'] = true;
@@ -318,32 +319,24 @@ class kolab_auth extends rcube_plugin
}
// check if the original user has/belongs to administrative role/group
- $isadmin = false;
- $group = $rcmail->config->get('kolab_auth_group');
- $role_attr = $rcmail->config->get('kolab_auth_role');
- $role_dn = $rcmail->config->get('kolab_auth_role_value');
+ $isadmin = false;
+ $group = $rcmail->config->get('kolab_auth_group');
+ $role_dn = $rcmail->config->get('kolab_auth_role_value');
// check role attribute
if (!empty($role_attr) && !empty($role_dn) && !empty($record[$role_attr])) {
- $role_dn = $this->parse_vars($role_dn, $user, $host);
- foreach ((array)$record[$role_attr] as $role) {
- if ($role == $role_dn) {
- $isadmin = true;
- break;
- }
+ $role_dn = $ldap->parse_vars($role_dn, $user, $host);
+ if (in_array($role_dn, (array)$record[$role_attr])) {
+ $isadmin = true;
}
}
// check group
if (!$isadmin && !empty($group)) {
- $groups = $ldap->get_record_groups($record['ID']);
- foreach (array_keys($groups) as $g) {
- if ($group == rcube_ldap::dn_decode($g)) {
- $isadmin = true;
- break;
- }
+ $groups = $ldap->get_user_groups($record['dn'], $user, $host);
+ if (in_array($group, $groups)) {
+ $isadmin = true;
}
-
}
// Save original user login for log (see below)
@@ -358,7 +351,7 @@ class kolab_auth extends rcube_plugin
// user has the privilage, get "login as" user credentials
if ($isadmin) {
- $record = $this->get_user_record($loginas, $host);
+ $record = $ldap->get_user_record($loginas, $host);
}
if (empty($record)) {
@@ -376,7 +369,7 @@ class kolab_auth extends rcube_plugin
// Store UID and DN of logged user in session for use by other plugins
$_SESSION['kolab_uid'] = is_array($record['uid']) ? $record['uid'][0] : $record['uid'];
- $_SESSION['kolab_dn'] = $record['ID']; // encoded
+ $_SESSION['kolab_dn'] = $record['dn'];
// Set user login
if ($login_attr) {
@@ -485,80 +478,10 @@ class kolab_auth extends rcube_plugin
return null;
}
- self::$ldap = new kolab_auth_ldap_backend(
- $addressbook,
- $rcmail->config->get('ldap_debug'),
- $rcmail->config->mail_domain($_SESSION['imap_host'])
- );
+ require_once __DIR__ . '/kolab_auth_ldap.php';
- $rcmail->add_shutdown_function(array(self::$ldap, 'close'));
+ self::$ldap = new kolab_auth_ldap($addressbook);
return self::$ldap;
}
-
- /**
- * Fetches user data from LDAP addressbook
- */
- private function get_user_record($user, $host)
- {
- $rcmail = rcube::get_instance();
- $filter = $rcmail->config->get('kolab_auth_filter');
- $filter = $this->parse_vars($filter, $user, $host);
- $ldap = self::ldap();
-
- // reset old result
- $ldap->reset();
-
- // get record
- $ldap->set_filter($filter);
- $results = $ldap->list_records();
-
- if (count($results->records) == 1) {
- return $results->records[0];
- }
- }
-
- /**
- * Prepares filter query for LDAP search
- */
- private function parse_vars($str, $user, $host)
- {
- $rcmail = rcube::get_instance();
- $domain = $rcmail->config->get('username_domain');
-
- if (!empty($domain) && strpos($user, '@') === false) {
- if (is_array($domain) && isset($domain[$host])) {
- $user .= '@'.rcube_utils::parse_host($domain[$host], $host);
- }
- else if (is_string($domain)) {
- $user .= '@'.rcube_utils::parse_host($domain, $host);
- }
- }
-
- // replace variables in filter
- list($u, $d) = explode('@', $user);
- $dc = 'dc='.strtr($d, array('.' => ',dc=')); // hierarchal domain string
- $replaces = array('%dc' => $dc, '%d' => $d, '%fu' => $user, '%u' => $u);
-
- return strtr($str, $replaces);
- }
-}
-
-/**
- * Wrapper class for rcube_ldap addressbook
- */
-class kolab_auth_ldap_backend extends rcube_ldap
-{
- function __construct($p, $debug=false, $mail_domain=null)
- {
- parent::__construct($p, $debug, $mail_domain);
- $this->fieldmap['uid'] = 'uid';
- }
-
- function set_filter($filter)
- {
- if ($filter) {
- $this->prop['filter'] = $filter;
- }
- }
}
diff --git a/plugins/kolab_auth/kolab_auth_ldap.php b/plugins/kolab_auth/kolab_auth_ldap.php
new file mode 100644
index 0000000..b9e557e
--- /dev/null
+++ b/plugins/kolab_auth/kolab_auth_ldap.php
@@ -0,0 +1,403 @@
+<?php
+
+/**
+ * Kolab Authentication
+ *
+ * @version @package_version@
+ * @author Aleksander Machniak <machniak@kolabsys.com>
+ *
+ * Copyright (C) 2011-2013, 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
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Wrapper class for rcube_ldap_generic
+ */
+class kolab_auth_ldap extends rcube_ldap_generic
+{
+
+ function __construct($p)
+ {
+ $rcmail = rcube::get_instance();
+
+ $this->debug = (bool) $rcmail->config->get('ldap_debug');
+ $this->domain = $rcmail->config->get('username_domain');
+ $this->fieldmap = $p['fieldmap'];
+ $this->fieldmap['uid'] = 'uid';
+
+ $p['attributes'] = array_values($this->fieldmap);
+
+ // Connect to the server (with bind)
+ parent::__construct($p);
+ $this->_connect();
+
+ $rcmail->add_shutdown_function(array($this, 'close'));
+ }
+
+ /**
+ * Establish a connection to the LDAP server
+ */
+ private function _connect()
+ {
+ $rcube = rcube::get_instance();
+
+ // try to connect + bind for every host configured
+ // with OpenLDAP 2.x ldap_connect() always succeeds but ldap_bind will fail if host isn't reachable
+ // see http://www.php.net/manual/en/function.ldap-connect.php
+ foreach ((array)$this->config['hosts'] as $host) {
+ // skip host if connection failed
+ if (!$this->connect($host)) {
+ continue;
+ }
+
+ $bind_pass = $this->config['bind_pass'];
+ $bind_user = $this->config['bind_user'];
+ $bind_dn = $this->config['bind_dn'];
+
+ if (empty($bind_pass)) {
+ $this->ready = true;
+ }
+ else {
+ if (!empty($bind_dn)) {
+ $this->ready = $this->bind($bind_dn, $bind_pass);
+ }
+ else if (!empty($this->config['auth_cid'])) {
+ $this->ready = $this->sasl_bind($this->config['auth_cid'], $bind_pass, $bind_user);
+ }
+ else {
+ $this->ready = $this->sasl_bind($bind_user, $bind_pass);
+ }
+ }
+
+ // connection established, we're done here
+ if ($this->ready) {
+ break;
+ }
+
+ } // end foreach hosts
+
+ if (!is_resource($this->conn)) {
+ rcube::raise_error(array('code' => 100, 'type' => 'ldap',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Could not connect to any LDAP server, last tried $host"), true);
+
+ $this->ready = false;
+ }
+
+ return $this->ready;
+ }
+
+ /**
+ * Fetches user data from LDAP addressbook
+ */
+ function get_user_record($user, $host)
+ {
+ $rcmail = rcube::get_instance();
+ $filter = $rcmail->config->get('kolab_auth_filter');
+ $filter = $this->parse_vars($filter, $user, $host);
+ $base_dn = $this->parse_vars($this->config['base_dn'], $user, $host);
+ $scope = $this->config['scope'];
+
+ // get record
+ if ($result = parent::search($base_dn, $filter, $scope, $this->attributes)) {
+ if ($result->count() == 1) {
+ $entries = $result->entries(true);
+ $dn = key($entries);
+ $entry = array_pop($entries);
+ $entry = $this->field_mapping($dn, $entry);
+
+ return $entry;
+ }
+ }
+ }
+
+ /**
+ * Fetches user data from LDAP addressbook
+ */
+ function get_user_groups($dn, $user, $host)
+ {
+ if (empty($dn) || empty($this->config['groups'])) {
+ return array();
+ }
+
+ $base_dn = $this->parse_vars($this->config['groups']['base_dn'], $user, $host);
+ $name_attr = $this->config['groups']['name_attr'] ? $this->config['groups']['name_attr'] : 'cn';
+ $member_attr = $this->get_group_member_attr();
+ $filter = "(member=$dn)(uniqueMember=$dn)";
+
+ if ($member_attr != 'member' && $member_attr != 'uniqueMember')
+ $filter .= "($member_attr=$dn)";
+ $filter = strtr("(|$filter)", array("\\" => "\\\\"));
+
+ $result = parent::search($base_dn, $filter, 'sub', array('dn', $name_attr));
+
+ if (!$result) {
+ return array();
+ }
+
+ $groups = array();
+ foreach ($result as $entry) {
+ $entry = rcube_ldap_generic::normalize_entry($entry);
+ if (!$entry['dn']) {
+ $entry['dn'] = $result->get_dn();
+ }
+ $groups[$entry['dn']] = $entry[$name_attr];
+ }
+
+ return $groups;
+ }
+
+ /**
+ * Get a specific LDAP record
+ *
+ * @param string DN
+ *
+ * @return array Record data
+ */
+ function get_record($dn)
+ {
+ if (!$this->ready) {
+ return;
+ }
+
+ if ($rec = $this->get_entry($dn)) {
+ $rec = rcube_ldap_generic::normalize_entry($rec);
+ $rec = $this->field_mapping($dn, $rec);
+ }
+
+ return $rec;
+ }
+
+ /**
+ * Replace LDAP record data items
+ *
+ * @param string $dn DN
+ * @param array $entry LDAP entry
+ *
+ * return bool True on success, False on failure
+ */
+ function replace($dn, $entry)
+ {
+ // fields mapping
+ foreach ($this->fieldmap as $field => $attr) {
+ if (array_key_exists($field, $entry)) {
+ $entry[$attr] = $entry[$field];
+ unset($entry[$field]);
+ }
+ }
+
+ return $this->mod_replace($dn, $entry);
+ }
+
+ /**
+ * Search records (simplified version of rcube_ldap::search)
+ *
+ * @param mixed $fields The field name of array of field names to search in
+ * @param mixed $value Search value (or array of values when $fields is array)
+ * @param int $mode Matching mode:
+ * 0 - partial (*abc*),
+ * 1 - strict (=),
+ * 2 - prefix (abc*)
+ * @param boolean $select True if results are requested, False if count only
+ * @param array $required List of fields that cannot be empty
+ * @param int $limit Number of records
+ *
+ * @return array List or false on error
+ */
+ function search($fields, $value, $mode=1, $required = array(), $limit = 0)
+ {
+ $mode = intval($mode);
+
+ // use AND operator for advanced searches
+ $filter = is_array($value) ? '(&' : '(|';
+
+ // set wildcards
+ $wp = $ws = '';
+ if (!empty($this->config['fuzzy_search']) && $mode != 1) {
+ $ws = '*';
+ if (!$mode) {
+ $wp = '*';
+ }
+ }
+
+ foreach ((array)$fields as $idx => $field) {
+ $val = is_array($value) ? $value[$idx] : $value;
+ if ($attrs = (array) $this->fieldmap[$field]) {
+ if (count($attrs) > 1)
+ $filter .= '(|';
+ foreach ($attrs as $f)
+ $filter .= "($f=$wp" . rcube_ldap_generic::quote_string($val) . "$ws)";
+ if (count($attrs) > 1)
+ $filter .= ')';
+ }
+ }
+ $filter .= ')';
+
+ // add required (non empty) fields filter
+ $req_filter = '';
+
+ foreach ((array)$required as $field) {
+ if (in_array($field, (array)$fields)) // required field is already in search filter
+ continue;
+ if ($attrs = (array) $this->fieldmap[$field]) {
+ if (count($attrs) > 1)
+ $req_filter .= '(|';
+ foreach ($attrs as $f)
+ $req_filter .= "($f=*)";
+ if (count($attrs) > 1)
+ $req_filter .= ')';
+ }
+ }
+
+ if (!empty($req_filter)) {
+ $filter = '(&' . $req_filter . $filter . ')';
+ }
+
+ // avoid double-wildcard if $value is empty
+ $filter = preg_replace('/\*+/', '*', $filter);
+
+ // add general filter to query
+ if (!empty($this->config['filter'])) {
+ $filter = '(&(' . preg_replace('/^\(|\)$/', '', $this->config['filter']) . ')' . $filter . ')';
+ }
+
+ $base_dn = $this->parse_vars($this->config['base_dn'], $_SESSION['username']);
+ $scope = $this->config['scope'];
+ $attrs = array_values($this->fieldmap);
+ $list = array();
+
+ if ($result = parent::search($base_dn, $filter, $scope, $attrs)) {
+ $i = 0;
+ foreach ($result as $entry) {
+ if ($limit && $limit <= $i) {
+ break;
+ }
+ $dn = $result->get_dn();
+ $list[$dn] = $this->field_mapping($dn, $entry);
+ $i++;
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * Set filter used in search()
+ */
+ function set_filter($filter)
+ {
+ $this->config['filter'] = $filter;
+ }
+
+ /**
+ * Maps LDAP attributes to defined fields
+ */
+ protected function field_mapping($dn, $entry)
+ {
+ $entry['dn'] = $dn;
+
+ // fields mapping
+ foreach ($this->fieldmap as $field => $attr) {
+ if (isset($entry[$attr])) {
+ $entry[$field] = $entry[$attr];
+ }
+ }
+
+ return $entry;
+ }
+
+ /**
+ * Detects group member attribute name
+ */
+ private function get_group_member_attr($object_classes = array())
+ {
+ if (empty($object_classes)) {
+ $object_classes = $this->config['groups']['object_classes'];
+ }
+ if (!empty($object_classes)) {
+ foreach ((array)$object_classes as $oc) {
+ switch (strtolower($oc)) {
+ case 'group':
+ case 'groupofnames':
+ case 'kolabgroupofnames':
+ $member_attr = 'member';
+ break;
+
+ case 'groupofuniquenames':
+ case 'kolabgroupofuniquenames':
+ $member_attr = 'uniqueMember';
+ break;
+ }
+ }
+ }
+
+ if (!empty($member_attr)) {
+ return $member_attr;
+ }
+
+ if (!empty($this->config['groups']['member_attr'])) {
+ return $this->config['groups']['member_attr'];
+ }
+
+ return 'member';
+ }
+
+ /**
+ * Prepares filter query for LDAP search
+ */
+ function parse_vars($str, $user, $host = null)
+ {
+ if (!empty($this->domain) && strpos($user, '@') === false) {
+ if ($host && is_array($this->domain) && isset($this->domain[$host])) {
+ $user .= '@'.rcube_utils::parse_host($this->domain[$host], $host);
+ }
+ else if (is_string($this->domain)) {
+ $user .= '@'.rcube_utils::parse_host($this->domain, $host);
+ }
+ }
+
+ // replace variables in filter
+ list($u, $d) = explode('@', $user);
+ $dc = 'dc='.strtr($d, array('.' => ',dc=')); // hierarchal domain string
+ $replaces = array('%dc' => $dc, '%d' => $d, '%fu' => $user, '%u' => $u);
+
+ return strtr($str, $replaces);
+ }
+
+ /**
+ * HTML-safe DN string encoding
+ *
+ * @param string $str DN string
+ *
+ * @return string Encoded HTML identifier string
+ */
+ static function dn_encode($str)
+ {
+ return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
+ }
+
+ /**
+ * Decodes DN string encoded with _dn_encode()
+ *
+ * @param string $str Encoded HTML identifier string
+ *
+ * @return string DN string
+ */
+ static function dn_decode($str)
+ {
+ $str = str_pad(strtr($str, '-_', '+/'), strlen($str) % 4, '=', STR_PAD_RIGHT);
+ return base64_decode($str);
+ }
+}
diff --git a/plugins/kolab_auth/package.xml b/plugins/kolab_auth/package.xml
index 2d75d83..00bc969 100644
--- a/plugins/kolab_auth/package.xml
+++ b/plugins/kolab_auth/package.xml
@@ -18,10 +18,10 @@
<email>machniak@kolabsys.com</email>
<active>yes</active>
</lead>
- <date>2012-12-19</date>
+ <date>2013-06-25</date>
<version>
- <release>0.6</release>
- <api>0.1</api>
+ <release>0.7</release>
+ <api>0.2</api>
</version>
<stability>
<release>stable</release>
@@ -35,6 +35,10 @@
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
+ <file name="kolab_auth_ldap.php" role="php">
+ <tasks:replace from="@name@" to="name" type="package-info"/>
+ <tasks:replace from="@package_version@" to="version" type="package-info"/>
+ </file>
<file name="config.inc.php.dist" role="data"></file>
<file name="LICENSE" role="data"></file>