Skip to content

Commit

Permalink
Remove XsrfTokenManager fallback for validating legacy tokens
Browse files Browse the repository at this point in the history
This is the third step of migrating to the new format: removing support
for the legacy format.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=150512556
  • Loading branch information
nfelt authored and CydeWeys committed Mar 21, 2017
1 parent 09f619c commit 232132e
Show file tree
Hide file tree
Showing 2 changed files with 18 additions and 54 deletions.
65 changes: 12 additions & 53 deletions java/google/registry/security/XsrfTokenManager.java
Expand Up @@ -14,7 +14,6 @@

package google.registry.security;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.io.BaseEncoding.base64Url;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
Expand All @@ -23,7 +22,6 @@
import com.google.appengine.api.users.UserService;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.google.common.hash.Hashing;
import google.registry.model.server.ServerSecret;
import google.registry.util.Clock;
Expand All @@ -45,9 +43,6 @@ public final class XsrfTokenManager {
/** Token version identifier for version 1. */
private static final String VERSION_1 = "1";

/** Legacy scope values that will be supported during the scope removal process. */
private static final ImmutableSet<String> LEGACY_SCOPES = ImmutableSet.of("admin", "console");

private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();

private final Clock clock;
Expand Down Expand Up @@ -82,36 +77,18 @@ private static String encodeToken(byte[] secret, String email, long timestampMil
return Joiner.on(':').join(VERSION_1, timestampMillis, hmac);
}

/**
* Computes the hash payload portion of a legacy-style XSRF token.
*
* <p>The result is a Base64-encoded SHA-256 hash of a string containing the secret, email, scope
* and creation time, separated by tabs.
*/
private static String computeLegacyHash(long creationTime, String scope, String userEmail) {
checkArgument(LEGACY_SCOPES.contains(scope), "Invalid scope value: %s", scope);
String token =
Joiner.on('\t').join(ServerSecret.get().asUuid(), userEmail, scope, creationTime);
return base64Url().encode(Hashing.sha256()
.newHasher(token.length())
.putString(token, UTF_8)
.hash()
.asBytes());
}

/**
* Validates an XSRF token against the current logged-in user.
*
* This accepts both legacy-style and new-style XSRF tokens. For legacy-style tokens, it will
* accept tokens generated with any scope from {@link #LEGACY_SCOPES}.
*/
/** Validates an XSRF token against the current logged-in user. */
public boolean validateToken(String token) {
checkArgumentNotNull(token);
List<String> tokenParts = Splitter.on(':').splitToList(token);
if (tokenParts.size() < 2) {
if (tokenParts.size() != 3) {
logger.warningfmt("Malformed XSRF token: %s", token);
return false;
}
if (!tokenParts.get(0).equals(VERSION_1)) {
logger.warningfmt("Unrecognized version in XSRF token: %s", token);
return false;
}
String timePart = tokenParts.get(1);
long timestampMillis;
try {
Expand All @@ -127,32 +104,14 @@ public boolean validateToken(String token) {
String currentUserEmail =
userService.isUserLoggedIn() ? userService.getCurrentUser().getEmail() : "";

// Reconstruct the token to verify validity, using version 1 format if detected.
if (tokenParts.get(0).equals(VERSION_1)) {
String reconstructedToken =
encodeToken(ServerSecret.get().asBytes(), currentUserEmail, timestampMillis);
if (!token.equals(reconstructedToken)) {
logger.warningfmt(
"Reconstructed XSRF mismatch (got != expected): %s != %s", token, reconstructedToken);
return false;
}
return true;
} 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.
String hash = tokenParts.get(0);
ImmutableSet.Builder<String> reconstructedTokenCandidates = new ImmutableSet.Builder<>();
for (String scope : LEGACY_SCOPES) {
String reconstructedHash = computeLegacyHash(timestampMillis, scope, currentUserEmail);
reconstructedTokenCandidates.add(reconstructedHash);
if (hash.equals(reconstructedHash)) {
return true;
}
}
// Reconstruct the token to verify validity.
String reconstructedToken =
encodeToken(ServerSecret.get().asBytes(), currentUserEmail, timestampMillis);
if (!token.equals(reconstructedToken)) {
logger.warningfmt(
"Reconstructed XSRF mismatch: %s matches none of %s",
hash, reconstructedTokenCandidates.build());
"Reconstructed XSRF mismatch (got != expected): %s != %s", token, reconstructedToken);
return false;
}
return true;
}
}
7 changes: 6 additions & 1 deletion javatests/google/registry/security/XsrfTokenManagerTest.java
Expand Up @@ -62,7 +62,12 @@ public void testValidate_validToken() {

@Test
public void testValidate_tokenWithMissingParts() {
assertThat(xsrfTokenManager.validateToken("foo")).isFalse();
assertThat(xsrfTokenManager.validateToken("1:123")).isFalse();
}

@Test
public void testValidate_tokenWithBadVersion() {
assertThat(xsrfTokenManager.validateToken("2:123:base64")).isFalse();
}

@Test
Expand Down

0 comments on commit 232132e

Please sign in to comment.