mirror of
https://github.com/google/nomulus
synced 2025-12-23 14:25:44 +00:00
Save session data directly in a cookie (#2732)
This commit is contained in:
@@ -0,0 +1,155 @@
|
|||||||
|
// Copyright 2025 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.flows;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.flogger.FluentLogger;
|
||||||
|
import com.google.common.io.BaseEncoding;
|
||||||
|
import google.registry.request.Response;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A metadata class that saves the data directly in cookies.
|
||||||
|
*
|
||||||
|
* <p>Unlike {@link HttpSessionMetadata}, this class does not rely on a session manager to translate
|
||||||
|
* an opaque session cookie into the metadata. This means that the locality of the session manager
|
||||||
|
* is irrelevant and as long as the client (the proxy) respects the {@code Set-Cookie} headers and
|
||||||
|
* sets the respective cookies in subsequent requests in a session, the metadata will be available
|
||||||
|
* to all servers, not just the one that created the session.
|
||||||
|
*
|
||||||
|
* <p>The string representation of the metadata is saved in Base64 URL-safe format in a cookie named
|
||||||
|
* {@code SESSION_INFO}.
|
||||||
|
*/
|
||||||
|
public class CookieSessionMetadata extends SessionMetadata {
|
||||||
|
|
||||||
|
protected static final String COOKIE_NAME = "SESSION_INFO";
|
||||||
|
protected static final String REGISTRAR_ID = "clientId";
|
||||||
|
protected static final String SERVICE_EXTENSIONS = "serviceExtensionUris";
|
||||||
|
protected static final String FAILED_LOGIN_ATTEMPTS = "failedLoginAttempts";
|
||||||
|
|
||||||
|
private static final Pattern COOKIE_PATTERN = Pattern.compile("SESSION_INFO=([^;\\s]+)?");
|
||||||
|
private static final Pattern REGISTRAR_ID_PATTERN = Pattern.compile("clientId=([^,\\s]+)?");
|
||||||
|
private static final Pattern SERVICE_EXTENSIONS_PATTERN =
|
||||||
|
Pattern.compile("serviceExtensionUris=([^,\\s}]+)?");
|
||||||
|
private static final Pattern FAILED_LOGIN_ATTEMPTS_PATTERN =
|
||||||
|
Pattern.compile("failedLoginAttempts=([^,\\s]+)?");
|
||||||
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
|
|
||||||
|
private final Map<String, String> data = new HashMap<>();
|
||||||
|
|
||||||
|
public CookieSessionMetadata(HttpServletRequest request) {
|
||||||
|
Optional.ofNullable(request.getHeader("Cookie"))
|
||||||
|
.ifPresent(
|
||||||
|
cookie -> {
|
||||||
|
Matcher matcher = COOKIE_PATTERN.matcher(cookie);
|
||||||
|
if (matcher.find()) {
|
||||||
|
String sessionInfo = decode(matcher.group(1));
|
||||||
|
logger.atInfo().log("SESSION INFO: %s", sessionInfo);
|
||||||
|
matcher = REGISTRAR_ID_PATTERN.matcher(sessionInfo);
|
||||||
|
if (matcher.find()) {
|
||||||
|
String registrarId = matcher.group(1);
|
||||||
|
if (!registrarId.equals("null")) {
|
||||||
|
data.put(REGISTRAR_ID, registrarId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matcher = SERVICE_EXTENSIONS_PATTERN.matcher(sessionInfo);
|
||||||
|
if (matcher.find()) {
|
||||||
|
String serviceExtensions = matcher.group(1);
|
||||||
|
if (serviceExtensions != null) {
|
||||||
|
data.put(SERVICE_EXTENSIONS, serviceExtensions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matcher = FAILED_LOGIN_ATTEMPTS_PATTERN.matcher(sessionInfo);
|
||||||
|
if (matcher.find()) {
|
||||||
|
String failedLoginAttempts = matcher.group(1);
|
||||||
|
data.put(FAILED_LOGIN_ATTEMPTS, failedLoginAttempts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidate() {
|
||||||
|
data.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRegistrarId() {
|
||||||
|
return data.getOrDefault(REGISTRAR_ID, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getServiceExtensionUris() {
|
||||||
|
return Optional.ofNullable(data.getOrDefault(SERVICE_EXTENSIONS, null))
|
||||||
|
.map(s -> Splitter.on('.').splitToList(s))
|
||||||
|
.map(ImmutableSet::copyOf)
|
||||||
|
.orElse(ImmutableSet.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFailedLoginAttempts() {
|
||||||
|
return Optional.ofNullable(data.getOrDefault(FAILED_LOGIN_ATTEMPTS, null))
|
||||||
|
.map(Integer::parseInt)
|
||||||
|
.orElse(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegistrarId(String registrarId) {
|
||||||
|
data.put(REGISTRAR_ID, registrarId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setServiceExtensionUris(Set<String> serviceExtensionUris) {
|
||||||
|
if (serviceExtensionUris == null || serviceExtensionUris.isEmpty()) {
|
||||||
|
data.remove(SERVICE_EXTENSIONS);
|
||||||
|
} else {
|
||||||
|
data.put(SERVICE_EXTENSIONS, Joiner.on('.').join(serviceExtensionUris));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void incrementFailedLoginAttempts() {
|
||||||
|
data.put(FAILED_LOGIN_ATTEMPTS, String.valueOf(getFailedLoginAttempts() + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetFailedLoginAttempts() {
|
||||||
|
data.remove(FAILED_LOGIN_ATTEMPTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(Response response) {
|
||||||
|
String value = encode(toString());
|
||||||
|
response.setHeader("Set-Cookie", COOKIE_NAME + "=" + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String encode(String plainText) {
|
||||||
|
return BaseEncoding.base64Url().encode(plainText.getBytes(US_ASCII));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String decode(String cipherText) {
|
||||||
|
return new String(BaseEncoding.base64Url().decode(cipherText), US_ASCII);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -78,6 +78,8 @@ public class EppRequestHandler {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.atWarning().withCause(e).log("handleEppCommand general exception.");
|
logger.atWarning().withCause(e).log("handleEppCommand general exception.");
|
||||||
response.setStatus(SC_BAD_REQUEST);
|
response.setStatus(SC_BAD_REQUEST);
|
||||||
|
} finally {
|
||||||
|
sessionMetadata.save(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import google.registry.request.Action.Method;
|
|||||||
import google.registry.request.Payload;
|
import google.registry.request.Payload;
|
||||||
import google.registry.request.auth.Auth;
|
import google.registry.request.auth.Auth;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Establishes a transport for EPP+TLS over HTTP. All commands and responses are EPP XML according
|
* Establishes a transport for EPP+TLS over HTTP. All commands and responses are EPP XML according
|
||||||
@@ -35,18 +35,18 @@ public class EppTlsAction implements Runnable {
|
|||||||
|
|
||||||
@Inject @Payload byte[] inputXmlBytes;
|
@Inject @Payload byte[] inputXmlBytes;
|
||||||
@Inject TlsCredentials tlsCredentials;
|
@Inject TlsCredentials tlsCredentials;
|
||||||
@Inject HttpSession session;
|
@Inject HttpServletRequest request;
|
||||||
@Inject EppRequestHandler eppRequestHandler;
|
@Inject EppRequestHandler eppRequestHandler;
|
||||||
@Inject EppTlsAction() {}
|
@Inject EppTlsAction() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
eppRequestHandler.executeEpp(
|
eppRequestHandler.executeEpp(
|
||||||
new HttpSessionMetadata(session),
|
new CookieSessionMetadata(request),
|
||||||
tlsCredentials,
|
tlsCredentials,
|
||||||
EppRequestSource.TLS,
|
EppRequestSource.TLS,
|
||||||
false, // This endpoint is never a dry run.
|
false, // This endpoint is never a dry run.
|
||||||
false, // This endpoint is never a superuser.
|
false, // This endpoint is never a superuser.
|
||||||
inputXmlBytes);
|
inputXmlBytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,16 +14,14 @@
|
|||||||
|
|
||||||
package google.registry.flows;
|
package google.registry.flows;
|
||||||
|
|
||||||
import static com.google.common.base.MoreObjects.toStringHelper;
|
|
||||||
import static google.registry.util.CollectionUtils.nullToEmpty;
|
import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
|
||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.servlet.http.HttpSession;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/** A metadata class that is a wrapper around {@link HttpSession}. */
|
/** A metadata class that is a wrapper around {@link HttpSession}. */
|
||||||
public class HttpSessionMetadata implements SessionMetadata {
|
public class HttpSessionMetadata extends SessionMetadata {
|
||||||
|
|
||||||
private static final String REGISTRAR_ID = "REGISTRAR_ID";
|
private static final String REGISTRAR_ID = "REGISTRAR_ID";
|
||||||
private static final String SERVICE_EXTENSIONS = "SERVICE_EXTENSIONS";
|
private static final String SERVICE_EXTENSIONS = "SERVICE_EXTENSIONS";
|
||||||
@@ -75,13 +73,4 @@ public class HttpSessionMetadata implements SessionMetadata {
|
|||||||
public void resetFailedLoginAttempts() {
|
public void resetFailedLoginAttempts() {
|
||||||
session.removeAttribute(FAILED_LOGIN_ATTEMPTS);
|
session.removeAttribute(FAILED_LOGIN_ATTEMPTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return toStringHelper(getClass())
|
|
||||||
.add("clientId", getRegistrarId())
|
|
||||||
.add("failedLoginAttempts", getFailedLoginAttempts())
|
|
||||||
.add("serviceExtensionUris", Joiner.on('.').join(nullToEmpty(getServiceExtensionUris())))
|
|
||||||
.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,29 +14,45 @@
|
|||||||
|
|
||||||
package google.registry.flows;
|
package google.registry.flows;
|
||||||
|
|
||||||
|
import static com.google.common.base.MoreObjects.toStringHelper;
|
||||||
|
import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import google.registry.request.Response;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/** Object to allow setting and retrieving session information in flows. */
|
/** Object to allow setting and retrieving session information in flows. */
|
||||||
public interface SessionMetadata {
|
public abstract class SessionMetadata {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidates the session. A new instance must be created after this for future sessions.
|
* Invalidates the session. A new instance must be created after this for future sessions.
|
||||||
* Attempts to invoke methods of this class after this method has been called will throw
|
* Attempts to invoke methods of this class after this method has been called will throw {@code
|
||||||
* {@code IllegalStateException}.
|
* IllegalStateException}.
|
||||||
*/
|
*/
|
||||||
void invalidate();
|
public abstract void invalidate();
|
||||||
|
|
||||||
String getRegistrarId();
|
public abstract String getRegistrarId();
|
||||||
|
|
||||||
Set<String> getServiceExtensionUris();
|
public abstract Set<String> getServiceExtensionUris();
|
||||||
|
|
||||||
int getFailedLoginAttempts();
|
public abstract int getFailedLoginAttempts();
|
||||||
|
|
||||||
void setRegistrarId(String registrarId);
|
public abstract void setRegistrarId(String registrarId);
|
||||||
|
|
||||||
void setServiceExtensionUris(Set<String> serviceExtensionUris);
|
public abstract void setServiceExtensionUris(Set<String> serviceExtensionUris);
|
||||||
|
|
||||||
void incrementFailedLoginAttempts();
|
public abstract void incrementFailedLoginAttempts();
|
||||||
|
|
||||||
void resetFailedLoginAttempts();
|
public abstract void resetFailedLoginAttempts();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return toStringHelper(getClass())
|
||||||
|
.add("clientId", getRegistrarId())
|
||||||
|
.add("failedLoginAttempts", getFailedLoginAttempts())
|
||||||
|
.add("serviceExtensionUris", Joiner.on('.').join(nullToEmpty(getServiceExtensionUris())))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(Response response) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,16 +14,13 @@
|
|||||||
|
|
||||||
package google.registry.flows;
|
package google.registry.flows;
|
||||||
|
|
||||||
import static com.google.common.base.MoreObjects.toStringHelper;
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static google.registry.util.CollectionUtils.nullToEmpty;
|
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/** A read-only {@link SessionMetadata} that doesn't support login/logout. */
|
/** A read-only {@link SessionMetadata} that doesn't support login/logout. */
|
||||||
public class StatelessRequestSessionMetadata implements SessionMetadata {
|
public class StatelessRequestSessionMetadata extends SessionMetadata {
|
||||||
|
|
||||||
private final String registrarId;
|
private final String registrarId;
|
||||||
private final ImmutableSet<String> serviceExtensionUris;
|
private final ImmutableSet<String> serviceExtensionUris;
|
||||||
@@ -74,13 +71,6 @@ public class StatelessRequestSessionMetadata implements SessionMetadata {
|
|||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return toStringHelper(getClass())
|
|
||||||
.add("clientId", getRegistrarId())
|
|
||||||
.add("failedLoginAttempts", getFailedLoginAttempts())
|
|
||||||
.add("serviceExtensionUris", Joiner.on('.').join(nullToEmpty(getServiceExtensionUris())))
|
|
||||||
.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,211 @@
|
|||||||
|
// Copyright 2025 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.flows;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static google.registry.flows.CookieSessionMetadata.COOKIE_NAME;
|
||||||
|
import static google.registry.flows.CookieSessionMetadata.decode;
|
||||||
|
import static google.registry.flows.CookieSessionMetadata.encode;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import google.registry.testing.FakeResponse;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/** Unit tests for {@link CookieSessionMetadata}. */
|
||||||
|
public class CookieSessionMetadataTest {
|
||||||
|
|
||||||
|
private HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
private FakeResponse response = new FakeResponse();
|
||||||
|
private CookieSessionMetadata cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNoCookie() {
|
||||||
|
assertThat(cookieSessionMetadata.getRegistrarId()).isNull();
|
||||||
|
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(0);
|
||||||
|
assertThat(cookieSessionMetadata.getServiceExtensionUris()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCookieWithAllFields() {
|
||||||
|
when(request.getHeader("Cookie"))
|
||||||
|
.thenReturn(
|
||||||
|
"THIS_COOKIE=foo; SESSION_INFO="
|
||||||
|
+ encode(
|
||||||
|
"CookieSessionMetadata{clientId=test_registrar, failedLoginAttempts=5, "
|
||||||
|
+ " serviceExtensionUris=A.B.C}")
|
||||||
|
+ "; THAT_COOKIE=bar");
|
||||||
|
cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||||
|
assertThat(cookieSessionMetadata.getRegistrarId()).isEqualTo("test_registrar");
|
||||||
|
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(5);
|
||||||
|
assertThat(cookieSessionMetadata.getServiceExtensionUris()).containsExactly("A", "B", "C");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCookieWithNullRegistrar() {
|
||||||
|
when(request.getHeader("Cookie"))
|
||||||
|
.thenReturn(
|
||||||
|
"SESSION_INFO="
|
||||||
|
+ encode(
|
||||||
|
"CookieSessionMetadata{clientId=null, failedLoginAttempts=5, "
|
||||||
|
+ " serviceExtensionUris=A.B.C}"));
|
||||||
|
cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||||
|
assertThat(cookieSessionMetadata.getRegistrarId()).isNull();
|
||||||
|
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(5);
|
||||||
|
assertThat(cookieSessionMetadata.getServiceExtensionUris()).containsExactly("A", "B", "C");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCookieWithEmptyExtension() {
|
||||||
|
when(request.getHeader("Cookie"))
|
||||||
|
.thenReturn(
|
||||||
|
"SESSION_INFO="
|
||||||
|
+ encode(
|
||||||
|
"CookieSessionMetadata{clientId=test_registrar, failedLoginAttempts=5, "
|
||||||
|
+ " serviceExtensionUris=}"));
|
||||||
|
cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||||
|
assertThat(cookieSessionMetadata.getRegistrarId()).isEqualTo("test_registrar");
|
||||||
|
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(5);
|
||||||
|
assertThat(cookieSessionMetadata.getServiceExtensionUris()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCookieWithSingleExtension() {
|
||||||
|
when(request.getHeader("Cookie"))
|
||||||
|
.thenReturn(
|
||||||
|
"SESSION_INFO="
|
||||||
|
+ encode(
|
||||||
|
"CookieSessionMetadata{clientId=test_registrar, failedLoginAttempts=5, "
|
||||||
|
+ " serviceExtensionUris=Foo}"));
|
||||||
|
cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||||
|
assertThat(cookieSessionMetadata.getRegistrarId()).isEqualTo("test_registrar");
|
||||||
|
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(5);
|
||||||
|
assertThat(cookieSessionMetadata.getServiceExtensionUris()).containsExactly("Foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testIncrementFailedLoginAttempts() {
|
||||||
|
when(request.getHeader("Cookie"))
|
||||||
|
.thenReturn(
|
||||||
|
"SESSION_INFO="
|
||||||
|
+ encode(
|
||||||
|
"CookieSessionMetadata{clientId=test_registrar, failedLoginAttempts=5, "
|
||||||
|
+ " serviceExtensionUris=Foo}"));
|
||||||
|
cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||||
|
cookieSessionMetadata.incrementFailedLoginAttempts();
|
||||||
|
assertThat(cookieSessionMetadata.getRegistrarId()).isEqualTo("test_registrar");
|
||||||
|
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(6);
|
||||||
|
assertThat(cookieSessionMetadata.getServiceExtensionUris()).containsExactly("Foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testResetFailedLoginAttempts() {
|
||||||
|
when(request.getHeader("Cookie"))
|
||||||
|
.thenReturn(
|
||||||
|
"SESSION_INFO="
|
||||||
|
+ encode(
|
||||||
|
"CookieSessionMetadata{clientId=test_registrar, failedLoginAttempts=5, "
|
||||||
|
+ " serviceExtensionUris=Foo}"));
|
||||||
|
cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||||
|
cookieSessionMetadata.resetFailedLoginAttempts();
|
||||||
|
assertThat(cookieSessionMetadata.getRegistrarId()).isEqualTo("test_registrar");
|
||||||
|
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(0);
|
||||||
|
assertThat(cookieSessionMetadata.getServiceExtensionUris()).containsExactly("Foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetRegistrarId() {
|
||||||
|
when(request.getHeader("Cookie"))
|
||||||
|
.thenReturn(
|
||||||
|
"SESSION_INFO="
|
||||||
|
+ encode(
|
||||||
|
"CookieSessionMetadata{clientId=test_registrar, failedLoginAttempts=5, "
|
||||||
|
+ " serviceExtensionUris=Foo}"));
|
||||||
|
cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||||
|
cookieSessionMetadata.setRegistrarId("new_registrar");
|
||||||
|
assertThat(cookieSessionMetadata.getRegistrarId()).isEqualTo("new_registrar");
|
||||||
|
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(5);
|
||||||
|
assertThat(cookieSessionMetadata.getServiceExtensionUris()).containsExactly("Foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetExtensions() {
|
||||||
|
when(request.getHeader("Cookie"))
|
||||||
|
.thenReturn(
|
||||||
|
"SESSION_INFO="
|
||||||
|
+ encode(
|
||||||
|
"CookieSessionMetadata{clientId=test_registrar, failedLoginAttempts=5, "
|
||||||
|
+ " serviceExtensionUris=Foo}"));
|
||||||
|
cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||||
|
cookieSessionMetadata.setServiceExtensionUris(ImmutableSet.of("Bar", "Baz"));
|
||||||
|
assertThat(cookieSessionMetadata.getRegistrarId()).isEqualTo("test_registrar");
|
||||||
|
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(5);
|
||||||
|
assertThat(cookieSessionMetadata.getServiceExtensionUris()).containsExactly("Bar", "Baz");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetEmptyExtensions() {
|
||||||
|
when(request.getHeader("Cookie"))
|
||||||
|
.thenReturn(
|
||||||
|
"SESSION_INFO="
|
||||||
|
+ encode(
|
||||||
|
"CookieSessionMetadata{clientId=test_registrar, failedLoginAttempts=5, "
|
||||||
|
+ " serviceExtensionUris=Foo}"));
|
||||||
|
cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||||
|
cookieSessionMetadata.setServiceExtensionUris(ImmutableSet.of());
|
||||||
|
assertThat(cookieSessionMetadata.getRegistrarId()).isEqualTo("test_registrar");
|
||||||
|
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(5);
|
||||||
|
assertThat(cookieSessionMetadata.getServiceExtensionUris()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testInvalidate() {
|
||||||
|
when(request.getHeader("Cookie"))
|
||||||
|
.thenReturn(
|
||||||
|
"SESSION_INFO="
|
||||||
|
+ encode(
|
||||||
|
"CookieSessionMetadata{clientId=test_registrar, failedLoginAttempts=5, "
|
||||||
|
+ " serviceExtensionUris=Foo}"));
|
||||||
|
cookieSessionMetadata = new CookieSessionMetadata(request);
|
||||||
|
cookieSessionMetadata.invalidate();
|
||||||
|
assertThat(cookieSessionMetadata.getRegistrarId()).isNull();
|
||||||
|
assertThat(cookieSessionMetadata.getFailedLoginAttempts()).isEqualTo(0);
|
||||||
|
assertThat(cookieSessionMetadata.getServiceExtensionUris()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSave() {
|
||||||
|
cookieSessionMetadata.save(response);
|
||||||
|
String value =
|
||||||
|
decode(
|
||||||
|
response.getHeaders().get("Set-Cookie").toString().substring(COOKIE_NAME.length() + 1));
|
||||||
|
assertThat(value)
|
||||||
|
.isEqualTo(
|
||||||
|
"CookieSessionMetadata{clientId=null, failedLoginAttempts=0, serviceExtensionUris=}");
|
||||||
|
cookieSessionMetadata.setRegistrarId("new_registrar");
|
||||||
|
cookieSessionMetadata.setServiceExtensionUris(ImmutableSet.of("Bar", "Baz"));
|
||||||
|
cookieSessionMetadata.incrementFailedLoginAttempts();
|
||||||
|
cookieSessionMetadata.save(response);
|
||||||
|
value =
|
||||||
|
decode(
|
||||||
|
response.getHeaders().get("Set-Cookie").toString().substring(COOKIE_NAME.length() + 1));
|
||||||
|
assertThat(value)
|
||||||
|
.isEqualTo(
|
||||||
|
"CookieSessionMetadata{clientId=new_registrar, failedLoginAttempts=1,"
|
||||||
|
+ " serviceExtensionUris=Bar.Baz}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,17 +12,19 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
package google.registry.flows;
|
package google.registry.flows;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.ArgumentMatchers.same;
|
import static org.mockito.ArgumentMatchers.same;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import google.registry.testing.FakeHttpSession;
|
import com.google.common.io.BaseEncoding;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
@@ -36,18 +38,22 @@ class EppTlsActionTest {
|
|||||||
EppTlsAction action = new EppTlsAction();
|
EppTlsAction action = new EppTlsAction();
|
||||||
action.inputXmlBytes = INPUT_XML_BYTES;
|
action.inputXmlBytes = INPUT_XML_BYTES;
|
||||||
action.tlsCredentials = mock(TlsCredentials.class);
|
action.tlsCredentials = mock(TlsCredentials.class);
|
||||||
action.session = new FakeHttpSession();
|
action.request = mock(HttpServletRequest.class);
|
||||||
action.session.setAttribute("REGISTRAR_ID", "ClientIdentifier");
|
when(action.request.getHeader("Cookie"))
|
||||||
|
.thenReturn(
|
||||||
|
"SESSION_INFO="
|
||||||
|
+ BaseEncoding.base64Url().encode("clientId=ClientIdentifier".getBytes(US_ASCII)));
|
||||||
action.eppRequestHandler = mock(EppRequestHandler.class);
|
action.eppRequestHandler = mock(EppRequestHandler.class);
|
||||||
action.run();
|
action.run();
|
||||||
ArgumentCaptor<SessionMetadata> captor = ArgumentCaptor.forClass(SessionMetadata.class);
|
ArgumentCaptor<SessionMetadata> captor = ArgumentCaptor.forClass(SessionMetadata.class);
|
||||||
verify(action.eppRequestHandler).executeEpp(
|
verify(action.eppRequestHandler)
|
||||||
captor.capture(),
|
.executeEpp(
|
||||||
same(action.tlsCredentials),
|
captor.capture(),
|
||||||
eq(EppRequestSource.TLS),
|
same(action.tlsCredentials),
|
||||||
eq(false),
|
eq(EppRequestSource.TLS),
|
||||||
eq(false),
|
eq(false),
|
||||||
eq(INPUT_XML_BYTES));
|
eq(false),
|
||||||
|
eq(INPUT_XML_BYTES));
|
||||||
assertThat(captor.getValue().getRegistrarId()).isEqualTo("ClientIdentifier");
|
assertThat(captor.getValue().getRegistrarId()).isEqualTo("ClientIdentifier");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,15 +62,4 @@ do
|
|||||||
kubectl apply -f -
|
kubectl apply -f -
|
||||||
done
|
done
|
||||||
|
|
||||||
# Restart proxies
|
|
||||||
while read line
|
|
||||||
do
|
|
||||||
parts=(${line})
|
|
||||||
echo "Updating cluster ${parts[0]} in location ${parts[1]}..."
|
|
||||||
gcloud container clusters get-credentials ${parts[0]} \
|
|
||||||
--project ${project} --location ${parts[1]}
|
|
||||||
kubectl rollout restart deployment/proxy-deployment
|
|
||||||
kubectl rollout restart deployment/proxy-deployment-canary
|
|
||||||
done < <(gcloud container clusters list --project ${project} | grep proxy-cluster)
|
|
||||||
|
|
||||||
kubectl config use-context "$current_context"
|
kubectl config use-context "$current_context"
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ import java.security.cert.CertificateException;
|
|||||||
import java.security.cert.CertificateExpiredException;
|
import java.security.cert.CertificateExpiredException;
|
||||||
import java.security.cert.CertificateNotYetValidException;
|
import java.security.cert.CertificateNotYetValidException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
@@ -272,17 +271,11 @@ class SslServerInitializerTest {
|
|||||||
getClientHandler(
|
getClientHandler(
|
||||||
sslProvider, serverSsc.cert(), clientSsc.key(), clientSsc.cert(), "TLSv1.1", null));
|
sslProvider, serverSsc.cert(), clientSsc.key(), clientSsc.cert(), "TLSv1.1", null));
|
||||||
|
|
||||||
ImmutableList<Integer> jdkVersion =
|
|
||||||
Arrays.stream(System.getProperty("java.version").split("\\."))
|
|
||||||
.map(Integer::parseInt)
|
|
||||||
.collect(ImmutableList.toImmutableList());
|
|
||||||
|
|
||||||
// In JDK v11.0.11 and above, TLS 1.1 is not supported anymore, in which case attempting to
|
// In JDK v11.0.11 and above, TLS 1.1 is not supported anymore, in which case attempting to
|
||||||
// connect with TLS 1.1 results in a ClosedChannelException instead of a SSLHandShakeException.
|
// connect with TLS 1.1 results in a ClosedChannelException instead of a SSLHandShakeException.
|
||||||
// See https://www.oracle.com/java/technologies/javase/11-0-11-relnotes.html#JDK-8202343
|
// See https://www.oracle.com/java/technologies/javase/11-0-11-relnotes.html#JDK-8202343
|
||||||
Class<? extends Exception> rootCause =
|
Class<? extends Exception> rootCause =
|
||||||
sslProvider == SslProvider.JDK
|
sslProvider == SslProvider.JDK
|
||||||
&& compareSemanticVersion(jdkVersion, ImmutableList.of(11, 0, 11))
|
|
||||||
? ClosedChannelException.class
|
? ClosedChannelException.class
|
||||||
: SSLHandshakeException.class;
|
: SSLHandshakeException.class;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user