Start of implementation of Import and Export using LDIF
This commit is contained in:
@@ -168,6 +168,11 @@ class Attribute implements \Countable, \ArrayAccess
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function addValue(string $value): void
|
||||
{
|
||||
$this->values->push($value);
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return $this->values->count();
|
||||
|
@@ -50,7 +50,7 @@ class Factory
|
||||
*/
|
||||
public static function create(string $attribute,array $values): Attribute
|
||||
{
|
||||
$class = Arr::get(self::map,$attribute,Attribute::class);
|
||||
$class = Arr::get(self::map,strtolower($attribute),Attribute::class);
|
||||
Log::debug(sprintf('%s:Creating LDAP Attribute [%s] as [%s]',static::LOGKEY,$attribute,$class));
|
||||
|
||||
return new $class($attribute,$values);
|
||||
|
53
app/Classes/LDAP/Export.php
Normal file
53
app/Classes/LDAP/Export.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use LdapRecord\Query\Collection;
|
||||
|
||||
/**
|
||||
* Export Class
|
||||
*
|
||||
* This abstract classes provides all the common methods and variables for the
|
||||
* export classes.
|
||||
*/
|
||||
abstract class Export
|
||||
{
|
||||
// Line Break
|
||||
protected string $br = "\r\n";
|
||||
|
||||
// Item(s) being Exported
|
||||
protected Collection $items;
|
||||
|
||||
// Type of export
|
||||
protected const type = 'Unknown';
|
||||
|
||||
public function __construct(Collection $items)
|
||||
{
|
||||
$this->items = $items;
|
||||
}
|
||||
|
||||
abstract public function __toString(): string;
|
||||
|
||||
protected function header()
|
||||
{
|
||||
$output = '';
|
||||
|
||||
$output .= sprintf('# %s %s',__(static::type.' for'),($x=$this->items->first())).$this->br;
|
||||
$output .= sprintf('# %s: %s (%s)',
|
||||
__('Server'),
|
||||
$x->getConnection()->getConfiguration()->get('name'),
|
||||
$x->getConnection()->getLdapConnection()->getHost()).$this->br;
|
||||
//$output .= sprintf('# %s: %s',__('Search Scope'),$this->scope).$this->br;
|
||||
//$output .= sprintf('# %s: %s',__('Search Filter'),$this->entry->dn).$this->br;
|
||||
$output .= sprintf('# %s: %s',__('Total Entries'),$this->items->count()).$this->br;
|
||||
$output .= '#'.$this->br;
|
||||
$output .= sprintf('# %s %s (%s) on %s',__('Generated by'),config('app.name'),config('app.url'),date('F j, Y g:i a')).$this->br;
|
||||
$output .= sprintf('# %s %s',__('Exported by'),Auth::user() ?: 'Anonymous').$this->br;
|
||||
$output .= sprintf('# %s: %s',__('Version'),config('app.version')).$this->br;
|
||||
|
||||
$output .= $this->br;
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
78
app/Classes/LDAP/Export/LDIF.php
Normal file
78
app/Classes/LDAP/Export/LDIF.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Export;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
use App\Classes\LDAP\Export;
|
||||
|
||||
/**
|
||||
* Export from LDAP using an LDIF format
|
||||
*/
|
||||
class LDIF extends Export
|
||||
{
|
||||
// The maximum length of the ldif line
|
||||
private int $line_length = 76;
|
||||
protected const type = 'LDIF Export';
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$result = parent::header();
|
||||
$result .= 'version: 1';
|
||||
$result .= $this->br;
|
||||
|
||||
$c = 1;
|
||||
foreach ($this->items as $o) {
|
||||
if ($c > 1)
|
||||
$result .= $this->br;
|
||||
|
||||
$title = (string)$o;
|
||||
if (strlen($title) > $this->line_length)
|
||||
$title = Str::of($title)->limit($this->line_length-3-5,'...'.substr($title,-5));
|
||||
|
||||
$result .= sprintf('# %s %s: %s',__('Entry'),$c++,$title).$this->br;
|
||||
|
||||
// Display DN
|
||||
$result .= $this->multiLineDisplay(
|
||||
Str::isAscii($o)
|
||||
? sprintf('dn: %s',$o)
|
||||
: sprintf('dn:: %s',base64_encode($o))
|
||||
,$this->br);
|
||||
|
||||
// Display Attributes
|
||||
foreach ($o->getObjects() as $ao) {
|
||||
foreach ($ao->values as $value) {
|
||||
$result .= $this->multiLineDisplay(
|
||||
Str::isAscii($value)
|
||||
? sprintf('%s: %s',$ao->name,$value)
|
||||
: sprintf('%s:: %s',$ao->name,base64_encode($value))
|
||||
,$this->br);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to wrap LDIF lines
|
||||
*
|
||||
* @param string $str The line to be wrapped if needed.
|
||||
*/
|
||||
private function multiLineDisplay(string $str,string $br): string
|
||||
{
|
||||
$length_string = strlen($str);
|
||||
$length_max = $this->line_length;
|
||||
|
||||
$output = '';
|
||||
while ($length_string > $length_max) {
|
||||
$output .= substr($str,0,$length_max).$br;
|
||||
$str = ' '.substr($str,$length_max);
|
||||
$length_string = strlen($str);
|
||||
}
|
||||
|
||||
$output .= $str.$br;
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
79
app/Classes/LDAP/Import.php
Normal file
79
app/Classes/LDAP/Import.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Exceptions\Import\GeneralException;
|
||||
use App\Exceptions\Import\ObjectExistsException;
|
||||
use App\Ldap\Entry;
|
||||
|
||||
/**
|
||||
* Import Class
|
||||
*
|
||||
* This abstract classes provides all the common methods and variables for the
|
||||
* import classes.
|
||||
*/
|
||||
abstract class Import
|
||||
{
|
||||
// Valid LDIF commands
|
||||
protected const LDAP_IMPORT_ADD = 1;
|
||||
protected const LDAP_IMPORT_DELETE = 2;
|
||||
protected const LDAP_IMPORT_MODRDN = 3;
|
||||
protected const LDAP_IMPORT_MODDN = 4;
|
||||
protected const LDAP_IMPORT_MODIFY = 5;
|
||||
|
||||
protected const LDAP_ACTIONS = [
|
||||
'add' => self::LDAP_IMPORT_ADD,
|
||||
'delete' => self::LDAP_IMPORT_DELETE,
|
||||
'modrdn' => self::LDAP_IMPORT_MODRDN,
|
||||
'moddn' => self::LDAP_IMPORT_MODDN,
|
||||
'modify' => self::LDAP_IMPORT_MODIFY,
|
||||
];
|
||||
|
||||
// The import data to process
|
||||
protected string $input;
|
||||
// The attributes the server knows about
|
||||
protected Collection $server_attributes;
|
||||
|
||||
public function __construct(string $input) {
|
||||
$this->input = $input;
|
||||
$this->server_attributes = config('server')->schema('attributetypes');
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to commit an entry and return the result.
|
||||
*
|
||||
* @param Entry $o
|
||||
* @param int $action
|
||||
* @return Collection
|
||||
* @throws GeneralException
|
||||
* @throws ObjectExistsException
|
||||
*/
|
||||
final protected function commit(Entry $o,int $action): Collection
|
||||
{
|
||||
switch ($action) {
|
||||
case static::LDAP_IMPORT_ADD:
|
||||
try {
|
||||
$o->save();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return collect([
|
||||
'dn'=>$o->getDN(),
|
||||
'result'=>sprintf('%d: %s (%s)',
|
||||
($x=$e->getDetailedError())->getErrorCode(),
|
||||
$x->getErrorMessage(),
|
||||
$x->getDiagnosticMessage(),
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
return collect(['dn'=>$o->getDN(),'result'=>__('Created')]);
|
||||
|
||||
default:
|
||||
throw new GeneralException('Unhandled action during commit: '.$action);
|
||||
}
|
||||
}
|
||||
|
||||
abstract public function process(): Collection;
|
||||
}
|
233
app/Classes/LDAP/Import/LDIF.php
Normal file
233
app/Classes/LDAP/Import/LDIF.php
Normal file
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\LDAP\Import;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Nette\NotImplementedException;
|
||||
|
||||
use App\Classes\LDAP\Import;
|
||||
use App\Exceptions\Import\{GeneralException,VersionException};
|
||||
use App\Ldap\Entry;
|
||||
|
||||
/**
|
||||
* Import LDIF to LDAP using an LDIF format
|
||||
*
|
||||
* The LDIF spec is described by RFC2849
|
||||
* http://www.ietf.org/rfc/rfc2849.txt
|
||||
*/
|
||||
class LDIF extends Import
|
||||
{
|
||||
private const LOGKEY = 'ILF';
|
||||
|
||||
public function process(): Collection
|
||||
{
|
||||
$c = 0;
|
||||
$action = NULL;
|
||||
$attribute = NULL;
|
||||
$base64encoded = FALSE;
|
||||
$o = NULL;
|
||||
$value = '';
|
||||
$version = NULL;
|
||||
$result = collect();
|
||||
|
||||
// @todo When renaming DNs, the hotlink should point to the new entry on success, or the old entry on failure.
|
||||
foreach (preg_split('/(\r?\n|\r)/',$this->input) as $line) {
|
||||
$c++;
|
||||
Log::debug(sprintf('%s: LDIF Line [%s]',self::LOGKEY,$line));
|
||||
$line = trim($line);
|
||||
|
||||
// If the line starts with a comment, ignore it
|
||||
if (preg_match('/^#/',$line))
|
||||
continue;
|
||||
|
||||
// If we have a blank line, then that completes this command
|
||||
if (! $line) {
|
||||
if (! is_null($o)) {
|
||||
// Add the last attribute;
|
||||
$o->addAttribute($attribute,$base64encoded ? base64_decode($value) : $value);
|
||||
|
||||
Log::debug(sprintf('%s: Committing Entry [%s]',self::LOGKEY,$o->getDN()));
|
||||
|
||||
// Commit
|
||||
$result->push($this->commit($o,$action));
|
||||
$result->last()->put('line',$c);
|
||||
|
||||
$o = NULL;
|
||||
$action = NULL;
|
||||
$base64encoded = FALSE;
|
||||
$attribute = NULL;
|
||||
$value = '';
|
||||
|
||||
// Else its a blank line
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$m = [];
|
||||
preg_match('/^([a-zA-Z0-9;-]+)(:+)\s+(.*)$/',$line,$m);
|
||||
|
||||
switch ($x=Arr::get($m,1)) {
|
||||
case 'changetype':
|
||||
if ($m[2] !== ':')
|
||||
throw new GeneralException(sprintf('ChangeType cannot be base64 encoded set at [%d]. (line %d)',$version,$c));
|
||||
|
||||
switch ($m[3]) {
|
||||
// if (preg_match('/^changetype:[ ]*(delete|add|modrdn|moddn|modify)/i',$lines[0])) {
|
||||
default:
|
||||
throw new NotImplementedException(sprintf('Unknown change type [%s]? (line %d)',$m[3],$c));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'version':
|
||||
if (! is_null($version))
|
||||
throw new VersionException(sprintf('Version has already been set at [%d]. (line %d)',$version,$c));
|
||||
|
||||
if ($m[2] !== ':')
|
||||
throw new VersionException(sprintf('Version cannot be base64 encoded set at [%d]. (line %d)',$version,$c));
|
||||
|
||||
$version = (int)$m[3];
|
||||
break;
|
||||
|
||||
// Treat it as an attribute
|
||||
default:
|
||||
// If $m is NULL, then this is the 2nd (or more) line of a base64 encoded value
|
||||
if (! $m) {
|
||||
$value .= $line;
|
||||
Log::debug(sprintf('%s: Attribute [%s] adding [%s] (%d)',self::LOGKEY,$attribute,$line,$c));
|
||||
|
||||
// add to last attr value
|
||||
continue 2;
|
||||
}
|
||||
|
||||
// We are ready to create the entry or add the attribute
|
||||
if ($attribute) {
|
||||
if ($attribute === 'dn') {
|
||||
if (! is_null($o))
|
||||
throw new GeneralException(sprintf('Previous Entry not complete? (line %d)',$c));
|
||||
|
||||
$dn = $base64encoded ? base64_decode($value) : $value;
|
||||
Log::debug(sprintf('%s: Creating new entry:',self::LOGKEY,$dn));
|
||||
//$o = Entry::find($dn);
|
||||
|
||||
// If it doesnt exist, we'll create it
|
||||
//if (! $o) {
|
||||
$o = new Entry;
|
||||
$o->setDn($dn);
|
||||
//}
|
||||
|
||||
$action = self::LDAP_IMPORT_ADD;
|
||||
|
||||
} else {
|
||||
Log::debug(sprintf('%s: Adding Attribute [%s] value [%s] (%d)',self::LOGKEY,$attribute,$value,$c));
|
||||
|
||||
if ($value)
|
||||
$o->addAttribute($attribute,$base64encoded ? base64_decode($value) : $value);
|
||||
else
|
||||
throw new GeneralException(sprintf('Attribute has no value [%s] (line %d)',$attribute,$c));
|
||||
}
|
||||
}
|
||||
|
||||
// Start of a new attribute
|
||||
$base64encoded = ($m[2] === '::');
|
||||
// @todo Need to parse attributes with ';' options
|
||||
$attribute = $m[1];
|
||||
$value = $m[3];
|
||||
|
||||
Log::debug(sprintf('%s: New Attribute [%s] with [%s] (%d)',self::LOGKEY,$attribute,$value,$c));
|
||||
}
|
||||
|
||||
if ($version !== 1)
|
||||
throw new VersionException('LDIF import cannot handle version: '.($version ?: __('NOT DEFINED')));
|
||||
}
|
||||
|
||||
// We may still have a pending action
|
||||
if ($action) {
|
||||
// Add the last attribute;
|
||||
$o->addAttribute($attribute,$base64encoded ? base64_decode($value) : $value);
|
||||
|
||||
Log::debug(sprintf('%s: Committing Entry [%s]',self::LOGKEY,$o->getDN()));
|
||||
|
||||
// Commit
|
||||
$result->push($this->commit($o,$action));
|
||||
$result->last()->put('line',$c);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function readEntry() {
|
||||
static $haveVersion = false;
|
||||
|
||||
if ($lines = $this->nextLines()) {
|
||||
|
||||
$server = $this->getServer();
|
||||
|
||||
# The first line should be the DN
|
||||
if (preg_match('/^dn:/',$lines[0])) {
|
||||
list($text,$dn) = $this->getAttrValue(array_shift($lines));
|
||||
|
||||
# The second line should be our changetype
|
||||
if (preg_match('/^changetype:[ ]*(delete|add|modrdn|moddn|modify)/i',$lines[0])) {
|
||||
$attrvalue = $this->getAttrValue($lines[0]);
|
||||
$changetype = $attrvalue[1];
|
||||
array_shift($lines);
|
||||
|
||||
} else
|
||||
$changetype = 'add';
|
||||
|
||||
$this->template = new Template($this->server_id,null,null,$changetype);
|
||||
|
||||
switch ($changetype) {
|
||||
case 'add':
|
||||
$rdn = get_rdn($dn);
|
||||
$container = $server->getContainer($dn);
|
||||
|
||||
$this->template->setContainer($container);
|
||||
$this->template->accept();
|
||||
|
||||
$this->getAddDetails($lines);
|
||||
$this->template->setRDNAttributes($rdn);
|
||||
|
||||
return $this->template;
|
||||
|
||||
break;
|
||||
|
||||
case 'modify':
|
||||
if (! $server->dnExists($dn))
|
||||
return $this->error(sprintf('%s %s',_('DN does not exist'),$dn),$lines);
|
||||
|
||||
$this->template->setDN($dn);
|
||||
$this->template->accept(false,true);
|
||||
|
||||
return $this->getModifyDetails($lines);
|
||||
|
||||
break;
|
||||
|
||||
case 'moddn':
|
||||
case 'modrdn':
|
||||
if (! $server->dnExists($dn))
|
||||
return $this->error(sprintf('%s %s',_('DN does not exist'),$dn),$lines);
|
||||
|
||||
$this->template->setDN($dn);
|
||||
$this->template->accept();
|
||||
|
||||
return $this->getModRDNAttributes($lines);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
if (! $server->dnExists($dn))
|
||||
return $this->error(_('Unkown change type'),$lines);
|
||||
}
|
||||
|
||||
} else
|
||||
return $this->error(_('A valid dn line is required'),$lines);
|
||||
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user