Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions core/src/main/java/org/fao/geonet/kernel/XmlSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,7 @@

import org.fao.geonet.ApplicationContextHolder;
import org.fao.geonet.constants.Geonet;
import org.fao.geonet.domain.AbstractMetadata;
import org.fao.geonet.domain.ISODate;
import org.fao.geonet.domain.Pair;
import org.fao.geonet.domain.ReservedOperation;
import org.fao.geonet.domain.*;
import org.fao.geonet.kernel.datamanager.IMetadataManager;
import org.fao.geonet.kernel.datamanager.IMetadataUtils;
import org.fao.geonet.kernel.schema.MetadataSchema;
Expand Down Expand Up @@ -186,6 +183,16 @@ public Element removeHiddenElements(boolean isIndexingTask, AbstractMetadata met
removeFilteredElement(metadataXml, authenticatedFilter, namespaces);
}
}
MetadataSchemaOperationFilter groupOwnerFilter = mds.getOperationFilter("groupOwner");
if (groupOwnerFilter != null) {

List<Integer> userGroups = AccessManager.getGroups(context.getUserSession(), Profile.Editor);
boolean isGroupOwnerFilter = userGroups.contains(metadata.getSourceInfo().getGroupOwner());

if (!isGroupOwnerFilter) {
removeFilteredElement(metadataXml, groupOwnerFilter, namespaces);
}
}

