com.google.gerrit.httpd.TokenVerifiedRestApiServlet.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gerrit.httpd.TokenVerifiedRestApiServlet.java

Source

// Copyright (C) 2012 The Android Open Source Project
//
// 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.gerrit.httpd;

import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;

import com.google.common.base.Strings;
import com.google.common.collect.Iterators;
import com.google.common.collect.Maps;
import com.google.gerrit.httpd.RestTokenVerifier.InvalidTokenException;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.OutputFormat;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.inject.Inject;
import com.google.inject.Provider;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Enumeration;
import java.util.Map;

import javax.annotation.Nullable;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

public abstract class TokenVerifiedRestApiServlet extends RestApiServlet {
    private static final long serialVersionUID = 1L;
    private static final String FORM_ENCODED = "application/x-www-form-urlencoded";
    private static final String UTF_8 = "UTF-8";
    private static final String AUTHKEY_NAME = "_authkey";
    private static final String AUTHKEY_HEADER = "X-authkey";

    private final Gson gson;
    private final Provider<CurrentUser> userProvider;
    private final RestTokenVerifier verifier;

    @Inject
    protected TokenVerifiedRestApiServlet(Provider<CurrentUser> userProvider, RestTokenVerifier verifier) {
        super(userProvider);
        this.gson = OutputFormat.JSON_COMPACT.newGson();
        this.userProvider = userProvider;
        this.verifier = verifier;
    }

    /**
     * Process the (possibly state changing) request.
     *
     * @param req incoming HTTP request.
     * @param res outgoing response.
     * @param requestData JSON object representing the HTTP request parameters.
     *        Null if the request body was not supplied in JSON format.
     * @throws IOException
     * @throws ServletException
     */
    protected abstract void doRequest(HttpServletRequest req, HttpServletResponse res,
            @Nullable JsonObject requestData) throws IOException, ServletException;

    @Override
    protected final void doGet(HttpServletRequest req, HttpServletResponse res)
            throws ServletException, IOException {
        CurrentUser user = userProvider.get();
        if (!(user instanceof IdentifiedUser)) {
            sendError(res, SC_UNAUTHORIZED, "API requires authentication");
            return;
        }

        TokenInfo info = new TokenInfo();
        info._authkey = verifier.sign(((IdentifiedUser) user).getAccountId(), computeUrl(req));

        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        String type;
        buf.write(JSON_MAGIC);
        if (acceptsJson(req)) {
            type = JSON_TYPE;
            buf.write(gson.toJson(info).getBytes(UTF_8));
        } else {
            type = FORM_ENCODED;
            buf.write(
                    String.format("%s=%s", AUTHKEY_NAME, URLEncoder.encode(info._authkey, UTF_8)).getBytes(UTF_8));
        }

        res.setContentType(type);
        res.setCharacterEncoding(UTF_8);
        res.setHeader("Content-Disposition", "attachment");
        send(req, res, buf.toByteArray());
    }

    @Override
    protected final void doPost(HttpServletRequest req, HttpServletResponse res)
            throws IOException, ServletException {
        CurrentUser user = userProvider.get();
        if (!(user instanceof IdentifiedUser)) {
            sendError(res, SC_UNAUTHORIZED, "API requires authentication");
            return;
        }

        ParsedBody body;
        if (JSON_TYPE.equals(req.getContentType())) {
            body = parseJson(req, res);
        } else if (FORM_ENCODED.equals(req.getContentType())) {
            body = parseForm(req, res);
        } else {
            sendError(res, SC_BAD_REQUEST,
                    String.format("Expected Content-Type: %s or %s", JSON_TYPE, FORM_ENCODED));
            return;
        }

        if (body == null) {
            return;
        }

        if (Strings.isNullOrEmpty(body._authkey)) {
            String h = req.getHeader(AUTHKEY_HEADER);
            if (Strings.isNullOrEmpty(h)) {
                sendError(res, SC_BAD_REQUEST, String.format("Expected %s in request body or %s in HTTP headers",
                        AUTHKEY_NAME, AUTHKEY_HEADER));
                return;
            }
            body._authkey = URLDecoder.decode(h, UTF_8);
        }

        try {
            verifier.verify(((IdentifiedUser) user).getAccountId(), computeUrl(req), body._authkey);
        } catch (InvalidTokenException err) {
            sendError(res, SC_BAD_REQUEST, String.format("Invalid or expired %s", AUTHKEY_NAME));
            return;
        }

        doRequest(body.req, res, body.json);
    }

    private static ParsedBody parseJson(HttpServletRequest req, HttpServletResponse res) throws IOException {
        try {
            JsonElement element = new JsonParser().parse(req.getReader());
            if (!element.isJsonObject()) {
                sendError(res, SC_BAD_REQUEST, "Expected JSON object in request body");
                return null;
            }

            ParsedBody body = new ParsedBody();
            body.req = req;
            body.json = (JsonObject) element;
            JsonElement authKey = body.json.remove(AUTHKEY_NAME);
            if (authKey != null && authKey.isJsonPrimitive() && authKey.getAsJsonPrimitive().isString()) {
                body._authkey = authKey.getAsString();
            }
            return body;
        } catch (JsonParseException e) {
            sendError(res, SC_BAD_REQUEST, "Invalid JSON object in request body");
            return null;
        }
    }

    private static ParsedBody parseForm(HttpServletRequest req, HttpServletResponse res) throws IOException {
        ParsedBody body = new ParsedBody();
        body.req = new WrappedRequest(req);
        body._authkey = req.getParameter(AUTHKEY_NAME);
        return body;
    }

    private static String computeUrl(HttpServletRequest req) {
        StringBuffer url = req.getRequestURL();
        String qs = req.getQueryString();
        if (!Strings.isNullOrEmpty(qs)) {
            url.append('?').append(qs);
        }
        return url.toString();
    }

    private static class TokenInfo {
        String _authkey;
    }

    private static class ParsedBody {
        HttpServletRequest req;
        String _authkey;
        JsonObject json;
    }

    private static class WrappedRequest extends HttpServletRequestWrapper {
        @SuppressWarnings("rawtypes")
        private Map parameters;

        WrappedRequest(HttpServletRequest req) {
            super(req);
        }

        @Override
        public String getParameter(String name) {
            if (AUTHKEY_NAME.equals(name)) {
                return null;
            }
            return super.getParameter(name);
        }

        @Override
        public String[] getParameterValues(String name) {
            if (AUTHKEY_NAME.equals(name)) {
                return null;
            }
            return super.getParameterValues(name);
        }

        @SuppressWarnings({ "rawtypes", "unchecked" })
        @Override
        public Map getParameterMap() {
            Map m = parameters;
            if (m == null) {
                m = super.getParameterMap();
                if (m.containsKey(AUTHKEY_NAME)) {
                    m = Maps.newHashMap(m);
                    m.remove(AUTHKEY_NAME);
                }
                parameters = m;
            }
            return m;
        }

        @SuppressWarnings({ "rawtypes", "unchecked" })
        @Override
        public Enumeration getParameterNames() {
            return Iterators.asEnumeration(getParameterMap().keySet().iterator());
        }
    }
}