Compare commits

...

12 Commits

Author SHA1 Message Date
0a268fb653 Revert version to 2.1.2-dev
All checks were successful
Create Docker Image / Test Application (x86_64) (push) Successful in 4m23s
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 2m24s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 4m31s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2025-04-27 14:12:24 +10:00
6954b09089 @todo udpates 2025-04-27 14:12:24 +10:00
a336e58b7a Fixes for 389 Directory Server - addresses recursion issue #314. The primary issue was that 389DS doesnt render the subschemaSubentry attribute unless it is specifically requested. 2025-04-27 14:12:24 +10:00
53880121b6 Server::class optimisations, minimal functional changes - basically caching/performance improvements 2025-04-27 14:12:24 +10:00
ea46cf36d0 Remove deprecteated Entry::query() override and associated noObjectAttributes() it wasnt used 2025-04-27 14:12:24 +10:00
36f8f57b77 When opening the export modal, limit selection to inside the modal. Generally when opening modals disable selection.
When selecting a DN on a DN fragment, autoselect the whole DN.
2025-04-27 14:12:24 +10:00
3604f1498c Update existing LDAP instance configuration instead of replacing it. Caching was not enabled as per the configuration, so this fixes this. 2025-04-27 14:12:24 +10:00
808934ebfe Change we now store logged in user details in session, instead of cookies.
This is so when the session expires, the logged in user details are expired as well, which wasnt happening with cookies.
2025-04-27 14:12:24 +10:00
21a690c6dd Move our /api routes into /ajax under web.php. The /api routes werent authenticated and may not have been using the logged in users details 2025-04-27 14:12:24 +10:00
0083e9158b Move out view variables until after our session has been setup. This was needed so that auth()->user() could be resolved correctly and needed to be done after we have started the session and swapped in the users cookies 2025-04-27 14:12:24 +10:00
f4cc559931 Dynamically work out objectclasses on the current entry, this fixes usage issues between adding objectclasses and adding attribute that are now available from new objectclasses, as well as determining that they are not dynamic 2025-04-27 14:12:24 +10:00
3de46ac28e Fix when rendering changes to 2 or more attributes, the update confirmation table had one too many rowspan values for the Attribute.
Fix updating an entry by adding an new objectclass
2025-04-27 14:12:24 +10:00
35 changed files with 257 additions and 290 deletions

View File

@ -15,4 +15,4 @@ LDAP_HOST=
LDAP_BASE_DN=
LDAP_USERNAME=
LDAP_PASSWORD=
LDAP_CACHE=true
LDAP_CACHE=false

View File

@ -61,7 +61,7 @@ Support is known for these LDAP servers:
- [X] OpenLDAP
- [X] OpenDJ
- [ ] Microsoft Active Directory
- [ ] 389 Directory Server
- [X] 389 Directory Server
If there is an LDAP server that you have that you would like to have supported, please open an issue to request it.
You might need to provide access, provide a copy or instructions to get an environment for testing. If you have enabled

View File

