Compare commits

...

5 Commits

Author SHA1 Message Date
098b75085e Remove old 1.2 code no longer in use
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 30s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 1m35s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 9m37s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-03-02 08:57:39 +11:00
4ef6cbc00e Set our HTML to tell browsers not to translate the page - closes #290 2025-03-02 08:35:29 +11:00
8e66b2adbc Enable getDNSecure to include a command, that is encrypted with the DN 2025-03-02 08:35:29 +11:00
dfc76ef789 This commit is mainly as a result of creating DN entries and improves some backend functions:
* Enable creation of new entries,
* Change all our ajax frames to go through /frames URI instead of /dn,
* Add our frame command to the encrypted DN,
* Automatically redirect to root URL when selecting a tree item and currently in another path (as a result of a prior POST activity),
* Some validation improvements DNExists/HasStructuralObjectClass
2025-03-02 08:35:29 +11:00
f08fdb1bcd Update Request validation, so that it also knows about required schema attributes 2025-03-01 19:32:40 +11:00
40 changed files with 733 additions and 6804 deletions

View File

@ -28,7 +28,7 @@ Take a look at the [Docker Container](https://github.com/leenooks/phpLDAPadmin/w
The update to v2 is progressing well - here is a list of work to do and done:
- [ ] Creating new LDAP entries
- [X] Creating new LDAP entries
- [ ] Delete existing LDAP entries
- [X] Updating existing LDAP Entries
- [X] Password attributes

View File

@ -145,9 +145,9 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
// Attribute values
'values' => $this->values,
// Required by Object Classes
'required_by' => $this->schema->required_by_object_classes,
'required_by' => $this->schema?->required_by_object_classes ?: collect(),
// Used in Object Classes
'used_in' => $this->schema->used_in_object_classes,
'used_in' => $this->schema?->used_in_object_classes ?: collect(),
default => throw new \Exception('Unknown key:' . $key),
};
@ -311,707 +311,4 @@ class Attribute implements \Countable, \ArrayAccess, \Iterator
{
$this->is_rdn = TRUE;
}
/**
* Return the name of the attribute.
*
* @param boolean $lower - Return the attribute in normal or lower case (default lower)
* @param boolean $real - Return the real attribute name (with ;binary, or just the name)
* @return string Attribute name
*
public function getName($lower=true,$real=false) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs,$this->name);
if ($real)
return $lower ? strtolower($this->name) : $this->name;
else
return $lower ? strtolower($this->real_attr_name()) : $this->real_attr_name();
}
public function getValues() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->values);
return $this->values;
}
public function getOldValues() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->oldvalues);
return $this->oldvalues;
}
public function getValueCount() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs,$this->values);
return count($this->values);
}
public function getSource() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->source);
return $this->source;
}
/**
* Autovalue is called after the attribute is initialised, and thus the values from the ldap server will be set.
*
public function autoValue($new_val) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
if ($this->values)
return;
$this->values = $new_val;
}
public function initValue($new_val) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
if ($this->values || $this->oldvalues) {
debug_dump(array('new_val'=>$new_val,'this'=>$this));
debug_dump_backtrace('new and/or old values are set',1);
}
$this->values = $new_val;
}
public function clearValue() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
$this->values = array();
}
public function setOldValue($val) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs);
$this->oldvalues = $val;
}
public function setValue($new_val) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
if ($this->values) {
if ($this->values == $new_val)
return;
if ($this->oldvalues) {
debug_dump($this);
debug_dump_backtrace('old values are set',1);
} else
$this->oldvalues = $this->values;
}
if ($new_val == $this->values)
return;
$this->values = $new_val;
$this->justModified();
}
public function addValue($new_val,$i=-1) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
if ($i < 0)
$i = $this->getValueCount();
$old_val = $this->getValue($i);
if (is_null($old_val) || ($old_val != $new_val))
$this->justModified();
$this->values[$i] = $new_val;
}
public function delValue($i=-1) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
if ($i < 0)
$this->setValue(array());
if (! $this->hasBeenModified())
$this->oldvalues = $this->values;
if (isset($this->values[$i])) {
unset($this->values[$i]);
$this->values = array_values($this->values);
$this->justModified();
}
}
public function justModified() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
$this->modified = true;
}
public function hasBeenModified() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->modified);
return $this->modified;
}
public function isForceDelete() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->forcedelete);
return $this->forcedelete;
}
public function setForceDelete() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
$this->forcedelete = true;
$this->oldvalues = $this->values;
$this->values = array();
$this->justModified();
}
public function isRequired() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
if ($this->getMinValueCount() > 0)
return true;
elseif ($this->ldaptype == 'must')
return true;
elseif ($this->isRDN())
return true;
else
return false;
}
public function isMay() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
if (($this->ldaptype == 'may') && ! $this->isRequired())
return true;
else
return false;
}
public function setType($type) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs);
$this->type = strtolower($type);
}
public function getType() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->type);
return $this->type;
}
public function setLDAPtype($type) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs);
$this->ldaptype = strtolower($type);
}
public function getLDAPtype() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->ldaptype);
return $this->ldaptype;
}
public function setProperties($properties) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
foreach ($properties as $index => $value) {
if ($index == 'maxvalnb') {
$this->setMaxValueCount($value);
continue;
} elseif ($index == 'minvalnb') {
$this->setMinValueCount($value);
continue;
} elseif ($index == 'maxlength') {
$this->setMinValueCount($value);
continue;
} elseif ($index == 'hidden') {
$this->visible = $value;
continue;
} elseif (in_array($index,array('cols','rows'))) {
# @todo To be implemented
continue;
}
if (isset($this->$index))
$this->$index = $value;
else {
debug_dump($this);
debug_dump_backtrace(sprintf('Unknown property (%s) with value (%s) for (%s)',$index,$value,$this->getName()),1);
}
}
}
public function setRequired() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
if ($this->getMinValueCount() <= 0)
$this->setMinValueCount(1);
}
public function setOptional() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs);
$this->setMinValueCount(0);
}
public function isReadOnly() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->readonly);
return $this->readonly;
}
public function setReadOnly() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
$this->readonly = true;
}
public function isMultiple() {
return false;
}
public function isVisible() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
return $this->visible && (! $this->forcehide);
}
public function hide() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
$this->visible = false;
}
public function show() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
$this->visible = true;
}
public function haveFriendlyName() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
return $_SESSION[APPCONFIG]->haveFriendlyName($this);
}
public function getFriendlyName() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->display);
if ($this->display)
return $this->display;
else
return $_SESSION[APPCONFIG]->getFriendlyName($this);
}
public function setDescription($description) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs);
$this->description = $description;
}
public function getDescription() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->description);
return $this->description;
}
public function setIcon($icon) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs);
$this->icon = $icon;
}
public function getIcon() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->icon);
return $this->icon ? sprintf('%s/%s',IMGDIR,$this->icon) : '';
}
public function getHint() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->hint);
return $this->hint;
}
public function setHint($hint) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs);
$this->hint = $hint;
}
public function getMaxLength() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->maxlength);
return $this->maxlength;
}
public function setMaxLength($maxlength) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs);
$this->maxlength = $maxlength;
}
public function getSize() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->size);
return $this->size;
}
public function setSize($size) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs);
$this->size = $size;
}
public function getSpacer() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->spacer);
return $this->spacer;
}
public function getPage() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->page);
return $this->page;
}
public function setPage($page) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs);
$this->page = $page;
}
public function getOnChange() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->onchange);
return $this->onchange;
}
public function getHelper() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->helper);
return $this->helper;
}
public function getHelperValue() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->helpervalue);
return $this->helpervalue;
}
public function getVerify() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->verify);
return $this->verify;
}
/**
* Capture all the LDAP details we are interested in
*
* @param sattr Schema Attribute
*
private function setLDAPdetails($sattr) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
# By default, set this as a MAY attribute, later processing should make it a MUST attribute if it is.
if (! $this->ldaptype)
$this->ldaptype = 'may';
# Store our Aliases
foreach ($sattr->getAliases() as $alias)
array_push($this->aliases,strtolower($alias));
if ($sattr->getIsSingleValue())
$this->setMaxValueCount(1);
}
/**
* Return a list of aliases for this Attribute (as defined by the schema)
* This list will be lowercase.
*
public function getAliases() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->aliases);
return $this->aliases;
}
public function getAutoValue() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->autovalue);
return $this->autovalue;
}
public function getPostValue() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->postvalue);
return $this->postvalue;
}
public function setPostValue($postvalue) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs);
$this->postvalue = $postvalue;
}
public function setXML($values) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
# Mostly all the time, this should be an array
if (is_array($values))
foreach ($values as $index => $value)
switch ($index) {
# Helpers should be accompanied with a <post> attribute.
case 'helper':
if (! isset($values['post']) && ! $_SESSION[APPCONFIG]->getValue('appearance','hide_template_warning'))
system_message(array(
'title'=>sprintf('%s [<i>%s</i>]',_('Missing [post] setting in XML file'),$index),
'body'=>_('[helper] needs an accompanying [post] action.'),
'type'=>'warn'));
if (isset($value['value']) && ! is_array($value['value']) && preg_match('/^=php\.(\w+)\((.*)\)$/',$value['value'],$matches)) {
$this->helpervalue['function'] = $matches[1];
$this->helpervalue['args'] = $matches[2];
unset ($value['value']);
}
foreach ($value as $i => $detail) {
if (! in_array($i,array('default','display','id','value'))) {
if (! $_SESSION[APPCONFIG]->getValue('appearance','hide_template_warning'))
system_message(array(
'title'=>sprintf('%s [<i>%s</i>]',_('Unknown XML setting'),$i),
'body'=>sprintf('%s <small>[%s]</small>',_('Unknown XML type setting for helper will be ignored.'),$detail),
'type'=>'warn'));
unset($value[$i]);
}
}
$this->$index = $value;
break;
case 'hidden': $value ? $this->visible = false : $this->visible = true;
break;
case 'spacer': $value ? $this->$index = true : $this->$index = false;
break;
# Essentially, we ignore type, it is used to select an Attribute type in the Factory. But we'll generated a warning if there is an unknown type.
case 'type':
if (! in_array($value,array('password','multiselect','select','textarea')) && ! $_SESSION[APPCONFIG]->getValue('appearance','hide_template_warning'))
system_message(array(
'title'=>sprintf('%s [<i>%s</i>]',_('Unknown XML setting'),$index),
'body'=>sprintf('%s <small>[%s]</small>',_('Unknown XML type setting will be ignored.'),$value),
'type'=>'warn'));
break;
case 'post':
if (preg_match('/^=php\.(\w+)\((.*)\)$/',$value,$matches)) {
$this->postvalue['function'] = $matches[1];
$this->postvalue['args'] = $matches[2];
} else
if (! $_SESSION[APPCONFIG]->getValue('appearance','hide_template_warning'))
system_message(array(
'title'=>sprintf('%s [<i>%s</i>]',_('Unknown XML setting'),$index),
'body'=>sprintf('%s <small>[%s]</small>',_('Unknown XML type setting will be ignored.'),$value),
'type'=>'warn'));
case 'value':
if (is_array($value))
foreach ($value as $x => $y) {
if (! $this->haveMoreValues()) {
system_message(array(
'title'=>_('Automatically removed attribute values from template'),
'body'=>sprintf('%s <small>[%s]</small>',_('Template defines more values than can be accepted by attribute.'),$this->getName(true)),
'type'=>'warn'));
$this->clearValue();
break;
} else
$this->addValue($x,$y);
}
else
# Check to see if the value is auto generated.
if (preg_match('/^=php\.(\w+)\((.*)\)$/',$value,$matches)) {
$this->autovalue['function'] = $matches[1];
$this->autovalue['args'] = $matches[2];
# We'll add a hint too
if (! $this->hint)
$this->hint = _('Automatically determined');
} else
$this->addValue($value);
break;
# Queries
case 'ordersort':
# Creation/Editing Templates
case 'cols':
case 'default':
case 'display':
case 'hint':
case 'icon':
case 'maxlength':
case 'onchange':
case 'order':
case 'page':
case 'readonly':
case 'rows':
case 'size':
case 'values':
case 'verify': $this->$index = $value;
break;
case 'max':
if ($this->getMaxValueCount() == -1)
$this->setMaxValueCount($value);
default:
if (! $_SESSION[APPCONFIG]->getValue('appearance','hide_template_warning'))
system_message(array(
'title'=>sprintf('%s [<i>%s</i>]',_('Unknown XML setting'),$index),
'body'=>sprintf('%s <small>[%s]</small>',_('Unknown attribute setting will be ignored.'),serialize($value)),
'type'=>'warn'));
}
elseif (is_string($values) && (strlen($values) > 0))
$this->values = array($values);
}
/**
* Display the values removed in an attribute.
*
public function getRemovedValues() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
return array_diff($this->getOldValues(),$this->getValues());
}
/**
* Display the values removed in an attribute.
*
public function getAddedValues() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
return array_diff($this->getValues(),$this->getOldValues());
}
/**
* Prunes off anything after the ";" in an attr name. This is useful for
* attributes that may have ";binary" appended to their names. With
* real_attr_name(), you can more easily fetch these attributes' schema
* with their "real" attribute name.
*
* @param string $attr_name The name of the attribute to examine.
* @return string
*
private function real_attr_name() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->name);
return preg_replace('/;.*$/U','',$this->name);
}
/**
* Does this attribute need supporting JS
*
public function needJS($type=null) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',5,0,__FILE__,__LINE__,__METHOD__,$fargs);
if (is_null($type)) {
foreach (array('focus','blur','validate') as $type)
if ($this->needJS($type))
return true;
return false;
} elseif ($type == 'focus') {
# We dont have any focus javascript routines.
return false;
} elseif ($type == 'blur') {
if ($this->onchange || $this->isRequired())
return true;
else
return false;
} elseif ($type == 'validate') {
if ($this->isRequired())
return true;
else
return false;
} else
debug_dump_backtrace(sprintf('Unknown JS request %s',$type),1);
}
*/
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Classes\LDAP\Attribute;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use App\Classes\LDAP\Attribute;
/**
* Represents the RDN for an Entry
*/
final class RDN extends Attribute
{
private string $base;
private Collection $attrs;
public function __get(string $key): mixed
{
return match ($key) {
'base' => $this->base,
'attrs' => $this->attrs->pluck('name'),
default => parent::__get($key),
};
}
public function hints(): array
{
return [
'required' => __('RDN is required')
];
}
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
return view('components.attribute.rdn')
->with('o',$this);
}
public function setAttributes(Collection $attrs): void
{
$this->attrs = $attrs;
}
public function setBase(string $base): void
{
$this->base = $base;
}
}

