Update import to include changetype add/modify operations

This commit is contained in:
2025-07-03 22:56:21 +08:00
parent 7cf81c14d3
commit eeed0b04f6
15 changed files with 655 additions and 122 deletions

View File

@@ -190,6 +190,21 @@ class Attribute implements \Countable, \ArrayAccess
->get($tag,[]),$values)));
}
/**
* If this attribute has changes, re-render the attribute values
*
* @return array
*/
public function getDirty(): array
{
$dirty = [];
if ($this->isDirty())
$dirty = [$this->name_lc => $this->_values->toArray()];
return $dirty;
}
/**
* Return the hints about this attribute, ie: RDN, Required, etc
*

View File

@@ -56,6 +56,7 @@ abstract class Import
{
switch ($action) {
case static::LDAP_IMPORT_ADD:
case static::LDAP_IMPORT_MODIFY:
try {
$o->save();
@@ -65,15 +66,17 @@ abstract class Import
if ($e->getDetailedError())
return collect([
'dn'=>$o->getDN(),
'result'=>sprintf('%d: %s (%s)',
'link'=>$o->getDNSecure(),
'result'=>sprintf('%d: %s%s',
($x=$e->getDetailedError())->getErrorCode(),
$x->getErrorMessage(),
$x->getDiagnosticMessage(),
$x->getDiagnosticMessage() ? ' ('.$x->getDiagnosticMessage().')' : '',
)
]);
else
return collect([
'dn'=>$o->getDN(),
'link'=>$o->getDNSecure(),
'result'=>sprintf('%d: %s',
$e->getCode(),
$e->getMessage(),
@@ -81,9 +84,13 @@ abstract class Import
]);
}
Log::debug(sprintf('%s:Import Commited',self::LOGKEY));
Log::debug(sprintf('%s:= Import Commited',self::LOGKEY));
return collect(['dn'=>$o->getDN(),'result'=>__('Created')]);
return collect([
'dn'=>$o->getDN(),
'link'=>$o->getDNSecure(),
'result'=>$action === self::LDAP_IMPORT_ADD ? __('Created') : __('Modified'),
]);
default:
throw new GeneralException('Unhandled action during commit: '.$action);

View File

@@ -24,7 +24,9 @@ class LDIF extends Import
public function process(): Collection
{
$c = 0;
$action = NULL;
$action = self::LDAP_IMPORT_ADD; // Assume add mode
$subaction = 'add'; // Assume add
$dn = NULL;
$attribute = NULL;
$base64encoded = FALSE;
$o = NULL;
@@ -32,23 +34,38 @@ class LDIF extends Import
$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));
Log::debug(sprintf('%s:LDIF Line [%s] (%d)',self::LOGKEY,$line,$c));
$line = trim($line);
// If the line starts with a comment, ignore it
if (preg_match('/^#/',$line))
continue;
// If the line starts with a dash, its more updates to the same entry
if (preg_match('/^-/',$line)) {
Log::debug(sprintf('%s:/ DASH Line [%s] (%d)',self::LOGKEY,$line,$c),['action'=>$action,'subaction'=>$subaction,'attribute'=>$attribute,'value'=>$value]);
if ($attribute)
$o = $this->entry($o,$attribute,$subaction,$base64encoded ? base64_decode($value) : $value,$c);
$base64encoded = FALSE;
$attribute = NULL;
$value = '';
continue;
}
// If we have a blank line, then that completes this command
if (! $line) {
if (! is_null($o)) {
// Add the last attribute;
$o->addAttributeItem($attribute,$base64encoded ? base64_decode($value) : $value);
// If we havent got a version yet, then we havent started
if (! $version) {
continue;
Log::debug(sprintf('%s:- Committing Entry [%s]',self::LOGKEY,$o->getDN()));
} elseif (! is_null($o)) {
if ($attribute)
$o = $this->entry($o,$attribute,$subaction,$base64encoded ? base64_decode($value) : $value,$c);
Log::debug(sprintf('%s:- Committing Entry (More) [%s]',self::LOGKEY,$o->getDN()));
// Commit
$result->push($this->commit($o,$action));
@@ -59,27 +76,123 @@ class LDIF extends Import
$base64encoded = FALSE;
$attribute = NULL;
$value = '';
} else {
throw new GeneralException(sprintf('Processing Error - Line exists [%s] on (%d) but object is NULL',$line,$c));
}
continue;
}
$m = [];
preg_match('/^([a-zA-Z0-9;-]+)(:+)\s+(.*)$/',$line,$m);
preg_match('/^([a-zA-Z0-9;-]+)(:+)\s*(.*)$/',$line,$m);
// 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] appending [%s] (%d)',self::LOGKEY,$attribute,$line,$c));
// add to last attr value
continue;
} else {
// If base64 mode was enabled, and there is a following attribute after a base64encoded attribute, it hasnt been processed yet
if ($base64encoded) {
Log::debug(sprintf('%s:- Completing base64 attribute [%s]',self::LOGKEY,$attribute));
$o = $this->entry($o,$attribute,$subaction,base64_decode($value),$c);
$attribute = NULL;
}
$base64encoded = ($m[2] === '::');
$value = $m[3];
// If we are base64encoded, we need to loop around
if ($base64encoded) {
$attribute = $m[1];
Log::debug(sprintf('%s:/ Retrieving base64 attribute [%s] (%c)',self::LOGKEY,$attribute,$c));
continue;
}
}
// changetype needs to be after the dn, and if it isnt we'll assume add.
if ($dn && Arr::get($m,1) !== 'changetype') {
if ($action === self::LDAP_IMPORT_ADD) {
Log::debug(sprintf('%s:Creating new entry [%s]:',self::LOGKEY,$dn),['o'=>$o]);
$o = new Entry;
$o->setDn($dn);
$dn = NULL;
} else {
Log::debug(sprintf('%s:Looking for existing entry [%s]:',self::LOGKEY,$dn),['o'=>$o]);
$o = Entry::find($dn);
$dn = NULL;
if (! $o) {
$result->push(collect(['dn'=>$dn,'result'=>__('Entry doesnt exist')]));
$result->last()->put('line',$c);;
continue;
}
}
}
switch (Arr::get($m,1)) {
case 'dn':
$dn = $base64encoded ? base64_decode($value) : $value;
Log::debug(sprintf('%s:# Got DN [%s]:',self::LOGKEY,$dn));
$value = '';
$base64encoded = FALSE;
break;
case 'changetype':
if ($m[2] !== ':')
throw new GeneralException(sprintf('ChangeType cannot be base64 encoded set at [%d]. (line %d)',$version,$c));
throw new GeneralException(sprintf('changetype cannot be base64 encoded set at [%d]. (line %d)',$version,$c));
if (! is_null($o))
throw new GeneralException(sprintf('Previous Entry not complete? (line %d)',$c));
Log::debug(sprintf('%s:- Action [%s]',self::LOGKEY,$m[3]));
switch ($m[3]) {
// if (preg_match('/^changetype:[ ]*(delete|add|modrdn|moddn|modify)/i',$lines[0])) {
case 'add':
$action = self::LDAP_IMPORT_ADD;
break;
case 'modify':
$action = self::LDAP_IMPORT_MODIFY;
break;
/*
case 'delete':
$action = self::LDAP_IMPORT_DELETE;
break;
*/
// @todo modrdn|moddn
default:
throw new NotImplementedException(sprintf('Unknown change type [%s]? (line %d)',$m[3],$c));
}
break;
case 'add':
case 'replace':
if ($action !== self::LDAP_IMPORT_MODIFY)
throw new GeneralException(sprintf('%s action can only be used with changetype: modify (line %d)',$m[1],$c));
$subaction = $m[1];
break;
case 'delete':
$subaction = $m[1];
$attribute = $m[3];
$value = NULL;
break;
case 'version':
if (! is_null($version))
throw new VersionException(sprintf('Version has already been set at [%d]. (line %d)',$version,$c));
@@ -92,49 +205,14 @@ class LDIF extends Import
// 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;
}
// Start of a new attribute
$attribute = $m[1];
Log::debug(sprintf('%s:- Working with Attribute [%s] with [%s] (%d)',self::LOGKEY,$attribute,$value,$c));
// 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->addAttributeItem($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] === '::');
$attribute = $m[1];
$value = $m[3];
Log::debug(sprintf('%s:- New Attribute [%s] with [%s] (%d)',self::LOGKEY,$attribute,$value,$c));
$o = $this->entry($o,$attribute,$subaction,$base64encoded ? base64_decode($value) : $value,$c);
$attribute = NULL;
$value = NULL;
}
if ($version !== 1)
@@ -142,11 +220,11 @@ class LDIF extends Import
}
// We may still have a pending action
if ($action) {
// Add the last attribute;
$o->addAttributeItem($attribute,$base64encoded ? base64_decode($value) : $value);
if ($o) {
if ($attribute)
$o = $this->entry($o,$attribute,$subaction,$base64encoded ? base64_decode($value) : $value,$c);
Log::debug(sprintf('%s:- Committing Entry [%s]',self::LOGKEY,$o->getDN()));
Log::debug(sprintf('%s:- Committing Entry (Final) [%s]',self::LOGKEY,$o->getDN()));
// Commit
$result->push($this->commit($o,$action));
@@ -156,75 +234,46 @@ class LDIF extends Import
return $result;
}
public function xreadEntry() {
static $haveVersion = FALSE;
private function entry(Entry $o,string $attribute,string $subaction,?string $value,int $c): Entry
{
Log::debug(sprintf('%s:. %s Attribute [%s] value [%s] (%d)',self::LOGKEY,$subaction,$attribute,$value,$c));
if ($lines = $this->nextLines()) {
switch ($subaction) {
case 'add':
if (! $value)
throw new GeneralException(sprintf('Attribute has no value [%s] (line %d)',$attribute,$c));
$server = $this->getServer();
$o->addAttributeItem($attribute,$value);
break;
# The first line should be the DN
if (preg_match('/^dn:/',$lines[0])) {
list($text,$dn) = $this->getAttrValue(array_shift($lines));
case 'replace':
if (! $value)
throw new GeneralException(sprintf('Attribute has no value [%s] (line %d)',$attribute,$c));
# 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);
if (! ($x=$o->getObject(($xx=strstr($attribute,';',TRUE)) ?: $attribute)))
throw new \Exception(sprintf('Attribute [%s] doesnt exist in [%s] (line %d)',$attribute,$o->getDn(),$c));
// If the attribute has changed, we'll assume this is an additional value for it
if ($x->isDirty()) {
Log::debug(sprintf('%s:/ Attribute [%s] has changed, assuming add',self::LOGKEY,$attribute));
$o->addAttributeItem($attribute,$value);
} else
$changetype = 'add';
$o->{$attribute} = [Entry::TAG_NOTAG=>[$value]];
$this->template = new Template($this->server_id,NULL,NULL,$changetype);
break;
switch ($changetype) {
case 'add':
$rdn = get_rdn($dn);
$container = $server->getContainer($dn);
case 'delete':
if (! $o->getObject($attribute))
throw new \Exception(sprintf('Attribute [%s] doesnt exist in [%s] (line %d)',$attribute,$o->getDn(),$c));
$this->template->setContainer($container);
$this->template->accept();
$o->{$attribute} = [];
break;
$this->getAddDetails($lines);
$this->template->setRDNAttributes($rdn);
default:
throw new \Exception('Unknown subaction:'.$subaction);
}
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(_('Unknown change type'),$lines);
}
} else
return $this->error(_('A valid dn line is required'),$lines);
} else
return FALSE;
return $o;
}
}

View File

@@ -538,9 +538,13 @@ class HomeController extends Controller
$result = $import->process();
} catch (NotImplementedException $e) {
Log::error(sprintf('Import Exception [%s]',$e->getMessage()));
abort(555,$e->getMessage());
} catch (\Exception $e) {
Log::error(sprintf('Import Exception [%s]',$e->getMessage()));
abort(598,$e->getMessage());
}

