metadataRecord = new ArrayList<>(isUserHistoryEnabled ? 7 : 6);
metadataRecord.add(username);
metadataRecord.add(surname);
metadataRecord.add(name);
@@ -120,6 +132,13 @@ public void create(final ServiceContext context,
metadataRecord.add(userGroupsInfo);
metadataRecord.add(lastLoginDate);
+ if (isUserHistoryEnabled) {
+ String userChanges = userAuditableService.getEntityHistoryAsString(user.getId(), messages);
+ if (StringUtils.hasLength(userChanges)) {
+ metadataRecord.add(userChanges);
+ }
+ }
+
csvFilePrinter.printRecord(metadataRecord);
}
} finally {
@@ -129,8 +148,8 @@ public void create(final ServiceContext context,
/**
* Creates a string with the list of groups / profiles of a user:
- *
- * group1/profileGroup1-group2/profileGroup2 ...
+ *
+ * group1/profileGroup1-group2/profileGroup2 ...
*
* @param context
* @param user
@@ -159,7 +178,7 @@ private String retrieveGroupsListInfo(final ServiceContext context, User user) {
if (i++ > 0) {
userGroupsList.append("-");
}
- userGroupsList.append(groupName + "/" + groupProfile);
+ userGroupsList.append(groupName).append("/").append(groupProfile);
}
return userGroupsList.toString();
diff --git a/services/src/main/java/org/fao/geonet/api/users/UsersApi.java b/services/src/main/java/org/fao/geonet/api/users/UsersApi.java
index c6911f9f2bfb..6f8d72ebf0dc 100644
--- a/services/src/main/java/org/fao/geonet/api/users/UsersApi.java
+++ b/services/src/main/java/org/fao/geonet/api/users/UsersApi.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2001-2021 Food and Agriculture Organization of the
+ * Copyright (C) 2001-2024 Food and Agriculture Organization of the
* United Nations (FAO-UN), United Nations World Food Programme (WFP)
* and United Nations Environment Programme (UNEP)
*
@@ -38,7 +38,9 @@
import org.fao.geonet.api.users.model.UserDto;
import org.fao.geonet.api.users.validation.PasswordResetDtoValidator;
import org.fao.geonet.api.users.validation.UserDtoValidator;
+import org.fao.geonet.auditable.UserAuditableService;
import org.fao.geonet.domain.*;
+import org.fao.geonet.domain.auditable.UserAuditable;
import org.fao.geonet.exceptions.UserNotFoundEx;
import org.fao.geonet.kernel.DataManager;
import org.fao.geonet.kernel.datamanager.IMetadataUtils;
@@ -116,6 +118,9 @@ public class UsersApi {
@Autowired(required=false)
SecurityProviderConfiguration securityProviderConfiguration;
+ @Autowired
+ UserAuditableService userAuditableService;
+
private BufferedImage pixel;
public UsersApi() {
@@ -341,6 +346,9 @@ public ResponseEntity deleteUser(
}
}
+ Optional userToDelete = userRepository.findById(userIdentifier);
+ List userGroups = userGroupRepository.findAll(UserGroupSpecs.hasUserId(userIdentifier));
+
userGroupRepository.deleteAllByIdAttribute(UserGroupId_.userId,
Arrays.asList(userIdentifier));
@@ -352,6 +360,12 @@ public ResponseEntity deleteUser(
throw new UserNotFoundEx(Integer.toString(userIdentifier));
}
+ if (userToDelete.isPresent()) {
+ UserAuditable userAuditable = UserAuditable.build(userToDelete.get(), userGroups);
+ userAuditableService.auditDelete(userAuditable);
+ }
+
+
return new ResponseEntity(HttpStatus.NO_CONTENT);
}
@@ -393,7 +407,7 @@ public ResponseEntity checkUserPropertyExist(
return new ResponseEntity<>(HttpStatus.OK);
}
} else {
- throw new IllegalArgumentException(String.format("Property '%s' is not supported. You can only check username and email"));
+ throw new IllegalArgumentException("Property is not supported. You can only check username and email");
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
@@ -488,6 +502,12 @@ public ResponseEntity createUser(
user = userRepository.save(user);
setUserGroups(user, groups);
+ List userGroups = userGroupRepository.findAll(UserGroupSpecs
+ .hasUserId(user.getId()));
+
+ UserAuditable userAuditable = UserAuditable.build(user, userGroups);
+ userAuditableService.auditSave(userAuditable);
+
return new ResponseEntity(HttpStatus.NO_CONTENT);
}
@@ -543,8 +563,8 @@ public ResponseEntity updateUser(
// Check no duplicated username and if we are adding a duplicate existing name with other case combination
List usersWithUsernameIgnoreCase = userRepository.findByUsernameIgnoreCase(userDto.getUsername());
- if (usersWithUsernameIgnoreCase.size() != 0 &&
- (!usersWithUsernameIgnoreCase.stream().anyMatch(u -> u.getId() == userIdentifier)
+ if (!usersWithUsernameIgnoreCase.isEmpty() &&
+ (usersWithUsernameIgnoreCase.stream().noneMatch(u -> u.getId() == userIdentifier)
|| usersWithUsernameIgnoreCase.stream().anyMatch(u ->
u.getUsername().equals(userDto.getUsername()) && u.getId() != userIdentifier)
)) {
@@ -566,7 +586,7 @@ public ResponseEntity updateUser(
groups.addAll(processGroups(userDto.getGroupsReviewer(), Profile.Reviewer));
groups.addAll(processGroups(userDto.getGroupsUserAdmin(), Profile.UserAdmin));
- //If it is a useradmin updating,
+ //If it is an useradmin updating,
//maybe we don't know all the groups the user is part of
if (!Profile.Administrator.equals(myProfile)) {
List myUserAdminGroups = userGroupRepository.findGroupIds(Specification.where(
@@ -615,6 +635,12 @@ public ResponseEntity updateUser(
setUserGroups(user, groups);
}
+ List userGroups = userGroupRepository.findAll(UserGroupSpecs
+ .hasUserId(user.getId()));
+
+ UserAuditable userAuditable = UserAuditable.build(user, userGroups);
+ userAuditableService.auditSave(userAuditable);
+
return new ResponseEntity(HttpStatus.NO_CONTENT);
}
@@ -667,8 +693,8 @@ public ResponseEntity resetUserPassword(
Profile myProfile = session.getProfile();
String myUserId = session.getUserId();
- if (!Profile.Administrator.equals(myProfile)
- && !Profile.UserAdmin.equals(myProfile)
+ if (!Profile.Administrator.equals(myProfile)
+ && !Profile.UserAdmin.equals(myProfile)
&& !myUserId.equals(Integer.toString(userIdentifier))) {
throw new IllegalArgumentException("You don't have rights to do this");
}
@@ -793,7 +819,7 @@ private void setUserGroups(final User user, List userGroups)
.hasUserId(user.getId()));
// Have a quick reference of existing groups and profiles for this user
- Set listOfAddedProfiles = new HashSet();
+ Set listOfAddedProfiles = new HashSet<>();
for (UserGroup ug : all) {
String key = ug.getProfile().name() + ug.getGroup().getId();
listOfAddedProfiles.add(key);
@@ -801,11 +827,10 @@ private void setUserGroups(final User user, List userGroups)
// We start removing all old usergroup objects. We will remove the
// explicitly defined for this call
- Collection toRemove = new ArrayList();
- toRemove.addAll(all);
+ Collection toRemove = new ArrayList<>(all);
// New pairs of group-profile we need to add
- Collection toAdd = new ArrayList();
+ Collection toAdd = new ArrayList<>();
// For each of the parameters on the request, make sure the group is
// updated.
@@ -865,7 +890,7 @@ private void setUserGroups(final User user, List userGroups)
private List processGroups(List groupsToProcessList, Profile profile) {
- List groups = new LinkedList();
+ List groups = new LinkedList<>();
for (String g : groupsToProcessList) {
groups.add(new GroupElem(profile.name(), Integer.parseInt(g)));
}
diff --git a/web-ui/src/main/resources/catalog/components/auditable/AuditableDirective.js b/web-ui/src/main/resources/catalog/components/auditable/AuditableDirective.js
new file mode 100644
index 000000000000..b80673301e82
--- /dev/null
+++ b/web-ui/src/main/resources/catalog/components/auditable/AuditableDirective.js
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2001-2024 Food and Agriculture Organization of the
+ * United Nations (FAO-UN), United Nations World Food Programme (WFP)
+ * and United Nations Environment Programme (UNEP)
+ *
+ * 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
+ *
+ * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
+ * Rome - Italy. email: geonetwork@osgeo.org
+ */
+
+(function () {
+ goog.provide("gn_auditable_directive");
+ goog.require("gn_auditable_service");
+
+ var module = angular.module("gn_auditable_directive", ["gn_auditable_service"]);
+
+ module.directive("gnAuditableHistory", [
+ "gnAuditableService",
+ "gnConfigService",
+ "gnConfig",
+ function (gnAuditableService, gnConfigService, gnConfig) {
+ return {
+ restrict: "A",
+ replace: true,
+ scope: {
+ id: "=gnAuditableHistory",
+ type: "@"
+ },
+ templateUrl: "../../catalog/components/auditable/partials/auditableHistory.html",
+ link: function (scope, element, attrs) {
+ scope.history = [];
+
+ gnConfigService.load().then(function (c) {
+ if (gnConfig["system.auditable.enable"]) {
+ scope.$watch("id", function (n, o) {
+ if (n !== o && n !== undefined) {
+ scope.history = [];
+
+ gnAuditableService
+ .getEntityHistory(scope.type, scope.id)
+ .then(function (response) {
+ scope.history = response.data;
+ });
+ }
+ });
+ }
+ });
+ }
+ };
+ }
+ ]);
+})();
diff --git a/web-ui/src/main/resources/catalog/components/auditable/AuditableModule.js b/web-ui/src/main/resources/catalog/components/auditable/AuditableModule.js
new file mode 100644
index 000000000000..c9c653e62778
--- /dev/null
+++ b/web-ui/src/main/resources/catalog/components/auditable/AuditableModule.js
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2001-2024 Food and Agriculture Organization of the
+ * United Nations (FAO-UN), United Nations World Food Programme (WFP)
+ * and United Nations Environment Programme (UNEP)
+ *
+ * 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
+ *
+ * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
+ * Rome - Italy. email: geonetwork@osgeo.org
+ */
+
+(function () {
+ goog.provide("gn_auditable");
+
+ goog.require("gn_auditable_directive");
+ goog.require("gn_auditable_service");
+
+ var module = angular.module("gn_auditable", [
+ "gn_auditable_directive",
+ "gn_auditable_service"
+ ]);
+})();
diff --git a/web-ui/src/main/resources/catalog/components/auditable/AuditableService.js b/web-ui/src/main/resources/catalog/components/auditable/AuditableService.js
new file mode 100644
index 000000000000..304ef4913ba5
--- /dev/null
+++ b/web-ui/src/main/resources/catalog/components/auditable/AuditableService.js
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2001-2024 Food and Agriculture Organization of the
+ * United Nations (FAO-UN), United Nations World Food Programme (WFP)
+ * and United Nations Environment Programme (UNEP)
+ *
+ * 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
+ *
+ * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
+ * Rome - Italy. email: geonetwork@osgeo.org
+ */
+
+(function () {
+ goog.provide("gn_auditable_service");
+
+ var module = angular.module("gn_auditable_service", []);
+
+ /**
+ * Service to deal with Auditable entities.
+ */
+ module.service("gnAuditableService", [
+ "$http",
+ function ($http) {
+ function getEntityHistory(entityType, entityId) {
+ return $http.get("../api/auditable/" + entityType + "/" + entityId);
+ }
+
+ return {
+ getEntityHistory: getEntityHistory
+ };
+ }
+ ]);
+})();
diff --git a/web-ui/src/main/resources/catalog/components/auditable/partials/auditableHistory.html b/web-ui/src/main/resources/catalog/components/auditable/partials/auditableHistory.html
new file mode 100644
index 000000000000..7a2366a3761f
--- /dev/null
+++ b/web-ui/src/main/resources/catalog/components/auditable/partials/auditableHistory.html
@@ -0,0 +1,30 @@
+
diff --git a/web-ui/src/main/resources/catalog/js/admin/UserGroupController.js b/web-ui/src/main/resources/catalog/js/admin/UserGroupController.js
index d639c8b24e9c..8883dab9c0d3 100644
--- a/web-ui/src/main/resources/catalog/js/admin/UserGroupController.js
+++ b/web-ui/src/main/resources/catalog/js/admin/UserGroupController.js
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2001-2016 Food and Agriculture Organization of the
+ * Copyright (C) 2001-2024 Food and Agriculture Organization of the
* United Nations (FAO-UN), United Nations World Food Programme (WFP)
* and United Nations Environment Programme (UNEP)
*
@@ -27,10 +27,12 @@
goog.require("gn_dbtranslation");
goog.require("gn_multiselect");
goog.require("gn_mdtypewidget");
+ goog.require("gn_auditable");
var module = angular.module("gn_usergroup_controller", [
"gn_dbtranslation",
"gn_multiselect",
+ "gn_auditable",
"gn_mdtypewidget",
"blueimp.fileupload"
]);
@@ -46,8 +48,10 @@
"$rootScope",
"$translate",
"$timeout",
+ "$log",
"gnConfig",
"gnConfigService",
+ "gnAuditableService",
function (
$scope,
$routeParams,
@@ -55,8 +59,10 @@
$rootScope,
$translate,
$timeout,
+ $log,
gnConfig,
- gnConfigService
+ gnConfigService,
+ gnAuditableService
) {
$scope.searchObj = {
params: {
@@ -116,6 +122,7 @@
$scope.isLoadingUsers = false;
$scope.isLoadingGroups = false;
+ $scope.auditableEnabled = false;
gnConfigService.load().then(function (c) {
// take the bigger of the two values
@@ -134,6 +141,8 @@
gnConfig["system.security.passwordEnforcement.pattern"]
);
}
+
+ $scope.auditableEnabled = gnConfig["system.auditable.enable"];
});
// This is to force IE11 NOT to cache json requests
@@ -310,9 +319,21 @@
// TODO
}
);
+
+ // Load user changes
+ gnAuditableService.getEntityHistory("user", u.id).then(
+ function (response) {
+ $scope.userHistory = response.data;
+ },
+ function (response) {
+ // TODO
+ $log.error("Error retrieving the audit history of user " + u.id);
+ }
+ );
},
function (response) {
// TODO
+ $log.error("Error retrieving the info of user " + u.id);
}
);
diff --git a/web-ui/src/main/resources/catalog/locales/en-admin.json b/web-ui/src/main/resources/catalog/locales/en-admin.json
index a094d620fbe0..bd5d1c0b08b9 100644
--- a/web-ui/src/main/resources/catalog/locales/en-admin.json
+++ b/web-ui/src/main/resources/catalog/locales/en-admin.json
@@ -866,6 +866,9 @@
"system/banner": "Application banner",
"system/banner/enable": "Enable",
"system/banner/enable-help": "If set, an application banner is displayed with the message configured. To configure the message, go to Language and translations and configure a translation with the key application-banner",
+ "system/auditable": "Audit changes",
+ "system/auditable/enable": "Allow auditing changes",
+ "system/auditable/enable-help": "When enabled, audits changes in users configuration",
"metadata/workflow": "Metadata workflow",
"metadata/workflow/automaticUnpublishInvalidMd": "Automatic unpublication of invalid metadata",
"metadata/workflow/automaticUnpublishInvalidMd-help": " Automatically unpublishes metadata that is edited that becomes not valid according to xsd or schematron rules.",
@@ -1502,6 +1505,12 @@
"es.index": "Index name",
"systemPropertiesProxyConfiguration": "Using http proxy settings in system properties.",
"NoTranslationProvider": "No translation provider",
- "LibreTranslate": "Libretranslate"
+ "LibreTranslate": "Libretranslate",
+ "userHistory": "User history",
+ "userHistoryRevision": "Updated by {{revisionUser}} on {{revisionDate}}:",
+ "userHistoryFieldUpdate": "Field '{{fieldName}}' changed from '{{oldValue}}' to '{{newValue}}'",
+ "userHistoryFieldSet": "Field '{{fieldName}}' set to '{{newValue}}'",
+ "userHistoryFieldUnset": "Field '{{fieldName}}' unset",
+ "noUserHistory": "No user history available"
}
diff --git a/web-ui/src/main/resources/catalog/templates/admin/usergroup/users.html b/web-ui/src/main/resources/catalog/templates/admin/usergroup/users.html
index c6ea00580f58..d6546b63fe58 100644
--- a/web-ui/src/main/resources/catalog/templates/admin/usergroup/users.html
+++ b/web-ui/src/main/resources/catalog/templates/admin/usergroup/users.html
@@ -520,6 +520,21 @@ UserAdmin
+
+
+
+
${project.version}
+
+ ${project.groupId}
+ gn-auditable
+ ${project.version}
+
+
dlib
diff --git a/web/src/main/webapp/WEB-INF/classes/org/fao/geonet/api/Messages.properties b/web/src/main/webapp/WEB-INF/classes/org/fao/geonet/api/Messages.properties
index dd05bc8cf309..b9ee5e681a44 100644
--- a/web/src/main/webapp/WEB-INF/classes/org/fao/geonet/api/Messages.properties
+++ b/web/src/main/webapp/WEB-INF/classes/org/fao/geonet/api/Messages.properties
@@ -251,3 +251,9 @@ api.metadata.status.errorGetStatusNotAllowed=Only the owner of the metadata can
api.metadata.status.errorSetStatusNotAllowed=Only the owner of the metadata can set the status of this record. User is not the owner of the metadata.
feedback_subject_userFeedback=User feedback
+
+audit.revision=Updated by %s on %s:\n\
+%s
+audit.revision.field.set=- Field '%s' set to '%s'
+audit.revision.field.unset=- Field '%s' unset
+audit.revision.field.updated=- Field '%s' changed from '%s' to '%s'
diff --git a/web/src/main/webapp/WEB-INF/classes/org/fao/geonet/api/Messages_fre.properties b/web/src/main/webapp/WEB-INF/classes/org/fao/geonet/api/Messages_fre.properties
index 282f20855f2e..0b00cfea4bf7 100644
--- a/web/src/main/webapp/WEB-INF/classes/org/fao/geonet/api/Messages_fre.properties
+++ b/web/src/main/webapp/WEB-INF/classes/org/fao/geonet/api/Messages_fre.properties
@@ -241,3 +241,9 @@ api.metadata.status.errorGetStatusNotAllowed=Seul le propri\u00E9taire des m\u00
api.metadata.status.errorSetStatusNotAllowed=Seul le propri\u00E9taire des m\u00E9tadonn\u00E9es peut d\u00E9finir le statut de cet enregistrement. L'utilisateur n'est pas le propri\u00E9taire des m\u00E9tadonn\u00E9es
feedback_subject_userFeedback=Commentaire de l'utilisateur
+
+audit.revision=Mise \u00E0 jour par %s le %s:\n\
+%s
+audit.revision.field.set=- Champ '%s' d\u00E9fini \u00E0 '%s'
+audit.revision.field.unset=- Champ '%s' d\u00E9sactiv\u00E9
+audit.revision.field.updated=- Champ '%s' modifi\u00E9 de '%s' \u00E0 '%s'
diff --git a/web/src/main/webapp/WEB-INF/classes/setup/sql/data/data-db-default.sql b/web/src/main/webapp/WEB-INF/classes/setup/sql/data/data-db-default.sql
index 4d2a58ada957..e8a23860495f 100644
--- a/web/src/main/webapp/WEB-INF/classes/setup/sql/data/data-db-default.sql
+++ b/web/src/main/webapp/WEB-INF/classes/setup/sql/data/data-db-default.sql
@@ -750,6 +750,9 @@ INSERT INTO Settings (name, value, datatype, position, internal, editable) VALUE
INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/banner/enable', 'false', 2, 1920, 'n');
+INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/auditable/enable', 'false', 2, 12010, 'n');
+
+
-- WARNING: Security / Add this settings only if you need to allow admin
-- users to be able to reset user password. If you have mail server configured
diff --git a/web/src/main/webapp/WEB-INF/classes/setup/sql/migrate/v4212/migrate-default.sql b/web/src/main/webapp/WEB-INF/classes/setup/sql/migrate/v4212/migrate-default.sql
index bbb9a129ba3e..348a4c7c18a9 100644
--- a/web/src/main/webapp/WEB-INF/classes/setup/sql/migrate/v4212/migrate-default.sql
+++ b/web/src/main/webapp/WEB-INF/classes/setup/sql/migrate/v4212/migrate-default.sql
@@ -2,3 +2,4 @@ UPDATE Settings SET value='4.2.12' WHERE name='system/platform/version';
UPDATE Settings SET value='SNAPSHOT' WHERE name='system/platform/subVersion';
INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/banner/enable', 'false', 2, 1920, 'n');
+INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/auditable/enable', 'false', 2, 12010, 'n');
diff --git a/workers/camelPeriodicProducer/pom.xml b/workers/camelPeriodicProducer/pom.xml
index 4f773d497953..ac7cfc58591f 100644
--- a/workers/camelPeriodicProducer/pom.xml
+++ b/workers/camelPeriodicProducer/pom.xml
@@ -72,5 +72,10 @@
org.quartz-scheduler
quartz
+
+ org.springframework.data
+ spring-data-envers
+ test
+
diff --git a/workers/camelPeriodicProducer/src/test/resources/domain-repository-test-context.xml b/workers/camelPeriodicProducer/src/test/resources/domain-repository-test-context.xml
index 0fe4d59e39e2..572d8a95a50f 100644
--- a/workers/camelPeriodicProducer/src/test/resources/domain-repository-test-context.xml
+++ b/workers/camelPeriodicProducer/src/test/resources/domain-repository-test-context.xml
@@ -65,7 +65,8 @@
+ transaction-manager-ref="transactionManager"
+ factory-class="org.springframework.data.envers.repository.support.EnversRevisionRepositoryFactoryBean"/>
@@ -94,4 +95,9 @@
class="org.jasypt.encryption.pbe.StandardPBEStringEncryptor" />
+
+
+
+
+