mirror of
https://github.com/google/nomulus
synced 2026-07-08 17:16:54 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 92ebd0dedb | |||
| b49e37feee | |||
| bede56598c | |||
| 467d9c7bf1 | |||
| e5ebe96c74 | |||
| 2ff4d97b0a | |||
| 6b0beeb477 | |||
| d2d43f4115 | |||
| 12fd206c35 | |||
| a3f510d0db | |||
| fa54c26ee2 |
@@ -12,16 +12,16 @@ Nomulus is an open source, scalable, cloud-based service for operating
|
||||
[top-level domains](https://en.wikipedia.org/wiki/Top-level_domain) (TLDs). It
|
||||
is the authoritative source for the TLDs that it runs, meaning that it is
|
||||
responsible for tracking domain name ownership and handling registrations,
|
||||
renewals, availability checks, and WHOIS requests. End-user registrants (i.e.
|
||||
renewals, availability checks, and WHOIS requests. End-user registrants (i.e.,
|
||||
people or companies that want to register a domain name) use an intermediate
|
||||
domain name registrar acting on their behalf to interact with the registry.
|
||||
|
||||
Nomulus runs on [Google App Engine][gae] and is written primarily in Java. It is
|
||||
the software that [Google Registry](https://www.registry.google/) uses to
|
||||
operate TLDs such as .google, .app, .how, .soy, and .みんな. It can run any
|
||||
number of TLDs in a single shared registry system using horizontal scaling. Its
|
||||
source code is publicly available in this repository under the [Apache 2.0 free
|
||||
and open source license](https://www.apache.org/licenses/LICENSE-2.0).
|
||||
Nomulus runs on [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine)
|
||||
and is written primarily in Java. It is the software that
|
||||
[Google Registry](https://www.registry.google/) uses to operate TLDs such as .google,
|
||||
.app, .how, .soy, and .みんな. It can run any number of TLDs in a single shared registry
|
||||
system using horizontal scaling. Its source code is publicly available in this
|
||||
repository under the [Apache 2.0 free and open source license](https://www.apache.org/licenses/LICENSE-2.0).
|
||||
|
||||
## Getting started
|
||||
|
||||
@@ -54,9 +54,11 @@ Nomulus has the following capabilities:
|
||||
checking, updating, and transferring domain names.
|
||||
* **[DNS](https://en.wikipedia.org/wiki/Domain_Name_System) interface**: The
|
||||
registry provides a pluggable interface that can be implemented to handle
|
||||
different DNS providers. It includes a sample implementation using Google
|
||||
Cloud DNS as well as an RFC 2136 compliant implementation that works with
|
||||
BIND.
|
||||
different DNS providers. It includes a sample implementation using [Google
|
||||
Cloud DNS](https://cloud.google.com/dns/), as well as an RFC 2136 compliant
|
||||
implementation that works with BIND. If you are using Google Cloud DNS, you
|
||||
may need to understand its capabilities and provide your own
|
||||
multi-[AS](https://en.wikipedia.org/wiki/Autonomous_system_\(Internet\)) solution.
|
||||
* **[WHOIS](https://en.wikipedia.org/wiki/WHOIS)**: A text-based protocol that
|
||||
returns ownership and contact information on registered domain names.
|
||||
* **[Registration Data Access Protocol
|
||||
@@ -68,7 +70,7 @@ Nomulus has the following capabilities:
|
||||
provider to allow take-over by another registry operator in the event of
|
||||
serious failure. This is required by ICANN for all [new
|
||||
gTLDs](https://newgtlds.icann.org/).
|
||||
* **Premium pricing**: Communicates prices for premium domain names (i.e.
|
||||
* **Premium pricing**: Communicates prices for premium domain names (i.e.,
|
||||
those that are highly desirable) and supports configurable premium
|
||||
registration and renewal prices. An extensible interface allows fully
|
||||
programmatic pricing.
|
||||
@@ -91,56 +93,50 @@ Nomulus has the following capabilities:
|
||||
* **Administrative tool**: Performs the full range of administrative tasks
|
||||
needed to manage a running registry system, including creating and
|
||||
configuring new TLDs.
|
||||
* **DNS interface**: An interface for DNS operations is provided so you can
|
||||
write an implementation for your chosen provider, along with a sample
|
||||
implementation that uses [Google Cloud DNS](https://cloud.google.com/dns/).
|
||||
If you are using Google Cloud DNS you may need to understand its
|
||||
capabilities and provide your own
|
||||
multi-[AS](https://en.wikipedia.org/wiki/Autonomous_system_\(Internet\))
|
||||
solution.
|
||||
* **GAE Proxy**: App Engine Standard only serves HTTP/S traffic. A proxy to
|
||||
forward traffic on EPP and WHOIS ports to App Engine via HTTPS is provided.
|
||||
Instructions on setting up the proxy on
|
||||
[Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/)
|
||||
is [available](https://github.com/google/nomulus/blob/master/docs/proxy-setup.md).
|
||||
Running the proxy on GKE supports IPv4 and IPv6 access, per ICANN's
|
||||
requirements for gTLDs. The proxy can also run as a single jar file, or on
|
||||
other Kubernetes providers, with modifications.
|
||||
* **Secure storage of cryptographic keys**: A keyring interface is
|
||||
provided for plugging in your own implementation (see [configuration
|
||||
doc](https://github.com/google/nomulus/blob/master/docs/configuration.md)
|
||||
for details), and an implementation based on
|
||||
[Google Cloud Secret Manager](https://cloud.google.com/security/products/secret-manager) is
|
||||
available.
|
||||
* **TPC Proxy**: Nomulus is built on top of the [Jetty](https://jetty.org/)
|
||||
container that implements the [Jakarta Servlet](https://jakarta.ee/specifications/servlet/)
|
||||
specification and only serves HTTP/S traffic. A proxy to translate raw TCP traffic (e.g., EPP)
|
||||
to and from HTTP is provided.
|
||||
Instructions on setting up the proxy
|
||||
are [available](https://github.com/google/nomulus/blob/master/docs/proxy-setup.md).
|
||||
The proxy can either run in a separate cluster and communicate to Nomulus public HTTP
|
||||
endpoints via the Internet, or as a sidecar with the Nomulus image in the same pod and
|
||||
communicate to it via loopback.
|
||||
|
||||
## Additional components
|
||||
|
||||
Registry operators interested in deploying Nomulus will likely require some
|
||||
additional components that are need to be configured separately.
|
||||
additional components that need to be configured separately.
|
||||
|
||||
* A way to invoice registrars for domain name registrations and accept
|
||||
payments. Nomulus records the information required to generate invoices in
|
||||
[billing
|
||||
events](https://github.com/google/nomulus/blob/master/docs/code-structure.md#billing-events).
|
||||
* Fully automated reporting to meet ICANN's requirements for gTLDs. Nomulus
|
||||
includes substantial reporting functionality but some additional work will
|
||||
includes substantial reporting functionality, but some additional work will
|
||||
be required by the operator in this area.
|
||||
* A secure method for storing cryptographic keys. A keyring interface is
|
||||
provided for plugging in your own implementation (see [configuration
|
||||
doc](https://github.com/google/nomulus/blob/master/docs/configuration.md)
|
||||
for details).
|
||||
|
||||
* System status and uptime monitoring.
|
||||
|
||||
## Outside references
|
||||
|
||||
* [Donuts](http://donuts.domains) Registry has helped review the code and
|
||||
provided valuable feedback
|
||||
* [Identity Digital](http://identity.digital) has helped review the code and
|
||||
provided valuable feedback.
|
||||
* [CoCCa](http://cocca.org.nz) and [FRED](https://fred.nic.cz) are other
|
||||
open-source registry platforms in use by many TLDs
|
||||
open-source registry platforms in use by many TLDs.
|
||||
* We are not aware of any fully open source domain registrar projects, but
|
||||
open source EPP Toolkits (not yet tested with Nomulus; may require
|
||||
integration work) include:
|
||||
* [EPP RTK Project](http://epp-rtk.sourceforge.net/)
|
||||
* [CentralNic](https://www.centralnic.com/registry/labs)
|
||||
* [Universal Registry/Registrar Toolkit](https://sourceforge.net/projects/epp-rtk/)
|
||||
* [ari-toolkit](https://github.com/AusRegistry/ari-toolkit)
|
||||
* [Net::DRI](https://metacpan.org/pod/Net::DRI)
|
||||
* Some Open Source DNS Projects that may be useful, but which we have not
|
||||
tested:
|
||||
* [AtomiaDNS](http://atomiadns.com/)
|
||||
* [PowerDNS](https://doc.powerdns.com/md/)
|
||||
|
||||
[gae]:https://cloud.google.com/appengine/docs/about-the-standard-environment
|
||||
* [AtomiaDNS](https://github.com/atomia/atomiadns)
|
||||
* [PowerDNS](https://github.com/PowerDNS/pdns)
|
||||
+3
-7
@@ -95,26 +95,22 @@ task stage {
|
||||
description = 'Generates application directories for all services.'
|
||||
}
|
||||
|
||||
// App-engine environment configuration. We set up all of the variables in
|
||||
// the root project.
|
||||
|
||||
def environments = ['production', 'sandbox', 'alpha', 'crash', 'qa']
|
||||
|
||||
def gcpProject = null
|
||||
|
||||
apply from: "${rootDir.path}/projects.gradle"
|
||||
|
||||
if (environment == '') {
|
||||
// Keep the project null, this will prevent deployment. Set the
|
||||
// Keep the project null, this will prevent deployment. Set the
|
||||
// environment to "alpha" because other code needs this property to
|
||||
// explode the war file.
|
||||
environment = 'alpha'
|
||||
} else if (environment != 'production' && environment != 'sandbox') {
|
||||
} else {
|
||||
gcpProject = projects[environment]
|
||||
if (gcpProject == null) {
|
||||
throw new GradleException("-Penvironment must be one of " +
|
||||
"${projects.keySet()}.")
|
||||
}
|
||||
project(':console-webapp').setProperty('configuration', environment)
|
||||
}
|
||||
|
||||
rootProject.ext.environment = environment
|
||||
|
||||
@@ -25,7 +25,7 @@ import textwrap
|
||||
import re
|
||||
|
||||
# We should never analyze any generated files
|
||||
UNIVERSALLY_SKIPPED_PATTERNS = {"/build/", "cloudbuild-caches", "/out/", ".git/", ".gradle/", "/dist/", "karma.conf.js", "polyfills.ts", "test.ts", "/docs/console-endpoints/"}
|
||||
UNIVERSALLY_SKIPPED_PATTERNS = {"/build/", "cloudbuild-caches", "/out/", ".git/", ".gradle/", "/dist/", "/console-alpha/", "/console-crash/", "/console-qa", "/console-sandbox", "/console-production", "karma.conf.js", "polyfills.ts", "test.ts", "/docs/console-endpoints/"}
|
||||
# We can't rely on CI to have the Enum package installed so we do this instead.
|
||||
FORBIDDEN = 1
|
||||
REQUIRED = 2
|
||||
|
||||
@@ -44,3 +44,4 @@ Thumbs.db
|
||||
|
||||
# Build artifact
|
||||
/staged/dist
|
||||
/staged/console-*
|
||||
|
||||
@@ -96,11 +96,17 @@
|
||||
"sourceMap": true,
|
||||
"namedChunks": true
|
||||
},
|
||||
"development": {
|
||||
"qa": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true,
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
@@ -120,6 +126,9 @@
|
||||
"sandbox": {
|
||||
"buildTarget": "console-webapp:build:sandbox"
|
||||
},
|
||||
"qa": {
|
||||
"buildTarget": "console-webapp:build:qa"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "console-webapp:build:development"
|
||||
}
|
||||
|
||||
@@ -40,15 +40,55 @@ task runConsoleWebappUnitTests(type: Exec) {
|
||||
task buildConsoleWebapp(type: Exec) {
|
||||
workingDir "${consoleDir}/"
|
||||
executable 'npm'
|
||||
def configuration = project.hasProperty('configuration') ?
|
||||
project.getProperty('configuration') :
|
||||
'production'
|
||||
def configuration = project.getProperty('configuration')
|
||||
args 'run', "build", "--configuration=${configuration}"
|
||||
doFirst {
|
||||
println "Building console for environment: ${configuration}"
|
||||
}
|
||||
}
|
||||
|
||||
task buildConsoleForAll() {}
|
||||
|
||||
def createConsoleTask = { env ->
|
||||
project.tasks.register("buildConsoleFor${env.capitalize()}", Exec) {
|
||||
workingDir "${consoleDir}/"
|
||||
executable 'npm'
|
||||
args 'run', 'build', "--configuration=${env}"
|
||||
doFirst {
|
||||
println "Building console for environment: ${env}"
|
||||
}
|
||||
doLast {
|
||||
copy {
|
||||
from "${consoleDir}/staged/dist/"
|
||||
into "${consoleDir}/staged/console-${env}"
|
||||
}
|
||||
delete "${consoleDir}/staged/dist"
|
||||
}
|
||||
dependsOn(tasks.npmInstallDeps)
|
||||
}
|
||||
project.tasks.register("deleteConsoleFor${env.capitalize()}", Delete) {
|
||||
delete "${consoleDir}/staged/console-${env}"
|
||||
}
|
||||
tasks.named('clean') {
|
||||
dependsOn(tasks.named("deleteConsoleFor${env.capitalize()}"))
|
||||
}
|
||||
tasks.named('buildConsoleForAll') {
|
||||
dependsOn(tasks.named("buildConsoleFor${env.capitalize()}"))
|
||||
}
|
||||
}
|
||||
|
||||
['alpha', 'crash', 'qa', 'sandbox', 'production'].forEach {env ->
|
||||
createConsoleTask(env)
|
||||
}
|
||||
|
||||
// Force an order so we don't run these tasks in parallel.
|
||||
tasks.buildConsoleForCrash.mustRunAfter(tasks.buildConsoleForAlpha)
|
||||
tasks.buildConsoleForQa.mustRunAfter(tasks.buildConsoleForCrash)
|
||||
tasks.buildConsoleForSandbox.mustRunAfter(tasks.buildConsoleForQa)
|
||||
tasks.buildConsoleForProduction.mustRunAfter(tasks.buildConsoleForSandbox)
|
||||
// This task must run last, otherwise the previous tasks will have deleted the "dist" folder.
|
||||
tasks.buildConsoleWebapp.mustRunAfter(tasks.buildConsoleForProduction)
|
||||
|
||||
task applyFormatting(type: Exec) {
|
||||
workingDir "${consoleDir}/"
|
||||
executable 'npm'
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
configuration=production
|
||||
@@ -687,6 +687,7 @@ artifacts {
|
||||
task runTestServer(type: JavaExec) {
|
||||
main = 'google.registry.server.RegistryTestServerMain'
|
||||
classpath = sourceSets.test.runtimeClasspath
|
||||
dependsOn(rootProject.project('console-webapp').tasks.named('buildConsoleWebapp'))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+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;
|
||||
|
||||
@@ -16,7 +16,9 @@ package google.registry.module;
|
||||
|
||||
import static google.registry.util.GcpJsonFormatter.setCurrentRequest;
|
||||
import static google.registry.util.GcpJsonFormatter.setCurrentTraceId;
|
||||
import static google.registry.util.GcpJsonFormatter.setLabel;
|
||||
import static google.registry.util.GcpJsonFormatter.unsetCurrentRequest;
|
||||
import static google.registry.util.GcpJsonFormatter.unsetLabels;
|
||||
import static google.registry.util.RandomStringGenerator.insecureRandomStringGenerator;
|
||||
import static google.registry.util.StringGenerator.Alphabets.HEX_DIGITS_ONLY;
|
||||
|
||||
@@ -30,9 +32,12 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.ConsoleHandler;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** Servlet that handles all requests. */
|
||||
public class RegistryServlet extends ServletBase {
|
||||
@@ -48,6 +53,10 @@ public class RegistryServlet extends ServletBase {
|
||||
private static final RequestHandler<RequestComponent> requestHandler = component.requestHandler();
|
||||
private static final Lazy<MetricReporter> metricReporter = component.metricReporter();
|
||||
|
||||
// The regex pattern to capture the cookies that we want to log.
|
||||
private static final Pattern COOKIE_REGEX_PATTERN =
|
||||
Pattern.compile("(JSESSIONID|GCLB)=[\"]?([^;\"\\s]+)[\"]?");
|
||||
|
||||
private final String projectId;
|
||||
|
||||
static {
|
||||
@@ -74,11 +83,20 @@ public class RegistryServlet extends ServletBase {
|
||||
String userAgent = String.valueOf(req.getHeader("User-Agent"));
|
||||
String protocol = req.getProtocol();
|
||||
setCurrentRequest(requestMethod, requestUrl, userAgent, protocol);
|
||||
Optional.ofNullable(req.getHeader("Cookie"))
|
||||
.ifPresent(
|
||||
cookie -> {
|
||||
Matcher matcher = COOKIE_REGEX_PATTERN.matcher(cookie);
|
||||
while (matcher.find()) {
|
||||
setLabel(matcher.group(1), matcher.group(2));
|
||||
}
|
||||
});
|
||||
try {
|
||||
super.service(req, rsp);
|
||||
} finally {
|
||||
setCurrentTraceId(null);
|
||||
unsetCurrentRequest();
|
||||
unsetLabels();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
+4
@@ -276,6 +276,10 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
}
|
||||
T result = work.call();
|
||||
txn.commit();
|
||||
long duration = clock.nowUtc().getMillis() - txnInfo.transactionTime.getMillis();
|
||||
if (duration >= 100) {
|
||||
logger.atInfo().log("Transaction duration: %d milliseconds", duration);
|
||||
}
|
||||
return result;
|
||||
} catch (Throwable e) {
|
||||
// Catch a Throwable here so even Errors would lead to a rollback.
|
||||
|
||||
@@ -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: -
|
||||
--
|
||||
|
||||
+4
-4
@@ -37,11 +37,11 @@ dependencies {
|
||||
}
|
||||
|
||||
tasks.register('copyConsole', Copy) {
|
||||
from("${rootDir}/console-webapp/staged/dist") {
|
||||
include "**/*"
|
||||
from("${rootDir}/console-webapp/staged/") {
|
||||
include "console-*/*"
|
||||
}
|
||||
into layout.buildDirectory.dir('jetty-base/webapps/console')
|
||||
dependsOn(':console-webapp:buildConsoleWebapp')
|
||||
into layout.buildDirectory.dir('jetty-base/webapps/')
|
||||
dependsOn(':console-webapp:buildConsoleForAll')
|
||||
}
|
||||
|
||||
tasks.register('stage') {
|
||||
|
||||
+4
-2
@@ -13,8 +13,10 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
environment=$1
|
||||
env=${1:-"alpha"}
|
||||
cd /jetty-base
|
||||
java -Dgoogle.registry.environment=${environment:-"alpha"} \
|
||||
cp -rf webapps/console-${env} webapps/console
|
||||
echo "Running ${env}"
|
||||
java -Dgoogle.registry.environment=${env} \
|
||||
-Djava.util.logging.config.file=/logging.properties \
|
||||
-jar /usr/local/jetty/start.jar
|
||||
|
||||
@@ -51,7 +51,7 @@ else
|
||||
mv services/"${service}"/build/staged-app "${dest}/${service}"
|
||||
done
|
||||
|
||||
./gradlew :console-webapp:buildConsoleWebapp -Pconfiguration="${environment}"
|
||||
./gradlew :console-webapp:buildConsoleWebapp -Penvironment="${environment}"
|
||||
mkdir -p "${dest}/console" && cp -r console-webapp/staged/* "${dest}/console"
|
||||
|
||||
mv core/build/resources/main/google/registry/env/common/META-INF \
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# This will delete canary GAE versions named "nomulus".
|
||||
#
|
||||
# For reasons unknown, Spinnaker occasionally gets stuck when deploying to GAE
|
||||
# canary, and the fix is to manually delete the canary versions before the
|
||||
# deployment.
|
||||
#
|
||||
# To manually trigger a build on GCB, run:
|
||||
# gcloud builds submit --config=cloudbuild-delete-canary.yaml \
|
||||
# --substitutions=_ENV=[ENV] ..
|
||||
#
|
||||
# To trigger a build automatically, follow the instructions below and add a trigger:
|
||||
# https://cloud.google.com/cloud-build/docs/running-builds/automate-builds
|
||||
#
|
||||
steps:
|
||||
# Pull the credential for nomulus tool.
|
||||
- name: 'gcr.io/$PROJECT_ID/builder:latest'
|
||||
entrypoint: /bin/bash
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
gcloud secrets versions access latest \
|
||||
--secret nomulus-tool-cloudbuild-credential > tool-credential.json
|
||||
# Delete unused GAE versions.
|
||||
- name: 'gcr.io/$PROJECT_ID/builder:latest'
|
||||
entrypoint: /bin/bash
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
if [ ${_ENV} == production ]
|
||||
then
|
||||
project_id="domain-registry"
|
||||
else
|
||||
project_id="domain-registry-${_ENV}"
|
||||
fi
|
||||
|
||||
gcloud auth activate-service-account --key-file=tool-credential.json
|
||||
|
||||
for service in default pubapi backend bsa tools console
|
||||
do
|
||||
gcloud app versions delete nomulus --service=$service \
|
||||
--project=$project_id --quiet;
|
||||
done
|
||||
timeout: 3600s
|
||||
options:
|
||||
machineType: 'N1_HIGHCPU_8'
|
||||
@@ -88,6 +88,7 @@ steps:
|
||||
sed -i s/builder:latest/builder@$builder_digest/g release/cloudbuild-schema-deploy.yaml
|
||||
sed -i s/builder:latest/builder@$builder_digest/g release/cloudbuild-schema-verify.yaml
|
||||
sed -i s/builder:latest/builder@$builder_digest/g release/cloudbuild-delete.yaml
|
||||
sed -i s/builder:latest/builder@$builder_digest/g release/cloudbuild-delete-canary.yaml
|
||||
sed -i s/builder:latest/builder@$builder_digest/g release/cloudbuild-restart-proxies.yaml
|
||||
sed -i s/GCP_PROJECT/${PROJECT_ID}/ proxy/kubernetes/proxy-*.yaml
|
||||
sed -i s/'$${TAG_NAME}'/${TAG_NAME}/g release/cloudbuild-sync-and-tag.yaml
|
||||
@@ -100,6 +101,8 @@ steps:
|
||||
> release/cloudbuild-deploy-gke-${environment}.yaml
|
||||
sed s/'$${_ENV}'/${environment}/g release/cloudbuild-delete.yaml \
|
||||
> release/cloudbuild-delete-${environment}.yaml
|
||||
sed s/'$${_ENV}'/${environment}/g release/cloudbuild-delete-canary.yaml \
|
||||
> release/cloudbuild-delete-canary-${environment}.yaml
|
||||
sed s/'$${_ENV}'/${environment}/g release/cloudbuild-restart-proxies.yaml \
|
||||
> release/cloudbuild-restart-proxies-${environment}.yaml
|
||||
sed s/'$${_ENV}'/${environment}/g release/cloudbuild-restart-proxies.yaml | \
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.google.common.flogger.backend.system.SimpleLogRecord;
|
||||
import com.google.gson.Gson;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -58,6 +59,13 @@ public class GcpJsonFormatter extends Formatter {
|
||||
/** JSON field that stores the parameters of the current request, if any. */
|
||||
private static final String HTTP_REQUEST = "httRequest";
|
||||
|
||||
/**
|
||||
* JSON field that stores the arbitrary labels.
|
||||
*
|
||||
* <p>In practice, we store session cookies so we can identify requests from the same session.
|
||||
*/
|
||||
private static final String LABELS = "logging.googleapis.com/labels";
|
||||
|
||||
/** JSON field that contains the content, this will show up as the main entry in a log. */
|
||||
private static final String MESSAGE = "message";
|
||||
|
||||
@@ -83,6 +91,9 @@ public class GcpJsonFormatter extends Formatter {
|
||||
|
||||
private static final ThreadLocal<HttpRequest> request = new ThreadLocal<>();
|
||||
|
||||
private static final ThreadLocal<Map<String, String>> labels =
|
||||
ThreadLocal.withInitial(HashMap::new);
|
||||
|
||||
/**
|
||||
* Set the Trace ID associated with any logging done by the current thread.
|
||||
*
|
||||
@@ -107,6 +118,19 @@ public class GcpJsonFormatter extends Formatter {
|
||||
request.set(new HttpRequest(requestMethod, requestUrl, userAgent, protocol));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an arbitrary key-value pair as a label for the current request.
|
||||
*
|
||||
* <p>Existing labels will be overwritten.
|
||||
*/
|
||||
public static void setLabel(String key, String value) {
|
||||
labels.get().put(key, value);
|
||||
}
|
||||
|
||||
public static void unsetLabels() {
|
||||
labels.get().clear();
|
||||
}
|
||||
|
||||
public static void unsetCurrentRequest() {
|
||||
request.remove();
|
||||
}
|
||||
@@ -180,6 +204,9 @@ public class GcpJsonFormatter extends Formatter {
|
||||
if (request.get() != null) {
|
||||
json.put(HTTP_REQUEST, request.get());
|
||||
}
|
||||
if (!labels.get().isEmpty()) {
|
||||
json.put(LABELS, labels.get());
|
||||
}
|
||||
// This trailing newline is required for the proxy because otherwise multiple logs might be
|
||||
// sent to Stackdriver together (due to the async nature of the proxy), and not parsed
|
||||
// correctly.
|
||||
|
||||
@@ -72,6 +72,7 @@ class GcpJsonFormatterTest {
|
||||
jdkLogger.removeHandler(handler);
|
||||
GcpJsonFormatter.setCurrentTraceId(null);
|
||||
GcpJsonFormatter.unsetCurrentRequest();
|
||||
GcpJsonFormatter.unsetLabels();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -79,7 +80,7 @@ class GcpJsonFormatterTest {
|
||||
logger.atInfo().log("Something I have to say");
|
||||
handler.close();
|
||||
String output = ostream.toString(StandardCharsets.US_ASCII);
|
||||
assertThat(output).isEqualTo(makeJson("INFO", 79, "testSuccess", "Something I have to say"));
|
||||
assertThat(output).isEqualTo(makeJson("INFO", 80, "testSuccess", "Something I have to say"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -88,7 +89,7 @@ class GcpJsonFormatterTest {
|
||||
logger.atInfo().log("Something I have to say");
|
||||
handler.close();
|
||||
String output = ostream.toString(StandardCharsets.US_ASCII);
|
||||
String expected = makeJson("INFO", 88, "testSuccess_traceId", "Something I have to say");
|
||||
String expected = makeJson("INFO", 89, "testSuccess_traceId", "Something I have to say");
|
||||
// Remove the last two characters (}, \n) from the template and add the trace ID.
|
||||
expected =
|
||||
expected.substring(0, expected.length() - 2)
|
||||
@@ -103,7 +104,7 @@ class GcpJsonFormatterTest {
|
||||
handler.close();
|
||||
String output = ostream.toString(StandardCharsets.US_ASCII);
|
||||
String expected =
|
||||
makeJson("INFO", 102, "testSuccess_currentRequest", "Something I have to say");
|
||||
makeJson("INFO", 103, "testSuccess_currentRequest", "Something I have to say");
|
||||
// Remove the last two characters (}, \n) from the template and add the request.
|
||||
expected =
|
||||
expected.substring(0, expected.length() - 2)
|
||||
@@ -112,13 +113,28 @@ class GcpJsonFormatterTest {
|
||||
assertThat(output).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_labels() {
|
||||
GcpJsonFormatter.setLabel("label1", "value1");
|
||||
GcpJsonFormatter.setLabel("label2", "value2");
|
||||
logger.atInfo().log("Something I have to say");
|
||||
handler.close();
|
||||
String output = ostream.toString(StandardCharsets.US_ASCII);
|
||||
String expected = makeJson("INFO", 120, "testSuccess_labels", "Something I have to say");
|
||||
// Remove the last two characters (}, \n) from the template and add the labels.
|
||||
expected =
|
||||
expected.substring(0, expected.length() - 2)
|
||||
+ ",\"logging.googleapis.com/labels\":{\"label1\":\"value1\",\"label2\":\"value2\"}}\n";
|
||||
assertThat(output).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_logLevel() {
|
||||
logger.atSevere().log("Something went terribly wrong");
|
||||
handler.close();
|
||||
String output = ostream.toString(StandardCharsets.US_ASCII);
|
||||
assertThat(output)
|
||||
.isEqualTo(makeJson("ERROR", 117, "testSuccess_logLevel", "Something went terribly wrong"));
|
||||
.isEqualTo(makeJson("ERROR", 133, "testSuccess_logLevel", "Something went terribly wrong"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -127,7 +143,7 @@ class GcpJsonFormatterTest {
|
||||
handler.close();
|
||||
String output = ostream.toString(StandardCharsets.US_ASCII);
|
||||
String prefix =
|
||||
makeJson("ERROR", 126, "testSuccess_withCause", "Something went terribly wrong");
|
||||
makeJson("ERROR", 142, "testSuccess_withCause", "Something went terribly wrong");
|
||||
// Remove the last two characters (}, \n) from the template as the actual output contains
|
||||
// the full stack trace.
|
||||
prefix = prefix.substring(0, prefix.length() - 2);
|
||||
@@ -141,7 +157,7 @@ class GcpJsonFormatterTest {
|
||||
handler.close();
|
||||
String output = ostream.toString(StandardCharsets.US_ASCII);
|
||||
String prefix =
|
||||
makeJson("ERROR", 140, "testSuccess_withStackTrace", "Something is worth checking");
|
||||
makeJson("ERROR", 156, "testSuccess_withStackTrace", "Something is worth checking");
|
||||
// Remove the last three characters (}, \n) from the template as the actual output contains
|
||||
// the full stack trace.
|
||||
prefix = prefix.substring(0, prefix.length() - 2);
|
||||
@@ -167,7 +183,7 @@ class GcpJsonFormatterTest {
|
||||
TimeLimiter.create().callWithTimeout(this::logSomething, 1, TimeUnit.SECONDS);
|
||||
handler.close();
|
||||
String output = ostream.toString(StandardCharsets.US_ASCII);
|
||||
assertThat(output).isEqualTo(makeJson("INFO", 188, "logSomething", "Something I have to say"));
|
||||
assertThat(output).isEqualTo(makeJson("INFO", 204, "logSomething", "Something I have to say"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -176,7 +192,7 @@ class GcpJsonFormatterTest {
|
||||
TimeLimiter.create().callWithTimeout(this::logSomething, 1, TimeUnit.SECONDS);
|
||||
handler.close();
|
||||
String output = ostream.toString(StandardCharsets.US_ASCII);
|
||||
String expected = makeJson("INFO", 188, "logSomething", "Something I have to say");
|
||||
String expected = makeJson("INFO", 204, "logSomething", "Something I have to say");
|
||||
// Remove the last two characters (}, \n) from the template and add the trace ID.
|
||||
expected =
|
||||
expected.substring(0, expected.length() - 2)
|
||||
|
||||
Reference in New Issue
Block a user