/* * * Headwind MDM: Open Source Android MDM Software * https://h-mdm.com * * Copyright (C) 2019 Headwind Solutions LLC (http://h-sms.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.hmdm.plugins.messaging.rest; import com.hmdm.notification.PushService; import com.hmdm.notification.persistence.domain.PushMessage; import com.hmdm.persistence.DeviceDAO; import com.hmdm.persistence.UnsecureDAO; import com.hmdm.persistence.domain.Device; import com.hmdm.persistence.domain.DeviceSearchRequest; import com.hmdm.plugin.service.PluginStatusCache; import com.hmdm.plugins.messaging.persistence.MessagingDAO; import com.hmdm.plugins.messaging.persistence.domain.Message; import com.hmdm.plugins.messaging.rest.json.MessageFilter; import com.hmdm.plugins.messaging.rest.json.SendRequest; import com.hmdm.rest.json.PaginatedData; import com.hmdm.rest.json.Response; import com.hmdm.security.SecurityContext; import com.hmdm.security.SecurityException; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import io.swagger.annotations.Authorization; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Singleton; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import java.util.LinkedList; import java.util.List; /** * <p>A resource to be used for managing the <code>Messaging</code> plugin data for customer account associated * with current user.</p> * * @author isv */ @Singleton @Path("/plugins/messaging") @Api(tags = {"Messaging plugin"}) public class MessagingResource { private static final Logger logger = LoggerFactory.getLogger(MessagingResource.class); /** * <p>An interface to message records persistence.</p> */ private MessagingDAO messagingDAO; /** * <p>An interface to persistence without security checks.</p> */ private UnsecureDAO unsecureDAO; /** * <p>An interface to device records persistence.</p> */ private DeviceDAO deviceDAO; /** * <p>An interface to notification services.</p> */ private PushService pushService; private PluginStatusCache pluginStatusCache; /** * <p>A constructor required by swagger.</p> */ public MessagingResource() { } /** * <p>Constructs new <code>MessagingResource</code> instance. This implementation does nothing.</p> */ @Inject public MessagingResource(MessagingDAO messagingDAO, UnsecureDAO unsecureDAO, DeviceDAO deviceDAO, PushService pushService, PluginStatusCache pluginStatusCache) { this.messagingDAO = messagingDAO; this.unsecureDAO = unsecureDAO; this.deviceDAO = deviceDAO; this.pushService = pushService; this.pluginStatusCache = pluginStatusCache; } // ================================================================================================================= /** * <p>Gets the list of device log records matching the specified filter.</p> * * @param filter a filter to be used for filtering the records. * @return a response with list of device log records matching the specified filter. */ @ApiOperation( value = "Search messages", notes = "Gets the list of message records matching the specified filter", response = PaginatedData.class, authorizations = {@Authorization("Bearer Token")} ) @POST @Path("/private/search") @Produces(MediaType.APPLICATION_JSON) public Response getMessages(MessageFilter filter) { try { List<Message> records = this.messagingDAO.findAll(filter); long count = this.messagingDAO.countAll(filter); return Response.OK(new PaginatedData<>(records, count)); } catch (Exception e) { logger.error("Failed to search the message records due to unexpected error. Filter: {}", filter, e); return Response.INTERNAL_ERROR(); } } // ================================================================================================================= @ApiOperation( value = "Send new message", notes = "Sends a new message to a specified device.", authorizations = {@Authorization("Bearer Token")} ) @POST @Path("/private/send") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response sendMessage(SendRequest sendRequest) { try { final boolean canSendMessages = SecurityContext.get().hasPermission("plugin_messaging_send"); if (!canSendMessages) { logger.error("Unauthorized attempt to send a message", SecurityException.onCustomerDataAccessViolation(0, "message")); return Response.PERMISSION_DENIED(); } List<Message> messages = new LinkedList<>(); if (sendRequest.getScope().equals("device")) { // Send by device number if (sendRequest.getDeviceNumber() != null) { Message message = new Message(); Device device = deviceDAO.getDeviceByNumber(sendRequest.getDeviceNumber()); if (device == null) { String error = "Attempt to send message to wrong device number " + sendRequest.getDeviceNumber(); logger.error(error); return Response.ERROR(error); } message.setDeviceId(device.getId()); messages.add(message); } else { String error = "Empty device number while trying to send a message!"; logger.error(error); return Response.ERROR(error); } } else { DeviceSearchRequest dsr = new DeviceSearchRequest(); dsr.setPageSize(1000000); // No page limitations dsr.setCustomerId(SecurityContext.get().getCurrentCustomerId().get()); dsr.setUserId(SecurityContext.get().getCurrentUser().get().getId()); if (sendRequest.getScope().equals("group")) { if (sendRequest.getGroupId() == null || sendRequest.getGroupId() == 0) { String error = "Empty group id while trying to send a message to group!"; logger.error(error); return Response.ERROR(error); } dsr.setGroupId(sendRequest.getGroupId()); } else if (sendRequest.getScope().equals("configuration")) { if (sendRequest.getConfigurationId() == null || sendRequest.getConfigurationId() == 0) { String error = "Empty configuration id while trying to send a message to configuration!"; logger.error(error); return Response.ERROR(error); } dsr.setConfigurationId(sendRequest.getConfigurationId()); } List<Device> devices = deviceDAO.getAllDevices(dsr).getItems(); for (Device device : devices) { Message message = new Message(); message.setDeviceId(device.getId()); messages.add(message); } } for (Message message : messages) { message.setMessage(sendRequest.getMessage()); message.setTs(System.currentTimeMillis()); sendSingleMessage(message); } return Response.OK(); } catch (Exception e) { logger.error("Unexpected error when sending a message", e); return Response.ERROR(); } } private boolean sendSingleMessage(Message message) { try { this.messagingDAO.insertMessage(message); PushMessage pushMessage = new PushMessage(); pushMessage.setDeviceId(message.getDeviceId()); pushMessage.setPayload("{id:" + message.getId() + ",text:\"" + message.getMessage().trim().replace("\"", "\\\"") + "\"}"); pushMessage.setMessageType("textMessage"); this.pushService.send(pushMessage); return true; } catch (Exception e) { logger.error("Unexpected error when sending a message to " + message.getDeviceId(), e); return false; } } // ================================================================================================================= @ApiOperation( value = "Delete message", notes = "Delete an existing message" ) @DELETE @Path("/{id}") @Produces(MediaType.APPLICATION_JSON) public Response removeDevice(@PathParam("id") @ApiParam("Message ID") Integer id) { final boolean canSendMessages = SecurityContext.get().hasPermission("plugin_messaging_delete"); if (!(canSendMessages)) { logger.error("Unauthorized attempt to delete message", SecurityException.onCustomerDataAccessViolation(id, "message")); return Response.PERMISSION_DENIED(); } this.messagingDAO.deleteMessage(id); return Response.OK(); } // ================================================================================================================= @ApiOperation( value = "Purge old messages", notes = "Deletes all messages older than a specified number of days.", authorizations = {@Authorization("Bearer Token")} ) @GET @Path("/private/purge/{days}") @Produces(MediaType.APPLICATION_JSON) public Response purgeMessages(@PathParam("days") Integer days) { try { final boolean canPurgeMessages = SecurityContext.get().hasPermission("plugin_messaging_delete"); if (!canPurgeMessages) { logger.error("Unauthorized attempt to purge old messages", SecurityException.onCustomerDataAccessViolation(0, "message")); return Response.PERMISSION_DENIED(); } this.messagingDAO.purgeOldMessages(days); return Response.OK(); } catch (Exception e) { logger.error("Unexpected error when purging old messages", e); return Response.ERROR(); } } // ================================================================================================================= @ApiOperation( value = "Sets the message status", notes = "Marks message as delivered or read." ) @GET @Path("/public/status/{id}/{status}") @Produces(MediaType.APPLICATION_JSON) public Response setMessageStatus(@PathParam("id") Integer id, @PathParam("status") Integer status) { if (status == null || status < 0 || status > Message.STATUS_READ) { logger.error("Wrong status " + status + " for message id " + id); return Response.ERROR(); } try { this.messagingDAO.updateMessageStatus(id, status); return Response.OK(); } catch (Exception e) { logger.error("Unexpected error when marking the message " + id + " as read", e); return Response.ERROR(); } } }