Ported the schema browser

This commit is contained in:
Deon George 2023-02-14 21:38:42 +11:00
parent 815cd49868
commit 8ec1d2b1fe
31 changed files with 2498 additions and 3093 deletions

View File

@ -0,0 +1,583 @@
<?php
namespace App\Classes\LDAP\Schema;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Represents an LDAP AttributeType
*
* @package phpLDAPadmin
* @subpackage Schema
*/
class AttributeType extends Base {
// The attribute from which this attribute inherits (if any)
private ?string $sup_attribute = NULL;
// Array of AttributeTypes which inherit from this one
private Collection $children;
// The equality rule used
private ?string $equality = NULL;
// The ordering of the attributeType
private ?string $ordering = NULL;
// Supports substring matching?
private ?string $sub_str_rule = NULL;
// The full syntax string, ie 1.2.3.4{16}
private ?string $syntax = NULL;
private ?string $syntax_oid = NULL;
// boolean: is single valued only?
private bool $is_single_value = FALSE;
// boolean: is collective?
private bool $is_collective = FALSE;
// boolean: can use modify?
private bool $is_no_user_modification = FALSE;
// The usage string set by the LDAP schema
private ?string $usage = NULL;
// An array of alias attribute names, strings
private Collection $aliases;
// The max number of characters this attribute can be
private ?int $max_length = NULL;
// A string description of the syntax type (taken from the LDAPSyntaxes)
/**
* @deprecated - reference syntaxes directly if possible
* @var string
*/
private ?string $type = NULL;
// An array of objectClasses which use this attributeType (must be set by caller)
private Collection $used_in_object_classes;
// A list of object class names that require this attribute type.
private Collection $required_by_object_classes;
// This attribute has been forced a MAY attribute by the configuration.
private bool $forced_as_may = FALSE;
/**
* Creates a new AttributeType object from a raw LDAP AttributeType string.
*
* eg: ( 2.5.4.0 NAME 'objectClass' DESC 'RFC4512: object classes of the entity' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )
*/
public function __construct(string $line) {
Log::debug(sprintf('Parsing AttributeType [%s]',$line));
parent::__construct($line);
$strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE);
// Init
$this->children = collect();
$this->aliases = collect();
$this->used_in_object_classes = collect();
$this->required_by_object_classes = collect();
for ($i=0; $i < count($strings); $i++) {
switch ($strings[$i]) {
case '(':
case ')':
break;
case 'NAME':
// @note Some schema's return a (' instead of a ( '
if ($strings[$i+1] != '(' && ! preg_match('/^\(/',$strings[$i+1])) {
do {
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
} while (! preg_match("/\'$/s",$strings[$i]));
// This attribute has no aliases
//$this->aliases = collect();
} else {
$i++;
do {
// In case we came here becaues of a ('
if (preg_match('/^\(/',$strings[$i]))
$strings[$i] = preg_replace('/^\(/','',$strings[$i]);
else
$i++;
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
} while (! preg_match("/\'$/s",$strings[$i]));
// Add alias names for this attribute
while ($strings[++$i] != ')') {
$alias = $strings[$i];
$alias = preg_replace("/^\'(.*)\'$/",'$1',$alias);
$this->addAlias($alias);
}
}
$this->name = preg_replace("/^\'(.*)\'$/",'$1',$this->name);
Log::debug(sprintf('- Case NAME returned (%s)',$this->name),['aliases'=>$this->aliases]);
break;
case 'DESC':
do {
$this->description .= (strlen($this->description) ? ' ' : '').$strings[++$i];
} while (! preg_match("/\'$/s",$strings[$i]));
$this->description = preg_replace("/^\'(.*)\'$/",'$1',$this->description);
Log::debug(sprintf('- Case DESC returned (%s)',$this->description));
break;
case 'OBSOLETE':
$this->is_obsolete = TRUE;
Log::debug(sprintf('- Case OBSOLETE returned (%s)',$this->is_obsolete));
break;
case 'SUP':
$i++;
$this->sup_attribute = preg_replace("/^\'(.*)\'$/",'$1',$strings[$i]);
Log::debug(sprintf('- Case SUP returned (%s)',$this->sup_attribute));
break;
case 'EQUALITY':
$this->equality = $strings[++$i];
Log::debug(sprintf('- Case EQUALITY returned (%s)',$this->equality));
break;
case 'ORDERING':
$this->ordering = $strings[++$i];
Log::debug(sprintf('- Case ORDERING returned (%s)',$this->ordering));
break;
case 'SUBSTR':
$this->sub_str_rule = $strings[++$i];
Log::debug(sprintf('- Case SUBSTR returned (%s)',$this->sub_str_rule));
break;
case 'SYNTAX':
$this->syntax = $strings[++$i];
$this->syntax_oid = preg_replace('/{\d+}$/','',$this->syntax);
Log::debug(sprintf('/ Evaluating SYNTAX returned (%s) [%s]',$this->syntax,$this->syntax_oid));
// Does this SYNTAX string specify a max length (ie, 1.2.3.4{16})
$m = [];
if (preg_match('/{(\d+)}$/',$this->syntax,$m))
$this->max_length = $m[1];
else
$this->max_length = NULL;
if ($i < count($strings) - 1 && $strings[$i+1] == '{')
do {
$this->name .= ' '.$strings[++$i];
} while ($strings[$i] != '}');
$this->syntax = preg_replace("/^\'(.*)\'$/",'$1',$this->syntax);
$this->syntax_oid = preg_replace("/^\'(.*)\'$/",'$1',$this->syntax_oid);
Log::debug(sprintf('- Case SYNTAX returned (%s) [%s] {%d}',$this->syntax,$this->syntax_oid,$this->max_length));
break;
case 'SINGLE-VALUE':
$this->is_single_value = TRUE;
Log::debug(sprintf('- Case SINGLE-VALUE returned (%s)',$this->is_single_value));
break;
case 'COLLECTIVE':
$this->is_collective = TRUE;
Log::debug(sprintf('- Case COLLECTIVE returned (%s)',$this->is_collective));
break;
case 'NO-USER-MODIFICATION':
$this->is_no_user_modification = TRUE;
Log::debug(sprintf('- Case NO-USER-MODIFICATION returned (%s)',$this->is_no_user_modification));
break;
case 'USAGE':
$this->usage = $strings[++$i];
Log::debug(sprintf('- Case USAGE returned (%s)',$this->usage));
break;
// @note currently not captured
case 'X-ORDERED':
Log::error(sprintf('- Case X-ORDERED returned (%s)',$strings[++$i]));
break;
// @note currently not captured
case 'X-ORIGIN':
$value = '';
do {
$value .= (strlen($value) ? ' ' : '').$strings[++$i];
} while (! preg_match("/\'$/s",$strings[$i]));
Log::error(sprintf('- Case X-ORIGIN returned (%s)',$value));
break;
default:
if (preg_match('/[\d\.]+/i',$strings[$i]) && ($i === 1)) {
$this->oid = $strings[$i];
Log::debug(sprintf('- Case default returned (%s)',$this->oid));
} elseif ($strings[$i])
Log::alert(sprintf('! Case default discovered a value NOT parsed (%s)',$strings[$i]),['line'=>$line]);
}
}
}
public function __clone()
{
// When we clone, we need to break the reference to
$this->aliases = clone $this->aliases;
}
public function __get(string $key): mixed
{
switch ($key) {
case 'aliases': return $this->aliases;
case 'children': return $this->children;
case 'forced_as_may': return $this->forced_as_may;
case 'is_collective': return $this->is_collective;
case 'is_no_user_modification': return $this->is_no_user_modification;
case 'is_single_value': return $this->is_single_value;
case 'equality': return $this->equality;
case 'max_length': return $this->max_length;
case 'ordering': return $this->ordering;
case 'sub_str_rule': return $this->sub_str_rule;
case 'sup_attribute': return $this->sup_attribute;
case 'syntax': return $this->syntax;
case 'syntax_oid': return $this->syntax_oid;
case 'type': return $this->type;
case 'usage': return $this->usage;
case 'used_in_object_classes': return $this->used_in_object_classes;
default: return parent::__get($key);
}
}
/**
* Adds an attribute name to the alias array.
*
* @param string $alias The name of a new attribute to add to this attribute's list of aliases.
*/
public function addAlias(string $alias): void
{
$this->aliases->push($alias);
}
/**
* Children of this attribute type that inherit from this one
*
* @param string $child
* @return void
*/
public function addChild(string $child): void
{
$this->children->push($child);
}
/**
* Adds an objectClass name to this attribute's list of "required by" objectClasses,
* that is the list of objectClasses which must have this attribute.
*
* @param string $name The name of the objectClass to add.
*/
public function addRequiredByObjectClass(string $name): void
{
if ($this->required_by_object_classes->search($name) === FALSE)
$this->required_by_object_classes->push($name);
}
/**
* Adds an objectClass name to this attribute's list of "used in" objectClasses,
* that is the list of objectClasses which provide this attribute.
*
* @param string $name The name of the objectClass to add.
*/
public function addUsedInObjectClass(string $name): void
{
if ($this->used_in_object_classes->search($name) === FALSE)
$this->used_in_object_classes->push($name);
}
/**
* Gets the names of attributes that are an alias for this attribute (if any).
*
* @return Collection An array of names of attributes which alias this attribute or
* an empty array if no attribute aliases this object.
* @deprecated use class->aliases
*/
public function getAliases(): Collection
{
return $this->aliases;
}
/**
* Gets this attribute's equality string
*
* @return string
* @deprecated use $this->equality
*/
public function getEquality()
{
return $this->equality;
}
/**
* Gets whether this attribute is collective.
*
* @return boolean Returns TRUE if this attribute is collective and FALSE otherwise.
* @deprecated use $this->is_collective
*/
public function getIsCollective(): bool
{
return $this->is_collective;
}
/**
* Gets whether this attribute is not modifiable by users.
*
* @return boolean Returns TRUE if this attribute is not modifiable by users.
* @deprecated use $this->is_no_user_modification
*/
public function getIsNoUserModification(): bool
{
return $this->is_no_user_modification;
}
/**
* Gets whether this attribute is single-valued. If this attribute only supports single values, TRUE
* is returned. If this attribute supports multiple values, FALSE is returned.
*
* @return boolean Returns TRUE if this attribute is single-valued or FALSE otherwise.
* @deprecated use class->is_single_value
*/
public function getIsSingleValue(): bool
{
return $this->is_single_value;
}
/**
* Gets this attribute's the maximum length. If no maximum is defined by the LDAP server, NULL is returned.
*
* @return int The maximum length (in characters) of this attribute or NULL if no maximum is specified.
* @deprecated use $this->max_length;
*/
public function getMaxLength()
{
return $this->max_length;
}
/**
* Gets this attribute's ordering specification.
*
* @return string
* @deprecated use $this->ordering
*/
public function getOrdering(): string
{
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
*
* @return string
* @deprecated use $this->sub_str_rule;
*/
public function getSubstr() {
return $this->sub_str_rule;
}
/**
* Gets this attribute's parent attribute (if any). If this attribute does not
* inherit from another attribute, NULL is returned.
*
* @return string
* @deprecated use $class->sup_attribute directly
*/
public function getSupAttribute() {
return $this->sup_attribute;
}
/**
* Gets this attribute's syntax OID. Differs from getSyntaxString() in that this
* function only returns the actual OID with any length specification removed.
* Ie, if the syntax string is "1.2.3.4{16}", this function only retruns
* "1.2.3.4".
*
* @return string The syntax OID string.
* @deprecated use $this->syntax_oid;
*/
public function getSyntaxOID()
{
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
*
* @return string
* @deprecated use $this->usage
*/
public function getUsage()
{
return $this->usage;
}
/**
* Gets the list of "used in" objectClasses, that is the list of objectClasses
* which provide this attribute.
*
* @return Collection An array of names of objectclasses (strings) which provide this attribute
* @deprecated use $this->used_in_object_classes
*/
public function getUsedInObjectClasses(): Collection
{
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
*/
public function isForceMay(): bool
{
return $this->forced_as_may;
}
/**
* Removes an attribute name from this attribute's alias array.
*
* @param string $alias The name of the attribute to remove.
*/
public function removeAlias(string $alias): void
{
if (($x=$this->aliases->search($alias)) !== FALSE)
$this->aliases->forget($x);
}
/**
* Sets this attribute's list of aliases.
*
* @param Collection $aliases The array of alias names (strings)
* @deprecated use $this->aliases =
*/
public function setAliases(Collection $aliases): void
{
$this->aliases = $aliases;
}
/**
* 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;
}
/**
* Sets whether this attribute is single-valued.
*
* @param boolean $is
*/
public function setIsSingleValue(bool $is): void
{
$this->is_single_value = $is;
}
/**
* Sets this attribute's SUP attribute (ie, the attribute from which this attribute inherits).
*
* @param string $attr The name of the new parent (SUP) attribute
*/
public function setSupAttribute(string $attr): void
{
$this->sup_attribute = trim($attr);
}
/**
* Sets this attribute's type.
*
* @param string $type The new type.
*/
public function setType($type) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',9,1,__FILE__,__LINE__,__METHOD__,$fargs);
$this->type = $type;
}
}

View File

@ -0,0 +1,110 @@
<?php
namespace App\Classes\LDAP\Schema;
use App\Exceptions\InvalidUsage;
/**
* Generic parent class for all schema items.
*
* A schema item is an ObjectClass, an AttributeBype, a MatchingRule, or a Syntax.
* All schema items have at least two things in common: An OID and a Description.
*/
abstract class Base {
// Record the LDAP String
private string $line;
// The schema item's name.
protected ?string $name = NULL;
// The OID of this schema item.
protected string $oid;
# The description of this schema item.
protected ?string $description = NULL;
// Boolean value indicating whether this objectClass is obsolete
private bool $is_obsolete = FALSE;
public function __construct(string $line)
{
$this->line = $line;
}
public function __get(string $key): mixed
{
switch ($key) {
case 'description': return $this->description;
case 'is_obsolete': return $this->is_obsolete;
case 'line': return $this->line;
case 'name': return $this->name;
case 'name_lc': return strtolower($this->name);
case 'oid': return $this->oid;
default:
throw new InvalidUsage('Unknown key: '.$key);
}
}
/**
* @return string
* @deprecated replace with $class->description
*/
public function getDescription(): string
{
return $this->description;
}
/**
* Gets whether this item is flagged as obsolete by the LDAP server.
*
* @deprecated replace with $this->is_obsolete
*/
public function getIsObsolete(): bool
{
return $this->is_obsolete;
}
/**
* Return the objects name.
*
* @param boolean $lower Return the name in lower case (default)
* @return string The name
* @deprecated use object->name
*/
public function getName(bool $lower=TRUE): string
{
return $lower ? strtolower($this->name) : $this->name;
}
/**
* Return the objects name.
*
* @return string The name
* @deprecated use object->oid
*/
public function getOID(): string
{
return $this->oid;
}
public function setDescription(string $desc): void
{
$this->description = $desc;
}
/**
* Sets this attribute's name.
*
* @param string $name The new name to give this attribute.
*/
public function setName($name): void
{
$this->name = $name;
}
public function setOID(string $oid): void
{
$this->oid = $oid;
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace App\Classes\LDAP\Schema;
use Illuminate\Support\Facades\Log;
/**
* Represents an LDAP Syntax
*
* @package phpLDAPadmin
* @subpackage Schema
*/
class LDAPSyntax extends Base {
// Is human readable?
private ?bool $is_not_human_readable = NULL;
// Binary transfer required?
private ?bool $binary_transfer_required = NULL;
/**
* Creates a new Syntax object from a raw LDAP syntax string.
*/
public function __construct(string $line) {
Log::debug(sprintf('Parsing LDAPSyntax [%s]',$line));
parent::__construct($line);
$strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE);
for ($i=0; $i<count($strings); $i++) {
switch($strings[$i]) {
case '(':
case ')':
break;
case 'DESC':
do {
$this->description .= (strlen($this->description) ? ' ' : '').$strings[++$i];
} while (! preg_match("/\'$/s",$strings[$i]));
$this->description = preg_replace("/^\'(.*)\'$/",'$1',$this->description);
Log::debug(sprintf('- Case DESC returned (%s)',$this->description));
break;
case 'X-BINARY-TRANSFER-REQUIRED':
$this->binary_transfer_required = (str_replace("'",'',$strings[++$i]) === 'TRUE');
Log::debug(sprintf('- Case X-BINARY-TRANSFER-REQUIRED returned (%s)',$this->binary_transfer_required));
break;
case 'X-NOT-HUMAN-READABLE':
$this->is_not_human_readable = (str_replace("'",'',$strings[++$i]) === 'TRUE');
Log::debug(sprintf('- Case X-NOT-HUMAN-READABLE returned (%s)',$this->is_not_human_readable));
break;
default:
if (preg_match('/[\d\.]+/i',$strings[$i]) && ($i === 1)) {
$this->oid = $strings[$i];
Log::debug(sprintf('- Case default returned (%s)',$this->oid));
} elseif ($strings[$i])
Log::alert(sprintf('! Case default discovered a value NOT parsed (%s)',$strings[$i]),['line'=>$line]);
}
}
}
public function __get(string $key): mixed
{
switch ($key) {
case 'binary_transfer_required': return $this->binary_transfer_required;
case 'is_not_human_readable': return $this->is_not_human_readable;
default: return parent::__get($key);
}
}
}

View File

@ -0,0 +1,142 @@
<?php
namespace App\Classes\LDAP\Schema;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Represents an LDAP MatchingRule
*
* @package phpLDAPadmin
* @subpackage Schema
*/
class MatchingRule extends Base {
// This rule's syntax OID
private ?string $syntax = NULL;
// An array of attribute names who use this MatchingRule
private Collection $used_by_attrs;
/**
* Creates a new MatchingRule object from a raw LDAP MatchingRule string.
*/
function __construct(string $line) {
Log::debug(sprintf('Parsing MatchingRule [%s]',$line));
parent::__construct($line);
$strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE);
// Init
$this->used_by_attrs = collect();
for ($i=0; $i<count($strings); $i++) {
switch ($strings[$i]) {
case '(':
case ')':
break;
case 'NAME':
if ($strings[$i+1] != '(') {
do {
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
} while (! preg_match("/\'$/s",$strings[$i]));
} else {
$i++;
do {
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
} while (! preg_match("/\'$/s",$strings[$i]));
do {
$i++;
} while (! preg_match('/\)+\)?/',$strings[$i]));
}
$this->name = preg_replace("/^\'/",'',$this->name);
$this->name = preg_replace("/\'$/",'',$this->name);
Log::debug(sprintf(sprintf('- Case NAME returned (%s)',$this->name)));
break;
case 'DESC':
do {
$this->description .= (strlen($this->description) ? ' ' : '').$strings[++$i];
} while (! preg_match("/\'$/s",$strings[$i]));
$this->description = preg_replace("/^\'(.*)\'$/",'$1',$this->description);
Log::debug(sprintf('- Case DESC returned (%s)',$this->description));
break;
case 'OBSOLETE':
$this->is_obsolete = TRUE;
Log::debug(sprintf('- Case OBSOLETE returned (%s)',$this->is_obsolete));
break;
case 'SYNTAX':
$this->syntax = $strings[++$i];
Log::debug(sprintf('- Case SYNTAX returned (%s)',$this->syntax));
break;
default:
if (preg_match('/[\d\.]+/i',$strings[$i]) && ($i === 1)) {
$this->oid = $strings[$i];
Log::debug(sprintf('- Case default returned (%s)',$this->oid));
} elseif ($strings[$i])
Log::alert(sprintf('! Case default discovered a value NOT parsed (%s)',$strings[$i]),['line'=>$line]);
}
}
}
public function __get(string $key): mixed
{
switch ($key) {
case 'syntax': return $this->syntax;
case 'used_by_attrs': return $this->used_by_attrs;
default: return parent::__get($key);
}
}
/**
* Adds an attribute name to the list of attributes who use this MatchingRule
*/
public function addUsedByAttr(string $name): void
{
$name = trim($name);
if ($this->used_by_attrs->search($name) === FALSE)
$this->used_by_attrs->push($name);
}
/**
* Gets an array of attribute names (strings) which use this MatchingRule
*
* @return array The array of attribute names (strings).
* @deprecated use $this->used_by_attrs
*/
public function getUsedByAttrs()
{
return $this->used_by_attrs;
}
/**
* Sets the list of used_by_attrs to the array specified by $attrs;
*
* @param Collection $attrs The array of attribute names (strings) which use this MatchingRule
*/
public function setUsedByAttrs(Collection $attrs): void
{
$this->used_by_attrs = $attrs;
}
}

View File

@ -0,0 +1,99 @@
<?php
namespace App\Classes\LDAP\Schema;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Represents an LDAP schema matchingRuleUse entry
*
* @package phpLDAPadmin
* @subpackage Schema
*/
class MatchingRuleUse extends Base {
// An array of attribute names who use this MatchingRule
private Collection $used_by_attrs;
function __construct(string $line) {
Log::debug(sprintf('Parsing MatchingRuleUse [%s]',$line));
parent::__construct($line);
$strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE);
// Init
$this->used_by_attrs = collect();
for ($i=0; $i<count($strings); $i++) {
switch ($strings[$i]) {
case '(':
case ')':
break;
case 'NAME':
if ($strings[$i+1] != '(') {
do {
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
} while (! preg_match("/\'$/s",$strings[$i]));
} else {
$i++;
do {
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
} while (! preg_match("/\'$/s",$strings[$i]));
do {
$i++;
} while (! preg_match('/\)+\)?/',$strings[$i]));
}
$this->name = preg_replace("/^\'(.*)\'$/",'$1',$this->name);
Log::debug(sprintf(sprintf('- Case NAME returned (%s)',$this->name)));
break;
case 'APPLIES':
if ($strings[$i+1] != '(') {
// Has a single attribute name
$this->used_by_attrs = collect($strings[++$i]);
} else {
// Has multiple attribute names
while ($strings[++$i] != ')') {
$new_attr = $strings[++$i];
$new_attr = preg_replace("/^\'(.*)\'$/",'$1',$new_attr);
$this->used_by_attrs->push($new_attr);
}
}
Log::debug(sprintf('- Case APPLIES returned (%s)',$this->used_by_attrs->join(',')));
break;
default:
if (preg_match('/[\d\.]+/i',$strings[$i]) && ($i === 1)) {
$this->oid = $strings[$i];
Log::debug(sprintf('- Case default returned (%s)',$this->oid));
} elseif ($strings[$i])
Log::alert(sprintf('! Case default discovered a value NOT parsed (%s)',$strings[$i]),['line'=>$line]);
}
}
}
/**
* Gets an array of attribute names (strings) which use this MatchingRuleUse object.
*
* @return array The array of attribute names (strings).
* @deprecated use $this->used_by_attrs
*/
public function getUsedByAttrs()
{
return $this->used_by_attrs;
}
}

View File

@ -0,0 +1,529 @@
<?php
namespace App\Classes\LDAP\Schema;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use LdapRecord\Connection;
use App\Classes\LDAP\Server;
use App\Exceptions\InvalidUsage;
use App\Ldap\Entry;
/**
* Represents an LDAP Schema objectClass
*
* @package phpLDAPadmin
* @subpackage Schema
*/
class ObjectClass extends Base {
// The server ID that this objectclass belongs to.
private Server $server;
private Connection $connection;
// Array of objectClass names from which this objectClass inherits
private Collection $sup_classes;
// One of STRUCTURAL, ABSTRACT, or AUXILIARY
private int $type;
// Arrays of attribute names that this objectClass requires
private Collection $must_attrs;
// Arrays of attribute names that this objectClass allows, but does not require
private Collection $may_attrs;
// Arrays of attribute names that this objectClass has been forced to MAY attrs, due to configuration
private Collection $may_force;
// Array of objectClasses which inherit from this one
private Collection $child_objectclasses;
private bool $is_obsolete;
/* ObjectClass Types */
private const OC_STRUCTURAL = 0x01;
private const OC_ABSTRACT = 0x02;
private const OC_AUXILIARY = 0x03;
/**
* Creates a new ObjectClass object given a raw LDAP objectClass string.
*
* eg: ( 2.5.6.0 NAME 'top' DESC 'top of the superclass chain' ABSTRACT MUST objectClass )
*/
public function __construct(string $line,Entry $entry,Server $server)
{
parent::__construct($line);
Log::debug(sprintf('Parsing ObjectClass [%s]',$line));
$strings = preg_split('/[\s,]+/',$line,-1,PREG_SPLIT_DELIM_CAPTURE);
// Init
$this->connection = $entry->getConnection();
$this->server = $server;
$this->may_attrs = collect();
$this->may_force = collect();
$this->must_attrs = collect();
$this->sup_classes = collect();
$this->child_objectclasses = collect();
for ($i=0; $i < count($strings); $i++) {
switch ($strings[$i]) {
case '(':
case ')':
break;
case 'NAME':
if ($strings[$i+1] != '(') {
do {
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
} while (! preg_match('/\'$/s',$strings[$i]));
} else {
$i++;
do {
$this->name .= (strlen($this->name) ? ' ' : '').$strings[++$i];
} while (! preg_match('/\'$/s',$strings[$i]));
do {
$i++;
} while (! preg_match('/\)+\)?/',$strings[$i]));
}
$this->name = preg_replace("/^\'(.*)\'$/",'$1',$this->name);
Log::debug(sprintf(sprintf('- Case NAME returned (%s)',$this->name)));
break;
case 'DESC':
do {
$this->description .= (strlen($this->description) ? ' ' : '').$strings[++$i];
} while (! preg_match('/\'$/s',$strings[$i]));
$this->description = preg_replace("/^\'(.*)\'$/",'$1',$this->description);
Log::debug(sprintf('- Case DESC returned (%s)',$this->description));
break;
case 'OBSOLETE':
$this->is_obsolete = TRUE;
Log::debug(sprintf('- Case OBSOLETE returned (%s)',$this->is_obsolete));
break;
case 'SUP':
if ($strings[$i+1] != '(') {
$this->sup_classes->push(preg_replace("/'/",'',$strings[++$i]));
} else {
$i++;
do {
$i++;
if ($strings[$i] != '$')
$this->sup_classes->push(preg_replace("/'/",'',$strings[$i]));
} while (! preg_match('/\)+\)?/',$strings[$i+1]));
}
Log::debug(sprintf('- Case SUP returned (%s)',$this->sup_classes->join(',')));
break;
case 'ABSTRACT':
$this->type = self::OC_ABSTRACT;
Log::debug(sprintf('- Case ABSTRACT returned (%s)',$this->type));
break;
case 'STRUCTURAL':
$this->type = self::OC_STRUCTURAL;
Log::debug(sprintf('- Case STRUCTURAL returned (%s)',$this->type));
break;
case 'AUXILIARY':
$this->type = self::OC_AUXILIARY;
Log::debug(sprintf('- Case AUXILIARY returned (%s)',$this->type));
break;
case 'MUST':
$attrs = collect();
$i = $this->parseList(++$i,$strings,$attrs);
Log::debug(sprintf('= parseList returned %d (%s)',$i,$attrs->join(',')));
foreach ($attrs as $string) {
$attr = new ObjectClassAttribute($string,$this->name);
if ($server->isForceMay($attr->getName())) {
$this->may_force->push($attr);
$this->may_attrs->push($attr);
} else
$this->must_attrs->push($attr);
}
Log::debug(sprintf('- Case MUST returned (%s) (%s)',$this->must_attrs->join(','),$this->may_force->join(',')));
break;
case 'MAY':
$attrs = collect();
$i = $this->parseList(++$i,$strings,$attrs);
Log::debug(sprintf('parseList returned %d (%s)',$i,$attrs->join(',')));
foreach ($attrs as $string) {
$attr = new ObjectClassAttribute($string,$this->name);
$this->may_attrs->push($attr);
}
Log::debug(sprintf('- Case MAY returned (%s)',$this->may_attrs->join(',')));
break;
default:
if (preg_match('/[\d\.]+/i',$strings[$i]) && ($i === 1)) {
$this->oid = $strings[$i];
Log::debug(sprintf('- Case default returned (%s)',$this->oid));
} elseif ($strings[$i])
Log::alert(sprintf('! Case default discovered a value NOT parsed (%s)',$strings[$i]),['line'=>$line]);
}
}
}
public function __get(string $key): mixed
{
switch ($key) {
case 'sup':
return $this->sup_classes;
case 'type_name':
switch ($this->type) {
case self::OC_STRUCTURAL: return 'Structural';
case self::OC_ABSTRACT: return 'Abstract';
case self::OC_AUXILIARY: return 'Auxiliary';
default:
throw new InvalidUsage('Unknown ObjectClass Type: '.$this->type);
}
default: return parent::__get($key);
}
}
/**
* Adds an objectClass to the list of objectClasses that inherit
* from this objectClass.
*
* @param String $name The name of the objectClass to add
*/
public function addChildObjectClass(string $name): void
{
if ($this->child_objectclasses->search($name) === FALSE) {
$this->child_objectclasses->push($name);
}
}
/**
* Behaves identically to addMustAttrs, but it operates on the MAY
* attributes of this objectClass.
*
* @param array $attr An array of attribute names (strings) to add.
*/
private function addMayAttrs(array $attr): void
{
if (! is_array($attr) || ! count($attr))
return;
$this->may_attrs = $this->may_attrs->merge($attr)->unique();
}
/**
* Adds the specified array of attributes to this objectClass' list of
* MUST attributes. The resulting array of must attributes will contain
* unique members.
*
* @param array $attr An array of attribute names (strings) to add.
*/
private function addMustAttrs(array $attr): void
{
if (! is_array($attr) || ! count($attr))
return;
$this->must_attrs = $this->must_attrs->merge($attr)->unique();
}
/**
* @return Collection
* @deprecated use $this->may_force
*/
public function getForceMayAttrs(): Collection
{
return $this->may_force;
}
/**
* Gets an array of AttributeType objects that entries of this ObjectClass may define.
* This differs from getMayAttrNames in that it returns an array of AttributeType objects
*
* @param bool $parents Also get the may attrs of our parents.
* @return Collection The array of allowed AttributeType objects.
*
* @throws InvalidUsage
* @see getMustAttrNames
* @see getMustAttrs
* @see getMayAttrNames
* @see AttributeType
*/
public function getMayAttrs(bool $parents=FALSE): Collection
{
// If we dont need our parents, then we'll just return ours.
if (! $parents)
return $this->may_attrs->sortBy(function($item) { return strtolower($item->name.$item->source); });
$attrs = $this->may_attrs;
foreach ($this->getParents() as $object_class) {
$sc = $this->server->schema('objectclasses',$object_class);
$attrs = $attrs->merge($sc->getMayAttrs($parents));
}
// Remove any duplicates
$attrs = $attrs->unique(function($item) { return $item->name; });
// Return a sorted list
return $attrs->sortBy(function($item) { return strtolower($item->name.$item->source); });
}
/**
* Gets an array of attribute names (strings) that entries of this ObjectClass must define.
* This differs from getMayAttrs in that it returns an array of strings rather than
* array of AttributeType objects
*
* @param bool $parents An array of ObjectClass objects to use when traversing
* the inheritance tree. This presents some what of a bootstrapping problem
* as we must fetch all objectClasses to determine through inheritance which
* attributes this objectClass provides.
* @return Collection The array of allowed attribute names (strings).
*
* @throws InvalidUsage
* @see getMustAttrs
* @see getMayAttrs
* @see getMustAttrNames
*/
public function getMayAttrNames(bool $parents=FALSE): Collection
{
return $this->getMayAttrs($parents)->ppluck('name');
}
/**
* Gets an array of AttributeType objects that entries of this ObjectClass must define.
* This differs from getMustAttrNames in that it returns an array of AttributeType objects
*
* @param bool $parents Also get the must attrs of our parents.
* @return Collection The array of required AttributeType objects.
*
* @throws InvalidUsage
* @see getMustAttrNames
* @see getMayAttrs
* @see getMayAttrNames
*/
public function getMustAttrs(bool $parents=FALSE): Collection
{
// If we dont need our parents, then we'll just return ours.
if (! $parents)
return $this->must_attrs->sortBy(function($item) { return strtolower($item->name.$item->source); });
$attrs = $this->must_attrs;
foreach ($this->getParents() as $object_class) {
$sc = $this->server->schema('objectclasses',$object_class);
$attrs = $attrs->merge($sc->getMustAttrs($parents));
}
// Remove any duplicates
$attrs = $attrs->unique(function($item) { return $item->name; });
// Return a sorted list
return $attrs->sortBy(function($item) { return strtolower($item->name.$item->source); });
}
/**
* Gets an array of attribute names (strings) that entries of this ObjectClass must define.
* This differs from getMustAttrs in that it returns an array of strings rather than
* array of AttributeType objects
*
* @param bool $parents An array of ObjectClass objects to use when traversing
* the inheritance tree. This presents some what of a bootstrapping problem
* as we must fetch all objectClasses to determine through inheritance which
* attributes this objectClass provides.
* @return Collection The array of allowed attribute names (strings).
*
* @throws InvalidUsage
* @see getMustAttrs
* @see getMayAttrs
* @see getMayAttrNames
*/
public function getMustAttrNames(bool $parents=FALSE): Collection
{
return $this->getMustAttrs($parents)->ppluck('name');
}
/**
* This will return all our parent ObjectClass Objects
*/
public function getParents(): Collection
{
// If the only class is 'top', then we have no more parents
if (($this->sup_classes->count() === 1) && (strtolower($this->sup_classes->first()) === 'top'))
return collect();
$result = collect();
foreach ($this->sup_classes as $object_class) {
$result->push($object_class);
$oc = $this->server->schema('objectclasses',$object_class);
if ($oc)
$result = $result->merge($oc->getParents());
}
return $result;
}
/**
* Determine if an array is listed in the may_force attrs
*/
public function isForceMay(string $attr): bool
{
return $this->may_force->ppluck('name')->contains($attr);
}
/**
* Return if this objectClass is related to $oclass
*
* @param array $oclass ObjectClasses that this attribute may be related to
* @return bool
* @throws InvalidUsage
*/
public function isRelated(array $oclass): bool
{
// If I am in the array, we'll just return false
if (in_array_ignore_case($this->name,$oclass))
return FALSE;
foreach ($oclass as $object_class) {
$oc = $this->server->schema('objectclasses',$object_class);
if ($oc->isStructural() && in_array_ignore_case($this->name,$oc->getParents()))
return TRUE;
}
return FALSE;
}
public function isStructural(): bool
{
return $this->type === self::OC_STRUCTURAL;
}
/**
* Parse an LDAP schema list
*
* A list starts with a ( followed by a list of attributes separated by $ terminated by )
* The first token can therefore be a ( or a (NAME or a (NAME)
* The last token can therefore be a ) or NAME)
* The last token may be terminated by more than one bracket
*/
private function parseList(int $i,array $strings,Collection &$attrs): int
{
$string = $strings[$i];
if (! preg_match('/^\(/',$string)) {
// A bareword only - can be terminated by a ) if the last item
if (preg_match('/\)+$/',$string))
$string = preg_replace('/\)+$/','',$string);
$attrs->push($string);
} elseif (preg_match('/^\(.*\)$/',$string)) {
$string = preg_replace('/^\(/','',$string);
$string = preg_replace('/\)+$/','',$string);
$attrs->push($string);
} else {
// Handle the opening cases first
if ($string === '(') {
$i++;
} elseif (preg_match('/^\(./',$string)) {
$string = preg_replace('/^\(/','',$string);
$attrs->push($string);
$i++;
}
// Token is either a name, a $ or a ')'
// NAME can be terminated by one or more ')'
while (! preg_match('/\)+$/',$strings[$i])) {
$string = $strings[$i];
if ($string === '$') {
$i++;
continue;
}
if (preg_match('/\)$/',$string))
$string = preg_replace('/\)+$/','',$string);
else
$i++;
$attrs->push($string);
}
}
$attrs = $attrs->sort();
return $i;
}
/**
* Returns the array of objectClass names which inherit from this objectClass.
*
* @return Collection Names of objectClasses which inherit from this objectClass.
* @deprecated use $this->child_objectclasses
*/
public function getChildObjectClasses(): Collection
{
return $this->child_objectclasses;
}
/**
* Gets the objectClass names from which this objectClass inherits.
*
* @return array An array of objectClass names (strings)
* @deprecated use $this->sup_classes;
*/
public function getSupClasses() {
return $this->sup_classes;
}
/**
* Gets the type of this objectClass: STRUCTURAL, ABSTRACT, or AUXILIARY.
*
* @deprecated use $this->type_name
*/
public function getType()
{
return $this->type;
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Classes\LDAP\Schema;
/**
* A simple class for representing AttributeTypes used only by the ObjectClass class.
*
* Users should never instantiate this class. It represents an attribute internal to
* an ObjectClass. If PHP supported inner-classes and variable permissions, this would
* be interior to class ObjectClass and flagged private. The reason this class is used
* and not the "real" class AttributeType is because this class supports the notion of
* a "source" objectClass, meaning that it keeps track of which objectClass originally
* specified it. This class is therefore used by the class ObjectClass to determine
* inheritance.
*/
class ObjectClassAttribute extends Base {
// This Attribute's root.
private string $source;
/**
* Creates a new ObjectClassAttribute with specified name and source objectClass.
*
* @param string $name the name of the new attribute.
* @param string $source the name of the ObjectClass which specifies this attribute.
*/
public function __construct($name,$source)
{
$this->name = $name;
$this->source = $source;
}
public function __get(string $key): mixed
{
switch ($key) {
case 'source':
return $this->source;
default: return parent::__get($key);
}
}
}

View File

@ -5,21 +5,38 @@ namespace App\Classes\LDAP;
use Carbon\Carbon; use Carbon\Carbon;
use Exception; use Exception;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Collection as ArrayCollection;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Config;
use LdapRecord\Models\Model; use Illuminate\Support\Facades\Log;
use LdapRecord\Query\Collection; use LdapRecord\Query\Collection;
use LdapRecord\Query\Model\Builder;
use App\Classes\LDAP\Schema\{AttributeType,Base,LDAPSyntax,MatchingRule,MatchingRuleUse,ObjectClass};
use App\Exceptions\InvalidUsage;
use App\Ldap\Entry; use App\Ldap\Entry;
class Server class Server
{ {
// This servers schema objectclasses
private ArrayCollection $attributetypes;
private ArrayCollection $ldapsyntaxes;
private ArrayCollection $matchingrules;
private ArrayCollection $matchingruleuse;
private ArrayCollection $objectclasses;
// Valid items that can be fetched
public const schema_types = [
'objectclasses',
'attributetypes',
'ldapsyntaxes',
'matchingrules',
];
/** /**
* Query the server for a DN and return it's children and if those children have children. * Query the server for a DN and return its children and if those children have children.
* *
* @param string $dn * @param string $dn
* @return array|Collection|null * @return Collection|null
*/ */
public function children(string $dn): ?Collection public function children(string $dn): ?Collection
{ {
@ -37,7 +54,7 @@ class Server
* *
* @param string $dn * @param string $dn
* @param array $attrs * @param array $attrs
* @return array|Model|Collection|Builder|null * @return Entry|null
*/ */
public function fetch(string $dn,array $attrs=['*','+']): ?Entry public function fetch(string $dn,array $attrs=['*','+']): ?Entry
{ {
@ -103,4 +120,265 @@ class Server
($key == 'desc' ? 'No description available, can you help with one?' : ($key == 'title' ? $oid : NULL)) ($key == 'desc' ? 'No description available, can you help with one?' : ($key == 'title' ? $oid : NULL))
); );
} }
}
/**
* This function determines if the specified attribute is contained in the force_may list
* as configured in config.php.
*
* @return boolean True if the specified attribute is configured to be force as a may attribute
*/
public function isForceMay($attr_name): bool
{
return in_array($attr_name,config('pla.force_may',[]));
}
/**
* Return the server's schema
*
* @param string $item Schema Item to Fetch
* @param string|null $key
* @return ArrayCollection|Base
* @throws InvalidUsage
*/
public function schema(string $item,string $key=NULL): ArrayCollection|Base|NULL
{
// Ensure our item to fetch is lower case
$item = strtolower($item);
if ($key)
$key = strtolower($key);
// This error message is not localized as only developers should ever see it
if (! in_array($item,self::schema_types))
throw new InvalidUsage('Invalid request to fetch schema: '.$item);
// First pass if we have already retrieved the schema item
switch ($item) {
case 'attributetypes':
if (isset($this->attributetypes))
return is_null($key) ? $this->attributetypes : $this->attributetypes->get($key);
else
$this->attributetypes = collect();
break;
case 'ldapsyntaxes':
if (isset($this->ldapsyntaxes))
return is_null($key) ? $this->ldapsyntaxes : $this->ldapsyntaxes->get($key);
else
$this->ldapsyntaxes = collect();
break;
case 'matchingrules':
if (isset($this->matchingrules))
return is_null($key) ? $this->matchingrules : $this->matchingrules->get($key);
else
$this->matchingrules = collect();
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 is_null($key) ? $this->objectclasses : $this->objectclasses->get($key);
else
$this->objectclasses = collect();
break;
// Shouldnt get here
default:
throw new InvalidUsage('Invalid request to fetch schema: '.$item);
}
// Try to get the schema DN from the specified entry.
$schema_dn = Entry::schemaDN();
$schema = (new Server)->fetch($schema_dn);
switch ($item) {
case 'attributetypes':
Log::debug('Attribute Types');
// build the array of attribueTypes
//$syntaxes = $this->SchemaSyntaxes($dn);
foreach ($schema->{$item} as $line) {
if (is_null($line) || ! strlen($line))
continue;
$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
foreach ($this->attributetypes as $o) {
/* foreach of the attribute's aliases, create a new entry in the attrs array
* with its name set to the alias name, and all other data copied.*/
if ($o->aliases->count()) {
Log::debug(sprintf('\ Attribute [%s] has the following aliases [%s]',$o->name,$o->aliases->join(',')));
foreach ($o->aliases as $alias) {
$new_attr = clone $o;
$new_attr->setName($alias);
$new_attr->addAlias($o->name);
$new_attr->removeAlias($alias);
$this->attributetypes->put(strtolower($alias),$new_attr);
}
}
}
// Now go through and reference the parent/child relationships
foreach ($this->attributetypes as $o)
if ($o->sup_attribute) {
$parent = strtolower($o->sup_attribute);
if ($this->attributetypes->has($parent) !== FALSE)
$this->attributetypes[$parent]->addChild($o->name);
}
// go through any children and add details if the child doesnt have them (ie, cn inherits name)
// @todo This doesnt traverse children properly, so children of children may not get the settings they should
foreach ($this->attributetypes as $parent) {
foreach ($parent->children as $child) {
$child = strtolower($child);
/* only overwrite the child's SINGLE-VALUE property if the parent has it set, and the child doesnt
* (note: All LDAP attributes default to multi-value if not explicitly set SINGLE-VALUE) */
if (! is_null($parent->is_single_value) && is_null($this->attributetypes[$child]->is_single_value))
$this->attributetypes[$child]->setIsSingleValue($parent->is_single_value);
}
}
// Add the used in and required_by values.
foreach ($this->schema('objectclasses') as $object_class) {
$must_attrs = $object_class->getMustAttrNames();
$may_attrs = $object_class->getMayAttrNames();
$oclass_attrs = $must_attrs->merge($may_attrs)->unique();
// Add Used In.
foreach ($oclass_attrs as $attr_name)
if ($this->attributetypes->has(strtolower($attr_name)))
$this->attributetypes[strtolower($attr_name)]->addUsedInObjectClass($object_class->name);
// Add Required By.
foreach ($must_attrs as $attr_name)
if ($this->attributetypes->has(strtolower($attr_name)))
$this->attributetypes[strtolower($attr_name)]->addRequiredByObjectClass($object_class->name);
// Force May
foreach ($object_class->getForceMayAttrs() as $attr_name)
if ($this->attributetypes->has(strtolower($attr_name->name)))
$this->attributetypes[strtolower($attr_name->name)]->setForceMay();
}
return is_null($key) ? $this->attributetypes : $this->attributetypes->get($key);
case 'objectclasses':
Log::debug('Object Classes');
foreach ($schema->{$item} as $line) {
if (is_null($line) || ! strlen($line))
continue;
$o = new ObjectClass($line,$schema,$this);
$this->objectclasses->put($o->name_lc,$o);
}
// Now go through and reference the parent/child relationships
foreach ($this->objectclasses as $o)
foreach ($o->getSupClasses() as $parent) {
$parent = strtolower($parent);
if ($this->objectclasses->has($parent) !== FALSE)
$this->objectclasses[$parent]->addChildObjectClass($o->name);
}
return is_null($key) ? $this->objectclasses : $this->objectclasses->get($key);
case 'ldapsyntaxes':
Log::debug('LDAP Syntaxes');
foreach ($schema->{$item} as $line) {
if (is_null($line) || ! strlen($line))
continue;
$o = new LDAPSyntax($line);
$this->ldapsyntaxes->put(strtolower($o->oid),$o);
}
return is_null($key) ? $this->ldapsyntaxes : $this->ldapsyntaxes->get($key);
case 'matchingrules':
Log::debug('Matching Rules');
$this->matchingruleuse = collect();
foreach ($schema->{$item} as $line) {
if (is_null($line) || ! strlen($line))
continue;
$o = new MatchingRule($line);
$this->matchingrules->put($o->name_lc,$o);
}
/*
* For each MatchingRuleUse entry, add the attributes who use it to the
* MatchingRule in the $rules array.
*/
if ($schema->matchingruleuse) {
foreach ($schema->matchingruleuse as $line) {
if (is_null($line) || ! strlen($line))
continue;
$o = new MatchingRuleUse($line);
$this->matchingruleuse->put($o->name_lc,$o);
if ($this->matchingrules->has($o->name_lc) !== FALSE)
$this->matchingrules[$o->name_lc]->setUsedByAttrs($o->getUsedByAttrs());
}
} else {
/* No MatchingRuleUse entry in the subschema, so brute-forcing
* the reverse-map for the "$rule->getUsedByAttrs()" data.*/
foreach ($this->schema('attributetypes') as $attr) {
$rule_key = strtolower($attr->getEquality());
if ($this->matchingrules->has($rule_key) !== FALSE)
$this->matchingrules[$rule_key]->addUsedByAttr($attr->name);
}
}
return is_null($key) ? $this->matchingrules : $this->matchingrules->get($key);
}
return NULL;
}
public function schemaSyntaxName(string $oid): ?LDAPSyntax
{
return $this->schema('ldapsyntaxes',$oid);
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class InvalidUsage extends Exception
{
//
}

View File

@ -34,4 +34,31 @@ class APIController extends Controller
]; ];
}); });
} }
public function schema_view(Request $request)
{
$server = new Server;
switch($request->type) {
case 'objectclasses':
return view('frames.schema.objectclasses')
->with('objectclasses',$server->schema('objectclasses')->sortBy(function($item) { return strtolower($item->name); }));
case 'attributetypes':
return view('frames.schema.attributetypes')
->with('server',$server)
->with('attributetypes',$server->schema('attributetypes')->sortBy(function($item) { return strtolower($item->name); }));
case 'ldapsyntaxes':
return view('frames.schema.ldapsyntaxes')
->with('ldapsyntaxes',$server->schema('ldapsyntaxes')->sortBy(function($item) { return strtolower($item->description); }));
case 'matchingrules':
return view('frames.schema.matchingrules')
->with('matchingrules',$server->schema('matchingrules')->sortBy(function($item) { return strtolower($item->name); }));
default:
abort(404);
}
}
} }

View File

@ -8,7 +8,7 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use LdapRecord\Models\ModelNotFoundException; use LdapRecord\Query\ObjectNotFoundException;
use App\Ldap\Entry; use App\Ldap\Entry;
use App\Classes\LDAP\Server; use App\Classes\LDAP\Server;
@ -49,7 +49,7 @@ class HomeController extends Controller
* LDAP Server INFO * LDAP Server INFO
* *
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
* @throws ModelNotFoundException * @throws ObjectNotFoundException
*/ */
public function info() public function info()
{ {
@ -74,7 +74,7 @@ class HomeController extends Controller
}); });
// @todo If we cant get server info, we should probably show a nice error dialog // @todo If we cant get server info, we should probably show a nice error dialog
} catch (ModelNotFoundException $e) { } catch (ObjectNotFoundException $e) {
$attrs = collect(); $attrs = collect();
} }
@ -98,6 +98,11 @@ class HomeController extends Controller
->with('dn',$dn); ->with('dn',$dn);
} }
public function schema_frame()
{
return view('frames.schema');
}
/** /**
* Sort the attributes * Sort the attributes
* *

View File

@ -14,13 +14,6 @@ use App\Classes\LDAP\Attribute\Factory;
class Entry extends Model class Entry extends Model
{ {
/**
* The object classes of the LDAP model.
*
* @var array
*/
public static $objectClasses = [];
/* OVERRIDES */ /* OVERRIDES */
public function getAttributes(): array public function getAttributes(): array
@ -39,23 +32,18 @@ class Entry extends Model
* Gets the root DN of the specified LDAPServer, or throws an exception if it * Gets the root DN of the specified LDAPServer, or throws an exception if it
* can't find it. * can't find it.
* *
* @param null $connection * @param null $connection Return a collection of baseDNs
* @param bool $objects Return a collection of Entry Models
* @return Collection * @return Collection
* @throws ObjectNotFoundException * @throws ObjectNotFoundException
* @testedin GetBaseDNTest::testBaseDNExists(); * @testedin GetBaseDNTest::testBaseDNExists();
*/ */
public static function baseDNs($connection = NULL): Collection public static function baseDNs($connection=NULL,bool $objects=TRUE): Collection
{ {
$cachetime = Carbon::now()->addSeconds(Config::get('ldap.cache.time')); $cachetime = Carbon::now()->addSeconds(Config::get('ldap.cache.time'));
try { try {
$base = static::on($connection ?? (new static)->getConnectionName()) $base = self::rootDSE($connection,$cachetime);
->cache($cachetime)
->in(NULL)
->read()
->select(['namingcontexts'])
->whereHas('objectclass')
->firstOrFail();
/** /**
* LDAP Error Codes: * LDAP Error Codes:
@ -160,6 +148,9 @@ class Entry extends Model
} }
} }
if (! $objects)
return collect($base->namingcontexts);
/** /**
* @note While we are caching our baseDNs, it seems if we have more than 1, * @note While we are caching our baseDNs, it seems if we have more than 1,
* our caching doesnt generate a hit on a subsequent call to this function (before the cache expires). * our caching doesnt generate a hit on a subsequent call to this function (before the cache expires).
@ -174,6 +165,32 @@ class Entry extends Model
return $result; return $result;
} }
/**
* Obtain the rootDSE for the server, that gives us server information
*
* @param null $connection
* @return Entry|null
* @throws ObjectNotFoundException
* @testedin TranslateOidTest::testRootDSE();
*/
public static function rootDSE($connection=NULL,Carbon $cachetime=NULL): ?Model
{
return static::on($connection ?? (new static)->getConnectionName())
->cache($cachetime)
->in(NULL)
->read()
->select(['+'])
->whereHas('objectclass')
->firstOrFail();
}
public static function schemaDN($connection = NULL): string
{
$cachetime = Carbon::now()->addSeconds(Config::get('ldap.cache.time'));
return collect(self::rootDSE($connection,$cachetime)->subschemasubentry)->first();
}
/* ATTRIBUTES */ /* ATTRIBUTES */
/** /**
@ -254,22 +271,4 @@ class Entry extends Model
// Default // Default
return 'fa-fw fas fa-cog'; return 'fa-fw fas fa-cog';
} }
}
/**
* Obtain the rootDSE for the server, that gives us server information
*
* @param null $connection
* @return Entry|null
* @throws ObjectNotFoundException
* @testedin TranslateOidTest::testRootDSE();
*/
public function rootDSE($connection = NULL): ?Entry
{
return static::on($connection ?? (new static)->getConnectionName())
->in(NULL)
->read()
->select(['+'])
->whereHas('objectclass')
->firstOrFail();
}
}

View File

@ -2,6 +2,7 @@
namespace App\Providers; namespace App\Providers;
use Illuminate\Support\Collection;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use LdapRecord\Configuration\DomainConfiguration; use LdapRecord\Configuration\DomainConfiguration;
use LdapRecord\Laravel\LdapRecord; use LdapRecord\Laravel\LdapRecord;
@ -32,5 +33,12 @@ class AppServiceProvider extends ServiceProvider
public function boot() public function boot()
{ {
$this->loadViewsFrom(__DIR__.'/../../resources/themes/architect/views/','architect'); $this->loadViewsFrom(__DIR__.'/../../resources/themes/architect/views/','architect');
// Enable pluck on collections to work on private values
Collection::macro('ppluck', function ($attr) {
return $this->map(function (object $item) use ($attr) {
return $item->{$attr};
})->values();
});
} }
} }

View File

@ -492,15 +492,6 @@ $servers->setValue('server','name','My LDAP Server');
// $servers->setValue('server','custom_attrs',array('')); // $servers->setValue('server','custom_attrs',array(''));
# $servers->setValue('server','custom_attrs',array('nsRoleDN','nsRole','nsAccountLock')); # $servers->setValue('server','custom_attrs',array('nsRoleDN','nsRole','nsAccountLock'));
/* These attributes will be forced to MAY attributes and become option in the
templates. If they are not defined in the templates, then they wont appear
as per normal template processing. You may want to do this because your LDAP
server may automatically calculate a default value.
In Fedora Directory Server using the DNA Plugin one could ignore uidNumber,
gidNumber and sambaSID. */
// $servers->setValue('server','force_may',array(''));
# $servers->setValue('server','force_may',array('uidNumber','gidNumber','sambaSID'));
/********************************************* /*********************************************
* Unique attributes * * Unique attributes *
*********************************************/ *********************************************/

16
config/pla.php Normal file
View File

@ -0,0 +1,16 @@
<?php
return [
/**
* These attributes will be forced to MAY attributes and become optional in the
* templates. If they are not defined in the templates, then they wont appear
* as per normal template processing. You may want to do this because your LDAP
* server may automatically calculate a default value.
*
* In Fedora Directory Server using the DNA Plugin one could ignore uidNumber,
* gidNumber and sambaSID.
*
# 'force_may' => ['uidNumber','gidNumber','sambaSID'],
*/
'force_may' => [],
];

View File

@ -1,640 +0,0 @@
<?php
/**
* Displays the schema for the specified server
*
* Variables that come in as GET vars:
* - view (optional)
* Shows attribute, objectclass or matching rule
* - viewvalue (optional)
* Shows the attribute, objectclass or matching rule
* - highlight_oid (optional)
* Use to higlight the oid in the syntaxes view.
*
* @package phpLDAPadmin
* @subpackage Page
*/
/**
*/
require './common.php';
$entry = array();
$entry['view'] = get_request('view','GET','false','objectclasses');
$entry['value'] = get_request('viewvalue','GET');
if (! is_null($entry['value'])) {
$entry['viewed'] = false;
$entry['value'] = strtolower($entry['value']);
}
$schema_error_str = sprintf('%s <b>%s</b>.<br /><br /></div>%s<ul><li>%s</li><li>%s</li><li>%s</li><li>%s</li></ul>',
_('Could not retrieve schema from'),$app['server']->getName(),
_('This could happen for several reasons, the most probable of which are:'),_('The server does not fully support the LDAP protocol.'),
_('Your version of PHP does not correctly perform the query.'),_('phpLDAPadmin doesn\'t know how to fetch the schema for your server.'),
_('Or lastly, your LDAP server doesnt provide this information.'));
printf('<h3 class="title">%s <b>%s</b></h3>',_('Schema for server'),$app['server']->getName());
$entry['schema_types'] = array(
'objectclasses'=>_('ObjectClasses'),
'attributes'=>_('Attribute Types'),
'syntaxes'=>_('Syntaxes'),
'matching_rules'=>_('Matching Rules'));
echo '<br />';
echo '<div style="text-align: center;">';
$counter = 0;
foreach ($entry['schema_types'] as $item => $value) {
if ($counter++)
echo ' | ';
$entry['href'][$item] = sprintf('cmd=schema&server_id=%s&view=%s',$app['server']->getIndex(),$item);
if ($entry['view'] == $item) {
echo _($value);
} else {
if (isAjaxEnabled())
printf('<a href="cmd.php?%s" onclick="return ajDISPLAY(\'BODY\',\'%s\',\'Loading %s\');" title="Loading %s">%s</a>',
htmlspecialchars($entry['href'][$item]),htmlspecialchars($entry['href'][$item]),$value,$value,$value);
else
printf('<a href="cmd.php?%s">%s</a>',htmlspecialchars($entry['href'][$item]),_($value));
}
}
echo '</div>';
echo '<br />';
switch($entry['view']) {
case 'syntaxes':
$highlight_oid = get_request('highlight_oid','GET',false,false);
echo '<table class="result_table" border="0" style="margin-left: auto; margin-right: auto;">';
printf('<tr class="heading"><td>%s</td><td>%s</td></tr>',_('Syntax OID'),_('Description'));
$counter = 1;
$schema_syntaxes = $app['server']->SchemaSyntaxes();
if (! $schema_syntaxes)
error($schema_error_str,'error','index.php');
foreach ($schema_syntaxes as $syntax) {
$counter++;
$oid = $syntax->getOID();
$desc = $syntax->getDescription();
if ($highlight_oid && $highlight_oid == $oid)
echo '<tr class="highlight">';
else
printf('<tr class="%s">',$counter%2==0?'even':'odd');
printf('<td>%s</td><td>%s</td></tr>',$oid,$desc);
}
echo '</table>';
break;
case 'attributes':
$entry['attr_types'] = array(
'desc' => _('Description'),
'obsolete' => _('Obsolete'),
'inherits' => _('Inherits from'),
'equality' => _('Equality'),
'ordering' => _('Ordering'),
'substring_rule' => _('Substring Rule'),
'syntax' => _('Syntax'),
'single_valued' => _('Single Valued'),
'collective' => _('Collective'),
'user_modification' => _('User Modification'),
'usage' => _('Usage'),
'maximum_length' => _('Maximum Length'),
'aliases' => _('Aliases'),
'used_by_objectclasses' => _('Used by objectClasses'),
'force_as_may' => _('Force as MAY by config')
);
$sattrs = $app['server']->SchemaAttributes();
if (! $sattrs || ! $app['server']->SchemaObjectClasses())
error($schema_error_str,'error','index.php');
printf('<small>%s:</small>',_('Jump to an attribute type'));
echo '<form action="cmd.php" method="get">';
echo '<div>';
echo '<input type="hidden" name="cmd" value="schema" />';
printf('<input type="hidden" name="view" value="%s" />',$entry['view']);
printf('<input type="hidden" name="server_id" value="%s" />',$app['server']->getIndex());
if (isAjaxEnabled()) {
drawJSItems($sattrs);
echo '<select name="viewvalue" onchange="ajSHOWSCHEMA(\'attributes\',\'at\')" id="attributes">';
} else
echo '<select name="viewvalue" onchange="submit()">';
echo '<option value=""> - all -</option>';
foreach ($sattrs as $name => $attr)
printf('<option value="%s" %s>%s</option>',
$name,$name == $entry['value'] ? 'selected="selected" ': '',$attr->getName(false));
echo '</select>';
if (isAjaxEnabled())
printf('<input type="button" value="%s" onclick="ajSHOWSCHEMA(\'attributes\',\'at\')"/>',_('Go'));
else
printf('<input type="submit" value="%s" />',_('Go'));
echo '</div>';
echo '</form>';
echo '<br />';
foreach ($sattrs as $attr) {
if (isAjaxEnabled() || (is_null($entry['value']) || ! trim($entry['value']) || $entry['value']==$attr->getName())) {
if ((! is_null($entry['value']) && $entry['value']==$attr->getName()) || ! trim($entry['value']))
$entry['viewed'] = true;
if (isAjaxEnabled() && $entry['value'])
printf('<div id="at%s" style="display: %s">',$attr->getName(),strcasecmp($entry['value'],$attr->getName()) ? 'none' : 'block');
else
printf('<div id="at%s">',$attr->getName());
echo '<table class="result_table" width="100%" border="0">';
printf('<tr class="heading"><td colspan="2"><a name="%s">%s</a></td></tr>',
$attr->getName(),$attr->getName(false));
$counter = 0;
foreach ($entry['attr_types'] as $item => $value) {
printf('<tr class="%s">',++$counter%2 ? 'odd' : 'even');
printf('<td class="title" style="width: 30%%;">%s</td>',$value);
switch ($item) {
case 'desc':
printf('<td>%s</td>',
is_null($attr->getDescription()) ?
'('._('no description').')' : $attr->getDescription());
echo '</tr>';
printf('<tr class="%s">',++$counter%2 ? 'odd' : 'even');
echo '<td class="title"><acronym title="Object Identier">OID</acronym></td>';
printf('<td>%s</td>',$attr->getOID());
break;
case 'obsolete':
printf('<td>%s</td>',$attr->getIsObsolete() ? '<b>'._('Yes').'</b>' : _('No'));
break;
case 'inherits':
echo '<td>';
if (is_null($attr->getSupAttribute()))
printf('(%s)',_('none'));
else {
$href = htmlspecialchars(sprintf('%s&viewvalue=%s',$entry['href']['attributes'],strtolower($attr->getSupAttribute())));
if (isAjaxEnabled())
printf('<a href="cmd.php?%s" onclick="return ajSHOWSCHEMA(\'attributes\',\'at\',\'%s\');">%s</a>',
$href,strtolower($attr->getSupAttribute()),$attr->getSupAttribute());
else
printf('<a href="cmd.php?%s">%s</a>',$href,$attr->getSupAttribute());
}
echo '</td>';
break;
case 'equality':
echo '<td>';
if (is_null($attr->getEquality()))
printf('(%s)',_('not specified'));
else {
$href = htmlspecialchars(sprintf('%s&viewvalue=%s',$entry['href']['matching_rules'],$attr->getEquality()));
if (isAjaxEnabled())
printf('<a href="cmd.php?%s" onclick="return ajJUMP(\'%s\',\'%s\',\'%s\');">%s</a>',
$href,$href,_('Matching Rules'),$attr->getEquality(),$attr->getEquality());
else
printf('<a href="cmd.php?%s">%s</a>',$href,$attr->getEquality());
}
echo '</td>';
break;
case 'ordering':
printf('<td>%s</td>',
is_null($attr->getOrdering()) ? '('._('not specified').')' : $attr->getOrdering());
break;
case 'substring_rule':
printf('<td>%s</td>',
is_null($attr->getSubstr()) ? '('._('not specified').')' : $attr->getSubstr());
break;
case 'syntax':
echo '<td>';
if (is_null($attr->getType())) {
echo $attr->getSyntaxOID();
} else {
$href = htmlspecialchars(sprintf('%s&highlight_oid=%s',$entry['href']['syntaxes'],$attr->getSyntaxOID()));
if (isAjaxEnabled())
printf('<a href="cmd.php?%s" onclick="return ajJUMP(\'%s\',\'%s\',\'%s\');">%s (%s)</a>',
$href,$href,_('Syntaxes'),'',$attr->getType(),$attr->getSyntaxOID());
else
printf('<a href="cmd.php?%s">%s (%s)</a>',$href,$attr->getType(),$attr->getSyntaxOID());
}
echo '</td>';
break;
case 'single_valued':
printf('<td>%s</td>',$attr->getIsSingleValue() ? _('Yes') : _('No'));
break;
case 'collective':
printf('<td>%s</td>',$attr->getIsCollective() ? _('Yes') : _('No'));
break;
case 'user_modification':
printf('<td>%s</td>',$attr->getIsNoUserModification() ? _('No') : _('Yes'));
break;
case 'usage':
printf('<td>%s</td>',$attr->getUsage() ? $attr->getUsage() : '('._('not specified').')');
break;
case 'maximum_length':
echo '<td>';
if ( is_null($attr->getMaxLength()))
echo '('._('not applicable').')';
else
printf('%s %s',number_format($attr->getMaxLength()),
$attr->getMaxLength()>1 ? _('characters') : _('character'));
echo '</td>';
break;
case 'aliases':
echo '<td>';
if (count($attr->getAliases()) == 0)
printf('(%s)',_('none'));
else
foreach ($attr->getAliases() as $alias) {
$href = htmlspecialchars(sprintf('%s&viewvalue=%s',$entry['href']['attributes'],strtolower($alias)));
if (isAjaxEnabled())
printf('<a href="cmd.php?%s" onclick="return ajSHOWSCHEMA(\'attributes\',\'at\',\'%s\');">%s</a>',
$href,strtolower($alias),$alias);
else
printf('<a href="cmd.php?%s">%s</a>',$href,$alias);
}
echo '</td>';
break;
case 'used_by_objectclasses':
echo '<td>';
if (count($attr->getUsedInObjectClasses()) == 0)
printf('(%s)',_('none'));
else
foreach ($attr->getUsedInObjectClasses() as $objectclass) {
$href = htmlspecialchars(sprintf('%s&viewvalue=%s',$entry['href']['objectclasses'],strtolower($objectclass)));
if (isAjaxEnabled())
printf('<a href="cmd.php?%s" onclick="return ajJUMP(\'%s\',\'%s\',\'%s\');">%s</a> ',
$href,$href,_('ObjectClasses'),strtolower($objectclass),$objectclass);
else
printf('<a href="cmd.php?%s">%s</a> ',$href,$objectclass);
}
echo '</td>';
break;
case 'force_as_may':
printf('<td>%s</td>',$attr->isForceMay() ? _('Yes') : _('No'));
break;
}
echo '</tr>';
}
echo '</table>';
echo '<br />';
echo '</div>';
}
}
break;
case 'matching_rules':
$schema_matching_rules = $app['server']->MatchingRules();
if (! $schema_matching_rules)
error($schema_error_str,'error','index.php');
printf('<small>%s</small><br />',_('Jump to a matching rule'));
echo '<form action="cmd.php" method="get">';
echo '<div>';
echo '<input type="hidden" name="cmd" value="schema" />';
printf('<input type="hidden" name="server_id" value="%s" />',$app['server']->getIndex());
echo '<input type="hidden" name="view" value="matching_rules" />';
if (isAjaxEnabled()) {
drawJSItems($schema_matching_rules);
echo '<select name="viewvalue" onchange="ajSHOWSCHEMA(\'matchingrules\',\'mr\')" id="matchingrules">';
} else
echo '<select name="viewvalue" onchange="submit()">';
echo '<option value=""> - all -</option>';
foreach ($schema_matching_rules as $rule)
printf('<option value="%s" %s>%s</option>',
$rule->getName(),
($rule->getName() == $entry['value'] ? 'selected="selected"': ''),
$rule->getName(false));
echo '</select>';
if (isAjaxEnabled())
printf('<input type="button" value="%s" onclick="ajSHOWSCHEMA(\'matchingrules\',\'mr\')"/>',_('Go'));
else
printf('<input type="submit" value="%s" />',_('Go'));
echo '</div>';
echo '</form>';
echo '<br />';
echo '<table class="result_table" width="100%" border="0">';
printf('<tr class="heading"><td>%s</td><td>%s</td><td>%s</td></tr>',
_('Matching Rule OID'),_('Name'),_('Used by Attributes'));
$counter = 1;
foreach ($schema_matching_rules as $rule) {
$counter++;
$oid = $rule->getOID();
$desc = $rule->getName(false);
if (isAjaxEnabled() || (is_null($entry['value']) || ! trim($entry['value']) || $entry['value']==$rule->getName())) {
if ((! is_null($entry['value']) && $entry['value']==$rule->getName()) || ! trim($entry['value']))
$entry['viewed'] = true;
if (null != $rule->getDescription())
$desc .= sprintf(' (%s)',$rule->getDescription());
if ( $rule->getIsObsolete())
$desc .= sprintf(' <span style="color:red">%s</span>',_('Obsolete'));
if (isAjaxEnabled() && $entry['value'])
printf('<tr class="%s" id="mr%s" style="display: %s">',$counter%2 ? 'odd' : 'even',$rule->getName(),
strcasecmp($entry['value'],$rule->getName()) ? 'none' : '');
else
printf('<tr class="%s" id="mr%s">',$counter%2 ? 'odd' : 'even',$rule->getName());
printf('<td>%s</td>',$oid);
printf('<td>%s</td>',$desc);
echo '<td>';
if (count($rule->getUsedByAttrs()) == 0) {
printf('<div style="text-align: center;">(%s)</div><br /><br />',_('none'));
} else {
echo '<table width="100%" border="0"><tr><td>';
echo '<form action="cmd.php" method="get">';
echo '<div>';
echo '<input type="hidden" name="cmd" value="schema" />';
printf('<input type="hidden" name="server_id" value="%s" />',$app['server']->getIndex());
echo '<input type="hidden" name="view" value="attributes" />';
printf('<select size="4" name="viewvalue" id="vv%s">',$rule->getName());
foreach ($rule->getUsedByAttrs() as $attr)
printf('<option>%s</option>',$attr);
echo '</select><br />';
if (isAjaxEnabled())
printf('<input type="button" value="%s" onclick="return ajJUMP(\'cmd=schema&amp;view=attributes&amp;server_id=%s\',\'%s\',\'%s\',\'vv\');"/>',
_('Go'),$app['server']->getIndex(),_('Attributes'),$rule->getName());
else
printf('<input type="submit" value="%s" />',_('Go'));
echo '</div>';
echo '</form>';
echo '</td></tr></table>';
}
echo '</td>';
echo '</tr>';
}
}
echo '</table>';
break;
case 'objectclasses':
$socs = $app['server']->SchemaObjectClasses();
if (! $socs)
error($schema_error_str,'error','index.php');
printf('<small>%s:</small>',_('Jump to an objectClass'));
echo '<form action="cmd.php" method="get">';
echo '<div>';
echo '<input type="hidden" name="cmd" value="schema" />';
printf('<input type="hidden" name="view" value="%s" />',$entry['view']);
printf('<input type="hidden" name="server_id" value="%s" />',$app['server']->getIndex());
if (isAjaxEnabled()) {
drawJSItems($socs);
echo '<select name="viewvalue" onchange="ajSHOWSCHEMA(\'objectclasses\',\'oc\')" id="objectclasses">';
} else
echo '<select name="viewvalue" onchange="submit()">';
echo '<option value=""> - all - </option>';
foreach ($socs as $name => $oclass)
printf('<option value="%s" %s>%s</option>',
$name,$name == $entry['value'] ? 'selected="selected" ': '',$oclass->getName(false));
echo '</select>';
if (isAjaxEnabled())
printf('<input type="button" value="%s" onclick="ajSHOWSCHEMA(\'objectclasses\',\'oc\')"/>',_('Go'));
else
printf('<input type="submit" value="%s" />',_('Go'));
echo '</div>';
echo '</form>';
echo '<br />';
foreach ($socs as $name => $oclass) {
if (isAjaxEnabled() || (is_null($entry['value']) || ! trim($entry['value']) || $entry['value']==$oclass->getName())) {
if ((! is_null($entry['value']) && $entry['value']==$oclass->getName()) || ! trim($entry['value']))
$entry['viewed'] = true;
if (isAjaxEnabled() && $entry['value'])
printf('<div id="oc%s" style="display: %s">',$oclass->getName(),strcasecmp($entry['value'],$oclass->getName()) ? 'none' : '');
else
printf('<div id="oc%s">',$oclass->getName());
echo '<table class="result_table" width="100%" border="0">';
printf('<tr class="heading"><td colspan="4"><a name="%s">%s</a></td></tr>',$name,$oclass->getName(false));
printf('<tr class="odd"><td colspan="4">%s: <b>%s</b></td></tr>',_('OID'),$oclass->getOID());
if ($oclass->getDescription())
printf('<tr class="odd"><td colspan="4">%s: <b>%s</b></td></tr>',_('Description'),$oclass->getDescription());
printf('<tr class="odd"><td colspan="4">%s: <b>%s</b></td></tr>',_('Type'),$oclass->getType());
if ($oclass->getIsObsolete())
printf('<tr class="odd"><td colspan="4">%s</td></tr>',_('This objectClass is obsolete.'));
printf('<tr class="odd"><td colspan="4">%s: <b>',_('Inherits from'));
if (count($oclass->getSupClasses()) == 0)
printf('(%s)',_('none'));
else
foreach ($oclass->getSupClasses() as $i => $object_class) {
$href = htmlspecialchars(sprintf('%s&viewvalue=%s',$entry['href']['objectclasses'],strtolower($object_class)));
if (isAjaxEnabled())
printf('<a href="cmd.php?%s" onclick="return ajSHOWSCHEMA(\'objectclasses\',\'oc\',\'%s\');">%s</a>',
$href,strtolower($object_class),$object_class);
else
printf('<a href="cmd.php?%s&viewvalue=%s" title="%s">%s</a>',
$href,$object_class,_('Jump to this objectClass definition'),$object_class);
if ($i < count($oclass->getSupClasses()) - 1)
echo ', ';
}
echo '</b></td></tr>';
printf('<tr class="odd"><td colspan="4">%s: <b>',_('Parent to'));
if (strcasecmp($oclass->getName(),'top') == 0) {
$href = htmlspecialchars($entry['href']['objectclasses']);
if (isAjaxEnabled())
printf('<a href="cmd.php?%s" onclick="return ajSHOWSCHEMA(\'objectclasses\',\'oc\',\'\');">all</a>',
$href);
else
printf('(<a href="cmd.php?%s">all</a>)',$href);
} elseif (count($oclass->getChildObjectClasses()) == 0)
printf('(%s)',_('none'));
else
foreach ($oclass->getChildObjectClasses() as $i => $object_class) {
$href = htmlspecialchars(sprintf('%s&viewvalue=%s',$entry['href']['objectclasses'],strtolower($object_class)));
if (isAjaxEnabled())
printf('<a href="cmd.php?%s" title="%s" onclick="return ajSHOWSCHEMA(\'objectclasses\',\'oc\',\'%s\');">%s</a>',
$href,_('Jump to this objectClass definition'),strtolower($object_class),$object_class);
else
printf('<a href="cmd.php?%s" title="%s">%s</a>',$href,_('Jump to this objectClass definition'),$object_class);
if ( $i < count($oclass->getChildObjectClasses()) - 1)
echo ', ';
}
echo '</b></td></tr>';
printf('<tr class="even"><td class="blank" rowspan="2" style="width: 5%%;">&nbsp;</td><td style="width: 45%%;"><b>%s</b></td><td style="width: 45%%;"><b>%s</b></td><td class="blank" rowspan="2" style="width: 5%%;">&nbsp;</td></tr>',
_('Required Attributes'),_('Optional Attributes'));
echo '<tr class="odd">';
echo '<td>';
if ($attrs = $oclass->getMustAttrs(true)) {
echo '<ul class="list">';
foreach ($attrs as $attr) {
echo '<li>';
$href = htmlspecialchars(sprintf('%s&viewvalue=%s',$entry['href']['attributes'],$attr->getName()));
if (isAjaxEnabled())
printf('<a href="cmd.php?%s" onclick="return ajJUMP(\'%s\',\'%s\',\'%s\');">%s</a>',
$href,$href,_('Attributes'),$attr->getName(),$attr->getName(false));
else
printf('<a href="cmd.php?%s">%s</a>',$href,$attr->getName(false));
if ($attr->getSource() != $oclass->getName(false)) {
echo '<br />';
$href = htmlspecialchars(sprintf('%s&viewvalue=%s',$entry['href']['objectclasses'],strtolower($attr->getSource())));
printf('<small>(%s ',_('Inherited from'));
if (isAjaxEnabled())
printf('<a href="cmd.php?%s" title="%s" onclick="return ajSHOWSCHEMA(\'objectclasses\',\'oc\',\'%s\');">%s</a>',
$href,_('Jump to this objectClass definition'),strtolower($attr->getSource()),$attr->getSource());
else
printf('<a href="cmd.php?%s">%s</a>',$href,$attr->getSource());
echo ')</small>';
}
echo '</li>';
}
echo '</ul>';
} else
printf('(%s)',_('none'));
echo '</td>';
echo '<td>';
if ($attrs = $oclass->getMayAttrs(true)) {
echo '<ul class="list">';
foreach ($attrs as $attr) {
echo '<li>';
$href = htmlspecialchars(sprintf('%s&viewvalue=%s',$entry['href']['attributes'],$attr->getName()));
if (isAjaxEnabled())
printf('<a href="cmd.php?%s" onclick="return ajJUMP(\'%s\',\'%s\',\'%s\');">%s</a>',
$href,$href,_('Attributes'),$attr->getName(),$attr->getName(false));
else
printf('<a href="cmd.php?%s">%s</a>',$href,$attr->getName(false));
if ($attr->getSource() != $oclass->getName(false)) {
echo '<br />';
$href = htmlspecialchars(sprintf('%s&viewvalue=%s',$entry['href']['objectclasses'],strtolower($attr->getSource())));
printf('<small>(%s ',_('Inherited from'));
if (isAjaxEnabled())
printf('<a href="cmd.php?%s" title="%s" onclick="return ajSHOWSCHEMA(\'objectclasses\',\'oc\',\'%s\');">%s</a>',
$href,_('Jump to this objectClass definition'),strtolower($attr->getSource()),$attr->getSource());
else
printf('<a href="cmd.php?%s">%s</a>',$href,$attr->getSource());
echo ')</small>';
}
if ($oclass->isForceMay($attr->getName())) {
echo '<br />';
printf('<small>%s</small>',_('This attribute has been forced as a MAY attribute by the configuration'));
}
echo '</li>';
}
echo '</ul>';
} else
printf('(%s)',_('none'));
echo '</td>';
echo '</tr>';
echo '</table>';
echo '<br />';
echo '</div>';
}
}
break;
}
if (! is_null($entry['value']) && ! $entry['viewed'])
error(sprintf(_('No such schema item: "%s"'),$entry['value']),'error','index.php');
function drawJSItems($object) {
echo '<script type="text/javascript">'."\n";
echo "
function items() {
var \$items = new Array();";
$counter = 0;
foreach ($object as $attr) {
printf(' items[%s] = "%s";',$counter++,$attr->getName());
echo "\n";
}
echo '
return items;
}';
echo '</script>';
}
?>

View File

@ -1134,859 +1134,7 @@ class ldap extends DS {
} }
} }
public function getRootDSE($method=null) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
$query = array();
$query['base'] = '';
$query['scope'] = 'base';
$query['attrs'] = $this->getValue('server','root_dse_attributes');
$query['baseok'] = true;
$results = $this->query($query,$method);
if (is_array($results) && count($results) == 1)
return array_change_key_case(array_pop($results));
else
return array();
}
/** Schema Methods **/ /** Schema Methods **/
/**
* This function will query the ldap server and request the subSchemaSubEntry which should be the Schema DN.
*
* If we cant connect to the LDAP server, we'll return false.
* If we can connect but cant get the entry, then we'll return null.
*
* @param string Which connection method resource to use
* @param dn The DN to use to obtain the schema
* @return array|false Schema if available, null if its not or false if we cant connect.
*/
private function getSchemaDN($method=null,$dn='') {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',25,0,__FILE__,__LINE__,__METHOD__,$fargs);
# If we already got the SchemaDN, then return it.
if ($this->_schemaDN)
return $this->_schemaDN;
if (! $this->connect($method))
return false;
$search = @ldap_read($this->connect($method),$dn,'objectclass=*',array('subschemaSubentry'),false,0,10,LDAP_DEREF_NEVER);
if (DEBUG_ENABLED)
debug_log('Search returned (%s)',24,0,__FILE__,__LINE__,__METHOD__,is_resource($search));
# Fix for broken ldap.conf configuration.
if (! $search && ! $dn) {
if (DEBUG_ENABLED)
debug_log('Trying to find the DN for "broken" ldap.conf',80,0,__FILE__,__LINE__,__METHOD__);
if (isset($this->_baseDN)) {
foreach ($this->_baseDN as $base) {
$search = @ldap_read($this->connect($method),$base,'objectclass=*',array('subschemaSubentry'),false,0,10,LDAP_DEREF_NEVER);
if (DEBUG_ENABLED)
debug_log('Search returned (%s) for base (%s)',24,0,__FILE__,__LINE__,__METHOD__,
is_resource($search),$base);
if ($search)
break;
}
}
}
if (! $search)
return null;
if (! @ldap_count_entries($this->connect($method),$search)) {
if (DEBUG_ENABLED)
debug_log('Search returned 0 entries. Returning NULL',25,0,__FILE__,__LINE__,__METHOD__);
return null;
}
$entries = @ldap_get_entries($this->connect($method),$search);
if (DEBUG_ENABLED)
debug_log('Search returned [%s]',24,0,__FILE__,__LINE__,__METHOD__,$entries);
if (! $entries || ! is_array($entries))
return null;
$entry = isset($entries[0]) ? $entries[0] : false;
if (! $entry) {
if (DEBUG_ENABLED)
debug_log('Entry is false, Returning NULL',80,0,__FILE__,__LINE__,__METHOD__);
return null;
}
$sub_schema_sub_entry = isset($entry[0]) ? $entry[0] : false;
if (! $sub_schema_sub_entry) {
if (DEBUG_ENABLED)
debug_log('Sub Entry is false, Returning NULL',80,0,__FILE__,__LINE__,__METHOD__);
return null;
}
$this->_schemaDN = isset($entry[$sub_schema_sub_entry][0]) ? $entry[$sub_schema_sub_entry][0] : false;
if (DEBUG_ENABLED)
debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$this->_schemaDN);
return $this->_schemaDN;
}
/**
* Fetches the raw schema array for the subschemaSubentry of the server. Note,
* this function has grown many hairs to accomodate more LDAP servers. It is
* needfully complicated as it now supports many popular LDAP servers that
* don't necessarily expose their schema "the right way".
*
* Please note: On FC systems, it seems that php_ldap uses /etc/openldap/ldap.conf in
* the search base if it is blank - so edit that file and comment out the BASE line.
*
* @param string Which connection method resource to use
* @param string A string indicating which type of schema to
* fetch. Five valid values: 'objectclasses', 'attributetypes',
* 'ldapsyntaxes', 'matchingruleuse', or 'matchingrules'.
* Case insensitive.
* @param dn (optional) This paremeter is the DN of the entry whose schema you
* would like to fetch. Entries have the option of specifying
* their own subschemaSubentry that points to the DN of the system
* schema entry which applies to this attribute. If unspecified,
* this will try to retrieve the schema from the RootDSE subschemaSubentry.
* Failing that, we use some commonly known schema DNs. Default
* value is the Root DSE DN (zero-length string)
* @return array an array of strings of this form:
* Array (
* [0] => "(1.3.6.1.4.1.7165.1.2.2.4 NAME 'gidPool' DESC 'Pool ...
* [1] => "(1.3.6.1.4.1.7165.2.2.3 NAME 'sambaAccount' DESC 'Sa ...
* etc.
*/
private function getRawSchema($method,$schema_to_fetch,$dn='') {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',25,0,__FILE__,__LINE__,__METHOD__,$fargs);
$valid_schema_to_fetch = array('objectclasses','attributetypes','ldapsyntaxes','matchingrules','matchingruleuse');
if (! $this->connect($method) || $this->noconnect)
return false;
# error checking
$schema_to_fetch = strtolower($schema_to_fetch);
if (! is_null($this->_schema_entries) && isset($this->_schema_entries[$schema_to_fetch])) {
$schema = $this->_schema_entries[$schema_to_fetch];
if (DEBUG_ENABLED)
debug_log('Returning CACHED (%s)',25,0,__FILE__,__LINE__,__METHOD__,$schema);
return $schema;
}
# This error message is not localized as only developers should ever see it
if (! in_array($schema_to_fetch,$valid_schema_to_fetch))
error(sprintf('Bad parameter provided to function to %s::getRawSchema(). "%s" is not valid for the schema_to_fetch parameter.',
get_class($this),$schema_to_fetch),'error','index.php');
# Try to get the schema DN from the specified entry.
$schema_dn = $this->getSchemaDN($method,$dn);
# Do we need to try again with the Root DSE?
if (! $schema_dn && trim($dn))
$schema_dn = $this->getSchemaDN($method,'');
# Store the eventual schema retrieval in $schema_search
$schema_search = null;
if ($schema_dn) {
if (DEBUG_ENABLED)
debug_log('Using Schema DN (%s)',24,0,__FILE__,__LINE__,__METHOD__,$schema_dn);
foreach (array('(objectClass=*)','(objectClass=subschema)') as $schema_filter) {
if (DEBUG_ENABLED)
debug_log('Looking for schema with Filter (%s)',24,0,__FILE__,__LINE__,__METHOD__,$schema_filter);
$schema_search = @ldap_read($this->connect($method),$schema_dn,$schema_filter,array($schema_to_fetch),false,0,10,LDAP_DEREF_NEVER);
if (is_null($schema_search))
continue;
$schema_entries = @ldap_get_entries($this->connect($method),$schema_search);
if (DEBUG_ENABLED)
debug_log('Search returned [%s]',24,0,__FILE__,__LINE__,__METHOD__,$schema_entries);
if (is_array($schema_entries) && isset($schema_entries['count']) && $schema_entries['count']) {
if (DEBUG_ENABLED)
debug_log('Found schema with (DN:%s) (FILTER:%s) (ATTR:%s)',24,0,__FILE__,__LINE__,__METHOD__,
$schema_dn,$schema_filter,$schema_to_fetch);
break;
}
if (DEBUG_ENABLED)
debug_log('Didnt find schema with filter (%s)',24,0,__FILE__,__LINE__,__METHOD__,$schema_filter);
unset($schema_entries);
$schema_search = null;
}
}
/* Second chance: If the DN or Root DSE didn't give us the subschemaSubentry, ie $schema_search
* is still null, use some common subSchemaSubentry DNs as a work-around. */
if (is_null($schema_search)) {
if (DEBUG_ENABLED)
debug_log('Attempting work-arounds for "broken" LDAP servers...',24,0,__FILE__,__LINE__,__METHOD__);
foreach ($this->getBaseDN() as $base) {
$ldap['W2K3 AD'][expand_dn_with_base($base,'cn=Aggregate,cn=Schema,cn=configuration,')] = '(objectClass=*)';
$ldap['W2K AD'][expand_dn_with_base($base,'cn=Schema,cn=configuration,')] = '(objectClass=*)';
$ldap['W2K AD'][expand_dn_with_base($base,'cn=Schema,ou=Admin,')] = '(objectClass=*)';
}
# OpenLDAP and Novell
$ldap['OpenLDAP']['cn=subschema'] = '(objectClass=*)';
foreach ($ldap as $ldap_server_name => $ldap_options) {
foreach ($ldap_options as $ldap_dn => $ldap_filter) {
if (DEBUG_ENABLED)
debug_log('Attempting [%s] (%s) (%s)<BR>',24,0,__FILE__,__LINE__,__METHOD__,
$ldap_server_name,$ldap_dn,$ldap_filter);
$schema_search = @ldap_read($this->connect($method),$ldap_dn,$ldap_filter,array($schema_to_fetch),false,0,10,LDAP_DEREF_NEVER);
if (is_null($schema_search))
continue;
$schema_entries = @ldap_get_entries($this->connect($method),$schema_search);
if (DEBUG_ENABLED)
debug_log('Search returned [%s]',24,0,__FILE__,__LINE__,__METHOD__,$schema_entries);
if ($schema_entries && isset($schema_entries[0][$schema_to_fetch])) {
if (DEBUG_ENABLED)
debug_log('Found schema with filter of (%s)',24,0,__FILE__,__LINE__,__METHOD__,$ldap_filter);
break;
}
if (DEBUG_ENABLED)
debug_log('Didnt find schema with filter (%s)',24,0,__FILE__,__LINE__,__METHOD__,$ldap_filter);
unset($schema_entries);
$schema_search = null;
}
if ($schema_search)
break;
}
}
# Option 3: try cn=config
$olc_schema = 'olc'.$schema_to_fetch;
$olc_schema_found = false;
if (is_null($schema_search)) {
if (DEBUG_ENABLED)
debug_log('Attempting cn=config work-around...',24,0,__FILE__,__LINE__,__METHOD__);
$ldap_dn = 'cn=schema,cn=config';
$ldap_filter = '(objectClass=*)';
$schema_search = @ldap_search($this->connect($method),$ldap_dn,$ldap_filter,array($olc_schema),false,0,10,LDAP_DEREF_NEVER);
if (! is_null($schema_search)) {
$schema_entries = @ldap_get_entries($this->connect($method),$schema_search);
if (DEBUG_ENABLED)
debug_log('Search returned [%s]',24,0,__FILE__,__LINE__,__METHOD__,$schema_entries);
if ($schema_entries) {
if (DEBUG_ENABLED)
debug_log('Found schema with filter of (%s) and attribute filter (%s)',24,0,__FILE__,__LINE__,__METHOD__,$ldap_filter,$olc_schema);
$olc_schema_found = true;
} else {
if (DEBUG_ENABLED)
debug_log('Didnt find schema with filter (%s) and attribute filter (%s)',24,0,__FILE__,__LINE__,__METHOD__,$ldap_filter,$olc_schema);
unset($schema_entries);
$schema_search = null;
}
}
}
if (is_null($schema_search)) {
/* Still cant find the schema, try with the RootDSE
* Attempt to pull schema from Root DSE with scope "base", or
* Attempt to pull schema from Root DSE with scope "one" (work-around for Isode M-Vault X.500/LDAP) */
foreach (array('base','one') as $ldap_scope) {
if (DEBUG_ENABLED)
debug_log('Attempting to find schema with scope (%s), filter (objectClass=*) and a blank base.',24,0,__FILE__,__LINE__,__METHOD__,
$ldap_scope);
switch ($ldap_scope) {
case 'base':
$schema_search = @ldap_read($this->connect($method),'','(objectClass=*)',array($schema_to_fetch),false,0,10,LDAP_DEREF_NEVER);
break;
case 'one':
$schema_search = @ldap_list($this->connect($method),'','(objectClass=*)',array($schema_to_fetch),false,0,10,LDAP_DEREF_NEVER);
break;
}
if (is_null($schema_search))
continue;
$schema_entries = @ldap_get_entries($this->connect($method),$schema_search);
if (DEBUG_ENABLED)
debug_log('Search returned [%s]',24,0,__FILE__,__LINE__,__METHOD__,$schema_entries);
if ($schema_entries && isset($schema_entries[0][$schema_to_fetch])) {
if (DEBUG_ENABLED)
debug_log('Found schema with filter of (%s)',24,0,__FILE__,__LINE__,__METHOD__,'(objectClass=*)');
break;
}
if (DEBUG_ENABLED)
debug_log('Didnt find schema with filter (%s)',24,0,__FILE__,__LINE__,__METHOD__,'(objectClass=*)');
unset($schema_entries);
$schema_search = null;
}
}
$schema_error_message = 'Please contact the phpLDAPadmin developers and let them know:<ul><li>Which LDAP server you are running, including which version<li>What OS it is running on<li>Which version of PHP<li>As well as a link to some documentation that describes how to obtain the SCHEMA information</ul><br />We\'ll then add support for your LDAP server in an upcoming release.';
$schema_error_message_array = array('objectclasses','attributetypes');
# Shall we just give up?
if (is_null($schema_search)) {
# We need to have objectclasses and attribues, so display an error, asking the user to get us this information.
if (in_array($schema_to_fetch,$schema_error_message_array))
system_message(array(
'title'=>sprintf('%s (%s)',_('Our attempts to find your SCHEMA have failed'),$schema_to_fetch),
'body'=>sprintf('<b>%s</b>: %s',_('Error'),$schema_error_message),
'type'=>'error'));
else
if (DEBUG_ENABLED)
debug_log('Returning because schema_search is NULL ()',25,0,__FILE__,__LINE__,__METHOD__);
# We'll set this, so if we return here our cache will return the known false.
$this->_schema_entries[$schema_to_fetch] = false;
return false;
}
if (! $schema_entries) {
$return = false;
if (DEBUG_ENABLED)
debug_log('Returning false since ldap_get_entries() returned false.',25,0,__FILE__,__LINE__,__METHOD__,$return);
return $return;
}
if ($olc_schema_found) {
unset ($schema_entries['count']);
foreach ($schema_entries as $entry) {
if (isset($entry[$olc_schema])) {
unset($entry[$olc_schema]['count']);
foreach ($entry[$olc_schema] as $schema_definition)
/* Schema definitions in child nodes prefix the schema entries with "{n}"
the preg_replace call strips out this prefix. */
$schema[] = preg_replace('/^\{\d*\}\(/','(',$schema_definition);
}
}
if (isset($schema)) {
$this->_schema_entries[$olc_schema] = $schema;
if (DEBUG_ENABLED)
debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$schema);
return $schema;
} else
return null;
}
if (! isset($schema_entries[0][$schema_to_fetch])) {
if (in_array($schema_to_fetch,$schema_error_message_array)) {
error(sprintf('Our attempts to find your SCHEMA for "%s" have return UNEXPECTED results.<br /><br /><small>(We expected a "%s" in the $schema array but it wasnt there.)</small><br /><br />%s<br /><br />Dump of $schema_search:<hr /><pre><small>%s</small></pre>',
$schema_to_fetch,gettype($schema_search),$schema_error_message,serialize($schema_entries)),'error','index.php');
} else {
$return = false;
if (DEBUG_ENABLED)
debug_log('Returning because (%s) isnt in the schema array. (%s)',25,0,__FILE__,__LINE__,__METHOD__,$schema_to_fetch,$return);
return $return;
}
}
/* Make a nice array of this form:
Array (
[0] => "(1.3.6.1.4.1.7165.1.2.2.4 NAME 'gidPool' DESC 'Pool ...)"
[1] => "(1.3.6.1.4.1.7165.2.2.3 NAME 'sambaAccount' DESC 'Sa ...)"
etc.) */
$schema = $schema_entries[0][$schema_to_fetch];
unset($schema['count']);
$this->_schema_entries[$schema_to_fetch] = $schema;
if (DEBUG_ENABLED)
debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$schema);
return $schema;
}
/**
* Gets a single ObjectClass object specified by name.
*
* @param string $oclass_name The name of the objectClass to fetch.
* @param string $dn (optional) It is easier to fetch schema if a DN is provided
* which defines the subschemaSubEntry attribute (all entries should).
*
* @return ObjectClass The specified ObjectClass object or false on error.
*
* @see ObjectClass
* @see SchemaObjectClasses
*/
public function getSchemaObjectClass($oclass_name,$method=null,$dn='') {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',25,0,__FILE__,__LINE__,__METHOD__,$fargs);
$oclass_name = strtolower($oclass_name);
$socs = $this->SchemaObjectClasses($method,$dn);
# Default return value
$return = false;
if (isset($socs[$oclass_name]))
$return = $socs[$oclass_name];
if (DEBUG_ENABLED)
debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$return);
return $return;
}
/**
* Gets a single AttributeType object specified by name.
*
* @param string $oclass_name The name of the AttributeType to fetch.
* @param string $dn (optional) It is easier to fetch schema if a DN is provided
* which defines the subschemaSubEntry attribute (all entries should).
*
* @return AttributeType The specified AttributeType object or false on error.
*
* @see AttributeType
* @see SchemaAttributes
*/
public function getSchemaAttribute($attr_name,$method=null,$dn='') {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',25,0,__FILE__,__LINE__,__METHOD__,$fargs);
$attr_name = strtolower($attr_name);
$sattrs = $this->SchemaAttributes($method,$dn);
# Default return value
$return = false;
if (isset($sattrs[$attr_name]))
$return = $sattrs[$attr_name];
if (DEBUG_ENABLED)
debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$return);
return $return;
}
/**
* Gets an associative array of ObjectClass objects for the specified
* server. Each array entry's key is the name of the objectClass
* in lower-case and the value is an ObjectClass object.
*
* @param string $dn (optional) It is easier to fetch schema if a DN is provided
* which defines the subschemaSubEntry attribute (all entries should).
*
* @return array An array of ObjectClass objects.
*
* @see ObjectClass
* @see getSchemaObjectClass
*/
public function SchemaObjectClasses($method=null,$dn='') {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',25,0,__FILE__,__LINE__,__METHOD__,$fargs);
# Set default return
$return = null;
if ($return = get_cached_item($this->index,'schema','objectclasses')) {
if (DEBUG_ENABLED)
debug_log('Returning CACHED [%s] (%s)',25,0,__FILE__,__LINE__,__METHOD__,$this->index,'objectclasses');
return $return;
}
$raw = $this->getRawSchema($method,'objectclasses',$dn);
if ($raw) {
# Build the array of objectClasses
$return = array();
foreach ($raw as $line) {
if (is_null($line) || ! strlen($line))
continue;
$object_class = new ObjectClass($line,$this);
$return[$object_class->getName()] = $object_class;
}
# Now go through and reference the parent/child relationships
foreach ($return as $oclass)
foreach ($oclass->getSupClasses() as $parent_name)
if (isset($return[strtolower($parent_name)]))
$return[strtolower($parent_name)]->addChildObjectClass($oclass->getName(false));
ksort($return);
# cache the schema to prevent multiple schema fetches from LDAP server
set_cached_item($this->index,'schema','objectclasses',$return);
}
if (DEBUG_ENABLED)
debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$return);
return $return;
}
/**
* Gets an associative array of AttributeType objects for the specified
* server. Each array entry's key is the name of the attributeType
* in lower-case and the value is an AttributeType object.
*
* @param string $dn (optional) It is easier to fetch schema if a DN is provided
* which defines the subschemaSubEntry attribute (all entries should).
*
* @return array An array of AttributeType objects.
*/
public function SchemaAttributes($method=null,$dn='') {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',25,0,__FILE__,__LINE__,__METHOD__,$fargs);
# Set default return
$return = null;
if ($return = get_cached_item($this->index,'schema','attributes')) {
if (DEBUG_ENABLED)
debug_log('(): Returning CACHED [%s] (%s)',25,0,__FILE__,__LINE__,__METHOD__,$this->index,'attributes');
return $return;
}
$raw = $this->getRawSchema($method,'attributeTypes',$dn);
if ($raw) {
# build the array of attribueTypes
$syntaxes = $this->SchemaSyntaxes($method,$dn);
$attrs = array();
/**
* bug 856832: create two arrays - one indexed by name (the standard
* $attrs array above) and one indexed by oid (the new $attrs_oid array
* below). This will help for directory servers, like IBM's, that use OIDs
* in their attribute definitions of SUP, etc
*/
$attrs_oid = array();
foreach ($raw as $line) {
if (is_null($line) || ! strlen($line))
continue;
$attr = new AttributeType($line);
if (isset($syntaxes[$attr->getSyntaxOID()])) {
$syntax = $syntaxes[$attr->getSyntaxOID()];
$attr->setType($syntax->getDescription());
}
$attrs[$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
foreach ($attrs as $name => $attr) {
$aliases = $attr->getAliases();
if (is_array($aliases) && count($aliases) > 0) {
/* foreach of the attribute's aliases, create a new entry in the attrs array
* with its name set to the alias name, and all other data copied.*/
foreach ($aliases as $alias_attr_name) {
$new_attr = clone $attr;
$new_attr->setName($alias_attr_name);
$new_attr->addAlias($attr->getName(false));
$new_attr->removeAlias($alias_attr_name);
$new_attr_key = strtolower($alias_attr_name);
$attrs[$new_attr_key] = $new_attr;
}
}
}
# go back and add any inherited descriptions from parent attributes (ie, cn inherits name)
foreach ($attrs as $key => $attr) {
$sup_attr_name = $attr->getSupAttribute();
$sup_attr = null;
if (trim($sup_attr_name)) {
/* This loop really should traverse infinite levels of inheritance (SUP) for attributeTypes,
* but just in case we get carried away, stop at 100. This shouldn't happen, but for
* some weird reason, we have had someone report that it has happened. Oh well.*/
$i = 0;
while ($i++<100 /** 100 == INFINITY ;) */) {
if (isset($attrs_oid[$sup_attr_name])) {
$attr->setSupAttribute($attrs_oid[$sup_attr_name]->getName());
$sup_attr_name = $attr->getSupAttribute();
}
if (! isset($attrs[strtolower($sup_attr_name)])){
error(sprintf('Schema error: attributeType "%s" inherits from "%s", but attributeType "%s" does not exist.',
$attr->getName(),$sup_attr_name,$sup_attr_name),'error','index.php');
return;
}
$sup_attr = $attrs[strtolower($sup_attr_name)];
$sup_attr_name = $sup_attr->getSupAttribute();
# Does this superior attributeType not have a superior attributeType?
if (is_null($sup_attr_name) || strlen(trim($sup_attr_name)) == 0) {
/* Since this attribute's superior attribute does not have another superior
* attribute, clone its properties for this attribute. Then, replace
* those cloned values with those that can be explicitly set by the child
* attribute attr). Save those few properties which the child can set here:*/
$tmp_name = $attr->getName(false);
$tmp_oid = $attr->getOID();
$tmp_sup = $attr->getSupAttribute();
$tmp_aliases = $attr->getAliases();
$tmp_single_val = $attr->getIsSingleValue();
$tmp_desc = $attr->getDescription();
/* clone the SUP attributeType and populate those values
* that were set by the child attributeType */
$attr = clone $sup_attr;
$attr->setOID($tmp_oid);
$attr->setName($tmp_name);
$attr->setSupAttribute($tmp_sup);
$attr->setAliases($tmp_aliases);
$attr->setDescription($tmp_desc);
/* only overwrite the SINGLE-VALUE property if the child explicitly sets it
* (note: All LDAP attributes default to multi-value if not explicitly set SINGLE-VALUE) */
if ($tmp_single_val)
$attr->setIsSingleValue(true);
/* replace this attribute in the attrs array now that we have populated
new values therein */
$attrs[$key] = $attr;
# very important: break out after we are done with this attribute
$sup_attr_name = null;
$sup_attr = null;
break;
}
}
}
}
ksort($attrs);
# Add the used in and required_by values.
$socs = $this->SchemaObjectClasses($method);
if (! is_array($socs))
return array();
foreach ($socs as $object_class) {
$must_attrs = $object_class->getMustAttrNames();
$may_attrs = $object_class->getMayAttrNames();
$oclass_attrs = array_unique(array_merge($must_attrs,$may_attrs));
# Add Used In.
foreach ($oclass_attrs as $attr_name)
if (isset($attrs[strtolower($attr_name)]))
$attrs[strtolower($attr_name)]->addUsedInObjectClass($object_class->getName(false));
# Add Required By.
foreach ($must_attrs as $attr_name)
if (isset($attrs[strtolower($attr_name)]))
$attrs[strtolower($attr_name)]->addRequiredByObjectClass($object_class->getName(false));
# Force May
foreach ($object_class->getForceMayAttrs() as $attr_name)
if (isset($attrs[strtolower($attr_name->name)]))
$attrs[strtolower($attr_name->name)]->setForceMay();
}
$return = $attrs;
# cache the schema to prevent multiple schema fetches from LDAP server
set_cached_item($this->index,'schema','attributes',$return);
}
if (DEBUG_ENABLED)
debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$return);
return $return;
}
/**
* Returns an array of MatchingRule objects for the specified server.
* The key of each entry is the OID of the matching rule.
*/
public function MatchingRules($method=null,$dn='') {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',25,0,__FILE__,__LINE__,__METHOD__,$fargs);
# Set default return
$return = null;
if ($return = get_cached_item($this->index,'schema','matchingrules')) {
if (DEBUG_ENABLED)
debug_log('Returning CACHED [%s] (%s).',25,0,__FILE__,__LINE__,__METHOD__,$this->index,'matchingrules');
return $return;
}
# build the array of MatchingRule objects
$raw = $this->getRawSchema($method,'matchingRules',$dn);
if ($raw) {
$rules = array();
foreach ($raw as $line) {
if (is_null($line) || ! strlen($line))
continue;
$rule = new MatchingRule($line);
$key = $rule->getName();
$rules[$key] = $rule;
}
ksort($rules);
/* For each MatchingRuleUse entry, add the attributes who use it to the
* MatchingRule in the $rules array.*/
$raw = $this->getRawSchema($method,'matchingRuleUse');
if ($raw != false) {
foreach ($raw as $line) {
if (is_null($line) || ! strlen($line))
continue;
$rule_use = new MatchingRuleUse($line);
$key = $rule_use->getName();
if (isset($rules[$key]))
$rules[$key]->setUsedByAttrs($rule_use->getUsedByAttrs());
}
} else {
/* No MatchingRuleUse entry in the subschema, so brute-forcing
* the reverse-map for the "$rule->getUsedByAttrs()" data.*/
$sattrs = $this->SchemaAttributes($method,$dn);
if (is_array($sattrs))
foreach ($sattrs as $attr) {
$rule_key = strtolower($attr->getEquality());
if (isset($rules[$rule_key]))
$rules[$rule_key]->addUsedByAttr($attr->getName(false));
}
}
$return = $rules;
# cache the schema to prevent multiple schema fetches from LDAP server
set_cached_item($this->index,'schema','matchingrules',$return);
}
if (DEBUG_ENABLED)
debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$return);
return $return;
}
/**
* Returns an array of Syntax objects that this LDAP server uses mapped to
* their descriptions. The key of each entry is the OID of the Syntax.
*/
public function SchemaSyntaxes($method=null,$dn='') {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',25,0,__FILE__,__LINE__,__METHOD__,$fargs);
# Set default return
$return = null;
if ($return = get_cached_item($this->index,'schema','syntaxes')) {
if (DEBUG_ENABLED)
debug_log('Returning CACHED [%s] (%s).',25,0,__FILE__,__LINE__,__METHOD__,$this->index,'syntaxes');
return $return;
}
$raw = $this->getRawSchema($method,'ldapSyntaxes',$dn);
if ($raw) {
# build the array of attributes
$return = array();
foreach ($raw as $line) {
if (is_null($line) || ! strlen($line))
continue;
$syntax = new Syntax($line);
$key = strtolower(trim($syntax->getOID()));
if (! $key)
continue;
$return[$key] = $syntax;
}
ksort($return);
# cache the schema to prevent multiple schema fetches from LDAP server
set_cached_item($this->index,'schema','syntaxes',$return);
}
if (DEBUG_ENABLED)
debug_log('Returning (%s)',25,0,__FILE__,__LINE__,__METHOD__,$return);
return $return;
}
/**
* This function determines if the specified attribute is contained in the force_may list
* as configured in config.php.
*
* @return boolean True if the specified attribute is configured to be force as a may attribute
*/
function isForceMay($attr_name) {
if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs);
return in_array($attr_name,unserialize(strtolower(serialize($this->getValue('server','force_may')))));
}
/** /**
* Much like getDNAttrValues(), but only returns the values for * Much like getDNAttrValues(), but only returns the values for

File diff suppressed because it is too large Load Diff

View File

@ -18,13 +18,13 @@
<div class="page-title-actions"> <div class="page-title-actions">
{{-- {{--
<button type="button" data-toggle="tooltip" title="Example Tooltip" data-placement="bottom" class="btn-shadow mr-3 btn btn-dark"> <button type="button" data-toggle="tooltip" title="Example Tooltip" data-placement="bottom" class="btn-shadow mr-3 btn btn-dark">
<i class="fa fa-star"></i> <i class="fas fa-star"></i>
</button> </button>
--}} --}}
<div class="d-inline-block dropdown"> <div class="d-inline-block dropdown">
<button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn-shadow dropdown-toggle btn btn-info"> <button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn-shadow dropdown-toggle btn btn-info">
<span class="btn-icon-wrapper pr-2 opacity-7"> <span class="btn-icon-wrapper pr-2 opacity-7">
<i class="fa fa-business-time fa-w-20"></i> <i class="fas fa-business-time fa-w-20"></i>
</span> </span>
Item Menu Item Menu
</button> </button>

View File

@ -44,6 +44,9 @@
<!-- Theme style --> <!-- Theme style -->
<link rel="stylesheet" href="{{ asset('/css/architect.min.css') }}"> <link rel="stylesheet" href="{{ asset('/css/architect.min.css') }}">
<!-- Select 2 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" />
@if(file_exists('css/fixes.css')) @if(file_exists('css/fixes.css'))
<!-- CSS Fixes --> <!-- CSS Fixes -->
<link rel="stylesheet" href="{{ asset('/css/fixes.css') }}"> <link rel="stylesheet" href="{{ asset('/css/fixes.css') }}">

View File

@ -37,11 +37,9 @@
<div class="font-icon-wrapper float-left mr-1 server-icon"> <div class="font-icon-wrapper float-left mr-1 server-icon">
<a class="p-0 m-0" href="{{ LaravelLocalization::localizeUrl('info') }}" onclick="return false;" style="display: contents;"><i class="fas fa-fw fa-info"></i></a> <a class="p-0 m-0" href="{{ LaravelLocalization::localizeUrl('info') }}" onclick="return false;" style="display: contents;"><i class="fas fa-fw fa-info"></i></a>
</div> </div>
{{--
<div class="font-icon-wrapper float-left ml-1 mr-1 server-icon"> <div class="font-icon-wrapper float-left ml-1 mr-1 server-icon">
<a class="p-0 m-0" href="{{ LaravelLocalization::localizeUrl('schema') }}" onclick="return false;" style="display: contents;"><i class="fas fa-fw fa-fingerprint"></i></a> <a class="p-0 m-0" href="{{ LaravelLocalization::localizeUrl('schema') }}" onclick="return false;" style="display: contents;"><i class="fas fa-fw fa-fingerprint"></i></a>
</div> </div>
--}}
@env(['local']) @env(['local'])
<div class="font-icon-wrapper float-right ml-1 server-icon"> <div class="font-icon-wrapper float-right ml-1 server-icon">
<a class="p-0 m-0" href="{{ LaravelLocalization::localizeUrl('debug') }}" onclick="return false;" style="display: contents;"><i class="fas fa-fw fa-toolbox"></i></a> <a class="p-0 m-0" href="{{ LaravelLocalization::localizeUrl('debug') }}" onclick="return false;" style="display: contents;"><i class="fas fa-fw fa-toolbox"></i></a>

View File

@ -32,6 +32,12 @@
</table> </table>
</td> </td>
</tr> </tr>
<!-- Schema DN -->
<tr>
<td>Schema DN</td>
<td>{{ \App\Ldap\Entry::schemaDN() }}</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@ -0,0 +1,78 @@
@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-right align-text-top p-0 pt-2"><strong>{{ \App\Ldap\Entry::schemaDN() }}</strong><br><small>{{ $o->entryuuid[0] ?? '' }}</small></td>
</tr>
</table>
@endsection
@section('main-content')
<div class="main-card mb-3 card">
<div class="card-body"><h5 class="card-title">{{ __('Schema Information') }}</h5>
<ul class="nav nav-tabs">
<li class="nav-item"><a data-toggle="tab" href="#objectclasses" class="nav-link">{{ __('Object Classes') }}</a></li>
<li class="nav-item"><a data-toggle="tab" href="#attributetypes" class="nav-link">{{ __('Attribute Types') }}</a></li>
<li class="nav-item"><a data-toggle="tab" href="#ldapsyntaxes" class="nav-link">{{ __('Syntaxes') }}</a></li>
<li class="nav-item"><a data-toggle="tab" href="#matchingrules" class="nav-link">{{ __('Matching Rules') }}</a></li>
</ul>
<div class="tab-content">
<!-- Object Classes -->
<div class="tab-pane" id="objectclasses" role="tabpanel">
<div id="schema.objectclasses"><i class="fas fa-fw fa-spinner fa-pulse"></i></div>
</div>
<!-- Attribute Types -->
<div class="tab-pane" id="attributetypes" role="tabpanel">
<div id="schema.attributetypes"><i class="fas fa-fw fa-spinner fa-pulse"></i></div>
</div>
<!-- Syntaxes -->
<div class="tab-pane" id="ldapsyntaxes" role="tabpanel">
<div id="schema.ldapsyntaxes"><i class="fas fa-fw fa-spinner fa-pulse"></i></div>
</div>
<!-- Matching Rules -->
<div class="tab-pane" id="matchingrules" role="tabpanel">
<div id="schema.matchingrules"><i class="fas fa-fw fa-spinner fa-pulse"></i></div>
</div>
</div>
</div>
</div>
@endsection
@section('page-scripts')
<script type="text/javascript">
var loaded = [];
$(document).ready(function() {
$('a[data-toggle="tab"]').on('shown.bs.tab', function (item) {
// activated tab
var type = $(item.target).attr('href').substring(1);
if (loaded[type])
return false;
$.ajax({
url: '{{ url('api/schema/view') }}',
method: 'POST',
data: { type: type },
dataType: 'html',
}).done(function(html) {
$('div[id="schema.'+type+'"]').empty().append(html);
loaded[type] = true;
}).fail(function() {
alert('Failed');
});
item.stopPropagation();
});
// Open our objectclasses tab automatically
$('.nav-item a[href="#objectclasses"]').tab('show');
});
</script>
@append

View File

@ -0,0 +1,153 @@
<div class="row">
<div class="col-12 col-xl-3">
<select id="attributetype" class="form-control">
<option value="-all-">-all-</option>
@foreach ($attributetypes as $o)
<option value="{{ $o->name_lc }}">{{ $o->name }}</option>
@endforeach
</select>
</div>
<div class="col-12 col-xl-9">
@foreach ($attributetypes as $o)
<span id="at-{{ $o->name_lc }}">
<table class="schema table table-sm table-bordered table-striped">
<thead>
<tr>
<th class="table-dark" colspan="2">{{ $o->name }}<span class="float-right"><abbr title="{{ $o->line }}"><i class="fas fa-fw fa-file-contract"></i></abbr></span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="w-25">{{ __('Description') }}</td><td><strong>{{ __($o->description ?: '(no description)') }}</strong></td>
</tr>
<tr>
<td><abbr title="{{ __('Object Identifier') }}">OID</abbr></td><td><strong>{{ $o->oid }}</strong></td>
</tr>
<tr>
<td>{{ __('Obsolete') }}</td><td><strong>{{ $o->is_obsolete ? __('Yes') : __('No') }}</strong></td>
</tr>
<tr>
<td>{{ __('Inherits from') }}</td>
<td><strong>@if ($o->sup_attribute)<a class="attributetype" id="{{ strtolower($o->sup_attribute) }}" href="#{{ strtolower($o->sup_attribute) }}">{{ $o->sup_attribute }}</a>@else {{ __('(none)') }}@endif</strong></td>
</tr>
<tr>
<td>{{ __('Parent to') }}</td>
<td>
<strong>
@if (! $o->children->count())
{{ __('(none)') }}
@else
@foreach ($o->children->sort() as $child)
@if($loop->index)</strong> <strong>@endif
<a class="attributetype" id="{{ strtolower($child) }}" href="#{{ strtolower($child) }}">{{ $child }}</a>
@endforeach
@endif
</strong>
</td>
</tr>
<tr>
<td>{{ __('Equality') }}</td><td><strong>{{ $o->equality ?: __('(not specified)') }}</strong></td>
</tr>
<tr>
<td>{{ __('Ordering') }}</td><td><strong>{{ $o->ordering ?: __('(not specified)') }}</strong></td>
</tr>
<tr>
<td>{{ __('Substring Rule') }}</td><td><strong>{{ $o->sub_str_rule ?: __('(not specified)') }}</strong></td>
</tr>
<tr>
<td>{{ __('Syntax') }}</td><td><strong>{{ ($o->syntax_oid && $x=$server->schemaSyntaxName($o->syntax_oid)) ? $x->description : __('(unknown syntax)') }} @if($o->syntax_oid)({{ $o->syntax_oid }})@endif</strong></td>
</tr>
<tr>
<td>{{ __('Single Valued') }}</td><td><strong>{{ $o->is_single_value ? __('Yes') : __('No') }}</strong></td>
</tr>
<tr>
<td>{{ __('Collective') }}</td><td><strong>{{ $o->is_collective ? __('Yes') : __('No') }}</strong></td>
</tr>
<tr>
<td>{{ __('User Modification') }}</td><td><strong>{{ $o->is_no_user_modification ? __('Yes') : __('No') }}</strong></td>
</tr>
<tr>
<td>{{ __('Usage') }}</td><td><strong>{{ $o->usage ?: __('(not specified)') }}</strong></td>
</tr>
<tr>
<td>{{ __('Maximum Length') }}</td><td><strong>{{ is_null($o->max_length) ? __('(not applicable)') : sprintf('%s %s',number_format($o->max_length),Str::plural('character',$o->max_length)) }}</strong></td>
</tr>
<tr>
<td>{{ __('Aliases') }}</td>
<td><strong>
@if ($o->aliases->count())
@foreach ($o->aliases as $alias)
@if ($loop->index)</strong> <strong>@endif
<a class="attributetype" id="{{ strtolower($alias) }}" href="#{{ strtolower($alias) }}">{{ $alias }}</a>
@endforeach
@else
{{ __('(none)') }}
@endif
</strong></td>
</tr>
<tr>
<td>{{ __('Used by ObjectClasses') }}</td>
<td><strong>
@if ($o->used_in_object_classes->count())
@foreach ($o->used_in_object_classes as $class)
@if ($loop->index)</strong> <strong>@endif
<a class="objectclass" id="{{ strtolower($class) }}" href="#{{ strtolower($class) }}">{{ $class }}</a>
@endforeach
@else
{{ __('(none)') }}
@endif
</strong></td>
</tr>
<tr>
<td>{{ __('Force as MAY by config') }}</td><td><strong>{{ $o->forced_as_may ? __('Yes') : __('No') }}</strong></td>
</tr>
</tbody>
</table>
</span>
@endforeach
</div>
</div>
<script type="text/javascript">
$(document).ready(function() {
<!-- Links to object class -->
$('.objectclass')
.on('click',function(item) {
$('.nav-item a[href="#objectclasses"]').tab('show');
$('#objectclass').val(item.target.id).trigger('change');
});
<!-- Handle our parent to/inherits from fields -->
$('.attributetype')
.on('click',function(item) {
$('.nav-item a[href="#attributetypes"]').tab('show');
$('#attributetype').val(item.target.id).trigger('change');
return false;
});
<!-- Handle our select list -->
$('#attributetype')
.select2({width: '100%'})
.on('change',function(item) {
if (item.target.value === '-all-') {
$('#attributetypes span').each(function() { $(this).show(); });
} else {
$('#attributetypes span').each(function() {
if ($(this)[0].id.match(/select2/) || (! $(this)[0].id))
return;
if ('at-'+item.target.value === $(this)[0].id)
$(this).show();
else
$(this).hide();
});
}
});
});
</script>

View File

@ -0,0 +1,29 @@
<div class="row">
<div class="col-5 m-auto">
<table class="schema table table-sm table-bordered table-striped">
<thead>
<tr>
<th class="table-dark">{{ __('Description') }}</th>
<th class="table-dark">OID</th>
</tr>
</thead>
<tbody>
@foreach ($ldapsyntaxes as $o)
<tr>
<td>
<abbr title="{{ $o->line }}">{{ $o->description }}</abbr>
@if ($o->binary_transfer_required)
<span class="float-right"><i class="fas fa-fw fa-file-download"></i></span>
@endif
@if ($o->is_not_human_readable)
<span class="float-right"><i class="fas fa-fw fa-tools"></i></span>
@endif
</td>
<td>{{ $o->oid }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>

View File

@ -0,0 +1,101 @@
<div class="row">
<div class="col-12 col-xl-3">
<select id="matchingrule" class="form-control">
<option value="-all-">-all-</option>
@foreach ($matchingrules as $o)
<option value="{{ $o->name_lc }}">{{ $o->name }}</option>
@endforeach
</select>
</div>
<div class="col-12 col-xl-9">
@foreach ($matchingrules as $o)
<span id="mr-{{ $o->name_lc }}">
<table class="schema table table-sm table-bordered table-striped">
<thead>
<tr>
<th class="table-dark" colspan="2">{{ $o->name }}<span class="float-right"><abbr title="{{ $o->line }}"><i class="fas fa-fw fa-file-contract"></i></abbr></span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="w-25">{{ __('Description') }}</td><td><strong>{{ __($o->description ?: '(no description)') }}</strong></td>
</tr>
<tr>
<td class="w-25"><abbr title="{{ __('Object Identifier') }}">OID</abbr></td><td><strong>{{ $o->oid }}</strong></td>
</tr>
<tr>
<td class="w-25">{{ __('Syntax') }}</td><td><strong>{{ $o->syntax }}</strong></td>
</tr>
<tr>
<td>{{ __('Used by Attributes') }}</td>
<td>
<strong>
@if ($o->used_by_attrs->count() === 0)
{{ __('(none)') }}
@else
@foreach ($o->used_by_attrs as $attr)
@if($loop->index)</strong> <strong>@endif
<a class="attributetype" id="{{ strtolower($attr) }}" href="#at-{{ strtolower($attr) }}">{{ $attr }}</a>
@endforeach
@endif
</strong>
</td>
</tr>
</tbody>
</table>
</span>
@endforeach
</div>
</div>
<script type="text/javascript">
function hl_attribute(item,count) {
if ((count < 50) && (! loaded['attributetypes'])) {
setTimeout(hl_attribute,250,item,++count);
} else if (count >= 50) {
return false;
} else {
$('#attributetype').val(item.target.id).trigger('change');
return true;
}
}
$(document).ready(function() {
$('.attributetype')
.on('click',function(item) {
$('.nav-item a[href="#attributetypes"]').tab('show');
return hl_attribute(item,0);
});
<!-- Handle our parent to/inherits from fields -->
$('.matchingrule')
.on('click',function(item) {
$('#matchingrule').val(item.target.id).trigger('change');
return false;
});
<!-- Handle our select list -->
$('#matchingrule')
.select2({width: '100%'})
.on('change',function(item) {
if (item.target.value === '-all-') {
$('#matchingrules span').each(function() { $(this).show(); });
} else {
$('#matchingrules span').each(function() {
if ($(this)[0].id.match(/select2/) || (! $(this)[0].id))
return;
if ('mr-'+item.target.value === $(this)[0].id)
$(this).show();
else
$(this).hide();
});
}
});
});
</script>

View File

@ -0,0 +1,147 @@
<div class="row">
<div class="col-12 col-xl-3">
<select id="objectclass" class="form-control">
<option value="-all-">-all-</option>
@foreach ($objectclasses as $o)
<option value="{{ $o->name_lc }}">{{ $o->name }}</option>
@endforeach
</select>
</div>
<div class="col-12 col-xl-9">
@foreach ($objectclasses as $o)
<span id="oc-{{ $o->name_lc }}">
<table class="schema table table-sm table-bordered table-striped">
<thead>
<tr>
<th class="table-dark" colspan="4">{{ $o->name }}<span class="float-right"><abbr title="{{ $o->line }}"><i class="fas fa-fw fa-file-contract"></i></abbr></span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="w-25">{{ __('Description') }}</td><td colspan="3"><strong>{{ __($o->description ?: '(no description)') }}</strong></td>
</tr>
<tr>
<td class="w-25"><abbr title="{{ __('Object Identifier') }}">OID</abbr></td><td colspan="3"><strong>{{ $o->oid }}</strong></td>
</tr>
<tr>
<td>{{ __('Type') }}</td><td colspan="3"><strong>{{ __($o->type_name) }}</strong></td>
</tr>
<tr>
<td>{{ __('Inherits from') }}</td>
<td colspan="3">
<strong>
@if ($o->sup->count() === 0)
{{ __('(none)') }}
@else
@foreach ($o->sup as $sup)
@if($loop->index)</strong> <strong>@endif
<a class="objectclass" id="{{ strtolower($sup) }}" href="#{{ strtolower($sup) }}">{{ $sup }}</a>
@endforeach
@endif
</strong>
</td>
</tr>
<tr>
<td>{{ __('Parent to') }}</td>
<td colspan="3">
<strong>
@if (strtolower($o->name) === 'top')
<a class="objectclass" id="-all-">(all)</a>
@elseif (! $o->getChildObjectClasses()->count())
{{ __('(none)') }}
@else
@foreach ($o->getChildObjectClasses() as $childoc)
@if($loop->index)</strong> <strong>@endif
<a class="objectclass" id="{{ strtolower($childoc) }}" href="#{{ strtolower($childoc) }}">{{ $childoc }}</a>
@endforeach
@endif
</strong>
</td>
</tr>
<tr>
<td class="align-top w-50" colspan="2">
<table class="clearfix table table-sm table-borderless">
<thead>
<tr>
<th class="table-primary">{{ __('Required Attributes') }}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<ul class="pl-3" style="list-style-type: square;">
@foreach ($o->getMustAttrs(TRUE) as $oo)
<li>{{ $oo->name }} @if($oo->source !== $o->name)[<strong><a class="objectclass" id="{{ strtolower($oo->source) }}" href="#{{ strtolower($oo->source) }}">{{ $oo->source }}</a></strong>]@endif</li>
@endforeach
</ul>
</td>
</tr>
</tbody>
</table>
</td>
<td class="align-top w-50" colspan="2">
<table class="clearfix table table-sm table-borderless">
<thead>
<tr>
<th class="table-primary">{{ __('Optional Attributes') }}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<ul class="pl-3" style="list-style-type: square;">
@foreach ($o->getMayAttrs(TRUE) as $oo)
<li>{{ $oo->name }} @if($oo->source !== $o->name)[<strong><a class="objectclass" id="{{ strtolower($oo->source) }}" href="#{{ strtolower($oo->source) }}">{{ $oo->source }}</a></strong>]@endif</li>
@endforeach
</ul>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</span>
@endforeach
</div>
</div>
<script type="text/javascript">
$(document).ready(function() {
<!-- Handle our parent to/inherits from fields -->
$('.objectclass')
.on('click',function(item) {
$('#objectclass').val(item.target.id).trigger('change');
return false;
});
<!-- Handle our select list -->
$('#objectclass')
.select2({width: '100%'})
.on('change',function(item) {
if (item.target.value === '-all-') {
$('#objectclasses span').each(function() { $(this).show(); });
} else {
$('#objectclasses span').each(function() {
if ($(this)[0].id.match(/select2/) || (! $(this)[0].id))
return;
if ('oc-'+item.target.value === $(this)[0].id)
$(this).show();
else
$(this).hide();
});
}
});
});
</script>

View File

@ -50,7 +50,7 @@
@endsection @endsection
@section('page-scripts') @section('page-scripts')
<script> <script type="text/javascript">
var basedn = {!! $bases->toJson() !!}; var basedn = {!! $bases->toJson() !!};
</script> </script>
@append @append

View File

@ -8,4 +8,6 @@
<!-- Your Page Content Here --> <!-- Your Page Content Here -->
@yield('main-content') @yield('main-content')
</div> </div>
</div> </div>
@yield('page-scripts')

View File

@ -16,8 +16,9 @@ use App\Http\Controllers\APIController;
*/ */
Route::group([],function() { Route::group([],function() {
Route::get('/bases',[APIController::class,'bases']); Route::get('bases',[APIController::class,'bases']);
Route::get('/children',[APIController::class,'children']); Route::get('children',[APIController::class,'children']);
Route::post('schema/view',[APIController::class,'schema_view']);
}); });
Route::group(['middleware'=>'auth:api','prefix'=>'user'],function() { Route::group(['middleware'=>'auth:api','prefix'=>'user'],function() {

View File

@ -30,6 +30,7 @@ Route::group(['prefix' => LaravelLocalization::setLocale()], function() {
Route::get('info',[HomeController::class,'info']); Route::get('info',[HomeController::class,'info']);
Route::post('dn',[HomeController::class,'dn_frame']); Route::post('dn',[HomeController::class,'dn_frame']);
Route::get('debug',[HomeController::class,'debug']); Route::get('debug',[HomeController::class,'debug']);
Route::get('schema',[HomeController::class,'schema_frame']);
}); });
Route::get('logout',[LoginController::class,'logout']); Route::get('logout',[LoginController::class,'logout']);