Move some server function to Server::class (from Entry::class)

This commit is contained in:
Deon George 2023-02-19 16:35:07 +11:00
parent 92e5afd614
commit 4f9accbadf
11 changed files with 258 additions and 227 deletions

View File

@ -1 +1 @@
GIT

View File

@ -5,11 +5,14 @@ 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\Collection;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use LdapRecord\Query\Collection; use LdapRecord\LdapRecordException;
use LdapRecord\Models\Model;
use LdapRecord\Query\Collection as LDAPCollection;
use LdapRecord\Query\ObjectNotFoundException;
use App\Classes\LDAP\Schema\{AttributeType,Base,LDAPSyntax,MatchingRule,MatchingRuleUse,ObjectClass}; use App\Classes\LDAP\Schema\{AttributeType,Base,LDAPSyntax,MatchingRule,MatchingRuleUse,ObjectClass};
use App\Exceptions\InvalidUsage; use App\Exceptions\InvalidUsage;
@ -18,11 +21,11 @@ use App\Ldap\Entry;
class Server class Server
{ {
// This servers schema objectclasses // This servers schema objectclasses
private ArrayCollection $attributetypes; private Collection $attributetypes;
private ArrayCollection $ldapsyntaxes; private Collection $ldapsyntaxes;
private ArrayCollection $matchingrules; private Collection $matchingrules;
private ArrayCollection $matchingruleuse; private Collection $matchingruleuse;
private ArrayCollection $objectclasses; private Collection $objectclasses;
// Valid items that can be fetched // Valid items that can be fetched
public const schema_types = [ public const schema_types = [
@ -45,13 +48,187 @@ class Server
} }
} }
/* STATIC METHODS */
/**
* Gets the root DN of the specified LDAPServer, or throws an exception if it
* can't find it.
*
* @param null $connection Return a collection of baseDNs
* @param bool $objects Return a collection of Entry Models
* @return Collection
* @throws ObjectNotFoundException
* @testedin GetBaseDNTest::testBaseDNExists();
*/
public static function baseDNs($connection=NULL,bool $objects=TRUE): Collection
{
$cachetime = Carbon::now()->addSeconds(Config::get('ldap.cache.time'));
try {
$base = self::rootDSE($connection,$cachetime);
/**
* LDAP Error Codes:
* https://ldap.com/ldap-result-code-reference/
* + success 0
* + operationsError 1
* + protocolError 2
* + timeLimitExceeded 3
* + sizeLimitExceeded 4
* + compareFalse 5
* + compareTrue 6
* + authMethodNotSupported 7
* + strongerAuthRequired 8
* + referral 10
* + adminLimitExceeded 11
* + unavailableCriticalExtension 12
* + confidentialityRequired 13
* + saslBindInProgress 14
* + noSuchAttribute 16
* + undefinedAttributeType 17
* + inappropriateMatching 18
* + constraintViolation 19
* + attributeOrValueExists 20
* + invalidAttributeSyntax 21
* + noSuchObject 32
* + aliasProblem 33
* + invalidDNSyntax 34
* + isLeaf 35
* + aliasDereferencingProblem 36
* + inappropriateAuthentication 48
* + invalidCredentials 49
* + insufficientAccessRights 50
* + busy 51
* + unavailable 52
* + unwillingToPerform 53
* + loopDetect 54
* + sortControlMissing 60
* + offsetRangeError 61
* + namingViolation 64
* + objectClassViolation 65
* + notAllowedOnNonLeaf 66
* + notAllowedOnRDN 67
* + entryAlreadyExists 68
* + objectClassModsProhibited 69
* + resultsTooLarge 70
* + affectsMultipleDSAs 71
* + virtualListViewError or controlError 76
* + other 80
* + serverDown 81
* + localError 82
* + encodingError 83
* + decodingError 84
* + timeout 85
* + authUnknown 86
* + filterError 87
* + userCanceled 88
* + paramError 89
* + noMemory 90
* + connectError 91
* + notSupported 92
* + controlNotFound 93
* + noResultsReturned 94
* + moreResultsToReturn 95
* + clientLoop 96
* + referralLimitExceeded 97
* + invalidResponse 100
* + ambiguousResponse 101
* + tlsNotSupported 112
* + intermediateResponse 113
* + unknownType 114
* + canceled 118
* + noSuchOperation 119
* + tooLate 120
* + cannotCancel 121
* + assertionFailed 122
* + authorizationDenied 123
* + e-syncRefreshRequired 4096
* + noOperation 16654
*
* LDAP Tag Codes:
* + A client bind operation 97
* + The entry for which you were searching 100
* + The result from a search operation 101
* + The result from a modify operation 103
* + The result from an add operation 105
* + The result from a delete operation 107
* + The result from a modify DN operation 109
* + The result from a compare operation 111
* + A search reference when the entry you perform your search on holds a referral to the entry you require.
* + Search references are expressed in terms of a referral.
* 115
* + A result from an extended operation 120
*/
// If we cannot get to our LDAP server we'll head straight to the error page
} catch (LdapRecordException $e) {
switch ($e->getDetailedError()->getErrorCode()) {
case 49:
abort(401,$e->getDetailedError()->getErrorMessage());
default:
abort(597,$e->getDetailedError()->getErrorMessage());
}
}
if (! $objects)
return collect($base->namingcontexts);
/**
* @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).
* IE: If we have 5 baseDNs, it takes 5 calls to this function to case them all.
* @todo Possibly a bug wtih ldaprecord, so need to investigate
*/
$result = collect();
foreach ($base->namingcontexts as $dn) {
$result->push((new Entry)->cache($cachetime)->findOrFail($dn));
}
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
{
$e = new Entry;
return Entry::on($connection ?? $e->getConnectionName())
->cache($cachetime)
->in(NULL)
->read()
->select(['+'])
->whereHas('objectclass')
->firstOrFail();
}
/**
* Get the Schema DN
*
* @param $connection
* @return string
* @throws ObjectNotFoundException
*/
public static function schemaDN($connection=NULL): string
{
$cachetime = Carbon::now()->addSeconds(Config::get('ldap.cache.time'));
return collect(self::rootDSE($connection,$cachetime)->subschemasubentry)->first();
}
/** /**
* Query the server for a DN and return its 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 Collection|null * @return LDAPCollection|NULL
*/ */
public function children(string $dn): ?Collection public function children(string $dn): ?LDAPCollection
{ {
return ($x=(new Entry) return ($x=(new Entry)
->query() ->query()
@ -150,10 +327,10 @@ class Server
* *
* @param string $item Schema Item to Fetch * @param string $item Schema Item to Fetch
* @param string|null $key * @param string|null $key
* @return ArrayCollection|Base * @return Collection|Base|NULL
* @throws InvalidUsage * @throws InvalidUsage
*/ */
public function schema(string $item,string $key=NULL): ArrayCollection|Base|NULL public function schema(string $item,string $key=NULL): Collection|Base|NULL
{ {
// Ensure our item to fetch is lower case // Ensure our item to fetch is lower case
$item = strtolower($item); $item = strtolower($item);
@ -215,8 +392,8 @@ class Server
} }
// Try to get the schema DN from the specified entry. // Try to get the schema DN from the specified entry.
$schema_dn = Entry::schemaDN(); $schema_dn = $this->schemaDN();
$schema = (new Server)->fetch($schema_dn); $schema = $this->fetch($schema_dn);
switch ($item) { switch ($item) {
case 'attributetypes': case 'attributetypes':

View File

@ -10,8 +10,8 @@ use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use LdapRecord\Query\ObjectNotFoundException; use LdapRecord\Query\ObjectNotFoundException;
use App\Ldap\Entry;
use App\Classes\LDAP\Server; use App\Classes\LDAP\Server;
use App\Exceptions\InvalidUsage;
class HomeController extends Controller class HomeController extends Controller
{ {
@ -30,7 +30,7 @@ class HomeController extends Controller
*/ */
public function home() public function home()
{ {
$base = (new Entry)->baseDNs() ?: collect(); $base = Server::baseDNs() ?: collect();
return view('home') return view('home')
->with('server',config('ldap.connections.default.name')) ->with('server',config('ldap.connections.default.name'))
@ -53,34 +53,13 @@ class HomeController extends Controller
*/ */
public function info() public function info()
{ {
$root = (new Entry)->rootDSE(); // Load our attributes
$s = new Server;
$s->schema('objectclasses');
$s->schema('attributetypes');
try { return view('frames.info')
$attrs = collect($root->getAttributes()) ->with('s',$s);
->transform(function($item) {
foreach ($item as $k=>$v) {
if (preg_match('/[0-9]+\.[0-9]+\.[0-9]+/',$v)) {
$format = sprintf(
'<abbr class="pb-1" title="%s"><i class="fas fa-list-ol pr-2"></i>%s</abbr>%s<p class="mb-0">%s</p>',
$v,
Server::getOID($v,'title'),
($x=Server::getOID($v,'ref')) ? sprintf('<abbr class="pl-2" title="%s"><i class="fas fa-comment-dots"></i></abbr>',$x) : '',
Server::getOID($v,'desc'),
);
$item[$k] = $format;
}
}
return $item;
});
// @todo If we cant get server info, we should probably show a nice error dialog
} catch (ObjectNotFoundException $e) {
$attrs = collect();
}
return view('frames.dn')
->with('o',$root)
->with('dn',__('Server Info'));
} }
/** /**
@ -98,9 +77,25 @@ class HomeController extends Controller
->with('dn',$dn); ->with('dn',$dn);
} }
public function schema_frame() /**
* Show the Schema Viewer
*
* @note Our route will validate that types are valid.
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
* @throws InvalidUsage
*/
public function schema_frame(Request $request)
{ {
return view('frames.schema'); $s = new Server;
// If an invalid key, we'll 404
if ($request->type && $request->key && ($s->schema($request->type)->has($request->key) === FALSE))
abort(404);
return view('frames.schema')
->with('type',$request->type)
->with('key',$request->key);
} }
/** /**

View File

@ -2,13 +2,8 @@
namespace App\Ldap; namespace App\Ldap;
use Carbon\Carbon;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Config;
use LdapRecord\LdapRecordException;
use LdapRecord\Models\Model; use LdapRecord\Models\Model;
use LdapRecord\Query\ObjectNotFoundException;
use App\Classes\LDAP\Attribute\Factory; use App\Classes\LDAP\Attribute\Factory;
@ -26,171 +21,6 @@ class Entry extends Model
return $result->toArray(); return $result->toArray();
} }
/* STATIC METHODS */
/**
* Gets the root DN of the specified LDAPServer, or throws an exception if it
* can't find it.
*
* @param null $connection Return a collection of baseDNs
* @param bool $objects Return a collection of Entry Models
* @return Collection
* @throws ObjectNotFoundException
* @testedin GetBaseDNTest::testBaseDNExists();
*/
public static function baseDNs($connection=NULL,bool $objects=TRUE): Collection
{
$cachetime = Carbon::now()->addSeconds(Config::get('ldap.cache.time'));
try {
$base = self::rootDSE($connection,$cachetime);
/**
* LDAP Error Codes:
* https://ldap.com/ldap-result-code-reference/
* + success 0
* + operationsError 1
* + protocolError 2
* + timeLimitExceeded 3
* + sizeLimitExceeded 4
* + compareFalse 5
* + compareTrue 6
* + authMethodNotSupported 7
* + strongerAuthRequired 8
* + referral 10
* + adminLimitExceeded 11
* + unavailableCriticalExtension 12
* + confidentialityRequired 13
* + saslBindInProgress 14
* + noSuchAttribute 16
* + undefinedAttributeType 17
* + inappropriateMatching 18
* + constraintViolation 19
* + attributeOrValueExists 20
* + invalidAttributeSyntax 21
* + noSuchObject 32
* + aliasProblem 33
* + invalidDNSyntax 34
* + isLeaf 35
* + aliasDereferencingProblem 36
* + inappropriateAuthentication 48
* + invalidCredentials 49
* + insufficientAccessRights 50
* + busy 51
* + unavailable 52
* + unwillingToPerform 53
* + loopDetect 54
* + sortControlMissing 60
* + offsetRangeError 61
* + namingViolation 64
* + objectClassViolation 65
* + notAllowedOnNonLeaf 66
* + notAllowedOnRDN 67
* + entryAlreadyExists 68
* + objectClassModsProhibited 69
* + resultsTooLarge 70
* + affectsMultipleDSAs 71
* + virtualListViewError or controlError 76
* + other 80
* + serverDown 81
* + localError 82
* + encodingError 83
* + decodingError 84
* + timeout 85
* + authUnknown 86
* + filterError 87
* + userCanceled 88
* + paramError 89
* + noMemory 90
* + connectError 91
* + notSupported 92
* + controlNotFound 93
* + noResultsReturned 94
* + moreResultsToReturn 95
* + clientLoop 96
* + referralLimitExceeded 97
* + invalidResponse 100
* + ambiguousResponse 101
* + tlsNotSupported 112
* + intermediateResponse 113
* + unknownType 114
* + canceled 118
* + noSuchOperation 119
* + tooLate 120
* + cannotCancel 121
* + assertionFailed 122
* + authorizationDenied 123
* + e-syncRefreshRequired 4096
* + noOperation 16654
*
* LDAP Tag Codes:
* + A client bind operation 97
* + The entry for which you were searching 100
* + The result from a search operation 101
* + The result from a modify operation 103
* + The result from an add operation 105
* + The result from a delete operation 107
* + The result from a modify DN operation 109
* + The result from a compare operation 111
* + A search reference when the entry you perform your search on holds a referral to the entry you require.
* + Search references are expressed in terms of a referral.
* 115
* + A result from an extended operation 120
*/
// If we cannot get to our LDAP server we'll head straight to the error page
} catch (LdapRecordException $e) {
switch ($e->getDetailedError()->getErrorCode()) {
case 49:
abort(401,$e->getDetailedError()->getErrorMessage());
default:
abort(597,$e->getDetailedError()->getErrorMessage());
}
}
if (! $objects)
return collect($base->namingcontexts);
/**
* @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).
* IE: If we have 5 baseDNs, it takes 5 calls to this function to case them all.
* @todo Possibly a bug wtih ldaprecord, so need to investigate
*/
$result = collect();
foreach ($base->namingcontexts as $dn) {
$result->push((new self)->cache($cachetime)->findOrFail($dn));
}
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 */
/** /**

View File

@ -8,6 +8,8 @@ use LdapRecord\Laravel\Events\Auth\DiscoveredWithCredentials;
use LdapRecord\Laravel\LdapUserRepository as LdapUserRepositoryBase; use LdapRecord\Laravel\LdapUserRepository as LdapUserRepositoryBase;
use LdapRecord\Models\Model; use LdapRecord\Models\Model;
use App\Classes\LDAP\Server;
class LdapUserRepository extends LdapUserRepositoryBase class LdapUserRepository extends LdapUserRepositoryBase
{ {
/** /**
@ -25,7 +27,7 @@ class LdapUserRepository extends LdapUserRepositoryBase
} }
// Look for a user using all our baseDNs // Look for a user using all our baseDNs
foreach ((new Entry)->baseDNs() as $base) { foreach (Server::baseDNs() as $base) {
$query = $this->query()->setBaseDn($base); $query = $this->query()->setBaseDn($base);
foreach ($credentials as $key => $value) { foreach ($credentials as $key => $value) {
@ -61,7 +63,7 @@ class LdapUserRepository extends LdapUserRepositoryBase
public function findByGuid($guid): ?Model public function findByGuid($guid): ?Model
{ {
// Look for a user using all our baseDNs // Look for a user using all our baseDNs
foreach ((new Entry)->baseDNs() as $base) { foreach (Server::baseDNs() as $base) {
$user = $this->query()->setBaseDn($base)->findByGuid($guid); $user = $this->query()->setBaseDn($base)->findByGuid($guid);
if ($user) if ($user)

View File

@ -24,7 +24,7 @@
<td>BaseDN(s)</td> <td>BaseDN(s)</td>
<td> <td>
<table class="table table-sm table-borderless"> <table class="table table-sm table-borderless">
@foreach(\App\Ldap\Entry::baseDNs()->sort(function($item) { return $item->sortKey; }) as $item) @foreach(\App\Classes\LDAP\Server::baseDNs()->sort(function($item) { return $item->sortKey; }) as $item)
<tr> <tr>
<td class="pl-0">{{ $item->getDn() }}</td> <td class="pl-0">{{ $item->getDn() }}</td>
</tr> </tr>
@ -36,7 +36,7 @@
<!-- Schema DN --> <!-- Schema DN -->
<tr> <tr>
<td>Schema DN</td> <td>Schema DN</td>
<td>{{ \App\Ldap\Entry::schemaDN() }}</td> <td>{{ \App\Classes\LDAP\Server::schemaDN() }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -14,7 +14,7 @@
<table class="table"> <table class="table">
@foreach ($o->getAttributes() as $attribute => $value) @foreach ($o->getAttributes() as $attribute => $value)
<tr> <tr>
<th>{{ $attribute }}</th> <th class="w-25">{{ $attribute }}</th>
<td>{!! $value !!}</td> <td>{!! $value !!}</td>
</tr> </tr>
@endforeach @endforeach

View File

@ -0,0 +1,27 @@
@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-info"></i></div></td>
<td class="top text-right align-text-top p-0 pt-2 }}"><strong>{{ __('Server Info') }}</strong><br><small>{{ $s->rootDSE()->entryuuid[0] ?? '' }}</small></td>
</tr>
</table>
@endsection
@section('main-content')
<div class="bg-white p-3">
<table class="table">
@foreach ($s->rootDSE()->getAttributes() as $attribute => $value)
<tr>
<th class="w-25">
{!! ($x=$s->schema('attributetypes',$attribute))
? sprintf('<a class="attributetype" id="strtolower(%s)" href="%s">%s</a>',$x->name_lc,url('schema/attributetypes',$x->name_lc),$x->name)
: $attribute !!}
</th>
<td>{!! $value !!}</td>
</tr>
@endforeach
</table>
</div>
@endsection

View File

@ -4,7 +4,7 @@
<table class="table table-borderless"> <table class="table table-borderless">
<tr> <tr>
<td style="border-radius: 5px;"><div class="page-title-icon f32"><i class="fas fa-fingerprint"></i></div></td> <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> <td class="top text-right align-text-top p-0 pt-2"><strong>{{ \App\Classes\LDAP\Server::schemaDN() }}</strong></td>
</tr> </tr>
</table> </table>
@endsection @endsection

View File

@ -4,7 +4,7 @@ namespace Tests\Unit;
use Tests\TestCase; use Tests\TestCase;
use App\Ldap\Entry; use App\Classes\LDAP\Server;
class GetBaseDNTest extends TestCase class GetBaseDNTest extends TestCase
{ {
@ -13,11 +13,11 @@ class GetBaseDNTest extends TestCase
* *
* @return void * @return void
* @throws \LdapRecord\Query\ObjectNotFoundException * @throws \LdapRecord\Query\ObjectNotFoundException
* @covers \App\Ldap\Entry::baseDNs() * @covers \App\Classes\LDAP\Server::baseDNs()
*/ */
public function testBaseDnExists() public function testBaseDnExists()
{ {
$o = (new Entry)->baseDNs(); $o = Server::baseDNs();
$this->assertIsObject($o); $this->assertIsObject($o);
$this->assertCount(6,$o->toArray()); $this->assertCount(6,$o->toArray());

View File

@ -2,10 +2,10 @@
namespace Tests\Unit; namespace Tests\Unit;
use LdapRecord\Query\ObjectNotFoundException;
use Tests\TestCase; use Tests\TestCase;
use App\Classes\LDAP\Server; use App\Classes\LDAP\Server;
use App\Ldap\Entry;
class TranslateOidTest extends TestCase class TranslateOidTest extends TestCase
{ {
@ -13,12 +13,12 @@ class TranslateOidTest extends TestCase
* A basic feature test example. * A basic feature test example.
* *
* @return void * @return void
* @throws \LdapRecord\Query\ObjectNotFoundException
* @covers \App\Classes\LDAP\Server::getOID() * @covers \App\Classes\LDAP\Server::getOID()
* @throws ObjectNotFoundException
*/ */
public function testRootDse() public function testRootDse()
{ {
$dse = (new Entry)->rootDSE(); $dse = Server::rootDSE();
// Test our rootDSE returns an objectclass attribute // Test our rootDSE returns an objectclass attribute
$this->assertIsArray($dse->objectclass); $this->assertIsArray($dse->objectclass);