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