mirror of
https://github.com/google/nomulus
synced 2026-07-06 08:06:33 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e5ebe96c74 | |||
| 2ff4d97b0a | |||
| 6b0beeb477 | |||
| d2d43f4115 |
+1
@@ -301,6 +301,7 @@
|
||||
<url><![CDATA[/_dr/task/bsaValidate]]></url>
|
||||
<name>bsaValidate</name>
|
||||
<service>bsa</service>
|
||||
<timeout>10m</timeout>
|
||||
<description>
|
||||
Validates the processed BSA data in the database against the original
|
||||
block lists.
|
||||
|
||||
@@ -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<Tld> get(Set<String> tlds) {
|
||||
Map<String, Optional<Tld>> registries = CACHE.getAll(tlds);
|
||||
ImmutableSet<String> missingRegistries =
|
||||
registries.entrySet().stream()
|
||||
Map<String, Optional<Tld>> tldObjects = CACHE.getAll(tlds);
|
||||
ImmutableSet<String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Boolean> 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()));
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Notice> domainBoilerplateNotices =
|
||||
static final ImmutableList<Notice> 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<Notice> DOMAIN_BLOCKED_BY_BSA_BOILERPLATE_NOTICES =
|
||||
ImmutableList.of(DOMAIN_BLOCKED_BY_BSA_NOTICE);
|
||||
|
||||
/** Boilerplate remarks required by nameserver and entity responses. */
|
||||
static final ImmutableList<Notice> nameserverAndEntityBoilerplateNotices =
|
||||
static final ImmutableList<Notice> NAMESERVER_AND_ENTITY_BOILERPLATE_NOTICES =
|
||||
ImmutableList.of(CONFORMANCE_NOTICE);
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<Notice> notices;
|
||||
|
||||
BoilerplateType(ImmutableList<Notice> notices) {
|
||||
this.notices = notices;
|
||||
}
|
||||
|
||||
public ImmutableList<Notice> getNotices() {
|
||||
return notices;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,14 +186,7 @@ final class RdapObjectClasses {
|
||||
@JsonableElement("notices[]") abstract Notice aTosNotice();
|
||||
|
||||
@JsonableElement("notices") ImmutableList<Notice> 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<String> 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
|
||||
|
||||
@@ -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<T extends EppResource>(
|
||||
public record RdapResultSet<T extends EppResource>(
|
||||
ImmutableList<T> resources,
|
||||
IncompletenessWarningType incompletenessWarningType,
|
||||
int numResourcesRetrieved) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<String, URI> navigationLinks();
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+4
-8
@@ -14,12 +14,11 @@
|
||||
|
||||
package google.registry.ui.server.console.domains;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.JsonElement;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
|
||||
/** An action that will run a delete EPP command on the given domain. */
|
||||
public class ConsoleBulkDomainDeleteActionType implements ConsoleDomainActionType {
|
||||
public class ConsoleBulkDomainDeleteActionType extends ConsoleDomainActionType {
|
||||
|
||||
private static final String DOMAIN_DELETE_XML =
|
||||
"""
|
||||
@@ -42,16 +41,13 @@ public class ConsoleBulkDomainDeleteActionType implements ConsoleDomainActionTyp
|
||||
</command>
|
||||
</epp>""";
|
||||
|
||||
private final String reason;
|
||||
|
||||
public ConsoleBulkDomainDeleteActionType(JsonElement jsonElement) {
|
||||
this.reason = jsonElement.getAsJsonObject().get("reason").getAsString();
|
||||
super(jsonElement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getXmlContentsToRun(String domainName) {
|
||||
return ConsoleDomainActionType.fillSubstitutions(
|
||||
DOMAIN_DELETE_XML, ImmutableMap.of("DOMAIN_NAME", domainName, "REASON", reason));
|
||||
protected String getXmlTemplate() {
|
||||
return DOMAIN_DELETE_XML;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+4
-8
@@ -14,12 +14,11 @@
|
||||
|
||||
package google.registry.ui.server.console.domains;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.JsonElement;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
|
||||
/** An action that will suspend the given domain, assigning all 5 server*Prohibited statuses. */
|
||||
public class ConsoleBulkDomainSuspendActionType implements ConsoleDomainActionType {
|
||||
public class ConsoleBulkDomainSuspendActionType extends ConsoleDomainActionType {
|
||||
|
||||
private static final String DOMAIN_SUSPEND_XML =
|
||||
"""
|
||||
@@ -52,16 +51,13 @@ public class ConsoleBulkDomainSuspendActionType implements ConsoleDomainActionTy
|
||||
</command>
|
||||
</epp>""";
|
||||
|
||||
private final String reason;
|
||||
|
||||
public ConsoleBulkDomainSuspendActionType(JsonElement jsonElement) {
|
||||
this.reason = jsonElement.getAsJsonObject().get("reason").getAsString();
|
||||
super(jsonElement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getXmlContentsToRun(String domainName) {
|
||||
return ConsoleDomainActionType.fillSubstitutions(
|
||||
DOMAIN_SUSPEND_XML, ImmutableMap.of("DOMAIN_NAME", domainName, "REASON", reason));
|
||||
protected String getXmlTemplate() {
|
||||
return DOMAIN_SUSPEND_XML;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+4
-8
@@ -14,12 +14,11 @@
|
||||
|
||||
package google.registry.ui.server.console.domains;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.JsonElement;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
|
||||
/** An action that will unsuspend the given domain, removing all 5 server*Prohibited statuses. */
|
||||
public class ConsoleBulkDomainUnsuspendActionType implements ConsoleDomainActionType {
|
||||
public class ConsoleBulkDomainUnsuspendActionType extends ConsoleDomainActionType {
|
||||
|
||||
private static final String DOMAIN_SUSPEND_XML =
|
||||
"""
|
||||
@@ -52,16 +51,13 @@ public class ConsoleBulkDomainUnsuspendActionType implements ConsoleDomainAction
|
||||
</command>
|
||||
</epp>""";
|
||||
|
||||
private final String reason;
|
||||
|
||||
public ConsoleBulkDomainUnsuspendActionType(JsonElement jsonElement) {
|
||||
this.reason = jsonElement.getAsJsonObject().get("reason").getAsString();
|
||||
super(jsonElement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getXmlContentsToRun(String domainName) {
|
||||
return ConsoleDomainActionType.fillSubstitutions(
|
||||
DOMAIN_SUSPEND_XML, ImmutableMap.of("DOMAIN_NAME", domainName, "REASON", reason));
|
||||
protected String getXmlTemplate() {
|
||||
return DOMAIN_SUSPEND_XML;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+37
-19
@@ -14,20 +14,18 @@
|
||||
|
||||
package google.registry.ui.server.console.domains;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.escape.Escaper;
|
||||
import com.google.common.xml.XmlEscapers;
|
||||
import com.google.gson.JsonElement;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A type of EPP action to perform on domain(s), run by the {@link ConsoleBulkDomainAction}.
|
||||
*
|
||||
* <p>Each {@link BulkAction} defines the class that implements that action, including the EPP XML
|
||||
* that will be run and the permission required.
|
||||
* <p>Each {@link BulkAction} defines the class that extends that action, including the EPP XML that
|
||||
* will be run and the permission required.
|
||||
*/
|
||||
public interface ConsoleDomainActionType {
|
||||
public abstract class ConsoleDomainActionType {
|
||||
|
||||
enum BulkAction {
|
||||
DELETE(ConsoleBulkDomainDeleteActionType.class),
|
||||
@@ -45,20 +43,6 @@ public interface ConsoleDomainActionType {
|
||||
}
|
||||
}
|
||||
|
||||
Escaper XML_ESCAPER = XmlEscapers.xmlContentEscaper();
|
||||
|
||||
static String fillSubstitutions(String xmlTemplate, ImmutableMap<String, String> replacements) {
|
||||
String xml = xmlTemplate;
|
||||
for (Map.Entry<String, String> entry : replacements.entrySet()) {
|
||||
xml = xml.replaceAll("%" + entry.getKey() + "%", XML_ESCAPER.escape(entry.getValue()));
|
||||
}
|
||||
return xml;
|
||||
}
|
||||
|
||||
String getXmlContentsToRun(String domainName);
|
||||
|
||||
ConsolePermission getNecessaryPermission();
|
||||
|
||||
static ConsoleDomainActionType parseActionType(String bulkDomainAction, JsonElement jsonElement) {
|
||||
BulkAction bulkAction = BulkAction.valueOf(bulkDomainAction);
|
||||
try {
|
||||
@@ -67,4 +51,38 @@ public interface ConsoleDomainActionType {
|
||||
throw new RuntimeException(e); // shouldn't happen
|
||||
}
|
||||
}
|
||||
|
||||
private static final Escaper XML_ESCAPER = XmlEscapers.xmlContentEscaper();
|
||||
|
||||
private final String reason;
|
||||
|
||||
public ConsoleDomainActionType(JsonElement jsonElement) {
|
||||
this.reason = jsonElement.getAsJsonObject().get("reason").getAsString();
|
||||
}
|
||||
|
||||
/** Returns the full XML representing this action, including all substitutions. */
|
||||
public String getXmlContentsToRun(String domainName) {
|
||||
return fillSubstitutions(getXmlTemplate(), domainName);
|
||||
}
|
||||
|
||||
/** Returns the permission necessary to successfully perform this action. */
|
||||
public abstract ConsolePermission getNecessaryPermission();
|
||||
|
||||
/** Returns the XML template contents for this action. */
|
||||
protected abstract String getXmlTemplate();
|
||||
|
||||
/**
|
||||
* Fills out the default set of substitutions in the provided XML template.
|
||||
*
|
||||
* <p>Override this method if non-default substitutions are required.
|
||||
*/
|
||||
protected String fillSubstitutions(String xmlTemplate, String domainName) {
|
||||
String xml = xmlTemplate;
|
||||
xml = replaceValue(xml, "DOMAIN_NAME", domainName);
|
||||
return replaceValue(xml, "REASON", reason);
|
||||
}
|
||||
|
||||
private String replaceValue(String xml, String key, String value) {
|
||||
return xml.replaceAll("%" + key + "%", XML_ESCAPER.escape(value));
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+1
-2
@@ -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);
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ abstract class RdapActionBaseTestCase<A extends RdapActionBase> {
|
||||
action.rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter(clock);
|
||||
action.rdapMetrics = rdapMetrics;
|
||||
action.requestMethod = GET;
|
||||
action.clock = new FakeClock(DateTime.parse("2025-01-01T00:00:00.000Z"));
|
||||
logout();
|
||||
}
|
||||
|
||||
|
||||
@@ -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<RdapDomainAction> {
|
||||
.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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -184,3 +184,4 @@ V183__remove_fk_billingrecurrence_domainhistory.sql
|
||||
V184__remove_fk_pollmessage_domainhistory.sql
|
||||
V185__remove_fk_domaintransactionrecord_domainhistory.sql
|
||||
V186__remove_fk_domaindsdatahistory_domainhistory.sql
|
||||
V187__console_update_history.sql
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
-- Copyright 2025 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.
|
||||
|
||||
CREATE TABLE "ConsoleUpdateHistory" (
|
||||
revision_id bigint NOT NULL,
|
||||
modification_time timestamp with time zone NOT NULL,
|
||||
method text NOT NULL,
|
||||
type text NOT NULL,
|
||||
url text NOT NULL,
|
||||
description text,
|
||||
acting_user text NOT NULL,
|
||||
PRIMARY KEY (revision_id),
|
||||
CONSTRAINT fk_console_update_history_acting_user
|
||||
FOREIGN KEY(acting_user)
|
||||
REFERENCES "User" (email_address)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_console_update_history_acting_user
|
||||
ON "ConsoleUpdateHistory" (acting_user);
|
||||
CREATE INDEX IF NOT EXISTS idx_console_update_history_type
|
||||
ON "ConsoleUpdateHistory" (type);
|
||||
CREATE INDEX IF NOT EXISTS idx_console_update_history_modification_time
|
||||
ON "ConsoleUpdateHistory" (modification_time);
|
||||
@@ -272,6 +272,21 @@ CREATE TABLE public."ConsoleEppActionHistory" (
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: ConsoleUpdateHistory; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public."ConsoleUpdateHistory" (
|
||||
revision_id bigint NOT NULL,
|
||||
modification_time timestamp with time zone NOT NULL,
|
||||
method text NOT NULL,
|
||||
type text NOT NULL,
|
||||
url text NOT NULL,
|
||||
description text,
|
||||
acting_user text NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: Contact; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
@@ -1530,6 +1545,14 @@ ALTER TABLE ONLY public."ConsoleEppActionHistory"
|
||||
ADD CONSTRAINT "ConsoleEppActionHistory_pkey" PRIMARY KEY (history_revision_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: ConsoleUpdateHistory ConsoleUpdateHistory_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public."ConsoleUpdateHistory"
|
||||
ADD CONSTRAINT "ConsoleUpdateHistory_pkey" PRIMARY KEY (revision_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: ContactHistory ContactHistory_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
@@ -2052,6 +2075,27 @@ CREATE INDEX idx9g3s7mjv1yn4t06nqid39whss ON public."AllocationToken" USING btre
|
||||
CREATE INDEX idx9q53px6r302ftgisqifmc6put ON public."ContactHistory" USING btree (history_type);
|
||||
|
||||
|
||||
--
|
||||
-- Name: idx_console_update_history_acting_user; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX idx_console_update_history_acting_user ON public."ConsoleUpdateHistory" USING btree (acting_user);
|
||||
|
||||
|
||||
--
|
||||
-- Name: idx_console_update_history_modification_time; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX idx_console_update_history_modification_time ON public."ConsoleUpdateHistory" USING btree (modification_time);
|
||||
|
||||
|
||||
--
|
||||
-- Name: idx_console_update_history_type; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX idx_console_update_history_type ON public."ConsoleUpdateHistory" USING btree (type);
|
||||
|
||||
|
||||
--
|
||||
-- Name: idx_registry_lock_registrar_id; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
@@ -2690,6 +2734,14 @@ ALTER TABLE ONLY public."BillingRecurrence"
|
||||
ADD CONSTRAINT fk_billing_recurrence_registrar_id FOREIGN KEY (registrar_id) REFERENCES public."Registrar"(registrar_id) DEFERRABLE INITIALLY DEFERRED;
|
||||
|
||||
|
||||
--
|
||||
-- Name: ConsoleUpdateHistory fk_console_update_history_acting_user; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public."ConsoleUpdateHistory"
|
||||
ADD CONSTRAINT fk_console_update_history_acting_user FOREIGN KEY (acting_user) REFERENCES public."User"(email_address);
|
||||
|
||||
|
||||
--
|
||||
-- Name: ContactHistory fk_contact_history_contact_repo_id; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
@@ -70,6 +70,7 @@ type Task struct {
|
||||
URL string `xml:"url"`
|
||||
Description string `xml:"description"`
|
||||
Service string `xml:"service"`
|
||||
Timeout string `xml:"timeout"`
|
||||
Schedule string `xml:"schedule"`
|
||||
Name string `xml:"name"`
|
||||
}
|
||||
@@ -189,7 +190,9 @@ func (manager TasksSyncManager) getArgs(task Task, operationType string) []strin
|
||||
description = strings.ReplaceAll(description, "\n", " ")
|
||||
|
||||
var service = "backend"
|
||||
if task.Service != "backend" && task.Service != "" {
|
||||
// Only BSA tasks run on the BSA service in GAE. GKE tasks are always
|
||||
// on the backend service.
|
||||
if task.Service != "backend" && task.Service != "" && !gke {
|
||||
service = task.Service
|
||||
}
|
||||
|
||||
@@ -200,7 +203,7 @@ func (manager TasksSyncManager) getArgs(task Task, operationType string) []strin
|
||||
uri = fmt.Sprintf("https://%s-dot-%s.appspot.com%s", service, projectName, strings.TrimSpace(task.URL))
|
||||
}
|
||||
|
||||
return []string{
|
||||
args := []string{
|
||||
"--project", projectName,
|
||||
"scheduler", "jobs", operationType,
|
||||
"http", task.Name,
|
||||
@@ -212,6 +215,12 @@ func (manager TasksSyncManager) getArgs(task Task, operationType string) []strin
|
||||
"--oidc-service-account-email", getCloudSchedulerServiceAccountEmail(),
|
||||
"--oidc-token-audience", clientId,
|
||||
}
|
||||
|
||||
if task.Timeout != "" {
|
||||
args = append(args, "--attempt-deadline", task.Timeout)
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
func (manager TasksSyncManager) fetchExistingRecords() ExistingEntries {
|
||||
|
||||
Reference in New Issue
Block a user