View File

@ -289,7 +289,6 @@ final class AttributeType extends Base {
case 'type': return $this->type;
case 'usage': return $this->usage;
case 'used_in_object_classes': return $this->used_in_object_classes;
case 'validation': return Arr::get(config('ldap.validation'),$this->name_lc);
default: return parent::__get($key);
}
@ -419,18 +418,6 @@ final class AttributeType extends Base {
return $this->ordering;
}
/**
* Gets the list of "required by" objectClasses, that is the list of objectClasses
* which provide must have attribute.
*
* @return array An array of names of objectclasses (strings) which provide this attribute
*/
public function getRequiredByObjectClasses() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',9,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->required_by_object_classes);
return $this->required_by_object_classes;
}
/**
* Gets this attribute's substring matching specification
*
@ -466,29 +453,6 @@ final class AttributeType extends Base {
return $this->syntax_oid;
}
/**
* Gets this attribute's raw syntax string (ie: "1.2.3.4{16}").
*
* @return string The raw syntax string
*/
public function getSyntaxString() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',9,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->syntax);
return $this->syntax;
}
/**
* Gets this attribute's type
*
* @return string The attribute's type.
* @deprecated use $this->type;
*/
public function getType()
{
return $this->type;
}
/**
* Gets this attribute's usage string as defined by the LDAP server
*
@ -512,23 +476,6 @@ final class AttributeType extends Base {
return $this->used_in_object_classes;
}
/**
* Returns whether the specified attribute is an alias for this one (based on this attribute's alias list).
*
* @param string $attr_name The name of the attribute to check.
* @return boolean TRUE if the specified attribute is an alias for this one, or FALSE otherwise.
*/
public function isAliasFor($attr_name) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',9,0,__FILE__,__LINE__,__METHOD__,$fargs);
foreach ($this->aliases as $alias_attr_name)
if (strcasecmp($alias_attr_name,$attr_name) == 0)
return TRUE;
return FALSE;
}
/**
* @return bool
* @deprecated use $this->forced_as_may
@ -564,9 +511,6 @@ final class AttributeType extends Base {
* This function will mark this attribute as a forced MAY attribute
*/
public function setForceMay() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',9,1,__FILE__,__LINE__,__METHOD__,$fargs);
$this->forced_as_may = TRUE;
}
@ -591,14 +535,32 @@ final class AttributeType extends Base {
}
/**
* Sets this attribute's type.
* Return Request validation array
*
* @param string $type The new type.
* This will merge configured validation with schema required attributes
*
* @param array $array
* @return array|null
*/
public function setType($type) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',9,1,__FILE__,__LINE__,__METHOD__,$fargs);
public function validation(array $array): ?array
{
// For each item in array, we need to get the OC heirachy
$heirachy = collect($array)
->filter()
->map(fn($item)=>config('server')
->schema('objectclasses',$item)
->getSupClasses()
->push($item))
->flatten()
->unique();
$this->type = $type;
$validation = collect(Arr::get(config('ldap.validation'),$this->name_lc,[]));
if (($heirachy->intersect($this->required_by_object_classes)->count() > 0)
&& (! collect($validation->get($this->name_lc))->contains('required'))) {
$validation->put($this->name_lc,array_merge(['required','min:1'],$validation->get($this->name_lc,[])))
->put($this->name_lc.'.*',array_merge(['required','min:1'],$validation->get($this->name_lc.'.*',[])));
}
return $validation->toArray();
}
}

View File

