Add sending notification email mechanism for expiring certificates (#1179)

* Resolve rebase conflict

* Fix and imporove based on feedback.
This commit is contained in:
Rachel Guan 2021-08-19 12:49:45 -04:00 committed by GitHub
parent 03bb360a94
commit 2cea084e4d
6 changed files with 1001 additions and 49 deletions

View file

@ -0,0 +1,326 @@
// Copyright 2021 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.batch;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
import static org.apache.http.HttpStatus.SC_OK;
import static org.joda.time.DateTimeZone.UTC;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.config.RegistryConfig.Config;
import google.registry.flows.certs.CertificateChecker;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarContact;
import google.registry.model.registrar.RegistrarContact.Type;
import google.registry.request.Action;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.util.EmailMessage;
import google.registry.util.SendEmailService;
import java.util.Date;
import java.util.Optional;
import javax.inject.Inject;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
/** An action that sends notification emails to registrars whose certificates are expiring soon. */
@Action(
service = Action.Service.BACKEND,
path = SendExpiringCertificateNotificationEmailAction.PATH,
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
public class SendExpiringCertificateNotificationEmailAction implements Runnable {
public static final String PATH = "/_dr/task/sendExpiringCertificateNotificationEmail";
/**
* Used as an offset when storing the last notification email sent date.
*
* <p>This is used to handle edges cases when the update happens in between the day switch. For
* instance,if the job starts at 2:00 am every day and it finishes at 2:03 of the same day, then
* next day at 2am, the date difference will be less than a day, which will lead to the date
* difference between two successive email sent date being the expected email interval days + 1;
*/
protected static final Duration UPDATE_TIME_OFFSET = Duration.standardMinutes(10);
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd");
private final CertificateChecker certificateChecker;
private final String expirationWarningEmailBodyText;
private final SendEmailService sendEmailService;
private final String expirationWarningEmailSubjectText;
private final InternetAddress gSuiteOutgoingEmailAddress;
private final Response response;
@Inject
public SendExpiringCertificateNotificationEmailAction(
@Config("expirationWarningEmailBodyText") String expirationWarningEmailBodyText,
@Config("expirationWarningEmailSubjectText") String expirationWarningEmailSubjectText,
@Config("gSuiteOutgoingEmailAddress") InternetAddress gSuiteOutgoingEmailAddress,
SendEmailService sendEmailService,
CertificateChecker certificateChecker,
Response response) {
this.certificateChecker = certificateChecker;
this.expirationWarningEmailSubjectText = expirationWarningEmailSubjectText;
this.sendEmailService = sendEmailService;
this.gSuiteOutgoingEmailAddress = gSuiteOutgoingEmailAddress;
this.expirationWarningEmailBodyText = expirationWarningEmailBodyText;
this.response = response;
}
@Override
public void run() {
response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
try {
sendNotificationEmails();
response.setStatus(SC_OK);
} catch (Exception e) {
logger.atWarning().withCause(e).log(
"Exception thrown when sending expiring certificate notification emails.");
response.setStatus(SC_INTERNAL_SERVER_ERROR);
response.setPayload(String.format("Exception thrown with cause: %s", e));
}
}
/**
* Returns a list of registrars that should receive expiring notification emails. There are two
* certificates that should be considered (the main certificate and failOver certificate). The
* registrars should receive notifications if one of the certificate checks returns true.
*/
@VisibleForTesting
ImmutableList<RegistrarInfo> getRegistrarsWithExpiringCertificates() {
return Streams.stream(Registrar.loadAllCached())
.map(
registrar ->
RegistrarInfo.create(
registrar,
registrar.getClientCertificate().isPresent()
&& certificateChecker.shouldReceiveExpiringNotification(
registrar.getLastExpiringCertNotificationSentDate(),
registrar.getClientCertificate().get()),
registrar.getFailoverClientCertificate().isPresent()
&& certificateChecker.shouldReceiveExpiringNotification(
registrar.getLastExpiringFailoverCertNotificationSentDate(),
registrar.getFailoverClientCertificate().get())))
.filter(
registrarInfo ->
registrarInfo.isCertExpiring() || registrarInfo.isFailOverCertExpiring())
.collect(toImmutableList());
}
/**
* Sends a notification email to the registrar regarding the expiring certificate and returns true
* if it's sent successfully.
*/
@VisibleForTesting
boolean sendNotificationEmail(
Registrar registrar,
DateTime lastExpiringCertNotificationSentDate,
CertificateType certificateType,
Optional<String> certificate) {
if (!certificate.isPresent()
|| !certificateChecker.shouldReceiveExpiringNotification(
lastExpiringCertNotificationSentDate, certificate.get())) {
return false;
}
try {
ImmutableSet<InternetAddress> recipients = getEmailAddresses(registrar, Type.TECH);
if (recipients.isEmpty()) {
logger.atWarning().log(
"Registrar %s contains no email addresses to receive notification email.",
registrar.getRegistrarName());
return false;
}
sendEmailService.sendEmail(
EmailMessage.newBuilder()
.setFrom(gSuiteOutgoingEmailAddress)
.setSubject(expirationWarningEmailSubjectText)
.setBody(
getEmailBody(
registrar.getRegistrarName(),
certificateType,
certificateChecker.getCertificate(certificate.get()).getNotAfter()))
.setRecipients(recipients)
.setCcs(getEmailAddresses(registrar, Type.ADMIN))
.build());
/*
* A duration time offset is used here to ensure that date comparison between two
* successive dates is always greater than 1 day. This date is set as last updated date,
* for applicable certificate.
*/
updateLastNotificationSentDate(
registrar,
DateTime.now(UTC).minusMinutes((int) UPDATE_TIME_OFFSET.getStandardMinutes()),
certificateType);
return true;
} catch (Exception e) {
throw new RuntimeException(
String.format(
"Failed to send expiring certificate notification email to registrar %s.",
registrar.getRegistrarName()));
}
}
/** Updates the last notification sent date in database. */
@VisibleForTesting
void updateLastNotificationSentDate(
Registrar registrar, DateTime now, CertificateType certificateType) {
try {
tm().transact(
() -> {
Registrar.Builder newRegistrar = tm().loadByEntity(registrar).asBuilder();
switch (certificateType) {
case PRIMARY:
newRegistrar.setLastExpiringCertNotificationSentDate(now);
tm().put(newRegistrar.build());
logger.atInfo().log(
"Updated last notification email sent date for %s certificate of "
+ "registrar %s.",
certificateType.getDisplayName(), registrar.getRegistrarName());
break;
case FAILOVER:
newRegistrar.setLastExpiringFailoverCertNotificationSentDate(now);
tm().put(newRegistrar.build());
logger.atInfo().log(
"Updated last notification email sent date for %s certificate of "
+ "registrar %s.",
certificateType.getDisplayName(), registrar.getRegistrarName());
break;
default:
throw new IllegalArgumentException(
String.format(
"Unsupported certificate type: %s being passed in when updating "
+ "the last notification sent date to registrar %s.",
certificateType.toString(), registrar.getRegistrarName()));
}
});
} catch (Exception e) {
throw new RuntimeException(
String.format(
"Failed to update the last notification sent date to Registrar %s for the %s "
+ "certificate.",
registrar.getRegistrarName(), certificateType.getDisplayName()));
}
}
/** Sends notification emails to registrars with expiring certificates. */
@VisibleForTesting
int sendNotificationEmails() {
int emailsSent = 0;
for (RegistrarInfo registrarInfo : getRegistrarsWithExpiringCertificates()) {
Registrar registrar = registrarInfo.registrar();
if (registrarInfo.isCertExpiring()) {
sendNotificationEmail(
registrar,
registrar.getLastExpiringCertNotificationSentDate(),
CertificateType.PRIMARY,
registrar.getClientCertificate());
emailsSent++;
}
if (registrarInfo.isFailOverCertExpiring()) {
sendNotificationEmail(
registrar,
registrar.getLastExpiringFailoverCertNotificationSentDate(),
CertificateType.FAILOVER,
registrar.getFailoverClientCertificate());
emailsSent++;
}
}
logger.atInfo().log(
"Sent %d expiring certificate notification emails to registrars.", emailsSent);
return emailsSent;
}
/** Returns a list of email addresses of the registrar that should receive a notification email */
@VisibleForTesting
ImmutableSet<InternetAddress> getEmailAddresses(Registrar registrar, Type contactType) {
ImmutableSortedSet<RegistrarContact> contacts = registrar.getContactsOfType(contactType);
ImmutableSet.Builder<InternetAddress> recipientEmails = new ImmutableSet.Builder<>();
for (RegistrarContact contact : contacts) {
try {
recipientEmails.add(new InternetAddress(contact.getEmailAddress()));
} catch (AddressException e) {
logger.atWarning().withCause(e).log(
"Registrar Contact email address %s of Registrar %s is invalid; skipping.",
contact.getEmailAddress(), registrar.getRegistrarName());
}
}
return recipientEmails.build();
}
/**
* Generates email content by taking registrar name, certificate type and expiration date as
* parameters.
*/
@VisibleForTesting
@SuppressWarnings("lgtm[java/dereferenced-value-may-be-null]")
String getEmailBody(String registrarName, CertificateType type, Date expirationDate) {
checkArgumentNotNull(expirationDate, "Expiration date cannot be null");
checkArgumentNotNull(type, "Certificate type cannot be null");
return String.format(
expirationWarningEmailBodyText,
registrarName,
type.getDisplayName(),
DATE_FORMATTER.print(new DateTime(expirationDate)));
}
/**
* Certificate types for X509Certificate.
*
* <p><b>Note:</b> These types are only used to indicate the type of expiring certificate in
* notification emails.
*/
protected enum CertificateType {
PRIMARY("primary"),
FAILOVER("fail-over");
private final String displayName;
CertificateType(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
}
@AutoValue
public abstract static class RegistrarInfo {
static RegistrarInfo create(
Registrar registrar, boolean isCertExpiring, boolean isFailOverCertExpiring) {
return new AutoValue_SendExpiringCertificateNotificationEmailAction_RegistrarInfo(
registrar, isCertExpiring, isFailOverCertExpiring);
}
public abstract Registrar registrar();
public abstract boolean isCertExpiring();
public abstract boolean isFailOverCertExpiring();
}
}

View file

@ -355,6 +355,12 @@
<url-pattern>/_dr/task/deleteExpiredDomains</url-pattern> <url-pattern>/_dr/task/deleteExpiredDomains</url-pattern>
</servlet-mapping> </servlet-mapping>
<!-- Background action to send notification emails to registrars with expiring certificate. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/sendExpiringCertificateNotificationEmail</url-pattern>
</servlet-mapping>
<!-- Mapreduce to import contacts from escrow file --> <!-- Mapreduce to import contacts from escrow file -->
<servlet-mapping> <servlet-mapping>
<servlet-name>backend-servlet</servlet-name> <servlet-name>backend-servlet</servlet-name>

View file

@ -168,6 +168,15 @@
<target>backend</target> <target>backend</target>
</cron> </cron>
<cron>
<url><![CDATA[/_dr/task/sendExpiringCertificateNotificationEmail]]></url>
<description>
This job runs an action that sends emails to partners if their certificates are expiring soon.
</description>
<schedule>every day 04:30</schedule>
<target>backend</target>
</cron>
<cron> <cron>
<url><![CDATA[/_dr/cron/fanout?queue=export-snapshot&endpoint=/_dr/task/backupDatastore&runInEmpty]]></url> <url><![CDATA[/_dr/cron/fanout?queue=export-snapshot&endpoint=/_dr/task/backupDatastore&runInEmpty]]></url>
<description> <description>

View file

@ -31,6 +31,7 @@ import google.registry.batch.RefreshDnsOnHostRenameAction;
import google.registry.batch.RelockDomainAction; import google.registry.batch.RelockDomainAction;
import google.registry.batch.ResaveAllEppResourcesAction; import google.registry.batch.ResaveAllEppResourcesAction;
import google.registry.batch.ResaveEntityAction; import google.registry.batch.ResaveEntityAction;
import google.registry.batch.SendExpiringCertificateNotificationEmailAction;
import google.registry.batch.WipeOutCloudSqlAction; import google.registry.batch.WipeOutCloudSqlAction;
import google.registry.batch.WipeoutDatastoreAction; import google.registry.batch.WipeoutDatastoreAction;
import google.registry.cron.CommitLogFanoutAction; import google.registry.cron.CommitLogFanoutAction;
@ -193,6 +194,8 @@ interface BackendRequestComponent {
ResaveEntityAction resaveEntityAction(); ResaveEntityAction resaveEntityAction();
SendExpiringCertificateNotificationEmailAction sendExpiringCertificateNotificationEmailAction();
SyncGroupMembersAction syncGroupMembersAction(); SyncGroupMembersAction syncGroupMembersAction();
SyncRegistrarsSheetAction syncRegistrarsSheetAction(); SyncRegistrarsSheetAction syncRegistrarsSheetAction();

View file

@ -0,0 +1,607 @@
// Copyright 2021 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.batch;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.AppEngineExtension.makeRegistrar1;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.persistSimpleResources;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.batch.SendExpiringCertificateNotificationEmailAction.CertificateType;
import google.registry.batch.SendExpiringCertificateNotificationEmailAction.RegistrarInfo;
import google.registry.flows.certs.CertificateChecker;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registrar.RegistrarContact;
import google.registry.model.registrar.RegistrarContact.Type;
import google.registry.request.Response;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.FakeClock;
import google.registry.testing.InjectExtension;
import google.registry.testing.TestOfyAndSql;
import google.registry.util.SelfSignedCaCertificate;
import google.registry.util.SendEmailService;
import java.security.cert.X509Certificate;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.mail.internet.InternetAddress;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link SendExpiringCertificateNotificationEmailAction}. */
@DualDatabaseTest
class SendExpiringCertificateNotificationEmailActionTest {
@RegisterExtension
public final AppEngineExtension appEngine =
AppEngineExtension.builder().withDatastoreAndCloudSql().withTaskQueue().build();
@RegisterExtension public final InjectExtension inject = new InjectExtension();
private final FakeClock clock = new FakeClock(DateTime.parse("2021-05-24T20:21:22Z"));
private final SendEmailService sendEmailService = mock(SendEmailService.class);
private CertificateChecker certificateChecker;
private SendExpiringCertificateNotificationEmailAction action;
private Registrar sampleRegistrar;
private Response response;
@BeforeEach
void beforeEach() throws Exception {
certificateChecker =
new CertificateChecker(
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
30,
15,
2048,
ImmutableSet.of("secp256r1", "secp384r1"),
clock);
String expirationWarningEmailBodyText =
" Hello Registrar %s,\n" + " The %s certificate is expiring on %s.";
String expirationWarningEmailSubjectText = "expiring certificate notification email";
action =
new SendExpiringCertificateNotificationEmailAction(
expirationWarningEmailBodyText,
expirationWarningEmailSubjectText,
new InternetAddress("test@example.com"),
sendEmailService,
certificateChecker,
response);
sampleRegistrar =
persistResource(createRegistrar("clientId", "sampleRegistrar", null, null).build());
}
/** Returns a sample registrar with a customized registrar name, client id and certificate* */
private Registrar.Builder createRegistrar(
String clientId,
String registrarName,
@Nullable X509Certificate certificate,
@Nullable X509Certificate failOverCertificate)
throws Exception {
// set up only required fields sample test data
Registrar.Builder builder =
new Registrar.Builder()
.setClientId(clientId)
.setRegistrarName(registrarName)
.setType(Registrar.Type.REAL)
.setIanaIdentifier(8L)
.setState(Registrar.State.ACTIVE)
.setInternationalizedAddress(
new RegistrarAddress.Builder()
.setStreet(ImmutableList.of("very fake street"))
.setCity("city")
.setState("state")
.setZip("99999")
.setCountryCode("US")
.build())
.setPhoneNumber("+0.000000000")
.setFaxNumber("+9.999999999")
.setEmailAddress("contact-us@test.example")
.setWhoisServer("whois.registrar.example")
.setUrl("http://www.test.example");
if (failOverCertificate != null) {
builder.setFailoverClientCertificate(
certificateChecker.serializeCertificate(failOverCertificate), clock.nowUtc());
}
if (certificate != null) {
builder.setClientCertificate(
certificateChecker.serializeCertificate(certificate), clock.nowUtc());
}
return builder;
}
@TestOfyAndSql
void sendNotificationEmail_returnsTrue() throws Exception {
X509Certificate expiringCertificate =
SelfSignedCaCertificate.create(
"www.example.tld",
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-06-01T00:00:00Z"))
.cert();
Optional<String> cert =
Optional.of(certificateChecker.serializeCertificate(expiringCertificate));
Registrar registrar =
persistResource(
makeRegistrar1()
.asBuilder()
.setFailoverClientCertificate(cert.get(), clock.nowUtc())
.build());
ImmutableList<RegistrarContact> contacts =
ImmutableList.of(
new RegistrarContact.Builder()
.setParent(registrar)
.setName("Will Doe")
.setEmailAddress("will@example-registrar.tld")
.setPhoneNumber("+1.3105551213")
.setFaxNumber("+1.3105551213")
.setTypes(ImmutableSet.of(RegistrarContact.Type.TECH))
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
.build());
persistSimpleResources(contacts);
persistResource(registrar);
assertThat(
action.sendNotificationEmail(registrar, START_OF_TIME, CertificateType.FAILOVER, cert))
.isEqualTo(true);
}
@TestOfyAndSql
void sendNotificationEmail_returnsFalse_noEmailRecipients() throws Exception {
X509Certificate expiringCertificate =
SelfSignedCaCertificate.create(
"www.example.tld",
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-06-02T00:00:00Z"))
.cert();
Optional<String> cert =
Optional.of(certificateChecker.serializeCertificate(expiringCertificate));
assertThat(
action.sendNotificationEmail(
sampleRegistrar, START_OF_TIME, CertificateType.FAILOVER, cert))
.isEqualTo(false);
}
@TestOfyAndSql
void sendNotificationEmail_throwsRunTimeException() throws Exception {
doThrow(new RuntimeException("this is a runtime exception"))
.when(sendEmailService)
.sendEmail(any());
X509Certificate expiringCertificate =
SelfSignedCaCertificate.create(
"www.example.tld",
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-06-01T00:00:00Z"))
.cert();
Optional<String> cert =
Optional.of(certificateChecker.serializeCertificate(expiringCertificate));
Registrar registrar =
persistResource(
makeRegistrar1()
.asBuilder()
.setFailoverClientCertificate(cert.get(), clock.nowUtc())
.build());
ImmutableList<RegistrarContact> contacts =
ImmutableList.of(
new RegistrarContact.Builder()
.setParent(registrar)
.setName("Will Doe")
.setEmailAddress("will@example-registrar.tld")
.setPhoneNumber("+1.3105551213")
.setFaxNumber("+1.3105551213")
.setTypes(ImmutableSet.of(RegistrarContact.Type.TECH))
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
.build());
persistSimpleResources(contacts);
RuntimeException thrown =
assertThrows(
RuntimeException.class,
() ->
action.sendNotificationEmail(
registrar, START_OF_TIME, CertificateType.FAILOVER, cert));
assertThat(thrown)
.hasMessageThat()
.contains(
String.format(
"Failed to send expiring certificate notification email to registrar %s",
registrar.getRegistrarName()));
}
@TestOfyAndSql
void sendNotificationEmail_returnsFalse_noCertificate() {
assertThat(
action.sendNotificationEmail(
sampleRegistrar, START_OF_TIME, CertificateType.FAILOVER, Optional.empty()))
.isEqualTo(false);
}
@TestOfyAndSql
void sendNotificationEmails_allEmailsBeingAttemptedToSend() throws Exception {
X509Certificate expiringCertificate =
SelfSignedCaCertificate.create(
"www.example.tld",
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-06-01T00:00:00Z"))
.cert();
X509Certificate certificate =
SelfSignedCaCertificate.create(
"www.example.tld",
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-10-01T00:00:00Z"))
.cert();
int numOfRegistrars = 10;
int numOfRegistrarsWithExpiringCertificates = 2;
for (int i = 1; i <= numOfRegistrarsWithExpiringCertificates; i++) {
persistResource(
createRegistrar("oldcert" + i, "name" + i, expiringCertificate, null).build());
}
for (int i = numOfRegistrarsWithExpiringCertificates; i <= numOfRegistrars; i++) {
persistResource(createRegistrar("goodcert" + i, "name" + i, certificate, null).build());
}
assertThat(action.sendNotificationEmails()).isEqualTo(numOfRegistrarsWithExpiringCertificates);
}
@TestOfyAndSql
void sendNotificationEmails_allEmailsBeingAttemptedToSend_onlyMainCertificates()
throws Exception {
X509Certificate expiringCertificate =
SelfSignedCaCertificate.create(
"www.example.tld",
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-06-01T00:00:00Z"))
.cert();
int numOfRegistrars = 10;
for (int i = 1; i <= numOfRegistrars; i++) {
persistResource(
createRegistrar("oldcert" + i, "name" + i, expiringCertificate, null).build());
}
assertThat(action.sendNotificationEmails()).isEqualTo(numOfRegistrars);
}
@TestOfyAndSql
void sendNotificationEmails_allEmailsBeingAttemptedToSend_onlyFailOverCertificates()
throws Exception {
X509Certificate expiringCertificate =
SelfSignedCaCertificate.create(
"www.example.tld",
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-06-01T00:00:00Z"))
.cert();
int numOfRegistrars = 10;
for (int i = 1; i <= numOfRegistrars; i++) {
persistResource(
createRegistrar("oldcert" + i, "name" + i, null, expiringCertificate).build());
}
assertThat(action.sendNotificationEmails()).isEqualTo(numOfRegistrars);
}
@TestOfyAndSql
void sendNotificationEmails_allEmailsBeingAttemptedToSend_mixedOfCertificates() throws Exception {
X509Certificate expiringCertificate =
SelfSignedCaCertificate.create(
"www.example.tld",
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-06-01T00:00:00Z"))
.cert();
int numOfRegistrars = 10;
int numOfExpiringFailOverOnly = 2;
int numOfExpiringPrimaryOnly = 3;
for (int i = 1; i <= numOfExpiringFailOverOnly; i++) {
persistResource(
createRegistrar("cl" + i, "expiringFailOverOnly" + i, null, expiringCertificate).build());
}
for (int i = 1; i <= numOfExpiringPrimaryOnly; i++) {
persistResource(
createRegistrar("cli" + i, "expiringPrimaryOnly" + i, expiringCertificate, null).build());
}
for (int i = numOfExpiringFailOverOnly + numOfExpiringPrimaryOnly + 1;
i <= numOfRegistrars;
i++) {
persistResource(
createRegistrar("client" + i, "regularReg" + i, expiringCertificate, expiringCertificate)
.build());
}
assertThat(action.sendNotificationEmails())
.isEqualTo(numOfRegistrars + numOfExpiringFailOverOnly + numOfExpiringPrimaryOnly);
}
@TestOfyAndSql
void updateLastNotificationSentDate_updatedSuccessfully_primaryCertificate() throws Exception {
X509Certificate expiringCertificate =
SelfSignedCaCertificate.create(
"www.example.tld",
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-06-02T00:00:00Z"))
.cert();
Registrar registrar =
createRegistrar("testClientId", "registrar", expiringCertificate, null).build();
persistResource(registrar);
action.updateLastNotificationSentDate(registrar, clock.nowUtc(), CertificateType.PRIMARY);
assertThat(loadByEntity(registrar).getLastExpiringCertNotificationSentDate())
.isEqualTo(clock.nowUtc());
}
@TestOfyAndSql
void updateLastNotificationSentDate_updatedSuccessfully_failOverCertificate() throws Exception {
X509Certificate expiringCertificate =
SelfSignedCaCertificate.create(
"www.example.tld",
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-06-01T00:00:00Z"))
.cert();
Registrar registrar =
createRegistrar("testClientId", "registrar", null, expiringCertificate).build();
persistResource(registrar);
action.updateLastNotificationSentDate(registrar, clock.nowUtc(), CertificateType.FAILOVER);
assertThat(loadByEntity(registrar).getLastExpiringFailoverCertNotificationSentDate())
.isEqualTo(clock.nowUtc());
}
@TestOfyAndSql
void updateLastNotificationSentDate_noUpdates_noLastNotificationSentDate() throws Exception {
X509Certificate expiringCertificate =
SelfSignedCaCertificate.create(
"www.example.tld",
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-06-01T00:00:00Z"))
.cert();
Registrar registrar =
createRegistrar("testClientId", "registrar", null, expiringCertificate).build();
persistResource(registrar);
RuntimeException thrown =
assertThrows(
RuntimeException.class,
() -> action.updateLastNotificationSentDate(registrar, null, CertificateType.FAILOVER));
assertThat(thrown)
.hasMessageThat()
.contains("Failed to update the last notification sent date to Registrar");
}
@TestOfyAndSql
void updateLastNotificationSentDate_noUpdates_invalidCertificateType() throws Exception {
X509Certificate expiringCertificate =
SelfSignedCaCertificate.create(
"www.example.tld",
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-06-01T00:00:00Z"))
.cert();
Registrar registrar =
createRegistrar("testClientId", "registrar", null, expiringCertificate).build();
persistResource(registrar);
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
action.updateLastNotificationSentDate(
registrar, clock.nowUtc(), CertificateType.valueOf("randomType")));
assertThat(thrown).hasMessageThat().contains("No enum constant");
}
@TestOfyAndSql
void getRegistrarsWithExpiringCertificates_returnsPartOfRegistrars() throws Exception {
X509Certificate expiringCertificate =
SelfSignedCaCertificate.create(
"www.example.tld",
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-06-01T00:00:00Z"))
.cert();
X509Certificate certificate =
SelfSignedCaCertificate.create(
"www.example.tld",
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-10-01T00:00:00Z"))
.cert();
int numOfRegistrars = 10;
int numOfRegistrarsWithExpiringCertificates = 2;
for (int i = 1; i <= numOfRegistrarsWithExpiringCertificates; i++) {
persistResource(
createRegistrar("oldcert" + i, "name" + i, expiringCertificate, null).build());
}
for (int i = numOfRegistrarsWithExpiringCertificates; i <= numOfRegistrars; i++) {
persistResource(createRegistrar("goodcert" + i, "name" + i, certificate, null).build());
}
ImmutableList<RegistrarInfo> results = action.getRegistrarsWithExpiringCertificates();
assertThat(results.size()).isEqualTo(numOfRegistrarsWithExpiringCertificates);
}
@TestOfyAndSql
void getRegistrarsWithExpiringCertificates_returnsPartOfRegistrars_failOverCertificateBranch()
throws Exception {
X509Certificate expiringCertificate =
SelfSignedCaCertificate.create(
"www.example.tld",
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-06-01T00:00:00Z"))
.cert();
X509Certificate certificate =
SelfSignedCaCertificate.create(
"www.example.tld",
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-10-01T00:00:00Z"))
.cert();
int numOfRegistrars = 10;
int numOfRegistrarsWithExpiringCertificates = 2;
for (int i = 1; i <= numOfRegistrarsWithExpiringCertificates; i++) {
persistResource(
createRegistrar("oldcert" + i, "name" + i, null, expiringCertificate).build());
}
for (int i = numOfRegistrarsWithExpiringCertificates; i <= numOfRegistrars; i++) {
persistResource(createRegistrar("goodcert" + i, "name" + i, null, certificate).build());
}
assertThat(action.getRegistrarsWithExpiringCertificates().size())
.isEqualTo(numOfRegistrarsWithExpiringCertificates);
}
@TestOfyAndSql
void getRegistrarsWithExpiringCertificates_returnsAllRegistrars() throws Exception {
X509Certificate expiringCertificate =
SelfSignedCaCertificate.create(
"www.example.tld",
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-06-01T00:00:00Z"))
.cert();
int numOfRegistrarsWithExpiringCertificates = 5;
for (int i = 1; i <= numOfRegistrarsWithExpiringCertificates; i++) {
persistResource(
createRegistrar("oldcert" + i, "name" + i, expiringCertificate, null).build());
}
assertThat(action.getRegistrarsWithExpiringCertificates().size())
.isEqualTo(numOfRegistrarsWithExpiringCertificates);
}
@TestOfyAndSql
void getRegistrarsWithExpiringCertificates_returnsNoRegistrars() throws Exception {
X509Certificate certificate =
SelfSignedCaCertificate.create(
"www.example.tld",
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-10-01T00:00:00Z"))
.cert();
int numOfRegistrars = 10;
for (int i = 1; i <= numOfRegistrars; i++) {
persistResource(createRegistrar("goodcert" + i, "name" + i, certificate, null).build());
}
assertThat(action.getRegistrarsWithExpiringCertificates()).isEmpty();
}
@TestOfyAndSql
void getRegistrarsWithExpiringCertificates_noRegistrarsInDatabase() {
assertThat(action.getRegistrarsWithExpiringCertificates()).isEmpty();
}
@TestOfyAndSql
void getEmailAddresses_success_returnsAnEmptyList() {
assertThat(action.getEmailAddresses(sampleRegistrar, Type.TECH)).isEmpty();
assertThat(action.getEmailAddresses(sampleRegistrar, Type.ADMIN)).isEmpty();
}
@TestOfyAndSql
void getEmailAddresses_success_returnsAListOfEmails() throws Exception {
Registrar registrar = persistResource(makeRegistrar1());
ImmutableList<RegistrarContact> contacts =
ImmutableList.of(
new RegistrarContact.Builder()
.setParent(registrar)
.setName("John Doe")
.setEmailAddress("jd@example-registrar.tld")
.setPhoneNumber("+1.3105551213")
.setFaxNumber("+1.3105551213")
.setTypes(ImmutableSet.of(RegistrarContact.Type.TECH))
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
.build(),
new RegistrarContact.Builder()
.setParent(registrar)
.setName("John Smith")
.setEmailAddress("js@example-registrar.tld")
.setPhoneNumber("+1.1111111111")
.setFaxNumber("+1.1111111111")
.setTypes(ImmutableSet.of(RegistrarContact.Type.TECH))
.build(),
new RegistrarContact.Builder()
.setParent(registrar)
.setName("Will Doe")
.setEmailAddress("will@example-registrar.tld")
.setPhoneNumber("+1.3105551213")
.setFaxNumber("+1.3105551213")
.setTypes(ImmutableSet.of(RegistrarContact.Type.TECH))
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
.build(),
new RegistrarContact.Builder()
.setParent(registrar)
.setName("Mike Doe")
.setEmailAddress("mike@example-registrar.tld")
.setPhoneNumber("+1.1111111111")
.setFaxNumber("+1.1111111111")
.setTypes(ImmutableSet.of(RegistrarContact.Type.ADMIN))
.build(),
new RegistrarContact.Builder()
.setParent(registrar)
.setName("John T")
.setEmailAddress("john@example-registrar.tld")
.setPhoneNumber("+1.3105551215")
.setFaxNumber("+1.3105551216")
.setTypes(ImmutableSet.of(RegistrarContact.Type.ADMIN))
.setVisibleInWhoisAsTech(true)
.build());
persistSimpleResources(contacts);
assertThat(action.getEmailAddresses(registrar, Type.TECH))
.containsExactly(
new InternetAddress("will@example-registrar.tld"),
new InternetAddress("jd@example-registrar.tld"),
new InternetAddress("js@example-registrar.tld"));
assertThat(action.getEmailAddresses(registrar, Type.ADMIN))
.containsExactly(
new InternetAddress("janedoe@theregistrar.com"), // comes with makeRegistrar1()
new InternetAddress("mike@example-registrar.tld"),
new InternetAddress("john@example-registrar.tld"));
}
@TestOfyAndSql
void getEmailAddresses_failure_returnsPartialListOfEmails_skipInvalidEmails() {
// when building a new RegistrarContact object, there's already an email validation process.
// if the registrarContact is created successful, the email address of the contact object
// should already be validated. Ideally, there should not be an AddressException when creating
// a new InternetAddress using the email address string of the contact object.
}
@TestOfyAndSql
void getEmailBody_returnsEmailBodyText() {
String registrarName = "good registrar";
String certExpirationDateStr = "2021-06-15";
CertificateType certificateType = CertificateType.PRIMARY;
String emailBody =
action.getEmailBody(
registrarName, certificateType, DateTime.parse(certExpirationDateStr).toDate());
assertThat(emailBody).contains(registrarName);
assertThat(emailBody).contains(certificateType.getDisplayName());
assertThat(emailBody).contains(certExpirationDateStr);
}
@TestOfyAndSql
void getEmailBody_throwsIllegalArgumentException_noExpirationDate() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> action.getEmailBody("good registrar", CertificateType.FAILOVER, null));
assertThat(thrown).hasMessageThat().contains("Expiration date cannot be null");
}
@TestOfyAndSql
void getEmailBody_throwsIllegalArgumentException_noCertificateType() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
action.getEmailBody("good registrar", null, DateTime.parse("2021-06-15").toDate()));
assertThat(thrown).hasMessageThat().contains("Certificate type cannot be null");
}
}

View file

@ -1,49 +1,50 @@
PATH CLASS METHODS OK AUTH_METHODS MIN USER_POLICY PATH CLASS METHODS OK AUTH_METHODS MIN USER_POLICY
/_dr/cron/commitLogCheckpoint CommitLogCheckpointAction GET y INTERNAL,API APP ADMIN /_dr/cron/commitLogCheckpoint CommitLogCheckpointAction GET y INTERNAL,API APP ADMIN
/_dr/cron/commitLogFanout CommitLogFanoutAction GET y INTERNAL,API APP ADMIN /_dr/cron/commitLogFanout CommitLogFanoutAction GET y INTERNAL,API APP ADMIN
/_dr/cron/fanout TldFanoutAction GET y INTERNAL,API APP ADMIN /_dr/cron/fanout TldFanoutAction GET y INTERNAL,API APP ADMIN
/_dr/cron/readDnsQueue ReadDnsQueueAction GET y INTERNAL,API APP ADMIN /_dr/cron/readDnsQueue ReadDnsQueueAction GET y INTERNAL,API APP ADMIN
/_dr/dnsRefresh RefreshDnsAction GET y INTERNAL,API APP ADMIN /_dr/dnsRefresh RefreshDnsAction GET y INTERNAL,API APP ADMIN
/_dr/task/backupDatastore BackupDatastoreAction POST y INTERNAL,API APP ADMIN /_dr/task/backupDatastore BackupDatastoreAction POST y INTERNAL,API APP ADMIN
/_dr/task/brdaCopy BrdaCopyAction POST y INTERNAL,API APP ADMIN /_dr/task/brdaCopy BrdaCopyAction POST y INTERNAL,API APP ADMIN
/_dr/task/checkDatastoreBackup CheckBackupAction POST,GET y INTERNAL,API APP ADMIN /_dr/task/checkDatastoreBackup CheckBackupAction POST,GET y INTERNAL,API APP ADMIN
/_dr/task/copyDetailReports CopyDetailReportsAction POST n INTERNAL,API APP ADMIN /_dr/task/copyDetailReports CopyDetailReportsAction POST n INTERNAL,API APP ADMIN
/_dr/task/createSyntheticHistoryEntries CreateSyntheticHistoryEntriesAction GET n INTERNAL,API APP ADMIN /_dr/task/createSyntheticHistoryEntries CreateSyntheticHistoryEntriesAction GET n INTERNAL,API APP ADMIN
/_dr/task/deleteContactsAndHosts DeleteContactsAndHostsAction GET n INTERNAL,API APP ADMIN /_dr/task/deleteContactsAndHosts DeleteContactsAndHostsAction GET n INTERNAL,API APP ADMIN
/_dr/task/deleteExpiredDomains DeleteExpiredDomainsAction GET n INTERNAL,API APP ADMIN /_dr/task/deleteExpiredDomains DeleteExpiredDomainsAction GET n INTERNAL,API APP ADMIN
/_dr/task/deleteLoadTestData DeleteLoadTestDataAction POST n INTERNAL,API APP ADMIN /_dr/task/deleteLoadTestData DeleteLoadTestDataAction POST n INTERNAL,API APP ADMIN
/_dr/task/deleteOldCommitLogs DeleteOldCommitLogsAction GET n INTERNAL,API APP ADMIN /_dr/task/deleteOldCommitLogs DeleteOldCommitLogsAction GET n INTERNAL,API APP ADMIN
/_dr/task/deleteProberData DeleteProberDataAction POST n INTERNAL,API APP ADMIN /_dr/task/deleteProberData DeleteProberDataAction POST n INTERNAL,API APP ADMIN
/_dr/task/expandRecurringBillingEvents ExpandRecurringBillingEventsAction GET n INTERNAL,API APP ADMIN /_dr/task/expandRecurringBillingEvents ExpandRecurringBillingEventsAction GET n INTERNAL,API APP ADMIN
/_dr/task/exportCommitLogDiff ExportCommitLogDiffAction POST y INTERNAL,API APP ADMIN /_dr/task/exportCommitLogDiff ExportCommitLogDiffAction POST y INTERNAL,API APP ADMIN
/_dr/task/exportDomainLists ExportDomainListsAction POST n INTERNAL,API APP ADMIN /_dr/task/exportDomainLists ExportDomainListsAction POST n INTERNAL,API APP ADMIN
/_dr/task/exportPremiumTerms ExportPremiumTermsAction POST n INTERNAL,API APP ADMIN /_dr/task/exportPremiumTerms ExportPremiumTermsAction POST n INTERNAL,API APP ADMIN
/_dr/task/exportReservedTerms ExportReservedTermsAction POST n INTERNAL,API APP ADMIN /_dr/task/exportReservedTerms ExportReservedTermsAction POST n INTERNAL,API APP ADMIN
/_dr/task/generateInvoices GenerateInvoicesAction POST n INTERNAL,API APP ADMIN /_dr/task/generateInvoices GenerateInvoicesAction POST n INTERNAL,API APP ADMIN
/_dr/task/generateSpec11 GenerateSpec11ReportAction POST n INTERNAL,API APP ADMIN /_dr/task/generateSpec11 GenerateSpec11ReportAction POST n INTERNAL,API APP ADMIN
/_dr/task/icannReportingStaging IcannReportingStagingAction POST n INTERNAL,API APP ADMIN /_dr/task/icannReportingStaging IcannReportingStagingAction POST n INTERNAL,API APP ADMIN
/_dr/task/icannReportingUpload IcannReportingUploadAction POST n INTERNAL,API APP ADMIN /_dr/task/icannReportingUpload IcannReportingUploadAction POST n INTERNAL,API APP ADMIN
/_dr/task/nordnUpload NordnUploadAction POST y INTERNAL,API APP ADMIN /_dr/task/nordnUpload NordnUploadAction POST y INTERNAL,API APP ADMIN
/_dr/task/nordnVerify NordnVerifyAction POST y INTERNAL,API APP ADMIN /_dr/task/nordnVerify NordnVerifyAction POST y INTERNAL,API APP ADMIN
/_dr/task/pollBigqueryJob BigqueryPollJobAction GET,POST y INTERNAL APP IGNORED /_dr/task/pollBigqueryJob BigqueryPollJobAction GET,POST y INTERNAL APP IGNORED
/_dr/task/publishDnsUpdates PublishDnsUpdatesAction POST y INTERNAL,API APP ADMIN /_dr/task/publishDnsUpdates PublishDnsUpdatesAction POST y INTERNAL,API APP ADMIN
/_dr/task/publishInvoices PublishInvoicesAction POST n INTERNAL,API APP ADMIN /_dr/task/publishInvoices PublishInvoicesAction POST n INTERNAL,API APP ADMIN
/_dr/task/publishSpec11 PublishSpec11ReportAction POST n INTERNAL,API APP ADMIN /_dr/task/publishSpec11 PublishSpec11ReportAction POST n INTERNAL,API APP ADMIN
/_dr/task/rdeReport RdeReportAction POST n INTERNAL,API APP ADMIN /_dr/task/rdeReport RdeReportAction POST n INTERNAL,API APP ADMIN
/_dr/task/rdeStaging RdeStagingAction GET,POST n INTERNAL,API APP ADMIN /_dr/task/rdeStaging RdeStagingAction GET,POST n INTERNAL,API APP ADMIN
/_dr/task/rdeUpload RdeUploadAction POST n INTERNAL,API APP ADMIN /_dr/task/rdeUpload RdeUploadAction POST n INTERNAL,API APP ADMIN
/_dr/task/refreshDnsOnHostRename RefreshDnsOnHostRenameAction GET n INTERNAL,API APP ADMIN /_dr/task/refreshDnsOnHostRename RefreshDnsOnHostRenameAction GET n INTERNAL,API APP ADMIN
/_dr/task/relockDomain RelockDomainAction POST y INTERNAL,API APP ADMIN /_dr/task/relockDomain RelockDomainAction POST y INTERNAL,API APP ADMIN
/_dr/task/replayCommitLogsToSql ReplayCommitLogsToSqlAction POST y INTERNAL,API APP ADMIN /_dr/task/replayCommitLogsToSql ReplayCommitLogsToSqlAction POST y INTERNAL,API APP ADMIN
/_dr/task/resaveAllEppResources ResaveAllEppResourcesAction GET n INTERNAL,API APP ADMIN /_dr/task/resaveAllEppResources ResaveAllEppResourcesAction GET n INTERNAL,API APP ADMIN
/_dr/task/resaveEntity ResaveEntityAction POST n INTERNAL,API APP ADMIN /_dr/task/resaveEntity ResaveEntityAction POST n INTERNAL,API APP ADMIN
/_dr/task/syncGroupMembers SyncGroupMembersAction POST n INTERNAL,API APP ADMIN /_dr/task/sendExpiringCertificateNotificationEmail SendExpiringCertificateNotificationEmailAction GET n INTERNAL,API APP ADMIN
/_dr/task/syncRegistrarsSheet SyncRegistrarsSheetAction POST n INTERNAL,API APP ADMIN /_dr/task/syncGroupMembers SyncGroupMembersAction POST n INTERNAL,API APP ADMIN
/_dr/task/tmchCrl TmchCrlAction POST y INTERNAL,API APP ADMIN /_dr/task/syncRegistrarsSheet SyncRegistrarsSheetAction POST n INTERNAL,API APP ADMIN
/_dr/task/tmchDnl TmchDnlAction POST y INTERNAL,API APP ADMIN /_dr/task/tmchCrl TmchCrlAction POST y INTERNAL,API APP ADMIN
/_dr/task/tmchSmdrl TmchSmdrlAction POST y INTERNAL,API APP ADMIN /_dr/task/tmchDnl TmchDnlAction POST y INTERNAL,API APP ADMIN
/_dr/task/updateRegistrarRdapBaseUrls UpdateRegistrarRdapBaseUrlsAction GET y INTERNAL,API APP ADMIN /_dr/task/tmchSmdrl TmchSmdrlAction POST y INTERNAL,API APP ADMIN
/_dr/task/updateSnapshotView UpdateSnapshotViewAction POST n INTERNAL,API APP ADMIN /_dr/task/updateRegistrarRdapBaseUrls UpdateRegistrarRdapBaseUrlsAction GET y INTERNAL,API APP ADMIN
/_dr/task/uploadDatastoreBackup UploadDatastoreBackupAction POST n INTERNAL,API APP ADMIN /_dr/task/updateSnapshotView UpdateSnapshotViewAction POST n INTERNAL,API APP ADMIN
/_dr/task/wipeOutCloudSql WipeOutCloudSqlAction GET n INTERNAL,API APP ADMIN /_dr/task/uploadDatastoreBackup UploadDatastoreBackupAction POST n INTERNAL,API APP ADMIN
/_dr/task/wipeOutDatastore WipeoutDatastoreAction GET n INTERNAL,API APP ADMIN /_dr/task/wipeOutCloudSql WipeOutCloudSqlAction GET n INTERNAL,API APP ADMIN
/_dr/task/wipeOutDatastore WipeoutDatastoreAction GET n INTERNAL,API APP ADMIN