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() {
return moderationEnabled;
return true;
// return moderationEnabled;
}
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.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);

View File

@ -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<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 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.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;

View File

@ -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;

View File

@ -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<String> 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();

View File

@ -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);