-
dockx thibault authored
Fixes ACL checking LDAP options and returning errors.
Verified864b2d14
<?php
/*
This code is part of FusionDirectory (http://www.fusiondirectory.org/)
Copyright (C) 2013-2016 FusionDirectory
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/*!
* \brief Class for handling objects and their types
*
* Allows to list, open, create and delete objects
*/
class objects
{
/*!
* \brief Get list of object of objectTypes from $types in $ou
*
* \param array $types the objectTypes to list
* \param mixed $attrs The attributes to fetch.
* If this is a single value, the resulting associative array will have for each dn the value of this attribute.
* If this is an array, the keys must be the wanted attributes, and the values can be either 1, '*', 'b64' or 'raw'
* depending if you want a single value or an array of values. 'raw' means untouched LDAP value and is only useful for dns.
* Other values are considered to be 1. 'b64' means an array of base64 encoded values and is mainly useful through webservice for binary attributes.
* \param string $ou the LDAP branch to search in, base will be used if it is NULL
* \param string $filter an additional filter to use in the LDAP search. (Might use special _template_cn field to search in template cn).
* \param boolean $checkAcl should ACL be ignored or checked? Defaults to FALSE.
* \param string $scope scope, defaults to subtree. When using one, be careful what you put in $ou.
* \param boolean $templateSearch Whether to search for templates or normal objects.
*
* \return The list of objects as an associative array (keys are dns)
*/
static function ls ($types, $attrs = NULL, string $ou = NULL, string $filter = '', bool $checkAcl = FALSE, string $scope = 'subtree', bool $templateSearch = FALSE, bool $sizeLimit = FALSE): array
{
global $ui, $config;
if ($ou === NULL) {
$ou = $config->current['BASE'];
}
if (!is_array($types)) {
$types = [$types];
}
if ($checkAcl) {
if (count($types) > 1) {
throw new FusionDirectoryException('Cannot evaluate ACL for several types');
}
$infos = static::infos(reset($types));
$acl = $infos['aclCategory'].'/'.$infos['mainTab'];
$tplAcl = $infos['aclCategory'].'/template';
}
$attrsAcls = [];
if ($attrs === NULL) {
if ($templateSearch) {
$attrs = 'cn';
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
} else {
$attrs = [];
foreach ($types as $type) {
$infos = static::infos($type);
if ($infos['mainAttr']) {
$attrs[] = $infos['mainAttr'];
}
}
$attrs = array_unique($attrs);
if (count($attrs) == 1) {
$attrs = $attrs[0];
} elseif (count($attrs) == 0) {
$attrs = ['dn' => 'raw'];
}
}
} elseif ($checkAcl) {
if (is_array($attrs)) {
$search_attrs = array_keys($attrs);
} else {
$search_attrs = [$attrs];
}
foreach ($search_attrs as $search_attr) {
//Below str_replace allows us to remove the options, resulting in proper ACL inspection. (ACLs do not take options).
$search_attr = preg_replace('/;x-.*/', '', $search_attr);
$category = $ui->getAttributeCategory($types[0], $search_attr);
if ($category === FALSE) {
throw new FusionDirectoryException('Could not find ACL for attribute "'.$search_attr.'" for type "'.$types[0].'"');
}
if ($category === TRUE) {
continue;
}
if (strpos($ui->get_permissions($ou, $category, $search_attr), 'r') === FALSE) {
$attrsAcls[$search_attr] = [$category, $search_attr];
}
}
}
if (is_array($attrs)) {
$search_attrs = array_keys($attrs);
} else {
$search_attrs = [$attrs];
}
if ($templateSearch) {
$search_attrs[] = 'fdTemplateField';
$search_attrs[] = 'cn';
}
try {
$ldap = static::search($types, $search_attrs, $ou, $filter, $checkAcl, $scope, $templateSearch, $partialFilterAcls, $sizeLimit);
} catch (NonExistingBranchException $e) {
return [];
}
$result = [];
while ($fetched_attrs = $ldap->fetch()) {
$key = $fetched_attrs['dn'];
if ($checkAcl) {
if (strpos($ui->get_permissions($key, $acl), 'r') === FALSE) {
continue;
}
foreach ($partialFilterAcls as $partialFilterAcl) {
if (strpos($ui->get_permissions($key, $partialFilterAcl[0], $partialFilterAcl[1]), 'r') === FALSE) {
continue 2;
}
}
}
if (is_array($attrs)) {
$result[$key] = [];
foreach ($attrs as $attr => $mode) {
if (isset($fetched_attrs[$attr])) {
if (isset($attrsAcls[$attr]) &&
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
(strpos($ui->get_permissions($key, $attrsAcls[$attr][0], $attrsAcls[$attr][1]), 'r') === FALSE)) {
continue;
}
switch ($mode) {
case '*':
unset($fetched_attrs[$attr]['count']);
case 'raw':
$result[$key][$attr] = $fetched_attrs[$attr];
break;
case 'b64':
unset($fetched_attrs[$attr]['count']);
$result[$key][$attr] = array_map('base64_encode', $fetched_attrs[$attr]);
break;
case 1:
default:
$result[$key][$attr] = $fetched_attrs[$attr][0];
}
}
}
if ($templateSearch) {
if (
isset($fetched_attrs['cn']) &&
(!$checkAcl || (strpos($ui->get_permissions($key, $tplAcl, 'template_cn'), 'r') !== FALSE))
) {
$result[$key]['cn'] = $fetched_attrs['cn'][0];
}
$result[$key]['fdTemplateField'] = [];
foreach ($fetched_attrs['fdTemplateField'] as $templateField) {
$attr = explode(':', $templateField, 2)[0];
if (isset($attrs[$attr])) {
if (isset($attrsAcls[$attr]) &&
(strpos($ui->get_permissions($key, $attrsAcls[$attr][0], $attrsAcls[$attr][1]), 'r') === FALSE)) {
continue;
}
$result[$key]['fdTemplateField'][] = $templateField;
}
}
if (empty($result[$key]['fdTemplateField'])) {
unset($result[$key]['fdTemplateField']);
}
}
if (count($result[$key]) === 0) {
unset($result[$key]);
}
} elseif ($templateSearch) {
if ($attrs == 'cn') {
if (
isset($fetched_attrs['cn']) &&
(!$checkAcl || (strpos($ui->get_permissions($key, $tplAcl, 'template_cn'), 'r') !== FALSE))
) {
$result[$key] = $fetched_attrs['cn'][0];
}
} else {
if (isset($attrsAcls[$attrs]) &&
(strpos($ui->get_permissions($key, $attrsAcls[$attrs][0], $attrsAcls[$attrs][1]), 'r') === FALSE)) {
continue;
}
foreach ($fetched_attrs['fdTemplateField'] as $templateField) {
list($attr, $value) = explode(':', $templateField, 2);
if ($attrs == $attr) {
$result[$key] = $value;
break;
}
}
}
} elseif (isset($fetched_attrs[$attrs])) {
if (isset($attrsAcls[$attrs]) &&
(strpos($ui->get_permissions($key, $attrsAcls[$attrs][0], $attrsAcls[$attrs][1]), 'r') === FALSE)) {
continue;
}
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
$result[$key] = $fetched_attrs[$attrs][0];
}
}
return $result;
}
/*!
* \brief Get count of objects of objectTypes from $types in $ou
*
* \param array $types the objectTypes to list
* \param string $ou the LDAP branch to search in, base will be used if it is NULL
* \param string $filter an additional filter to use in the LDAP search.
* \param boolean $checkAcl Should ACL be checked on the filtered attributes.
*
* \return The number of objects of type $type in $ou
*/
static function count ($types, string $ou = NULL, string $filter = '', bool $checkAcl = FALSE, bool $templateSearch = FALSE): int
{
try {
$ldap = static::search($types, ['dn'], $ou, $filter, $checkAcl, 'subtree', $templateSearch, $partialFilterAcls);
if (!empty($partialFilterAcls)) {
throw new FusionDirectoryException('Not enough rights to use "'.$partialFilterAcls[0][1].'" in filter');
}
} catch (EmptyFilterException $e) {
return 0;
} catch (NonExistingBranchException $e) {
return 0;
}
return $ldap->count();
}
private static function search ($types, $search_attrs, string $ou = NULL, string $filter = '', bool $checkAcl = FALSE, string $scope = 'subtree', bool $templateSearch = FALSE, &$partialFilterAcls = [], bool $sizeLimit = FALSE): ldapMultiplexer
{
global $config, $ui;
$partialFilterAcls = [];
if (!is_array($types)) {
$types = [$types];
}
if ($ou === NULL) {
$ou = $config->current['BASE'];
}
$typeFilters = [];
foreach ($types as $type) {
$infos = static::infos($type);
if ($infos['filter'] == '') {
if ($infos['filterRDN'] == '') {
continue;
} else {
$typeFilters[] = $infos['filterRDN'];
}
} elseif ($infos['filterRDN'] == '') {
$typeFilters[] = $infos['filter'];
} else {
$typeFilters[] = '(&'.$infos['filter'].$infos['filterRDN'].')';
}
}
if (empty($typeFilters)) {
throw new EmptyFilterException();
}
$ldap = $config->get_ldap_link($sizeLimit);
if (!$ldap->dn_exists($ou)) {
throw new NonExistingBranchException($ou);
}
if (empty($filter)) {
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
$filter = '(|'.implode($typeFilters).')';
} else {
if ($checkAcl) {
if (count($types) > 1) {
throw new FusionDirectoryException('Cannot evaluate ACL for several types');
}
$filterObject = ldapFilter::parse($filter);
$filterAttributes = $filterObject->listUsedAttributes();
unset($filterAttributes['_template_cn']);
foreach ($filterAttributes as $acl) {
$category = $ui->getAttributeCategory($types[0], $acl);
if ($category === FALSE) {
throw new FusionDirectoryException('Could not find ACL for attribute "'.$acl.'" for type "'.$types[0].'"');
}
if ($category === TRUE) {
continue;
}
if (strpos($ui->get_permissions($ou, $category, $acl), 'r') === FALSE) {
$partialFilterAcls[] = [$category, $acl];
}
}
}
if (!preg_match('/^\(.*\)$/', $filter)) {
$filter = '('.$filter.')';
}
$filter = '(&'.$filter.'(|'.implode($typeFilters).'))';
}
if ($templateSearch) {
$templateFilterObject = new ldapFilter(
'&',
[
new ldapFilterLeaf('objectClass', '=', 'fdTemplate'),
fdTemplateFilter(ldapFilter::parse($filter)),
]
);
$filter = "$templateFilterObject";
} else {
$filterObject = fdNoTemplateFilter(ldapFilter::parse($filter));
$filter = "$filterObject";
}
$ldap->cd($ou);
$ldap->search($filter, $search_attrs, $scope);
if (!$ldap->success()) {
if ($sizeLimit && $ldap->hitSizeLimit()) {
// Check for size limit exceeded messages for GUI feedback
$ui->getSizeLimitHandler()->setLimitExceeded();
} else {
throw new LDAPFailureException($ldap->get_error());
}
}
return $ldap;
}
/*!
* \brief Create the tab object for the given dn
*
* \param string $type the objectType to open
* \param string $dn the dn to open
*
* \return The created tab object
*/
static function open (string $dn, string $type): simpleTabs
{
$infos = static::infos($type);
$tabClass = $infos['tabClass'];
$tabObject = new $tabClass($type, $dn);
logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, "Openned as $type object");
return $tabObject;
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
}
/**
* @param string|array|null $text
*/
static function link (string $dn, string $type, string $subaction = '', $text = NULL, bool $icon = TRUE, bool $link = TRUE): string
{
global $config;
$infos = static::infos($type);
if ($link) {
if (!isset($infos['management'])) {
throw new NoManagementClassException('Asked for link for type "'.$type.'" but it does not have a management class');
}
$pInfos = pluglist::pluginInfos($infos['management']);
$index = $pInfos['INDEX'];
$action = 'edit';
if ($subaction != '') {
$action .= '_'.$subaction;
}
$href = "main.php?plug=$index&reset=1&act=listing_$action&dn=".urlencode($dn);
}
if ($text === NULL) {
$ldap = $config->get_ldap_link();
$ldap->cat($dn, [$infos['nameAttr']]);
if ($attrs = $ldap->fetch()) {
if (isset($attrs[$infos['nameAttr']][0])) {
$text = $attrs[$infos['nameAttr']][0];
} else {
$text = $dn;
}
} else {
throw new NonExistingLdapNodeException($dn);
}
} elseif (is_array($text)) {
$text = $text[$infos['nameAttr']][0];
}
$text = htmlescape($text);
if ($icon && isset($infos['icon'])) {
$text = '<img alt="'.htmlescape($infos['name']).'" title="'.htmlescape($dn).'" src="'.htmlescape($infos['icon']).'" class="center"/> '.$text;
}
if ($link) {
$text = '<a href="'.$href.'">'.$text.'</a>';
}
return $text;
}
static function create (string $type): simpleTabs
{
return static::open('new', $type);
}
static function delete (string $dn, string $type, bool $checkAcl = TRUE): array
{
$tabObject = static::open($dn, $type);
return $tabObject->delete($checkAcl);
}
static function createTemplate (string $type): simpleTabs
{
$infos = static::infos($type);
$tabClass = $infos['tabClass'];
/* Pass fake attrs object to force template mode */
$attrsObject = new stdClass();
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
$attrsObject->attrs = [];
$attrsObject->is_template = TRUE;
$tabObject = new $tabClass($type, 'new', $attrsObject);
logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $type, 'Create template of type');
return $tabObject;
}
static function &infos (string $type): array
{
global $config;
if (!isset($config->data['OBJECTS'][strtoupper($type)])) {
throw new NonExistingObjectTypeException($type);
}
$infos =& $config->data['OBJECTS'][strtoupper($type)];
if (!isset($infos['filterRDN'])) {
if (empty($infos['ou'])) {
$infos['filterRDN'] = '';
} else {
$parts = ldap_explode_dn(preg_replace('/,$/', '', $infos['ou']), 0);
if ($parts !== FALSE) {
unset($parts['count']);
$dnFilter = [];
foreach ($parts as $part) {
preg_match('/([^=]+)=(.*)$/', $part, $m);
$dnFilter[] = '('.$m[1].':dn:='.$m[2].')';
}
if (count($dnFilter) > 1) {
$infos['filterRDN'] = '(&'.implode('', $dnFilter).')';
} else {
$infos['filterRDN'] = $dnFilter[0];
}
}
}
}
return $infos;
}
static function isOfType ($attrs, string $type): bool
{
$filter = static::getFilterObject($type);
return $filter($attrs);
}
/* This method allows to cache parsed filter in filterObject key in objectTypes */
static function getFilterObject (string $type): ldapFilter
{
global $config;
$infos =& static::infos($type);
if (!isset($infos['filterObject'])) {
$infos['filterObject'] = ldapFilter::parse($infos['filter']);
}
return $infos['filterObject'];
}
/* This method allows to cache searched attributes list in objectTypes */
static function getSearchedAttributes (string $type): array
{
global $config;
$infos =& static::infos($type);
if (!isset($infos['searchAttributes'])) {
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
$searchAttrs = [];
if (!empty($infos['mainAttr'])) {
$searchAttrs[$infos['mainAttr']] = $infos['aclCategory'].'/'.$infos['mainTab'];
}
if (!empty($infos['nameAttr'])) {
$searchAttrs[$infos['nameAttr']] = $infos['aclCategory'].'/'.$infos['mainTab'];
}
foreach ($config->data['TABS'][$infos['tabGroup']] as $tab) {
if (!plugin_available($tab['CLASS'])) {
continue;
}
$plInfos = pluglist::pluginInfos($tab['CLASS']);
if (isset($plInfos['plSearchAttrs'])) {
foreach ($plInfos['plSearchAttrs'] as $attr) {
$searchAttrs[$attr] = $infos['aclCategory'].'/'.$tab['CLASS'];
}
}
}
$infos['searchAttributes'] = $searchAttrs;
}
return $infos['searchAttributes'];
}
static function types (): array
{
global $config;
return array_keys($config->data['OBJECTS']);
}
/* !\brief This method returns a list of all available templates for the given type
*/
static function getTemplates (string $type, string $requiredPermissions = 'r', string $filter = ''): array
{
global $config, $ui;
$infos = static::infos($type);
$templates = [];
$departments = $config->getDepartmentList();
foreach ($departments as $key => $value) {
// Search all templates from the current dn.
try {
$ldap = static::search($type, ['cn'], $infos['ou'].$value, $filter, FALSE, 'subtree', TRUE);
} catch (NonExistingBranchException $e) {
continue;
}
if ($ldap->count() != 0) {
while ($attrs = $ldap->fetch()) {
$dn = $attrs['dn'];
if (($requiredPermissions != '')
&& !preg_match('/'.$requiredPermissions.'/', $ui->get_permissions($dn, $infos['aclCategory'].'/'.'template'))) {
continue;
}
$templates[$dn] = $attrs['cn'][0].' - '.$key;
}
}
}
natcasesort($templates);
reset($templates);
return $templates;
}
}