Implementing content moderation

This commit is contained in:
Luca Frosini 2021-05-11 16:05:39 +02:00
parent e00b2010d1
commit b31befdc8d
7 changed files with 103 additions and 73 deletions

View File

@ -255,7 +255,8 @@ public class CKANInstance {
} }
public boolean isModerationEnabled() { public boolean isModerationEnabled() {
return moderationEnabled; return true;
// return moderationEnabled;
} }
public String getSysAdminToken() throws Exception { public String getSysAdminToken() throws Exception {

View File

@ -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.com.fasterxml.jackson.databind.node.ObjectNode;
import org.gcube.common.scope.impl.ScopeBean; import org.gcube.common.scope.impl.ScopeBean;
import org.gcube.common.scope.impl.ScopeBean.Type; 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.GCatConstants;
import org.gcube.gcat.api.Role;
import org.gcube.gcat.oldutils.Validator; import org.gcube.gcat.oldutils.Validator;
import org.gcube.gcat.profile.MetadataUtility; import org.gcube.gcat.profile.MetadataUtility;
import org.gcube.gcat.social.PortalUser;
import org.gcube.gcat.social.SocialPost; import org.gcube.gcat.social.SocialPost;
import org.gcube.gcat.utils.URIResolver; import org.gcube.gcat.utils.URIResolver;
import org.slf4j.Logger; 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 // 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"; 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 // limit in https://docs.ckan.org/en/latest/api/index.html#ckan.logic.action.get.package_search
protected static final String ROWS_KEY = "rows"; protected static final String ROWS_KEY = "rows";
// offset in https://docs.ckan.org/en/latest/api/index.html#ckan.logic.action.get.package_search // 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 SEARCHABLE_KEY = "searchable";
protected static final String CAPACITY_KEY = "capacity"; 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"; // protected static final String INCLUDE_DRAFTS_KEY = "include_drafts";
public static final String GROUPS_KEY = "groups"; public static final String GROUPS_KEY = "groups";
@ -338,9 +346,19 @@ public class CKANPackage extends CKAN {
} }
if(ckanInstance.isModerationEnabled()) { 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); return list(parameters);
} }
@ -477,7 +495,7 @@ public class CKANPackage extends CKAN {
} }
} }
} else { } else {
extras = mapper.createArrayNode(); extras = ((ObjectNode) jsonNode).putArray(EXTRAS_KEY);
} }
if(!found) { 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 @Override
public String read() { public String read() {
try { try {
if(ckanInstance.isModerationEnabled()) {
// TODO
}
String ret = super.read(); String ret = super.read();
result = mapper.readTree(ret); result = mapper.readTree(ret);
result = cleanResult(result); 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); return getAsString(result);
} catch(WebApplicationException e) { } catch(WebApplicationException e) {
throw e; throw e;
} catch(Exception e) { } catch(Exception e) {
@ -555,11 +621,17 @@ public class CKANPackage extends CKAN {
if(ckanInstance.isModerationEnabled()) { if(ckanInstance.isModerationEnabled()) {
// TODO // TODO
} }
JsonNode jsonNode = validateJson(json); 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(); ArrayNode resourcesToBeCreated = mapper.createArrayNode();
if(jsonNode.has(RESOURCES_KEY)) { if(jsonNode.has(RESOURCES_KEY)) {
resourcesToBeCreated = (ArrayNode) jsonNode.get(RESOURCES_KEY); resourcesToBeCreated = (ArrayNode) jsonNode.get(RESOURCES_KEY);

View File

@ -1,13 +1,12 @@
package org.gcube.gcat.persistence.ckan; package org.gcube.gcat.persistence.ckan;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import javax.ws.rs.InternalServerErrorException; import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.WebApplicationException; import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.Response.Status;
import org.gcube.gcat.api.Role;
import org.gcube.gcat.social.PortalUser; import org.gcube.gcat.social.PortalUser;
import org.gcube.gcat.utils.ContextUtility; import org.gcube.gcat.utils.ContextUtility;
import org.gcube.gcat.utils.RandomString; import org.gcube.gcat.utils.RandomString;
@ -46,60 +45,6 @@ public class CKANUser extends CKAN {
private static final String API_KEY = "apikey"; 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<String,Role> ROLE_BY_PORTAL_ROLE;
protected static final Map<String,Role> ROLE_BY_CKAN_ROLE;
static {
ROLE_BY_PORTAL_ROLE = new HashMap<String,Role>();
// 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<String,Role>();
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 PortalUser portalUser;
protected Role role; protected Role role;

View File

@ -13,8 +13,8 @@ import org.gcube.common.resources.gcore.GenericResource;
import org.gcube.common.resources.gcore.Resources; import org.gcube.common.resources.gcore.Resources;
import org.gcube.datacatalogue.metadatadiscovery.reader.MetadataFormatDiscovery; import org.gcube.datacatalogue.metadatadiscovery.reader.MetadataFormatDiscovery;
import org.gcube.datacatalogue.metadatadiscovery.reader.QueryForResourceUtil; 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;
import org.gcube.gcat.persistence.ckan.CKANUser.Role;
import org.gcube.gcat.persistence.ckan.CKANUserCache; import org.gcube.gcat.persistence.ckan.CKANUserCache;
import org.gcube.gcat.utils.Constants; import org.gcube.gcat.utils.Constants;
import org.gcube.informationsystem.publisher.RegistryPublisher; import org.gcube.informationsystem.publisher.RegistryPublisher;

View File

@ -19,8 +19,8 @@ import org.gcube.common.resources.gcore.GenericResource;
import org.gcube.common.resources.gcore.Resources; import org.gcube.common.resources.gcore.Resources;
import org.gcube.datacatalogue.metadatadiscovery.reader.MetadataFormatDiscovery; import org.gcube.datacatalogue.metadatadiscovery.reader.MetadataFormatDiscovery;
import org.gcube.datacatalogue.metadatadiscovery.reader.QueryForResourceUtil; 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;
import org.gcube.gcat.persistence.ckan.CKANUser.Role;
import org.gcube.gcat.persistence.ckan.CKANUserCache; import org.gcube.gcat.persistence.ckan.CKANUserCache;
import org.gcube.gcat.utils.Constants; import org.gcube.gcat.utils.Constants;
import org.gcube.informationsystem.publisher.RegistryPublisher; import org.gcube.informationsystem.publisher.RegistryPublisher;

View File

@ -8,12 +8,16 @@ import java.util.List;
import javax.ws.rs.InternalServerErrorException; import javax.ws.rs.InternalServerErrorException;
import org.gcube.common.gxhttp.request.GXHTTPStringRequest; import org.gcube.common.gxhttp.request.GXHTTPStringRequest;
import org.gcube.gcat.api.GCatConstants;
import org.gcube.gcat.utils.HTTPUtility; import org.gcube.gcat.utils.HTTPUtility;
import org.gcube.com.fasterxml.jackson.databind.JsonNode; import org.gcube.com.fasterxml.jackson.databind.JsonNode;
import org.gcube.com.fasterxml.jackson.databind.ObjectMapper; import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
import org.gcube.com.fasterxml.jackson.databind.node.ArrayNode; import org.gcube.com.fasterxml.jackson.databind.node.ArrayNode;
/**
* @author Luca Frosini (ISTI - CNR)
*/
public class PortalUser { public class PortalUser {
protected static final String RESPONSE_SUCCESS_KEY = "success"; protected static final String RESPONSE_SUCCESS_KEY = "success";
@ -42,6 +46,8 @@ public class PortalUser {
protected List<String> roles; protected List<String> roles;
protected Boolean catalogueModerator;
public PortalUser() { public PortalUser() {
this.objectMapper = new ObjectMapper(); this.objectMapper = new ObjectMapper();
} }
@ -120,6 +126,14 @@ public class PortalUser {
return roles; return roles;
} }
public boolean isCatalogueModerator() {
if(catalogueModerator == null) {
catalogueModerator = getRoles().contains(GCatConstants.CATALOGUE_MODERATOR);
}
return catalogueModerator;
}
public String getJobTitle() { public String getJobTitle() {
if(jobTitle == null) { if(jobTitle == null) {
jobTitle = getOAuthUserProfile().get(OAUTH_USER_PROFILE_JOB_TITLE_KEY).asText(); jobTitle = getOAuthUserProfile().get(OAUTH_USER_PROFILE_JOB_TITLE_KEY).asText();

View File

@ -54,6 +54,7 @@ public class CKANPackageTest extends ContextTest {
@Test @Test
public void list() throws Exception { public void list() throws Exception {
ContextTest.setContextByName("/gcube/devsec/devVRE");
CKANPackage ckanPackage = new CKANPackage(); CKANPackage ckanPackage = new CKANPackage();
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
String ret = ckanPackage.list(10, 0); 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 * Workspace(luca.frosini) > RESTful Transaction Model v 1.0.pdf
* https://data1-d.d4science.org/shub/E_aThRa1NpWFJpTGEydEU2bEJhMXNjZy8wK3BxekJKYnpYTy81cUkwZVdicEZ0aGFRZmY4MkRnUC8xWW0zYzVoVg== * https://data1-d.d4science.org/shub/E_aThRa1NpWFJpTGEydEU2bEJhMXNjZy8wK3BxekJKYnpYTy81cUkwZVdicEZ0aGFRZmY4MkRnUC8xWW0zYzVoVg==
* https://goo.gl/J8AwQW * https://goo.gl/J8AwQW
* * ContextTest.setContextByName("/gcube/devsec/devVRE");
* *
* Workspace(luca.frosini) > RESTful Transaction Model v 1.1.pdf * Workspace(luca.frosini) > RESTful Transaction Model v 1.1.pdf
* https://data1-d.d4science.org/shub/E_NkhrbVV4VTluT0RKVUtCRldobFZTQU5ySTZneFdpUzJ2UjJBNlZWNDlURDVHamo4WjY5RnlrcHZGTGNkT2prUg== * https://data1-d.d4science.org/shub/E_NkhrbVV4VTluT0RKVUtCRldobFZTQU5ySTZneFdpUzJ2UjJBNlZWNDlURDVHamo4WjY5RnlrcHZGTGNkT2prUg==
@ -330,7 +332,6 @@ public class CKANPackageTest extends ContextTest {
@Test @Test
public void create() throws Exception { public void create() throws Exception {
ContextTest.setContextByName("/gcube/devsec/devVRE");
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
createPackage(mapper); createPackage(mapper);
} }
@ -346,8 +347,6 @@ public class CKANPackageTest extends ContextTest {
@Test @Test
public void createReadUpdateUpdatePurge() throws Exception { public void createReadUpdateUpdatePurge() throws Exception {
ContextTest.setContextByName("/gcube/devsec/devVRE");
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
createPackage(mapper); createPackage(mapper);
@ -420,7 +419,6 @@ public class CKANPackageTest extends ContextTest {
@Test @Test
//(expected = NotFoundException.class) //(expected = NotFoundException.class)
public void delete() throws Exception { public void delete() throws Exception {
ContextTest.setContextByName("/gcube/devNext/NextNext");
CKANPackage ckanPackage = new CKANPackage(); CKANPackage ckanPackage = new CKANPackage();
ckanPackage.setName(ITEM_NAME_VALUE); ckanPackage.setName(ITEM_NAME_VALUE);
ckanPackage.delete(true); ckanPackage.delete(true);