google-nomulus/java/google/registry/flows/session/LoginFlow.java
mcilwain c0f8da0c6e Switch from Guava Optionals to Java 8 Optionals
This was a surprisingly involved change. Some of the difficulties included
java.util.Optional purposely not being Serializable (so I had to move a
few Optionals in mapreduce classes to @Nullable) and having to add the Truth
Java8 extension library for assertion support.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=171863777
2017-10-24 16:53:47 -04:00

191 lines
8.1 KiB
Java

// Copyright 2017 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.session;
import static com.google.common.collect.Sets.difference;
import static google.registry.util.CollectionUtils.nullToEmpty;
import com.google.common.collect.ImmutableSet;
import google.registry.flows.EppException;
import google.registry.flows.EppException.AuthenticationErrorClosingConnectionException;
import google.registry.flows.EppException.AuthenticationErrorException;
import google.registry.flows.EppException.AuthorizationErrorException;
import google.registry.flows.EppException.CommandUseErrorException;
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
import google.registry.flows.EppException.UnimplementedExtensionException;
import google.registry.flows.EppException.UnimplementedObjectServiceException;
import google.registry.flows.EppException.UnimplementedOptionException;
import google.registry.flows.ExtensionManager;
import google.registry.flows.Flow;
import google.registry.flows.FlowModule.ClientId;
import google.registry.flows.SessionMetadata;
import google.registry.flows.TransportCredentials;
import google.registry.model.eppcommon.ProtocolDefinition;
import google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension;
import google.registry.model.eppinput.EppInput;
import google.registry.model.eppinput.EppInput.Login;
import google.registry.model.eppinput.EppInput.Options;
import google.registry.model.eppinput.EppInput.Services;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.registrar.Registrar;
import google.registry.util.FormattingLogger;
import java.util.Optional;
import java.util.Set;
import javax.inject.Inject;
/**
* An EPP flow for login.
*
* @error {@link google.registry.flows.EppException.UnimplementedExtensionException}
* @error {@link google.registry.flows.EppException.UnimplementedObjectServiceException}
* @error {@link google.registry.flows.EppException.UnimplementedProtocolVersionException}
* @error {@link google.registry.flows.GaeUserCredentials.BadGaeUserIdException}
* @error {@link google.registry.flows.GaeUserCredentials.UserNotLoggedInException}
* @error {@link google.registry.flows.TlsCredentials.BadRegistrarCertificateException}
* @error {@link google.registry.flows.TlsCredentials.BadRegistrarIpAddressException}
* @error {@link google.registry.flows.TlsCredentials.MissingRegistrarCertificateException}
* @error {@link google.registry.flows.TlsCredentials.NoSniException}
* @error {@link google.registry.flows.TransportCredentials.BadRegistrarPasswordException}
* @error {@link LoginFlow.AlreadyLoggedInException}
* @error {@link LoginFlow.BadRegistrarClientIdException}
* @error {@link LoginFlow.TooManyFailedLoginsException}
* @error {@link LoginFlow.PasswordChangesNotSupportedException}
* @error {@link LoginFlow.RegistrarAccountNotActiveException}
* @error {@link LoginFlow.UnsupportedLanguageException}
*/
public class LoginFlow implements Flow {
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
/** Maximum number of failed login attempts allowed per connection. */
private static final int MAX_FAILED_LOGIN_ATTEMPTS_PER_CONNECTION = 3;
@Inject ExtensionManager extensionManager;
@Inject EppInput eppInput;
@Inject SessionMetadata sessionMetadata;
@Inject TransportCredentials credentials;
@Inject @ClientId String clientId;
@Inject EppResponse.Builder responseBuilder;
@Inject LoginFlow() {}
/** Run the flow and log errors. */
@Override
public final EppResponse run() throws EppException {
try {
return runWithoutLogging();
} catch (EppException e) {
logger.warning("Login failed: " + e.getMessage());
throw e;
}
}
/** Run the flow without bothering to log errors. The {@link #run} method will do that for us. */
public final EppResponse runWithoutLogging() throws EppException {
extensionManager.validate(); // There are no legal extensions for this flow.
Login login = (Login) eppInput.getCommandWrapper().getCommand();
if (!clientId.isEmpty()) {
throw new AlreadyLoggedInException();
}
Options options = login.getOptions();
if (!ProtocolDefinition.LANGUAGE.equals(options.getLanguage())) {
throw new UnsupportedLanguageException();
}
Services services = login.getServices();
Set<String> unsupportedObjectServices = difference(
nullToEmpty(services.getObjectServices()),
ProtocolDefinition.SUPPORTED_OBJECT_SERVICES);
if (!unsupportedObjectServices.isEmpty()) {
throw new UnimplementedObjectServiceException();
}
ImmutableSet.Builder<String> serviceExtensionUrisBuilder = new ImmutableSet.Builder<>();
for (String uri : nullToEmpty(services.getServiceExtensions())) {
ServiceExtension serviceExtension = ProtocolDefinition.getServiceExtensionFromUri(uri);
if (serviceExtension == null) {
throw new UnimplementedExtensionException();
}
serviceExtensionUrisBuilder.add(uri);
}
Optional<Registrar> registrar = Registrar.loadByClientIdCached(login.getClientId());
if (!registrar.isPresent()) {
throw new BadRegistrarClientIdException(login.getClientId());
}
// AuthenticationErrorExceptions will propagate up through here.
try {
credentials.validate(registrar.get(), login.getPassword());
} catch (AuthenticationErrorException e) {
sessionMetadata.incrementFailedLoginAttempts();
if (sessionMetadata.getFailedLoginAttempts() > MAX_FAILED_LOGIN_ATTEMPTS_PER_CONNECTION) {
throw new TooManyFailedLoginsException();
} else {
throw e;
}
}
if (registrar.get().getState().equals(Registrar.State.PENDING)) {
throw new RegistrarAccountNotActiveException();
}
if (login.getNewPassword() != null) { // We don't support in-band password changes.
throw new PasswordChangesNotSupportedException();
}
// We are in!
sessionMetadata.resetFailedLoginAttempts();
sessionMetadata.setClientId(login.getClientId());
sessionMetadata.setServiceExtensionUris(serviceExtensionUrisBuilder.build());
return responseBuilder.build();
}
/** Registrar with this client ID could not be found. */
static class BadRegistrarClientIdException extends AuthenticationErrorException {
public BadRegistrarClientIdException(String clientId) {
super("Registrar with this client ID could not be found: " + clientId);
}
}
/** Registrar login failed too many times. */
static class TooManyFailedLoginsException extends AuthenticationErrorClosingConnectionException {
public TooManyFailedLoginsException() {
super("Registrar login failed too many times");
}
}
/** Registrar account is not active. */
static class RegistrarAccountNotActiveException extends AuthorizationErrorException {
public RegistrarAccountNotActiveException() {
super("Registrar account is not active");
}
}
/** Registrar is already logged in. */
static class AlreadyLoggedInException extends CommandUseErrorException {
public AlreadyLoggedInException() {
super("Registrar is already logged in");
}
}
/** Specified language is not supported. */
static class UnsupportedLanguageException extends ParameterValuePolicyErrorException {
public UnsupportedLanguageException() {
super("Specified language is not supported");
}
}
/** In-band password changes are not supported. */
static class PasswordChangesNotSupportedException extends UnimplementedOptionException {
public PasswordChangesNotSupportedException() {
super("In-band password changes are not supported");
}
}
}