diff --git a/CHANGELOG.md b/CHANGELOG.md
index d895df1..3dde4ff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,12 @@
# Changelog
+## [v2.7.1-SNAPSHOT] - 2022-09-14
+
+- - Feature #23847 Social service: temporarily block of notifications for given username(s)
+
## [v2.7.0] - 2022-09-12
-- Sphinx documentation added
+ - Sphinx documentation added
## [v2.6.2] - 2022-07-27
diff --git a/pom.xml b/pom.xml
index 28ea8e3..b9995d9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,7 +12,7 @@
org.gcube.portal
social-networking-library-ws
war
- 2.7.0
+ 2.7.1-SNAPSHOT]
social-networking-library-ws
Rest interface for the social networking library.
@@ -39,6 +39,11 @@
pom
import
+
+ org.aspectj
+ aspectjrt
+ 1.8.2
+
@@ -73,6 +78,16 @@
ehcache
2.10.0
+
+ net.spy
+ spymemcached
+ 2.12.3
+
+
+ org.gcube.common
+ authorization-control-library
+ [1.0.1,2.0.0-SNAPSHOT)
+
org.gcube.common.portal
portal-manager
@@ -301,7 +316,6 @@
-
kr.motd.maven
sphinx-maven-plugin
@@ -348,7 +362,29 @@
-
+
+ org.codehaus.mojo
+ aspectj-maven-plugin
+ 1.7
+
+ 1.8
+
+ 1.8
+
+
+ org.gcube.common
+ authorization-control-library
+
+
+
+
+
+
+ compile
+
+
+
+
org.apache.maven.plugins
diff --git a/src/main/java/org/gcube/portal/social/networking/ws/ex/AuthException.java b/src/main/java/org/gcube/portal/social/networking/ws/ex/AuthException.java
new file mode 100644
index 0000000..e1ebdd3
--- /dev/null
+++ b/src/main/java/org/gcube/portal/social/networking/ws/ex/AuthException.java
@@ -0,0 +1,16 @@
+package org.gcube.portal.social.networking.ws.ex;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response.Status;
+
+public class AuthException extends WebApplicationException {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ public AuthException(Throwable cause) {
+ super(cause, Status.FORBIDDEN);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Notifications.java b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Notifications.java
index 61fc7fe..07f6546 100644
--- a/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Notifications.java
+++ b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Notifications.java
@@ -26,6 +26,7 @@ import org.gcube.applicationsupportlayer.social.NotificationsManager;
import org.gcube.applicationsupportlayer.social.ScopeBeanExt;
import org.gcube.applicationsupportlayer.social.shared.SocialNetworkingSite;
import org.gcube.applicationsupportlayer.social.shared.SocialNetworkingUser;
+import org.gcube.common.authorization.control.annotations.AuthorizationControl;
import org.gcube.common.authorization.library.provider.AuthorizationProvider;
import org.gcube.common.authorization.library.utils.Caller;
import org.gcube.common.scope.api.ScopeProvider;
@@ -40,11 +41,13 @@ import org.gcube.portal.social.networking.caches.UsersCache;
import org.gcube.portal.social.networking.liferay.ws.GroupManagerWSBuilder;
import org.gcube.portal.social.networking.liferay.ws.LiferayJSONWsCredentials;
import org.gcube.portal.social.networking.liferay.ws.UserManagerWSBuilder;
+import org.gcube.portal.social.networking.ws.ex.AuthException;
import org.gcube.portal.social.networking.ws.mappers.CatalogueEventTypeMapper;
import org.gcube.portal.social.networking.ws.mappers.JobMapper;
import org.gcube.portal.social.networking.ws.mappers.WorkspaceItemMapper;
import org.gcube.portal.social.networking.ws.outputs.ResponseBean;
import org.gcube.portal.social.networking.ws.utils.CassandraConnection;
+import org.gcube.portal.social.networking.ws.utils.DistributedCacheClient;
import org.gcube.portal.social.networking.ws.utils.ErrorMessages;
import org.gcube.social_networking.socialnetworking.model.beans.JobNotificationBean;
@@ -72,6 +75,8 @@ import com.webcohesion.enunciate.metadata.rs.RequestHeaders;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
+import net.spy.memcached.MemcachedClient;
+
/**
* REST interface for the social networking library (notifications).
*/
@@ -84,6 +89,7 @@ public class Notifications {
// Logger
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Notifications.class);
+ private static final String INFRASTRUCTURE_MANAGER_ROLE = "Infrastructure-Manager";
@GET
@Path("get-range-notifications/")
@@ -130,6 +136,47 @@ public class Notifications {
return Response.status(status).entity(responseBean).build();
}
+ @GET
+ @Path("is-user-disabled/")
+ @Produces(MediaType.APPLICATION_JSON)
+ /**
+ * Return whether the notifications for this user are enabled or not
+ * @param from must be greater or equal to 1, range[0, infinity]
+ * @param quantity quantity must be greater or equal to 0
+ * @return notifications up to quantity
+ * @throws ValidationException
+ */
+ @StatusCodes ({
+ @ResponseCode ( code = 200, condition = "Users with silenced notifications are reported in the 'result' field of the returned object"),
+ @ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT)
+ })
+ @AuthorizationControl(allowedRoles={INFRASTRUCTURE_MANAGER_ROLE}, exception=AuthException.class)
+ public Response isUserDisabled(String usernameToCheck) throws ValidationException{
+ ResponseBean responseBean = new ResponseBean();
+ Status status = Status.OK;
+ try{
+ Boolean userDisabled= !isNotificationEnabled(usernameToCheck);
+ responseBean.setResult(userDisabled);
+ responseBean.setSuccess(true);
+ logger.debug("are User " + usernameToCheck + " Notifications Disabled?"+userDisabled);
+ }catch(Exception e){
+ logger.error("Unable to read whether the notifications for this user are enabled or not.", e);
+ responseBean.setMessage(e.getMessage());
+ responseBean.setSuccess(false);
+ status = Status.INTERNAL_SERVER_ERROR;
+ }
+
+ return Response.status(status).entity(responseBean).build();
+ }
+
+ private boolean isNotificationEnabled(String usernameToCheck) {
+ MemcachedClient entries = new DistributedCacheClient().getMemcachedClient();
+ Boolean userEnabled = false;
+ if(entries.get(usernameToCheck) == null)
+ userEnabled = true;
+ return userEnabled;
+ }
+
/**
* Send a JOB notification to a given recipient
* @param job The job bean
@@ -225,26 +272,28 @@ public class Notifications {
if (! event.idsAsGroup()) {
for (int i = 0; i < idsToNotify.length; i++) {
String userIdToNotify = idsToNotify[i];
- String username2Notify = null;
- try {
- username2Notify = um.getUserByUsername(userIdToNotify).getUsername();
- }
- catch (Exception e) {
- status = Status.BAD_REQUEST;
- logger.error("Username not found", e);
- responseBean.setSuccess(false);
- responseBean.setMessage("Username not found, got: " + userIdToNotify);
- return Response.status(status).entity(responseBean).build();
- }
-
- deliveryResult =
+ if (isNotificationEnabled(userIdToNotify)) {
+ String username2Notify = null;
+ try {
+ username2Notify = um.getUserByUsername(userIdToNotify).getUsername();
+ }
+ catch (Exception e) {
+ status = Status.BAD_REQUEST;
+ logger.error("Username not found", e);
+ responseBean.setSuccess(false);
+ responseBean.setMessage("Username not found, got: " + userIdToNotify);
+ return Response.status(status).entity(responseBean).build();
+ }
+
+ deliveryResult =
nm.notifyCatalogueEvent(
CatalogueEventTypeMapper.getType(event.getType()),
username2Notify,
event.getItemId(),
event.getNotifyText(),
event.getItemURL());
-
+ }
+
}
} else { //the ids are contexts
for (int i = 0; i < idsToNotify.length; i++) {
@@ -261,13 +310,15 @@ public class Notifications {
String[] userIdsToNotify = getUsernamesByContext(scope).toArray(new String[0]); //resolve the members
for (int j = 0; j < userIdsToNotify.length; j++) {
String userIdToNotify = userIdsToNotify[j];
- deliveryResult =
- nm.notifyCatalogueEvent(
- CatalogueEventTypeMapper.getType(event.getType()),
- userIdToNotify,
- event.getItemId(),
- event.getNotifyText(),
- event.getItemURL());
+ if (isNotificationEnabled(userIdToNotify)) {
+ deliveryResult =
+ nm.notifyCatalogueEvent(
+ CatalogueEventTypeMapper.getType(event.getType()),
+ userIdToNotify,
+ event.getItemId(),
+ event.getNotifyText(),
+ event.getItemURL());
+ }
}
}
}
@@ -338,18 +389,20 @@ public class Notifications {
if (! event.idsAsGroup()) {
for (int i = 0; i < idsToNotify.length; i++) {
String userIdToNotify = idsToNotify[i];
- String username2Notify = "";
- try {
- username2Notify = um.getUserByUsername(userIdToNotify).getUsername();
+ if (isNotificationEnabled(userIdToNotify)) {
+ String username2Notify = "";
+ try {
+ username2Notify = um.getUserByUsername(userIdToNotify).getUsername();
+ }
+ catch (Exception e) {
+ status = Status.NOT_ACCEPTABLE;
+ logger.error("Username not found", e);
+ responseBean.setSuccess(false);
+ responseBean.setMessage("Username not found, received: " + userIdToNotify);
+ return Response.status(status).entity(responseBean).build();
+ }
+ deliveryResult = notifyWorkspaceEvent(event, nm, username2Notify);
}
- catch (Exception e) {
- status = Status.NOT_ACCEPTABLE;
- logger.error("Username not found", e);
- responseBean.setSuccess(false);
- responseBean.setMessage("Username not found, received: " + userIdToNotify);
- return Response.status(status).entity(responseBean).build();
- }
- deliveryResult = notifyWorkspaceEvent(event, nm, username2Notify);
}
} else { //the ids are contexts
for (int i = 0; i < idsToNotify.length; i++) {
@@ -366,7 +419,8 @@ public class Notifications {
String[] userIdsToNotify = getUsernamesByContext(scope).toArray(new String[0]); //resolve the members
for (int j = 0; j < userIdsToNotify.length; j++) {
String userIdToNotify = userIdsToNotify[j];
- deliveryResult = notifyWorkspaceEvent(event, nm, userIdToNotify);
+ if (isNotificationEnabled(userIdToNotify))
+ deliveryResult = notifyWorkspaceEvent(event, nm, userIdToNotify);
}
}
}
diff --git a/src/main/java/org/gcube/portal/social/networking/ws/utils/DistributedCacheClient.java b/src/main/java/org/gcube/portal/social/networking/ws/utils/DistributedCacheClient.java
new file mode 100644
index 0000000..38fb3b2
--- /dev/null
+++ b/src/main/java/org/gcube/portal/social/networking/ws/utils/DistributedCacheClient.java
@@ -0,0 +1,95 @@
+package org.gcube.portal.social.networking.ws.utils;
+
+import static org.gcube.resources.discovery.icclient.ICFactory.clientFor;
+import static org.gcube.resources.discovery.icclient.ICFactory.queryFor;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.gcube.common.resources.gcore.ServiceEndpoint;
+import org.gcube.common.resources.gcore.ServiceEndpoint.AccessPoint;
+import org.gcube.common.resources.gcore.utils.Group;
+import org.gcube.common.scope.api.ScopeProvider;
+import org.gcube.resources.discovery.client.api.DiscoveryClient;
+import org.gcube.resources.discovery.client.queries.api.SimpleQuery;
+import org.gcube.smartgears.ContextProvider;
+import org.gcube.smartgears.context.application.ApplicationContext;
+import org.slf4j.LoggerFactory;
+
+import net.spy.memcached.KetamaConnectionFactory;
+import net.spy.memcached.MemcachedClient;
+
+/**
+ * @author Massimiliano Assante at ISTI-CNR
+ */
+public class DistributedCacheClient {
+
+ // Logger
+ private static final org.slf4j.Logger logger = LoggerFactory.getLogger(DistributedCacheClient.class);
+
+ private static final String MEMCACHED_RESOURCE_NAME = "Memcached";
+ private static final String CATEGORY = "Database";
+
+ private MemcachedClient mClient;
+
+ /**
+ * Build the singleton instance
+ */
+ public DistributedCacheClient(){
+ List addrs = discoverHostOfServiceEndpoint();
+ try {
+ mClient = new MemcachedClient(new KetamaConnectionFactory(), addrs);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public MemcachedClient getMemcachedClient() {
+ return mClient;
+ }
+
+ /**
+ * Retrieve endpoint resoruce from IS
+ * @return List of InetSocketAddresses
+ * @throws Exception
+ */
+ private static List discoverHostOfServiceEndpoint(){
+
+ String currentScope = ScopeProvider.instance.get();
+ ApplicationContext ctx = ContextProvider.get(); // get this info from SmartGears
+ String infrastructure = "/"+ctx.container().configuration().infrastructure();
+ ScopeProvider.instance.set(infrastructure);
+ List toReturn = new ArrayList();
+ try{
+ SimpleQuery query = queryFor(ServiceEndpoint.class);
+ query.addCondition("$resource/Profile/Name/text() eq '"+ MEMCACHED_RESOURCE_NAME +"'");
+ query.addCondition("$resource/Profile/Category/text() eq '"+ CATEGORY +"'");
+ DiscoveryClient client = clientFor(ServiceEndpoint.class);
+ List ses = client.submit(query);
+ if (ses.isEmpty()) {
+ logger.error("There is no Memcached cluster having name: " + MEMCACHED_RESOURCE_NAME + " and Category " + CATEGORY + " on root context in this infrastructure: ");
+ return null;
+ }
+ for (ServiceEndpoint se : ses) {
+ Group aps = se.profile().accessPoints();
+ for (AccessPoint ap : aps.asCollection()) {
+ String address = ap.address(); //e.g. socialnetworking-d-d4s.d4science.org:11211
+ String[] splits = address.split(":");
+ String hostname = splits[0];
+ int port = Integer.parseInt(splits[1]);
+ toReturn.add(new InetSocketAddress(hostname, port));
+ }
+ break;
+ }
+ } catch(Exception e){
+ logger.error("Error while retrieving hosts for the Memcached cluster having name: " + MEMCACHED_RESOURCE_NAME + " and Category " + CATEGORY + " on root context");
+ }finally{
+ ScopeProvider.instance.set(currentScope);
+ }
+ ScopeProvider.instance.set(currentScope);
+ return toReturn;
+ }
+
+}