From b31befdc8d36da379659964a6b55cdcdee655d47 Mon Sep 17 00:00:00 2001 From: Luca Frosini Date: Tue, 11 May 2021 16:05:39 +0200 Subject: [PATCH] Implementing content moderation --- .../gcat/persistence/ckan/CKANInstance.java | 3 +- .../gcat/persistence/ckan/CKANPackage.java | 90 +++++++++++++++++-- .../gcube/gcat/persistence/ckan/CKANUser.java | 57 +----------- .../org/gcube/gcat/profile/ISProfile.java | 2 +- .../gcat/profile/ResourceRegistryProfile.java | 2 +- .../org/gcube/gcat/social/PortalUser.java | 14 +++ .../persistence/ckan/CKANPackageTest.java | 8 +- 7 files changed, 103 insertions(+), 73 deletions(-) diff --git a/src/main/java/org/gcube/gcat/persistence/ckan/CKANInstance.java b/src/main/java/org/gcube/gcat/persistence/ckan/CKANInstance.java index 916164a..23e8013 100644 --- a/src/main/java/org/gcube/gcat/persistence/ckan/CKANInstance.java +++ b/src/main/java/org/gcube/gcat/persistence/ckan/CKANInstance.java @@ -255,7 +255,8 @@ public class CKANInstance { } public boolean isModerationEnabled() { - return moderationEnabled; + return true; +// return moderationEnabled; } public String getSysAdminToken() throws Exception { diff --git a/src/main/java/org/gcube/gcat/persistence/ckan/CKANPackage.java b/src/main/java/org/gcube/gcat/persistence/ckan/CKANPackage.java index 854782c..d118a4a 100644 --- a/src/main/java/org/gcube/gcat/persistence/ckan/CKANPackage.java +++ b/src/main/java/org/gcube/gcat/persistence/ckan/CKANPackage.java @@ -22,9 +22,13 @@ import org.gcube.com.fasterxml.jackson.databind.node.ArrayNode; import org.gcube.com.fasterxml.jackson.databind.node.ObjectNode; import org.gcube.common.scope.impl.ScopeBean; import org.gcube.common.scope.impl.ScopeBean.Type; +import org.gcube.gcat.api.CMItemStatus; +import org.gcube.gcat.api.CMItemVisibility; import org.gcube.gcat.api.GCatConstants; +import org.gcube.gcat.api.Role; import org.gcube.gcat.oldutils.Validator; import org.gcube.gcat.profile.MetadataUtility; +import org.gcube.gcat.social.PortalUser; import org.gcube.gcat.social.SocialPost; import org.gcube.gcat.utils.URIResolver; import org.slf4j.Logger; @@ -55,6 +59,7 @@ public class CKANPackage extends CKAN { // see https://docs.ckan.org/en/latest/api/#ckan.logic.action.delete.dataset_purge public static final String ITEM_PURGE = CKAN.CKAN_API_PATH + "dataset_purge"; + // limit in https://docs.ckan.org/en/latest/api/index.html#ckan.logic.action.get.package_search protected static final String ROWS_KEY = "rows"; // offset in https://docs.ckan.org/en/latest/api/index.html#ckan.logic.action.get.package_search @@ -96,7 +101,10 @@ public class CKANPackage extends CKAN { protected static final String SEARCHABLE_KEY = "searchable"; protected static final String CAPACITY_KEY = "capacity"; - // protected static final String INCLUDE_PRIVATE_KEY = "include_private"; + + protected static final String CM_STATUS_QUERY_FILTER_KEY = "extras_systemcm_item_status"; + + protected static final String INCLUDE_PRIVATE_KEY = "include_private"; // protected static final String INCLUDE_DRAFTS_KEY = "include_drafts"; public static final String GROUPS_KEY = "groups"; @@ -338,10 +346,20 @@ public class CKANPackage extends CKAN { } if(ckanInstance.isModerationEnabled()) { - // TODO + String q = parameters.get(GCatConstants.Q_KEY); + + StringBuffer stringBuffer = new StringBuffer(); + stringBuffer.append(CM_STATUS_QUERY_FILTER_KEY); + stringBuffer.append(":"); + + // Default se non viene richiesto esplicitamente lo status o per i ruoli che possono solo vedere approved + stringBuffer.append(CMItemStatus.APPROVED.getValue()); + + parameters.put(GCatConstants.Q_KEY, String.format("%s AND %s", q, stringBuffer.toString())); + + parameters.put(INCLUDE_PRIVATE_KEY, String.valueOf(true)); } - return list(parameters); } @@ -477,7 +495,7 @@ public class CKANPackage extends CKAN { } } } else { - extras = mapper.createArrayNode(); + extras = ((ObjectNode) jsonNode).putArray(EXTRAS_KEY); } if(!found) { @@ -528,17 +546,65 @@ public class CKANPackage extends CKAN { } } + protected CMItemStatus getCMItemStatus() { + + String cmItemStatusString = CMItemStatus.APPROVED.getValue(); + boolean found = false; + if(result.has(EXTRAS_KEY)) { + ArrayNode extras = (ArrayNode) result.get(EXTRAS_KEY); + for(JsonNode extra : extras) { + if(extra.has(EXTRAS_KEY_KEY) && extra.get(EXTRAS_KEY_KEY).asText().compareTo(GCatConstants.CM_ITEM_STATUS) == 0) { + cmItemStatusString = extra.get(EXTRAS_VALUE_KEY).asText(); + found = true; + break; + } + } + } + + CMItemStatus cmItemStatus = CMItemStatus.getCMItemStatusFromValue(cmItemStatusString); + + if(!found) { + // TODO can be used to fix an item published before activating the moderation. + // The item is considered ad approved and the item representation must be updateds + return cmItemStatus; + } + + return cmItemStatus; + } + @Override public String read() { try { - if(ckanInstance.isModerationEnabled()) { - // TODO - } - String ret = super.read(); result = mapper.readTree(ret); result = cleanResult(result); + + if(ckanInstance.isModerationEnabled()) { + + CMItemStatus cmItemStatus = getCMItemStatus(); + + if(cmItemStatus == CMItemStatus.APPROVED) { + return getAsString(result); + } + + PortalUser portalUser = ckanUser.getPortalUser(); + + if(result.get(AUTHOR_EMAIL_KEY).asText().compareTo(portalUser.getEMail())==0) { + // The author is entitled to read its own items independently from the status + return getAsString(result); + } + + if(ckanUser.getRole() == Role.ADMIN || portalUser.isCatalogueModerator()) { + // Catalogue-Admin and Catalogue-Moderator are entitled to read items with any statues + return getAsString(result); + } + + throw new ForbiddenException("You are not entitled to read a " + cmItemStatus.getValue() + " item"); + + } + return getAsString(result); + } catch(WebApplicationException e) { throw e; } catch(Exception e) { @@ -555,11 +621,17 @@ public class CKANPackage extends CKAN { if(ckanInstance.isModerationEnabled()) { // TODO + } - JsonNode jsonNode = validateJson(json); + if(ckanInstance.isModerationEnabled()) { + addExtraField(jsonNode, GCatConstants.CM_ITEM_STATUS, CMItemStatus.APPROVED.getValue()); + addExtraField(jsonNode, GCatConstants.CM_ITEM_VISIBILITY, CMItemVisibility.PUBLIC.getValue()); + } + + ArrayNode resourcesToBeCreated = mapper.createArrayNode(); if(jsonNode.has(RESOURCES_KEY)) { resourcesToBeCreated = (ArrayNode) jsonNode.get(RESOURCES_KEY); diff --git a/src/main/java/org/gcube/gcat/persistence/ckan/CKANUser.java b/src/main/java/org/gcube/gcat/persistence/ckan/CKANUser.java index 1327a80..58c5cdb 100644 --- a/src/main/java/org/gcube/gcat/persistence/ckan/CKANUser.java +++ b/src/main/java/org/gcube/gcat/persistence/ckan/CKANUser.java @@ -1,13 +1,12 @@ package org.gcube.gcat.persistence.ckan; -import java.util.HashMap; import java.util.List; -import java.util.Map; import javax.ws.rs.InternalServerErrorException; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response.Status; +import org.gcube.gcat.api.Role; import org.gcube.gcat.social.PortalUser; import org.gcube.gcat.utils.ContextUtility; import org.gcube.gcat.utils.RandomString; @@ -46,60 +45,6 @@ public class CKANUser extends CKAN { private static final String API_KEY = "apikey"; - public enum Role { - MEMBER("Catalogue-Member", "member"), EDITOR("Catalogue-Editor", "editor"), ADMIN("Catalogue-Admin", "admin"); - - private final String portalRole; - private final String ckanRole; - - Role(String portalRole, String ckanRole) { - this.portalRole = portalRole; - this.ckanRole = ckanRole; - } - - public String getPortalRole() { - return portalRole; - } - - public String getCkanRole() { - return ckanRole; - } - - protected static final Map ROLE_BY_PORTAL_ROLE; - protected static final Map ROLE_BY_CKAN_ROLE; - - static { - ROLE_BY_PORTAL_ROLE = new HashMap(); - - // null or empty string identify a member - ROLE_BY_PORTAL_ROLE.put(null, MEMBER); - ROLE_BY_PORTAL_ROLE.put("", MEMBER); - - ROLE_BY_CKAN_ROLE = new HashMap(); - - for(Role role : Role.values()) { - ROLE_BY_PORTAL_ROLE.put(role.getPortalRole(), role); - ROLE_BY_CKAN_ROLE.put(role.getCkanRole(), role); - } - } - - public static Role getRoleFromPortalRole(String portalRole) { - return ROLE_BY_PORTAL_ROLE.get(portalRole); - } - - public static String getCkanRoleFromPortalRole(String portalRole) { - return getRoleFromPortalRole(portalRole).getCkanRole(); - } - - public static Role getRoleFromCkanRole(String ckanRole) { - return ROLE_BY_CKAN_ROLE.get(ckanRole); - } - - public static String getPortalRoleFromCkanRole(String ckanRole) { - return getRoleFromCkanRole(ckanRole).getPortalRole(); - } - } - protected PortalUser portalUser; protected Role role; diff --git a/src/main/java/org/gcube/gcat/profile/ISProfile.java b/src/main/java/org/gcube/gcat/profile/ISProfile.java index 591544b..7076a5c 100644 --- a/src/main/java/org/gcube/gcat/profile/ISProfile.java +++ b/src/main/java/org/gcube/gcat/profile/ISProfile.java @@ -13,8 +13,8 @@ import org.gcube.common.resources.gcore.GenericResource; import org.gcube.common.resources.gcore.Resources; import org.gcube.datacatalogue.metadatadiscovery.reader.MetadataFormatDiscovery; import org.gcube.datacatalogue.metadatadiscovery.reader.QueryForResourceUtil; +import org.gcube.gcat.api.Role; import org.gcube.gcat.persistence.ckan.CKANUser; -import org.gcube.gcat.persistence.ckan.CKANUser.Role; import org.gcube.gcat.persistence.ckan.CKANUserCache; import org.gcube.gcat.utils.Constants; import org.gcube.informationsystem.publisher.RegistryPublisher; diff --git a/src/main/java/org/gcube/gcat/profile/ResourceRegistryProfile.java b/src/main/java/org/gcube/gcat/profile/ResourceRegistryProfile.java index f8a4bee..c95f721 100644 --- a/src/main/java/org/gcube/gcat/profile/ResourceRegistryProfile.java +++ b/src/main/java/org/gcube/gcat/profile/ResourceRegistryProfile.java @@ -19,8 +19,8 @@ import org.gcube.common.resources.gcore.GenericResource; import org.gcube.common.resources.gcore.Resources; import org.gcube.datacatalogue.metadatadiscovery.reader.MetadataFormatDiscovery; import org.gcube.datacatalogue.metadatadiscovery.reader.QueryForResourceUtil; +import org.gcube.gcat.api.Role; import org.gcube.gcat.persistence.ckan.CKANUser; -import org.gcube.gcat.persistence.ckan.CKANUser.Role; import org.gcube.gcat.persistence.ckan.CKANUserCache; import org.gcube.gcat.utils.Constants; import org.gcube.informationsystem.publisher.RegistryPublisher; diff --git a/src/main/java/org/gcube/gcat/social/PortalUser.java b/src/main/java/org/gcube/gcat/social/PortalUser.java index fa5acbd..898f1fe 100644 --- a/src/main/java/org/gcube/gcat/social/PortalUser.java +++ b/src/main/java/org/gcube/gcat/social/PortalUser.java @@ -8,12 +8,16 @@ import java.util.List; import javax.ws.rs.InternalServerErrorException; import org.gcube.common.gxhttp.request.GXHTTPStringRequest; +import org.gcube.gcat.api.GCatConstants; import org.gcube.gcat.utils.HTTPUtility; import org.gcube.com.fasterxml.jackson.databind.JsonNode; import org.gcube.com.fasterxml.jackson.databind.ObjectMapper; import org.gcube.com.fasterxml.jackson.databind.node.ArrayNode; +/** + * @author Luca Frosini (ISTI - CNR) + */ public class PortalUser { protected static final String RESPONSE_SUCCESS_KEY = "success"; @@ -42,6 +46,8 @@ public class PortalUser { protected List roles; + protected Boolean catalogueModerator; + public PortalUser() { this.objectMapper = new ObjectMapper(); } @@ -120,6 +126,14 @@ public class PortalUser { return roles; } + + public boolean isCatalogueModerator() { + if(catalogueModerator == null) { + catalogueModerator = getRoles().contains(GCatConstants.CATALOGUE_MODERATOR); + } + return catalogueModerator; + } + public String getJobTitle() { if(jobTitle == null) { jobTitle = getOAuthUserProfile().get(OAUTH_USER_PROFILE_JOB_TITLE_KEY).asText(); diff --git a/src/test/java/org/gcube/gcat/persistence/ckan/CKANPackageTest.java b/src/test/java/org/gcube/gcat/persistence/ckan/CKANPackageTest.java index 7ee5c88..9bc8f41 100644 --- a/src/test/java/org/gcube/gcat/persistence/ckan/CKANPackageTest.java +++ b/src/test/java/org/gcube/gcat/persistence/ckan/CKANPackageTest.java @@ -54,6 +54,7 @@ public class CKANPackageTest extends ContextTest { @Test public void list() throws Exception { + ContextTest.setContextByName("/gcube/devsec/devVRE"); CKANPackage ckanPackage = new CKANPackage(); ObjectMapper mapper = new ObjectMapper(); String ret = ckanPackage.list(10, 0); @@ -242,7 +243,8 @@ public class CKANPackageTest extends ContextTest { * Workspace(luca.frosini) > RESTful Transaction Model v 1.0.pdf * https://data1-d.d4science.org/shub/E_aThRa1NpWFJpTGEydEU2bEJhMXNjZy8wK3BxekJKYnpYTy81cUkwZVdicEZ0aGFRZmY4MkRnUC8xWW0zYzVoVg== * https://goo.gl/J8AwQW - * + * ContextTest.setContextByName("/gcube/devsec/devVRE"); + * * Workspace(luca.frosini) > RESTful Transaction Model v 1.1.pdf * https://data1-d.d4science.org/shub/E_NkhrbVV4VTluT0RKVUtCRldobFZTQU5ySTZneFdpUzJ2UjJBNlZWNDlURDVHamo4WjY5RnlrcHZGTGNkT2prUg== @@ -330,7 +332,6 @@ public class CKANPackageTest extends ContextTest { @Test public void create() throws Exception { - ContextTest.setContextByName("/gcube/devsec/devVRE"); ObjectMapper mapper = new ObjectMapper(); createPackage(mapper); } @@ -346,8 +347,6 @@ public class CKANPackageTest extends ContextTest { @Test public void createReadUpdateUpdatePurge() throws Exception { - ContextTest.setContextByName("/gcube/devsec/devVRE"); - ObjectMapper mapper = new ObjectMapper(); createPackage(mapper); @@ -420,7 +419,6 @@ public class CKANPackageTest extends ContextTest { @Test //(expected = NotFoundException.class) public void delete() throws Exception { - ContextTest.setContextByName("/gcube/devNext/NextNext"); CKANPackage ckanPackage = new CKANPackage(); ckanPackage.setName(ITEM_NAME_VALUE); ckanPackage.delete(true);