mirror of
https://github.com/google/nomulus.git
synced 2025-05-14 08:27:14 +02:00
Allow XSRF to be sent as POST parameter in addition to HTML header
HTML headers can only be sent via JS, we need this change to allow secure POST form submission. The form itself will have a hidden "input" tag with the XSRF token in it. This is how other framework do it as well - see https://en.wikipedia.org/wiki/Cross-site_request_forgery#Synchronizer_token_pattern This is in preparation for the OT&E setup page, which will be a simple form with a "submit" button, so using JS for it is overkill. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=226178070
This commit is contained in:
parent
7c9b2172fd
commit
da5a8796b8
3 changed files with 202 additions and 2 deletions
|
@ -0,0 +1,173 @@
|
|||
// Copyright 2018 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.request.auth;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.atLeast;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.appengine.api.users.User;
|
||||
import com.google.appengine.api.users.UserService;
|
||||
import google.registry.security.XsrfTokenManager;
|
||||
import google.registry.testing.AppEngineRule;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.MockitoJUnitRule;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import org.mockito.Mock;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public final class LegacyAuthenticationMechanismTest {
|
||||
|
||||
|
||||
@Rule
|
||||
public final AppEngineRule appEngine = AppEngineRule.builder()
|
||||
.withDatastore()
|
||||
.build();
|
||||
|
||||
@Rule public final MockitoJUnitRule mocks = MockitoJUnitRule.create();
|
||||
|
||||
@Mock private UserService userService;
|
||||
@Mock private HttpServletRequest req;
|
||||
|
||||
private final FakeClock clock = new FakeClock();
|
||||
private XsrfTokenManager xsrfTokenManager;
|
||||
private LegacyAuthenticationMechanism legacyAuthenticationMechanism;
|
||||
private String goodToken;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
xsrfTokenManager = new XsrfTokenManager(clock, userService);
|
||||
legacyAuthenticationMechanism =
|
||||
new LegacyAuthenticationMechanism(userService, xsrfTokenManager);
|
||||
when(userService.getCurrentUser()).thenReturn(new User("email@example.com", "example.com"));
|
||||
when(userService.isUserAdmin()).thenReturn(false);
|
||||
goodToken = xsrfTokenManager.generateToken("email@example.com");
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
// Make sure we didn't use getParameter or getInputStream or any of the other "with side
|
||||
// effects" getters unexpectedly. But allow "no side effect" getters.
|
||||
//
|
||||
// Unfortunately HttpServletRequest doesn't document well which getters "have side effects". It
|
||||
// does explicitly state getReader and getInputStream, and that getParameter can also interfere
|
||||
// with them, but it doesn't say anything about getParameterNames, getParameterValues,
|
||||
// getParameterMap - even though I'm pretty sure they are similar to getParameter in that
|
||||
// effect.
|
||||
//
|
||||
// Feel free to add other "no side effect" functions with atLeast(0) to exempt them from the
|
||||
// verifyNoMoreInteractions
|
||||
verify(req, atLeast(0)).getMethod();
|
||||
verify(req, atLeast(0)).getHeader(any());
|
||||
verifyNoMoreInteractions(req);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticate_notLoggedIn() {
|
||||
when(userService.isUserLoggedIn()).thenReturn(false);
|
||||
assertThat(legacyAuthenticationMechanism.authenticate(req).authLevel())
|
||||
.isEqualTo(AuthLevel.NONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticate_loggedInSafeMethod_get() {
|
||||
when(userService.isUserLoggedIn()).thenReturn(true);
|
||||
when(req.getMethod()).thenReturn("GET");
|
||||
assertThat(legacyAuthenticationMechanism.authenticate(req).authLevel())
|
||||
.isEqualTo(AuthLevel.USER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticate_loggedInSafeMethod_head() {
|
||||
when(userService.isUserLoggedIn()).thenReturn(true);
|
||||
when(req.getMethod()).thenReturn("HEAD");
|
||||
assertThat(legacyAuthenticationMechanism.authenticate(req).authLevel())
|
||||
.isEqualTo(AuthLevel.USER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticate_loggedInUnsafeMethod_post_noXsrfToken() {
|
||||
when(userService.isUserLoggedIn()).thenReturn(true);
|
||||
when(req.getMethod()).thenReturn("POST");
|
||||
assertThat(legacyAuthenticationMechanism.authenticate(req).authLevel())
|
||||
.isEqualTo(AuthLevel.NONE);
|
||||
|
||||
// Make sure we looked for the token in all relevant places before giving up
|
||||
verify(req).getHeader("X-CSRF-Token");
|
||||
verify(req).getParameter("xsrfToken");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticate_loggedInUnsafeMethod_post_goodTokenInHeader() {
|
||||
when(userService.isUserLoggedIn()).thenReturn(true);
|
||||
when(req.getMethod()).thenReturn("POST");
|
||||
when(req.getHeader("X-CSRF-Token")).thenReturn(goodToken);
|
||||
assertThat(legacyAuthenticationMechanism.authenticate(req).authLevel())
|
||||
.isEqualTo(AuthLevel.USER);
|
||||
|
||||
// Make sure we didn't call getParameter (we already verify it in the @After, but we're doing it
|
||||
// here explicitly as well for clarity, since this is important in this test)
|
||||
verify(req, times(0)).getParameter(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticate_loggedInUnsafeMethod_post_badTokenInHeader() {
|
||||
when(userService.isUserLoggedIn()).thenReturn(true);
|
||||
when(req.getMethod()).thenReturn("POST");
|
||||
when(req.getHeader("X-CSRF-Token")).thenReturn("bad");
|
||||
assertThat(legacyAuthenticationMechanism.authenticate(req).authLevel())
|
||||
.isEqualTo(AuthLevel.NONE);
|
||||
|
||||
// Make sure we didn't call getParameter (we already verify it in the @After, but we're doing it
|
||||
// here explicitly as well for clarity, since this is important in this test)
|
||||
verify(req, times(0)).getParameter(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticate_loggedInUnsafeMethod_post_goodTokenInParam() {
|
||||
when(userService.isUserLoggedIn()).thenReturn(true);
|
||||
when(req.getMethod()).thenReturn("POST");
|
||||
when(req.getParameter("xsrfToken")).thenReturn(goodToken);
|
||||
assertThat(legacyAuthenticationMechanism.authenticate(req).authLevel())
|
||||
.isEqualTo(AuthLevel.USER);
|
||||
|
||||
// we allow getParameter to be called in this case (we verify it so it's not caught in the
|
||||
// @After's verifyNoMoreInteractions)
|
||||
verify(req).getParameter("xsrfToken");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticate_loggedInUnsafeMethod_post_badTokenInParam() {
|
||||
when(userService.isUserLoggedIn()).thenReturn(true);
|
||||
when(req.getMethod()).thenReturn("POST");
|
||||
when(req.getParameter("xsrfToken")).thenReturn("bad");
|
||||
assertThat(legacyAuthenticationMechanism.authenticate(req).authLevel())
|
||||
.isEqualTo(AuthLevel.NONE);
|
||||
|
||||
// we allow getParameter to be called in this case (we verify it so it's not caught in the
|
||||
// @After's verifyNoMoreInteractions)
|
||||
verify(req).getParameter("xsrfToken");
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue