diff --git a/app/Classes/LDAP/Attribute.php b/app/Classes/LDAP/Attribute.php index 3b144083..f0403bb4 100644 --- a/app/Classes/LDAP/Attribute.php +++ b/app/Classes/LDAP/Attribute.php @@ -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 * diff --git a/app/Classes/LDAP/Import.php b/app/Classes/LDAP/Import.php index 61525508..6dfa783a 100644 --- a/app/Classes/LDAP/Import.php +++ b/app/Classes/LDAP/Import.php @@ -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); diff --git a/app/Classes/LDAP/Import/LDIF.php b/app/Classes/LDAP/Import/LDIF.php index fc4e5896..6d6df978 100644 --- a/app/Classes/LDAP/Import/LDIF.php +++ b/app/Classes/LDAP/Import/LDIF.php @@ -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,21 +34,29 @@ 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)) { + $o = $this->entry($o,$attribute,$subaction,$base64encoded ? base64_decode($value) : $value,$c); + + $base64encoded = FALSE; + $value = ''; + $attribute = NULL; + 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); + $o = $this->entry($o,$attribute,$subaction,$base64encoded ? base64_decode($value) : $value,$c); Log::debug(sprintf('%s:- Committing Entry [%s]',self::LOGKEY,$o->getDN())); @@ -59,27 +69,106 @@ 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] adding [%s] (%d)',self::LOGKEY,$attribute,$line,$c)); + + // add to last attr value + continue; + + } else { + $base64encoded = ($m[2] === '::'); + $value = $m[3]; + } + + // 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 = NULL; + $o = $this->entry($o,$m[3],'delete','',$c); + break; + case 'version': if (! is_null($version)) throw new VersionException(sprintf('Version has already been set at [%d]. (line %d)',$version,$c)); @@ -92,49 +181,12 @@ 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); } if ($version !== 1) @@ -142,9 +194,9 @@ 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())); @@ -156,75 +208,40 @@ 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 (! $o->getObject($attribute)) + throw new \Exception(sprintf('Attribute [%s] doesnt exist in [%s] (line %d)',$attribute,$o->getDn(),$c)); - } 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; } } \ No newline at end of file diff --git a/app/Ldap/Entry.php b/app/Ldap/Entry.php index d266d549..084b4f1e 100644 --- a/app/Ldap/Entry.php +++ b/app/Ldap/Entry.php @@ -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); } diff --git a/resources/views/frames/import_result.blade.php b/resources/views/frames/import_result.blade.php index acd8b04b..cc24baca 100644 --- a/resources/views/frames/import_result.blade.php +++ b/resources/views/frames/import_result.blade.php @@ -30,7 +30,13 @@ @foreach($result as $item) - {{ $item->get('dn') }} + + @if($x=$item->get('link')) + {{ $item->get('dn') }} + @else + {{ $item->get('dn') }} + @endif + {{ $item->get('result') }} {{ $item->get('line') }}