diff --git a/contrib/openldap/core-fd-conf.schema b/contrib/openldap/core-fd-conf.schema index 98f049a175ac6c77e1068c7ae1ff49314d7bc9ce..191abfa0063155f7642731fe229b8e2c2f7444e0 100644 --- a/contrib/openldap/core-fd-conf.schema +++ b/contrib/openldap/core-fd-conf.schema @@ -361,6 +361,31 @@ attributetype ( 1.3.6.1.4.1.38414.8.17.2 NAME 'fdSnapshotBase' SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE) +attributetype ( 1.3.6.1.4.1.38414.8.17.3 NAME 'fdEnableAutomaticSnapshots' + DESC 'FusionDirectory - Weither or not to enable snapshots' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.38414.8.17.4 NAME 'fdSnapshotMinRetention' + DESC 'Minimum number of snapshots to be kept in store' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.38414.8.17.5 NAME 'fdSnapshotRetentionDays' + DESC 'Number of days a snapshot should be kept' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.38414.8.17.6 NAME 'fdSnapshotSourceData' + DESC 'Possible Origin / Source of data received ' + EQUALITY octetStringMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.40) + SINGLE-VALUE) + # Miscellaneous attributetype ( 1.3.6.1.4.1.38414.8.18.2 NAME 'fdTabHook' @@ -643,9 +668,9 @@ objectclass ( 1.3.6.1.4.1.38414.8.2.1 NAME 'fusionDirectoryConf' fdPluginsMenuBlacklist $ fdManagementConfig $ fdManagementUserConfig $ fdAclTabOnObjects $ fdDepartmentCategories $ fdAclTargetFilterLimit $ fdIncrementalModifierStates $ - fdSslCaCertPath $ fdSslKeyPath $ fdSslCertPath $ + fdSslCaCertPath $ fdSslKeyPath $ fdSslCertPath $ fdSnapshotRetentionDays $ fdSnapshotSourceData $ fdCasActivated $ fdCasServerCaCertPath $ fdCasHost $ fdCasPort $ fdCasContext $ fdCasVerbose $ - fdLoginMethod $ fdCasLibraryBool $ fdCasClientServiceName + fdLoginMethod $ fdCasLibraryBool $ fdCasClientServiceName $ fdEnableAutomaticSnapshots $ fdSnapshotMinRetention ) ) objectclass ( 1.3.6.1.4.1.38414.8.2.2 NAME 'fusionDirectoryPluginsConf' diff --git a/contrib/openldap/core-fd.schema b/contrib/openldap/core-fd.schema index cb5367d10d5a5691ddc52f054540b6f2fc23ad4b..da9453bad3c0a1cc1ae722f28fd9c10f1d793793 100644 --- a/contrib/openldap/core-fd.schema +++ b/contrib/openldap/core-fd.schema @@ -64,6 +64,12 @@ attributetype ( 1.3.6.1.4.1.38414.62.1.4 NAME 'fdSnapshotObjectType' SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) +attributetype ( 1.3.6.1.4.1.38414.62.1.51 NAME 'fdSnapshotDataSource' + DESC 'FusionDirectory - snapshot data origin / source' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) + ##### Subscriptions Attributes ###### attributetype ( 1.3.6.1.4.1.38414.62.11.1 NAME 'fdSubscriptionStartDate' @@ -394,7 +400,7 @@ objectclass ( 1.3.6.1.4.1.10098.1.2.1.19.18 NAME 'gosaAcl' 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 ) + MUST ( gosaSnapshotTimestamp $ gosaSnapshotDN $ gosaSnapshotData $ fdSnapshotDataSource ) MAY ( fdSnapshotObjectType $ description ) ) ### New FusionDirectory Objectclass ### diff --git a/include/management/class_management.inc b/include/management/class_management.inc index 51631eb49df402d0f3f12e577592905407becb97..27f806e8c115addc6ebab784c8b89f6e73d0e7e5 100644 --- a/include/management/class_management.inc +++ b/include/management/class_management.inc @@ -1318,17 +1318,19 @@ class management implements FusionDirectoryDialog /*! * \brief Creates a new snapshot entry + * If source arg is not set, default to 'FD'. */ - function createSnapshot (string $dn, string $description) + function createSnapshot (string $dn, string $description, string $snapshotSource = 'FD') { global $ui; + if (empty($dn) || ($this->currentDn !== $dn)) { trigger_error('There was a problem with the snapshot workflow'); return; } $entry = $this->listing->getEntry($dn); if ($entry->snapshotCreationAllowed()) { - $this->snapHandler->createSnapshot($dn, $description, $entry->type); + $this->snapHandler->createSnapshot($dn, $description, $entry->type, $snapshotSource); logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $dn, 'Snapshot created!'); } else { $error = new FusionDirectoryPermissionError(htmlescape(sprintf(_('You are not allowed to restore a snapshot for %s.'), $dn))); diff --git a/include/management/snapshot/class_SnapshotCreateDialog.inc b/include/management/snapshot/class_SnapshotCreateDialog.inc index 89aebcf3c702e8c2b320db91919e69378cac64ed..b8f7d50b84bb22d369cc3bc2e8a65736f8a50ff4 100644 --- a/include/management/snapshot/class_SnapshotCreateDialog.inc +++ b/include/management/snapshot/class_SnapshotCreateDialog.inc @@ -56,17 +56,46 @@ class SnapshotCreateDialog extends ManagementDialog ), ] ], + 'dataSource' => [ + 'name' => _('dataSource - only available via web-service.'), + 'attrs' => [ + new SelectAttribute( + 'Data source', _('Origin / Source of the data'), + 'snapshotSource', FALSE, + ), + ] + ], ]; } function __construct (string $dn, management $parent, string $aclCategory) { parent::__construct(NULL, NULL, $parent); + // The attribut will be passed to parent for later saving, dataSource might require same logic. $this->attributesAccess['description']->setInLdap(FALSE); + $this->attributesAccess['snapshotSource']->setInLdap(FALSE); + $this->attributesAccess['snapshotSource']->setVisible(FALSE); + + $recordedDataSources = $this->getLdapRecordedDataSources(); + if (!empty($recordedDataSources)) { + $this->attributesAccess['snapshotSource']->setChoices($recordedDataSources); + } + $this->object_dn = $dn; $this->aclCategory = $aclCategory; } + /* + * Retrieve the data sources from configuration. + */ + public function getLdapRecordedDataSources () : array + { + global $config; + $recordedDataSources = $config->current['SNAPSHOTSOURCEDATA']; + + return $recordedDataSources; + } + /*! * \brief Get LDAP base to use for ACL checks */ @@ -116,7 +145,11 @@ class SnapshotCreateDialog extends ManagementDialog function save (): array { - $this->parent->createSnapshot($this->object_dn, $this->description); + // snapshotSource is always set but can be empty and must be defaulted. + if (empty($this->snapshotSource)) { + $this->snapshotSource = 'FD'; + } + $this->parent->createSnapshot($this->object_dn, $this->description, $this->snapshotSource); return []; } diff --git a/include/management/snapshot/class_SnapshotHandler.inc b/include/management/snapshot/class_SnapshotHandler.inc index 34ad2e2fbfba8f8f098d9d0d0ebf71f915e1ec8e..130552e4a4df95117e4ec78a5125336b6b965bd4 100644 --- a/include/management/snapshot/class_SnapshotHandler.inc +++ b/include/management/snapshot/class_SnapshotHandler.inc @@ -197,8 +197,10 @@ class SnapshotHandler * \param string $description Snapshot description * * \param string $objectType Type of snapshotted object + * + * \param string $snapshotSource source of the data. */ - function createSnapshot ($dn, string $description, string $objectType) + function createSnapshot ($dn, string $description, string $objectType, string $snapshotSource = 'FD') { global $config; if (!$this->enabled()) { @@ -251,6 +253,7 @@ class SnapshotHandler $target['gosaSnapshotDN'] = $dn; $target['description'] = $description; $target['fdSnapshotObjectType'] = $objectType; + $target['fdSnapshotDataSource'] = $snapshotSource; /* Insert the new snapshot But we have to check first, if the given gosaSnapshotTimestamp @@ -281,6 +284,48 @@ class SnapshotHandler logging::log('snapshot', 'create', $new_dn, array_keys($target), $ldap->get_error()); } + // function verifing the configuration retention for snapshots. + // Remove snapshots from the user if retention rules approves. + public function verifySnapshotRetention (string $dn) : void + { + global $config; + + $snapMinRetention = $config->current['SNAPSHOTMINRETENTION']; + $snapRetentionDays = $config->current['SNAPSHOTRETENTIONDAYS']; + + // calculate the epoch date on which snaps can be delete. + $todayMinusRetention = time() - ($snapRetentionDays * 24 * 60 * 60); + $snapDateToDelete = strtotime(date('Y-m-d H:i:s', $todayMinusRetention)); + + $dnSnapshotsList = $this->getSnapshots($dn, TRUE); + $snapToDelete = []; + $snapCount = 0; + + if (isset($dnSnapshotsList) && !empty($dnSnapshotsList)) { + foreach ($dnSnapshotsList as $snap) { + $snapCount += 1; + // let's keep seconds instead of nanosecs + $snapEpoch = preg_split('/-/', $snap['gosaSnapshotTimestamp'][0]); + if ($snapEpoch[0] < $snapDateToDelete) { + $snapToDelete[] = $snap['dn']; + } + } + } + + // The not empty is not mandatory but is more ressource friendly + if (!empty($snapToDelete) && ($snapCount > $snapMinRetention)) { + $snapToKeep = $snapCount - $snapMinRetention; + // Sort snapToDelete by old first DN timestamp is the only thing different. + sort($snapToDelete); + for ($i = 0; $i < $snapToKeep; $i++) { + // not empty required because array keeps on being iterated even if NULL object. + if (!empty($snapToDelete[$i])) { + $this->removeSnapshot($snapToDelete[$i]); + } + } + } + } + /*! * \brief Remove a snapshot * diff --git a/plugins/config/class_configInLdap.inc b/plugins/config/class_configInLdap.inc index 6ff6597911033d52c75ef298d5a2f6af73afda1e..8cf0034b45ef282dcfce54cfaf8bb38848e47d40 100644 --- a/plugins/config/class_configInLdap.inc +++ b/plugins/config/class_configInLdap.inc @@ -101,16 +101,6 @@ class configInLdap extends simplePlugin 'fdSchemaCheck', FALSE, TRUE ), - new BooleanAttribute( - _('Enable snapshots'), _('This enables you to save certain states of entries and restore them later on.'), - 'fdEnableSnapshots', FALSE, - TRUE - ), - new StringAttribute( - _('Snapshot base'), _('The base where snapshots should be stored inside the LDAP directory.'), - 'fdSnapshotBase', FALSE, - 'ou=snapshots,'.$config->current['BASE'] - ), new BooleanAttribute( _('Wildcard foreign keys'), _('Enables wildcard searches like member=* when moving a whole department. This will open all existing groups and roles to make sure foreign keys are respected. Slow on big trees.'), 'fdWildcardForeignKeys', FALSE, @@ -475,16 +465,6 @@ class configInLdap extends simplePlugin $this->fusionConfigMd5 = md5_file(CACHE_DIR."/".CLASS_CACHE); - $this->attributesAccess['fdEnableSnapshots']->setManagedAttributes( - [ - 'disable' => [ - FALSE => [ - 'fdSnapshotBase', - ] - ] - ] - ); - $this->attributesAccess['fdForceSSL']->setManagedAttributes( [ 'disable' => [ diff --git a/plugins/config/class_snapshotConfig.inc b/plugins/config/class_snapshotConfig.inc new file mode 100644 index 0000000000000000000000000000000000000000..8c260e08228c0982cba47d0f110a921d1a31bf7d --- /dev/null +++ b/plugins/config/class_snapshotConfig.inc @@ -0,0 +1,106 @@ +<?php +/* +This code is part of FusionDirectory (http://www.fusiondirectory.org/) +Copyright (C) 2012-2023 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 +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +class snapshotsConfig extends simplePlugin +{ + static function plInfo (): array + { + return [ + 'plShortName' => _('Snapshots'), + 'plDescription' => _('FusionDirectory Snapshot Configuration'), + 'plObjectClass' => ['fusionDirectoryConf'], + 'plObjectType' => ['configuration'], + 'plProvidedAcls' => parent::generatePlProvidedAcls(static::getAttributesInfo()) + ]; + } + + static function getAttributesInfo (): array + { + global $config; + + return [ + 'snapshotsConf' => [ + 'name' => _('Snapshots Configuration'), + 'attrs' => [ + new BooleanAttribute( + _('Enable snapshots'), _('This enables you to save certain states of entries and restore them later on.'), + 'fdEnableSnapshots', FALSE, + TRUE + ), + new BooleanAttribute( + _('Enable automatic snapshots'), _('This enables you to automatically create a snapshot upon saving if any modifications have been found.'), + 'fdEnableAutomaticSnapshots', FALSE, + FALSE + ), + new StringAttribute( + _('Snapshot base'), _('The base where snapshots should be stored inside the LDAP directory.'), + 'fdSnapshotBase', FALSE, + 'ou=snapshots,'.$config->current['BASE'] + ), + ] + ], + 'snapshotsAdvanceConf' => [ + 'name' => _('Snapshots Advance Configuration'), + 'attrs' => [ + new IntAttribute( + _('Minimum number of snapshots to be kept'), _('Set the minimum number of snapshots to be kept'), + 'fdSnapshotMinRetention', FALSE, '', FALSE, '' + ), + new IntAttribute( + _('Retention time in days'), _('Set the retention time in days for a snapshots to be kept'), + 'fdSnapshotRetentionDays', FALSE, '', FALSE, '' + ), + ] + ], + 'OriginDataSource' => [ + 'name' => _('List of available sources / origin of data'), + 'attrs' => [ + new SetAttribute( + new StringAttribute( + _('Origin / source of data'), _('Origin / Source of data'), + 'fdSnapshotSourceData', FALSE, + ) + ), + ] + ], + ]; + } + + function __construct ($dn = NULL, $object = NULL, $parent = NULL, $mainTab = FALSE) + { + global $config; + parent::__construct($dn, $object, $parent, $mainTab); + + $this->attributesAccess['fdEnableSnapshots']->setManagedAttributes( + [ + 'disable' => [ + FALSE => [ + 'fdSnapshotBase', + 'fdEnableAutomaticSnapshots', + 'fdSnapshotMinRetention', + 'fdSnapshotRetentionDays', + ] + ] + ] + ); + } + +} + diff --git a/plugins/personal/generic/class_user.inc b/plugins/personal/generic/class_user.inc index 35531802a2c968264821b0dfbc2935fc5f7a09d0..08f4f2053bf3fe8230c6e64064b54c325b369f20 100644 --- a/plugins/personal/generic/class_user.inc +++ b/plugins/personal/generic/class_user.inc @@ -375,7 +375,7 @@ class user extends simplePlugin function post_save () { - global $ui; + global $ui, $config; /* Update current locale settings, if we have edited ourselves */ if (isset($this->attrs['preferredLanguage']) && ($this->dn == $ui->dn)) { @@ -383,10 +383,26 @@ class user extends simplePlugin session::set('ui', $ui); session::set('Last_init_lang', 'update'); } - + // Verification is snapshot is enabled and automatic. + // Note : string values are stored in that array. + if (isset($config->current['ENABLEAUTOMATICSNAPSHOTS']) && isset($config->current['ENABLESNAPSHOTS'])) { + if (strtolower($config->current['ENABLEAUTOMATICSNAPSHOTS']) === 'true' && strtolower($config->current['ENABLESNAPSHOTS']) === 'true' ) { + $this->generateAutomaticSnapshot(); + } + } return parent::post_save(); } + /* + * Create the snapshot object in case of automated snapshot. + */ + public function generateAutomaticSnapshot () + { + $snapshotHandler = new SnapshotHandler(); + $snapshotHandler->createSnapshot($this->dn, 'automatic snapshot', 'USER', 'FD'); + $snapshotHandler->verifySnapshotRetention($this->dn); + } + function adapt_from_template (array $attrs, array $skip = []) { if ($this->uid != '') {