@ -208,7 +208,7 @@ final class ObjectClass extends Base
public function __get(string $key): mixed
{
return match ($key) {
'attributes' => $this->getAllAttrs(),
'attributes' => $this->getAllAttrs(TRUE),
'sup' => $this->sup_classes,
'type_name' => match ($this->type) {
Server::OC_STRUCTURAL => 'Structural',
@ -223,13 +223,18 @@ final class ObjectClass extends Base
/**
* Return a list of attributes that this objectClass provides
*
* @param bool $parents
* @return Collection
* @throws InvalidUsage
*/
public function getAllAttrs(): Collection
public function getAllAttrs(bool $parents=FALSE): Collection
{
return $this->getMustAttrs()
->merge($this->getMayAttrs());
return $this->getMustAttrs($parents)
->transform(function($item) {
$item->required = true;
return $item;
})
->merge($this->getMayAttrs($parents));
}
/**

View File

@ -16,6 +16,7 @@ namespace App\Classes\LDAP\Schema;
final class ObjectClassAttribute extends Base {
// This Attribute's root.
private string $source;
public bool $required = FALSE;
/**
* Creates a new ObjectClassAttribute with specified name and source objectClass.
@ -31,11 +32,9 @@ final class ObjectClassAttribute extends Base {
public function __get(string $key): mixed
{
switch ($key) {
case 'source':
return $this->source;
default: return parent::__get($key);
}
return match ($key) {
'source' => $this->source,
default => parent::__get($key),
};
}
}

View File

@ -338,16 +338,6 @@ final class Server
break;
/*
case 'matchingruleuse':
if (isset($this->matchingruleuse))
return is_null($key) ? $this->matchingruleuse : $this->matchingruleuse->get($key);
else
$this->matchingruleuse = collect();
break;
*/
case 'objectclasses':
if (isset($this->objectclasses))
return $this->objectclasses;
@ -381,21 +371,6 @@ final class Server
$o = new AttributeType($line);
$this->attributetypes->put($o->name_lc,$o);
/*
if (isset($syntaxes[$attr->getSyntaxOID()])) {
$syntax = $syntaxes[$attr->getSyntaxOID()];
$attr->setType($syntax->getDescription());
}
$this->attributetypes[$attr->getName()] = $attr;
*/
/**
* bug 856832: create an entry in the $attrs_oid array too. This
* will be a ref to the $attrs entry for maintenance and performance
* reasons
*/
//$attrs_oid[$attr->getOID()] = &$attrs[$attr->getName()];
}
// go back and add data from aliased attributeTypes

View File

@ -16,21 +16,21 @@ class APIController extends Controller
* Get the LDAP server BASE DNs
*
* @return Collection
* @throws LdapRecord\Query\ObjectNotFoundException
* @throws \LdapRecord\Query\ObjectNotFoundException
*/
public function bases(): Collection
{
$base = Server::baseDNs() ?: collect();
return $base->transform(function($item) {
return [
'title'=>$item->getRdn(),
'item'=>$item->getDNSecure(),
'lazy'=>TRUE,
'icon'=>'fa-fw fas fa-sitemap',
'tooltip'=>$item->getDn(),
];
});
return $base
->transform(fn($item)=>
[
'title'=>$item->getRdn(),
'item'=>$item->getDNSecure(),
'lazy'=>TRUE,
'icon'=>'fa-fw fas fa-sitemap',
'tooltip'=>$item->getDn(),
]);
}
/**
@ -41,19 +41,31 @@ class APIController extends Controller
{
$levels = $request->query('depth',1);
$dn = Crypt::decryptString($request->query('key'));
// Sometimes our key has a command, so we'll ignore it
if (str_starts_with($dn,'*') && ($x=strpos($dn,'|')))
$dn = substr($dn,$x+1);
Log::debug(sprintf('%s: Query [%s] - Levels [%d]',__METHOD__,$dn,$levels));
return (config('server'))
->children($dn)
->transform(function($item) {
return [
->transform(fn($item)=>
[
'title'=>$item->getRdn(),
'item'=>$item->getDNSecure(),
'icon'=>$item->icon(),
'lazy'=>Arr::get($item->getAttribute('hassubordinates'),0) == 'TRUE',
'tooltip'=>$item->getDn(),
];
});
])
->prepend(
[
'title'=>sprintf('[%s]',__('Create Entry')),
'item'=>Crypt::encryptString(sprintf('*%s|%s','create',$dn)),
'lazy'=>FALSE,
'icon'=>'fas fa-fw fa-square-plus text-warning',
'tooltip'=>__('Create new LDAP item here'),
]);
}
public function schema_view(Request $request)
@ -63,20 +75,20 @@ class APIController extends Controller
switch($request->type) {
case 'objectclasses':
return view('fragment.schema.objectclasses')
->with('objectclasses',$server->schema('objectclasses')->sortBy(function($item) { return strtolower($item->name); }));
->with('objectclasses',$server->schema('objectclasses')->sortBy(fn($item)=>strtolower($item->name)));
case 'attributetypes':
return view('fragment.schema.attributetypes')
->with('server',$server)
->with('attributetypes',$server->schema('attributetypes')->sortBy(function($item) { return strtolower($item->name); }));
->with('attributetypes',$server->schema('attributetypes')->sortBy(fn($item)=>strtolower($item->name)));
case 'ldapsyntaxes':
return view('fragment.schema.ldapsyntaxes')
->with('ldapsyntaxes',$server->schema('ldapsyntaxes')->sortBy(function($item) { return strtolower($item->description); }));
->with('ldapsyntaxes',$server->schema('ldapsyntaxes')->sortBy(fn($item)=>strtolower($item->description)));
case 'matchingrules':
return view('fragment.schema.matchingrules')
->with('matchingrules',$server->schema('matchingrules')->sortBy(function($item) { return strtolower($item->name); }));
->with('matchingrules',$server->schema('matchingrules')->sortBy(fn($item)=>strtolower($item->name)));
default:
abort(404);

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
@ -12,20 +13,21 @@ use Illuminate\Support\Facades\Redirect;
use LdapRecord\Exceptions\InsufficientAccessException;
use LdapRecord\LdapRecordException;
use LdapRecord\Query\ObjectNotFoundException;
use Nette\NotImplementedException;
use App\Classes\LDAP\Attribute\Factory;
use App\Classes\LDAP\{Attribute,Server};
use App\Classes\LDAP\Import\LDIF as LDIFImport;
use App\Classes\LDAP\Export\LDIF as LDIFExport;
use App\Exceptions\Import\{GeneralException,VersionException};
use App\Exceptions\InvalidUsage;
use App\Http\Requests\{EntryRequest,ImportRequest};
use App\Http\Requests\{EntryRequest,EntryAddRequest,ImportRequest};
use App\Ldap\Entry;
use App\View\Components\AttributeType;
use Nette\NotImplementedException;
class HomeController extends Controller
{
private function bases()
private function bases(): Collection
{
$base = Server::baseDNs() ?: collect();
@ -51,21 +53,38 @@ class HomeController extends Controller
}
/**
* Render a specific DN
* Create a new object in the LDAP server
*
* @param Request $request
* @param EntryAddRequest $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
* @throws InvalidUsage
*/
public function dn_frame(Request $request)
public function entry_add(EntryAddRequest $request)
{
$dn = Crypt::decryptString($request->post('key'));
if (! old('step',$request->validated('step')))
abort(404);
$page_actions = collect(['edit'=>TRUE,'copy'=>TRUE]);
$key = $this->request_key($request,collect(old()));
return view('frames.dn')
->with('o',config('server')->fetch($dn))
->with('dn',$dn)
->with('page_actions',$page_actions);
$o = new Entry;
if (count(array_filter($x=old('objectclass',$request->objectclass)))) {
$o->objectclass = $x;
foreach($o->getAvailableAttributes()->filter(fn($item)=>$item->required) as $ao)
$o->addAttribute($ao,'');
$o->setRDNBase($key['dn']);
}
$step = $request->step ? $request->step+1 : old('step');
return view('frame')
->with('subframe','create')
->with('bases',$this->bases())
->with('o',$o)
->with('step',$step)
->with('container',old('container',$key['dn']));
}
/**
@ -75,7 +94,7 @@ class HomeController extends Controller
* @param string $id
* @return \Closure|\Illuminate\Contracts\View\View|string
*/
public function entry_attr_add(Request $request,string $id)
public function entry_attr_add(Request $request,string $id): string
{
$xx = new \stdClass();
$xx->index = 0;
@ -90,6 +109,54 @@ class HomeController extends Controller
return $x;
}
public function entry_create(EntryAddRequest $request)
{
$key = $this->request_key($request,collect(old()));
$dn = sprintf('%s=%s,%s',$request->rdn,$request->rdn_value,$key['dn']);
$o = new Entry;
$o->setDn($dn);
foreach ($request->except(['_token','key','rdn','rdn_value']) as $key => $value)
$o->{$key} = array_filter($value);
try {
$o->save();
// @todo To test and valide this Exception is caught
} catch (InsufficientAccessException $e) {
$request->flash();
switch ($x=$e->getDetailedError()->getErrorCode()) {
case 50:
return Redirect::to('/')
->withInput()
->withErrors(sprintf('%s: %s (%s)',__('LDAP Server Error Code'),$x,__($e->getDetailedError()->getErrorMessage())));
default:
abort(599,$e->getDetailedError()->getErrorMessage());
}
// @todo To test and valide this Exception is caught
} catch (LdapRecordException $e) {
$request->flash();
switch ($x=$e->getDetailedError()->getErrorCode()) {
case 8:
return Redirect::to('/')
->withInput()
->withErrors(sprintf('%s: %s (%s)',__('LDAP Server Error Code'),$x,__($e->getDetailedError()->getErrorMessage())));
default:
abort(599,$e->getDetailedError()->getErrorMessage());
}
}
return Redirect::to('/')
->withFragment($o->getDNSecure());
}
public function entry_export(Request $request,string $id)
{
$dn = Crypt::decryptString($id);
@ -112,11 +179,9 @@ class HomeController extends Controller
* @param string $id
* @return mixed
*/
public function entry_objectclass_add(string $id)
public function entry_objectclass_add(Request $request)
{
$dn = Crypt::decryptString($id);
$o = config('server')->fetch($dn);
$oc = $o->getObject('objectclass');
$oc = Factory::create('objectclass',$request->oc);
$ocs = $oc
->structural
@ -259,26 +324,51 @@ class HomeController extends Controller
}
/**
* Application home page
* Render a frame, normally as a result of an AJAX call
* This will render the right frame.
*
* @param Request $request
* @param Collection|null $old
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|View
*/
public function home()
public function frame(Request $request,?Collection $old=NULL): View
{
if (old('dn'))
return view('frame')
->with('subframe','dn')
->with('bases',$this->bases())
->with('o',config('server')->fetch($dn=Crypt::decryptString(old('dn'))))
->with('dn',$dn);
// If our index was not render from a root url, then redirect to it
if (($request->root().'/' !== url()->previous()) && $request->method() === 'POST')
abort(409);
elseif (old('frame'))
return view('frame')
->with('subframe',old('frame'))
$key = $this->request_key($request,$old);
$view = ($old
? view('frame')->with('subframe',$key['cmd'])
: view('frames.'.$key['cmd']))
->with('bases',$this->bases());
return match ($key['cmd']) {
'create' => $view
->with('container',old('container',$key['dn']))
->with('step',1),
'dn' => $view
->with('dn',$key['dn'])
->with('page_actions',collect(['edit'=>TRUE,'copy'=>TRUE])),
'import' => $view,
default => abort(404),
};
}
/**
* This is the main page render function
*/
public function home(Request $request)
{
// Did we come here as a result of a redirect
return count(old())
? $this->frame($request,collect(old()))
: view('home')
->with('bases',$this->bases());
else
return view('home')
->with('bases',$this->bases())
->with('server',config('ldap.connections.default.name'));
}
/**
@ -334,6 +424,39 @@ class HomeController extends Controller
->with('s',config('server'));
}
/**
* For any incoming request, work out the command and DN involved
*
* @param Request $request
* @param Collection|null $old
* @return array
*/
private function request_key(Request $request,?Collection $old=NULL): array
{
// Setup
$cmd = NULL;
$dn = NULL;
$key = $request->get('key',old('key'))
? Crypt::decryptString($request->get('key',old('key')))
: NULL;
// Determine if our key has a command
if (str_contains($key,'|')) {
$m = [];
if (preg_match('/\*([a-z_]+)\|(.+)$/',$key,$m)) {
$cmd = $m[1];
$dn = ($m[2] !== '_NOP') ? $m[2] : NULL;
}
} elseif (old('dn',$request->get('key'))) {
$cmd = 'dn';
$dn = Crypt::decryptString(old('dn',$request->get('key')));
}
return ['cmd'=>$cmd,'dn'=>$dn];
}
/**
* Show the Schema Viewer
*

View File

@ -0,0 +1,71 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Crypt;
use App\Rules\{DNExists,HasStructuralObjectClass};
class EntryAddRequest extends FormRequest
{
/**
* Get the error messages for the defined validation rules.
*
* @return array<string, string>
*/
public function messages(): array
{
return [
'rdn' => __('RDN is required.'),
'rdn_value' => __('RDN value is required.'),
];
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function rules(): array
{
if (request()->method() === 'GET')
return [];
return config('server')
->schema('attributetypes')
->intersectByKeys($this->request)
->map(fn($item)=>$item->validation(request()->get('objectclass')))
->filter()
->flatMap(fn($item)=>$item)
->merge([
'key' => [
'required',
new DNExists,
function (string $attribute,mixed $value,\Closure $fail) {
$cmd = Crypt::decryptString($value);
// Sometimes our key has a command, so we'll ignore it
if (str_starts_with($cmd,'*') && ($x=strpos($cmd,'|')))
$cmd = substr($cmd,1,$x-1);
if ($cmd !== 'create') {
$fail(sprintf('Invalid command: %s',$cmd));
}
},
],
'rdn' => 'required_if:step,2|string|min:1',
'rdn_value' => 'required_if:step,2|string|min:1',
'step' => 'int|min:1|max:2',
'objectclass'=>[
'required',
'array',
'min:1',
new HasStructuralObjectClass,
]
])
->toArray();
}
}

View File

@ -6,29 +6,19 @@ use Illuminate\Foundation\Http\FormRequest;
class EntryRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return TRUE;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules()
public function rules(): array
{
return config('server')
->schema('attributetypes')
->intersectByKeys($this->request)
->transform(function($item) { return $item->validation; })
->map(fn($item)=>$item->validation(request()?->get('objectclass') ?: []))
->filter()
->flatMap(function($item) { return $item; })
->flatMap(fn($item)=>$item)
->toArray();
}
}

View File

@ -6,15 +6,9 @@ use Illuminate\Foundation\Http\FormRequest;
class ImportRequest extends FormRequest
{
public function authorize()
{
return TRUE;
}
public function rules()
public function rules(): array
{
return [
'frame' => 'required|string|in:import',
'file' => 'nullable|extensions:ldif|required_without:text',
'text'=> 'nullable|prohibits:file|string|min:16',
];

View File

@ -12,11 +12,14 @@ use App\Classes\LDAP\Attribute;
use App\Classes\LDAP\Attribute\Factory;
use App\Classes\LDAP\Export\LDIF;
use App\Exceptions\Import\AttributeException;
use App\Exceptions\InvalidUsage;
class Entry extends Model
{
private Collection $objects;
private bool $noObjectAttributes = FALSE;
// For new entries, this is the container that this entry will be stored in
private string $rdnbase;
/* OVERRIDES */
@ -46,7 +49,7 @@ class Entry extends Model
public function getAttributes(): array
{
return $this->objects
->map(fn($item)=>$item->values->toArray())
->map(fn($item)=>$item->values)
->toArray();
}
@ -92,10 +95,7 @@ class Entry extends Model
$key = $this->normalizeAttributeKey($key);
if ((! $this->objects->get($key)) && $value) {
$o = new Attribute($key,[]);
$o->value = $value;
$this->objects->put($key,$o);
$this->objects->put($key,Factory::create($key,$value));
} elseif ($this->objects->get($key)) {
$this->objects->get($key)->value = $this->attributes[$key];
@ -144,6 +144,10 @@ class Entry extends Model
public function addAttribute(string $key,mixed $value): void
{
// While $value is mixed, it can only be a string
if (! is_string($value))
throw new \Exception('value should be a string');
$key = $this->normalizeAttributeKey($key);
if (! config('server')->schema('attributetypes')->has($key))
@ -239,11 +243,12 @@ class Entry extends Model
/**
* Return a secure version of the DN
* @param string $cmd
* @return string
*/
public function getDNSecure(): string
public function getDNSecure(string $cmd=''): string
{
return Crypt::encryptString($this->getDn());
return Crypt::encryptString(($cmd ? sprintf('*%s|',$cmd) : '').$this->getDn());
}
/**
@ -265,8 +270,12 @@ class Entry extends Model
*/
public function getObject(string $key): Attribute|null
{
return $this->objects
->get($this->normalizeAttributeKey($key));
return match ($key) {
'rdn' => $this->getRDNObject(),
default => $this->objects
->get($this->normalizeAttributeKey($key))
};
}
public function getObjects(): Collection
@ -289,6 +298,16 @@ class Entry extends Model
->filter(fn($a)=>(! $this->getVisibleAttributes()->contains(fn($b)=>($a->name === $b->name))));
}
private function getRDNObject(): Attribute\RDN
{
$o = new Attribute\RDN('dn',['']);
// @todo for an existing object, return the base.
$o->setBase($this->rdnbase);
$o->setAttributes($this->getAvailableAttributes()->filter(fn($item)=>$item->required));
return $o;
}
/**
* Return this list of user attributes
*
@ -413,4 +432,12 @@ class Entry extends Model
return $this;
}
public function setRDNBase(string $bdn): void
{
if ($this->exists)
throw new InvalidUsage('Cannot set RDN base on existing entries');
$this->rdnbase = $bdn;
}
}

27
app/Rules/DNExists.php Normal file
View File

@ -0,0 +1,27 @@
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Crypt;
class DNExists implements ValidationRule
{
/**
* Run the validation rule.
*
* @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute,mixed $value,Closure $fail): void
{
$dn = Crypt::decryptString($value);
// Sometimes our key has a command, so we'll ignore it
if (str_starts_with($dn,'*') && ($x=strpos($dn,'|')))
$dn = substr($dn,$x+1);
if (! config('server')->fetch($dn))
$fail(sprintf('The DN %s doesnt exist.',$dn));
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class HasStructuralObjectClass implements ValidationRule
{
/**
* Run the validation rule.
*
* @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute,mixed $value,Closure $fail): void
{
foreach ($value as $item)
if ($item && config('server')->schema('objectclasses',$item)->isStructural())
return;
$fail('There isnt a Structural Objectclass.');
}
}

View File

@ -1,5 +1,7 @@
<?php
use App\Rules\HasStructuralObjectClass;
return [
/*
@ -119,8 +121,10 @@ return [
'validation' => [
'objectclass' => [
'objectclass'=>[
'required',
'array',
'min:1'
'min:1',
new HasStructuralObjectClass,
]
],
'gidnumber' => [
@ -170,4 +174,4 @@ return [
]
],
],
];
];

View File

@ -1,14 +0,0 @@
<?php
/**
* This script provides a convienent method to call the proper common.php
*
* @package phpLDAPadmin
*/
/**
*/
if (! defined('LIBDIR'))
define('LIBDIR',sprintf('%s/',realpath('../lib/')));
require_once LIBDIR.'common.php';
?>

View File

@ -1,103 +0,0 @@
<?php
/**
* Creates a new object in LDAP.
*
* @package phpLDAPadmin
* @subpackage Page
*/
/**
*/
require './common.php';
# If cancel was selected, we'll redirect
if (get_request('cancel','REQUEST')) {
header('Location: index.php');
die();
}
$request = array();
$request['redirect'] = get_request('redirect','POST',false,false);
$request['page'] = new PageRender($app['server']->getIndex(),get_request('template','REQUEST',false,'none'));
$request['page']->setContainer(get_request('container','REQUEST',true));
$request['page']->accept();
$request['template'] = $request['page']->getTemplate();
if ((! $request['template']->getContainer() || ! $app['server']->dnExists($request['template']->getContainer())) && ! get_request('create_base'))
error(sprintf(_('The container you specified (%s) does not exist. Please try again.'),$request['template']->getContainer()),'error','index.php');
# Check if the container is a leaf - we shouldnt really return a hit here, the template engine shouldnt have allowed a user to attempt to create an entry...
$tree = get_cached_item($app['server']->getIndex(),'tree');
$request['container'] = $tree->getEntry($request['template']->getContainer());
if (! $request['container'] && ! get_request('create_base')) {
$tree->addEntry($request['template']->getContainer());
$request['container'] = $tree->getEntry($request['template']->getContainer());
}
# Check our RDN
if (! count($request['template']->getRDNAttrs()))
error(_('The were no attributes marked as an RDN attribute.'),'error','index.php');
if (! $request['template']->getRDN())
error(_('The RDN field is empty?'),'error','index.php');
# Some other attribute checking...
foreach ($request['template']->getAttributes() as $attribute) {
# Check that our Required Attributes have a value - we shouldnt really return a hit here, the template engine shouldnt have allowed this to slip through.
if ($attribute->isRequired() && ! count($attribute->getValues()))
error(sprintf(_('You left the value blank for required attribute (%s).'),
$attribute->getName(false)),'error','index.php');
}
# Create the entry
$add_result = $app['server']->add($request['template']->getDN(),$request['template']->getLDAPadd());
if ($add_result) {
$action_number = $_SESSION[APPCONFIG]->getValue('appearance','action_after_creation');
$href = sprintf('cmd=template_engine&server_id=%s',$app['server']->getIndex());
if ($request['redirect'])
$redirect_url = $request['redirect'];
else if ($action_number == 2)
$redirect_url = sprintf('cmd.php?%s&template=%s&container=%s',
$href,'default',$request['template']->getContainerEncode());
else
$redirect_url = sprintf('cmd.php?%s&template=%s&dn=%s',
$href,'default',$request['template']->getDNEncode());
if ($action_number == 1 || $action_number == 2)
printf('<meta http-equiv="refresh" content="0; url=%s" />',$redirect_url);
if ($action_number == 1 || $action_number == 2) {
$create_message = sprintf('%s %s: <b>%s</b> %s',
_('Creation successful!'),_('DN'),$request['template']->getDN(),_('has been created.'));
if (isAjaxEnabled())
$redirect_url .= sprintf('&refresh=SID_%s_nodes&noheader=1',$app['server']->getIndex());
system_message(array(
'title'=>_('Create Entry'),
'body'=>$create_message,
'type'=>'info'),
$redirect_url);
} else {
$request['page']->drawTitle(_('Entry created'));
$request['page']->drawSubTitle(sprintf('%s: <b>%s</b>&nbsp;&nbsp;&nbsp;%s: <b>%s</b>',
_('Server'),$app['server']->getName(),_('Distinguished Name'),$request['template']->getDN()));
echo '<br />';
echo '<center>';
printf('<a href="cmd.php?%s&amp;dn=%s">%s</a>.',
htmlspecialchars($href),rawurlencode($request['template']->getDN()),_('Display the new created entry'));
echo '<br />';
printf('<a href="cmd.php?%s&amp;container=%s">%s</a>.',
htmlspecialchars($href),rawurlencode($request['template']->getContainer()),_('Create another entry'));
echo '</center>';
}
}
?>

View File

@ -1,142 +0,0 @@
<?php
/**
* Creates a new object in LDAP.
*
* @package phpLDAPadmin
* @subpackage Page
*/
/**
*/
require './common.php';
$request = array();
$request['redirect'] = get_request('redirect','POST',false,false);
$request['page'] = new PageRender($app['server']->getIndex(),get_request('template','REQUEST',false,'none'));
$request['page']->setContainer(get_request('container','REQUEST',true));
$request['page']->accept();
$request['template'] = $request['page']->getTemplate();
if (! $request['template']->getContainer() || ! $app['server']->dnExists($request['template']->getContainer()))
error(sprintf(_('The container you specified (%s) does not exist. Please try again.'),$request['template']->getContainer()),'error','index.php');
# Check if the container is a leaf - we shouldnt really return a hit here, the template engine shouldnt have allowed a user to attempt to create an entry...
$tree = get_cached_item($app['server']->getIndex(),'tree');
$request['container'] = $tree->getEntry($request['template']->getContainer());
if (! $request['container']) {
$tree->addEntry($request['template']->getContainer());
$request['container'] = $tree->getEntry($request['template']->getContainer());
}
# Check our RDN
if (! count($request['template']->getRDNAttrs()))
error(_('The were no attributes marked as an RDN attribute.'),'error','index.php');
if (! $request['template']->getRDN())
error(_('The RDN field is empty?'),'error','index.php');
# Some other attribute checking...
foreach ($request['template']->getAttributes() as $attribute) {
# Check that our Required Attributes have a value - we shouldnt really return a hit here, the template engine shouldnt have allowed this to slip through.
if ($attribute->isRequired() && ! count($attribute->getValues()))
error(sprintf(_('You left the value blank for required attribute (%s).'),
$attribute->getName(false)),'error','index.php');
}
# Check for unique attributes
$app['server']->checkUniqueAttrs($request['template']->getDN(),$request['template']->getLDAPadd());
$request['page']->drawTitle(_('Create LDAP Entry'));
$request['page']->drawSubTitle(sprintf('%s: <b>%s</b>&nbsp;&nbsp;&nbsp;%s: <b>%s</b>',
_('Server'),$app['server']->getName(),_('Container'),$request['template']->getContainer()));
# Confirm the creation
if (count($request['template']->getLDAPadd(true))) {
echo '<div style="text-align: center;">';
echo _('Do you want to create this entry?');
echo '<br /><br />';
echo '</div>';
echo "\n\n";
echo '<form action="cmd.php" method="post" id="create_form">';
echo '<div>';
echo '<input type="hidden" name="cmd" value="create" />';
printf('<input type="hidden" name="server_id" value="%s" />',$app['server']->getIndex());
printf('<input type="hidden" name="container" value="%s" />',$request['template']->getContainerEncode(false));
printf('<input type="hidden" name="template" value="%s" />',$request['template']->getID());
foreach ($request['template']->getRDNAttrs() as $rdn)
printf('<input type="hidden" name="rdn_attribute[]" value="%s" />',htmlspecialchars($rdn));
echo "\n";
$request['page']->drawHiddenAttributes();
echo '</div>';
echo '<table class="result_table" style="margin-left: auto; margin-right: auto;">';
echo "\n";
printf('<tr class="heading"><td>%s</td><td>%s</td><td>%s</td></tr>',
_('Attribute'),_('New Value'),_('Skip'));
echo "\n\n";
$counter = 0;
printf('<tr class="%s"><td colspan="3" style="text-align: center;"><b>%s</b></td></tr>',$counter%2 ? 'even' : 'odd',htmlspecialchars($request['template']->getDN()));
foreach ($request['template']->getLDAPadd(true) as $attribute) {
$counter++;
printf('<tr class="%s">',$counter%2 ? 'even' : 'odd');
printf('<td><b>%s</b></td>',$attribute->getFriendlyName());
# Show NEW Values
echo '<td><span style="white-space: nowrap;">';
$request['page']->draw('CurrentValues',$attribute);
echo '</span></td>';
# Show SKIP Option
$input_disabled = '';
$input_onclick = '';
if ($attribute->isRequired())
$input_disabled = 'disabled="disabled"';
printf('<td><input name="skip_array[%s]" id="skip_array_%s" type="checkbox" %s %s/></td>',
htmlspecialchars($attribute->getName()),htmlspecialchars($attribute->getName()),$input_disabled,$input_onclick);
echo '</tr>';
echo "\n\n";
}
echo '</table>';
echo '<div style="text-align: center;">';
echo '<br />';
printf('<input type="submit" value="%s" %s/>',
_('Commit'),
(isAjaxEnabled() ? sprintf('onclick="return ajSUBMIT(\'BODY\',document.getElementById(\'create_form\'),\'%s\');"',_('Updating Object')) : ''));
printf('<input type="submit" name="cancel" value="%s" %s/>',
_('Cancel'),
(isAjaxEnabled() ? sprintf('onclick="return ajDISPLAY(\'BODY\',\'cmd=template_engine&server_id=%s&container=%s\',\'%s\');"',$app['server']->getIndex(),$request['template']->getContainer(),_('Retrieving DN')) : ''));
echo '</div>';
echo '</form>';
echo '<br />';
} else {
$href = sprintf('cmd=template_engine&server_id=%s&dn=%s',
$app['server']->getIndex(),$request['template']->getDNEncode());
echo '<div style="text-align: center;">';
echo _('You made no changes');
if (isAjaxEnabled())
printf(' <a href="cmd.php?%s" onclick="return ajDISPLAY(\'BODY\',\'%s\',\'%s\');">%s</a>.',
htmlspecialchars($href),htmlspecialchars($href),_('Retrieving DN'),_('Go back'));
else
printf(' <a href="cmd.php?%s">%s</a>.',htmlspecialchars($href),_('Go back'));
echo '</div>';
}
?>

View File

@ -1,88 +0,0 @@
<?php
/**
* Check the password used by an entry.
*
* @package phpLDAPadmin
* @subpackage Page
*/
/**
*/
require './common.php';
$www['page'] = new page();
$request = array();
$request['componentid'] = get_request('componentid','REQUEST');
$request['hash'] = get_request('hash','REQUEST');
$request['password'] = get_request('check_password','REQUEST');
$request['action'] = get_request('action','REQUEST');
$request['attribute'] = get_request('attr','REQUEST');
if (get_request('base64','REQUEST')) {
$request['hash'] = base64_decode($request['hash']);
$request['password'] = base64_decode($request['password']);
}
$request['enc_type'] = get_enc_type($request['hash']);
printf('<h3 class="subtitle">%s</h3>',_('Password Checker Tool'));
echo '<form action="password_checker.php" method="post">';
echo '<input type="hidden" name="action" value="compare" />';
printf('<input type="hidden" name="attr" value="%s" />',$request['attribute']);
echo '<table class="forminput" width="100%" border="0">';
echo '<tr>';
printf('<td class="heading">%s</td>',_('Compare'));
printf('<td><input type="%s" name="hash" id="hash" value="%s" /></td>',
(obfuscate_password_display($request['enc_type']) ? 'password' : 'text'),htmlspecialchars($request['hash']));
echo '</tr>';
echo '<tr>';
printf('<td class="heading">%s</td>',_('To'));
printf('<td><input type="password" name="check_password" value="%s" /></td>',
htmlspecialchars($request['password']));
echo '</tr>';
echo '<tr>';
echo '<td>&nbsp;</td>';
echo '<td><input type="submit" value="Compare" />';
if ($request['action'] == 'compare') {
echo '&nbsp;&nbsp;&nbsp;&nbsp;<b>';
if (password_check($request['hash'],$request['password'],$request['attribute']))
printf('<span class="good">%s</span>',_('Passwords match!'));
else
printf('<span class="bad">%s</span>',_('Passwords do not match!'));
echo '</b>';
}
echo '</td>';
echo '</tr>';
echo '</table>';
echo '</form>';
# Pull our password from the form that opened this window.
if ($request['componentid']) {
echo '<script type="text/javascript">';
printf('var c = window.opener.document.getElementById("%s");',$request['componentid']);
printf('var h = document.getElementById("%s");','hash');
echo 'if (c && h) { h.value = c.value; }';
echo '</script>';
}
# Capture the output and put into the body of the page.
$www['body'] = new block();
$www['body']->SetBody(ob_get_contents());
$www['page']->block_add('body',$www['body']);
ob_end_clean();
# Render the popup.
$www['page']->display(array('CONTROL'=>false,'FOOT'=>false,'HEAD'=>false,'TREE'=>false));
?>

View File

@ -1,735 +0,0 @@
<?php
/**
* Classes and functions for communication of Data Stores
*
* @author The phpLDAPadmin development team
* @package phpLDAPadmin
*/
/**
* This abstract class provides the basic variables and methods.
*
* @package phpLDAPadmin
* @subpackage DataStore
*/
abstract class DS {
# ID of this db.
protected $index;
# Configuration paramters.
protected $default;
protected $custom;
protected $type;
abstract function __construct($index);
/**
* This will make the connection to the datasource
*/
abstract protected function connect($method,$debug=false);
/**
* Login to the datastore
* method: default = anon, connect to ds using bind_id not auth_id.
* method: 'user', connect with auth_id
* method: '<freetext>', any custom extra connection to ds.
*/
abstract public function login($user=null,$pass=null,$method=null);
/**
* Query the datasource
*/
abstract public function query($query,$method,$index=null,$debug=false);
/**
* Return error details from previous operation
*/
abstract protected function getErrorMessage();
abstract protected function getErrorNum();
/**
* Functions that set and verify object configuration details
*/
public function setDefaults($defaults) {
foreach ($defaults as $key => $details)
foreach ($details as $setting => $value)
$this->default->{$key}[$setting] = $value;
}
public function isDefaultKey($key) {
return isset($this->default->$key);
}
public function isDefaultSetting($key,$setting) {
return array_key_exists($setting,$this->default->{$key});
}
/**
* Return a configuration value
*/
public function getValue($key,$setting,$fatal=true) {
if (defined('DEBUG_ENABLED') && DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,1,__FILE__,__LINE__,__METHOD__,$fargs);
if (isset($this->custom->{$key}[$setting]))
return $this->custom->{$key}[$setting];
elseif (isset($this->default->{$key}[$setting]) && array_key_exists('default',$this->default->{$key}[$setting]))
return $this->default->{$key}[$setting]['default'];
elseif ($fatal)
debug_dump_backtrace("Error trying to get a non-existant value ($key,$setting)",1);
else
return null;
}
/**
* Set a configuration value
*/
public function setValue($key,$setting,$value) {
if (isset($this->custom->{$key}[$setting]))
system_message(array(
'title'=>_('Configuration setting already defined.'),
'body'=>sprintf('A call has been made to reset a configuration value (%s,%s,%s)',
$key,$setting,$value),
'type'=>'info'));
$this->custom->{$key}[$setting] = $value;
}
/**
* Return the untested config items
*/
public function untested() {
$result = array();
foreach ($this->default as $option => $details)
foreach ($details as $param => $values)
if (isset($values['untested']) && $values['untested'])
array_push($result,sprintf('%s.%s',$option,$param));
return $result;
}
/**
* Get the name of this datastore
*/
public function getName() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
return $this->getValue('server','name');
}
/**
* Functions that enable login and logout of the application
*/
/**
* Return the authentication type for this object
*/
public function getAuthType() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
switch ($this->getValue('login','auth_type')) {
case 'cookie':
case 'config':
case 'http':
case 'proxy':
case 'session':
case 'sasl':
return $this->getValue('login','auth_type');
default:
die(sprintf('Error: <b>%s</b> hasnt been configured for auth_type <b>%s</b>',__METHOD__,
$this->getValue('login','auth_type')));
}
}
/**
* Get the login name of the user logged into this datastore's connection method
* If this returns null, we are not logged in.
* If this returns '', we are logged in with anonymous
*/
public function getLogin($method=null) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
$method = $this->getMethod($method);
# For anonymous binds
if ($method == 'anon')
if (isset($_SESSION['USER'][$this->index][$method]['name']))
return '';
else
return null;
switch ($this->getAuthType()) {
case 'cookie':
if (! isset($_COOKIE[$method.'-USER']))
# If our bind_id is set, we'll pass that back for logins.
return (! is_null($this->getValue('login','bind_id')) && $method == 'login') ? $this->getValue('login','bind_id') : null;
else
return blowfish_decrypt($_COOKIE[$method.'-USER']);
case 'config':
if (! isset($_SESSION['USER'][$this->index][$method]['name']))
return $this->getValue('login','bind_id');
else
return blowfish_decrypt($_SESSION['USER'][$this->index][$method]['name']);
case 'proxy':
if (! isset($_SESSION['USER'][$this->index][$method]['proxy']))
return $this->getValue('login','bind_id');
else
return blowfish_decrypt($_SESSION['USER'][$this->index][$method]['proxy']);
case 'http':
case 'session':
case 'sasl':
if (! isset($_SESSION['USER'][$this->index][$method]['name']))
# If our bind_id is set, we'll pass that back for logins.
return (! is_null($this->getValue('login','bind_id')) && $method == 'login') ? $this->getValue('login','bind_id') : null;
else
return blowfish_decrypt($_SESSION['USER'][$this->index][$method]['name']);
default:
die(sprintf('Error: %s hasnt been configured for auth_type %s',__METHOD__,$this->getAuthType()));
}
}
/**
* Set the login details of the user logged into this datastore's connection method
*/
protected function setLogin($user,$pass,$method=null) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
$method = $this->getMethod($method);
switch ($this->getAuthType()) {
case 'cookie':
set_cookie($method.'-USER',blowfish_encrypt($user),NULL,'/');
set_cookie($method.'-PASS',blowfish_encrypt($pass),NULL,'/');
return true;
case 'config':
return true;
case 'proxy':
if (isset($_SESSION['USER'][$this->index][$method]['proxy']))
unset($_SESSION['USER'][$this->index][$method]['proxy']);
case 'http':
case 'session':
case 'sasl':
$_SESSION['USER'][$this->index][$method]['name'] = blowfish_encrypt($user);
$_SESSION['USER'][$this->index][$method]['pass'] = blowfish_encrypt($pass);
return true;
default:
die(sprintf('Error: %s hasnt been configured for auth_type %s',__METHOD__,$this->getAuthType()));
}
}
/**
* Get the login password of the user logged into this datastore's connection method
*/
protected function getPassword($method=null) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
$method = $this->getMethod($method);
# For anonymous binds
if ($method == 'anon')
if (isset($_SESSION['USER'][$this->index][$method]['name']))
return '';
else
return null;
switch ($this->getAuthType()) {
case 'cookie':
if (! isset($_COOKIE[$method.'-PASS']))
# If our bind_id is set, we'll pass that back for logins.
return (! is_null($this->getValue('login','bind_pass')) && $method == 'login') ? $this->getValue('login','bind_pass') : null;
else
return blowfish_decrypt($_COOKIE[$method.'-PASS']);
case 'config':
case 'proxy':
if (! isset($_SESSION['USER'][$this->index][$method]['pass']))
return $this->getValue('login','bind_pass');
else
return blowfish_decrypt($_SESSION['USER'][$this->index][$method]['pass']);
case 'http':
case 'session':
case 'sasl':
if (! isset($_SESSION['USER'][$this->index][$method]['pass']))
# If our bind_pass is set, we'll pass that back for logins.
return (! is_null($this->getValue('login','bind_pass')) && $method == 'login') ? $this->getValue('login','bind_pass') : null;
else
return blowfish_decrypt($_SESSION['USER'][$this->index][$method]['pass']);
default:
die(sprintf('Error: %s hasnt been configured for auth_type %s',__METHOD__,$this->getAuthType()));
}
}
/**
* Return if this datastore's connection method has been logged into
*/
public function isLoggedIn($method=null) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
static $CACHE = array();
$method = $this->getMethod($method);
if (isset($CACHE[$this->index][$method]) && ! is_null($CACHE[$this->index][$method]))
return $CACHE[$this->index][$method];
$CACHE[$this->index][$method] = null;
# For some authentication types, we need to do the login here
switch ($this->getAuthType()) {
case 'config':
if (! $CACHE[$this->index][$method] = $this->login($this->getLogin($method),$this->getPassword($method),$method))
system_message(array(
'title'=>_('Unable to login.'),
'body'=>_('Your configuration file has authentication set to CONFIG based authentication, however, the userid/password failed to login'),
'type'=>'error'));
break;
case 'http':
# If our auth vars are not set, throw up a login box.
if (! isset($_SERVER['PHP_AUTH_USER'])) {
# If this server is not in focus, skip the basic auth prompt.
if (get_request('server_id','REQUEST') != $this->getIndex()) {
$CACHE[$this->index][$method] = false;
break;
}
header(sprintf('WWW-Authenticate: Basic realm="%s %s"',app_name(),_('login')));
if ($_SERVER['SERVER_PROTOCOL'] == 'HTTP/1.0')
header('HTTP/1.0 401 Unauthorized'); // http 1.0 method
else
header('Status: 401 Unauthorized'); // http 1.1 method
# If we still dont have login details...
if (! isset($_SERVER['PHP_AUTH_USER'])) {
system_message(array(
'title'=>_('Unable to login.'),
'body'=>_('Your configuration file has authentication set to HTTP based authentication, however, there was none presented'),
'type'=>'error'));
$CACHE[$this->index][$method] = false;
}
# Check our auth vars are valid.
} else {
if (! $this->login($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW'],$method)) {
system_message(array(
'title'=>_('Unable to login.'),
'body'=>_('Your HTTP based authentication is not accepted by the LDAP server'),
'type'=>'error'));
$CACHE[$this->index][$method] = false;
} else
$CACHE[$this->index][$method] = true;
}
break;
case 'proxy':
$CACHE[$this->index][$method] = $this->login($this->getValue('login','bind_id'),$this->getValue('login','bind_pass'),$method);
break;
case 'sasl':
# Propogate any given Kerberos credential cache location
if (isset($_ENV['REDIRECT_KRB5CCNAME']))
putenv(sprintf('KRB5CCNAME=%s',$_ENV['REDIRECT_KRB5CCNAME']));
elseif (isset($_SERVER['KRB5CCNAME']))
putenv(sprintf('KRB5CCNAME=%s',$_SERVER['KRB5CCNAME']));
# Map the SASL auth ID to a DN
$regex = $this->getValue('login', 'sasl_dn_regex');
$replacement = $this->getValue('login', 'sasl_dn_replacement');
if ($regex && $replacement) {
$userDN = preg_replace($regex, $replacement, $_SERVER['REMOTE_USER']);
$CACHE[$this->index][$method] = $this->login($userDN, '', $method);
# Otherwise, use the user name as is
# For GSSAPI Authentication + mod_auth_kerb and Basic Authentication
} else
$CACHE[$this->index][$method] = $this->login(isset($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'] : '', '', $method);
break;
default:
$CACHE[$this->index][$method] = is_null($this->getLogin($method)) ? false : true;
}
return $CACHE[$this->index][$method];
}
/**
* Logout of this datastore's connection method
*/
public function logout($method=null) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
$method = $this->getMethod($method);
unset ($_SESSION['cache'][$this->index]);
switch ($this->getAuthType()) {
case 'cookie':
set_cookie($method.'-USER','',time()-3600,'/');
set_cookie($method.'-PASS','',time()-3600,'/');
case 'config':
return true;
case 'http':
case 'proxy':
case 'session':
case 'sasl':
if (isset($_SESSION['USER'][$this->index][$method]))
unset($_SESSION['USER'][$this->index][$method]);
return true;
default:
die(sprintf('Error: %s hasnt been configured for auth_type %s',__METHOD__,$this->getAuthType()));
}
}
/**
* Functions that return the condition of the datasource
*/
public function isVisible() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
return $this->getValue('server','visible');
}
public function isReadOnly() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
if (! trim($this->getLogin(null)) && $_SESSION[APPCONFIG]->getValue('appearance','anonymous_bind_implies_read_only'))
return true;
else
return $this->getValue('server','read_only');
}
public function getIndex() {
if (defined('DEBUG_ENABLED') && DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,1,__FILE__,__LINE__,__METHOD__,$fargs,$this->index);
return $this->index;
}
/**
* Work out which connection method to use.
* If a method is passed, then it will be passed back. If no method is passed, then we'll
* check to see if the user is logged in. If they are, then 'user' is used, otherwise
* 'anon' is used.
*
* @param int Server ID
* @return string Connection Method
*/
protected function getMethod($method=null) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
static $CACHE = array();
# Immediately return if method is set.
if (! is_null($method))
return $method;
# If we have been here already, then return our result
if (isset($CACHE[$this->index]) && ! is_null($CACHE))
return $CACHE[$this->index];
$CACHE[$this->index] = 'anon';
if ($this->isLoggedIn('user'))
$CACHE[$this->index] = 'user';
return $CACHE[$this->index];
}
/**
* This method should be overridden in application specific ds files
*/
public function isSessionValid() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,1,__FILE__,__LINE__,__METHOD__,$fargs,true);
return true;
}
/**
* Return the time left in seconds until this connection times out. If there is not timeout,
* this function will return null.
*/
public function inactivityTime() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
if ($this->isLoggedIn() && ! in_array($this->getAuthType(),array('config','http')))
return time()+($this->getValue('login','timeout')*60);
else
return null;
}
}
/**
* The list of database sources
*
* @package phpLDAPadmin
* @subpackage DataStore
*/
class Datastore {
# Out DS index id
private $index;
# List of all the objects
private $objects = array();
# Default settings
private $default;
public function __construct() {
$this->default = new StdClass;
$this->default->server['id'] = array(
'desc'=>'Server ID',
'default'=>null);
$this->default->server['name'] = array(
'desc'=>'Server name',
'default'=>null);
# Connectivity Info
$this->default->server['host'] = array(
'desc'=>'Host Name',
'default'=>'127.0.0.1');
$this->default->server['port'] = array(
'desc'=>'Port Number',
'default'=>null);
# Read or write only access
$this->default->server['read_only'] = array(
'desc'=>'Server is in READ ONLY mode',
'default'=>false);
$this->default->server['visible'] = array(
'desc'=>'Whether this server is visible',
'default'=>true);
$this->default->server['hide_noaccess_base'] = array(
'desc'=>'If base DNs are not accessible, hide them instead of showing create',
'default'=>false);
# Authentication Information
$this->default->login['auth_type'] = array(
'desc'=>'Authentication Type',
'default'=>'session');
/*
/* ID to login to this application, this assumes that there is
* application authentication on top of authentication required to
* access the data source **
$this->default->login['auth_id'] = array(
'desc'=>'User Login ID to login to this DS',
'untested'=>true,
'default'=>null);
$this->default->login['auth_pass'] = array(
'desc'=>'User Login Password to login to this DS',
'untested'=>true,
'default'=>null);
*/
$this->default->login['auth_text'] = array(
'desc'=>'Text to show at the login prompt',
'default'=>null);
$this->default->login['bind_id'] = array(
'desc'=>'User Login ID to bind to this DS',
'default'=>null);
$this->default->login['bind_dn_template'] = array(
'desc'=>'Template string for user login DN to bind to this DS. Use \'%s\' where user input should be inserted.',
'default'=>null);
$this->default->login['bind_pass'] = array(
'desc'=>'User Login Password to bind to this DS',
'default'=>null);
$this->default->login['timeout'] = array(
'desc'=>'Session timout in seconds',
'default'=>session_cache_expire()-1);
$this->default->login['sasl_dn_regex'] = array(
'desc'=>'SASL authorization id to user dn PCRE regular expression',
'untested'=>true,
'default'=>null);
$this->default->login['sasl_dn_replacement'] = array(
'desc'=>'SASL authorization id to user dn PCRE regular expression replacement string',
'untested'=>true,
'default'=>null);
# Prefix for custom pages
$this->default->custom['pages_prefix'] = array(
'desc'=>'Prefix name for custom pages',
'default'=>'custom_');
}
/**
* Create a new database object
*/
public function newServer($type) {
if (class_exists($type)) {
$this->index = count($this->objects)+1;
$this->objects[$this->index] = new $type($this->index);
$this->objects[$this->index]->setDefaults($this->default);
return $this->index;
} else {
printf('ERROR: Class [%s] doesnt exist',$type);
die();
}
}
/**
* Set values for a database object.
*/
public function setValue($key,$setting,$value) {
if (! $this->objects[$this->index]->isDefaultKey($key))
error("ERROR: Setting a key [$key] that isnt predefined.",'error',true);
if (! $this->objects[$this->index]->isDefaultSetting($key,$setting))
error("ERROR: Setting a index [$key,$setting] that isnt predefined.",'error',true);
# Test if its should be an array or not.
if (is_array($this->objects[$this->index]->getValue($key,$setting)) && ! is_array($value))
error("Error in configuration file, {$key}['$setting'] SHOULD be an array of values.",'error',true);
if (! is_array($this->objects[$this->index]->getValue($key,$setting)) && is_array($value))
error("Error in configuration file, {$key}['$setting'] should NOT be an array of values.",'error',true);
# Store the value in the object.
$this->objects[$this->index]->setValue($key,$setting,$value);
}
/**
* Get a list of all the configured servers.
*
* @param boolean Only show visible servers.
* @return array list of all configured servers.
*/
public function getServerList($isVisible=true) {
if (defined('DEBUG_ENABLED') && DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
static $CACHE;
if (isset($CACHE[$isVisible]))
return $CACHE[$isVisible];
$CACHE[$isVisible] = array();
# Debugging incase objects is not set.
if (! $this->objects) {
print "<PRE>";
debug_print_backtrace();
die();
}
foreach ($this->objects as $id => $server)
if (! $isVisible || ($isVisible && $server->getValue('server','visible')))
$CACHE[$isVisible][$id] = $server;
masort($CACHE[$isVisible],'name');
return $CACHE[$isVisible];
}
/**
* Return an object Instance of a configured database.
*
* @param int Index
* @return object Datastore instance object.
*/
public function Instance($index=null) {
if (defined('DEBUG_ENABLED') && DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
# If no index defined, then pick the lowest one.
if (is_null($index) || ! trim($index) || ! is_numeric($index))
$index = min($this->GetServerList())->getIndex();
if (! isset($this->objects[$index]))
debug_dump_backtrace(sprintf('Error: Datastore instance [%s] doesnt exist?',htmlspecialchars($index)),1);
if (defined('DEBUG_ENABLED') && DEBUG_ENABLED)
debug_log('Returning instance of database (%s)',3,0,__FILE__,__LINE__,__METHOD__,$index);
return $this->objects[$index];
}
/**
* Return an object Instance of a configured database.
*
* @param string Name of the instance to retrieve
* @return object Datastore instance object.
*/
public function InstanceName($name=null) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
foreach ($this->getServerList(false) as $index)
if ($this->objects[$index]->getName() == $name)
return $this->objects[$index];
# If we get here, then no object with the name exists.
return null;
}
/**
* Return an object Instance of a configured database.
*
* @param string ID of the instance to retrieve
* @return object Datastore instance object.
*/
public function InstanceId($id=null) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
foreach ($this->getServerList(false) as $index)
if ($this->objects[$index->getIndex()]->getValue('server','id') == $id)
return $this->objects[$index->getIndex()];
# If we get here, then no object with the name exists.
return null;
}
}
?>