MetadataSchemaOperationFilter downloadFilter = mds.getOperationFilter(ReservedOperation.download);
if (downloadFilter != null) {
Expand Down
251 changes: 251 additions & 0 deletions core/src/test/java/org/fao/geonet/kernel/XmlSerializerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
/*
* Copyright (C) 2001-2025 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.kernel;

import jeeves.server.UserSession;
import jeeves.server.context.ServiceContext;
import org.fao.geonet.ApplicationContextHolder;
import org.fao.geonet.domain.AbstractMetadata;
import org.fao.geonet.domain.MetadataDataInfo;
import org.fao.geonet.domain.MetadataSourceInfo;
import org.fao.geonet.domain.ReservedOperation;
import org.fao.geonet.domain.UserGroup;
import org.fao.geonet.kernel.schema.MetadataSchema;
import org.fao.geonet.kernel.schema.MetadataSchemaOperationFilter;
import org.fao.geonet.repository.UserGroupRepository;
import org.jdom.Element;
import org.jdom.Namespace;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentMatchers;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.data.jpa.domain.Specification;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class XmlSerializerTest {
private static final int GROUP_ID_1 = 10;
private static final int GROUP_ID_2 = 20;
private static final int GROUP_ID_3 = 30;

private ConfigurableApplicationContext mockContext;
private AccessManager mockAccessManager;
private DataManager mockDataManager;
private UserGroupRepository mockUserGroupRepository;

@Before
public void setUp() {
mockContext = mock(ConfigurableApplicationContext.class);
mockAccessManager = mock(AccessManager.class);
mockDataManager = mock(DataManager.class);
mockUserGroupRepository = mock(UserGroupRepository.class);

when(mockContext.getBean(AccessManager.class)).thenReturn(mockAccessManager);
when(mockContext.getBean(DataManager.class)).thenReturn(mockDataManager);
when(mockContext.getBean(UserGroupRepository.class)).thenReturn(mockUserGroupRepository);

ApplicationContextHolder.set(mockContext);
}

@After
public void tearDown() {
ApplicationContextHolder.clear();
}

private XmlSerializer createXmlSerializer() {
return new XmlSerializer() {
@Override public void delete(String id, ServiceContext context) throws Exception {}
@Override public void update(String id, Element xml, String changeDate, boolean updateDateStamp, String uuid, ServiceContext context) throws Exception {}
@Override public AbstractMetadata insert(AbstractMetadata metadata, Element dataXml, ServiceContext context) throws Exception { return null; }
@Override public Element select(ServiceContext context, String id) throws Exception { return null; }
@Override public Element selectNoXLinkResolver(String id, boolean isIndexingTask, boolean applyOperationsFilters) throws Exception { return null; }
};
}

private ServiceContext setupServiceContextWithUserSession(UserSession mockUserSession) {
Map<String, Object> contexts = new HashMap<>();
ServiceContext context = new ServiceContext("test", mockContext, contexts, null);
context.setUserSession(mockUserSession);
context.setAsThreadLocal();
return context;
}

@Test
public void testRemoveHiddenElementsGroupOwnerFilter() throws Exception {
XmlSerializer xmlSerializer = createXmlSerializer();

Namespace gmd = Namespace.getNamespace("gmd", "http://www.isotc211.org/2005/gmd");
List<Namespace> namespaces = Collections.singletonList(gmd);

AbstractMetadata metadata = mock(AbstractMetadata.class);
MetadataDataInfo dataInfo = mock(MetadataDataInfo.class);
MetadataSourceInfo sourceInfo = mock(MetadataSourceInfo.class);

when(metadata.getId()).thenReturn(1);
when(metadata.getDataInfo()).thenReturn(dataInfo);
when(metadata.getSourceInfo()).thenReturn(sourceInfo);
when(dataInfo.getSchemaId()).thenReturn("iso19139");
when(sourceInfo.getGroupOwner()).thenReturn(GROUP_ID_1);

MetadataSchema mockSchema = mock(MetadataSchema.class);
when(mockDataManager.getSchema("iso19139")).thenReturn(mockSchema);
when(mockSchema.getNamespaces()).thenReturn(namespaces);

MetadataSchemaOperationFilter groupOwnerFilter = new MetadataSchemaOperationFilter("gmd:hidden", "", "groupOwner", null);
when(mockSchema.getOperationFilter("groupOwner")).thenReturn(groupOwnerFilter);

UserSession mockUserSession = mock(UserSession.class);
ServiceContext realServiceContext = setupServiceContextWithUserSession(mockUserSession);

// Case 1: User IS in the owner group
when(mockUserGroupRepository.findGroupIds(ArgumentMatchers.<Specification<UserGroup>>any())).thenReturn(Arrays.asList(GROUP_ID_1, GROUP_ID_2));

Element metadataXml1 = new Element("MD_Metadata", gmd);
Element hiddenElement1 = new Element("hidden", gmd);
metadataXml1.addContent(hiddenElement1);
when(metadata.getXmlData(false)).thenReturn(metadataXml1);

Element result1 = xmlSerializer.removeHiddenElements(false, metadata, true);
assertNotNull(result1.getChild("hidden", gmd));

// Case 2: User IS NOT in the owner group
when(mockUserGroupRepository.findGroupIds(ArgumentMatchers.<Specification<UserGroup>>any())).thenReturn(Arrays.asList(GROUP_ID_2, GROUP_ID_3));

Element metadataXml2 = new Element("MD_Metadata", gmd);
Element hiddenElement2 = new Element("hidden", gmd);
metadataXml2.addContent(hiddenElement2);
when(metadata.getXmlData(false)).thenReturn(metadataXml2);

Element result2 = xmlSerializer.removeHiddenElements(false, metadata, true);
assertNull(result2.getChild("hidden", gmd));
}

@Test
public void testRemoveHiddenElementsAuthenticatedFilter() throws Exception {
XmlSerializer xmlSerializer = createXmlSerializer();

Namespace gmd = Namespace.getNamespace("gmd", "http://www.isotc211.org/2005/gmd");
List<Namespace> namespaces = Collections.singletonList(gmd);

AbstractMetadata metadata = mock(AbstractMetadata.class);
MetadataDataInfo dataInfo = mock(MetadataDataInfo.class);
MetadataSourceInfo sourceInfo = mock(MetadataSourceInfo.class);

when(metadata.getId()).thenReturn(1);
when(metadata.getDataInfo()).thenReturn(dataInfo);
when(metadata.getSourceInfo()).thenReturn(sourceInfo);
when(dataInfo.getSchemaId()).thenReturn("iso19139");

MetadataSchema mockSchema = mock(MetadataSchema.class);
when(mockDataManager.getSchema("iso19139")).thenReturn(mockSchema);
when(mockSchema.getNamespaces()).thenReturn(namespaces);

MetadataSchemaOperationFilter authenticatedFilter = new MetadataSchemaOperationFilter("gmd:authenticatedOnly", "", "authenticated", null);
when(mockSchema.getOperationFilter("authenticated")).thenReturn(authenticatedFilter);

UserSession mockUserSession = mock(UserSession.class);
ServiceContext realServiceContext = setupServiceContextWithUserSession(mockUserSession);

// Case 1: User IS authenticated
when(mockUserSession.isAuthenticated()).thenReturn(true);

Element metadataXml1 = new Element("MD_Metadata", gmd);
Element authElement1 = new Element("authenticatedOnly", gmd);
metadataXml1.addContent(authElement1);
when(metadata.getXmlData(false)).thenReturn(metadataXml1);

Element result1 = xmlSerializer.removeHiddenElements(false, metadata, true);
assertNotNull(result1.getChild("authenticatedOnly", gmd));

// Case 2: User IS NOT authenticated
when(mockUserSession.isAuthenticated()).thenReturn(false);

Element metadataXml2 = new Element("MD_Metadata", gmd);
Element authElement2 = new Element("authenticatedOnly", gmd);
metadataXml2.addContent(authElement2);
when(metadata.getXmlData(false)).thenReturn(metadataXml2);

Element result2 = xmlSerializer.removeHiddenElements(false, metadata, true);
assertNull(result2.getChild("authenticatedOnly", gmd));
}

@Test
public void testRemoveHiddenElementsDownloadFilter() throws Exception {
XmlSerializer xmlSerializer = createXmlSerializer();

Namespace gmd = Namespace.getNamespace("gmd", "http://www.isotc211.org/2005/gmd");
List<Namespace> namespaces = Collections.singletonList(gmd);

AbstractMetadata metadata = mock(AbstractMetadata.class);
MetadataDataInfo dataInfo = mock(MetadataDataInfo.class);
MetadataSourceInfo sourceInfo = mock(MetadataSourceInfo.class);

when(metadata.getId()).thenReturn(1);
when(metadata.getDataInfo()).thenReturn(dataInfo);
when(metadata.getSourceInfo()).thenReturn(sourceInfo);
when(dataInfo.getSchemaId()).thenReturn("iso19139");

MetadataSchema mockSchema = mock(MetadataSchema.class);
when(mockDataManager.getSchema("iso19139")).thenReturn(mockSchema);
when(mockSchema.getNamespaces()).thenReturn(namespaces);

MetadataSchemaOperationFilter downloadFilter = new MetadataSchemaOperationFilter("gmd:downloadOnly", "", ReservedOperation.download.name(), null);
when(mockSchema.getOperationFilter(ReservedOperation.download)).thenReturn(downloadFilter);

UserSession mockUserSession = mock(UserSession.class);
ServiceContext realServiceContext = setupServiceContextWithUserSession(mockUserSession);

// Case 1: User can download
when(mockAccessManager.canDownload(ArgumentMatchers.any(ServiceContext.class), ArgumentMatchers.eq("1"))).thenReturn(true);

Element metadataXml1 = new Element("MD_Metadata", gmd);
Element downloadElement1 = new Element("downloadOnly", gmd);
metadataXml1.addContent(downloadElement1);
when(metadata.getXmlData(false)).thenReturn(metadataXml1);

Element result1 = xmlSerializer.removeHiddenElements(false, metadata, true);
assertNotNull(result1.getChild("downloadOnly", gmd));

// Case 2: User cannot download
when(mockAccessManager.canDownload(ArgumentMatchers.any(ServiceContext.class), ArgumentMatchers.eq("1"))).thenReturn(false);

Element metadataXml2 = new Element("MD_Metadata", gmd);
Element downloadElement2 = new Element("downloadOnly", gmd);
metadataXml2.addContent(downloadElement2);
when(metadata.getXmlData(false)).thenReturn(metadataXml2);

Element result2 = xmlSerializer.removeHiddenElements(false, metadata, true);
assertNull(result2.getChild("downloadOnly", gmd));
}
}
15 changes: 14 additions & 1 deletion services/src/main/java/org/fao/geonet/api/es/EsHTTPProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.util.*;
import java.util.zip.DeflaterInputStream;
import java.util.zip.DeflaterOutputStream;
Expand Down Expand Up @@ -868,7 +869,7 @@ protected boolean isContentTypeValid(final String contentType) {
* @param doc
* @throws JsonProcessingException
*/
private void processMetadataSchemaFilters(ServiceContext context, MetadataSchema mds, ObjectNode doc) throws JsonProcessingException {
private void processMetadataSchemaFilters(ServiceContext context, MetadataSchema mds, ObjectNode doc) throws JsonProcessingException, SQLException {
if (!doc.has("_source")) {
return;
}
Expand All @@ -883,6 +884,18 @@ private void processMetadataSchemaFilters(ServiceContext context, MetadataSchema
if (authenticatedFilter != null && !context.getUserSession().isAuthenticated()) {
jsonpathFilters.add(authenticatedFilter.getJsonpath());
}
//do the same for groupOwner
MetadataSchemaOperationFilter groupOwnerFilter = mds.getOperationFilter("groupOwner");
Copy link
Copy Markdown
Member

@josegar74 josegar74 Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe for the authenticated and groupOwner we can create an enum like MetadataOperationFilterType to use it instead string values, similar as other filters use the ReservedOperation enum.


if (groupOwnerFilter != null) {
List<Integer> userGroups = AccessManager.getGroups(context.getUserSession(), Profile.Editor);
Integer groupOwner = getSourceInteger(doc, Geonet.IndexFieldNames.GROUP_OWNER);
boolean isGroupOwner = groupOwner != null && userGroups.contains(groupOwner);

if (!isGroupOwner) {
jsonpathFilters.add(groupOwnerFilter.getJsonpath());
}
}

MetadataSchemaOperationFilter editFilter = mds.getOperationFilter(ReservedOperation.editing);

Expand Down
Loading