Files
server/usr/share/psa-pear/pear/php/Horde/Group/Ldap.php
2026-01-07 20:52:11 +01:00

534 lines
15 KiB
PHP

<?php
/**
* Copyright 2005-2016 Horde LLC (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*
* @author Ben Chavet <ben@horde.org>
* @author Jan Schneider <jan@horde.org>
* @category Horde
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @package Group
*/
/**
* This class provides an LDAP driver for the Horde group system.
*
* @author Ben Chavet <ben@horde.org>
* @author Jan Schneider <jan@horde.org>
* @category Horde
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @package Group
*/
class Horde_Group_Ldap extends Horde_Group_Base
{
/**
* Handle for the current LDAP connection.
*
* @var Horde_Ldap
*/
protected $_ldap;
/**
* Any additional parameters for the driver.
*
* @var array
*/
protected $_params;
/**
* LDAP filter for searching groups.
*
* @var Horde_Ldap_Filter
*/
protected $_filter;
/**
* Constructor.
*
* @throws Horde_Group_Exception
*/
public function __construct($params)
{
parent::__construct($params);
$params = array_merge(
array('binddn' => '',
'bindpw' => '',
'gid' => 'cn',
'memberuid' => 'memberUid',
'objectclass' => array('posixGroup'),
'newgroup_objectclass' => array('posixGroup')),
$params
);
/* Check mandatory parameters. */
foreach (array('ldap', 'basedn') as $param) {
if (!isset($params[$param])) {
throw new Horde_Group_Exception('The \'' . $param . '\' parameter is missing.');
}
}
/* Set Horde_Ldap object. */
$this->_ldap = $params['ldap'];
unset($params['ldap']);
/* Lowercase attribute names. */
$params['gid'] = Horde_String::lower($params['gid']);
$params['memberuid'] = Horde_String::lower($params['memberuid']);
if (!is_array($params['newgroup_objectclass'])) {
$params['newgroup_objectclass'] = array($params['newgroup_objectclass']);
}
foreach ($params['newgroup_objectclass'] as &$objectClass) {
$objectClass = Horde_String::lower($objectClass);
}
/* Generate LDAP search filter. */
try {
$this->_filter = Horde_Ldap_Filter::build($params['search']);
} catch (Horde_Ldap_Exception $e) {
throw new Horde_Group_Exception($e);
}
$this->_params = $params;
}
/**
* Returns whether the group backend is read-only.
*
* @return boolean
*/
public function readOnly()
{
return !isset($this->_params['writedn']) ||
!isset($this->_params['writepw']);
}
/**
* Returns whether groups can be renamed.
*
* @return boolean
*/
public function renameSupported()
{
return false;
}
/**
* Creates a new group.
*
* @param string $name A group name.
* @param string $email The group's email address.
*
* @return mixed The ID of the created group.
* @throws Horde_Group_Exception
*/
protected function _create($name, $email = null)
{
if ($this->readOnly()) {
throw new Horde_Group_Exception('This group backend is read-only.');
}
$attributes = array(
$this->_params['gid'] => $name,
'objectclass' => $this->_params['newgroup_objectclass'],
'gidnumber' => $this->_nextGid()
);
if (!empty($email)) {
$attributes['mail'] = $email;
}
$dn = Horde_Ldap::quoteDN(array(array($this->_params['gid'], $name)))
. ',' . $this->_params['basedn'];
try {
$entry = Horde_Ldap_Entry::createFresh($dn, $attributes);
$this->_rebind(true);
$this->_ldap->add($entry);
$this->_rebind(false);
} catch (Horde_Ldap_Exception $e) {
throw new Horde_Group_Exception($e);
}
return $dn;
}
/**
* Renames a group.
*
* @param mixed $gid A group ID.
* @param string $name The new name.
*
* @throws Horde_Group_Exception
*/
protected function _rename($gid, $name)
{
throw new Horde_Group_Exception('Renaming groups is not supported with the LDAP driver.');
}
/**
* Removes a group.
*
* @param mixed $gid A group ID.
*
* @throws Horde_Group_Exception
*/
protected function _remove($gid)
{
if ($this->readOnly()) {
throw new Horde_Group_Exception('This group backend is read-only.');
}
try {
$this->_rebind(true);
$this->_ldap->delete($gid);
$this->_rebind(false);
} catch (Horde_Ldap_Exception $e) {
throw new Horde_Group_Exception($e);
}
}
/**
* Checks if a group exists.
*
* @param mixed $gid A group ID.
*
* @return boolean True if the group exists.
* @throws Horde_Group_Exception
*/
protected function _exists($gid)
{
try {
return $this->_ldap->exists($gid);
} catch (Horde_Ldap_Exception $e) {
throw new Horde_Group_Exception($e);
}
}
/**
* Returns a group name.
*
* @param mixed $gid A group ID.
*
* @return string The group's name.
* @throws Horde_Group_Exception
* @throws Horde_Exception_NotFound
*/
protected function _getName($gid)
{
try {
$entry = $this->_ldap->getEntry($gid);
return $entry->getValue($this->_params['gid'], 'single');
} catch (Horde_Ldap_Exception $e) {
throw new Horde_Group_Exception($e);
}
}
/**
* Returns all available attributes of a group.
*
* @param mixed $gid A group ID.
*
* @return array The group's date.
* @throws Horde_Group_Exception
* @throws Horde_Exception_NotFound
*/
protected function _getData($gid)
{
try {
$entry = $this->_ldap->getEntry($gid);
$attributes = $entry->getValues();
} catch (Horde_Ldap_Exception $e) {
throw new Horde_Group_Exception($e);
}
$data = array();
foreach ($attributes as $attribute => $value) {
switch ($attribute) {
case $this->_params['gid']:
$attribute = 'name';
break;
case 'mail':
$attribute = 'email';
break;
}
$data[$attribute] = $value;
}
return $data;
}
/**
* Sets one or more attributes of a group.
*
* @param mixed $gid A group ID.
* @param array|string $attribute An attribute name or a hash of
* attributes.
* @param string $value An attribute value if $attribute is a
* string.
*
* @throws Horde_Group_Exception
* @throws Horde_Exception_NotFound
*/
protected function _setData($gid, $attribute, $value = null)
{
if ($this->readOnly()) {
throw new Horde_Group_Exception('This group backend is read-only.');
}
$attributes = is_array($attribute)
? $attribute
: array($attribute => $value);
try {
$entry = $this->_ldap->getEntry($gid);
foreach ($attributes as $attribute => $value) {
switch ($attribute) {
case 'name':
$attribute = $this->_params['gid'];
break;
case 'email':
$attribute = 'mail';
break;
}
$entry->replace(array($attribute => $value));
}
$this->_rebind(true);
$entry->update();
$this->_rebind(false);
} catch (Horde_Ldap_Exception $e) {
throw new Horde_Group_Exception($e);
}
}
/**
* Returns a list of all groups a user may see, with IDs as keys and names
* as values.
*
* @return array All existing groups.
* @throws Horde_Group_Exception
*/
protected function _listAll()
{
$attr = $this->_params['gid'];
try {
$search = $this->_ldap->search($this->_params['basedn'],
$this->_filter,
array($attr));
} catch (Horde_Ldap_Exception $e) {
throw new Horde_Group_Exception($e);
}
$entries = array();
foreach ($search->sortedAsArray(array($attr)) as $entry) {
$entries[$entry['dn']] = $entry[$attr][0];
}
return $entries;
}
/**
* Returns a list of users in a group.
*
* @param mixed $gid A group ID.
*
* @return array List of group users.
* @throws Horde_Group_Exception
* @throws Horde_Exception_NotFound
*/
protected function _listUsers($gid)
{
$attr = $this->_params['memberuid'];
try {
$entry = $this->_ldap->getEntry($gid, array($attr));
if (!$entry->exists($attr)) {
return array();
}
if (empty($this->_params['attrisdn'])) {
return $entry->getValue($attr, 'all');
}
$users = array();
foreach ($entry->getValue($attr, 'all') as $user) {
$dn = Horde_Ldap_Util::explodeDN($user,
array('onlyvalues' => true));
// Very simplified approach: assume the first element of the DN
// contains the user ID.
$user = $dn[0];
// Check for multi-value RDNs.
if (is_array($element)) {
$user = $element[0];
}
$users[] = $user;
}
return $users;
} catch (Horde_Exception_NotFound $e) {
return array();
} catch (Horde_Ldap_Exception $e) {
throw new Horde_Group_Exception($e);
}
}
/**
* Returns a list of groups a user belongs to.
*
* @param string $user A user name.
*
* @return array A list of groups, with IDs as keys and names as values.
* @throws Horde_Group_Exception
*/
protected function _listGroups($user)
{
$attr = $this->_params['gid'];
try {
if (!empty($this->_params['attrisdn'])) {
$user = $this->_ldap->findUserDN($user);
}
$filter = Horde_Ldap_Filter::create($this->_params['memberuid'],
'equals', $user);
$filter = Horde_Ldap_Filter::combine('and', array($this->_filter, $filter));
$search = $this->_ldap->search($this->_params['basedn'], $filter,
array($attr));
} catch (Horde_Ldap_Exception $e) {
throw new Horde_Group_Exception($e);
}
$entries = array();
foreach ($search->sortedAsArray(array($attr)) as $entry) {
$entries[$entry['dn']] = $entry[$attr][0];
}
return $entries;
}
/**
* Add a user to a group.
*
* @param mixed $gid A group ID.
* @param string $user A user name.
*
* @throws Horde_Group_Exception
* @throws Horde_Exception_NotFound
*/
protected function _addUser($gid, $user)
{
if ($this->readOnly()) {
throw new Horde_Group_Exception('This group backend is read-only.');
}
$attr = $this->_params['memberuid'];
try {
if (!empty($this->_params['attrisdn'])) {
$user = $this->_ldap->findUserDN($user);
}
$entry = $this->_ldap->getEntry($gid, array($attr));
$entry->add(array($attr => $user));
$this->_rebind(true);
$entry->update();
$this->_rebind(false);
} catch (Horde_Ldap_Exception $e) {
throw new Horde_Group_Exception($e);
}
}
/**
* Removes a user from a group.
*
* @param mixed $gid A group ID.
* @param string $user A user name.
*
* @throws Horde_Group_Exception
* @throws Horde_Exception_NotFound
*/
protected function _removeUser($gid, $user)
{
if ($this->readOnly()) {
throw new Horde_Group_Exception('This group backend is read-only.');
}
$attr = $this->_params['memberuid'];
try {
if (!empty($this->_params['attrisdn'])) {
$user = $this->_ldap->findUserDN($user);
}
$entry = $this->_ldap->getEntry($gid, array($attr));
$entry->delete(array($attr => $user));
$this->_rebind(true);
$entry->update();
$this->_rebind(false);
} catch (Horde_Ldap_Exception $e) {
throw new Horde_Group_Exception($e);
}
}
/**
* Searches for group names.
*
* @param string $name A search string.
*
* @return array A list of matching groups, with IDs as keys and names as
* values.
* @throws Horde_Group_Exception
*/
protected function _search($name)
{
$attr = $this->_params['gid'];
try {
$result = $this->_ldap->search(
$this->_params['basedn'],
Horde_Ldap_Filter::create($attr, 'contains', $name),
array($attr));
} catch (Horde_Ldap_Exception $e) {
throw new Horde_Group_Exception($e);
}
$entries = array();
foreach ($result->sortedAsArray(array($attr)) as $entry) {
$entries[$entry['dn']] = $entry[$attr][0];
}
return $entries;
}
/**
* Searches existing groups for the highest gidnumber, and returns one
* higher.
*
* @return integer The next group ID.
*
* @throws Horde_Group_Exception
*/
protected function _nextGid()
{
try {
$search = $this->_ldap->search(
$this->_params['basedn'],
$this->_filter,
array('attributes' => array('gidnumber')));
} catch (Horde_Ldap_Exception $e) {
throw new Horde_Group_Exception($e);
}
if (!$search->count()) {
return 1;
}
$nextgid = 0;
foreach ($search as $entry) {
$nextgid = max($nextgid, $entry->getValue('gidnumber', 'single'));
}
return $nextgid + 1;
}
/**
* Rebinds to the LDAP server.
*
* @param boolean $write Whether to rebind for write access. Use false
* after finishing write actions.
*
* @throws Horde_Ldap_Exception
*/
protected function _rebind($write)
{
if ($write) {
$this->_ldap->bind($this->_params['writedn'], $this->_params['writepw']);
} else {
$this->_ldap->bind();
}
}
}