1
0
mirror of https://github.com/google/nomulus synced 2026-05-23 16:21:55 +00:00

Compare commits

...

39 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
Ben McIlwain
847ef12a4f Remove Tld.allowedRegistrantContactIds field (#2867)
We no longer need this now that no contacts can be applied to any domains at all.

A follow-up PR in subsequent weeks will delete the column from the DB schema.

BUG= http://b/448619572
2025-10-31 15:52:10 +00:00
gbrodman
d9349be18e Modify the way we load resources via foreign keys (#2852)
Previously, we would have separate database calls for mapping from
foreign key to repo ID and then from repo ID to object. This PR modifies
those calls to load the resource directly (the old system was an
artifact of the Datastore key-value storage system).

In this PR, we merge the load-resource-by-foreign-key calls into a
single database load, as well as adding a separate cache object for
(foreign key) -> (resource). Now we cache, and have separate cleaner
code paths, for fk -> resource, fk -> repo ID, and repo ID -> resource.

Also removes the unused RdeFragmenter class
2025-10-29 19:21:27 +00:00
gbrodman
0c74883428 Remove WHOIS references from activity reporting (#2865)
This also changes a screenshot test filename since there wasn't a great
other place to put that change
2025-10-29 16:42:16 +00:00
Weimin Yu
b357fc79f7 Support Fee Extension standard in rfc 8748 (#2855)
* Support Fee Extension standard in rfc 8748

Adding support to the final version of RFC 8748.

Compared with draft-0.12, the only meaningful change is in the namespace.
The rest is either schema-tightening that reflects actual usage, or
optional server-side features that we do not support.

We reuse draft-0.12 tests, only changing namespace uris in the input and
output files for the new version.

* Addressing reviews
2025-10-28 20:54:02 +00:00
gbrodman
754e7fbddc Remove old console soy/js and related files (#2861)
We haven't been serving this for a while, let's finally get rid of them.

We keep some Soy rules around in the presubmits file because we use some
Soy files as XML templates for EPP actions.
2025-10-28 20:34:34 +00:00
Ben McIlwain
ad07b32638 Refactor EppResourceUtils.loadByForeignKey(...) -> ForeignKeyUtils.loadResource(...) (#2864)
This doesn't make any underlying implementation details, and is mainly useful to
reduce the number of diffs in PR #2852 (which does change implementation
details) thus making that easier to review.
2025-10-28 19:47:37 +00:00
Ben McIlwain
8f69b48e87 Add a @GetterDelegate annotation for better handling of ImmutableObject fields (#2860)
This allows us to specify a getter delegation to bypass Hibernate's limitations
on field types for the purposes of, e.g., using a sorted set in toString()
output rather than the base Hibernate unsorted HashSet type.

BUG=http://b/448631639
2025-10-28 17:10:27 +00:00
Ben McIlwain
c33f0dc07f Remove all foreign key constraints pointing to contact tables (#2857)
This affects FKs pointing to both Contact and ContactHistory. This is in
preparation to us deleting all rows in those two tables, and then subsequently
removing all application logic having to do with contacts entirely.
2025-10-27 20:34:50 +00:00
Juan Celhay
969353d4e2 Fix documentation for renew probers cb file (#2862) 2025-10-27 20:24:07 +00:00
gbrodman
6cd351ec7c Remove WHOIS classes and configuration (#2859)
This is steps one and two of b/454947209

We already haven't been serving WHOIS for a while, so there's no point
in keeping the old code around. This can simplify some code paths in the
future (like, certain foreign-key-loads that are only used in WHOIS
queries).
2025-10-27 18:57:25 +00:00
Pavlo Tkach
19e03dbd2e Update proxy and nomulus cluster resources (#2858) 2025-10-24 20:19:41 +00:00
Pavlo Tkach
fc1eb162f2 Remove Primary Contact from users editing screen (#2856) 2025-10-24 20:12:18 +00:00
gbrodman
ed25854fbc Add unique index for not-deleted domain names (#2853)
This is a backstop against multiple domain creations for the same domain
name getting through
2025-10-24 15:38:23 +00:00
Juan Celhay
0aa6bc6aaa Change regex format in release cb file (#2854) 2025-10-23 19:49:29 +00:00
Juan Celhay
ff4c326ebe Delete step to push to release repo, trigger next release steps based on tag format (#2833)
* Change release cb file

* Add brackets around tag variable

* Redo tag matching

* Have tag matcher like the one in cb dev
2025-10-21 18:52:42 +00:00
Pavlo Tkach
51b579871a Anonymize support users in console history, add minor UI updates (#2851) 2025-10-17 18:57:40 +00:00
gbrodman
b144aafb22 Use transaction time for deletion time cache ticker (#2848)
Basically, what happened is that the cache's expireAfterWrite was being
called some number of milliseconds (say, 50-100) after the transaction
was started. That method used the transaction time instead of the
current time, so as a result the entries were sticking around 50-100ms
longer in the cache than they should have been.

This fix contains two parts, each of which I believe would be sufficient
on their own to fix the issue:
1. Use the currentTime passed in in Expiry::expireAfterCreate
2. Use the transaction time in the cache's Ticker. This keeps everything
   on the same schedule.
2025-10-16 20:01:17 +00:00
Weimin Yu
ddd955e156 Fix dependency of Gradle task for schema test (#2849)
Problem not showing up because all use cases run this test after
`build`.
2025-10-16 15:33:28 +00:00
gbrodman
6863f678f1 Allow Gradle to use more heap space (#2847)
During the release process, we are seeing the message "Gradle build daemon disappeared unexpectedly (it may have been killed or may have crashed)" which seemingly can be caused by OOMs
2025-10-13 18:25:08 +00:00
gbrodman
6bd90e967b Add more hash indexes used during common flows (#2845)
I analyzed SQL statements run during the following flows and EXPLAIN
ANALYZEd each of them to figure out if there are any additional hash
indexes we could add that could be particularly helpful. Note: it's not
worth adding a hash index on the host_repo_id field in DomainHost
because so many rows (domains) use the same host.

- domain create
- domain delete
- domain info
- domain renew
- domain update
- host create
- host delete
- host update

I skipped the ones that use the read-only replica, as well as contact
flows (we're getting rid of them), and domain transfer/restore-related
flows as those are extremely infrequent.
2025-10-13 18:07:47 +00:00
gbrodman
5faf3d283c Differentiate between inserts and updates in flows (#2846)
Updates (AKA merges) run an extra SELECT statement to figure out if the
resource exists so that it can merge the entity into the existing object
in Hibernate's schema. When we're inserting new rows (such as new poll
messages or resource creates), we know that we don't need to do that
merge. Doing this should save us some SELECT statements (this has borne
out to be the truth in alpha)
2025-10-13 15:43:18 +00:00
gbrodman
149fb66ac5 Add cache for deletion times of existing domains (#2840)
This should help in instances of popular domains dropping, since we
won't need to do an additional two database loads every time (assuming
the deletion time is in the future).
2025-10-09 17:22:24 +00:00
gbrodman
8c96940a27 Only load from ClaimsList once when filling the cache (#2843) 2025-10-09 16:57:21 +00:00
Ben McIlwain
9c5510f05d Add a rate limiter to remove all domain contacts action (#2838)
The maximum QPS defaults to 10, but can also be specified at runtime through
use of a query-string parameter.

BUG = http://b/439636188
2025-10-02 22:15:19 +00:00
gbrodman
84884de77b Verify existence of TLDs and registrars for tokens (#2837)
Just in case someone makes a typo when running the commands
2025-10-02 20:10:58 +00:00
Ben McIlwain
d6c35df9bc Ignore single domain failures in remove contacts from all domains action (#2836)
When running the action in sandbox on 1.5M domains, it failed a few times
updating individual domains (requiring a manual restart of the entire action).
It's better to just log the individual failures for manual inspection and then
otherwise continue running the action to process the vast majority of other
updates that won't fail.

BUG = http://b/439636188
2025-10-02 18:58:23 +00:00
Juan Celhay
7caa0ec9d6 Add environment configuration files to .gitignore (#2830)
* Add environment configuration files to .gitignore

* Delete config files from repo

* Refactor release cb file to delete config file lines from gitignore

* Reorder env files

* Add README for config files
2025-10-02 18:36:43 +00:00
Weimin Yu
ee3866ec4a Allow top level tld creation in Sandbox (#2835)
Add a flag to the CreateCdnsTld command to bypass the dns name format
check in Sandbox (limiting names to `*.test.`). With this flag, we
can create TLDs for RST testing in Sandbox.

Note that if the new flag is wrongly set for a disallowed name, the
request to the Cloud DNS API will fail. The format check in the command
just provides a user-friendly error message.
2025-10-01 14:20:33 +00:00
gbrodman
97d0b7680f Add hash indexes for common use cases (#2834)
I went through all the SQL statements generated by some sample
DomainCreateFlow and DomainDeleteFlow cases to find situations where we
were either SELECTing from, or UPDATEing, tables with a direct "field =
value" format. These are the situations that I found where we can add
hash indexes. This does two things:

1. Makes these queries slight faster, since these are usually queries on
   columns that are either unique or very close to unique, and O(1) is
   faster than O(log(n))
2. Spreads around the optimistic predicate locks on the previously-used
   btree indexes. Many of our serialization errors came from the fact
   that we were autogenerating incrementing ID values for various
   tables, meaning that SELECTs, INSERTs, and UPDATEs would all try to
   take predicate locks out on the same page of the btree index. Using a
   hash index means that the page locks will be spread out to various
   index pages, rather than conflicting with each other.

Running load tests on alpha I see significant improvements in speed and
error rates. Speed is hard to quantify due to the nature of the way the
load tests distribute tasks among the queues but it could be more than
50% improvement, and serialization errors in the logs drop by more than
90%.
2025-09-29 22:16:24 +00:00
Pavlo Tkach
5700a008d6 Add console history frontend (#2832) 2025-09-26 21:25:03 +00:00
Ben McIlwain
dc9f5b99bc Add a batch action to remove all contacts from domains (#2827)
This implements the first part of Minimum Data Set phase 3, wherein we delete
all contact data. This action is necessary to leave a permanent record on the
domain (in the form of a domain history entry) documenting when the contacts
were removed by the administrative user.

Then, after this has finished removing all contact assocations, we can simply
empty out or drop the Contact/ContactHistory tables and associated join tables.
2025-09-25 20:47:17 +00:00
Ben McIlwain
d3c6de7a38 Modify the base Latin LGR with our intended changes to improve security (#2829) 2025-09-24 21:04:37 +00:00
491 changed files with 5145 additions and 30437 deletions

11
.gitignore vendored
View File

@@ -18,6 +18,13 @@ gjf.out
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# Environment-specific configuration files
core/src/main/java/google/registry/config/files/nomulus-config-alpha.yaml
core/src/main/java/google/registry/config/files/nomulus-config-crash.yaml
core/src/main/java/google/registry/config/files/nomulus-config-production.yaml
core/src/main/java/google/registry/config/files/nomulus-config-qa.yaml
core/src/main/java/google/registry/config/files/nomulus-config-sandbox.yaml
######################################################################
# Eclipse Ignores
@@ -114,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

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

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.
//

View File

@@ -56,7 +56,7 @@ PROPERTIES_HEADER = """\
# nom_build), run ./nom_build --help.
#
# DO NOT EDIT THIS FILE BY HAND
org.gradle.jvmargs=-Xmx1024m
org.gradle.jvmargs=-Xmx2048m
org.gradle.caching=true
org.gradle.parallel=true
"""

View File

@@ -105,9 +105,8 @@ PRESUBMITS = {
# System.(out|err).println should only appear in tools/ or load-testing/
PresubmitCheck(
r".*\bSystem\.(out|err)\.print", "java", {
"StackdriverDashboardBuilder.java", "/tools/", "/example/",
"/load-testing/", "RegistryTestServerMain.java",
"TestServerExtension.java", "FlowDocumentationTool.java"
"/tools/", "/example/", "/load-testing/",
"RegistryTestServerMain.java", "TestServerExtension.java"
}):
"System.(out|err).println is only allowed in tools/ packages. Please "
"use a logger instead.",
@@ -120,7 +119,7 @@ PRESUBMITS = {
):
"In SOY please use the ({@param name: string} /** User name. */) style"
" parameter passing instead of the ( * @param name User name.) style "
"parameter pasing.",
"parameter passing.",
PresubmitCheck(
r'.*\{[^}]+\w+:\s+"',
"soy",
@@ -139,41 +138,6 @@ PRESUBMITS = {
{},
):
"All soy templates must use strict autoescaping",
# various JS linting checks
PresubmitCheck(
r".*goog\.base\(",
"js",
{"/node_modules/"},
):
"Use of goog.base is not allowed.",
PresubmitCheck(
r".*goog\.dom\.classes",
"js",
{"/node_modules/"},
):
"Instead of goog.dom.classes, use goog.dom.classlist which is smaller "
"and faster.",
PresubmitCheck(
r".*goog\.getMsg",
"js",
{"/node_modules/"},
):
"Put messages in Soy, instead of using goog.getMsg().",
PresubmitCheck(
r".*(innerHTML|outerHTML)\s*(=|[+]=)([^=]|$)",
"js",
{"/node_modules/", "registrar_bin."},
):
"Do not assign directly to the dom. Use goog.dom.setTextContent to set"
" to plain text, goog.dom.removeChildren to clear, or "
"soy.renderElement to render anything else",
PresubmitCheck(
r".*console\.(log|info|warn|error)",
"js",
{"/node_modules/", "google/registry/ui/js/util.js", "registrar_bin."},
):
"JavaScript files should not include console logging.",
PresubmitCheck(
r".*\nimport (static )?.*\.shaded\..*",
"java",
@@ -303,26 +267,6 @@ def verify_flyway_index():
return not success
def verify_javascript_deps():
"""Verifies that we haven't introduced any new javascript dependencies."""
with open('package.json') as f:
package = json.load(f)
deps = list(package['dependencies'].keys())
if deps != EXPECTED_JS_PACKAGES:
print('Unexpected javascript dependencies. Was expecting '
'%s, got %s.' % (EXPECTED_JS_PACKAGES, deps))
print(textwrap.dedent("""
* If the new dependencies are intentional, please verify that the
* license is one of the allowed licenses (see
* config/dependency-license/allowed_licenses.json) and add an entry
* for the package (with the license in a comment) to the
* EXPECTED_JS_PACKAGES variable in config/presubmits.py.
"""))
return True
return False
def get_files():
for root, dirnames, filenames in os.walk("."):
for filename in filenames:
@@ -347,8 +291,5 @@ if __name__ == "__main__":
# when we put it here it fails fast before all of the tests are run.
failed |= verify_flyway_index()
# Make sure we haven't introduced any javascript dependencies.
failed |= verify_javascript_deps()
if failed:
sys.exit(1)

View File

@@ -26,6 +26,7 @@ import SecurityComponent from './settings/security/security.component';
import { SettingsComponent } from './settings/settings.component';
import { SupportComponent } from './support/support.component';
import RdapComponent from './settings/rdap/rdap.component';
import { HistoryComponent } from './history/history.component';
import { PasswordResetVerifyComponent } from './shared/components/passwordReset/passwordResetVerify.component';
export interface RouteWithIcon extends Route {
@@ -64,13 +65,18 @@ export const routes: RouteWithIcon[] = [
title: 'Dashboard',
iconName: 'view_comfy_alt',
},
// { path: 'tlds', component: TldsComponent, title: "TLDs", iconName: "event_list" },
{
path: DomainListComponent.PATH,
component: DomainListComponent,
title: 'Domains',
iconName: 'view_list',
},
{
path: HistoryComponent.PATH,
component: HistoryComponent,
// title: 'History',
// iconName: 'history',
},
{
path: SettingsComponent.PATH,
component: SettingsComponent,

View File

@@ -56,13 +56,14 @@ import { GlobalLoaderService } from './shared/services/globalLoader.service';
import { UserDataService } from './shared/services/userData.service';
import { SnackBarModule } from './snackbar.module';
import { SupportComponent } from './support/support.component';
import { TldsComponent } from './tlds/tlds.component';
import { ForceFocusDirective } from './shared/directives/forceFocus.directive';
import RdapComponent from './settings/rdap/rdap.component';
import RdapEditComponent from './settings/rdap/rdapEdit.component';
import { PocReminderComponent } from './shared/components/pocReminder/pocReminder.component';
import { PasswordResetVerifyComponent } from './shared/components/passwordReset/passwordResetVerify.component';
import { PasswordInputForm } from './shared/components/passwordReset/passwordInputForm.component';
import { HistoryComponent } from './history/history.component';
import { HistoryListComponent } from './history/historyList.component';
@NgModule({
declarations: [SelectedRegistrarWrapper],
@@ -81,6 +82,8 @@ export class SelectedRegistrarModule {}
EppPasswordEditComponent,
ForceFocusDirective,
HeaderComponent,
HistoryComponent,
HistoryListComponent,
HomeComponent,
LocationBackDirective,
NavigationComponent,
@@ -104,7 +107,6 @@ export class SelectedRegistrarModule {}
SettingsComponent,
SettingsContactComponent,
SupportComponent,
TldsComponent,
UserLevelVisibility,
],
bootstrap: [AppComponent],

View File

@@ -0,0 +1,62 @@
<app-selected-registrar-wrapper>
<div class="history-log">
<h1 class="mat-headline-4" forceFocus>
Registrar Console Activity History
</h1>
<mat-tab-group
[elementId]="getElementIdForUserLog()"
class="history-log__tabs"
>
<mat-tab label="Registrar Activity">
<div class="spacer"></div>
<app-history-list
[historyRecords]="historyService.historyRecordsRegistrar()"
[isLoading]="isLoading"
/>
</mat-tab>
<mat-tab label="User Activity">
<div class="spacer"></div>
<form (ngSubmit)="loadHistory()" #form="ngForm">
<section>
<mat-form-field appearance="outline">
<mat-label>Console User Email: </mat-label>
<input
matInput
id="email"
type="email"
name="consoleUserEmail"
required
email
[(ngModel)]="consoleUserEmail"
#emailControl="ngModel"
/>
</mat-form-field>
</section>
<div class="spacer"></div>
<button
mat-flat-button
color="primary"
type="submit"
aria-label="Search user history"
[disabled]="!form.valid"
>
Search
</button>
</form>
<div class="spacer"></div>
<app-history-list
[historyRecords]="historyService.historyRecordsUser()"
[isLoading]="isLoading"
/>
</mat-tab>
</mat-tab-group>
</div>
<app-history-list
[elementId]="getElementIdForUserLog()"
[isReverse]="true"
[historyRecords]="historyService.historyRecordsUser()"
[isLoading]="isLoading"
/>
</app-selected-registrar-wrapper>

View File

@@ -1,4 +1,4 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,5 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
@javax.annotation.ParametersAreNonnullByDefault
package google.registry.ui.server;
.history-log {
font-family: "Roboto", sans-serif;
max-width: 760px;
.spacer {
margin: 20px 0;
}
}

View File

@@ -0,0 +1,80 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, effect } from '@angular/core';
import { UserDataService } from '../shared/services/userData.service';
import { BackendService } from '../shared/services/backend.service';
import { RegistrarService } from '../registrar/registrar.service';
import { HistoryService } from './history.service';
import { MatSnackBar } from '@angular/material/snack-bar';
import {
GlobalLoader,
GlobalLoaderService,
} from '../shared/services/globalLoader.service';
import { HttpErrorResponse } from '@angular/common/http';
import { RESTRICTED_ELEMENTS } from '../shared/directives/userLevelVisiblity.directive';
@Component({
selector: 'app-history',
templateUrl: './history.component.html',
styleUrls: ['./history.component.scss'],
providers: [HistoryService],
standalone: false,
})
export class HistoryComponent implements GlobalLoader {
public static PATH = 'history';
consoleUserEmail: string = '';
isLoading: boolean = false;
constructor(
private backendService: BackendService,
private registrarService: RegistrarService,
protected historyService: HistoryService,
protected globalLoader: GlobalLoaderService,
protected userDataService: UserDataService,
private _snackBar: MatSnackBar
) {
effect(() => {
if (registrarService.registrarId()) {
this.loadHistory();
}
});
}
getElementIdForUserLog() {
return RESTRICTED_ELEMENTS.ACTIVITY_PER_USER;
}
loadingTimeout() {
this._snackBar.open('Timeout loading records history');
}
loadHistory() {
this.globalLoader.startGlobalLoader(this);
this.isLoading = true;
this.historyService
.getHistoryLog(this.registrarService.registrarId(), this.consoleUserEmail)
.subscribe({
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error || err.message);
this.isLoading = false;
},
next: () => {
this.globalLoader.stopGlobalLoader(this);
this.isLoading = false;
},
});
}
}

View File

@@ -0,0 +1,46 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable, signal } from '@angular/core';
import { BackendService } from '../shared/services/backend.service';
import { tap } from 'rxjs';
export interface HistoryRecord {
modificationTime: string;
type: string;
description: string;
actingUser: {
emailAddress: string;
};
}
@Injectable()
export class HistoryService {
historyRecordsRegistrar = signal<HistoryRecord[]>([]);
historyRecordsUser = signal<HistoryRecord[]>([]);
constructor(private backendService: BackendService) {}
getHistoryLog(registrarId: string, userEmail?: string) {
return this.backendService.getHistoryLog(registrarId, userEmail).pipe(
tap((historyRecords: HistoryRecord[]) => {
if (userEmail) {
this.historyRecordsUser.set(historyRecords);
} else {
this.historyRecordsRegistrar.set(historyRecords);
}
})
);
}
}

View File

@@ -0,0 +1,50 @@
@if (!isLoading && historyRecords.length == 0) {
<div class="history-list__no-records">
<mat-icon class="history-list__no-records-icon secondary-text"
>apps_outage</mat-icon
>
<h1>No records found</h1>
</div>
} @else {
<mat-card>
<mat-card-content>
<mat-list role="list">
<ng-container *ngFor="let item of historyRecords; let last = last">
<mat-list-item class="history-list__item">
<mat-icon
[ngClass]="getIconClass(item.type)"
class="history-list__icon"
>
{{ getIconForType(item.type) }}
</mat-icon>
<div class="history-list__content">
<div class="history-list__description">
<span class="history-list__description--main">{{
item.type
}}</span>
<div>
<mat-chip
*ngIf="parseDescription(item.description).detail"
class="history-list__chip"
>
{{ parseDescription(item.description).detail }}
</mat-chip>
</div>
</div>
<div class="history-list__user">
<b>User - {{ item.actingUser.emailAddress }}</b>
</div>
</div>
<span class="history-list__timestamp">
{{ item.modificationTime | date : "MMM d, y, h:mm a" }}
</span>
</mat-list-item>
<mat-divider *ngIf="!last"></mat-divider>
</ng-container>
</mat-list>
</mat-card-content>
</mat-card>
}

View File

@@ -0,0 +1,81 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
.history-list {
font-family: "Roboto", sans-serif;
&__item {
display: flex;
align-items: center;
// Override default mat-list-item height to fit content
height: auto !important;
padding: 16px 0;
}
&__no-records {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
&__no-records-icon {
width: 4rem;
height: 4rem;
font-size: 4rem;
margin-top: 1.5rem;
}
&__icon {
margin-right: 16px;
&--update {
color: #1976d2;
}
&--security {
color: #d32f2f;
}
}
&__description {
&--main {
font-size: 1rem;
font-weight: 500;
color: rgba(0, 0, 0, 0.87);
margin-bottom: 1em;
}
}
&__content {
flex-grow: 1;
display: flex;
flex-direction: column;
gap: 4px;
margin-right: 16px;
}
&__chip {
margin: 0.5rem 0;
}
&__user {
font-size: 0.9rem;
color: rgba(0, 0, 0, 0.6);
}
&__timestamp {
color: rgba(0, 0, 0, 0.6);
white-space: nowrap;
text-align: right;
}
}

View File

@@ -0,0 +1,66 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { HistoryRecord } from './history.service';
@Component({
selector: 'app-history-list',
templateUrl: './historyList.component.html',
styleUrls: ['./historyList.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
export class HistoryListComponent {
@Input() historyRecords: HistoryRecord[] = [];
@Input() isLoading: boolean = false;
getIconForType(type: string): string {
switch (type) {
case 'REGISTRAR_UPDATE':
return 'edit';
case 'REGISTRAR_SECURITY_UPDATE':
return 'security';
default:
return 'history'; // A fallback icon
}
}
getIconClass(type: string): string {
switch (type) {
case 'REGISTRAR_UPDATE':
return 'history-log__icon--update';
case 'REGISTRAR_SECURITY_UPDATE':
return 'history-log__icon--security';
default:
return '';
}
}
parseDescription(description: string): {
main: string;
detail: string | null;
} {
if (!description) {
return { main: 'N/A', detail: null };
}
const parts = description.split('|');
const detail = parts.length > 1 ? parts[1].replace(/_/g, ' ') : parts[0];
return {
main: parts[0],
detail: detail,
};
}
}

View File

@@ -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 {

View File

@@ -57,6 +57,11 @@
[(ngModel)]="contactService.contactInEdit.emailAddress"
[ngModelOptions]="{ standalone: true }"
[disabled]="emailAddressIsDisabled()"
[matTooltip]="
emailAddressIsDisabled()
? 'Reach out to registry customer support to update email address'
: ''
"
/>
</mat-form-field>
@@ -84,6 +89,7 @@
<h1>Contact Type</h1>
<p class="console-app__contact-required">
<mat-icon color="accent">error</mat-icon>Required to select at least one
(primary contact can't be updated)
</p>
<div class="">
<ng-container
@@ -105,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
>
@@ -113,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
>
@@ -121,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
@@ -183,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"

View File

@@ -16,6 +16,7 @@ import { Directive, ElementRef, Input, effect } from '@angular/core';
import { UserDataService } from '../services/userData.service';
export enum RESTRICTED_ELEMENTS {
ACTIVITY_PER_USER,
REGISTRAR_ELEMENT,
OTE,
USERS,
@@ -28,9 +29,10 @@ export const DISABLED_ELEMENTS_PER_ROLE = {
RESTRICTED_ELEMENTS.REGISTRAR_ELEMENT,
RESTRICTED_ELEMENTS.OTE,
RESTRICTED_ELEMENTS.SUSPEND,
RESTRICTED_ELEMENTS.ACTIVITY_PER_USER,
],
SUPPORT_LEAD: [],
SUPPORT_AGENT: [],
SUPPORT_AGENT: [RESTRICTED_ELEMENTS.ACTIVITY_PER_USER],
};
@Directive({
@@ -40,6 +42,8 @@ export const DISABLED_ELEMENTS_PER_ROLE = {
export class UserLevelVisibility {
@Input() elementId!: RESTRICTED_ELEMENTS | null;
@Input() isReverse: boolean = false;
constructor(
private userDataService: UserDataService,
private el: ElementRef
@@ -56,9 +60,9 @@ export class UserLevelVisibility {
// @ts-ignore
(DISABLED_ELEMENTS_PER_ROLE[globalRole] || []).includes(this.elementId)
) {
this.el.nativeElement.style.display = 'none';
this.el.nativeElement.style.display = this.isReverse ? '' : 'none';
} else {
this.el.nativeElement.style.display = '';
this.el.nativeElement.style.display = this.isReverse ? 'none' : '';
}
}
}

View File

@@ -31,6 +31,7 @@ import { Contact } from '../../settings/contact/contact.service';
import { EppPasswordBackendModel } from '../../settings/security/security.service';
import { UserData } from './userData.service';
import { PasswordResetVerifyResponse } from '../components/passwordReset/passwordResetVerify.component';
import { HistoryRecord } from '../../history/history.service';
@Injectable()
export class BackendService {
@@ -123,6 +124,16 @@ export class BackendService {
.pipe(catchError((err) => this.errorCatcher<DomainListResult>(err)));
}
getHistoryLog(registrarId: string, userEmail?: string) {
return this.http
.get<HistoryRecord[]>(
userEmail
? `/console-api/history?registrarId=${registrarId}&consoleUserEmail=${userEmail}`
: `/console-api/history?registrarId=${registrarId}`
)
.pipe(catchError((err) => this.errorCatcher<HistoryRecord[]>(err)));
}
getRegistrars(): Observable<Registrar[]> {
return this.http
.get<Registrar[]>('/console-api/registrars')

View File

@@ -1 +0,0 @@
<div class="console-tlds__cards"></div>

View File

@@ -1,38 +0,0 @@
// Copyright 2024 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.
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TldsComponent } from './tlds.component';
import { MaterialModule } from '../material.module';
describe('TldsComponent', () => {
let component: TldsComponent;
let fixture: ComponentFixture<TldsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MaterialModule],
declarations: [TldsComponent],
}).compileComponents();
fixture = TestBed.createComponent(TldsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,23 +0,0 @@
// Copyright 2024 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.
import { Component } from '@angular/core';
@Component({
selector: 'app-tlds',
templateUrl: './tlds.component.html',
styleUrls: ['./tlds.component.scss'],
standalone: false,
})
export class TldsComponent {}

View File

@@ -29,7 +29,7 @@
></mat-label
>
<mat-select [(ngModel)]="user().role" name="userRole">
<mat-option value="PRIMARY_CONTACT">Editor</mat-option>
<mat-option value="TECH_CONTACT">Editor</mat-option>
<mat-option value="ACCOUNT_MANAGER">Viewer</mat-option>
</mat-select>
</mat-form-field>

View File

@@ -30,7 +30,6 @@ def screenshotsForGoldensDir = "${project.buildDir}/screenshots_for_goldens"
def newGoldensDir = "${project.buildDir}/new_golden_images"
def goldensDir =
"${javaTestDir}/google/registry/webdriver/goldens/chrome-linux"
def jsDir = "${project.projectDir}/src/main/javascript"
// Tests that fail when running Gradle in a docker container, e. g. when
// building the release artifacts in Google Cloud Build.
@@ -55,9 +54,6 @@ def dockerIncompatibleTestPatterns = [
// objects retained by frameworks.
// TODO(weiminyu): identify cause and fix offending tests.
def fragileTestPatterns = [
// Changes cache timeouts and for some reason appears to have contention
// with other tests.
"google/registry/whois/WhoisCommandFactoryTest.*",
// Breaks random other tests when running with standardTests.
"google/registry/bsa/UploadBsaUnavailableDomainsActionTest.*",
// Currently changes a global configuration parameter that for some reason

View File

@@ -29,9 +29,11 @@ import static google.registry.request.RequestParameters.extractRequiredParameter
import static google.registry.request.RequestParameters.extractSetOfDatetimeParameters;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.RateLimiter;
import dagger.Module;
import dagger.Provides;
import google.registry.request.Parameter;
import jakarta.inject.Named;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Optional;
import org.joda.time.DateTime;
@@ -137,4 +139,18 @@ public class BatchModule {
static boolean provideIsFast(HttpServletRequest req) {
return extractBooleanParameter(req, PARAM_FAST);
}
private static final int DEFAULT_MAX_QPS = 10;
@Provides
@Parameter("maxQps")
static int provideMaxQps(HttpServletRequest req) {
return extractOptionalIntParameter(req, "maxQps").orElse(DEFAULT_MAX_QPS);
}
@Provides
@Named("removeAllDomainContacts")
static RateLimiter provideRemoveAllDomainContactsRateLimiter(@Parameter("maxQps") int maxQps) {
return RateLimiter.create(maxQps);
}
}

View File

@@ -0,0 +1,246 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.batch;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
import static google.registry.flows.FlowUtils.marshalWithLenientRetry;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.ResourceUtils.readResourceUtf8;
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static jakarta.servlet.http.HttpServletResponse.SC_NO_CONTENT;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import static java.nio.charset.StandardCharsets.US_ASCII;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.RateLimiter;
import google.registry.config.RegistryConfig.Config;
import google.registry.flows.EppController;
import google.registry.flows.EppRequestSource;
import google.registry.flows.PasswordOnlyTransportCredentials;
import google.registry.flows.StatelessRequestSessionMetadata;
import google.registry.model.common.FeatureFlag;
import google.registry.model.contact.Contact;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.Domain;
import google.registry.model.eppcommon.ProtocolDefinition;
import google.registry.model.eppoutput.EppOutput;
import google.registry.persistence.VKey;
import google.registry.request.Action;
import google.registry.request.Action.GaeService;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.request.lock.LockHandler;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import javax.annotation.Nullable;
import org.joda.time.Duration;
/**
* An action that removes all contacts from all active (non-deleted) domains.
*
* <p>This implements part 1 of phase 3 of the Minimum Dataset migration, wherein we remove all uses
* of contact objects in preparation for later removing all contact data from the system.
*
* <p>This runs as a singly threaded, resumable action that loads batches of domains still
* containing contacts, and runs a superuser domain update on each one to remove the contacts,
* leaving behind a record recording that update.
*/
@Action(
service = GaeService.BACKEND,
path = RemoveAllDomainContactsAction.PATH,
method = Action.Method.POST,
auth = Auth.AUTH_ADMIN)
public class RemoveAllDomainContactsAction implements Runnable {
public static final String PATH = "/_dr/task/removeAllDomainContacts";
private static final String LOCK_NAME = "Remove all domain contacts";
private static final String CONTACT_FMT = "<domain:contact type=\"%s\">%s</domain:contact>";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final EppController eppController;
private final String registryAdminClientId;
private final LockHandler lockHandler;
private final RateLimiter rateLimiter;
private final Response response;
private final String updateDomainXml;
private int successes = 0;
private int failures = 0;
private static final int BATCH_SIZE = 10000;
@Inject
RemoveAllDomainContactsAction(
EppController eppController,
@Config("registryAdminClientId") String registryAdminClientId,
LockHandler lockHandler,
@Named("removeAllDomainContacts") RateLimiter rateLimiter,
Response response) {
this.eppController = eppController;
this.registryAdminClientId = registryAdminClientId;
this.lockHandler = lockHandler;
this.rateLimiter = rateLimiter;
this.response = response;
this.updateDomainXml =
readResourceUtf8(RemoveAllDomainContactsAction.class, "domain_remove_contacts.xml");
}
@Override
public void run() {
checkState(
tm().transact(() -> FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_PROHIBITED)),
"Minimum dataset migration must be completed prior to running this action");
response.setContentType(PLAIN_TEXT_UTF_8);
Callable<Void> runner =
() -> {
try {
runLocked();
response.setStatus(SC_OK);
} catch (Exception e) {
logger.atSevere().withCause(e).log("Errored out during execution.");
response.setStatus(SC_INTERNAL_SERVER_ERROR);
response.setPayload(String.format("Errored out with cause: %s", e));
}
return null;
};
if (!lockHandler.executeWithLocks(runner, null, Duration.standardHours(1), LOCK_NAME)) {
// Send a 200-series status code to prevent this conflicting action from retrying.
response.setStatus(SC_NO_CONTENT);
response.setPayload("Could not acquire lock; already running?");
}
}
private void runLocked() {
logger.atInfo().log("Removing contacts on all active domains.");
List<String> domainRepoIdsBatch;
do {
domainRepoIdsBatch =
tm().<List<String>>transact(
() ->
tm().getEntityManager()
.createQuery(
"""
SELECT repoId FROM Domain WHERE deletionTime = :end_of_time AND NOT (
adminContact IS NULL AND billingContact IS NULL
AND registrantContact IS NULL AND techContact IS NULL)
""")
.setParameter("end_of_time", END_OF_TIME)
.setMaxResults(BATCH_SIZE)
.getResultList());
for (String domainRepoId : domainRepoIdsBatch) {
rateLimiter.acquire();
runDomainUpdateFlow(domainRepoId);
}
} while (!domainRepoIdsBatch.isEmpty());
String msg =
String.format(
"Finished; %d domains were successfully updated and %d errored out.",
successes, failures);
logger.at(failures == 0 ? Level.INFO : Level.WARNING).log(msg);
response.setPayload(msg);
}
private void runDomainUpdateFlow(String repoId) {
// Create a new transaction that the flow's execution will be enlisted in that loads the domain
// transactionally. This way we can ensure that nothing else has modified the domain in question
// in the intervening period since the query above found it. If a single domain update fails
// permanently, log it and move on to not block processing all the other domains.
try {
boolean success = tm().transact(() -> runDomainUpdateFlowInner(repoId));
if (success) {
successes++;
} else {
failures++;
}
} catch (Throwable t) {
logger.atWarning().withCause(t).log(
"Failed updating domain with repoId %s; skipping.", repoId);
}
}
/**
* Runs the actual domain update flow and returns whether the contact removals were successful.
*/
private boolean runDomainUpdateFlowInner(String repoId) {
Domain domain = tm().loadByKey(VKey.create(Domain.class, repoId));
if (!domain.getDeletionTime().equals(END_OF_TIME)) {
// Domain has been deleted since the action began running; nothing further to be
// done here.
logger.atInfo().log("Nothing to process for deleted domain '%s'.", domain.getDomainName());
return false;
}
logger.atInfo().log("Attempting to remove contacts on domain '%s'.", domain.getDomainName());
StringBuilder sb = new StringBuilder();
ImmutableMap<VKey<? extends Contact>, Contact> contacts =
tm().loadByKeys(
domain.getContacts().stream()
.map(DesignatedContact::getContactKey)
.collect(ImmutableSet.toImmutableSet()));
// Collect all the (non-registrant) contacts referenced by the domain and compile an EPP XML
// string that removes each one.
for (DesignatedContact designatedContact : domain.getContacts()) {
@Nullable Contact contact = contacts.get(designatedContact.getContactKey());
if (contact == null) {
logger.atWarning().log(
"Domain '%s' referenced contact with repo ID '%s' that couldn't be" + " loaded.",
domain.getDomainName(), designatedContact.getContactKey().getKey());
continue;
}
sb.append(
String.format(
CONTACT_FMT,
Ascii.toLowerCase(designatedContact.getType().name()),
contact.getContactId()))
.append("\n");
}
String compiledXml =
updateDomainXml
.replace("%DOMAIN%", domain.getDomainName())
.replace("%CONTACTS%", sb.toString());
EppOutput output =
eppController.handleEppCommand(
new StatelessRequestSessionMetadata(
registryAdminClientId, ProtocolDefinition.getVisibleServiceExtensionUris()),
new PasswordOnlyTransportCredentials(),
EppRequestSource.BACKEND,
false,
true,
compiledXml.getBytes(US_ASCII));
if (output.isSuccess()) {
logger.atInfo().log(
"Successfully removed contacts from domain '%s'.", domain.getDomainName());
} else {
logger.atWarning().log(
"Failed removing contacts from domain '%s' with error %s.",
domain.getDomainName(), new String(marshalWithLenientRetry(output), US_ASCII));
}
return output.isSuccess();
}
}

View File

@@ -28,7 +28,6 @@ import static google.registry.bsa.persistence.Queries.queryMissedRegisteredUnblo
import static google.registry.bsa.persistence.Queries.queryUnblockableDomainByLabels;
import static google.registry.model.tld.Tld.isEnrolledWithBsa;
import static google.registry.model.tld.Tlds.getTldEntitiesOfType;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.POST;
import static google.registry.util.BatchedStreams.toBatches;
@@ -53,7 +52,6 @@ import google.registry.model.ForeignKeyUtils;
import google.registry.model.domain.Domain;
import google.registry.model.tld.Tld;
import google.registry.model.tld.Tld.TldType;
import google.registry.persistence.VKey;
import google.registry.request.Action;
import google.registry.request.Action.GaeService;
import google.registry.request.Response;
@@ -185,8 +183,8 @@ public class BsaValidateAction implements Runnable {
ImmutableList<UnblockableDomain> batch;
do {
batch = Queries.batchReadUnblockableDomains(lastRead, transactionBatchSize);
ImmutableMap<String, VKey<Domain>> activeDomains =
ForeignKeyUtils.load(
ImmutableMap<String, Domain> activeDomains =
ForeignKeyUtils.loadResources(
Domain.class,
batch.stream().map(UnblockableDomain::domainName).collect(toImmutableList()),
clock.nowUtc());
@@ -201,7 +199,7 @@ public class BsaValidateAction implements Runnable {
}
Optional<String> verifyDomainStillUnblockableWithReason(
UnblockableDomain domain, ImmutableMap<String, VKey<Domain>> activeDomains) {
UnblockableDomain domain, ImmutableMap<String, Domain> activeDomains) {
DateTime now = clock.nowUtc();
boolean isRegistered = activeDomains.containsKey(domain.domainName());
boolean isReserved = isReservedDomain(domain.domainName(), now);
@@ -230,10 +228,8 @@ public class BsaValidateAction implements Runnable {
domain.reason()));
}
boolean isStalenessAllowed(VKey<Domain> domainVKey) {
Domain domain = bsaQuery(() -> replicaTm().loadByKey(domainVKey));
var now = clock.nowUtc();
return domain.getCreationTime().plus(maxStaleness).isAfter(now);
boolean isStalenessAllowed(Domain domain) {
return domain.getCreationTime().plus(maxStaleness).isAfter(clock.nowUtc());
}
/** Returns unique labels across all block lists in the download specified by {@code jobName}. */

View File

@@ -156,7 +156,7 @@ public final class DomainsRefresher {
.collect(toImmutableSet());
ImmutableSet<String> currRegistered =
ImmutableSet.copyOf(
ForeignKeyUtils.load(Domain.class, nameToEntity.keySet(), now).keySet());
ForeignKeyUtils.loadKeys(Domain.class, nameToEntity.keySet(), now).keySet());
SetView<String> noLongerRegistered = Sets.difference(prevRegistered, currRegistered);
SetView<String> newlyRegistered = Sets.difference(currRegistered, prevRegistered);

View File

@@ -145,11 +145,10 @@ public final class LabelDiffUpdates {
ImmutableSet<String> validDomainNames =
labels.stream()
.map(label -> validDomainNamesForLabel(label, idnChecker))
.flatMap(x -> x)
.flatMap(label -> validDomainNamesForLabel(label, idnChecker))
.collect(toImmutableSet());
ImmutableSet<String> registeredDomainNames =
ImmutableSet.copyOf(ForeignKeyUtils.load(Domain.class, validDomainNames, now).keySet());
ForeignKeyUtils.loadKeys(Domain.class, validDomainNames, now).keySet();
for (String domain : registeredDomainNames) {
nonBlockedDomains.add(new UnblockableDomain(domain, Reason.REGISTERED));
tm().put(BsaUnblockableDomain.of(domain, BsaUnblockableDomain.Reason.REGISTERED));

View File

@@ -976,17 +976,6 @@ public final class RegistryConfig {
return config.misc.transientFailureRetries;
}
/**
* Amount of time public HTTP proxies are permitted to cache our WHOIS responses.
*
* @see google.registry.whois.WhoisHttpAction
*/
@Provides
@Config("whoisHttpExpires")
public static Duration provideWhoisHttpExpires() {
return Duration.standardDays(1);
}
/**
* Maximum number of results to return for an RDAP search query
*
@@ -998,39 +987,6 @@ public final class RegistryConfig {
return 100;
}
/**
* Redaction text for email address in WHOIS
*
* @see google.registry.whois.WhoisResponse
*/
@Provides
@Config("whoisRedactedEmailText")
public static String provideWhoisRedactedEmailText(RegistryConfigSettings config) {
return config.registryPolicy.whoisRedactedEmailText;
}
/**
* Disclaimer displayed at the end of WHOIS query results.
*
* @see google.registry.whois.WhoisResponse
*/
@Provides
@Config("whoisDisclaimer")
public static String provideWhoisDisclaimer(RegistryConfigSettings config) {
return config.registryPolicy.whoisDisclaimer;
}
/**
* Message template for whois response when queried domain is blocked by BSA.
*
* @see google.registry.whois.WhoisResponse
*/
@Provides
@Config("domainBlockedByBsaTemplate")
public static String provideDomainBlockedByBsaTemplate(RegistryConfigSettings config) {
return config.registryPolicy.domainBlockedByBsaTemplate;
}
/**
* Maximum QPS for the Google Cloud Monitoring V3 (aka Stackdriver) API. The QPS limit can be
* adjusted by contacting Cloud Support.
@@ -1105,12 +1061,6 @@ public final class RegistryConfig {
return config.registryPolicy.customLogicFactoryClass;
}
@Provides
@Config("whoisCommandFactoryClass")
public static String provideWhoisCommandFactoryClass(RegistryConfigSettings config) {
return config.registryPolicy.whoisCommandFactoryClass;
}
@Provides
@Config("dnsCountQueryCoordinatorClass")
public static String dnsCountQueryCoordinatorClass(RegistryConfigSettings config) {
@@ -1606,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;
}

View File

@@ -90,7 +90,6 @@ public class RegistryConfigSettings {
public String contactAndHostRoidSuffix;
public String productName;
public String customLogicFactoryClass;
public String whoisCommandFactoryClass;
public String dnsCountQueryCoordinatorClass;
public int contactAutomaticTransferDays;
public String greetingServerId;
@@ -102,9 +101,6 @@ public class RegistryConfigSettings {
public String registryAdminClientId;
public String premiumTermsExportDisclaimer;
public String reservedTermsExportDisclaimer;
public String whoisRedactedEmailText;
public String whoisDisclaimer;
public String domainBlockedByBsaTemplate;
public String rdapTos;
public String rdapTosStaticUrl;
public String registryName;

View File

@@ -0,0 +1,13 @@
# Nomulus Environment Configuration
The configuration files for the different Nomulus environments are not included in this repository. To configure and run a specific environment, you will need to create the corresponding YAML configuration file in this directory.
The following is a list of the environment configuration files that you may need to create:
* `nomulus-config-alpha.yaml`
* `nomulus-config-crash.yaml`
* `nomulus-config-qa.yaml`
* `nomulus-config-sandbox.yaml`
* `nomulus-config-production.yaml`
Please create the relevant file for the environment you intend to use and populate it with the necessary configuration details.

View File

@@ -65,10 +65,6 @@ registryPolicy:
# See flows/custom/CustomLogicFactory.java
customLogicFactoryClass: google.registry.flows.custom.CustomLogicFactory
# WHOIS command factory fully-qualified class name.
# See whois/WhoisCommandFactory.java
whoisCommandFactoryClass: google.registry.whois.WhoisCommandFactory
# Custom logic class for handling DNS query count reporting for ICANN.
# See reporting/icann/DnsCountQueryCoordinator.java
dnsCountQueryCoordinatorClass: google.registry.reporting.icann.DummyDnsCountQueryCoordinator
@@ -114,31 +110,6 @@ registryPolicy:
to publish. This list is subject to change. The most up-to-date source
is always the registry itself, by sending domain check EPP commands.
# Redaction text for email address in WHOIS
whoisRedactedEmailText: |
Please query the WHOIS server of the owning registrar identified in this
output for information on how to contact the Registrant, Admin, or Tech
contact of the queried domain name.
# Disclaimer at the top of WHOIS results.
whoisDisclaimer: |
WHOIS information is provided by the registry solely for query-based,
informational purposes. Any information provided is "as is" without any
guarantee of accuracy. You may not use such information to (a) allow,
enable, or otherwise support the transmission of mass unsolicited,
commercial advertising or solicitations; (b) enable high volume, automated,
electronic processes that access the registry's systems or any
ICANN-Accredited Registrar, except as reasonably necessary to register
domain names or modify existing registrations; or (c) engage in or support
unlawful behavior. We reserve the right to restrict or deny your access to
the WHOIS database, and may modify these terms at any time.
# BSA blocked domain name template.
domainBlockedByBsaTemplate: |
Domain Name: %s
>>> This name is not available for registration.
>>> This name has been blocked by a GlobalBlock service.
# RDAP Terms of Service text displayed at the /rdap/help/tos endpoint.
rdapTos: >
By querying our Domain Database as part of the RDAP pilot program (RDAP

View File

@@ -1 +0,0 @@
# Add environment-specific configuration here.

View File

@@ -1 +0,0 @@
# Add environment-specific configuration here.

View File

@@ -1 +0,0 @@
# Add environment-specific configuration here.

View File

@@ -1 +0,0 @@
# Add environment-specific configuration here.

View File

@@ -1 +0,0 @@
# Add environment-specific configuration here.

View File

@@ -5,7 +5,6 @@
addGracePeriodLength: "PT432000S"
allowedFullyQualifiedHostNames: []
allowedRegistrantContactIds: []
anchorTenantAddGracePeriodLength: "PT2592000S"
autoRenewGracePeriodLength: "PT3888000S"
automaticTransferLength: "PT432000S"

View File

@@ -25,7 +25,6 @@ import static google.registry.dns.DnsModule.PARAM_REFRESH_REQUEST_TIME;
import static google.registry.dns.DnsUtils.DNS_PUBLISH_PUSH_QUEUE_NAME;
import static google.registry.dns.DnsUtils.requestDomainDnsRefresh;
import static google.registry.dns.DnsUtils.requestHostDnsRefresh;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.POST;
import static google.registry.request.RequestParameters.PARAM_TLD;
@@ -46,6 +45,7 @@ import google.registry.dns.DnsMetrics.CommitStatus;
import google.registry.dns.DnsMetrics.PublishStatus;
import google.registry.dns.writer.DnsWriter;
import google.registry.groups.GmailClient;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.domain.Domain;
import google.registry.model.host.Host;
import google.registry.model.registrar.Registrar;
@@ -237,7 +237,8 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
.findFirst()
.ifPresent(
dn -> {
Optional<Domain> domain = loadByForeignKey(Domain.class, dn, clock.nowUtc());
Optional<Domain> domain =
ForeignKeyUtils.loadResource(Domain.class, dn, clock.nowUtc());
if (domain.isPresent()) {
notifyWithEmailAboutDnsUpdateFailure(
domain.get().getCurrentSponsorRegistrarId(), dn, false);
@@ -250,7 +251,8 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
.findFirst()
.ifPresent(
hn -> {
Optional<Host> host = loadByForeignKey(Host.class, hn, clock.nowUtc());
Optional<Host> host =
ForeignKeyUtils.loadResource(Host.class, hn, clock.nowUtc());
if (host.isPresent()) {
notifyWithEmailAboutDnsUpdateFailure(
host.get().getPersistedCurrentSponsorRegistrarId(), hn, true);

View File

@@ -16,12 +16,12 @@ package google.registry.dns;
import static google.registry.dns.DnsUtils.requestDomainDnsRefresh;
import static google.registry.dns.DnsUtils.requestHostDnsRefresh;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import google.registry.dns.DnsUtils.TargetType;
import google.registry.model.EppResource;
import google.registry.model.EppResource.ForeignKeyedEppResource;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.annotations.ExternalMessagingName;
import google.registry.model.domain.Domain;
import google.registry.model.host.Host;
@@ -79,7 +79,7 @@ public final class RefreshDnsAction implements Runnable {
private <T extends EppResource & ForeignKeyedEppResource>
T loadAndVerifyExistence(Class<T> clazz, String foreignKey) {
return loadByForeignKey(clazz, foreignKey, clock.nowUtc())
return ForeignKeyUtils.loadResource(clazz, foreignKey, clock.nowUtc())
.orElseThrow(
() ->
new NotFoundException(

View File

@@ -17,7 +17,6 @@ package google.registry.dns.writer.clouddns;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.dns.DnsUtils.getDnsAPlusAAAATtlForHost;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.util.DomainNameUtils.getSecondLevelDomain;
import com.google.api.client.googleapis.json.GoogleJsonError;
@@ -37,6 +36,7 @@ import google.registry.config.RegistryConfig.Config;
import google.registry.dns.writer.BaseDnsWriter;
import google.registry.dns.writer.DnsWriter;
import google.registry.dns.writer.DnsWriterZone;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.domain.Domain;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.host.Host;
@@ -123,7 +123,8 @@ public class CloudDnsWriter extends BaseDnsWriter {
String absoluteDomainName = getAbsoluteHostName(domainName);
// Load the target domain. Note that it can be absent if this domain was just deleted.
Optional<Domain> domain = loadByForeignKey(Domain.class, domainName, clock.nowUtc());
Optional<Domain> domain =
ForeignKeyUtils.loadResource(Domain.class, domainName, clock.nowUtc());
// Return early if no DNS records should be published.
// desiredRecordsBuilder is populated with an empty set to indicate that all existing records
@@ -189,7 +190,7 @@ public class CloudDnsWriter extends BaseDnsWriter {
// Load the target host. Note that it can be absent if this host was just deleted.
// desiredRecords is populated with an empty set to indicate that all existing records
// should be deleted.
Optional<Host> host = loadByForeignKey(Host.class, hostName, clock.nowUtc());
Optional<Host> host = ForeignKeyUtils.loadResource(Host.class, hostName, clock.nowUtc());
// Return early if the host is deleted.
if (host.isEmpty()) {

View File

@@ -19,7 +19,6 @@ import static com.google.common.base.Verify.verify;
import static com.google.common.collect.Sets.intersection;
import static com.google.common.collect.Sets.union;
import static google.registry.dns.DnsUtils.getDnsAPlusAAAATtlForHost;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
@@ -28,6 +27,7 @@ import com.google.common.net.InternetDomainName;
import google.registry.config.RegistryConfig.Config;
import google.registry.dns.writer.BaseDnsWriter;
import google.registry.dns.writer.DnsWriterZone;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.domain.Domain;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.host.Host;
@@ -129,7 +129,8 @@ public class DnsUpdateWriter extends BaseDnsWriter {
* this domain refresh request
*/
private void publishDomain(String domainName, String requestingHostName) {
Optional<Domain> domainOptional = loadByForeignKey(Domain.class, domainName, clock.nowUtc());
Optional<Domain> domainOptional =
ForeignKeyUtils.loadResource(Domain.class, domainName, clock.nowUtc());
update.delete(toAbsoluteName(domainName), Type.ANY);
// If the domain is now deleted, then don't update DNS for it.
if (domainOptional.isPresent()) {
@@ -218,7 +219,7 @@ public class DnsUpdateWriter extends BaseDnsWriter {
private void addInBailiwickNameServerSet(Domain domain, Update update) {
for (String hostName :
intersection(domain.loadNameserverHostNames(), domain.getSubordinateHosts())) {
Optional<Host> host = loadByForeignKey(Host.class, hostName, clock.nowUtc());
Optional<Host> host = ForeignKeyUtils.loadResource(Host.class, hostName, clock.nowUtc());
checkState(host.isPresent(), "Host %s cannot be loaded", hostName);
update.add(makeAddressSet(host.get()));
update.add(makeV6AddressSet(host.get()));

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -1,107 +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>
<!-- TODO(b/26776367): Move these files to /assets/sources. -->
<url-pattern>/assets/js/registrar_bin.js.map</url-pattern>
<url-pattern>/assets/js/registrar_dbg.js</url-pattern>
<url-pattern>/assets/css/registrar_dbg.css</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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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()));

View File

@@ -38,7 +38,6 @@ import static google.registry.pricing.PricingEngineProxy.isDomainPremium;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import static org.json.simple.JSONValue.toJSONString;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
@@ -185,8 +184,7 @@ public class CheckApiAction implements Runnable {
}
private boolean checkExists(String domainString, DateTime now) {
return !ForeignKeyUtils.loadByCache(Domain.class, ImmutableList.of(domainString), now)
.isEmpty();
return ForeignKeyUtils.loadKeyByCache(Domain.class, domainString, now).isPresent();
}
private Optional<String> checkReserved(InternetDomainName domainName) {

View File

@@ -56,9 +56,10 @@ public final class FlowUtils {
}
}
/** Persists the saves and deletes in an {@link EntityChanges} to the DB. */
/** Persists the inserts, updates, and deletes in an {@link EntityChanges} to the DB. */
public static void persistEntityChanges(EntityChanges entityChanges) {
tm().putAll(entityChanges.getSaves());
tm().insertAll(entityChanges.getInserts());
tm().updateAll(entityChanges.getUpdates());
tm().delete(entityChanges.getDeletes());
}

View File

@@ -16,7 +16,6 @@ package google.registry.flows;
import static com.google.common.collect.Sets.intersection;
import static google.registry.model.EppResourceUtils.isLinked;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableSet;
@@ -72,10 +71,9 @@ public final class ResourceFlowUtils {
*/
public static <R extends EppResource> void checkLinkedDomains(
final String targetId, final DateTime now, final Class<R> resourceClass) throws EppException {
VKey<R> key = ForeignKeyUtils.load(resourceClass, targetId, now);
if (key == null) {
throw new ResourceDoesNotExistException(resourceClass, targetId);
}
VKey<R> key =
ForeignKeyUtils.loadKey(resourceClass, targetId, now)
.orElseThrow(() -> new ResourceDoesNotExistException(resourceClass, targetId));
if (isLinked(key, now)) {
throw new ResourceToDeleteIsReferencedException();
}
@@ -97,7 +95,7 @@ public final class ResourceFlowUtils {
public static <R extends EppResource & ForeignKeyedEppResource> R loadAndVerifyExistence(
Class<R> clazz, String targetId, DateTime now) throws ResourceDoesNotExistException {
return verifyExistence(clazz, targetId, loadByForeignKey(clazz, targetId, now));
return verifyExistence(clazz, targetId, ForeignKeyUtils.loadResource(clazz, targetId, now));
}
public static <R extends EppResource> R verifyExistence(
@@ -107,11 +105,10 @@ public final class ResourceFlowUtils {
public static <R extends EppResource> void verifyResourceDoesNotExist(
Class<R> clazz, String targetId, DateTime now, String registrarId) throws EppException {
VKey<R> key = ForeignKeyUtils.load(clazz, targetId, now);
if (key != null) {
R resource = tm().loadByKey(key);
Optional<R> resource = ForeignKeyUtils.loadResource(clazz, targetId, now);
if (resource.isPresent()) {
// These are similar exceptions, but we can track them internally as log-based metrics.
if (Objects.equals(registrarId, resource.getPersistedCurrentSponsorRegistrarId())) {
if (Objects.equals(registrarId, resource.get().getPersistedCurrentSponsorRegistrarId())) {
throw new ResourceAlreadyExistsForThisClientException(targetId);
} else {
throw new ResourceCreateContentionException(targetId);

View File

@@ -16,7 +16,6 @@ package google.registry.flows.contact;
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.verifyTargetIdCount;
import static google.registry.model.EppResourceUtils.checkResourcesExist;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -26,6 +25,7 @@ import google.registry.flows.ExtensionManager;
import google.registry.flows.FlowModule.RegistrarId;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactCommand.Check;
import google.registry.model.eppinput.ResourceCommand;
@@ -62,7 +62,7 @@ public final class ContactCheckFlow implements TransactionalFlow {
ImmutableList<String> targetIds = ((Check) resourceCommand).getTargetIds();
verifyTargetIdCount(targetIds, maxChecks);
ImmutableSet<String> existingIds =
checkResourcesExist(Contact.class, targetIds, clock.nowUtc());
ForeignKeyUtils.loadKeys(Contact.class, targetIds, clock.nowUtc()).keySet();
ImmutableList.Builder<ContactCheck> checks = new ImmutableList.Builder<>();
for (String id : targetIds) {
boolean unused = !existingIds.contains(id);

View File

@@ -19,25 +19,30 @@ import com.google.common.collect.ImmutableSet;
import google.registry.model.ImmutableObject;
import google.registry.persistence.VKey;
/** A record that encapsulates database entities to both save and delete. */
/** A record that encapsulates database entities to insert, update, and delete. */
public record EntityChanges(
ImmutableSet<ImmutableObject> saves, ImmutableSet<VKey<ImmutableObject>> deletes) {
ImmutableSet<ImmutableObject> inserts,
ImmutableSet<ImmutableObject> updates,
ImmutableSet<VKey<ImmutableObject>> deletes) {
public ImmutableSet<ImmutableObject> getSaves() {
return saves;
public ImmutableSet<ImmutableObject> getInserts() {
return inserts;
}
public ImmutableSet<ImmutableObject> getUpdates() {
return updates;
}
;
public ImmutableSet<VKey<ImmutableObject>> getDeletes() {
return deletes;
}
;
public static Builder newBuilder() {
// Default both entities to save and entities to delete to empty sets, so that the build()
// method won't subsequently throw an exception if one doesn't end up being applicable.
// Default inserts, updates, and deletes to empty sets, so that the build() method won't
// subsequently throw an exception if one doesn't end up being applicable.
return new AutoBuilder_EntityChanges_Builder()
.setSaves(ImmutableSet.of())
.setInserts(ImmutableSet.of())
.setUpdates(ImmutableSet.of())
.setDeletes(ImmutableSet.of());
}
@@ -45,12 +50,21 @@ public record EntityChanges(
@AutoBuilder
public interface Builder {
Builder setSaves(ImmutableSet<ImmutableObject> entitiesToSave);
Builder setInserts(ImmutableSet<ImmutableObject> entitiesToInsert);
ImmutableSet.Builder<ImmutableObject> savesBuilder();
ImmutableSet.Builder<ImmutableObject> insertsBuilder();
default Builder addSave(ImmutableObject entityToSave) {
savesBuilder().add(entityToSave);
default Builder addInsert(ImmutableObject entityToInsert) {
insertsBuilder().add(entityToInsert);
return this;
}
Builder setUpdates(ImmutableSet<ImmutableObject> entitiesToUpdate);
ImmutableSet.Builder<ImmutableObject> updatesBuilder();
default Builder addUpdate(ImmutableObject entityToUpdate) {
updatesBuilder().add(entityToUpdate);
return this;
}

View File

@@ -181,7 +181,7 @@ public final class DomainCheckFlow implements TransactionalFlow {
.setAsOfDate(now)
.build());
ImmutableMap<String, VKey<Domain>> existingDomains =
ForeignKeyUtils.load(Domain.class, domainNames, now);
ForeignKeyUtils.loadKeys(Domain.class, domainNames, now);
// Check block labels only when there are unregistered domains, since "In use" goes before
// "Blocked by BSA".
ImmutableSet<InternetDomainName> bsaBlockedDomainNames =

View File

@@ -18,7 +18,6 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.dns.DnsUtils.requestDomainDnsRefresh;
import static google.registry.flows.FlowUtils.persistEntityChanges;
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.verifyResourceDoesNotExist;
import static google.registry.flows.domain.DomainFlowUtils.COLLISION_MESSAGE;
import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld;
import static google.registry.flows.domain.DomainFlowUtils.checkHasBillingAccount;
@@ -196,7 +195,6 @@ import org.joda.time.Duration;
* @error {@link DomainFlowUtils.NameserversNotAllowedForTldException}
* @error {@link DomainFlowUtils.NameserversNotSpecifiedForTldWithNameserverAllowListException}
* @error {@link DomainFlowUtils.PremiumNameBlockedException}
* @error {@link DomainFlowUtils.RegistrantNotAllowedException}
* @error {@link RegistrantProhibitedException}
* @error {@link DomainFlowUtils.RegistrarMustBeActiveForThisOperationException}
* @error {@link DomainFlowUtils.TldDoesNotExistException}
@@ -224,6 +222,7 @@ public final class DomainCreateFlow implements MutatingFlow {
@Inject DomainCreateFlowCustomLogic flowCustomLogic;
@Inject DomainFlowTmchUtils tmchUtils;
@Inject DomainPricingLogic pricingLogic;
@Inject DomainDeletionTimeCache domainDeletionTimeCache;
@Inject DomainCreateFlow() {}
@@ -239,13 +238,13 @@ public final class DomainCreateFlow implements MutatingFlow {
validateRegistrarIsLoggedIn(registrarId);
verifyRegistrarIsActive(registrarId);
extensionManager.validate();
verifyDomainDoesNotExist();
DateTime now = tm().getTransactionTime();
DomainCommand.Create command = cloneAndLinkReferences((Create) resourceCommand, now);
Period period = command.getPeriod();
verifyUnitIsYears(period);
int years = period.getValue();
validateRegistrationPeriod(years);
verifyResourceDoesNotExist(Domain.class, targetId, now, registrarId);
// Validate that this is actually a legal domain name on a TLD that the registrar has access to.
InternetDomainName domainName = validateDomainName(command.getDomainName());
String domainLabel = domainName.parts().getFirst();
@@ -357,11 +356,11 @@ public final class DomainCreateFlow implements MutatingFlow {
domainHistoryId, registrationExpirationTime, isAnchorTenant, allocationToken);
PollMessage.Autorenew autorenewPollMessage =
createAutorenewPollMessage(domainHistoryId, registrationExpirationTime);
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
entitiesToSave.add(createBillingEvent, autorenewBillingEvent, autorenewPollMessage);
ImmutableSet.Builder<ImmutableObject> entitiesToInsert = new ImmutableSet.Builder<>();
entitiesToInsert.add(createBillingEvent, autorenewBillingEvent, autorenewPollMessage);
// Bill for EAP cost, if any.
if (!feesAndCredits.getEapCost().isZero()) {
entitiesToSave.add(createEapBillingEvent(feesAndCredits, createBillingEvent));
entitiesToInsert.add(createEapBillingEvent(feesAndCredits, createBillingEvent));
}
ImmutableSet<ReservationType> reservationTypes = getReservationTypes(domainName);
@@ -404,12 +403,13 @@ public final class DomainCreateFlow implements MutatingFlow {
DomainHistory domainHistory =
buildDomainHistory(domain, tld, now, period, tld.getAddGracePeriodLength());
if (reservationTypes.contains(NAME_COLLISION)) {
entitiesToSave.add(
entitiesToInsert.add(
createNameCollisionOneTimePollMessage(targetId, domainHistory, registrarId, now));
}
entitiesToSave.add(domain, domainHistory);
entitiesToInsert.add(domain, domainHistory);
ImmutableSet.Builder<ImmutableObject> entitiesToUpdate = new ImmutableSet.Builder<>();
if (allocationToken.isPresent() && allocationToken.get().getTokenType().isOneTimeUse()) {
entitiesToSave.add(
entitiesToUpdate.add(
AllocationTokenFlowUtils.redeemToken(
allocationToken.get(), domainHistory.getHistoryEntryId()));
}
@@ -422,7 +422,10 @@ public final class DomainCreateFlow implements MutatingFlow {
.setNewDomain(domain)
.setHistoryEntry(domainHistory)
.setEntityChanges(
EntityChanges.newBuilder().setSaves(entitiesToSave.build()).build())
EntityChanges.newBuilder()
.setInserts(entitiesToInsert.build())
.setUpdates(entitiesToUpdate.build())
.build())
.setYears(years)
.build());
persistEntityChanges(entityChanges);
@@ -649,6 +652,15 @@ public final class DomainCreateFlow implements MutatingFlow {
.build();
}
private void verifyDomainDoesNotExist() throws ResourceCreateContentionException {
Optional<DateTime> previousDeletionTime =
domainDeletionTimeCache.getDeletionTimeForDomain(targetId);
if (previousDeletionTime.isPresent()
&& !tm().getTransactionTime().isAfter(previousDeletionTime.get())) {
throw new ResourceCreateContentionException(targetId);
}
}
private static BillingEvent createEapBillingEvent(
FeesAndCredits feesAndCredits, BillingEvent createBillingEvent) {
return new BillingEvent.Builder()

View File

@@ -77,6 +77,7 @@ import google.registry.model.domain.fee.FeeTransformResponseExtension;
import google.registry.model.domain.fee06.FeeDeleteResponseExtensionV06;
import google.registry.model.domain.fee11.FeeDeleteResponseExtensionV11;
import google.registry.model.domain.fee12.FeeDeleteResponseExtensionV12;
import google.registry.model.domain.feestdv1.FeeDeleteResponseExtensionStdV1;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.secdns.SecDnsCreateExtension;
@@ -151,7 +152,7 @@ public final class DomainDeleteFlow implements MutatingFlow, SqlStatementLogging
verifyDeleteAllowed(existingDomain, tld, now);
flowCustomLogic.afterValidation(
AfterValidationParameters.newBuilder().setExistingDomain(existingDomain).build());
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
ImmutableSet.Builder<ImmutableObject> entitiesToInsert = new ImmutableSet.Builder<>();
Domain.Builder builder;
if (existingDomain.getStatusValues().contains(StatusValue.PENDING_TRANSFER)) {
builder =
@@ -221,7 +222,7 @@ public final class DomainDeleteFlow implements MutatingFlow, SqlStatementLogging
} else {
PollMessage.OneTime deletePollMessage =
createDeletePollMessage(existingDomain, domainHistoryId, deletionTime);
entitiesToSave.add(deletePollMessage);
entitiesToInsert.add(deletePollMessage);
builder.setDeletePollMessage(deletePollMessage.createVKey());
}
}
@@ -230,7 +231,7 @@ public final class DomainDeleteFlow implements MutatingFlow, SqlStatementLogging
// registrar other than the sponsoring registrar (which will necessarily be a superuser).
if (durationUntilDelete.isLongerThan(Duration.ZERO)
&& !registrarId.equals(existingDomain.getPersistedCurrentSponsorRegistrarId())) {
entitiesToSave.add(
entitiesToInsert.add(
createImmediateDeletePollMessage(existingDomain, domainHistoryId, now, deletionTime));
}
@@ -239,7 +240,7 @@ public final class DomainDeleteFlow implements MutatingFlow, SqlStatementLogging
for (GracePeriod gracePeriod : existingDomain.getGracePeriods()) {
// No cancellation is written if the grace period was not for a billable event.
if (gracePeriod.hasBillingEvent()) {
entitiesToSave.add(
entitiesToInsert.add(
BillingCancellation.forGracePeriod(gracePeriod, now, domainHistoryId, targetId));
if (gracePeriod.getBillingEvent() != null) {
// Take the amount of registration time being refunded off the expiration time.
@@ -271,7 +272,7 @@ public final class DomainDeleteFlow implements MutatingFlow, SqlStatementLogging
// ResourceDeleteFlow since it's listed in serverApproveEntities.
requestDomainDnsRefresh(existingDomain.getDomainName());
entitiesToSave.add(newDomain, domainHistory);
entitiesToInsert.add(domainHistory);
EntityChanges entityChanges =
flowCustomLogic.beforeSave(
BeforeSaveParameters.newBuilder()
@@ -279,7 +280,10 @@ public final class DomainDeleteFlow implements MutatingFlow, SqlStatementLogging
.setNewDomain(newDomain)
.setHistoryEntry(domainHistory)
.setEntityChanges(
EntityChanges.newBuilder().setSaves(entitiesToSave.build()).build())
EntityChanges.newBuilder()
.setInserts(entitiesToInsert.build())
.addUpdate(newDomain)
.build())
.build());
BeforeResponseReturnData responseData =
flowCustomLogic.beforeResponse(
@@ -425,6 +429,9 @@ public final class DomainDeleteFlow implements MutatingFlow, SqlStatementLogging
@Nullable
private FeeTransformResponseExtension.Builder getDeleteResponseBuilder() {
Set<String> uris = nullToEmpty(sessionMetadata.getServiceExtensionUris());
if (uris.contains(ServiceExtension.FEE_1_00.getUri())) {
return new FeeDeleteResponseExtensionStdV1.Builder();
}
if (uris.contains(ServiceExtension.FEE_0_12.getUri())) {
return new FeeDeleteResponseExtensionV12.Builder();
}

View File

@@ -0,0 +1,133 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.flows.domain;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Expiry;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.Ticker;
import com.google.common.collect.ImmutableSet;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.domain.Domain;
import google.registry.util.DateTimeUtils;
import java.util.Optional;
import org.joda.time.DateTime;
/**
* Functionally-static loading cache that keeps track of deletion (AKA drop) times for domains.
*
* <p>Some domain names may have many create requests issued shortly before (and directly after) the
* name is released due to a previous registrant deleting it. In those cases, caching the deletion
* time of the existing domain allows us to short-circuit the request and avoid any load on the
* database checking the existing domain (at least, in cases where the request hits a particular
* node more than once).
*
* <p>The cache is fairly short-lived (as we're concerned about many requests at basically the same
* time), and entries also expire when the drop actually happens. If the domain is re-created after
* a drop, the next load attempt will populate the cache with a deletion time of END_OF_TIME, which
* will be read from the cache by subsequent attempts.
*
* <p>We take advantage of the fact that Caffeine caches don't store nulls returned from the
* CacheLoader, so a null result (meaning the domain doesn't exist) won't affect future calls (this
* avoids a stale-cache situation where the cache "thinks" the domain doesn't exist, but it does).
* Put another way, if a domain really doesn't exist, we'll re-attempt the database load every time.
*
* <p>We don't explicitly set the cache inside domain create/delete flows, in case the transaction
* fails at commit time. It's better to have stale data, or to require an additional database load,
* than to have incorrect data.
*
* <p>Note: this should be injected as a singleton -- it's essentially static, but we have it as a
* non-static object for concurrent testing purposes.
*/
public class DomainDeletionTimeCache {
// Max expiry time is ten minutes
private static final long NANOS_IN_ONE_MILLISECOND = 100000L;
private static final long MAX_EXPIRY_NANOS = 10L * 60L * 1000L * NANOS_IN_ONE_MILLISECOND;
private static final int MAX_ENTRIES = 500;
/**
* Expire after the max duration, or after the domain is set to drop (whichever comes first).
*
* <p>If the domain has already been deleted (the deletion time is <= now), the entry will
* immediately be expired/removed.
*
* <p>NB: the Expiry class requires the return value in <b>nanoseconds</b>, not milliseconds
*/
private static final Expiry<String, DateTime> EXPIRY_POLICY =
new Expiry<>() {
@Override
public long expireAfterCreate(String key, DateTime value, long currentTime) {
// Watch out for Long overflow
long deletionTimeNanos =
value.equals(DateTimeUtils.END_OF_TIME)
? Long.MAX_VALUE
: value.getMillis() * NANOS_IN_ONE_MILLISECOND;
long nanosUntilDeletion = deletionTimeNanos - currentTime;
return Math.max(0L, Math.min(MAX_EXPIRY_NANOS, nanosUntilDeletion));
}
/** Reset the time entirely on update, as if we were creating the entry anew. */
@Override
public long expireAfterUpdate(
String key, DateTime value, long currentTime, long currentDuration) {
return expireAfterCreate(key, value, currentTime);
}
/** Reads do not change the expiry duration. */
@Override
public long expireAfterRead(
String key, DateTime value, long currentTime, long currentDuration) {
return currentDuration;
}
};
/** Attempt to load the domain's deletion time if the domain exists. */
private static final CacheLoader<String, DateTime> CACHE_LOADER =
(domainName) -> {
ForeignKeyUtils.MostRecentResource mostRecentResource =
ForeignKeyUtils.loadMostRecentResources(
Domain.class, ImmutableSet.of(domainName), false)
.get(domainName);
return mostRecentResource == null ? null : mostRecentResource.deletionTime();
};
// Unfortunately, maintenance tasks aren't necessarily already in a transaction
private static final Ticker TRANSACTION_TIME_TICKER =
() -> tm().reTransact(() -> tm().getTransactionTime().getMillis() * NANOS_IN_ONE_MILLISECOND);
public static DomainDeletionTimeCache create() {
return new DomainDeletionTimeCache(
Caffeine.newBuilder()
.ticker(TRANSACTION_TIME_TICKER)
.expireAfter(EXPIRY_POLICY)
.maximumSize(MAX_ENTRIES)
.build(CACHE_LOADER));
}
private final LoadingCache<String, DateTime> cache;
private DomainDeletionTimeCache(LoadingCache<String, DateTime> cache) {
this.cache = cache;
}
/** Returns the domain's deletion time, or null if it doesn't currently exist. */
public Optional<DateTime> getDeletionTimeForDomain(String domainName) {
return Optional.ofNullable(cache.get(domainName));
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,17 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
.console-tlds {
&__cards {
display: flex;
border-top: 1px solid #ddd;
padding: 1rem;
}
&__card {
max-width: 300px;
}
&__card-links {
display: flex;
flex-direction: column;
package google.registry.flows.domain;
import dagger.Module;
import dagger.Provides;
import jakarta.inject.Singleton;
/** Dagger module to provide the {@link DomainDeletionTimeCache}. */
@Module
public class DomainDeletionTimeCacheModule {
@Provides
@Singleton
public static DomainDeletionTimeCache provideDomainDeletionTimeCache() {
return DomainDeletionTimeCache.create();
}
}

View File

@@ -553,17 +553,6 @@ public class DomainFlowUtils {
}
}
static void validateRegistrantAllowedOnTld(String tld, Optional<String> registrantContactId)
throws RegistrantNotAllowedException {
ImmutableSet<String> allowedRegistrants = Tld.get(tld).getAllowedRegistrantContactIds();
// Empty allow list or null registrantContactId are ignored.
if (registrantContactId.isPresent()
&& !allowedRegistrants.isEmpty()
&& !allowedRegistrants.contains(registrantContactId.get())) {
throw new RegistrantNotAllowedException(registrantContactId.get());
}
}
static void validateNameserversAllowedOnTld(String tld, Set<String> fullyQualifiedHostNames)
throws EppException {
ImmutableSet<String> allowedHostNames = Tld.get(tld).getAllowedFullyQualifiedHostNames();
@@ -711,7 +700,7 @@ public class DomainFlowUtils {
BillingRecurrence newBillingRecurrence =
existingBillingRecurrence.asBuilder().setRecurrenceEndTime(newEndTime).build();
tm().put(newBillingRecurrence);
tm().update(newBillingRecurrence);
return newBillingRecurrence;
}
@@ -1091,7 +1080,6 @@ public class DomainFlowUtils {
command.getContacts(), command.getRegistrant(), command.getNameservers());
validateContactsHaveTypes(command.getContacts());
String tldStr = tld.getTldStr();
validateRegistrantAllowedOnTld(tldStr, command.getRegistrantContactId());
validateNoDuplicateContacts(command.getContacts());
validateCreateContactData(command.getRegistrant(), command.getContacts());
ImmutableSet<String> hostNames = command.getNameserverHostNames();
@@ -1630,13 +1618,6 @@ public class DomainFlowUtils {
}
}
/** Registrant is not allow-listed for this TLD. */
public static class RegistrantNotAllowedException extends StatusProhibitsOperationException {
public RegistrantNotAllowedException(String contactId) {
super(String.format("Registrant with id %s is not allow-listed for this TLD", contactId));
}
}
/** Nameservers are not allow-listed for this TLD. */
public static class NameserversNotAllowedForTldException
extends StatusProhibitsOperationException {

View File

@@ -15,12 +15,11 @@
package google.registry.flows.domain;
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.verifyExistence;
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
import static google.registry.flows.domain.DomainFlowUtils.addSecDnsExtensionIfPresent;
import static google.registry.flows.domain.DomainFlowUtils.handleFeeRequest;
import static google.registry.flows.domain.DomainFlowUtils.loadForeignKeyedDesignatedContacts;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableList;
@@ -108,8 +107,7 @@ public final class DomainInfoFlow implements MutatingFlow {
validateRegistrarIsLoggedIn(registrarId);
extensionManager.validate();
DateTime now = clock.nowUtc();
Domain domain =
verifyExistence(Domain.class, targetId, loadByForeignKey(Domain.class, targetId, now));
Domain domain = loadAndVerifyExistence(Domain.class, targetId, now);
verifyOptionalAuthInfo(authInfo, domain);
flowCustomLogic.afterValidation(
AfterValidationParameters.newBuilder().setDomain(domain).build());

View File

@@ -245,11 +245,13 @@ public final class DomainRenewFlow implements MutatingFlow {
.build();
DomainHistory domainHistory =
buildDomainHistory(newDomain, now, command.getPeriod(), tld.getRenewGracePeriodLength());
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
entitiesToSave.add(
newDomain, domainHistory, explicitRenewEvent, newAutorenewEvent, newAutorenewPollMessage);
ImmutableSet.Builder<ImmutableObject> entitiesToInsert = new ImmutableSet.Builder<>();
ImmutableSet.Builder<ImmutableObject> entitiesToUpdate = new ImmutableSet.Builder<>();
entitiesToInsert.add(
domainHistory, explicitRenewEvent, newAutorenewEvent, newAutorenewPollMessage);
entitiesToUpdate.add(newDomain);
if (allocationToken.isPresent() && allocationToken.get().getTokenType().isOneTimeUse()) {
entitiesToSave.add(
entitiesToUpdate.add(
AllocationTokenFlowUtils.redeemToken(
allocationToken.get(), domainHistory.getHistoryEntryId()));
}
@@ -262,7 +264,10 @@ public final class DomainRenewFlow implements MutatingFlow {
.setYears(years)
.setHistoryEntry(domainHistory)
.setEntityChanges(
EntityChanges.newBuilder().setSaves(entitiesToSave.build()).build())
EntityChanges.newBuilder()
.setInserts(entitiesToInsert.build())
.setUpdates(entitiesToUpdate.build())
.build())
.build());
BeforeResponseReturnData responseData =
flowCustomLogic.beforeResponse(

View File

@@ -146,18 +146,18 @@ public final class DomainRestoreRequestFlow implements MutatingFlow {
verifyRestoreAllowed(command, existingDomain, feeUpdate, feesAndCredits, now);
HistoryEntryId domainHistoryId = createHistoryEntryId(existingDomain);
historyBuilder.setRevisionId(domainHistoryId.getRevisionId());
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
ImmutableSet.Builder<ImmutableObject> entitiesToInsert = new ImmutableSet.Builder<>();
DateTime newExpirationTime =
existingDomain.getRegistrationExpirationTime().plusYears(isExpired ? 1 : 0);
// Restore the expiration time on the deleted domain, except if that's already passed, then add
// a year and bill for it immediately, with no grace period.
if (isExpired) {
entitiesToSave.add(
entitiesToInsert.add(
createRenewBillingEvent(domainHistoryId, feesAndCredits.getRenewCost(), now));
}
// Always bill for the restore itself.
entitiesToSave.add(
entitiesToInsert.add(
createRestoreBillingEvent(domainHistoryId, feesAndCredits.getRestoreCost(), now));
BillingRecurrence autorenewEvent =
@@ -166,12 +166,14 @@ public final class DomainRestoreRequestFlow implements MutatingFlow {
.setRecurrenceEndTime(END_OF_TIME)
.setDomainHistoryId(domainHistoryId)
.build();
entitiesToInsert.add(autorenewEvent);
PollMessage.Autorenew autorenewPollMessage =
newAutorenewPollMessage(existingDomain)
.setEventTime(newExpirationTime)
.setAutorenewEndTime(END_OF_TIME)
.setDomainHistoryId(domainHistoryId)
.build();
entitiesToInsert.add(autorenewPollMessage);
Domain newDomain =
performRestore(
existingDomain,
@@ -181,8 +183,9 @@ public final class DomainRestoreRequestFlow implements MutatingFlow {
now,
registrarId);
DomainHistory domainHistory = buildDomainHistory(newDomain, now);
entitiesToSave.add(newDomain, domainHistory, autorenewEvent, autorenewPollMessage);
tm().putAll(entitiesToSave.build());
entitiesToInsert.add(domainHistory);
tm().update(newDomain);
tm().insertAll(entitiesToInsert.build());
if (existingDomain.getDeletePollMessage() != null) {
tm().delete(existingDomain.getDeletePollMessage());
}

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