From 1dcf34ccc20e8bfcf1ed0500d7488fc3500be37c Mon Sep 17 00:00:00 2001 From: Weimin Yu Date: Fri, 12 Jan 2024 17:17:51 -0500 Subject: [PATCH] Report BSA block status in DomainCheckFlow (#2288) - Registered names are not affected. - Reserved names are not affected. - Names that are none of the above and match some BSA labels are reported as blocked. --- .../bsa/persistence/BsaLabelUtils.java | 27 ++++++++++-- .../google/registry/flows/CheckApiAction.java | 2 +- .../flows/domain/DomainCheckFlow.java | 41 ++++++++++++++++++- .../registry/flows/CheckApiActionTest.java | 2 +- .../flows/domain/DomainCheckFlowTest.java | 32 +++++++++++++++ 5 files changed, 98 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/google/registry/bsa/persistence/BsaLabelUtils.java b/core/src/main/java/google/registry/bsa/persistence/BsaLabelUtils.java index dd03b0b34..f320ca027 100644 --- a/core/src/main/java/google/registry/bsa/persistence/BsaLabelUtils.java +++ b/core/src/main/java/google/registry/bsa/persistence/BsaLabelUtils.java @@ -14,6 +14,9 @@ package google.registry.bsa.persistence; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import static google.registry.config.RegistryConfig.getEppResourceCachingDuration; import static google.registry.config.RegistryConfig.getEppResourceMaxCachedEntries; import static google.registry.model.CacheUtils.newCacheBuilder; @@ -22,10 +25,15 @@ import static google.registry.persistence.transaction.TransactionManagerFactory. import com.github.benmanes.caffeine.cache.CacheLoader; import com.github.benmanes.caffeine.cache.LoadingCache; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import google.registry.persistence.VKey; import java.time.Duration; import java.util.Map; import java.util.Optional; +import org.apache.commons.lang3.stream.Streams; /** Helpers for {@link BsaLabel}. */ public final class BsaLabelUtils { @@ -43,9 +51,11 @@ public final class BsaLabelUtils { @Override public Map, Optional> loadAll( Iterable> keys) { - // TODO(b/309173359): need this for DomainCheckFlow - throw new UnsupportedOperationException( - "LoadAll not supported by the BsaLabel cache loader."); + ImmutableMap, BsaLabel> existingLabels = + replicaTm().reTransact(() -> replicaTm().loadByKeysIfPresent(keys)); + return Streams.of(keys) + .collect( + toImmutableMap(key -> key, key -> Optional.ofNullable(existingLabels.get(key)))); } }; @@ -84,4 +94,15 @@ public final class BsaLabelUtils { public static boolean isLabelBlocked(String domainLabel) { return cacheBsaLabels.get(BsaLabel.vKey(domainLabel)).isPresent(); } + + /** Returns the elements in {@code domainLabels} that are blocked by BSA. */ + public static ImmutableSet getBlockedLabels(ImmutableCollection domainLabels) { + ImmutableList> queriedLabels = + domainLabels.stream().map(BsaLabel::vKey).collect(toImmutableList()); + return cacheBsaLabels.getAll(queriedLabels).values().stream() + .filter(Optional::isPresent) + .map(Optional::get) + .map(BsaLabel::getLabel) + .collect(toImmutableSet()); + } } diff --git a/core/src/main/java/google/registry/flows/CheckApiAction.java b/core/src/main/java/google/registry/flows/CheckApiAction.java index 21aa726b4..233a8d35e 100644 --- a/core/src/main/java/google/registry/flows/CheckApiAction.java +++ b/core/src/main/java/google/registry/flows/CheckApiAction.java @@ -159,7 +159,7 @@ public class CheckApiAction implements Runnable { ? "In use" : (isReserved ? reservedError.get() - : (isBsaBlocked ? "Blocked by the Brand Safety Alliance" : null)); + : (isBsaBlocked ? "Blocked by a GlobalBlock service" : null)); ImmutableMap.Builder responseBuilder = new ImmutableMap.Builder<>(); metricBuilder.status(SUCCESS).availability(availability); diff --git a/core/src/main/java/google/registry/flows/domain/DomainCheckFlow.java b/core/src/main/java/google/registry/flows/domain/DomainCheckFlow.java index 44ac3f616..0179f2dc6 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainCheckFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainCheckFlow.java @@ -18,6 +18,7 @@ import static com.google.common.base.Strings.emptyToNull; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static google.registry.bsa.persistence.BsaLabelUtils.getBlockedLabels; import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn; import static google.registry.flows.ResourceFlowUtils.verifyTargetIdCount; import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld; @@ -34,6 +35,7 @@ import static google.registry.model.tld.Tld.TldState.START_DATE_SUNRISE; import static google.registry.model.tld.label.ReservationType.getTypeOfHighestSeverity; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -83,9 +85,12 @@ import google.registry.model.tld.label.ReservationType; import google.registry.persistence.VKey; import google.registry.pricing.PricingEngineProxy; import google.registry.util.Clock; +import java.util.Collection; import java.util.HashSet; +import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import javax.inject.Inject; import org.joda.time.DateTime; @@ -177,6 +182,12 @@ public final class DomainCheckFlow implements TransactionalFlow { .build()); ImmutableMap> existingDomains = ForeignKeyUtils.load(Domain.class, domainNames, now); + // Check block labels only when there are unregistered domains, since "In use" goes before + // "Blocked by BSA". + ImmutableSet bsaBlockedDomainNames = + existingDomains.size() == parsedDomains.size() + ? ImmutableSet.of() + : getBsaBlockedDomains(parsedDomains.values()); Optional allocationTokenExtension = eppInput.getSingleExtension(AllocationTokenExtension.class); Optional tokenDomainCheckResults = @@ -203,6 +214,7 @@ public final class DomainCheckFlow implements TransactionalFlow { getMessageForCheck( parsedDomains.get(domainName), existingDomains, + bsaBlockedDomainNames, domainCheckResults, tldStates, allocationToken); @@ -234,6 +246,7 @@ public final class DomainCheckFlow implements TransactionalFlow { private Optional getMessageForCheck( InternetDomainName domainName, ImmutableMap> existingDomains, + ImmutableSet bsaBlockedDomains, ImmutableMap tokenCheckResults, ImmutableMap tldStates, Optional allocationToken) { @@ -251,7 +264,18 @@ public final class DomainCheckFlow implements TransactionalFlow { } } } - return Optional.ofNullable(emptyToNull(tokenCheckResults.get(domainName))); + Optional tokenResult = + Optional.ofNullable(emptyToNull(tokenCheckResults.get(domainName))); + if (tokenResult.isPresent()) { + return tokenResult; + } + if (bsaBlockedDomains.contains(domainName)) { + // TODO(weiminyu): extract to a constant for here and CheckApiAction. + // Excerpt from BSA's custom message. Max len 32 chars by EPP XML schema. + return Optional.of("Blocked by a GlobalBlock service"); + } else { + return Optional.empty(); + } } /** Handle the fee check extension. */ @@ -415,6 +439,21 @@ public final class DomainCheckFlow implements TransactionalFlow { return availabilityCheckDomains; } + static ImmutableSet getBsaBlockedDomains( + ImmutableCollection parsedDomains) { + Map> labelToDomainNames = + parsedDomains.stream() + .collect( + Collectors.groupingBy( + parsedDomain -> parsedDomain.parts().get(0), toImmutableList())); + ImmutableSet blockedLabels = + getBlockedLabels(ImmutableList.copyOf(labelToDomainNames.keySet())); + labelToDomainNames.keySet().retainAll(blockedLabels); + return labelToDomainNames.values().stream() + .flatMap(Collection::stream) + .collect(toImmutableSet()); + } + /** By server policy, fee check names must be listed in the availability check. */ static class OnlyCheckedNamesCanBeFeeCheckedException extends ParameterValuePolicyErrorException { OnlyCheckedNamesCanBeFeeCheckedException() { diff --git a/core/src/test/java/google/registry/flows/CheckApiActionTest.java b/core/src/test/java/google/registry/flows/CheckApiActionTest.java index f36d8601b..a773e6d8c 100644 --- a/core/src/test/java/google/registry/flows/CheckApiActionTest.java +++ b/core/src/test/java/google/registry/flows/CheckApiActionTest.java @@ -296,7 +296,7 @@ class CheckApiActionTest { "tier", "premium", "status", "success", "available", false, - "reason", "Blocked by the Brand Safety Alliance"); + "reason", "Blocked by a GlobalBlock service"); verifySuccessMetric(PREMIUM, BSA_BLOCKED); } diff --git a/core/src/test/java/google/registry/flows/domain/DomainCheckFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainCheckFlowTest.java index 49075a846..b05b5642f 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainCheckFlowTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainCheckFlowTest.java @@ -44,6 +44,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Ordering; +import google.registry.bsa.persistence.BsaTestingUtils; import google.registry.flows.EppException; import google.registry.flows.FlowUtils.NotLoggedInException; import google.registry.flows.FlowUtils.UnknownCurrencyEppException; @@ -157,6 +158,37 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase