Decouple SessionMetadata and TransportCredentials

TransportCredentials are per-request, not per-session, and
there's no reason to carry them within SessionMetadata.

While I'm in here, get rid of "null" credentials.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=125202213
This commit is contained in:
cgoldfeder 2016-06-17 14:24:04 -07:00 committed by Ben McIlwain
parent fe1cd06da8
commit 3ae646d687
26 changed files with 134 additions and 120 deletions

View file

@ -41,7 +41,8 @@ public class EppConsoleAction implements Runnable {
@Override @Override
public void run() { public void run() {
eppRequestHandler.executeEpp( eppRequestHandler.executeEpp(
new HttpSessionMetadata(new GaeUserCredentials(getUserService().getCurrentUser()), session), new HttpSessionMetadata(session),
new GaeUserCredentials(getUserService().getCurrentUser()),
inputXmlBytes); inputXmlBytes);
} }
} }

View file

@ -51,7 +51,10 @@ public final class EppController {
* Read an EPP envelope from the client, find the matching flow, execute it, and return * Read an EPP envelope from the client, find the matching flow, execute it, and return
* the response marshalled to a byte array. * the response marshalled to a byte array.
*/ */
public byte[] handleEppCommand(SessionMetadata sessionMetadata, byte[] inputXmlBytes) { public byte[] handleEppCommand(
SessionMetadata sessionMetadata,
TransportCredentials credentials,
byte[] inputXmlBytes) {
Trid trid = null; Trid trid = null;
try { try {
EppInput eppInput = unmarshal(EppInput.class, inputXmlBytes); EppInput eppInput = unmarshal(EppInput.class, inputXmlBytes);
@ -68,6 +71,7 @@ public final class EppController {
eppInput, eppInput,
trid, trid,
sessionMetadata, sessionMetadata,
credentials,
inputXmlBytes, inputXmlBytes,
metrics, metrics,
clock); clock);

View file

@ -39,10 +39,13 @@ public class EppRequestHandler {
@Inject EppRequestHandler() {} @Inject EppRequestHandler() {}
/** Handle an EPP request and write out a servlet response. */ /** Handle an EPP request and write out a servlet response. */
public void executeEpp(SessionMetadata sessionMetadata, byte[] inputXmlBytes) { public void executeEpp(
SessionMetadata sessionMetadata,
TransportCredentials credentials,
byte[] inputXmlBytes) {
try { try {
response.setPayload(new String( response.setPayload(new String(
eppController.handleEppCommand(sessionMetadata, inputXmlBytes), UTF_8)); eppController.handleEppCommand(sessionMetadata, credentials, inputXmlBytes), UTF_8));
response.setContentType(APPLICATION_EPP_XML); response.setContentType(APPLICATION_EPP_XML);
// Note that we always return 200 (OK) even if the EppController returns an error response. // Note that we always return 200 (OK) even if the EppController returns an error response.
// This is because returning an non-OK HTTP status code will cause the proxy server to // This is because returning an non-OK HTTP status code will cause the proxy server to

View file

@ -46,7 +46,7 @@ public class EppTlsAction implements Runnable {
if (!tlsCredentials.hasSni()) { if (!tlsCredentials.hasSni()) {
logger.warning("Request did not include required SNI header."); logger.warning("Request did not include required SNI header.");
} }
eppRequestHandler.executeEpp(new HttpSessionMetadata(tlsCredentials, session), inputXmlBytes); eppRequestHandler.executeEpp(new HttpSessionMetadata(session), tlsCredentials, inputXmlBytes);
} }
} }

View file

@ -54,6 +54,7 @@ public class EppToolAction implements Runnable {
dryRun, dryRun,
ProtocolDefinition.getVisibleServiceExtensionUris(), ProtocolDefinition.getVisibleServiceExtensionUris(),
SessionSource.TOOL), SessionSource.TOOL),
new PasswordOnlyTransportCredentials(),
xml.getBytes(UTF_8)); xml.getBytes(UTF_8));
} }

View file

@ -42,6 +42,7 @@ public abstract class Flow {
protected EppInput eppInput; protected EppInput eppInput;
protected SessionMetadata sessionMetadata; protected SessionMetadata sessionMetadata;
protected TransportCredentials credentials;
protected Trid trid; protected Trid trid;
protected DateTime now; protected DateTime now;
protected byte[] inputXmlBytes; protected byte[] inputXmlBytes;
@ -101,11 +102,13 @@ public abstract class Flow {
EppInput eppInput, EppInput eppInput,
Trid trid, Trid trid,
SessionMetadata sessionMetadata, SessionMetadata sessionMetadata,
TransportCredentials credentials,
DateTime now, DateTime now,
byte[] inputXmlBytes) throws EppException { byte[] inputXmlBytes) throws EppException {
this.eppInput = eppInput; this.eppInput = eppInput;
this.trid = trid; this.trid = trid;
this.sessionMetadata = sessionMetadata; this.sessionMetadata = sessionMetadata;
this.credentials = credentials;
this.now = now; this.now = now;
this.superuser = sessionMetadata.isSuperuser(); this.superuser = sessionMetadata.isSuperuser();
this.inputXmlBytes = inputXmlBytes; this.inputXmlBytes = inputXmlBytes;

View file

@ -36,7 +36,7 @@ import org.joda.time.DateTime;
/** Run a flow, either transactionally or not, with logging and retrying as needed. */ /** Run a flow, either transactionally or not, with logging and retrying as needed. */
public class FlowRunner { public class FlowRunner {
private static final String COMMAND_LOG_FORMAT = "EPP Command" + Strings.repeat("\n\t%s", 4); private static final String COMMAND_LOG_FORMAT = "EPP Command" + Strings.repeat("\n\t%s", 5);
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass(); private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
@ -44,6 +44,7 @@ public class FlowRunner {
private final EppInput eppInput; private final EppInput eppInput;
private final Trid trid; private final Trid trid;
private final SessionMetadata sessionMetadata; private final SessionMetadata sessionMetadata;
private final TransportCredentials credentials;
private final byte[] inputXmlBytes; private final byte[] inputXmlBytes;
private final EppMetrics metrics; private final EppMetrics metrics;
private final Clock clock; private final Clock clock;
@ -53,13 +54,16 @@ public class FlowRunner {
EppInput eppInput, EppInput eppInput,
Trid trid, Trid trid,
SessionMetadata sessionMetadata, SessionMetadata sessionMetadata,
TransportCredentials credentials,
byte[] inputXmlBytes, byte[] inputXmlBytes,
final EppMetrics metrics, final EppMetrics metrics,
Clock clock) { Clock clock) {
credentials.toString();
this.flowClass = flowClass; this.flowClass = flowClass;
this.eppInput = eppInput; this.eppInput = eppInput;
this.trid = trid; this.trid = trid;
this.sessionMetadata = sessionMetadata; this.sessionMetadata = sessionMetadata;
this.credentials = credentials;
this.inputXmlBytes = inputXmlBytes; this.inputXmlBytes = inputXmlBytes;
this.metrics = metrics; this.metrics = metrics;
this.clock = clock; this.clock = clock;
@ -72,7 +76,8 @@ public class FlowRunner {
trid.getServerTransactionId(), trid.getServerTransactionId(),
clientId, clientId,
sessionMetadata, sessionMetadata,
prettyPrint(inputXmlBytes).replaceAll("\n", "\n\t")); prettyPrint(inputXmlBytes).replaceAll("\n", "\n\t"),
credentials);
if (!isTransactional()) { if (!isTransactional()) {
if (metrics != null) { if (metrics != null) {
metrics.incrementAttempts(); metrics.incrementAttempts();
@ -124,6 +129,7 @@ public class FlowRunner {
eppInput, eppInput,
trid, trid,
sessionMetadata, sessionMetadata,
credentials,
now, now,
inputXmlBytes); inputXmlBytes);
} }

View file

@ -38,12 +38,8 @@ public class GaeUserCredentials implements TransportCredentials {
} }
@Override @Override
public boolean performsLoginCheck() { public void validate(Registrar registrar, String ignoredPassword)
return true; throws AuthenticationErrorException {
}
@Override
public void validate(Registrar r) throws AuthenticationErrorException {
if (gaeUser == null) { if (gaeUser == null) {
throw new UserNotLoggedInException(); throw new UserNotLoggedInException();
} }
@ -53,7 +49,7 @@ public class GaeUserCredentials implements TransportCredentials {
} }
// Check Registrar's contacts to see if any are associated with this gaeUserId. // Check Registrar's contacts to see if any are associated with this gaeUserId.
final String gaeUserId = gaeUser.getUserId(); final String gaeUserId = gaeUser.getUserId();
for (RegistrarContact rc : r.getContacts()) { for (RegistrarContact rc : registrar.getContacts()) {
if (gaeUserId.equals(rc.getGaeUserId())) { if (gaeUserId.equals(rc.getGaeUserId())) {
return; return;
} }

View file

@ -24,9 +24,8 @@ public class HttpSessionMetadata extends SessionMetadata {
private final HttpSession session; private final HttpSession session;
private boolean isValid = true; private boolean isValid = true;
public HttpSessionMetadata(TransportCredentials credentials, HttpSession session) { public HttpSessionMetadata(HttpSession session) {
this.session = session; this.session = session;
setTransportCredentials(credentials);
} }
@Override @Override

View file

@ -0,0 +1,28 @@
// Copyright 2016 The Domain Registry 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 google.registry.flows.EppException.AuthenticationErrorException;
import google.registry.model.registrar.Registrar;
/** A transport credentials that validates the registrar's EPP password and nothing else. */
public class PasswordOnlyTransportCredentials implements TransportCredentials {
@Override
public void validate(Registrar r, String password) throws AuthenticationErrorException {
if (!r.testPassword(password)) {
throw new BadRegistrarPasswordException();
}
}
}

View file

@ -46,9 +46,7 @@ public abstract class SessionMetadata {
NONE NONE
} }
private TransportCredentials credentials; /** The key used for looking up the current client id on the session object. */
/** The key used for looking up the current client id on the session object. */
protected static final String CLIENT_ID_KEY = "CLIENT_ID"; protected static final String CLIENT_ID_KEY = "CLIENT_ID";
/** The key used for looking up the superuser bit on the session object. */ /** The key used for looking up the superuser bit on the session object. */
@ -91,16 +89,6 @@ public abstract class SessionMetadata {
return clazz.cast(getProperty(key)); return clazz.cast(getProperty(key));
} }
public TransportCredentials getTransportCredentials() {
checkValid();
return credentials;
}
public void setTransportCredentials(TransportCredentials credentials) {
checkValid();
this.credentials = credentials;
}
public String getClientId() { public String getClientId() {
return getProperty(String.class, CLIENT_ID_KEY); return getProperty(String.class, CLIENT_ID_KEY);
} }
@ -164,7 +152,6 @@ public abstract class SessionMetadata {
.add("failedLoginAttempts", getFailedLoginAttempts()) .add("failedLoginAttempts", getFailedLoginAttempts())
.add("sessionSource", getSessionSource()) .add("sessionSource", getSessionSource())
.add("serviceExtensionUris", Joiner.on('.').join(nullToEmpty(getServiceExtensionUris()))) .add("serviceExtensionUris", Joiner.on('.').join(nullToEmpty(getServiceExtensionUris())))
.add("transportCredentials", getTransportCredentials())
.toString(); .toString();
} }
} }

View file

@ -68,11 +68,6 @@ public class StatelessRequestSessionMetadata extends SessionMetadata {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public void setTransportCredentials(TransportCredentials credentials) {
throw new UnsupportedOperationException();
}
@Override @Override
protected void setProperty(String key, Object value) { protected void setProperty(String key, Object value) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();

View file

@ -86,20 +86,16 @@ public class TlsCredentials implements TransportCredentials {
} }
} }
@Override
public boolean performsLoginCheck() {
return false;
}
/** Returns {@code true} if frontend passed us the requested server name. */ /** Returns {@code true} if frontend passed us the requested server name. */
boolean hasSni() { boolean hasSni() {
return !isNullOrEmpty(sni); return !isNullOrEmpty(sni);
} }
@Override @Override
public void validate(Registrar registrar) throws AuthenticationErrorException { public void validate(Registrar registrar, String password) throws AuthenticationErrorException {
validateIp(registrar); validateIp(registrar);
validateCertificate(registrar); validateCertificate(registrar);
validatePassword(registrar, password);
} }
/** /**
@ -160,6 +156,13 @@ public class TlsCredentials implements TransportCredentials {
} }
} }
private void validatePassword(Registrar registrar, String password)
throws BadRegistrarPasswordException {
if (!registrar.testPassword(password)) {
throw new BadRegistrarPasswordException();
}
}
@Override @Override
public String toString() { public String toString() {
return toStringHelper(getClass()) return toStringHelper(getClass())

View file

@ -17,23 +17,21 @@ package google.registry.flows;
import google.registry.flows.EppException.AuthenticationErrorException; import google.registry.flows.EppException.AuthenticationErrorException;
import google.registry.model.registrar.Registrar; import google.registry.model.registrar.Registrar;
/** /** Interface for objects containing registrar credentials provided via an EPP transport. */
* A marker interface for objects containing registrar credentials provided via an EPP transport.
*/
public interface TransportCredentials { public interface TransportCredentials {
/** /**
* Indicates whether the transport takes the place of EPP login checks, in which case LoginFlow * Check that these credentials are valid for the registrar and optionally check the password.
* will not check the password. Alternatively, if the password should be checked, it MUST match *
* the user's and GAE's isUserAdmin should not be used to bypass this check as internal * Called by {@link google.registry.flows.session.LoginFlow LoginFlow} to check the transport
* connections over RPC will have this property for all registrars. * credentials against the stored registrar's credentials. If they do not match, throw an
* {@link AuthenticationErrorException}.
*/ */
boolean performsLoginCheck(); void validate(Registrar registrar, String password) throws AuthenticationErrorException;
/** /** Registrar password is incorrect. */
* Called by {@link google.registry.flows.session.LoginFlow LoginFlow} static class BadRegistrarPasswordException extends AuthenticationErrorException {
* to check the transport credentials against the stored registrar's credentials. public BadRegistrarPasswordException() {
* If they do not match, throw an AuthenticationErrorException. super("Registrar password is incorrect");
*/ }
void validate(Registrar r) throws AuthenticationErrorException; }
} }

View file

@ -29,7 +29,6 @@ import google.registry.flows.EppException.UnimplementedExtensionException;
import google.registry.flows.EppException.UnimplementedObjectServiceException; import google.registry.flows.EppException.UnimplementedObjectServiceException;
import google.registry.flows.EppException.UnimplementedOptionException; import google.registry.flows.EppException.UnimplementedOptionException;
import google.registry.flows.Flow; import google.registry.flows.Flow;
import google.registry.flows.TransportCredentials;
import google.registry.model.eppcommon.ProtocolDefinition; import google.registry.model.eppcommon.ProtocolDefinition;
import google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension; import google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension;
import google.registry.model.eppinput.EppInput.Login; import google.registry.model.eppinput.EppInput.Login;
@ -55,9 +54,9 @@ import java.util.Set;
* @error {@link google.registry.flows.TlsCredentials.BadRegistrarIpAddressException} * @error {@link google.registry.flows.TlsCredentials.BadRegistrarIpAddressException}
* @error {@link google.registry.flows.TlsCredentials.MissingRegistrarCertificateException} * @error {@link google.registry.flows.TlsCredentials.MissingRegistrarCertificateException}
* @error {@link google.registry.flows.TlsCredentials.NoSniException} * @error {@link google.registry.flows.TlsCredentials.NoSniException}
* @error {@link google.registry.flows.TransportCredentials.BadRegistrarPasswordException}
* @error {@link LoginFlow.AlreadyLoggedInException} * @error {@link LoginFlow.AlreadyLoggedInException}
* @error {@link LoginFlow.BadRegistrarClientIdException} * @error {@link LoginFlow.BadRegistrarClientIdException}
* @error {@link LoginFlow.BadRegistrarPasswordException}
* @error {@link LoginFlow.TooManyFailedLoginsException} * @error {@link LoginFlow.TooManyFailedLoginsException}
* @error {@link LoginFlow.PasswordChangesNotSupportedException} * @error {@link LoginFlow.PasswordChangesNotSupportedException}
* @error {@link LoginFlow.RegistrarAccountNotActiveException} * @error {@link LoginFlow.RegistrarAccountNotActiveException}
@ -114,24 +113,15 @@ public class LoginFlow extends Flow {
throw new BadRegistrarClientIdException(login.getClientId()); throw new BadRegistrarClientIdException(login.getClientId());
} }
TransportCredentials credentials = sessionMetadata.getTransportCredentials();
// AuthenticationErrorExceptions will propagate up through here. // AuthenticationErrorExceptions will propagate up through here.
if (credentials != null) { // Allow no-credential logins, for load-testing and RDE. try {
try { credentials.validate(registrar, login.getPassword());
credentials.validate(registrar); } catch (AuthenticationErrorException e) {
} catch (AuthenticationErrorException e) {
sessionMetadata.incrementFailedLoginAttempts();
throw e;
}
}
final boolean requiresLoginCheck = credentials == null || !credentials.performsLoginCheck();
if (requiresLoginCheck && !registrar.testPassword(login.getPassword())) {
sessionMetadata.incrementFailedLoginAttempts(); sessionMetadata.incrementFailedLoginAttempts();
if (sessionMetadata.getFailedLoginAttempts() > MAX_FAILED_LOGIN_ATTEMPTS_PER_CONNECTION) { if (sessionMetadata.getFailedLoginAttempts() > MAX_FAILED_LOGIN_ATTEMPTS_PER_CONNECTION) {
throw new TooManyFailedLoginsException(); throw new TooManyFailedLoginsException();
} else { } else {
throw new BadRegistrarPasswordException(); throw e;
} }
} }
if (registrar.getState().equals(Registrar.State.PENDING)) { if (registrar.getState().equals(Registrar.State.PENDING)) {
@ -157,13 +147,6 @@ public class LoginFlow extends Flow {
} }
} }
/** Registrar password is incorrect. */
static class BadRegistrarPasswordException extends AuthenticationErrorException {
public BadRegistrarPasswordException() {
super("Registrar password is incorrect");
}
}
/** Registrar login failed too many times. */ /** Registrar login failed too many times. */
static class TooManyFailedLoginsException extends AuthenticationErrorClosingConnectionException { static class TooManyFailedLoginsException extends AuthenticationErrorClosingConnectionException {
public TooManyFailedLoginsException() { public TooManyFailedLoginsException() {

View file

@ -104,12 +104,11 @@ final class ValidateLoginCredentialsCommand implements RemoteApiCommand, GtechCo
LoginFlow.class, LoginFlow.class,
unmarshal(EppInput.class, inputXmlBytes), unmarshal(EppInput.class, inputXmlBytes),
Trid.create(null), Trid.create(null),
new HttpSessionMetadata( new HttpSessionMetadata(new BasicHttpSession()),
new TlsCredentials( new TlsCredentials(
clientCertificateHash, clientCertificateHash,
Optional.of(clientIpAddress), Optional.of(clientIpAddress),
"placeholder"), // behave as if we have SNI on, since we're validating a cert "placeholder"), // behave as if we have SNI on, since we're validating a cert
new BasicHttpSession()),
inputXmlBytes, inputXmlBytes,
null, null,
new SystemClock()).run()), UTF_8)); new SystemClock()).run()), UTF_8));

