Finished LDAP storage backend to work with FreeIPA ipaToken subclasses
Resolves T421
This commit is contained in:
parent
7f3a76fdad
commit
3e52521c3e
7 changed files with 275 additions and 113 deletions
|
@ -29,32 +29,74 @@ $config['kolab_2fa_storage'] = 'roundcube';
|
|||
// additional config options for the above storage backend
|
||||
// here an example for the LDAP backend:
|
||||
$config['kolab_2fa_storage_config'] = array(
|
||||
'debug' => false,
|
||||
'hosts' => array('localhost'),
|
||||
'port' => 389,
|
||||
'bind_dn' => 'uid=kolab-service,ou=Special Users,dc=example,dc=org',
|
||||
'bind_dn' => 'uid=kolab-auth-service,ou=Special Users,dc=example,dc=org',
|
||||
'bind_pass' => 'Welcome2KolabSystems',
|
||||
'base_dn' => 'ou=People,dc=example,dc=org',
|
||||
'filter' => '(&(objectClass=inetOrgPerson)(mail=%fu))',
|
||||
'base_dn' => 'ou=Tokens,dc=example,dc=org',
|
||||
// filter used to list stored factors for a user
|
||||
'filter' => '(&(objectClass=ipaToken)(objectclass=ldapSubEntry)(ipatokenOwner=%fu))',
|
||||
'scope' => 'sub',
|
||||
'debug' => true,
|
||||
// translates driver properties to LDAP attributes
|
||||
'fieldmap' => array(
|
||||
'active' => 'nsroledn',
|
||||
'@totp' => 'kolabAuthTOTP',
|
||||
'@hotp' => 'kolabAuthHOTP',
|
||||
'@yubikey' => 'kolabAuthYubikey',
|
||||
'label' => 'cn',
|
||||
'id' => 'ipatokenUniqueID',
|
||||
'active' => 'ipatokenDisabled',
|
||||
'created' => 'ipatokenNotBefore',
|
||||
'userdn' => 'ipatokenOwner',
|
||||
'secret' => 'ipatokenOTPkey',
|
||||
// HOTP attributes
|
||||
'counter' => 'ipatokenHOTPcounter',
|
||||
'digest' => 'ipatokenOTPalgorithm',
|
||||
'digits' => 'ipatokenOTPdigits',
|
||||
),
|
||||
// LDAP object classes derived from factor IDs (prefix)
|
||||
// will be translated into the %c placeholder
|
||||
'classmap' => array(
|
||||
'totp:' => 'ipatokenTOTP',
|
||||
'hotp:' => 'ipatokenHOTP',
|
||||
'*' => 'ipaToken',
|
||||
),
|
||||
// translates property values into LDAP attribute values and vice versa
|
||||
'valuemap' => array(
|
||||
'nsroledn' => array(
|
||||
'totp' => 'cn=totp-user,dc=example,dc=org',
|
||||
'hotp' => 'cn=hotp-user,dc=example,dc=org',
|
||||
'yubikey' => 'cn=yubikey-user,dc=example,dc=org',
|
||||
'active' => array(
|
||||
false => 'TRUE',
|
||||
true => 'FALSE',
|
||||
),
|
||||
),
|
||||
// specify non-string data types for properties for implicit conversion
|
||||
'attrtypes' => array(
|
||||
'created' => 'datetime',
|
||||
'counter' => 'integer',
|
||||
'digits' => 'integer',
|
||||
),
|
||||
// apply these default values to factor records if not specified by the drivers
|
||||
'defaults' => array(
|
||||
'active' => false,
|
||||
// these are required for ipatokenHOTP records and should match the kolab_2fa_hotp parameters
|
||||
'digest' => 'sha1',
|
||||
'digits' => 6,
|
||||
),
|
||||
// use this LDAP attribute to compose DN values for factor entries
|
||||
'rdn' => 'ipatokenUniqueID',
|
||||
// assign these object classes to new factor entries
|
||||
'objectclass' => array(
|
||||
'top',
|
||||
'ipaToken',
|
||||
'%c',
|
||||
'ldapSubEntry',
|
||||
),
|
||||
// add these roles to the user's LDAP record if key prefix-matches a factor entry
|
||||
'user_roles' => array(
|
||||
'totp:' => 'cn=totp-user,dc=example,dc=org',
|
||||
'hotp:' => 'cn=hotp-user,dc=example,dc=org',
|
||||
),
|
||||
);
|
||||
|
||||
// force a two-factor authentication for all users
|
||||
// force a lookup for active authentication factors for this user.
|
||||
// to be set by another plugin (e.g. kolab_auth based on LDAP roles)
|
||||
// $config['kolab_2fa_factors'] = array('totp');
|
||||
// $config['kolab_2fa_check'] = true;
|
||||
|
||||
// timeout for 2nd factor auth submission (in seconds)
|
||||
$config['kolab_2fa_timeout'] = 60;
|
||||
|
|
|
@ -97,16 +97,17 @@ class kolab_2fa extends rcube_plugin
|
|||
}
|
||||
|
||||
// 2a. let plugins provide the list of active authentication factors
|
||||
$lookup = $rcmail->plugins->exec_hook('kolab_2fa_factors', array(
|
||||
'user' => $args['user'],
|
||||
'host' => $hostname,
|
||||
'active' => $rcmail->config->get('kolab_2fa_factors'),
|
||||
$lookup = $rcmail->plugins->exec_hook('kolab_2fa_lookup', array(
|
||||
'user' => $args['user'],
|
||||
'host' => $hostname,
|
||||
'factors' => $rcmail->config->get('kolab_2fa_factors'),
|
||||
'check' => $rcmail->config->get('kolab_2fa_check', true),
|
||||
));
|
||||
if (isset($lookup['active'])) {
|
||||
$factors = (array)$lookup['active'];
|
||||
if (isset($lookup['factors'])) {
|
||||
$factors = (array)$lookup['factors'];
|
||||
}
|
||||
// 2b. check storage if this user has 2FA enabled
|
||||
else if ($storage = $this->get_storage($args['user'])) {
|
||||
else if ($lookup['check'] !== false && ($storage = $this->get_storage($args['user']))) {
|
||||
$factors = (array)$storage->enumerate();
|
||||
}
|
||||
|
||||
|
@ -304,7 +305,6 @@ class kolab_2fa extends rcube_plugin
|
|||
// attach storage
|
||||
$driver->storage = $this->get_storage();
|
||||
|
||||
// set user properties from active session
|
||||
if ($rcmail->user->ID) {
|
||||
$driver->username = $rcmail->get_user_name();
|
||||
}
|
||||
|
@ -334,13 +334,18 @@ class kolab_2fa extends rcube_plugin
|
|||
{
|
||||
if (!isset($this->storage) || (!empty($for) && $this->storage->username !== $for)) {
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
try {
|
||||
$this->storage = \Kolab2FA\Storage\Base::factory(
|
||||
$rcmail->config->get('kolab_2fa_storage', 'roundcube'),
|
||||
$rcmail->config->get('kolab_2fa_storage_config', array())
|
||||
);
|
||||
|
||||
$this->storage->set_username($for);
|
||||
|
||||
// set user properties from active session
|
||||
if (!empty($_SESSION['kolab_dn'])) {
|
||||
$this->storage->userdn = $_SESSION['kolab_dn'];
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$this->storage = false;
|
||||
|
|
|
@ -33,6 +33,7 @@ abstract class Base
|
|||
protected $props = array();
|
||||
protected $user_props = array();
|
||||
protected $pending_changes = false;
|
||||
protected $temporary = false;
|
||||
|
||||
protected $allowed_props = array('username');
|
||||
|
||||
|
@ -91,6 +92,7 @@ abstract class Base
|
|||
}
|
||||
else { // generate random ID
|
||||
$this->id = $this->method . ':' . bin2hex(openssl_random_pseudo_bytes(12));
|
||||
$this->temporary = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -257,6 +259,7 @@ abstract class Base
|
|||
if (!empty($this->user_props) && $this->storage && $this->pending_changes) {
|
||||
if ($this->storage->write($this->id, $this->user_props)) {
|
||||
$this->pending_changes = false;
|
||||
$this->temporary = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -294,7 +297,7 @@ abstract class Base
|
|||
*/
|
||||
protected function get_user_prop($key)
|
||||
{
|
||||
if (!isset($this->user_props[$key]) && $this->storage && !$this->pending_changes) {
|
||||
if (!isset($this->user_props[$key]) && $this->storage && !$this->pending_changes && !$this->temporary) {
|
||||
$this->user_props = (array)$this->storage->read($this->id);
|
||||
}
|
||||
|
||||
|
@ -306,18 +309,9 @@ abstract class Base
|
|||
*/
|
||||
protected function set_user_prop($key, $value)
|
||||
{
|
||||
$success = true;
|
||||
$this->pending_changes |= ($this->user_props[$key] !== $value);
|
||||
$this->user_props[$key] = $value;
|
||||
|
||||
/*
|
||||
if ($this->user_settings[$key] && $this->storage) {
|
||||
$props = (array)$this->storage->read($this->id);
|
||||
$props[$key] = $value;
|
||||
$success = $this->storage->write($this->id, $props);
|
||||
}
|
||||
*/
|
||||
return $success;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -81,11 +81,19 @@ class HOTP extends Base
|
|||
return false;
|
||||
}
|
||||
|
||||
$this->backend->setLabel($this->username)->setSecret($secret)->setCounter($this->get('counter'));
|
||||
$pass = $this->backend->verify($code, $counter, $this->config['window']);
|
||||
try {
|
||||
$this->backend->setLabel($this->username)->setSecret($secret)->setCounter(intval($this->get('counter')));
|
||||
$pass = $this->backend->verify($code, $counter, $this->config['window']);
|
||||
|
||||
// store incremented counter value
|
||||
$this->set('counter', $this->backend->getCounter());
|
||||
// store incremented counter value
|
||||
$this->set('counter', $this->backend->getCounter());
|
||||
$this->commit();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// LOG: exception
|
||||
console("VERIFY HOTP: $this->id, " . strval($e));
|
||||
$pass = false;
|
||||
}
|
||||
|
||||
// console('VERIFY HOTP', $this->username, $secret, $counter, $code, $pass);
|
||||
return $pass;
|
||||
|
@ -106,7 +114,7 @@ class HOTP extends Base
|
|||
|
||||
// TODO: deny call if already active?
|
||||
|
||||
$this->backend->setLabel($this->username)->setSecret($this->secret)->setCounter($this->get('counter'));
|
||||
$this->backend->setLabel($this->username)->setSecret($this->secret)->setCounter(intval($this->get('counter')));
|
||||
return $this->backend->getProvisioningUri();
|
||||
}
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ abstract class Base
|
|||
abstract public function write($key, $value);
|
||||
|
||||
/**
|
||||
* Remove the data stoed for the given key
|
||||
* Remove the data stored for the given key
|
||||
*/
|
||||
abstract public function remove($key);
|
||||
}
|
||||
|
|
|
@ -27,8 +27,10 @@ use \Net_LDAP3;
|
|||
|
||||
class LDAP extends Base
|
||||
{
|
||||
public $userdn;
|
||||
|
||||
private $cache = array();
|
||||
private $users = array();
|
||||
private $ldapcache = array();
|
||||
private $conn;
|
||||
private $error;
|
||||
|
||||
|
@ -52,20 +54,37 @@ class LDAP extends Base
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List/set methods activated for this user
|
||||
*/
|
||||
public function enumerate($active = true)
|
||||
{
|
||||
$filter = $this->parse_vars($this->config['filter'], '*');
|
||||
$base_dn = $this->parse_vars($this->config['base_dn'], '*');
|
||||
$scope = $this->config['scope'] ?: 'sub';
|
||||
$ids = array();
|
||||
|
||||
if ($this->ready && ($result = $this->conn->search($base_dn, $filter, $scope, array($this->config['fieldmap']['id'], $this->config['fieldmap']['active'])))) {
|
||||
foreach ($result as $dn => $entry) {
|
||||
$rec = $this->field_mapping($dn, Net_LDAP3::normalize_entry($entry, true));
|
||||
if (!empty($rec['id']) && ($active === null || $active == $rec['active'])) {
|
||||
$ids[] = $rec['id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: cache this in memory
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read data for the given key
|
||||
*/
|
||||
public function read($key)
|
||||
{
|
||||
if (!isset($this->cache[$key]) && ($rec = $this->get_ldap_record($this->username, $key))) {
|
||||
$pkey = '@' . $key;
|
||||
if (!empty($this->config['fieldmap'][$pkey])) {
|
||||
$rec = @json_decode($rec[$pkey], true);
|
||||
}
|
||||
else if ($this->config['fieldmap'][$key]) {
|
||||
$rec = $rec[$key];
|
||||
}
|
||||
$this->cache[$key] = $rec;
|
||||
if (!isset($this->cache[$key])) {
|
||||
$this->cache[$key] = $this->get_ldap_record($this->username, $key);
|
||||
}
|
||||
|
||||
return $this->cache[$key];
|
||||
|
@ -76,54 +95,83 @@ class LDAP extends Base
|
|||
*/
|
||||
public function write($key, $value)
|
||||
{
|
||||
if ($rec = $this->get_ldap_record($this->username, $key)) {
|
||||
$old_attrs = $rec['_raw'];
|
||||
$new_attrs = $old_attrs;
|
||||
$success = false;
|
||||
$ldap_attrs = array();
|
||||
|
||||
// serialize $value into one attribute
|
||||
$pkey = '@' . $key;
|
||||
if ($attr = $this->config['fieldmap'][$pkey]) {
|
||||
$new_attrs[$attr] = $value === null ? '' : json_encode($value);
|
||||
}
|
||||
else if ($attr = $this->config['fieldmap'][$key]) {
|
||||
$new_attrs[$attr] = $this->value_mapping($attr, $value, false);
|
||||
if (is_array($value)) {
|
||||
// add some default values
|
||||
$value += (array)$this->config['defaults'] + array('active' => false, 'username' => $this->username, 'userdn' => $this->userdn);
|
||||
|
||||
// special case nsroledn: keep other roles unknown to us
|
||||
if ($attr == 'nsroledn' && is_array($this->config['valuemap'][$attr])) {
|
||||
$map = $this->config['valuemap'][$attr];
|
||||
$new_attrs[$attr] = array_merge(
|
||||
$new_attrs[$attr],
|
||||
array_filter((array)$old_attrs[$attr], function($f) use ($map) { return !in_array($f, $map); })
|
||||
);
|
||||
foreach ($value as $k => $val) {
|
||||
if ($attr = $this->config['fieldmap'][$k]) {
|
||||
$ldap_attrs[$attr] = $this->value_mapping($k, $val, false);
|
||||
}
|
||||
}
|
||||
else if (is_array($value)) {
|
||||
foreach ($value as $k => $val) {
|
||||
if ($attr = $this->config['fieldmap'][$k]) {
|
||||
$new_attrs[$attr] = $this->value_mapping($attr, $value, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$result = $this->conn->modify_entry($rec['_dn'], $old_attrs, $new_attrs);
|
||||
|
||||
if (!empty($result)) {
|
||||
$this->cache[$key] = $value;
|
||||
$this->users = array();
|
||||
}
|
||||
|
||||
return !empty($result);
|
||||
}
|
||||
else {
|
||||
// invalid data structure
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
// update existing record
|
||||
if ($rec = $this->get_ldap_record($this->username, $key)) {
|
||||
$old_attrs = $rec['_raw'];
|
||||
$new_attrs = array_merge($old_attrs, $ldap_attrs);
|
||||
|
||||
$result = $this->conn->modify_entry($rec['_dn'], $old_attrs, $new_attrs);
|
||||
$success = !empty($result);
|
||||
}
|
||||
// insert new record
|
||||
else if ($this->ready) {
|
||||
$entry_dn = $this->get_entry_dn($this->username, $key);
|
||||
|
||||
// add object class attribute
|
||||
$me = $this;
|
||||
$ldap_attrs['objectclass'] = array_map(function($cls) use ($me, $key) {
|
||||
return $me->parse_vars($cls, $key);
|
||||
}, (array)$this->config['objectclass']);
|
||||
|
||||
$success = $this->conn->add_entry($entry_dn, $ldap_attrs);
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
$this->cache[$key] = $value;
|
||||
$this->ldapcache = array();
|
||||
|
||||
// cleanup: remove disabled/inactive/temporary entries
|
||||
if ($value['active']) {
|
||||
foreach ($this->enumerate(false) as $id) {
|
||||
if ($id != $key) {
|
||||
$this->remove($id);
|
||||
}
|
||||
}
|
||||
|
||||
// set user roles according to active factors
|
||||
$this->set_user_roles();
|
||||
}
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the data stoed for the given key
|
||||
* Remove the data stored for the given key
|
||||
*/
|
||||
public function remove($key)
|
||||
{
|
||||
return $this->write($key, null);
|
||||
if ($this->ready) {
|
||||
$entry_dn = $this->get_entry_dn($this->username, $key);
|
||||
$success = $this->conn->delete_entry($entry_dn);
|
||||
|
||||
// set user roles according to active factors
|
||||
if ($success) {
|
||||
$this->set_user_roles();
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -135,7 +183,41 @@ class LDAP extends Base
|
|||
|
||||
// reset cached values
|
||||
$this->cache = array();
|
||||
$this->users = array();
|
||||
$this->ldapcache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function set_user_roles()
|
||||
{
|
||||
if (!$this->ready || !$this->userdn || empty($this->config['user_roles'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$auth_roles = array();
|
||||
foreach ($this->enumerate(true) as $id) {
|
||||
foreach ($this->config['user_roles'] as $prefix => $role) {
|
||||
if (strpos($id, $prefix) === 0) {
|
||||
$auth_roles[] = $role;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$role_attr = $this->config['fieldmap']['roles'] ?: 'nsroledn';
|
||||
if ($user_attrs = $this->conn->get_entry($this->userdn, array($role_attr))) {
|
||||
$internals = array_values($this->config['user_roles']);
|
||||
$new_attrs = $old_attrs = Net_LDAP3::normalize_entry($user_attrs);
|
||||
$new_attrs[$role_attr] = array_merge(
|
||||
array_unique($auth_roles),
|
||||
array_filter((array)$old_attrs[$role_attr], function($f) use ($internals) { return !in_array($f, $internals); })
|
||||
);
|
||||
|
||||
$result = $this->conn->modify_entry($this->userdn, $old_attrs, $new_attrs);
|
||||
return !empty($result);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -143,25 +225,26 @@ class LDAP extends Base
|
|||
*/
|
||||
protected function get_ldap_record($user, $key)
|
||||
{
|
||||
$filter = $this->parse_vars($this->config['filter'], $user, $key);
|
||||
$base_dn = $this->parse_vars($this->config['base_dn'], $user, $key);
|
||||
$scope = $this->config['scope'] ?: 'sub';
|
||||
$entry_dn = $this->get_entry_dn($user, $key);
|
||||
|
||||
$cachekey = $base_dn . $filter;
|
||||
if (!isset($this->users[$cachekey])) {
|
||||
$this->users[$cachekey] = array();
|
||||
if (!isset($this->ldapcache[$entry_dn])) {
|
||||
$this->ldapcache[$entry_dn] = array();
|
||||
|
||||
if ($this->ready && ($result = $this->conn->search($base_dn, $filter, $scope, array_values($this->config['fieldmap'])))) {
|
||||
if ($result->count() == 1) {
|
||||
$entries = $result->entries(true);
|
||||
$dn = key($entries);
|
||||
$entry = array_pop($entries);
|
||||
$this->users[$cachekey] = $this->field_mapping($dn, $entry);
|
||||
}
|
||||
if ($this->ready && ($entry = $this->conn->get_entry($entry_dn, array_values($this->config['fieldmap'])))) {
|
||||
$this->ldapcache[$entry_dn] = $this->field_mapping($entry_dn, Net_LDAP3::normalize_entry($entry, true));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->users[$cachekey];
|
||||
return $this->ldapcache[$entry_dn];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose a full DN for the given record identifier
|
||||
*/
|
||||
protected function get_entry_dn($user, $key)
|
||||
{
|
||||
$base_dn = $this->parse_vars($this->config['base_dn'], $key);
|
||||
return sprintf('%s=%s,%s', $this->config['rdn'], Net_LDAP3::quote_string($key, true), $base_dn);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -176,10 +259,10 @@ class LDAP extends Base
|
|||
foreach ($this->config['fieldmap'] as $field => $attr) {
|
||||
$attr_lc = strtolower($attr);
|
||||
if (isset($entry[$attr_lc])) {
|
||||
$entry[$field] = $this->value_mapping($attr_lc, $entry[$attr_lc], true);
|
||||
$entry[$field] = $this->value_mapping($field, $entry[$attr_lc], true);
|
||||
}
|
||||
else if (isset($entry[$attr])) {
|
||||
$entry[$field] = $this->value_mapping($attr, $entry[$attr], true);
|
||||
$entry[$field] = $this->value_mapping($field, $entry[$attr], true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,28 +289,58 @@ class LDAP extends Base
|
|||
}
|
||||
}
|
||||
|
||||
// convert (date) type
|
||||
switch ($this->config['attrtypes'][$attr]) {
|
||||
case 'datetime':
|
||||
$ts = is_numeric($value) ? $value : strtotime($value);
|
||||
if ($ts) {
|
||||
$value = gmdate($reverse ? 'U' : 'YmdHi\Z', $ts);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'integer':
|
||||
$value = intval($value);
|
||||
break;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares filter query for LDAP search
|
||||
*/
|
||||
protected function parse_vars($str, $user, $key)
|
||||
protected function parse_vars($str, $key)
|
||||
{
|
||||
// replace variables in filter
|
||||
list($u, $d) = explode('@', $user);
|
||||
$user = $this->username;
|
||||
|
||||
if (strpos($user, '@') > 0) {
|
||||
list($u, $d) = explode('@', $user);
|
||||
}
|
||||
else if ($this->userdn) {
|
||||
$u = $this->userdn;
|
||||
$d = trim(str_replace(',dc=', '.', substr($u, strpos($u, ',dc='))), '.');
|
||||
}
|
||||
|
||||
if ($this->userdn) {
|
||||
$user = $this->userdn;
|
||||
}
|
||||
|
||||
// build hierarchal domain string
|
||||
$dc = $this->conn->domain_root_dn($d);
|
||||
|
||||
// map key value
|
||||
if (is_array($this->config['keymap']) && isset($this->config['keymap'][$key])) {
|
||||
$key = $this->config['keymap'][$key];
|
||||
$class = $this->config['classmap'] ? $this->config['classmap']['*'] : '*';
|
||||
|
||||
// map key to objectclass
|
||||
if (is_array($this->config['classmap'])) {
|
||||
foreach ($this->config['classmap'] as $k => $c) {
|
||||
if (strpos($key, $k) === 0) {
|
||||
$class = $c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: resolve $user into its DN for %udn
|
||||
|
||||
$replaces = array('%dc' => $dc, '%d' => $d, '%fu' => $user, '%u' => $u, '%k' => $key);
|
||||
$replaces = array('%dc' => $dc, '%d' => $d, '%fu' => $user, '%u' => $u, '%c' => $class);
|
||||
|
||||
return strtr($str, $replaces);
|
||||
}
|
||||
|
|
|
@ -116,7 +116,7 @@ class RcubeUser extends Base
|
|||
}
|
||||
|
||||
/**
|
||||
* Remove the data stoed for the given key
|
||||
* Remove the data stored for the given key
|
||||
*/
|
||||
public function remove($key)
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue