attributetypes; case 'ldapsyntaxes': return $this->ldapsyntaxes; case 'matchingrules': return $this->matchingrules; case 'objectclasses': return $this->objectclasses; default: throw new Exception('Unknown key:'.$key); } } /** * Query the server for a DN and return its children and if those children have children. * * @param string $dn * @return Collection|null */ public function children(string $dn): ?Collection { return ($x=(new Entry) ->query() ->cache(Carbon::now()->addSeconds(Config::get('ldap.cache.time'))) ->select(['*','hassubordinates']) ->setDn($dn) ->listing() ->get()) ? $x : NULL; } /** * Fetch a DN from the server * * @param string $dn * @param array $attrs * @return Entry|null */ public function fetch(string $dn,array $attrs=['*','+']): ?Entry { return ($x=(new Entry) ->query() ->cache(Carbon::now()->addSeconds(Config::get('ldap.cache.time'))) ->select($attrs) ->find($dn)) ? $x : NULL; } /** * Given an LDAP OID number, returns a verbose description of the OID. * This function parses ldap_supported_oids.txt and looks up the specified * OID, and returns the verbose message defined in that file. * * * Array ( * [title] => All Operational Attribute * [ref] => RFC 3673 * [desc] => An LDAP extension which clients may use to request the return of all operational attributes. * ) * * * @param string $oid The OID number (ie, "1.3.6.1.4.1.4203.1.5.1") of the OID of interest. * @param string $key The title|ref|desc to return * @return string|null * @testedby TranslateOidTest::testRootDSE() */ public static function getOID(string $oid,string $key): ?string { $oids = Cache::remember('oids',86400,function() { try { $f = fopen(config_path('ldap_supported_oids.txt'),'r'); } catch (Exception $e) { return NULL; } $result = collect(); while (! feof($f)) { $line = trim(fgets($f)); if (! $line OR preg_match('/^#/',$line)) continue; $fields = explode(':',$line); $result->put(Arr::get($fields,0),[ 'title'=>Arr::get($fields,1), 'ref'=>Arr::get($fields,2), 'desc'=>Arr::get($fields,3), ]); } fclose($f); return $result; }); return Arr::get( ($oids ? $oids->get($oid) : []), $key, ($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); $result = Cache::remember('schema'.$item,config('ldap.cache.time'),function() use ($item) { // First pass if we have already retrieved the schema item switch ($item) { case 'attributetypes': if (isset($this->attributetypes)) return $this->attributetypes; else $this->attributetypes = collect(); break; case 'ldapsyntaxes': if (isset($this->ldapsyntaxes)) return $this->ldapsyntaxes; else $this->ldapsyntaxes = collect(); break; case 'matchingrules': if (isset($this->matchingrules)) return $this->matchingrules; 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 $this->objectclasses; 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 $this->attributetypes; 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 $this->ldapsyntaxes; 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 $this->matchingrules; case 'objectclasses': Log::debug('Object Classes'); foreach ($schema->{$item} as $line) { if (is_null($line) || ! strlen($line)) continue; $o = new ObjectClass($line,$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 $this->objectclasses; } }); return is_null($key) ? $result : $result->get($key); } public function schemaSyntaxName(string $oid): ?LDAPSyntax { return $this->schema('ldapsyntaxes',$oid); } }