@ -311,7 +311,7 @@ class Attribute implements \Countable, \ArrayAccess
*/
public function render(bool $edit=FALSE,bool $old=FALSE,bool $new=FALSE): View
{
$view = match ($this->schema->syntax_oid) {
$view = match ($this->schema?->syntax_oid) {
self::SYNTAX_CERTIFICATE => view('components.syntax.certificate'),
self::SYNTAX_CERTIFICATE_LIST => view('components.syntax.certificatelist'),

View File

@ -20,7 +20,7 @@ abstract class Base {
protected string $name = '';
// The OID of this schema item.
protected string $oid;
protected string $oid = '';
# The description of this schema item.
protected string $description = '';

View File

@ -43,7 +43,7 @@ final class ObjectClass extends Base
*
* @param string $line Schema Line
* @param Server $server
* @todo Change $server to $connection, no need to store the server object here
* @todo Deprecate this $server variable? It is only used for isForceMay() determination, and that might be better done elsewhere?
*/
public function __construct(string $line,Server $server)
{

View File

@ -8,11 +8,11 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Cookie;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Session;
use LdapRecord\LdapRecordException;
use LdapRecord\Models\Model;
use LdapRecord\Query\Builder;
use LdapRecord\Query\Collection as LDAPCollection;
use LdapRecord\Query\ObjectNotFoundException;
@ -22,8 +22,7 @@ use App\Ldap\Entry;
final class Server
{
// Connection information used for these object and children
private ?string $connection;
private const LOGKEY = 'SVR';
// This servers schema objectclasses
private Collection $attributetypes;
@ -37,20 +36,22 @@ final class Server
public const OC_ABSTRACT = 0x02;
public const OC_AUXILIARY = 0x03;
public function __construct(?string $connection=NULL)
public function __construct()
{
$this->connection = $connection;
$this->attributetypes = collect();
$this->ldapsyntaxes = collect();
$this->matchingrules = collect();
$this->objectclasses = collect();
}
public function __get(string $key): mixed
{
return match($key) {
'attributetypes' => $this->attributetypes,
'connection' => $this->connection,
'ldapsyntaxes' => $this->ldapsyntaxes,
'matchingrules' => $this->matchingrules,
'objectclasses' => $this->objectclasses,
'config' => config('ldap.connections.'.config('ldap.default')),
'config' => config(sprintf('ldap.connections.%s',config('ldap.default'))),
'name' => Arr::get($this->config,'name',__('No Server Name Yet')),
default => throw new Exception('Unknown key:'.$key),
};
@ -62,20 +63,14 @@ final class Server
* Gets the root DN of the specified LDAPServer, or throws an exception if it
* can't find it.
*
* @param string|null $connection Return a collection of baseDNs
* @param bool $objects Return a collection of Entry Models
* @return Collection
* @throws ObjectNotFoundException
* @testedin GetBaseDNTest::testBaseDNExists();
* @todo Need to allow for the scenario if the baseDN is not readable by ACLs
*/
public static function baseDNs(?string $connection=NULL,bool $objects=TRUE): Collection
public static function baseDNs(bool $objects=FALSE): Collection
{
$cachetime = Carbon::now()
->addSeconds(Config::get('ldap.cache.time'));
try {
$base = self::rootDSE($connection,$cachetime);
$rootdse = self::rootDSE();
/**
* LDAP Error Codes:
@ -173,16 +168,6 @@ final class Server
} catch (LdapRecordException $e) {
switch ($e->getDetailedError()?->getErrorCode()) {
case 49:
// Since we failed authentication, we should delete our auth cookie
if (Cookie::has('password_encrypt')) {
Log::alert('Clearing user credentials and logging out');
Cookie::queue(Cookie::forget('password_encrypt'));
Cookie::queue(Cookie::forget('username_encrypt'));
Session::invalidate();
}
abort(401,$e->getDetailedError()->getErrorMessage());
default:
@ -191,57 +176,100 @@ final class Server
}
if (! $objects)
return collect($base->namingcontexts);
return collect($rootdse->namingcontexts);
return Cache::remember('basedns'.Session::id(),config('ldap.cache.time'),function() use ($rootdse) {
$result = collect();
// @note: Incase our rootDSE didnt return a namingcontext, we'll have no base DNs
foreach (($rootdse->namingcontexts ?: []) as $dn)
$result->push(self::get($dn)->read()->find($dn));
return $result->filter();
});
}
/**
* @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
* Work out if we should flush the cache when retrieving an entry
*
* @param string $dn
* @return bool
* @note: We dont need to flush the cache for internal LDAP attributes, as we dont change them
*/
$result = collect();
foreach ($base->namingcontexts as $dn)
$result->push((new Entry)->cache($cachetime)->findOrFail($dn));
private static function cacheflush(string $dn): bool
{
$cache = (! config('ldap.cache.enabled'))
|| match (strtolower($dn)) {
'','cn=schema','cn=subschema' => FALSE,
default => TRUE,
};
return $result;
Log::debug(sprintf('%s:%s - %s',self::LOGKEY,$cache ? 'Caching' : 'Not Cached',$dn));
return $cache;
}
/**
* Return our cache time as per the configuration
*
* @return Carbon
*/
private static function cachetime(): Carbon
{
return Carbon::now()
->addSeconds(Config::get('ldap.cache.time'));
}
/**
* Generic Builder method to setup our queries consistently - mainly to ensure we cache results
*
* @param string $dn
* @param array $attrs
* @return Builder
*/
private static function get(string $dn,array $attrs=['*','+']): Builder
{
return Entry::query()
->setDN($dn)
->cache(
until: self::cachetime(),
flush: self::cacheflush($dn))
->select($attrs);
}
/**
* Obtain the rootDSE for the server, that gives us server information
*
* @param string|null $connection
* @param Carbon|null $cachetime
* @return Entry|null
* @return Model
* @throws ObjectNotFoundException
* @testedin TranslateOidTest::testRootDSE();
* @note While we are using a static variable for in session performance, we'll also cache the result normally
*/
public static function rootDSE(?string $connection=NULL,?Carbon $cachetime=NULL): ?Model
public static function rootDSE(): Model
{
$e = new Entry;
static $rootdse = NULL;
return Entry::on($connection ?? $e->getConnectionName())
->cache($cachetime)
->in(NULL)
if (is_null($rootdse))
$rootdse = self::get('',['+','*'])
->read()
->select(['+'])
->whereHas('objectclass')
->firstOrFail();
return $rootdse;
}
/**
* Get the Schema DN
*
* @param string|null $connection
* @return string
* @throws ObjectNotFoundException
*/
public static function schemaDN(?string $connection=NULL): string
public static function schemaDN(): string
{
$cachetime = Carbon::now()->addSeconds(Config::get('ldap.cache.time'));
return collect(self::rootDSE($connection,$cachetime)->subschemasubentry)->first();
return collect(self::rootDSE()->subschemasubentry)
->first();
}
/* METHODS */
/**
* Query the server for a DN and return its children and if those children have children.
*
@ -251,17 +279,16 @@ final class Server
*/
public function children(string $dn,array $attrs=['dn']): ?LDAPCollection
{
return ($x=(new Entry)
->on($this->connection)
->cache(Carbon::now()->addSeconds(Config::get('ldap.cache.time')))
->select(array_merge($attrs,[
return $this
->get(
dn: $dn,
attrs: array_merge($attrs,[
'hassubordinates', // Needed for the tree to know if an entry has children
'c' // Needed for the tree to show icons for countries
]))
->setDn($dn)
->list()
->orderBy('dn')
->get()) ? $x : NULL;
->get() ?: NULL;
}
/**
@ -269,15 +296,13 @@ final class Server
*
* @param string $dn
* @param array $attrs
* @return Entry|null
* @return Model|null
*/
public function fetch(string $dn,array $attrs=['*','+']): ?Entry
public function fetch(string $dn,array $attrs=['*','+']): ?Model
{
return ($x=(new Entry)
->on($this->connection)
->cache(Carbon::now()->addSeconds(Config::get('ldap.cache.time')))
->select($attrs)
->find($dn)) ? $x : NULL;
return $this->get($dn,$attrs)
->read()
->first() ?: NULL;
}
/**
@ -285,6 +310,7 @@ final class Server
* as configured in config.php.
*
* @return boolean True if the specified attribute is configured to be force as a may attribute
* @todo There are 3 isForceMay() functions - we only need one
*/
public function isForceMay($attr_name): bool
{
@ -321,34 +347,11 @@ final class Server
// 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 'objectclasses':
if (isset($this->objectclasses))
return $this->objectclasses;
else
$this->objectclasses = collect();
if ($this->{$item}->count())
return $this->{$item};
break;
@ -358,8 +361,9 @@ final class Server
}
// Try to get the schema DN from the specified entry.
$schema_dn = $this->schemaDN($this->connection);
$schema = $this->fetch($schema_dn);
$schema_dn = $this->schemaDN();
// @note: 389DS does not return subschemaSubentry unless it is requested
$schema = $this->fetch($schema_dn,['*','+','subschemaSubentry']);
// If our schema's null, we didnt find it.
if (! $schema)
@ -367,7 +371,7 @@ final class Server
switch ($item) {
case 'attributetypes':
Log::debug('Attribute Types');
Log::debug(sprintf('%s:Attribute Types',self::LOGKEY));
// build the array of attribueTypes
//$syntaxes = $this->SchemaSyntaxes($dn);
@ -385,7 +389,7 @@ final class Server
* 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(',')));
Log::debug(sprintf('%s:\ Attribute [%s] has the following aliases [%s]',self::LOGKEY,$o->name,$o->aliases->join(',')));
foreach ($o->aliases as $alias) {
$new_attr = clone $o;
@ -445,7 +449,7 @@ final class Server
return $this->attributetypes;
case 'ldapsyntaxes':
Log::debug('LDAP Syntaxes');
Log::debug(sprintf('%s:LDAP Syntaxes',self::LOGKEY));
foreach ($schema->{$item} as $line) {
if (is_null($line) || ! strlen($line))
@ -458,7 +462,7 @@ final class Server
return $this->ldapsyntaxes;
case 'matchingrules':
Log::debug('Matching Rules');
Log::debug(sprintf('%s:Matching Rules',self::LOGKEY));
$this->matchingruleuse = collect();
foreach ($schema->{$item} as $line) {
@ -499,7 +503,7 @@ final class Server
return $this->matchingrules;
case 'objectclasses':
Log::debug('Object Classes');
Log::debug(sprintf('%s:Object Classes',self::LOGKEY));
foreach ($schema->{$item} as $line) {
if (is_null($line) || ! strlen($line))

View File

@ -10,17 +10,18 @@ use Illuminate\Support\Collection;
use App\Classes\LDAP\Server;
class APIController extends Controller
class AjaxController extends Controller
{
/**
* Get the LDAP server BASE DNs
*
* @return Collection
* @throws \LdapRecord\Query\ObjectNotFoundException
* @todo This should be consolidated with HomeController
*/
public function bases(): Collection
{
$base = Server::baseDNs() ?: collect();
$base = Server::baseDNs(TRUE) ?: collect();
return $base
->transform(fn($item)=>

View File

@ -5,7 +5,8 @@ namespace App\Http\Controllers\Auth;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cookie;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use App\Http\Controllers\Controller;
@ -38,7 +39,8 @@ class LoginController extends Controller
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
$this->middleware('guest')
->except('logout');
}
protected function credentials(Request $request): array
@ -58,17 +60,14 @@ class LoginController extends Controller
*/
public function logout(Request $request)
{
// Delete our LDAP authentication cookies
Cookie::queue(Cookie::forget('username_encrypt'));
Cookie::queue(Cookie::forget('password_encrypt'));
$user = Auth::user();
$this->guard()->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
if ($response = $this->loggedOut($request)) {
Log::info(sprintf('Logged out [%s]',$user->dn));
return $response;
}

View File

@ -28,7 +28,7 @@ class HomeController extends Controller
{
private function bases(): Collection
{
$base = Server::baseDNs() ?: collect();
$base = Server::baseDNs(TRUE) ?: collect();
return $base->transform(function($item) {
return [
@ -45,7 +45,7 @@ class HomeController extends Controller
* Create a new object in the LDAP server
*
* @param EntryAddRequest $request
* @return View
* @return \Illuminate\View\View
* @throws InvalidUsage
*/
public function entry_add(EntryAddRequest $request): \Illuminate\View\View
@ -189,8 +189,7 @@ class HomeController extends Controller
{
$dn = Crypt::decryptString($id);
$result = (new Entry)
->query()
$result = Entry::query()
->setDn($dn)
->recursive()
->get();

View File

@ -4,7 +4,7 @@ namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cookie;
use Illuminate\Support\Facades\Session;
class AllowAnonymous
{
@ -17,7 +17,9 @@ class AllowAnonymous
*/
public function handle(Request $request,Closure $next): mixed
{
if (((! Cookie::has('username_encrypt')) || (! Cookie::has('password_encrypt'))) && (! config('pla.allow_guest',FALSE)))
if ((! config('pla.allow_guest',FALSE))
&& ($request->path() !== 'login')
&& ((! Session::has('username_encrypt')) || (! Session::has('password_encrypt'))))
return redirect()
->to('/login');

View File

@ -7,7 +7,6 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use App\Classes\LDAP\Server;
use App\Ldap\User;
/**
* This sets up our application session with any required values, ultimately for cache optimisation reasons
@ -25,9 +24,6 @@ class ApplicationSession
{
Config::set('server',new Server);
view()->share('server', Config::get('server'));
view()->share('user', auth()->user() ?: new User);
return $next($request);
}
}

View File

@ -5,11 +5,12 @@ namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Cookie;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Session;
use LdapRecord\Container;
use App\Ldap\Connection;
use App\Ldap\Guard;
class SwapinAuthUser
{
@ -28,25 +29,19 @@ class SwapinAuthUser
if (! array_key_exists($key,config('ldap.connections')))
abort(599,sprintf('LDAP default server [%s] configuration doesnt exist?',$key));
/*
// Rebuild our connection with the authenticated user.
if (Session::has('username_encrypt') && Session::has('password_encrypt')) {
Config::set('ldap.connections.'.$key.'.username',Crypt::decryptString(Session::get('username_encrypt')));
Config::set('ldap.connections.'.$key.'.password',Crypt::decryptString(Session::get('password_encrypt')));
} else
*/
// @todo it seems sometimes we have cookies that show the logged in user, but Auth::user() has expired?
if (Cookie::has('username_encrypt') && Cookie::has('password_encrypt')) {
Config::set('ldap.connections.'.$key.'.username',Cookie::get('username_encrypt'));
Config::set('ldap.connections.'.$key.'.password',Cookie::get('password_encrypt'));
Log::debug('Swapping out configured LDAP credentials with the user\'s cookie.',['key'=>$key,'user'=>Cookie::get('username_encrypt')]);
Log::debug('Swapping out configured LDAP credentials with the user\'s session.',['key'=>$key]);
}
// We need to override our Connection object so that we can store and retrieve the logged in user and swap out the credentials to use them.
Container::getInstance()->addConnection(new Connection(config('ldap.connections.'.$key)),$key);
$c = Container::getInstance()
->getConnection($key);
$c->setConfiguration(config('ldap.connections.'.$key));
$c->setGuardResolver(fn()=>new Guard($c->getLdapConnection(),$c->getConfiguration()));
return $next($request);
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use App\Ldap\User;
/**
* This sets up our application session with any required values, ultimately for cache optimisation reasons
*/
class ViewVariables
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @return mixed
*/
public function handle(Request $request,Closure $next): mixed
{
view()->share('server',Config::get('server'));
view()->share('user',auth()->user() ?: new User);
return $next($request);
}
}

View File

@ -1,21 +0,0 @@
<?php
namespace App\Ldap;
use LdapRecord\Configuration\DomainConfiguration;
use LdapRecord\Connection as ConnectionBase;
use LdapRecord\LdapInterface;
class Connection extends ConnectionBase
{
public function __construct(DomainConfiguration|array $config=[],?LdapInterface $ldap=NULL)
{
parent::__construct($config,$ldap);
// We need to override this so that we use our own Guard, that stores the users credentials in the session
$this->authGuardResolver = function () {
return new Guard($this->ldap, $this->configuration);
};
}
}

View File

@ -27,8 +27,6 @@ class Entry extends Model
// Our Attribute objects
private Collection $objects;
/* @deprecated */
private bool $noObjectAttributes = FALSE;
// For new entries, this is the container that this entry will be stored in
private string $rdnbase;
@ -71,8 +69,6 @@ class Entry extends Model
/**
* Determine if the new and old values for a given key are equivalent.
*
* @todo This function barfs on language tags, eg: key = givenname;lang-ja
*/
protected function originalIsEquivalent(string $key): bool
{
@ -84,16 +80,6 @@ class Entry extends Model
|| (! $this->getObject($attribute)->isDirty());
}
public static function query(bool $noattrs=false): Builder
{
$o = new static;
if ($noattrs)
$o->noObjectAttributes();
return $o->newQuery();
}
/**
* As attribute values are updated, or new ones created, we need to mirror that
* into our $objects. This is called when we $o->key = $value
@ -539,19 +525,6 @@ class Entry extends Model
return [$attribute,$tags];
}
/**
* Dont convert our $this->attributes to $this->objects when creating a new Entry::class
*
* @return $this
* @deprecated
*/
public function noObjectAttributes(): static
{
$this->noObjectAttributes = TRUE;
return $this;
}
public function setRDNBase(string $bdn): void
{
if ($this->exists)

View File

@ -2,26 +2,20 @@
namespace App\Ldap;
use Illuminate\Support\Facades\Cookie;
// use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Log;
use LdapRecord\Auth\Guard as GuardBase;
class Guard extends GuardBase
{
public function attempt(string $username, string $password, bool $stayBound = false): bool
{
if ($result = parent::attempt($username,$password,$stayBound)) {
/*
* We can either use our session or cookies to store this. If using session, then Http/Kernel needs to be
* updated to start a session for API calls.
// We need to store our password so that we can swap in the user in during SwapinAuthUser::class middleware
request()->session()->put('username_encrypt',Crypt::encryptString($username));
request()->session()->put('password_encrypt',Crypt::encryptString($password));
*/
Log::info(sprintf('Attempting login for [%s] with password [%s]',$username,($password ? str_repeat('*',16) : str_repeat('?',16))));
// For our API calls, we store the cookie - which our cookies are already encrypted
Cookie::queue('username_encrypt',$username);
Cookie::queue('password_encrypt',$password);
if ($result = parent::attempt($username,$password,$stayBound)) {
// Store user details so we can swap in auth details in SwapinAuthUser
session()->put('username_encrypt',Crypt::encryptString($username));
session()->put('password_encrypt',Crypt::encryptString($password));
}
return $result;

View File

@ -31,7 +31,7 @@ class LdapUserRepository extends LdapUserRepositoryBase
return $this->query()->find($credentials['dn']);
// Look for a user using all our baseDNs
foreach (Server::baseDNs() as $base) {
foreach (Server::baseDNs(FALSE) as $base) {
$query = $this->query()->setBaseDn($base);
foreach ($credentials as $key => $value) {
@ -67,7 +67,7 @@ class LdapUserRepository extends LdapUserRepositoryBase
public function findByGuid($guid): ?Model
{
// Look for a user using all our baseDNs
foreach (Server::baseDNs() as $base) {
foreach (Server::baseDNs(FALSE) as $base) {
$user = $this->query()->setBaseDn($base)->findByGuid($guid);
if ($user)

View File

@ -1,31 +1,26 @@
<?php
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use App\Http\Middleware\{AllowAnonymous,ApplicationSession,CheckUpdate,SwapinAuthUser};
use App\Http\Middleware\{AllowAnonymous,ApplicationSession,CheckUpdate,SwapinAuthUser,ViewVariables};
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->appendToGroup('web', [
SwapinAuthUser::class,
ApplicationSession::class,
CheckUpdate::class,
]);
$middleware->prependToGroup('api', [
EncryptCookies::class,
SwapinAuthUser::class,
ApplicationSession::class,
$middleware->appendToGroup(
group: 'web',
middleware: [
AllowAnonymous::class,
ApplicationSession::class,
SwapinAuthUser::class,
ViewVariables::class,
CheckUpdate::class,
]);
$middleware->trustProxies(at: [

View File

@ -68,7 +68,7 @@ return [
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'level' => env('LOG_LEVEL', 'info'),
'days' => env('LOG_DAILY_DAYS', 14),
'replace_placeholders' => true,
],

View File

@ -1 +1 @@
v2.1.1-rel
v2.1.2-dev

View File

@ -253,3 +253,8 @@ select2-container--bootstrap-5 .select2-selection--multiple .select2-selection__
.btn-check:checked+.btn, .btn.active, .btn.show, .btn:first-child:active, :not(.btn-check)+.btn:active {
border-color: var(--bs-btn-bg);
}
/* limit selection to inside the modal */
body.modal-open {
user-select: none;
}

4
public/js/custom.js vendored
View File

@ -59,7 +59,7 @@ $(document).ready(function() {
if (typeof basedn !== 'undefined') {
sources = basedn;
} else {
sources = { url: 'api/bases' };
sources = { url: 'ajax/bases' };
}
// Attach the fancytree widget to an existing <div id="tree"> element
@ -95,7 +95,7 @@ $(document).ready(function() {
source: sources,
lazyLoad: function(event,data) {
data.result = {
url: '/api/children',
url: '/ajax/children',
data: {key: data.node.data.item,depth: 1}
};

View File

@ -54,6 +54,9 @@
var rendered = false;
var newadded = [];
var oc = $('attribute#objectClass input[type=text]')
.map((key,item)=>{return $(item).val()}).toArray();
if (newadded.length)
process_oc();
@ -88,7 +91,7 @@
// Get a list of attributes already on the page, so we dont double up
$.ajax({
method: 'POST',
url: '{{ url('api/schema/objectclass/attrs') }}/'+item,
url: '{{ url('ajax/schema/objectclass/attrs') }}/'+item,
cache: false,
success: function(data) {
// Render any must attributes
@ -153,7 +156,7 @@
$.ajax({
method: 'POST',
url: '{{ url('api/schema/objectclass/attrs') }}/'+item,
url: '{{ url('ajax/schema/objectclass/attrs') }}/'+item,
cache: false,
success: function(data) {
var attrs = [];

View File

@ -24,7 +24,7 @@
<td>BaseDN(s)</td>
<td>
<table class="table table-sm table-borderless">
@foreach($server->baseDNs()->sort(fn($item)=>$item->sort_key) as $item)
@foreach($server->baseDNs(TRUE)->sort(fn($item)=>$item->sort_key) as $item)
<tr>
<td class="ps-0">{{ $item->getDn() }}</td>
</tr>

View File

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

View File

@ -103,9 +103,10 @@
$(document).ready(function() {
@if($step === 2)
var oc = {!! $o->getObject('objectclass')->values !!};
$('#newattr').on('change',function(item) {
var oc = $('attribute#objectClass input[type=text]')
.map((key,item)=>{return $(item).val()}).toArray();
$.ajax({
type: 'POST',
url: '{{ url('entry/attr/add') }}/'+item.target.value,

View File

@ -174,7 +174,6 @@
@section('page-scripts')
<script type="text/javascript">
var dn = '{{ $o->getDNSecure() }}';
var oc = {!! $o->getObject('objectclass')->values !!};
function editmode() {
$('#dn-edit input[name="dn"]').val(dn);
@ -225,6 +224,9 @@
});
$('#newattr').on('change',function(item) {
var oc = $('attribute#objectClass input[type=text]')
.map((key,item)=>{return $(item).val()}).toArray();
$.ajax({
type: 'POST',
beforeSend: function() {},
@ -334,6 +336,11 @@
}
});
$('#page-modal').on('hide.bs.modal',function() {
// Clear any select ranges that occurred while the modal was open
document.getSelection().removeAllRanges();
});
@if(old())
editmode();
@endif

View File

@ -1,11 +1,10 @@
@use(App\Classes\LDAP\Server)
@extends('layouts.dn')
@section('page_title')
<table class="table table-borderless">
<tr>
<td style="border-radius: 5px;"><div class="page-title-icon f32"><i class="fas fa-fingerprint"></i></div></td>
<td class="top text-end align-text-top p-2"><strong>{{ Server::schemaDN() }}</strong></td>
<td class="top text-end align-text-top p-2"><strong>{{ $server->schemaDN() }}</strong></td>
</tr>
</table>
@endsection
@ -58,7 +57,7 @@
return false;
$.ajax({
url: '{{ url('api/schema/view') }}',
url: '{{ url('ajax/schema/view') }}',
method: 'POST',
data: { type: type },
dataType: 'html',

View File

@ -5,7 +5,7 @@
</div>
<div class="modal-body">
<div id="entry_export"></div>
<div id="entry_export" style="user-select: text;"></div>
</div>
<div class="modal-footer">

View File

@ -37,7 +37,7 @@
<tbody>
@foreach ($o->getObjects()->filter(fn($item)=>$item->isDirty()) as $key => $oo)
<tr>
<th rowspan="{{ $x=max($oo->values->dot()->keys()->count(),$oo->values_old->dot()->keys()->count())+1}}">
<th rowspan="{{ $x=max($oo->values->dot()->keys()->count(),$oo->values_old->dot()->keys()->count())}}">
<abbr title="{{ $oo->description }}">{{ $oo->name }}</abbr>
</th>
@ -54,7 +54,7 @@
<td colspan="2" class="text-center">@lang('Ignoring blank value')</td>
@else
<td>{{ (($r=$oo->render_item_old($dotkey)) !== NULL) ? $r : '['.strtoupper(__('New Value')).']' }}</td>
<td>{{ (($r=$oo->render_item_new($dotkey)) !== NULL) ? $r : '['.strtoupper(__('Deleted')).']' }}<input type="hidden" name="{{ $key }}[{{ collect(explode('.',$dotkey))->first() }}][]" value="{{ Arr::get($oo,$dotkey) }}"></td>
<td>{{ (($r=$oo->render_item_new($dotkey)) !== NULL) ? $r : '['.strtoupper(__('Deleted')).']' }}<input type="hidden" name="{{ $key }}[{{ $oo->no_attr_tags ? \App\Ldap\Entry::TAG_NOTAG : collect(explode('.',$dotkey))->first() }}][]" value="{{ Arr::get($oo->values->dot(),$dotkey) }}"></td>
@endif
@endforeach
</tr>

View File

@ -1,23 +0,0 @@
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\APIController;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::controller(APIController::class)->group(function() {
Route::get('bases','bases');
Route::get('children','children');
Route::post('schema/view','schema_view');
Route::post('schema/objectclass/attrs/{id}','schema_objectclass_attrs');
});

View File

@ -2,7 +2,7 @@
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\HomeController;
use App\Http\Controllers\{AjaxController,HomeController};
use App\Http\Controllers\Auth\LoginController;
use App\Http\Middleware\AllowAnonymous;
@ -58,3 +58,12 @@ Route::controller(HomeController::class)->group(function() {
Route::view('modal/userpassword-check/{dn}','modals.entry-userpassword-check');
});
});
Route::controller(AjaxController::class)
->prefix('ajax')
->group(function() {
Route::get('bases','bases');
Route::get('children','children');
Route::post('schema/view','schema_view');
Route::post('schema/objectclass/attrs/{id}','schema_objectclass_attrs');
});

View File

@ -12,7 +12,7 @@ class ExampleTest extends TestCase
*/
public function test_the_application_returns_a_successful_response(): void
{
$response = $this->get('/');
$response = $this->get('/login');
$response->assertStatus(200);
}

View File

@ -13,11 +13,10 @@ class GetBaseDNTest extends TestCase
*
* @return void
* @throws \LdapRecord\Query\ObjectNotFoundException
* @covers \App\Classes\LDAP\Server::baseDNs()
*/
public function testBaseDnExists()
{
$o = Server::baseDNs();
$o = Server::baseDNs(TRUE);
$this->assertIsObject($o);
$this->assertCount(6,$o->toArray());