Make OutgoingEmailValidator a Singleton

Make the class a Singleton so that it can be used by classes that get
it injected, rather than calling its methods statically.

This is a preparatory step to allowing the validator to get the gerrit
configuration injected, which will allow to disable validation using
this validator.

Change-Id: If5c7cc6d2c034b9ed16b1358b4d1d186d57daf98
This commit is contained in:
David Pursehouse
2017-01-28 22:03:01 +09:00
parent d2676e215f
commit c7e6021e8b
8 changed files with 46 additions and 14 deletions

View File

@@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assert_;
import com.google.gerrit.acceptance.AbstractDaemonTest; import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.server.mail.send.OutgoingEmailValidator; import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
import com.google.inject.Inject;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@@ -27,9 +28,11 @@ import org.junit.Test;
public class EmailValidatorIT extends AbstractDaemonTest { public class EmailValidatorIT extends AbstractDaemonTest {
private static final String UNSUPPORTED_PREFIX = "#! "; private static final String UNSUPPORTED_PREFIX = "#! ";
@Inject private OutgoingEmailValidator validator;
@Test @Test
public void validateLocalDomain() throws Exception { public void validateLocalDomain() throws Exception {
assertThat(OutgoingEmailValidator.isValid("foo@bar.local")).isTrue(); assertThat(validator.isValid("foo@bar.local")).isTrue();
} }
@Test @Test
@@ -49,13 +52,13 @@ public class EmailValidatorIT extends AbstractDaemonTest {
String test = "test@example." + tld.toLowerCase().substring(UNSUPPORTED_PREFIX.length()); String test = "test@example." + tld.toLowerCase().substring(UNSUPPORTED_PREFIX.length());
assert_() assert_()
.withFailureMessage("expected invalid TLD \"" + test + "\"") .withFailureMessage("expected invalid TLD \"" + test + "\"")
.that(OutgoingEmailValidator.isValid(test)) .that(validator.isValid(test))
.isFalse(); .isFalse();
} else { } else {
String test = "test@example." + tld.toLowerCase(); String test = "test@example." + tld.toLowerCase();
assert_() assert_()
.withFailureMessage("failed to validate TLD \"" + test + "\"") .withFailureMessage("failed to validate TLD \"" + test + "\"")
.that(OutgoingEmailValidator.isValid(test)) .that(validator.isValid(test))
.isTrue(); .isTrue();
} }
} }

View File

@@ -77,6 +77,7 @@ public class CreateAccount implements RestModifyView<TopLevelResource, AccountIn
private final AuditService auditService; private final AuditService auditService;
private final ExternalIds externalIds; private final ExternalIds externalIds;
private final ExternalIdsUpdate.User externalIdsUpdateFactory; private final ExternalIdsUpdate.User externalIdsUpdateFactory;
private final OutgoingEmailValidator validator;
private final String username; private final String username;
@Inject @Inject
@@ -95,6 +96,7 @@ public class CreateAccount implements RestModifyView<TopLevelResource, AccountIn
AuditService auditService, AuditService auditService,
ExternalIds externalIds, ExternalIds externalIds,
ExternalIdsUpdate.User externalIdsUpdateFactory, ExternalIdsUpdate.User externalIdsUpdateFactory,
OutgoingEmailValidator validator,
@Assisted String username) { @Assisted String username) {
this.db = db; this.db = db;
this.currentUser = currentUser; this.currentUser = currentUser;
@@ -110,6 +112,7 @@ public class CreateAccount implements RestModifyView<TopLevelResource, AccountIn
this.auditService = auditService; this.auditService = auditService;
this.externalIds = externalIds; this.externalIds = externalIds;
this.externalIdsUpdateFactory = externalIdsUpdateFactory; this.externalIdsUpdateFactory = externalIdsUpdateFactory;
this.validator = validator;
this.username = username; this.username = username;
} }
@@ -141,7 +144,7 @@ public class CreateAccount implements RestModifyView<TopLevelResource, AccountIn
if (externalIds.get(db, ExternalId.Key.create(SCHEME_MAILTO, input.email)) != null) { if (externalIds.get(db, ExternalId.Key.create(SCHEME_MAILTO, input.email)) != null) {
throw new UnprocessableEntityException("email '" + input.email + "' already exists"); throw new UnprocessableEntityException("email '" + input.email + "' already exists");
} }
if (!OutgoingEmailValidator.isValid(input.email)) { if (!validator.isValid(input.email)) {
throw new BadRequestException("invalid email address"); throw new BadRequestException("invalid email address");
} }
} }

View File

@@ -57,6 +57,7 @@ public class CreateEmail implements RestModifyView<AccountResource, EmailInput>
private final AccountManager accountManager; private final AccountManager accountManager;
private final RegisterNewEmailSender.Factory registerNewEmailFactory; private final RegisterNewEmailSender.Factory registerNewEmailFactory;
private final PutPreferred putPreferred; private final PutPreferred putPreferred;
private final OutgoingEmailValidator validator;
private final String email; private final String email;
private final boolean isDevMode; private final boolean isDevMode;
@@ -69,6 +70,7 @@ public class CreateEmail implements RestModifyView<AccountResource, EmailInput>
AccountManager accountManager, AccountManager accountManager,
RegisterNewEmailSender.Factory registerNewEmailFactory, RegisterNewEmailSender.Factory registerNewEmailFactory,
PutPreferred putPreferred, PutPreferred putPreferred,
OutgoingEmailValidator validator,
@Assisted String email) { @Assisted String email) {
this.self = self; this.self = self;
this.realm = realm; this.realm = realm;
@@ -76,6 +78,7 @@ public class CreateEmail implements RestModifyView<AccountResource, EmailInput>
this.accountManager = accountManager; this.accountManager = accountManager;
this.registerNewEmailFactory = registerNewEmailFactory; this.registerNewEmailFactory = registerNewEmailFactory;
this.putPreferred = putPreferred; this.putPreferred = putPreferred;
this.validator = validator;
this.email = email; this.email = email;
this.isDevMode = authConfig.getAuthType() == DEVELOPMENT_BECOME_ANY_ACCOUNT; this.isDevMode = authConfig.getAuthType() == DEVELOPMENT_BECOME_ANY_ACCOUNT;
} }
@@ -93,7 +96,7 @@ public class CreateEmail implements RestModifyView<AccountResource, EmailInput>
input = new EmailInput(); input = new EmailInput();
} }
if (!OutgoingEmailValidator.isValid(email)) { if (!validator.isValid(email)) {
throw new BadRequestException("invalid email address"); throw new BadRequestException("invalid email address");
} }

View File

