keyserver.KeyServerServlet.java Source code

Java tutorial

Introduction

Here is the source code for keyserver.KeyServerServlet.java

Source

package keyserver;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import javax.servlet.http.*;

import org.datanucleus.util.Base64;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.Filter;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.FilterPredicate;
import com.google.appengine.api.datastore.Query.SortDirection;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.appengine.labs.repackaged.org.json.JSONArray;
import com.google.appengine.labs.repackaged.org.json.JSONException;
import com.google.appengine.labs.repackaged.org.json.JSONObject;

import static keyserver.Constants.*;

// All constants are declared in keyserver.Constants.

/**
 * Copyright (C) 2014  Carlos Pars: carlosparespulido (at) gmail (dot) com
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

@SuppressWarnings("serial")
public class KeyServerServlet extends HttpServlet {

    //   private static final Logger log = Logger.getLogger(KeyServerServlet.class.getName());

    private String retrieveKey(DatastoreService datastore, Key ownerId) {
        Query query = new Query(DB_KIND_USER, ownerId);
        Entity identification = datastore.prepare(query).asSingleEntity();
        return (String) identification.getProperty(DB_USER_KEY);
    }

    protected class Rectangle {
        int x0, xEnd, y0, yEnd;

        Rectangle(int x0, int xEnd, int y0, int yEnd) {
            this.x0 = x0;
            this.xEnd = xEnd;
            this.y0 = y0;
            this.yEnd = yEnd;
        }
    }

    private User getCurrentGoogleUser() {
        return UserServiceFactory.getUserService().getCurrentUser();
    }

    private boolean isUserLoggedIn() {
        return getCurrentGoogleUser() != null;
    }

    private String createKeyForPermission(String userKey, Key picId, int x0, int xEnd, int y0, int yEnd) {
        String plaintextKey = userKey + ":" + KeyFactory.keyToString(picId) + ":" + x0 + ":" + xEnd + ":" + y0 + ":"
                + yEnd;
        MessageDigest md;
        String result = null;
        try {
            md = MessageDigest.getInstance("SHA-256");
            md.update(plaintextKey.getBytes());
            byte[] hash = md.digest();
            result = new String(Base64.encode(hash));
        } catch (NoSuchAlgorithmException e) {
            // Not going to happen. Every implementation of Java is required 
            // to support SHA-256, please see here:
            // http://docs.oracle.com/javase/7/docs/api/java/security/MessageDigest.html
        }
        return result;
    }

    private String createKeyForUser(User _user) {
        SecureRandom srand = new SecureRandom();
        byte[] iv = new byte[512];
        srand.nextBytes(iv);
        MessageDigest md = null;

        // This block initializes the MessageDigest
        try {
            md = MessageDigest.getInstance("SHA-256");
        } catch (NoSuchAlgorithmException e) {
            // Not going to happen. Every implementation of Java is required 
            // to support SHA-256, please see here:
            // http://docs.oracle.com/javase/7/docs/api/java/security/MessageDigest.html
        }

        md.update(iv);
        byte[] hash = md.digest();
        return new String(Base64.encode(hash));
    }

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {

        if (!isUserLoggedIn()) {
            JSONArray error = createJsonLoginErrorObject("Please login with your Google account");
            resp.getWriter().print(error.toString());
        } else { // user is logged in
            String action = req.getParameter(QUERYSTRING_ACTION);
            if (action == null) {
                // Error! A valid query will always declare an action
                JSONArray error = createJsonErrorObject("No action declared");
                resp.getWriter().print(error.toString());
            } else if (action.equalsIgnoreCase(ACTION_READPERMISSIONS)) {
                //query for permissions
                processRequestForInformation(req, resp);
            } else if (action.equalsIgnoreCase(ACTION_MYPICTURES)) {
                // view all pictures uploaded by user
                viewMyPictures(req, resp);
            } else if (action.equalsIgnoreCase(ACTION_PICTUREDETAILS)) {
                // return all permissions for one picture
                viewPictureDetails(req, resp);
            } else {
                // unsupported operation
                JSONArray error = createJsonErrorObject("Operation " + action + " unknown");
                resp.getWriter().print(error.toString());
            }
        }
    }

    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {

        resp.setContentType("application/json");
        if (!isUserLoggedIn()) {
            JSONArray error = createJsonLoginErrorObject("Please login with your Google account");
            resp.getWriter().print(error.toString());
        } else { // user is logged in
            String action = req.getParameter(QUERYSTRING_ACTION);
            if (action == null) {
                // Error! A valid query will always declare an action
                JSONArray error = createJsonErrorObject("No action declared");
                resp.getWriter().print(error.toString());
            } else if (action.equalsIgnoreCase(ACTION_ONESTEPUPLOAD)) {
                oneStepPictureUpload(req, resp);
            } else if (action.equalsIgnoreCase(ACTION_UPDATE)) {
                performUpdate(req, resp);
            } else {
                // unsupported operation
                JSONArray error = createJsonErrorObject("Operation " + action + " unknown");
                resp.getWriter().print(error.toString());
            }
        }
    }

    private void oneStepPictureUpload(HttpServletRequest req, HttpServletResponse resp) throws IOException {

        User user = getCurrentGoogleUser();
        try {
            DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

            Key userId = KeyFactory.createKey(DB_KIND_USER, user.getUserId());
            Query query = new Query(DB_KIND_USER, userId);
            Entity identification = datastore.prepare(query).asSingleEntity();

            String userKey;

            if (identification == null) { // a new user (not in the DB)
                Entity newUser = new Entity(DB_KIND_USER, user.getUserId());
                userKey = createKeyForUser(user);
                newUser.setProperty(DB_USER_KEY, userKey);
                datastore.put(newUser);
                identification = newUser;
            } else { // user was already in the DB
                userKey = (String) identification.getProperty(DB_USER_KEY);
            }

            JSONArray array = new JSONArray(
                    (new BufferedReader(new InputStreamReader(req.getInputStream()))).readLine());
            JSONObject obj;
            obj = array.getJSONObject(0);
            Entity newPicture = new Entity(DB_KIND_PICTURE, identification.getKey());
            newPicture.setProperty(DB_PICTURE_CREATED,
                    new SimpleDateFormat(MISC_DATE_FORMAT, Locale.US).format(new Date()));
            newPicture.setProperty(DB_PICTURE_FILENAME, obj.get(JSON_FILENAME));
            newPicture.setProperty(DB_PICTURE_HEIGHT, obj.get(JSON_IMGHEIGHT));
            newPicture.setProperty(DB_PICTURE_WIDTH, obj.get(JSON_IMGWIDTH));
            datastore.put(newPicture);

            JSONArray response = new JSONArray();
            Key picId = newPicture.getKey();

            response.put(new JSONObject(JSON_DEFAULTOK));
            response.put(new JSONObject("{\"" + JSON_PICTUREID + "\": \"" + KeyFactory.keyToString(picId) + "\"}"));

            Entity newLine;
            JSONArray usernames;
            String key;
            int x0, xEnd, y0, yEnd;
            for (int i = 1; i < array.length(); i++) {
                obj = array.getJSONObject(i);
                x0 = obj.getInt(JSON_HSTART);
                xEnd = obj.getInt(JSON_HEND);
                y0 = obj.getInt(JSON_VSTART);
                yEnd = obj.getInt(JSON_VEND);
                usernames = obj.getJSONArray(JSON_USERNAME);

                for (int j = 0; j < usernames.length(); j++) {
                    newLine = new Entity(DB_KIND_PERMISSION, picId);
                    newLine.setProperty(DB_PERMISSION_H_START, x0);
                    newLine.setProperty(DB_PERMISSION_H_END, xEnd);
                    newLine.setProperty(DB_PERMISSION_V_START, y0);
                    newLine.setProperty(DB_PERMISSION_V_END, yEnd);
                    newLine.setProperty(DB_PERMISSION_USERNAME, (String) usernames.get(j));
                    datastore.put(newLine);
                }
                key = createKeyForPermission(userKey, picId, obj.getInt(JSON_HSTART), obj.getInt(JSON_HEND),
                        obj.getInt(JSON_VSTART), obj.getInt(JSON_VEND));
                response.put(new JSONObject("{\"" + JSON_KEY + "\": \"" + key + "\"}"));
            }
            resp.getWriter().print(response.toString());
        } catch (JSONException jsonEx) {
            JSONArray error = createJsonErrorObject("Malformed JSON object received");
            resp.getWriter().print(error);
        }
    }

    private void processRequestForInformation(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String id = req.getParameter(QUERYSTRING_PICTUREID);
        User user = getCurrentGoogleUser();
        if (id == null) {
            JSONArray error = createJsonErrorObject("No picture ID provided");
            resp.getWriter().print(error.toString());
        } else {
            DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
            try {
                Key key = KeyFactory.stringToKey(id);
                Query query = new Query(DB_KIND_PERMISSION, key);
                Filter usernameFilter = new FilterPredicate(DB_PERMISSION_USERNAME, FilterOperator.EQUAL,
                        user.getEmail().toLowerCase(Locale.ENGLISH));
                query.setFilter(usernameFilter);
                List<Entity> permissions = datastore.prepare(query)
                        .asList(FetchOptions.Builder.withLimit(MAX_REGIONS_ALLOWED));
                PrintWriter response = resp.getWriter();
                JSONArray array = new JSONArray();
                JSONObject permission;
                int x0, xEnd, y0, yEnd;
                // first element in a JSON returned array will always be the result
                // of whether the call was successful or not
                array.put(new JSONObject(JSON_DEFAULTOK));
                for (Entity p : permissions) {
                    permission = new JSONObject();
                    // properties are returned as Long, but int is expected. Direct conversion
                    // from Long to int is unsupported, so we have to include an intermediate
                    // casting from Long to long, which can be casted to int.
                    x0 = (int) (long) p.getProperty(DB_PERMISSION_H_START);
                    xEnd = (int) (long) p.getProperty(DB_PERMISSION_H_END);
                    y0 = (int) (long) p.getProperty(DB_PERMISSION_V_START);
                    yEnd = (int) (long) p.getProperty(DB_PERMISSION_V_END);
                    Entity pic = datastore.get(key);
                    permission.put(JSON_KEY, createKeyForPermission(retrieveKey(datastore, pic.getParent()),
                            pic.getKey(), x0, xEnd, y0, yEnd));
                    permission.put(JSON_HSTART, x0);
                    permission.put(JSON_HEND, xEnd);
                    permission.put(JSON_VSTART, y0);
                    permission.put(JSON_VEND, yEnd);
                    array.put(permission);
                }
                response.print(array.toString());
            } catch (JSONException ex) {
                // Exceptions should not happen here. The {"result": "ok"} JSON
                // will always be correctly formed, and the rest of the objects
                // come from the (validated on input) database.
                // Just in case, we notify of an error.
                JSONArray error = createJsonErrorObject("Serverside JSON problem: " + ex.getMessage());
                resp.getWriter().print(error.toString());
            } catch (IllegalArgumentException illArgExc) {
                // Might be thrown if the queried ID does not match the
                // standard format.
                JSONArray error = createJsonErrorObject("Malformed or non-existing ID requested");
                resp.getWriter().print(error.toString());
            } catch (EntityNotFoundException e) {
                // In theory might be thrown if user is not in database.
                // Shouldn't happen in practice, but just in case: 
                JSONArray error = createJsonErrorObject("Picture owner no longer in database");
                resp.getWriter().print(error.toString());
            }
        }
    }

    private void viewMyPictures(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        User user = getCurrentGoogleUser();
        DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
        Key userId = KeyFactory.createKey(DB_KIND_USER, user.getUserId());
        // this custom query needs to be specified also in war/WEB-INF/datastore-indexes.xml
        Query query = new Query(DB_KIND_PICTURE).setAncestor(userId).addSort(DB_PICTURE_CREATED,
                SortDirection.DESCENDING);
        List<Entity> pictures = datastore.prepare(query).asList(FetchOptions.Builder.withDefaults());
        JSONArray array = new JSONArray();
        try {
            array.put(new JSONObject(JSON_DEFAULTOK));
            JSONObject obj;
            for (Entity entity : pictures) {
                obj = new JSONObject();
                obj.put(JSON_FILENAME, entity.getProperty(DB_PICTURE_FILENAME));
                obj.put(JSON_DATECREATED, entity.getProperty(DB_PICTURE_CREATED));
                obj.put(JSON_PICTUREID, KeyFactory.keyToString(entity.getKey()));
                array.put(obj);
            }
            resp.getWriter().print(array);

        } catch (JSONException jsonex) {
            // Should never happen, but just in case
            JSONArray error = createJsonErrorObject("Problem loading pictures from database");
            resp.getWriter().print(error);
        }
    }

    private void viewPictureDetails(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        User user = getCurrentGoogleUser();
        String id = req.getParameter(QUERYSTRING_PICTUREID);
        if (id == null) {
            JSONArray error = createJsonErrorObject("No picture ID provided");
            resp.getWriter().print(error.toString());
        } else {
            DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
            try {
                Key key = KeyFactory.stringToKey(id);
                // if user is the owner of the picture
                if (KeyFactory.createKey(DB_KIND_USER, user.getUserId()).equals(key.getParent())) {
                    Query query = new Query(DB_KIND_PERMISSION, key);
                    List<Entity> permissions = datastore.prepare(query)
                            .asList(FetchOptions.Builder.withLimit(MAX_PERMISSIONS_RETURNED_PER_PICTURE));
                    query = new Query(DB_KIND_PICTURE, key);
                    Entity picture = datastore.prepare(query).asSingleEntity();
                    PrintWriter response = resp.getWriter();
                    JSONArray array = new JSONArray();
                    JSONObject permission;
                    int x0, xEnd, y0, yEnd;
                    // first element in a JSON returned array will always be the result
                    // of whether the call was successful or not
                    JSONObject metadata = new JSONObject(JSON_DEFAULTOK);
                    metadata.put(JSON_PICTUREID, id);
                    metadata.put(JSON_IMGHEIGHT, picture.getProperty(DB_PICTURE_HEIGHT));
                    metadata.put(JSON_IMGWIDTH, picture.getProperty(DB_PICTURE_WIDTH));
                    array.put(metadata);
                    for (Entity p : permissions) {
                        permission = new JSONObject();
                        // properties are returned as Long, but int is expected. Direct conversion
                        // from Long to int is unsupported, so we have to include an intermediate
                        // casting from Long to long, which can be casted to int.
                        x0 = (int) (long) p.getProperty(DB_PERMISSION_H_START);
                        xEnd = (int) (long) p.getProperty(DB_PERMISSION_H_END);
                        y0 = (int) (long) p.getProperty(DB_PERMISSION_V_START);
                        yEnd = (int) (long) p.getProperty(DB_PERMISSION_V_END);
                        permission.put(JSON_HSTART, x0);
                        permission.put(JSON_VSTART, y0);
                        permission.put(JSON_HEND, xEnd);
                        permission.put(JSON_VEND, yEnd);
                        permission.put(JSON_USERNAME, (String) p.getProperty(DB_PERMISSION_USERNAME));
                        permission.put(JSON_PERMISSIONID, KeyFactory.keyToString(p.getKey()));
                        array.put(permission);
                    }
                    response.print(array.toString());
                }
            } catch (JSONException ex) {
                // Exceptions should not happen here. The {"result": "ok"} JSON
                // will always be correctly formed, and the rest of the objects
                // come from the (validated on input) database.
                // Just in case, we notify of an error.
                JSONArray error = createJsonErrorObject("Serverside JSON problem: " + ex.getMessage());
                resp.getWriter().print(error.toString());
            } catch (IllegalArgumentException illArgExc) {
                // Might be thrown if the queried ID does not match the
                // standard format.
                JSONArray error = createJsonErrorObject("Malformed or non-existing ID requested");
                resp.getWriter().print(error.toString());
            }
        }
    }

    private void performUpdate(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        try {
            User user = getCurrentGoogleUser();
            DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
            Key userId = KeyFactory.createKey(DB_KIND_USER, user.getUserId());
            Query query = new Query(DB_KIND_USER, userId);
            Entity identification = datastore.prepare(query).asSingleEntity();

            JSONArray array = new JSONArray(
                    (new BufferedReader(new InputStreamReader(req.getInputStream()))).readLine());
            JSONObject obj;

            obj = array.getJSONObject(0);
            Key pictureKey = KeyFactory.stringToKey(obj.getString(JSON_PICTUREID));
            if (pictureKey != null && pictureKey.getParent().equals(identification.getKey())) {
                // if requester is the owner of the picture, otherwise the request is dismissed
                for (int i = 1; i < array.length(); i++) {
                    obj = array.getJSONObject(i);
                    if (obj.getString(JSON_UPDATEACTION).equalsIgnoreCase(JSON_UPDATEACTION_ADD)) {
                        Entity newPermission = new Entity(DB_KIND_PERMISSION, pictureKey);
                        newPermission.setProperty(DB_PERMISSION_H_START, obj.getInt(JSON_HSTART));
                        newPermission.setProperty(DB_PERMISSION_H_END, obj.getInt(JSON_HEND));
                        newPermission.setProperty(DB_PERMISSION_V_START, obj.getInt(JSON_VSTART));
                        newPermission.setProperty(DB_PERMISSION_V_END, obj.getInt(JSON_VEND));
                        newPermission.setProperty(DB_PERMISSION_USERNAME, obj.getString(JSON_USERNAME));
                        datastore.put(newPermission);
                    } else if (obj.getString(JSON_UPDATEACTION).equalsIgnoreCase(JSON_UPDATEACTION_DELETE)) {
                        datastore.delete(KeyFactory.stringToKey(obj.getString(JSON_PERMISSIONID)));
                    }
                }
            }
            JSONArray response = new JSONArray();
            response.put(new JSONObject(JSON_DEFAULTOK));
            resp.getWriter().print(response.toString());
        } catch (JSONException jsonEx) {
            JSONArray error = createJsonErrorObject("Malformed JSON object received");
            resp.getWriter().print(error);
        } catch (IllegalArgumentException illargex) {
            JSONArray error = createJsonErrorObject("Malformed or incomplete key received");
            resp.getWriter().print(error);
        }
    }

    private JSONArray createJsonErrorObject(String reason) {
        JSONArray array = null;
        JSONObject error = null;
        try {
            array = new JSONArray();
            error = new JSONObject();
            error.put(JSON_RESULT, JSON_RESULT_ERROR);
            error.put(JSON_REASON, reason);
            error.put(JSON_PROTOCOLVERSION, CURRENT_VERSION);
            error.put(JSON_ISAUTHERROR, false);
            array.put(error);
        } catch (JSONException jsonException) {
            // Won't happen: exception is thrown only if the first argument
            // is null, and clearly it won't be.
        }
        return array;
    }

    private JSONArray createJsonLoginErrorObject(String reason) {
        JSONArray array = null;
        JSONObject error = null;
        try {
            array = new JSONArray();
            error = new JSONObject();
            error.put(JSON_RESULT, JSON_RESULT_ERROR);
            error.put(JSON_REASON, reason);
            error.put(JSON_PROTOCOLVERSION, CURRENT_VERSION);
            error.put(JSON_ISAUTHERROR, true);
            array.put(error);
        } catch (JSONException jsonException) {
            // Won't happen: exception is thrown only if the first argument
            // is null, and clearly it won't be.
        }
        return array;
    }
}