1
0
mirror of https://github.com/google/nomulus synced 2026-07-19 14:32:44 +00:00

Compare commits

...

7 Commits

Author SHA1 Message Date
Ben McIlwain bf66b374c6 Address ICANN feedback on proposed Latin IDN table (#2880) 2025-11-10 20:33:29 +00:00
Weimin Yu 666cee1d9f Fix broken release script (#2878)
go/r3pr/2870 deleted release/cloudbuild-deploy.yaml.

Remove references to the file in the release script.
2025-11-07 20:59:05 +00:00
Weimin Yu d4a70c29a8 Upload compatibility test jars to release folder (#2873)
We need to stop using maven repo on GCS to store artifacts for the
schema compatibility tests. After public access is removed from GCS
buckets, Kokoro won't be able to access it: normal access will be
denied, and the repo is too large to map (copy) to Kokoro VM as a
resource.

This PR uploads the relevant jars to each release's folder. See
go/dr-gcs-public-access-prevention for details.
2025-11-06 15:33:02 +00:00
gbrodman 7b8d07954b Remove more old-console-related files (#2866) 2025-11-05 19:43:59 +00:00
gbrodman 34bea69a48 Remove no-longer-used servlets/components (#2868)
With GKE, we don't need the individual servlets because the services
aren't partitioned out the same way they were in GAE.

We keep FrontendServlet and BackendServlet around for now as they serve
as the backbone for the local RegistryTestServer (for testing things
like the console).

did some cursory tests on alpha and things seem to be unaffected -- I
was able to curl RDAP (pubapi) and create domains
2025-11-05 19:03:45 +00:00
Ben McIlwain 363800bd86 Remove App Engine build/environment scripts (#2870)
It's been over half a year now since we last used any of these and we definitely
no longer have any intentions of ever using App Engine again.

BUG= http://b/457471639
2025-11-05 19:02:24 +00:00
gbrodman dee132d04b Rename visibleInWhois fields to visibleInRdap (#2863)
Still part of b/454947209, removing references to WHOIS where we can. We
keep the registrar type and the column names (at least for now) because
changing those is much more complicated.
2025-11-04 17:37:44 +00:00
121 changed files with 284 additions and 5334 deletions
-4
View File
@@ -121,9 +121,5 @@ core/**/registrar_dbg*.js
core/**/registrar_bin*.css
core/**/registrar_dbg*.css
# Appengine generated files
core/WEB-INF/appengine-generated/*.bin
core/WEB-INF/appengine-generated/*.xml
# jEnv
.java-version
-108
View File
@@ -1,108 +0,0 @@
// Copyright 2019 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.
apply plugin: 'war'
def environment = rootProject.environment
def gcpProject = rootProject.gcpProject
// Set this directory before applying the appengine plugin so that the
// plugin will recognize this as an app-engine standard app (and also
// obtains the appengine-web.xml from the correct location)
project.convention.plugins['war'].webAppDirName =
"../../core/src/main/java/google/registry/env/${environment}/${project.name}"
apply plugin: 'com.google.cloud.tools.appengine'
def coreResourcesDir = "${rootDir}/core/build/resources/main"
def coreLibsDir = "${rootDir}/core/build/libs"
// Get the web.xml file for the service.
war {
webInf {
from "../../core/src/main/java/google/registry/env/common/${project.name}/WEB-INF"
}
}
war {
from("${coreResourcesDir}/google/registry/ui/html") {
include "*.html"
}
from("${coreLibsDir}") {
include "core.jar"
into("WEB-INF/lib")
}
}
if (project.path == ":services:default") {
war {
from("${coreResourcesDir}/google/registry/ui/html") {
include "*.html"
into("registrar")
}
}
}
appengine {
deploy {
// appengineDeployAll task requires the version to be set. So,
// this config lets gcloud select a version name when deploying
// to alpha or sandbox from our workstation.
if (!rootProject.prodOrSandboxEnv) {
version = 'GCLOUD_CONFIG'
}
// Don't set gcpProject directly, it gets overriden in ./build.gradle.
// Do -P environment={crash,alpha} instead. For sandbox/production,
// use Spinnaker.
projectId = gcpProject
}
}
dependencies {
implementation project(path: ':core', configuration: 'deploy_jar')
}
// The tools.jar file gets pulled in from the java environment and for some
// reason gets exploded "readonly", causing subsequent builds to fail when
// they can't overwrite it. The hack below makes the file writable after
// we're done exploding it.
//
// Fun fact: We only use this jar for documentation generation and as such we
// don't need it in our warfile, as it is not used by the application at
// runtime. But it's not clear how to exclude it, as we seem to be
// constructing the jar from the entire WEB-INF directory and per-file
// exclude rules don't seem to work on it. Better solutions are welcome :-)
explodeWar.doLast {
file("${it.explodedAppDirectory}/WEB-INF/lib/tools.jar").setWritable(true)
}
appengineDeployAll.mustRunAfter ':console-webapp:deploy'
appengineDeployAll.finalizedBy ':deployCloudSchedulerAndQueue'
rootProject.stage.dependsOn appengineStage
tasks['war'].dependsOn ':core:processResources'
tasks['war'].dependsOn ':core:jar'
// Impose verification for all of the deployment tasks. We haven't found a
// better way to do this other than to apply to each of them independently.
// If a new task gets added, it will still fail if "environment" is not defined
// because gcpProject is null. We just won't get as friendly an error message.
appengineDeployAll.configure rootProject.verifyDeploymentConfig
appengineDeploy.configure rootProject.verifyDeploymentConfig
appengineDeployCron.configure rootProject.verifyDeploymentConfig
appengineDeployDispatch.configure rootProject.verifyDeploymentConfig
appengineDeployDos.configure rootProject.verifyDeploymentConfig
appengineDeployIndex.configure rootProject.verifyDeploymentConfig
appengineDeployQueue.configure rootProject.verifyDeploymentConfig
-14
View File
@@ -331,9 +331,6 @@ subprojects {
// Set up all of the deployment projects.
if (services.contains(project.path)) {
apply from: "${rootDir.path}/appengine_war.gradle"
// Return early, do not apply the settings below.
return
}
@@ -380,17 +377,6 @@ subprojects {
}
}
// Force SDK download and deployment to be sequential, otherwise parallel tasks
// will fail. For SDK download, they will try to write to the same location to
// upgrade gcloud. For deployment, they will try to deploy different services to
// the same project at the same time.
for (int i = 1; i < services.size(); i++) {
project("${services[i]}").downloadCloudSdk
.dependsOn(project("${services[i - 1]}").downloadCloudSdk)
project("${services[i]}").appengineDeployAll
.dependsOn(project("${services[i - 1]}").appengineDeployAll)
}
// If "-P verboseTestOutput=true" is passed in, configure all subprojects to dump all of their
// output and final test status (pass/fail, errors) for each test class.
//
@@ -47,9 +47,9 @@ export interface Contact {
registrarId?: string;
faxNumber?: string;
types: Array<contactType>;
visibleInWhoisAsAdmin?: boolean;
visibleInWhoisAsTech?: boolean;
visibleInDomainWhoisAsAbuse?: boolean;
visibleInRdapAsAdmin?: boolean;
visibleInRdapAsTech?: boolean;
visibleInDomainRdapAsAbuse?: boolean;
}
export interface ViewReadyContact extends Contact {
@@ -111,7 +111,7 @@
<h1>RDAP Preferences</h1>
<div>
<mat-checkbox
[(ngModel)]="contactService.contactInEdit.visibleInWhoisAsAdmin"
[(ngModel)]="contactService.contactInEdit.visibleInRdapAsAdmin"
[ngModelOptions]="{ standalone: true }"
>Show in Registrar RDAP record as admin contact</mat-checkbox
>
@@ -119,7 +119,7 @@
<div>
<mat-checkbox
[(ngModel)]="contactService.contactInEdit.visibleInWhoisAsTech"
[(ngModel)]="contactService.contactInEdit.visibleInRdapAsTech"
[ngModelOptions]="{ standalone: true }"
>Show in Registrar RDAP record as technical contact</mat-checkbox
>
@@ -127,7 +127,7 @@
<div>
<mat-checkbox
[(ngModel)]="contactService.contactInEdit.visibleInDomainWhoisAsAbuse"
[(ngModel)]="contactService.contactInEdit.visibleInDomainRdapAsAbuse"
[ngModelOptions]="{ standalone: true }"
>Show Phone and Email in Domain RDAP Record as registrar abuse contact
(per CL&D requirements)</mat-checkbox
@@ -189,24 +189,24 @@
<mat-list-item role="listitem">
<h2>RDAP Preferences</h2>
</mat-list-item>
@if(contactService.contactInEdit.visibleInWhoisAsAdmin) {
@if(contactService.contactInEdit.visibleInRdapAsAdmin) {
<mat-divider></mat-divider>
<mat-list-item role="listitem">
<span class="console-app__list-value"
>Show in Registrar RDAP record as admin contact</span
>
</mat-list-item>
} @if(contactService.contactInEdit.visibleInWhoisAsTech) {
} @if(contactService.contactInEdit.visibleInRdapAsTech) {
<mat-divider></mat-divider>
<mat-list-item
role="listitem"
*ngIf="contactService.contactInEdit.visibleInWhoisAsTech"
*ngIf="contactService.contactInEdit.visibleInRdapAsTech"
>
<span class="console-app__list-value"
>Show in Registrar RDAP record as technical contact</span
>
</mat-list-item>
} @if(contactService.contactInEdit.visibleInDomainWhoisAsAbuse) {
} @if(contactService.contactInEdit.visibleInDomainRdapAsAbuse) {
<mat-divider></mat-divider>
<mat-list-item role="listitem">
<span class="console-app__list-value"
@@ -1556,12 +1556,7 @@ public final class RegistryConfig {
return CONFIG_SETTINGS.get().gSuite.outgoingEmailDisplayName;
}
/**
* Returns default WHOIS server to use when {@code Registrar#getWhoisServer()} is {@code null}.
*
* @see "google.registry.whois.DomainWhoisResponse"
* @see "google.registry.whois.RegistrarWhoisResponse"
*/
/** Returns default WHOIS server to use when {@code Registrar#getWhoisServer()} is null. */
public static String getDefaultRegistrarWhoisServer() {
return CONFIG_SETTINGS.get().registryPolicy.defaultRegistrarWhoisServer;
}
@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>backend</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>100</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="alpha"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1m"/>
</static-files>
</appengine-web-app>
@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>bsa</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>100</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="alpha"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<!-- Enable external traffic to go through VPC, required for static ip -->
<vpc-access-connector>
<name>projects/domain-registry-alpha/locations/us-central1/connectors/appengine-connector</name>
<egress-setting>all-traffic</egress-setting>
</vpc-access-connector>
<static-files>
<include path="/*.html" expiration="1m"/>
</static-files>
</appengine-web-app>
@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>default</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>8</max-instances>
<idle-timeout>30m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="alpha"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1m"/>
<include path="/registrar/*.html" expiration="1m"/>
</static-files>
</appengine-web-app>
@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>pubapi</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>8</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="alpha"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1m"/>
<include path="/assets/js/**" expiration="1m"/>
<include path="/assets/css/**" expiration="1m"/>
<include path="/assets/images/**" expiration="1m"/>
</static-files>
</appengine-web-app>
@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>tools</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>5</max-instances>
<idle-timeout>5m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="alpha"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1m"/>
<include path="/assets/js/**" expiration="1m"/>
<include path="/assets/css/**" expiration="1m"/>
<include path="/assets/images/**" expiration="1m"/>
</static-files>
</appengine-web-app>
@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<appengine-application xmlns="http://appengine.google.com/ns/1.0">
<application>domain-registry</application>
</appengine-application>
@@ -1,46 +0,0 @@
<?xml version="1.0"
encoding="UTF-8"?>
<application
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/application_5.xsd"
version="5">
<description>Google Registry</description>
<display-name>Google Registry</display-name>
<!-- Modules -->
<!-- The default module should be listed first -->
<module>
<web>
<web-uri>default</web-uri>
<context-root>default</context-root>
</web>
</module>
<module>
<web>
<web-uri>pubapi</web-uri>
<context-root>pubapi</context-root>
</web>
</module>
<module>
<web>
<web-uri>backend</web-uri>
<context-root>backend</context-root>
</web>
</module>
<module>
<web>
<web-uri>bsa</web-uri>
<context-root>bsa</context-root>
</web>
</module>
<module>
<web>
<web-uri>tools</web-uri>
<context-root>tools</context-root>
</web>
</module>
</application>
@@ -1,17 +0,0 @@
# A default java.util.logging configuration.
# (All App Engine logging is through java.util.logging by default).
#
# To use this configuration, copy it into your application's WEB-INF
# folder and add the following to your appengine-web.xml:
#
# <system-properties>
# <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
# </system-properties>
#
# Set the default logging level for all loggers to INFO.
.level = INFO
# Turn off logging in Hibernate classes for misleading ERROR-level logs
org.hibernate.orm.jdbc.batch.level=OFF
org.hibernate.engine.jdbc.spi.SqlExceptionHelper.level=OFF
@@ -1,333 +0,0 @@
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<!-- Servlets -->
<!-- Servlet for injected backends actions -->
<servlet>
<display-name>BackendServlet</display-name>
<servlet-name>backend-servlet</servlet-name>
<servlet-class>google.registry.module.backend.BackendServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- RDE -->
<!--
Responsible for scanning the database to create a full deposit for a single TLD
and streaming it to cloud storage. Requests are sent here by App Engine after
`RdeCreateCronServlet` enqueues a task specifying a URL that points to this servlet.
-->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/rdeStaging</url-pattern>
</servlet-mapping>
<!--
Once `rdeCreateFullCron` finishes writing a deposit file to cloud storage, it'll
launch this task with the cloud filename so it can be uploaded to Iron Mountain
via SFTP. The file is deleted once the upload completes. This should be run via
`rde-upload-backend`.
-->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/rdeUpload</url-pattern>
</servlet-mapping>
<!-- Sends an XML RDE report to ICANN's HTTP server after rdeUploadTask finishes. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/rdeReport</url-pattern>
</servlet-mapping>
<!--
Bulk Registration Data Access. This task creates a thin escrow deposit
and saves it to cloud storage, where a separate script owned by the SREs
uploads it to ICANN.
-->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/brdaCopy</url-pattern>
</servlet-mapping>
<!-- Billing -->
<!--
Generates the invoice CSV for the month, which we send to billing to charge
registrars for their registrations.
-->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/generateInvoices</url-pattern>
</servlet-mapping>
<!--
Emails the month's invoice CSV to the internal billing team, and publishes
the detail reports to the individual registrars' drive accounts.
-->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/publishInvoices</url-pattern>
</servlet-mapping>
<!--
Copies invoice detail reports from GCS to the associated registrar's Drive folder.
-->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/copyDetailReports</url-pattern>
</servlet-mapping>
<!-- ICANN Monthly Reporting -->
<!--
Monthly ICANN transaction and activity reports. This task generates report
files (in CSV format) and stores them in GCS under
gs://domain-registry-reporting/icann/monthly/YYYY-MM
by default.
-->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/icannReportingStaging</url-pattern>
</servlet-mapping>
<!--
Monthly ICANN transaction and activity reports. This task uploads the generated
report files (in CSV format) via an HTTP PUT to ICANN's endpoint.
-->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/icannReportingUpload</url-pattern>
</servlet-mapping>
<!--
Generates the Spec11 report for the month, storing it on GCS.
-->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/generateSpec11</url-pattern>
</servlet-mapping>
<!--
Publishes the Spec11 report for the month, emailing registrars about their
registrations which were flagged by the SafeBrowsing API.
-->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/publishSpec11</url-pattern>
</servlet-mapping>
<!-- Trademark Clearinghouse -->
<!-- Downloads TMCH DNL data from MarksDB. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/tmchDnl</url-pattern>
</servlet-mapping>
<!-- Downloads TMCH SMDRL data from MarksDB. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/tmchSmdrl</url-pattern>
</servlet-mapping>
<!-- Downloads TMCH CRL data from MarksDB. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/tmchCrl</url-pattern>
</servlet-mapping>
<!-- Reads the LORDN queues and uploads CSV data for sunrise and claims marks to MarksDB. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/nordnUpload</url-pattern>
</servlet-mapping>
<!-- Verifies upload of LORDN data to MarksDB. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/nordnVerify</url-pattern>
</servlet-mapping>
<!-- Reads the DNS refresh requests and kick off the appropriate tasks to update zone. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/readDnsRefreshRequests</url-pattern>
</servlet-mapping>
<!-- Publishes DNS updates. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/publishDnsUpdates</url-pattern>
</servlet-mapping>
<!-- Manually refreshes DNS information. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/dnsRefresh</url-pattern>
</servlet-mapping>
<!-- Fans out a cron task over an adjustable range of TLDs. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/cron/fanout</url-pattern>
</servlet-mapping>
<!-- Syncs registrars to the registrar spreadsheet. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/syncRegistrarsSheet</url-pattern>
</servlet-mapping>
<!-- Exports TLD premium terms. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/exportPremiumTerms</url-pattern>
</servlet-mapping>
<!-- Exports TLD reserved terms. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/exportReservedTerms</url-pattern>
</servlet-mapping>
<!-- Syncs RegistrarContact changes to Google Groups. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/syncGroupMembers</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/exportDomainLists</url-pattern>
</servlet-mapping>
<!-- Action to delete all prober data. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/deleteProberData</url-pattern>
</servlet-mapping>
<!-- Action to delete load test data. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/deleteLoadTestData</url-pattern>
</servlet-mapping>
<!-- Dataflow pipeline to re-save all EPP resources. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/resaveAllEppResourcesPipeline</url-pattern>
</servlet-mapping>
<!-- Reread all Registrar RDAP Base Urls from the ICANN endpoint. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/updateRegistrarRdapBaseUrls</url-pattern>
</servlet-mapping>
<!-- Action to re-save a given entity. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/resaveEntity</url-pattern>
</servlet-mapping>
<!-- Enqueues DNS update tasks following a host rename. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/refreshDnsOnHostRename</url-pattern>
</servlet-mapping>
<!-- Action to expand BillingRecurrences into BillingEvents. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/expandBillingRecurrences</url-pattern>
</servlet-mapping>
<!-- Background action to delete domains past end of autorenewal. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/deleteExpiredDomains</url-pattern>
</servlet-mapping>
<!-- Background action to send notification emails to registrars with expiring certificate. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/sendExpiringCertificateNotificationEmail</url-pattern>
</servlet-mapping>
<!-- Action to automatically re-lock a domain after unlocking it -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/relockDomain</url-pattern>
</servlet-mapping>
<!-- Background action to wipe out PII fields of ContactHistory entities that
have been in the database for a certain period of time. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/wipeOutContactHistoryPii</url-pattern>
</servlet-mapping>
<!-- Action to wipeout Cloud SQL data -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/wipeOutCloudSql</url-pattern>
</servlet-mapping>
<!-- Action to execute canned scripts -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/executeCannedScript</url-pattern>
</servlet-mapping>
<!-- Security config -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Internal</web-resource-name>
<description>
Admin-only internal section. Requests for paths covered by the URL patterns below will be
checked for a logged-in user account that's allowed to access the AppEngine admin console
(NOTE: this includes Editor/Viewer permissions in addition to Owner and the new IAM
App Engine Admin role. See https://cloud.google.com/appengine/docs/java/access-control
specifically the "Access handlers that have a login:admin restriction" line.)
TODO(b/28219927): lift some of these restrictions so that we can allow OAuth authentication
for endpoints that need to be accessed by open-source automated processes.
</description>
<!-- Internal AppEngine endpoints. The '_ah' is short for app hosting. -->
<url-pattern>/_ah/*</url-pattern>
<!-- Registrar console (should not be available on non-default module). -->
<url-pattern>/registrar*</url-pattern>
<!-- Verbatim JavaScript sources (only visible to admins for debugging). -->
<url-pattern>/assets/sources/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
<!-- Repeated here since catch-all rule below is not inherited. -->
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<!-- Require TLS on all requests. -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Secure</web-resource-name>
<description>
Require encryption for all paths. http URLs will be redirected to https.
</description>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
</web-app>
@@ -1,17 +0,0 @@
# A default java.util.logging configuration.
# (All App Engine logging is through java.util.logging by default).
#
# To use this configuration, copy it into your application's WEB-INF
# folder and add the following to your appengine-web.xml:
#
# <system-properties>
# <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
# </system-properties>
#
# Set the default logging level for all loggers to INFO.
.level = INFO
# Turn off logging in Hibernate classes for misleading ERROR-level logs
org.hibernate.orm.jdbc.batch.level=OFF
org.hibernate.engine.jdbc.spi.SqlExceptionHelper.level=OFF
@@ -1,87 +0,0 @@
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<!-- Servlets -->
<!-- Servlet for injected backends actions -->
<servlet>
<display-name>BsaServlet</display-name>
<servlet-name>bsa-servlet</servlet-name>
<servlet-class>google.registry.module.bsa.BsaServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Download action -->
<servlet-mapping>
<servlet-name>bsa-servlet</servlet-name>
<url-pattern>/_dr/task/bsaDownload</url-pattern>
</servlet-mapping>
<!-- Refresh action -->
<servlet-mapping>
<servlet-name>bsa-servlet</servlet-name>
<url-pattern>/_dr/task/bsaRefresh</url-pattern>
</servlet-mapping>
<!-- Refresh action -->
<servlet-mapping>
<servlet-name>bsa-servlet</servlet-name>
<url-pattern>/_dr/task/bsaValidate</url-pattern>
</servlet-mapping>
<!-- Upload unavailable domains to BSA action -->
<servlet-mapping>
<servlet-name>bsa-servlet</servlet-name>
<url-pattern>/_dr/task/uploadBsaUnavailableNames</url-pattern>
</servlet-mapping>
<!-- Security config -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Internal</web-resource-name>
<description>
Admin-only internal section. Requests for paths covered by the URL patterns below will be
checked for a logged-in user account that's allowed to access the AppEngine admin console
(NOTE: this includes Editor/Viewer permissions in addition to Owner and the new IAM
App Engine Admin role. See https://cloud.google.com/appengine/docs/java/access-control
specifically the "Access handlers that have a login:admin restriction" line.)
TODO(b/28219927): lift some of these restrictions so that we can allow OAuth authentication
for endpoints that need to be accessed by open-source automated processes.
</description>
<!-- Internal AppEngine endpoints. The '_ah' is short for app hosting. -->
<url-pattern>/_ah/*</url-pattern>
<!-- Registrar console (should not be available on non-default module). -->
<url-pattern>/registrar*</url-pattern>
<!-- Verbatim JavaScript sources (only visible to admins for debugging). -->
<url-pattern>/assets/sources/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
<!-- Repeated here since catch-all rule below is not inherited. -->
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<!-- Require TLS on all requests. -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Secure</web-resource-name>
<description>
Require encryption for all paths. http URLs will be redirected to https.
</description>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
</web-app>
@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<dispatch-entries>
<!-- Send all public-facing unauthenticated traffic to the pubapi service. -->
<dispatch>
<url>*/rdap/*</url>
<module>pubapi</module>
</dispatch>
<dispatch>
<url>*/whois/*</url>
<module>pubapi</module>
</dispatch>
<dispatch>
<url>*/_dr/whois</url>
<module>pubapi</module>
</dispatch>
<dispatch>
<url>*/check</url>
<module>pubapi</module>
</dispatch>
<dispatch>
<url>*/console/*</url>
<module>console</module>
</dispatch>
<dispatch>
<url>*/console</url>
<module>console</module>
</dispatch>
</dispatch-entries>
@@ -1,17 +0,0 @@
# A default java.util.logging configuration.
# (All App Engine logging is through java.util.logging by default).
#
# To use this configuration, copy it into your application's WEB-INF
# folder and add the following to your appengine-web.xml:
#
# <system-properties>
# <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
# </system-properties>
#
# Set the default logging level for all loggers to INFO.
.level = INFO
# Turn off logging in Hibernate classes for misleading ERROR-level logs
org.hibernate.orm.jdbc.batch.level=OFF
org.hibernate.engine.jdbc.spi.SqlExceptionHelper.level=OFF
@@ -1,66 +0,0 @@
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<!-- Servlets -->
<!-- Servlet for injected frontend actions -->
<servlet>
<display-name>FrontendServlet</display-name>
<servlet-name>frontend-servlet</servlet-name>
<servlet-class>google.registry.module.frontend.FrontendServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- The primary EPP endpoint for the Registry, which accepts EPP requests from our TLS proxy. -->
<servlet-mapping>
<servlet-name>frontend-servlet</servlet-name>
<url-pattern>/_dr/epp</url-pattern>
</servlet-mapping>
<!-- Registrar console endpoints -->
<servlet-mapping>
<servlet-name>frontend-servlet</servlet-name>
<url-pattern>/console-api/*</url-pattern>
</servlet-mapping>
<!-- Security config -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Internal</web-resource-name>
<description>
Admin-only internal section. Requests for paths covered by the URL patterns below will be
checked for a logged-in user account that's allowed to access the AppEngine admin console
(NOTE: this includes Editor/Viewer permissions in addition to Owner and the new IAM
App Engine Admin role. See https://cloud.google.com/appengine/docs/java/access-control
specifically the "Access handlers that have a login:admin restriction" line.)
TODO(b/28219927): lift some of these restrictions so that we can allow OAuth authentication
for endpoints that need to be accessed by open-source automated processes.
</description>
<!-- Internal AppEngine endpoints. The '_ah' is short for app hosting. -->
<url-pattern>/_ah/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
<!-- Repeated here since catch-all rule below is not inherited. -->
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<!-- Require TLS on all requests. -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Secure</web-resource-name>
<description>
Require encryption for all paths. http URLs will be redirected to https.
</description>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
</web-app>
@@ -1,17 +0,0 @@
# A default java.util.logging configuration.
# (All App Engine logging is through java.util.logging by default).
#
# To use this configuration, copy it into your application's WEB-INF
# folder and add the following to your appengine-web.xml:
#
# <system-properties>
# <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
# </system-properties>
#
# Set the default logging level for all loggers to INFO.
.level = INFO
# Turn off logging in Hibernate classes for misleading ERROR-level logs
org.hibernate.orm.jdbc.batch.level=OFF
org.hibernate.engine.jdbc.spi.SqlExceptionHelper.level=OFF
@@ -1,102 +0,0 @@
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<!-- Servlets -->
<!-- Servlet for injected frontend actions -->
<servlet>
<display-name>PubApiServlet</display-name>
<servlet-name>pubapi-servlet</servlet-name>
<servlet-class>google.registry.module.pubapi.PubApiServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- HTTP WHOIS. -->
<servlet-mapping>
<servlet-name>pubapi-servlet</servlet-name>
<url-pattern>/whois/*</url-pattern>
</servlet-mapping>
<!-- Protocol WHOIS. -->
<servlet-mapping>
<servlet-name>pubapi-servlet</servlet-name>
<url-pattern>/_dr/whois</url-pattern>
</servlet-mapping>
<!-- RDAP (new WHOIS). -->
<servlet-mapping>
<servlet-name>pubapi-servlet</servlet-name>
<url-pattern>/rdap/*</url-pattern>
</servlet-mapping>
<!-- Public API to do availability checks -->
<servlet-mapping>
<servlet-name>pubapi-servlet</servlet-name>
<url-pattern>/check</url-pattern>
</servlet-mapping>
<!-- Security config -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Internal</web-resource-name>
<description>
Admin-only internal section. Requests for paths covered by the URL patterns below will be
checked for a logged-in user account that's allowed to access the AppEngine admin console
(NOTE: this includes Editor/Viewer permissions in addition to Owner and the new IAM
App Engine Admin role. See https://cloud.google.com/appengine/docs/java/access-control
specifically the "Access handlers that have a login:admin restriction" line.)
TODO(b/28219927): lift some of these restrictions so that we can allow OAuth authentication
for endpoints that need to be accessed by open-source automated processes.
</description>
<!-- Internal AppEngine endpoints. The '_ah' is short for app hosting. -->
<url-pattern>/_ah/*</url-pattern>
<!-- Verbatim JavaScript sources (only visible to admins for debugging). -->
<url-pattern>/assets/sources/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
<!-- Repeated here since catch-all rule below is not inherited. -->
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Registrar console</web-resource-name>
<description>
Registrar console requires user login. This is in addition to the
code-level "requireLogin" configuration on individual @Actions.
</description>
<url-pattern>/registrar*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
<!-- Repeated here since catch-all rule below is not inherited. -->
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<!-- Require TLS on all requests. -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Secure</web-resource-name>
<description>
Require encryption for all paths. http URLs will be redirected to https.
</description>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
</web-app>
@@ -1,17 +0,0 @@
# A default java.util.logging configuration.
# (All App Engine logging is through java.util.logging by default).
#
# To use this configuration, copy it into your application's WEB-INF
# folder and add the following to your appengine-web.xml:
#
# <system-properties>
# <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
# </system-properties>
#
# Set the default logging level for all loggers to INFO.
.level = INFO
# Turn off logging in Hibernate classes for misleading ERROR-level logs
org.hibernate.orm.jdbc.batch.level=OFF
org.hibernate.engine.jdbc.spi.SqlExceptionHelper.level=OFF
@@ -1,120 +0,0 @@
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<!-- Servlets -->
<!-- Servlet for injected tools actions -->
<servlet>
<display-name>ToolsServlet</display-name>
<servlet-name>tools-servlet</servlet-name>
<servlet-class>google.registry.module.tools.ToolsServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>tools-servlet</servlet-name>
<url-pattern>/_dr/admin/updateUserGroup</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>tools-servlet</servlet-name>
<url-pattern>/_dr/admin/verifyOte</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>tools-servlet</servlet-name>
<url-pattern>/_dr/admin/createGroups</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>tools-servlet</servlet-name>
<url-pattern>/_dr/admin/createPremiumList</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>tools-servlet</servlet-name>
<url-pattern>/_dr/admin/list/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>tools-servlet</servlet-name>
<url-pattern>/_dr/admin/deleteEntity</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>tools-servlet</servlet-name>
<url-pattern>/_dr/admin/updatePremiumList</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>tools-servlet</servlet-name>
<url-pattern>/_dr/loadtest</url-pattern>
</servlet-mapping>
<!-- ExecuteEppCommand uses this to execute remotely. -->
<servlet-mapping>
<servlet-name>tools-servlet</servlet-name>
<url-pattern>/_dr/epptool</url-pattern>
</servlet-mapping>
<!-- Refreshes all active domains in DNS -->
<servlet-mapping>
<servlet-name>tools-servlet</servlet-name>
<url-pattern>/_dr/task/refreshDnsForAllDomains</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>tools-servlet</servlet-name>
<url-pattern>/_dr/task/generateZoneFiles</url-pattern>
</servlet-mapping>
<!-- Security config -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Internal</web-resource-name>
<description>
Admin-only internal section. Requests for paths covered by the URL patterns below will be
checked for a logged-in user account that's allowed to access the AppEngine admin console
(NOTE: this includes Editor/Viewer permissions in addition to Owner and the new IAM
App Engine Admin role. See https://cloud.google.com/appengine/docs/java/access-control
specifically the "Access handlers that have a login:admin restriction" line.)
TODO(b/28219927): lift some of these restrictions so that we can allow OAuth authentication
for endpoints that need to be accessed by open-source automated processes.
</description>
<!-- Internal AppEngine endpoints. The '_ah' is short for app hosting. -->
<url-pattern>/_ah/*</url-pattern>
<!-- Registrar console (should not be available on non-default module). -->
<url-pattern>/registrar*</url-pattern>
<!-- Verbatim JavaScript sources (only visible to admins for debugging). -->
<url-pattern>/assets/sources/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
<!-- Repeated here since catch-all rule below is not inherited. -->
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<!-- Require TLS on all requests. -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Secure</web-resource-name>
<description>
Require encryption for all paths. http URLs will be redirected to https.
</description>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
</web-app>
@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>backend</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>10</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="crash"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1m"/>
</static-files>
</appengine-web-app>
@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>bsa</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>10</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="crash"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<!-- Enable external traffic to go through VPC, required for static ip -->
<vpc-access-connector>
<name>projects/domain-registry-crash/locations/us-central1/connectors/appengine-connector</name>
<egress-setting>all-traffic</egress-setting>
</vpc-access-connector>
<static-files>
<include path="/*.html" expiration="1m"/>
</static-files>
</appengine-web-app>
@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>default</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<basic-scaling>
<max-instances>10</max-instances>
<idle-timeout>30m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="crash"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1m"/>
<include path="/registrar/*.html" expiration="1m"/>
</static-files>
</appengine-web-app>
@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>pubapi</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>8</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="crash"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1m"/>
<include path="/assets/js/**" expiration="1m"/>
<include path="/assets/css/**" expiration="1m"/>
<include path="/assets/images/**" expiration="1m"/>
</static-files>
</appengine-web-app>
@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>tools</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>10</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="crash"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1m"/>
<include path="/assets/js/**" expiration="1m"/>
<include path="/assets/css/**" expiration="1m"/>
<include path="/assets/images/**" expiration="1m"/>
</static-files>
</appengine-web-app>
@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>backend</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>10</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="local"/>
<property name="appengine.generated.dir"
value="/tmp/domain-registry-appengine-generated/local/"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html">
<http-header name="Cache-Control" value="max-age=0,must-revalidate" />
</include>
</static-files>
</appengine-web-app>
@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>bsa</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>10</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="local"/>
<property name="appengine.generated.dir"
value="/tmp/domain-registry-appengine-generated/local/"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html">
<http-header name="Cache-Control" value="max-age=0,must-revalidate" />
</include>
</static-files>
</appengine-web-app>
@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>default</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<basic-scaling>
<max-instances>10</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="local"/>
<property name="appengine.generated.dir"
value="/tmp/domain-registry-appengine-generated/local/"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html">
<http-header name="Cache-Control" value="max-age=0,must-revalidate" />
</include>
<include path="/registrar/*.html">
<http-header name="Cache-Control" value="max-age=0,must-revalidate" />
</include>
</static-files>
</appengine-web-app>
@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>pubapi</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>8</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="local"/>
<property name="appengine.generated.dir"
value="/tmp/domain-registry-appengine-generated/local/"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html">
<http-header name="Cache-Control" value="max-age=0,must-revalidate" />
</include>
<include path="/assets/js/**">
<http-header name="Cache-Control" value="max-age=0,must-revalidate" />
</include>
<include path="/assets/css/**">
<http-header name="Cache-Control" value="max-age=0,must-revalidate" />
</include>
<include path="/assets/images/**">
<http-header name="Cache-Control" value="max-age=0,must-revalidate" />
</include>
</static-files>
</appengine-web-app>
@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>tools</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>10</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="local"/>
<property name="appengine.generated.dir"
value="/tmp/domain-registry-appengine-generated/local/"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html">
<http-header name="Cache-Control" value="max-age=0,must-revalidate" />
</include>
<include path="/assets/js/**">
<http-header name="Cache-Control" value="max-age=0,must-revalidate" />
</include>
<include path="/assets/css/**">
<http-header name="Cache-Control" value="max-age=0,must-revalidate" />
</include>
<include path="/assets/images/**">
<http-header name="Cache-Control" value="max-age=0,must-revalidate" />
</include>
</static-files>
</appengine-web-app>
@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>backend</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<basic-scaling>
<max-instances>100</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="production"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1d"/>
</static-files>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->
<static-error-handlers>
<handler file="error.html"/>
</static-error-handlers>
</appengine-web-app>
@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>bsa</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<basic-scaling>
<max-instances>3</max-instances>
<idle-timeout>60m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="production"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<!-- Enable external traffic to go through VPC, required for static ip -->
<vpc-access-connector>
<name>projects/domain-registry/locations/us-central1/connectors/appengine-connector</name>
<egress-setting>all-traffic</egress-setting>
</vpc-access-connector>
<static-files>
<include path="/*.html" expiration="1d"/>
</static-files>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->
<static-error-handlers>
<handler file="error.html"/>
</static-error-handlers>
</appengine-web-app>
@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>default</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<manual-scaling>
<instances>24</instances>
</manual-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="production"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1d"/>
<include path="/registrar/*.html" expiration="1d"/>
</static-files>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->
<static-error-handlers>
<handler file="error.html"/>
</static-error-handlers>
</appengine-web-app>
@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>pubapi</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<manual-scaling>
<instances>24</instances>
</manual-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="production"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1d"/>
<include path="/assets/js/**" expiration="1d"/>
<include path="/assets/css/**" expiration="1d"/>
<include path="/assets/images/**" expiration="1d"/>
</static-files>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->
<static-error-handlers>
<handler file="error.html"/>
</static-error-handlers>
</appengine-web-app>
@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>tools</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<basic-scaling>
<max-instances>5</max-instances>
<idle-timeout>60m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="production"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1d"/>
<include path="/assets/js/**" expiration="1d"/>
<include path="/assets/css/**" expiration="1d"/>
<include path="/assets/images/**" expiration="1d"/>
</static-files>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->
<static-error-handlers>
<handler file="error.html"/>
</static-error-handlers>
</appengine-web-app>
@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>backend</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>10</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="qa"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1h"/>
</static-files>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->
<static-error-handlers>
<handler file="error.html"/>
</static-error-handlers>
</appengine-web-app>
@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>bsa</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>10</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="qa"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1h"/>
</static-files>
<!-- Enable external traffic to go through VPC, required for static ip -->
<vpc-access-connector>
<name>projects/domain-registry-qa/locations/us-central1/connectors/appengine-connector</name>
<egress-setting>all-traffic</egress-setting>
</vpc-access-connector>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->
<static-error-handlers>
<handler file="error.html"/>
</static-error-handlers>
</appengine-web-app>
@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>default</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>F4_1G</instance-class>
<automatic-scaling>
<min-idle-instances>1</min-idle-instances>
<max-idle-instances>automatic</max-idle-instances>
<min-pending-latency>automatic</min-pending-latency>
<max-pending-latency>100ms</max-pending-latency>
<max-concurrent-requests>10</max-concurrent-requests>
</automatic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="qa"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1h"/>
<include path="/registrar/*.html" expiration="1h"/>
</static-files>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->
<static-error-handlers>
<handler file="error.html"/>
</static-error-handlers>
</appengine-web-app>
@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>pubapi</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>10</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="qa"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1h"/>
<include path="/assets/js/**" expiration="1h"/>
<include path="/assets/css/**" expiration="1h"/>
<include path="/assets/images/**" expiration="1h"/>
</static-files>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->
<static-error-handlers>
<handler file="error.html"/>
</static-error-handlers>
</appengine-web-app>
@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>tools</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>10</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="qa"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1h"/>
<include path="/assets/js/**" expiration="1h"/>
<include path="/assets/css/**" expiration="1h"/>
<include path="/assets/images/**" expiration="1h"/>
</static-files>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->
<static-error-handlers>
<handler file="error.html"/>
</static-error-handlers>
</appengine-web-app>
@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>backend</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>100</max-instances>
<idle-timeout>10m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="sandbox"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1d"/>
</static-files>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->
<static-error-handlers>
<handler file="error.html"/>
</static-error-handlers>
</appengine-web-app>
@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>bsa</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>3</max-instances>
<idle-timeout>60m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="sandbox"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1d"/>
</static-files>
<!-- Enable external traffic to go through VPC, required for static ip -->
<vpc-access-connector>
<name>projects/domain-registry-sandbox/locations/us-central1/connectors/appengine-connector</name>
<egress-setting>all-traffic</egress-setting>
</vpc-access-connector>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->
<static-error-handlers>
<handler file="error.html"/>
</static-error-handlers>
</appengine-web-app>
@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>default</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<manual-scaling>
<instances>6</instances>
</manual-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="sandbox"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1d"/>
<include path="/registrar/*.html" expiration="1d"/>
</static-files>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->
<static-error-handlers>
<handler file="error.html"/>
</static-error-handlers>
</appengine-web-app>
@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>pubapi</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<manual-scaling>
<instances>5</instances>
</manual-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="sandbox"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1d"/>
<include path="/assets/js/**" expiration="1d"/>
<include path="/assets/css/**" expiration="1d"/>
<include path="/assets/images/**" expiration="1d"/>
</static-files>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->
<static-error-handlers>
<handler file="error.html"/>
</static-error-handlers>
</appengine-web-app>
@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java21</runtime>
<service>tools</service>
<app-engine-apis>true</app-engine-apis>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
<max-instances>5</max-instances>
<idle-timeout>60m</idle-timeout>
</basic-scaling>
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
<property name="google.registry.environment"
value="sandbox"/>
</system-properties>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE" value="true"/>
</env-variables>
<static-files>
<include path="/*.html" expiration="1d"/>
<include path="/assets/js/**" expiration="1d"/>
<include path="/assets/css/**" expiration="1d"/>
<include path="/assets/images/**" expiration="1d"/>
</static-files>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->
<static-error-handlers>
<handler file="error.html"/>
</static-error-handlers>
</appengine-web-app>
@@ -128,10 +128,10 @@ class SyncRegistrarsSheet {
builder.put("billingContacts", convertContacts(contacts, byType(BILLING)));
builder.put(
"contactsMarkedAsWhoisAdmin",
convertContacts(contacts, RegistrarPoc::getVisibleInWhoisAsAdmin));
convertContacts(contacts, RegistrarPoc::getVisibleInRdapAsAdmin));
builder.put(
"contactsMarkedAsWhoisTech",
convertContacts(contacts, RegistrarPoc::getVisibleInWhoisAsTech));
convertContacts(contacts, RegistrarPoc::getVisibleInRdapAsTech));
builder.put("emailAddress", convert(registrar.getEmailAddress()));
builder.put("address.street", convert(address.getStreet()));
builder.put("address.city", convert(address.getCity()));
@@ -4,7 +4,7 @@
<version comment="Latin LGR">1</version>
<date>2025-10-01</date>
<language>und-Latn</language>
<unicode-version>2</unicode-version>
<unicode-version>11.1.0</unicode-version>
<description type="text/html"><![CDATA[
<div class="instructions">
<h2>INSTRUCTIONS</h2>
@@ -650,7 +650,6 @@
<!--Action elements go here - order defines precedence-->
<action disp="invalid" match="leading-combining-mark" comment="labels with leading combining marks are invalid &#x235F;" />
<action disp="invalid" any-variant="out-of-repertoire-var" comment="any variant label with a code point out of repertoire is invalid &#x235F;" />
<action disp="invalid" match="dot-L-dot" comment="labels with one L sharing two middle dots are invalid" />
<action disp="blocked" any-variant="blocked" comment="any variant label containing blocked variants is blocked &#x235F;" />
<action disp="allocatable" all-variants="allocatable" comment="variant labels with all variants allocatable are allocatable &#x235F;" />
<action disp="allocatable" all-variants="fallback" comment="any label with all variants of type fallback is allocatable &#x235F;" />
@@ -192,7 +192,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
immutableEnumSet(State.ACTIVE, State.SUSPENDED);
/**
* The types for which a {@link Registrar} should be included in WHOIS and RDAP output. We exclude
* The types for which a {@link Registrar} should be included in RDAP output. We exclude
* registrars of type TEST. We considered excluding INTERNAL as well, but decided that
* troubleshooting would be easier with INTERNAL registrars visible. Before removing other types
* from view, carefully consider the effect on things like prober monitoring and OT&E.
@@ -416,13 +416,13 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
String phonePasscode;
/**
* A dirty bit for whether RegistrarContact changes have been made that haven't been synced to
* Google Groups yet. When creating a new instance, contacts require syncing by default.
* A dirty bit for whether RegistrarPoc changes have been made that haven't been synced to Google
* Groups yet. When creating a new instance, contacts require syncing by default.
*/
@Column(nullable = false)
boolean contactsRequireSyncing = true;
/** Whether or not registry lock is allowed for this registrar. */
/** Whether registry lock is allowed for this registrar. */
@Column(nullable = false)
@Expose
boolean registryLockAllowed = false;
@@ -612,11 +612,11 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
}
/**
* Returns the {@link RegistrarPoc} that is the WHOIS abuse contact for this registrar, or empty
* if one does not exist.
* Returns the {@link RegistrarPoc} that is the RDAP abuse contact for this registrar, or empty if
* one does not exist.
*/
public Optional<RegistrarPoc> getWhoisAbuseContact() {
return getContacts().stream().filter(RegistrarPoc::getVisibleInDomainWhoisAsAbuse).findFirst();
public Optional<RegistrarPoc> getRdapAbuseContact() {
return getContacts().stream().filter(RegistrarPoc::getVisibleInDomainRdapAsAbuse).findFirst();
}
private ImmutableList<RegistrarPoc> getPocs(TransactionManager txnManager) {
@@ -48,9 +48,9 @@ import java.util.Set;
* A contact for a Registrar. Note, equality, hashCode and comparable have been overridden to only
* enable key equality.
*
* <p>IMPORTANT NOTE: Any time that you change, update, or delete RegistrarContact entities, you
* *MUST* also modify the persisted Registrar entity with {@link Registrar#contactsRequireSyncing}
* set to true.
* <p>IMPORTANT NOTE: Any time that you change, update, or delete RegistrarPoc entities, you *MUST*
* also modify the persisted Registrar entity with {@link Registrar#contactsRequireSyncing} set to
* true.
*/
@Entity
@IdClass(RegistrarPoc.RegistrarPocId.class)
@@ -58,8 +58,8 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
/**
* Registrar contacts types for partner communication tracking.
*
* <p><b>Note:</b> These types only matter to the registry. They are not meant to be used for
* WHOIS or RDAP results.
* <p><b>Note:</b> These types only matter to the registry. They are not meant to be used for RDAP
* results.
*/
public enum Type {
ABUSE("abuse", true),
@@ -115,32 +115,23 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
@Expose
Set<Type> types;
/**
* Whether this contact is publicly visible in WHOIS registrar query results as an Admin contact.
*/
@Column(nullable = false)
/** If this contact is publicly visible in RDAP registrar query results as an Admin contact */
@Column(nullable = false, name = "visibleInWhoisAsAdmin")
@Expose
boolean visibleInWhoisAsAdmin = false;
boolean visibleInRdapAsAdmin = false;
/** If this contact is publicly visible in RDAP registrar query results as a Technical contact */
@Column(nullable = false, name = "visibleInWhoisAsTech")
@Expose
boolean visibleInRdapAsTech = false;
/**
* Whether this contact is publicly visible in WHOIS registrar query results as a Technical
* contact.
*/
@Column(nullable = false)
@Expose
boolean visibleInWhoisAsTech = false;
/**
* Whether this contact's phone number and email address is publicly visible in WHOIS domain query
* If this contact's phone number and email address are publicly visible in RDAP domain query
* results as registrar abuse contact info.
*/
@Column(nullable = false)
@Column(nullable = false, name = "visibleInDomainWhoisAsAbuse")
@Expose
boolean visibleInDomainWhoisAsAbuse = false;
/** Legacy field, around until we can remove the non-null constraint and the column from SQL. */
@Column(nullable = false)
boolean allowedToSetRegistryLockPassword = false;
boolean visibleInDomainRdapAsAbuse = false;
/**
* Helper to update the contacts associated with a Registrar. This requires querying for the
@@ -188,16 +179,16 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
return nullToEmptyImmutableSortedCopy(types);
}
public boolean getVisibleInWhoisAsAdmin() {
return visibleInWhoisAsAdmin;
public boolean getVisibleInRdapAsAdmin() {
return visibleInRdapAsAdmin;
}
public boolean getVisibleInWhoisAsTech() {
return visibleInWhoisAsTech;
public boolean getVisibleInRdapAsTech() {
return visibleInRdapAsTech;
}
public boolean getVisibleInDomainWhoisAsAbuse() {
return visibleInDomainWhoisAsAbuse;
public boolean getVisibleInDomainRdapAsAbuse() {
return visibleInDomainRdapAsAbuse;
}
public Builder asBuilder() {
@@ -214,8 +205,8 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
* person@example.com
* Tel: +1.2125650666
* Types: [ADMIN, WHOIS]
* Visible in WHOIS as Admin contact: Yes
* Visible in WHOIS as Technical contact: No
* Visible in RDAP as Admin contact: Yes
* Visible in RDAP as Technical contact: No
* Registrar-Console access: Yes
* Login Email Address: person@registry.example
* }</pre>
@@ -232,18 +223,18 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
}
result.append("Types: ").append(getTypes()).append('\n');
result
.append("Visible in registrar WHOIS query as Admin contact: ")
.append(getVisibleInWhoisAsAdmin() ? "Yes" : "No")
.append("Visible in registrar RDAP query as Admin contact: ")
.append(getVisibleInRdapAsAdmin() ? "Yes" : "No")
.append('\n');
result
.append("Visible in registrar WHOIS query as Technical contact: ")
.append(getVisibleInWhoisAsTech() ? "Yes" : "No")
.append("Visible in registrar RDAP query as Technical contact: ")
.append(getVisibleInRdapAsTech() ? "Yes" : "No")
.append('\n');
result
.append(
"Phone number and email visible in domain WHOIS query as "
"Phone number and email visible in domain RDAP query as "
+ "Registrar Abuse contact info: ")
.append(getVisibleInDomainWhoisAsAbuse() ? "Yes" : "No")
.append(getVisibleInDomainRdapAsAbuse() ? "Yes" : "No")
.append('\n');
return result.toString();
}
@@ -256,9 +247,9 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
.put("phoneNumber", phoneNumber)
.put("faxNumber", faxNumber)
.put("types", getTypes().stream().map(Object::toString).collect(joining(",")))
.put("visibleInWhoisAsAdmin", visibleInWhoisAsAdmin)
.put("visibleInWhoisAsTech", visibleInWhoisAsTech)
.put("visibleInDomainWhoisAsAbuse", visibleInDomainWhoisAsAbuse)
.put("visibleInRdapAsAdmin", visibleInRdapAsAdmin)
.put("visibleInRdapAsTech", visibleInRdapAsTech)
.put("visibleInDomainRdapAsAbuse", visibleInDomainRdapAsAbuse)
.put("id", getId())
.build();
}
@@ -336,18 +327,18 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
return this;
}
public Builder setVisibleInWhoisAsAdmin(boolean visible) {
getInstance().visibleInWhoisAsAdmin = visible;
public Builder setVisibleInRdapAsAdmin(boolean visible) {
getInstance().visibleInRdapAsAdmin = visible;
return this;
}
public Builder setVisibleInWhoisAsTech(boolean visible) {
getInstance().visibleInWhoisAsTech = visible;
public Builder setVisibleInRdapAsTech(boolean visible) {
getInstance().visibleInRdapAsTech = visible;
return this;
}
public Builder setVisibleInDomainWhoisAsAbuse(boolean visible) {
getInstance().visibleInDomainWhoisAsAbuse = visible;
public Builder setVisibleInDomainRdapAsAbuse(boolean visible) {
getInstance().visibleInDomainRdapAsAbuse = visible;
return this;
}
}
@@ -49,7 +49,6 @@ import google.registry.request.Modules.UrlConnectionServiceModule;
import google.registry.request.RequestHandler;
import google.registry.request.auth.AuthModule;
import google.registry.request.auth.RequestAuthenticator;
import google.registry.ui.ConsoleDebug.ConsoleConfigModule;
import google.registry.util.UtilsModule;
import jakarta.inject.Provider;
import jakarta.inject.Singleton;
@@ -63,7 +62,6 @@ import jakarta.inject.Singleton;
BigqueryModule.class,
CloudTasksUtilsModule.class,
ConfigModule.class,
ConsoleConfigModule.class,
CredentialModule.class,
CustomLogicFactoryModule.class,
DirectoryModule.class,
@@ -1,54 +0,0 @@
// Copyright 2023 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.
package google.registry.module.bsa;
import com.google.monitoring.metrics.MetricReporter;
import dagger.Component;
import dagger.Lazy;
import google.registry.config.CredentialModule;
import google.registry.config.RegistryConfig.ConfigModule;
import google.registry.groups.GmailModule;
import google.registry.keyring.KeyringModule;
import google.registry.module.bsa.BsaRequestComponent.BsaRequestComponentModule;
import google.registry.monitoring.whitebox.StackdriverModule;
import google.registry.persistence.PersistenceModule;
import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.UrlConnectionServiceModule;
import google.registry.request.auth.AuthModule;
import google.registry.util.UtilsModule;
import jakarta.inject.Singleton;
@Singleton
@Component(
modules = {
AuthModule.class,
BsaRequestComponentModule.class,
ConfigModule.class,
CredentialModule.class,
GmailModule.class,
GsonModule.class,
PersistenceModule.class,
KeyringModule.class,
SecretManagerModule.class,
StackdriverModule.class,
UrlConnectionServiceModule.class,
UtilsModule.class
})
interface BsaComponent {
BsaRequestHandler requestHandler();
Lazy<MetricReporter> metricReporter();
}
@@ -1,52 +0,0 @@
// Copyright 2023 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.
package google.registry.module.bsa;
import dagger.Module;
import dagger.Subcomponent;
import google.registry.bsa.BsaDownloadAction;
import google.registry.bsa.BsaRefreshAction;
import google.registry.bsa.BsaValidateAction;
import google.registry.bsa.UploadBsaUnavailableDomainsAction;
import google.registry.request.Modules.UrlConnectionServiceModule;
import google.registry.request.RequestComponentBuilder;
import google.registry.request.RequestModule;
import google.registry.request.RequestScope;
@RequestScope
@Subcomponent(modules = {RequestModule.class, UrlConnectionServiceModule.class})
public interface BsaRequestComponent {
BsaDownloadAction bsaDownloadAction();
BsaRefreshAction bsaRefreshAction();
BsaValidateAction bsaValidateAction();
UploadBsaUnavailableDomainsAction uploadBsaUnavailableDomains();
@Subcomponent.Builder
abstract class Builder implements RequestComponentBuilder<BsaRequestComponent> {
@Override
public abstract Builder requestModule(RequestModule requestModule);
@Override
public abstract BsaRequestComponent build();
}
@Module(subcomponents = BsaRequestComponent.class)
class BsaRequestComponentModule {}
}
@@ -1,29 +0,0 @@
// Copyright 2023 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.
package google.registry.module.bsa;
import google.registry.request.RequestHandler;
import google.registry.request.auth.RequestAuthenticator;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
public class BsaRequestHandler extends RequestHandler<BsaRequestComponent> {
@Inject
public BsaRequestHandler(
Provider<BsaRequestComponent.Builder> componentBuilderProvider,
RequestAuthenticator requestAuthenticator) {
super(componentBuilderProvider, requestAuthenticator);
}
}
@@ -1,30 +0,0 @@
// Copyright 2023 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.
package google.registry.module.bsa;
import com.google.monitoring.metrics.MetricReporter;
import dagger.Lazy;
import google.registry.module.ServletBase;
public final class BsaServlet extends ServletBase {
private static final BsaComponent component = DaggerBsaComponent.create();
private static final BsaRequestHandler requestHandler = component.requestHandler();
private static final Lazy<MetricReporter> metricReporter = component.metricReporter();
public BsaServlet() {
super(requestHandler, metricReporter);
}
}
@@ -1,16 +0,0 @@
// Copyright 2023 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.
@javax.annotation.ParametersAreNonnullByDefault
package google.registry.module.bsa;
@@ -35,7 +35,6 @@ import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.NetHttpTransportModule;
import google.registry.request.auth.AuthModule;
import google.registry.ui.ConsoleDebug.ConsoleConfigModule;
import google.registry.util.UtilsModule;
import jakarta.inject.Singleton;
@@ -46,7 +45,6 @@ import jakarta.inject.Singleton;
AuthModule.class,
CloudTasksUtilsModule.class,
ConfigModule.class,
ConsoleConfigModule.class,
CredentialModule.class,
CustomLogicFactoryModule.class,
CloudTasksUtilsModule.class,
@@ -1,65 +0,0 @@
// Copyright 2018 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.
package google.registry.module.pubapi;
import com.google.monitoring.metrics.MetricReporter;
import dagger.Component;
import dagger.Lazy;
import google.registry.config.CredentialModule;
import google.registry.config.RegistryConfig.ConfigModule;
import google.registry.flows.ServerTridProviderModule;
import google.registry.flows.custom.CustomLogicFactoryModule;
import google.registry.groups.DirectoryModule;
import google.registry.groups.GroupsModule;
import google.registry.groups.GroupssettingsModule;
import google.registry.keyring.KeyringModule;
import google.registry.keyring.api.KeyModule;
import google.registry.module.pubapi.PubApiRequestComponent.PubApiRequestComponentModule;
import google.registry.monitoring.whitebox.StackdriverModule;
import google.registry.persistence.PersistenceModule;
import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.NetHttpTransportModule;
import google.registry.request.auth.AuthModule;
import google.registry.util.UtilsModule;
import jakarta.inject.Singleton;
/** Dagger component with instance lifetime for "pubapi" App Engine module. */
@Singleton
@Component(
modules = {
AuthModule.class,
ConfigModule.class,
CredentialModule.class,
CustomLogicFactoryModule.class,
DirectoryModule.class,
GroupsModule.class,
GroupssettingsModule.class,
GsonModule.class,
KeyModule.class,
KeyringModule.class,
NetHttpTransportModule.class,
PersistenceModule.class,
PubApiRequestComponentModule.class,
SecretManagerModule.class,
ServerTridProviderModule.class,
StackdriverModule.class,
UtilsModule.class
})
interface PubApiComponent {
PubApiRequestHandler requestHandler();
Lazy<MetricReporter> metricReporter();
}
@@ -1,74 +0,0 @@
// Copyright 2018 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.
package google.registry.module.pubapi;
import dagger.Module;
import dagger.Subcomponent;
import google.registry.dns.DnsModule;
import google.registry.flows.CheckApiAction;
import google.registry.flows.CheckApiAction.CheckApiModule;
import google.registry.flows.TlsCredentials.EppTlsModule;
import google.registry.module.ReadinessProbeAction.ReadinessProbeActionPubApi;
import google.registry.monitoring.whitebox.WhiteboxModule;
import google.registry.rdap.RdapAutnumAction;
import google.registry.rdap.RdapDomainAction;
import google.registry.rdap.RdapDomainSearchAction;
import google.registry.rdap.RdapEmptyAction;
import google.registry.rdap.RdapEntityAction;
import google.registry.rdap.RdapEntitySearchAction;
import google.registry.rdap.RdapHelpAction;
import google.registry.rdap.RdapIpAction;
import google.registry.rdap.RdapModule;
import google.registry.rdap.RdapNameserverAction;
import google.registry.rdap.RdapNameserverSearchAction;
import google.registry.request.RequestComponentBuilder;
import google.registry.request.RequestModule;
import google.registry.request.RequestScope;
/** Dagger component with per-request lifetime for "pubapi" App Engine module. */
@RequestScope
@Subcomponent(
modules = {
CheckApiModule.class,
DnsModule.class,
EppTlsModule.class,
RdapModule.class,
RequestModule.class,
WhiteboxModule.class
})
public interface PubApiRequestComponent {
CheckApiAction checkApiAction();
RdapAutnumAction rdapAutnumAction();
RdapDomainAction rdapDomainAction();
RdapDomainSearchAction rdapDomainSearchAction();
RdapEmptyAction rdapEmptyAction();
RdapEntityAction rdapEntityAction();
RdapEntitySearchAction rdapEntitySearchAction();
RdapHelpAction rdapHelpAction();
RdapIpAction rdapDefaultAction();
RdapNameserverAction rdapNameserverAction();
RdapNameserverSearchAction rdapNameserverSearchAction();
ReadinessProbeActionPubApi readinessProbeActionPubApi();
@Subcomponent.Builder
abstract class Builder implements RequestComponentBuilder<PubApiRequestComponent> {
@Override public abstract Builder requestModule(RequestModule requestModule);
@Override public abstract PubApiRequestComponent build();
}
@Module(subcomponents = PubApiRequestComponent.class)
class PubApiRequestComponentModule {}
}
@@ -1,31 +0,0 @@
// Copyright 2018 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.
package google.registry.module.pubapi;
import google.registry.request.RequestHandler;
import google.registry.request.auth.RequestAuthenticator;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
/** Request handler for the frontend module. */
public class PubApiRequestHandler extends RequestHandler<PubApiRequestComponent> {
@Inject
PubApiRequestHandler(
Provider<PubApiRequestComponent.Builder> componentBuilderProvider,
RequestAuthenticator requestAuthenticator) {
super(componentBuilderProvider, requestAuthenticator);
}
}
@@ -1,31 +0,0 @@
// Copyright 2018 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.
package google.registry.module.pubapi;
import com.google.monitoring.metrics.MetricReporter;
import dagger.Lazy;
import google.registry.module.ServletBase;
/** Servlet that should handle all requests to our "default" App Engine module. */
public final class PubApiServlet extends ServletBase {
private static final PubApiComponent component = DaggerPubApiComponent.create();
private static final PubApiRequestHandler requestHandler = component.requestHandler();
private static final Lazy<MetricReporter> metricReporter = component.metricReporter();
public PubApiServlet() {
super(requestHandler, metricReporter);
}
}
@@ -1,16 +0,0 @@
// Copyright 2018 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.
@javax.annotation.ParametersAreNonnullByDefault
package google.registry.module.pubapi;
@@ -1,69 +0,0 @@
// Copyright 2017 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.
package google.registry.module.tools;
import com.google.monitoring.metrics.MetricReporter;
import dagger.Component;
import dagger.Lazy;
import google.registry.config.CloudTasksUtilsModule;
import google.registry.config.CredentialModule;
import google.registry.config.RegistryConfig.ConfigModule;
import google.registry.export.DriveModule;
import google.registry.flows.ServerTridProviderModule;
import google.registry.flows.custom.CustomLogicFactoryModule;
import google.registry.flows.domain.DomainDeletionTimeCacheModule;
import google.registry.groups.DirectoryModule;
import google.registry.groups.GroupsModule;
import google.registry.groups.GroupssettingsModule;
import google.registry.keyring.KeyringModule;
import google.registry.keyring.api.KeyModule;
import google.registry.module.tools.ToolsRequestComponent.ToolsRequestComponentModule;
import google.registry.monitoring.whitebox.StackdriverModule;
import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.NetHttpTransportModule;
import google.registry.request.auth.AuthModule;
import google.registry.util.UtilsModule;
import jakarta.inject.Singleton;
/** Dagger component with instance lifetime for "tools" App Engine module. */
@Singleton
@Component(
modules = {
AuthModule.class,
ConfigModule.class,
CredentialModule.class,
CustomLogicFactoryModule.class,
CloudTasksUtilsModule.class,
DirectoryModule.class,
DomainDeletionTimeCacheModule.class,
DriveModule.class,
GroupsModule.class,
GroupssettingsModule.class,
GsonModule.class,
KeyModule.class,
KeyringModule.class,
NetHttpTransportModule.class,
SecretManagerModule.class,
ServerTridProviderModule.class,
StackdriverModule.class,
ToolsRequestComponentModule.class,
UtilsModule.class
})
interface ToolsComponent {
ToolsRequestHandler requestHandler();
Lazy<MetricReporter> metricReporter();
}
@@ -1,80 +0,0 @@
// Copyright 2017 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.
package google.registry.module.tools;
import dagger.Module;
import dagger.Subcomponent;
import google.registry.dns.DnsModule;
import google.registry.flows.EppToolAction;
import google.registry.flows.EppToolAction.EppToolModule;
import google.registry.flows.FlowComponent;
import google.registry.loadtest.LoadTestAction;
import google.registry.loadtest.LoadTestModule;
import google.registry.monitoring.whitebox.WhiteboxModule;
import google.registry.request.RequestComponentBuilder;
import google.registry.request.RequestModule;
import google.registry.request.RequestScope;
import google.registry.tools.server.CreateGroupsAction;
import google.registry.tools.server.GenerateZoneFilesAction;
import google.registry.tools.server.ListDomainsAction;
import google.registry.tools.server.ListHostsAction;
import google.registry.tools.server.ListPremiumListsAction;
import google.registry.tools.server.ListRegistrarsAction;
import google.registry.tools.server.ListReservedListsAction;
import google.registry.tools.server.ListTldsAction;
import google.registry.tools.server.RefreshDnsForAllDomainsAction;
import google.registry.tools.server.ToolsServerModule;
import google.registry.tools.server.UpdateUserGroupAction;
import google.registry.tools.server.VerifyOteAction;
/** Dagger component with per-request lifetime for "tools" App Engine module. */
@RequestScope
@Subcomponent(
modules = {
DnsModule.class,
EppToolModule.class,
LoadTestModule.class,
RequestModule.class,
ToolsServerModule.class,
WhiteboxModule.class,
})
public interface ToolsRequestComponent {
FlowComponent.Builder flowComponentBuilder();
CreateGroupsAction createGroupsAction();
EppToolAction eppToolAction();
GenerateZoneFilesAction generateZoneFilesAction();
ListDomainsAction listDomainsAction();
ListHostsAction listHostsAction();
ListPremiumListsAction listPremiumListsAction();
ListRegistrarsAction listRegistrarsAction();
ListReservedListsAction listReservedListsAction();
ListTldsAction listTldsAction();
LoadTestAction loadTestAction();
RefreshDnsForAllDomainsAction refreshDnsForAllDomainsAction();
UpdateUserGroupAction updateUserGroupAction();
VerifyOteAction verifyOteAction();
@Subcomponent.Builder
abstract class Builder implements RequestComponentBuilder<ToolsRequestComponent> {
@Override public abstract Builder requestModule(RequestModule requestModule);
@Override public abstract ToolsRequestComponent build();
}
@Module(subcomponents = ToolsRequestComponent.class)
class ToolsRequestComponentModule {}
}
@@ -1,30 +0,0 @@
// Copyright 2017 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.
package google.registry.module.tools;
import google.registry.request.RequestHandler;
import google.registry.request.auth.RequestAuthenticator;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
/** Request handler for the tools module. */
public class ToolsRequestHandler extends RequestHandler<ToolsRequestComponent> {
@Inject ToolsRequestHandler(
Provider<ToolsRequestComponent.Builder> componentBuilderProvider,
RequestAuthenticator requestAuthenticator) {
super(componentBuilderProvider, requestAuthenticator);
}
}
@@ -1,31 +0,0 @@
// Copyright 2017 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.
package google.registry.module.tools;
import com.google.monitoring.metrics.MetricReporter;
import dagger.Lazy;
import google.registry.module.ServletBase;
/** Servlet that should handle all requests to our "tools" App Engine module. */
public final class ToolsServlet extends ServletBase {
private static final ToolsComponent component = DaggerToolsComponent.create();
private static final ToolsRequestHandler requestHandler = component.requestHandler();
private static final Lazy<MetricReporter> metricReporter = component.metricReporter();
public ToolsServlet() {
super(requestHandler, metricReporter);
}
}
@@ -658,7 +658,7 @@ public class RdapJsonFormatter {
}
/**
* Creates the list of RDAP roles for a registrar POC, using the visibleInWhoisAs* flags.
* Creates the list of RDAP roles for a registrar POC, using the visibleInRdapAs* flags.
*
* <p>Only POCs with a non-empty role list should be visible.
*
@@ -672,13 +672,13 @@ public class RdapJsonFormatter {
*/
private static ImmutableList<RdapEntity.Role> makeRdapRoleList(RegistrarPoc registrarPoc) {
ImmutableList.Builder<RdapEntity.Role> rolesBuilder = new ImmutableList.Builder<>();
if (registrarPoc.getVisibleInWhoisAsAdmin()) {
if (registrarPoc.getVisibleInRdapAsAdmin()) {
rolesBuilder.add(RdapEntity.Role.ADMIN);
}
if (registrarPoc.getVisibleInWhoisAsTech()) {
if (registrarPoc.getVisibleInRdapAsTech()) {
rolesBuilder.add(RdapEntity.Role.TECH);
}
if (registrarPoc.getVisibleInDomainWhoisAsAbuse()) {
if (registrarPoc.getVisibleInDomainRdapAsAbuse()) {
rolesBuilder.add(RdapEntity.Role.ABUSE);
}
return rolesBuilder.build();
@@ -226,7 +226,7 @@ public class Spec11EmailUtils {
private InternetAddress getEmailAddressForRegistrar(String registrarId)
throws MessagingException {
// Attempt to use the registrar's WHOIS abuse contact, then fall back to the regular address.
// Attempt to use the registrar's RDAP abuse contact, then fall back to the regular address.
Registrar registrar =
Registrar.loadByRegistrarIdCached(registrarId)
.orElseThrow(
@@ -235,7 +235,7 @@ public class Spec11EmailUtils {
String.format("Could not find registrar %s", registrarId)));
return new InternetAddress(
registrar
.getWhoisAbuseContact()
.getRdapAbuseContact()
.map(RegistrarPoc::getEmailAddress)
.orElse(registrar.getEmailAddress()));
}
@@ -105,7 +105,7 @@ final class CreateRegistrarCommand extends CreateOrUpdateRegistrarCommand
"Cannot add allowed TLDs when creating a REAL registrar in a production environment."
+ " Please create the registrar without allowed TLDs, then use `nomulus"
+ " registrar_contact` to create a registrar contact for it that is visible as the"
+ " abuse contact in WHOIS. Then use `nomulus update_registrar` to add the allowed"
+ " abuse contact in RDAP. Then use `nomulus update_registrar` to add the allowed"
+ " TLDs.");
}
@@ -103,28 +103,27 @@ final class RegistrarPocCommand extends MutatingCommand {
@Nullable
@Parameter(
names = "--visible_in_whois_as_admin",
description = " Whether this contact is publicly visible in WHOIS results as an "
+ "Admin contact.",
names = "--visible_in_rdap_as_admin",
description = "If this contact is publicly visible in RDAP results as an " + "Admin contact.",
arity = 1)
private Boolean visibleInWhoisAsAdmin;
private Boolean visibleInRdapAsAdmin;
@Nullable
@Parameter(
names = "--visible_in_whois_as_tech",
description = " Whether this contact is publicly visible in WHOIS results as a "
+ "Tech contact.",
names = "--visible_in_rdap_as_tech",
description = "If this contact is publicly visible in RDAP results as a " + "Tech contact.",
arity = 1)
private Boolean visibleInWhoisAsTech;
private Boolean visibleInRdapAsTech;
@Nullable
@Parameter(
names = "--visible_in_domain_whois_as_abuse",
description = " Whether this contact is publicly visible in WHOIS domain results as the "
+ "registry abuse phone and email. If this flag is set, it will be cleared from all "
+ "other contacts for the same registrar.",
names = "--visible_in_domain_rdap_as_abuse",
description =
" Whether this contact is publicly visible in RDAP domain results as the "
+ "registry abuse phone and email. If this flag is set, it will be cleared from all "
+ "other contacts for the same registrar.",
arity = 1)
private Boolean visibleInDomainWhoisAsAbuse;
private Boolean visibleInDomainRdapAsAbuse;
@Parameter(
names = {"-o", "--output"},
@@ -166,8 +165,8 @@ final class RegistrarPocCommand extends MutatingCommand {
case LIST -> listContacts(contacts);
case CREATE -> {
stageEntityChange(null, createContact(registrar));
if (visibleInDomainWhoisAsAbuse != null && visibleInDomainWhoisAsAbuse) {
unsetOtherWhoisAbuseFlags(contacts, null);
if (visibleInDomainRdapAsAbuse != null && visibleInDomainRdapAsAbuse) {
unsetOtherRdapAbuseFlags(contacts, null);
}
}
case UPDATE -> {
@@ -178,13 +177,13 @@ final class RegistrarPocCommand extends MutatingCommand {
email);
RegistrarPoc newContact = updateContact(oldContact, registrar);
checkArgument(
!oldContact.getVisibleInDomainWhoisAsAbuse()
|| newContact.getVisibleInDomainWhoisAsAbuse(),
"Cannot clear visible_in_domain_whois_as_abuse flag, as that would leave no domain"
+ " WHOIS abuse contacts; instead, set the flag on another contact");
!oldContact.getVisibleInDomainRdapAsAbuse()
|| newContact.getVisibleInDomainRdapAsAbuse(),
"Cannot clear visible_in_domain_rdap_as_abuse flag, as that would leave no domain"
+ " RDAP abuse contacts; instead, set the flag on another contact");
stageEntityChange(oldContact, newContact);
if (visibleInDomainWhoisAsAbuse != null && visibleInDomainWhoisAsAbuse) {
unsetOtherWhoisAbuseFlags(contacts, oldContact.getEmailAddress());
if (visibleInDomainRdapAsAbuse != null && visibleInDomainRdapAsAbuse) {
unsetOtherRdapAbuseFlags(contacts, oldContact.getEmailAddress());
}
}
case DELETE -> {
@@ -194,8 +193,8 @@ final class RegistrarPocCommand extends MutatingCommand {
"No contact with the given email: %s",
email);
checkArgument(
!oldContact.getVisibleInDomainWhoisAsAbuse(),
"Cannot delete the domain WHOIS abuse contact; set the flag on another contact first");
!oldContact.getVisibleInDomainRdapAsAbuse(),
"Cannot delete the domain RDAP abuse contact; set the flag on another contact first");
stageEntityChange(oldContact, null);
}
default -> throw new AssertionError();
@@ -228,14 +227,14 @@ final class RegistrarPocCommand extends MutatingCommand {
}
builder.setTypes(nullToEmpty(contactTypes));
if (visibleInWhoisAsAdmin != null) {
builder.setVisibleInWhoisAsAdmin(visibleInWhoisAsAdmin);
if (visibleInRdapAsAdmin != null) {
builder.setVisibleInRdapAsAdmin(visibleInRdapAsAdmin);
}
if (visibleInWhoisAsTech != null) {
builder.setVisibleInWhoisAsTech(visibleInWhoisAsTech);
if (visibleInRdapAsTech != null) {
builder.setVisibleInRdapAsTech(visibleInRdapAsTech);
}
if (visibleInDomainWhoisAsAbuse != null) {
builder.setVisibleInDomainWhoisAsAbuse(visibleInDomainWhoisAsAbuse);
if (visibleInDomainRdapAsAbuse != null) {
builder.setVisibleInDomainRdapAsAbuse(visibleInDomainRdapAsAbuse);
}
return builder.build();
}
@@ -257,24 +256,24 @@ final class RegistrarPocCommand extends MutatingCommand {
if (contactTypes != null) {
builder.setTypes(contactTypes);
}
if (visibleInWhoisAsAdmin != null) {
builder.setVisibleInWhoisAsAdmin(visibleInWhoisAsAdmin);
if (visibleInRdapAsAdmin != null) {
builder.setVisibleInRdapAsAdmin(visibleInRdapAsAdmin);
}
if (visibleInWhoisAsTech != null) {
builder.setVisibleInWhoisAsTech(visibleInWhoisAsTech);
if (visibleInRdapAsTech != null) {
builder.setVisibleInRdapAsTech(visibleInRdapAsTech);
}
if (visibleInDomainWhoisAsAbuse != null) {
builder.setVisibleInDomainWhoisAsAbuse(visibleInDomainWhoisAsAbuse);
if (visibleInDomainRdapAsAbuse != null) {
builder.setVisibleInDomainRdapAsAbuse(visibleInDomainRdapAsAbuse);
}
return builder.build();
}
private void unsetOtherWhoisAbuseFlags(
private void unsetOtherRdapAbuseFlags(
ImmutableSet<RegistrarPoc> contacts, @Nullable String emailAddressNotToChange) {
for (RegistrarPoc contact : contacts) {
if (!contact.getEmailAddress().equals(emailAddressNotToChange)
&& contact.getVisibleInDomainWhoisAsAbuse()) {
RegistrarPoc newContact = contact.asBuilder().setVisibleInDomainWhoisAsAbuse(false).build();
&& contact.getVisibleInDomainRdapAsAbuse()) {
RegistrarPoc newContact = contact.asBuilder().setVisibleInDomainRdapAsAbuse(false).build();
stageEntityChange(contact, newContact);
}
}
@@ -35,7 +35,7 @@ final class UpdateRegistrarCommand extends CreateOrUpdateRegistrarCommand {
@Override
void checkModifyAllowedTlds(@Nullable Registrar oldRegistrar) {
// Only allow modifying allowed TLDs if we're in a non-PRODUCTION environment, if the registrar
// is not REAL, or the registrar has a WHOIS abuse contact set.
// is not REAL, or the registrar has an RDAP abuse contact set.
checkArgumentNotNull(oldRegistrar, "Old registrar was not present during modification");
boolean isRealRegistrar =
@@ -43,9 +43,9 @@ final class UpdateRegistrarCommand extends CreateOrUpdateRegistrarCommand {
|| (Registrar.Type.REAL.equals(oldRegistrar.getType()) && registrarType == null);
if (RegistryEnvironment.PRODUCTION.equals(RegistryEnvironment.get()) && isRealRegistrar) {
checkArgumentPresent(
oldRegistrar.getWhoisAbuseContact(),
"Cannot modify allowed TLDs if there is no WHOIS abuse contact set. Please use the"
+ " \"nomulus registrar_contact\" command on this registrar to set a WHOIS abuse"
oldRegistrar.getRdapAbuseContact(),
"Cannot modify allowed TLDs if there is no RDAP abuse contact set. Please use the"
+ " \"nomulus registrar_contact\" command on this registrar to set an RDAP abuse"
+ " contact.");
}
}
@@ -1,60 +0,0 @@
// Copyright 2017 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.
package google.registry.ui;
import dagger.Module;
import dagger.Provides;
/** Enum defining which JS/CSS files get rendered in a soy templates. */
public enum ConsoleDebug {
/** Use compiled CSS and JS. */
PRODUCTION,
/** Use debug compiled CSS and JS, where symbols are only <i>slightly</i> mangled. */
DEBUG,
/**
* Use debug compiled CSS and raw JS from internal source code dependency-managed directory
* structure.
*/
RAW,
/** Don't use any CSS or JS. This is used by JSTD unit tests. */
TEST;
private static final String PROPERTY = "console.debug";
private static final String DEFAULT = PRODUCTION.name();
/** Returns value configured by system property {@code #PROPERTY}. */
public static ConsoleDebug get() {
return valueOf(System.getProperty(PROPERTY, DEFAULT));
}
/** Sets the global {@link ConsoleDebug} state. */
public static void set(ConsoleDebug value) {
System.setProperty(PROPERTY, value.toString());
}
/** Dagger module for ConsoleDebug. */
@Module
public static final class ConsoleConfigModule {
@Provides
static ConsoleDebug provideConsoleDebug() {
return ConsoleDebug.get();
}
}
}
@@ -1,58 +0,0 @@
// Copyright 2017 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.
package google.registry.ui.forms;
import static com.google.common.base.Preconditions.checkNotNull;
import javax.annotation.Detainted;
import javax.annotation.concurrent.NotThreadSafe;
/**
* Exception thrown when a form is invalid. Problems with a specific
* form field should use {@link FormFieldException} instead.
*
* <p>You can safely throw {@code FormException} from within your form
* validator, and the message will automatically be propagated to the
* client form interface.
*/
@NotThreadSafe
public class FormException extends RuntimeException {
/**
* Creates a new {@link FormException}
*
* @param userMessage should be a friendly message that's safe to show to the user.
*/
public FormException(@Detainted String userMessage) {
super(checkNotNull(userMessage, "userMessage"), null);
}
/**
* Creates a new {@link FormException}
*
* @param userMessage should be a friendly message that's safe to show to the user.
* @param cause the original cause of this exception. May be null.
*/
public FormException(@Detainted String userMessage, Throwable cause) {
super(checkNotNull(userMessage, "userMessage"), cause);
}
/** Returns an error message that's safe to display to the user. */
@Override
@Detainted
public String getMessage() {
return super.getMessage();
}
}
@@ -1,762 +0,0 @@
// Copyright 2017 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.
package google.registry.ui.forms;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import com.google.common.base.Ascii;
import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Range;
import com.google.common.collect.Streams;
import com.google.re2j.Pattern;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import javax.annotation.Detainted;
import javax.annotation.Nullable;
import javax.annotation.Tainted;
import javax.annotation.concurrent.Immutable;
/**
* Declarative functional fluent form field converter / validator.
*
* <p>This class is responsible for converting arbitrary data, sent to us by the web browser, into
* validated data structures that the server-side code can use. For example:
*
* <pre>{@code
* private enum Gender { MALE, FEMALE }
*
* private static final FormField<String, String> NAME_FIELD = FormField.named("name")
* .matches("[a-z]+")
* .range(atMost(16))
* .required()
* .build();
*
* private static final FormField<String, Gender> GENDER_FIELD = FormField.named("gender")
* .asEnum(Gender.class)
* .required()
* .build();
*
* public Person makePerson(Map<String, String> params) {
* Person.Builder person = new Person.Builder();
* for (String name : NAME_FIELD.extract(params).asSet()) {
* person.setName(name);
* }
* for (Gender name : GENDER_FIELD.extract(params).asSet()) {
* person.setGender(name);
* }
* return person.build();
* }
* }</pre>
*
* <p>This class provides <b>full type-safety</b> <i>if and only if</i> you statically initialize
* your FormField objects and write a unit test that causes the class to be loaded.
*
* <h2>Exception Handling</h2>
*
* <p>When values passed to {@link #convert} or {@link #extract} don't meet the contract, {@link
* FormFieldException} will be thrown, which provides the field name and a short error message
* that's safe to pass along to the client.
*
* <p>You can safely throw {@code FormFieldException} from within your validator functions, and the
* field name will automatically be propagated into the exception object for you.
*
* <p>In situations when you're validating lists or maps, you'll end up with a hierarchical field
* naming structure. For example, if you were validating a list of maps, an error generated by the
* {@code bar} field of the fifth item in the {@code foo} field would have a fully-qualified field
* name of: {@code foo[5][bar]}.
*
* <h3>Library Definitions</h3>
*
* <p>You should never assign a partially constructed {@code FormField.Builder} to a variable or
* constant. Instead, you should use {@link #asBuilder()} or {@link #asBuilderNamed(String)}.
*
* <p>Here is an example of how you might go about defining library definitions:
*
* <pre>{@code
* final class FormFields {
* private static final FormField<String, String> COUNTRY_CODE =
* FormField.named("countryCode")
* .range(Range.singleton(2))
* .uppercased()
* .in(ImmutableSet.copyOf(Locale.getISOCountries()))
* .build();
* }
*
* final class Form {
* private static final FormField<String, String> COUNTRY_CODE_FIELD =
* FormFields.COUNTRY_CODE.asBuilder()
* .required()
* .build();
* }
* }</pre>
*
* @param <I> input value type
* @param <O> output value type
*/
@Immutable
public final class FormField<I, O> {
private final String name;
private final Class<I> typeIn;
private final Class<O> typeOut;
private final Function<I, O> converter;
private FormField(String name, Class<I> typeIn, Class<O> typeOut, Function<I, O> converter) {
this.name = name;
this.typeIn = typeIn;
this.typeOut = typeOut;
this.converter = converter;
}
/** Returns an optional string form field named {@code name}. */
public static Builder<String, String> named(String name) {
return named(name, String.class);
}
/** Returns an optional form field named {@code name} with a specific {@code inputType}. */
public static <T> Builder<T, T> named(String name, Class<T> typeIn) {
checkArgument(!name.isEmpty());
return new Builder<>(name, checkNotNull(typeIn), typeIn, x -> x);
}
/**
* Returns a form field builder for validating JSON nested maps.
*
* <p>Here's an example of how you'd use this feature:
*
* <pre>
* private static final FormField&lt;String, String&gt; REGISTRAR_NAME_FIELD =
* FormField.named("name")
* .emptyToNull()
* .required()
* .build();
*
* private static final FormField&lt;Map&lt;String, ?&gt;, Registrar&gt; REGISTRAR_FIELD =
* FormField.mapNamed("registrar")
* .transform(Registrar.class, new Function&lt;Map&lt;String, ?&gt;, Registrar&gt;() {
* &#064;Nullable
* &#064;Override
* public Registrar apply(&#064;Nullable Map&lt;String, ?&gt; params) {
* Registrar.Builder builder = new Registrar.Builder();
* for (String name : REGISTRAR_NAME_FIELD.extractUntyped(params).asSet()) {
* builder.setName(name);
* }
* return builder.build();
* }})
* .build();</pre>
*
* <p>When a {@link FormFieldException} is thrown, it'll be propagated to create a fully-qualified
* field name. For example, if the JSON input is <pre>{registrar: {name: ""}}</pre> then the
* {@link FormFieldException#getFieldName() field name} will be {@code registrar.name}.
*/
public static Builder<Map<String, ?>, Map<String, ?>> mapNamed(String name) {
@SuppressWarnings("unchecked")
Class<Map<String, ?>> typeIn = (Class<Map<String, ?>>) (Class<?>) Map.class;
return named(name, typeIn);
}
/** Returns the name of this field. */
public String name() {
return name;
}
/**
* Convert and validate a raw user-supplied value.
*
* @throws FormFieldException if value does not meet expected contracts.
*/
@Detainted
public Optional<O> convert(@Tainted @Nullable I value) {
try {
return Optional.ofNullable(converter.apply(value));
} catch (FormFieldException e) {
throw e.propagate(name);
}
}
/**
* Convert and validate a raw user-supplied value from a map.
*
* <p>This is the same as saying: {@code field.convert(valueMap.get(field.name())}
*
* @throws FormFieldException if value does not meet expected contracts.
*/
@Detainted
public Optional<O> extract(@Tainted Map<String, I> valueMap) {
return convert(valueMap.get(name));
}
/**
* Convert and validate a raw user-supplied value from an untyped JSON map.
*
* @throws FormFieldException if value is wrong type or does not meet expected contracts.
*/
@Detainted
public Optional<O> extractUntyped(@Tainted Map<String, ?> jsonMap) {
Object value = jsonMap.get(name);
I castedValue;
try {
castedValue = typeIn.cast(value);
} catch (ClassCastException e) {
throw new FormFieldException(String.format("Type error: got: %s, expected: %s",
value.getClass().getSimpleName(),
typeIn.getSimpleName())).propagate(name);
}
return convert(castedValue);
}
/**
* Returns a builder of this object, which can be used to further restrict validation.
*
* @see #asBuilderNamed(String)
*/
public Builder<I, O> asBuilder() {
return new Builder<>(name, typeIn, typeOut, converter);
}
/** Same as {@link #asBuilder()} but changes the field name. */
public Builder<I, O> asBuilderNamed(String newName) {
checkArgument(!newName.isEmpty());
return new Builder<>(newName, typeIn, typeOut, converter);
}
/**
* Mutable builder for {@link FormField}.
*
* @param <I> input value type
* @param <O> output value type
*/
public static final class Builder<I, O> {
private final String name;
private final Class<I> typeIn;
private final Class<O> typeOut;
private Function<I, O> converter;
private Builder(String name, Class<I> typeIn, Class<O> typeOut, Function<I, O> converter) {
this.name = name;
this.typeIn = typeIn;
this.typeOut = typeOut;
this.converter = converter;
}
/** Causes {@code defaultValue} to be substituted if value is {@code null}. */
public Builder<I, O> withDefault(O defaultValue) {
return transform(new DefaultFunction<>(checkNotNull(defaultValue)));
}
/** Ensure value is not {@code null}. */
public Builder<I, O> required() {
return transform(Builder::checkNotNullTransform);
}
/**
* Transform empty values into {@code null}.
*
* @throws IllegalStateException if current output type is not a {@link CharSequence} or
* {@link Collection}.
*/
public Builder<I, O> emptyToNull() {
checkState(CharSequence.class.isAssignableFrom(typeOut)
|| Collection.class.isAssignableFrom(typeOut));
return transform(
input ->
((input instanceof CharSequence) && (((CharSequence) input).length() == 0))
|| ((input instanceof Collection) && ((Collection<?>) input).isEmpty())
? null
: input);
}
/**
* Modify {@link String} input to remove whitespace around the sides.
*
* <p>{@code null} values are passed through.
*
* @throws IllegalStateException if current output type is not a String.
*/
public Builder<I, String> trimmed() {
checkState(String.class.isAssignableFrom(typeOut));
@SuppressWarnings("unchecked")
Function<O, String> trimFunction =
(Function<O, String>)
((Function<String, String>) input -> input != null ? input.trim() : null);
return transform(String.class, trimFunction);
}
/**
* Modify {@link String} input to be uppercase.
*
* <p>{@code null} values are passed through.
*
* @throws IllegalStateException if current output type is not a String.
*/
public Builder<I, String> uppercased() {
checkState(String.class.isAssignableFrom(typeOut));
@SuppressWarnings("unchecked")
Function<O, String> funk =
(Function<O, String>)
((Function<String, String>)
input -> input != null ? input.toUpperCase(Locale.ENGLISH) : null);
return transform(String.class, funk);
}
/**
* Modify {@link String} input to be lowercase.
*
* <p>{@code null} values are passed through.
*
* @throws IllegalStateException if current output type is not a String.
*/
public Builder<I, String> lowercased() {
checkState(String.class.isAssignableFrom(typeOut));
@SuppressWarnings("unchecked")
Function<O, String> funk =
(Function<O, String>)
((Function<String, String>)
input -> input != null ? input.toLowerCase(Locale.ENGLISH) : null);
return transform(String.class, funk);
}
/**
* Ensure input matches {@code pattern}.
*
* <p>{@code null} values are passed through.
*
* @param pattern is used to validate the user input. It matches against the whole string, so
* you don't need to use the ^$ characters.
* @param errorMessage is a helpful error message, which should include an example. If this is
* not provided, a default error message will be shown that includes the regexp pattern.
* @throws IllegalStateException if current output type is not a {@link CharSequence}.
* @see #matches(Pattern)
*/
public Builder<I, O> matches(Pattern pattern, @Nullable String errorMessage) {
checkState(CharSequence.class.isAssignableFrom(typeOut));
return transform(
new MatchesFunction<>(checkNotNull(pattern), Optional.ofNullable(errorMessage)));
}
/** Alias for {@link #matches(Pattern, String) matches(pattern, null)} */
public Builder<I, O> matches(Pattern pattern) {
return matches(pattern, null);
}
/**
* Removes all characters not in {@code matcher}.
*
* <p>{@code null} values are passed through.
*
* @param matcher indicates which characters are to be retained
* @throws IllegalStateException if current output type is not a {@link CharSequence}
*/
public Builder<I, String> retains(CharMatcher matcher) {
checkState(CharSequence.class.isAssignableFrom(typeOut));
@SuppressWarnings("unchecked") // safe due to checkState call
Function<O, String> function =
(Function<O, String>) new RetainFunction(checkNotNull(matcher));
return transform(String.class, function);
}
/**
* Enforce value length/size/value is within {@code range}.
*
* <p>The following input value types are supported:
*
* <ul>
* <li>{@link CharSequence}: Length must be within {@code range}.
* <li>{@link Collection}: Size must be within {@code range}.
* <li>{@link Number}: Value must be within {@code range}.
* </ul>
*
* <p>{@code null} values are passed through. Please note that setting a lower bound on your
* range does not imply {@link #required()}, as range checking only applies to non-{@code null}
* values.
*
* @throws IllegalStateException if current output type is not one of the above types.
*/
public Builder<I, O> range(Range<Integer> range) {
checkState(CharSequence.class.isAssignableFrom(typeOut)
|| Collection.class.isAssignableFrom(typeOut)
|| Number.class.isAssignableFrom(typeOut));
return transform(new RangeFunction<>(checkNotNull(range)));
}
/**
* Enforce value be a member of {@code values}.
*
* <p>{@code null} values are passed through.
*
* @throws IllegalArgumentException if {@code values} is empty.
*/
public Builder<I, O> in(Set<O> values) {
checkArgument(!values.isEmpty());
return transform(new InFunction<>(values));
}
/**
* Performs arbitrary type transformation from {@code O} to {@code T}.
*
* <p>Your {@code transform} function is expected to pass-through {@code null} values as a
* no-op, since it's up to {@link #required()} to block them. You might also want to consider
* using a try block that rethrows exceptions as {@link FormFieldException}.
*
* <p>Here's an example of how you'd convert from String to Integer:
*
* <pre>
* FormField.named("foo", String.class)
* .transform(Integer.class, new Function&lt;String, Integer&gt;() {
* &#064;Nullable
* &#064;Override
* public Integer apply(&#064;Nullable String input) {
* try {
* return input != null ? Integer.parseInt(input) : null;
* } catch (IllegalArgumentException e) {
* throw new FormFieldException("Invalid number.", e);
* }
* }})
* .build();</pre>
*
* @see #transform(Function)
*/
public <T> Builder<I, T> transform(Class<T> newType, Function<O, T> transform) {
return new Builder<>(
name, typeIn, checkNotNull(newType), this.converter.andThen(checkNotNull(transform)));
}
/**
* Manipulates values without changing type.
*
* <p>Please see {@link #transform(Class, Function)} for information about the contract to
* which {@code transform} is expected to conform.
*/
public Builder<I, O> transform(Function<O, O> transform) {
this.converter = this.converter.andThen(checkNotNull(transform));
return this;
}
/**
* Uppercases value and converts to an enum field of {@code enumClass}.
*
* <p>{@code null} values are passed through.
*
* @throws IllegalArgumentException if {@code enumClass} is not an enum class.
* @throws IllegalStateException if current output type is not a String.
*/
public <C extends Enum<C>> Builder<I, C> asEnum(Class<C> enumClass) {
checkArgument(enumClass.isEnum());
checkState(String.class.isAssignableFrom(typeOut));
return transform(enumClass, new ToEnumFunction<>(enumClass));
}
/**
* Turns this form field into something that processes lists.
*
* <p>The current object definition will be applied to each item in the list. If a
* {@link FormFieldException} is thrown when processing an item, then its
* {@link FormFieldException#getFieldName() fieldName} will be rewritten to include the index,
* e.g. {@code name} becomes {@code name[0]}.
*
* <p>The outputted list will be an {@link ImmutableList}. This is not reflected in the generic
* typing for the sake of brevity.
*
* <p>A {@code null} value for list will be passed through. List items that convert to
* {@code null} will be discarded (since {@code ImmutableList} does not permit {@code null}
* values).
*/
public Builder<List<I>, List<O>> asList() {
@SuppressWarnings("unchecked") Class<List<I>> in = (Class<List<I>>) (Class<I>) List.class;
@SuppressWarnings("unchecked") Class<List<O>> out = (Class<List<O>>) (Class<O>) List.class;
return new Builder<>(name, in, out, new ToListFunction<>(build()));
}
/**
* Turns this form field into a split string list that applies itself to each item.
*
* <p>The behavior of this method is counter-intuitive. It behaves similar to {@link #asList()}
* in the sense that all transforms specified <i>before</i> this method will be applied to the
* individual resulting list items.
*
* <p>For example, to turn a comma-delimited string into an enum list:<pre> {@code
*
* private static final FormField<String, List<State>> STATES_FIELD =
* FormField.named("states")
* .uppercased()
* .asEnum(State.class)
* .asList(Splitter.on(',').omitEmptyStrings().trimResults())
* .build();}</pre>
*
* <p>You'll notice that the transforms specified before this method are applied to each list
* item. However unlike {@link #asList()}, if an error is thrown on an individual item, then
* {@link FormFieldException#getFieldName()} will <i>not</i> contain the index.
*
* @throws IllegalStateException If either the current input type isn't String.
*/
public Builder<String, List<O>> asList(Splitter splitter) {
checkNotNull(splitter);
checkState(String.class.isAssignableFrom(typeIn));
@SuppressWarnings("unchecked") Class<List<O>> out = (Class<List<O>>) (Class<O>) List.class;
@SuppressWarnings("unchecked") FormField<String, O> inField = (FormField<String, O>) build();
return new Builder<>(name, String.class, out, new SplitToListFunction<>(inField, splitter));
}
/**
* Same as {@link #asList()} but outputs an {@link ImmutableSet} instead.
*
* @throws IllegalStateException if you called asList() before calling this method.
*/
public Builder<List<I>, Set<O>> asSet() {
checkState(!List.class.isAssignableFrom(typeOut));
@SuppressWarnings("unchecked")
Class<Set<O>> setOut = (Class<Set<O>>) (Class<O>) Set.class;
@SuppressWarnings("unchecked")
Function<List<O>, Set<O>> toSetFunction =
(Function<List<O>, Set<O>>)
(Function<O, O>)
((Function<List<Object>, Set<Object>>)
input -> input != null ? ImmutableSet.copyOf(input) : null);
return asList().transform(setOut, toSetFunction);
}
/**
* Same as {@link #asList(Splitter)} but outputs an {@link ImmutableSet} instead.
*
* @throws IllegalStateException If the current input type isn't String.
*/
public Builder<String, Set<O>> asSet(Splitter splitter) {
checkNotNull(splitter);
checkState(String.class.isAssignableFrom(typeIn));
@SuppressWarnings("unchecked") Class<Set<O>> out = (Class<Set<O>>) (Class<O>) Set.class;
@SuppressWarnings("unchecked") FormField<String, O> inField = (FormField<String, O>) build();
return new Builder<>(name, String.class, out, new SplitToSetFunction<>(inField, splitter));
}
/** Creates a new {@link FormField} instance. */
public FormField<I, O> build() {
return new FormField<>(name, typeIn, typeOut, converter);
}
private static <O> O checkNotNullTransform(@Nullable O input) {
if (input == null) {
throw new FormFieldException("This field is required.");
}
return input;
}
private static final class DefaultFunction<O> implements Function<O, O> {
private final O defaultValue;
DefaultFunction(O defaultValue) {
this.defaultValue = defaultValue;
}
@Nullable
@Override
public O apply(@Nullable O input) {
return input != null ? input : defaultValue;
}
}
private static final class RangeFunction<O> implements Function<O, O> {
private final Range<Integer> range;
RangeFunction(Range<Integer> range) {
this.range = range;
}
@Nullable
@Override
public O apply(@Nullable O input) {
if (input == null) {
return null;
}
if (input instanceof CharSequence) {
checkRangeContains(range, ((CharSequence) input).length(), "Number of characters");
} else if (input instanceof Collection) {
checkRangeContains(range, ((Collection<?>) input).size(), "Number of items");
} else if (input instanceof Number) {
checkRangeContains(range, ((Number) input).intValue(), "Value");
} else {
throw new AssertionError();
}
return input;
}
private void checkRangeContains(Range<Integer> range, int value, String message) {
if (!range.contains(value)) {
throw new FormFieldException(
String.format("%s (%,d) not in range %s", message, value, range));
}
}
}
private static final class InFunction<O> implements Function<O, O> {
private final Set<O> values;
InFunction(Set<O> values) {
this.values = values;
}
@Nullable
@Override
public O apply(@Nullable O input) {
if (input == null) {
return null;
}
if (!values.contains(input)) {
throw new FormFieldException("Unrecognized value.");
}
return input;
}
}
private static final class MatchesFunction<O> implements Function<O, O> {
private final Pattern pattern;
private final Optional<String> errorMessage;
MatchesFunction(Pattern pattern, Optional<String> errorMessage) {
this.pattern = pattern;
this.errorMessage = errorMessage;
}
@Nullable
@Override
public O apply(@Nullable O input) {
if (input == null) {
return null;
}
if (!pattern.matcher((CharSequence) input).matches()) {
throw new FormFieldException(errorMessage.orElse("Must match pattern: " + pattern));
}
return input;
}
}
private static final class RetainFunction implements Function<CharSequence, String> {
private final CharMatcher matcher;
RetainFunction(CharMatcher matcher) {
this.matcher = matcher;
}
@Nullable
@Override
public String apply(@Nullable CharSequence input) {
if (input == null) {
return null;
}
return matcher.retainFrom(input);
}
}
private static final class ToEnumFunction<O, C extends Enum<C>> implements Function<O, C> {
private final Class<C> enumClass;
ToEnumFunction(Class<C> enumClass) {
this.enumClass = enumClass;
}
@Nullable
@Override
public C apply(@Nullable O input) {
try {
return input != null ? Enum.valueOf(enumClass, Ascii.toUpperCase((String) input)) : null;
} catch (IllegalArgumentException e) {
throw new FormFieldException(
String.format("Enum %s does not contain '%s'", enumClass.getSimpleName(), input));
}
}
}
private static final class ToListFunction<I, O> implements Function<List<I>, List<O>> {
private final FormField<I, O> itemField;
ToListFunction(FormField<I, O> itemField) {
this.itemField = itemField;
}
@Nullable
@Override
public List<O> apply(@Nullable List<I> input) {
if (input == null) {
return null;
}
ImmutableList.Builder<O> builder = new ImmutableList.Builder<>();
for (int i = 0; i < input.size(); i++) {
I inputItem = itemField.typeIn.cast(input.get(i));
O outputItem;
try {
outputItem = itemField.converter.apply(inputItem);
} catch (FormFieldException e) {
throw e.propagate(i);
}
if (outputItem != null) {
builder.add(outputItem);
}
}
return builder.build();
}
}
private static final class SplitToListFunction<O> implements Function<String, List<O>> {
private final FormField<String, O> itemField;
private final Splitter splitter;
SplitToListFunction(FormField<String, O> itemField, Splitter splitter) {
this.itemField = itemField;
this.splitter = splitter;
}
@Nullable
@Override
public List<O> apply(@Nullable String input) {
return input == null
? null
: Streams.stream(splitter.split(input))
.map(itemField.converter)
.collect(toImmutableList());
}
}
private static final class SplitToSetFunction<O> implements Function<String, Set<O>> {
private final FormField<String, O> itemField;
private final Splitter splitter;
SplitToSetFunction(FormField<String, O> itemField, Splitter splitter) {
this.itemField = itemField;
this.splitter = splitter;
}
@Nullable
@Override
public Set<O> apply(@Nullable String input) {
return input == null
? null
: Streams.stream(splitter.split(input))
.map(itemField.converter)
.collect(toImmutableSet());
}
}
}
}
@@ -1,178 +0,0 @@
// Copyright 2017 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.
package google.registry.ui.forms;
import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import javax.annotation.CheckReturnValue;
import javax.annotation.Detainted;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
/**
* Exception thrown when a form field contains a bad value.
*
* <p>You can safely throw {@code FormFieldException} from within your validator functions, and the
* field name will automatically be propagated into the exception object for you.
*
* <p>The way that field names work is a bit complicated, because we need to support complex nested
* field names like {@code foo[3].bar}. So what happens is the original exception will be thrown by
* a {@link FormField} validator without the field set. Then as the exception bubbles up the stack,
* it'll be caught by the {@link FormField#convert(Object) convert} method, which then prepends the
* name of that component. Then when the exception reaches the user, the {@link #getFieldName()}
* method will produce the fully-qualified field name.
*
* <p>This propagation mechanism is also very important when writing {@link
* FormField.Builder#transform} functions, which oftentimes will not know the name of the field
* they're validating.
*/
@NotThreadSafe
@SuppressWarnings("OverrideThrowableToString")
public final class FormFieldException extends FormException {
private final List<Object> names = new ArrayList<>();
@Nullable
private String lazyFieldName;
/**
* Creates a new {@link FormFieldException}
*
* <p>This exception should only be thrown from within a {@link FormField} converter function.
* The field name will automatically be propagated into the exception object for you.
*
* @param userMessage should be a friendly message that's safe to show to the user.
*/
public FormFieldException(@Detainted String userMessage) {
super(checkNotNull(userMessage, "userMessage"), null);
}
/**
* Creates a new {@link FormFieldException}
*
* <p>This exception should only be thrown from within a {@link FormField} converter function.
* The field name will automatically be propagated into the exception object for you.
*
* @param userMessage should be a friendly message that's safe to show to the user.
* @param cause the original cause of this exception (non-null).
*/
public FormFieldException(@Detainted String userMessage, Throwable cause) {
super(checkNotNull(userMessage, "userMessage"), checkNotNull(cause, "cause"));
}
/**
* Creates a new {@link FormFieldException} for a particular form field.
*
* <p>This exception should only be thrown from within a {@link FormField} MAP converter function
* in situations where you're performing additional manual validation.
*
* @param userMessage should be a friendly message that's safe to show to the user.
*/
public FormFieldException(FormField<?, ?> field, @Detainted String userMessage) {
this(field.name(), userMessage);
}
/**
* Creates a new {@link FormFieldException} for a particular field name.
*
* @param field name corresponding to a {@link FormField#name()}
* @param userMessage friendly message that's safe to show to the user
*/
public FormFieldException(String field, @Detainted String userMessage) {
super(checkNotNull(userMessage, "userMessage"), null);
propagateImpl(field);
}
/** Returns the fully-qualified name (JavaScript syntax) of the form field causing this error. */
public String getFieldName() {
String fieldName = lazyFieldName;
if (fieldName == null) {
lazyFieldName = fieldName = getFieldNameImpl();
}
return fieldName;
}
private String getFieldNameImpl() {
checkState(!names.isEmpty(),
"FormFieldException was thrown outside FormField infrastructure!");
Iterator<Object> namesIterator = Lists.reverse(names).iterator();
StringBuilder result = new StringBuilder((String) namesIterator.next());
while (namesIterator.hasNext()) {
Object name = namesIterator.next();
if (name instanceof String) {
result.append('.').append(name);
} else if (name instanceof Integer) {
result.append('[').append(name).append(']');
} else {
throw new AssertionError();
}
}
return result.toString();
}
/**
* Returns self with {@code name} prepended, for propagating exceptions up the stack.
*
* <p>This would be package-private except that it needs to be called by a test class in another
* package.
*/
@CheckReturnValue
public FormFieldException propagate(String name) {
return propagateImpl(name);
}
/** Returns self with {@code index} prepended, for propagating exceptions up the stack. */
@CheckReturnValue
FormFieldException propagate(int index) {
return propagateImpl(index);
}
/** Returns self with {@code name} prepended, for propagating exceptions up the stack. */
private FormFieldException propagateImpl(Object name) {
lazyFieldName = null;
names.add(checkNotNull(name));
return this;
}
@Override
public boolean equals(@Nullable Object obj) {
return this == obj
|| (obj instanceof FormFieldException
&& Objects.equals(getCause(), ((FormFieldException) obj).getCause())
&& Objects.equals(getMessage(), ((FormFieldException) obj).getMessage())
&& Objects.equals(names, ((FormFieldException) obj).names));
}
@Override
public int hashCode() {
return Objects.hash(getCause(), getMessage(), getFieldName());
}
@Override
public String toString() {
return toStringHelper(getClass())
.add("fieldName", getFieldName())
.add("message", getMessage())
.add("cause", getCause())
.toString();
}
}
@@ -1,116 +0,0 @@
// Copyright 2017 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.
package google.registry.ui.forms;
import static com.google.common.collect.Range.atMost;
import static com.google.common.collect.Range.closed;
import static com.google.common.collect.Range.singleton;
import static java.util.Locale.getISOCountries;
import com.google.common.collect.ImmutableSet;
import com.google.re2j.Pattern;
/** Utility class of {@link FormField} objects for validating EPP related things. */
public final class FormFields {
private static final Pattern WHITESPACE = Pattern.compile("[ \\t\\r\\n]+");
/**
* Form field that applies XML Schema Token cleanup to input.
*
* <p>This trims the input and collapses whitespace.
*
* @see <a href="http://www.w3.org/TR/xmlschema11-2/#token">XSD Datatypes - token</a>
*/
public static final FormField<String, String> XS_TOKEN =
FormField.named("xsToken")
.emptyToNull()
.trimmed()
.transform(input -> input != null ? WHITESPACE.matcher(input).replaceAll(" ") : null)
.build();
/**
* Form field that ensures input does not contain tabs, line feeds, or carriage returns.
*
* @see <a href="http://www.w3.org/TR/xmlschema11-2/#normalizedString">
* XSD Datatypes - normalizedString</a>
*/
public static final FormField<String, String> XS_NORMALIZED_STRING =
FormField.named("xsNormalizedString")
.emptyToNull()
.matches(Pattern.compile("[^\\t\\r\\n]*"), "Must not contain tabs or multiple lines.")
.build();
/**
* Form field for +E164 phone numbers with a dot after the country prefix.
*
* @see <a href="http://tools.ietf.org/html/rfc5733#section-4">RFC 5733 - EPP - Formal Syntax</a>
*/
public static final FormField<String, String> PHONE_NUMBER =
XS_TOKEN.asBuilderNamed("phoneNumber")
.range(atMost(17))
.matches(Pattern.compile("(\\+[0-9]{1,3}\\.[0-9]{1,14})?"),
"Must be a valid +E.164 phone number, e.g. +1.2125650000")
.build();
/** Form field for EPP client identifiers. */
public static final FormField<String, String> CLID = XS_TOKEN.asBuilderNamed("clid")
.range(closed(3, 16))
.build();
/** Form field for passwords (see pwType in epp.xsd). */
public static final FormField<String, String> PASSWORD = XS_TOKEN.asBuilderNamed("password")
.range(closed(6, 16))
.build();
/** Form field for non-empty tokens (see minToken in eppcom.xsd). */
public static final FormField<String, String> MIN_TOKEN = XS_TOKEN.asBuilderNamed("minToken")
.emptyToNull()
.build();
/** Form field for nameType (see rde-registrar/notification). */
public static final FormField<String, String> NAME = XS_NORMALIZED_STRING.asBuilderNamed("name")
.range(closed(1, 255))
.build();
/** Form field for {@code labelType} from {@code eppcom.xsd}. */
public static final FormField<String, String> LABEL = XS_TOKEN.asBuilderNamed("label")
.range(closed(1, 255))
.build();
/** Email address form field. */
public static final FormField<String, String> EMAIL = XS_TOKEN.asBuilderNamed("email")
.matches(Pattern.compile("[^@]+@[^@.]+\\.[^@]+"), "Please enter a valid email address.")
.build();
/** Two-letter ISO country code form field. */
public static final FormField<String, String> COUNTRY_CODE =
XS_TOKEN.asBuilderNamed("countryCode")
.range(singleton(2))
.uppercased()
.in(ImmutableSet.copyOf(getISOCountries()))
.build();
/**
* Ensure value is an EPP Repository Object IDentifier (ROID).
*
* @see <a href="http://tools.ietf.org/html/rfc5730#section-4.2">Shared Structure Schema</a>
*/
public static final FormField<String, String> ROID = XS_TOKEN.asBuilderNamed("roid")
.matches(Pattern.compile("(\\w|_){1,80}-\\w{1,8}"),
"Please enter a valid EPP ROID, e.g. SH8013-REP")
.build();
private FormFields() {}
}
@@ -1,17 +0,0 @@
// Copyright 2017 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.
/** Web application backend form processing utilities. */
@javax.annotation.ParametersAreNonnullByDefault
package google.registry.ui.forms;
@@ -1,15 +0,0 @@
<!doctype html>
<meta charset=utf-8>
<title>Server Error</title>
<style>
*{margin:0;padding:0}
html,code{font:15px/22px arial,sans-serif}
html{background:#fff;color:#222;padding:15px}
body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}
* > body{background:url(/manager/img/ui/robot.png) 100% 5px no-repeat;padding-right:205px}
p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}
a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}
</style>
<a href="/"><img src="/assets/images/logo_sm.gif" alt=Google></a>
<p><b>500.</b> <ins>That's an error.</ins>
<p>Sorry, but the server encountered an error while processing your request.
@@ -1,6 +0,0 @@
<!doctype html>
<meta http-equiv="refresh" content="0;URL=/console">
<title>Nomulus</title>
<body lang="en-US">
If this page doesn't change automatically, please go
to <a href="/console">https://www.registry.google/console</a>
@@ -1,16 +0,0 @@
// Copyright 2017 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.
@javax.annotation.ParametersAreNonnullByDefault
package google.registry.ui;
@@ -1,82 +0,0 @@
// Copyright 2017 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.
package google.registry.ui.server;
import com.google.common.collect.ImmutableBiMap;
/**
* Bimap of state codes and names for the US Regime.
*
* @see <a href="http://statetable.com/">State Table</a>
*/
public final class StateCode {
public static final ImmutableBiMap<String, String> US_MAP =
new ImmutableBiMap.Builder<String, String>()
.put("AL", "Alabama")
.put("AK", "Alaska")
.put("AZ", "Arizona")
.put("AR", "Arkansas")
.put("CA", "California")
.put("CO", "Colorado")
.put("CT", "Connecticut")
.put("DE", "Delaware")
.put("FL", "Florida")
.put("GA", "Georgia")
.put("HI", "Hawaii")
.put("ID", "Idaho")
.put("IL", "Illinois")
.put("IN", "Indiana")
.put("IA", "Iowa")
.put("KS", "Kansas")
.put("KY", "Kentucky")
.put("LA", "Louisiana")
.put("ME", "Maine")
.put("MD", "Maryland")
.put("MA", "Massachusetts")
.put("MI", "Michigan")
.put("MN", "Minnesota")
.put("MS", "Mississippi")
.put("MO", "Missouri")
.put("MT", "Montana")
.put("NE", "Nebraska")
.put("NV", "Nevada")
.put("NH", "New Hampshire")
.put("NJ", "New Jersey")
.put("NM", "New Mexico")
.put("NY", "New York")
.put("NC", "North Carolina")
.put("ND", "North Dakota")
.put("OH", "Ohio")
.put("OK", "Oklahoma")
.put("OR", "Oregon")
.put("PA", "Pennsylvania")
.put("RI", "Rhode Island")
.put("SC", "South Carolina")
.put("SD", "South Dakota")
.put("TN", "Tennessee")
.put("TX", "Texas")
.put("UT", "Utah")
.put("VT", "Vermont")
.put("VA", "Virginia")
.put("WA", "Washington")
.put("WV", "West Virginia")
.put("WI", "Wisconsin")
.put("WY", "Wyoming")
.put("DC", "Washington DC")
.build();
private StateCode() {}
}
@@ -79,17 +79,17 @@ public class ConsoleUpdateRegistrarAction extends ConsoleApiAction {
registrarParam.getRegistrarId());
// Only allow modifying allowed TLDs if we're in a non-PRODUCTION environment, if the
// registrar is not REAL, or the registrar has a WHOIS abuse contact set.
// registrar is not REAL, or the registrar has a RDAP abuse contact set.
if (!registrarParam.getAllowedTlds().isEmpty()) {
boolean isRealRegistrar =
Registrar.Type.REAL.equals(existingRegistrar.get().getType());
if (RegistryEnvironment.PRODUCTION.equals(RegistryEnvironment.get())
&& isRealRegistrar) {
checkArgumentPresent(
existingRegistrar.get().getWhoisAbuseContact(),
"Cannot modify allowed TLDs if there is no WHOIS abuse contact set. Please"
existingRegistrar.get().getRdapAbuseContact(),
"Cannot modify allowed TLDs if there is no RDAP abuse contact set. Please"
+ " use the \"nomulus registrar_contact\" command on this registrar to"
+ " set a WHOIS abuse contact.");
+ " set a RDAP abuse contact.");
}
}
@@ -40,7 +40,6 @@ import google.registry.request.Action.GaeService;
import google.registry.request.Action.GkeService;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.ui.forms.FormException;
import google.registry.ui.server.console.ConsoleApiAction;
import google.registry.ui.server.console.ConsoleApiParams;
import jakarta.inject.Inject;
@@ -106,9 +105,9 @@ public class ContactAction extends ConsoleApiAction {
new RegistrarPoc()
.asBuilder()
.setTypes(newContact.getTypes())
.setVisibleInWhoisAsTech(newContact.getVisibleInWhoisAsTech())
.setVisibleInWhoisAsAdmin(newContact.getVisibleInWhoisAsAdmin())
.setVisibleInDomainWhoisAsAbuse(newContact.getVisibleInDomainWhoisAsAbuse())
.setVisibleInRdapAsTech(newContact.getVisibleInRdapAsTech())
.setVisibleInRdapAsAdmin(newContact.getVisibleInRdapAsAdmin())
.setVisibleInDomainRdapAsAbuse(newContact.getVisibleInDomainRdapAsAbuse())
.setFaxNumber(newContact.getFaxNumber())
.setName(newContact.getName())
.setEmailAddress(newContact.getEmailAddress())
@@ -133,10 +132,10 @@ public class ContactAction extends ConsoleApiAction {
? oldContact
.asBuilder()
.setTypes(updatedContact.getTypes())
.setVisibleInWhoisAsTech(updatedContact.getVisibleInWhoisAsTech())
.setVisibleInWhoisAsAdmin(updatedContact.getVisibleInWhoisAsAdmin())
.setVisibleInDomainWhoisAsAbuse(
updatedContact.getVisibleInDomainWhoisAsAbuse())
.setVisibleInRdapAsTech(updatedContact.getVisibleInRdapAsTech())
.setVisibleInRdapAsAdmin(updatedContact.getVisibleInRdapAsAdmin())
.setVisibleInDomainRdapAsAbuse(
updatedContact.getVisibleInDomainRdapAsAbuse())
.setFaxNumber(updatedContact.getFaxNumber())
.setName(updatedContact.getName())
.setEmailAddress(updatedContact.getEmailAddress())
@@ -166,7 +165,7 @@ public class ContactAction extends ConsoleApiAction {
try {
checkContactRequirements(oldContacts, newContacts);
} catch (FormException e) {
} catch (ContactRequirementException e) {
logger.atWarning().withCause(e).log(
"Error processing contacts post request for registrar: %s", registrarId);
throw new IllegalArgumentException(e);
@@ -196,7 +195,7 @@ public class ContactAction extends ConsoleApiAction {
/**
* Enforces business logic checks on registrar contacts.
*
* @throws FormException if the checks fail.
* @throws ContactRequirementException if the checks fail.
*/
private static void checkContactRequirements(
ImmutableSet<RegistrarPoc> existingContacts, ImmutableSet<RegistrarPoc> updatedContacts) {
@@ -231,19 +230,19 @@ public class ContactAction extends ConsoleApiAction {
enforcePrimaryContactRestrictions(oldContactsByType, newContactsByType);
ensurePhoneNumberNotRemovedForContactTypes(oldContactsByType, newContactsByType, Type.TECH);
Optional<RegistrarPoc> domainWhoisAbuseContact =
getDomainWhoisVisibleAbuseContact(updatedContacts);
// If the new set has a domain WHOIS abuse contact, it must have a phone number.
if (domainWhoisAbuseContact.isPresent()
&& domainWhoisAbuseContact.get().getPhoneNumber() == null) {
Optional<RegistrarPoc> domainRdapAbuseContact =
getDomainRdapVisibleAbuseContact(updatedContacts);
// If the new set has a domain RDAP abuse contact, it must have a phone number.
if (domainRdapAbuseContact.isPresent()
&& domainRdapAbuseContact.get().getPhoneNumber() == null) {
throw new ContactRequirementException(
"The abuse contact visible in domain WHOIS query must have a phone number");
"The abuse contact visible in domain RDAP query must have a phone number");
}
// If there was a domain WHOIS abuse contact in the old set, the new set must have one.
if (getDomainWhoisVisibleAbuseContact(existingContacts).isPresent()
&& domainWhoisAbuseContact.isEmpty()) {
// If there was a domain RDAP abuse contact in the old set, the new set must have one.
if (getDomainRdapVisibleAbuseContact(existingContacts).isPresent()
&& domainRdapAbuseContact.isEmpty()) {
throw new ContactRequirementException(
"An abuse contact visible in domain WHOIS query must be designated");
"An abuse contact visible in domain RDAP query must be designated");
}
}
@@ -265,17 +264,17 @@ public class ContactAction extends ConsoleApiAction {
}
/**
* Retrieves the registrar contact whose phone number and email address is visible in domain WHOIS
* Retrieves the registrar contact whose phone number and email address is visible in domain RDAP
* query as abuse contact (if any).
*
* <p>Frontend processing ensures that only one contact can be set as abuse contact in domain
* WHOIS record.
* <p>Frontend processing ensures that only one contact can be set as abuse contact in domain RDAP
* record.
*
* <p>Therefore, it is possible to return inside the loop once one such contact is found.
*/
private static Optional<RegistrarPoc> getDomainWhoisVisibleAbuseContact(
private static Optional<RegistrarPoc> getDomainRdapVisibleAbuseContact(
Set<RegistrarPoc> contacts) {
return contacts.stream().filter(RegistrarPoc::getVisibleInDomainWhoisAsAbuse).findFirst();
return contacts.stream().filter(RegistrarPoc::getVisibleInDomainRdapAsAbuse).findFirst();
}
/**
@@ -299,7 +298,7 @@ public class ContactAction extends ConsoleApiAction {
}
/** Thrown when a set of contacts doesn't meet certain constraints. */
private static class ContactRequirementException extends FormException {
private static class ContactRequirementException extends RuntimeException {
ContactRequirementException(String msg) {
super(msg);
}
@@ -1,16 +0,0 @@
// Copyright 2017 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.
@javax.annotation.ParametersAreNonnullByDefault
package google.registry.ui.server;
@@ -221,8 +221,8 @@ class SendExpiringCertificateNotificationEmailActionTest {
.setPhoneNumber("+1.3105551213")
.setFaxNumber("+1.3105551213")
.setTypes(ImmutableSet.of(RegistrarPoc.Type.TECH))
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
.setVisibleInRdapAsAdmin(true)
.setVisibleInRdapAsTech(false)
.build());
persistResources(contacts);
RuntimeException thrown =
@@ -511,8 +511,8 @@ class SendExpiringCertificateNotificationEmailActionTest {
.setPhoneNumber("+1.3105551213")
.setFaxNumber("+1.3105551213")
.setTypes(ImmutableSet.of(RegistrarPoc.Type.TECH))
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
.setVisibleInRdapAsAdmin(true)
.setVisibleInRdapAsTech(false)
.build(),
new RegistrarPoc.Builder()
.setRegistrar(registrar)
@@ -529,8 +529,8 @@ class SendExpiringCertificateNotificationEmailActionTest {
.setPhoneNumber("+1.3105551213")
.setFaxNumber("+1.3105551213")
.setTypes(ImmutableSet.of(RegistrarPoc.Type.TECH))
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
.setVisibleInRdapAsAdmin(true)
.setVisibleInRdapAsTech(false)
.build(),
new RegistrarPoc.Builder()
.setRegistrar(registrar)
@@ -547,7 +547,7 @@ class SendExpiringCertificateNotificationEmailActionTest {
.setPhoneNumber("+1.3105551215")
.setFaxNumber("+1.3105551216")
.setTypes(ImmutableSet.of(RegistrarPoc.Type.ADMIN))
.setVisibleInWhoisAsTech(true)
.setVisibleInRdapAsTech(true)
.build());
persistResources(contacts);
assertThat(action.getEmailAddresses(registrar, Type.TECH))
@@ -168,8 +168,8 @@ public class SyncRegistrarsSheetTest {
.setTypes(ImmutableSet.of(RegistrarPoc.Type.ADMIN))
// Purposely flip the internal/external admin/tech
// distinction to make sure we're not relying on it. Sigh.
.setVisibleInWhoisAsAdmin(false)
.setVisibleInWhoisAsTech(true)
.setVisibleInRdapAsAdmin(false)
.setVisibleInRdapAsTech(true)
.build(),
new RegistrarPoc.Builder()
.setRegistrar(registrar)
@@ -201,9 +201,9 @@ public class SyncRegistrarsSheetTest {
contact@example.com
Tel: +1.1234567890
Types: [ADMIN, BILLING]
Visible in registrar WHOIS query as Admin contact: No
Visible in registrar WHOIS query as Technical contact: No
Phone number and email visible in domain WHOIS query as Registrar Abuse contact\
Visible in registrar RDAP query as Admin contact: No
Visible in registrar RDAP query as Technical contact: No
Phone number and email visible in domain RDAP query as Registrar Abuse contact\
info: No
John Doe
@@ -211,9 +211,9 @@ public class SyncRegistrarsSheetTest {
Tel: +1.1234567890
Fax: +1.1234567891
Types: [ADMIN]
Visible in registrar WHOIS query as Admin contact: No
Visible in registrar WHOIS query as Technical contact: Yes
Phone number and email visible in domain WHOIS query as Registrar Abuse contact\
Visible in registrar RDAP query as Admin contact: No
Visible in registrar RDAP query as Technical contact: Yes
Phone number and email visible in domain RDAP query as Registrar Abuse contact\
info: No
""");
assertThat(row)
@@ -223,9 +223,9 @@ public class SyncRegistrarsSheetTest {
Jane Smith
pride@example.net
Types: [TECH]
Visible in registrar WHOIS query as Admin contact: No
Visible in registrar WHOIS query as Technical contact: No
Phone number and email visible in domain WHOIS query as Registrar Abuse contact\
Visible in registrar RDAP query as Admin contact: No
Visible in registrar RDAP query as Technical contact: No
Phone number and email visible in domain RDAP query as Registrar Abuse contact\
info: No
""");
assertThat(row).containsEntry("marketingContacts", "");
@@ -240,9 +240,9 @@ public class SyncRegistrarsSheetTest {
contact@example.com
Tel: +1.1234567890
Types: [ADMIN, BILLING]
Visible in registrar WHOIS query as Admin contact: No
Visible in registrar WHOIS query as Technical contact: No
Phone number and email visible in domain WHOIS query as Registrar Abuse contact\
Visible in registrar RDAP query as Admin contact: No
Visible in registrar RDAP query as Technical contact: No
Phone number and email visible in domain RDAP query as Registrar Abuse contact\
info: No
""");
assertThat(row).containsEntry("contactsMarkedAsWhoisAdmin", "");
@@ -255,9 +255,9 @@ public class SyncRegistrarsSheetTest {
Tel: +1.1234567890
Fax: +1.1234567891
Types: [ADMIN]
Visible in registrar WHOIS query as Admin contact: No
Visible in registrar WHOIS query as Technical contact: Yes
Phone number and email visible in domain WHOIS query as Registrar Abuse contact\
Visible in registrar RDAP query as Admin contact: No
Visible in registrar RDAP query as Technical contact: Yes
Phone number and email visible in domain RDAP query as Registrar Abuse contact\
info: No
""");
assertThat(row).containsEntry("emailAddress", "nowhere@example.org");
@@ -126,8 +126,8 @@ class RegistrarTest extends EntityTestCase {
.setRegistrar(registrar)
.setName("John Abused")
.setEmailAddress("johnabuse@example.com")
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
.setVisibleInRdapAsAdmin(true)
.setVisibleInRdapAsTech(false)
.setPhoneNumber("+1.2125551213")
.setFaxNumber("+1.2125551213")
.setTypes(ImmutableSet.of(RegistrarPoc.Type.ABUSE, RegistrarPoc.Type.ADMIN))
@@ -323,8 +323,8 @@ class RegistrarTest extends EntityTestCase {
.setRegistrar(registrar)
.setName("Jake Tech")
.setEmailAddress("jaketech@example.com")
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(true)
.setVisibleInRdapAsAdmin(true)
.setVisibleInRdapAsTech(true)
.setPhoneNumber("+1.2125551213")
.setFaxNumber("+1.2125551213")
.setTypes(ImmutableSet.of(RegistrarPoc.Type.TECH))
@@ -335,8 +335,8 @@ class RegistrarTest extends EntityTestCase {
.setRegistrar(registrar)
.setName("Jim Tech-Abuse")
.setEmailAddress("jimtechAbuse@example.com")
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(true)
.setVisibleInRdapAsAdmin(true)
.setVisibleInRdapAsTech(true)
.setPhoneNumber("+1.2125551213")
.setFaxNumber("+1.2125551213")
.setTypes(ImmutableSet.of(RegistrarPoc.Type.TECH, RegistrarPoc.Type.ABUSE))
@@ -18,29 +18,14 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import google.registry.module.backend.BackendRequestComponent;
import google.registry.module.bsa.BsaRequestComponent;
import google.registry.module.frontend.FrontendRequestComponent;
import google.registry.module.pubapi.PubApiRequestComponent;
import google.registry.module.tools.ToolsRequestComponent;
import google.registry.testing.GoldenFileTestHelper;
import google.registry.testing.TestDataHelper;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link RequestComponent}. */
public class RequestComponentTest {
private static final ImmutableMap<Class<?>, String> GaeComponents =
ImmutableMap.of(
FrontendRequestComponent.class, "frontend",
BackendRequestComponent.class, "backend",
ToolsRequestComponent.class, "tools",
PubApiRequestComponent.class, "pubapi",
BsaRequestComponent.class, "bsa");
@Test
void testRoutingMap() {
@@ -49,17 +34,6 @@ public class RequestComponentTest {
.isEqualToGolden(RequestComponentTest.class, "routing.txt");
}
@Test
@Disabled("To be removed with GAE components")
void testGaeToJettyRoutingCoverage() {
Set<Route> jettyRoutes = getRoutes(RequestComponent.class, "routing.txt");
Set<Route> gaeRoutes = new HashSet<>();
for (var component : GaeComponents.entrySet()) {
gaeRoutes.addAll(getRoutes(component.getKey(), component.getValue() + "_routing.txt"));
}
assertThat(jettyRoutes).isEqualTo(gaeRoutes);
}
private Set<Route> getRoutes(Class<?> context, String filename) {
return TestDataHelper.loadFile(context, filename)
.trim()
@@ -1,41 +0,0 @@
// Copyright 2023 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.
package google.registry.module.bsa;
import static com.google.common.truth.Truth.assertThat;
import google.registry.request.Action.GaeService;
import google.registry.request.RouterDisplayHelper;
import google.registry.testing.GoldenFileTestHelper;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link BsaRequestComponent}. */
public class BsaRequestComponentTest {
@Test
void testRoutingMap() {
GoldenFileTestHelper.assertThatRoutesFromComponent(BsaRequestComponent.class)
.describedAs("bsa routing map")
.isEqualToGolden(BsaRequestComponent.class, "bsa_routing.txt");
}
@Test
void testRoutingService() {
assertThat(
RouterDisplayHelper.extractHumanReadableRoutesWithWrongService(
BsaRequestComponent.class, GaeService.BSA))
.isEmpty();
}
}
@@ -1,36 +0,0 @@
// Copyright 2023 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.
package google.registry.module.bsa;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test;
class BsaServletTest {
private final HttpServletRequest req = mock(HttpServletRequest.class);
private final HttpServletResponse rsp = mock(HttpServletResponse.class);
@Test
void testService_unknownPath_returnsNotFound() throws Exception {
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/lol");
new BsaServlet().service(req, rsp);
verify(rsp).sendError(404);
}
}
@@ -30,7 +30,6 @@ import google.registry.monitoring.whitebox.StackdriverModule;
import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.request.Modules;
import google.registry.request.auth.AuthModule;
import google.registry.ui.ConsoleDebug;
import google.registry.util.UtilsModule;
import jakarta.inject.Singleton;
@@ -40,7 +39,6 @@ import jakarta.inject.Singleton;
AuthModule.class,
CloudTasksUtilsModule.class,
RegistryConfig.ConfigModule.class,
ConsoleDebug.ConsoleConfigModule.class,
CredentialModule.class,
CustomLogicFactoryModule.class,
CloudTasksUtilsModule.class,
@@ -1,41 +0,0 @@
// Copyright 2018 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.
package google.registry.module.pubapi;
import static com.google.common.truth.Truth.assertThat;
import google.registry.request.Action.GaeService;
import google.registry.request.RouterDisplayHelper;
import google.registry.testing.GoldenFileTestHelper;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link PubApiRequestComponent}. */
class PubApiRequestComponentTest {
@Test
void testRoutingMap() {
GoldenFileTestHelper.assertThatRoutesFromComponent(PubApiRequestComponent.class)
.describedAs("pubapi routing map")
.isEqualToGolden(PubApiRequestComponentTest.class, "pubapi_routing.txt");
}
@Test
void testRoutingService() {
assertThat(
RouterDisplayHelper.extractHumanReadableRoutesWithWrongService(
PubApiRequestComponent.class, GaeService.PUBAPI))
.isEmpty();
}
}
@@ -1,38 +0,0 @@
// Copyright 2018 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.
package google.registry.module.pubapi;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link PubApiServlet}. */
class PubApiServletTest {
private final HttpServletRequest req = mock(HttpServletRequest.class);
private final HttpServletResponse rsp = mock(HttpServletResponse.class);
@Test
void testService_unknownPath_returnNotFound() throws Exception {
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/lol");
new PubApiServlet().service(req, rsp);
verify(rsp).sendError(404);
}
}
@@ -1,41 +0,0 @@
// Copyright 2017 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.
package google.registry.module.tools;
import static com.google.common.truth.Truth.assertThat;
import google.registry.request.Action.GaeService;
import google.registry.request.RouterDisplayHelper;
import google.registry.testing.GoldenFileTestHelper;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link ToolsRequestComponent}. */
class ToolsRequestComponentTest {
@Test
void testRoutingMap() {
GoldenFileTestHelper.assertThatRoutesFromComponent(ToolsRequestComponent.class)
.describedAs("tools routing map")
.isEqualToGolden(ToolsRequestComponentTest.class, "tools_routing.txt");
}
@Test
void testRoutingService() {
assertThat(
RouterDisplayHelper.extractHumanReadableRoutesWithWrongService(
ToolsRequestComponent.class, GaeService.TOOLS))
.isEmpty();
}
}
@@ -1,38 +0,0 @@
// Copyright 2017 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.
package google.registry.module.tools;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link ToolsServlet}. */
class ToolsServletTest {
private final HttpServletRequest req = mock(HttpServletRequest.class);
private final HttpServletResponse rsp = mock(HttpServletResponse.class);
@Test
void testService_unknownPath_returnsNotFound() throws Exception {
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/lol");
new ToolsServlet().service(req, rsp);
verify(rsp).sendError(404);
}
}
@@ -407,8 +407,8 @@ public abstract class JpaTransactionManagerExtension
return new RegistrarPoc.Builder()
.setRegistrar(makeRegistrar1())
.setName("Jane Doe")
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
.setVisibleInRdapAsAdmin(true)
.setVisibleInRdapAsTech(false)
.setEmailAddress("janedoe@theregistrar.com")
.setPhoneNumber("+1.1234567890")
.setTypes(ImmutableSet.of(RegistrarPoc.Type.ADMIN))
@@ -198,8 +198,8 @@ class RdapJsonFormatterTest {
.setPhoneNumber("+1.2125551217")
.setFaxNumber("+1.2125551218")
.setTypes(ImmutableSet.of(RegistrarPoc.Type.ADMIN))
.setVisibleInWhoisAsAdmin(false)
.setVisibleInWhoisAsTech(false)
.setVisibleInRdapAsAdmin(false)
.setVisibleInRdapAsTech(false)
.build(),
new RegistrarPoc.Builder()
.setRegistrar(registrar)
@@ -207,8 +207,8 @@ class RdapJsonFormatterTest {
.setEmailAddress("johndoe@example.com")
.setFaxNumber("+1.2125551213")
.setTypes(ImmutableSet.of(RegistrarPoc.Type.ADMIN))
.setVisibleInWhoisAsAdmin(false)
.setVisibleInWhoisAsTech(true)
.setVisibleInRdapAsAdmin(false)
.setVisibleInRdapAsTech(true)
.build(),
new RegistrarPoc.Builder()
.setRegistrar(registrar)
@@ -216,8 +216,8 @@ class RdapJsonFormatterTest {
.setEmailAddress("janedoe@example.com")
.setPhoneNumber("+1.2125551215")
.setTypes(ImmutableSet.of(RegistrarPoc.Type.TECH, RegistrarPoc.Type.ADMIN))
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
.setVisibleInRdapAsAdmin(true)
.setVisibleInRdapAsTech(false)
.build(),
new RegistrarPoc.Builder()
.setRegistrar(registrar)
@@ -226,8 +226,8 @@ class RdapJsonFormatterTest {
.setPhoneNumber("+1.2125551217")
.setFaxNumber("+1.2125551218")
.setTypes(ImmutableSet.of(RegistrarPoc.Type.BILLING))
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(true)
.setVisibleInRdapAsAdmin(true)
.setVisibleInRdapAsTech(true)
.build());
}

Some files were not shown because too many files have changed in this diff Show More