diff --git a/auditable/pom.xml b/auditable/pom.xml index 2fd095e8ed5..4d475ecd0db 100644 --- a/auditable/pom.xml +++ b/auditable/pom.xml @@ -6,7 +6,7 @@ org.geonetwork-opensource geonetwork - 4.4.6-SNAPSHOT + 4.4.7-SNAPSHOT diff --git a/auditable/src/main/java/org/fao/geonet/auditable/BaseAuditableService.java b/auditable/src/main/java/org/fao/geonet/auditable/BaseAuditableService.java index e78650f33f9..9268195da1f 100644 --- a/auditable/src/main/java/org/fao/geonet/auditable/BaseAuditableService.java +++ b/auditable/src/main/java/org/fao/geonet/auditable/BaseAuditableService.java @@ -20,7 +20,6 @@ * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, * Rome - Italy. email: geonetwork@osgeo.org */ - package org.fao.geonet.auditable; import com.fasterxml.jackson.databind.ObjectMapper; @@ -39,11 +38,13 @@ import java.util.*; public abstract class BaseAuditableService { - protected static final String LINE_SEPARATOR = System.getProperty("line.separator"); + protected static final String LINE_SEPARATOR = System.lineSeparator(); protected BaseAuditableRepository repository; protected SettingManager settingManager; + public abstract String getEntityType(); + public void auditSave(U auditableEntity) { if (!isAuditableEnabled()) return; @@ -72,7 +73,6 @@ public List getEntityHistory(Integer entityIdentifier) { return retrieveRevisionHistory(revisions); } - public abstract String getEntityType(); protected String retrieveRevisionHistoryAsString(Revisions revisions, ResourceBundle messages) { List revisionInfoList = retrieveRevisionHistory(revisions); @@ -123,7 +123,7 @@ protected List retrieveRevisionHistory(Revisions revis // Initial revision ObjectMapper objectMapper = new ObjectMapper(); - Map revisionMap = objectMapper.convertValue(initialRevision.getEntity(), Map.class); + Map revisionMap = objectMapper.convertValue(initialRevision.getEntity(), Map.class); // Remove empty values and id revisionMap.values().removeAll(Arrays.asList("", null)); revisionMap.remove(idFieldName); @@ -136,13 +136,13 @@ protected List retrieveRevisionHistory(Revisions revis revisionInfoList.add(initialRevisionInfo); int i = 0; - while (i+1 < numRevisions) { + while (i + 1 < numRevisions) { Revision revision1 = revisionList.get(i); - Revision revision2 = revisionList.get(i+1); + Revision revision2 = revisionList.get(i + 1); - Map revision1Map = objectMapper.convertValue(revision1.getEntity(), Map.class); + Map revision1Map = objectMapper.convertValue(revision1.getEntity(), Map.class); revision1Map.remove(idFieldName); - Map revision2Map = objectMapper.convertValue(revision2.getEntity(), Map.class); + Map revision2Map = objectMapper.convertValue(revision2.getEntity(), Map.class); revision2Map.remove(idFieldName); MapDifference diff = Maps.difference(revision1Map, revision2Map); @@ -162,7 +162,6 @@ protected List retrieveRevisionHistory(Revisions revis RevisionFieldChange revisionFieldChange = new RevisionFieldChange(key, oldValueAsString, newValueAsString); revisionInfo.addChange(revisionFieldChange); - }); diff --git a/auditable/src/main/java/org/fao/geonet/auditable/UserAuditableService.java b/auditable/src/main/java/org/fao/geonet/auditable/UserAuditableService.java index cd1fbb8c8f8..2d00b824360 100644 --- a/auditable/src/main/java/org/fao/geonet/auditable/UserAuditableService.java +++ b/auditable/src/main/java/org/fao/geonet/auditable/UserAuditableService.java @@ -30,6 +30,9 @@ @Service public class UserAuditableService extends BaseAuditableService { + + public static final String ENTITY_TYPE = "user"; + public UserAuditableService(SettingManager settingManager, UserAuditableRepository repository) { this.settingManager = settingManager; this.repository = repository; @@ -37,6 +40,6 @@ public UserAuditableService(SettingManager settingManager, UserAuditableReposito @Override public String getEntityType() { - return "user"; + return ENTITY_TYPE; } } diff --git a/auditable/src/main/java/org/fao/geonet/auditable/model/RevisionFieldChange.java b/auditable/src/main/java/org/fao/geonet/auditable/model/RevisionFieldChange.java index 18a682ab150..a55bfe5cea0 100644 --- a/auditable/src/main/java/org/fao/geonet/auditable/model/RevisionFieldChange.java +++ b/auditable/src/main/java/org/fao/geonet/auditable/model/RevisionFieldChange.java @@ -1,10 +1,35 @@ +/* + * 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 + */ package org.fao.geonet.auditable.model; +/** + * This class represents a change in an entity field. It stores the field name, + * the previous and the new value. + */ public class RevisionFieldChange { - private String name; - private String oldValue; - - private String newValue; + private final String name; + private final String oldValue; + private final String newValue; public RevisionFieldChange(String name, String oldValue, String newValue) { this.name = name; diff --git a/auditable/src/main/java/org/fao/geonet/auditable/model/RevisionInfo.java b/auditable/src/main/java/org/fao/geonet/auditable/model/RevisionInfo.java index 05a52f0f0d5..3441a6e8c23 100644 --- a/auditable/src/main/java/org/fao/geonet/auditable/model/RevisionInfo.java +++ b/auditable/src/main/java/org/fao/geonet/auditable/model/RevisionInfo.java @@ -20,23 +20,20 @@ * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, * Rome - Italy. email: geonetwork@osgeo.org */ - package org.fao.geonet.auditable.model; -import org.fao.geonet.domain.ISODate; - import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.List; +import org.fao.geonet.domain.ISODate; public class RevisionInfo { - private int revisionNumber; - private String user; - private String date; - - private String value; - - private List changes; + private final int revisionNumber; + private final String user; + private final String date; + private final String value; + private final List changes; public RevisionInfo(int revisionNumber, String user, Date date, String value) { this.revisionNumber = revisionNumber; @@ -62,8 +59,11 @@ public String getValue() { return value; } + /** + * @return an unmodifiable view of the list of changes. + */ public List getChanges() { - return changes; + return Collections.unmodifiableList(changes); } public void addChange(RevisionFieldChange change) { diff --git a/auditable/src/main/resources/config-spring-geonetwork.xml b/auditable/src/main/resources/config-spring-geonetwork.xml index 95f0ac4e6d2..61411f180ac 100644 --- a/auditable/src/main/resources/config-spring-geonetwork.xml +++ b/auditable/src/main/resources/config-spring-geonetwork.xml @@ -23,12 +23,11 @@ --> + xmlns="http://www.springframework.org/schema/beans" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd"> diff --git a/auditable/src/test/java/org/fao/geonet/auditable/model/UserAuditableTest.java b/auditable/src/test/java/org/fao/geonet/auditable/model/UserAuditableTest.java index 0e931ea99c5..0b6a47b8393 100644 --- a/auditable/src/test/java/org/fao/geonet/auditable/model/UserAuditableTest.java +++ b/auditable/src/test/java/org/fao/geonet/auditable/model/UserAuditableTest.java @@ -20,9 +20,11 @@ * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, * Rome - Italy. email: geonetwork@osgeo.org */ - package org.fao.geonet.auditable.model; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; import org.fao.geonet.domain.Group; import org.fao.geonet.domain.Profile; import org.fao.geonet.domain.User; @@ -31,21 +33,16 @@ import org.junit.Test; import org.springframework.util.StringUtils; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; - import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class UserAuditableTest { @Test public void testBuildUserAuditable() { - Group group = new Group() - .setId(1) - .setName("sample"); + Group group = new Group().setId(1).setName("sample"); + Group group2 = new Group().setId(2).setName("sampleGroup2"); User user = new User() @@ -54,7 +51,7 @@ public void testBuildUserAuditable() { .setSurname("surname") .setUsername("username") .setEnabled(true) - .setEmailAddresses(new HashSet<>(Arrays.asList("test@mail.com"))) + .setEmailAddresses(new HashSet<>(List.of("test@mail.com"))) .setProfile(Profile.Reviewer); @@ -65,7 +62,7 @@ public void testBuildUserAuditable() { .setProfile(Profile.Editor); UserGroup userGroup2 = new UserGroup() - .setGroup(group) + .setGroup(group2) .setUser(user) .setProfile(Profile.Reviewer); @@ -81,12 +78,12 @@ public void testBuildUserAuditable() { assertEquals(user.getUsername(), userAuditable.getUsername()); assertEquals(user.getEmailAddresses().toArray()[0], userAuditable.getEmailAddress()); assertEquals(user.getProfile().toString(), userAuditable.getProfile()); - assertTrue(!StringUtils.hasLength(userAuditable.getGroupsRegisteredUser())); + assertFalse(StringUtils.hasLength(userAuditable.getGroupsRegisteredUser())); assertTrue(userAuditable.getGroupsEditor().contains(group.getName())); - assertTrue(userAuditable.getGroupsReviewer().contains(group.getName())); - assertTrue(!StringUtils.hasLength(userAuditable.getGroupsUserAdmin())); + assertTrue(userAuditable.getGroupsReviewer().contains(group2.getName())); + assertFalse(StringUtils.hasLength(userAuditable.getGroupsUserAdmin())); assertEquals(group.getName(), userAuditable.getGroupsEditor()); - assertEquals(group.getName(), userAuditable.getGroupsReviewer()); + assertEquals(group2.getName(), userAuditable.getGroupsReviewer()); } } diff --git a/core/src/test/resources/org/fao/geonet/api/Messages.properties b/core/src/test/resources/org/fao/geonet/api/Messages.properties index 5b92703e6cd..6a9f4e059eb 100644 --- a/core/src/test/resources/org/fao/geonet/api/Messages.properties +++ b/core/src/test/resources/org/fao/geonet/api/Messages.properties @@ -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) # @@ -20,7 +20,6 @@ # Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, # Rome - Italy. email: geonetwork@osgeo.org # - mail_error=Failed to send email. mail_config_test_subject=%s / Test / Mail configuration mail_config_test_message=Test message from %s\n\ diff --git a/core/src/test/resources/org/fao/geonet/api/Messages_fre.properties b/core/src/test/resources/org/fao/geonet/api/Messages_fre.properties index 79833693833..97ae6d70643 100644 --- a/core/src/test/resources/org/fao/geonet/api/Messages_fre.properties +++ b/core/src/test/resources/org/fao/geonet/api/Messages_fre.properties @@ -20,7 +20,6 @@ # Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, # Rome - Italy. email: geonetwork@osgeo.org # - mail_error=Erreur lors de l'envoi du mail. mail_config_test_subject=%s / Test / Configuration serveur de mail mail_config_test_message=Message de test de %s\n\ @@ -237,8 +236,8 @@ api.metadata.status.errorSetStatusNotAllowed=Seul le propri\u00E9taire des m\u00 feedback_subject_userFeedback=Commentaire de l'utilisateur -audit.revision=Updated by %s on %s:\n\ +audit.revision=Mise \u00E0 jour par %s le %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' +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/domain/src/main/java/org/fao/geonet/auditable/UsernameAuditorAware.java b/domain/src/main/java/org/fao/geonet/auditable/UsernameAuditorAware.java index a354ee61f47..3f98f4ea5f2 100644 --- a/domain/src/main/java/org/fao/geonet/auditable/UsernameAuditorAware.java +++ b/domain/src/main/java/org/fao/geonet/auditable/UsernameAuditorAware.java @@ -20,16 +20,14 @@ * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, * Rome - Italy. email: geonetwork@osgeo.org */ - package org.fao.geonet.auditable; +import java.util.Optional; import org.fao.geonet.domain.User; import org.springframework.data.domain.AuditorAware; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import java.util.Optional; - /** * Extracts the current user used, to store the information in the auditable entities. */ diff --git a/domain/src/main/java/org/fao/geonet/auditable/package-info.java b/domain/src/main/java/org/fao/geonet/auditable/package-info.java new file mode 100644 index 00000000000..48c47d3fabf --- /dev/null +++ b/domain/src/main/java/org/fao/geonet/auditable/package-info.java @@ -0,0 +1,26 @@ +/* + * 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 + */ +@NonNullApi +package org.fao.geonet.auditable; + +import org.springframework.lang.NonNullApi; diff --git a/domain/src/main/java/org/fao/geonet/domain/auditable/AuditableEntity.java b/domain/src/main/java/org/fao/geonet/domain/auditable/AuditableEntity.java index 8f193825d88..536f12b536d 100644 --- a/domain/src/main/java/org/fao/geonet/domain/auditable/AuditableEntity.java +++ b/domain/src/main/java/org/fao/geonet/domain/auditable/AuditableEntity.java @@ -20,10 +20,15 @@ * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, * Rome - Italy. email: geonetwork@osgeo.org */ - package org.fao.geonet.domain.auditable; +import javax.persistence.Column; +import javax.persistence.EntityListeners; +import javax.persistence.MappedSuperclass; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; import com.fasterxml.jackson.annotation.JsonIgnore; +import java.util.Date; import org.hibernate.envers.Audited; import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.CreatedDate; @@ -31,9 +36,6 @@ import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import javax.persistence.*; -import java.util.Date; - /** * Base class for auditable entities, providing fields for creation and last modification user / dates. */ @@ -46,12 +48,6 @@ public abstract class AuditableEntity { @JsonIgnore protected String createdBy; - @CreatedDate - @Column(name = "created_date") - @Temporal(TemporalType.TIMESTAMP) - @JsonIgnore - private Date createdDate; - @LastModifiedBy @Column(name = "last_modified_by", nullable = true, updatable = true) @JsonIgnore @@ -63,6 +59,12 @@ public abstract class AuditableEntity { @JsonIgnore protected Date lastModifiedDate; + @CreatedDate + @Column(name = "created_date") + @Temporal(TemporalType.TIMESTAMP) + @JsonIgnore + private Date createdDate; + public String getCreatedBy() { return createdBy; } diff --git a/domain/src/main/java/org/fao/geonet/domain/auditable/UserAuditable.java b/domain/src/main/java/org/fao/geonet/domain/auditable/UserAuditable.java index 0b79be4216e..232b8ae3cbf 100644 --- a/domain/src/main/java/org/fao/geonet/domain/auditable/UserAuditable.java +++ b/domain/src/main/java/org/fao/geonet/domain/auditable/UserAuditable.java @@ -20,29 +20,30 @@ * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, * Rome - Italy. email: geonetwork@osgeo.org */ - package org.fao.geonet.domain.auditable; +import javax.annotation.Nonnull; +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.Entity; +import javax.persistence.Id; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; import org.apache.commons.lang.StringUtils; import org.fao.geonet.domain.Address; import org.fao.geonet.domain.User; import org.fao.geonet.domain.UserGroup; import org.hibernate.envers.Audited; -import javax.annotation.Nonnull; -import javax.persistence.*; -import java.util.ArrayList; -import java.util.List; - /** * An entity to audit the changes for user entities. * * @see org.fao.geonet.domain.User - * */ @Entity @Access(AccessType.PROPERTY) -@Audited(withModifiedFlag=true) +@Audited(withModifiedFlag = true) public class UserAuditable extends AuditableEntity { private int id; @@ -64,6 +65,68 @@ public class UserAuditable extends AuditableEntity { private String groupsUserAdmin; private boolean enabled; + public static UserAuditable build(User user, List userGroups) { + UserAuditable userAuditable = new UserAuditable(); + + userAuditable.setId(user.getId()); + userAuditable.setUsername(user.getUsername()); + userAuditable.setName(user.getName()); + userAuditable.setSurname(user.getSurname()); + userAuditable.setEnabled(user.isEnabled()); + userAuditable.setKind(user.getKind()); + userAuditable.setOrganisation(user.getOrganisation()); + userAuditable.setProfile(user.getProfile().name()); + if (!user.getEmailAddresses().isEmpty()) { + // A user can have only 1 address defined in the UI. + userAuditable.setEmailAddress((String) user.getEmailAddresses().toArray()[0]); + } + if (!user.getAddresses().isEmpty()) { + // A user can have only 1 address defined in the UI. + Address userAddress = (Address) user.getAddresses().toArray()[0]; + userAuditable.setAddress(userAddress.getAddress()); + userAuditable.setZip(userAddress.getZip()); + userAuditable.setState(userAddress.getState()); + userAuditable.setCity(userAddress.getCity()); + userAuditable.setCountry(userAddress.getCountry()); + } + userAuditable.setEnabled(user.isEnabled()); + + Set groupsRegisteredUserList = new TreeSet<>(); + Set groupsEditorList = new TreeSet<>(); + Set groupsReviewerList = new TreeSet<>(); + Set groupsUserAdminList = new TreeSet<>(); + + // Groups + if (userGroups != null) { + userGroups.forEach(userGroup -> { + switch (userGroup.getProfile()) { + case RegisteredUser: + groupsRegisteredUserList.add(userGroup.getGroup().getName()); + break; + case Editor: + groupsEditorList.add(userGroup.getGroup().getName()); + break; + case Reviewer: + groupsReviewerList.add(userGroup.getGroup().getName()); + break; + case UserAdmin: + groupsUserAdminList.add(userGroup.getGroup().getName()); + break; + default: + break; + } + }); + } + + + userAuditable.setGroupsRegisteredUser(StringUtils.join(groupsRegisteredUserList, ",")); + userAuditable.setGroupsEditor(StringUtils.join(groupsEditorList, ",")); + userAuditable.setGroupsReviewer(StringUtils.join(groupsReviewerList, ",")); + userAuditable.setGroupsUserAdmin(StringUtils.join(groupsUserAdminList, ",")); + + return userAuditable; + } + @Id public int getId() { return id; @@ -86,8 +149,7 @@ public String getUsername() { return username; } - @Nonnull - public void setUsername(String username) { + public void setUsername(@Nonnull String username) { this.username = username; } @@ -210,63 +272,4 @@ public boolean isEnabled() { public void setEnabled(boolean enabled) { this.enabled = enabled; } - - public static UserAuditable build(User user, List userGroups) { - UserAuditable userAuditable = new UserAuditable(); - - userAuditable.setId(user.getId()); - userAuditable.setUsername(user.getUsername()); - userAuditable.setName(user.getName()); - userAuditable.setSurname(user.getSurname()); - userAuditable.setEnabled(user.isEnabled()); - userAuditable.setKind(user.getKind()); - userAuditable.setOrganisation(user.getOrganisation()); - userAuditable.setProfile(user.getProfile().name()); - if (!user.getEmailAddresses().isEmpty()) { - // A user can have only 1 address defined in the UI. - userAuditable.setEmailAddress((String) user.getEmailAddresses().toArray()[0]); - } - if (!user.getAddresses().isEmpty()) { - // A user can have only 1 address defined in the UI. - Address userAddress = (Address) user.getAddresses().toArray()[0]; - userAuditable.setAddress(userAddress.getAddress()); - userAuditable.setZip(userAddress.getZip()); - userAuditable.setState(userAddress.getState()); - userAuditable.setCity(userAddress.getCity()); - userAuditable.setCountry(userAddress.getCountry()); - } - userAuditable.setEnabled(user.isEnabled()); - - List groupsRegisteredUserList = new ArrayList<>(); - List groupsEditorList = new ArrayList<>(); - List groupsReviewerList = new ArrayList<>(); - List groupsUserAdminList = new ArrayList<>(); - - // Groups - userGroups.stream().forEach(userGroup -> { - switch (userGroup.getProfile()) { - case RegisteredUser: - groupsRegisteredUserList.add(userGroup.getGroup().getName()); - break; - case Editor: - groupsEditorList.add(userGroup.getGroup().getName()); - break; - case Reviewer: - groupsReviewerList.add(userGroup.getGroup().getName()); - break; - case UserAdmin: - groupsUserAdminList.add(userGroup.getGroup().getName()); - break; - default: - break; - } - }); - - userAuditable.setGroupsRegisteredUser(StringUtils.join(groupsRegisteredUserList.toArray(), ",")); - userAuditable.setGroupsEditor(StringUtils.join(groupsEditorList.toArray(), ",")); - userAuditable.setGroupsReviewer(StringUtils.join(groupsReviewerList.toArray(), ",")); - userAuditable.setGroupsUserAdmin(StringUtils.join(groupsUserAdminList.toArray(), ",")); - - return userAuditable; - } } diff --git a/domain/src/main/java/org/fao/geonet/repository/BaseAuditableRepository.java b/domain/src/main/java/org/fao/geonet/repository/BaseAuditableRepository.java index d758e640a62..0a7cb21747a 100644 --- a/domain/src/main/java/org/fao/geonet/repository/BaseAuditableRepository.java +++ b/domain/src/main/java/org/fao/geonet/repository/BaseAuditableRepository.java @@ -1,6 +1,27 @@ +/* + * 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 + */ package org.fao.geonet.repository; - import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.NoRepositoryBean; import org.springframework.data.repository.history.RevisionRepository; diff --git a/domain/src/main/java/org/fao/geonet/repository/UserAuditableRepository.java b/domain/src/main/java/org/fao/geonet/repository/UserAuditableRepository.java index de9cb692e9b..86ec4cc8f4e 100644 --- a/domain/src/main/java/org/fao/geonet/repository/UserAuditableRepository.java +++ b/domain/src/main/java/org/fao/geonet/repository/UserAuditableRepository.java @@ -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) * @@ -20,19 +20,12 @@ * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, * Rome - Italy. email: geonetwork@osgeo.org */ - package org.fao.geonet.repository; - import org.fao.geonet.domain.auditable.UserAuditable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.repository.history.RevisionRepository; -import org.springframework.stereotype.Repository; - /** * Data Access object for accessing {@link UserAuditable} entities. - * */ public interface UserAuditableRepository extends BaseAuditableRepository { diff --git a/services/src/main/java/org/fao/geonet/api/auditable/AuditableApi.java b/services/src/main/java/org/fao/geonet/api/auditable/AuditableApi.java index 5f2778d3695..880c6e48ec8 100644 --- a/services/src/main/java/org/fao/geonet/api/auditable/AuditableApi.java +++ b/services/src/main/java/org/fao/geonet/api/auditable/AuditableApi.java @@ -23,24 +23,26 @@ package org.fao.geonet.api.auditable; +import javax.servlet.ServletRequest; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; -import org.fao.geonet.api.ApiUtils; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.fao.geonet.auditable.BaseAuditableService; import org.fao.geonet.auditable.model.RevisionInfo; import org.fao.geonet.domain.auditable.AuditableEntity; import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.core.ResolvableType; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; - -import javax.servlet.ServletRequest; -import java.util.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; @RequestMapping(value = { @@ -52,7 +54,7 @@ public class AuditableApi { // Auditable service beans - private Map> factory = new HashMap<>(); + private final Map> factory = new HashMap<>(); public AuditableApi(ListableBeanFactory beanFactory) { Collection auditableServiceBeans = beanFactory.getBeansOfType(BaseAuditableService.class).values(); @@ -82,14 +84,9 @@ public List getEntityHistory( required = true ) @PathVariable - Integer entityIdentifier, - @Parameter(hidden = true) - ServletRequest request - ) { - ResourceBundle messages = ApiUtils.getMessagesResourceBundle(request.getLocales()); - + Integer entityIdentifier + ) { BaseAuditableService service = factory.get(entityType); - return service.getEntityHistory(entityIdentifier); } } diff --git a/services/src/main/java/org/fao/geonet/api/reports/ReportUsers.java b/services/src/main/java/org/fao/geonet/api/reports/ReportUsers.java index 151a83a43ed..236237c24d0 100644 --- a/services/src/main/java/org/fao/geonet/api/reports/ReportUsers.java +++ b/services/src/main/java/org/fao/geonet/api/reports/ReportUsers.java @@ -20,13 +20,22 @@ * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, * Rome - Italy. email: geonetwork@osgeo.org */ - package org.fao.geonet.api.reports; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.ResourceBundle; import jeeves.server.context.ServiceContext; import org.apache.commons.csv.CSVPrinter; import org.fao.geonet.auditable.UserAuditableService; -import org.fao.geonet.domain.*; +import org.fao.geonet.domain.Group; +import org.fao.geonet.domain.User; +import org.fao.geonet.domain.UserGroup; +import org.fao.geonet.domain.User_; import org.fao.geonet.kernel.setting.SettingManager; import org.fao.geonet.kernel.setting.Settings; import org.fao.geonet.repository.SortUtils; @@ -37,14 +46,10 @@ import org.springframework.data.domain.Sort; import org.springframework.util.StringUtils; -import java.io.PrintWriter; -import java.util.*; - import static org.fao.geonet.api.reports.ReportUtils.CSV_FORMAT; /** * Creates a users report including last login date. - * */ public class ReportUsers implements IReport { /** @@ -72,7 +77,6 @@ public ReportUsers(final ReportFilter filter) { */ public void create(final ServiceContext context, final PrintWriter writer) throws Exception { - SettingManager settingManager = context.getBean(SettingManager.class); UserAuditableService userAuditableService = context.getBean(UserAuditableService.class); boolean isUserHistoryEnabled = settingManager.getValueAsBool(Settings.SYSTEM_AUDITABLE_ENABLE, false); @@ -81,7 +85,7 @@ public void create(final ServiceContext context, new Locale(lang)); // Initialize CSVPrinter object - try(CSVPrinter csvFilePrinter = new CSVPrinter(writer, CSV_FORMAT)) { + try (CSVPrinter csvFilePrinter = new CSVPrinter(writer, CSV_FORMAT)) { // Retrieve users final UserRepository userRepository = context.getBean(UserRepository.class); @@ -99,7 +103,7 @@ public void create(final ServiceContext context, csvFilePrinter.println(); String[] entries = ("Username#Surname#Name#" - + "Email#User groups/Profile#Last login date" + (isUserHistoryEnabled?"#Change history": "")).split("#"); + + "Email#User groups/Profile#Last login date" + (isUserHistoryEnabled ? "#Change history" : "")).split("#"); csvFilePrinter.printRecord(Arrays.asList(entries)); for (User user : records) { @@ -120,7 +124,7 @@ public void create(final ServiceContext context, } // Build the record element with the information for the report - List metadataRecord = new ArrayList<>(); + List metadataRecord = new ArrayList<>(isUserHistoryEnabled ? 7 : 6); metadataRecord.add(username); metadataRecord.add(surname); metadataRecord.add(name); @@ -144,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 @@ -174,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 eb27d0dd82f..c23134054ec 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) * @@ -202,7 +202,7 @@ public User getUser( myUserId.equals(Integer.toString(userIdentifier))) { Optional user = userRepository.findById(userIdentifier); - if (!user.isPresent()) { + if (user.isEmpty()) { throw new UserNotFoundEx(Integer.toString(userIdentifier)); } @@ -251,7 +251,7 @@ public void getUserIdenticon( try { Optional user = userRepository.findById(userIdentifier); - if (!user.isPresent()) { + if (user.isEmpty()) { throw new UserNotFoundEx(Integer.toString(userIdentifier)); } @@ -350,7 +350,7 @@ public ResponseEntity deleteUser( List userGroups = userGroupRepository.findAll(UserGroupSpecs.hasUserId(userIdentifier)); userGroupRepository.deleteAllByIdAttribute(UserGroupId_.userId, - Arrays.asList(userIdentifier)); + List.of(userIdentifier)); userSavedSelectionRepository.deleteAllByUser(userIdentifier); @@ -409,7 +409,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); } @@ -557,16 +557,16 @@ public ResponseEntity updateUser( // TODO: CheckAccessRights - User user = userRepository.findById(userIdentifier).get(); - if (user == null) { - throw new IllegalArgumentException("No user found with id: " - + userDto.getId()); + Optional userOptional = userRepository.findById(userIdentifier); + if (userOptional.isEmpty()) { + throw new IllegalArgumentException(String.format("No user found with id: %s", userDto.getId())); } + User user = userOptional.get(); // 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) )) { @@ -588,7 +588,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( @@ -642,7 +642,6 @@ public ResponseEntity updateUser( List userGroups = userGroupRepository.findAll(UserGroupSpecs .hasUserId(user.getId())); - //userAudit.setUserGroups(userGroups); UserAuditable userAuditable = UserAuditable.build(user, userGroups); userAuditableService.auditSave(userAuditable); @@ -706,7 +705,7 @@ public ResponseEntity resetUserPassword( } Optional user = userRepository.findById(userIdentifier); - if (!user.isPresent()) { + if (user.isEmpty()) { throw new UserNotFoundEx(Integer.toString(userIdentifier)); } @@ -756,10 +755,12 @@ public List retrieveUserGroups( if (Profile.Administrator.equals(myProfile) || Profile.UserAdmin.equals(myProfile) || myUserId.equals(Integer.toString(userIdentifier))) { // -- get the profile of the user id supplied - User user = userRepository.findById(userIdentifier).get(); - if (user == null) { + Optional userOptional = userRepository.findById(userIdentifier); + + if (userOptional.isEmpty()) { throw new IllegalArgumentException("user " + userIdentifier + " doesn't exist"); } + User user = userOptional.get(); String userProfile = user.getProfile().name(); @@ -825,7 +826,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); @@ -833,11 +834,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. @@ -897,7 +897,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/viewer/layermanager/partials/layermanageritem.html b/web-ui/src/main/resources/catalog/components/viewer/layermanager/partials/layermanageritem.html index b40b9faa08b..5a1e1fee2ff 100644 --- a/web-ui/src/main/resources/catalog/components/viewer/layermanager/partials/layermanageritem.html +++ b/web-ui/src/main/resources/catalog/components/viewer/layermanager/partials/layermanageritem.html @@ -29,7 +29,6 @@

- -
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 9b9987b94c7..3eace89f974 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) * @@ -49,8 +49,10 @@ "$rootScope", "$translate", "$timeout", + "$log", "gnConfig", "gnConfigService", + "gnAuditableService", function ( $scope, $routeParams, @@ -58,8 +60,10 @@ $rootScope, $translate, $timeout, + $log, gnConfig, - gnConfigService + gnConfigService, + gnAuditableService ) { $scope.searchObj = { params: { @@ -319,17 +323,19 @@ ); // Load user changes - $http.get("../api/auditable/user/" + u.id).then( + 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/style/gn_viewer.less b/web-ui/src/main/resources/catalog/style/gn_viewer.less index 2ca082d6b22..e2e1deddcc9 100644 --- a/web-ui/src/main/resources/catalog/style/gn_viewer.less +++ b/web-ui/src/main/resources/catalog/style/gn_viewer.less @@ -308,7 +308,6 @@ } } .dropdown-left { - @toggleWidth: 32px; @toggleHeight: 32px; @@ -339,10 +338,7 @@ } } } - } - - } .gn-searchlayer-list { 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 b4342a19af9..cf055c76285 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 @@ -244,8 +244,8 @@ api.metadata.status.errorSetStatusNotAllowed=Seul le propri\u00E9taire des m\u00 feedback_subject_userFeedback=Commentaire de l'utilisateur -audit.revision=Updated by %s on %s:\n\ +audit.revision=Mise \u00E0 jour par %s le %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' +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'