Java tutorial
// 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()); } } }