From b6468a5630e043dd29a7a980978a931c13579e13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= <come@opensides.be> Date: Wed, 5 Jun 2019 11:06:30 +0200 Subject: [PATCH] :sparkles: feat(snapshots) Automatically open/save object after snapshot restore This is an attempt at putting restored object through FD open/save cycle to make sure they save without error and trigger any hook that may look for changes. Also store object type in the snapshot LDAP node, even if it is unused at the moment. issue #5715 --- contrib/openldap/core-fd.schema | 8 +++- include/class_ldap.inc | 47 +++++++++++-------- include/management/class_management.inc | 30 ++++++++---- .../management/class_managementListing.inc | 28 +++++++---- .../snapshot/class_SnapshotHandler.inc | 41 +++++++++------- .../simpleplugin/class_simpleManagement.inc | 10 ++-- 6 files changed, 102 insertions(+), 62 deletions(-) diff --git a/contrib/openldap/core-fd.schema b/contrib/openldap/core-fd.schema index f43752f7a..0f41946cf 100644 --- a/contrib/openldap/core-fd.schema +++ b/contrib/openldap/core-fd.schema @@ -57,6 +57,12 @@ attributetype ( 1.3.6.1.4.1.38414.62.1.3 NAME 'fdLockTimestamp' ORDERING generalizedTimeOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE ) +attributetype ( 1.3.6.1.4.1.38414.62.1.4 NAME 'fdSnapshotObjectType' + DESC 'FusionDirectory - object type of the snapshotted object' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) + # Classes objectclass ( 1.3.6.1.4.1.10098.1.2.1.19.4 NAME 'gosaDepartment' SUP top AUXILIARY @@ -84,7 +90,7 @@ objectclass ( 1.3.6.1.4.1.10098.1.2.1.19.19 NAME 'gosaSnapshotObject' DESC 'GOsa - Container object for undo and snapshot data' SUP top STRUCTURAL MUST ( gosaSnapshotTimestamp $ gosaSnapshotDN $ gosaSnapshotData ) - MAY ( description ) ) + MAY ( fdSnapshotObjectType $ description ) ) objectclass ( 1.3.6.1.4.1.38414.62.2.1 NAME 'fdLockEntry' SUP top STRUCTURAL DESC 'FusionDirectory - Class for FD locking' diff --git a/include/class_ldap.inc b/include/class_ldap.inc index 48d58f9ff..71f2be226 100644 --- a/include/class_ldap.inc +++ b/include/class_ldap.inc @@ -1037,27 +1037,8 @@ class LDAP return @ldap_read($this->cid, $dn, "(objectClass=*)", ["objectClass"]); } - /*! - * \brief Function to imports ldifs - * - * If DeleteOldEntries is TRUE, the destination entry will be deleted first. - * If JustModify is TRUE the destination entry will only be touched by the attributes specified in the ldif. - * if JustMofify is FALSE the destination dn will be overwritten by the new ldif. - * - * \param integer $srp - * - * \param string $str_attr - * - * \param boolean $JustModify - * - * \param boolean $DeleteOldEntries - */ - function import_complete_ldif ($srp, $str_attr, $JustModify, $DeleteOldEntries) + function parseLdif (string $str_attr): array { - if ($this->reconnect) { - $this->connect(); - } - /* First we split the string into lines */ $fileLines = preg_split("/\n/", $str_attr); if (end($fileLines) != '') { @@ -1126,6 +1107,32 @@ class LDAP } } + return $entries; + } + + /*! + * \brief Function to imports ldifs + * + * If DeleteOldEntries is TRUE, the destination entry will be deleted first. + * If JustModify is TRUE the destination entry will only be touched by the attributes specified in the ldif. + * if JustMofify is FALSE the destination dn will be overwritten by the new ldif. + * + * \param integer $srp + * + * \param string $str_attr + * + * \param boolean $JustModify + * + * \param boolean $DeleteOldEntries + */ + function import_complete_ldif ($srp, $str_attr, $JustModify, $DeleteOldEntries) + { + $entries = $this->parseLdif($str_attr); + + if ($this->reconnect) { + $this->connect(); + } + foreach ($entries as $startLine => $entry) { /* Delete before insert */ $usermdir = ($this->dn_exists($entry['dn']) && $DeleteOldEntries); diff --git a/include/management/class_management.inc b/include/management/class_management.inc index 03534ec60..223e72993 100644 --- a/include/management/class_management.inc +++ b/include/management/class_management.inc @@ -1055,7 +1055,7 @@ class management function createSnapshotDialog (array $action) { global $config, $ui; - @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $action['targets'], 'Snaptshot creation initiated!'); + @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $action['targets'], 'Snapshot creation initiated!'); $this->currentDn = array_pop($action['targets']); if (empty($this->currentDn)) { @@ -1093,7 +1093,7 @@ class management } if ($ui->allow_snapshot_restore($this->currentDn, $aclCategory, empty($action['targets']))) { - @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $this->currentDn, 'Snaptshot restoring initiated!'); + @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $this->currentDn, 'Snapshot restoring initiated!'); $this->dialogObject = new SnapshotRestoreDialog($this->currentDn, $this, empty($action['targets']), $aclCategory); } else { msg_dialog::display(_('Permission'), sprintf(_('You are not allowed to restore a snapshot for %s.'), $this->currentDn), @@ -1176,13 +1176,14 @@ class management function createSnapshot (string $dn, string $description) { global $ui; - if ($this->currentDn !== $dn) { + if (empty($dn) || ($this->currentDn !== $dn)) { trigger_error('There was a problem with the snapshot workflow'); return; } - if (!empty($dn) && $ui->allow_snapshot_create($dn, $this->dialogObject->aclCategory)) { - $this->snapHandler->createSnapshot($dn, $description); - @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Snaptshot created!'); + $entry = $this->listing->getEntry($dn); + if ($entry->snapshotCreationAllowed()) { + $this->snapHandler->createSnapshot($dn, $description, $entry->type); + @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Snapshot created!'); } else { msg_dialog::display(_('Permission'), sprintf(_('You are not allowed to restore a snapshot for %s.'), $dn), ERROR_DIALOG); @@ -1198,9 +1199,20 @@ class management { global $ui; if (!empty($dn) && $ui->allow_snapshot_restore($dn, $this->dialogObject->aclCategory, $this->dialogObject->global)) { - $this->snapHandler->restoreSnapshot($dn); - @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Snaptshot restored'); + $dn = $this->snapHandler->restoreSnapshot($dn); + @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Snapshot restored'); $this->closeDialogs(); + if ($dn !== FALSE) { + $this->listing->focusDn($dn); + $entry = $this->listing->getEntry($dn); + $this->currentDn = $dn; + set_object_info($this->currentDn); + add_lock($this->currentDn, $ui->dn); + + // Open object + $this->openTabObject(objects::open($this->currentDn, $entry->getTemplatedType()), $this->currentDn); + $this->saveChanges(); + } } else { msg_dialog::display(_('Permission'), sprintf(_('You are not allowed to restore a snapshot for %s.'), $dn), ERROR_DIALOG); @@ -1217,7 +1229,7 @@ class management global $ui; if (!empty($dn) && $ui->allow_snapshot_delete($dn, $this->dialogObject->aclCategory)) { $this->snapHandler->removeSnapshot($dn); - @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Snaptshot deleted'); + @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Snapshot deleted'); } else { msg_dialog::display(_('Permission'), sprintf(_('You are not allowed to delete a snapshot for %s.'), $dn), ERROR_DIALOG); diff --git a/include/management/class_managementListing.inc b/include/management/class_managementListing.inc index e98ece742..b8110f383 100644 --- a/include/management/class_managementListing.inc +++ b/include/management/class_managementListing.inc @@ -462,16 +462,7 @@ class managementListing /* Pre-render list to init things if a dn is gonna be opened on first load */ $dn = urldecode($_REQUEST['dn']); $action = $m[1]; - /* Detect the longer base valid for this dn */ - $longerBase = ''; - foreach (array_keys($this->bases) as $base) { - if (preg_match('/'.preg_quote($base, '/').'$/i', $dn) - && (strlen($base) > strlen($longerBase))) { - $longerBase = $base; - } - } - $this->setBase($longerBase); - $this->update($dn); + $this->focusDn($dn); $this->render(); $result['action'] = $action; @@ -527,6 +518,23 @@ class managementListing return $result; } + /*! + * \brief Set base close to this dn and load only him + */ + function focusDn (string $dn) + { + /* Detect the longer base valid for this dn */ + $longerBase = ''; + foreach (array_keys($this->bases) as $base) { + if (preg_match('/'.preg_quote($base, '/').'$/i', $dn) + && (strlen($base) > strlen($longerBase))) { + $longerBase = $base; + } + } + $this->setBase($longerBase); + $this->update($dn); + } + /*! * \brief Refresh the bases list */ diff --git a/include/management/snapshot/class_SnapshotHandler.inc b/include/management/snapshot/class_SnapshotHandler.inc index 2e98d5a58..e2dc19cf7 100644 --- a/include/management/snapshot/class_SnapshotHandler.inc +++ b/include/management/snapshot/class_SnapshotHandler.inc @@ -1,8 +1,9 @@ <?php /* This code is part of FusionDirectory (http://www.fusiondirectory.org/) + Copyright (C) 2003-2010 Cajus Pollmeier - Copyright (C) 2011-2016 FusionDirectory + Copyright (C) 2011-2019 FusionDirectory This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -190,13 +191,15 @@ class SnapshotHandler * * \param string $dn The DN * - * \param array $description Snapshot description + * \param string $description Snapshot description + * + * \param string $objectType Type of snapshotted object */ - function createSnapshot ($dn, $description = []) + function createSnapshot ($dn, string $description, string $objectType) { global $config; if (!$this->enabled()) { - @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Snaptshot are disabled but tried to create snapshot'); + @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Snapshot are disabled but tried to create snapshot'); return; } @@ -230,12 +233,13 @@ class SnapshotHandler } } - $target = []; + $target = []; - $target['objectClass'] = ['top', 'gosaSnapshotObject']; - $target['gosaSnapshotData'] = gzcompress($data, 6); - $target['gosaSnapshotDN'] = $dn; - $target['description'] = $description; + $target['objectClass'] = ['top', 'gosaSnapshotObject']; + $target['gosaSnapshotData'] = gzcompress($data, 6); + $target['gosaSnapshotDN'] = $dn; + $target['description'] = $description; + $target['fdSnapshotObjectType'] = $objectType; /* Insert the new snapshot But we have to check first, if the given gosaSnapshotTimestamp @@ -299,7 +303,7 @@ class SnapshotHandler $ldap->cd($new_base); $ldap->search( '(&(objectClass=gosaSnapshotObject)(gosaSnapshotDN='.ldap_escape_f($dn).'))', - ['gosaSnapshotTimestamp','gosaSnapshotDN','description'], + ['gosaSnapshotTimestamp','gosaSnapshotDN','description','fdSnapshotObjectType'], 'one' ); @@ -335,7 +339,7 @@ class SnapshotHandler $ldap->cd($new_base); $ldap->search( '(objectClass=gosaSnapshotObject)', - ['gosaSnapshotTimestamp','gosaSnapshotDN','description'], + ['gosaSnapshotTimestamp','gosaSnapshotDN','description','fdSnapshotObjectType'], 'one' ); while ($entry = $ldap->fetch()) { @@ -371,34 +375,37 @@ class SnapshotHandler { global $config; if (!$this->enabled()) { - @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Snaptshot are disabled but tried to restore snapshot'); - return []; + @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Snapshot are disabled but tried to restore snapshot'); + return FALSE; } $ldap = $config->get_ldap_link(); /* Get the snapshot */ - $ldap->cat($dn, ['gosaSnapshotData'], '(gosaSnapshotData=*)'); + $ldap->cat($dn, ['gosaSnapshotData','gosaSnapshotDN','fdSnapshotObjectType'], '(gosaSnapshotData=*)'); if ($attrs = $ldap->fetch()) { /* Prepare import string */ $data = gzuncompress($attrs['gosaSnapshotData'][0]); if ($data === FALSE) { msg_dialog::display(_('Error'), _('There was a problem uncompressing snapshot data'), ERROR_DIALOG); - return []; + return FALSE; } } else { msg_dialog::display(_('Error'), _('Snapshot data could not be fetched'), ERROR_DIALOG); - return []; + return FALSE; } /* Import the given data */ try { $ldap->import_complete_ldif($data, FALSE, FALSE); if (!$ldap->success()) { - msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $dn, "", get_class()), LDAP_ERROR); + msg_dialog::display(_('LDAP error'), msgPool::ldaperror($ldap->get_error(), $dn, '', get_class()), LDAP_ERROR); + return FALSE; } + return $attrs['gosaSnapshotDN'][0]; } catch (LDIFImportException $e) { msg_dialog::display(_('LDAP error'), $e->getMessage(), ERROR_DIALOG); + return FALSE; } } } diff --git a/include/simpleplugin/class_simpleManagement.inc b/include/simpleplugin/class_simpleManagement.inc index 2fb141b69..b07705d37 100644 --- a/include/simpleplugin/class_simpleManagement.inc +++ b/include/simpleplugin/class_simpleManagement.inc @@ -1107,7 +1107,7 @@ class simpleManagement function createSnapshotDialog ($action, array $target) { global $config, $ui; - @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $target, 'Snaptshot creation initiated!'); + @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $target, 'Snapshot creation initiated!'); if (count($target) == 1) { $this->dn = array_pop($target); @@ -1163,7 +1163,7 @@ class simpleManagement } if ($ui->allow_snapshot_restore($this->dn, $aclCategory, !count($target))) { - @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $this->dn, 'Snaptshot restoring initiated!'); + @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $this->dn, 'Snapshot restoring initiated!'); $this->dialogObject = new SnapshotRestoreDialog($this->dn, $this, !count($target), $aclCategory); } else { msg_dialog::display(_('Permission'), sprintf(_('You are not allowed to restore a snapshot for %s.'), $this->dn), @@ -1244,7 +1244,7 @@ class simpleManagement } if (!empty($dn) && $ui->allow_snapshot_create($dn, $this->dialogObject->aclCategory)) { $this->snapHandler->createSnapshot($dn, $description); - @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Snaptshot created!'); + @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Snapshot created!'); } else { msg_dialog::display(_('Permission'), sprintf(_('You are not allowed to restore a snapshot for %s.'), $dn), ERROR_DIALOG); @@ -1261,7 +1261,7 @@ class simpleManagement global $ui; if (!empty($dn) && $ui->allow_snapshot_restore($dn, $this->dialogObject->aclCategory, $this->dialogObject->global)) { $this->snapHandler->restoreSnapshot($dn); - @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Snaptshot restored'); + @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Snapshot restored'); $this->closeDialogs(); } else { msg_dialog::display(_('Permission'), sprintf(_('You are not allowed to restore a snapshot for %s.'), $dn), @@ -1317,7 +1317,7 @@ class simpleManagement global $ui; if (!empty($dn) && $ui->allow_snapshot_delete($dn, $this->dialogObject->aclCategory)) { $this->snapHandler->removeSnapshot($dn); - @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Snaptshot deleted'); + @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Snapshot deleted'); } else { msg_dialog::display(_('Permission'), sprintf(_('You are not allowed to delete a snapshot for %s.'), $dn), ERROR_DIALOG); -- GitLab