/** * Copyright 2014 Google Inc. All rights reserved. * * 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.google.samples.apps.iosched.server.gcm.api; import com.google.samples.apps.iosched.server.gcm.AuthHelper; import com.google.samples.apps.iosched.server.gcm.AuthHelper.AuthInfo; import com.google.samples.apps.iosched.server.gcm.BaseServlet; import com.google.samples.apps.iosched.server.gcm.db.DeviceStore; import com.google.samples.apps.iosched.server.gcm.db.models.Device; import com.google.samples.apps.iosched.server.gcm.device.MessageSender; import java.io.IOException; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.logging.Logger; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** Send message to all registered devices */ @SuppressWarnings("serial") public class SendMessageServlet extends BaseServlet { private static final Logger LOG = Logger.getLogger(SendMessageServlet.class.getName()); private static final String SELF = "self"; /** Actions that can be executed by non admins. */ private static final HashSet<String> UNPRIVILEGED_ACTIONS = new HashSet<String>( Arrays.asList(new String[]{"sync_schedule", "test", "sync_user"})); @Override protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws IOException { // Allow CORS requests from any domain. The response below allows // preflight requests to be correctly responded. resp.addHeader("Access-Control-Allow-Origin", "*"); resp.addHeader("Access-Control-Allow-Headers", "authorization"); resp.setStatus(200); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { // Extract URL components String result = req.getPathInfo(); if (result == null) { send(resp, 400, "Bad request (check request format)"); return; } String[] components = result.split("/"); if (components.length != 3) { send(resp, 400, "Bad request (check request format)"); return; } String target = components[1]; String action = components[2]; // Let's see what this user is authorized to do AuthInfo authInfo = AuthHelper.processAuthorization(req); // If no auth info or non-admin trying to run non-whitelisted actions, no access. if (authInfo == null || action == null || (!UNPRIVILEGED_ACTIONS.contains(action) && !authInfo.permAdmin)) { send(resp, 403, "Not authorized"); return; } // Extract extraData String payload = readBody(req); // Request decoding complete. Log request parameters LOG.info("Authorized User: " + authInfo.clientName + "\nTarget: " + target + "\nAction: " + action + "\nExtra Data: " + payload); MessageSender sender = new MessageSender(getServletConfig()); // what's the audience of the message? if ("global".equals(target)) { // Only admins can spam the world if (!authInfo.permAdmin) { LOG.info("Attempt to send global message, but no admin perm."); send(resp, 403, "Not authorized"); return; } List<Device> allDevices = DeviceStore.getAllDevices(); if (allDevices == null || allDevices.isEmpty()) { send(resp, 404, "No devices registered"); } else { int resultCount = allDevices.size(); LOG.info("Selected " + resultCount + " devices"); sender.multicastSend(allDevices, action, payload); send(resp, 200, "Message queued: " + resultCount + " devices"); } } else { // Send message to one device // If target is SELF, the GCM Group ID will be the auth key in header if (SELF.equals(target)) { // do we have permission to send message to self? if (!authInfo.permSendSelfMessage) { LOG.info("Attempt to send self message, but no self message perm."); send(resp, 403, "Not authorized"); return; } // the target is the auth key (it represents the group) target = authInfo.authKey; } else { // sending message to a specific user. Only admin can do it. if (!authInfo.permAdmin) { LOG.info("Attempt to send message to specific target, but no admin perm."); send(resp, 403, "Not authorized"); return; } } List<Device> userDevices = DeviceStore.findDevicesByGcmGroupId(target); if (userDevices == null || userDevices.isEmpty()) { send(resp, 404, "User not found"); } else { int resultCount = userDevices.size(); LOG.info("Selected " + resultCount + " devices"); sender.multicastSend(userDevices, action, payload); send(resp, 200, "Message queued: " + resultCount + " devices"); } } } private String readBody(HttpServletRequest req) throws IOException { ServletInputStream inputStream = req.getInputStream(); java.util.Scanner s = new java.util.Scanner(inputStream).useDelimiter("\\A"); return s.hasNext() ? s.next() : ""; } }