View File

@@ -101,6 +101,30 @@ class Entry extends Model
->toArray();
}
/**
* This replaces the model's get dirty, given that we store LDAP attributes in $this->objects, replacing
* $this->original/$this->attributes
*
* @return array
*/
public function getDirty(): array
{
$result = collect();
foreach ($this->objects as $o)
if ($o->isDirty())
$result = $result->merge($o->getDirty());
$result = $result
->flatMap(function($item,$attr) {
return ($x=collect($item))->count()
? $x->flatMap(fn($v,$k)=>[strtolower($attr.(($k !== self::TAG_NOTAG) ? ';'.$k : ''))=>$v])
: [strtolower($attr)=>[]];
});
return $result->toArray();
}
/**
* Determine if the new and old values for a given key are equivalent.
*/
@@ -454,7 +478,7 @@ class Entry extends Model
$ot = $this->getOtherTags();
$cache[$tag ?: '_all_'] = $this->objects
->filter(fn($item)=>(! $item->is_internal) && ((! $item->no_attr_tags) || (! $tag) || ($tag === Entry::TAG_NOTAG)))
->filter(fn($item)=>(! $item->is_internal) && ((! $item->no_attr_tags) || (! $tag) || ($tag === self::TAG_NOTAG)))
->filter(fn($item)=>(! $tag) || $ot->has($item->name_lc) || count($item->tagValues($tag)) > 0);
}