View file

@ -38,6 +38,7 @@ import dagger.Provides;
import google.registry.config.RegistryEnvironment; import google.registry.config.RegistryEnvironment;
import google.registry.flows.EppException; import google.registry.flows.EppException;
import google.registry.flows.FlowRunner; import google.registry.flows.FlowRunner;
import google.registry.flows.PasswordOnlyTransportCredentials;
import google.registry.flows.SessionMetadata.SessionSource; import google.registry.flows.SessionMetadata.SessionSource;
import google.registry.flows.StatelessRequestSessionMetadata; import google.registry.flows.StatelessRequestSessionMetadata;
import google.registry.flows.domain.DomainCheckFlow; import google.registry.flows.domain.DomainCheckFlow;
@ -119,6 +120,7 @@ public class CheckApiAction implements Runnable {
unmarshal(EppInput.class, inputXmlBytes), unmarshal(EppInput.class, inputXmlBytes),
Trid.create(getClass().getSimpleName()), Trid.create(getClass().getSimpleName()),
sessionMetadata, sessionMetadata,
new PasswordOnlyTransportCredentials(),
inputXmlBytes, inputXmlBytes,
null, null,
clock) clock)

View file

@ -51,11 +51,14 @@ public class EppConsoleActionTest extends ShardableTestCase {
action.session.setAttribute("SUPERUSER", superuser); action.session.setAttribute("SUPERUSER", superuser);
action.eppRequestHandler = mock(EppRequestHandler.class); action.eppRequestHandler = mock(EppRequestHandler.class);
action.run(); action.run();
ArgumentCaptor<SessionMetadata> captor = ArgumentCaptor.forClass(SessionMetadata.class); ArgumentCaptor<TransportCredentials> credentialsCaptor =
verify(action.eppRequestHandler).executeEpp(captor.capture(), eq(INPUT_XML_BYTES)); ArgumentCaptor.forClass(TransportCredentials.class);
SessionMetadata sessionMetadata = captor.getValue(); ArgumentCaptor<SessionMetadata> metadataCaptor = ArgumentCaptor.forClass(SessionMetadata.class);
assertThat(((GaeUserCredentials) sessionMetadata.getTransportCredentials()).gaeUser.getEmail()) verify(action.eppRequestHandler).executeEpp(
metadataCaptor.capture(), credentialsCaptor.capture(), eq(INPUT_XML_BYTES));
assertThat(((GaeUserCredentials) credentialsCaptor.getValue()).gaeUser.getEmail())
.isEqualTo("person@example.com"); .isEqualTo("person@example.com");
SessionMetadata sessionMetadata = metadataCaptor.getValue();
assertThat(sessionMetadata.getClientId()).isEqualTo("ClientIdentifier"); assertThat(sessionMetadata.getClientId()).isEqualTo("ClientIdentifier");
assertThat(sessionMetadata.isDryRun()).isFalse(); // Should always be false for console. assertThat(sessionMetadata.isDryRun()).isFalse(); // Should always be false for console.
assertThat(sessionMetadata.isSuperuser()).isEqualTo(superuser); assertThat(sessionMetadata.isSuperuser()).isEqualTo(superuser);

View file

@ -50,8 +50,8 @@ public class EppTestCase extends ShardableTestCase {
private final FakeClock clock = new FakeClock(); private final FakeClock clock = new FakeClock();
private TestSessionMetadata sessionMetadata; private TestSessionMetadata sessionMetadata;
private TransportCredentials credentials; private TransportCredentials credentials = new PasswordOnlyTransportCredentials();
private boolean superuser; private boolean isSuperuser;
@Before @Before
public void initTestCase() { public void initTestCase() {
@ -70,8 +70,8 @@ public class EppTestCase extends ShardableTestCase {
this.credentials = credentials; this.credentials = credentials;
} }
protected void setSuperuser(boolean superuser) { protected void setSuperuser(boolean isSuperuser) {
this.superuser = superuser; this.isSuperuser = isSuperuser;
} }
String assertCommandAndResponse(String inputFilename, String outputFilename) throws Exception { String assertCommandAndResponse(String inputFilename, String outputFilename) throws Exception {
@ -95,9 +95,8 @@ public class EppTestCase extends ShardableTestCase {
loadFileWithSubstitutions(getClass(), outputFilename, outputSubstitutions); loadFileWithSubstitutions(getClass(), outputFilename, outputSubstitutions);
if (sessionMetadata == null) { if (sessionMetadata == null) {
sessionMetadata = new TestSessionMetadata(); sessionMetadata = new TestSessionMetadata();
sessionMetadata.setTransportCredentials(credentials);
} }
sessionMetadata.setSuperuser(superuser); sessionMetadata.setSuperuser(isSuperuser);
String actualOutput = executeXmlCommand(input); String actualOutput = executeXmlCommand(input);
if (!sessionMetadata.isValid()) { if (!sessionMetadata.isValid()) {
sessionMetadata = null; sessionMetadata = null;
@ -119,7 +118,7 @@ public class EppTestCase extends ShardableTestCase {
handler.eppController = new EppController(); handler.eppController = new EppController();
handler.eppController.clock = clock; handler.eppController.clock = clock;
handler.eppController.metrics = mock(EppMetrics.class); handler.eppController.metrics = mock(EppMetrics.class);
handler.executeEpp(sessionMetadata, inputXml.getBytes(UTF_8)); handler.executeEpp(sessionMetadata, credentials, inputXml.getBytes(UTF_8));
assertThat(response.getStatus()).isEqualTo(SC_OK); assertThat(response.getStatus()).isEqualTo(SC_OK);
assertThat(response.getContentType()).isEqualTo(APPLICATION_EPP_XML_UTF8); assertThat(response.getContentType()).isEqualTo(APPLICATION_EPP_XML_UTF8);
String result = response.getPayload(); String result = response.getPayload();

View file

@ -17,7 +17,8 @@ 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.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static org.mockito.Mockito.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.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 static org.mockito.Mockito.when;
@ -47,12 +48,12 @@ public class EppTlsActionTest extends ShardableTestCase {
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(captor.capture(), eq(INPUT_XML_BYTES)); verify(action.eppRequestHandler)
.executeEpp(captor.capture(), same(action.tlsCredentials), eq(INPUT_XML_BYTES));
SessionMetadata sessionMetadata = captor.getValue(); SessionMetadata sessionMetadata = captor.getValue();
assertThat(sessionMetadata.getClientId()).isEqualTo("ClientIdentifier"); assertThat(sessionMetadata.getClientId()).isEqualTo("ClientIdentifier");
assertThat(sessionMetadata.isDryRun()).isFalse(); // Should always be false for TLS. assertThat(sessionMetadata.isDryRun()).isFalse(); // Should always be false for TLS.
assertThat(sessionMetadata.isSuperuser()).isEqualTo(superuser); assertThat(sessionMetadata.isSuperuser()).isEqualTo(superuser);
assertThat(sessionMetadata.getTransportCredentials()).isSameAs(action.tlsCredentials);
} }
@Test @Test

View file

@ -16,7 +16,8 @@ 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.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static org.mockito.Mockito.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@ -38,7 +39,10 @@ public class EppToolActionTest {
action.xml = "<xml>"; action.xml = "<xml>";
action.run(); action.run();
ArgumentCaptor<SessionMetadata> captor = ArgumentCaptor.forClass(SessionMetadata.class); ArgumentCaptor<SessionMetadata> captor = ArgumentCaptor.forClass(SessionMetadata.class);
verify(action.eppRequestHandler).executeEpp(captor.capture(), eq(action.xml.getBytes(UTF_8))); verify(action.eppRequestHandler).executeEpp(
captor.capture(),
isA(PasswordOnlyTransportCredentials.class),
eq(action.xml.getBytes(UTF_8)));
SessionMetadata sessionMetadata = captor.getValue(); SessionMetadata sessionMetadata = captor.getValue();
assertThat(sessionMetadata.getClientId()).isEqualTo("ClientIdentifier"); assertThat(sessionMetadata.getClientId()).isEqualTo("ClientIdentifier");
assertThat(sessionMetadata.isDryRun()).isEqualTo(dryRun); assertThat(sessionMetadata.isDryRun()).isEqualTo(dryRun);

View file

@ -93,6 +93,7 @@ public abstract class FlowTestCase<F extends Flow> {
protected Class<? extends Flow> flowClass; protected Class<? extends Flow> flowClass;
protected TestSessionMetadata sessionMetadata; protected TestSessionMetadata sessionMetadata;
protected FakeClock clock = new FakeClock(DateTime.now(UTC)); protected FakeClock clock = new FakeClock(DateTime.now(UTC));
protected TransportCredentials credentials = new PasswordOnlyTransportCredentials();
@Before @Before
public void init() throws Exception { public void init() throws Exception {
@ -141,6 +142,7 @@ public abstract class FlowTestCase<F extends Flow> {
eppInput, eppInput,
getTrid(), getTrid(),
sessionMetadata, sessionMetadata,
credentials,
"<xml></xml>".getBytes(), "<xml></xml>".getBytes(),
null, null,
clock); clock);

View file

@ -22,9 +22,9 @@ import google.registry.flows.EppException.UnimplementedExtensionException;
import google.registry.flows.EppException.UnimplementedObjectServiceException; import google.registry.flows.EppException.UnimplementedObjectServiceException;
import google.registry.flows.EppException.UnimplementedProtocolVersionException; import google.registry.flows.EppException.UnimplementedProtocolVersionException;
import google.registry.flows.FlowTestCase; import google.registry.flows.FlowTestCase;
import google.registry.flows.TransportCredentials.BadRegistrarPasswordException;
import google.registry.flows.session.LoginFlow.AlreadyLoggedInException; import google.registry.flows.session.LoginFlow.AlreadyLoggedInException;
import google.registry.flows.session.LoginFlow.BadRegistrarClientIdException; import google.registry.flows.session.LoginFlow.BadRegistrarClientIdException;
import google.registry.flows.session.LoginFlow.BadRegistrarPasswordException;
import google.registry.flows.session.LoginFlow.PasswordChangesNotSupportedException; import google.registry.flows.session.LoginFlow.PasswordChangesNotSupportedException;
import google.registry.flows.session.LoginFlow.RegistrarAccountNotActiveException; import google.registry.flows.session.LoginFlow.RegistrarAccountNotActiveException;
import google.registry.flows.session.LoginFlow.TooManyFailedLoginsException; import google.registry.flows.session.LoginFlow.TooManyFailedLoginsException;

View file

@ -15,9 +15,9 @@
package google.registry.flows.session; package google.registry.flows.session;
import static com.google.appengine.api.users.UserServiceFactory.getUserService;
import static google.registry.testing.DatastoreHelper.persistResource; import static google.registry.testing.DatastoreHelper.persistResource;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.apphosting.api.ApiProxy; import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.ApiProxy.Environment; import com.google.apphosting.api.ApiProxy.Environment;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
@ -150,15 +150,13 @@ public class LoginFlowViaConsoleTest extends LoginFlowTestCase {
return envAttr; return envAttr;
} }
}); });
sessionMetadata.setTransportCredentials(new GaeUserCredentials( credentials = new GaeUserCredentials(getUserService().getCurrentUser());
UserServiceFactory.getUserService().getCurrentUser()));
return oldEnv; return oldEnv;
} }
void noLogin() { void noLogin() {
oldEnv = ApiProxy.getCurrentEnvironment(); oldEnv = ApiProxy.getCurrentEnvironment();
sessionMetadata.setTransportCredentials(new GaeUserCredentials( credentials = new GaeUserCredentials(getUserService().getCurrentUser());
UserServiceFactory.getUserService().getCurrentUser()));
} }
void persistLinkedAccount(String email, String gaeUserId) { void persistLinkedAccount(String email, String gaeUserId) {

View file

@ -52,7 +52,7 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
@Test @Test
public void testSuccess_withGoodCredentials() throws Exception { public void testSuccess_withGoodCredentials() throws Exception {
persistResource(getRegistrarBuilder().build()); persistResource(getRegistrarBuilder().build());
sessionMetadata.setTransportCredentials(new TlsCredentials(GOOD_CERT, GOOD_IP, "goo.example")); credentials = new TlsCredentials(GOOD_CERT, GOOD_IP, "goo.example");
doSuccessfulTest("login_valid.xml"); doSuccessfulTest("login_valid.xml");
} }
@ -63,8 +63,7 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
.setIpAddressWhitelist(ImmutableList.of( .setIpAddressWhitelist(ImmutableList.of(
CidrAddressBlock.create("2001:db8:0:0:0:0:1:1/32"))) CidrAddressBlock.create("2001:db8:0:0:0:0:1:1/32")))
.build()); .build());
sessionMetadata.setTransportCredentials( credentials = new TlsCredentials(GOOD_CERT, GOOD_IPV6, "goo.example");
new TlsCredentials(GOOD_CERT, GOOD_IPV6, "goo.example"));
doSuccessfulTest("login_valid.xml"); doSuccessfulTest("login_valid.xml");
} }
@ -75,8 +74,7 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
.setIpAddressWhitelist(ImmutableList.of( .setIpAddressWhitelist(ImmutableList.of(
CidrAddressBlock.create("2001:db8:0:0:0:0:1:1/32"))) CidrAddressBlock.create("2001:db8:0:0:0:0:1:1/32")))
.build()); .build());
sessionMetadata.setTransportCredentials( credentials = new TlsCredentials(GOOD_CERT, GOOD_IPV6, "goo.example");
new TlsCredentials(GOOD_CERT, GOOD_IPV6, "goo.example"));
doSuccessfulTest("login_valid.xml"); doSuccessfulTest("login_valid.xml");
} }
@ -87,28 +85,28 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
.setIpAddressWhitelist(ImmutableList.of( .setIpAddressWhitelist(ImmutableList.of(
CidrAddressBlock.create("192.168.1.255/24"))) CidrAddressBlock.create("192.168.1.255/24")))
.build()); .build());
sessionMetadata.setTransportCredentials(new TlsCredentials(GOOD_CERT, GOOD_IP, "goo.example")); credentials = new TlsCredentials(GOOD_CERT, GOOD_IP, "goo.example");
doSuccessfulTest("login_valid.xml"); doSuccessfulTest("login_valid.xml");
} }
@Test @Test
public void testFailure_incorrectClientCertificateHash() throws Exception { public void testFailure_incorrectClientCertificateHash() throws Exception {
persistResource(getRegistrarBuilder().build()); persistResource(getRegistrarBuilder().build());
sessionMetadata.setTransportCredentials(new TlsCredentials(BAD_CERT, GOOD_IP, "goo.example")); credentials = new TlsCredentials(BAD_CERT, GOOD_IP, "goo.example");
doFailingTest("login_valid.xml", BadRegistrarCertificateException.class); doFailingTest("login_valid.xml", BadRegistrarCertificateException.class);
} }
@Test @Test
public void testFailure_missingClientCertificateHash() throws Exception { public void testFailure_missingClientCertificateHash() throws Exception {
persistResource(getRegistrarBuilder().build()); persistResource(getRegistrarBuilder().build());
sessionMetadata.setTransportCredentials(new TlsCredentials(null, GOOD_IP, "goo.example")); credentials = new TlsCredentials(null, GOOD_IP, "goo.example");
doFailingTest("login_valid.xml", MissingRegistrarCertificateException.class); doFailingTest("login_valid.xml", MissingRegistrarCertificateException.class);
} }
@Test @Test
public void testFailure_noSniAndCertRequired() throws Exception { public void testFailure_noSniAndCertRequired() throws Exception {
persistResource(getRegistrarBuilder().build()); persistResource(getRegistrarBuilder().build());
sessionMetadata.setTransportCredentials(new TlsCredentials(null, GOOD_IP, null)); credentials = new TlsCredentials(null, GOOD_IP, null);
doFailingTest("login_valid.xml", NoSniException.class); doFailingTest("login_valid.xml", NoSniException.class);
} }
@ -120,8 +118,7 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
CidrAddressBlock.create(InetAddresses.forString("192.168.1.1"), 32), CidrAddressBlock.create(InetAddresses.forString("192.168.1.1"), 32),
CidrAddressBlock.create(InetAddresses.forString("2001:db8::1"), 128))) CidrAddressBlock.create(InetAddresses.forString("2001:db8::1"), 128)))
.build()); .build());
sessionMetadata.setTransportCredentials( credentials = new TlsCredentials(GOOD_CERT, Optional.<String>absent(), "goo.example");
new TlsCredentials(GOOD_CERT, Optional.<String>absent(), "goo.example"));
doFailingTest("login_valid.xml", BadRegistrarIpAddressException.class); doFailingTest("login_valid.xml", BadRegistrarIpAddressException.class);
} }
@ -133,7 +130,7 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
CidrAddressBlock.create(InetAddresses.forString("192.168.1.1"), 32), CidrAddressBlock.create(InetAddresses.forString("192.168.1.1"), 32),
CidrAddressBlock.create(InetAddresses.forString("2001:db8::1"), 128))) CidrAddressBlock.create(InetAddresses.forString("2001:db8::1"), 128)))
.build()); .build());
sessionMetadata.setTransportCredentials(new TlsCredentials(GOOD_CERT, BAD_IP, "goo.example")); credentials = new TlsCredentials(GOOD_CERT, BAD_IP, "goo.example");
doFailingTest("login_valid.xml", BadRegistrarIpAddressException.class); doFailingTest("login_valid.xml", BadRegistrarIpAddressException.class);
} }
@ -145,7 +142,7 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
CidrAddressBlock.create(InetAddresses.forString("192.168.1.1"), 32), CidrAddressBlock.create(InetAddresses.forString("192.168.1.1"), 32),
CidrAddressBlock.create(InetAddresses.forString("2001:db8::1"), 128))) CidrAddressBlock.create(InetAddresses.forString("2001:db8::1"), 128)))
.build()); .build());
sessionMetadata.setTransportCredentials(new TlsCredentials(GOOD_CERT, BAD_IPV6, "goo.example")); credentials = new TlsCredentials(GOOD_CERT, BAD_IPV6, "goo.example");
doFailingTest("login_valid.xml", BadRegistrarIpAddressException.class); doFailingTest("login_valid.xml", BadRegistrarIpAddressException.class);
} }
} }

View file

@ -31,6 +31,7 @@ import static org.joda.time.Duration.standardDays;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import google.registry.flows.FlowRunner; import google.registry.flows.FlowRunner;
import google.registry.flows.PasswordOnlyTransportCredentials;
import google.registry.flows.SessionMetadata; import google.registry.flows.SessionMetadata;
import google.registry.model.domain.DomainResource; import google.registry.model.domain.DomainResource;
import google.registry.model.eppcommon.Trid; import google.registry.model.eppcommon.Trid;
@ -84,6 +85,7 @@ public class EppResourceUtilsTest {
eppLoader.getEpp(), eppLoader.getEpp(),
Trid.create(null, "server-trid"), Trid.create(null, "server-trid"),
sessionMetadata, sessionMetadata,
new PasswordOnlyTransportCredentials(),
"<xml></xml>".getBytes(), "<xml></xml>".getBytes(),
null, null,
clock) clock)