Use the registrar client ID and abuse email address in Spec11

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=244899045
This commit is contained in:
gbrodman 2019-04-23 12:04:40 -07:00 committed by jianglai
parent 9f979790e4
commit 2b12ca42f5
9 changed files with 87 additions and 27 deletions

View file

@ -12,6 +12,7 @@ java_library(
"//java/google/registry/config", "//java/google/registry/config",
"//java/google/registry/gcs", "//java/google/registry/gcs",
"//java/google/registry/keyring/api", "//java/google/registry/keyring/api",
"//java/google/registry/model",
"//java/google/registry/reporting", "//java/google/registry/reporting",
"//java/google/registry/reporting/spec11/soy:soy_java_wrappers", "//java/google/registry/reporting/spec11/soy:soy_java_wrappers",
"//java/google/registry/request", "//java/google/registry/request",

View file

@ -52,8 +52,8 @@ import org.json.JSONException;
* Retries until a {@code Dataflow} job with a given {@code jobId} completes, continuing the Spec11 * Retries until a {@code Dataflow} job with a given {@code jobId} completes, continuing the Spec11
* pipeline accordingly. * pipeline accordingly.
* *
* <p>This calls {@link Spec11EmailUtils#emailSpec11Reports(SoyTemplateInfo, String, Set)} on * <p>This calls {@link Spec11EmailUtils#emailSpec11Reports(LocalDate, SoyTemplateInfo, String,
* success or {@link Spec11EmailUtils#sendAlertEmail(String, String)} on failure. * Set)} on success or {@link Spec11EmailUtils#sendAlertEmail(String, String)} on failure.
*/ */
@Action( @Action(
service = Action.Service.BACKEND, service = Action.Service.BACKEND,
@ -193,7 +193,7 @@ public class PublishSpec11ReportAction implements Runnable {
// Group by email address then flat-map all of the ThreatMatch objects together // Group by email address then flat-map all of the ThreatMatch objects together
return ImmutableMap.copyOf( return ImmutableMap.copyOf(
Maps.transformValues( Maps.transformValues(
Multimaps.index(registrarThreatMatches, RegistrarThreatMatches::registrarEmailAddress) Multimaps.index(registrarThreatMatches, RegistrarThreatMatches::clientId)
.asMap(), .asMap(),
registrarThreatMatchesCollection -> registrarThreatMatchesCollection ->
registrarThreatMatchesCollection.stream() registrarThreatMatchesCollection.stream()

View file

@ -23,13 +23,11 @@ import java.util.List;
@AutoValue @AutoValue
public abstract class RegistrarThreatMatches { public abstract class RegistrarThreatMatches {
public abstract String registrarEmailAddress(); public abstract String clientId();
public abstract ImmutableList<ThreatMatch> threatMatches(); public abstract ImmutableList<ThreatMatch> threatMatches();
static RegistrarThreatMatches create( static RegistrarThreatMatches create(String clientId, List<ThreatMatch> threatMatches) {
String registrarEmailAddress, List<ThreatMatch> threatMatches) { return new AutoValue_RegistrarThreatMatches(clientId, ImmutableList.copyOf(threatMatches));
return new AutoValue_RegistrarThreatMatches(
registrarEmailAddress, ImmutableList.copyOf(threatMatches));
} }
} }

View file

@ -27,6 +27,8 @@ import com.google.template.soy.parseinfo.SoyTemplateInfo;
import com.google.template.soy.tofu.SoyTofu; import com.google.template.soy.tofu.SoyTofu;
import com.google.template.soy.tofu.SoyTofu.Renderer; import com.google.template.soy.tofu.SoyTofu.Renderer;
import google.registry.config.RegistryConfig.Config; import google.registry.config.RegistryConfig.Config;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarContact;
import google.registry.reporting.spec11.soy.Spec11EmailSoyInfo; import google.registry.reporting.spec11.soy.Spec11EmailSoyInfo;
import google.registry.util.EmailMessage; import google.registry.util.EmailMessage;
import google.registry.util.SendEmailService; import google.registry.util.SendEmailService;
@ -105,11 +107,10 @@ public class Spec11EmailUtils {
String.format("Spec11 Emailing Failure %s", date), String.format("Spec11 Emailing Failure %s", date),
String.format("Emailing Spec11 reports failed due to %s", firstThrowable.getMessage())); String.format("Emailing Spec11 reports failed due to %s", firstThrowable.getMessage()));
for (int i = 1; i < failedMatches.size(); i++) { for (int i = 1; i < failedMatches.size(); i++) {
// TODO(b/129401965): Use only client IDs in this message
logger.atSevere().withCause(failedMatchesList.get(i).getValue()).log( logger.atSevere().withCause(failedMatchesList.get(i).getValue()).log(
"Additional exception thrown when sending email to registrar %s, in addition to the" "Additional exception thrown when sending email to registrar %s, in addition to the"
+ " re-thrown exception", + " re-thrown exception",
failedMatchesList.get(i).getKey().registrarEmailAddress()); failedMatchesList.get(i).getKey().clientId());
} }
throw new RuntimeException( throw new RuntimeException(
"Emailing Spec11 reports failed, first exception:", firstThrowable); "Emailing Spec11 reports failed, first exception:", firstThrowable);
@ -131,7 +132,7 @@ public class Spec11EmailUtils {
.setBody(getContent(date, soyTemplateInfo, registrarThreatMatches)) .setBody(getContent(date, soyTemplateInfo, registrarThreatMatches))
.setContentType(MediaType.HTML_UTF_8) .setContentType(MediaType.HTML_UTF_8)
.setFrom(outgoingEmailAddress) .setFrom(outgoingEmailAddress)
.addRecipient(new InternetAddress(registrarThreatMatches.registrarEmailAddress())) .addRecipient(getEmailAddressForRegistrar(registrarThreatMatches.clientId()))
.setBcc(spec11ReplyToAddress) .setBcc(spec11ReplyToAddress)
.build()); .build());
} }
@ -176,4 +177,19 @@ public class Spec11EmailUtils {
throw new RuntimeException("The spec11 alert e-mail system failed.", e); throw new RuntimeException("The spec11 alert e-mail system failed.", e);
} }
} }
private InternetAddress getEmailAddressForRegistrar(String clientId) throws MessagingException {
// Attempt to use the registrar's WHOIS abuse contact, then fall back to the regular address.
Registrar registrar =
Registrar.loadByClientIdCached(clientId)
.orElseThrow(
() ->
new IllegalArgumentException(
String.format("Could not find registrar %s", clientId)));
return new InternetAddress(
registrar
.getWhoisAbuseContact()
.map(RegistrarContact::getEmailAddress)
.orElse(registrar.getEmailAddress()));
}
} }

View file

@ -98,12 +98,12 @@ public class Spec11RegistrarThreatMatchesParser {
private RegistrarThreatMatches parseRegistrarThreatMatch(String line) throws JSONException { private RegistrarThreatMatches parseRegistrarThreatMatch(String line) throws JSONException {
JSONObject reportJSON = new JSONObject(line); JSONObject reportJSON = new JSONObject(line);
String registrarEmail = reportJSON.getString(Spec11Pipeline.REGISTRAR_EMAIL_FIELD); String clientId = reportJSON.getString(Spec11Pipeline.REGISTRAR_CLIENT_ID_FIELD);
JSONArray threatMatchesArray = reportJSON.getJSONArray(Spec11Pipeline.THREAT_MATCHES_FIELD); JSONArray threatMatchesArray = reportJSON.getJSONArray(Spec11Pipeline.THREAT_MATCHES_FIELD);
ImmutableList.Builder<ThreatMatch> threatMatches = ImmutableList.builder(); ImmutableList.Builder<ThreatMatch> threatMatches = ImmutableList.builder();
for (int i = 0; i < threatMatchesArray.length(); i++) { for (int i = 0; i < threatMatchesArray.length(); i++) {
threatMatches.add(ThreatMatch.fromJSON(threatMatchesArray.getJSONObject(i))); threatMatches.add(ThreatMatch.fromJSON(threatMatchesArray.getJSONObject(i)));
} }
return RegistrarThreatMatches.create(registrarEmail, threatMatches.build()); return RegistrarThreatMatches.create(clientId, threatMatches.build());
} }
} }