File diff suppressed because it is too large Load Diff

View File

@ -1,677 +0,0 @@
<?php
/**
* Classes and functions for communication of Data Stores
*
* @author The phpLDAPadmin development team
* @package phpLDAPadmin
*/
/**
* This abstract class provides variables and methods for LDAP datastores for use by PLA.
*
* @package phpLDAPadmin
* @subpackage DataStore
*/
class ldap_pla extends ldap {
function __construct($index) {
parent::__construct($index);
$this->default->appearance['pla_password_hash'] = array(
'desc'=>'Default HASH to use for passwords',
'default'=>'md5');
$this->default->appearance['show_create'] = array(
'desc'=>'Whether to show the "Create new Entry here" in the tree browser',
'default'=>true);
$this->default->appearance['open_tree'] = array(
'desc'=>'Whether to initially open each tree',
'default'=>false);
$this->default->appearance['show_authz'] = array(
'desc'=>'Enable display of authorization ID as login',
'default'=>false);
$this->default->login['fallback_dn'] = array(
'desc'=>'If the attribute base login fails, see if a DN was entered',
'default'=>false);
$this->default->query['disable_default'] = array(
'desc'=>'Configuration to disable the default query template',
'default'=>false);
$this->default->query['custom_only'] = array(
'desc'=>'Configuration to force the usage of custom query templates',
'default'=>false);
$this->default->server['branch_rename'] = array(
'desc'=>'Enable renaming of branches',
'default'=>false);
$this->default->server['custom_attrs'] = array(
'desc'=>'Custom operational attributes to be treated as regular attributes',
'default'=>array(''));
$this->default->server['custom_sys_attrs'] = array(
'desc'=>'Custom operational attributes to be treated as internal attributes',
'default'=>array('+'));
# This was added in case the LDAP server doesnt provide them with a base +,* query.
$this->default->server['root_dse_attributes'] = array(
'desc'=>'RootDSE attributes for use when displaying server info',
'default'=>array(
'namingContexts',
'subschemaSubentry',
'altServer',
'supportedExtension',
'supportedControl',
'supportedSASLMechanisms',
'supportedLDAPVersion',
'currentTime',
'dsServiceName',
'defaultNamingContext',
'schemaNamingContext',
'configurationNamingContext',
'rootDomainNamingContext',
'supportedLDAPPolicies',
'highestCommittedUSN',
'dnsHostName',
'ldapServiceName',
'serverName',
'supportedCapabilities',
'changeLog',
'tlsAvailableCipherSuites',
'tlsImplementationVersion',
'supportedSASLMechanisms',
'dsaVersion',
'myAccessPoint',
'dseType',
'+',
'*'
));
$this->default->server['force_may'] = array(
'desc'=>'Force server MUST attributes as MAY attributes',
'default'=>array(
));
# Settings for auto_number
$this->default->auto_number['enable'] = array(
'desc'=>'Enable the AUTO UID feature',
'default'=>true);
$this->default->auto_number['mechanism'] = array(
'desc'=>'Mechanism to use to search for automatic numbers',
'default'=>'search');
$this->default->auto_number['search_base'] = array(
'desc'=>'Base DN to use for search mechanisms',
'default'=>null);
$this->default->auto_number['min'] = array(
'desc'=>'Minimum number to start with',
'default'=>array('uidNumber'=>1000,'gidNumber'=>500));
$this->default->auto_number['dn'] = array(
'desc'=>'DN to use when evaluating numbers',
'default'=>null);
$this->default->auto_number['pass'] = array(
'desc'=>'Password for DN to use when evaluating numbers',
'default'=>null);
$this->default->unique['attrs'] = array(
'desc'=>'Attributes to check for uniqueness before allowing updates',
'default'=>array('mail','uid','uidNumber'));
$this->default->unique['dn'] = array(
'desc'=>'DN to use when evaluating attribute uniqueness',
'default'=>null);
$this->default->unique['pass'] = array(
'desc'=>'Password for DN to use when evaluating attribute uniqueness',
'default'=>null);
}
public function __get($key) {
switch ($key) {
case 'name':
return $this->getValue('server','name');
default:
system_message(array(
'title'=>_('Unknown request for Object value.'),
'body'=>sprintf(_('Attempt to obtain value %s from %s'),$key,get_class($this)),
'type'=>'error'));
}
}
/**
* Gets whether the admin has configured phpLDAPadmin to show the "Create New" link in the tree viewer.
* <code>
* $servers->setValue('appearance','show_create',true|false);
* </code>
* If NOT set, then default to show the Create New item.
* If IS set, then return the value (it should be true or false).
*
* The entry creation command must be available.
* <code>
* $config->custom->commands['script'] = array('create' => true);
* </code>
*
* @return boolean true if the feature is enabled and false otherwise.
*/
function isShowCreateEnabled() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
if (! $_SESSION[APPCONFIG]->isCommandAvailable('script','create'))
return false;
else
return $this->getValue('appearance','show_create');
}
/**
* Fetch whether the user has configured a certain server login to be non anonymous
*
* <code>
* $servers->setValue('login','anon_bind',true|false);
* </code>
*
* @return boolean
*/
public function isAnonBindAllowed() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
# If only_login_allowed_dns is set, then we cant have anonymous.
if (count($this->getValue('login','allowed_dns')) > 0)
$return = false;
else
$return = $this->getValue('login','anon_bind');
if (DEBUG_ENABLED)
debug_log('Returning (%s)',17,0,__FILE__,__LINE__,__METHOD__,$return);
return $return;
}
/**
* Returns true if the user has configured the specified server to enable branch (non-leaf) renames.
*
* This is configured in config.php thus:
* <code>
* $servers->setValue('server','branch_rename',true|false);
* </code>
*
* @return boolean
*/
function isBranchRenameEnabled() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
return $this->getValue('server','branch_rename');
}
/**
* Determines if an attribute's value can contain multiple lines. Attributes that fall
* in this multi-line category may be configured in config.php. Hence, this function
* accesses the global variable $_SESSION[APPCONFIG]->custom->appearance['multi_line_attributes'];
*
* Usage example:
* <code>
* if ($ldapserver->isMultiLineAttr('postalAddress'))
* echo '<textarea name="postalAddress"></textarea>';
* else
* echo '<input name="postalAddress" type="text">';
* </code>
*
* @param string The name of the attribute of interested (case insensivite)
* @param string (optional) The current value of the attribute (speeds up the process by searching for carriage returns already in the attribute value)
* @return boolean
*/
function isMultiLineAttr($attr_name,$val=null) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
# Set default return
$return = false;
# First, check the optional val param for a \n or a \r
if (! is_null($val) && (strpos($val,"\n") || strpos($val,"\r")))
$return = true;
# Next, compare strictly by name first
else
foreach ($_SESSION[APPCONFIG]->getValue('appearance','multi_line_attributes') as $multi_line_attr_name)
if (strcasecmp($multi_line_attr_name,$attr_name) == 0) {
$return = true;
break;
}
# If unfound, compare by syntax OID
if (! $return) {
$sattr = $this->getSchemaAttribute($attr_name);
if ($sattr) {
$syntax_oid = $sattr->getSyntaxOID();
if ($syntax_oid)
foreach ($_SESSION[APPCONFIG]->getValue('appearance','multi_line_syntax_oids') as $multi_line_syntax_oid)
if ($multi_line_syntax_oid == $syntax_oid) {
$return = true;
break;
}
}
}
if (DEBUG_ENABLED)
debug_log('Returning (%s)',17,0,__FILE__,__LINE__,__METHOD__,$return);
return $return;
}
/**
* Returns true if the specified attribute is configured according to
* the test enabled in config.php
*
* @param string The name of the attribute to test.
* @param array The attributes to test against.
* @param dn A DN that is exempt from these tests.
* @return boolean
*/
private function isAttrTest($attr,$attrs,$except_dn) {
$attr = trim($attr);
if (! trim($attr) || ! count($attrs))
return false;
# Is the user excluded?
if ($except_dn && $this->userIsMember($this->getLogin(),$except_dn))
return false;
foreach ($attrs as $attr_name)
if (strcasecmp($attr,trim($attr_name)) == 0)
return true;
return false;
}
/**
* Returns true if the specified attribute is configured as read only
* in config.php.
* Attributes are configured as read-only in config.php thus:
* <code>
* $config->custom->appearance['readonly_attrs'] = array('objectClass');
* </code>
*
* @param string The name of the attribute to test.
* @return boolean
*/
public function isAttrReadOnly($attr) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
$attrs = $_SESSION[APPCONFIG]->getValue('appearance','readonly_attrs');
$except_dn = $_SESSION[APPCONFIG]->getValue('appearance','readonly_attrs_exempt');
return $this->isAttrTest($attr,$attrs,$except_dn);
}
/**
* Returns true if the specified attribute is configured as hidden
* in config.php.
* Attributes are configured as hidden in config.php thus:
* <code>
* $config->custom->appearance['hide_attrs'] = array('objectClass');
* </code>
*
* @param string The name of the attribute to test.
* @return boolean
*/
public function isAttrHidden($attr) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
$attrs = $_SESSION[APPCONFIG]->getValue('appearance','hide_attrs');
$except_dn = $_SESSION[APPCONFIG]->getValue('appearance','hide_attrs_exempt');
return $this->isAttrTest($attr,$attrs,$except_dn);
}
/**
* Add objects
*/
public function add($dn,$entry_array,$method=null) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
foreach ($entry_array as $attr => $val)
$entry_array[$attr] = dn_unescape($val);
$result = false;
# Check our unique attributes.
if (! $this->checkUniqueAttrs($dn,$entry_array))
return false;
if (run_hook('pre_entry_create',array('server_id'=>$this->index,'method'=>$method,'dn'=>$dn,'attrs'=>$entry_array))) {
$result = @ldap_add($this->connect($method),dn_escape($dn),$entry_array);
if ($result) {
# Update the tree
$tree = get_cached_item($this->index,'tree');
# If we created the base, delete it, then add it back
if (get_request('create_base'))
$tree->delEntry($dn);
$tree->addEntry($dn);
set_cached_item($this->index,'tree','null',$tree);
run_hook('post_entry_create',array('server_id'=>$this->index,'method'=>$method,'dn'=>$dn,'attrs'=>$entry_array));
} else {
system_message(array(
'title'=>_('Could not add the object to the LDAP server.'),
'body'=>ldap_error_msg($this->getErrorMessage(null),$this->getErrorNum(null)),
'type'=>'error'));
}
}
return $result;
}
/**
* Delete objects
*/
public function delete($dn,$method=null) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
$result = false;
if (run_hook('pre_entry_delete',array('server_id'=>$this->index,'method'=>$method,'dn'=>$dn))) {
$result = @ldap_delete($this->connect($method),dn_escape($dn));
if ($result) {
# Update the tree
$tree = get_cached_item($this->index,'tree');
$tree->delEntry($dn);
set_cached_item($this->index,'tree','null',$tree);
run_hook('post_entry_delete',array('server_id'=>$this->index,'method'=>$method,'dn'=>$dn));
}
}
return $result;
}
/**
* Rename objects
*/
public function rename($dn,$new_rdn,$container,$deleteoldrdn,$method=null) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
$result = false;
if (run_hook('pre_entry_rename',array('server_id'=>$this->index,'method'=>$method,'dn'=>$dn,'rdn'=>$new_rdn,'container'=>$container))) {
$result = @ldap_rename($this->connect($method),$dn,$new_rdn,$container,$deleteoldrdn);
if ($result) {
# Update the tree
$tree = get_cached_item($this->index,'tree');
$newdn = sprintf('%s,%s',$new_rdn,$container);
$tree->renameEntry($dn,$newdn);
set_cached_item($this->index,'tree','null',$tree);
run_hook('post_entry_rename',array('server_id'=>$this->index,'method'=>$method,'dn'=>$dn,'rdn'=>$new_rdn,'container'=>$container));
}
}
return $result;
}
/**
* Modify objects
*/
public function modify($dn,$attrs,$method=null) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
# Check our unique attributes.
if (! $this->checkUniqueAttrs($dn,$attrs))
return false;
$result = false;
$summary = array();
$current_attrs = $this->getDNAttrValues($dn,$method,LDAP_DEREF_NEVER,array('*'));
# Go through our attributes and call our hooks for each attribute changing its value
foreach ($attrs as $attr => $values) {
# For new attributes
if (count($values) && ! isset($current_attrs[$attr])) {
if (! run_hook('pre_attr_add',
array('server_id'=>$this->index,'method'=>$method,'dn'=>$dn,'attr'=>$attr,'newvalue'=>$values))) {
unset($attrs[$attr]);
system_message(array(
'title'=>_('Attribute not added'),
'body'=>sprintf('%s (<b>%s</b>)',_('Hook pre_attr_add prevented attribute from being added'),$attr),
'type'=>'warn'));
} else
$summary['add'][$attr]['new'] = $values;
# For modify attributes
} elseif (count($values)) {
if (! run_hook('pre_attr_modify',
array('server_id'=>$this->index,'method'=>$method,'dn'=>$dn,'attr'=>$attr,'oldvalue'=>$current_attrs[$attr],'newvalue'=>$values))) {
unset($attrs[$attr]);
system_message(array(
'title'=>_('Attribute not modified'),
'body'=>sprintf('%s (<b>%s</b>)',_('Hook pre_attr_modify prevented attribute from being modified'),$attr),
'type'=>'warn'));
} else {
$summary['modify'][$attr]['new'] = $values;
$summary['modify'][$attr]['old'] = $current_attrs[$attr];
}
# For delete attributes
} else {
if (! run_hook('pre_attr_delete',
array('server_id'=>$this->index,'method'=>$method,'dn'=>$dn,'attr'=>$attr,'oldvalue'=>$current_attrs[$attr]))) {
unset($attrs[$attr]);
system_message(array(
'title'=>_('Attribute not deleted'),
'body'=>sprintf('%s (<b>%s</b>)',_('Hook pre_attr_delete prevented attribute from being deleted'),$attr),
'type'=>'warn'));
} else
$summary['delete'][$attr]['old'] = $current_attrs[$attr];
}
}
if (! count($attrs))
return false;
if (run_hook('pre_entry_modify',array('server_id'=>$this->index,'method'=>$method,'dn'=>$dn,'attrs'=>$attrs))) {
$result = @ldap_modify($this->connect($method),$dn,$attrs);
if ($result) {
run_hook('post_entry_modify',array('server_id'=>$this->index,'method'=>$method,'dn'=>$dn,'attrs'=>$attrs));
foreach (array('add','modify','delete') as $mode)
if (isset($summary[$mode]))
foreach ($summary[$mode] as $attr => $values)
switch ($mode) {
case 'add':
run_hook(sprintf('post_attr_%s',$mode),
array('server_id'=>$this->index,'method'=>$method,'dn'=>$dn,'attr'=>$attr,'newvalue'=>$values['new']));
break;
case 'modify':
run_hook(sprintf('post_attr_%s',$mode),
array('server_id'=>$this->index,'method'=>$method,'dn'=>$dn,'attr'=>$attr,'oldvalue'=>$values['old'],'newvalue'=>$values['new']));
break;
case 'delete':
run_hook(sprintf('post_attr_%s',$mode),
array('server_id'=>$this->index,'method'=>$method,'dn'=>$dn,'attr'=>$attr,'oldvalue'=>$values['old']));
break;
default:
debug_dump_backtrace(sprintf('Unkown mode %s',$mode),1);
}
} else {
system_message(array(
'title'=>_('Could not perform ldap_modify operation.'),
'body'=>ldap_error_msg($this->getErrorMessage($method),$this->getErrorNum($method)),
'type'=>'error'));
}
}
return $result;
}
/**
* Returns true if the specified attribute is configured as unique
* in config.php.
* Attributes are configured as hidden in config.php thus:
* <code>
* $servers->setValue('unique','attrs',array('mail','uid','uidNumber'));
* </code>
*
* @param string $attr The name of the attribute to test.
* @return boolean
*/
public function isAttrUnique($attr) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
# Should this attribute value be unique
if (in_array_ignore_case($attr,$this->getValue('unique','attrs')))
return true;
else
return false;
}
/**
* This function will check whether the value for an attribute being changed
* is already assigned to another DN.
*
* Returns the bad value, or null if all values are OK
*
* @param dn DN that is being changed
* @param string Attribute being changed
* @param string|array New values for the attribute
*/
public function checkUniqueAttrs($dn,$attrs) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
# If none of the attributes are defined unique, we'll return immediately;
if (! $checkattrs = array_intersect(arrayLower($this->getValue('unique','attrs')),array_keys(array_change_key_case($attrs))))
return true;
# Check see and use our alternate uid_dn and password if we have it.
if (! $this->login($this->getValue('unique','dn'),$this->getValue('unique','pass'),'unique')) {
system_message(array(
'title'=>_('UNIQUE invalid login/password'),
'body'=>sprintf('%s (<b>%s</b>)',_('Unable to connect to LDAP server with the unique login/password, please check your configuration.'),
$this->getName()),
'type'=>'warn'));
return false;
}
$query = array();
# Build our search filter to double check each attribute.
$query['filter'] = '(|';
foreach ($checkattrs as $attr)
foreach ($attrs[$attr] as $val)
if ($val)
$query['filter'] .= sprintf('(%s=%s)',$attr,$val);
$query['filter'] .= ')';
$query['attrs'] = $checkattrs;
# Search through our bases and see if we have match
foreach ($this->getBaseDN() as $base) {
$query['base'] = $base;
# Do the search
$results = $this->query($query,'unique');
# If we have a match.
if (count($results))
foreach ($results as $values)
# If one of the attributes is owned to somebody else, then we may as well die here.
if ($values['dn'] != $dn) {
$href = sprintf('cmd.php?cmd=query_engine&server_id=%s&filter=%s&scope=sub&query=none&format=list&search=true',$this->index,$query['filter']);
system_message(array(
'title'=>_('Attribute value would not be unique'),
'body'=>sprintf('%s (<b><a href="%s">%s</a></b>)',
_('This update has been or will be cancelled, it would result in an attribute value not being unique. You might like to search the LDAP server for the offending entry.'),
htmlspecialchars($href),
_('Search')),
'type'=>'warn'));
return false;
}
}
# If we get here, then it must be OK?
return true;
}
/**
* Check if the session timeout has occured for this LDAP server.
*/
public function isSessionValid() {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
# If inactiveTime() returns a value, we need to check that it has not expired.
if (is_null($this->inactivityTime()) || ! $this->isLoggedIn())
return true;
# If session has expired
if ((isset($_SESSION['ACTIVITY'][$this->getIndex()])) && ($_SESSION['ACTIVITY'][$this->getIndex()] < time())) {
$this->logout();
unset($_SESSION['ACTIVITY'][$this->getIndex()]);
return false;
}
$_SESSION['ACTIVITY'][$this->getIndex()] = $this->inactivityTime();
return true;
}
/**
* Return login, or authorization ID if show_authz enabled
*/
public function displayLogin($method=null) {
// check for whoami function, added in 7.2
if ($this->getValue('appearance', 'show_authz') && function_exists('ldap_exop_whoami')) {
$result = @ldap_exop_whoami($this->connect($method));
if ($result) // strip any dn: or u: prefix
$result = preg_replace('/^(u|dn):/i', '', $result);
else // fall back to login on error
$result = $this->getLogin($method);
return $result;
}
else {
return $this->getLogin($method);
}
}
}
?>

File diff suppressed because it is too large Load Diff

View File

@ -7,9 +7,12 @@ img.jpegphoto {
/** ensure our userpassword has select is next to the password input */
div#userPassword .select2-container--bootstrap-5 .select2-selection {
font-size: inherit;
border-bottom-right-radius: unset;
border-top-right-radius: unset;
width: 9em;
border: #444054 1px solid;
background-color: #f0f0f0;
}
.input-group:first-child .select2-container--bootstrap-5 .select2-selection {
border-bottom-right-radius: unset;
border-top-right-radius: unset;
}

View File

@ -303,4 +303,8 @@ div#objectClass .input-group-delete {
bottom: 30px;
right: 10px;
height: 5px;
}
.input-group-text {
background-color: #fafafa;
}

27
public/js/custom.js vendored
View File

@ -13,32 +13,41 @@ function expandChildren(node) {
function getNode(item) {
$.ajax({
url: '/dn',
url: '/frame',
method: 'POST',
data: { key: item },
dataType: 'html',
beforeSend: function() {
content = $('.main-content').contents();
$('.main-content').empty().append('<div class="fa-3x"><i class="fas fa-spinner fa-pulse"></i></div>');
content = $('.main-content')
.contents();
$('.main-content')
.empty()
.append('<div class="fa-3x"><i class="fas fa-spinner fa-pulse"></i></div>');
}
}).done(function(html) {
$('.main-content').empty().append(html);
$('.main-content')
.empty()
.append(html);
}).fail(function(item) {
switch(item.status) {
}).fail(function(e) {
switch(e.status) {
case 404:
$('.main-content').empty().append(item.responseText);
$('.main-content').empty().append(e.responseText);
break;
case 409:
location.replace('/#'+item);
break;
case 419:
alert('Session has expired, reloading the page and try again...');
location.reload();
break;
case 500:
$('.main-content').empty().append(item.responseText);
$('.main-content').empty().append(e.responseText);
break;
default:
alert(item.status+': Well that didnt work?');
alert('Well that didnt work? Code ['+e.status+']');
}
});
}

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html translate="no">
@section('htmlheader')
@include('architect::layouts.partials.htmlheader')
@show

View File

@ -0,0 +1,64 @@
<!-- $o=RDN::class -->
<x-attribute.layout :edit="$edit ?? FALSE" :new="$new ?? FALSE" :o="$o">
@foreach($o->values as $value)
@if($edit)
<div class="input-group has-validation mb-3">
<select class="form-select @error('rdn')is-invalid @enderror" id="rdn" name="rdn">
<option value=""></option>
@foreach($o->attrs->map(fn($item)=>['id'=>$item,'value'=>$item]) as $option)
@continue(! Arr::get($option,'value'))
<option value="{{ Arr::get($option,'id') }}" @selected(Arr::get($option,'id') == old('rdn',$value ?? ''))>{{ Arr::get($option,'value') }}</option>
@endforeach
</select>
<span class="input-group-text">=</span>
<input type="text" @class(['form-control','is-invalid'=>$errors->get('rdn_value')]) id="rdn_value" name="rdn_value" value="{{ old('rdn_value') }}" placeholder="rdn">
<label class="input-group-text" for="inputGroupSelect02">,{{ $o->base }}</label>
<div class="invalid-feedback pb-2">
@error('rdn')
{{ $message }}
@enderror
@error('rdn_value')
{{ $message }}
@enderror
</div>
</div>
@else
{{ $value }}
@endif
@endforeach
</x-attribute.layout>
@section('page-scripts')
<script type="text/javascript">
$(document).ready(function() {
var rdn_value_set = null;
var rdn_attr = null;
function set_rdn_value() {
if (rdn_attr && rdn_value_set)
$('#'+rdn_attr).find('input').val($('input#rdn_value').val());
}
$('select#rdn').on('change',function() {
// if rdn_attr is already set (and its now different), remove read only and clear value
if (rdn_attr)
$('#'+rdn_attr).find('input').attr('readonly',false).val('');
// set RDN attribute read-only
if (rdn_attr = $(this).val())
$('#'+rdn_attr).find('input').attr('readonly',true).val('');
set_rdn_value();
})
$('input#rdn_value').on('change',function() {
rdn_value_set = $(this).val();
set_rdn_value();
})
});
</script>
@endsection

View File

@ -46,12 +46,15 @@
if (! rendered)
$.ajax({
type: 'POST',
// @todo When this is opened a second time, the data is appended.
cache: false,
url: '{{ url('entry/objectclass/add') }}',
data: {
oc: oc,
},
success: function(data) {
$('select#newoc').select2({
dropdownParent: $('#new_objectclass-modal'),
theme: 'bootstrap-5',
allowClear: true,
multiple: true,
data: data,
});
@ -60,8 +63,6 @@
if (e.status != 412)
alert('That didnt work? Please try again....');
},
url: '{{ url('entry/objectclass/add') }}/'+dn,
cache: false
});
rendered = true;

View File

@ -15,7 +15,7 @@
{{ $slot }}
@isset($name)
<span class="invalid-feedback">
@error((! empty($old)) ? $old : $name)
@error((! empty($old)) ? $old : ($id ?? $name))
{{ $message }}
@elseif(isset($feedback))
{{ $feedback }}

View File

@ -2,7 +2,7 @@
@isset($name)
<input type="hidden" id="{{ $id ?? $name }}_disabled" name="{{ $name }}" value="" disabled>
@endisset
<select class="form-select @isset($name)@error((! empty($old)) ? $old : $name) is-invalid @enderror @endisset" id="{{ $id ?? $name }}" @isset($name)name="{{ $name }}"@endisset @required(isset($required) && $required) @disabled(isset($disabled) && $disabled)>
<select class="form-select @isset($name)@error((! empty($old)) ? $old : ($id ?? $name)) is-invalid @enderror @endisset" id="{{ $id ?? $name }}" @isset($name)name="{{ $name }}"@endisset @required(isset($required) && $required) @disabled(isset($disabled) && $disabled)>
@if((empty($value) && ! empty($options)) || isset($addnew) || isset($choose))
<option value=""></option>
@isset($addnew)
@ -54,10 +54,24 @@
width: 'style',
allowClear: {{ $allowclear ?? 'false' }},
placeholder: '{{ $placeholder ?? '' }}',
multiple: {{ $multiple ?? 'false' }},
@isset($addvalues)
tags: true,
@endisset
});
@if(isset($multiple) && (! $multiple))
$('#{{ $id ?? $name }}').val(' ');
$('#{{ $id ?? $name }}').trigger('change');
@endif
@isset($options)
@if($options->count() === 1)
$('#{{ $id ?? $name }}')
.val('{{ $options->first()['id'] }}')
.trigger("change")
@endif
@endisset
});
</script>
@append

