mirror of
https://github.com/google/nomulus.git
synced 2025-05-14 00:17:20 +02:00
Cut over to generating new HMAC-based XSRF tokens
This is the second step of migrating to our new XSRF token format. The first step ([] made validate() start accepting new tokens (basically, dual-read). This step cuts over our "writing" to write the new token format. The third and final step will drop support for validating the old token format (back to single-read). We'll do that in a subsequent push so that we don't invalidate all the current XSRF tokens that people might have in their browsers. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=149790648
This commit is contained in:
parent
ebcdae7361
commit
2353bcd8c5
7 changed files with 14 additions and 90 deletions
|
@ -183,7 +183,7 @@ public class LoadTestAction implements Runnable {
|
||||||
xmlHostCreateTmpl = loadXml("host_create");
|
xmlHostCreateTmpl = loadXml("host_create");
|
||||||
xmlHostCreateFail = xmlHostCreateTmpl.replace("%host%", EXISTING_HOST);
|
xmlHostCreateFail = xmlHostCreateTmpl.replace("%host%", EXISTING_HOST);
|
||||||
xmlHostInfo = loadXml("host_info").replace("%host%", EXISTING_HOST);
|
xmlHostInfo = loadXml("host_info").replace("%host%", EXISTING_HOST);
|
||||||
xsrfToken = xsrfTokenManager.generateLegacyToken("admin", "");
|
xsrfToken = xsrfTokenManager.generateToken("");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -99,25 +99,6 @@ public final class XsrfTokenManager {
|
||||||
.asBytes());
|
.asBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a legacy-style XSRF token for a given scope and user.
|
|
||||||
*
|
|
||||||
* <p>If there is no user (email is an empty string), the entire xsrf check becomes basically a
|
|
||||||
* no-op, but that's ok because any callback that doesn't have a user shouldn't be able to access
|
|
||||||
* any per-user resources anyways.
|
|
||||||
*
|
|
||||||
* <p>The scope is passed to {@link #computeLegacyHash}. Use of a scope in xsrf tokens is
|
|
||||||
* deprecated; instead, use {@link #generateToken}.
|
|
||||||
*/
|
|
||||||
// TODO(b/35388772): remove this in favor of generateToken()
|
|
||||||
@Deprecated
|
|
||||||
public String generateLegacyToken(String scope, String email) {
|
|
||||||
checkArgumentNotNull(scope);
|
|
||||||
checkArgumentNotNull(email);
|
|
||||||
long now = clock.nowUtc().getMillis();
|
|
||||||
return Joiner.on(':').join(computeLegacyHash(now, scope, email), now);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates an XSRF token against the current logged-in user.
|
* Validates an XSRF token against the current logged-in user.
|
||||||
*
|
*
|
||||||
|
@ -157,6 +138,7 @@ public final class XsrfTokenManager {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
// TODO(b/35388772): remove this fallback once we no longer generate legacy tokens.
|
||||||
// Fall back to the legacy format, and try the few possible scopes.
|
// Fall back to the legacy format, and try the few possible scopes.
|
||||||
String hash = tokenParts.get(0);
|
String hash = tokenParts.get(0);
|
||||||
ImmutableSet.Builder<String> reconstructedTokenCandidates = new ImmutableSet.Builder<>();
|
ImmutableSet.Builder<String> reconstructedTokenCandidates = new ImmutableSet.Builder<>();
|
||||||
|
|
|
@ -66,7 +66,7 @@ class AppEngineConnection implements Connection {
|
||||||
memoize(new Supplier<String>() {
|
memoize(new Supplier<String>() {
|
||||||
@Override
|
@Override
|
||||||
public String get() {
|
public String get() {
|
||||||
return xsrfTokenManager.generateLegacyToken("admin", getUserId());
|
return xsrfTokenManager.generateToken(getUserId());
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -27,7 +27,6 @@ import com.google.template.soy.data.SoyMapData;
|
||||||
import com.google.template.soy.shared.SoyCssRenamingMap;
|
import com.google.template.soy.shared.SoyCssRenamingMap;
|
||||||
import com.google.template.soy.tofu.SoyTofu;
|
import com.google.template.soy.tofu.SoyTofu;
|
||||||
import google.registry.config.RegistryConfig.Config;
|
import google.registry.config.RegistryConfig.Config;
|
||||||
import google.registry.flows.EppConsoleAction;
|
|
||||||
import google.registry.model.registrar.Registrar;
|
import google.registry.model.registrar.Registrar;
|
||||||
import google.registry.request.Action;
|
import google.registry.request.Action;
|
||||||
import google.registry.request.Response;
|
import google.registry.request.Response;
|
||||||
|
@ -119,8 +118,7 @@ public final class ConsoleUiAction implements Runnable {
|
||||||
Registrar registrar = Registrar.loadByClientId(sessionUtils.getRegistrarClientId(req));
|
Registrar registrar = Registrar.loadByClientId(sessionUtils.getRegistrarClientId(req));
|
||||||
data.put(
|
data.put(
|
||||||
"xsrfToken",
|
"xsrfToken",
|
||||||
xsrfTokenManager.generateLegacyToken(
|
xsrfTokenManager.generateToken(userService.getCurrentUser().getEmail()));
|
||||||
EppConsoleAction.XSRF_SCOPE, userService.getCurrentUser().getEmail()));
|
|
||||||
data.put("clientId", registrar.getClientId());
|
data.put("clientId", registrar.getClientId());
|
||||||
data.put("showPaymentLink", registrar.getBillingMethod() == Registrar.BillingMethod.BRAINTREE);
|
data.put("showPaymentLink", registrar.getBillingMethod() == Registrar.BillingMethod.BRAINTREE);
|
||||||
|
|
||||||
|
|
|
@ -403,7 +403,7 @@ public final class RequestHandlerTest {
|
||||||
userService.setUser(testUser, false);
|
userService.setUser(testUser, false);
|
||||||
when(req.getMethod()).thenReturn("POST");
|
when(req.getMethod()).thenReturn("POST");
|
||||||
when(req.getHeader("X-CSRF-Token"))
|
when(req.getHeader("X-CSRF-Token"))
|
||||||
.thenReturn(xsrfTokenManager.generateLegacyToken("admin", testUser.getEmail()));
|
.thenReturn(xsrfTokenManager.generateToken(testUser.getEmail()));
|
||||||
when(req.getRequestURI()).thenReturn("/safe-sloth");
|
when(req.getRequestURI()).thenReturn("/safe-sloth");
|
||||||
handler.handleRequest(req, rsp);
|
handler.handleRequest(req, rsp);
|
||||||
verify(safeSlothTask).run();
|
verify(safeSlothTask).run();
|
||||||
|
@ -414,7 +414,7 @@ public final class RequestHandlerTest {
|
||||||
userService.setUser(testUser, false);
|
userService.setUser(testUser, false);
|
||||||
when(req.getMethod()).thenReturn("POST");
|
when(req.getMethod()).thenReturn("POST");
|
||||||
when(req.getHeader("X-CSRF-Token"))
|
when(req.getHeader("X-CSRF-Token"))
|
||||||
.thenReturn(xsrfTokenManager.generateLegacyToken("admin", "wrong@example.com"));
|
.thenReturn(xsrfTokenManager.generateToken("wrong@example.com"));
|
||||||
when(req.getRequestURI()).thenReturn("/safe-sloth");
|
when(req.getRequestURI()).thenReturn("/safe-sloth");
|
||||||
handler.handleRequest(req, rsp);
|
handler.handleRequest(req, rsp);
|
||||||
verify(rsp).sendError(403, "Invalid X-CSRF-Token");
|
verify(rsp).sendError(403, "Invalid X-CSRF-Token");
|
||||||
|
|
|
@ -220,7 +220,7 @@ public class RequestAuthenticatorTest {
|
||||||
public void testAnyUserAnyMethod_success() throws Exception {
|
public void testAnyUserAnyMethod_success() throws Exception {
|
||||||
fakeUserService.setUser(testUser, false /* isAdmin */);
|
fakeUserService.setUser(testUser, false /* isAdmin */);
|
||||||
when(req.getHeader(XsrfTokenManager.X_CSRF_TOKEN))
|
when(req.getHeader(XsrfTokenManager.X_CSRF_TOKEN))
|
||||||
.thenReturn(xsrfTokenManager.generateLegacyToken("console", testUser.getEmail()));
|
.thenReturn(xsrfTokenManager.generateToken(testUser.getEmail()));
|
||||||
|
|
||||||
Optional<AuthResult> authResult = runTest(fakeUserService, AuthAnyUserAnyMethod.class);
|
Optional<AuthResult> authResult = runTest(fakeUserService, AuthAnyUserAnyMethod.class);
|
||||||
|
|
||||||
|
@ -275,7 +275,7 @@ public class RequestAuthenticatorTest {
|
||||||
public void testAdminUserAnyMethod_success() throws Exception {
|
public void testAdminUserAnyMethod_success() throws Exception {
|
||||||
fakeUserService.setUser(testUser, true /* isAdmin */);
|
fakeUserService.setUser(testUser, true /* isAdmin */);
|
||||||
when(req.getHeader(XsrfTokenManager.X_CSRF_TOKEN))
|
when(req.getHeader(XsrfTokenManager.X_CSRF_TOKEN))
|
||||||
.thenReturn(xsrfTokenManager.generateLegacyToken("console", testUser.getEmail()));
|
.thenReturn(xsrfTokenManager.generateToken(testUser.getEmail()));
|
||||||
|
|
||||||
Optional<AuthResult> authResult = runTest(fakeUserService, AuthAdminUserAnyMethod.class);
|
Optional<AuthResult> authResult = runTest(fakeUserService, AuthAdminUserAnyMethod.class);
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ package google.registry.security;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
import com.google.appengine.api.users.User;
|
import com.google.appengine.api.users.User;
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.base.Splitter;
|
||||||
|
@ -49,52 +48,30 @@ public class XsrfTokenManagerTest {
|
||||||
private final XsrfTokenManager xsrfTokenManager = new XsrfTokenManager(clock, userService);
|
private final XsrfTokenManager xsrfTokenManager = new XsrfTokenManager(clock, userService);
|
||||||
|
|
||||||
private String token;
|
private String token;
|
||||||
private String legacyToken;
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void init() {
|
public void init() {
|
||||||
userService.setUser(testUser, false);
|
userService.setUser(testUser, false);
|
||||||
token = xsrfTokenManager.generateToken(testUser.getEmail());
|
token = xsrfTokenManager.generateToken(testUser.getEmail());
|
||||||
legacyToken = xsrfTokenManager.generateLegacyToken("console", testUser.getEmail());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGenerateLegacyToken_invalidScope() {
|
public void testValidate_validToken() {
|
||||||
try {
|
|
||||||
xsrfTokenManager.generateLegacyToken("foo", testUser.getEmail());
|
|
||||||
fail("Expected IllegalArgumentException");
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
assertThat(e).hasMessageThat().isEqualTo("Invalid scope value: foo");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testValidate_token() {
|
|
||||||
assertThat(xsrfTokenManager.validateToken(token)).isTrue();
|
assertThat(xsrfTokenManager.validateToken(token)).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testValidate_legacyToken() {
|
public void testValidate_tokenWithMissingParts() {
|
||||||
assertThat(xsrfTokenManager.validateToken(legacyToken)).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testValidate_token_missingParts() {
|
|
||||||
assertThat(xsrfTokenManager.validateToken("foo")).isFalse();
|
assertThat(xsrfTokenManager.validateToken("foo")).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testValidate_token_badNumberTimestamp() {
|
public void testValidate_tokenWithBadNumberTimestamp() {
|
||||||
assertThat(xsrfTokenManager.validateToken("1:notanumber:base64")).isFalse();
|
assertThat(xsrfTokenManager.validateToken("1:notanumber:base64")).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testValidate_legacyToken_badNumberTimestamp() {
|
public void testValidate_tokenExpiresAfterOneDay() {
|
||||||
assertThat(xsrfTokenManager.validateToken("base64:notanumber")).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testValidate_token_expiresAfterOneDay() {
|
|
||||||
clock.advanceBy(Duration.standardDays(1));
|
clock.advanceBy(Duration.standardDays(1));
|
||||||
assertThat(xsrfTokenManager.validateToken(token)).isTrue();
|
assertThat(xsrfTokenManager.validateToken(token)).isTrue();
|
||||||
clock.advanceOneMilli();
|
clock.advanceOneMilli();
|
||||||
|
@ -102,48 +79,15 @@ public class XsrfTokenManagerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testValidate_legacyToken_expiresAfterOneDay() {
|
public void testValidate_tokenTimestampTamperedWith() {
|
||||||
clock.advanceBy(Duration.standardDays(1));
|
|
||||||
assertThat(xsrfTokenManager.validateToken(legacyToken)).isTrue();
|
|
||||||
clock.advanceOneMilli();
|
|
||||||
assertThat(xsrfTokenManager.validateToken(legacyToken)).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testValidate_token_timestampTamperedWith() {
|
|
||||||
String encodedPart = Splitter.on(':').splitToList(token).get(2);
|
String encodedPart = Splitter.on(':').splitToList(token).get(2);
|
||||||
long fakeTimestamp = clock.nowUtc().plusMillis(1).getMillis();
|
long fakeTimestamp = clock.nowUtc().plusMillis(1).getMillis();
|
||||||
assertThat(xsrfTokenManager.validateToken("1:" + fakeTimestamp + ":" + encodedPart)).isFalse();
|
assertThat(xsrfTokenManager.validateToken("1:" + fakeTimestamp + ":" + encodedPart)).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testValidate_legacyToken_timestampTamperedWith() {
|
public void testValidate_tokenForDifferentUser() {
|
||||||
String encodedPart = Splitter.on(':').splitToList(legacyToken).get(0);
|
|
||||||
long tamperedTimestamp = clock.nowUtc().plusMillis(1).getMillis();
|
|
||||||
assertThat(xsrfTokenManager.validateToken(encodedPart + ":" + tamperedTimestamp)).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testValidate_token_differentUser() {
|
|
||||||
String otherToken = xsrfTokenManager.generateToken("eve@example.com");
|
String otherToken = xsrfTokenManager.generateToken("eve@example.com");
|
||||||
assertThat(xsrfTokenManager.validateToken(otherToken)).isFalse();
|
assertThat(xsrfTokenManager.validateToken(otherToken)).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testValidate_legacyToken_differentUser() {
|
|
||||||
String otherToken = xsrfTokenManager.generateLegacyToken("console", "eve@example.com");
|
|
||||||
assertThat(xsrfTokenManager.validateToken(otherToken)).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testValidate_legacyToken_adminScope() {
|
|
||||||
String adminToken = xsrfTokenManager.generateLegacyToken("admin", testUser.getEmail());
|
|
||||||
assertThat(xsrfTokenManager.validateToken(adminToken)).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testValidate_legacyToken_consoleScope() {
|
|
||||||
String consoleToken = xsrfTokenManager.generateLegacyToken("console", testUser.getEmail());
|
|
||||||
assertThat(xsrfTokenManager.validateToken(consoleToken)).isTrue();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue