diff --git a/core/build.gradle b/core/build.gradle index c1315e9ac..797580b32 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -38,7 +38,6 @@ def jsDir = "${project.projectDir}/src/main/javascript" def outcastTestPatterns = [ // Problem seems to lie with AppEngine TaskQueue for test. "google/registry/batch/RefreshDnsOnHostRenameActionTest.*", - "google/registry/flows/CheckApiActionTest.*", "google/registry/flows/EppLifecycleHostTest.*", "google/registry/flows/domain/DomainCreateFlowTest.*", "google/registry/flows/domain/DomainUpdateFlowTest.*", diff --git a/core/src/main/java/google/registry/flows/CheckApiAction.java b/core/src/main/java/google/registry/flows/CheckApiAction.java index 7f27a58f5..21aa726b4 100644 --- a/core/src/main/java/google/registry/flows/CheckApiAction.java +++ b/core/src/main/java/google/registry/flows/CheckApiAction.java @@ -16,12 +16,14 @@ package google.registry.flows; import static com.google.common.base.Strings.nullToEmpty; import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN; +import static google.registry.flows.domain.DomainFlowUtils.isBlockedByBsa; import static google.registry.flows.domain.DomainFlowUtils.validateDomainName; import static google.registry.flows.domain.DomainFlowUtils.validateDomainNameWithIdnTables; import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPredelegation; import static google.registry.model.tld.label.ReservationType.getTypeOfHighestSeverity; import static google.registry.model.tld.label.ReservedList.getReservationTypes; import static google.registry.monitoring.whitebox.CheckApiMetric.Availability.AVAILABLE; +import static google.registry.monitoring.whitebox.CheckApiMetric.Availability.BSA_BLOCKED; import static google.registry.monitoring.whitebox.CheckApiMetric.Availability.REGISTERED; import static google.registry.monitoring.whitebox.CheckApiMetric.Availability.RESERVED; import static google.registry.monitoring.whitebox.CheckApiMetric.Status.INVALID_NAME; @@ -30,6 +32,7 @@ import static google.registry.monitoring.whitebox.CheckApiMetric.Status.SUCCESS; import static google.registry.monitoring.whitebox.CheckApiMetric.Status.UNKNOWN_ERROR; import static google.registry.monitoring.whitebox.CheckApiMetric.Tier.PREMIUM; import static google.registry.monitoring.whitebox.CheckApiMetric.Tier.STANDARD; +import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.pricing.PricingEngineProxy.isDomainPremium; import static google.registry.util.DomainNameUtils.canonicalizeHostname; import static org.json.simple.JSONValue.toJSONString; @@ -55,7 +58,6 @@ import google.registry.request.Parameter; import google.registry.request.RequestParameters; import google.registry.request.Response; import google.registry.request.auth.Auth; -import google.registry.util.Clock; import java.util.Map; import java.util.Optional; import javax.inject.Inject; @@ -79,7 +81,6 @@ public class CheckApiAction implements Runnable { String domain; @Inject Response response; - @Inject Clock clock; @Inject CheckApiMetric.Builder metricBuilder; @Inject CheckApiMetrics checkApiMetrics; @@ -104,6 +105,20 @@ public class CheckApiAction implements Runnable { private Map doCheck() { String domainString; InternetDomainName domainName; + try { + domainString = canonicalizeHostname(nullToEmpty(domain)); + domainName = validateDomainName(domainString); + return tm().transact(() -> checkDomainName(domainName)); + } catch (IllegalArgumentException | EppException e) { + metricBuilder.status(INVALID_NAME); + return fail("Must supply a valid domain name on an authoritative TLD"); + } + } + + private Map checkDomainName(InternetDomainName domainName) { + tm().assertInTransaction(); + + String domainString; try { domainString = canonicalizeHostname(nullToEmpty(domain)); domainName = validateDomainName(domainString); @@ -115,7 +130,7 @@ public class CheckApiAction implements Runnable { // Throws an EppException with a reasonable error message which will be sent back to caller. validateDomainNameWithIdnTables(domainName); - DateTime now = clock.nowUtc(); + DateTime now = tm().getTransactionTime(); Tld tld = Tld.get(domainName.parent().toString()); try { verifyNotInPredelegation(tld, now); @@ -126,13 +141,25 @@ public class CheckApiAction implements Runnable { boolean isRegistered = checkExists(domainString, now); Optional reservedError = Optional.empty(); + boolean isBsaBlocked = false; boolean isReserved = false; if (!isRegistered) { reservedError = checkReserved(domainName); isReserved = reservedError.isPresent(); } - Availability availability = isRegistered ? REGISTERED : (isReserved ? RESERVED : AVAILABLE); - String errorMsg = isRegistered ? "In use" : (isReserved ? reservedError.get() : null); + if (!isRegistered && !isReserved) { + isBsaBlocked = isBlockedByBsa(domainName.parts().get(0), tld, now); + } + Availability availability = + isRegistered + ? REGISTERED + : (isReserved ? RESERVED : (isBsaBlocked ? BSA_BLOCKED : AVAILABLE)); + String errorMsg = + isRegistered + ? "In use" + : (isReserved + ? reservedError.get() + : (isBsaBlocked ? "Blocked by the Brand Safety Alliance" : null)); ImmutableMap.Builder responseBuilder = new ImmutableMap.Builder<>(); metricBuilder.status(SUCCESS).availability(availability); diff --git a/core/src/main/java/google/registry/monitoring/whitebox/CheckApiMetric.java b/core/src/main/java/google/registry/monitoring/whitebox/CheckApiMetric.java index 5ace99012..a59799fd1 100644 --- a/core/src/main/java/google/registry/monitoring/whitebox/CheckApiMetric.java +++ b/core/src/main/java/google/registry/monitoring/whitebox/CheckApiMetric.java @@ -45,6 +45,7 @@ public abstract class CheckApiMetric { public enum Availability { RESERVED("reserved"), REGISTERED("registered"), + BSA_BLOCKED("blocked"), AVAILABLE("available"); private final String displayLabel; diff --git a/core/src/test/java/google/registry/flows/CheckApiActionTest.java b/core/src/test/java/google/registry/flows/CheckApiActionTest.java index 15e6a8ed1..162e98d99 100644 --- a/core/src/test/java/google/registry/flows/CheckApiActionTest.java +++ b/core/src/test/java/google/registry/flows/CheckApiActionTest.java @@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth8.assertThat; import static google.registry.model.tld.Tld.TldState.PREDELEGATION; import static google.registry.monitoring.whitebox.CheckApiMetric.Availability.AVAILABLE; +import static google.registry.monitoring.whitebox.CheckApiMetric.Availability.BSA_BLOCKED; import static google.registry.monitoring.whitebox.CheckApiMetric.Availability.REGISTERED; import static google.registry.monitoring.whitebox.CheckApiMetric.Availability.RESERVED; import static google.registry.monitoring.whitebox.CheckApiMetric.Tier.PREMIUM; @@ -26,8 +27,10 @@ import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.persistActiveDomain; import static google.registry.testing.DatabaseHelper.persistReservedList; import static google.registry.testing.DatabaseHelper.persistResource; +import static google.registry.util.DateTimeUtils.START_OF_TIME; import static org.mockito.Mockito.verify; +import google.registry.bsa.persistence.BsaLabelTestingUtils; import google.registry.model.tld.Tld; import google.registry.monitoring.whitebox.CheckApiMetric; import google.registry.monitoring.whitebox.CheckApiMetric.Availability; @@ -38,6 +41,7 @@ import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationT import google.registry.testing.FakeClock; import google.registry.testing.FakeResponse; import java.util.Map; +import java.util.Optional; import org.joda.time.DateTime; import org.json.simple.JSONValue; import org.junit.jupiter.api.BeforeEach; @@ -54,10 +58,11 @@ import org.mockito.junit.jupiter.MockitoExtension; class CheckApiActionTest { private static final DateTime START_TIME = DateTime.parse("2000-01-01T00:00:00.0Z"); + private final FakeClock fakeClock = new FakeClock(START_TIME); @RegisterExtension final JpaIntegrationTestExtension jpa = - new JpaTestExtensions.Builder().buildIntegrationTestExtension(); + new JpaTestExtensions.Builder().withClock(fakeClock).buildIntegrationTestExtension(); @Mock private CheckApiMetrics checkApiMetrics; @Captor private ArgumentCaptor metricCaptor; @@ -84,8 +89,6 @@ class CheckApiActionTest { CheckApiAction action = new CheckApiAction(); action.domain = domain; action.response = new FakeResponse(); - FakeClock fakeClock = new FakeClock(START_TIME); - action.clock = fakeClock; action.metricBuilder = CheckApiMetric.builder(fakeClock); action.checkApiMetrics = checkApiMetrics; fakeClock.advanceOneMilli(); @@ -283,6 +286,21 @@ class CheckApiActionTest { verifySuccessMetric(PREMIUM, RESERVED); } + @Test + void testSuccess_blockedByBsa() { + BsaLabelTestingUtils.persistBsaLabel("rich", START_OF_TIME); + persistResource( + Tld.get("example").asBuilder().setBsaEnrollStartTime(Optional.of(START_OF_TIME)).build()); + assertThat(getCheckResponse("rich.example")) + .containsExactly( + "tier", "premium", + "status", "success", + "available", false, + "reason", "Blocked by the Brand Safety Alliance"); + + verifySuccessMetric(PREMIUM, BSA_BLOCKED); + } + private void verifySuccessMetric(Tier tier, Availability availability) { verify(checkApiMetrics).incrementCheckApiRequest(metricCaptor.capture()); CheckApiMetric metric = metricCaptor.getValue();