diff --git a/core/src/main/java/google/registry/model/tld/Tld.java b/core/src/main/java/google/registry/model/tld/Tld.java index 2a621ddac..60b7403dc 100644 --- a/core/src/main/java/google/registry/model/tld/Tld.java +++ b/core/src/main/java/google/registry/model/tld/Tld.java @@ -199,16 +199,16 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl /** Returns the TLD entities for the given TLD strings, throwing if any don't exist. */ public static ImmutableSet get(Set tlds) { - Map> registries = CACHE.getAll(tlds); - ImmutableSet missingRegistries = - registries.entrySet().stream() + Map> tldObjects = CACHE.getAll(tlds); + ImmutableSet missingTlds = + tldObjects.entrySet().stream() .filter(e -> e.getValue().isEmpty()) .map(Map.Entry::getKey) .collect(toImmutableSet()); - if (missingRegistries.isEmpty()) { - return registries.values().stream().map(Optional::get).collect(toImmutableSet()); + if (missingTlds.isEmpty()) { + return tldObjects.values().stream().map(Optional::get).collect(toImmutableSet()); } else { - throw new TldNotFoundException(missingRegistries); + throw new TldNotFoundException(missingTlds); } } diff --git a/core/src/main/java/google/registry/model/tld/label/PremiumList.java b/core/src/main/java/google/registry/model/tld/label/PremiumList.java index ffe3930b1..80672466f 100644 --- a/core/src/main/java/google/registry/model/tld/label/PremiumList.java +++ b/core/src/main/java/google/registry/model/tld/label/PremiumList.java @@ -14,10 +14,10 @@ package google.registry.model.tld.label; -import static com.google.common.base.Charsets.US_ASCII; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.hash.Funnels.stringFunnel; +import static java.nio.charset.StandardCharsets.US_ASCII; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableMap; diff --git a/core/src/main/java/google/registry/persistence/converter/BloomFilterConverter.java b/core/src/main/java/google/registry/persistence/converter/BloomFilterConverter.java index 9f180f0a3..0b4794438 100644 --- a/core/src/main/java/google/registry/persistence/converter/BloomFilterConverter.java +++ b/core/src/main/java/google/registry/persistence/converter/BloomFilterConverter.java @@ -13,8 +13,8 @@ // limitations under the License. package google.registry.persistence.converter; -import static com.google.common.base.Charsets.US_ASCII; import static com.google.common.hash.Funnels.stringFunnel; +import static java.nio.charset.StandardCharsets.US_ASCII; import com.google.common.hash.BloomFilter; import jakarta.persistence.AttributeConverter; diff --git a/core/src/main/java/google/registry/rdap/RdapActionBase.java b/core/src/main/java/google/registry/rdap/RdapActionBase.java index f7c70ed0c..d81952067 100644 --- a/core/src/main/java/google/registry/rdap/RdapActionBase.java +++ b/core/src/main/java/google/registry/rdap/RdapActionBase.java @@ -14,14 +14,15 @@ package google.registry.rdap; -import static com.google.common.base.Charsets.UTF_8; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN; import static google.registry.request.Actions.getPathForAction; import static google.registry.util.DomainNameUtils.canonicalizeHostname; import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST; import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; +import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND; import static jakarta.servlet.http.HttpServletResponse.SC_OK; +import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.flogger.FluentLogger; import com.google.common.net.MediaType; @@ -41,6 +42,7 @@ import google.registry.request.Parameter; import google.registry.request.RequestMethod; import google.registry.request.RequestPath; import google.registry.request.Response; +import google.registry.util.Clock; import java.net.URI; import java.net.URISyntaxException; import java.util.Optional; @@ -60,6 +62,10 @@ public abstract class RdapActionBase implements Runnable { private static final MediaType RESPONSE_MEDIA_TYPE = MediaType.create("application", "rdap+json").withCharset(UTF_8); + private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().create(); + private static final Gson FORMATTED_OUTPUT_GSON = + new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); + /** Whether to include or exclude deleted items from a query. */ protected enum DeletedItemHandling { EXCLUDE, @@ -75,6 +81,7 @@ public abstract class RdapActionBase implements Runnable { @Inject @Parameter("formatOutput") Optional formatOutputParam; @Inject @Config("rdapResultSetMaxSize") int rdapResultSetMaxSize; @Inject RdapMetrics rdapMetrics; + @Inject Clock clock; /** Builder for metric recording. */ final RdapMetrics.RdapMetricInformation.Builder metricInformationBuilder = @@ -152,6 +159,10 @@ public abstract class RdapActionBase implements Runnable { response.setStatus(SC_OK); setPayload(replyObject); metricInformationBuilder.setStatusCode(SC_OK); + } catch (RdapDomainAction.DomainBlockedByBsaException e) { + logger.atInfo().withCause(e).log("Domain blocked by BSA"); + setErrorCodes(SC_NOT_FOUND); + setPayload(new RdapObjectClasses.DomainBlockedByBsaErrorResponse(e.getMessage())); } catch (HttpException e) { logger.atInfo().withCause(e).log("Error in RDAP."); setError(e.getResponseCode(), e.getResponseCodeString(), e.getMessage()); @@ -166,8 +177,7 @@ public abstract class RdapActionBase implements Runnable { } void setError(int status, String title, String description) { - metricInformationBuilder.setStatusCode(status); - response.setStatus(status); + setErrorCodes(status); try { setPayload(ErrorResponse.create(status, title, description)); } catch (Exception ex) { @@ -176,21 +186,18 @@ public abstract class RdapActionBase implements Runnable { } } + void setErrorCodes(int status) { + metricInformationBuilder.setStatusCode(status); + response.setStatus(status); + } + void setPayload(ReplyPayloadBase replyObject) { if (requestMethod == Action.Method.HEAD) { return; } - - GsonBuilder gsonBuilder = new GsonBuilder(); - gsonBuilder.disableHtmlEscaping(); - if (formatOutputParam.orElse(false)) { - gsonBuilder.setPrettyPrinting(); - } - Gson gson = gsonBuilder.create(); - TopLevelReplyObject topLevelObject = TopLevelReplyObject.create(replyObject, rdapJsonFormatter.createTosNotice()); - + Gson gson = formatOutputParam.orElse(false) ? FORMATTED_OUTPUT_GSON : GSON; response.setPayload(gson.toJson(topLevelObject.toJson())); } diff --git a/core/src/main/java/google/registry/rdap/RdapDataStructures.java b/core/src/main/java/google/registry/rdap/RdapDataStructures.java index 5814da2df..2947dcf2c 100644 --- a/core/src/main/java/google/registry/rdap/RdapDataStructures.java +++ b/core/src/main/java/google/registry/rdap/RdapDataStructures.java @@ -119,7 +119,7 @@ final class RdapDataStructures { */ @AutoValue @RestrictJsonNames("notices[]") - abstract static class Notice extends NoticeOrRemark { + public abstract static class Notice extends NoticeOrRemark { /** * Notice and Remark Type are defined in 10.2.1 of RFC 9083. diff --git a/core/src/main/java/google/registry/rdap/RdapDomainAction.java b/core/src/main/java/google/registry/rdap/RdapDomainAction.java index 5f8a9524a..5738eb2ce 100644 --- a/core/src/main/java/google/registry/rdap/RdapDomainAction.java +++ b/core/src/main/java/google/registry/rdap/RdapDomainAction.java @@ -20,8 +20,11 @@ import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.HEAD; import static google.registry.util.DateTimeUtils.START_OF_TIME; +import com.google.common.net.InternetDomainName; import google.registry.flows.EppException; +import google.registry.flows.domain.DomainFlowUtils; import google.registry.model.domain.Domain; +import google.registry.model.tld.Tld; import google.registry.rdap.RdapJsonFormatter.OutputDataType; import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapObjectClasses.RdapDomain; @@ -51,8 +54,9 @@ public class RdapDomainAction extends RdapActionBase { // RDAP Technical Implementation Guide 2.1.1 - we must support A-label (Punycode) and U-label // (Unicode) formats. canonicalizeName will transform Unicode to Punycode so we support both. pathSearchString = canonicalizeName(pathSearchString); + InternetDomainName domainName; try { - validateDomainName(pathSearchString); + domainName = validateDomainName(pathSearchString); } catch (EppException e) { throw new BadRequestException( String.format( @@ -66,6 +70,7 @@ public class RdapDomainAction extends RdapActionBase { pathSearchString, shouldIncludeDeleted() ? START_OF_TIME : rdapJsonFormatter.getRequestTime()); if (domain.isEmpty() || !isAuthorized(domain.get())) { + handlePossibleBsaBlock(domainName); // RFC7480 5.3 - if the server wishes to respond that it doesn't have data satisfying the // query, it MUST reply with 404 response code. // @@ -75,4 +80,17 @@ public class RdapDomainAction extends RdapActionBase { } return rdapJsonFormatter.createRdapDomain(domain.get(), OutputDataType.FULL); } + + private void handlePossibleBsaBlock(InternetDomainName domainName) { + Tld tld = Tld.get(domainName.parent().toString()); + if (DomainFlowUtils.isBlockedByBsa(domainName.parts().getFirst(), tld, clock.nowUtc())) { + throw new DomainBlockedByBsaException(domainName + " blocked by BSA"); + } + } + + static class DomainBlockedByBsaException extends RuntimeException { + DomainBlockedByBsaException(String message) { + super(message); + } + } } diff --git a/core/src/main/java/google/registry/rdap/RdapIcannStandardInformation.java b/core/src/main/java/google/registry/rdap/RdapIcannStandardInformation.java index 3f255d944..3b8e4a700 100644 --- a/core/src/main/java/google/registry/rdap/RdapIcannStandardInformation.java +++ b/core/src/main/java/google/registry/rdap/RdapIcannStandardInformation.java @@ -63,8 +63,21 @@ public class RdapIcannStandardInformation { .build()) .build(); + /** Not required, but provided when a domain is blocked by BSA. */ + private static final Notice DOMAIN_BLOCKED_BY_BSA_NOTICE = + Notice.builder() + .setTitle("Blocked Domain") + .setDescription("This name has been blocked by a GlobalBlock service") + .addLink( + Link.builder() + .setRel("alternate") + .setHref("https://brandsafetyalliance.co") + .setType("text/html") + .build()) + .build(); + /** Boilerplate notices required by domain responses. */ - static final ImmutableList domainBoilerplateNotices = + static final ImmutableList DOMAIN_BOILERPLATE_NOTICES = ImmutableList.of( CONFORMANCE_NOTICE, // RDAP Response Profile 2.6.3 @@ -72,8 +85,12 @@ public class RdapIcannStandardInformation { // RDAP Response Profile 2.11 INACCURACY_COMPLAINT_FORM_NOTICE); + /** Boilerplate notice for when a domain is blocked by BSA. */ + static final ImmutableList DOMAIN_BLOCKED_BY_BSA_BOILERPLATE_NOTICES = + ImmutableList.of(DOMAIN_BLOCKED_BY_BSA_NOTICE); + /** Boilerplate remarks required by nameserver and entity responses. */ - static final ImmutableList nameserverAndEntityBoilerplateNotices = + static final ImmutableList NAMESERVER_AND_ENTITY_BOILERPLATE_NOTICES = ImmutableList.of(CONFORMANCE_NOTICE); /** diff --git a/core/src/main/java/google/registry/rdap/RdapObjectClasses.java b/core/src/main/java/google/registry/rdap/RdapObjectClasses.java index 6936f61dc..1a54e0fb1 100644 --- a/core/src/main/java/google/registry/rdap/RdapObjectClasses.java +++ b/core/src/main/java/google/registry/rdap/RdapObjectClasses.java @@ -39,6 +39,7 @@ import google.registry.rdap.RdapDataStructures.RdapConformance; import google.registry.rdap.RdapDataStructures.RdapStatus; import google.registry.rdap.RdapDataStructures.Remark; import google.registry.util.Idn; +import jakarta.servlet.http.HttpServletResponse; import java.util.Optional; /** Object Classes defined in RFC 9083 section 5. */ @@ -137,10 +138,22 @@ final class RdapObjectClasses { * suppress them for other types of responses (e.g. help). */ public enum BoilerplateType { - DOMAIN, - NAMESERVER, - ENTITY, - OTHER + DOMAIN(RdapIcannStandardInformation.DOMAIN_BOILERPLATE_NOTICES), + DOMAIN_BLOCKED_BY_BSA(RdapIcannStandardInformation.DOMAIN_BLOCKED_BY_BSA_BOILERPLATE_NOTICES), + NAMESERVER(RdapIcannStandardInformation.NAMESERVER_AND_ENTITY_BOILERPLATE_NOTICES), + ENTITY(RdapIcannStandardInformation.NAMESERVER_AND_ENTITY_BOILERPLATE_NOTICES), + OTHER(ImmutableList.of()); + + @SuppressWarnings("ImmutableEnumChecker") // immutable lists are, in fact, immutable + private final ImmutableList notices; + + BoilerplateType(ImmutableList notices) { + this.notices = notices; + } + + public ImmutableList getNotices() { + return notices; + } } /** @@ -173,14 +186,7 @@ final class RdapObjectClasses { @JsonableElement("notices[]") abstract Notice aTosNotice(); @JsonableElement("notices") ImmutableList boilerplateNotices() { - return switch (aAreplyObject().boilerplateType) { - case DOMAIN -> RdapIcannStandardInformation.domainBoilerplateNotices; - case NAMESERVER, ENTITY -> - RdapIcannStandardInformation.nameserverAndEntityBoilerplateNotices; - default -> // things other than domains, nameservers and entities do not yet have - // boilerplate - ImmutableList.of(); - }; + return aAreplyObject().boilerplateType.getNotices(); } static TopLevelReplyObject create(ReplyPayloadBase replyObject, Notice tosNotice) { @@ -532,6 +538,25 @@ final class RdapObjectClasses { } } + /** Specialized error response body for when a domain is blocked by BSA. */ + @RestrictJsonNames({}) + @SuppressWarnings("UnusedVariable") + public static class DomainBlockedByBsaErrorResponse extends ReplyPayloadBase { + + @JsonableElement private static final LanguageIdentifier lang = LanguageIdentifier.EN; + + @JsonableElement private static final int errorCode = HttpServletResponse.SC_NOT_FOUND; + + @JsonableElement private static final String title = "Not Found"; + + @JsonableElement private final ImmutableList description; + + DomainBlockedByBsaErrorResponse(String message) { + super(BoilerplateType.DOMAIN_BLOCKED_BY_BSA); + this.description = ImmutableList.of(message); + } + } + /** Error Response Body defined in 6 of RFC 9083. */ @RestrictJsonNames({}) @AutoValue diff --git a/core/src/main/java/google/registry/rdap/RdapResultSet.java b/core/src/main/java/google/registry/rdap/RdapResultSet.java index bbaa41760..a65faa52e 100644 --- a/core/src/main/java/google/registry/rdap/RdapResultSet.java +++ b/core/src/main/java/google/registry/rdap/RdapResultSet.java @@ -27,7 +27,7 @@ import java.util.List; * @param numResourcesRetrieved Number of resources retrieved from the database in the process of * assembling the data set. */ -record RdapResultSet( +public record RdapResultSet( ImmutableList resources, IncompletenessWarningType incompletenessWarningType, int numResourcesRetrieved) { diff --git a/core/src/main/java/google/registry/rdap/RdapSearchActionBase.java b/core/src/main/java/google/registry/rdap/RdapSearchActionBase.java index a6138e8ee..c6f89b35f 100644 --- a/core/src/main/java/google/registry/rdap/RdapSearchActionBase.java +++ b/core/src/main/java/google/registry/rdap/RdapSearchActionBase.java @@ -14,9 +14,9 @@ package google.registry.rdap; -import static com.google.common.base.Charsets.UTF_8; import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm; import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; @@ -69,7 +69,7 @@ public abstract class RdapSearchActionBase extends RdapActionBase { public final BaseSearchResponse getJsonObjectForResource( String pathSearchString, boolean isHeadRequest) { // The pathSearchString is not used by search commands. - if (pathSearchString.length() > 0) { + if (!pathSearchString.isEmpty()) { throw new BadRequestException("Unexpected path"); } decodeCursorToken(); @@ -323,7 +323,8 @@ public abstract class RdapSearchActionBase extends RdapActionBase { if (partialStringQuery.getHasWildcard()) { builder = builder.where( - filterField, criteriaBuilder::like, + filterField, + criteriaBuilder::like, String.format("%s%%", partialStringQuery.getInitialString())); } else { // no wildcard means we use a standard equals query diff --git a/core/src/main/java/google/registry/rdap/RdapSearchResults.java b/core/src/main/java/google/registry/rdap/RdapSearchResults.java index 8b6cf0b73..d20eef85d 100644 --- a/core/src/main/java/google/registry/rdap/RdapSearchResults.java +++ b/core/src/main/java/google/registry/rdap/RdapSearchResults.java @@ -40,7 +40,7 @@ import java.util.Optional; abstract class RdapSearchResults { /** Responding To Searches defined in 8 of RFC 9083. */ - abstract static class BaseSearchResponse extends ReplyPayloadBase { + public abstract static class BaseSearchResponse extends ReplyPayloadBase { abstract IncompletenessWarningType incompletenessWarningType(); abstract ImmutableMap navigationLinks(); diff --git a/core/src/main/java/google/registry/request/HttpException.java b/core/src/main/java/google/registry/request/HttpException.java index 287b35cde..28c160903 100644 --- a/core/src/main/java/google/registry/request/HttpException.java +++ b/core/src/main/java/google/registry/request/HttpException.java @@ -20,6 +20,7 @@ import com.google.common.flogger.FluentLogger; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.logging.Level; +import javax.annotation.Nullable; /** Base for exceptions that cause an HTTP error response. */ public abstract class HttpException extends RuntimeException { @@ -33,13 +34,14 @@ public abstract class HttpException extends RuntimeException { private final int responseCode; - protected HttpException(int responseCode, String message, Throwable cause, Level logLevel) { + protected HttpException( + int responseCode, String message, @Nullable Throwable cause, Level logLevel) { super(message, cause); this.responseCode = responseCode; this.logLevel = logLevel; } - protected HttpException(int responseCode, String message, Throwable cause) { + protected HttpException(int responseCode, String message, @Nullable Throwable cause) { this(responseCode, message, cause, Level.INFO); } @@ -117,22 +119,6 @@ public abstract class HttpException extends RuntimeException { } } - /** Exception that causes a 403 response. */ - public static final class ForbiddenException extends HttpException { - public ForbiddenException(String message) { - super(HttpServletResponse.SC_FORBIDDEN, message, null); - } - - public ForbiddenException(String message, Exception cause) { - super(HttpServletResponse.SC_FORBIDDEN, message, cause); - } - - @Override - public String getResponseCodeString() { - return "Forbidden"; - } - } - /** Exception that causes a 404 response. */ public static final class NotFoundException extends HttpException { public NotFoundException() { @@ -149,18 +135,6 @@ public abstract class HttpException extends RuntimeException { } } - /** Exception that causes a 405 response. */ - public static final class MethodNotAllowedException extends HttpException { - public MethodNotAllowedException() { - super(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "Method not allowed", null); - } - - @Override - public String getResponseCodeString() { - return "Method Not Allowed"; - } - } - /** Exception that causes a 409 response. */ public static final class ConflictException extends HttpException { public ConflictException(String message) { diff --git a/core/src/test/java/google/registry/persistence/converter/BloomFilterConverterTest.java b/core/src/test/java/google/registry/persistence/converter/BloomFilterConverterTest.java index bbaf9d735..4ac4aad85 100644 --- a/core/src/test/java/google/registry/persistence/converter/BloomFilterConverterTest.java +++ b/core/src/test/java/google/registry/persistence/converter/BloomFilterConverterTest.java @@ -13,11 +13,11 @@ // limitations under the License. package google.registry.persistence.converter; -import static com.google.common.base.Charsets.US_ASCII; import static com.google.common.hash.Funnels.stringFunnel; import static com.google.common.truth.Truth.assertThat; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.testing.DatabaseHelper.insertInDb; +import static java.nio.charset.StandardCharsets.US_ASCII; import com.google.common.collect.ImmutableSet; import com.google.common.hash.BloomFilter; diff --git a/core/src/test/java/google/registry/persistence/transaction/JpaTransactionManagerExtension.java b/core/src/test/java/google/registry/persistence/transaction/JpaTransactionManagerExtension.java index 72436681b..fe6d0f09b 100644 --- a/core/src/test/java/google/registry/persistence/transaction/JpaTransactionManagerExtension.java +++ b/core/src/test/java/google/registry/persistence/transaction/JpaTransactionManagerExtension.java @@ -21,7 +21,6 @@ import static google.registry.testing.DatabaseHelper.insertSimpleResources; import static java.nio.charset.StandardCharsets.UTF_8; import static org.testcontainers.containers.PostgreSQLContainer.POSTGRESQL_PORT; -import com.google.common.base.Charsets; import com.google.common.base.Joiner; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; @@ -305,7 +304,7 @@ public abstract class JpaTransactionManagerExtension private static String readSqlInClassPath(String sqlScriptPath) { try { - return Resources.toString(Resources.getResource(sqlScriptPath), Charsets.UTF_8); + return Resources.toString(Resources.getResource(sqlScriptPath), UTF_8); } catch (IOException e) { throw new UncheckedIOException(e); } diff --git a/core/src/test/java/google/registry/rdap/RdapActionBaseTestCase.java b/core/src/test/java/google/registry/rdap/RdapActionBaseTestCase.java index b36095718..b278046be 100644 --- a/core/src/test/java/google/registry/rdap/RdapActionBaseTestCase.java +++ b/core/src/test/java/google/registry/rdap/RdapActionBaseTestCase.java @@ -85,6 +85,7 @@ abstract class RdapActionBaseTestCase { action.rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter(clock); action.rdapMetrics = rdapMetrics; action.requestMethod = GET; + action.clock = new FakeClock(DateTime.parse("2025-01-01T00:00:00.000Z")); logout(); } diff --git a/core/src/test/java/google/registry/rdap/RdapDomainActionTest.java b/core/src/test/java/google/registry/rdap/RdapDomainActionTest.java index 957713ff2..4ea3d648a 100644 --- a/core/src/test/java/google/registry/rdap/RdapDomainActionTest.java +++ b/core/src/test/java/google/registry/rdap/RdapDomainActionTest.java @@ -15,6 +15,7 @@ package google.registry.rdap; import static com.google.common.truth.Truth.assertThat; +import static google.registry.bsa.persistence.BsaTestingUtils.persistBsaLabel; import static google.registry.model.EppResourceUtils.loadByForeignKey; import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.persistActiveDomain; @@ -27,8 +28,11 @@ import static google.registry.testing.FullFieldsTestEntityHelper.makeHistoryEntr import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar; import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarPocs; import static google.registry.testing.GsonSubject.assertAboutJson; +import static google.registry.util.DateTimeUtils.START_OF_TIME; import static org.mockito.Mockito.verify; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.gson.JsonObject; import google.registry.model.contact.Contact; @@ -608,6 +612,34 @@ class RdapDomainActionTest extends RdapActionBaseTestCase { .build()); } + @Test + void testBlockedByBsa() { + persistResource( + Tld.get("lol").asBuilder().setBsaEnrollStartTime(Optional.of(START_OF_TIME)).build()); + persistBsaLabel("example"); + ImmutableMap expectedBsaNotice = + ImmutableMap.of( + "description", + ImmutableList.of("This name has been blocked by a GlobalBlock service"), + "title", + "Blocked Domain", + "links", + ImmutableList.of( + ImmutableMap.of( + "href", + "https://brandsafetyalliance.co", + "rel", + "alternate", + "type", + "text/html"))); + JsonObject expectedErrorResponse = generateExpectedJsonError("example.lol blocked by BSA", 404); + expectedErrorResponse + .getAsJsonArray("notices") + .add(RdapTestHelper.GSON.toJsonTree(expectedBsaNotice)); + assertAboutJson().that(generateActualJson("example.lol")).isEqualTo(expectedErrorResponse); + assertThat(response.getStatus()).isEqualTo(404); + } + private Domain persistActiveDomainWithHost( String label, String tld, DateTime creationTime, DateTime expirationTime) { return persistResource( diff --git a/core/src/test/java/google/registry/rdap/RdapTestHelper.java b/core/src/test/java/google/registry/rdap/RdapTestHelper.java index 5bc323b8e..b4e7435d9 100644 --- a/core/src/test/java/google/registry/rdap/RdapTestHelper.java +++ b/core/src/test/java/google/registry/rdap/RdapTestHelper.java @@ -33,8 +33,7 @@ import java.util.Map; /** Test helper methods for RDAP tests. */ class RdapTestHelper { - private static final Gson GSON = - new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); + static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); static JsonElement createJson(String... lines) { return GSON.fromJson(Joiner.on("\n").join(lines), JsonElement.class); @@ -240,5 +239,4 @@ class RdapTestHelper { obj.remove("rdapConformance"); return reply; } - }