View File

@ -0,0 +1,22 @@
<div id="newattrs"></div>
<!-- Add new attributes -->
<div class="row">
<div class="col-12 col-sm-1 col-md-2"></div>
<div class="col-12 col-sm-10 col-md-8">
<div class="d-none" id="newattr-select">
<div class="row">
<div class="col-12 bg-dark text-light p-2">
<i class="fas fa-plus-circle"></i> Add New Attribute
</div>
</div>
<div class="row">
<div class="col-12 pt-2">
<x-form.select id="newattr" label="Select from..." :options="$o->getMissingAttributes()->sortBy('name')->map(fn($item)=>['id'=>$item->name,'value'=>$item->name_lc])"/>
</div>
</div>
</div>
</div>
<div class="col-2"></div>
</div>

View File

@ -3,7 +3,7 @@
<td class="{{ ($x=$o->getObject('jpegphoto')) ? 'border' : '' }}" rowspan="2">
{!! $x ? $x->render(FALSE,TRUE) : sprintf('<div class="page-title-icon f32"><i class="%s"></i></div>',$o->icon() ?? "fas fa-info") !!}
</td>
<td class="text-end align-text-top p-0 {{ $x ? 'ps-5' : 'pt-2' }}"><strong>{{ $dn }}</strong></td>
<td class="text-end align-text-top p-0 {{ $x ? 'ps-5' : 'pt-2' }}"><strong>{{ $o->getDn() }}</strong></td>
</tr>
<tr>
<td class="line-height-1" style="font-size: 55%;vertical-align: bottom;" colspan="2">

View File

@ -0,0 +1,128 @@
@extends('layouts.dn')
@section('page_title')
@include('fragment.dn.header',['o'=>($oo=config('server')->fetch(old('container',$container)))])
@endsection
@section('main-content')
<div class="row">
<p class="alert alert-danger text-center">
This is a tech preview of what is to come, and it is by no means complete.
</p>
</div>
<div class="row">
<div class="offset-1 col-10">
<div class="main-card mb-3 card">
<div class="card-header">
@lang('Create New Entry') - @lang('Step') {{ $step }}
</div>
<div class="card-body">
<form id="dn-create" method="POST" class="needs-validation" action="{{ url((int)$step === 2 ? 'entry/create' : 'entry/add') }}" enctype="multipart/form-data" novalidate>
@csrf
<input type="hidden" name="key" value="{{ Crypt::encryptString('*create|'.$container) }}">
@if($step < 2)
<input type="hidden" name="step" value="{{ $step }}">
@endif
@switch($step)
@case(1)
<div class="row">
<div class="col-12 col-sm-6">
<x-form.select
id="objectclass"
name="objectclass[]"
:label="__('Select a Structural ObjectClass...')"
:options="($oc=config('server')->schema('objectclasses'))
->filter(fn($item)=>$item->isStructural())
->sortBy(fn($item)=>$item->name_lc)
->map(fn($item)=>['id'=>$item->name,'value'=>$item->name])"
multiple="false"
/>
</div>
</div>
@break
@case(2)
<x-attribute-type :edit="true" :o="$o->getObject('rdn')"/>
@foreach ($o->getVisibleAttributes() as $ao)
<x-attribute-type :edit="true" :o="$ao"/>
@endforeach
@include('fragment.dn.add_attr')
@break;
@endswitch
</form>
<div class="row d-none pt-3">
<div class="col-12 {{ $step > 1 ? 'offset-sm-2' : '' }} col-sm-4 col-lg-2">
<x-form.reset form="dn-create"/>
<x-form.submit action="Next" form="dn-create"/>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('page-scripts')
<script type="text/javascript">
var oc = {!! $oo->getObject('objectclass')->values !!};
function editmode() {
// Find all input items and turn off readonly
$('input.form-control').each(function() {
// Except for objectClass - @todo show an "X" instead
if ($(this)[0].name.match(/^objectclass/))
return;
$(this).attr('readonly',false);
});
// Our password type
$('div#userPassword .form-select').each(function() {
$(this).prop('disabled',false);
})
$('.row.d-none').removeClass('d-none');
$('.addable.d-none').removeClass('d-none');
$('.deletable.d-none').removeClass('d-none');
$('#newattr-select.d-none').removeClass('d-none');
}
$(document).ready(function() {
$('#newattr').on('change',function(item) {
$.ajax({
type: 'POST',
beforeSend: function() {},
success: function(data) {
$('#newattrs').append(data);
},
error: function(e) {
if (e.status != 412)
alert('That didnt work? Please try again....');
},
url: '{{ url('entry/attr/add') }}/'+item.target.value,
data: {
objectclasses: oc,
},
cache: false
});
// Remove the option from the list
$(this).find('[value="'+item.target.value+'"]').remove()
// If there are no more options
if ($(this).find("option").length === 1)
$('#newattr-select').remove();
});
editmode();
});
</script>
@append

