1
0
mirror of https://github.com/google/nomulus synced 2026-07-09 01:27:09 +00:00

Compare commits

...

8 Commits

Author SHA1 Message Date
Ben McIlwain 53b92d602e Migrate EPP/Email from Soy to JAXB/FreeMarker (#3038)
- Replace deprecated Soy templates for EPP XML with JAXB models and a refined Fluent DSL.
- Migrate Spec11 and administrative emails to FreeMarker with HTML auto-escaping.
- Remove Soy compiler, Gradle tasks, and library dependencies.
- Address PR feedback regarding shadowing, version locking, and security warnings.
- Enhance tests with comprehensive XML equality assertions using Java 15 text blocks.
- Improve Javadocs and maintain strict temporal consistency using java.time.

FreeMarker replaces Soy for email templating, providing native HTML auto-escaping and allowing the removal of the complex 'soyToJava' compilation step from the build process. This significantly simplifies the build system and reduces maintenance overhead. For EPP XML, migrating to JAXB allows tool-generated commands to use the same model classes as the server-side EPP flows. This ensures that tool-generated XML is always schema-compliant and eliminates the risk of divergence between tool templates and actual server-side implementation. This unified approach provides compile-time type safety and improves developer ergonomics via a refined fluent DSL.

The base ImmutableObject class now provides a public clone() override that correctly resets the cached hashCode to null. This centralizes the custom cloning logic previously handled by a static helper and ensures that all subclasses—including the newly added JAXB models—satisfy CodeQL security requirements without needing redundant per-class overrides. The legacy static clone(T) helper has been updated to delegate to this instance method to maintain compatibility and architectural consistency.
2026-05-22 19:33:47 +00:00
gbrodman b3fc57c7f7 Inject a singleton Gson instead of creating it many times (#3058)
Creation of Gson objects is nontrivial and it's thread-safe so we might
as well just use some singleton objects as much as possible rather than
recreating them.
2026-05-22 15:20:23 +00:00
Juan Celhay bd70cd91a5 Add canary strategy to crash stage in Cloud Deploy pipeline (#3057)
* Add customCanaryDeployment strategy to crash

* change key to partialDeploymentAlertPolicyChecks

* add more indentation to alert policies config
2026-05-21 19:39:46 +00:00
gbrodman 73725e94fe Avoid injection of a possibly-null string value if thee Valkey cert key doesn't exist (#3055) 2026-05-20 20:02:35 +00:00
Juan Celhay c3f8ec8c85 Generate partial phases kubernetes manifests in CB release job (#3048)
* generate kubernetes partial/canary manifests in release job

* rename partial phase labels

* replace container name value to be stage dependent

* just keep the new partial deployment update in the manifest generation
2026-05-20 16:44:45 +00:00
Weimin Yu 3894ca6971 More debugging code for replyTo address (#3053)
The  replyTo header works in manual tests. Add code emulating
BillingEmailUtils behavior and see if that causes the problem.

Also experimenting not to set the From header in GmaiClient, since
whatever we set is overridden anyway.
2026-05-19 21:02:01 +00:00
Weimin Yu 5f06581572 Add a reminder to run update_dependency to PR authors (#3050)
After the public-access removal from GCS buckets, the Kokoro tests can
no longer use our private repo for resolve dependencies. And breakage is
discovered only during build.

This PR lets Github to create review comment, which triggers on PRs that
contain *.lockfile changes and asks the PR author to confirm that the
update_dependency script has been executed.
2026-05-18 19:27:22 +00:00
gbrodman 17b851de42 Remove remaining references to contacts in XML files and flows (#2979)
This requires moving phone numbers from the contact XSD file to the
eppcommon XSD file (they're still used by registrars).

The remaining changes are related to removing the XML infrastructure
that allows for contacts and any uses of contacts.

We shouldn't merge this until
https://github.com/google/nomulus/pull/2954 is deployed to production
and has had a little bit to bake and make sure that nothing is wrong.
2026-05-18 18:55:00 +00:00
343 changed files with 3025 additions and 7497 deletions
@@ -0,0 +1,48 @@
name: Request Lockfile Review
on:
pull_request_target:
branches: ["master"]
types: [opened, synchronize, reopened]
jobs:
review-lockfiles:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
# We intentionally do NOT use actions/checkout here.
# This keeps the environment completely secure and satisfies CodeQL.
- name: Check files via GitHub API
id: check_files
uses: actions/github-script@v7
with:
script: |
const prNumber = context.payload.pull_request.number;
// Get the list of files in the PR directly from the API
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
per_page: 100
});
// Look for any file **ending** in gradle.lockfile
const hasLockfile = files.some(file => file.filename.endsWith('gradle.lockfile'));
core.setOutput('has_lockfile', hasLockfile ? 'true' : 'false');
- name: Post unresolved review comment
if: steps.check_files.outputs.has_lockfile == 'true'
uses: actions/github-script@v7
with:
script: |
await github.rest.pulls.createReview({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
event: 'REQUEST_CHANGES',
body: `### ⚠️ Attention Required: Lockfile Detected\nThis pull request contains modifications to one or more \`*.lockfile\` files. Please confirm that you have run update_dependency.sh to push new dependencies to the private repo.\n\n_Someone with Admin role must manually dismiss this review before merging._`
});
+1
View File
@@ -58,6 +58,7 @@ dependencies {
implementation deps['com.google.code.findbugs:jsr305']
implementation deps['com.google.guava:guava']
implementation deps['jakarta.inject:jakarta.inject-api']
implementation deps['org.freemarker:freemarker']
implementation deps['com.google.flogger:flogger']
implementation deps['io.github.java-diff-utils:java-diff-utils']
implementation deps['com.google.truth:truth']
+2 -1
View File
@@ -34,7 +34,7 @@ commons-collections:commons-collections:3.2.2=checkstyle
info.picocli:picocli:4.7.7=checkstyle
io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,testAnnotationProcessor,testingAnnotationProcessor
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,testAnnotationProcessor,testingAnnotationProcessor
io.github.java-diff-utils:java-diff-utils:4.16=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
io.github.java-diff-utils:java-diff-utils:4.17=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
jakarta.inject:jakarta.inject-api:2.0.1=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
javax.inject:javax.inject:1=annotationProcessor,testAnnotationProcessor,testingAnnotationProcessor
junit:junit:4.13.2=testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
@@ -60,6 +60,7 @@ org.codehaus.plexus:plexus-classworlds:2.6.0=checkstyle
org.codehaus.plexus:plexus-component-annotations:2.1.0=checkstyle
org.codehaus.plexus:plexus-container-default:2.1.0=checkstyle
org.codehaus.plexus:plexus-utils:3.3.0=checkstyle
org.freemarker:freemarker:2.3.34=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
org.hamcrest:hamcrest-core:1.3=testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
org.jacoco:org.jacoco.agent:0.8.14=jacocoAgent,jacocoAnt
org.jacoco:org.jacoco.ant:0.8.14=jacocoAnt
@@ -0,0 +1,67 @@
// Copyright 2026 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.util;
import com.google.common.collect.ImmutableMap;
import freemarker.core.HTMLOutputFormat;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import jakarta.inject.Inject;
import java.io.StringWriter;
/**
* A utility class for rendering FreeMarker templates.
*
* <p>This renderer is configured to use HTML as the default output format, which enables automatic
* escaping of all interpolated variables. It also uses the "computer" number format to ensure
* consistent formatting of numeric values across different locales.
*/
public class TemplateRenderer {
private final Configuration configuration;
@Inject
public TemplateRenderer() {
this.configuration = new Configuration(Configuration.VERSION_2_3_32);
this.configuration.setClassLoaderForTemplateLoading(getClass().getClassLoader(), "");
this.configuration.setDefaultEncoding("UTF-8");
this.configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
this.configuration.setLogTemplateExceptions(false);
this.configuration.setWrapUncheckedExceptions(true);
this.configuration.setFallbackOnNullLoopVariable(false);
this.configuration.setOutputFormat(HTMLOutputFormat.INSTANCE);
this.configuration.setNumberFormat("computer");
}
/**
* Renders the specified template with the given data model.
*
* @param templatePath the path to the template file relative to the classpath root
* @param dataModel an immutable map containing the data to be used in the template
* @return the rendered template as a string
* @throws RuntimeException if the template cannot be found, parsed, or processed
*/
public String render(String templatePath, ImmutableMap<String, Object> dataModel) {
try {
Template template = configuration.getTemplate(templatePath);
StringWriter writer = new StringWriter();
template.process(dataModel, writer);
return writer.toString();
} catch (Exception e) {
throw new RuntimeException(String.format("Error rendering template %s", templatePath), e);
}
}
}
@@ -0,0 +1,83 @@
// Copyright 2026 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.util;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableMap;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link TemplateRenderer}. */
class TemplateRendererTest {
private final TemplateRenderer renderer = new TemplateRenderer();
@Test
void testRender_success() {
ImmutableMap<String, Object> data =
ImmutableMap.of(
"name", "World", "score", 42, "showMessage", true, "message", "Keep going!");
String result = renderer.render("google/registry/util/test_template.ftl", data);
assertThat(result).isEqualTo("Hello World!\nYour score is 42.\nMessage: Keep going!\n");
}
@Test
void testRender_conditional_false() {
ImmutableMap<String, Object> data =
ImmutableMap.of("name", "User", "score", 0, "showMessage", false);
String result = renderer.render("google/registry/util/test_template.ftl", data);
assertThat(result).isEqualTo("Hello User!\nYour score is 0.\n");
}
@Test
void testRender_htmlEscaping() {
ImmutableMap<String, Object> data =
ImmutableMap.of("name", "<b>World</b>", "score", 42, "showMessage", false);
String result = renderer.render("google/registry/util/test_template.ftl", data);
assertThat(result).contains("Hello &lt;b&gt;World&lt;/b&gt;!");
}
@Test
void testRender_missingTemplate_throwsException() {
assertThrows(
RuntimeException.class,
() -> renderer.render("non/existent/template.ftl", ImmutableMap.of()));
}
@Test
void testRender_missingVariable_throwsException() {
// The template expects 'name', 'score', and 'showMessage', but the map is empty.
assertThrows(
RuntimeException.class,
() -> renderer.render("google/registry/util/test_template.ftl", ImmutableMap.of()));
}
@Test
void testRender_unusedVariable_ignored() {
ImmutableMap<String, Object> data =
ImmutableMap.of(
"name",
"User",
"score",
100,
"showMessage",
false,
"unusedKey",
"This should be ignored");
String result = renderer.render("google/registry/util/test_template.ftl", data);
assertThat(result).isEqualTo("Hello User!\nYour score is 100.\n");
}
}
@@ -0,0 +1,6 @@
<#-- Copyright 2026 The Nomulus Authors. All Rights Reserved. -->
Hello ${name}!
Your score is ${score}.
<#if showMessage>
Message: ${message}
</#if>
+3 -30
View File
@@ -90,15 +90,15 @@ PRESUBMITS = {
# License check
PresubmitCheck(
r".*Copyright 20\d{2} The Nomulus Authors\. All Rights Reserved\.",
("java", "js", "soy", "sql", "py", "sh", "gradle", "ts"), {
("java", "js", "sql", "py", "sh", "gradle", "ts", "ftl"), {
".git", "/build/", "node_modules/", "LoggerConfig.java", "registrar_bin.",
"registrar_dbg.", "google-java-format-diff.py",
"nomulus.golden.sql", "soyutils_usegoog.js", "javascript/checks.js"
"nomulus.golden.sql", "javascript/checks.js"
}, REQUIRED):
"File did not include the license header.",
# Files must end in a newline
PresubmitCheck(r".*\n$", ("java", "js", "soy", "sql", "py", "sh", "gradle", "ts", "xml"),
PresubmitCheck(r".*\n$", ("java", "js", "sql", "py", "sh", "gradle", "ts", "xml", "ftl"),
{"node_modules/", ".idea"}, REQUIRED):
"Source files must end in a newline.",
@@ -127,33 +127,6 @@ PRESUBMITS = {
"System.(out|err).println is only allowed in tools/ packages. Please "
"use a logger instead.",
# Various Soy linting checks
PresubmitCheck(
r".* (/\*)?\* {?@param ",
"soy",
{},
):
"In SOY please use the ({@param name: string} /** User name. */) style"
" parameter passing instead of the ( * @param name User name.) style "
"parameter passing.",
PresubmitCheck(
r'.*\{[^}]+\w+:\s+"',
"soy",
{},
):
"Please don't use double-quoted string literals in Soy parameters",
PresubmitCheck(
r'.*autoescape\s*=\s*"[^s]',
"soy",
{},
):
"All soy templates must use strict autoescaping",
PresubmitCheck(
r".*noAutoescape",
"soy",
{},
):
"All soy templates must use strict autoescaping",
PresubmitCheck(
r".*\nimport\s+(?:static\s+)?.*\.shaded\..*",
"java",
+2 -61
View File
@@ -94,7 +94,6 @@ processTestResources {
configurations {
jaxb
soy
devtool
nonprodImplementation.extendsFrom implementation
@@ -120,7 +119,7 @@ configurations {
// For reasons we do not understand, marking the following dependencies as
// compileOnly instead of compile does not exclude them from runtimeClasspath.
all {
// servlet-api:3.1 pulled in but not used by soy compiler
// servlet-api:3.1 pulled in but not used
exclude group: 'javax.servlet', module: 'javax.servlet-api'
}
}
@@ -181,7 +180,7 @@ dependencies {
implementation deps['com.google.oauth-client:google-oauth-client-jetty']
implementation deps['com.google.oauth-client:google-oauth-client-servlet']
implementation deps['com.google.re2j:re2j']
implementation deps['com.google.template:soy']
implementation deps['org.freemarker:freemarker']
implementation deps['com.googlecode.json-simple:json-simple']
implementation deps['com.jcraft:jsch']
implementation deps['com.zaxxer:HikariCP']
@@ -299,9 +298,6 @@ dependencies {
jaxb deps['org.glassfish.jaxb:jaxb-runtime']
jaxb deps['org.glassfish.jaxb:jaxb-xjc']
// Dependency needed for soy to java compilation.
soy deps['com.google.template:soy']
// Flyway classes needed to generate the golden file.
implementation deps['org.flywaydb:flyway-core']
implementation deps['org.flywaydb:flyway-database-postgresql']
@@ -371,62 +367,7 @@ task jaxbToJava {
}
}
task soyToJava {
// Relative paths of soy directories.
def spec11SoyDir = "google/registry/reporting/spec11/soy"
def toolsSoyDir = "google/registry/tools/soy"
def soyRelativeDirs = [spec11SoyDir, toolsSoyDir]
soyRelativeDirs.each {
inputs.dir "${resourcesSourceDir}/${it}"
outputs.dir "${generatedDir}/${it}"
}
ext.soyToJava = { javaPackage, outputDirectory, soyFiles ->
project.services.get(ExecOperations).javaexec {
mainClass = "com.google.template.soy.SoyParseInfoGenerator"
classpath = configurations.soy
jvmArgs = ["--sun-misc-unsafe-memory-access=allow", "--enable-native-access=ALL-UNNAMED"]
args = ["--javaPackage", "${javaPackage}",
"--outputDirectory", "${outputDirectory}",
"--javaClassNameSource", "filename",
"--srcs", "${soyFiles.join(',')}"]
}
// Replace the "@link" tags after the "@deprecated" tags in the generated
// files. The soy compiler doesn't generate imports for these, causing
// us to get warnings when we generate javadocs.
// TODO(b/200296387): To be fair, the deprecations are accurate: we're
// using the old "SoyInfo" classes instead of the new "Templates" files.
// When we convert to the new classes, this hack can go away.
def outputs = fileTree(outputDirectory) {
include '**/*.java'
}
outputs.each { file ->
project.services.get(ExecOperations).exec {
commandLine = ['sed', '-i""', '-e', 's/@link/LINK/g', file.getCanonicalPath()]
}
}
}
doLast {
soyToJava('google.registry.tools.soy',
"${generatedDir}/${toolsSoyDir}",
fileTree(
dir: "${resourcesSourceDir}/${toolsSoyDir}",
include: ['**/*.soy']))
soyToJava('google.registry.reporting.spec11.soy',
"${generatedDir}/${spec11SoyDir}",
fileTree(
dir: "${resourcesSourceDir}/${spec11SoyDir}",
include: ['**/*.soy']))
}
}
compileJava.dependsOn jaxbToJava
compileJava.dependsOn soyToJava
// Make testing artifacts available to be depended up on by other projects.
// TODO: factor out google.registry.testing to be a separate project.
+18 -37
View File
@@ -1,8 +1,7 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
aopalliance:aopalliance:1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
args4j:args4j:2.33=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
args4j:args4j:2.33=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.charleskorn.kaml:kaml:0.20.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-core:2.20.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -81,13 +80,13 @@ com.google.api:gax-grpc:2.80.0=testCompileClasspath,testRuntimeClasspath
com.google.api:gax-httpjson:2.74.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.api:gax-httpjson:2.80.0=testCompileClasspath,testRuntimeClasspath
com.google.api:gax:2.74.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-admin-directory:directory_v1-rev20260227-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-admin-directory:directory_v1-rev20260421-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-bigquery:v2-rev20251012-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-cloudresourcemanager:v1-rev20250606-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-dataflow:v1b3-rev20260405-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-dataflow:v1b3-rev20260503-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-dns:v1-rev20260421-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-drive:v3-rev20260428-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-gmail:v1-rev20260427-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-gmail:v1-rev20260511-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-groupssettings:v1-rev20220614-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-healthcare:v1-rev20240130-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-iam:v2-rev20250502-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -138,33 +137,26 @@ com.google.cloud:google-cloud-tasks:2.51.0=compileClasspath,deploy_jar,nonprodCo
com.google.cloud:grpc-gcp:1.9.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:libraries-bom:26.48.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.37.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.code.gson:gson:2.10.1=soy
com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.code.gson:gson:2.13.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.common.html.types:types:1.0.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
com.google.dagger:dagger-compiler:2.59.2=annotationProcessor,testAnnotationProcessor
com.google.dagger:dagger-spi:2.59.2=annotationProcessor,testAnnotationProcessor
com.google.dagger:dagger:2.59.2=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.devtools.ksp:symbol-processing-api:2.2.20-2.0.3=annotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_annotation:2.48.0=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.20.0=soy
com.google.errorprone:error_prone_annotations:2.36.0=checkstyle
com.google.errorprone:error_prone_annotations:2.48.0=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.49.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.errorprone:error_prone_check_api:2.48.0=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_core:2.48.0=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.escapevelocity:escapevelocity:1.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
com.google.flatbuffers:flatbuffers-java:24.3.25=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.flogger:flogger-system-backend:0.7.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
com.google.flogger:flogger:0.7.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
com.google.flogger:google-extensions:0.7.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
com.google.flogger:flogger-system-backend:0.7.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.flogger:flogger:0.7.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.flogger:google-extensions:0.7.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.googlejavaformat:google-java-format:1.34.1=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.guava:failureaccess:1.0.1=soy
com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.guava:guava-parent:32.1.1-jre=soy
com.google.guava:guava-testlib:33.3.0-jre=testRuntimeClasspath
com.google.guava:guava-testlib:33.6.0-jre=testCompileClasspath
com.google.guava:guava:32.1.1-jre=soy
com.google.guava:guava:33.4.8-jre=checkstyle
com.google.guava:guava:33.5.0-jre=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.guava:guava:33.6.0-jre=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -179,10 +171,8 @@ com.google.http-client:google-http-client-jackson2:1.46.3=compileClasspath,deplo
com.google.http-client:google-http-client-jackson2:2.1.0=testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-protobuf:2.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client:2.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.inject:guice:7.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
com.google.j2objc:j2objc-annotations:3.0.0=checkstyle
com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,compileClasspath,deploy_jar,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.jsinterop:jsinterop-annotations:1.0.1=soy
com.google.jsinterop:jsinterop-annotations:2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.monitoring-client:contrib:1.0.7=testCompileClasspath,testRuntimeClasspath
com.google.monitoring-client:metrics:1.0.7=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -195,15 +185,13 @@ com.google.oauth-client:google-oauth-client-servlet:1.36.0=deploy_jar,nonprodRun
com.google.oauth-client:google-oauth-client-servlet:1.39.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.oauth-client:google-oauth-client:1.39.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.protobuf:protobuf-java-util:4.33.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.protobuf:protobuf-java-util:4.35.0-RC2=testCompileClasspath,testRuntimeClasspath
com.google.protobuf:protobuf-java:3.21.7=soy
com.google.protobuf:protobuf-java-util:4.35.0=testCompileClasspath,testRuntimeClasspath
com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.protobuf:protobuf-java:4.35.0-RC2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.protobuf:protobuf-java:4.35.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.re2j:re2j:1.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.template:soy:2024-02-26=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
com.google.truth:truth:1.4.5=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.googlecode.json-simple:json-simple:1.1.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.ibm.icu:icu4j:73.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
com.ibm.icu:icu4j:73.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.jcraft:jsch:0.1.55=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.lmax:disruptor:3.4.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.puppycrawl.tools:checkstyle:10.24.0=checkstyle
@@ -252,7 +240,7 @@ io.apicurio:apicurio-registry-protobuf-schema-utilities:3.0.0.M2=compileClasspat
io.github.classgraph:classgraph:4.8.162=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
io.github.java-diff-utils:java-diff-utils:4.16=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.github.java-diff-utils:java-diff-utils:4.17=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.github.ss-bhatt:testcontainers-valkey:1.0.0=testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-alts:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.grpc:grpc-alts:1.81.0=testCompileClasspath,testRuntimeClasspath
@@ -351,7 +339,7 @@ jakarta-regexp:jakarta-regexp:1.4=compileClasspath,deploy_jar,nonprodCompileClas
jakarta.activation:jakarta.activation-api:2.1.4=jaxb
jakarta.activation:jakarta.activation-api:2.2.0-M1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
jakarta.activation:jakarta.activation-api:2.2.0-M2=compileClasspath,nonprodCompileClasspath,testCompileClasspath
jakarta.inject:jakarta.inject-api:2.0.1=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
jakarta.inject:jakarta.inject-api:2.0.1=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
jakarta.mail:jakarta.mail-api:2.2.0-M1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.persistence:jakarta.persistence-api:3.2.0=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
jakarta.servlet:jakarta.servlet-api:6.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -360,8 +348,7 @@ jakarta.xml.bind:jakarta.xml.bind-api:4.0.4=deploy_jar,nonprodRuntimeClasspath,r
jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=jaxb
jakarta.xml.bind:jakarta.xml.bind-api:4.1.0-M1=compileClasspath,nonprodCompileClasspath,testCompileClasspath
javax.annotation:javax.annotation-api:1.3.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
javax.annotation:jsr250-api:1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
javax.inject:javax.inject:1=annotationProcessor,compileClasspath,deploy_jar,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
javax.inject:javax.inject:1=annotationProcessor,compileClasspath,deploy_jar,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
javax.jdo:jdo2-api:2.3-20090302111651=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
javax.validation:validation-api:1.0.0.GA=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
joda-time:joda-time:2.14.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -438,10 +425,9 @@ org.bouncycastle:bcpg-jdk18on:1.84=compileClasspath,deploy_jar,nonprodCompileCla
org.bouncycastle:bcpkix-jdk18on:1.84=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.bouncycastle:bcprov-jdk18on:1.84=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.bouncycastle:bcutil-jdk18on:1.84=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.checkerframework:checker-compat-qual:2.5.3=annotationProcessor,compileClasspath,nonprodCompileClasspath,soy,testAnnotationProcessor,testCompileClasspath
org.checkerframework:checker-compat-qual:2.5.3=annotationProcessor,compileClasspath,nonprodCompileClasspath,testAnnotationProcessor,testCompileClasspath
org.checkerframework:checker-compat-qual:2.5.6=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.checkerframework:checker-qual:3.19.0=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
org.checkerframework:checker-qual:3.33.0=soy
org.checkerframework:checker-qual:3.49.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.checkerframework:checker-qual:3.49.3=checkstyle
org.codehaus.mojo:animal-sniffer-annotations:1.24=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
@@ -465,8 +451,9 @@ org.eclipse.jetty:jetty-server:12.1.9=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-session:12.1.9=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-util:12.1.9=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-xml:12.1.9=testCompileClasspath,testRuntimeClasspath
org.flywaydb:flyway-core:12.6.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.flywaydb:flyway-database-postgresql:12.6.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.flywaydb:flyway-core:12.6.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.flywaydb:flyway-database-postgresql:12.6.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.freemarker:freemarker:2.3.34=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.glassfish.jaxb:codemodel:4.0.8=jaxb
org.glassfish.jaxb:jaxb-core:4.0.6=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.glassfish.jaxb:jaxb-core:4.0.8=jaxb
@@ -514,7 +501,6 @@ org.jetbrains:annotations:13.0=annotationProcessor,testAnnotationProcessor
org.jetbrains:annotations:17.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jline:jline:3.30.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.joda:joda-money:2.0.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.json:json:20230618=soy
org.json:json:20251224=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jsoup:jsoup:1.22.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jspecify:jspecify:1.0.0=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
@@ -535,17 +521,12 @@ org.mockito:mockito-junit-jupiter:5.23.0=testCompileClasspath,testRuntimeClasspa
org.objenesis:objenesis:3.3=testRuntimeClasspath
org.ogce:xpp3:1.1.6=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm-analysis:9.5=soy
org.ow2.asm:asm-analysis:9.7.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm-commons:9.5=soy
org.ow2.asm:asm-commons:9.7.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm-commons:9.9=jacocoAnt
org.ow2.asm:asm-tree:9.5=soy
org.ow2.asm:asm-tree:9.7.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm-tree:9.9=jacocoAnt
org.ow2.asm:asm-util:9.5=soy
org.ow2.asm:asm-util:9.7.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm:9.5=soy
org.ow2.asm:asm:9.7.1=compileClasspath,nonprodCompileClasspath
org.ow2.asm:asm:9.8=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm:9.9=jacocoAnt
@@ -54,9 +54,9 @@ public class BatchModule {
static final int DEFAULT_MAX_QPS = 10;
@Provides
@Parameter("sender")
static String provideSender(HttpServletRequest req) {
return extractRequiredParameter(req, "sender");
@Parameter("replyTo")
static String provideReplyTo(HttpServletRequest req) {
return extractRequiredParameter(req, "replyTo");
}
@Provides
@@ -18,7 +18,9 @@ import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.POST;
import com.google.api.services.gmail.Gmail;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import dagger.Lazy;
import google.registry.config.RegistryConfig.Config;
import google.registry.groups.GmailClient;
@@ -31,6 +33,7 @@ import google.registry.util.Retrier;
import jakarta.inject.Inject;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import java.util.Optional;
/**
* Action that executes a canned script specified by the caller.
@@ -62,13 +65,16 @@ public class CannedScriptExecutionAction implements Runnable {
@Inject Response response;
@Inject
@Parameter("sender")
String sender;
@Parameter("replyTo")
String replyTo;
@Inject
@Parameter("receiver")
String receiver;
@Config("invoiceReplyToEmailAddress")
Optional<InternetAddress> replyToEmailAddressFromConfig;
@Inject
CannedScriptExecutionAction() {}
@@ -77,20 +83,36 @@ public class CannedScriptExecutionAction implements Runnable {
// For b/510340944, validating a new G Workspace user can send email. Code below can be
// removed or changed afterward.
try {
logger.atInfo().log("Sending email from %s to %s", sender, receiver);
logger.atInfo().log("Sending email to %s with replyTo %s", receiver, replyTo);
GmailClient gmailClient =
new GmailClient(
gmail, retrier, isEmailSendingEnabled, sender, sender, new InternetAddress(sender));
new GmailClient(gmail, retrier, isEmailSendingEnabled, new InternetAddress(replyTo));
gmailClient.sendEmail(
EmailMessage.newBuilder()
.addRecipient(new InternetAddress(receiver))
.setSubject(String.format("Email send test from %s", sender))
.setBody(String.format("This is a test email sent from %s to %s.", sender, receiver))
.setSubject(String.format("Email with manually set replyTo header %s", replyTo))
.setBody("See subject")
.build());
response.setPayload("Email sent successfully.");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
gmailClient.sendEmail(
EmailMessage.newBuilder()
.setSubject(
String.format(
"Email with injected replyTo header %s", replyToEmailAddressFromConfig))
.setBody("See header")
.setRecipients(ImmutableList.of(new InternetAddress(receiver)))
.setReplyToEmailAddress(replyToEmailAddressFromConfig)
.setContentType(MediaType.HTML_UTF_8)
.build());
response.setPayload("Emails sent successfully.");
} catch (AddressException e) {
logger.atWarning().withCause(e).log(
"Invalid email address: sender=%s, receiver=%s", sender, receiver);
"Invalid email address: sender=%s, receiver=%s", replyTo, receiver);
response.setStatus(400);
response.setPayload("Invalid email address provided.");
} catch (Exception e) {
@@ -21,7 +21,6 @@ import static google.registry.beam.rde.RdePipeline.TupleTags.DOMAIN_FRAGMENTS;
import static google.registry.beam.rde.RdePipeline.TupleTags.EXTERNAL_HOST_FRAGMENTS;
import static google.registry.beam.rde.RdePipeline.TupleTags.HOST_TO_PENDING_DEPOSIT;
import static google.registry.beam.rde.RdePipeline.TupleTags.PENDING_DEPOSIT;
import static google.registry.beam.rde.RdePipeline.TupleTags.REFERENCED_CONTACTS;
import static google.registry.beam.rde.RdePipeline.TupleTags.REFERENCED_HOSTS;
import static google.registry.beam.rde.RdePipeline.TupleTags.REVISION_ID;
import static google.registry.beam.rde.RdePipeline.TupleTags.SUPERORDINATE_DOMAINS;
@@ -131,9 +130,8 @@ import org.apache.beam.sdk.values.TypeDescriptor;
*
* After the most recent (live) domain resources are loaded from the corresponding history objects,
* we marshall them to deposit fragments and emit the (pending deposit: deposit fragment) pairs for
* further processing. We also find all the contacts and hosts referenced by a given domain and emit
* pairs of (contact/host repo ID: pending deposit) for all RDE pending deposits for further
* processing.
* further processing. We also find all the hosts referenced by a given domain and emit pairs of
* (host repo ID: pending deposit) for all RDE pending deposits for further processing.
*
* <h3>{@link Host}</h3>
*
@@ -358,7 +356,6 @@ public class RdePipeline implements Serializable {
private <T extends HistoryEntry> EppResource loadResourceByHistoryEntryId(
Class<T> historyEntryClazz, String repoId, long revisionId) {
return tm().transact(
() ->
tm().loadByKey(
@@ -372,8 +369,8 @@ public class RdePipeline implements Serializable {
* Remove unreferenced resources by joining the (repoId, pendingDeposit) pair with the (repoId,
* revisionId) on the repoId.
*
* <p>The (repoId, pendingDeposit) pairs denote resources (contact, host) that are referenced from
* a domain, that are to be included in the corresponding pending deposit.
* <p>The (repoId, pendingDeposit) pairs denote hosts that are referenced from a domain, that are
* to be included in the corresponding pending deposit.
*
* <p>The (repoId, revisionId) paris come from the most recent history entry query, which can be
* used to load the embedded resources themselves.
@@ -423,7 +420,7 @@ public class RdePipeline implements Serializable {
Counter domainFragmentCounter = Metrics.counter("RDE", "DomainFragment");
Counter referencedHostCounter = Metrics.counter("RDE", "ReferencedHost");
return domainHistories.apply(
"Map DomainHistory to DepositFragment " + "and emit referenced Contact and Host",
"Map DomainHistory to DepositFragment and emit referenced Host",
ParDo.of(
new DoFn<KV<String, Long>, KV<PendingDeposit, DepositFragment>>() {
@ProcessElement
@@ -465,8 +462,7 @@ public class RdePipeline implements Serializable {
});
}
})
.withOutputTags(
DOMAIN_FRAGMENTS, TupleTagList.of(REFERENCED_CONTACTS).and(REFERENCED_HOSTS)));
.withOutputTags(DOMAIN_FRAGMENTS, TupleTagList.of(REFERENCED_HOSTS)));
}
private PCollectionTuple processHostHistories(
@@ -627,9 +623,6 @@ public class RdePipeline implements Serializable {
protected static final TupleTag<KV<PendingDeposit, DepositFragment>> DOMAIN_FRAGMENTS =
new TupleTag<>() {};
protected static final TupleTag<KV<String, PendingDeposit>> REFERENCED_CONTACTS =
new TupleTag<>() {};
protected static final TupleTag<KV<String, PendingDeposit>> REFERENCED_HOSTS =
new TupleTag<>() {};
@@ -68,6 +68,8 @@ public class BsaCredential {
private final Keyring keyring;
private final Gson gson;
private final Clock clock;
@Nullable private String authToken;
@@ -79,11 +81,13 @@ public class BsaCredential {
@Config("bsaAuthUrl") String authUrl,
@Config("bsaAuthTokenExpiry") Duration authTokenExpiry,
Keyring keyring,
Gson gson,
Clock clock) {
this.urlConnectionService = urlConnectionService;
this.authUrl = authUrl;
this.authTokenExpiry = authTokenExpiry;
this.keyring = keyring;
this.gson = gson;
this.clock = clock;
}
@@ -143,8 +147,7 @@ public class BsaCredential {
// TODO: catch json syntax exception
@SuppressWarnings("unchecked")
String idToken =
new Gson()
.fromJson(new String(getResponseBytes(connection), UTF_8), Map.class)
gson.fromJson(new String(getResponseBytes(connection), UTF_8), Map.class)
.getOrDefault(ID_TOKEN, "")
.toString();
if (idToken.isEmpty()) {
+6 -7
View File
@@ -60,12 +60,15 @@ public final class CacheModule {
public static Optional<UnifiedJedis> provideJedis(
@ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle,
@Config("valkeyHostsAndPorts") Optional<ImmutableList<String>> valkeyHostsAndPorts,
@Config("valkeySslSocketFactory") SSLSocketFactory valkeySslSocketFactory) {
if (valkeyHostsAndPorts.map(ImmutableList::isEmpty).orElse(true)) {
@Config("valkeyCertificateAuthority") Optional<String> valkeyCertificateAuthority) {
if (valkeyHostsAndPorts.map(ImmutableList::isEmpty).orElse(true)
|| valkeyCertificateAuthority.isEmpty()) {
return Optional.empty();
}
ImmutableSet<HostAndPort> hostsAndPorts =
valkeyHostsAndPorts.get().stream().map(HostAndPort::from).collect(toImmutableSet());
SSLSocketFactory valkeySslSocketFactory =
createValkeySslSocketFactory(valkeyCertificateAuthority.get());
JedisClientConfig clientConfig =
DefaultJedisClientConfig.builder()
.ssl(true)
@@ -111,11 +114,7 @@ public final class CacheModule {
return new MultilayerHostCache(jedisClient.get(), cacheMetrics);
}
@Provides
@Singleton
@Config("valkeySslSocketFactory")
static SSLSocketFactory provideValkeySslSocketFactory(
@Config("valkeyCertificateAuthority") String valkeyCertificateAuthority) {
private static SSLSocketFactory createValkeySslSocketFactory(String valkeyCertificateAuthority) {
try {
ImmutableList<X509Certificate> trustedCerts =
CertificateFactory.getInstance("X.509")
@@ -19,16 +19,6 @@ import dagger.Provides;
import dagger.Subcomponent;
import google.registry.batch.BatchModule;
import google.registry.dns.DnsModule;
import google.registry.flows.contact.ContactCheckFlow;
import google.registry.flows.contact.ContactCreateFlow;
import google.registry.flows.contact.ContactDeleteFlow;
import google.registry.flows.contact.ContactInfoFlow;
import google.registry.flows.contact.ContactTransferApproveFlow;
import google.registry.flows.contact.ContactTransferCancelFlow;
import google.registry.flows.contact.ContactTransferQueryFlow;
import google.registry.flows.contact.ContactTransferRejectFlow;
import google.registry.flows.contact.ContactTransferRequestFlow;
import google.registry.flows.contact.ContactUpdateFlow;
import google.registry.flows.custom.CustomLogicModule;
import google.registry.flows.domain.DomainCheckFlow;
import google.registry.flows.domain.DomainClaimsCheckFlow;
@@ -54,6 +44,8 @@ import google.registry.flows.session.HelloFlow;
import google.registry.flows.session.LoginFlow;
import google.registry.flows.session.LogoutFlow;
import google.registry.model.eppcommon.Trid;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/** Dagger component for flow classes. */
@FlowScope
@@ -69,16 +61,6 @@ public interface FlowComponent {
FlowRunner flowRunner();
// Flows must be added here and in FlowComponentModule below.
ContactCheckFlow contactCheckFlow();
ContactCreateFlow contactCreateFlow();
ContactDeleteFlow contactDeleteFlow();
ContactInfoFlow contactInfoFlow();
ContactTransferApproveFlow contactTransferApproveFlow();
ContactTransferCancelFlow contactTransferCancelFlow();
ContactTransferQueryFlow contactTransferQueryFlow();
ContactTransferRejectFlow contactTransferRejectFlow();
ContactTransferRequestFlow contactTransferRequestFlow();
ContactUpdateFlow contactUpdateFlow();
DomainCheckFlow domainCheckFlow();
DomainClaimsCheckFlow domainClaimsCheckFlow();
DomainCreateFlow domainCreateFlow();
@@ -118,40 +100,16 @@ public interface FlowComponent {
// TODO(b/29874464): fix this in a cleaner way.
@Provides
static Flow provideFlow(FlowComponent flows, Class<? extends Flow> clazz) {
return clazz.equals(ContactCheckFlow.class) ? flows.contactCheckFlow()
: clazz.equals(ContactCreateFlow.class) ? flows.contactCreateFlow()
: clazz.equals(ContactDeleteFlow.class) ? flows.contactDeleteFlow()
: clazz.equals(ContactInfoFlow.class) ? flows.contactInfoFlow()
: clazz.equals(ContactTransferApproveFlow.class) ? flows.contactTransferApproveFlow()
: clazz.equals(ContactTransferCancelFlow.class) ? flows.contactTransferCancelFlow()
: clazz.equals(ContactTransferQueryFlow.class) ? flows.contactTransferQueryFlow()
: clazz.equals(ContactTransferRejectFlow.class) ? flows.contactTransferRejectFlow()
: clazz.equals(ContactTransferRequestFlow.class) ? flows.contactTransferRequestFlow()
: clazz.equals(ContactUpdateFlow.class) ? flows.contactUpdateFlow()
: clazz.equals(DomainCheckFlow.class) ? flows.domainCheckFlow()
: clazz.equals(DomainClaimsCheckFlow.class) ? flows.domainClaimsCheckFlow()
: clazz.equals(DomainCreateFlow.class) ? flows.domainCreateFlow()
: clazz.equals(DomainDeleteFlow.class) ? flows.domainDeleteFlow()
: clazz.equals(DomainInfoFlow.class) ? flows.domainInfoFlow()
: clazz.equals(DomainRenewFlow.class) ? flows.domainRenewFlow()
: clazz.equals(DomainRestoreRequestFlow.class) ? flows.domainRestoreRequestFlow()
: clazz.equals(DomainTransferApproveFlow.class) ? flows.domainTransferApproveFlow()
: clazz.equals(DomainTransferCancelFlow.class) ? flows.domainTransferCancelFlow()
: clazz.equals(DomainTransferQueryFlow.class) ? flows.domainTransferQueryFlow()
: clazz.equals(DomainTransferRejectFlow.class) ? flows.domainTransferRejectFlow()
: clazz.equals(DomainTransferRequestFlow.class) ? flows.domainTransferRequestFlow()
: clazz.equals(DomainUpdateFlow.class) ? flows.domainUpdateFlow()
: clazz.equals(HostCheckFlow.class) ? flows.hostCheckFlow()
: clazz.equals(HostCreateFlow.class) ? flows.hostCreateFlow()
: clazz.equals(HostDeleteFlow.class) ? flows.hostDeleteFlow()
: clazz.equals(HostInfoFlow.class) ? flows.hostInfoFlow()
: clazz.equals(HostUpdateFlow.class) ? flows.hostUpdateFlow()
: clazz.equals(PollAckFlow.class) ? flows.pollAckFlow()
: clazz.equals(PollRequestFlow.class) ? flows.pollRequestFlow()
: clazz.equals(HelloFlow.class) ? flows.helloFlow()
: clazz.equals(LoginFlow.class) ? flows.loginFlow()
: clazz.equals(LogoutFlow.class) ? flows.logoutFlow()
: null;
String simpleName = clazz.getSimpleName();
// The method name is the same as the class name but with the first character being lowercase
String methodName = Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
try {
Method method = FlowComponent.class.getMethod(methodName);
method.setAccessible(true);
return (Flow) method.invoke(flows);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
@@ -1,31 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.flows.contact;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import jakarta.inject.Inject;
/**
* An EPP flow that is meant to check whether a contact can be provisioned.
*
* @error {@link ContactsProhibitedException}
*/
@Deprecated
@ReportingSpec(ActivityReportField.CONTACT_CHECK)
public final class ContactCheckFlow extends ContactsProhibitedFlow {
@Inject ContactCheckFlow() {}
}
@@ -1,31 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.flows.contact;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import jakarta.inject.Inject;
/**
* An EPP flow meant to create a new contact.
*
* @error {@link ContactsProhibitedException}
*/
@Deprecated
@ReportingSpec(ActivityReportField.CONTACT_CREATE)
public final class ContactCreateFlow extends ContactsProhibitedFlow {
@Inject ContactCreateFlow() {}
}
@@ -1,32 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.flows.contact;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import jakarta.inject.Inject;
/**
* An EPP flow that is meant to delete a contact.
*
* @error {@link ContactsProhibitedException}
*/
@Deprecated
@ReportingSpec(ActivityReportField.CONTACT_DELETE)
public final class ContactDeleteFlow extends ContactsProhibitedFlow {
@Inject
ContactDeleteFlow() {}
}
@@ -1,32 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.flows.contact;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import jakarta.inject.Inject;
/**
* An EPP flow that is meant to return information about a contact.
*
* @error {@link ContactsProhibitedException}
*/
@Deprecated
@ReportingSpec(ActivityReportField.CONTACT_INFO)
public final class ContactInfoFlow extends ContactsProhibitedFlow {
@Inject
ContactInfoFlow() {}
}
@@ -1,31 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.flows.contact;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import jakarta.inject.Inject;
/**
* An EPP flow that is meant to approve a pending transfer on a contact.
*
* @error {@link ContactsProhibitedException}
*/
@Deprecated
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_APPROVE)
public final class ContactTransferApproveFlow extends ContactsProhibitedFlow {
@Inject ContactTransferApproveFlow() {}
}
@@ -1,31 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.flows.contact;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import jakarta.inject.Inject;
/**
* An EPP flow that is meant to cancel a pending transfer on a contact.
*
* @error {@link ContactsProhibitedException}
*/
@Deprecated
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_CANCEL)
public final class ContactTransferCancelFlow extends ContactsProhibitedFlow {
@Inject ContactTransferCancelFlow() {}
}
@@ -1,31 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.flows.contact;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import jakarta.inject.Inject;
/**
* An EPP flow that is meant to query a pending transfer on a contact.
*
* @error {@link ContactsProhibitedException}
*/
@Deprecated
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_QUERY)
public final class ContactTransferQueryFlow extends ContactsProhibitedFlow {
@Inject ContactTransferQueryFlow() {}
}
@@ -1,31 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.flows.contact;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import jakarta.inject.Inject;
/**
* An EPP flow that is meant to reject a pending transfer on a contact.
*
* @error {@link ContactsProhibitedException}
*/
@Deprecated
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_REJECT)
public final class ContactTransferRejectFlow extends ContactsProhibitedFlow {
@Inject ContactTransferRejectFlow() {}
}
@@ -1,32 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.flows.contact;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import jakarta.inject.Inject;
/**
* An EPP flow that is meant to request a transfer on a contact.
*
* @error {@link ContactsProhibitedException}
*/
@Deprecated
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_REQUEST)
public final class ContactTransferRequestFlow extends ContactsProhibitedFlow {
@Inject
ContactTransferRequestFlow() {}
}
@@ -1,31 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.flows.contact;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import jakarta.inject.Inject;
/**
* An EPP flow meant to update a contact.
*
* @error {@link ContactsProhibitedException}
*/
@Deprecated
@ReportingSpec(ActivityReportField.CONTACT_UPDATE)
public final class ContactUpdateFlow extends ContactsProhibitedFlow {
@Inject ContactUpdateFlow() {}
}
@@ -1,28 +0,0 @@
// 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.contact;
import google.registry.flows.EppException;
import google.registry.flows.Flow;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.model.eppoutput.EppResponse;
/** Nomulus follows the Minimum Dataset Requirements, meaning it stores no contact information. */
public abstract class ContactsProhibitedFlow implements Flow {
@Override
public EppResponse run() throws EppException {
throw new ContactsProhibitedException();
}
}
@@ -43,8 +43,8 @@ import static google.registry.util.CollectionUtils.nullToEmpty;
import static google.registry.util.DateTimeUtils.END_INSTANT;
import static google.registry.util.DateTimeUtils.isAtOrAfter;
import static google.registry.util.DateTimeUtils.minusDays;
import static google.registry.util.DateTimeUtils.plusYears;
import static google.registry.util.DomainNameUtils.ACE_PREFIX;
import static java.time.ZoneOffset.UTC;
import static java.util.stream.Collectors.joining;
import com.google.common.base.CharMatcher;
@@ -154,7 +154,7 @@ public class DomainFlowUtils {
/** Warning message for allocation of collision domains in sunrise. */
public static final String COLLISION_MESSAGE =
"Domain on the name collision list was allocated. But by policy, the domain will not be "
+ "delegated. Please visit https://www.icann.org/namecollision for more information on "
+ "delegated. Please visit https://www.icann.org/namecollision for more information on "
+ "name collision.";
/** Strict validator for ascii lowercase letters, digits, and "-", allowing "." as a separator */
@@ -581,13 +581,12 @@ public class DomainFlowUtils {
InternetDomainName domainName,
Optional<Domain> domain,
@Nullable CurrencyUnit topLevelCurrency,
Instant currentDate,
Instant now,
DomainPricingLogic pricingLogic,
Optional<AllocationToken> allocationToken,
boolean isAvailable,
@Nullable BillingRecurrence billingRecurrence)
throws EppException {
Instant now = currentDate;
// Use the custom effective date specified in the fee check request, if there is one.
if (feeRequest.getEffectiveDate().isPresent()) {
now = feeRequest.getEffectiveDate().get();
@@ -816,7 +815,7 @@ public class DomainFlowUtils {
return fee.getType();
}
ImmutableList<FeeType> types = fee.parseDescriptionForTypes();
if (types.size() == 0) {
if (types.isEmpty()) {
throw new FeeDescriptionParseException(fee.getDescription());
} else if (types.size() > 1) {
throw new FeeDescriptionMultipleMatchesException(fee.getDescription(), types);
@@ -848,7 +847,7 @@ public class DomainFlowUtils {
*/
public static void validateRegistrationPeriod(Instant now, Instant newExpirationTime)
throws EppException {
if (now.atZone(UTC).plusYears(MAX_REGISTRATION_YEARS).toInstant().isBefore(newExpirationTime)) {
if (plusYears(now, MAX_REGISTRATION_YEARS).isBefore(newExpirationTime)) {
throw new ExceedsMaxRegistrationYearsException();
}
}
@@ -907,7 +906,7 @@ public class DomainFlowUtils {
return ImmutableSet.copyOf(union(difference(oldDsData, toRemove), toAdd));
}
/** If a domain "clientUpdateProhibited" set, updates must clear it or fail. */
/** If a domain has "clientUpdateProhibited" set, updates must clear it or fail. */
static void verifyClientUpdateNotProhibited(Update command, Domain existingResource)
throws ResourceHasClientUpdateProhibitedException {
if (existingResource.getStatusValues().contains(StatusValue.CLIENT_UPDATE_PROHIBITED)
@@ -996,7 +995,13 @@ public class DomainFlowUtils {
}
}
/** Check that the claims period hasn't ended. */
/**
* Check that the claims period hasn't ended.
*
* @param tld the {@link Tld} to check
* @param now the current {@link Instant}
* @throws ClaimsPeriodEndedException if the claims period has ended
*/
static void verifyClaimsPeriodNotEnded(Tld tld, Instant now) throws ClaimsPeriodEndedException {
if (!now.isBefore(tld.getClaimsPeriodEnd())) {
throw new ClaimsPeriodEndedException(tld.getTldStr());
@@ -1008,6 +1013,9 @@ public class DomainFlowUtils {
*
* <p>{@link BigDecimal} has a concept of significant figures, so zero is not always zero. E.g.
* zero in USD is 0.00, whereas zero in Yen is 0, and zero in Dinars is 0.000 (!).
*
* @param currencyUnit the {@link CurrencyUnit}
* @return zero in the given currency
*/
static BigDecimal zeroInCurrency(CurrencyUnit currencyUnit) {
return Money.of(currencyUnit, BigDecimal.ZERO).getAmount();
@@ -1016,6 +1024,12 @@ public class DomainFlowUtils {
/**
* Check that if there's a claims notice it's on the claims list, and that if there's not one it's
* not on the claims list.
*
* @param domainName the {@link InternetDomainName} to check
* @param claimsList the current {@link ClaimsList}
* @param hasSignedMarks whether signed marks are present
* @param hasClaimsNotice whether a claims notice is present
* @throws EppException if the claims notice status is incorrect
*/
static void verifyClaimsNoticeIfAndOnlyIfNeeded(
InternetDomainName domainName,
@@ -1032,7 +1046,12 @@ public class DomainFlowUtils {
}
}
/** Check that there are no code marks, which is a type of mark we don't support. */
/**
* Check that there are no code marks, which is a type of mark we don't support.
*
* @param launchCreate the {@link LaunchCreateExtension}
* @throws UnsupportedMarkTypeException if code marks are present
*/
static void verifyNoCodeMarks(LaunchCreateExtension launchCreate)
throws UnsupportedMarkTypeException {
if (launchCreate.hasCodeMarks()) {
@@ -1040,7 +1059,13 @@ public class DomainFlowUtils {
}
}
/** Create a response extension listing the fees on a domain create. */
/**
* Create a response extension listing the fees on a domain create.
*
* @param feeCreate the {@link FeeTransformCommandExtension}
* @param feesAndCredits the {@link FeesAndCredits}
* @return the {@link FeeTransformResponseExtension}
*/
static FeeTransformResponseExtension createFeeCreateResponse(
FeeTransformCommandExtension feeCreate, FeesAndCredits feesAndCredits) {
return feeCreate
@@ -1058,10 +1083,21 @@ public class DomainFlowUtils {
* their flow. For example, if a grace period delete occurs, we must add -1 counters for the
* associated NET_ADDS_#_YRS field, if it exists.
*
* <p>The steps are as follows: 1. Find all HistoryEntries under the domain modified in the past,
* up to the maxSearchPeriod. 2. Only keep HistoryEntries with a DomainTransactionRecord that a)
* hasn't been reported yet and b) matches the predicate 3. Return the transactionRecords under
* the most recent HistoryEntry that fits the above criteria, with negated reportAmounts.
* <p>The steps are as follows:
*
* <ol>
* <li>Find all HistoryEntries under the domain modified in the past, up to the maxSearchPeriod.
* <li>Only keep HistoryEntries with a DomainTransactionRecord that a) hasn't been reported yet
* and b) matches the predicate
* <li>Return the transactionRecords under the most recent HistoryEntry that fits the above
* criteria, with negated reportAmounts.
* </ol>
*
* @param domain the {@link Domain} to create records for
* @param now the current {@link Instant}
* @param maxSearchPeriod the {@link Duration} to search back
* @param cancelableFields the set of {@link TransactionReportField}s that can be canceled
* @return the set of canceling {@link DomainTransactionRecord}s
*/
public static ImmutableSet<DomainTransactionRecord> createCancelingRecords(
Domain domain,
@@ -1225,13 +1261,6 @@ public class DomainFlowUtils {
}
}
/** Having a registrant is prohibited by registry policy. */
public static class RegistrantProhibitedException extends ParameterValuePolicyErrorException {
public RegistrantProhibitedException() {
super("Having a registrant is prohibited by registry policy");
}
}
/** Too many nameservers set on this domain. */
static class TooManyNameserversException extends ParameterValuePolicyErrorException {
public TooManyNameserversException(String message) {
@@ -1384,6 +1413,13 @@ public class DomainFlowUtils {
}
}
/** Having a registrant is prohibited by registry policy. */
public static class RegistrantProhibitedException extends ParameterValuePolicyErrorException {
public RegistrantProhibitedException() {
super("Having a registrant is prohibited by registry policy");
}
}
/** The fee description passed in the transform command cannot be parsed. */
public static class FeeDescriptionParseException extends ParameterValuePolicyErrorException {
public FeeDescriptionParseException(String description) {
@@ -34,8 +34,8 @@ import static google.registry.flows.domain.token.AllocationTokenFlowUtils.maybeA
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.verifyBulkTokenAllowedOnDomain;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_RENEW;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.DateTimeUtils.plusYears;
import static google.registry.util.DateTimeUtils.toLocalDate;
import static java.time.ZoneOffset.UTC;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -192,11 +192,7 @@ public final class DomainRenewFlow implements MutatingFlow {
existingDomain = maybeApplyBulkPricingRemovalToken(existingDomain, allocationToken);
Instant newExpirationTime =
existingDomain
.getRegistrationExpirationTime()
.atZone(UTC)
.plusYears(years)
.toInstant(); // Uncapped
plusYears(existingDomain.getRegistrationExpirationTime(), years); // Uncapped
validateRegistrationPeriod(now, newExpirationTime);
Optional<FeeRenewCommandExtension> feeRenew =
eppInput.getSingleExtension(FeeRenewCommandExtension.class);
@@ -14,7 +14,6 @@
package google.registry.flows.domain;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
import static com.google.common.collect.Sets.symmetricDifference;
import static com.google.common.collect.Sets.union;
@@ -58,17 +57,17 @@ import google.registry.flows.custom.DomainUpdateFlowCustomLogic.BeforeSaveParame
import google.registry.flows.custom.EntityChanges;
import google.registry.flows.domain.DomainFlowUtils.NameserversNotSpecifiedForTldWithNameserverAllowListException;
import google.registry.flows.domain.DomainFlowUtils.RegistrantProhibitedException;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingBase.Reason;
import google.registry.model.billing.BillingEvent;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainCommand.Update;
import google.registry.model.domain.DomainCommand.Update.AddRemove;
import google.registry.model.domain.DomainCommand.Update.Change;
import google.registry.model.domain.DomainCommand.Update.DomainAddRemove;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.fee.FeeUpdateCommandExtension;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.domain.secdns.SecDnsUpdateExtension;
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Add;
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Remove;
@@ -118,6 +117,7 @@ import java.util.Optional;
* @error {@link NameserversNotSpecifiedForTldWithNameserverAllowListException}
* @error {@link DomainFlowUtils.NotAuthorizedForTldException}
* @error {@link RegistrantProhibitedException}
* @error {@link ContactsProhibitedException}
* @error {@link DomainFlowUtils.SecDnsAllUsageException}
* @error {@link DomainFlowUtils.TooManyDsRecordsException}
* @error {@link DomainFlowUtils.TooManyNameserversException}
@@ -214,8 +214,8 @@ public final class DomainUpdateFlow implements MutatingFlow {
private void verifyUpdateAllowed(Update command, Domain existingDomain, Instant now)
throws EppException {
verifyOptionalAuthInfo(authInfo, existingDomain);
AddRemove add = command.getInnerAdd();
AddRemove remove = command.getInnerRemove();
DomainAddRemove add = command.getInnerAdd();
DomainAddRemove remove = command.getInnerRemove();
String tldStr = existingDomain.getTld();
if (!isSuperuser) {
verifyNoDisallowedStatuses(existingDomain, UPDATE_DISALLOWED_STATUSES);
@@ -234,8 +234,8 @@ public final class DomainUpdateFlow implements MutatingFlow {
}
private Domain performUpdate(Update command, Domain domain, Instant now) throws EppException {
AddRemove add = command.getInnerAdd();
AddRemove remove = command.getInnerRemove();
DomainAddRemove add = command.getInnerAdd();
DomainAddRemove remove = command.getInnerRemove();
Optional<SecDnsUpdateExtension> secDnsUpdate =
eppInput.getSingleExtension(SecDnsUpdateExtension.class);
verifyAddsAndRemoves(domain.getNameservers(), add.getNameservers(), remove.getNameservers());
@@ -251,28 +251,29 @@ public final class DomainUpdateFlow implements MutatingFlow {
Domain.Builder domainBuilder =
domain
.asBuilder()
// Handle the secDNS extension. As dsData in secDnsUpdate is read from EPP input and
// does not have domainRepoId set, we create a copy of the existing dsData without
// domainRepoId for comparison.
// Handle the secDNS extension.
.setDsData(
secDnsUpdate.isPresent()
? updateDsData(
domain.getDsData().stream()
.map(DomainDsData::cloneWithoutDomainRepoId)
.collect(toImmutableSet()),
secDnsUpdate.get())
? updateDsData(domain.getDsData(), secDnsUpdate.get())
: domain.getDsData())
.setLastEppUpdateTime(now)
.setLastEppUpdateRegistrarId(registrarId)
.addStatusValues(add.getStatusValues())
.removeStatusValues(remove.getStatusValues())
.setAuthInfo(Optional.ofNullable(change.getAuthInfo()).orElse(domain.getAuthInfo()));
.setLastEppUpdateRegistrarId(registrarId);
if (!add.getStatusValues().isEmpty()) {
domainBuilder.addStatusValues(add.getStatusValues());
}
if (!remove.getStatusValues().isEmpty()) {
domainBuilder.removeStatusValues(remove.getStatusValues());
}
domainBuilder.setAuthInfo(
Optional.ofNullable(change.getAuthInfo()).orElse(domain.getAuthInfo()));
if (!add.getNameservers().isEmpty()) {
domainBuilder.addNameservers(add.getNameservers().stream().collect(toImmutableSet()));
domainBuilder.addNameservers(add.getNameservers());
}
if (!remove.getNameservers().isEmpty()) {
domainBuilder.removeNameservers(remove.getNameservers().stream().collect(toImmutableSet()));
domainBuilder.removeNameservers(remove.getNameservers());
}
Optional<DomainUpdateSuperuserExtension> superuserExt =
@@ -36,7 +36,6 @@ import static google.registry.util.CollectionUtils.isNullOrEmpty;
import com.google.cloud.tasks.v2.Task;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import google.registry.batch.AsyncTaskEnqueuer;
import google.registry.batch.CloudTasksUtils;
import google.registry.dns.RefreshDnsOnHostRenameAction;
import google.registry.flows.EppException;
@@ -59,8 +58,8 @@ import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.host.Host;
import google.registry.model.host.HostCommand.Update;
import google.registry.model.host.HostCommand.Update.AddRemove;
import google.registry.model.host.HostCommand.Update.Change;
import google.registry.model.host.HostCommand.Update.HostAddRemove;
import google.registry.model.host.HostHistory;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.persistence.VKey;
@@ -122,7 +121,6 @@ public final class HostUpdateFlow implements MutatingFlow {
@Inject @TargetId String targetId;
@Inject @Superuser boolean isSuperuser;
@Inject HostHistory.Builder historyBuilder;
@Inject AsyncTaskEnqueuer asyncTaskEnqueuer;
@Inject EppResponse.Builder responseBuilder;
@Inject CloudTasksUtils cloudTasksUtils;
@@ -148,6 +146,7 @@ public final class HostUpdateFlow implements MutatingFlow {
? tm().loadByKey(existingHost.getSuperordinateDomain()).cloneProjectedAtTime(now)
: null;
// Note that lookupSuperordinateDomain calls cloneProjectedAtTime on the domain for us.
Optional<Domain> newSuperordinateDomain =
lookupSuperordinateDomain(validateHostName(newHostName), now);
verifySuperordinateDomainNotInPendingDelete(newSuperordinateDomain.orElse(null));
@@ -157,8 +156,8 @@ public final class HostUpdateFlow implements MutatingFlow {
if (isHostRename && ForeignKeyUtils.loadKey(Host.class, newHostName, now).isPresent()) {
throw new HostAlreadyExistsException(newHostName);
}
AddRemove add = command.getInnerAdd();
AddRemove remove = command.getInnerRemove();
HostAddRemove add = command.getInnerAdd();
HostAddRemove remove = command.getInnerRemove();
verifyAddsAndRemoves(
existingHost.getStatusValues(), add.getStatusValues(), remove.getStatusValues());
verifyAddsAndRemoves(
@@ -22,16 +22,6 @@ import google.registry.flows.EppException;
import google.registry.flows.EppException.SyntaxErrorException;
import google.registry.flows.EppException.UnimplementedCommandException;
import google.registry.flows.Flow;
import google.registry.flows.contact.ContactCheckFlow;
import google.registry.flows.contact.ContactCreateFlow;
import google.registry.flows.contact.ContactDeleteFlow;
import google.registry.flows.contact.ContactInfoFlow;
import google.registry.flows.contact.ContactTransferApproveFlow;
import google.registry.flows.contact.ContactTransferCancelFlow;
import google.registry.flows.contact.ContactTransferQueryFlow;
import google.registry.flows.contact.ContactTransferRejectFlow;
import google.registry.flows.contact.ContactTransferRequestFlow;
import google.registry.flows.contact.ContactUpdateFlow;
import google.registry.flows.domain.DomainCheckFlow;
import google.registry.flows.domain.DomainClaimsCheckFlow;
import google.registry.flows.domain.DomainCreateFlow;
@@ -55,7 +45,6 @@ import google.registry.flows.poll.PollRequestFlow;
import google.registry.flows.session.HelloFlow;
import google.registry.flows.session.LoginFlow;
import google.registry.flows.session.LogoutFlow;
import google.registry.model.contact.ContactCommand;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.launch.LaunchCheckExtension;
import google.registry.model.domain.launch.LaunchCheckExtension.CheckType;
@@ -202,11 +191,6 @@ public class FlowPicker {
private static final FlowProvider RESOURCE_CRUD_FLOW_PROVIDER = new FlowProvider() {
private final Map<Class<?>, Class<? extends Flow>> resourceCrudFlows =
new ImmutableMap.Builder<Class<?>, Class<? extends Flow>>()
.put(ContactCommand.Check.class, ContactCheckFlow.class)
.put(ContactCommand.Create.class, ContactCreateFlow.class)
.put(ContactCommand.Delete.class, ContactDeleteFlow.class)
.put(ContactCommand.Info.class, ContactInfoFlow.class)
.put(ContactCommand.Update.class, ContactUpdateFlow.class)
.put(DomainCommand.Create.class, DomainCreateFlow.class)
.put(DomainCommand.Delete.class, DomainDeleteFlow.class)
.put(DomainCommand.Info.class, DomainInfoFlow.class)
@@ -230,24 +214,6 @@ public class FlowPicker {
new FlowProvider() {
private final Table<Class<?>, TransferOp, Class<? extends Flow>> transferFlows =
ImmutableTable.<Class<?>, TransferOp, Class<? extends Flow>>builder()
.put(
ContactCommand.Transfer.class,
TransferOp.APPROVE,
ContactTransferApproveFlow.class)
.put(
ContactCommand.Transfer.class,
TransferOp.CANCEL,
ContactTransferCancelFlow.class)
.put(
ContactCommand.Transfer.class, TransferOp.QUERY, ContactTransferQueryFlow.class)
.put(
ContactCommand.Transfer.class,
TransferOp.REJECT,
ContactTransferRejectFlow.class)
.put(
ContactCommand.Transfer.class,
TransferOp.REQUEST,
ContactTransferRequestFlow.class)
.put(
DomainCommand.Transfer.class,
TransferOp.APPROVE,
@@ -15,7 +15,6 @@
package google.registry.flows.session;
import static com.google.common.collect.Sets.difference;
import static google.registry.model.common.FeatureFlag.FeatureName.PROHIBIT_CONTACT_OBJECTS_ON_LOGIN;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.CollectionUtils.nullToEmpty;
@@ -40,7 +39,6 @@ import google.registry.flows.TlsCredentials.BadRegistrarIpAddressException;
import google.registry.flows.TlsCredentials.MissingRegistrarCertificateException;
import google.registry.flows.TransportCredentials;
import google.registry.flows.TransportCredentials.BadRegistrarPasswordException;
import google.registry.model.common.FeatureFlag;
import google.registry.model.eppcommon.ProtocolDefinition;
import google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension;
import google.registry.model.eppinput.EppInput;
@@ -120,9 +118,7 @@ public class LoginFlow implements MutatingFlow {
Set<String> unsupportedObjectServices =
difference(
nullToEmpty(services.getObjectServices()),
FeatureFlag.isActiveNow(PROHIBIT_CONTACT_OBJECTS_ON_LOGIN)
? ProtocolDefinition.SUPPORTED_OBJECT_SERVICES
: ProtocolDefinition.SUPPORTED_OBJECT_SERVICES_WITH_CONTACT);
ProtocolDefinition.SUPPORTED_OBJECT_SERVICES);
stopwatch.tick("LoginFlow difference unsupportedObjectServices");
if (!unsupportedObjectServices.isEmpty()) {
throw new UnimplementedObjectServiceException();
@@ -56,9 +56,9 @@ public final class GmailClient {
private final InternetAddress outgoingEmailAddressWithUsername;
private final InternetAddress replyToEmailAddress;
// TODO(b/510340944): make package private after feature is rolled out
// TODO(b/510340944): drop sender info. Sender is determined by the Gmail credential owner.
@Inject
public GmailClient(
GmailClient(
Lazy<Gmail> gmail,
Retrier retrier,
@Config("isEmailSendingEnabled") boolean isEmailSendingEnabled,
@@ -78,6 +78,20 @@ public final class GmailClient {
}
}
// TODO(b/510340944): Remove this experiment method
public GmailClient(
Lazy<Gmail> gmail,
Retrier retrier,
@Config("isEmailSendingEnabled") boolean isEmailSendingEnabled,
@Config("replyToEmailAddress") InternetAddress replyToEmailAddress) {
this.gmail = gmail;
this.retrier = retrier;
this.isEmailSendingEnabled = isEmailSendingEnabled;
this.replyToEmailAddress = replyToEmailAddress;
this.outgoingEmailAddressWithUsername = null;
}
/**
* Sends {@code emailMessage} using {@link Gmail}.
*/
@@ -117,7 +131,9 @@ public final class GmailClient {
try {
MimeMessage msg =
new MimeMessage(Session.getDefaultInstance(new Properties(), /* authenticator= */ null));
msg.setFrom(this.outgoingEmailAddressWithUsername);
if (this.outgoingEmailAddressWithUsername != null) {
msg.setFrom(this.outgoingEmailAddressWithUsername);
}
msg.setReplyTo(
new InternetAddress[] {emailMessage.replyToEmailAddress().orElse(replyToEmailAddress)});
msg.addRecipients(
@@ -22,6 +22,7 @@ import google.registry.config.RegistryConfig.Config;
import google.registry.keyring.api.Keyring;
import google.registry.keyring.secretmanager.SecretManagerKeyring;
import jakarta.inject.Singleton;
import java.util.Optional;
/** Dagger module for {@link Keyring} */
@Module
@@ -55,7 +56,7 @@ public abstract class KeyringModule {
@Provides
@Config("valkeyCertificateAuthority")
public static String provideValkeyCertificateAuthority(Keyring keyring) {
return keyring.getValkeyCertificateAuthority();
public static Optional<String> provideValkeyCertificateAuthority(Keyring keyring) {
return Optional.ofNullable(keyring.getValkeyCertificateAuthority());
}
}
@@ -106,19 +106,24 @@ public abstract class ImmutableObject implements Cloneable {
return hashCode;
}
/** Returns a clone of the given object. */
@SuppressWarnings("unchecked")
protected static <T extends ImmutableObject> T clone(T t) {
@Override
@SuppressWarnings("AmbiguousMethodReference")
public ImmutableObject clone() {
try {
T clone = (T) t.clone();
// Clear the hashCode since we often mutate clones before handing them out.
ImmutableObject clone = (ImmutableObject) super.clone();
clone.hashCode = null;
return clone;
} catch (CloneNotSupportedException e) { // Yes it is.
throw new IllegalStateException();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
/** Returns a clone of the given object. */
@SuppressWarnings({"unchecked", "AmbiguousMethodReference"})
protected static <T extends ImmutableObject> T clone(T t) {
return (T) t.clone();
}
/** Returns a clone of the given object with empty fields set to null. */
protected static <T extends ImmutableObject> T cloneEmptyToNull(T t) {
return ModelUtils.cloneEmptyToNull(t);
@@ -233,7 +238,7 @@ public abstract class ImmutableObject implements Cloneable {
}
}
/** Marker to indicate that this filed should be ignored by {@link #toDiffableFieldMap}. */
/** Marker to indicate that this field should be ignored by {@link #toDiffableFieldMap}. */
@Documented
@Retention(RUNTIME)
@Target(FIELD)
@@ -1,37 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.contact;
import google.registry.model.eppcommon.Address;
import jakarta.persistence.Embeddable;
/**
* EPP Contact Address
*
* <p>This class is embedded inside the {@link PostalInfo} of an EPP contact to hold its address.
* The fields are all defined in parent class {@link Address}, but the subclass is still necessary
* to pick up the contact namespace.
*
* <p>This does not implement {@code Overlayable} because it is intended to be bulk replaced on
* update.
*
* @see PostalInfo
*/
@Embeddable
public class ContactAddress extends Address {
/** Builder for {@link ContactAddress}. */
public static class Builder extends Address.Builder<ContactAddress> {}
}
@@ -1,30 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.contact;
import google.registry.model.eppcommon.AuthInfo;
import jakarta.persistence.Embeddable;
import jakarta.xml.bind.annotation.XmlType;
/** A version of authInfo specifically for contacts. */
@Embeddable
@XmlType(namespace = "urn:ietf:params:xml:ns:contact-1.0")
public class ContactAuthInfo extends AuthInfo {
public static ContactAuthInfo create(PasswordAuth pw) {
ContactAuthInfo instance = new ContactAuthInfo();
instance.pw = pw;
return instance;
}
}
@@ -1,198 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.contact;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.util.CollectionUtils.nullToEmpty;
import com.google.common.collect.Maps;
import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.contact.PostalInfo.Type;
import google.registry.model.eppinput.ResourceCommand.AbstractSingleResourceCommand;
import google.registry.model.eppinput.ResourceCommand.ResourceCheck;
import google.registry.model.eppinput.ResourceCommand.ResourceCreateOrChange;
import google.registry.model.eppinput.ResourceCommand.ResourceUpdate;
import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.XmlType;
import jakarta.xml.bind.annotation.adapters.CollapsedStringAdapter;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.util.List;
import java.util.Map;
/** A collection of (vestigial) Contact commands. */
public class ContactCommand {
/** The fields on "chgType" from <a href="http://tools.ietf.org/html/rfc5733">RFC5733</a>. */
@XmlTransient
public static class ContactCreateOrChange extends ImmutableObject
implements ResourceCreateOrChange<EppResource.Builder<?, ?>> {
/** Postal info for the contact. */
List<PostalInfo> postalInfo;
/** Contacts voice number. */
ContactPhoneNumber voice;
/** Contacts fax number. */
ContactPhoneNumber fax;
/** Contacts email address. */
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
String email;
/** Authorization info (aka transfer secret) of the contact. */
ContactAuthInfo authInfo;
/** Disclosure policy. */
Disclose disclose;
/** Helper method to move between the postal infos list and the individual getters. */
protected Map<Type, PostalInfo> getPostalInfosAsMap() {
// There can be no more than 2 postalInfos (enforced by the schema), and if there are 2 they
// must be of different types (not enforced). If the type is repeated, uniqueIndex will throw.
checkState(nullToEmpty(postalInfo).size() <= 2);
return Maps.uniqueIndex(nullToEmpty(postalInfo), PostalInfo::getType);
}
public ContactPhoneNumber getVoice() {
return voice;
}
public ContactPhoneNumber getFax() {
return fax;
}
public String getEmail() {
return email;
}
public ContactAuthInfo getAuthInfo() {
return authInfo;
}
public Disclose getDisclose() {
return disclose;
}
public PostalInfo getInternationalizedPostalInfo() {
return getPostalInfosAsMap().get(Type.INTERNATIONALIZED);
}
public PostalInfo getLocalizedPostalInfo() {
return getPostalInfosAsMap().get(Type.LOCALIZED);
}
}
/** An abstract contact command that contains authorization info. */
@XmlTransient
public static class AbstractContactAuthCommand extends AbstractSingleResourceCommand {
/** Authorization info used to validate if client has permissions to perform this operation. */
ContactAuthInfo authInfo;
@Override
public ContactAuthInfo getAuthInfo() {
return authInfo;
}
}
/**
* A create command for a (vestigial) Contact, mapping "createType" from <a
* href="http://tools.ietf.org/html/rfc5733">RFC5733</a>}.
*/
@XmlType(propOrder = {"contactId", "postalInfo", "voice", "fax", "email", "authInfo", "disclose"})
@XmlRootElement
public static class Create extends ContactCreateOrChange
implements SingleResourceCommand, ResourceCreateOrChange<EppResource.Builder<?, ?>> {
/**
* Unique identifier for this contact.
*
* <p>This is only unique in the sense that for any given lifetime specified as the time range
* from (creationTime, deletionTime) there can only be one contact in the database with this id.
* However, there can be many contacts with the same id and non-overlapping lifetimes.
*/
@XmlElement(name = "id")
String contactId;
@Override
public String getTargetId() {
return contactId;
}
@Override
public ContactAuthInfo getAuthInfo() {
return authInfo;
}
}
/** A delete command for a (vestigial) Contact. */
@XmlRootElement
public static class Delete extends AbstractSingleResourceCommand {}
/** An info request for a (vestigial) Contact. */
@XmlRootElement
@XmlType(propOrder = {"targetId", "authInfo"})
public static class Info extends AbstractContactAuthCommand {}
/** A check request for (vestigial) Contact. */
@XmlRootElement
public static class Check extends ResourceCheck {}
/** A transfer operation for a (vestigial) Contact. */
@XmlRootElement
@XmlType(propOrder = {"targetId", "authInfo"})
public static class Transfer extends AbstractContactAuthCommand {}
/** An update to a (vestigial) Contact. */
@XmlRootElement
@XmlType(propOrder = {"targetId", "innerAdd", "innerRemove", "innerChange"})
public static class Update
extends ResourceUpdate<Update.AddRemove, EppResource.Builder<?, ?>, Update.Change> {
@XmlElement(name = "chg")
protected Change innerChange;
@XmlElement(name = "add")
protected AddRemove innerAdd;
@XmlElement(name = "rem")
protected AddRemove innerRemove;
@Override
protected Change getNullableInnerChange() {
return innerChange;
}
@Override
protected AddRemove getNullableInnerAdd() {
return innerAdd;
}
@Override
protected AddRemove getNullableInnerRemove() {
return innerRemove;
}
/** The inner change type on a contact update command. */
public static class AddRemove extends ResourceUpdate.AddRemove {}
/** The inner change type on a contact update command. */
@XmlType(propOrder = {"postalInfo", "voice", "fax", "email", "authInfo", "disclose"})
public static class Change extends ContactCreateOrChange {}
}
}
@@ -1,140 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.contact;
import com.google.auto.value.AutoValue;
import com.google.auto.value.AutoValue.CopyAnnotations;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppoutput.EppResponse.ResponseData;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
import jakarta.xml.bind.annotation.adapters.CollapsedStringAdapter;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.time.Instant;
import javax.annotation.Nullable;
/** The {@link ResponseData} returned for an EPP info flow on a contact. */
@XmlRootElement(name = "infData")
@XmlType(
propOrder = {
"contactId",
"repoId",
"statusValues",
"postalInfos",
"voiceNumber",
"faxNumber",
"emailAddress",
"currentSponsorRegistrarId",
"creationRegistrarId",
"creationTime",
"lastEppUpdateRegistrarId",
"lastEppUpdateTime",
"lastTransferTime",
"authInfo",
"disclose"
})
@AutoValue
@CopyAnnotations
public abstract class ContactInfoData implements ResponseData {
@XmlElement(name = "id")
abstract String getContactId();
@XmlElement(name = "roid")
abstract String getRepoId();
@XmlElement(name = "status")
abstract ImmutableSet<StatusValue> getStatusValues();
@XmlElement(name = "postalInfo")
abstract ImmutableList<PostalInfo> getPostalInfos();
@XmlElement(name = "voice")
@Nullable
abstract ContactPhoneNumber getVoiceNumber();
@XmlElement(name = "fax")
@Nullable
abstract ContactPhoneNumber getFaxNumber();
@XmlElement(name = "email")
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
@Nullable
abstract String getEmailAddress();
@XmlElement(name = "clID")
abstract String getCurrentSponsorRegistrarId();
@XmlElement(name = "crID")
abstract String getCreationRegistrarId();
@XmlElement(name = "crDate")
abstract Instant getCreationTime();
@XmlElement(name = "upID")
@Nullable
abstract String getLastEppUpdateRegistrarId();
@XmlElement(name = "upDate")
@Nullable
abstract Instant getLastEppUpdateTime();
@XmlElement(name = "trDate")
@Nullable
abstract Instant getLastTransferTime();
@XmlElement(name = "authInfo")
@Nullable
abstract ContactAuthInfo getAuthInfo();
@XmlElement(name = "disclose")
@Nullable
abstract Disclose getDisclose();
/** Builder for {@link ContactInfoData}. */
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setContactId(String contactId);
public abstract Builder setRepoId(String repoId);
public abstract Builder setStatusValues(ImmutableSet<StatusValue> statusValues);
public abstract Builder setPostalInfos(ImmutableList<PostalInfo> postalInfos);
public abstract Builder setVoiceNumber(@Nullable ContactPhoneNumber voiceNumber);
public abstract Builder setFaxNumber(@Nullable ContactPhoneNumber faxNumber);
public abstract Builder setEmailAddress(@Nullable String emailAddress);
public abstract Builder setCurrentSponsorRegistrarId(String currentSponsorRegistrarId);
public abstract Builder setCreationRegistrarId(String creationRegistrarId);
public abstract Builder setCreationTime(Instant creationTime);
public abstract Builder setLastEppUpdateRegistrarId(@Nullable String lastEppUpdateRegistrarId);
public abstract Builder setLastEppUpdateTime(@Nullable Instant lastEppUpdateTime);
public abstract Builder setLastTransferTime(@Nullable Instant lastTransferTime);
public abstract Builder setAuthInfo(@Nullable ContactAuthInfo authInfo);
public abstract Builder setDisclose(@Nullable Disclose disclose);
public abstract ContactInfoData build();
}
public static Builder newBuilder() {
return new AutoValue_ContactInfoData.Builder();
}
}
@@ -1,32 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.contact;
import google.registry.model.eppcommon.PhoneNumber;
import jakarta.persistence.Embeddable;
/**
* EPP Contact Phone Number
*
* <p>This class is embedded inside a (vestigial) Contact to hold the phone number of an EPP
* contact. The fields are all defined in the parent class {@link PhoneNumber}, but the subclass is
* still necessary to pick up the contact namespace.
*/
@Embeddable
public class ContactPhoneNumber extends PhoneNumber {
/** Builder for {@link ContactPhoneNumber}. */
public static class Builder extends PhoneNumber.Builder<ContactPhoneNumber> {}
}
@@ -1,137 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.contact;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableList;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.eppcommon.PresenceMarker;
import google.registry.persistence.converter.PostalInfoChoiceListUserType;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Embedded;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlType;
import java.io.Serializable;
import java.util.List;
import org.hibernate.annotations.Type;
/** The "discloseType" from <a href="http://tools.ietf.org/html/rfc5733">RFC5733</a>. */
@Embeddable
@XmlType(propOrder = {"name", "org", "addr", "voice", "fax", "email"})
public class Disclose extends ImmutableObject implements UnsafeSerializable {
@Type(PostalInfoChoiceListUserType.class)
List<PostalInfoChoice> name;
@Type(PostalInfoChoiceListUserType.class)
List<PostalInfoChoice> org;
@Type(PostalInfoChoiceListUserType.class)
List<PostalInfoChoice> addr;
@Embedded PresenceMarker voice;
@Embedded PresenceMarker fax;
@Embedded PresenceMarker email;
@XmlAttribute
Boolean flag;
public ImmutableList<PostalInfoChoice> getNames() {
return nullToEmptyImmutableCopy(name);
}
public ImmutableList<PostalInfoChoice> getOrgs() {
return nullToEmptyImmutableCopy(org);
}
public ImmutableList<PostalInfoChoice> getAddrs() {
return nullToEmptyImmutableCopy(addr);
}
public PresenceMarker getVoice() {
return voice;
}
public PresenceMarker getFax() {
return fax;
}
public PresenceMarker getEmail() {
return email;
}
public Boolean getFlag() {
return flag;
}
/** The "intLocType" from <a href="http://tools.ietf.org/html/rfc5733">RFC5733</a>. */
public static class PostalInfoChoice extends ImmutableObject implements Serializable {
@XmlAttribute
PostalInfo.Type type;
public PostalInfo.Type getType() {
return type;
}
public static PostalInfoChoice create(PostalInfo.Type type) {
PostalInfoChoice instance = new PostalInfoChoice();
instance.type = type;
return instance;
}
}
/** A builder for {@link Disclose} since it is immutable. */
public static class Builder extends Buildable.Builder<Disclose> {
public Builder setNames(ImmutableList<PostalInfoChoice> names) {
getInstance().name = names;
return this;
}
public Builder setOrgs(ImmutableList<PostalInfoChoice> orgs) {
getInstance().org = orgs;
return this;
}
public Builder setAddrs(ImmutableList<PostalInfoChoice> addrs) {
getInstance().addr = addrs;
return this;
}
public Builder setVoice(PresenceMarker voice) {
getInstance().voice = voice;
return this;
}
public Builder setFax(PresenceMarker fax) {
getInstance().fax = fax;
return this;
}
public Builder setEmail(PresenceMarker email) {
getInstance().email = email;
return this;
}
public Builder setFlag(boolean flag) {
getInstance().flag = flag;
return this;
}
}
}
@@ -1,124 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.contact;
import static com.google.common.base.Preconditions.checkState;
import google.registry.model.Buildable;
import google.registry.model.Buildable.Overlayable;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import jakarta.persistence.Embeddable;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlEnumValue;
import jakarta.xml.bind.annotation.XmlType;
import jakarta.xml.bind.annotation.adapters.NormalizedStringAdapter;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.util.Optional;
/**
* Implementation of both "postalInfoType" and "chgPostalInfoType" from <a href=
* "http://tools.ietf.org/html/rfc5733">RFC5733</a>.
*/
@Embeddable
@XmlType(propOrder = {"name", "org", "address", "type"})
public class PostalInfo extends ImmutableObject
implements Overlayable<PostalInfo>, UnsafeSerializable {
/** The type of the address, either localized or international. */
public enum Type {
@XmlEnumValue("loc")
LOCALIZED,
@XmlEnumValue("int")
INTERNATIONALIZED
}
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
String name;
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
String org;
@XmlElement(name = "addr")
ContactAddress address;
@Enumerated(EnumType.STRING)
@XmlAttribute
Type type;
public String getName() {
return name;
}
public String getOrg() {
return org;
}
public ContactAddress getAddress() {
return address;
}
public Type getType() {
return type;
}
@Override
public PostalInfo overlay(PostalInfo source) {
// Don't overlay the type field, as that should never change.
checkState(source.type == null || source.type == type);
return asBuilder()
.setName(Optional.ofNullable(source.getName()).orElse(name))
.setOrg(Optional.ofNullable(source.getOrg()).orElse(org))
.setAddress(Optional.ofNullable(source.getAddress()).orElse(address))
.build();
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** A builder for constructing {@link PostalInfo}, since its changes get overlayed. */
public static class Builder extends Buildable.Builder<PostalInfo> {
public Builder() {}
private Builder(PostalInfo instance) {
super(instance);
}
public Builder setName(String name) {
getInstance().name = name;
return this;
}
public Builder setOrg(String org) {
getInstance().org = org;
return this;
}
public Builder setAddress(ContactAddress address) {
getInstance().address = address;
return this;
}
public Builder setType(Type type) {
getInstance().type = type;
return this;
}
}
}
@@ -1,30 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
@XmlSchema(
namespace = "urn:ietf:params:xml:ns:contact-1.0",
xmlns = @XmlNs(prefix = "contact", namespaceURI = "urn:ietf:params:xml:ns:contact-1.0"),
elementFormDefault = XmlNsForm.QUALIFIED)
@XmlAccessorType(XmlAccessType.FIELD)
@XmlJavaTypeAdapters({@XmlJavaTypeAdapter(UtcInstantAdapter.class)})
package google.registry.model.contact;
import google.registry.xml.UtcInstantAdapter;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlNs;
import jakarta.xml.bind.annotation.XmlNsForm;
import jakarta.xml.bind.annotation.XmlSchema;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
@@ -17,7 +17,6 @@ package google.registry.model.domain;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Sets.difference;
import static google.registry.util.CollectionUtils.difference;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
import static google.registry.util.CollectionUtils.nullSafeImmutableCopy;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
@@ -28,8 +27,11 @@ import com.google.common.collect.ImmutableSet;
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
import google.registry.flows.domain.DomainFlowUtils.RegistrantProhibitedException;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.model.Buildable;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.ImmutableObject;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppinput.ResourceCommand.AbstractSingleResourceCommand;
import google.registry.model.eppinput.ResourceCommand.ResourceCheck;
import google.registry.model.eppinput.ResourceCommand.ResourceCreateOrChange;
@@ -37,6 +39,8 @@ import google.registry.model.eppinput.ResourceCommand.ResourceUpdate;
import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
import google.registry.model.host.Host;
import google.registry.persistence.VKey;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
@@ -68,10 +72,10 @@ public class DomainCommand {
throws InvalidReferencesException, ParameterValuePolicyErrorException;
}
/** The fields on "chgType" from <a href="http://tools.ietf.org/html/rfc5731">RFC5731</a>. */
/** The fields on "chgType" from <a href="https://tools.ietf.org/html/rfc5731">RFC5731</a>. */
@XmlTransient
public static class DomainCreateOrChange<B extends Domain.Builder> extends ImmutableObject
implements ResourceCreateOrChange<B> {
public abstract static class DomainCreateOrChange<B extends Domain.Builder>
extends ImmutableObject implements ResourceCreateOrChange<B> {
/** The contactId of the registrant who registered this domain. */
@XmlElement(name = "registrant")
@@ -92,9 +96,10 @@ public class DomainCommand {
/**
* A create command for a {@link Domain}, mapping "createType" from <a
* href="http://tools.ietf.org/html/rfc5731">RFC5731</a>.
* href="https://tools.ietf.org/html/rfc5731">RFC5731</a>.
*/
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(
propOrder = {
"domainName",
@@ -147,17 +152,12 @@ public class DomainCommand {
return nullToEmptyImmutableCopy(nameservers);
}
@Override
public DomainAuthInfo getAuthInfo() {
return authInfo;
}
/** Creates a copy of this {@link Create} with hard links to hosts and contacts. */
@Override
public Create cloneAndLinkReferences(Instant now)
throws InvalidReferencesException, ParameterValuePolicyErrorException {
Create clone = clone(this);
clone.nameservers = linkHosts(clone.nameserverHostNames, now);
clone.nameservers = linkHosts(nullSafeImmutableCopy(clone.nameserverHostNames), now);
if (registrantContactId != null) {
throw new RegistrantProhibitedException();
}
@@ -166,14 +166,65 @@ public class DomainCommand {
}
return clone;
}
/** Builder for {@link Create}. */
public static class Builder extends Buildable.Builder<Create> {
public Builder setDomainName(String domainName) {
getInstance().domainName = domainName;
return this;
}
public Builder setPeriod(Period period) {
getInstance().period = period;
return this;
}
public Builder setNameserverHostNames(ImmutableSet<String> nameserverHostNames) {
getInstance().nameserverHostNames =
isNullOrEmpty(nameserverHostNames) ? null : nameserverHostNames;
return this;
}
public Builder setForeignKeyedDesignatedContacts(
ImmutableSet<ForeignKeyedDesignatedContact> foreignKeyedDesignatedContacts) {
getInstance().foreignKeyedDesignatedContacts =
isNullOrEmpty(foreignKeyedDesignatedContacts) ? null : foreignKeyedDesignatedContacts;
return this;
}
public Builder setRegistrant(String registrant) {
getInstance().registrantContactId = registrant;
return this;
}
public Builder setAuthInfo(DomainAuthInfo authInfo) {
getInstance().authInfo = authInfo;
return this;
}
}
}
/** A delete command for a {@link Domain}. */
@XmlRootElement
public static class Delete extends AbstractSingleResourceCommand {}
@XmlAccessorType(XmlAccessType.FIELD)
public static class Delete extends AbstractSingleResourceCommand {
@XmlElement(name = "name")
String name;
@Override
public String getTargetId() {
return name;
}
@Override
public void setTargetId(String targetId) {
this.name = targetId;
}
}
/** An info request for a {@link Domain}. */
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public static class Info extends ImmutableObject implements SingleResourceCommand {
/** The name of the domain to look up, and an attribute specifying the host lookup type. */
@@ -226,7 +277,7 @@ public class DomainCommand {
}
@Override
public DomainAuthInfo getAuthInfo() {
public AuthInfo getAuthInfo() {
return authInfo;
}
}
@@ -237,12 +288,27 @@ public class DomainCommand {
/** A renew command for a {@link Domain}. */
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"name", "currentExpirationDate", "period"})
public static class Renew extends AbstractSingleResourceCommand {
@XmlElement(name = "name")
String name;
@Override
public String getTargetId() {
return name;
}
@Override
public void setTargetId(String targetId) {
this.name = targetId;
}
@XmlElement(name = "curExpDate")
LocalDate currentExpirationDate;
/** The period that this domain's state was set to last for. */
Period period;
@XmlElement Period period;
public LocalDate getCurrentExpirationDate() {
return currentExpirationDate;
@@ -251,13 +317,46 @@ public class DomainCommand {
public Period getPeriod() {
return firstNonNull(period, DEFAULT_PERIOD);
}
/** Builder for {@link Renew}. */
public static class Builder extends Buildable.Builder<Renew> {
public Builder setTargetId(String targetId) {
getInstance().setTargetId(targetId);
return this;
}
public Builder setCurrentExpirationDate(LocalDate currentExpirationDate) {
getInstance().currentExpirationDate = currentExpirationDate;
return this;
}
public Builder setPeriod(Period period) {
getInstance().period = period;
return this;
}
}
}
/** A transfer operation for a {@link Domain}. */
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"name", "period", "authInfo"})
public static class Transfer extends AbstractSingleResourceCommand {
@XmlElement(name = "name")
String name;
@Override
public String getTargetId() {
return name;
}
@Override
public void setTargetId(String targetId) {
this.name = targetId;
}
/** The period to extend this domain's registration upon completion of the transfer. */
Period period;
@XmlElement Period period;
/** Authorization info used to validate if client has permissions to perform this operation. */
DomainAuthInfo authInfo;
@@ -267,25 +366,40 @@ public class DomainCommand {
}
@Override
public DomainAuthInfo getAuthInfo() {
public AuthInfo getAuthInfo() {
return authInfo;
}
}
/** An update to a {@link Domain}. */
@XmlRootElement
@XmlType(propOrder = {"targetId", "innerAdd", "innerRemove", "innerChange"})
public static class Update extends ResourceUpdate<Update.AddRemove, Domain.Builder, Update.Change>
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"name", "innerAdd", "innerRemove", "innerChange"})
public static class Update
extends ResourceUpdate<Update.DomainAddRemove, Domain.Builder, Update.Change>
implements CreateOrUpdate<Update> {
@XmlElement(name = "name")
String name;
@Override
public String getTargetId() {
return name;
}
@Override
public void setTargetId(String targetId) {
this.name = targetId;
}
@XmlElement(name = "chg")
protected Change innerChange;
@XmlElement(name = "add")
protected AddRemove innerAdd;
protected DomainAddRemove innerAdd;
@XmlElement(name = "rem")
protected AddRemove innerRemove;
protected DomainAddRemove innerRemove;
@Override
protected Change getNullableInnerChange() {
@@ -293,25 +407,49 @@ public class DomainCommand {
}
@Override
protected AddRemove getNullableInnerAdd() {
protected DomainAddRemove getNullableInnerAdd() {
return innerAdd;
}
@Override
protected AddRemove getNullableInnerRemove() {
protected DomainAddRemove getNullableInnerRemove() {
return innerRemove;
}
public boolean noChangesPresent() {
AddRemove emptyAddRemove = new AddRemove();
DomainAddRemove emptyAddRemove = new DomainAddRemove();
return emptyAddRemove.equals(getInnerAdd())
&& emptyAddRemove.equals(getInnerRemove())
&& new Change().equals(getInnerChange());
}
/** Builder for {@link Update}. */
public static class Builder extends Buildable.Builder<Update> {
public Builder setTargetId(String targetId) {
getInstance().setTargetId(targetId);
return this;
}
public Builder setInnerAdd(DomainAddRemove innerAdd) {
getInstance().innerAdd = innerAdd;
return this;
}
public Builder setInnerRemove(DomainAddRemove innerRemove) {
getInstance().innerRemove = innerRemove;
return this;
}
public Builder setInnerChange(Change innerChange) {
getInstance().innerChange = innerChange;
return this;
}
}
/** The inner change type on a domain update command. */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"nameserverHostNames", "foreignKeyedDesignatedContacts", "statusValues"})
public static class AddRemove extends ResourceUpdate.AddRemove {
public static class DomainAddRemove extends ResourceUpdate.AddRemove {
/** Fully qualified host names of the hosts that are the nameservers for the domain. */
@XmlElementWrapper(name = "ns")
@XmlElement(name = "hostObj")
@@ -324,6 +462,25 @@ public class DomainCommand {
@XmlElement(name = "contact")
Set<ForeignKeyedDesignatedContact> foreignKeyedDesignatedContacts;
@XmlElement(name = "status")
Set<StatusValue> statusValues;
public boolean isEmpty() {
return isNullOrEmpty(nameserverHostNames)
&& isNullOrEmpty(foreignKeyedDesignatedContacts)
&& isNullOrEmpty(statusValues);
}
@Override
public void setStatusValues(ImmutableSet<StatusValue> statusValues) {
this.statusValues = statusValues;
}
@Override
public ImmutableSet<StatusValue> getStatusValues() {
return nullToEmptyImmutableCopy(statusValues);
}
public ImmutableSet<String> getNameserverHostNames() {
return nullSafeImmutableCopy(nameserverHostNames);
}
@@ -332,11 +489,25 @@ public class DomainCommand {
return nullToEmptyImmutableCopy(nameservers);
}
/** Creates a copy of this {@link AddRemove} with hard links to hosts and contacts. */
private AddRemove cloneAndLinkReferences(Instant now)
/** Builder for {@link DomainAddRemove}. */
public static class Builder extends Buildable.Builder<DomainAddRemove> {
public Builder setNameserverHostNames(ImmutableSet<String> nameserverHostNames) {
getInstance().nameserverHostNames =
isNullOrEmpty(nameserverHostNames) ? null : nameserverHostNames;
return this;
}
public Builder setStatusValues(ImmutableSet<StatusValue> statusValues) {
getInstance().statusValues = isNullOrEmpty(statusValues) ? null : statusValues;
return this;
}
}
/** Creates a copy of this {@link DomainAddRemove} with hard links to hosts and contacts. */
private DomainAddRemove cloneAndLinkReferences(Instant now)
throws InvalidReferencesException, ContactsProhibitedException {
AddRemove clone = clone(this);
clone.nameservers = linkHosts(clone.nameserverHostNames, now);
DomainAddRemove clone = clone(this);
clone.nameservers = linkHosts(nullSafeImmutableCopy(clone.nameserverHostNames), now);
if (!isNullOrEmpty(foreignKeyedDesignatedContacts)) {
throw new ContactsProhibitedException();
}
@@ -345,8 +516,17 @@ public class DomainCommand {
}
/** The inner change type on a domain update command. */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"registrantContactId", "authInfo"})
public static class Change extends DomainCreateOrChange<Domain.Builder> {
/** Builder for {@link Change}. */
public static class Builder extends Buildable.Builder<Change> {
public Builder setAuthInfo(DomainAuthInfo authInfo) {
getInstance().authInfo = authInfo;
return this;
}
}
Change cloneAndLinkReferences() throws RegistrantProhibitedException {
Change clone = clone(this);
if (clone.registrantContactId != null) {
@@ -373,7 +553,7 @@ public class DomainCommand {
}
}
private static Set<VKey<Host>> linkHosts(Set<String> hostNames, Instant now)
private static ImmutableSet<VKey<Host>> linkHosts(ImmutableSet<String> hostNames, Instant now)
throws InvalidReferencesException {
if (hostNames == null) {
return null;
@@ -383,7 +563,7 @@ public class DomainCommand {
/** Loads host keys to cached EPP resources by their foreign keys. */
private static ImmutableMap<String, VKey<Host>> loadByForeignKeysCached(
Set<String> foreignKeys, Instant now) throws InvalidReferencesException {
ImmutableSet<String> foreignKeys, Instant now) throws InvalidReferencesException {
ImmutableMap<String, VKey<Host>> fks =
ForeignKeyUtils.loadKeysByCacheIfEnabled(Host.class, foreignKeys, now);
if (!fks.keySet().equals(foreignKeys)) {
@@ -394,14 +574,14 @@ public class DomainCommand {
}
/** Exception to throw when referenced objects don't exist. */
public static class InvalidReferencesException extends Exception {
public static class InvalidReferencesException extends ParameterValuePolicyErrorException {
private final ImmutableSet<String> foreignKeys;
private final Class<?> type;
InvalidReferencesException(Class<?> type, ImmutableSet<String> foreignKeys) {
public InvalidReferencesException(Class<?> type, Set<String> foreignKeys) {
super(String.format("Invalid %s reference IDs: %s", type.getSimpleName(), foreignKeys));
this.type = checkNotNull(type);
this.foreignKeys = foreignKeys;
this.foreignKeys = nullToEmptyImmutableCopy(foreignKeys);
}
public ImmutableSet<String> getForeignKeys() {
@@ -20,16 +20,22 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Range;
import google.registry.model.Buildable;
import google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.Period;
/**
* A fee, in currency units specified elsewhere in the xml, with type of the fee an optional fee
* description.
* A fee, in currency units specified elsewhere in the XML, with a type and an optional description.
*/
public class Fee extends BaseFee {
@Override
public Fee clone() {
return (Fee) super.clone();
}
/** Creates a Fee for the given cost and type with the default description. */
public static Fee create(
BigDecimal cost, FeeType type, boolean isPremium, Object... descriptionArgs) {
@@ -55,7 +61,7 @@ public class Fee extends BaseFee {
BigDecimal cost, FeeType type, boolean isPremium, String description) {
Fee instance = new Fee();
instance.cost = checkNotNull(cost);
checkArgument(instance.cost.signum() >= 0, "Cost must be a positive number");
checkArgument(instance.cost.signum() >= 0, "Cost must be a non-negative number");
instance.type = checkNotNull(type);
instance.isPremium = isPremium;
instance.description = description;
@@ -68,4 +74,38 @@ public class Fee extends BaseFee {
ServiceExtension.FEE_0_12.getUri(),
ServiceExtension.FEE_0_11.getUri(),
ServiceExtension.FEE_0_6.getUri());
/** Builder for {@link Fee}. */
public static class Builder extends Buildable.Builder<Fee> {
/** Sets the cost of the fee. */
public Builder setCost(BigDecimal cost) {
getInstance().cost = cost;
return this;
}
/** Sets the description of the fee. */
public Builder setDescription(String description) {
getInstance().description = description;
return this;
}
/** Sets whether the fee is refundable. */
public Builder setRefundable(Boolean refundable) {
getInstance().refundable = refundable;
return this;
}
/** Sets the grace period of the fee. */
public Builder setGracePeriod(Period gracePeriod) {
getInstance().gracePeriod = gracePeriod;
return this;
}
/** Sets when the fee is applied. */
public Builder setApplied(AppliedType applied) {
getInstance().applied = applied;
return this;
}
}
}
@@ -78,6 +78,10 @@ public abstract class FeeQueryCommandExtensionItem extends ImmutableObject {
/** The period for the command being checked. */
Period period;
public void setPeriod(Period period) {
this.period = period;
}
/**
* Three-character ISO4217 currency code.
*
@@ -30,7 +30,7 @@ public abstract class FeeTransformCommandExtension
extends ImmutableObject implements CommandExtension {
/** The currency of the fee. */
CurrencyUnit currency;
@XmlElement public CurrencyUnit currency;
/**
* The magnitude of the fee, in the specified units, with an optional description.
@@ -38,7 +38,7 @@ public abstract class FeeTransformCommandExtension
* <p>This is a list because a single operation can involve multiple fees.
*/
@XmlElement(name = "fee")
List<Fee> fees;
public List<Fee> fees;
public CurrencyUnit getCurrency() {
return currency;
@@ -31,7 +31,7 @@ import org.joda.money.CurrencyUnit;
public class FeeTransformResponseExtension extends ImmutableObject implements ResponseExtension {
/** The currency of the fee. */
CurrencyUnit currency;
@XmlElement CurrencyUnit currency;
/**
* The magnitude of the fee, in the specified units, with an optional description.
@@ -14,6 +14,7 @@
package google.registry.model.domain.fee06;
import google.registry.model.domain.Period;
import google.registry.model.domain.fee.FeeCheckCommandExtensionItem;
import google.registry.model.domain.fee.FeeExtensionCommandDescriptor;
import jakarta.xml.bind.annotation.XmlType;
@@ -33,6 +34,16 @@ public class FeeCheckCommandExtensionItemV06 extends FeeCheckCommandExtensionIte
/** The command being checked. */
FeeExtensionCommandDescriptor command;
public static FeeCheckCommandExtensionItemV06 create(
String name, CurrencyUnit currency, FeeExtensionCommandDescriptor command, Period period) {
FeeCheckCommandExtensionItemV06 instance = new FeeCheckCommandExtensionItemV06();
instance.name = name;
instance.currency = currency;
instance.command = command;
instance.setPeriod(period);
return instance;
}
/** The name of the command being checked. */
@Override
public CommandName getCommandName() {
@@ -25,16 +25,22 @@ import jakarta.xml.bind.annotation.XmlRootElement;
import java.util.List;
import org.joda.money.CurrencyUnit;
/** Version 0.6 of the fee extension that may be present on domain check commands. */
/**
* An XML data object that represents version 0.6 of the fee extension that may be present on EPP
* domain check commands.
*/
@XmlRootElement(name = "check")
public class FeeCheckCommandExtensionV06 extends ImmutableObject
implements FeeCheckCommandExtension<
FeeCheckCommandExtensionItemV06,
FeeCheckResponseExtensionV06> {
FeeCheckCommandExtensionItemV06, FeeCheckResponseExtensionV06> {
@XmlElement(name = "domain")
List<FeeCheckCommandExtensionItemV06> items;
public void setItems(ImmutableList<FeeCheckCommandExtensionItemV06> items) {
this.items = items;
}
@Override
public CurrencyUnit getCurrency() {
return null; // This version of the fee extension doesn't specify a top-level currency.
@@ -15,13 +15,20 @@
package google.registry.model.domain.fee06;
import com.google.common.collect.ImmutableList;
import google.registry.model.Buildable;
import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeCreateCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.XmlType;
import org.joda.money.CurrencyUnit;
/** A fee extension that may be present on domain create commands. */
/**
* An XML data object that represents a fee extension that may be present on EPP domain create
* commands.
*/
@XmlRootElement(name = "create")
@XmlType(propOrder = {"currency", "fees"})
public class FeeCreateCommandExtensionV06 extends FeeCreateCommandExtension {
@@ -31,12 +38,23 @@ public class FeeCreateCommandExtensionV06 extends FeeCreateCommandExtension {
return new FeeTransformResponseExtension.Builder(new FeeCreateResponseExtensionV06());
}
/**
* This method is overridden and not annotated for JAXB because this version of the extension
* doesn't support the "credit" field.
*/
/** This version of the extension doesn't support the "credit" field. */
@Override
@XmlTransient
public ImmutableList<Credit> getCredits() {
return ImmutableList.of();
}
/** Builder for {@link FeeCreateCommandExtensionV06}. */
public static class Builder extends Buildable.Builder<FeeCreateCommandExtensionV06> {
public Builder setCurrency(CurrencyUnit currency) {
getInstance().currency = currency;
return this;
}
public Builder setFees(ImmutableList<Fee> fees) {
getInstance().fees = fees;
return this;
}
}
}
@@ -14,8 +14,6 @@
package google.registry.model.domain.fee06;
import com.google.common.collect.ImmutableList;
import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
@@ -25,12 +23,5 @@ import jakarta.xml.bind.annotation.XmlType;
* domain create commands.
*/
@XmlRootElement(name = "creData")
@XmlType(propOrder = {"currency", "fees"})
public class FeeCreateResponseExtensionV06 extends FeeTransformResponseExtension {
/** This version of the extension doesn't support the "credit" field. */
@Override
public ImmutableList<Credit> getCredits() {
return ImmutableList.of();
}
}
@XmlType(propOrder = {"currency", "fees", "credits"})
public class FeeCreateResponseExtensionV06 extends FeeTransformResponseExtension {}
@@ -20,7 +20,7 @@ import jakarta.xml.bind.annotation.XmlType;
/**
* An XML data object that represents a fee extension that may be present on the response to EPP
* domain create commands.
* domain delete commands.
*/
@XmlRootElement(name = "delData")
@XmlType(propOrder = {"currency", "fees", "credits"})
@@ -19,9 +19,13 @@ import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.FeeRenewCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.XmlType;
/** A fee extension that may be present on domain renew commands. */
/**
* An XML data object that represents a fee extension that may be present on EPP domain renew
* commands.
*/
@XmlRootElement(name = "renew")
@XmlType(propOrder = {"currency", "fees"})
public class FeeRenewCommandExtensionV06 extends FeeRenewCommandExtension {
@@ -33,6 +37,7 @@ public class FeeRenewCommandExtensionV06 extends FeeRenewCommandExtension {
/** This version of the extension doesn't support the "credit" field. */
@Override
@XmlTransient
public ImmutableList<Credit> getCredits() {
return ImmutableList.of();
}
@@ -14,8 +14,6 @@
package google.registry.model.domain.fee06;
import com.google.common.collect.ImmutableList;
import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
@@ -25,12 +23,5 @@ import jakarta.xml.bind.annotation.XmlType;
* domain renew commands.
*/
@XmlRootElement(name = "renData")
@XmlType(propOrder = {"currency", "fees"})
public class FeeRenewResponseExtensionV06 extends FeeTransformResponseExtension {
/** This version of the extension doesn't support the "credit" field. */
@Override
public ImmutableList<Credit> getCredits() {
return super.getCredits();
}
}
@XmlType(propOrder = {"currency", "fees", "credits"})
public class FeeRenewResponseExtensionV06 extends FeeTransformResponseExtension {}
@@ -19,9 +19,13 @@ import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.FeeTransferCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.XmlType;
/** A fee extension that may be present on domain transfer requests. */
/**
* An XML data object that represents a fee extension that may be present on EPP domain transfer
* commands.
*/
@XmlRootElement(name = "transfer")
@XmlType(propOrder = {"currency", "fees"})
public class FeeTransferCommandExtensionV06 extends FeeTransferCommandExtension {
@@ -33,6 +37,7 @@ public class FeeTransferCommandExtensionV06 extends FeeTransferCommandExtension
/** This version of the extension doesn't support the "credit" field. */
@Override
@XmlTransient
public ImmutableList<Credit> getCredits() {
return ImmutableList.of();
}
@@ -14,23 +14,14 @@
package google.registry.model.domain.fee06;
import com.google.common.collect.ImmutableList;
import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
/**
* An XML data object that represents a fee extension that may be present on the response to EPP
* domain transfer requests.
* domain transfer commands.
*/
@XmlRootElement(name = "trnData")
@XmlType(propOrder = {"currency", "fees"})
public class FeeTransferResponseExtensionV06 extends FeeTransformResponseExtension {
/** This version of the extension doesn't support the "credit" field. */
@Override
public ImmutableList<Credit> getCredits() {
return super.getCredits();
}
}
@XmlType(propOrder = {"currency", "fees", "credits"})
public class FeeTransferResponseExtensionV06 extends FeeTransformResponseExtension {}
@@ -19,9 +19,13 @@ import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import google.registry.model.domain.fee.FeeUpdateCommandExtension;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.XmlType;
/** A fee extension that may be present on domain update commands. */
/**
* An XML data object that represents a fee extension that may be present on EPP domain update
* commands.
*/
@XmlRootElement(name = "update")
@XmlType(propOrder = {"currency", "fees"})
public class FeeUpdateCommandExtensionV06 extends FeeUpdateCommandExtension {
@@ -33,6 +37,7 @@ public class FeeUpdateCommandExtensionV06 extends FeeUpdateCommandExtension {
/** This version of the extension doesn't support the "credit" field. */
@Override
@XmlTransient
public ImmutableList<Credit> getCredits() {
return ImmutableList.of();
}
@@ -14,8 +14,6 @@
package google.registry.model.domain.fee06;
import com.google.common.collect.ImmutableList;
import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
@@ -25,12 +23,5 @@ import jakarta.xml.bind.annotation.XmlType;
* domain update commands.
*/
@XmlRootElement(name = "updData")
@XmlType(propOrder = {"currency", "fees"})
public class FeeUpdateResponseExtensionV06 extends FeeTransformResponseExtension {
/** This version of the extension doesn't support the "credit" field. */
@Override
public ImmutableList<Credit> getCredits() {
return super.getCredits();
}
}
@XmlType(propOrder = {"currency", "fees", "credits"})
public class FeeUpdateResponseExtensionV06 extends FeeTransformResponseExtension {}
@@ -17,13 +17,16 @@ package google.registry.model.domain.fee12;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableList;
import google.registry.model.Buildable;
import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeCreateCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
import java.util.List;
import org.joda.money.CurrencyUnit;
/** A fee extension that may be present on domain create commands. */
@XmlRootElement(name = "create")
@@ -42,4 +45,22 @@ public class FeeCreateCommandExtensionV12 extends FeeCreateCommandExtension {
public FeeTransformResponseExtension.Builder createResponseBuilder() {
return new FeeTransformResponseExtension.Builder(new FeeCreateResponseExtensionV12());
}
/** Builder for {@link FeeCreateCommandExtensionV12}. */
public static class Builder extends Buildable.Builder<FeeCreateCommandExtensionV12> {
public Builder setCurrency(CurrencyUnit currency) {
getInstance().currency = currency;
return this;
}
public Builder setFees(ImmutableList<Fee> fees) {
getInstance().fees = fees;
return this;
}
public Builder setCredits(ImmutableList<Credit> credits) {
getInstance().credits = credits;
return this;
}
}
}
@@ -17,18 +17,21 @@ package google.registry.model.domain.fee12;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableList;
import google.registry.model.Buildable;
import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeRenewCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
import java.util.List;
import org.joda.money.CurrencyUnit;
/** A fee extension that may be present on domain renew commands. */
@XmlRootElement(name = "renew")
@XmlType(propOrder = {"currency", "fees", "credits"})
public class FeeRenewCommandExtensionV12 extends FeeRenewCommandExtension {
public class FeeRenewCommandExtensionV12 extends FeeRenewCommandExtension {
@XmlElement(name = "credit")
List<Credit> credits;
@@ -42,4 +45,22 @@ public class FeeRenewCommandExtensionV12 extends FeeRenewCommandExtension {
public FeeTransformResponseExtension.Builder createResponseBuilder() {
return new FeeTransformResponseExtension.Builder(new FeeRenewResponseExtensionV12());
}
/** Builder for {@link FeeRenewCommandExtensionV12}. */
public static class Builder extends Buildable.Builder<FeeRenewCommandExtensionV12> {
public Builder setCurrency(CurrencyUnit currency) {
getInstance().currency = currency;
return this;
}
public Builder setFees(ImmutableList<Fee> fees) {
getInstance().fees = fees;
return this;
}
public Builder setCredits(ImmutableList<Credit> credits) {
getInstance().credits = credits;
return this;
}
}
}
@@ -17,13 +17,16 @@ package google.registry.model.domain.fee12;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableList;
import google.registry.model.Buildable;
import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeTransferCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
import java.util.List;
import org.joda.money.CurrencyUnit;
/** A fee extension that may be present on domain transfer requests. */
@XmlRootElement(name = "transfer")
@@ -42,4 +45,22 @@ public class FeeTransferCommandExtensionV12 extends FeeTransferCommandExtension
public FeeTransformResponseExtension.Builder createResponseBuilder() {
return new FeeTransformResponseExtension.Builder(new FeeTransferResponseExtensionV12());
}
/** Builder for {@link FeeTransferCommandExtensionV12}. */
public static class Builder extends Buildable.Builder<FeeTransferCommandExtensionV12> {
public Builder setCurrency(CurrencyUnit currency) {
getInstance().currency = currency;
return this;
}
public Builder setFees(ImmutableList<Fee> fees) {
getInstance().fees = fees;
return this;
}
public Builder setCredits(ImmutableList<Credit> credits) {
getInstance().credits = credits;
return this;
}
}
}
@@ -17,13 +17,16 @@ package google.registry.model.domain.fee12;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableList;
import google.registry.model.Buildable;
import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import google.registry.model.domain.fee.FeeUpdateCommandExtension;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
import java.util.List;
import org.joda.money.CurrencyUnit;
/** A fee extension that may be present on domain update commands. */
@XmlRootElement(name = "update")
@@ -42,4 +45,22 @@ public class FeeUpdateCommandExtensionV12 extends FeeUpdateCommandExtension {
public FeeTransformResponseExtension.Builder createResponseBuilder() {
return new FeeTransformResponseExtension.Builder(new FeeUpdateResponseExtensionV12());
}
/** Builder for {@link FeeUpdateCommandExtensionV12}. */
public static class Builder extends Buildable.Builder<FeeUpdateCommandExtensionV12> {
public Builder setCurrency(CurrencyUnit currency) {
getInstance().currency = currency;
return this;
}
public Builder setFees(ImmutableList<Fee> fees) {
getInstance().fees = fees;
return this;
}
public Builder setCredits(ImmutableList<Credit> credits) {
getInstance().credits = credits;
return this;
}
}
}
@@ -19,34 +19,37 @@ import static com.google.common.base.MoreObjects.firstNonNull;
import google.registry.model.ImmutableObject;
import google.registry.model.eppinput.EppInput.CommandExtension;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlEnumValue;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
/**
* An XML data object that represents a launch extension that may be present on EPP domain check
* commands.
*
* <p>This object holds XML data which JAXB will unmarshal from an EPP domain check command
* extension. The XML will have the following enclosing structure:
* extension. The XML will have the following enclosing structure:
*
* <pre> {@code
* <epp>
* <command>
* <create>
* <!-- domain check XML data -->
* </create>
* <extension>
* <launch:check>
* <!-- launch check XML payload data -->
* </launch:check>
* </extension>
* </command>
* </epp>
* } </pre>
* <pre>{@code
* <epp>
* <command>
* <create>
* <!-- domain check XML data -->
* </create>
* <extension>
* <launch:check>
* <!-- launch check XML payload data -->
* </launch:check>
* </extension>
* </command>
* </epp>
* }</pre>
*
* @see CommandExtension
*/
@XmlRootElement(name = "check")
@XmlType(propOrder = "phase")
public class LaunchCheckExtension extends ImmutableObject implements CommandExtension {
/** The default check type is "claims" if not specified. */
@@ -67,11 +70,18 @@ public class LaunchCheckExtension extends ImmutableObject implements CommandExte
* The launch phase this command is intended to run against. If it does not match the server's
* current launch phase, the command will be rejected.
*/
LaunchPhase phase;
@XmlElement LaunchPhase phase;
@XmlAttribute
CheckType type;
public static LaunchCheckExtension create(CheckType type, LaunchPhase phase) {
LaunchCheckExtension instance = new LaunchCheckExtension();
instance.type = type;
instance.phase = phase;
return instance;
}
public CheckType getCheckType() {
return firstNonNull(type, DEFAULT_CHECK_TYPE);
}
@@ -14,39 +14,62 @@
package google.registry.model.domain.metadata;
import static com.google.common.base.MoreObjects.firstNonNull;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.eppinput.EppInput.CommandExtension;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
import javax.annotation.Nullable;
/** A metadata extension that may be present on EPP create/mutate commands. */
/**
* Extension for EPP commands that provides metadata.
*
* @see <a href="https://www.google.com/search?q=EPP+metadata+extension">EPP Metadata Extension</a>
*/
@XmlRootElement(name = "metadata")
@XmlType(propOrder = {"reason", "requestedByRegistrar", "isAnchorTenant"})
public class MetadataExtension extends ImmutableObject implements CommandExtension {
/** The reason for the change. */
@XmlElement(name = "reason")
String reason;
/** Reason for the command. */
@XmlElement @Nullable String reason;
/** Whether a change was requested by a registrar. */
@XmlElement(name = "requestedByRegistrar")
boolean requestedByRegistrar;
/** Whether the command was requested by a registrar. */
@XmlElement Boolean requestedByRegistrar;
/**
* Whether a domain is being created for an anchor tenant. This field is only
* relevant for domain creates, and should be omitted for all other operations.
*/
/** Whether this is an anchor tenant. */
@XmlElement(name = "anchorTenant")
boolean isAnchorTenant;
Boolean isAnchorTenant;
public String getReason() {
return reason;
}
public boolean getRequestedByRegistrar() {
public Boolean getRequestedByRegistrar() {
return requestedByRegistrar;
}
public boolean getIsAnchorTenant() {
return isAnchorTenant;
public Boolean getIsAnchorTenant() {
return firstNonNull(isAnchorTenant, false);
}
/** Builder for {@link MetadataExtension}. */
public static class Builder extends Buildable.Builder<MetadataExtension> {
public Builder setReason(String reason) {
getInstance().reason = reason;
return this;
}
public Builder setRequestedByRegistrar(Boolean requestedByRegistrar) {
getInstance().requestedByRegistrar = requestedByRegistrar;
return this;
}
public Builder setAnchorTenant(Boolean isAnchorTenant) {
getInstance().isAnchorTenant = isAnchorTenant;
return this;
}
}
}
@@ -23,12 +23,14 @@ import jakarta.persistence.Transient;
import jakarta.xml.bind.DatatypeConverter;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.XmlType;
import jakarta.xml.bind.annotation.adapters.HexBinaryAdapter;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/** Base class for {@link DomainDsData} and {@link DomainDsDataHistory}. */
@MappedSuperclass
@Access(AccessType.FIELD)
@XmlType(propOrder = {"keyTag", "algorithm", "digestType", "digest"})
public abstract class DomainDsDataBase extends ImmutableObject implements UnsafeSerializable {
@XmlTransient @Transient @Insignificant String domainRepoId;
@@ -14,11 +14,13 @@
package google.registry.model.domain.secdns;
import static google.registry.util.CollectionUtils.nullSafeImmutableCopy;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableSet;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.eppinput.EppInput.CommandExtension;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
import java.util.Set;
@@ -33,9 +35,10 @@ public class SecDnsCreateExtension extends ImmutableObject implements CommandExt
* <p>We do not support expirations, but we need this field to be able to return appropriate
* errors.
*/
Long maxSigLife;
@XmlElement Long maxSigLife;
/** Signatures for this domain. */
@XmlElement(name = "dsData")
Set<DomainDsData> dsData;
public Long getMaxSigLife() {
@@ -43,6 +46,19 @@ public class SecDnsCreateExtension extends ImmutableObject implements CommandExt
}
public ImmutableSet<DomainDsData> getDsData() {
return nullSafeImmutableCopy(dsData);
return nullToEmptyImmutableCopy(dsData);
}
/** Builder for {@link SecDnsCreateExtension}. */
public static class Builder extends Buildable.Builder<SecDnsCreateExtension> {
public Builder setDsData(ImmutableSet<DomainDsData> dsData) {
getInstance().dsData = dsData;
return this;
}
public Builder setMaxSigLife(Long maxSigLife) {
getInstance().maxSigLife = maxSigLife;
return this;
}
}
}
@@ -17,6 +17,7 @@ package google.registry.model.domain.secdns;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableSet;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.eppinput.EppInput.CommandExtension;
import jakarta.xml.bind.annotation.XmlAttribute;
@@ -46,7 +47,7 @@ public class SecDnsUpdateExtension extends ImmutableObject implements CommandExt
Remove remove;
/** Allows adding new delegations. */
Add add;
@XmlElement Add add;
/** Would allow changing maxSigLife except that we don't support it. */
@XmlElement(name = "chg")
@@ -68,31 +69,88 @@ public class SecDnsUpdateExtension extends ImmutableObject implements CommandExt
return Optional.ofNullable(change);
}
/** Builder for {@link SecDnsUpdateExtension}. */
public static class Builder extends Buildable.Builder<SecDnsUpdateExtension> {
public Builder setUrgent(Boolean urgent) {
getInstance().urgent = urgent;
return this;
}
public Builder setRemove(Remove remove) {
getInstance().remove = remove;
return this;
}
public Builder setAdd(Add add) {
getInstance().add = add;
return this;
}
}
@XmlTransient
abstract static class AddRemoveBase extends ImmutableObject {
/** Delegations to add or remove. */
abstract static class Builder<T extends AddRemoveBase, B extends Builder<T, B>>
extends Buildable.Builder<T> {
public abstract B setDsData(ImmutableSet<DomainDsData> dsData);
}
}
/** The inner add type on the update extension. */
@XmlType(propOrder = "dsData")
public static class Add extends AddRemoveBase {
/** Delegations to add. */
@XmlElement(name = "dsData")
Set<DomainDsData> dsData;
public ImmutableSet<DomainDsData> getDsData() {
return nullToEmptyImmutableCopy(dsData);
}
}
/** The inner add type on the update extension. */
public static class Add extends AddRemoveBase {}
/** Builder for {@link Add}. */
public static class Builder extends AddRemoveBase.Builder<Add, Builder> {
@Override
public Builder setDsData(ImmutableSet<DomainDsData> dsData) {
getInstance().dsData = dsData;
return this;
}
}
}
/** The inner remove type on the update extension. */
@XmlType(propOrder = {"all", "dsData"})
public static class Remove extends AddRemoveBase {
/** Whether to remove all delegations. */
Boolean all;
@XmlElement Boolean all;
/** Delegations to remove. */
@XmlElement(name = "dsData")
Set<DomainDsData> dsData;
public Boolean getAll() {
return all;
}
public ImmutableSet<DomainDsData> getDsData() {
return nullToEmptyImmutableCopy(dsData);
}
/** Builder for {@link Remove}. */
public static class Builder extends AddRemoveBase.Builder<Remove, Builder> {
public Builder setAll(Boolean all) {
getInstance().all = all;
return this;
}
@Override
public Builder setDsData(ImmutableSet<DomainDsData> dsData) {
getInstance().dsData = dsData;
return this;
}
}
}
/** The inner change type on the update extension, though we don't actually support changes. */
@XmlType(propOrder = "maxSigLife")
public static class Change extends ImmutableObject {
/**
* Time in seconds until the signature should expire.
@@ -100,6 +158,7 @@ public class SecDnsUpdateExtension extends ImmutableObject implements CommandExt
* <p>We do not support expirations, but we need this field to be able to return appropriate
* errors.
*/
@XmlElement(name = "maxSigLife")
Long maxSigLife;
}
}
@@ -16,9 +16,11 @@ package google.registry.model.domain.superuser;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
/** A superuser extension that may be present on domain delete commands. */
@XmlRootElement(name = "domainDelete")
@XmlType(propOrder = {"redemptionGracePeriodDays", "pendingDeleteDays"})
public class DomainDeleteSuperuserExtension extends SuperuserExtension {
@XmlElement(name = "redemptionGracePeriodDays")
@@ -27,6 +29,14 @@ public class DomainDeleteSuperuserExtension extends SuperuserExtension {
@XmlElement(name = "pendingDeleteDays")
int pendingDeleteDays;
public static DomainDeleteSuperuserExtension create(
int redemptionGracePeriodDays, int pendingDeleteDays) {
DomainDeleteSuperuserExtension instance = new DomainDeleteSuperuserExtension();
instance.redemptionGracePeriodDays = redemptionGracePeriodDays;
instance.pendingDeleteDays = pendingDeleteDays;
return instance;
}
public int getRedemptionGracePeriodDays() {
return redemptionGracePeriodDays;
}
@@ -14,22 +14,28 @@
package google.registry.model.domain.superuser;
import static com.google.common.base.Strings.isNullOrEmpty;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
import java.util.Optional;
import javax.annotation.Nullable;
/** A superuser extension that may be present on domain update commands. */
@XmlRootElement(name = "domainUpdate")
@XmlType(propOrder = "autorenews")
public class DomainUpdateSuperuserExtension extends SuperuserExtension {
@XmlElement(name = "autorenews")
@Nullable
String autorenews;
Boolean autorenews;
public static DomainUpdateSuperuserExtension create(@Nullable Boolean autorenews) {
DomainUpdateSuperuserExtension instance = new DomainUpdateSuperuserExtension();
instance.autorenews = autorenews;
return instance;
}
public Optional<Boolean> getAutorenews() {
return Optional.ofNullable(isNullOrEmpty(autorenews) ? null : Boolean.valueOf(autorenews));
return Optional.ofNullable(autorenews);
}
}
@@ -35,6 +35,12 @@ public class AllocationTokenExtension extends ImmutableObject implements Command
@XmlJavaTypeAdapter(TrimWhitespaceAdapter.class)
String allocationToken;
public static AllocationTokenExtension create(String allocationToken) {
AllocationTokenExtension instance = new AllocationTokenExtension();
instance.allocationToken = allocationToken;
return instance;
}
public String getAllocationToken() {
return allocationToken;
}
@@ -49,7 +49,6 @@ import java.util.stream.Stream;
* also matches the "addrType" type from <a
* href="http://tools.ietf.org/html/draft-lozano-tmch-smd">Mark and Signed Mark Objects Mapping</a>.
*
* @see google.registry.model.contact.ContactAddress
* @see google.registry.model.mark.MarkAddress
* @see google.registry.model.registrar.RegistrarAddress
*/
@@ -40,7 +40,6 @@ public class EppXmlTransformer {
ImmutableList.of(
"eppcom.xsd",
"epp.xsd",
"contact.xsd",
"host.xsd",
"domain.xsd",
"rgp.xsd",
@@ -44,7 +44,6 @@ import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
*
* </blockquote>
*
* @see google.registry.model.contact.ContactPhoneNumber
* @see google.registry.model.mark.MarkPhoneNumber
*/
@XmlTransient
@@ -52,12 +52,6 @@ public class ProtocolDefinition {
public static final ImmutableSet<String> SUPPORTED_OBJECT_SERVICES =
ImmutableSet.of("urn:ietf:params:xml:ns:host-1.0", "urn:ietf:params:xml:ns:domain-1.0");
public static final ImmutableSet<String> SUPPORTED_OBJECT_SERVICES_WITH_CONTACT =
new ImmutableSet.Builder<String>()
.addAll(SUPPORTED_OBJECT_SERVICES)
.add("urn:ietf:params:xml:ns:contact-1.0")
.build();
/** Enum representing which environments should have which service extensions enabled. */
private enum ServiceExtensionVisibility {
ALL,
@@ -0,0 +1,181 @@
// Copyright 2026 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.model.eppinput;
import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName.CREATE;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import google.registry.model.domain.Period;
import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeExtensionCommandDescriptor;
import google.registry.model.domain.fee06.FeeCheckCommandExtensionItemV06;
import google.registry.model.domain.fee06.FeeCheckCommandExtensionV06;
import google.registry.model.domain.fee06.FeeCreateCommandExtensionV06;
import google.registry.model.domain.fee12.FeeCreateCommandExtensionV12;
import google.registry.model.domain.launch.LaunchCheckExtension;
import google.registry.model.domain.launch.LaunchCheckExtension.CheckType;
import google.registry.model.domain.launch.LaunchPhase;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.domain.secdns.SecDnsCreateExtension;
import google.registry.model.domain.secdns.SecDnsUpdateExtension;
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Add;
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Remove;
import google.registry.model.domain.superuser.DomainDeleteSuperuserExtension;
import google.registry.model.domain.superuser.DomainUpdateSuperuserExtension;
import google.registry.model.domain.token.AllocationTokenExtension;
import java.math.BigDecimal;
import javax.annotation.Nullable;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
/** Static helpers for creating common EPP extensions. */
public class EppExtensions {
/**
* Returns a metadata extension with the specified reason and flags.
*
* @param reason the reason for the change, recorded in history entries
* @param requestedByRegistrar whether the change was requested by a registrar
* @param isAnchorTenant whether the domain is an anchor tenant
*/
@Nullable
public static MetadataExtension metadata(
@Nullable String reason,
@Nullable Boolean requestedByRegistrar,
@Nullable Boolean isAnchorTenant) {
if (isNullOrEmpty(reason) && requestedByRegistrar == null && isAnchorTenant == null) {
return null;
}
return new MetadataExtension.Builder()
.setReason(reason)
.setRequestedByRegistrar(requestedByRegistrar)
.setAnchorTenant(isAnchorTenant)
.build();
}
/** Returns a metadata extension for standard tool commands. */
@Nullable
public static MetadataExtension toolMetadata(
@Nullable String reason, @Nullable Boolean requestedByRegistrar) {
return metadata(reason, requestedByRegistrar, null);
}
/** Returns an allocation token extension for the specified token string. */
@Nullable
public static AllocationTokenExtension allocationToken(@Nullable String token) {
return isNullOrEmpty(token) ? null : AllocationTokenExtension.create(token);
}
/** Returns a domain update superuser extension with the specified autorenew flag. */
@Nullable
public static DomainUpdateSuperuserExtension updateSuperuser(@Nullable Boolean autorenews) {
return autorenews == null ? null : DomainUpdateSuperuserExtension.create(autorenews);
}
/** Returns a domain delete superuser extension for immediate deletion if requested. */
@Nullable
public static DomainDeleteSuperuserExtension deleteSuperuser(boolean immediately) {
return immediately ? DomainDeleteSuperuserExtension.create(0, 0) : null;
}
/** Returns a fee create extension (V12) for a single fee. */
@Nullable
public static FeeCreateCommandExtensionV12 feeCreate(@Nullable Money cost) {
return cost == null ? null : feeCreate(cost.getCurrencyUnit(), cost.getAmount());
}
/** Returns a fee create extension (V12) for a single fee with a simple currency and cost. */
@Nullable
public static FeeCreateCommandExtensionV12 feeCreate(
@Nullable CurrencyUnit currency, @Nullable BigDecimal cost) {
if (currency == null || cost == null) {
return null;
}
return new FeeCreateCommandExtensionV12.Builder()
.setCurrency(currency)
.setFees(ImmutableList.of(new Fee.Builder().setCost(cost).build()))
.build();
}
/** Returns a fee create extension (V06) for a single fee. */
@Nullable
public static FeeCreateCommandExtensionV06 feeCreateV06(@Nullable Money cost) {
if (cost == null) {
return null;
}
return new FeeCreateCommandExtensionV06.Builder()
.setCurrency(cost.getCurrencyUnit())
.setFees(ImmutableList.of(new Fee.Builder().setCost(cost.getAmount()).build()))
.build();
}
/** Returns a secDNS create extension with the specified DS records. */
@Nullable
public static SecDnsCreateExtension secDnsCreate(ImmutableSet<DomainDsData> dsData) {
if (dsData.isEmpty()) {
return null;
}
return new SecDnsCreateExtension.Builder().setDsData(dsData).build();
}
/** Returns a secDNS update extension to replace or modify DS records. */
@Nullable
public static SecDnsUpdateExtension secDnsUpdate(
ImmutableSet<DomainDsData> add, ImmutableSet<DomainDsData> remove, boolean removeAll) {
if (add.isEmpty() && remove.isEmpty() && !removeAll) {
return null;
}
SecDnsUpdateExtension.Builder builder = new SecDnsUpdateExtension.Builder();
if (removeAll) {
builder.setRemove(new Remove.Builder().setAll(true).build());
} else if (!remove.isEmpty()) {
builder.setRemove(new Remove.Builder().setDsData(remove).build());
}
if (!add.isEmpty()) {
builder.setAdd(new Add.Builder().setDsData(add).build());
}
return builder.build();
}
/** Returns a fee check extension for domain creations (V06). */
public static FeeCheckCommandExtensionV06 feeCheckCreateV06(ImmutableList<String> domainNames) {
return feeCheckCreateV06(domainNames, 1);
}
/** Returns a fee check extension for domain creations (V06) with a specific period. */
public static FeeCheckCommandExtensionV06 feeCheckCreateV06(
ImmutableList<String> domainNames, int years) {
FeeCheckCommandExtensionV06 feeCheck = new FeeCheckCommandExtensionV06();
ImmutableList.Builder<FeeCheckCommandExtensionItemV06> items = new ImmutableList.Builder<>();
for (String domainName : domainNames) {
items.add(
FeeCheckCommandExtensionItemV06.create(
domainName,
null,
FeeExtensionCommandDescriptor.create(CREATE, null, null),
Period.create(years, Period.Unit.YEARS)));
}
feeCheck.setItems(items.build());
return feeCheck;
}
/** Returns a launch check extension for claims. */
public static LaunchCheckExtension launchCheckClaims() {
return LaunchCheckExtension.create(CheckType.CLAIMS, LaunchPhase.CLAIMS);
}
}
@@ -14,14 +14,15 @@
package google.registry.model.eppinput;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
import static google.registry.util.CollectionUtils.nullSafeImmutableCopy;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.contact.ContactCommand;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.bulktoken.BulkTokenExtension;
import google.registry.model.domain.fee06.FeeCheckCommandExtensionV06;
@@ -61,6 +62,8 @@ import google.registry.model.domain.token.AllocationTokenExtension;
import google.registry.model.eppinput.ResourceCommand.ResourceCheck;
import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
import google.registry.model.host.HostCommand;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementRef;
@@ -70,21 +73,26 @@ import jakarta.xml.bind.annotation.XmlElements;
import jakarta.xml.bind.annotation.XmlEnumValue;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlSchema;
import jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.XmlType;
import jakarta.xml.bind.annotation.adapters.XmlAdapter;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
/** This class represents the root EPP XML element for input. */
@XmlRootElement(name = "epp")
@XmlAccessorType(XmlAccessType.FIELD)
public class EppInput extends ImmutableObject {
@XmlElements({
@XmlElement(name = "command", type = CommandWrapper.class),
@XmlElement(name = "hello", type = Hello.class) })
@XmlElement(name = "command", type = CommandWrapper.class),
@XmlElement(name = "hello", type = Hello.class)
})
CommandWrapper commandWrapper;
public CommandWrapper getCommandWrapper() {
@@ -108,11 +116,11 @@ public class EppInput extends ImmutableObject {
public Optional<String> getResourceType() {
ResourceCommand resourceCommand = getResourceCommand();
if (resourceCommand != null) {
XmlSchema xmlSchemaAnnotation =
resourceCommand.getClass().getPackage().getAnnotation(XmlSchema.class);
if (xmlSchemaAnnotation != null && xmlSchemaAnnotation.xmlns().length > 0) {
return Optional.of(xmlSchemaAnnotation.xmlns()[0].prefix());
}
XmlSchema xmlSchemaAnnotation =
resourceCommand.getClass().getPackage().getAnnotation(XmlSchema.class);
if (xmlSchemaAnnotation != null && xmlSchemaAnnotation.xmlns().length > 0) {
return Optional.of(xmlSchemaAnnotation.xmlns()[0].prefix());
}
}
return Optional.empty();
}
@@ -124,6 +132,9 @@ public class EppInput extends ImmutableObject {
@Nullable
private ResourceCommand getResourceCommand() {
if (commandWrapper == null) {
return null;
}
InnerCommand innerCommand = commandWrapper.getCommand();
return innerCommand instanceof ResourceCommandWrapper resourceCommandWrapper
? resourceCommandWrapper.getResourceCommand()
@@ -137,7 +148,7 @@ public class EppInput extends ImmutableObject {
public Optional<String> getSingleTargetId() {
ResourceCommand resourceCommand = getResourceCommand();
return resourceCommand instanceof SingleResourceCommand singleResourceCommand
? Optional.of(singleResourceCommand.getTargetId())
? Optional.ofNullable(singleResourceCommand.getTargetId())
: Optional.empty();
}
@@ -148,7 +159,8 @@ public class EppInput extends ImmutableObject {
public ImmutableList<String> getTargetIds() {
ResourceCommand resourceCommand = getResourceCommand();
if (resourceCommand instanceof SingleResourceCommand singleResourceCommand) {
return ImmutableList.of(singleResourceCommand.getTargetId());
String targetId = singleResourceCommand.getTargetId();
return targetId == null ? ImmutableList.of() : ImmutableList.of(targetId);
} else if (resourceCommand instanceof ResourceCheck resourceCheck) {
return resourceCheck.getTargetIds();
} else {
@@ -158,24 +170,54 @@ public class EppInput extends ImmutableObject {
/** Get the extension based on type, or null. If there are multiple, it chooses the first. */
public <E extends CommandExtension> Optional<E> getSingleExtension(Class<E> clazz) {
return getCommandWrapper().getExtensions().stream()
if (commandWrapper == null) {
return Optional.empty();
}
return commandWrapper.getExtensions().stream()
.filter(clazz::isInstance)
.map(clazz::cast)
.findFirst();
}
/**
* Static factory method to create an {@link EppInput} from an {@link InnerCommand} and
* extensions.
*/
public static EppInput create(InnerCommand command, CommandExtension... extensions) {
EppInput instance = new EppInput();
instance.commandWrapper = new CommandWrapper();
instance.commandWrapper.command = command;
ImmutableList<CommandExtension> validExtensions =
Arrays.stream(extensions).filter(Objects::nonNull).collect(ImmutableList.toImmutableList());
if (!validExtensions.isEmpty()) {
instance.commandWrapper.extension = validExtensions;
}
return instance;
}
public EppInput withClTrid(String clTrid) {
this.commandWrapper.clTrid = clTrid;
return this;
}
/** Builder for {@link EppInput}. */
public static class Builder extends Buildable.Builder<EppInput> {
public Builder setCommandWrapper(CommandWrapper commandWrapper) {
getInstance().commandWrapper = commandWrapper;
return this;
}
}
/** A tag that goes inside an EPP {@literal <command>}. */
public static class InnerCommand extends ImmutableObject {}
@XmlTransient
@XmlAccessorType(XmlAccessType.FIELD)
public abstract static class InnerCommand extends ImmutableObject {}
/** A command that has an extension inside of it. */
public static class ResourceCommandWrapper extends InnerCommand {
@XmlTransient
@XmlAccessorType(XmlAccessType.FIELD)
public abstract static class ResourceCommandWrapper extends InnerCommand {
@XmlElementRefs({
@XmlElementRef(type = ContactCommand.Check.class),
@XmlElementRef(type = ContactCommand.Create.class),
@XmlElementRef(type = ContactCommand.Delete.class),
@XmlElementRef(type = ContactCommand.Info.class),
@XmlElementRef(type = ContactCommand.Transfer.class),
@XmlElementRef(type = ContactCommand.Update.class),
@XmlElementRef(type = DomainCommand.Check.class),
@XmlElementRef(type = DomainCommand.Create.class),
@XmlElementRef(type = DomainCommand.Delete.class),
@@ -196,21 +238,65 @@ public class EppInput extends ImmutableObject {
}
/** Epp envelope wrapper for check on some objects. */
public static class Check extends ResourceCommandWrapper {}
@XmlAccessorType(XmlAccessType.FIELD)
public static class Check extends ResourceCommandWrapper {
public static Check create(ResourceCommand resourceCommand) {
Check instance = new Check();
instance.resourceCommand = resourceCommand;
return instance;
}
}
/** Epp envelope wrapper for create of some object. */
public static class Create extends ResourceCommandWrapper {}
@XmlAccessorType(XmlAccessType.FIELD)
public static class Create extends ResourceCommandWrapper {
public static Create create(ResourceCommand resourceCommand) {
Create instance = new Create();
instance.resourceCommand = resourceCommand;
return instance;
}
/** Builder for {@link Create}. */
public static class Builder extends Buildable.Builder<Create> {
public Builder setResourceCommand(ResourceCommand resourceCommand) {
getInstance().resourceCommand = resourceCommand;
return this;
}
}
}
/** Epp envelope wrapper for delete of some object. */
public static class Delete extends ResourceCommandWrapper {}
@XmlAccessorType(XmlAccessType.FIELD)
public static class Delete extends ResourceCommandWrapper {
public static Delete create(ResourceCommand resourceCommand) {
Delete instance = new Delete();
instance.resourceCommand = resourceCommand;
return instance;
}
}
/** Epp envelope wrapper for info on some object. */
public static class Info extends ResourceCommandWrapper {}
@XmlAccessorType(XmlAccessType.FIELD)
public static class Info extends ResourceCommandWrapper {
public static Info create(ResourceCommand resourceCommand) {
Info instance = new Info();
instance.resourceCommand = resourceCommand;
return instance;
}
}
/** Epp envelope wrapper for renewing some object. */
public static class Renew extends ResourceCommandWrapper {}
@XmlAccessorType(XmlAccessType.FIELD)
public static class Renew extends ResourceCommandWrapper {
public static Renew create(ResourceCommand resourceCommand) {
Renew instance = new Renew();
instance.resourceCommand = resourceCommand;
return instance;
}
}
/** Epp envelope wrapper for transferring some object. */
@XmlAccessorType(XmlAccessType.FIELD)
public static class Transfer extends ResourceCommandWrapper {
/** Enum of the possible values for the "op" attribute in transfer flows. */
@@ -237,12 +323,35 @@ public class EppInput extends ImmutableObject {
public TransferOp getTransferOp() {
return transferOp;
}
public static Transfer create(TransferOp transferOp, ResourceCommand resourceCommand) {
Transfer instance = new Transfer();
instance.transferOp = transferOp;
instance.resourceCommand = resourceCommand;
return instance;
}
}
/** Epp envelope wrapper for update of some object. */
public static class Update extends ResourceCommandWrapper {}
@XmlAccessorType(XmlAccessType.FIELD)
public static class Update extends ResourceCommandWrapper {
public static Update create(ResourceCommand resourceCommand) {
Update instance = new Update();
instance.resourceCommand = resourceCommand;
return instance;
}
/** Builder for {@link Update}. */
public static class Builder extends Buildable.Builder<Update> {
public Builder setResourceCommand(ResourceCommand resourceCommand) {
getInstance().resourceCommand = resourceCommand;
return this;
}
}
}
/** Poll command. */
@XmlAccessorType(XmlAccessType.FIELD)
public static class Poll extends InnerCommand {
/** Enum of the possible values for the "op" attribute in poll commands. */
@@ -260,19 +369,28 @@ public class EppInput extends ImmutableObject {
@XmlAttribute
PollOp op;
@XmlAttribute
String msgID;
@XmlAttribute(name = "msgID")
String msgId;
public PollOp getPollOp() {
return op;
}
public String getMessageId() {
return msgID;
return msgId;
}
public static Poll create(PollOp op, @Nullable String msgId) {
Poll instance = new Poll();
instance.op = op;
instance.msgId = msgId;
return instance;
}
}
/** Login command. */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"clientId", "password", "newPassword", "options", "services"})
public static class Login extends InnerCommand {
@XmlElement(name = "clID")
String clientId;
@@ -310,10 +428,12 @@ public class EppInput extends ImmutableObject {
}
/** Logout command. */
@XmlAccessorType(XmlAccessType.FIELD)
public static class Logout extends InnerCommand {}
/** The "command" element that holds an actual command inside of it. */
@XmlType(propOrder = {"command", "extension", "clTRID"})
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"command", "extension", "clTrid"})
public static class CommandWrapper extends ImmutableObject {
@XmlElements({
@XmlElement(name = "check", type = Check.class),
@@ -383,7 +503,9 @@ public class EppInput extends ImmutableObject {
@XmlElementWrapper
List<CommandExtension> extension;
@Nullable String clTRID;
@XmlElement(name = "clTRID")
@Nullable
String clTrid;
/**
* Returns the client transaction ID.
@@ -391,7 +513,7 @@ public class EppInput extends ImmutableObject {
* <p>This is optional (i.e. it may not be specified) per RFC 5730.
*/
public Optional<String> getClTrid() {
return Optional.ofNullable(clTRID);
return Optional.ofNullable(clTrid);
}
public InnerCommand getCommand() {
@@ -401,12 +523,34 @@ public class EppInput extends ImmutableObject {
public ImmutableList<CommandExtension> getExtensions() {
return nullToEmptyImmutableCopy(extension);
}
/** Builder for {@link CommandWrapper}. */
public static class Builder extends Buildable.Builder<CommandWrapper> {
public Builder setCommand(InnerCommand command) {
getInstance().command = command;
return this;
}
public Builder setExtensions(ImmutableList<CommandExtension> extension) {
getInstance().extension = isNullOrEmpty(extension) ? null : extension;
return this;
}
public Builder setClTrid(String clTrid) {
getInstance().clTrid = clTrid;
return this;
}
}
}
/** Empty type to represent the empty "hello" command. */
@XmlAccessorType(XmlAccessType.FIELD)
public static class Hello extends CommandWrapper {}
/** An options object inside of {@link Login}. */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"version", "language"})
public static class Options extends ImmutableObject {
@XmlJavaTypeAdapter(VersionAdapter.class)
String version;
@@ -420,6 +564,8 @@ public class EppInput extends ImmutableObject {
}
/** A services object inside of {@link Login}. */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"objectServices", "serviceExtensions"})
public static class Services extends ImmutableObject {
@XmlElement(name = "objURI")
Set<String> objectServices;
@@ -438,15 +584,15 @@ public class EppInput extends ImmutableObject {
}
/**
* RFC 5730 says we should check the version and return special error code 2100 if it isn't
* what we support, but it also specifies a schema that only allows 1.0 in the version field, so
* any other version doesn't validate. As a result, if we didn't do this here it would throw a
* {@code SyntaxErrorException} when it failed to validate.
* RFC 5730 says we should check the version and return special error code 2100 if it isn't what
* we support, but it also specifies a schema that only allows 1.0 in the version field, so any
* other version doesn't validate. As a result, if we didn't do this here it would throw a {@code
* SyntaxErrorException} when it failed to validate.
*
* @see <a href="http://tools.ietf.org/html/rfc5730#page-41">
* RFC 5730 - EPP - Command error responses</a>
* @see <a href="http://tools.ietf.org/html/rfc5730#page-41">RFC 5730 - EPP - Command error
* responses</a>
*/
public static class VersionAdapter extends XmlAdapter<String, String> {
public static class VersionAdapter extends XmlAdapter<String, String> {
@Override
public String unmarshal(String version) throws Exception {
if (!"1.0".equals(version)) {
@@ -456,8 +602,8 @@ public class EppInput extends ImmutableObject {
}
@Override
public String marshal(String ignored) {
throw new UnsupportedOperationException();
public String marshal(String version) {
return version;
}
}
@@ -15,7 +15,6 @@
package google.registry.model.eppinput;
import static google.registry.util.CollectionUtils.nullSafeImmutableCopy;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -24,41 +23,50 @@ import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppinput.ResourceCommand.ResourceUpdate.AddRemove;
import google.registry.util.TypeUtils.TypeInstantiator;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElements;
import jakarta.xml.bind.annotation.XmlTransient;
import java.util.List;
import java.util.Set;
/** Commands for EPP resources. */
public interface ResourceCommand {
/**
* A command for a single {@link EppResource}.
*
* <p>In general commands should extend {@link AbstractSingleResourceCommand} instead of
* implementing this directly, but "Create" commands can't do that since they need to inherit from
* a base class that gives them all of the resource's fields. The domain "Info" command also can't
* do that since it's "name" field is overloaded with a "hosts" attribute.
*/
/** Interface for EPP commands that operate on a single resource. */
interface SingleResourceCommand extends ResourceCommand {
@Override
String getTargetId();
@Override
AuthInfo getAuthInfo();
}
/** Returns the target ID for single-resource commands, or null otherwise. */
default String getTargetId() {
return null;
}
/** Returns the auth info for single-resource commands, or null otherwise. */
default AuthInfo getAuthInfo() {
return null;
}
/** Abstract implementation of {@link ResourceCommand}. */
@XmlTransient
abstract class AbstractSingleResourceCommand extends ImmutableObject
@XmlAccessorType(XmlAccessType.FIELD)
public abstract class AbstractSingleResourceCommand extends ImmutableObject
implements SingleResourceCommand {
@XmlElements({
@XmlElement(name = "id"),
@XmlElement(name = "name") })
String targetId;
@XmlTransient public String targetId;
public void setTargetId(String targetId) {
this.targetId = targetId;
}
@Override
@XmlTransient
public String getTargetId() {
return targetId;
}
@@ -71,11 +79,14 @@ public interface ResourceCommand {
/** A check command for an {@link EppResource}. */
@XmlTransient
class ResourceCheck extends ImmutableObject implements ResourceCommand {
@XmlElements({
@XmlElement(name = "id"),
@XmlElement(name = "name") })
List<String> targetUniqueIds;
@XmlAccessorType(XmlAccessType.FIELD)
public class ResourceCheck extends ImmutableObject implements ResourceCommand {
@XmlElements({@XmlElement(name = "id"), @XmlElement(name = "name")})
public List<String> targetUniqueIds;
public void setTargetIds(ImmutableList<String> targetUniqueIds) {
this.targetUniqueIds = targetUniqueIds;
}
public ImmutableList<String> getTargetIds() {
return nullSafeImmutableCopy(targetUniqueIds);
@@ -83,7 +94,7 @@ public interface ResourceCommand {
}
/** A create command, or the inner change (as opposed to add or remove) part of an update. */
interface ResourceCreateOrChange<B extends Buildable.Builder<?>> {}
public interface ResourceCreateOrChange<B extends Buildable.Builder<?>> {}
/**
* An update command for an {@link EppResource}.
@@ -92,21 +103,19 @@ public interface ResourceCommand {
* @param <C> the change type
*/
@XmlTransient
abstract class ResourceUpdate<
A extends AddRemove,
public abstract class ResourceUpdate<
A extends ResourceUpdate.AddRemove,
B extends EppResource.Builder<?, ?>,
C extends ResourceCreateOrChange<B>>
extends AbstractSingleResourceCommand {
/** Part of an update command that specifies set values to add or remove. */
@XmlTransient
@XmlAccessorType(XmlAccessType.FIELD)
public abstract static class AddRemove extends ImmutableObject {
@XmlElement(name = "status")
Set<StatusValue> statusValues;
public abstract void setStatusValues(ImmutableSet<StatusValue> statusValues);
public ImmutableSet<StatusValue> getStatusValues() {
return nullToEmptyImmutableCopy(statusValues);
}
public abstract ImmutableSet<StatusValue> getStatusValues();
}
protected abstract C getNullableInnerChange();
@@ -32,8 +32,6 @@ public abstract class CheckData extends ImmutableObject implements ResponseData
/** The check responses. We must explicitly list the namespaced versions of {@link Check}. */
@XmlElements({
@XmlElement(
name = "cd", namespace = "urn:ietf:params:xml:ns:contact-1.0", type = ContactCheck.class),
@XmlElement(
name = "cd", namespace = "urn:ietf:params:xml:ns:domain-1.0", type = DomainCheck.class),
@XmlElement(
@@ -114,14 +112,6 @@ public abstract class CheckData extends ImmutableObject implements ResponseData
}
}
/** A version with contact namespacing. */
@XmlType(namespace = "urn:ietf:params:xml:ns:contact-1.0")
public static class ContactCheck extends Check {
public static ContactCheck create(boolean avail, String id, String reason) {
return init(new ContactCheck(), CheckID.create(avail, id), reason);
}
}
/** A version with domain namespacing. */
@XmlType(namespace = "urn:ietf:params:xml:ns:domain-1.0")
public static class DomainCheck extends Check {
@@ -146,14 +136,6 @@ public abstract class CheckData extends ImmutableObject implements ResponseData
}
}
/** A version with contact namespacing. */
@XmlRootElement(name = "chkData", namespace = "urn:ietf:params:xml:ns:contact-1.0")
public static class ContactCheckData extends CheckData {
public static ContactCheckData create(ImmutableList<ContactCheck> checks) {
return init(new ContactCheckData(), checks);
}
}
/** A version with domain namespacing. */
@XmlRootElement(name = "chkData", namespace = "urn:ietf:params:xml:ns:domain-1.0")
public static class DomainCheckData extends CheckData {
@@ -28,21 +28,6 @@ public abstract class CreateData implements ResponseData {
@XmlElement(name = "crDate")
protected Instant creationDate;
/** An acknowledgment message indicating that a contact was created. */
@XmlRootElement(name = "creData", namespace = "urn:ietf:params:xml:ns:contact-1.0")
@XmlType(propOrder = {"id", "creationDate"}, namespace = "urn:ietf:params:xml:ns:contact-1.0")
public static class ContactCreateData extends CreateData {
String id;
public static ContactCreateData create(String id, Instant creationDate) {
ContactCreateData instance = new ContactCreateData();
instance.id = id;
instance.creationDate = creationDate;
return instance;
}
}
/** An acknowledgment message indicating that a domain was created. */
@XmlRootElement(name = "creData", namespace = "urn:ietf:params:xml:ns:domain-1.0")
@XmlType(
@@ -20,7 +20,6 @@ import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableList;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.contact.ContactInfoData;
import google.registry.model.domain.DomainInfoData;
import google.registry.model.domain.DomainRenewData;
import google.registry.model.domain.bulktoken.BulkTokenResponseExtension;
@@ -53,19 +52,15 @@ import google.registry.model.domain.launch.LaunchCheckResponseExtension;
import google.registry.model.domain.rgp.RgpInfoExtension;
import google.registry.model.domain.secdns.SecDnsInfoExtension;
import google.registry.model.eppcommon.Trid;
import google.registry.model.eppoutput.CheckData.ContactCheckData;
import google.registry.model.eppoutput.CheckData.DomainCheckData;
import google.registry.model.eppoutput.CheckData.HostCheckData;
import google.registry.model.eppoutput.CreateData.ContactCreateData;
import google.registry.model.eppoutput.CreateData.DomainCreateData;
import google.registry.model.eppoutput.CreateData.HostCreateData;
import google.registry.model.eppoutput.EppOutput.ResponseOrGreeting;
import google.registry.model.host.HostInfoData;
import google.registry.model.poll.MessageQueueInfo;
import google.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse;
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
import google.registry.model.poll.PendingActionNotificationResponse.HostPendingActionNotificationResponse;
import google.registry.model.transfer.TransferResponse.ContactTransferResponse;
import google.registry.model.transfer.TransferResponse.DomainTransferResponse;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementRef;
@@ -107,11 +102,6 @@ public class EppResponse extends ImmutableObject implements ResponseOrGreeting {
/** Zero or more response "resData" results. */
@XmlElementRefs({
@XmlElementRef(type = ContactCheckData.class),
@XmlElementRef(type = ContactCreateData.class),
@XmlElementRef(type = ContactInfoData.class),
@XmlElementRef(type = ContactPendingActionNotificationResponse.class),
@XmlElementRef(type = ContactTransferResponse.class),
@XmlElementRef(type = DomainCheckData.class),
@XmlElementRef(type = DomainCreateData.class),
@XmlElementRef(type = DomainInfoData.class),
@@ -14,14 +14,18 @@
package google.registry.model.host;
import static google.registry.util.CollectionUtils.nullSafeImmutableCopy;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableSet;
import google.registry.model.Buildable;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppinput.ResourceCommand.AbstractSingleResourceCommand;
import google.registry.model.eppinput.ResourceCommand.ResourceCheck;
import google.registry.model.eppinput.ResourceCommand.ResourceCreateOrChange;
import google.registry.model.eppinput.ResourceCommand.ResourceUpdate;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlTransient;
@@ -32,39 +36,95 @@ import java.util.Set;
/** A collection of {@link Host} commands. */
public class HostCommand {
/** The fields on "chgType" from <a href="http://tools.ietf.org/html/rfc5732">RFC5732</a>. */
/** The fields on "chgType" from <a href="https://tools.ietf.org/html/rfc5732">RFC5732</a>. */
@XmlTransient
abstract static class HostCreateOrChange extends AbstractSingleResourceCommand
@XmlAccessorType(XmlAccessType.FIELD)
public abstract static class HostCreateOrChange extends AbstractSingleResourceCommand
implements ResourceCreateOrChange<Host.Builder> {
@XmlElement(name = "name")
String name;
@Override
public String getTargetId() {
return name;
}
@Override
public void setTargetId(String targetId) {
this.name = targetId;
}
public String getHostName() {
return getTargetId();
return name;
}
}
/**
* A create command for a {@link Host}, mapping "createType" from <a
* href="http://tools.ietf.org/html/rfc5732">RFC5732</a>.
* href="https://tools.ietf.org/html/rfc5732">RFC5732</a>.
*/
@XmlType(propOrder = {"targetId", "inetAddresses"})
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"name", "inetAddresses"})
@XmlRootElement
public static class Create extends HostCreateOrChange
implements ResourceCreateOrChange<Host.Builder> {
public static class Create extends HostCreateOrChange {
/** IP Addresses for this host. Can be null if this is an external host. */
@XmlElement(name = "addr")
Set<InetAddress> inetAddresses;
public ImmutableSet<InetAddress> getInetAddresses() {
return nullSafeImmutableCopy(inetAddresses);
return nullToEmptyImmutableCopy(inetAddresses);
}
/** Builder for {@link Create}. */
public static class Builder extends Buildable.Builder<Create> {
public Builder setTargetId(String targetId) {
getInstance().setTargetId(targetId);
return this;
}
public Builder setInetAddresses(ImmutableSet<InetAddress> inetAddresses) {
getInstance().inetAddresses = inetAddresses;
return this;
}
}
}
/** A delete command for a {@link Host}. */
@XmlRootElement
public static class Delete extends AbstractSingleResourceCommand {}
@XmlAccessorType(XmlAccessType.FIELD)
public static class Delete extends AbstractSingleResourceCommand {
@XmlElement(name = "name")
String name;
@Override
public String getTargetId() {
return name;
}
@Override
public void setTargetId(String targetId) {
this.name = targetId;
}
}
/** An info request for a {@link Host}. */
@XmlRootElement
public static class Info extends AbstractSingleResourceCommand {}
@XmlAccessorType(XmlAccessType.FIELD)
public static class Info extends AbstractSingleResourceCommand {
@XmlElement(name = "name")
String name;
@Override
public String getTargetId() {
return name;
}
@Override
public void setTargetId(String targetId) {
this.name = targetId;
}
}
/** A check request for {@link Host}. */
@XmlRootElement
@@ -72,17 +132,32 @@ public class HostCommand {
/** An update to a {@link Host}. */
@XmlRootElement
@XmlType(propOrder = {"targetId", "innerAdd", "innerRemove", "innerChange"})
public static class Update extends ResourceUpdate<Update.AddRemove, Host.Builder, Update.Change> {
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"name", "innerAdd", "innerRemove", "innerChange"})
public static class Update
extends ResourceUpdate<Update.HostAddRemove, Host.Builder, Update.Change> {
@XmlElement(name = "name")
String name;
@Override
public String getTargetId() {
return name;
}
@Override
public void setTargetId(String targetId) {
this.name = targetId;
}
@XmlElement(name = "chg")
protected Change innerChange;
@XmlElement(name = "add")
protected AddRemove innerAdd;
protected HostAddRemove innerAdd;
@XmlElement(name = "rem")
protected AddRemove innerRemove;
protected HostAddRemove innerRemove;
@Override
protected Change getNullableInnerChange() {
@@ -90,28 +165,55 @@ public class HostCommand {
}
@Override
protected AddRemove getNullableInnerAdd() {
protected HostAddRemove getNullableInnerAdd() {
return innerAdd;
}
@Override
protected AddRemove getNullableInnerRemove() {
protected HostAddRemove getNullableInnerRemove() {
return innerRemove;
}
/** The add/remove type on a host update command. */
@XmlType(propOrder = { "inetAddresses", "statusValues" })
public static class AddRemove extends ResourceUpdate.AddRemove {
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"inetAddresses", "statusValues"})
public static class HostAddRemove extends ResourceUpdate.AddRemove {
/** IP Addresses for this host. Can be null if this is an external host. */
@XmlElement(name = "addr")
Set<InetAddress> inetAddresses;
@XmlElement(name = "status")
Set<StatusValue> statusValues;
@Override
public void setStatusValues(ImmutableSet<StatusValue> statusValues) {
this.statusValues = statusValues;
}
@Override
public ImmutableSet<StatusValue> getStatusValues() {
return nullToEmptyImmutableCopy(statusValues);
}
public ImmutableSet<InetAddress> getInetAddresses() {
return nullToEmptyImmutableCopy(inetAddresses);
}
/** Builder for {@link HostAddRemove}. */
public static class Builder extends Buildable.Builder<HostAddRemove> {
public Builder setInetAddresses(ImmutableSet<InetAddress> inetAddresses) {
getInstance().inetAddresses = isNullOrEmpty(inetAddresses) ? null : inetAddresses;
return this;
}
public Builder setStatusValues(ImmutableSet<StatusValue> statusValues) {
getInstance().statusValues = isNullOrEmpty(statusValues) ? null : statusValues;
return this;
}
}
}
/** The inner change type on a host update command. */
@XmlAccessorType(XmlAccessType.FIELD)
public static class Change extends HostCreateOrChange {}
}
}
@@ -39,15 +39,13 @@ public class PendingActionNotificationResponse extends ImmutableObject
/** The inner name type that contains a name and the result boolean. */
@Embeddable
static class NameOrId extends ImmutableObject implements UnsafeSerializable {
@XmlValue
String value;
@XmlValue String value;
@XmlAttribute(name = "paResult")
boolean actionResult;
}
@XmlTransient
NameOrId nameOrId;
@XmlTransient NameOrId nameOrId;
@XmlElement(name = "paTRID")
Trid trid;
@@ -104,36 +102,11 @@ public class PendingActionNotificationResponse extends ImmutableObject
}
}
/** An adapter to output the XML in response to resolving a pending command on a contact. */
@XmlRootElement(name = "panData", namespace = "urn:ietf:params:xml:ns:contact-1.0")
@XmlType(
propOrder = {"id", "trid", "processedDate"},
namespace = "urn:ietf:params:xml:ns:contact-1.0")
public static class ContactPendingActionNotificationResponse
extends PendingActionNotificationResponse {
@XmlElement
NameOrId getId() {
return nameOrId;
}
public static ContactPendingActionNotificationResponse create(
String contactId, boolean actionResult, Trid trid, Instant processedDate) {
return init(
new ContactPendingActionNotificationResponse(),
contactId,
actionResult,
trid,
processedDate);
}
}
/** An adapter to output the XML in response to resolving a pending command on a host. */
@XmlRootElement(name = "panData", namespace = "urn:ietf:params:xml:ns:domain-1.0")
@XmlType(
propOrder = {"name", "trid", "processedDate"},
namespace = "urn:ietf:params:xml:ns:domain-1.0"
)
propOrder = {"name", "trid", "processedDate"},
namespace = "urn:ietf:params:xml:ns:domain-1.0")
public static class HostPendingActionNotificationResponse
extends PendingActionNotificationResponse {
@@ -182,7 +182,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
DISABLED
}
/** Regex for E.164 phone number format specified by {@code contact.xsd}. */
/** Regex for E.164 phone number format. */
private static final Pattern E164_PATTERN = Pattern.compile("\\+[0-9]{1,3}\\.[0-9]{1,14}");
/** Regex for telephone support passcode (5 digit string). */
@@ -43,17 +43,7 @@ public final class IcannReportingTypes {
HOST_CREATE("srs-host-create"),
HOST_DELETE("srs-host-delete"),
HOST_INFO("srs-host-info"),
HOST_UPDATE("srs-host-update"),
CONTACT_CHECK("srs-cont-check"),
CONTACT_CREATE("srs-cont-create"),
CONTACT_DELETE("srs-cont-delete"),
CONTACT_INFO("srs-cont-info"),
CONTACT_TRANSFER_APPROVE("srs-cont-transfer-approve"),
CONTACT_TRANSFER_CANCEL("srs-cont-transfer-cancel"),
CONTACT_TRANSFER_QUERY("srs-cont-transfer-query"),
CONTACT_TRANSFER_REJECT("srs-cont-transfer-reject"),
CONTACT_TRANSFER_REQUEST("srs-cont-transfer-request"),
CONTACT_UPDATE("srs-cont-update");
HOST_UPDATE("srs-host-update");
/** Returns the actual field name from the specification. */
private final String fieldName;
@@ -83,33 +83,4 @@ public class TransferResponse extends BaseTransferObject implements ResponseData
}
}
}
/** An adapter to output the XML in response to a transfer command on a contact. */
@XmlRootElement(name = "trnData", namespace = "urn:ietf:params:xml:ns:contact-1.0")
@XmlType(propOrder = {
"contactId",
"transferStatus",
"gainingClientId",
"transferRequestTime",
"losingClientId",
"pendingTransferExpirationTime"},
namespace = "urn:ietf:params:xml:ns:contact-1.0")
public static class ContactTransferResponse extends TransferResponse {
@XmlElement(name = "id")
String contactId;
public String getContactId() {
return contactId;
}
/** Builder for {@link ContactTransferResponse}. */
public static class Builder
extends BaseTransferObject.Builder<ContactTransferResponse, Builder> {
public Builder setContactId(String contactId) {
getInstance().contactId = contactId;
return this;
}
}
}
}
@@ -1,50 +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.
package google.registry.persistence.converter;
import static com.google.common.collect.ImmutableList.toImmutableList;
import google.registry.model.contact.Disclose.PostalInfoChoice;
import google.registry.model.contact.PostalInfo;
import java.util.List;
import java.util.stream.Stream;
/** Hibernate custom type for {@link List} of {@link PostalInfoChoice}. */
public class PostalInfoChoiceListUserType
extends StringCollectionUserType<PostalInfoChoice, List<PostalInfoChoice>> {
@Override
String[] toJdbcObject(List<PostalInfoChoice> collection) {
return collection.stream()
.map(PostalInfoChoice::getType)
.map(Enum::name)
.toList()
.toArray(new String[0]);
}
@Override
List<PostalInfoChoice> toEntity(String[] data) {
return Stream.of(data)
.map(PostalInfo.Type::valueOf)
.map(PostalInfoChoice::create)
.collect(toImmutableList());
}
@SuppressWarnings("unchecked")
@Override
public Class<List<PostalInfoChoice>> returnedClass() {
return (Class<List<PostalInfoChoice>>) ((Object) List.class);
}
}
@@ -21,7 +21,7 @@ import com.google.common.collect.ImmutableMap;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.State;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.xjc.contact.XjcContactE164Type;
import google.registry.xjc.eppcom.XjcEppcomE164Type;
import google.registry.xjc.rderegistrar.XjcRdeRegistrar;
import google.registry.xjc.rderegistrar.XjcRdeRegistrarAddrType;
import google.registry.xjc.rderegistrar.XjcRdeRegistrarElement;
@@ -103,7 +103,7 @@ final class RegistrarToXjcConverter {
// telephone number.
// XXX: Make Registrar use PhoneNumber.
if (model.getPhoneNumber() != null) {
XjcContactE164Type phone = new XjcContactE164Type();
XjcEppcomE164Type phone = new XjcEppcomE164Type();
phone.setValue(model.getPhoneNumber());
bean.setVoice(phone);
}
@@ -111,7 +111,7 @@ final class RegistrarToXjcConverter {
// o An OPTIONAL <fax> element that contains the registrar's facsimile
// telephone number.
if (model.getFaxNumber() != null) {
XjcContactE164Type fax = new XjcContactE164Type();
XjcEppcomE164Type fax = new XjcEppcomE164Type();
fax.setValue(model.getFaxNumber());
bean.setFax(fax);
}
@@ -24,7 +24,6 @@ import static jakarta.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
import com.google.api.services.dataflow.Dataflow;
import com.google.api.services.dataflow.model.Job;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
@@ -32,11 +31,10 @@ import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import com.google.template.soy.parseinfo.SoyTemplateInfo;
import google.registry.beam.spec11.ThreatMatch;
import google.registry.config.RegistryConfig.Config;
import google.registry.reporting.ReportingModule;
import google.registry.reporting.spec11.soy.Spec11EmailSoyInfo;
import google.registry.reporting.spec11.Spec11EmailUtils.Spec11EmailTemplate;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.Response;
@@ -45,14 +43,13 @@ import jakarta.inject.Inject;
import java.io.IOException;
import java.time.LocalDate;
import java.util.Optional;
import java.util.Set;
import org.json.JSONException;
/**
* Retries until a {@code Dataflow} job with a given {@code jobId} completes, continuing the Spec11
* pipeline accordingly.
*
* <p>This calls {@link Spec11EmailUtils#emailSpec11Reports(LocalDate, SoyTemplateInfo, String,
* <p>This calls {@link Spec11EmailUtils#emailSpec11Reports(LocalDate, Spec11EmailTemplate, String,
* ImmutableSet)} on success or {@link Spec11EmailUtils#sendAlertEmail(String, String)} on failure.
*/
@Action(
@@ -134,7 +131,7 @@ public class PublishSpec11ReportAction implements Runnable {
String.format("Spec11 %s job %s ended in status failure.", date, jobId));
}
default -> {
logger.atInfo().log("Job in non-terminal state %s, retrying:", state);
logger.atInfo().log("Job in non-terminal state %s, retrying.", state);
response.setStatus(SC_SERVICE_UNAVAILABLE);
}
}
@@ -153,8 +150,7 @@ public class PublishSpec11ReportAction implements Runnable {
ImmutableSet<RegistrarThreatMatches> monthlyMatchesSet =
spec11RegistrarThreatMatchesParser.getRegistrarThreatMatches(date);
String subject = String.format("%s Monthly Threat Detector [%s]", registryName, date);
emailUtils.emailSpec11Reports(
date, Spec11EmailSoyInfo.MONTHLY_SPEC_11_EMAIL, subject, monthlyMatchesSet);
emailUtils.emailSpec11Reports(date, Spec11EmailTemplate.MONTHLY, subject, monthlyMatchesSet);
}
private void processDailyDiff(LocalDate previousDate) throws IOException, JSONException {
@@ -165,7 +161,7 @@ public class PublishSpec11ReportAction implements Runnable {
String dailySubject = String.format("%s Daily Threat Detector [%s]", registryName, date);
emailUtils.emailSpec11Reports(
date,
Spec11EmailSoyInfo.DAILY_SPEC_11_EMAIL,
Spec11EmailTemplate.DAILY,
dailySubject,
getNewMatches(previousMatches, currentMatches));
}
@@ -173,19 +169,20 @@ public class PublishSpec11ReportAction implements Runnable {
private ImmutableSet<RegistrarThreatMatches> getNewMatches(
ImmutableSet<RegistrarThreatMatches> previousMatchesSet,
ImmutableSet<RegistrarThreatMatches> currentMatchesSet) {
ImmutableMap<String, ImmutableSet<ThreatMatch>> previousMatchesByEmail =
ImmutableMap<String, ImmutableSet<ThreatMatch>> previousMatchesByRegistrarId =
groupByKeyAndFlatMap(previousMatchesSet);
ImmutableMap<String, ImmutableSet<ThreatMatch>> currentMatchesByEmail =
ImmutableMap<String, ImmutableSet<ThreatMatch>> currentMatchesByRegistrarId =
groupByKeyAndFlatMap(currentMatchesSet);
ImmutableSet.Builder<RegistrarThreatMatches> resultsBuilder = ImmutableSet.builder();
for (String email : currentMatchesByEmail.keySet()) {
for (String registrarId : currentMatchesByRegistrarId.keySet()) {
// Only include matches in the result if they're non-empty
Set<ThreatMatch> difference =
Sets.difference(
currentMatchesByEmail.get(email),
previousMatchesByEmail.getOrDefault(email, ImmutableSet.of()));
ImmutableSet<ThreatMatch> difference =
ImmutableSet.copyOf(
Sets.difference(
currentMatchesByRegistrarId.get(registrarId),
previousMatchesByRegistrarId.getOrDefault(registrarId, ImmutableSet.of())));
if (!difference.isEmpty()) {
resultsBuilder.add(RegistrarThreatMatches.create(email, ImmutableList.copyOf(difference)));
resultsBuilder.add(RegistrarThreatMatches.create(registrarId, difference.asList()));
}
}
return resultsBuilder.build();
@@ -193,13 +190,13 @@ public class PublishSpec11ReportAction implements Runnable {
private ImmutableMap<String, ImmutableSet<ThreatMatch>> groupByKeyAndFlatMap(
ImmutableSet<RegistrarThreatMatches> registrarThreatMatches) {
// Group by email address then flat-map all of the ThreatMatch objects together
// Group by registrarId then flat-map all of the ThreatMatch objects together
return ImmutableMap.copyOf(
Maps.transformValues(
Multimaps.index(registrarThreatMatches, RegistrarThreatMatches::clientId).asMap(),
Multimaps.index(registrarThreatMatches, RegistrarThreatMatches::registrarId).asMap(),
registrarThreatMatchesCollection ->
registrarThreatMatchesCollection.stream()
.flatMap(matches -> matches.threatMatches().stream())
.flatMap(rtm -> rtm.threatMatches().stream())
.collect(toImmutableSet())));
}
@@ -16,12 +16,17 @@ package google.registry.reporting.spec11;
import com.google.common.collect.ImmutableList;
import google.registry.beam.spec11.ThreatMatch;
import java.util.List;
/** Value record representing the registrar and list-of-threat-matches pair stored in GCS. */
public record RegistrarThreatMatches(String clientId, ImmutableList<ThreatMatch> threatMatches) {
static RegistrarThreatMatches create(String clientId, List<ThreatMatch> threatMatches) {
return new RegistrarThreatMatches(clientId, ImmutableList.copyOf(threatMatches));
/**
* A value record representing a registrar and its associated list of threat matches.
*
* @param registrarId the ID of the registrar
* @param threatMatches the list of {@link ThreatMatch} objects associated with the registrar
*/
public record RegistrarThreatMatches(String registrarId, ImmutableList<ThreatMatch> threatMatches) {
/** Creates a new {@link RegistrarThreatMatches} instance. */
static RegistrarThreatMatches create(
String registrarId, ImmutableList<ThreatMatch> threatMatches) {
return new RegistrarThreatMatches(registrarId, threatMatches);
}
}
@@ -16,7 +16,6 @@ package google.registry.reporting.spec11;
import static com.google.common.base.Throwables.getRootCause;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.io.Resources.getResource;
import static google.registry.persistence.transaction.QueryComposer.Comparator;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
@@ -25,41 +24,46 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import com.google.template.soy.SoyFileSet;
import com.google.template.soy.parseinfo.SoyTemplateInfo;
import com.google.template.soy.tofu.SoyTofu;
import com.google.template.soy.tofu.SoyTofu.Renderer;
import google.registry.beam.spec11.ThreatMatch;
import google.registry.config.RegistryConfig.Config;
import google.registry.groups.GmailClient;
import google.registry.model.domain.Domain;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.reporting.spec11.soy.Spec11EmailSoyInfo;
import google.registry.util.EmailMessage;
import google.registry.util.Sleeper;
import google.registry.util.TemplateRenderer;
import jakarta.inject.Inject;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.InternetAddress;
import java.time.Duration;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
/** Provides e-mail functionality for Spec11 tasks, such as sending Spec11 reports to registrars. */
public class Spec11EmailUtils {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final SoyTofu SOY_SAUCE =
SoyFileSet.builder()
.add(
getResource(
Spec11EmailSoyInfo.getInstance().getClass(),
Spec11EmailSoyInfo.getInstance().getFileName()))
.build()
.compileToTofu();
/** Enum of Spec11 email templates. */
public enum Spec11EmailTemplate {
DAILY("daily_spec11_email.ftl"),
MONTHLY("monthly_spec11_email.ftl");
private final String ftlPath;
Spec11EmailTemplate(String ftlPath) {
this.ftlPath = "google/registry/reporting/spec11/ftl/" + ftlPath;
}
public String getFtlPath() {
return ftlPath;
}
}
private final GmailClient gmailClient;
private final Sleeper sleeper;
private final TemplateRenderer templateRenderer;
private final Duration emailThrottleDuration;
private final InternetAddress outgoingEmailAddress;
private final ImmutableList<InternetAddress> spec11BccEmailAddresses;
@@ -71,6 +75,7 @@ public class Spec11EmailUtils {
Spec11EmailUtils(
GmailClient gmailClient,
Sleeper sleeper,
TemplateRenderer templateRenderer,
@Config("emailThrottleDuration") Duration emailThrottleDuration,
@Config("newAlertRecipientEmailAddress") InternetAddress alertRecipientAddress,
@Config("spec11OutgoingEmailAddress") InternetAddress spec11OutgoingEmailAddress,
@@ -79,6 +84,7 @@ public class Spec11EmailUtils {
@Config("registryName") String registryName) {
this.gmailClient = gmailClient;
this.sleeper = sleeper;
this.templateRenderer = templateRenderer;
this.emailThrottleDuration = emailThrottleDuration;
this.outgoingEmailAddress = spec11OutgoingEmailAddress;
this.spec11BccEmailAddresses = spec11BccEmailAddresses;
@@ -88,12 +94,18 @@ public class Spec11EmailUtils {
}
/**
* Processes a list of registrar/list-of-threat pairings and sends a notification email to the
* appropriate address.
* Processes a list of registrar/list-of-threat pairings and sends notification emails to the
* appropriate addresses.
*
* @param date the date the report was generated
* @param template the email template to use
* @param subject the subject line for the emails
* @param registrarThreatMatchesSet a set of {@link RegistrarThreatMatches} to be emailed
* @throws RuntimeException if emailing fails for one or more registrars
*/
void emailSpec11Reports(
LocalDate date,
SoyTemplateInfo soyTemplateInfo,
Spec11EmailTemplate template,
String subject,
ImmutableSet<RegistrarThreatMatches> registrarThreatMatchesSet) {
ImmutableMap.Builder<RegistrarThreatMatches, Throwable> failedMatchesBuilder =
@@ -108,14 +120,15 @@ public class Spec11EmailUtils {
try {
// Handle exceptions individually per registrar so that one failed email doesn't prevent
// the rest from being sent.
emailRegistrar(date, soyTemplateInfo, subject, filteredMatches);
emailRegistrar(date, template, subject, filteredMatches);
numRegistrarsEmailed++;
} catch (Throwable e) {
failedMatchesBuilder.put(registrarThreatMatches, getRootCause(e));
}
}
}
logger.atInfo().log("Emailed daily diffs to %s registrars.", numRegistrarsEmailed);
logger.atInfo().log("Emailed Spec11 reports to %s registrars.", numRegistrarsEmailed);
ImmutableMap<RegistrarThreatMatches, Throwable> failedMatches = failedMatchesBuilder.build();
if (!failedMatches.isEmpty()) {
ImmutableList<Map.Entry<RegistrarThreatMatches, Throwable>> failedMatchesList =
@@ -130,7 +143,7 @@ public class Spec11EmailUtils {
logger.atSevere().withCause(failedMatchesList.get(i).getValue()).log(
"Additional exception thrown when sending email to registrar %s, in addition to the"
+ " re-thrown exception.",
failedMatchesList.get(i).getKey().clientId());
failedMatchesList.get(i).getKey().registrarId());
}
throw new RuntimeException(
"Emailing Spec11 reports failed, first exception:", firstThrowable);
@@ -144,43 +157,49 @@ public class Spec11EmailUtils {
RegistrarThreatMatches registrarThreatMatches) {
ImmutableList<ThreatMatch> filteredMatches =
tm().transact(
() -> {
return registrarThreatMatches.threatMatches().stream()
.filter(
threatMatch ->
tm()
.createQueryComposer(Domain.class)
.where("domainName", Comparator.EQ, threatMatch.domainName())
.stream()
.anyMatch(Domain::shouldPublishToDns))
.collect(toImmutableList());
});
return RegistrarThreatMatches.create(registrarThreatMatches.clientId(), filteredMatches);
() ->
registrarThreatMatches.threatMatches().stream()
.filter(
threatMatch ->
tm()
.createQueryComposer(Domain.class)
.where("domainName", Comparator.EQ, threatMatch.domainName())
.stream()
.anyMatch(Domain::shouldPublishToDns))
.collect(toImmutableList()));
return RegistrarThreatMatches.create(registrarThreatMatches.registrarId(), filteredMatches);
}
private void emailRegistrar(
LocalDate date,
SoyTemplateInfo soyTemplateInfo,
Spec11EmailTemplate template,
String subject,
RegistrarThreatMatches registrarThreatMatches)
throws MessagingException {
gmailClient.sendEmail(
EmailMessage.newBuilder()
.setSubject(subject)
.setBody(getEmailBody(date, soyTemplateInfo, registrarThreatMatches))
.setBody(getEmailBody(date, template, registrarThreatMatches))
.setContentType(MediaType.HTML_UTF_8)
.addRecipient(getEmailAddressForRegistrar(registrarThreatMatches.clientId()))
.addRecipient(getEmailAddressForRegistrar(registrarThreatMatches.registrarId()))
.setBccs(spec11BccEmailAddresses)
.build());
}
/**
* Renders the email body using the specified template and registrar threat matches.
*
* @param date the date the report was generated
* @param template the email template to use
* @param registrarThreatMatches the matches for a specific registrar
* @return the rendered email body as an HTML string
*/
private String getEmailBody(
LocalDate date,
SoyTemplateInfo soyTemplateInfo,
RegistrarThreatMatches registrarThreatMatches) {
Renderer renderer = SOY_SAUCE.newRenderer(soyTemplateInfo);
// Soy templates require that data be in raw map/list form.
List<Map<String, String>> threatMatchMap =
LocalDate date, Spec11EmailTemplate template, RegistrarThreatMatches registrarThreatMatches) {
// FreeMarker templates require that data be in raw map/list form or bean-style POJOs.
// We convert the ThreatMatch records to maps here to ensure compatibility and to
// apply email-safe domain name transformations.
ImmutableList<ImmutableMap<String, String>> threatMatchMap =
registrarThreatMatches.threatMatches().stream()
.map(
threatMatch ->
@@ -189,15 +208,14 @@ public class Spec11EmailUtils {
"threatType", threatMatch.threatType()))
.collect(toImmutableList());
Map<String, Object> data =
ImmutableMap<String, Object> data =
ImmutableMap.of(
"date", date.toString(),
"registry", registryName,
"replyToEmail", outgoingEmailAddress.getAddress(),
"threats", threatMatchMap,
"resources", spec11WebResources);
renderer.setData(data);
return renderer.render();
return templateRenderer.render(template.getFtlPath(), data);
}
// Mutates a known bad domain to pass spam checks by Email sender and clients, as suggested by
@@ -18,8 +18,10 @@ import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.gson.Gson;
import dagger.Module;
import dagger.Provides;
import google.registry.tools.GsonUtils;
import jakarta.inject.Singleton;
import java.net.HttpURLConnection;
import javax.net.ssl.HttpsURLConnection;
@@ -49,9 +51,16 @@ public final class Modules {
@Module
public static final class GsonModule {
@Provides
@Singleton
static JsonFactory provideJsonFactory() {
return GsonFactory.getDefaultInstance();
}
@Provides
@Singleton
public static Gson provideGson() {
return GsonUtils.provideGson();
}
}
/** Dagger module that provides standard {@link NetHttpTransport}. */
@@ -44,7 +44,6 @@ import google.registry.request.HttpException.UnsupportedMediaTypeException;
import google.registry.request.auth.AuthResult;
import google.registry.request.lock.LockHandler;
import google.registry.request.lock.LockHandlerImpl;
import google.registry.tools.GsonUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
@@ -74,13 +73,6 @@ public final class RequestModule {
this.authResult = authResult;
}
@RequestScope
@VisibleForTesting
@Provides
public static Gson provideGson() {
return GsonUtils.provideGson();
}
@Provides
@Parameter(RequestParameters.PARAM_TLD)
static String provideTld(HttpServletRequest req) {
@@ -146,20 +146,20 @@ public class AuthModule {
@LocalCredentialJson
public static String provideLocalCredentialJson(
Lazy<GoogleClientSecrets> clientSecrets,
Gson gson,
@StoredCredential Lazy<Credential> credential,
@Nullable @Config("credentialFilePath") String credentialFilePath) {
try {
if (credentialFilePath != null) {
return Files.readString(Paths.get(credentialFilePath));
} else {
return new Gson()
.toJson(
ImmutableMap.<String, String>builder()
.put("type", "authorized_user")
.put("client_id", clientSecrets.get().getDetails().getClientId())
.put("client_secret", clientSecrets.get().getDetails().getClientSecret())
.put("refresh_token", credential.get().getRefreshToken())
.build());
return gson.toJson(
ImmutableMap.<String, String>builder()
.put("type", "authorized_user")
.put("client_id", clientSecrets.get().getDetails().getClientId())
.put("client_secret", clientSecrets.get().getDetails().getClientSecret())
.put("refresh_token", credential.get().getRefreshToken())
.build());
}
} catch (IOException e) {
throw new RuntimeException(e);
@@ -16,10 +16,12 @@ package google.registry.tools;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import com.google.template.soy.data.SoyMapData;
import google.registry.config.RegistryConfig.Config;
import google.registry.tools.soy.DomainCheckClaimsSoyInfo;
import google.registry.model.domain.DomainCommand;
import google.registry.model.eppinput.EppExtensions;
import google.registry.model.eppinput.EppInput;
import jakarta.inject.Inject;
import java.util.Collection;
import java.util.List;
@@ -50,11 +52,15 @@ final class CheckDomainClaimsCommand extends NonMutatingEppToolCommand {
clientId = registryAdminClientId;
}
Multimap<String, String> domainNameMap = validateAndGroupDomainNamesByTld(mainParameters);
Multimap<String, String> domainNameMap =
validateAndGroupDomainNamesByTld(ImmutableList.copyOf(mainParameters));
for (Collection<String> values : domainNameMap.asMap().values()) {
setSoyTemplate(
DomainCheckClaimsSoyInfo.getInstance(), DomainCheckClaimsSoyInfo.DOMAINCHECKCLAIMS);
addSoyRecord(clientId, new SoyMapData("domainNames", values));
DomainCommand.Check checkCommand = new DomainCommand.Check();
checkCommand.setTargetIds(ImmutableList.copyOf(values));
addEppInput(
clientId,
EppInput.create(EppInput.Check.create(checkCommand), EppExtensions.launchCheckClaims())
.withClTrid("RegistryTool"));
}
}
}
@@ -14,14 +14,14 @@
package google.registry.tools;
import static com.google.common.base.Strings.isNullOrEmpty;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import com.google.template.soy.data.SoyMapData;
import google.registry.config.RegistryConfig.Config;
import google.registry.tools.soy.DomainCheckSoyInfo;
import google.registry.model.domain.DomainCommand;
import google.registry.model.eppinput.EppExtensions;
import google.registry.model.eppinput.EppInput;
import jakarta.inject.Inject;
import java.util.Collection;
import java.util.List;
@@ -57,14 +57,20 @@ final class CheckDomainCommand extends NonMutatingEppToolCommand {
clientId = registryAdminClientId;
}
Multimap<String, String> domainNameMap = validateAndGroupDomainNamesByTld(mainParameters);
Multimap<String, String> domainNameMap =
validateAndGroupDomainNamesByTld(ImmutableList.copyOf(mainParameters));
for (Collection<String> values : domainNameMap.asMap().values()) {
setSoyTemplate(DomainCheckSoyInfo.getInstance(), DomainCheckSoyInfo.DOMAINCHECK);
SoyMapData soyMapData = new SoyMapData("domainNames", values);
if (!isNullOrEmpty(allocationToken)) {
soyMapData.put("allocationToken", allocationToken);
}
addSoyRecord(clientId, soyMapData);
ImmutableList<String> domainNames = ImmutableList.copyOf(values);
DomainCommand.Check checkCommand = new DomainCommand.Check();
checkCommand.setTargetIds(domainNames);
addEppInput(
clientId,
EppInput.create(
EppInput.Check.create(checkCommand),
EppExtensions.feeCheckCreateV06(domainNames),
EppExtensions.allocationToken(allocationToken))
.withClTrid("RegistryTool"));
}
}
}
@@ -23,8 +23,12 @@ import static google.registry.util.StringGenerator.DEFAULT_PASSWORD_LENGTH;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.net.InternetDomainName;
import com.google.template.soy.data.SoyMapData;
import google.registry.tools.soy.CreateAnchorTenantSoyInfo;
import google.registry.model.domain.DomainAuthInfo;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.Period;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
import google.registry.model.eppinput.EppExtensions;
import google.registry.model.eppinput.EppInput;
import google.registry.util.StringGenerator;
import jakarta.inject.Inject;
import jakarta.inject.Named;
@@ -48,10 +52,12 @@ final class CreateAnchorTenantCommand extends MutatingEppToolCommand {
required = true)
private String domainName;
@SuppressWarnings("UnusedVariable")
@Parameter(
names = {"--contact"},
description = "Contact ID for the request. This will be used for registrant, admin contact, "
+ "and tech contact.",
description =
"Contact ID for the request. This will be used for registrant, admin contact, "
+ "and tech contact.",
required = true)
private String contact;
@@ -87,15 +93,18 @@ final class CreateAnchorTenantCommand extends MutatingEppToolCommand {
cost = getDomainCreateCost(domainName, clock.now(), DEFAULT_ANCHOR_TENANT_PERIOD_YEARS);
}
setSoyTemplate(CreateAnchorTenantSoyInfo.getInstance(),
CreateAnchorTenantSoyInfo.CREATEANCHORTENANT);
addSoyRecord(clientId, new SoyMapData(
"domainName", domainName,
"contactId", contact,
"reason", reason,
"password", password,
"period", DEFAULT_ANCHOR_TENANT_PERIOD_YEARS,
"feeCurrency", cost != null ? cost.getCurrencyUnit().toString() : null,
"fee", cost != null ? cost.getAmount().toString() : null));
DomainCommand.Create.Builder createBuilder =
new DomainCommand.Create.Builder()
.setDomainName(domainName)
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create(password)))
.setPeriod(Period.create(DEFAULT_ANCHOR_TENANT_PERIOD_YEARS, Period.Unit.YEARS));
addEppInput(
clientId,
EppInput.create(
EppInput.Create.create(createBuilder.build()),
EppExtensions.metadata(reason, false, true),
EppExtensions.feeCreateV06(cost))
.withClTrid("RegistryTool"));
}
}
@@ -20,12 +20,20 @@ import static google.registry.pricing.PricingEngineProxy.getPricesForDomainName;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.template.soy.data.SoyMapData;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import google.registry.model.domain.DomainAuthInfo;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.Period;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
import google.registry.model.eppinput.EppExtensions;
import google.registry.model.eppinput.EppInput;
import google.registry.model.pricing.PremiumPricingEngine.DomainPrices;
import google.registry.tools.soy.DomainCreateSoyInfo;
import google.registry.util.StringGenerator;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.math.BigDecimal;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
/** A command to create a new domain via EPP. */
@@ -60,8 +68,8 @@ final class CreateDomainCommand extends CreateOrUpdateDomainCommand {
}
for (String domain : domains) {
String currency = null;
String cost = null;
CurrencyUnit currency = null;
BigDecimal cost = null;
DomainPrices prices = getPricesForDomainName(domain, clock.now());
// Check if the domain is premium and set the fee on the create command if so.
@@ -70,29 +78,32 @@ final class CreateDomainCommand extends CreateOrUpdateDomainCommand {
!force || forcePremiums,
"Forced creates on premium domain(s) require --force_premiums");
Money createCost = prices.getCreateCost();
currency = createCost.getCurrencyUnit().getCode();
cost = createCost.multipliedBy(period).getAmount().toString();
currency = createCost.getCurrencyUnit();
cost = createCost.multipliedBy(period).getAmount();
printStream.printf(
"NOTE: %s is premium at %s per year; sending total cost for %d year(s) of %s %s.\n",
domain, createCost, period, currency, cost);
}
setSoyTemplate(DomainCreateSoyInfo.getInstance(), DomainCreateSoyInfo.DOMAINCREATE);
SoyMapData soyMapData =
new SoyMapData(
"domain", domain,
"period", period,
"nameservers", nameservers,
"password", password,
"currency", currency,
"price", cost,
"dsRecords", DsRecord.convertToSoy(dsRecords),
"reason", reason,
"allocationToken", allocationToken);
if (requestedByRegistrar != null) {
soyMapData.put("requestedByRegistrar", requestedByRegistrar.toString());
}
addSoyRecord(clientId, soyMapData);
DomainCommand.Create.Builder createBuilder =
new DomainCommand.Create.Builder()
.setDomainName(domain)
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create(password)))
.setPeriod(Period.create(period, Period.Unit.YEARS))
.setNameserverHostNames(ImmutableSortedSet.copyOf(nameservers));
addEppInput(
clientId,
EppInput.create(
EppInput.Create.create(createBuilder.build()),
EppExtensions.feeCreate(currency, cost),
EppExtensions.secDnsCreate(
dsRecords.stream()
.map(DsRecord::toDsData)
.collect(ImmutableSet.toImmutableSet())),
EppExtensions.toolMetadata(reason, requestedByRegistrar),
EppExtensions.allocationToken(allocationToken))
.withClTrid("RegistryTool"));
}
}
}
@@ -18,14 +18,14 @@ import static google.registry.util.CollectionUtils.nullToEmpty;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.net.InetAddresses;
import com.google.template.soy.data.SoyMapData;
import google.registry.tools.soy.HostCreateSoyInfo;
import google.registry.model.eppinput.EppInput;
import google.registry.model.host.HostCommand;
import google.registry.util.DomainNameUtils;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.Comparator;
import java.util.List;
/** A command to create a new host via EPP. */
@@ -52,25 +52,19 @@ final class CreateHostCommand extends MutatingEppToolCommand {
@Override
protected void initMutatingEppToolCommand() {
setSoyTemplate(HostCreateSoyInfo.getInstance(), HostCreateSoyInfo.HOSTCREATE);
ImmutableList.Builder<String> ipv4Addresses = new ImmutableList.Builder<>();
ImmutableList.Builder<String> ipv6Addresses = new ImmutableList.Builder<>();
ImmutableSet.Builder<InetAddress> inetAddresses = new ImmutableSet.Builder<>();
for (String address : nullToEmpty(addresses)) {
InetAddress inetAddress = InetAddresses.forString(address);
if (inetAddress instanceof Inet4Address) {
ipv4Addresses.add(inetAddress.getHostAddress());
} else if (inetAddress instanceof Inet6Address) {
ipv6Addresses.add(inetAddress.getHostAddress());
} else {
throw new IllegalArgumentException(
String.format("IP address in unknown format: %s", address));
}
inetAddresses.add(InetAddresses.forString(address));
}
addSoyRecord(
HostCommand.Create.Builder createBuilder = new HostCommand.Create.Builder();
createBuilder.setTargetId(DomainNameUtils.canonicalizeHostname(hostName));
createBuilder.setInetAddresses(
ImmutableSortedSet.copyOf(
Comparator.comparing(InetAddresses::toAddrString), inetAddresses.build()));
addEppInput(
clientId,
new SoyMapData(
"hostname", DomainNameUtils.canonicalizeHostname(hostName),
"ipv4addresses", ipv4Addresses.build(),
"ipv6addresses", ipv6Addresses.build()));
EppInput.create(EppInput.Create.create(createBuilder.build())).withClTrid("RegistryTool"));
}
}
@@ -16,8 +16,9 @@ package google.registry.tools;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.template.soy.data.SoyMapData;
import google.registry.tools.soy.DomainDeleteSoyInfo;
import google.registry.model.domain.DomainCommand;
import google.registry.model.eppinput.EppExtensions;
import google.registry.model.eppinput.EppInput;
/** A command to delete a domain via EPP. */
@Parameters(separators = " =", commandDescription = "Delete domain")
@@ -60,11 +61,17 @@ final class DeleteDomainCommand extends MutatingEppToolCommand {
// Immediate deletion is accomplished using the superuser extension.
superuser = true;
}
setSoyTemplate(DomainDeleteSoyInfo.getInstance(), DomainDeleteSoyInfo.DELETEDOMAIN);
addSoyRecord(clientId, new SoyMapData(
"domainName", domainName,
"immediately", immediately,
"reason", reason,
"requestedByRegistrar", requestedByRegistrar));
DomainCommand.Delete deleteCommand = new DomainCommand.Delete();
deleteCommand.setTargetId(domainName);
addEppInput(
clientId,
EppInput.create(
EppInput.Delete.create(deleteCommand),
EppExtensions.toolMetadata(
"Deleted by registry administrator: " + reason, requestedByRegistrar),
EppExtensions.deleteSuperuser(immediately))
.withClTrid("RegistryTool"));
}
}

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