mirror of
https://github.com/google/nomulus
synced 2026-04-26 03:00:48 +00:00
Add handler for Console API requests and XSRF token creation and verification (#2211)
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
package google.registry.request;
|
||||
|
||||
import com.google.common.net.MediaType;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
@@ -51,4 +52,11 @@ public interface Response {
|
||||
* @see HttpServletResponse#setDateHeader(String, long)
|
||||
*/
|
||||
void setDateHeader(String header, DateTime timestamp);
|
||||
|
||||
/**
|
||||
* Adds a cookie to the response
|
||||
*
|
||||
* @see HttpServletResponse#addCookie(Cookie)
|
||||
*/
|
||||
void addCookie(Cookie cookie);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package google.registry.request;
|
||||
import com.google.common.net.MediaType;
|
||||
import java.io.IOException;
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
@@ -58,4 +59,9 @@ public final class ResponseImpl implements Response {
|
||||
public void setDateHeader(String header, DateTime timestamp) {
|
||||
rsp.setDateHeader(header, timestamp.getMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCookie(Cookie cookie) {
|
||||
rsp.addCookie(cookie);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ import org.joda.time.Duration;
|
||||
/** Helper class for generating and validate XSRF tokens. */
|
||||
public final class XsrfTokenManager {
|
||||
|
||||
/** HTTP header used for transmitting XSRF tokens. */
|
||||
/** HTTP header or cookie name used for transmitting XSRF tokens. */
|
||||
public static final String X_CSRF_TOKEN = "X-CSRF-Token";
|
||||
|
||||
/** POST parameter used for transmitting XSRF tokens. */
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2023 The Nomulus Authors. 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 google.registry.ui.server.console;
|
||||
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
|
||||
import com.google.api.client.http.HttpStatusCodes;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.security.XsrfTokenManager;
|
||||
import google.registry.ui.server.registrar.ConsoleApiParams;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import javax.servlet.http.Cookie;
|
||||
|
||||
/** Base class for handling Console API requests */
|
||||
public abstract class ConsoleApiAction implements Runnable {
|
||||
protected ConsoleApiParams consoleApiParams;
|
||||
|
||||
public ConsoleApiAction(ConsoleApiParams consoleApiParams) {
|
||||
this.consoleApiParams = consoleApiParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void run() {
|
||||
// Shouldn't be even possible because of Auth annotations on the various implementing classes
|
||||
if (!consoleApiParams.authResult().userAuthInfo().get().consoleUser().isPresent()) {
|
||||
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
|
||||
return;
|
||||
}
|
||||
User user = consoleApiParams.authResult().userAuthInfo().get().consoleUser().get();
|
||||
if (consoleApiParams.request().getMethod().equals(GET.toString())) {
|
||||
getHandler(user);
|
||||
} else {
|
||||
if (verifyXSRF()) {
|
||||
postHandler(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void postHandler(User user) {
|
||||
throw new UnsupportedOperationException("Console API POST handler not implemented");
|
||||
}
|
||||
|
||||
protected void getHandler(User user) {
|
||||
throw new UnsupportedOperationException("Console API GET handler not implemented");
|
||||
}
|
||||
|
||||
private boolean verifyXSRF() {
|
||||
Optional<Cookie> maybeCookie =
|
||||
Arrays.stream(consoleApiParams.request().getCookies())
|
||||
.filter(c -> XsrfTokenManager.X_CSRF_TOKEN.equals(c.getName()))
|
||||
.findFirst();
|
||||
if (!maybeCookie.isPresent()
|
||||
|| !consoleApiParams.xsrfTokenManager().validateToken(maybeCookie.get().getValue())) {
|
||||
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -21,12 +21,10 @@ import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.request.auth.AuthResult;
|
||||
import google.registry.request.auth.UserAuthInfo;
|
||||
import google.registry.ui.server.registrar.JsonGetAction;
|
||||
import google.registry.ui.server.registrar.ConsoleApiParams;
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.http.Cookie;
|
||||
import org.json.JSONObject;
|
||||
|
||||
@Action(
|
||||
@@ -34,12 +32,10 @@ import org.json.JSONObject;
|
||||
path = ConsoleUserDataAction.PATH,
|
||||
method = {GET},
|
||||
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
|
||||
public class ConsoleUserDataAction implements JsonGetAction {
|
||||
public class ConsoleUserDataAction extends ConsoleApiAction {
|
||||
|
||||
public static final String PATH = "/console-api/userdata";
|
||||
|
||||
private final AuthResult authResult;
|
||||
private final Response response;
|
||||
private final String productName;
|
||||
private final String supportPhoneNumber;
|
||||
private final String supportEmail;
|
||||
@@ -47,14 +43,12 @@ public class ConsoleUserDataAction implements JsonGetAction {
|
||||
|
||||
@Inject
|
||||
public ConsoleUserDataAction(
|
||||
AuthResult authResult,
|
||||
Response response,
|
||||
ConsoleApiParams consoleApiParams,
|
||||
@Config("productName") String productName,
|
||||
@Config("supportEmail") String supportEmail,
|
||||
@Config("supportPhoneNumber") String supportPhoneNumber,
|
||||
@Config("technicalDocsUrl") String technicalDocsUrl) {
|
||||
this.response = response;
|
||||
this.authResult = authResult;
|
||||
super(consoleApiParams);
|
||||
this.productName = productName;
|
||||
this.supportEmail = supportEmail;
|
||||
this.supportPhoneNumber = supportPhoneNumber;
|
||||
@@ -62,13 +56,15 @@ public class ConsoleUserDataAction implements JsonGetAction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
UserAuthInfo authInfo = authResult.userAuthInfo().get();
|
||||
if (!authInfo.consoleUser().isPresent()) {
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
|
||||
return;
|
||||
}
|
||||
User user = authInfo.consoleUser().get();
|
||||
protected void getHandler(User user) {
|
||||
// As this is a first GET request we use it as an opportunity to set a XSRF cookie
|
||||
// for angular to read - https://angular.io/guide/http-security-xsrf-protection
|
||||
Cookie xsrfCookie =
|
||||
new Cookie(
|
||||
consoleApiParams.xsrfTokenManager().X_CSRF_TOKEN,
|
||||
consoleApiParams.xsrfTokenManager().generateToken(user.getEmailAddress()));
|
||||
xsrfCookie.setSecure(true);
|
||||
consoleApiParams.response().addCookie(xsrfCookie);
|
||||
|
||||
JSONObject json =
|
||||
new JSONObject(
|
||||
@@ -90,7 +86,7 @@ public class ConsoleUserDataAction implements JsonGetAction {
|
||||
// Is used by UI to construct a link to registry resources
|
||||
"technicalDocsUrl", technicalDocsUrl));
|
||||
|
||||
response.setPayload(json.toString());
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_OK);
|
||||
consoleApiParams.response().setPayload(json.toString());
|
||||
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_OK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2023 The Nomulus Authors. 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 google.registry.ui.server.registrar;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.AuthResult;
|
||||
import google.registry.security.XsrfTokenManager;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/** Groups necessary dependencies for Console API actions * */
|
||||
@AutoValue
|
||||
public abstract class ConsoleApiParams {
|
||||
public static ConsoleApiParams create(
|
||||
HttpServletRequest request,
|
||||
Response response,
|
||||
AuthResult authResult,
|
||||
XsrfTokenManager xsrfTokenManager) {
|
||||
return new AutoValue_ConsoleApiParams(request, response, authResult, xsrfTokenManager);
|
||||
}
|
||||
|
||||
public abstract HttpServletRequest request();
|
||||
|
||||
public abstract Response response();
|
||||
|
||||
public abstract AuthResult authResult();
|
||||
|
||||
public abstract XsrfTokenManager xsrfTokenManager();
|
||||
}
|
||||
@@ -28,6 +28,10 @@ import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.request.OptionalJsonPayload;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.RequestScope;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.AuthResult;
|
||||
import google.registry.security.XsrfTokenManager;
|
||||
import java.util.Optional;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.joda.time.DateTime;
|
||||
@@ -35,9 +39,18 @@ import org.joda.time.DateTime;
|
||||
/** Dagger module for the Registrar Console parameters. */
|
||||
@Module
|
||||
public final class RegistrarConsoleModule {
|
||||
|
||||
static final String PARAM_CLIENT_ID = "clientId";
|
||||
|
||||
@Provides
|
||||
@RequestScope
|
||||
ConsoleApiParams provideConsoleApiParams(
|
||||
HttpServletRequest request,
|
||||
Response response,
|
||||
AuthResult authResult,
|
||||
XsrfTokenManager xsrfTokenManager) {
|
||||
return ConsoleApiParams.create(request, response, authResult, xsrfTokenManager);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter(PARAM_CLIENT_ID)
|
||||
static Optional<String> provideOptionalClientId(HttpServletRequest req) {
|
||||
|
||||
Reference in New Issue
Block a user