View file

@ -177,7 +177,7 @@ public class PublishSpec11ReportActionTest {
RegistrarThreatMatches firstMatches = getMatchA(); RegistrarThreatMatches firstMatches = getMatchA();
ImmutableList<ThreatMatch> secondMatchList = getMatchB().threatMatches(); ImmutableList<ThreatMatch> secondMatchList = getMatchB().threatMatches();
RegistrarThreatMatches secondMatches = RegistrarThreatMatches secondMatches =
RegistrarThreatMatches.create("a@fake.com", secondMatchList); RegistrarThreatMatches.create("TheRegistrar", secondMatchList);
when(parser.getRegistrarThreatMatches(date)) when(parser.getRegistrarThreatMatches(date))
.thenReturn(ImmutableSet.of(firstMatches, secondMatches)); .thenReturn(ImmutableSet.of(firstMatches, secondMatches));
expectedJob.setCurrentState("JOB_STATE_DONE"); expectedJob.setCurrentState("JOB_STATE_DONE");
@ -185,7 +185,7 @@ public class PublishSpec11ReportActionTest {
ImmutableSet<RegistrarThreatMatches> expectedMatchSet = ImmutableSet<RegistrarThreatMatches> expectedMatchSet =
ImmutableSet.of( ImmutableSet.of(
RegistrarThreatMatches.create( RegistrarThreatMatches.create(
"a@fake.com", "TheRegistrar",
ImmutableList.<ThreatMatch>builder() ImmutableList.<ThreatMatch>builder()
.addAll(firstMatches.threatMatches()) .addAll(firstMatches.threatMatches())
.addAll(secondMatchList) .addAll(secondMatchList)

View file

@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.reporting.spec11.Spec11RegistrarThreatMatchesParserTest.getMatchA; import static google.registry.reporting.spec11.Spec11RegistrarThreatMatchesParserTest.getMatchA;
import static google.registry.reporting.spec11.Spec11RegistrarThreatMatchesParserTest.getMatchB; import static google.registry.reporting.spec11.Spec11RegistrarThreatMatchesParserTest.getMatchB;
import static google.registry.reporting.spec11.Spec11RegistrarThreatMatchesParserTest.sampleThreatMatches; import static google.registry.reporting.spec11.Spec11RegistrarThreatMatchesParserTest.sampleThreatMatches;
import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.testing.JUnitBackports.assertThrows; import static google.registry.testing.JUnitBackports.assertThrows;
import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@ -26,8 +27,10 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.MediaType; import com.google.common.net.MediaType;
import google.registry.reporting.spec11.soy.Spec11EmailSoyInfo; import google.registry.reporting.spec11.soy.Spec11EmailSoyInfo;
import google.registry.testing.AppEngineRule;
import google.registry.util.EmailMessage; import google.registry.util.EmailMessage;
import google.registry.util.SendEmailService; import google.registry.util.SendEmailService;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
@ -37,6 +40,7 @@ import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress; import javax.mail.internet.InternetAddress;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.JUnit4; import org.junit.runners.JUnit4;
@ -106,6 +110,8 @@ public class Spec11EmailUtilsTest {
+ "<p>If you have any questions regarding this notice, please contact " + "<p>If you have any questions regarding this notice, please contact "
+ "my-reply-to@test.com.</p>"; + "my-reply-to@test.com.</p>";
@Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build();
private SendEmailService emailService; private SendEmailService emailService;
private Spec11EmailUtils emailUtils; private Spec11EmailUtils emailUtils;
private Spec11RegistrarThreatMatchesParser parser; private Spec11RegistrarThreatMatchesParser parser;
@ -141,7 +147,7 @@ public class Spec11EmailUtilsTest {
validateMessage( validateMessage(
capturedContents.get(0), capturedContents.get(0),
"my-sender@test.com", "my-sender@test.com",
"a@fake.com", "the.registrar@example.com",
Optional.of("my-reply-to@test.com"), Optional.of("my-reply-to@test.com"),
"Super Cool Registry Monthly Threat Detector [2018-07-15]", "Super Cool Registry Monthly Threat Detector [2018-07-15]",
String.format(MONTHLY_EMAIL_FORMAT, "<tr><td>a.com</td><td>MALWARE</td></tr>"), String.format(MONTHLY_EMAIL_FORMAT, "<tr><td>a.com</td><td>MALWARE</td></tr>"),
@ -149,7 +155,7 @@ public class Spec11EmailUtilsTest {
validateMessage( validateMessage(
capturedContents.get(1), capturedContents.get(1),
"my-sender@test.com", "my-sender@test.com",
"b@fake.com", "new.registrar@example.com",
Optional.of("my-reply-to@test.com"), Optional.of("my-reply-to@test.com"),
"Super Cool Registry Monthly Threat Detector [2018-07-15]", "Super Cool Registry Monthly Threat Detector [2018-07-15]",
String.format( String.format(
@ -179,7 +185,7 @@ public class Spec11EmailUtilsTest {
validateMessage( validateMessage(
capturedMessages.get(0), capturedMessages.get(0),
"my-sender@test.com", "my-sender@test.com",
"a@fake.com", "the.registrar@example.com",
Optional.of("my-reply-to@test.com"), Optional.of("my-reply-to@test.com"),
"Super Cool Registry Daily Threat Detector [2018-07-15]", "Super Cool Registry Daily Threat Detector [2018-07-15]",
String.format(DAILY_EMAIL_FORMAT, "<tr><td>a.com</td><td>MALWARE</td></tr>"), String.format(DAILY_EMAIL_FORMAT, "<tr><td>a.com</td><td>MALWARE</td></tr>"),
@ -187,7 +193,7 @@ public class Spec11EmailUtilsTest {
validateMessage( validateMessage(
capturedMessages.get(1), capturedMessages.get(1),
"my-sender@test.com", "my-sender@test.com",
"b@fake.com", "new.registrar@example.com",
Optional.of("my-reply-to@test.com"), Optional.of("my-reply-to@test.com"),
"Super Cool Registry Daily Threat Detector [2018-07-15]", "Super Cool Registry Daily Threat Detector [2018-07-15]",
String.format( String.format(
@ -234,7 +240,7 @@ public class Spec11EmailUtilsTest {
validateMessage( validateMessage(
capturedMessages.get(0), capturedMessages.get(0),
"my-sender@test.com", "my-sender@test.com",
"a@fake.com", "the.registrar@example.com",
Optional.of("my-reply-to@test.com"), Optional.of("my-reply-to@test.com"),
"Super Cool Registry Monthly Threat Detector [2018-07-15]", "Super Cool Registry Monthly Threat Detector [2018-07-15]",
String.format(MONTHLY_EMAIL_FORMAT, "<tr><td>a.com</td><td>MALWARE</td></tr>"), String.format(MONTHLY_EMAIL_FORMAT, "<tr><td>a.com</td><td>MALWARE</td></tr>"),
@ -242,7 +248,7 @@ public class Spec11EmailUtilsTest {
validateMessage( validateMessage(
capturedMessages.get(1), capturedMessages.get(1),
"my-sender@test.com", "my-sender@test.com",
"b@fake.com", "new.registrar@example.com",
Optional.of("my-reply-to@test.com"), Optional.of("my-reply-to@test.com"),
"Super Cool Registry Monthly Threat Detector [2018-07-15]", "Super Cool Registry Monthly Threat Detector [2018-07-15]",
String.format( String.format(
@ -260,7 +266,7 @@ public class Spec11EmailUtilsTest {
} }
@Test @Test
public void testSuccess_sendAlertEmail() throws MessagingException { public void testSuccess_sendAlertEmail() throws Exception {
emailUtils.sendAlertEmail("Spec11 Pipeline Alert: 2018-07", "Alert!"); emailUtils.sendAlertEmail("Spec11 Pipeline Alert: 2018-07", "Alert!");
verify(emailService).sendEmail(contentCaptor.capture()); verify(emailService).sendEmail(contentCaptor.capture());
validateMessage( validateMessage(
@ -273,6 +279,45 @@ public class Spec11EmailUtilsTest {
Optional.empty()); Optional.empty());
} }
@Test
public void testSuccess_useWhoisAbuseEmailIfAvailable() throws Exception {
// if John Doe is the whois abuse contact, email them instead of the regular email
persistResource(
AppEngineRule.makeRegistrarContact2()
.asBuilder()
.setEmailAddress("johndoe@theregistrar.com")
.setVisibleInDomainWhoisAsAbuse(true)
.build());
emailUtils.emailSpec11Reports(
date,
Spec11EmailSoyInfo.MONTHLY_SPEC_11_EMAIL,
"Super Cool Registry Monthly Threat Detector [2018-07-15]",
sampleThreatMatches());
verify(emailService, times(3)).sendEmail(contentCaptor.capture());
assertThat(contentCaptor.getAllValues().get(0).recipients())
.containsExactly(new InternetAddress("johndoe@theregistrar.com"));
}
@Test
public void testFailure_badClientId() {
RuntimeException thrown =
assertThrows(
RuntimeException.class,
() ->
emailUtils.emailSpec11Reports(
date,
Spec11EmailSoyInfo.MONTHLY_SPEC_11_EMAIL,
"Super Cool Registry Monthly Threat Detector [2018-07-15]",
ImmutableSet.of(
RegistrarThreatMatches.create(
"badClientId", getMatchA().threatMatches()))));
assertThat(thrown)
.hasCauseThat()
.hasMessageThat()
.isEqualTo("Could not find registrar badClientId");
assertThat(thrown).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
}
private void validateMessage( private void validateMessage(
EmailMessage message, EmailMessage message,
String from, String from,

View file

@ -84,7 +84,7 @@ public class Spec11RegistrarThreatMatchesParserTest {
static RegistrarThreatMatches getMatchA() throws Exception { static RegistrarThreatMatches getMatchA() throws Exception {
return RegistrarThreatMatches.create( return RegistrarThreatMatches.create(
"a@fake.com", "TheRegistrar",
ImmutableList.of( ImmutableList.of(
ThreatMatch.fromJSON( ThreatMatch.fromJSON(
new JSONObject( new JSONObject(
@ -97,7 +97,7 @@ public class Spec11RegistrarThreatMatchesParserTest {
static RegistrarThreatMatches getMatchB() throws Exception { static RegistrarThreatMatches getMatchB() throws Exception {
return RegistrarThreatMatches.create( return RegistrarThreatMatches.create(
"b@fake.com", "NewRegistrar",
ImmutableList.of( ImmutableList.of(
ThreatMatch.fromJSON( ThreatMatch.fromJSON(
new JSONObject( new JSONObject(

View file

@ -1,3 +1,3 @@
Map from registrar email to detected subdomain threats: Map from registrar email / name to detected subdomain threats:
{"threatMatches":[{"threatEntryMetadata":"NONE","threatType":"MALWARE","fullyQualifiedDomainName":"a.com","platformType":"ANY_PLATFORM"}],"registrarEmailAddress":"a@fake.com"} {"threatMatches":[{"threatEntryMetadata":"NONE","threatType":"MALWARE","fullyQualifiedDomainName":"a.com","platformType":"ANY_PLATFORM"}],"registrarClientId":"TheRegistrar","registrarEmailAddress":"the.registrar@example.com"}
{"threatMatches":[{"threatEntryMetadata":"NONE","threatType":"MALWARE","fullyQualifiedDomainName":"b.com","platformType":"ANY_PLATFORM"},{"threatEntryMetadata":"NONE","threatType":"MALWARE","fullyQualifiedDomainName":"c.com","platformType":"ANY_PLATFORM"}],"registrarEmailAddress":"b@fake.com"} {"threatMatches":[{"threatEntryMetadata":"NONE","threatType":"MALWARE","fullyQualifiedDomainName":"b.com","platformType":"ANY_PLATFORM"},{"threatEntryMetadata":"NONE","threatType":"MALWARE","fullyQualifiedDomainName":"c.com","platformType":"ANY_PLATFORM"}],"registrarClientId":"NewRegistrar","registrarEmailAddress":"new.registrar@example.com"}