@@ -44,13 +44,18 @@ public class ExternalIdsConsistencyChecker {
private final GitRepositoryManager repoManager; private final GitRepositoryManager repoManager;
private final AllUsersName allUsers; private final AllUsersName allUsers;
private final AccountCache accountCache; private final AccountCache accountCache;
private final OutgoingEmailValidator validator;
@Inject @Inject
ExternalIdsConsistencyChecker( ExternalIdsConsistencyChecker(
GitRepositoryManager repoManager, AllUsersName allUsers, AccountCache accountCache) { GitRepositoryManager repoManager,
AllUsersName allUsers,
AccountCache accountCache,
OutgoingEmailValidator validator) {
this.repoManager = repoManager; this.repoManager = repoManager;
this.allUsers = allUsers; this.allUsers = allUsers;
this.accountCache = accountCache; this.accountCache = accountCache;
this.validator = validator;
} }
public List<ConsistencyProblemInfo> check() throws IOException { public List<ConsistencyProblemInfo> check() throws IOException {
@@ -123,7 +128,7 @@ public class ExternalIdsConsistencyChecker {
problems); problems);
} }
if (extId.email() != null && !OutgoingEmailValidator.isValid(extId.email())) { if (extId.email() != null && !validator.isValid(extId.email())) {
addError( addError(
String.format( String.format(
"External ID '%s' has an invalid email: %s", extId.key().get(), extId.email()), "External ID '%s' has an invalid email: %s", extId.key().get(), extId.email()),

View File

@@ -101,6 +101,7 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
private final ProjectCache projectCache; private final ProjectCache projectCache;
private final Provider<AnonymousUser> anonymousProvider; private final Provider<AnonymousUser> anonymousProvider;
private final PostReviewersOp.Factory postReviewersOpFactory; private final PostReviewersOp.Factory postReviewersOpFactory;
private final OutgoingEmailValidator validator;
@Inject @Inject
PostReviewers( PostReviewers(
@@ -120,7 +121,8 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
NotifyUtil notifyUtil, NotifyUtil notifyUtil,
ProjectCache projectCache, ProjectCache projectCache,
Provider<AnonymousUser> anonymousProvider, Provider<AnonymousUser> anonymousProvider,
PostReviewersOp.Factory postReviewersOpFactory) { PostReviewersOp.Factory postReviewersOpFactory,
OutgoingEmailValidator validator) {
this.accounts = accounts; this.accounts = accounts;
this.reviewerFactory = reviewerFactory; this.reviewerFactory = reviewerFactory;
this.permissionBackend = permissionBackend; this.permissionBackend = permissionBackend;
@@ -138,6 +140,7 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
this.projectCache = projectCache; this.projectCache = projectCache;
this.anonymousProvider = anonymousProvider; this.anonymousProvider = anonymousProvider;
this.postReviewersOpFactory = postReviewersOpFactory; this.postReviewersOpFactory = postReviewersOpFactory;
this.validator = validator;
} }
@Override @Override
@@ -291,7 +294,7 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new UnprocessableEntityException(String.format("email invalid %s", reviewer)); throw new UnprocessableEntityException(String.format("email invalid %s", reviewer));
} }
if (!OutgoingEmailValidator.isValid(adr.getEmail())) { if (!validator.isValid(adr.getEmail())) {
throw new UnprocessableEntityException(String.format("email invalid %s", reviewer)); throw new UnprocessableEntityException(String.format("email invalid %s", reviewer));
} }
return new Addition( return new Addition(

View File

@@ -80,6 +80,7 @@ public class EmailArguments {
final DynamicSet<OutgoingEmailValidationListener> outgoingEmailValidationListeners; final DynamicSet<OutgoingEmailValidationListener> outgoingEmailValidationListeners;
final StarredChangesUtil starredChangesUtil; final StarredChangesUtil starredChangesUtil;
final Provider<InternalAccountQuery> accountQueryProvider; final Provider<InternalAccountQuery> accountQueryProvider;
final OutgoingEmailValidator validator;
@Inject @Inject
EmailArguments( EmailArguments(
@@ -111,7 +112,8 @@ public class EmailArguments {
SitePaths site, SitePaths site,
DynamicSet<OutgoingEmailValidationListener> outgoingEmailValidationListeners, DynamicSet<OutgoingEmailValidationListener> outgoingEmailValidationListeners,
StarredChangesUtil starredChangesUtil, StarredChangesUtil starredChangesUtil,
Provider<InternalAccountQuery> accountQueryProvider) { Provider<InternalAccountQuery> accountQueryProvider,
OutgoingEmailValidator validator) {
this.server = server; this.server = server;
this.projectCache = projectCache; this.projectCache = projectCache;
this.groupBackend = groupBackend; this.groupBackend = groupBackend;
@@ -141,5 +143,6 @@ public class EmailArguments {
this.outgoingEmailValidationListeners = outgoingEmailValidationListeners; this.outgoingEmailValidationListeners = outgoingEmailValidationListeners;
this.starredChangesUtil = starredChangesUtil; this.starredChangesUtil = starredChangesUtil;
this.accountQueryProvider = accountQueryProvider; this.accountQueryProvider = accountQueryProvider;
this.validator = validator;
} }
} }

View File

@@ -478,7 +478,7 @@ public abstract class OutgoingEmail {
/** Schedule delivery of this message to the given account. */ /** Schedule delivery of this message to the given account. */
protected void add(final RecipientType rt, final Address addr) { protected void add(final RecipientType rt, final Address addr) {
if (addr != null && addr.getEmail() != null && addr.getEmail().length() > 0) { if (addr != null && addr.getEmail() != null && addr.getEmail().length() > 0) {
if (!OutgoingEmailValidator.isValid(addr.getEmail())) { if (!args.validator.isValid(addr.getEmail())) {
log.warn("Not emailing " + addr.getEmail() + " (invalid email address)"); log.warn("Not emailing " + addr.getEmail() + " (invalid email address)");
} else if (!args.emailSender.canEmail(addr.getEmail())) { } else if (!args.emailSender.canEmail(addr.getEmail())) {
log.warn("Not emailing " + addr.getEmail() + " (prohibited by allowrcpt)"); log.warn("Not emailing " + addr.getEmail() + " (prohibited by allowrcpt)");

View File

@@ -16,15 +16,27 @@ package com.google.gerrit.server.mail.send;
import static org.apache.commons.validator.routines.DomainValidator.ArrayType.GENERIC_PLUS; import static org.apache.commons.validator.routines.DomainValidator.ArrayType.GENERIC_PLUS;
import com.google.inject.Singleton;
import org.apache.commons.validator.routines.DomainValidator; import org.apache.commons.validator.routines.DomainValidator;
import org.apache.commons.validator.routines.EmailValidator; import org.apache.commons.validator.routines.EmailValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
public class OutgoingEmailValidator { public class OutgoingEmailValidator {
static { private static final Logger log = LoggerFactory.getLogger(OutgoingEmailValidator.class);
OutgoingEmailValidator() {
try {
DomainValidator.updateTLDOverride(GENERIC_PLUS, new String[] {"local"}); DomainValidator.updateTLDOverride(GENERIC_PLUS, new String[] {"local"});
} catch (IllegalStateException e) {
// Should only happen in tests, where the OutgoingEmailValidator
// is instantiated repeatedly.
log.warn("Failed to update TLD override: " + e.getMessage());
}
} }
public static boolean isValid(String addr) { public boolean isValid(String addr) {
return EmailValidator.getInstance(true, true).isValid(addr); return EmailValidator.getInstance(true, true).isValid(addr);
} }
} }