diff --git a/include/class_template.inc b/include/class_template.inc index fb0deaea40e07b44986f3db6a486058dcec4fabc..f827e7707b45ef48f81892ff5f08a717fa9b1b34 100644 --- a/include/class_template.inc +++ b/include/class_template.inc @@ -357,7 +357,7 @@ class template foreach (static::$uiSpecialAttributes as $attr) { $specialAttrs['caller'.strtoupper($attr)] = $ui->$attr; } - $this->attrs = templateHandling::parseArray($this->attrs, $specialAttrs); + $this->attrs = templateHandling::parseArray($this->attrs, $specialAttrs, $targetdn); $this->tabObject->adapt_from_template($this->attrs, array_merge(...array_values($this->attributes))); $this->applied = TRUE; diff --git a/include/class_templateHandling.inc b/include/class_templateHandling.inc index 1bffdc7f5d81395a827ca477ff5523c3aedc0da4..d1072a28bf0ec518c58be51bf0db8e87a7b898c0 100644 --- a/include/class_templateHandling.inc +++ b/include/class_templateHandling.inc @@ -186,18 +186,22 @@ class templateHandling } /*! \brief Parse template masks in an array + * + * \param array $attrs The attributes in LDAP format + * \param array $specialAttrs Some additional fake attributes which can be used in masks + * \param string $target Dn of an object to ignore in the unicity check * * \return array An array with the final values of attributes */ - public static function parseArray(array $attrs, array $specialAttrs) + public static function parseArray(array $attrs, array $specialAttrs, $target = NULL) { - foreach ($attrs as &$attr) { + foreach ($attrs as $name => &$attr) { if (is_array($attr)) { foreach ($attr as $key => &$string) { if (!is_numeric($key)) { continue; } - $string = static::parseString($string, array_merge($attrs, $specialAttrs)); + $string = static::parseString($string, array_merge($attrs, $specialAttrs), NULL, $name, $target); } unset($string); } @@ -207,28 +211,82 @@ class templateHandling } /*! \brief Parse template masks in a single string + * + * \param string $string The mask + * \param array $attrs The attributes in LDAP format + * \param callable $escapeMethod Method to call to escape mask result + * \param string $unique Name of the LDAP attribute to check unicity on, if any + * \param string $target Dn of an object to ignore in the unicity check * * \return string the string with patterns replaced by their values */ - public static function parseString($string, array $attrs, $escapeMethod = NULL) + public static function parseString($string, array $attrs, $escapeMethod = NULL, $unique = NULL, $target = NULL) { + global $config; + if (preg_match('/^%%/', $string)) { /* Special case: %% at beginning of string means do not touch it. Used by binary attributes. */ return preg_replace('/^%%/', '', $string); } - $offset = 0; - while (preg_match('/%([^%]+)%/', $string, $m, PREG_OFFSET_CAPTURE, $offset)) { + + $vars = array(); + while (preg_match('/%([^%]+)%/', $string, $m, PREG_OFFSET_CAPTURE)) { $replace = static::parseMask($m[1][0], $attrs); - $replace = $replace[0]; - if ($escapeMethod !== NULL) { - $replace = $escapeMethod($replace); + $vars[] = array($m[0][1], strlen($m[0][0]), $replace); + } + + $generator = static::iteratePossibleValues($string, $vars, $escapeMethod); + + $string = $generator->current(); + + if (($unique !== NULL) && !empty($vars)) { + $ldap = $config->get_ldap_link(); + $ldap->cd($config->current['BASE']); + /* Return the first found unique value */ + foreach ($generator as $value) { + $filter = '('.ldap_escape_f($unique).'='.ldap_escape_f($value).')'; + $ldap->search($filter, array('dn')); + if ( + ($ldap->count() == 0) || + (($target !== NULL) && ($ldap->count() == 1) && ($ldap->getDN() == $target)) + ) { + return $value; + } } - $string = substr_replace($string, $replace, $m[0][1], strlen($m[0][0])); - $offset = $m[0][1] + strlen($replace); } + return $string; } + + /*! \brief Generator that yields possible template mask values + * + * \param string $rule The mask + * \param array $variables The possible values for each mask with its position and length + * \param callable $escapeMethod Method to call to escape mask result + */ + protected static function iteratePossibleValues($rule, array $variables, $escapeMethod = NULL) + { + if (!count($variables)) { + yield $rule; + return; + } + + /* Start from the end to avoid messing the positions, and to avoid ids at the end if not needed (common usecase) */ + list($pos, $length, $val) = array_pop($variables); + + /* $val may be an iterator or an array */ + foreach ($val as $possibility) { + if ($escapeMethod !== NULL) { + $possibility = $escapeMethod($possibility); + } + $nrule = mb_substr_replace($rule, $possibility, $pos, $length); + foreach (static::iteratePossibleValues($nrule, $variables, $escapeMethod) as $result) { + yield $result; + } + } + } + /*! \brief Parse template masks in a single string and list the fields it needs * * \return array An array with the names of the fields used in the string pattern @@ -389,7 +447,6 @@ class templateHandling /* $str is an array and $m is lowercase, so it's a string modifier */ $str = reset($str); } - $result = array($str); switch ($m) { case 'F': // First diff --git a/include/functions.inc b/include/functions.inc index 176e34ca0ce25ca3d17af86fd7524627b0c627ff..68fed3acb24dd25c55f76f8262b9ae7ec52121c9 100644 --- a/include/functions.inc +++ b/include/functions.inc @@ -1292,131 +1292,6 @@ function netmask_to_bits($netmask) return $res; } - -/*! - * \brief Recursion helper for gen_uids() - */ -function _recurse_gen_uids($rule, $variables) -{ - $result = array(); - - if (!count($variables)) { - return array($rule); - } - - reset($variables); - $key = key($variables); - $val = current($variables); - unset($variables[$key]); - - foreach ($val as $possibility) { - $nrule = str_replace("{$key}", $possibility, $rule); - $result = array_merge($result, _recurse_gen_uids($nrule, $variables)); - } - - return $result; -} - - -/*! - * \brief Generate a list of uid proposals based on a rule - * - * Unroll given rule string by filling in attributes and replacing - * all keywords. - * - * \param string $rule The rule string from fusiondirectory.conf. - * - * \param array $attributes A dictionary of attribute/value mappings - * - * \return array List of valid not used uids - */ -function gen_uids($rule, array $attributes) -{ - global $config; - - // Attributes should be arrays - foreach ($attributes as $name => $value) { - $attributes[$name] = array($value); - } - - /* Search for keys and fill the variables array with all - possible values for that key. */ - $stripped = $rule; - $variables = array(); - - for ($pos = 0; preg_match('/%([^%]+)%/', $stripped, $m, PREG_OFFSET_CAPTURE, $pos); ) { - $variables[$pos] = templateHandling::parseMask($m[1][0], $attributes); - $replace = '{'.$pos.'}'; - $stripped = substr_replace($stripped, $replace, $m[0][1], strlen($m[0][0])); - $pos = $m[0][1] + strlen($replace); - } - - /* Recurse through all possible combinations */ - $proposed = _recurse_gen_uids($stripped, $variables); - - /* Get list of used ID's */ - $ldap = $config->get_ldap_link(); - $ldap->cd($config->current['BASE']); - - /* Remove used uids and watch out for id tags */ - $ret = array(); - foreach ($proposed as $uid) { - /* Check for id tag and modify uid if needed */ - if (preg_match('/\{id(:|!)(\d+)}/', $uid, $m)) { - $size = $m[2]; - - $start = ($m[1] == ":" ? 0 : -1); - for ($i = $start, $p = (10 ** $size) - 1; $i < $p; $i++) { - if ($i == -1) { - $number = ""; - } else { - $number = sprintf("%0".$size."d", $i + 1); - } - $res = preg_replace('/{id(:|!)\d+}/', $number, $uid); - - $ldap->search('(uid='.ldap_escape_f(preg_replace('/[{}]/', '', $res)).')', array('dn')); - if ($ldap->count() == 0) { - $uid = $res; - break; - } - } - - /* Remove link if nothing has been found */ - $uid = preg_replace('/{id(:|!)\d+}/', '', $uid); - } - - if (preg_match('/\{id#\d+}/', $uid)) { - $size = preg_replace('/^.*{id#(\d+)}.*$/', '\\1', $uid); - - while (TRUE) { - $number = sprintf("%0".$size."d", random_int(0, (10 ** $size) - 1)); - $res = preg_replace('/{id#(\d+)}/', $number, $uid); - $ldap->search('(uid='.ldap_escape_f(preg_replace('/[{}]/', '', $res)).')', array('dn')); - if ($ldap->count() == 0) { - $uid = $res; - break; - } - } - - /* Remove link if nothing has been found */ - $uid = preg_replace('/{id#\d+}/', '', $uid); - } - - /* Don't assign used ones */ - $ldap->search('(uid='.ldap_escape_f(preg_replace('/[{}]/', '', $uid)).')', array('dn')); - if ($ldap->count() == 0) { - /* Add uid, but remove {} first. These are invalid anyway. */ - $uid = preg_replace('/[{}]/', '', $uid); - if ($uid != '') { - $ret[] = $uid; - } - } - } - - return array_unique($ret); -} - - /*! * \brief Convert various data sizes to bytes * @@ -2315,4 +2190,22 @@ function fopenWithErrorHandling(...$args) } return $errors; } -?> + +// Check to see if it exists in case PHP has this function later +if (!function_exists('mb_substr_replace')) { + // Same parameters as substr_replace with the extra encoding parameter + function mb_substr_replace ($string, $replacement, $start, $length = NULL, $encoding = NULL) + { + if ($encoding == NULL) { + $encoding = mb_internal_encoding(); + } + if ($length == NULL) { + return mb_substr($string, 0, $start, $encoding). + $replacement; + } else { + return mb_substr($string, 0, $start, $encoding). + $replacement. + mb_substr($string, $start + $length, mb_strlen($string, $encoding), $encoding); + } + } +}