Remove auth token based REST API support
This never turned into a real implementation. Drop it and look at other ways to do authentication. Change-Id: I404c834e5e9d0d61248d28471785d825c136f3ad
This commit is contained in:
parent
f8836d734f
commit
bf7fbd62bf
@ -44,11 +44,9 @@ public class RestApi {
|
||||
|
||||
private class MyRequestCallback<T extends JavaScriptObject> implements
|
||||
RequestCallback {
|
||||
private final boolean wasGet;
|
||||
private final AsyncCallback<T> cb;
|
||||
|
||||
public MyRequestCallback(boolean wasGet, AsyncCallback<T> cb) {
|
||||
this.wasGet = wasGet;
|
||||
MyRequestCallback(AsyncCallback<T> cb) {
|
||||
this.cb = cb;
|
||||
}
|
||||
|
||||
@ -79,11 +77,6 @@ public class RestApi {
|
||||
}
|
||||
json = json.substring(JSON_MAGIC.length());
|
||||
|
||||
if (wasGet && json.startsWith("{\"_authkey\":")) {
|
||||
RestApi.this.resendPost(cb, json);
|
||||
return;
|
||||
}
|
||||
|
||||
T data;
|
||||
try {
|
||||
// javac generics bug
|
||||
@ -168,7 +161,7 @@ public class RestApi {
|
||||
public <T extends JavaScriptObject> void send(final AsyncCallback<T> cb) {
|
||||
RequestBuilder req = new RequestBuilder(RequestBuilder.GET, url.toString());
|
||||
req.setHeader("Accept", JsonConstants.JSON_TYPE);
|
||||
req.setCallback(new MyRequestCallback<T>(true, cb));
|
||||
req.setCallback(new MyRequestCallback<T>(cb));
|
||||
try {
|
||||
RpcStatus.INSTANCE.onRpcStart();
|
||||
req.send();
|
||||
@ -178,21 +171,6 @@ public class RestApi {
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends JavaScriptObject> void resendPost(
|
||||
final AsyncCallback<T> cb, String token) {
|
||||
RequestBuilder req = new RequestBuilder(RequestBuilder.POST, url.toString());
|
||||
req.setHeader("Accept", JsonConstants.JSON_TYPE);
|
||||
req.setHeader("Content-Type", JsonConstants.JSON_TYPE);
|
||||
req.setRequestData(token);
|
||||
req.setCallback(new MyRequestCallback<T>(false, cb));
|
||||
try {
|
||||
req.send();
|
||||
} catch (RequestException e) {
|
||||
RpcStatus.INSTANCE.onRpcComplete();
|
||||
cb.onFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isJsonBody(Response res) {
|
||||
return isContentType(res, JsonConstants.JSON_TYPE);
|
||||
}
|
||||
|
@ -1,58 +0,0 @@
|
||||
// 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 com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.server.mail.RegisterNewEmailSender;
|
||||
|
||||
/** Verifies the token sent by {@link RegisterNewEmailSender}. */
|
||||
public interface RestTokenVerifier {
|
||||
/**
|
||||
* Construct a token to verify a REST PUT request.
|
||||
*
|
||||
* @param user the caller that wants to make a PUT request
|
||||
* @param url the URL being requested
|
||||
* @return an unforgeable string to send to the user as the body of a GET
|
||||
* request. Presenting the string in a follow-up POST request provides
|
||||
* proof the user has the ability to read messages sent to thier
|
||||
* browser and they likely aren't making the request via XSRF.
|
||||
*/
|
||||
public String sign(Account.Id user, String url);
|
||||
|
||||
/**
|
||||
* Decode a token previously created.
|
||||
*
|
||||
* @param user the user making the verify request.
|
||||
* @param url the url user is attempting to access.
|
||||
* @param token the string created by sign.
|
||||
* @throws InvalidTokenException the token is invalid, expired, malformed,
|
||||
* etc.
|
||||
*/
|
||||
public void verify(Account.Id user, String url, String token)
|
||||
throws InvalidTokenException;
|
||||
|
||||
/** Exception thrown when a token does not parse correctly. */
|
||||
public static class InvalidTokenException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public InvalidTokenException() {
|
||||
super("Invalid token");
|
||||
}
|
||||
|
||||
public InvalidTokenException(Throwable cause) {
|
||||
super("Invalid token", cause);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
// 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 com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.server.config.AuthConfig;
|
||||
import com.google.gwtjsonrpc.server.SignedToken;
|
||||
import com.google.gwtjsonrpc.server.ValidToken;
|
||||
import com.google.gwtjsonrpc.server.XsrfException;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.eclipse.jgit.util.Base64;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
/** Verifies the token sent by {@link RestApiServlet}. */
|
||||
public class SignedTokenRestTokenVerifier implements RestTokenVerifier {
|
||||
private final SignedToken restToken;
|
||||
|
||||
public static class Module extends AbstractModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(RestTokenVerifier.class).to(SignedTokenRestTokenVerifier.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject
|
||||
SignedTokenRestTokenVerifier(AuthConfig config) {
|
||||
restToken = config.getRestToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sign(Account.Id user, String url) {
|
||||
try {
|
||||
String payload = String.format("%s:%s", user, url);
|
||||
byte[] utf8 = payload.getBytes("UTF-8");
|
||||
String base64 = Base64.encodeBytes(utf8);
|
||||
return restToken.newToken(base64);
|
||||
} catch (XsrfException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(Account.Id user, String url, String tokenString)
|
||||
throws InvalidTokenException {
|
||||
ValidToken token;
|
||||
try {
|
||||
token = restToken.checkToken(tokenString, null);
|
||||
} catch (XsrfException err) {
|
||||
throw new InvalidTokenException(err);
|
||||
}
|
||||
if (token == null || token.getData() == null || token.getData().isEmpty()) {
|
||||
throw new InvalidTokenException();
|
||||
}
|
||||
|
||||
String payload;
|
||||
try {
|
||||
payload = new String(Base64.decode(token.getData()), "UTF-8");
|
||||
} catch (UnsupportedEncodingException err) {
|
||||
throw new InvalidTokenException(err);
|
||||
}
|
||||
|
||||
int colonPos = payload.indexOf(':');
|
||||
if (colonPos == -1) {
|
||||
throw new InvalidTokenException();
|
||||
}
|
||||
|
||||
Account.Id tokenUser;
|
||||
try {
|
||||
tokenUser = Account.Id.parse(payload.substring(0, colonPos));
|
||||
} catch (IllegalArgumentException err) {
|
||||
throw new InvalidTokenException(err);
|
||||
}
|
||||
|
||||
String tokenUrl = payload.substring(colonPos+1);
|
||||
|
||||
if (!tokenUser.equals(user) || !tokenUrl.equals(url)) {
|
||||
throw new InvalidTokenException();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,263 +0,0 @@
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ import com.google.gerrit.httpd.CacheBasedWebSession;
|
||||
import com.google.gerrit.httpd.GitOverHttpModule;
|
||||
import com.google.gerrit.httpd.HttpCanonicalWebUrlProvider;
|
||||
import com.google.gerrit.httpd.RequestContextFilter;
|
||||
import com.google.gerrit.httpd.SignedTokenRestTokenVerifier;
|
||||
import com.google.gerrit.httpd.WebModule;
|
||||
import com.google.gerrit.httpd.WebSshGlueModule;
|
||||
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
|
||||
@ -295,7 +294,6 @@ public class Daemon extends SiteProgram {
|
||||
modules.add(new DefaultCacheFactory.Module());
|
||||
modules.add(new SmtpEmailSender.Module());
|
||||
modules.add(new SignedTokenEmailTokenVerifier.Module());
|
||||
modules.add(new SignedTokenRestTokenVerifier.Module());
|
||||
modules.add(new PluginModule());
|
||||
if (httpd) {
|
||||
modules.add(new CanonicalWebUrlModule() {
|
||||
|
@ -231,7 +231,6 @@ public class WebAppInitializer extends GuiceServletContextListener {
|
||||
modules.add(new DefaultCacheFactory.Module());
|
||||
modules.add(new SmtpEmailSender.Module());
|
||||
modules.add(new SignedTokenEmailTokenVerifier.Module());
|
||||
modules.add(new SignedTokenRestTokenVerifier.Module());
|
||||
modules.add(new PluginModule());
|
||||
modules.add(new CanonicalWebUrlModule() {
|
||||
@Override
|
||||
|
Loading…
Reference in New Issue
Block a user