View File

@ -1,7 +1,7 @@
@extends('layouts.dn')
@section('page_title')
@include('fragment.dn.header')
@include('fragment.dn.header',['o'=>($o=config('server')->fetch($dn))])
@endsection
@section('main-content')
@ -9,8 +9,6 @@
<x-updated/>
<x-error/>
<!-- @todo If we are redirected here, check old() and add back any attributes that were in the original submission -->
<div class="main-card mb-3 card">
<div class="card-body">
<div class="card-header-tabs">
@ -34,31 +32,7 @@
<x-attribute-type :edit="true" :o="$ao"/>
@endforeach
<div id="newattrs"></div>
<!-- Add new attributes -->
<div class="row">
<div class="col-12 col-sm-1 col-md-2"></div>
<div class="col-12 col-sm-10 col-md-8">
<div class="d-none" id="newattr-select">
@if($o->getMissingAttributes()->count())
<div class="row">
<div class="col-12 bg-dark text-light p-2">
<i class="fas fa-plus-circle"></i> Add New Attribute
</div>
</div>
<div class="row">
<div class="col-12 pt-2">
<x-form.select id="newattr" label="Select from..." :options="$o->getMissingAttributes()->sortBy('name')->map(fn($item)=>['id'=>$item->name,'value'=>$item->name_lc])"/>
</div>
</div>
@endif
</div>
</div>
<div class="col-2"></div>
</div>
@include('fragment.dn.add_attr')
</form>
<div class="row d-none pt-3">

View File

@ -15,7 +15,7 @@
<div class="main-card mb-3 card">
<form id="import-form" action="{{ url('import/process/ldif') }}" method="POST" enctype="multipart/form-data">
@csrf
<input type="hidden" name="frame" value="import">
<input type="hidden" name="key" value="{{ Crypt::encryptString('*import|_NOP') }}">
<div class="card-header">
@lang('LDIF Import')

View File

@ -1,10 +1,11 @@
@use(App\Classes\LDAP\Server)
@extends('layouts.dn')
@section('page_title')
<table class="table table-borderless">
<tr>
<td style="border-radius: 5px;"><div class="page-title-icon f32"><i class="fas fa-fingerprint"></i></div></td>
<td class="top text-end align-text-top p-0 pt-2"><strong>{{ \App\Classes\LDAP\Server::schemaDN() }}</strong></td>
<td class="top text-end align-text-top p-0 pt-2"><strong>{{ Server::schemaDN() }}</strong></td>
</tr>
</table>
@endsection

View File

@ -17,7 +17,7 @@
<div class="row">
<div class="col-12 col-sm-4">
<h3 class="d-inline-block d-sm-none">phpLDAPadmin</h3>
<img src="{{ url('/images/logo.png') }}" class="logo-image col-12" alt="PLA Logo">
<img src="{{ url('images/logo.png') }}" class="logo-image col-12" alt="PLA Logo">
</div>
<div class="col-12 col-sm-8">
@ -48,14 +48,23 @@
var subpage = window.location.hash;
$(document).ready(function() {
// Enable navigating to a page via a URL fragment, and that fragment is defined with a server-icon
var valid = Object.values($('.server-icon > a').map(function(item) {
return $(this).attr('id');
})).indexOf(subpage.substring(1));
if (subpage) {
// Enable navigating to a page via a URL fragment, and that fragment is defined with a server-icon
var valid = Object.values($('.server-icon > a').map(function() {
return $(this).attr('id');
})).indexOf(subpage.substring(1));
if (valid !== -1 && subpage) {
// The click() event wont have been registered yet, so we need to delay us clicking it
setTimeout(function() { $(subpage).click(); },250);
if (valid !== -1) {
// @todo this condition can probably be removed
console.log('teleporting...:'+subpage.substring(1));
// The click() event wont have been registered yet, so we need to delay us clicking it
setTimeout(function() { $(subpage).click(); },250);
} else if (valid === -1) {
// Clear the hash
history.replaceState(null,null,' ');
getNode(subpage.substring(1));
}
}
});
</script>

View File

@ -32,8 +32,8 @@ Route::controller(HomeController::class)->group(function() {
Route::middleware(AllowAnonymous::class)->group(function() {
Route::get('/','home');
Route::get('info','info');
Route::post('dn','dn_frame');
Route::get('debug','debug');
Route::post('frame','frame');
Route::get('import','import_frame');
Route::get('schema','schema_frame');
@ -41,10 +41,12 @@ Route::controller(HomeController::class)->group(function() {
Route::get('image','user_image');
});
Route::match(['get','post'],'entry/add','entry_add');
Route::post('entry/create','entry_create');
Route::get('entry/export/{id}','entry_export');
Route::post('entry/password/check/','entry_password_check');
Route::post('entry/attr/add/{id}','entry_attr_add');
Route::post('entry/objectclass/add/{id}','entry_objectclass_add');
Route::post('entry/objectclass/add','entry_objectclass_add');
Route::post('entry/update/commit','entry_update');
Route::post('entry/update/pending','entry_pending_update');