mirror of
https://github.com/google/nomulus
synced 2026-07-10 10:02:50 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dee132d04b | |||
| 847ef12a4f | |||
| d9349be18e | |||
| 0c74883428 | |||
| b357fc79f7 | |||
| 754e7fbddc | |||
| ad07b32638 | |||
| 8f69b48e87 | |||
| c33f0dc07f | |||
| 969353d4e2 | |||
| 6cd351ec7c | |||
| 19e03dbd2e | |||
| fc1eb162f2 | |||
| ed25854fbc | |||
| 0aa6bc6aaa | |||
| ff4c326ebe |
+3
-62
@@ -105,9 +105,8 @@ PRESUBMITS = {
|
||||
# System.(out|err).println should only appear in tools/ or load-testing/
|
||||
PresubmitCheck(
|
||||
r".*\bSystem\.(out|err)\.print", "java", {
|
||||
"StackdriverDashboardBuilder.java", "/tools/", "/example/",
|
||||
"/load-testing/", "RegistryTestServerMain.java",
|
||||
"TestServerExtension.java", "FlowDocumentationTool.java"
|
||||
"/tools/", "/example/", "/load-testing/",
|
||||
"RegistryTestServerMain.java", "TestServerExtension.java"
|
||||
}):
|
||||
"System.(out|err).println is only allowed in tools/ packages. Please "
|
||||
"use a logger instead.",
|
||||
@@ -120,7 +119,7 @@ PRESUBMITS = {
|
||||
):
|
||||
"In SOY please use the ({@param name: string} /** User name. */) style"
|
||||
" parameter passing instead of the ( * @param name User name.) style "
|
||||
"parameter pasing.",
|
||||
"parameter passing.",
|
||||
PresubmitCheck(
|
||||
r'.*\{[^}]+\w+:\s+"',
|
||||
"soy",
|
||||
@@ -139,41 +138,6 @@ PRESUBMITS = {
|
||||
{},
|
||||
):
|
||||
"All soy templates must use strict autoescaping",
|
||||
|
||||
# various JS linting checks
|
||||
PresubmitCheck(
|
||||
r".*goog\.base\(",
|
||||
"js",
|
||||
{"/node_modules/"},
|
||||
):
|
||||
"Use of goog.base is not allowed.",
|
||||
PresubmitCheck(
|
||||
r".*goog\.dom\.classes",
|
||||
"js",
|
||||
{"/node_modules/"},
|
||||
):
|
||||
"Instead of goog.dom.classes, use goog.dom.classlist which is smaller "
|
||||
"and faster.",
|
||||
PresubmitCheck(
|
||||
r".*goog\.getMsg",
|
||||
"js",
|
||||
{"/node_modules/"},
|
||||
):
|
||||
"Put messages in Soy, instead of using goog.getMsg().",
|
||||
PresubmitCheck(
|
||||
r".*(innerHTML|outerHTML)\s*(=|[+]=)([^=]|$)",
|
||||
"js",
|
||||
{"/node_modules/", "registrar_bin."},
|
||||
):
|
||||
"Do not assign directly to the dom. Use goog.dom.setTextContent to set"
|
||||
" to plain text, goog.dom.removeChildren to clear, or "
|
||||
"soy.renderElement to render anything else",
|
||||
PresubmitCheck(
|
||||
r".*console\.(log|info|warn|error)",
|
||||
"js",
|
||||
{"/node_modules/", "google/registry/ui/js/util.js", "registrar_bin."},
|
||||
):
|
||||
"JavaScript files should not include console logging.",
|
||||
PresubmitCheck(
|
||||
r".*\nimport (static )?.*\.shaded\..*",
|
||||
"java",
|
||||
@@ -303,26 +267,6 @@ def verify_flyway_index():
|
||||
return not success
|
||||
|
||||
|
||||
def verify_javascript_deps():
|
||||
"""Verifies that we haven't introduced any new javascript dependencies."""
|
||||
with open('package.json') as f:
|
||||
package = json.load(f)
|
||||
|
||||
deps = list(package['dependencies'].keys())
|
||||
if deps != EXPECTED_JS_PACKAGES:
|
||||
print('Unexpected javascript dependencies. Was expecting '
|
||||
'%s, got %s.' % (EXPECTED_JS_PACKAGES, deps))
|
||||
print(textwrap.dedent("""
|
||||
* If the new dependencies are intentional, please verify that the
|
||||
* license is one of the allowed licenses (see
|
||||
* config/dependency-license/allowed_licenses.json) and add an entry
|
||||
* for the package (with the license in a comment) to the
|
||||
* EXPECTED_JS_PACKAGES variable in config/presubmits.py.
|
||||
"""))
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_files():
|
||||
for root, dirnames, filenames in os.walk("."):
|
||||
for filename in filenames:
|
||||
@@ -347,8 +291,5 @@ if __name__ == "__main__":
|
||||
# when we put it here it fails fast before all of the tests are run.
|
||||
failed |= verify_flyway_index()
|
||||
|
||||
# Make sure we haven't introduced any javascript dependencies.
|
||||
failed |= verify_javascript_deps()
|
||||
|
||||
if failed:
|
||||
sys.exit(1)
|
||||
|
||||
@@ -47,9 +47,9 @@ export interface Contact {
|
||||
registrarId?: string;
|
||||
faxNumber?: string;
|
||||
types: Array<contactType>;
|
||||
visibleInWhoisAsAdmin?: boolean;
|
||||
visibleInWhoisAsTech?: boolean;
|
||||
visibleInDomainWhoisAsAbuse?: boolean;
|
||||
visibleInRdapAsAdmin?: boolean;
|
||||
visibleInRdapAsTech?: boolean;
|
||||
visibleInDomainRdapAsAbuse?: boolean;
|
||||
}
|
||||
|
||||
export interface ViewReadyContact extends Contact {
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
<h1>RDAP Preferences</h1>
|
||||
<div>
|
||||
<mat-checkbox
|
||||
[(ngModel)]="contactService.contactInEdit.visibleInWhoisAsAdmin"
|
||||
[(ngModel)]="contactService.contactInEdit.visibleInRdapAsAdmin"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
>Show in Registrar RDAP record as admin contact</mat-checkbox
|
||||
>
|
||||
@@ -119,7 +119,7 @@
|
||||
|
||||
<div>
|
||||
<mat-checkbox
|
||||
[(ngModel)]="contactService.contactInEdit.visibleInWhoisAsTech"
|
||||
[(ngModel)]="contactService.contactInEdit.visibleInRdapAsTech"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
>Show in Registrar RDAP record as technical contact</mat-checkbox
|
||||
>
|
||||
@@ -127,7 +127,7 @@
|
||||
|
||||
<div>
|
||||
<mat-checkbox
|
||||
[(ngModel)]="contactService.contactInEdit.visibleInDomainWhoisAsAbuse"
|
||||
[(ngModel)]="contactService.contactInEdit.visibleInDomainRdapAsAbuse"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
>Show Phone and Email in Domain RDAP Record as registrar abuse contact
|
||||
(per CL&D requirements)</mat-checkbox
|
||||
@@ -189,24 +189,24 @@
|
||||
<mat-list-item role="listitem">
|
||||
<h2>RDAP Preferences</h2>
|
||||
</mat-list-item>
|
||||
@if(contactService.contactInEdit.visibleInWhoisAsAdmin) {
|
||||
@if(contactService.contactInEdit.visibleInRdapAsAdmin) {
|
||||
<mat-divider></mat-divider>
|
||||
<mat-list-item role="listitem">
|
||||
<span class="console-app__list-value"
|
||||
>Show in Registrar RDAP record as admin contact</span
|
||||
>
|
||||
</mat-list-item>
|
||||
} @if(contactService.contactInEdit.visibleInWhoisAsTech) {
|
||||
} @if(contactService.contactInEdit.visibleInRdapAsTech) {
|
||||
<mat-divider></mat-divider>
|
||||
<mat-list-item
|
||||
role="listitem"
|
||||
*ngIf="contactService.contactInEdit.visibleInWhoisAsTech"
|
||||
*ngIf="contactService.contactInEdit.visibleInRdapAsTech"
|
||||
>
|
||||
<span class="console-app__list-value"
|
||||
>Show in Registrar RDAP record as technical contact</span
|
||||
>
|
||||
</mat-list-item>
|
||||
} @if(contactService.contactInEdit.visibleInDomainWhoisAsAbuse) {
|
||||
} @if(contactService.contactInEdit.visibleInDomainRdapAsAbuse) {
|
||||
<mat-divider></mat-divider>
|
||||
<mat-list-item role="listitem">
|
||||
<span class="console-app__list-value"
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
></mat-label
|
||||
>
|
||||
<mat-select [(ngModel)]="user().role" name="userRole">
|
||||
<mat-option value="PRIMARY_CONTACT">Editor</mat-option>
|
||||
<mat-option value="TECH_CONTACT">Editor</mat-option>
|
||||
<mat-option value="ACCOUNT_MANAGER">Viewer</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
@@ -30,7 +30,6 @@ def screenshotsForGoldensDir = "${project.buildDir}/screenshots_for_goldens"
|
||||
def newGoldensDir = "${project.buildDir}/new_golden_images"
|
||||
def goldensDir =
|
||||
"${javaTestDir}/google/registry/webdriver/goldens/chrome-linux"
|
||||
def jsDir = "${project.projectDir}/src/main/javascript"
|
||||
|
||||
// Tests that fail when running Gradle in a docker container, e. g. when
|
||||
// building the release artifacts in Google Cloud Build.
|
||||
@@ -55,9 +54,6 @@ def dockerIncompatibleTestPatterns = [
|
||||
// objects retained by frameworks.
|
||||
// TODO(weiminyu): identify cause and fix offending tests.
|
||||
def fragileTestPatterns = [
|
||||
// Changes cache timeouts and for some reason appears to have contention
|
||||
// with other tests.
|
||||
"google/registry/whois/WhoisCommandFactoryTest.*",
|
||||
// Breaks random other tests when running with standardTests.
|
||||
"google/registry/bsa/UploadBsaUnavailableDomainsActionTest.*",
|
||||
// Currently changes a global configuration parameter that for some reason
|
||||
|
||||
@@ -28,7 +28,6 @@ import static google.registry.bsa.persistence.Queries.queryMissedRegisteredUnblo
|
||||
import static google.registry.bsa.persistence.Queries.queryUnblockableDomainByLabels;
|
||||
import static google.registry.model.tld.Tld.isEnrolledWithBsa;
|
||||
import static google.registry.model.tld.Tlds.getTldEntitiesOfType;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static google.registry.util.BatchedStreams.toBatches;
|
||||
@@ -53,7 +52,6 @@ import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.tld.Tld.TldType;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Response;
|
||||
@@ -185,8 +183,8 @@ public class BsaValidateAction implements Runnable {
|
||||
ImmutableList<UnblockableDomain> batch;
|
||||
do {
|
||||
batch = Queries.batchReadUnblockableDomains(lastRead, transactionBatchSize);
|
||||
ImmutableMap<String, VKey<Domain>> activeDomains =
|
||||
ForeignKeyUtils.load(
|
||||
ImmutableMap<String, Domain> activeDomains =
|
||||
ForeignKeyUtils.loadResources(
|
||||
Domain.class,
|
||||
batch.stream().map(UnblockableDomain::domainName).collect(toImmutableList()),
|
||||
clock.nowUtc());
|
||||
@@ -201,7 +199,7 @@ public class BsaValidateAction implements Runnable {
|
||||
}
|
||||
|
||||
Optional<String> verifyDomainStillUnblockableWithReason(
|
||||
UnblockableDomain domain, ImmutableMap<String, VKey<Domain>> activeDomains) {
|
||||
UnblockableDomain domain, ImmutableMap<String, Domain> activeDomains) {
|
||||
DateTime now = clock.nowUtc();
|
||||
boolean isRegistered = activeDomains.containsKey(domain.domainName());
|
||||
boolean isReserved = isReservedDomain(domain.domainName(), now);
|
||||
@@ -230,10 +228,8 @@ public class BsaValidateAction implements Runnable {
|
||||
domain.reason()));
|
||||
}
|
||||
|
||||
boolean isStalenessAllowed(VKey<Domain> domainVKey) {
|
||||
Domain domain = bsaQuery(() -> replicaTm().loadByKey(domainVKey));
|
||||
var now = clock.nowUtc();
|
||||
return domain.getCreationTime().plus(maxStaleness).isAfter(now);
|
||||
boolean isStalenessAllowed(Domain domain) {
|
||||
return domain.getCreationTime().plus(maxStaleness).isAfter(clock.nowUtc());
|
||||
}
|
||||
|
||||
/** Returns unique labels across all block lists in the download specified by {@code jobName}. */
|
||||
|
||||
@@ -156,7 +156,7 @@ public final class DomainsRefresher {
|
||||
.collect(toImmutableSet());
|
||||
ImmutableSet<String> currRegistered =
|
||||
ImmutableSet.copyOf(
|
||||
ForeignKeyUtils.load(Domain.class, nameToEntity.keySet(), now).keySet());
|
||||
ForeignKeyUtils.loadKeys(Domain.class, nameToEntity.keySet(), now).keySet());
|
||||
SetView<String> noLongerRegistered = Sets.difference(prevRegistered, currRegistered);
|
||||
SetView<String> newlyRegistered = Sets.difference(currRegistered, prevRegistered);
|
||||
|
||||
|
||||
@@ -145,11 +145,10 @@ public final class LabelDiffUpdates {
|
||||
|
||||
ImmutableSet<String> validDomainNames =
|
||||
labels.stream()
|
||||
.map(label -> validDomainNamesForLabel(label, idnChecker))
|
||||
.flatMap(x -> x)
|
||||
.flatMap(label -> validDomainNamesForLabel(label, idnChecker))
|
||||
.collect(toImmutableSet());
|
||||
ImmutableSet<String> registeredDomainNames =
|
||||
ImmutableSet.copyOf(ForeignKeyUtils.load(Domain.class, validDomainNames, now).keySet());
|
||||
ForeignKeyUtils.loadKeys(Domain.class, validDomainNames, now).keySet();
|
||||
for (String domain : registeredDomainNames) {
|
||||
nonBlockedDomains.add(new UnblockableDomain(domain, Reason.REGISTERED));
|
||||
tm().put(BsaUnblockableDomain.of(domain, BsaUnblockableDomain.Reason.REGISTERED));
|
||||
|
||||
@@ -976,17 +976,6 @@ public final class RegistryConfig {
|
||||
return config.misc.transientFailureRetries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Amount of time public HTTP proxies are permitted to cache our WHOIS responses.
|
||||
*
|
||||
* @see google.registry.whois.WhoisHttpAction
|
||||
*/
|
||||
@Provides
|
||||
@Config("whoisHttpExpires")
|
||||
public static Duration provideWhoisHttpExpires() {
|
||||
return Duration.standardDays(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum number of results to return for an RDAP search query
|
||||
*
|
||||
@@ -998,39 +987,6 @@ public final class RegistryConfig {
|
||||
return 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redaction text for email address in WHOIS
|
||||
*
|
||||
* @see google.registry.whois.WhoisResponse
|
||||
*/
|
||||
@Provides
|
||||
@Config("whoisRedactedEmailText")
|
||||
public static String provideWhoisRedactedEmailText(RegistryConfigSettings config) {
|
||||
return config.registryPolicy.whoisRedactedEmailText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disclaimer displayed at the end of WHOIS query results.
|
||||
*
|
||||
* @see google.registry.whois.WhoisResponse
|
||||
*/
|
||||
@Provides
|
||||
@Config("whoisDisclaimer")
|
||||
public static String provideWhoisDisclaimer(RegistryConfigSettings config) {
|
||||
return config.registryPolicy.whoisDisclaimer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Message template for whois response when queried domain is blocked by BSA.
|
||||
*
|
||||
* @see google.registry.whois.WhoisResponse
|
||||
*/
|
||||
@Provides
|
||||
@Config("domainBlockedByBsaTemplate")
|
||||
public static String provideDomainBlockedByBsaTemplate(RegistryConfigSettings config) {
|
||||
return config.registryPolicy.domainBlockedByBsaTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum QPS for the Google Cloud Monitoring V3 (aka Stackdriver) API. The QPS limit can be
|
||||
* adjusted by contacting Cloud Support.
|
||||
@@ -1105,12 +1061,6 @@ public final class RegistryConfig {
|
||||
return config.registryPolicy.customLogicFactoryClass;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("whoisCommandFactoryClass")
|
||||
public static String provideWhoisCommandFactoryClass(RegistryConfigSettings config) {
|
||||
return config.registryPolicy.whoisCommandFactoryClass;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("dnsCountQueryCoordinatorClass")
|
||||
public static String dnsCountQueryCoordinatorClass(RegistryConfigSettings config) {
|
||||
@@ -1606,12 +1556,7 @@ public final class RegistryConfig {
|
||||
return CONFIG_SETTINGS.get().gSuite.outgoingEmailDisplayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns default WHOIS server to use when {@code Registrar#getWhoisServer()} is {@code null}.
|
||||
*
|
||||
* @see "google.registry.whois.DomainWhoisResponse"
|
||||
* @see "google.registry.whois.RegistrarWhoisResponse"
|
||||
*/
|
||||
/** Returns default WHOIS server to use when {@code Registrar#getWhoisServer()} is null. */
|
||||
public static String getDefaultRegistrarWhoisServer() {
|
||||
return CONFIG_SETTINGS.get().registryPolicy.defaultRegistrarWhoisServer;
|
||||
}
|
||||
|
||||
@@ -90,7 +90,6 @@ public class RegistryConfigSettings {
|
||||
public String contactAndHostRoidSuffix;
|
||||
public String productName;
|
||||
public String customLogicFactoryClass;
|
||||
public String whoisCommandFactoryClass;
|
||||
public String dnsCountQueryCoordinatorClass;
|
||||
public int contactAutomaticTransferDays;
|
||||
public String greetingServerId;
|
||||
@@ -102,9 +101,6 @@ public class RegistryConfigSettings {
|
||||
public String registryAdminClientId;
|
||||
public String premiumTermsExportDisclaimer;
|
||||
public String reservedTermsExportDisclaimer;
|
||||
public String whoisRedactedEmailText;
|
||||
public String whoisDisclaimer;
|
||||
public String domainBlockedByBsaTemplate;
|
||||
public String rdapTos;
|
||||
public String rdapTosStaticUrl;
|
||||
public String registryName;
|
||||
|
||||
@@ -65,10 +65,6 @@ registryPolicy:
|
||||
# See flows/custom/CustomLogicFactory.java
|
||||
customLogicFactoryClass: google.registry.flows.custom.CustomLogicFactory
|
||||
|
||||
# WHOIS command factory fully-qualified class name.
|
||||
# See whois/WhoisCommandFactory.java
|
||||
whoisCommandFactoryClass: google.registry.whois.WhoisCommandFactory
|
||||
|
||||
# Custom logic class for handling DNS query count reporting for ICANN.
|
||||
# See reporting/icann/DnsCountQueryCoordinator.java
|
||||
dnsCountQueryCoordinatorClass: google.registry.reporting.icann.DummyDnsCountQueryCoordinator
|
||||
@@ -114,31 +110,6 @@ registryPolicy:
|
||||
to publish. This list is subject to change. The most up-to-date source
|
||||
is always the registry itself, by sending domain check EPP commands.
|
||||
|
||||
# Redaction text for email address in WHOIS
|
||||
whoisRedactedEmailText: |
|
||||
Please query the WHOIS server of the owning registrar identified in this
|
||||
output for information on how to contact the Registrant, Admin, or Tech
|
||||
contact of the queried domain name.
|
||||
|
||||
# Disclaimer at the top of WHOIS results.
|
||||
whoisDisclaimer: |
|
||||
WHOIS information is provided by the registry solely for query-based,
|
||||
informational purposes. Any information provided is "as is" without any
|
||||
guarantee of accuracy. You may not use such information to (a) allow,
|
||||
enable, or otherwise support the transmission of mass unsolicited,
|
||||
commercial advertising or solicitations; (b) enable high volume, automated,
|
||||
electronic processes that access the registry's systems or any
|
||||
ICANN-Accredited Registrar, except as reasonably necessary to register
|
||||
domain names or modify existing registrations; or (c) engage in or support
|
||||
unlawful behavior. We reserve the right to restrict or deny your access to
|
||||
the WHOIS database, and may modify these terms at any time.
|
||||
|
||||
# BSA blocked domain name template.
|
||||
domainBlockedByBsaTemplate: |
|
||||
Domain Name: %s
|
||||
>>> This name is not available for registration.
|
||||
>>> This name has been blocked by a GlobalBlock service.
|
||||
|
||||
# RDAP Terms of Service text displayed at the /rdap/help/tos endpoint.
|
||||
rdapTos: >
|
||||
By querying our Domain Database as part of the RDAP pilot program (RDAP
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
addGracePeriodLength: "PT432000S"
|
||||
allowedFullyQualifiedHostNames: []
|
||||
allowedRegistrantContactIds: []
|
||||
anchorTenantAddGracePeriodLength: "PT2592000S"
|
||||
autoRenewGracePeriodLength: "PT3888000S"
|
||||
automaticTransferLength: "PT432000S"
|
||||
|
||||
@@ -25,7 +25,6 @@ import static google.registry.dns.DnsModule.PARAM_REFRESH_REQUEST_TIME;
|
||||
import static google.registry.dns.DnsUtils.DNS_PUBLISH_PUSH_QUEUE_NAME;
|
||||
import static google.registry.dns.DnsUtils.requestDomainDnsRefresh;
|
||||
import static google.registry.dns.DnsUtils.requestHostDnsRefresh;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static google.registry.request.RequestParameters.PARAM_TLD;
|
||||
@@ -46,6 +45,7 @@ import google.registry.dns.DnsMetrics.CommitStatus;
|
||||
import google.registry.dns.DnsMetrics.PublishStatus;
|
||||
import google.registry.dns.writer.DnsWriter;
|
||||
import google.registry.groups.GmailClient;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
@@ -237,7 +237,8 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
|
||||
.findFirst()
|
||||
.ifPresent(
|
||||
dn -> {
|
||||
Optional<Domain> domain = loadByForeignKey(Domain.class, dn, clock.nowUtc());
|
||||
Optional<Domain> domain =
|
||||
ForeignKeyUtils.loadResource(Domain.class, dn, clock.nowUtc());
|
||||
if (domain.isPresent()) {
|
||||
notifyWithEmailAboutDnsUpdateFailure(
|
||||
domain.get().getCurrentSponsorRegistrarId(), dn, false);
|
||||
@@ -250,7 +251,8 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
|
||||
.findFirst()
|
||||
.ifPresent(
|
||||
hn -> {
|
||||
Optional<Host> host = loadByForeignKey(Host.class, hn, clock.nowUtc());
|
||||
Optional<Host> host =
|
||||
ForeignKeyUtils.loadResource(Host.class, hn, clock.nowUtc());
|
||||
if (host.isPresent()) {
|
||||
notifyWithEmailAboutDnsUpdateFailure(
|
||||
host.get().getPersistedCurrentSponsorRegistrarId(), hn, true);
|
||||
|
||||
@@ -16,12 +16,12 @@ package google.registry.dns;
|
||||
|
||||
import static google.registry.dns.DnsUtils.requestDomainDnsRefresh;
|
||||
import static google.registry.dns.DnsUtils.requestHostDnsRefresh;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import google.registry.dns.DnsUtils.TargetType;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.EppResource.ForeignKeyedEppResource;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.annotations.ExternalMessagingName;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
@@ -79,7 +79,7 @@ public final class RefreshDnsAction implements Runnable {
|
||||
|
||||
private <T extends EppResource & ForeignKeyedEppResource>
|
||||
T loadAndVerifyExistence(Class<T> clazz, String foreignKey) {
|
||||
return loadByForeignKey(clazz, foreignKey, clock.nowUtc())
|
||||
return ForeignKeyUtils.loadResource(clazz, foreignKey, clock.nowUtc())
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
|
||||
@@ -17,7 +17,6 @@ package google.registry.dns.writer.clouddns;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.dns.DnsUtils.getDnsAPlusAAAATtlForHost;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.util.DomainNameUtils.getSecondLevelDomain;
|
||||
|
||||
import com.google.api.client.googleapis.json.GoogleJsonError;
|
||||
@@ -37,6 +36,7 @@ import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.dns.writer.BaseDnsWriter;
|
||||
import google.registry.dns.writer.DnsWriter;
|
||||
import google.registry.dns.writer.DnsWriterZone;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.secdns.DomainDsData;
|
||||
import google.registry.model.host.Host;
|
||||
@@ -123,7 +123,8 @@ public class CloudDnsWriter extends BaseDnsWriter {
|
||||
String absoluteDomainName = getAbsoluteHostName(domainName);
|
||||
|
||||
// Load the target domain. Note that it can be absent if this domain was just deleted.
|
||||
Optional<Domain> domain = loadByForeignKey(Domain.class, domainName, clock.nowUtc());
|
||||
Optional<Domain> domain =
|
||||
ForeignKeyUtils.loadResource(Domain.class, domainName, clock.nowUtc());
|
||||
|
||||
// Return early if no DNS records should be published.
|
||||
// desiredRecordsBuilder is populated with an empty set to indicate that all existing records
|
||||
@@ -189,7 +190,7 @@ public class CloudDnsWriter extends BaseDnsWriter {
|
||||
// Load the target host. Note that it can be absent if this host was just deleted.
|
||||
// desiredRecords is populated with an empty set to indicate that all existing records
|
||||
// should be deleted.
|
||||
Optional<Host> host = loadByForeignKey(Host.class, hostName, clock.nowUtc());
|
||||
Optional<Host> host = ForeignKeyUtils.loadResource(Host.class, hostName, clock.nowUtc());
|
||||
|
||||
// Return early if the host is deleted.
|
||||
if (host.isEmpty()) {
|
||||
|
||||
@@ -19,7 +19,6 @@ import static com.google.common.base.Verify.verify;
|
||||
import static com.google.common.collect.Sets.intersection;
|
||||
import static com.google.common.collect.Sets.union;
|
||||
import static google.registry.dns.DnsUtils.getDnsAPlusAAAATtlForHost;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -28,6 +27,7 @@ import com.google.common.net.InternetDomainName;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.dns.writer.BaseDnsWriter;
|
||||
import google.registry.dns.writer.DnsWriterZone;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.secdns.DomainDsData;
|
||||
import google.registry.model.host.Host;
|
||||
@@ -129,7 +129,8 @@ public class DnsUpdateWriter extends BaseDnsWriter {
|
||||
* this domain refresh request
|
||||
*/
|
||||
private void publishDomain(String domainName, String requestingHostName) {
|
||||
Optional<Domain> domainOptional = loadByForeignKey(Domain.class, domainName, clock.nowUtc());
|
||||
Optional<Domain> domainOptional =
|
||||
ForeignKeyUtils.loadResource(Domain.class, domainName, clock.nowUtc());
|
||||
update.delete(toAbsoluteName(domainName), Type.ANY);
|
||||
// If the domain is now deleted, then don't update DNS for it.
|
||||
if (domainOptional.isPresent()) {
|
||||
@@ -218,7 +219,7 @@ public class DnsUpdateWriter extends BaseDnsWriter {
|
||||
private void addInBailiwickNameServerSet(Domain domain, Update update) {
|
||||
for (String hostName :
|
||||
intersection(domain.loadNameserverHostNames(), domain.getSubordinateHosts())) {
|
||||
Optional<Host> host = loadByForeignKey(Host.class, hostName, clock.nowUtc());
|
||||
Optional<Host> host = ForeignKeyUtils.loadResource(Host.class, hostName, clock.nowUtc());
|
||||
checkState(host.isPresent(), "Host %s cannot be loaded", hostName);
|
||||
update.add(makeAddressSet(host.get()));
|
||||
update.add(makeV6AddressSet(host.get()));
|
||||
|
||||
@@ -57,11 +57,6 @@
|
||||
<!-- Verbatim JavaScript sources (only visible to admins for debugging). -->
|
||||
<url-pattern>/assets/sources/*</url-pattern>
|
||||
|
||||
<!-- TODO(b/26776367): Move these files to /assets/sources. -->
|
||||
<url-pattern>/assets/js/registrar_bin.js.map</url-pattern>
|
||||
<url-pattern>/assets/js/registrar_dbg.js</url-pattern>
|
||||
<url-pattern>/assets/css/registrar_dbg.css</url-pattern>
|
||||
|
||||
</web-resource-collection>
|
||||
<auth-constraint>
|
||||
<role-name>admin</role-name>
|
||||
|
||||
@@ -128,10 +128,10 @@ class SyncRegistrarsSheet {
|
||||
builder.put("billingContacts", convertContacts(contacts, byType(BILLING)));
|
||||
builder.put(
|
||||
"contactsMarkedAsWhoisAdmin",
|
||||
convertContacts(contacts, RegistrarPoc::getVisibleInWhoisAsAdmin));
|
||||
convertContacts(contacts, RegistrarPoc::getVisibleInRdapAsAdmin));
|
||||
builder.put(
|
||||
"contactsMarkedAsWhoisTech",
|
||||
convertContacts(contacts, RegistrarPoc::getVisibleInWhoisAsTech));
|
||||
convertContacts(contacts, RegistrarPoc::getVisibleInRdapAsTech));
|
||||
builder.put("emailAddress", convert(registrar.getEmailAddress()));
|
||||
builder.put("address.street", convert(address.getStreet()));
|
||||
builder.put("address.city", convert(address.getCity()));
|
||||
|
||||
@@ -38,7 +38,6 @@ import static google.registry.pricing.PricingEngineProxy.isDomainPremium;
|
||||
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
|
||||
import static org.json.simple.JSONValue.toJSONString;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
@@ -185,8 +184,7 @@ public class CheckApiAction implements Runnable {
|
||||
}
|
||||
|
||||
private boolean checkExists(String domainString, DateTime now) {
|
||||
return !ForeignKeyUtils.loadByCache(Domain.class, ImmutableList.of(domainString), now)
|
||||
.isEmpty();
|
||||
return ForeignKeyUtils.loadKeyByCache(Domain.class, domainString, now).isPresent();
|
||||
}
|
||||
|
||||
private Optional<String> checkReserved(InternetDomainName domainName) {
|
||||
|
||||
@@ -16,7 +16,6 @@ package google.registry.flows;
|
||||
|
||||
import static com.google.common.collect.Sets.intersection;
|
||||
import static google.registry.model.EppResourceUtils.isLinked;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -72,10 +71,9 @@ public final class ResourceFlowUtils {
|
||||
*/
|
||||
public static <R extends EppResource> void checkLinkedDomains(
|
||||
final String targetId, final DateTime now, final Class<R> resourceClass) throws EppException {
|
||||
VKey<R> key = ForeignKeyUtils.load(resourceClass, targetId, now);
|
||||
if (key == null) {
|
||||
throw new ResourceDoesNotExistException(resourceClass, targetId);
|
||||
}
|
||||
VKey<R> key =
|
||||
ForeignKeyUtils.loadKey(resourceClass, targetId, now)
|
||||
.orElseThrow(() -> new ResourceDoesNotExistException(resourceClass, targetId));
|
||||
if (isLinked(key, now)) {
|
||||
throw new ResourceToDeleteIsReferencedException();
|
||||
}
|
||||
@@ -97,7 +95,7 @@ public final class ResourceFlowUtils {
|
||||
|
||||
public static <R extends EppResource & ForeignKeyedEppResource> R loadAndVerifyExistence(
|
||||
Class<R> clazz, String targetId, DateTime now) throws ResourceDoesNotExistException {
|
||||
return verifyExistence(clazz, targetId, loadByForeignKey(clazz, targetId, now));
|
||||
return verifyExistence(clazz, targetId, ForeignKeyUtils.loadResource(clazz, targetId, now));
|
||||
}
|
||||
|
||||
public static <R extends EppResource> R verifyExistence(
|
||||
@@ -107,11 +105,10 @@ public final class ResourceFlowUtils {
|
||||
|
||||
public static <R extends EppResource> void verifyResourceDoesNotExist(
|
||||
Class<R> clazz, String targetId, DateTime now, String registrarId) throws EppException {
|
||||
VKey<R> key = ForeignKeyUtils.load(clazz, targetId, now);
|
||||
if (key != null) {
|
||||
R resource = tm().loadByKey(key);
|
||||
Optional<R> resource = ForeignKeyUtils.loadResource(clazz, targetId, now);
|
||||
if (resource.isPresent()) {
|
||||
// These are similar exceptions, but we can track them internally as log-based metrics.
|
||||
if (Objects.equals(registrarId, resource.getPersistedCurrentSponsorRegistrarId())) {
|
||||
if (Objects.equals(registrarId, resource.get().getPersistedCurrentSponsorRegistrarId())) {
|
||||
throw new ResourceAlreadyExistsForThisClientException(targetId);
|
||||
} else {
|
||||
throw new ResourceCreateContentionException(targetId);
|
||||
|
||||
@@ -16,7 +16,6 @@ package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyTargetIdCount;
|
||||
import static google.registry.model.EppResourceUtils.checkResourcesExist;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -26,6 +25,7 @@ import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactCommand.Check;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
@@ -62,7 +62,7 @@ public final class ContactCheckFlow implements TransactionalFlow {
|
||||
ImmutableList<String> targetIds = ((Check) resourceCommand).getTargetIds();
|
||||
verifyTargetIdCount(targetIds, maxChecks);
|
||||
ImmutableSet<String> existingIds =
|
||||
checkResourcesExist(Contact.class, targetIds, clock.nowUtc());
|
||||
ForeignKeyUtils.loadKeys(Contact.class, targetIds, clock.nowUtc()).keySet();
|
||||
ImmutableList.Builder<ContactCheck> checks = new ImmutableList.Builder<>();
|
||||
for (String id : targetIds) {
|
||||
boolean unused = !existingIds.contains(id);
|
||||
|
||||
@@ -181,7 +181,7 @@ public final class DomainCheckFlow implements TransactionalFlow {
|
||||
.setAsOfDate(now)
|
||||
.build());
|
||||
ImmutableMap<String, VKey<Domain>> existingDomains =
|
||||
ForeignKeyUtils.load(Domain.class, domainNames, now);
|
||||
ForeignKeyUtils.loadKeys(Domain.class, domainNames, now);
|
||||
// Check block labels only when there are unregistered domains, since "In use" goes before
|
||||
// "Blocked by BSA".
|
||||
ImmutableSet<InternetDomainName> bsaBlockedDomainNames =
|
||||
|
||||
@@ -195,7 +195,6 @@ import org.joda.time.Duration;
|
||||
* @error {@link DomainFlowUtils.NameserversNotAllowedForTldException}
|
||||
* @error {@link DomainFlowUtils.NameserversNotSpecifiedForTldWithNameserverAllowListException}
|
||||
* @error {@link DomainFlowUtils.PremiumNameBlockedException}
|
||||
* @error {@link DomainFlowUtils.RegistrantNotAllowedException}
|
||||
* @error {@link RegistrantProhibitedException}
|
||||
* @error {@link DomainFlowUtils.RegistrarMustBeActiveForThisOperationException}
|
||||
* @error {@link DomainFlowUtils.TldDoesNotExistException}
|
||||
|
||||
@@ -77,6 +77,7 @@ import google.registry.model.domain.fee.FeeTransformResponseExtension;
|
||||
import google.registry.model.domain.fee06.FeeDeleteResponseExtensionV06;
|
||||
import google.registry.model.domain.fee11.FeeDeleteResponseExtensionV11;
|
||||
import google.registry.model.domain.fee12.FeeDeleteResponseExtensionV12;
|
||||
import google.registry.model.domain.feestdv1.FeeDeleteResponseExtensionStdV1;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.domain.secdns.SecDnsCreateExtension;
|
||||
@@ -428,6 +429,9 @@ public final class DomainDeleteFlow implements MutatingFlow, SqlStatementLogging
|
||||
@Nullable
|
||||
private FeeTransformResponseExtension.Builder getDeleteResponseBuilder() {
|
||||
Set<String> uris = nullToEmpty(sessionMetadata.getServiceExtensionUris());
|
||||
if (uris.contains(ServiceExtension.FEE_1_00.getUri())) {
|
||||
return new FeeDeleteResponseExtensionStdV1.Builder();
|
||||
}
|
||||
if (uris.contains(ServiceExtension.FEE_0_12.getUri())) {
|
||||
return new FeeDeleteResponseExtensionV12.Builder();
|
||||
}
|
||||
|
||||
@@ -553,17 +553,6 @@ public class DomainFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
static void validateRegistrantAllowedOnTld(String tld, Optional<String> registrantContactId)
|
||||
throws RegistrantNotAllowedException {
|
||||
ImmutableSet<String> allowedRegistrants = Tld.get(tld).getAllowedRegistrantContactIds();
|
||||
// Empty allow list or null registrantContactId are ignored.
|
||||
if (registrantContactId.isPresent()
|
||||
&& !allowedRegistrants.isEmpty()
|
||||
&& !allowedRegistrants.contains(registrantContactId.get())) {
|
||||
throw new RegistrantNotAllowedException(registrantContactId.get());
|
||||
}
|
||||
}
|
||||
|
||||
static void validateNameserversAllowedOnTld(String tld, Set<String> fullyQualifiedHostNames)
|
||||
throws EppException {
|
||||
ImmutableSet<String> allowedHostNames = Tld.get(tld).getAllowedFullyQualifiedHostNames();
|
||||
@@ -1091,7 +1080,6 @@ public class DomainFlowUtils {
|
||||
command.getContacts(), command.getRegistrant(), command.getNameservers());
|
||||
validateContactsHaveTypes(command.getContacts());
|
||||
String tldStr = tld.getTldStr();
|
||||
validateRegistrantAllowedOnTld(tldStr, command.getRegistrantContactId());
|
||||
validateNoDuplicateContacts(command.getContacts());
|
||||
validateCreateContactData(command.getRegistrant(), command.getContacts());
|
||||
ImmutableSet<String> hostNames = command.getNameserverHostNames();
|
||||
@@ -1630,13 +1618,6 @@ public class DomainFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** Registrant is not allow-listed for this TLD. */
|
||||
public static class RegistrantNotAllowedException extends StatusProhibitsOperationException {
|
||||
public RegistrantNotAllowedException(String contactId) {
|
||||
super(String.format("Registrant with id %s is not allow-listed for this TLD", contactId));
|
||||
}
|
||||
}
|
||||
|
||||
/** Nameservers are not allow-listed for this TLD. */
|
||||
public static class NameserversNotAllowedForTldException
|
||||
extends StatusProhibitsOperationException {
|
||||
|
||||
@@ -15,12 +15,11 @@
|
||||
package google.registry.flows.domain;
|
||||
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.addSecDnsExtensionIfPresent;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.handleFeeRequest;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.loadForeignKeyedDesignatedContacts;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -108,8 +107,7 @@ public final class DomainInfoFlow implements MutatingFlow {
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
DateTime now = clock.nowUtc();
|
||||
Domain domain =
|
||||
verifyExistence(Domain.class, targetId, loadByForeignKey(Domain.class, targetId, now));
|
||||
Domain domain = loadAndVerifyExistence(Domain.class, targetId, now);
|
||||
verifyOptionalAuthInfo(authInfo, domain);
|
||||
flowCustomLogic.afterValidation(
|
||||
AfterValidationParameters.newBuilder().setDomain(domain).build());
|
||||
|
||||
@@ -36,7 +36,6 @@ import static google.registry.flows.domain.DomainFlowUtils.validateFeesAckedIfPr
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateNameserversAllowedOnTld;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateNameserversCountForTld;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateNoDuplicateContacts;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateRegistrantAllowedOnTld;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateUpdateContactData;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.verifyClientUpdateNotProhibited;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPendingDelete;
|
||||
@@ -131,7 +130,6 @@ import org.joda.time.DateTime;
|
||||
* @error {@link DomainFlowUtils.NameserversNotAllowedForTldException}
|
||||
* @error {@link NameserversNotSpecifiedForTldWithNameserverAllowListException}
|
||||
* @error {@link DomainFlowUtils.NotAuthorizedForTldException}
|
||||
* @error {@link DomainFlowUtils.RegistrantNotAllowedException}
|
||||
* @error {@link RegistrantProhibitedException}
|
||||
* @error {@link DomainFlowUtils.SecDnsAllUsageException}
|
||||
* @error {@link DomainFlowUtils.TooManyDsRecordsException}
|
||||
@@ -248,7 +246,6 @@ public final class DomainUpdateFlow implements MutatingFlow {
|
||||
add.getNameservers());
|
||||
validateContactsHaveTypes(add.getContacts());
|
||||
validateContactsHaveTypes(remove.getContacts());
|
||||
validateRegistrantAllowedOnTld(tldStr, command.getInnerChange().getRegistrantContactId());
|
||||
validateNameserversAllowedOnTld(tldStr, add.getNameserverHostNames());
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ package google.registry.flows.host;
|
||||
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyTargetIdCount;
|
||||
import static google.registry.model.EppResourceUtils.checkResourcesExist;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -26,6 +25,7 @@ import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
import google.registry.model.eppoutput.CheckData.HostCheck;
|
||||
import google.registry.model.eppoutput.CheckData.HostCheckData;
|
||||
@@ -61,7 +61,8 @@ public final class HostCheckFlow implements TransactionalFlow {
|
||||
extensionManager.validate(); // There are no legal extensions for this flow.
|
||||
ImmutableList<String> hostnames = ((Check) resourceCommand).getTargetIds();
|
||||
verifyTargetIdCount(hostnames, maxChecks);
|
||||
ImmutableSet<String> existingIds = checkResourcesExist(Host.class, hostnames, clock.nowUtc());
|
||||
ImmutableSet<String> existingIds =
|
||||
ForeignKeyUtils.loadKeys(Host.class, hostnames, clock.nowUtc()).keySet();
|
||||
ImmutableList.Builder<HostCheck> checks = new ImmutableList.Builder<>();
|
||||
for (String hostname : hostnames) {
|
||||
boolean unused = !existingIds.contains(hostname);
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package google.registry.flows.host;
|
||||
|
||||
import static google.registry.model.EppResourceUtils.isActive;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.tld.Tlds.findTldForName;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
@@ -29,6 +28,7 @@ import google.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||
import google.registry.flows.EppException.ParameterValueRangeErrorException;
|
||||
import google.registry.flows.EppException.ParameterValueSyntaxErrorException;
|
||||
import google.registry.flows.EppException.StatusProhibitsOperationException;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.util.Idn;
|
||||
@@ -90,7 +90,8 @@ public class HostFlowUtils {
|
||||
hostName.parts().stream()
|
||||
.skip(hostName.parts().size() - (tld.get().parts().size() + 1))
|
||||
.collect(joining("."));
|
||||
Optional<Domain> superordinateDomain = loadByForeignKey(Domain.class, domainName, now);
|
||||
Optional<Domain> superordinateDomain =
|
||||
ForeignKeyUtils.loadResource(Domain.class, domainName, now);
|
||||
if (superordinateDomain.isEmpty() || !isActive(superordinateDomain.get(), now)) {
|
||||
throw new SuperordinateDomainDoesNotExistException(domainName);
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ public final class HostUpdateFlow implements MutatingFlow {
|
||||
EppResource owningResource = firstNonNull(oldSuperordinateDomain, existingHost);
|
||||
verifyUpdateAllowed(
|
||||
command, existingHost, newSuperordinateDomain.orElse(null), owningResource, isHostRename);
|
||||
if (isHostRename && ForeignKeyUtils.load(Host.class, newHostName, now) != null) {
|
||||
if (isHostRename && ForeignKeyUtils.loadKey(Host.class, newHostName, now).isPresent()) {
|
||||
throw new HostAlreadyExistsException(newHostName);
|
||||
}
|
||||
AddRemove add = command.getInnerAdd();
|
||||
|
||||
@@ -16,19 +16,14 @@ package google.registry.model;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static google.registry.util.DateTimeUtils.isAtOrAfter;
|
||||
import static google.registry.util.DateTimeUtils.isBeforeOrAt;
|
||||
import static google.registry.util.DateTimeUtils.latestOf;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.config.RegistryConfig;
|
||||
import google.registry.model.EppResource.BuilderWithTransferData;
|
||||
import google.registry.model.EppResource.ForeignKeyedEppResource;
|
||||
import google.registry.model.EppResource.ResourceWithTransferData;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
@@ -41,13 +36,9 @@ import google.registry.model.transfer.DomainTransferData;
|
||||
import google.registry.model.transfer.TransferData;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.transaction.TransactionManager;
|
||||
import jakarta.persistence.Query;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Interval;
|
||||
@@ -90,123 +81,6 @@ public final class EppResourceUtils {
|
||||
return (T) resource.cloneProjectedAtTime(now);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the last created version of an {@link EppResource} from the database by foreign key.
|
||||
*
|
||||
* <p>Returns empty if no resource with this foreign key was ever created, or if the most recently
|
||||
* created resource was deleted before time "now".
|
||||
*
|
||||
* <p>Loading an {@link EppResource} by itself is not sufficient to know its current state since
|
||||
* it may have various expirable conditions and status values that might implicitly change its
|
||||
* state as time progresses even if it has not been updated in the database. Rather, the resource
|
||||
* must be combined with a timestamp to view its current state. We use a global last updated
|
||||
* timestamp on the resource's entity group (which is essentially free since all writes to the
|
||||
* entity group must be serialized anyways) to guarantee monotonically increasing write times, and
|
||||
* forward our projected time to the greater of this timestamp or "now". This guarantees that
|
||||
* we're not projecting into the past.
|
||||
*
|
||||
* @param clazz the resource type to load
|
||||
* @param foreignKey id to match
|
||||
* @param now the current logical time to project resources at
|
||||
*/
|
||||
public static <T extends EppResource> Optional<T> loadByForeignKey(
|
||||
Class<T> clazz, String foreignKey, DateTime now) {
|
||||
return loadByForeignKeyHelper(tm(), clazz, foreignKey, now, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the last created version of an {@link EppResource} from the database by foreign key,
|
||||
* using a cache, if caching is enabled in config settings.
|
||||
*
|
||||
* <p>Returns null if no resource with this foreign key was ever created, or if the most recently
|
||||
* created resource was deleted before time "now".
|
||||
*
|
||||
* <p>Loading an {@link EppResource} by itself is not sufficient to know its current state since
|
||||
* it may have various expirable conditions and status values that might implicitly change its
|
||||
* state as time progresses even if it has not been updated in the database. Rather, the resource
|
||||
* must be combined with a timestamp to view its current state. We use a global last updated
|
||||
* timestamp to guarantee monotonically increasing write times, and forward our projected time to
|
||||
* the greater of this timestamp or "now". This guarantees that we're not projecting into the
|
||||
* past.
|
||||
*
|
||||
* <p>Do not call this cached version for anything that needs transactional consistency. It should
|
||||
* only be used when it's OK if the data is potentially being out of date, e.g. WHOIS.
|
||||
*
|
||||
* @param clazz the resource type to load
|
||||
* @param foreignKey id to match
|
||||
* @param now the current logical time to project resources at
|
||||
*/
|
||||
public static <T extends EppResource> Optional<T> loadByForeignKeyByCacheIfEnabled(
|
||||
Class<T> clazz, String foreignKey, DateTime now) {
|
||||
return loadByForeignKeyHelper(
|
||||
tm(), clazz, foreignKey, now, RegistryConfig.isEppResourceCachingEnabled());
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the last created version of an {@link EppResource} from the replica database by foreign
|
||||
* key, using a cache.
|
||||
*
|
||||
* <p>This method ignores the config setting for caching, and is reserved for use cases that can
|
||||
* tolerate slightly stale data.
|
||||
*/
|
||||
public static <T extends EppResource> Optional<T> loadByForeignKeyByCache(
|
||||
Class<T> clazz, String foreignKey, DateTime now) {
|
||||
return loadByForeignKeyHelper(replicaTm(), clazz, foreignKey, now, true);
|
||||
}
|
||||
|
||||
private static <T extends EppResource> Optional<T> loadByForeignKeyHelper(
|
||||
TransactionManager txnManager,
|
||||
Class<T> clazz,
|
||||
String foreignKey,
|
||||
DateTime now,
|
||||
boolean useCache) {
|
||||
checkArgument(
|
||||
ForeignKeyedEppResource.class.isAssignableFrom(clazz),
|
||||
"loadByForeignKey may only be called for foreign keyed EPP resources");
|
||||
VKey<T> key =
|
||||
useCache
|
||||
? ForeignKeyUtils.loadByCache(clazz, ImmutableList.of(foreignKey), now).get(foreignKey)
|
||||
: ForeignKeyUtils.load(clazz, foreignKey, now);
|
||||
// The returned key is null if the resource is hard deleted or soft deleted by the given time.
|
||||
if (key == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
T resource =
|
||||
useCache
|
||||
? EppResource.loadByCache(key)
|
||||
// This transaction is buried very deeply inside many outer nested calls, hence merits
|
||||
// the use of reTransact() for now pending a substantial refactoring.
|
||||
: txnManager.reTransact(() -> txnManager.loadByKeyIfPresent(key).orElse(null));
|
||||
if (resource == null || isAtOrAfter(now, resource.getDeletionTime())) {
|
||||
return Optional.empty();
|
||||
}
|
||||
// When setting status values based on a time, choose the greater of "now" and the resource's
|
||||
// UpdateAutoTimestamp. For non-mutating uses (info, whois, etc.), this is equivalent to rolling
|
||||
// "now" forward to at least the last update on the resource, so that a read right after a write
|
||||
// doesn't appear stale. For mutating flows, if we had to roll now forward then the flow will
|
||||
// fail when it tries to save anything, since "now" is needed to be > the last update time for
|
||||
// writes.
|
||||
return Optional.of(
|
||||
cloneProjectedAtTime(
|
||||
resource, latestOf(now, resource.getUpdateTimestamp().getTimestamp())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks multiple {@link EppResource} objects from the database by unique ids.
|
||||
*
|
||||
* <p>There are currently no resources that support checks and do not use foreign keys. If we need
|
||||
* to support that case in the future, we can loosen the type to allow any {@link EppResource} and
|
||||
* add code to do the lookup by id directly.
|
||||
*
|
||||
* @param clazz the resource type to load
|
||||
* @param uniqueIds a list of ids to match
|
||||
* @param now the logical time of the check
|
||||
*/
|
||||
public static <T extends EppResource> ImmutableSet<String> checkResourcesExist(
|
||||
Class<T> clazz, Collection<String> uniqueIds, final DateTime now) {
|
||||
return ForeignKeyUtils.load(clazz, uniqueIds, now).keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Function that transforms an EppResource to the given DateTime, suitable for use with
|
||||
* Iterables.transform() over a collection of EppResources.
|
||||
@@ -306,21 +180,6 @@ public final class EppResourceUtils {
|
||||
: null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewinds an {@link EppResource} object to a given point in time.
|
||||
*
|
||||
* <p>This method costs nothing if {@code resource} is already current. Otherwise, it returns an
|
||||
* async operation that performs a single fetch operation.
|
||||
*
|
||||
* @return an asynchronous operation returning resource at {@code timestamp} or {@code null} if
|
||||
* resource is deleted or not yet created
|
||||
* @see #loadAtPointInTime(EppResource, DateTime)
|
||||
*/
|
||||
public static <T extends EppResource> Supplier<T> loadAtPointInTimeAsync(
|
||||
final T resource, final DateTime timestamp) {
|
||||
return () -> loadAtPointInTime(resource, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most recent revision of a given EppResource before or at the provided timestamp,
|
||||
* falling back to using the resource as-is if there are no revisions.
|
||||
@@ -395,13 +254,11 @@ public final class EppResourceUtils {
|
||||
/**
|
||||
* Returns whether the given contact or host is linked to (that is, referenced by) a domain.
|
||||
*
|
||||
* <p>This is an eventually consistent query.
|
||||
*
|
||||
* @param key the referent key
|
||||
* @param now the logical time of the check
|
||||
*/
|
||||
public static boolean isLinked(VKey<? extends EppResource> key, DateTime now) {
|
||||
return getLinkedDomainKeys(key, now, 1).size() > 0;
|
||||
return !getLinkedDomainKeys(key, now, 1).isEmpty();
|
||||
}
|
||||
|
||||
private EppResourceUtils() {}
|
||||
|
||||
@@ -41,13 +41,17 @@ import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* Util class to map a foreign key to the {@link VKey} to the active instance of {@link EppResource}
|
||||
* whose unique repoId matches the foreign key string at a given time. The instance is never
|
||||
* deleted, but it is updated if a newer entity becomes the active entity.
|
||||
* Util class for mapping a foreign key to a {@link VKey} or {@link EppResource}.
|
||||
*
|
||||
* <p>We return the resource that matches the foreign key at a given time (this may vary over time
|
||||
* e.g. if a domain expires and is re-registered). The instance is never deleted, but it is updated
|
||||
* if a newer entity becomes the active entity.
|
||||
*
|
||||
* <p>One can retrieve either the {@link VKey} (the repo ID) in the situations where that's
|
||||
* sufficient, or the resource itself, either with or without caching.
|
||||
*/
|
||||
public final class ForeignKeyUtils {
|
||||
|
||||
@@ -60,21 +64,30 @@ public final class ForeignKeyUtils {
|
||||
Domain.class, "domainName",
|
||||
Host.class, "hostName");
|
||||
|
||||
public record MostRecentResource(String repoId, DateTime deletionTime) {}
|
||||
|
||||
/**
|
||||
* Loads a {@link VKey} to an {@link EppResource} from the database by foreign key.
|
||||
* Loads an optional {@link VKey} to an {@link EppResource} from the database by foreign key.
|
||||
*
|
||||
* <p>Returns null if no resource with this foreign key was ever created, or if the most recently
|
||||
* <p>Returns empty if no resource with this foreign key was ever created, or if the most recently
|
||||
* created resource was deleted before time "now".
|
||||
*
|
||||
* @param clazz the resource type to load
|
||||
* @param foreignKey foreign key to match
|
||||
* @param now the current logical time to use when checking for soft deletion of the foreign key
|
||||
* index
|
||||
*/
|
||||
@Nullable
|
||||
public static <E extends EppResource> VKey<E> load(
|
||||
public static <E extends EppResource> Optional<VKey<E>> loadKey(
|
||||
Class<E> clazz, String foreignKey, DateTime now) {
|
||||
return load(clazz, ImmutableList.of(foreignKey), now).get(foreignKey);
|
||||
return Optional.ofNullable(loadKeys(clazz, ImmutableList.of(foreignKey), now).get(foreignKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an {@link EppResource} from the database by foreign key.
|
||||
*
|
||||
* <p>Returns null if no resource with this foreign key was ever created or if the most recently
|
||||
* created resource was deleted before time "now".
|
||||
*/
|
||||
public static <E extends EppResource> Optional<E> loadResource(
|
||||
Class<E> clazz, String foreignKey, DateTime now) {
|
||||
// Note: no need to project to "now" because loadResources already does
|
||||
return Optional.ofNullable(
|
||||
loadResources(clazz, ImmutableList.of(foreignKey), now).get(foreignKey));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,13 +97,28 @@ public final class ForeignKeyUtils {
|
||||
* <p>The returned map will omit any foreign keys for which the {@link EppResource} doesn't exist
|
||||
* or has been soft-deleted.
|
||||
*/
|
||||
public static <E extends EppResource> ImmutableMap<String, VKey<E>> load(
|
||||
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
|
||||
public static <E extends EppResource> ImmutableMap<String, VKey<E>> loadKeys(
|
||||
Class<E> clazz, Collection<String> foreignKeys, DateTime now) {
|
||||
return loadMostRecentResources(clazz, foreignKeys, false).entrySet().stream()
|
||||
.filter(e -> now.isBefore(e.getValue().deletionTime()))
|
||||
.collect(toImmutableMap(Entry::getKey, e -> VKey.create(clazz, e.getValue().repoId())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a map of {@link String} foreign keys to the {@link EppResource} that are active at or
|
||||
* after the specified moment in time.
|
||||
*
|
||||
* <p>The returned map will omit any foreign keys for which the {@link EppResource} doesn't exist
|
||||
* or has been soft-deleted.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <E extends EppResource> ImmutableMap<String, E> loadResources(
|
||||
Class<E> clazz, Collection<String> foreignKeys, DateTime now) {
|
||||
return loadMostRecentResourceObjects(clazz, foreignKeys, false).entrySet().stream()
|
||||
.filter(e -> now.isBefore(e.getValue().getDeletionTime()))
|
||||
.collect(toImmutableMap(Entry::getKey, e -> (E) e.getValue().cloneProjectedAtTime(now)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to load {@link VKey}s to all the most recent {@link EppResource}s for the given
|
||||
* foreign keys, regardless of whether they have been soft-deleted.
|
||||
@@ -124,22 +152,49 @@ public final class ForeignKeyUtils {
|
||||
.collect(
|
||||
toImmutableMap(
|
||||
row -> (String) row[0],
|
||||
row -> MostRecentResource.create((String) row[1], (DateTime) row[2]))));
|
||||
row -> new MostRecentResource((String) row[1], (DateTime) row[2]))));
|
||||
}
|
||||
|
||||
private static final CacheLoader<VKey<? extends EppResource>, Optional<MostRecentResource>>
|
||||
CACHE_LOADER =
|
||||
new CacheLoader<>() {
|
||||
/** Method to load the most recent {@link EppResource}s for the given foreign keys. */
|
||||
private static <E extends EppResource> ImmutableMap<String, E> loadMostRecentResourceObjects(
|
||||
Class<E> clazz, Collection<String> foreignKeys, boolean useReplicaTm) {
|
||||
String fkProperty = RESOURCE_TYPE_TO_FK_PROPERTY.get(clazz);
|
||||
JpaTransactionManager tmToUse = useReplicaTm ? replicaTm() : tm();
|
||||
return tmToUse.reTransact(
|
||||
() ->
|
||||
tmToUse
|
||||
.query(
|
||||
("FROM %entity% WHERE (%fkProperty%, deletionTime) IN (SELECT %fkProperty%, "
|
||||
+ "MAX(deletionTime) FROM %entity% WHERE %fkProperty% IN (:fks) "
|
||||
+ "GROUP BY %fkProperty%)")
|
||||
.replace("%fkProperty%", fkProperty)
|
||||
.replace("%entity%", clazz.getSimpleName()),
|
||||
clazz)
|
||||
.setParameter("fks", foreignKeys)
|
||||
.getResultStream()
|
||||
.collect(toImmutableMap(EppResource::getForeignKey, e -> e)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache loader for loading repo IDs and deletion times for the given foreign keys.
|
||||
*
|
||||
* <p>Note: while this is given a {@link VKey}, one cannot use that key to load directly from the
|
||||
* database. That key is basically used to signify a foreign-key + resource-type pairing.
|
||||
*/
|
||||
private static final CacheLoader<VKey<? extends EppResource>, Optional<MostRecentResource>>
|
||||
REPO_ID_CACHE_LOADER =
|
||||
new CacheLoader<>() {
|
||||
@Override
|
||||
public Optional<MostRecentResource> load(VKey<? extends EppResource> key) {
|
||||
return loadAll(ImmutableSet.of(key)).get(key);
|
||||
String foreignKey = (String) key.getKey();
|
||||
return Optional.ofNullable(
|
||||
loadMostRecentResources(key.getKind(), ImmutableList.of(foreignKey), true)
|
||||
.get(foreignKey));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<
|
||||
? extends VKey<? extends EppResource>, ? extends Optional<MostRecentResource>>
|
||||
loadAll(Set<? extends VKey<? extends EppResource>> keys) {
|
||||
public Map<VKey<? extends EppResource>, ? extends Optional<MostRecentResource>> loadAll(
|
||||
Set<? extends VKey<? extends EppResource>> keys) {
|
||||
if (keys.isEmpty()) {
|
||||
return ImmutableMap.of();
|
||||
}
|
||||
@@ -149,7 +204,7 @@ public final class ForeignKeyUtils {
|
||||
ImmutableList<String> foreignKeys =
|
||||
keys.stream().map(key -> (String) key.getKey()).collect(toImmutableList());
|
||||
ImmutableMap<String, MostRecentResource> existingKeys =
|
||||
ForeignKeyUtils.loadMostRecentResources(clazz, foreignKeys, true);
|
||||
loadMostRecentResources(clazz, foreignKeys, true);
|
||||
// The above map only contains keys that exist in the database, so we re-add the
|
||||
// missing ones with Optional.empty() values for caching.
|
||||
return Maps.asMap(
|
||||
@@ -159,11 +214,10 @@ public final class ForeignKeyUtils {
|
||||
};
|
||||
|
||||
/**
|
||||
* A limited size, limited time cache for foreign-keyed entities.
|
||||
* A limited size, limited time cache for mapping foreign keys to repo IDs.
|
||||
*
|
||||
* <p>This is only used to cache foreign-keyed entities for the purposes of checking whether they
|
||||
* exist (and if so, what entity they point to) during a few domain flows. Any other operations on
|
||||
* foreign keys should not use this cache.
|
||||
* <p>This is only used to cache foreign-keyed entity keys for the purposes of checking whether
|
||||
* they exist (and if so, what entity they point to).
|
||||
*
|
||||
* <p>Note that here the key of the {@link LoadingCache} is of type {@code VKey<? extends
|
||||
* EppResource>}, but they are not legal {@link VKey}s to {@link EppResource}s, whose keys are the
|
||||
@@ -177,22 +231,26 @@ public final class ForeignKeyUtils {
|
||||
* our system that actually exist. So we cache the fact that they *don't* exist by using
|
||||
* Optional.empty(), then several layers up the EPP command will fail with an error message like
|
||||
* "The contact with given IDs (blah) don't exist."
|
||||
*
|
||||
* <p>If one wishes to load the entity itself via cache, use the {@link
|
||||
* #foreignKeyToResourceCache} instead, as that loads the entity instead. This cache is used for
|
||||
* situations where the repo ID, or the existence of the repo ID, is sufficient.
|
||||
*/
|
||||
@NonFinalForTesting
|
||||
private static LoadingCache<VKey<? extends EppResource>, Optional<MostRecentResource>>
|
||||
foreignKeyCache = createForeignKeyMapCache(getEppResourceCachingDuration());
|
||||
foreignKeyToRepoIdCache = createForeignKeyToRepoIdCache(getEppResourceCachingDuration());
|
||||
|
||||
private static LoadingCache<VKey<? extends EppResource>, Optional<MostRecentResource>>
|
||||
createForeignKeyMapCache(Duration expiry) {
|
||||
createForeignKeyToRepoIdCache(Duration expiry) {
|
||||
return CacheUtils.newCacheBuilder(expiry)
|
||||
.maximumSize(getEppResourceMaxCachedEntries())
|
||||
.build(CACHE_LOADER);
|
||||
.build(REPO_ID_CACHE_LOADER);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void setCacheForTest(Optional<Duration> expiry) {
|
||||
public static void setRepoIdCacheForTest(Optional<Duration> expiry) {
|
||||
Duration effectiveExpiry = expiry.orElse(getEppResourceCachingDuration());
|
||||
foreignKeyCache = createForeignKeyMapCache(effectiveExpiry);
|
||||
foreignKeyToRepoIdCache = createForeignKeyToRepoIdCache(effectiveExpiry);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,26 +263,12 @@ public final class ForeignKeyUtils {
|
||||
* <p>Don't use the cached version of this method unless you really need it for performance
|
||||
* reasons, and are OK with the trade-offs in loss of transactional consistency.
|
||||
*/
|
||||
public static <E extends EppResource> ImmutableMap<String, VKey<E>> loadByCacheIfEnabled(
|
||||
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
|
||||
public static <E extends EppResource> ImmutableMap<String, VKey<E>> loadKeysByCacheIfEnabled(
|
||||
Class<E> clazz, Collection<String> foreignKeys, DateTime now) {
|
||||
if (!RegistryConfig.isEppResourceCachingEnabled()) {
|
||||
return load(clazz, foreignKeys, now);
|
||||
return loadKeys(clazz, foreignKeys, now);
|
||||
}
|
||||
return loadByCache(clazz, foreignKeys, now);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a list of {@link VKey} to {@link EppResource} instances by class and foreign key strings
|
||||
* that are active at or after the specified moment in time, using the cache.
|
||||
*
|
||||
* <p>The returned map will omit any keys for which the {@link EppResource} doesn't exist or has
|
||||
* been soft-deleted.
|
||||
*
|
||||
* <p>This method is reserved for use cases that can tolerate slightly stale data.
|
||||
*/
|
||||
public static <E extends EppResource> ImmutableMap<String, VKey<E>> loadByCache(
|
||||
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
|
||||
return foreignKeyCache
|
||||
return foreignKeyToRepoIdCache
|
||||
.getAll(foreignKeys.stream().map(fk -> VKey.create(clazz, fk)).collect(toImmutableList()))
|
||||
.entrySet()
|
||||
.stream()
|
||||
@@ -235,10 +279,127 @@ public final class ForeignKeyUtils {
|
||||
e -> VKey.create(clazz, e.getValue().get().repoId())));
|
||||
}
|
||||
|
||||
public record MostRecentResource(String repoId, DateTime deletionTime) {
|
||||
/** Loads an optional {@link VKey} to an {@link EppResource} using the cache. */
|
||||
public static <E extends EppResource> Optional<VKey<E>> loadKeyByCache(
|
||||
Class<E> clazz, String foreignKey, DateTime now) {
|
||||
return foreignKeyToRepoIdCache
|
||||
.get(VKey.create(clazz, foreignKey))
|
||||
.filter(mrr -> now.isBefore(mrr.deletionTime()))
|
||||
.map(mrr -> VKey.create(clazz, mrr.repoId()));
|
||||
}
|
||||
|
||||
static MostRecentResource create(String repoId, DateTime deletionTime) {
|
||||
return new MostRecentResource(repoId, deletionTime);
|
||||
}
|
||||
/**
|
||||
* Cache loader for loading {@link EppResource}s for the given foreign keys.
|
||||
*
|
||||
* <p>Note: while this is given a {@link VKey}, one cannot use that key to load directly from the
|
||||
* database. That key is basically used to signify a foreign-key + resource-type pairing.
|
||||
*/
|
||||
private static final CacheLoader<VKey<? extends EppResource>, Optional<? extends EppResource>>
|
||||
RESOURCE_CACHE_LOADER =
|
||||
new CacheLoader<>() {
|
||||
@Override
|
||||
public Optional<? extends EppResource> load(VKey<? extends EppResource> key) {
|
||||
String foreignKey = (String) key.getKey();
|
||||
return Optional.ofNullable(
|
||||
loadMostRecentResourceObjects(key.getKind(), ImmutableList.of(foreignKey), true)
|
||||
.get(foreignKey));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<VKey<? extends EppResource>, Optional<? extends EppResource>> loadAll(
|
||||
Set<? extends VKey<? extends EppResource>> keys) {
|
||||
if (keys.isEmpty()) {
|
||||
return ImmutableMap.of();
|
||||
}
|
||||
// It is safe to use the resource type of first element because when this function is
|
||||
// called, it is always passed with a list of VKeys with the same type.
|
||||
Class<? extends EppResource> clazz = keys.iterator().next().getKind();
|
||||
ImmutableList<String> foreignKeys =
|
||||
keys.stream().map(key -> (String) key.getKey()).collect(toImmutableList());
|
||||
ImmutableMap<String, ? extends EppResource> existingResources =
|
||||
loadMostRecentResourceObjects(clazz, foreignKeys, true);
|
||||
// The above map only contains resources that exist in the database, so we re-add the
|
||||
// missing ones with Optional.empty() values for caching.
|
||||
return Maps.asMap(
|
||||
ImmutableSet.copyOf(keys),
|
||||
key -> Optional.ofNullable(existingResources.get((String) key.getKey())));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An additional limited size, limited time cache for foreign-keyed entities.
|
||||
*
|
||||
* <p>Note that here the key of the {@link LoadingCache} is of type {@code VKey<? extends
|
||||
* EppResource>}, but they are not legal {@link VKey}s to {@link EppResource}s, whose keys are the
|
||||
* SQL primary keys, i.e., the {@code repoId}s. Instead, their keys are the foreign keys used to
|
||||
* query the database. We use {@link VKey} here because it is a convenient composite class that
|
||||
* contains both the resource type and the foreign key, which are needed to for the query and
|
||||
* caching.
|
||||
*
|
||||
* <p>Also note that the value type of this cache is {@link Optional} because the foreign keys in
|
||||
* question are coming from external commands, and thus don't necessarily represent entities in
|
||||
* our system that actually exist. So we cache the fact that they *don't* exist by using
|
||||
* Optional.empty(), then several layers up the EPP command will fail with an error message like
|
||||
* "The contact with given IDs (blah) don't exist."
|
||||
*
|
||||
* <p>This cache bypasses the foreign-key-to-repo-ID lookup and maps directly from the foreign key
|
||||
* to the entity itself (at least, at this point in time).
|
||||
*/
|
||||
private static LoadingCache<VKey<? extends EppResource>, Optional<? extends EppResource>>
|
||||
foreignKeyToResourceCache = createForeignKeyToResourceCache(getEppResourceCachingDuration());
|
||||
|
||||
private static LoadingCache<VKey<? extends EppResource>, Optional<? extends EppResource>>
|
||||
createForeignKeyToResourceCache(Duration expiry) {
|
||||
return CacheUtils.newCacheBuilder(expiry)
|
||||
.maximumSize(getEppResourceMaxCachedEntries())
|
||||
.build(RESOURCE_CACHE_LOADER);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void setResourceCacheForTest(Optional<Duration> expiry) {
|
||||
Duration effectiveExpiry = expiry.orElse(getEppResourceCachingDuration());
|
||||
foreignKeyToResourceCache = createForeignKeyToResourceCache(effectiveExpiry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the last created version of an {@link EppResource} from the database by foreign key,
|
||||
* using a cache, if caching is enabled in config settings.
|
||||
*
|
||||
* <p>Returns null if no resource with this foreign key was ever created, or if the most recently
|
||||
* created resource was deleted before time "now".
|
||||
*
|
||||
* <p>Loading an {@link EppResource} by itself is not sufficient to know its current state since
|
||||
* it may have various expirable conditions and status values that might implicitly change its
|
||||
* state as time progresses even if it has not been updated in the database. Rather, the resource
|
||||
* must be combined with a timestamp to view its current state. We use a global last updated
|
||||
* timestamp to guarantee monotonically increasing write times, and forward our projected time to
|
||||
* the greater of this timestamp or "now". This guarantees that we're not projecting into the
|
||||
* past.
|
||||
*
|
||||
* <p>Do not call this cached version for anything that needs transactional consistency. It should
|
||||
* only be used when it's OK if the data is potentially being out of date, e.g. RDAP.
|
||||
*/
|
||||
public static <E extends EppResource> Optional<E> loadResourceByCacheIfEnabled(
|
||||
Class<E> clazz, String foreignKey, DateTime now) {
|
||||
return RegistryConfig.isEppResourceCachingEnabled()
|
||||
? loadResourceByCache(clazz, foreignKey, now)
|
||||
: loadResource(clazz, foreignKey, now);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the last created version of an {@link EppResource} from the replica database by foreign
|
||||
* key, using a cache.
|
||||
*
|
||||
* <p>This method ignores the config setting for caching, and is reserved for use cases that can
|
||||
* tolerate slightly stale data.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <E extends EppResource> Optional<E> loadResourceByCache(
|
||||
Class<E> clazz, String foreignKey, DateTime now) {
|
||||
return (Optional<E>)
|
||||
foreignKeyToResourceCache
|
||||
.get(VKey.create(clazz, foreignKey))
|
||||
.filter(e -> now.isBefore(e.getDeletionTime()))
|
||||
.map(e -> e.cloneProjectedAtTime(now));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// 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.model;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* A delegate getter method to be used when getting the value of an {@link ImmutableObject} field.
|
||||
*
|
||||
* <p>This is useful because Hibernate has limitations on what kinds of types can be used to
|
||||
* represent a field value, the most relevant being that it must be mutable. Since we use Guava's
|
||||
* ImmutableCollections widely, this means that a frequent pattern is to e.g. have a field be
|
||||
* declared as a Set (with a HashSet implementation), but then implement a getter method for that
|
||||
* field that returns the desired ImmutableSet or ImmutableSortedSet. For purposes where it matters
|
||||
* that the field be represented using the appropriate type, such as for outputting in sorted order
|
||||
* via toString, then declare a getter delegate as follows:
|
||||
*
|
||||
* <pre>{@code
|
||||
* @GetterDelegate(methodName = "getAllowedTlds")
|
||||
* Set<String> allowedTlds;
|
||||
*
|
||||
* public ImmutableSortedSet<String> getAllowedTlds() {
|
||||
* return nullToEmptyImmutableSortedCopy(allowedTlds);
|
||||
* }
|
||||
* }</pre>
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@Target(FIELD)
|
||||
public @interface GetterDelegate {
|
||||
String methodName();
|
||||
}
|
||||
@@ -76,11 +76,20 @@ public class ModelUtils {
|
||||
return ALL_FIELDS_CACHE.get(clazz);
|
||||
}
|
||||
|
||||
/** Retrieves a field value via reflection. */
|
||||
/**
|
||||
* Retrieves a field value via reflection, using the field's {@link GetterDelegate} if present.
|
||||
*/
|
||||
static Object getFieldValue(Object instance, Field field) {
|
||||
try {
|
||||
return field.get(instance);
|
||||
} catch (IllegalAccessException e) {
|
||||
if (field.isAnnotationPresent(GetterDelegate.class)) {
|
||||
return instance
|
||||
.getClass()
|
||||
.getMethod(field.getAnnotation(GetterDelegate.class).methodName())
|
||||
.invoke(instance);
|
||||
} else {
|
||||
return field.get(instance);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,7 +442,7 @@ public class DomainCommand {
|
||||
final Set<String> foreignKeys, final Class<T> clazz, final DateTime now)
|
||||
throws InvalidReferencesException {
|
||||
ImmutableMap<String, VKey<T>> fks =
|
||||
ForeignKeyUtils.loadByCacheIfEnabled(clazz, foreignKeys, now);
|
||||
ForeignKeyUtils.loadKeysByCacheIfEnabled(clazz, foreignKeys, now);
|
||||
if (!fks.keySet().equals(foreignKeys)) {
|
||||
throw new InvalidReferencesException(
|
||||
clazz, ImmutableSet.copyOf(difference(foreignKeys, fks.keySet())));
|
||||
|
||||
@@ -64,6 +64,7 @@ public class Fee extends BaseFee {
|
||||
|
||||
public static final ImmutableSet<String> FEE_EXTENSION_URIS =
|
||||
ImmutableSet.of(
|
||||
ServiceExtension.FEE_1_00.getUri(),
|
||||
ServiceExtension.FEE_0_12.getUri(),
|
||||
ServiceExtension.FEE_0_11.getUri(),
|
||||
ServiceExtension.FEE_0_6.getUri());
|
||||
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
// 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.model.domain.feestdv1;
|
||||
|
||||
import com.google.common.base.Ascii;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.domain.fee.FeeCheckCommandExtensionItem;
|
||||
import jakarta.xml.bind.annotation.XmlAttribute;
|
||||
import jakarta.xml.bind.annotation.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* An individual price check item in version 1.0 of the fee extension on domain check commands.
|
||||
* Items look like:
|
||||
*
|
||||
* <pre>{@code
|
||||
* <fee:command name="renew" phase="sunrise" subphase="hello">
|
||||
* <fee:period unit="y">1</fee:period>
|
||||
* <fee:class>premium</fee:class>
|
||||
* <fee:date>2017-05-17T13:22:21.0Z</fee:date>
|
||||
* </fee:command>
|
||||
* }</pre>
|
||||
*/
|
||||
@XmlType(propOrder = {"period", "feeClass", "feeDate"})
|
||||
public class FeeCheckCommandExtensionItemStdV1 extends FeeCheckCommandExtensionItem {
|
||||
|
||||
/** The default validity period (if not specified) is 1 year for all operations. */
|
||||
static final Period DEFAULT_PERIOD = Period.create(1, Period.Unit.YEARS);
|
||||
|
||||
@XmlAttribute(name = "name")
|
||||
String commandName;
|
||||
|
||||
@XmlAttribute String phase;
|
||||
|
||||
@XmlAttribute String subphase;
|
||||
|
||||
@XmlElement(name = "class")
|
||||
String feeClass;
|
||||
|
||||
@XmlElement(name = "date")
|
||||
DateTime feeDate;
|
||||
|
||||
/** Version 1.0 does not support domain name or currency in fee extension items. */
|
||||
@Override
|
||||
public boolean isDomainNameSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDomainName() {
|
||||
throw new UnsupportedOperationException("Domain not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CurrencyUnit getCurrency() {
|
||||
return null; // This version of the fee extension doesn't specify currency per-item.
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUnparsedCommandName() {
|
||||
return commandName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandName getCommandName() {
|
||||
// Require the xml string to be lowercase.
|
||||
if (commandName != null && commandName.toLowerCase(Locale.ENGLISH).equals(commandName)) {
|
||||
try {
|
||||
return CommandName.valueOf(Ascii.toUpperCase(commandName));
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Swallow this and return UNKNOWN below because there's no matching CommandName.
|
||||
}
|
||||
}
|
||||
return CommandName.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPhase() {
|
||||
return phase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSubphase() {
|
||||
return subphase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeeCheckResponseExtensionItemStdV1.Builder createResponseBuilder() {
|
||||
return new FeeCheckResponseExtensionItemStdV1.Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<DateTime> getEffectiveDate() {
|
||||
return Optional.ofNullable(feeDate);
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
// 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.model.domain.feestdv1;
|
||||
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.domain.fee.FeeCheckCommandExtension;
|
||||
import google.registry.model.domain.fee.FeeCheckResponseExtensionItem;
|
||||
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;
|
||||
|
||||
/** Version 1.0 of the fee extension that may be present on domain check commands. */
|
||||
@XmlRootElement(name = "check")
|
||||
@XmlType(propOrder = {"currency", "items"})
|
||||
public class FeeCheckCommandExtensionStdV1 extends ImmutableObject
|
||||
implements FeeCheckCommandExtension<
|
||||
FeeCheckCommandExtensionItemStdV1, FeeCheckResponseExtensionStdV1> {
|
||||
|
||||
CurrencyUnit currency;
|
||||
|
||||
@Override
|
||||
public CurrencyUnit getCurrency() {
|
||||
return currency;
|
||||
}
|
||||
|
||||
@XmlElement(name = "command")
|
||||
List<FeeCheckCommandExtensionItemStdV1> items;
|
||||
|
||||
@Override
|
||||
public ImmutableList<FeeCheckCommandExtensionItemStdV1> getItems() {
|
||||
return nullToEmptyImmutableCopy(items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeeCheckResponseExtensionStdV1 createResponse(
|
||||
ImmutableList<? extends FeeCheckResponseExtensionItem> items) {
|
||||
ImmutableList.Builder<FeeCheckResponseExtensionItemStdV1> builder =
|
||||
new ImmutableList.Builder<>();
|
||||
for (FeeCheckResponseExtensionItem item : items) {
|
||||
if (item instanceof FeeCheckResponseExtensionItemStdV1) {
|
||||
builder.add((FeeCheckResponseExtensionItemStdV1) item);
|
||||
}
|
||||
}
|
||||
return FeeCheckResponseExtensionStdV1.create(currency, builder.build());
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
// 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.model.domain.feestdv1;
|
||||
|
||||
import static google.registry.util.CollectionUtils.forceEmptyToNull;
|
||||
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.domain.fee.Fee;
|
||||
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
|
||||
import jakarta.xml.bind.annotation.XmlAttribute;
|
||||
import jakarta.xml.bind.annotation.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
import java.util.List;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** The version 1.0 response command entity for a domain check on a single resource. */
|
||||
@XmlType(propOrder = {"period", "fee", "feeClass", "effectiveDate", "notAfterDate"})
|
||||
public class FeeCheckResponseExtensionItemCommandStdV1 extends ImmutableObject {
|
||||
|
||||
/** The command that was checked. */
|
||||
@XmlAttribute(name = "name")
|
||||
String commandName;
|
||||
|
||||
/** The phase that was checked. */
|
||||
@XmlAttribute String phase;
|
||||
|
||||
/** The subphase that was checked. */
|
||||
@XmlAttribute String subphase;
|
||||
|
||||
/** The period that was checked. */
|
||||
Period period;
|
||||
|
||||
/**
|
||||
* The magnitude of the fee, in the specified units, with an optional description.
|
||||
*
|
||||
* <p>This is a list because a single operation can involve multiple fees.
|
||||
*/
|
||||
List<Fee> fee;
|
||||
|
||||
/**
|
||||
* The type of the fee.
|
||||
*
|
||||
* <p>We will use "premium" for fees on premium names, and omit the field otherwise.
|
||||
*/
|
||||
@XmlElement(name = "class")
|
||||
String feeClass;
|
||||
|
||||
/** The effective date that the check is to be performed on (if specified in the query). */
|
||||
@XmlElement(name = "date")
|
||||
DateTime effectiveDate;
|
||||
|
||||
/** The date after which the quoted fee is no longer valid (if applicable). */
|
||||
@XmlElement(name = "notAfter")
|
||||
DateTime notAfterDate;
|
||||
|
||||
public String getFeeClass() {
|
||||
return feeClass;
|
||||
}
|
||||
|
||||
/** Builder for {@link FeeCheckResponseExtensionItemCommandStdV1}. */
|
||||
public static class Builder extends Buildable.Builder<FeeCheckResponseExtensionItemCommandStdV1> {
|
||||
|
||||
public Builder setCommandName(CommandName commandName) {
|
||||
getInstance().commandName = Ascii.toLowerCase(commandName.name());
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPhase(String phase) {
|
||||
getInstance().phase = phase;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSubphase(String subphase) {
|
||||
getInstance().subphase = subphase;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPeriod(Period period) {
|
||||
getInstance().period = period;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setEffectiveDate(DateTime effectiveDate) {
|
||||
getInstance().effectiveDate = effectiveDate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setNotAfterDate(DateTime notAfterDate) {
|
||||
getInstance().notAfterDate = notAfterDate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setFee(List<Fee> fees) {
|
||||
getInstance().fee = forceEmptyToNull(ImmutableList.copyOf(fees));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setClass(String feeClass) {
|
||||
getInstance().feeClass = feeClass;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
// 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.model.domain.feestdv1;
|
||||
|
||||
import static google.registry.util.CollectionUtils.forceEmptyToNull;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.model.domain.DomainObjectSpec;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.domain.fee.Fee;
|
||||
import google.registry.model.domain.fee.FeeCheckResponseExtensionItem;
|
||||
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** The version 1.0 response for a domain check on a single resource. */
|
||||
@XmlType(propOrder = {"object", "command"})
|
||||
public class FeeCheckResponseExtensionItemStdV1 extends FeeCheckResponseExtensionItem {
|
||||
|
||||
/** The domain that was checked. */
|
||||
DomainObjectSpec object;
|
||||
|
||||
/** The command that was checked. */
|
||||
FeeCheckResponseExtensionItemCommandStdV1 command;
|
||||
|
||||
/**
|
||||
* This method is overridden and not annotated for JAXB because this version of the extension
|
||||
* doesn't support "period".
|
||||
*/
|
||||
@Override
|
||||
public Period getPeriod() {
|
||||
return super.getPeriod();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is overridden and not annotated for JAXB because this version of the extension
|
||||
* doesn't support "fee".
|
||||
*/
|
||||
@Override
|
||||
public ImmutableList<Fee> getFees() {
|
||||
return super.getFees();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is not annotated for JAXB because this version of the extension doesn't support
|
||||
* "feeClass" and because the data comes off of the command object rather than a field.
|
||||
*/
|
||||
@Override
|
||||
public String getFeeClass() {
|
||||
return command.getFeeClass();
|
||||
}
|
||||
|
||||
/** Builder for {@link FeeCheckResponseExtensionItemStdV1}. */
|
||||
public static class Builder
|
||||
extends FeeCheckResponseExtensionItem.Builder<FeeCheckResponseExtensionItemStdV1> {
|
||||
|
||||
final FeeCheckResponseExtensionItemCommandStdV1.Builder commandBuilder =
|
||||
new FeeCheckResponseExtensionItemCommandStdV1.Builder();
|
||||
|
||||
@Override
|
||||
public Builder setCommand(CommandName commandName, String phase, String subphase) {
|
||||
commandBuilder.setCommandName(commandName);
|
||||
commandBuilder.setPhase(phase);
|
||||
commandBuilder.setSubphase(subphase);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setPeriod(Period period) {
|
||||
commandBuilder.setPeriod(period);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setFees(ImmutableList<Fee> fees) {
|
||||
commandBuilder.setFee(forceEmptyToNull(ImmutableList.copyOf(fees)));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setClass(String feeClass) {
|
||||
commandBuilder.setClass(feeClass);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setDomainNameIfSupported(String name) {
|
||||
getInstance().object = new DomainObjectSpec(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeeCheckResponseExtensionItemStdV1 build() {
|
||||
getInstance().command = commandBuilder.build();
|
||||
return super.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setEffectiveDateIfSupported(DateTime effectiveDate) {
|
||||
commandBuilder.setEffectiveDate(effectiveDate);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setNotAfterDateIfSupported(DateTime notAfterDate) {
|
||||
commandBuilder.setNotAfterDate(notAfterDate);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// 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.model.domain.feestdv1;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.domain.fee.FeeCheckResponseExtension;
|
||||
import jakarta.xml.bind.annotation.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlRootElement;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
|
||||
/**
|
||||
* An XML data object that represents version 1.0 of the fee extension that may be present on the
|
||||
* response to EPP domain check commands.
|
||||
*/
|
||||
@XmlRootElement(name = "chkData")
|
||||
@XmlType(propOrder = {"currency", "items"})
|
||||
public class FeeCheckResponseExtensionStdV1 extends ImmutableObject
|
||||
implements FeeCheckResponseExtension<FeeCheckResponseExtensionItemStdV1> {
|
||||
|
||||
CurrencyUnit currency;
|
||||
|
||||
/** Check responses. */
|
||||
@XmlElement(name = "cd")
|
||||
ImmutableList<FeeCheckResponseExtensionItemStdV1> items;
|
||||
|
||||
@Override
|
||||
public void setCurrencyIfSupported(CurrencyUnit currency) {
|
||||
this.currency = currency;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@Override
|
||||
public ImmutableList<FeeCheckResponseExtensionItemStdV1> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
static FeeCheckResponseExtensionStdV1 create(
|
||||
CurrencyUnit currency, ImmutableList<FeeCheckResponseExtensionItemStdV1> items) {
|
||||
FeeCheckResponseExtensionStdV1 instance = new FeeCheckResponseExtensionStdV1();
|
||||
instance.currency = currency;
|
||||
instance.items = items;
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
// 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.model.domain.feestdv1;
|
||||
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.model.domain.fee.Credit;
|
||||
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;
|
||||
|
||||
/** A fee extension that may be present on domain create commands. */
|
||||
@XmlRootElement(name = "create")
|
||||
@XmlType(propOrder = {"currency", "fees", "credits"})
|
||||
public class FeeCreateCommandExtensionStdV1 extends FeeCreateCommandExtension {
|
||||
|
||||
@XmlElement(name = "credit")
|
||||
List<Credit> credits;
|
||||
|
||||
@Override
|
||||
public ImmutableList<Credit> getCredits() {
|
||||
return nullToEmptyImmutableCopy(credits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeeTransformResponseExtension.Builder createResponseBuilder() {
|
||||
return new FeeTransformResponseExtension.Builder(new FeeCreateResponseExtensionStdV1());
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// 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.model.domain.feestdv1;
|
||||
|
||||
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 create commands.
|
||||
*/
|
||||
@XmlRootElement(name = "creData")
|
||||
@XmlType(propOrder = {"currency", "fees", "credits"})
|
||||
public class FeeCreateResponseExtensionStdV1 extends FeeTransformResponseExtension {}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// 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.model.domain.feestdv1;
|
||||
|
||||
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 create commands.
|
||||
*/
|
||||
@XmlRootElement(name = "delData")
|
||||
@XmlType(propOrder = {"currency", "fees", "credits"})
|
||||
public class FeeDeleteResponseExtensionStdV1 extends FeeTransformResponseExtension {
|
||||
|
||||
/** Builder for {@link FeeDeleteResponseExtensionStdV1}. */
|
||||
public static class Builder extends FeeTransformResponseExtension.Builder {
|
||||
public Builder() {
|
||||
super(new FeeDeleteResponseExtensionStdV1());
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
// 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.model.domain.feestdv1;
|
||||
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
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.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlRootElement;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
import java.util.List;
|
||||
|
||||
/** A fee extension that may be present on domain renew commands. */
|
||||
@XmlRootElement(name = "renew")
|
||||
@XmlType(propOrder = {"currency", "fees", "credits"})
|
||||
public class FeeRenewCommandExtensionStdV1 extends FeeRenewCommandExtension {
|
||||
|
||||
@XmlElement(name = "credit")
|
||||
List<Credit> credits;
|
||||
|
||||
@Override
|
||||
public ImmutableList<Credit> getCredits() {
|
||||
return nullToEmptyImmutableCopy(credits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeeTransformResponseExtension.Builder createResponseBuilder() {
|
||||
return new FeeTransformResponseExtension.Builder(new FeeRenewResponseExtensionStdV1());
|
||||
}
|
||||
}
|
||||
+12
-9
@@ -1,4 +1,4 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -12,13 +12,16 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.model.domain.feestdv1;
|
||||
|
||||
import google.registry.model.domain.fee.FeeTransformResponseExtension;
|
||||
import jakarta.xml.bind.annotation.XmlRootElement;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
|
||||
/**
|
||||
* @fileoverview Test existing solely to run the :check BUILD rule.
|
||||
* An XML data object that represents a fee extension that may be present on the response to EPP
|
||||
* domain renew commands.
|
||||
*/
|
||||
|
||||
goog.setTestOnly();
|
||||
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
|
||||
function testNothing() {}
|
||||
@XmlRootElement(name = "renData")
|
||||
@XmlType(propOrder = {"currency", "fees", "credits"})
|
||||
public class FeeRenewResponseExtensionStdV1 extends FeeTransformResponseExtension {}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
// 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.model.domain.feestdv1;
|
||||
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
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.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlRootElement;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
import java.util.List;
|
||||
|
||||
/** A fee extension that may be present on domain transfer requests. */
|
||||
@XmlRootElement(name = "transfer")
|
||||
@XmlType(propOrder = {"currency", "fees", "credits"})
|
||||
public class FeeTransferCommandExtensionStdV1 extends FeeTransferCommandExtension {
|
||||
|
||||
@XmlElement(name = "credit")
|
||||
List<Credit> credits;
|
||||
|
||||
@Override
|
||||
public ImmutableList<Credit> getCredits() {
|
||||
return nullToEmptyImmutableCopy(credits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeeTransformResponseExtension.Builder createResponseBuilder() {
|
||||
return new FeeTransformResponseExtension.Builder(new FeeTransferResponseExtensionStdV1());
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// 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.model.domain.feestdv1;
|
||||
|
||||
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.
|
||||
*/
|
||||
@XmlRootElement(name = "trnData")
|
||||
@XmlType(propOrder = {"currency", "fees", "credits"})
|
||||
public class FeeTransferResponseExtensionStdV1 extends FeeTransformResponseExtension {}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
// 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.model.domain.feestdv1;
|
||||
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
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.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlRootElement;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
import java.util.List;
|
||||
|
||||
/** A fee extension that may be present on domain update commands. */
|
||||
@XmlRootElement(name = "update")
|
||||
@XmlType(propOrder = {"currency", "fees", "credits"})
|
||||
public class FeeUpdateCommandExtensionStdV1 extends FeeUpdateCommandExtension {
|
||||
|
||||
@XmlElement(name = "credit")
|
||||
List<Credit> credits;
|
||||
|
||||
@Override
|
||||
public ImmutableList<Credit> getCredits() {
|
||||
return nullToEmptyImmutableCopy(credits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeeTransformResponseExtension.Builder createResponseBuilder() {
|
||||
return new FeeTransformResponseExtension.Builder(new FeeUpdateResponseExtensionStdV1());
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// 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.model.domain.feestdv1;
|
||||
|
||||
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 update commands.
|
||||
*/
|
||||
@XmlRootElement(name = "updData")
|
||||
@XmlType(propOrder = {"currency", "fees", "credits"})
|
||||
public class FeeUpdateResponseExtensionStdV1 extends FeeTransformResponseExtension {}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
@XmlSchema(
|
||||
namespace = "urn:ietf:params:xml:ns:epp:fee-1.0",
|
||||
xmlns = @XmlNs(prefix = "fee_1_00", namespaceURI = "urn:ietf:params:xml:ns:epp:fee-1.0"),
|
||||
elementFormDefault = XmlNsForm.QUALIFIED)
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlJavaTypeAdapters({
|
||||
@XmlJavaTypeAdapter(CurrencyUnitAdapter.class),
|
||||
@XmlJavaTypeAdapter(UtcDateTimeAdapter.class)
|
||||
})
|
||||
package google.registry.model.domain.feestdv1;
|
||||
|
||||
import google.registry.model.adapters.CurrencyUnitAdapter;
|
||||
import google.registry.xml.UtcDateTimeAdapter;
|
||||
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;
|
||||
@@ -43,6 +43,7 @@ public class EppXmlTransformer {
|
||||
"fee06.xsd",
|
||||
"fee11.xsd",
|
||||
"fee12.xsd",
|
||||
"fee-std-v1.xsd",
|
||||
"metadata.xsd",
|
||||
"mark.xsd",
|
||||
"dsig.xsd",
|
||||
|
||||
@@ -25,6 +25,8 @@ import google.registry.model.domain.fee11.FeeCheckCommandExtensionV11;
|
||||
import google.registry.model.domain.fee11.FeeCheckResponseExtensionV11;
|
||||
import google.registry.model.domain.fee12.FeeCheckCommandExtensionV12;
|
||||
import google.registry.model.domain.fee12.FeeCheckResponseExtensionV12;
|
||||
import google.registry.model.domain.feestdv1.FeeCheckCommandExtensionStdV1;
|
||||
import google.registry.model.domain.feestdv1.FeeCheckResponseExtensionStdV1;
|
||||
import google.registry.model.domain.launch.LaunchCreateExtension;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.domain.rgp.RgpUpdateExtension;
|
||||
@@ -54,6 +56,7 @@ public class ProtocolDefinition {
|
||||
FEE_0_6(FeeCheckCommandExtensionV06.class, FeeCheckResponseExtensionV06.class, true),
|
||||
FEE_0_11(FeeCheckCommandExtensionV11.class, FeeCheckResponseExtensionV11.class, true),
|
||||
FEE_0_12(FeeCheckCommandExtensionV12.class, FeeCheckResponseExtensionV12.class, true),
|
||||
FEE_1_00(FeeCheckCommandExtensionStdV1.class, FeeCheckResponseExtensionStdV1.class, true),
|
||||
METADATA_1_0(MetadataExtension.class, null, false);
|
||||
|
||||
private final Class<? extends CommandExtension> commandExtensionClass;
|
||||
|
||||
@@ -40,6 +40,11 @@ import google.registry.model.domain.fee12.FeeCreateCommandExtensionV12;
|
||||
import google.registry.model.domain.fee12.FeeRenewCommandExtensionV12;
|
||||
import google.registry.model.domain.fee12.FeeTransferCommandExtensionV12;
|
||||
import google.registry.model.domain.fee12.FeeUpdateCommandExtensionV12;
|
||||
import google.registry.model.domain.feestdv1.FeeCheckCommandExtensionStdV1;
|
||||
import google.registry.model.domain.feestdv1.FeeCreateCommandExtensionStdV1;
|
||||
import google.registry.model.domain.feestdv1.FeeRenewCommandExtensionStdV1;
|
||||
import google.registry.model.domain.feestdv1.FeeTransferCommandExtensionStdV1;
|
||||
import google.registry.model.domain.feestdv1.FeeUpdateCommandExtensionStdV1;
|
||||
import google.registry.model.domain.launch.LaunchCheckExtension;
|
||||
import google.registry.model.domain.launch.LaunchCreateExtension;
|
||||
import google.registry.model.domain.launch.LaunchDeleteExtension;
|
||||
@@ -348,6 +353,13 @@ public class EppInput extends ImmutableObject {
|
||||
@XmlElementRef(type = FeeTransferCommandExtensionV12.class),
|
||||
@XmlElementRef(type = FeeUpdateCommandExtensionV12.class),
|
||||
|
||||
// Fee extension standard version 1.0 (RFC 8748)
|
||||
@XmlElementRef(type = FeeCheckCommandExtensionStdV1.class),
|
||||
@XmlElementRef(type = FeeCreateCommandExtensionStdV1.class),
|
||||
@XmlElementRef(type = FeeRenewCommandExtensionStdV1.class),
|
||||
@XmlElementRef(type = FeeTransferCommandExtensionStdV1.class),
|
||||
@XmlElementRef(type = FeeUpdateCommandExtensionStdV1.class),
|
||||
|
||||
// Launch phase extensions
|
||||
@XmlElementRef(type = LaunchCheckExtension.class),
|
||||
@XmlElementRef(type = LaunchCreateExtension.class),
|
||||
|
||||
@@ -43,6 +43,12 @@ import google.registry.model.domain.fee12.FeeDeleteResponseExtensionV12;
|
||||
import google.registry.model.domain.fee12.FeeRenewResponseExtensionV12;
|
||||
import google.registry.model.domain.fee12.FeeTransferResponseExtensionV12;
|
||||
import google.registry.model.domain.fee12.FeeUpdateResponseExtensionV12;
|
||||
import google.registry.model.domain.feestdv1.FeeCheckResponseExtensionStdV1;
|
||||
import google.registry.model.domain.feestdv1.FeeCreateResponseExtensionStdV1;
|
||||
import google.registry.model.domain.feestdv1.FeeDeleteResponseExtensionStdV1;
|
||||
import google.registry.model.domain.feestdv1.FeeRenewResponseExtensionStdV1;
|
||||
import google.registry.model.domain.feestdv1.FeeTransferResponseExtensionStdV1;
|
||||
import google.registry.model.domain.feestdv1.FeeUpdateResponseExtensionStdV1;
|
||||
import google.registry.model.domain.launch.LaunchCheckResponseExtension;
|
||||
import google.registry.model.domain.rgp.RgpInfoExtension;
|
||||
import google.registry.model.domain.secdns.SecDnsInfoExtension;
|
||||
@@ -142,6 +148,12 @@ public class EppResponse extends ImmutableObject implements ResponseOrGreeting {
|
||||
@XmlElementRef(type = FeeRenewResponseExtensionV12.class),
|
||||
@XmlElementRef(type = FeeTransferResponseExtensionV12.class),
|
||||
@XmlElementRef(type = FeeUpdateResponseExtensionV12.class),
|
||||
@XmlElementRef(type = FeeCheckResponseExtensionStdV1.class),
|
||||
@XmlElementRef(type = FeeCreateResponseExtensionStdV1.class),
|
||||
@XmlElementRef(type = FeeDeleteResponseExtensionStdV1.class),
|
||||
@XmlElementRef(type = FeeRenewResponseExtensionStdV1.class),
|
||||
@XmlElementRef(type = FeeTransferResponseExtensionStdV1.class),
|
||||
@XmlElementRef(type = FeeUpdateResponseExtensionStdV1.class),
|
||||
@XmlElementRef(type = LaunchCheckResponseExtension.class),
|
||||
@XmlElementRef(type = RgpInfoExtension.class),
|
||||
@XmlElementRef(type = SecDnsInfoExtension.class)
|
||||
|
||||
@@ -54,6 +54,7 @@ import com.google.gson.annotations.Expose;
|
||||
import com.google.re2j.Pattern;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.CreateAutoTimestamp;
|
||||
import google.registry.model.GetterDelegate;
|
||||
import google.registry.model.JsonMapBuilder;
|
||||
import google.registry.model.Jsonifiable;
|
||||
import google.registry.model.UpdateAutoTimestamp;
|
||||
@@ -191,7 +192,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
immutableEnumSet(State.ACTIVE, State.SUSPENDED);
|
||||
|
||||
/**
|
||||
* The types for which a {@link Registrar} should be included in WHOIS and RDAP output. We exclude
|
||||
* The types for which a {@link Registrar} should be included in RDAP output. We exclude
|
||||
* registrars of type TEST. We considered excluding INTERNAL as well, but decided that
|
||||
* troubleshooting would be easier with INTERNAL registrars visible. Before removing other types
|
||||
* from view, carefully consider the effect on things like prober monitoring and OT&E.
|
||||
@@ -245,12 +246,15 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
State state;
|
||||
|
||||
/** The set of TLDs which this registrar is allowed to access. */
|
||||
@Expose Set<String> allowedTlds;
|
||||
@GetterDelegate(methodName = "getAllowedTlds")
|
||||
@Expose
|
||||
Set<String> allowedTlds;
|
||||
|
||||
/** Host name of WHOIS server. */
|
||||
@Expose String whoisServer;
|
||||
|
||||
/** Base URLs for the registrar's RDAP servers. */
|
||||
@GetterDelegate(methodName = "getRdapBaseUrls")
|
||||
Set<String> rdapBaseUrls;
|
||||
|
||||
/**
|
||||
@@ -412,13 +416,13 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
String phonePasscode;
|
||||
|
||||
/**
|
||||
* A dirty bit for whether RegistrarContact changes have been made that haven't been synced to
|
||||
* Google Groups yet. When creating a new instance, contacts require syncing by default.
|
||||
* A dirty bit for whether RegistrarPoc changes have been made that haven't been synced to Google
|
||||
* Groups yet. When creating a new instance, contacts require syncing by default.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
boolean contactsRequireSyncing = true;
|
||||
|
||||
/** Whether or not registry lock is allowed for this registrar. */
|
||||
/** Whether registry lock is allowed for this registrar. */
|
||||
@Column(nullable = false)
|
||||
@Expose
|
||||
boolean registryLockAllowed = false;
|
||||
@@ -492,7 +496,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
return LIVE_STATES.contains(state);
|
||||
}
|
||||
|
||||
/** Returns {@code true} if registrar should be visible in WHOIS results. */
|
||||
/** Returns {@code true} if registrar should be visible in RDAP results. */
|
||||
public boolean isLiveAndPubliclyVisible() {
|
||||
return LIVE_STATES.contains(state) && PUBLICLY_VISIBLE_TYPES.contains(type);
|
||||
}
|
||||
@@ -608,11 +612,11 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link RegistrarPoc} that is the WHOIS abuse contact for this registrar, or empty
|
||||
* if one does not exist.
|
||||
* Returns the {@link RegistrarPoc} that is the RDAP abuse contact for this registrar, or empty if
|
||||
* one does not exist.
|
||||
*/
|
||||
public Optional<RegistrarPoc> getWhoisAbuseContact() {
|
||||
return getContacts().stream().filter(RegistrarPoc::getVisibleInDomainWhoisAsAbuse).findFirst();
|
||||
public Optional<RegistrarPoc> getRdapAbuseContact() {
|
||||
return getContacts().stream().filter(RegistrarPoc::getVisibleInDomainRdapAsAbuse).findFirst();
|
||||
}
|
||||
|
||||
private ImmutableList<RegistrarPoc> getPocs(TransactionManager txnManager) {
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.GetterDelegate;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.JsonMapBuilder;
|
||||
import google.registry.model.Jsonifiable;
|
||||
@@ -47,9 +48,9 @@ import java.util.Set;
|
||||
* A contact for a Registrar. Note, equality, hashCode and comparable have been overridden to only
|
||||
* enable key equality.
|
||||
*
|
||||
* <p>IMPORTANT NOTE: Any time that you change, update, or delete RegistrarContact entities, you
|
||||
* *MUST* also modify the persisted Registrar entity with {@link Registrar#contactsRequireSyncing}
|
||||
* set to true.
|
||||
* <p>IMPORTANT NOTE: Any time that you change, update, or delete RegistrarPoc entities, you *MUST*
|
||||
* also modify the persisted Registrar entity with {@link Registrar#contactsRequireSyncing} set to
|
||||
* true.
|
||||
*/
|
||||
@Entity
|
||||
@IdClass(RegistrarPoc.RegistrarPocId.class)
|
||||
@@ -57,8 +58,8 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
|
||||
/**
|
||||
* Registrar contacts types for partner communication tracking.
|
||||
*
|
||||
* <p><b>Note:</b> These types only matter to the registry. They are not meant to be used for
|
||||
* WHOIS or RDAP results.
|
||||
* <p><b>Note:</b> These types only matter to the registry. They are not meant to be used for RDAP
|
||||
* results.
|
||||
*/
|
||||
public enum Type {
|
||||
ABUSE("abuse", true),
|
||||
@@ -110,35 +111,27 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
|
||||
* data is internal to the registry.
|
||||
*/
|
||||
@Enumerated(EnumType.STRING)
|
||||
@GetterDelegate(methodName = "getTypes")
|
||||
@Expose
|
||||
Set<Type> types;
|
||||
|
||||
/**
|
||||
* Whether this contact is publicly visible in WHOIS registrar query results as an Admin contact.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
/** If this contact is publicly visible in RDAP registrar query results as an Admin contact */
|
||||
@Column(nullable = false, name = "visibleInWhoisAsAdmin")
|
||||
@Expose
|
||||
boolean visibleInWhoisAsAdmin = false;
|
||||
boolean visibleInRdapAsAdmin = false;
|
||||
|
||||
/** If this contact is publicly visible in RDAP registrar query results as a Technical contact */
|
||||
@Column(nullable = false, name = "visibleInWhoisAsTech")
|
||||
@Expose
|
||||
boolean visibleInRdapAsTech = false;
|
||||
|
||||
/**
|
||||
* Whether this contact is publicly visible in WHOIS registrar query results as a Technical
|
||||
* contact.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
@Expose
|
||||
boolean visibleInWhoisAsTech = false;
|
||||
|
||||
/**
|
||||
* Whether this contact's phone number and email address is publicly visible in WHOIS domain query
|
||||
* If this contact's phone number and email address are publicly visible in RDAP domain query
|
||||
* results as registrar abuse contact info.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
@Column(nullable = false, name = "visibleInDomainWhoisAsAbuse")
|
||||
@Expose
|
||||
boolean visibleInDomainWhoisAsAbuse = false;
|
||||
|
||||
/** Legacy field, around until we can remove the non-null constraint and the column from SQL. */
|
||||
@Column(nullable = false)
|
||||
boolean allowedToSetRegistryLockPassword = false;
|
||||
boolean visibleInDomainRdapAsAbuse = false;
|
||||
|
||||
/**
|
||||
* Helper to update the contacts associated with a Registrar. This requires querying for the
|
||||
@@ -186,16 +179,16 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
|
||||
return nullToEmptyImmutableSortedCopy(types);
|
||||
}
|
||||
|
||||
public boolean getVisibleInWhoisAsAdmin() {
|
||||
return visibleInWhoisAsAdmin;
|
||||
public boolean getVisibleInRdapAsAdmin() {
|
||||
return visibleInRdapAsAdmin;
|
||||
}
|
||||
|
||||
public boolean getVisibleInWhoisAsTech() {
|
||||
return visibleInWhoisAsTech;
|
||||
public boolean getVisibleInRdapAsTech() {
|
||||
return visibleInRdapAsTech;
|
||||
}
|
||||
|
||||
public boolean getVisibleInDomainWhoisAsAbuse() {
|
||||
return visibleInDomainWhoisAsAbuse;
|
||||
public boolean getVisibleInDomainRdapAsAbuse() {
|
||||
return visibleInDomainRdapAsAbuse;
|
||||
}
|
||||
|
||||
public Builder asBuilder() {
|
||||
@@ -212,8 +205,8 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
|
||||
* person@example.com
|
||||
* Tel: +1.2125650666
|
||||
* Types: [ADMIN, WHOIS]
|
||||
* Visible in WHOIS as Admin contact: Yes
|
||||
* Visible in WHOIS as Technical contact: No
|
||||
* Visible in RDAP as Admin contact: Yes
|
||||
* Visible in RDAP as Technical contact: No
|
||||
* Registrar-Console access: Yes
|
||||
* Login Email Address: person@registry.example
|
||||
* }</pre>
|
||||
@@ -230,18 +223,18 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
|
||||
}
|
||||
result.append("Types: ").append(getTypes()).append('\n');
|
||||
result
|
||||
.append("Visible in registrar WHOIS query as Admin contact: ")
|
||||
.append(getVisibleInWhoisAsAdmin() ? "Yes" : "No")
|
||||
.append("Visible in registrar RDAP query as Admin contact: ")
|
||||
.append(getVisibleInRdapAsAdmin() ? "Yes" : "No")
|
||||
.append('\n');
|
||||
result
|
||||
.append("Visible in registrar WHOIS query as Technical contact: ")
|
||||
.append(getVisibleInWhoisAsTech() ? "Yes" : "No")
|
||||
.append("Visible in registrar RDAP query as Technical contact: ")
|
||||
.append(getVisibleInRdapAsTech() ? "Yes" : "No")
|
||||
.append('\n');
|
||||
result
|
||||
.append(
|
||||
"Phone number and email visible in domain WHOIS query as "
|
||||
"Phone number and email visible in domain RDAP query as "
|
||||
+ "Registrar Abuse contact info: ")
|
||||
.append(getVisibleInDomainWhoisAsAbuse() ? "Yes" : "No")
|
||||
.append(getVisibleInDomainRdapAsAbuse() ? "Yes" : "No")
|
||||
.append('\n');
|
||||
return result.toString();
|
||||
}
|
||||
@@ -254,9 +247,9 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
|
||||
.put("phoneNumber", phoneNumber)
|
||||
.put("faxNumber", faxNumber)
|
||||
.put("types", getTypes().stream().map(Object::toString).collect(joining(",")))
|
||||
.put("visibleInWhoisAsAdmin", visibleInWhoisAsAdmin)
|
||||
.put("visibleInWhoisAsTech", visibleInWhoisAsTech)
|
||||
.put("visibleInDomainWhoisAsAbuse", visibleInDomainWhoisAsAbuse)
|
||||
.put("visibleInRdapAsAdmin", visibleInRdapAsAdmin)
|
||||
.put("visibleInRdapAsTech", visibleInRdapAsTech)
|
||||
.put("visibleInDomainRdapAsAbuse", visibleInDomainRdapAsAbuse)
|
||||
.put("id", getId())
|
||||
.build();
|
||||
}
|
||||
@@ -334,18 +327,18 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setVisibleInWhoisAsAdmin(boolean visible) {
|
||||
getInstance().visibleInWhoisAsAdmin = visible;
|
||||
public Builder setVisibleInRdapAsAdmin(boolean visible) {
|
||||
getInstance().visibleInRdapAsAdmin = visible;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setVisibleInWhoisAsTech(boolean visible) {
|
||||
getInstance().visibleInWhoisAsTech = visible;
|
||||
public Builder setVisibleInRdapAsTech(boolean visible) {
|
||||
getInstance().visibleInRdapAsTech = visible;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setVisibleInDomainWhoisAsAbuse(boolean visible) {
|
||||
getInstance().visibleInDomainWhoisAsAbuse = visible;
|
||||
public Builder setVisibleInDomainRdapAsAbuse(boolean visible) {
|
||||
getInstance().visibleInDomainRdapAsAbuse = visible;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,11 +521,6 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
|
||||
@Column(nullable = false)
|
||||
DateTime claimsPeriodEnd = END_OF_TIME;
|
||||
|
||||
/** An allowlist of clients allowed to be used on domains on this TLD (ignored if empty). */
|
||||
@Nullable
|
||||
@JsonSerialize(using = SortedSetSerializer.class)
|
||||
Set<String> allowedRegistrantContactIds;
|
||||
|
||||
/** An allowlist of hosts allowed to be used on domains on this TLD (ignored if empty). */
|
||||
@Nullable
|
||||
@JsonSerialize(using = SortedSetSerializer.class)
|
||||
@@ -786,10 +781,6 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
|
||||
return tldUnicode;
|
||||
}
|
||||
|
||||
public ImmutableSet<String> getAllowedRegistrantContactIds() {
|
||||
return nullToEmptyImmutableCopy(allowedRegistrantContactIds);
|
||||
}
|
||||
|
||||
public ImmutableSet<String> getAllowedFullyQualifiedHostNames() {
|
||||
return nullToEmptyImmutableCopy(allowedFullyQualifiedHostNames);
|
||||
}
|
||||
@@ -1077,12 +1068,6 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAllowedRegistrantContactIds(
|
||||
ImmutableSet<String> allowedRegistrantContactIds) {
|
||||
getInstance().allowedRegistrantContactIds = allowedRegistrantContactIds;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAllowedFullyQualifiedHostNames(
|
||||
ImmutableSet<String> allowedFullyQualifiedHostNames) {
|
||||
getInstance().allowedFullyQualifiedHostNames = allowedFullyQualifiedHostNames;
|
||||
|
||||
@@ -131,9 +131,6 @@ import google.registry.ui.server.console.domains.ConsoleBulkDomainAction;
|
||||
import google.registry.ui.server.console.settings.ContactAction;
|
||||
import google.registry.ui.server.console.settings.RdapRegistrarFieldsAction;
|
||||
import google.registry.ui.server.console.settings.SecurityAction;
|
||||
import google.registry.whois.WhoisAction;
|
||||
import google.registry.whois.WhoisHttpAction;
|
||||
import google.registry.whois.WhoisModule;
|
||||
|
||||
/** Dagger component with per-request lifetime. */
|
||||
@RequestScope
|
||||
@@ -161,8 +158,7 @@ import google.registry.whois.WhoisModule;
|
||||
Spec11Module.class,
|
||||
TmchModule.class,
|
||||
ToolsServerModule.class,
|
||||
WhiteboxModule.class,
|
||||
WhoisModule.class,
|
||||
WhiteboxModule.class
|
||||
})
|
||||
interface RequestComponent {
|
||||
FlowComponent.Builder flowComponentBuilder();
|
||||
@@ -343,10 +339,6 @@ interface RequestComponent {
|
||||
|
||||
VerifyOteAction verifyOteAction();
|
||||
|
||||
WhoisAction whoisAction();
|
||||
|
||||
WhoisHttpAction whoisHttpAction();
|
||||
|
||||
WipeOutContactHistoryPiiAction wipeOutContactHistoryPiiAction();
|
||||
|
||||
@Subcomponent.Builder
|
||||
|
||||
@@ -36,9 +36,6 @@ import google.registry.rdap.RdapNameserverSearchAction;
|
||||
import google.registry.request.RequestComponentBuilder;
|
||||
import google.registry.request.RequestModule;
|
||||
import google.registry.request.RequestScope;
|
||||
import google.registry.whois.WhoisAction;
|
||||
import google.registry.whois.WhoisHttpAction;
|
||||
import google.registry.whois.WhoisModule;
|
||||
|
||||
/** Dagger component with per-request lifetime for "pubapi" App Engine module. */
|
||||
@RequestScope
|
||||
@@ -49,8 +46,7 @@ import google.registry.whois.WhoisModule;
|
||||
EppTlsModule.class,
|
||||
RdapModule.class,
|
||||
RequestModule.class,
|
||||
WhiteboxModule.class,
|
||||
WhoisModule.class,
|
||||
WhiteboxModule.class
|
||||
})
|
||||
public interface PubApiRequestComponent {
|
||||
CheckApiAction checkApiAction();
|
||||
@@ -67,9 +63,6 @@ public interface PubApiRequestComponent {
|
||||
|
||||
ReadinessProbeActionPubApi readinessProbeActionPubApi();
|
||||
|
||||
WhoisHttpAction whoisHttpAction();
|
||||
WhoisAction whoisAction();
|
||||
|
||||
@Subcomponent.Builder
|
||||
abstract class Builder implements RequestComponentBuilder<PubApiRequestComponent> {
|
||||
@Override public abstract Builder requestModule(RequestModule requestModule);
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package google.registry.rdap;
|
||||
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateDomainName;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCache;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.HEAD;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
@@ -23,6 +22,7 @@ import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.domain.DomainFlowUtils;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.rdap.RdapJsonFormatter.OutputDataType;
|
||||
@@ -68,7 +68,7 @@ public class RdapDomainAction extends RdapActionBase {
|
||||
}
|
||||
// The query string is not used; the RDAP syntax is /rdap/domain/mydomain.com.
|
||||
Optional<Domain> domain =
|
||||
loadByForeignKeyByCache(
|
||||
ForeignKeyUtils.loadResourceByCache(
|
||||
Domain.class,
|
||||
pathSearchString,
|
||||
shouldIncludeDeleted() ? START_OF_TIME : rdapJsonFormatter.getRequestTime());
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package google.registry.rdap;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCache;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.HEAD;
|
||||
@@ -59,6 +58,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import org.hibernate.Hibernate;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* RDAP action for domain search requests.
|
||||
@@ -183,7 +183,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
private DomainSearchResponse searchByDomainNameWithoutWildcard(
|
||||
final RdapSearchPattern partialStringQuery) {
|
||||
Optional<Domain> domain =
|
||||
loadByForeignKeyByCache(
|
||||
ForeignKeyUtils.loadResourceByCache(
|
||||
Domain.class, partialStringQuery.getInitialString(), getRequestTime());
|
||||
return makeSearchResults(
|
||||
shouldBeVisible(domain) ? ImmutableList.of(domain.get()) : ImmutableList.of());
|
||||
@@ -333,40 +333,35 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
/** Assembles a list of {@link Host} keys by name when the pattern has no wildcard. */
|
||||
private ImmutableList<VKey<Host>> getNameserverRefsByLdhNameWithoutWildcard(
|
||||
final RdapSearchPattern partialStringQuery) {
|
||||
DateTime timeToQuery = shouldIncludeDeleted() ? START_OF_TIME : getRequestTime();
|
||||
// If we need to check the sponsoring registrar, we need to load the resource rather than just
|
||||
// the key.
|
||||
Optional<String> desiredRegistrar = getDesiredRegistrar();
|
||||
String queryString = partialStringQuery.getInitialString();
|
||||
if (desiredRegistrar.isPresent()) {
|
||||
Optional<Host> host =
|
||||
loadByForeignKeyByCache(
|
||||
Host.class,
|
||||
partialStringQuery.getInitialString(),
|
||||
shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
|
||||
ForeignKeyUtils.loadResourceByCache(Host.class, queryString, timeToQuery);
|
||||
return (host.isEmpty()
|
||||
|| !desiredRegistrar.get().equals(host.get().getPersistedCurrentSponsorRegistrarId()))
|
||||
? ImmutableList.of()
|
||||
: ImmutableList.of(host.get().createVKey());
|
||||
} else {
|
||||
VKey<Host> hostKey =
|
||||
ForeignKeyUtils.load(
|
||||
Host.class,
|
||||
partialStringQuery.getInitialString(),
|
||||
shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
|
||||
return (hostKey == null) ? ImmutableList.of() : ImmutableList.of(hostKey);
|
||||
Optional<VKey<Host>> hostKey =
|
||||
ForeignKeyUtils.loadKeyByCache(Host.class, queryString, timeToQuery);
|
||||
return hostKey.map(ImmutableList::of).orElseGet(ImmutableList::of);
|
||||
}
|
||||
}
|
||||
|
||||
/** Assembles a list of {@link Host} keys by name using a superordinate domain suffix. */
|
||||
private ImmutableList<VKey<Host>> getNameserverRefsByLdhNameWithSuffix(
|
||||
final RdapSearchPattern partialStringQuery) {
|
||||
RdapSearchPattern partialStringQuery) {
|
||||
DateTime timeToQuery = shouldIncludeDeleted() ? START_OF_TIME : getRequestTime();
|
||||
// The suffix must be a domain that we manage. That way, we can look up the domain and search
|
||||
// through the subordinate hosts. This is more efficient, and lets us permit wildcard searches
|
||||
// with no initial string.
|
||||
Domain domain =
|
||||
loadByForeignKeyByCache(
|
||||
Domain.class,
|
||||
partialStringQuery.getSuffix(),
|
||||
shouldIncludeDeleted() ? START_OF_TIME : getRequestTime())
|
||||
ForeignKeyUtils.loadResourceByCache(
|
||||
Domain.class, partialStringQuery.getSuffix(), timeToQuery)
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new UnprocessableEntityException(
|
||||
@@ -379,9 +374,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
// then the query ns.exam*.example.com would match against nameserver ns.example.com.
|
||||
if (partialStringQuery.matches(fqhn)) {
|
||||
if (desiredRegistrar.isPresent()) {
|
||||
Optional<Host> host =
|
||||
loadByForeignKeyByCache(
|
||||
Host.class, fqhn, shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
|
||||
Optional<Host> host = ForeignKeyUtils.loadResourceByCache(Host.class, fqhn, timeToQuery);
|
||||
if (host.isPresent()
|
||||
&& desiredRegistrar
|
||||
.get()
|
||||
@@ -389,14 +382,10 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
builder.add(host.get().createVKey());
|
||||
}
|
||||
} else {
|
||||
VKey<Host> hostKey =
|
||||
ForeignKeyUtils.load(
|
||||
Host.class, fqhn, shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
|
||||
if (hostKey != null) {
|
||||
builder.add(hostKey);
|
||||
} else {
|
||||
logger.atWarning().log("Host key unexpectedly null.");
|
||||
}
|
||||
Optional<VKey<Host>> hostKey =
|
||||
ForeignKeyUtils.loadKeyByCache(Host.class, fqhn, timeToQuery);
|
||||
hostKey.ifPresentOrElse(
|
||||
builder::add, () -> logger.atWarning().log("Host key unexpectedly null."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,7 +658,7 @@ public class RdapJsonFormatter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the list of RDAP roles for a registrar POC, using the visibleInWhoisAs* flags.
|
||||
* Creates the list of RDAP roles for a registrar POC, using the visibleInRdapAs* flags.
|
||||
*
|
||||
* <p>Only POCs with a non-empty role list should be visible.
|
||||
*
|
||||
@@ -672,13 +672,13 @@ public class RdapJsonFormatter {
|
||||
*/
|
||||
private static ImmutableList<RdapEntity.Role> makeRdapRoleList(RegistrarPoc registrarPoc) {
|
||||
ImmutableList.Builder<RdapEntity.Role> rolesBuilder = new ImmutableList.Builder<>();
|
||||
if (registrarPoc.getVisibleInWhoisAsAdmin()) {
|
||||
if (registrarPoc.getVisibleInRdapAsAdmin()) {
|
||||
rolesBuilder.add(RdapEntity.Role.ADMIN);
|
||||
}
|
||||
if (registrarPoc.getVisibleInWhoisAsTech()) {
|
||||
if (registrarPoc.getVisibleInRdapAsTech()) {
|
||||
rolesBuilder.add(RdapEntity.Role.TECH);
|
||||
}
|
||||
if (registrarPoc.getVisibleInDomainWhoisAsAbuse()) {
|
||||
if (registrarPoc.getVisibleInDomainRdapAsAbuse()) {
|
||||
rolesBuilder.add(RdapEntity.Role.ABUSE);
|
||||
}
|
||||
return rolesBuilder.build();
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
package google.registry.rdap;
|
||||
|
||||
import static google.registry.flows.host.HostFlowUtils.validateHostName;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCache;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.HEAD;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.rdap.RdapJsonFormatter.OutputDataType;
|
||||
import google.registry.rdap.RdapMetrics.EndpointType;
|
||||
@@ -63,7 +63,7 @@ public class RdapNameserverAction extends RdapActionBase {
|
||||
// If there are no undeleted nameservers with the given name, the foreign key should point to
|
||||
// the most recently deleted one.
|
||||
Optional<Host> host =
|
||||
loadByForeignKeyByCache(
|
||||
ForeignKeyUtils.loadResourceByCache(
|
||||
Host.class,
|
||||
pathSearchString,
|
||||
shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package google.registry.rdap;
|
||||
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCache;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.HEAD;
|
||||
@@ -25,6 +24,7 @@ import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import com.google.common.primitives.Booleans;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.transaction.CriteriaQueryBuilder;
|
||||
@@ -159,7 +159,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
|
||||
.setIncompletenessWarningType(IncompletenessWarningType.COMPLETE);
|
||||
|
||||
Optional<Host> host =
|
||||
loadByForeignKeyByCache(
|
||||
ForeignKeyUtils.loadResourceByCache(
|
||||
Host.class, partialStringQuery.getInitialString(), getRequestTime());
|
||||
|
||||
metricInformationBuilder.setNumHostsRetrieved(host.isPresent() ? 1 : 0);
|
||||
@@ -176,7 +176,8 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
|
||||
private NameserverSearchResponse searchByNameUsingSuperordinateDomain(
|
||||
RdapSearchPattern partialStringQuery) {
|
||||
Optional<Domain> domain =
|
||||
loadByForeignKeyByCache(Domain.class, partialStringQuery.getSuffix(), getRequestTime());
|
||||
ForeignKeyUtils.loadResourceByCache(
|
||||
Domain.class, partialStringQuery.getSuffix(), getRequestTime());
|
||||
if (domain.isEmpty()) {
|
||||
// Don't allow wildcards with suffixes which are not domains we manage. That would risk a
|
||||
// table scan in many easily foreseeable cases. The user might ask for ns*.zombo.com,
|
||||
@@ -194,7 +195,8 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
|
||||
// We can't just check that the host name starts with the initial query string, because
|
||||
// then the query ns.exam*.example.com would match against nameserver ns.example.com.
|
||||
if (partialStringQuery.matches(fqhn)) {
|
||||
Optional<Host> host = loadByForeignKeyByCache(Host.class, fqhn, getRequestTime());
|
||||
Optional<Host> host =
|
||||
ForeignKeyUtils.loadResourceByCache(Host.class, fqhn, getRequestTime());
|
||||
if (shouldBeVisible(host)) {
|
||||
hostList.add(host.get());
|
||||
if (hostList.size() > rdapResultSetMaxSize) {
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
// Copyright 2021 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.rde;
|
||||
|
||||
import static google.registry.model.EppResourceUtils.loadAtPointInTime;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.rde.RdeMode;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Loading cache that turns a resource into XML for the various points in time and modes. */
|
||||
public class RdeFragmenter {
|
||||
private final Map<WatermarkModePair, Optional<DepositFragment>> cache = new HashMap<>();
|
||||
private final ImmutableMap<DateTime, Supplier<EppResource>> resourceAtTimes;
|
||||
private final RdeMarshaller marshaller;
|
||||
|
||||
long cacheHits = 0;
|
||||
long resourcesNotFound = 0;
|
||||
long resourcesFound = 0;
|
||||
|
||||
public RdeFragmenter(
|
||||
ImmutableMap<DateTime, Supplier<EppResource>> resourceAtTimes, RdeMarshaller marshaller) {
|
||||
this.resourceAtTimes = resourceAtTimes;
|
||||
this.marshaller = marshaller;
|
||||
}
|
||||
|
||||
public Optional<DepositFragment> marshal(DateTime watermark, RdeMode mode) {
|
||||
Optional<DepositFragment> result = cache.get(WatermarkModePair.create(watermark, mode));
|
||||
if (result != null) {
|
||||
cacheHits++;
|
||||
return result;
|
||||
}
|
||||
EppResource resource = resourceAtTimes.get(watermark).get();
|
||||
if (resource == null) {
|
||||
result = Optional.empty();
|
||||
cache.put(WatermarkModePair.create(watermark, RdeMode.FULL), result);
|
||||
cache.put(WatermarkModePair.create(watermark, RdeMode.THIN), result);
|
||||
resourcesNotFound++;
|
||||
return result;
|
||||
}
|
||||
resourcesFound++;
|
||||
if (resource instanceof Domain) {
|
||||
result = Optional.of(marshaller.marshalDomain((Domain) resource, mode));
|
||||
cache.put(WatermarkModePair.create(watermark, mode), result);
|
||||
return result;
|
||||
} else if (resource instanceof Contact) {
|
||||
result = Optional.of(marshaller.marshalContact((Contact) resource));
|
||||
cache.put(WatermarkModePair.create(watermark, RdeMode.FULL), result);
|
||||
cache.put(WatermarkModePair.create(watermark, RdeMode.THIN), result);
|
||||
return result;
|
||||
} else if (resource instanceof Host host) {
|
||||
result =
|
||||
Optional.of(
|
||||
host.isSubordinate()
|
||||
? marshaller.marshalSubordinateHost(
|
||||
host,
|
||||
// Note that loadAtPointInTime() does cloneProjectedAtTime(watermark) for
|
||||
// us.
|
||||
Objects.requireNonNull(
|
||||
loadAtPointInTime(
|
||||
tm().loadByKey(host.getSuperordinateDomain()), watermark)))
|
||||
: marshaller.marshalExternalHost(host));
|
||||
cache.put(WatermarkModePair.create(watermark, RdeMode.FULL), result);
|
||||
cache.put(WatermarkModePair.create(watermark, RdeMode.THIN), result);
|
||||
return result;
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
String.format(
|
||||
"Resource %s of type %s cannot be converted to XML.",
|
||||
resource, resource.getClass().getSimpleName()));
|
||||
}
|
||||
}
|
||||
|
||||
/** Map key for {@link RdeFragmenter} cache. */
|
||||
record WatermarkModePair(DateTime watermark, RdeMode mode) {
|
||||
|
||||
static WatermarkModePair create(DateTime watermark, RdeMode mode) {
|
||||
return new WatermarkModePair(watermark, mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -36,7 +36,7 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
|
||||
static final String DNS_COUNTS = "dns_counts";
|
||||
static final String MONTHLY_LOGS = "monthly_logs";
|
||||
static final String EPP_METRICS = "epp_metrics";
|
||||
static final String WHOIS_COUNTS = "whois_counts";
|
||||
static final String RDAP_COUNTS = "rdap_counts";
|
||||
static final String ACTIVITY_REPORT_AGGREGATION = "activity_report_aggregation";
|
||||
|
||||
private final String projectId;
|
||||
@@ -98,13 +98,13 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
|
||||
.build();
|
||||
queriesBuilder.put(getTableName(EPP_METRICS, yearMonth), eppQuery);
|
||||
|
||||
String whoisQuery =
|
||||
SqlTemplate.create(getQueryFromFile(WHOIS_COUNTS + ".sql"))
|
||||
String rdapQuery =
|
||||
SqlTemplate.create(getQueryFromFile(RDAP_COUNTS + ".sql"))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("ICANN_REPORTING_DATA_SET", icannReportingDataSet)
|
||||
.put("MONTHLY_LOGS_TABLE", getTableName(MONTHLY_LOGS, yearMonth))
|
||||
.build();
|
||||
queriesBuilder.put(getTableName(WHOIS_COUNTS, yearMonth), whoisQuery);
|
||||
queriesBuilder.put(getTableName(RDAP_COUNTS, yearMonth), rdapQuery);
|
||||
|
||||
SqlTemplate aggregateQuery =
|
||||
SqlTemplate.create(getQueryFromFile(ACTIVITY_REPORT_AGGREGATION + ".sql"))
|
||||
@@ -115,7 +115,7 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
|
||||
.put("ICANN_REPORTING_DATA_SET", icannReportingDataSet)
|
||||
.put("DNS_COUNTS_TABLE", getTableName(DNS_COUNTS, yearMonth))
|
||||
.put("EPP_METRICS_TABLE", getTableName(EPP_METRICS, yearMonth))
|
||||
.put("WHOIS_COUNTS_TABLE", getTableName(WHOIS_COUNTS, yearMonth));
|
||||
.put("RDAP_COUNTS_TABLE", getTableName(RDAP_COUNTS, yearMonth));
|
||||
|
||||
queriesBuilder.put(
|
||||
getTableName(ACTIVITY_REPORT_AGGREGATION, yearMonth), aggregateQuery.build());
|
||||
|
||||
@@ -226,7 +226,7 @@ public class Spec11EmailUtils {
|
||||
|
||||
private InternetAddress getEmailAddressForRegistrar(String registrarId)
|
||||
throws MessagingException {
|
||||
// Attempt to use the registrar's WHOIS abuse contact, then fall back to the regular address.
|
||||
// Attempt to use the registrar's RDAP abuse contact, then fall back to the regular address.
|
||||
Registrar registrar =
|
||||
Registrar.loadByRegistrarIdCached(registrarId)
|
||||
.orElseThrow(
|
||||
@@ -235,7 +235,7 @@ public class Spec11EmailUtils {
|
||||
String.format("Could not find registrar %s", registrarId)));
|
||||
return new InternetAddress(
|
||||
registrar
|
||||
.getWhoisAbuseContact()
|
||||
.getRdapAbuseContact()
|
||||
.map(RegistrarPoc::getEmailAddress)
|
||||
.orElse(registrar.getEmailAddress()));
|
||||
}
|
||||
|
||||
@@ -67,15 +67,13 @@ class RequestMetrics {
|
||||
private static String truncatePath(String path) {
|
||||
// We want to bucket RDAP requests by type to use less metric space,
|
||||
// e.g. "/rdap/domains" rather than "/rdap/domains/foo.tld"
|
||||
|
||||
if (path.startsWith("/rdap")) {
|
||||
List<String> splitPath = Splitter.on("/").omitEmptyStrings().splitToList(path);
|
||||
return Streams.stream(Iterables.limit(splitPath, 2))
|
||||
.collect(Collectors.joining("/", "/", "/"));
|
||||
// Similarly, we put all web WHOIS requests under the same path because otherwise its
|
||||
// cardinality is unbound, and it is possible to generate a huge amount of metrics with all
|
||||
// the different paths.
|
||||
} else if (path.startsWith("/whois")) {
|
||||
// Though we don't serve WHOIS requests anymore, bucket any non-served WHOIS requests together
|
||||
// to avoid an unbound cardinality
|
||||
return "/whois";
|
||||
}
|
||||
return path;
|
||||
|
||||
@@ -23,7 +23,7 @@ public enum Auth {
|
||||
/**
|
||||
* Allows anyone to access.
|
||||
*
|
||||
* <p>This is used for public HTML endpoints like RDAP, the check API, and web WHOIS.
|
||||
* <p>This is used for public HTML endpoints like RDAP and the check API.
|
||||
*/
|
||||
AUTH_PUBLIC(AuthLevel.NONE, UserPolicy.PUBLIC),
|
||||
|
||||
|
||||
@@ -41,7 +41,10 @@ class CommandUtilities {
|
||||
}
|
||||
|
||||
public VKey<? extends EppResource> getKey(String uniqueId, DateTime now) {
|
||||
return ForeignKeyUtils.load(clazz, uniqueId, now);
|
||||
return ForeignKeyUtils.loadKey(clazz, uniqueId, now)
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalArgumentException(String.format("Invalid resource ID %s", uniqueId)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ final class CreateRegistrarCommand extends CreateOrUpdateRegistrarCommand
|
||||
"Cannot add allowed TLDs when creating a REAL registrar in a production environment."
|
||||
+ " Please create the registrar without allowed TLDs, then use `nomulus"
|
||||
+ " registrar_contact` to create a registrar contact for it that is visible as the"
|
||||
+ " abuse contact in WHOIS. Then use `nomulus update_registrar` to add the allowed"
|
||||
+ " abuse contact in RDAP. Then use `nomulus update_registrar` to add the allowed"
|
||||
+ " TLDs.");
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ package google.registry.tools;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_ACTIONS;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCacheIfEnabled;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STATUSES;
|
||||
|
||||
@@ -26,6 +25,7 @@ import com.google.common.collect.Sets;
|
||||
import google.registry.batch.CloudTasksUtils;
|
||||
import google.registry.batch.RelockDomainAction;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.billing.BillingBase.Reason;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.domain.Domain;
|
||||
@@ -327,7 +327,7 @@ public final class DomainLockUtils {
|
||||
|
||||
private Domain getDomain(String domainName, String registrarId, DateTime now) {
|
||||
Domain domain =
|
||||
loadByForeignKeyByCacheIfEnabled(Domain.class, domainName, now)
|
||||
ForeignKeyUtils.loadResource(Domain.class, domainName, now)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Domain doesn't exist"));
|
||||
// The user must have specified either the correct registrar ID or the admin registrar ID
|
||||
checkArgument(
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package google.registry.tools;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.SYNTHETIC;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.CollectionUtils.isNullOrEmpty;
|
||||
@@ -25,6 +24,7 @@ import com.beust.jcommander.Parameters;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Streams;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.flows.ResourceFlowUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
@@ -33,7 +33,6 @@ import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.util.DomainNameUtils;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Tool to enqueue a poll message for a registrar.
|
||||
@@ -85,11 +84,9 @@ class EnqueuePollMessageCommand extends MutatingCommand {
|
||||
!sendToAll || isNullOrEmpty(clientIds), "Cannot specify both --all and --clients");
|
||||
tm().transact(
|
||||
() -> {
|
||||
Optional<Domain> domainOpt =
|
||||
loadByForeignKey(Domain.class, domainName, tm().getTransactionTime());
|
||||
checkArgument(
|
||||
domainOpt.isPresent(), "Domain %s doesn't exist or isn't active", domainName);
|
||||
Domain domain = domainOpt.get();
|
||||
Domain domain =
|
||||
ResourceFlowUtils.loadAndVerifyExistence(
|
||||
Domain.class, domainName, tm().getTransactionTime());
|
||||
ImmutableList<String> registrarIds;
|
||||
if (sendToAll) {
|
||||
registrarIds =
|
||||
|
||||
@@ -14,10 +14,9 @@
|
||||
|
||||
package google.registry.tools;
|
||||
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.contact.Contact;
|
||||
import java.util.List;
|
||||
|
||||
@@ -34,7 +33,9 @@ final class GetContactCommand extends GetEppResourceCommand {
|
||||
public void runAndPrint() {
|
||||
for (String contactId : mainParameters) {
|
||||
printResource(
|
||||
"Contact", contactId, loadByForeignKey(Contact.class, contactId, readTimestamp));
|
||||
"Contact",
|
||||
contactId,
|
||||
ForeignKeyUtils.loadResource(Contact.class, contactId, readTimestamp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
|
||||
package google.registry.tools;
|
||||
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.persistence.transaction.QueryComposer.Comparator;
|
||||
import google.registry.util.DomainNameUtils;
|
||||
@@ -57,7 +57,7 @@ final class GetDomainCommand extends GetEppResourceCommand {
|
||||
printResource(
|
||||
"Domain",
|
||||
canonicalDomain,
|
||||
loadByForeignKey(Domain.class, canonicalDomain, readTimestamp));
|
||||
ForeignKeyUtils.loadResource(Domain.class, canonicalDomain, readTimestamp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ package google.registry.tools;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
@@ -60,7 +59,6 @@ final class GetHistoryEntriesCommand implements Command {
|
||||
type != null && uniqueId != null,
|
||||
"If either of 'type' or 'id' are set then both must be");
|
||||
VKey<? extends EppResource> parentKey = type.getKey(uniqueId, DateTime.now(UTC));
|
||||
checkArgumentNotNull(parentKey, "Invalid resource ID");
|
||||
historyEntries = HistoryEntryDao.loadHistoryObjectsForResource(parentKey, after, before);
|
||||
} else {
|
||||
historyEntries = HistoryEntryDao.loadAllHistoryObjects(after, before);
|
||||
|
||||
@@ -14,10 +14,9 @@
|
||||
|
||||
package google.registry.tools;
|
||||
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.util.DomainNameUtils;
|
||||
import java.util.List;
|
||||
@@ -35,6 +34,9 @@ final class GetHostCommand extends GetEppResourceCommand {
|
||||
public void runAndPrint() {
|
||||
mainParameters.stream()
|
||||
.map(DomainNameUtils::canonicalizeHostname)
|
||||
.forEach(h -> printResource("Host", h, loadByForeignKey(Host.class, h, readTimestamp)));
|
||||
.forEach(
|
||||
h ->
|
||||
printResource(
|
||||
"Host", h, ForeignKeyUtils.loadResource(Host.class, h, readTimestamp)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,28 +103,27 @@ final class RegistrarPocCommand extends MutatingCommand {
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = "--visible_in_whois_as_admin",
|
||||
description = " Whether this contact is publicly visible in WHOIS results as an "
|
||||
+ "Admin contact.",
|
||||
names = "--visible_in_rdap_as_admin",
|
||||
description = "If this contact is publicly visible in RDAP results as an " + "Admin contact.",
|
||||
arity = 1)
|
||||
private Boolean visibleInWhoisAsAdmin;
|
||||
private Boolean visibleInRdapAsAdmin;
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = "--visible_in_whois_as_tech",
|
||||
description = " Whether this contact is publicly visible in WHOIS results as a "
|
||||
+ "Tech contact.",
|
||||
names = "--visible_in_rdap_as_tech",
|
||||
description = "If this contact is publicly visible in RDAP results as a " + "Tech contact.",
|
||||
arity = 1)
|
||||
private Boolean visibleInWhoisAsTech;
|
||||
private Boolean visibleInRdapAsTech;
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = "--visible_in_domain_whois_as_abuse",
|
||||
description = " Whether this contact is publicly visible in WHOIS domain results as the "
|
||||
+ "registry abuse phone and email. If this flag is set, it will be cleared from all "
|
||||
+ "other contacts for the same registrar.",
|
||||
names = "--visible_in_domain_rdap_as_abuse",
|
||||
description =
|
||||
" Whether this contact is publicly visible in RDAP domain results as the "
|
||||
+ "registry abuse phone and email. If this flag is set, it will be cleared from all "
|
||||
+ "other contacts for the same registrar.",
|
||||
arity = 1)
|
||||
private Boolean visibleInDomainWhoisAsAbuse;
|
||||
private Boolean visibleInDomainRdapAsAbuse;
|
||||
|
||||
@Parameter(
|
||||
names = {"-o", "--output"},
|
||||
@@ -166,8 +165,8 @@ final class RegistrarPocCommand extends MutatingCommand {
|
||||
case LIST -> listContacts(contacts);
|
||||
case CREATE -> {
|
||||
stageEntityChange(null, createContact(registrar));
|
||||
if (visibleInDomainWhoisAsAbuse != null && visibleInDomainWhoisAsAbuse) {
|
||||
unsetOtherWhoisAbuseFlags(contacts, null);
|
||||
if (visibleInDomainRdapAsAbuse != null && visibleInDomainRdapAsAbuse) {
|
||||
unsetOtherRdapAbuseFlags(contacts, null);
|
||||
}
|
||||
}
|
||||
case UPDATE -> {
|
||||
@@ -178,13 +177,13 @@ final class RegistrarPocCommand extends MutatingCommand {
|
||||
email);
|
||||
RegistrarPoc newContact = updateContact(oldContact, registrar);
|
||||
checkArgument(
|
||||
!oldContact.getVisibleInDomainWhoisAsAbuse()
|
||||
|| newContact.getVisibleInDomainWhoisAsAbuse(),
|
||||
"Cannot clear visible_in_domain_whois_as_abuse flag, as that would leave no domain"
|
||||
+ " WHOIS abuse contacts; instead, set the flag on another contact");
|
||||
!oldContact.getVisibleInDomainRdapAsAbuse()
|
||||
|| newContact.getVisibleInDomainRdapAsAbuse(),
|
||||
"Cannot clear visible_in_domain_rdap_as_abuse flag, as that would leave no domain"
|
||||
+ " RDAP abuse contacts; instead, set the flag on another contact");
|
||||
stageEntityChange(oldContact, newContact);
|
||||
if (visibleInDomainWhoisAsAbuse != null && visibleInDomainWhoisAsAbuse) {
|
||||
unsetOtherWhoisAbuseFlags(contacts, oldContact.getEmailAddress());
|
||||
if (visibleInDomainRdapAsAbuse != null && visibleInDomainRdapAsAbuse) {
|
||||
unsetOtherRdapAbuseFlags(contacts, oldContact.getEmailAddress());
|
||||
}
|
||||
}
|
||||
case DELETE -> {
|
||||
@@ -194,8 +193,8 @@ final class RegistrarPocCommand extends MutatingCommand {
|
||||
"No contact with the given email: %s",
|
||||
email);
|
||||
checkArgument(
|
||||
!oldContact.getVisibleInDomainWhoisAsAbuse(),
|
||||
"Cannot delete the domain WHOIS abuse contact; set the flag on another contact first");
|
||||
!oldContact.getVisibleInDomainRdapAsAbuse(),
|
||||
"Cannot delete the domain RDAP abuse contact; set the flag on another contact first");
|
||||
stageEntityChange(oldContact, null);
|
||||
}
|
||||
default -> throw new AssertionError();
|
||||
@@ -228,14 +227,14 @@ final class RegistrarPocCommand extends MutatingCommand {
|
||||
}
|
||||
builder.setTypes(nullToEmpty(contactTypes));
|
||||
|
||||
if (visibleInWhoisAsAdmin != null) {
|
||||
builder.setVisibleInWhoisAsAdmin(visibleInWhoisAsAdmin);
|
||||
if (visibleInRdapAsAdmin != null) {
|
||||
builder.setVisibleInRdapAsAdmin(visibleInRdapAsAdmin);
|
||||
}
|
||||
if (visibleInWhoisAsTech != null) {
|
||||
builder.setVisibleInWhoisAsTech(visibleInWhoisAsTech);
|
||||
if (visibleInRdapAsTech != null) {
|
||||
builder.setVisibleInRdapAsTech(visibleInRdapAsTech);
|
||||
}
|
||||
if (visibleInDomainWhoisAsAbuse != null) {
|
||||
builder.setVisibleInDomainWhoisAsAbuse(visibleInDomainWhoisAsAbuse);
|
||||
if (visibleInDomainRdapAsAbuse != null) {
|
||||
builder.setVisibleInDomainRdapAsAbuse(visibleInDomainRdapAsAbuse);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
@@ -257,24 +256,24 @@ final class RegistrarPocCommand extends MutatingCommand {
|
||||
if (contactTypes != null) {
|
||||
builder.setTypes(contactTypes);
|
||||
}
|
||||
if (visibleInWhoisAsAdmin != null) {
|
||||
builder.setVisibleInWhoisAsAdmin(visibleInWhoisAsAdmin);
|
||||
if (visibleInRdapAsAdmin != null) {
|
||||
builder.setVisibleInRdapAsAdmin(visibleInRdapAsAdmin);
|
||||
}
|
||||
if (visibleInWhoisAsTech != null) {
|
||||
builder.setVisibleInWhoisAsTech(visibleInWhoisAsTech);
|
||||
if (visibleInRdapAsTech != null) {
|
||||
builder.setVisibleInRdapAsTech(visibleInRdapAsTech);
|
||||
}
|
||||
if (visibleInDomainWhoisAsAbuse != null) {
|
||||
builder.setVisibleInDomainWhoisAsAbuse(visibleInDomainWhoisAsAbuse);
|
||||
if (visibleInDomainRdapAsAbuse != null) {
|
||||
builder.setVisibleInDomainRdapAsAbuse(visibleInDomainRdapAsAbuse);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private void unsetOtherWhoisAbuseFlags(
|
||||
private void unsetOtherRdapAbuseFlags(
|
||||
ImmutableSet<RegistrarPoc> contacts, @Nullable String emailAddressNotToChange) {
|
||||
for (RegistrarPoc contact : contacts) {
|
||||
if (!contact.getEmailAddress().equals(emailAddressNotToChange)
|
||||
&& contact.getVisibleInDomainWhoisAsAbuse()) {
|
||||
RegistrarPoc newContact = contact.asBuilder().setVisibleInDomainWhoisAsAbuse(false).build();
|
||||
&& contact.getVisibleInDomainRdapAsAbuse()) {
|
||||
RegistrarPoc newContact = contact.asBuilder().setVisibleInDomainRdapAsAbuse(false).build();
|
||||
stageEntityChange(contact, newContact);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +122,6 @@ public final class RegistryTool {
|
||||
.put("validate_escrow_deposit", ValidateEscrowDepositCommand.class)
|
||||
.put("validate_login_credentials", ValidateLoginCredentialsCommand.class)
|
||||
.put("verify_ote", VerifyOteCommand.class)
|
||||
.put("whois_query", WhoisQueryCommand.class)
|
||||
.build();
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
@@ -37,7 +37,6 @@ import google.registry.request.Modules.GsonModule;
|
||||
import google.registry.request.Modules.UrlConnectionServiceModule;
|
||||
import google.registry.tools.AuthModule.LocalCredentialModule;
|
||||
import google.registry.util.UtilsModule;
|
||||
import google.registry.whois.NonCachingWhoisModule;
|
||||
import jakarta.inject.Singleton;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -67,8 +66,7 @@ import javax.annotation.Nullable;
|
||||
RequestFactoryModule.class,
|
||||
SecretManagerModule.class,
|
||||
UrlConnectionServiceModule.class,
|
||||
UtilsModule.class,
|
||||
NonCachingWhoisModule.class
|
||||
UtilsModule.class
|
||||
})
|
||||
interface RegistryToolComponent {
|
||||
void inject(AckPollMessagesCommand command);
|
||||
@@ -163,8 +161,6 @@ interface RegistryToolComponent {
|
||||
|
||||
void inject(ValidateLoginCredentialsCommand command);
|
||||
|
||||
void inject(WhoisQueryCommand command);
|
||||
|
||||
ServiceConnection serviceConnection();
|
||||
|
||||
@LocalCredentialJson
|
||||
|
||||
@@ -16,21 +16,19 @@ package google.registry.tools;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.util.CollectionUtils.findDuplicates;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.template.soy.data.SoyMapData;
|
||||
import google.registry.flows.ResourceFlowUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.tools.soy.DomainRenewSoyInfo;
|
||||
import google.registry.util.Clock;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
@@ -71,17 +69,15 @@ final class RenewDomainCommand extends MutatingEppToolCommand {
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormat.forPattern("YYYY-MM-dd");
|
||||
|
||||
@Override
|
||||
protected void initMutatingEppToolCommand() {
|
||||
protected void initMutatingEppToolCommand()
|
||||
throws ResourceFlowUtils.ResourceDoesNotExistException {
|
||||
String duplicates = Joiner.on(", ").join(findDuplicates(mainParameters));
|
||||
checkArgument(duplicates.isEmpty(), "Duplicate domain arguments found: '%s'", duplicates);
|
||||
checkArgument(period < 10, "Cannot renew domains for 10 or more years");
|
||||
DateTime now = clock.nowUtc();
|
||||
for (String domainName : mainParameters) {
|
||||
Optional<Domain> domainOptional = loadByForeignKey(Domain.class, domainName, now);
|
||||
checkArgumentPresent(domainOptional, "Domain '%s' does not exist or is deleted", domainName);
|
||||
Domain domain = ResourceFlowUtils.loadAndVerifyExistence(Domain.class, domainName, now);
|
||||
setSoyTemplate(DomainRenewSoyInfo.getInstance(), DomainRenewSoyInfo.RENEWDOMAIN);
|
||||
Domain domain = domainOptional.get();
|
||||
|
||||
SoyMapData soyMapData =
|
||||
new SoyMapData(
|
||||
"domainName", domain.getDomainName(),
|
||||
|
||||
@@ -17,10 +17,7 @@ package google.registry.tools;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.collect.Sets.difference;
|
||||
import static google.registry.model.EppResourceUtils.checkResourcesExist;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
@@ -31,6 +28,8 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.template.soy.data.SoyListData;
|
||||
import com.google.template.soy.data.SoyMapData;
|
||||
import google.registry.flows.ResourceFlowUtils;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.secdns.DomainDsData;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
@@ -44,7 +43,6 @@ import jakarta.xml.bind.annotation.adapters.HexBinaryAdapter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
@@ -127,13 +125,13 @@ final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
|
||||
@Inject Clock clock;
|
||||
|
||||
@Override
|
||||
protected void initMutatingEppToolCommand() {
|
||||
protected void initMutatingEppToolCommand()
|
||||
throws ResourceFlowUtils.ResourceDoesNotExistException {
|
||||
superuser = true;
|
||||
DateTime now = clock.nowUtc();
|
||||
Optional<Domain> domainOpt = loadByForeignKey(Domain.class, domainName, now);
|
||||
checkArgumentPresent(domainOpt, "Domain '%s' does not exist or is deleted", domainName);
|
||||
Domain domain = domainOpt.get();
|
||||
Set<String> missingHosts = difference(newHosts, checkResourcesExist(Host.class, newHosts, now));
|
||||
Domain domain = ResourceFlowUtils.loadAndVerifyExistence(Domain.class, domainName, now);
|
||||
Set<String> missingHosts =
|
||||
difference(newHosts, ForeignKeyUtils.loadKeys(Host.class, newHosts, now).keySet());
|
||||
checkArgument(missingHosts.isEmpty(), "Hosts do not exist: %s", missingHosts);
|
||||
checkArgument(
|
||||
locksToPreserve.isEmpty() || undo,
|
||||
|
||||
@@ -19,7 +19,6 @@ import static com.google.common.base.Preconditions.checkState;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.newAutorenewBillingEvent;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.newAutorenewPollMessage;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.updateAutorenewRecurrenceEndTime;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static google.registry.util.DateTimeUtils.isBeforeOrAt;
|
||||
@@ -88,11 +87,11 @@ class UnrenewDomainCommand extends ConfirmingCommand {
|
||||
new ImmutableMap.Builder<>();
|
||||
|
||||
for (String domainName : mainParameters) {
|
||||
if (ForeignKeyUtils.load(Domain.class, domainName, START_OF_TIME) == null) {
|
||||
if (ForeignKeyUtils.loadKey(Domain.class, domainName, START_OF_TIME).isEmpty()) {
|
||||
domainsNonexistentBuilder.add(domainName);
|
||||
continue;
|
||||
}
|
||||
Optional<Domain> domain = loadByForeignKey(Domain.class, domainName, now);
|
||||
Optional<Domain> domain = ForeignKeyUtils.loadResource(Domain.class, domainName, now);
|
||||
if (domain.isEmpty() || domain.get().getStatusValues().contains(StatusValue.PENDING_DELETE)) {
|
||||
domainsDeletingBuilder.add(domainName);
|
||||
continue;
|
||||
@@ -143,7 +142,7 @@ class UnrenewDomainCommand extends ConfirmingCommand {
|
||||
StringBuilder resultBuilder = new StringBuilder();
|
||||
DateTime now = clock.nowUtc();
|
||||
for (String domainName : mainParameters) {
|
||||
Domain domain = loadByForeignKey(Domain.class, domainName, now).get();
|
||||
Domain domain = ForeignKeyUtils.loadResource(Domain.class, domainName, now).get();
|
||||
DateTime previousTime = domain.getRegistrationExpirationTime();
|
||||
DateTime newTime = leapSafeSubtractYears(previousTime, period);
|
||||
resultBuilder.append(
|
||||
@@ -166,7 +165,7 @@ class UnrenewDomainCommand extends ConfirmingCommand {
|
||||
private void unrenewDomain(String domainName) {
|
||||
tm().assertInTransaction();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
Optional<Domain> domainOptional = loadByForeignKey(Domain.class, domainName, now);
|
||||
Optional<Domain> domainOptional = ForeignKeyUtils.loadResource(Domain.class, domainName, now);
|
||||
// Transactional sanity checks on the off chance that something changed between init() running
|
||||
// and here.
|
||||
checkState(
|
||||
|
||||
@@ -16,12 +16,10 @@ package google.registry.tools;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.domain.rgp.GracePeriodStatus.AUTO_RENEW;
|
||||
import static google.registry.model.eppcommon.StatusValue.PENDING_DELETE;
|
||||
import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
|
||||
import static java.util.function.Predicate.isEqual;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
@@ -31,6 +29,7 @@ import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.template.soy.data.SoyMapData;
|
||||
import google.registry.flows.ResourceFlowUtils;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.GracePeriodBase;
|
||||
@@ -42,7 +41,6 @@ import jakarta.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -146,7 +144,8 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
boolean forceInPendingDelete;
|
||||
|
||||
@Override
|
||||
protected void initMutatingEppToolCommand() {
|
||||
protected void initMutatingEppToolCommand()
|
||||
throws ResourceFlowUtils.ResourceDoesNotExistException {
|
||||
if (!nameservers.isEmpty()) {
|
||||
checkArgument(
|
||||
addNameservers.isEmpty() && removeNameservers.isEmpty(),
|
||||
@@ -184,9 +183,7 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
ImmutableSet.Builder<String> autorenewGracePeriodWarningDomains = new ImmutableSet.Builder<>();
|
||||
DateTime now = clock.nowUtc();
|
||||
for (String domainName : domains) {
|
||||
Optional<Domain> domainOptional = loadByForeignKey(Domain.class, domainName, now);
|
||||
checkArgumentPresent(domainOptional, "Domain '%s' does not exist or is deleted", domainName);
|
||||
Domain domain = domainOptional.get();
|
||||
Domain domain = ResourceFlowUtils.loadAndVerifyExistence(Domain.class, domainName, now);
|
||||
checkArgument(
|
||||
!domain.getStatusValues().contains(SERVER_UPDATE_PROHIBITED),
|
||||
"The domain '%s' has status SERVER_UPDATE_PROHIBITED. Verify that you are allowed "
|
||||
|
||||
@@ -23,7 +23,7 @@ import com.beust.jcommander.Parameters;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.EppResourceUtils;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.billing.BillingBase.RenewalPriceBehavior;
|
||||
import google.registry.model.billing.BillingRecurrence;
|
||||
import google.registry.model.domain.Domain;
|
||||
@@ -164,7 +164,7 @@ public class UpdateRecurrenceCommand extends ConfirmingCommand {
|
||||
DateTime now = tm().getTransactionTime();
|
||||
for (String domainName : mainParameters) {
|
||||
Domain domain =
|
||||
EppResourceUtils.loadByForeignKey(Domain.class, domainName, now)
|
||||
ForeignKeyUtils.loadResource(Domain.class, domainName, now)
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalArgumentException(
|
||||
|
||||
@@ -35,7 +35,7 @@ final class UpdateRegistrarCommand extends CreateOrUpdateRegistrarCommand {
|
||||
@Override
|
||||
void checkModifyAllowedTlds(@Nullable Registrar oldRegistrar) {
|
||||
// Only allow modifying allowed TLDs if we're in a non-PRODUCTION environment, if the registrar
|
||||
// is not REAL, or the registrar has a WHOIS abuse contact set.
|
||||
// is not REAL, or the registrar has an RDAP abuse contact set.
|
||||
checkArgumentNotNull(oldRegistrar, "Old registrar was not present during modification");
|
||||
|
||||
boolean isRealRegistrar =
|
||||
@@ -43,9 +43,9 @@ final class UpdateRegistrarCommand extends CreateOrUpdateRegistrarCommand {
|
||||
|| (Registrar.Type.REAL.equals(oldRegistrar.getType()) && registrarType == null);
|
||||
if (RegistryEnvironment.PRODUCTION.equals(RegistryEnvironment.get()) && isRealRegistrar) {
|
||||
checkArgumentPresent(
|
||||
oldRegistrar.getWhoisAbuseContact(),
|
||||
"Cannot modify allowed TLDs if there is no WHOIS abuse contact set. Please use the"
|
||||
+ " \"nomulus registrar_contact\" command on this registrar to set a WHOIS abuse"
|
||||
oldRegistrar.getRdapAbuseContact(),
|
||||
"Cannot modify allowed TLDs if there is no RDAP abuse contact set. Please use the"
|
||||
+ " \"nomulus registrar_contact\" command on this registrar to set an RDAP abuse"
|
||||
+ " contact.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +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.tools;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.common.base.Joiner;
|
||||
import google.registry.whois.Whois;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.List;
|
||||
|
||||
/** Command to execute a WHOIS query. */
|
||||
@Parameters(separators = " =", commandDescription = "Manually perform a WHOIS query")
|
||||
final class WhoisQueryCommand implements Command {
|
||||
|
||||
@Parameter(
|
||||
description = "WHOIS query string",
|
||||
required = true)
|
||||
private List<String> mainParameters;
|
||||
|
||||
@Parameter(
|
||||
names = "--unicode",
|
||||
description = "When set, output will be Unicode")
|
||||
private boolean unicode;
|
||||
|
||||
@Parameter(names = "--full_output", description = "When set, the full output will be displayed")
|
||||
private boolean fullOutput;
|
||||
|
||||
@Inject
|
||||
Whois whois;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println(whois.lookup(Joiner.on(' ').join(mainParameters), unicode, fullOutput));
|
||||
}
|
||||
}
|
||||
+8
-7
@@ -23,7 +23,7 @@ import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.model.EppResourceUtils;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.billing.BillingRecurrence;
|
||||
import google.registry.model.common.TimeOfYear;
|
||||
import google.registry.model.domain.Domain;
|
||||
@@ -61,11 +61,12 @@ public class RecreateBillingRecurrencesCommand extends ConfirmingCommand {
|
||||
convertRecurrencesWithoutSaving(existingRecurrences);
|
||||
return String.format(
|
||||
"""
|
||||
Create new BillingRecurrence(s)?
|
||||
Existing recurrences:
|
||||
%s
|
||||
New recurrences:
|
||||
%s""",
|
||||
Create new BillingRecurrence(s)?
|
||||
Existing recurrences:
|
||||
%s
|
||||
New recurrences:
|
||||
%s\
|
||||
""",
|
||||
Joiner.on('\n').join(existingRecurrences), Joiner.on('\n').join(newRecurrences));
|
||||
});
|
||||
}
|
||||
@@ -114,7 +115,7 @@ public class RecreateBillingRecurrencesCommand extends ConfirmingCommand {
|
||||
DateTime now = tm().getTransactionTime();
|
||||
for (String domainName : mainParameters) {
|
||||
Domain domain =
|
||||
EppResourceUtils.loadByForeignKey(Domain.class, domainName, now)
|
||||
ForeignKeyUtils.loadResource(Domain.class, domainName, now)
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalArgumentException(
|
||||
|
||||
@@ -1,371 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.ui.server;
|
||||
|
||||
import static com.google.common.collect.Range.atLeast;
|
||||
import static com.google.common.collect.Range.atMost;
|
||||
import static com.google.common.collect.Range.closed;
|
||||
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
|
||||
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import com.google.re2j.Pattern;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarAddress;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.ui.forms.FormField;
|
||||
import google.registry.ui.forms.FormFieldException;
|
||||
import google.registry.ui.forms.FormFields;
|
||||
import google.registry.util.CidrAddressBlock;
|
||||
import google.registry.util.X509Utils;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Form fields for validating input for the {@code Registrar} class. */
|
||||
public final class RegistrarFormFields {
|
||||
|
||||
public static final Pattern BASE64_PATTERN = Pattern.compile("[+/a-zA-Z0-9]*");
|
||||
public static final Pattern ASCII_PATTERN = Pattern.compile("[[:ascii:]]*");
|
||||
public static final String ASCII_ERROR = "Please only use ASCII-US characters.";
|
||||
|
||||
public static final FormField<String, String> NAME_FIELD =
|
||||
FormFields.NAME.asBuilderNamed("registrarName")
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, DateTime> LAST_UPDATE_TIME =
|
||||
FormFields.LABEL
|
||||
.asBuilderNamed("lastUpdateTime")
|
||||
.transform(DateTime.class, RegistrarFormFields::parseDateTime)
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> EMAIL_ADDRESS_FIELD_REQUIRED =
|
||||
FormFields.EMAIL.asBuilderNamed("emailAddress")
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> EMAIL_ADDRESS_FIELD_OPTIONAL =
|
||||
FormFields.EMAIL.asBuilderNamed("emailAddress")
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> ICANN_REFERRAL_EMAIL_FIELD =
|
||||
FormFields.EMAIL.asBuilderNamed("icannReferralEmail")
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> PHONE_NUMBER_FIELD =
|
||||
FormFields.PHONE_NUMBER.asBuilder()
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> FAX_NUMBER_FIELD =
|
||||
FormFields.PHONE_NUMBER.asBuilderNamed("faxNumber")
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.build();
|
||||
|
||||
public static final FormField<List<String>, Set<String>> ALLOWED_TLDS_FIELD =
|
||||
FormFields.LABEL.asBuilderNamed("allowedTlds")
|
||||
.asSet()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> WHOIS_SERVER_FIELD =
|
||||
FormFields.LABEL.asBuilderNamed("whoisServer")
|
||||
.transform(RegistrarFormFields::parseHostname)
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> CLIENT_CERTIFICATE_HASH_FIELD =
|
||||
FormField.named("clientCertificateHash")
|
||||
.emptyToNull()
|
||||
.matches(BASE64_PATTERN, "Field must contain a base64 value.")
|
||||
.range(closed(1, 255))
|
||||
.build();
|
||||
|
||||
private static final FormField<String, String> X509_PEM_CERTIFICATE =
|
||||
FormField.named("certificate")
|
||||
.emptyToNull()
|
||||
.transform(
|
||||
input -> {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
X509Utils.loadCertificate(input);
|
||||
} catch (CertificateParsingException e) {
|
||||
throw new FormFieldException("Invalid X.509 PEM certificate");
|
||||
}
|
||||
return input;
|
||||
})
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> CLIENT_CERTIFICATE_FIELD =
|
||||
X509_PEM_CERTIFICATE.asBuilderNamed("clientCertificate").build();
|
||||
|
||||
public static final FormField<String, String> FAILOVER_CLIENT_CERTIFICATE_FIELD =
|
||||
X509_PEM_CERTIFICATE.asBuilderNamed("failoverClientCertificate").build();
|
||||
|
||||
public static final FormField<Long, Long> BILLING_IDENTIFIER_FIELD =
|
||||
FormField.named("billingIdentifier", Long.class)
|
||||
.range(atLeast(1))
|
||||
.build();
|
||||
|
||||
public static final FormField<Long, Long> IANA_IDENTIFIER_FIELD =
|
||||
FormField.named("ianaIdentifier", Long.class)
|
||||
.range(atLeast(1))
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> URL_FIELD =
|
||||
FormFields.MIN_TOKEN.asBuilderNamed("url")
|
||||
.build();
|
||||
|
||||
public static final FormField<List<String>, List<CidrAddressBlock>> IP_ADDRESS_ALLOW_LIST_FIELD =
|
||||
FormField.named("ipAddressAllowList")
|
||||
.emptyToNull()
|
||||
.transform(CidrAddressBlock.class, RegistrarFormFields::parseCidr)
|
||||
.asList()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> PASSWORD_FIELD =
|
||||
FormFields.PASSWORD.asBuilderNamed("password")
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> PHONE_PASSCODE_FIELD =
|
||||
FormField.named("phonePasscode")
|
||||
.emptyToNull()
|
||||
.matches(Registrar.PHONE_PASSCODE_PATTERN)
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> CONTACT_NAME_FIELD =
|
||||
FormFields.NAME.asBuilderNamed("name")
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> CONTACT_EMAIL_ADDRESS_FIELD =
|
||||
FormFields.EMAIL.asBuilderNamed("emailAddress")
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> REGISTRY_LOCK_EMAIL_ADDRESS_FIELD =
|
||||
FormFields.EMAIL.asBuilderNamed("registryLockEmailAddress").build();
|
||||
|
||||
public static final FormField<Boolean, Boolean> CONTACT_VISIBLE_IN_WHOIS_AS_ADMIN_FIELD =
|
||||
FormField.named("visibleInWhoisAsAdmin", Boolean.class)
|
||||
.build();
|
||||
|
||||
public static final FormField<Boolean, Boolean> CONTACT_VISIBLE_IN_WHOIS_AS_TECH_FIELD =
|
||||
FormField.named("visibleInWhoisAsTech", Boolean.class)
|
||||
.build();
|
||||
|
||||
public static final FormField<Boolean, Boolean>
|
||||
PHONE_AND_EMAIL_VISIBLE_IN_DOMAIN_WHOIS_AS_ABUSE_FIELD =
|
||||
FormField.named("visibleInDomainWhoisAsAbuse", Boolean.class).build();
|
||||
|
||||
public static final FormField<String, String> CONTACT_PHONE_NUMBER_FIELD =
|
||||
FormFields.PHONE_NUMBER.asBuilder().build();
|
||||
|
||||
public static final FormField<String, String> CONTACT_FAX_NUMBER_FIELD =
|
||||
FormFields.PHONE_NUMBER.asBuilderNamed("faxNumber").build();
|
||||
|
||||
public static final FormField<String, Set<RegistrarPoc.Type>> CONTACT_TYPES =
|
||||
FormField.named("types")
|
||||
.uppercased()
|
||||
.asEnum(RegistrarPoc.Type.class)
|
||||
.asSet(Splitter.on(',').omitEmptyStrings().trimResults())
|
||||
.build();
|
||||
|
||||
public static final FormField<List<Map<String, ?>>, List<Map<String, ?>>> CONTACTS_AS_MAPS =
|
||||
FormField.mapNamed("contacts").asList().build();
|
||||
|
||||
public static final FormField<List<String>, List<String>> I18N_STREET_FIELD =
|
||||
FormFields.XS_NORMALIZED_STRING.asBuilderNamed("street")
|
||||
.range(closed(1, 255))
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.asList()
|
||||
.range(closed(1, 3))
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<List<String>, List<String>> L10N_STREET_FIELD =
|
||||
FormFields.XS_NORMALIZED_STRING.asBuilderNamed("street")
|
||||
.range(closed(1, 255))
|
||||
.asList()
|
||||
.range(closed(1, 3))
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> I18N_CITY_FIELD =
|
||||
FormFields.NAME.asBuilderNamed("city")
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> L10N_CITY_FIELD =
|
||||
FormFields.NAME.asBuilderNamed("city")
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> I18N_STATE_FIELD =
|
||||
FormFields.XS_NORMALIZED_STRING.asBuilderNamed("state")
|
||||
.range(atMost(255))
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> L10N_STATE_FIELD =
|
||||
FormFields.XS_NORMALIZED_STRING.asBuilderNamed("state")
|
||||
.range(atMost(255))
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> I18N_ZIP_FIELD =
|
||||
FormFields.XS_TOKEN.asBuilderNamed("zip")
|
||||
.range(atMost(16))
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> L10N_ZIP_FIELD =
|
||||
FormFields.XS_TOKEN.asBuilderNamed("zip")
|
||||
.range(atMost(16))
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> COUNTRY_CODE_FIELD =
|
||||
FormFields.COUNTRY_CODE.asBuilder()
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<Map<String, ?>, RegistrarAddress> L10N_ADDRESS_FIELD =
|
||||
FormField.mapNamed("localizedAddress")
|
||||
.transform(
|
||||
RegistrarAddress.class,
|
||||
args ->
|
||||
toNewAddress(
|
||||
args, L10N_STREET_FIELD, L10N_CITY_FIELD, L10N_STATE_FIELD, L10N_ZIP_FIELD))
|
||||
.build();
|
||||
|
||||
@Nullable
|
||||
private static RegistrarAddress toNewAddress(
|
||||
@Nullable Map<String, ?> args,
|
||||
final FormField<List<String>, List<String>> streetField,
|
||||
final FormField<String, String> cityField,
|
||||
final FormField<String, String> stateField,
|
||||
final FormField<String, String> zipField) {
|
||||
if (args == null) {
|
||||
return null;
|
||||
}
|
||||
RegistrarAddress.Builder builder = new RegistrarAddress.Builder();
|
||||
String countryCode = COUNTRY_CODE_FIELD.extractUntyped(args).get();
|
||||
builder.setCountryCode(countryCode);
|
||||
streetField
|
||||
.extractUntyped(args)
|
||||
.ifPresent(streets -> builder.setStreet(ImmutableList.copyOf(streets)));
|
||||
cityField.extractUntyped(args).ifPresent(builder::setCity);
|
||||
Optional<String> stateFieldValue = stateField.extractUntyped(args);
|
||||
if (stateFieldValue.isPresent()) {
|
||||
String state = stateFieldValue.get();
|
||||
if ("US".equals(countryCode)) {
|
||||
state = Ascii.toUpperCase(state);
|
||||
if (!StateCode.US_MAP.containsKey(state)) {
|
||||
throw new FormFieldException(stateField, "Unknown US state code.");
|
||||
}
|
||||
}
|
||||
builder.setState(state);
|
||||
}
|
||||
zipField.extractUntyped(args).ifPresent(builder::setZip);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static CidrAddressBlock parseCidr(String input) {
|
||||
try {
|
||||
return input != null ? CidrAddressBlock.create(input) : null;
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new FormFieldException("Not a valid CIDR notation IP-address block.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String parseHostname(@Nullable String input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
if (!InternetDomainName.isValid(input)) {
|
||||
throw new FormFieldException("Not a valid hostname.");
|
||||
}
|
||||
return canonicalizeHostname(input);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static DateTime parseDateTime(@Nullable String input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return DateTime.parse(input);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new FormFieldException("Not a valid ISO date-time string.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static ImmutableList<RegistrarPoc.Builder> getRegistrarContactBuilders(
|
||||
ImmutableSet<RegistrarPoc> existingContacts, @Nullable Map<String, ?> args) {
|
||||
if (args == null) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
Optional<List<Map<String, ?>>> contactsAsMaps = CONTACTS_AS_MAPS.extractUntyped(args);
|
||||
if (contactsAsMaps.isEmpty()) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
ImmutableList.Builder<RegistrarPoc.Builder> result = new ImmutableList.Builder<>();
|
||||
for (Map<String, ?> contactAsMap : contactsAsMaps.get()) {
|
||||
String emailAddress =
|
||||
CONTACT_EMAIL_ADDRESS_FIELD
|
||||
.extractUntyped(contactAsMap)
|
||||
.orElseThrow(
|
||||
() -> new IllegalArgumentException("Contacts from UI must have email addresses"));
|
||||
// Start with a new builder if the contact didn't previously exist
|
||||
RegistrarPoc.Builder contactBuilder =
|
||||
existingContacts.stream()
|
||||
.filter(rc -> rc.getEmailAddress().equals(emailAddress))
|
||||
.findFirst()
|
||||
.map(RegistrarPoc::asBuilder)
|
||||
.orElse(new RegistrarPoc.Builder());
|
||||
applyRegistrarContactArgs(contactBuilder, contactAsMap);
|
||||
result.add(contactBuilder);
|
||||
}
|
||||
return result.build();
|
||||
}
|
||||
|
||||
private static void applyRegistrarContactArgs(RegistrarPoc.Builder builder, Map<String, ?> args) {
|
||||
builder.setName(CONTACT_NAME_FIELD.extractUntyped(args).orElse(null));
|
||||
builder.setEmailAddress(CONTACT_EMAIL_ADDRESS_FIELD.extractUntyped(args).orElse(null));
|
||||
builder.setVisibleInWhoisAsAdmin(
|
||||
CONTACT_VISIBLE_IN_WHOIS_AS_ADMIN_FIELD.extractUntyped(args).orElse(false));
|
||||
builder.setVisibleInWhoisAsTech(
|
||||
CONTACT_VISIBLE_IN_WHOIS_AS_TECH_FIELD.extractUntyped(args).orElse(false));
|
||||
builder.setVisibleInDomainWhoisAsAbuse(
|
||||
PHONE_AND_EMAIL_VISIBLE_IN_DOMAIN_WHOIS_AS_ABUSE_FIELD.extractUntyped(args).orElse(false));
|
||||
builder.setPhoneNumber(CONTACT_PHONE_NUMBER_FIELD.extractUntyped(args).orElse(null));
|
||||
builder.setFaxNumber(CONTACT_FAX_NUMBER_FIELD.extractUntyped(args).orElse(null));
|
||||
builder.setTypes(CONTACT_TYPES.extractUntyped(args).orElse(ImmutableSet.of()));
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import google.registry.model.EppResourceUtils;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.domain.Domain;
|
||||
@@ -55,7 +55,7 @@ public class ConsoleDomainGetAction extends ConsoleApiAction {
|
||||
Optional<Domain> possibleDomain =
|
||||
tm().transact(
|
||||
() ->
|
||||
EppResourceUtils.loadByForeignKeyByCacheIfEnabled(
|
||||
ForeignKeyUtils.loadResourceByCacheIfEnabled(
|
||||
Domain.class, paramDomain, tm().getTransactionTime()));
|
||||
if (possibleDomain.isEmpty()) {
|
||||
consoleApiParams.response().setStatus(SC_NOT_FOUND);
|
||||
|
||||
+4
-4
@@ -79,17 +79,17 @@ public class ConsoleUpdateRegistrarAction extends ConsoleApiAction {
|
||||
registrarParam.getRegistrarId());
|
||||
|
||||
// Only allow modifying allowed TLDs if we're in a non-PRODUCTION environment, if the
|
||||
// registrar is not REAL, or the registrar has a WHOIS abuse contact set.
|
||||
// registrar is not REAL, or the registrar has a RDAP abuse contact set.
|
||||
if (!registrarParam.getAllowedTlds().isEmpty()) {
|
||||
boolean isRealRegistrar =
|
||||
Registrar.Type.REAL.equals(existingRegistrar.get().getType());
|
||||
if (RegistryEnvironment.PRODUCTION.equals(RegistryEnvironment.get())
|
||||
&& isRealRegistrar) {
|
||||
checkArgumentPresent(
|
||||
existingRegistrar.get().getWhoisAbuseContact(),
|
||||
"Cannot modify allowed TLDs if there is no WHOIS abuse contact set. Please"
|
||||
existingRegistrar.get().getRdapAbuseContact(),
|
||||
"Cannot modify allowed TLDs if there is no RDAP abuse contact set. Please"
|
||||
+ " use the \"nomulus registrar_contact\" command on this registrar to"
|
||||
+ " set a WHOIS abuse contact.");
|
||||
+ " set a RDAP abuse contact.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ package google.registry.ui.server.console;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.model.console.RegistrarRole.ACCOUNT_MANAGER;
|
||||
import static google.registry.model.console.RegistrarRole.TECH_CONTACT;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.request.Action.Method.DELETE;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
@@ -152,7 +153,7 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
updateUserRegistrarRoles(
|
||||
this.userData.get().emailAddress,
|
||||
registrarId,
|
||||
RegistrarRole.valueOf(this.userData.get().role));
|
||||
requestRoleToAllowedRoles(this.userData.get().role));
|
||||
|
||||
sendConfirmationEmail(registrarId, this.userData.get().emailAddress, "Added existing user");
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
@@ -222,11 +223,9 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
throw e;
|
||||
}
|
||||
|
||||
RegistrarRole newRole = requestRoleToAllowedRoles(userData.get().role);
|
||||
UserRoles userRoles =
|
||||
new UserRoles.Builder()
|
||||
.setRegistrarRoles(
|
||||
ImmutableMap.of(registrarId, RegistrarRole.valueOf(userData.get().role)))
|
||||
.build();
|
||||
new UserRoles.Builder().setRegistrarRoles(ImmutableMap.of(registrarId, newRole)).build();
|
||||
|
||||
User.Builder builder = new User.Builder().setUserRoles(userRoles).setEmailAddress(newEmail);
|
||||
tm().put(builder.build());
|
||||
@@ -238,9 +237,7 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
.setPayload(
|
||||
consoleApiParams
|
||||
.gson()
|
||||
.toJson(
|
||||
new UserData(
|
||||
newEmail, null, ACCOUNT_MANAGER.toString(), newUser.getPassword())));
|
||||
.toJson(new UserData(newEmail, null, newRole.toString(), newUser.getPassword())));
|
||||
finishAndPersistConsoleUpdateHistory(
|
||||
new ConsoleUpdateHistory.Builder()
|
||||
.setType(ConsoleUpdateHistory.Type.USER_CREATE)
|
||||
@@ -257,7 +254,7 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
updateUserRegistrarRoles(
|
||||
this.userData.get().emailAddress,
|
||||
registrarId,
|
||||
RegistrarRole.valueOf(this.userData.get().role));
|
||||
requestRoleToAllowedRoles(this.userData.get().role));
|
||||
|
||||
sendConfirmationEmail(registrarId, this.userData.get().emailAddress, "Updated user");
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
@@ -333,6 +330,11 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
.collect(toImmutableList()));
|
||||
}
|
||||
|
||||
/** Maps a request role string to a RegistrarRole, using ACCOUNT_MANAGER as the default. */
|
||||
private RegistrarRole requestRoleToAllowedRoles(String role) {
|
||||
return TECH_CONTACT.name().equals(role) ? TECH_CONTACT : ACCOUNT_MANAGER;
|
||||
}
|
||||
|
||||
private boolean sendConfirmationEmail(String registrarId, String emailAddress, String operation) {
|
||||
Optional<Registrar> registrar = Registrar.loadByRegistrarId(registrarId);
|
||||
if (registrar.isEmpty()) { // Shouldn't happen, but worth checking
|
||||
|
||||
@@ -106,9 +106,9 @@ public class ContactAction extends ConsoleApiAction {
|
||||
new RegistrarPoc()
|
||||
.asBuilder()
|
||||
.setTypes(newContact.getTypes())
|
||||
.setVisibleInWhoisAsTech(newContact.getVisibleInWhoisAsTech())
|
||||
.setVisibleInWhoisAsAdmin(newContact.getVisibleInWhoisAsAdmin())
|
||||
.setVisibleInDomainWhoisAsAbuse(newContact.getVisibleInDomainWhoisAsAbuse())
|
||||
.setVisibleInRdapAsTech(newContact.getVisibleInRdapAsTech())
|
||||
.setVisibleInRdapAsAdmin(newContact.getVisibleInRdapAsAdmin())
|
||||
.setVisibleInDomainRdapAsAbuse(newContact.getVisibleInDomainRdapAsAbuse())
|
||||
.setFaxNumber(newContact.getFaxNumber())
|
||||
.setName(newContact.getName())
|
||||
.setEmailAddress(newContact.getEmailAddress())
|
||||
@@ -133,10 +133,10 @@ public class ContactAction extends ConsoleApiAction {
|
||||
? oldContact
|
||||
.asBuilder()
|
||||
.setTypes(updatedContact.getTypes())
|
||||
.setVisibleInWhoisAsTech(updatedContact.getVisibleInWhoisAsTech())
|
||||
.setVisibleInWhoisAsAdmin(updatedContact.getVisibleInWhoisAsAdmin())
|
||||
.setVisibleInDomainWhoisAsAbuse(
|
||||
updatedContact.getVisibleInDomainWhoisAsAbuse())
|
||||
.setVisibleInRdapAsTech(updatedContact.getVisibleInRdapAsTech())
|
||||
.setVisibleInRdapAsAdmin(updatedContact.getVisibleInRdapAsAdmin())
|
||||
.setVisibleInDomainRdapAsAbuse(
|
||||
updatedContact.getVisibleInDomainRdapAsAbuse())
|
||||
.setFaxNumber(updatedContact.getFaxNumber())
|
||||
.setName(updatedContact.getName())
|
||||
.setEmailAddress(updatedContact.getEmailAddress())
|
||||
@@ -231,19 +231,19 @@ public class ContactAction extends ConsoleApiAction {
|
||||
|
||||
enforcePrimaryContactRestrictions(oldContactsByType, newContactsByType);
|
||||
ensurePhoneNumberNotRemovedForContactTypes(oldContactsByType, newContactsByType, Type.TECH);
|
||||
Optional<RegistrarPoc> domainWhoisAbuseContact =
|
||||
getDomainWhoisVisibleAbuseContact(updatedContacts);
|
||||
// If the new set has a domain WHOIS abuse contact, it must have a phone number.
|
||||
if (domainWhoisAbuseContact.isPresent()
|
||||
&& domainWhoisAbuseContact.get().getPhoneNumber() == null) {
|
||||
Optional<RegistrarPoc> domainRdapAbuseContact =
|
||||
getDomainRdapVisibleAbuseContact(updatedContacts);
|
||||
// If the new set has a domain RDAP abuse contact, it must have a phone number.
|
||||
if (domainRdapAbuseContact.isPresent()
|
||||
&& domainRdapAbuseContact.get().getPhoneNumber() == null) {
|
||||
throw new ContactRequirementException(
|
||||
"The abuse contact visible in domain WHOIS query must have a phone number");
|
||||
"The abuse contact visible in domain RDAP query must have a phone number");
|
||||
}
|
||||
// If there was a domain WHOIS abuse contact in the old set, the new set must have one.
|
||||
if (getDomainWhoisVisibleAbuseContact(existingContacts).isPresent()
|
||||
&& domainWhoisAbuseContact.isEmpty()) {
|
||||
// If there was a domain RDAP abuse contact in the old set, the new set must have one.
|
||||
if (getDomainRdapVisibleAbuseContact(existingContacts).isPresent()
|
||||
&& domainRdapAbuseContact.isEmpty()) {
|
||||
throw new ContactRequirementException(
|
||||
"An abuse contact visible in domain WHOIS query must be designated");
|
||||
"An abuse contact visible in domain RDAP query must be designated");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,17 +265,17 @@ public class ContactAction extends ConsoleApiAction {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the registrar contact whose phone number and email address is visible in domain WHOIS
|
||||
* Retrieves the registrar contact whose phone number and email address is visible in domain RDAP
|
||||
* query as abuse contact (if any).
|
||||
*
|
||||
* <p>Frontend processing ensures that only one contact can be set as abuse contact in domain
|
||||
* WHOIS record.
|
||||
* <p>Frontend processing ensures that only one contact can be set as abuse contact in domain RDAP
|
||||
* record.
|
||||
*
|
||||
* <p>Therefore, it is possible to return inside the loop once one such contact is found.
|
||||
*/
|
||||
private static Optional<RegistrarPoc> getDomainWhoisVisibleAbuseContact(
|
||||
private static Optional<RegistrarPoc> getDomainRdapVisibleAbuseContact(
|
||||
Set<RegistrarPoc> contacts) {
|
||||
return contacts.stream().filter(RegistrarPoc::getVisibleInDomainWhoisAsAbuse).findFirst();
|
||||
return contacts.stream().filter(RegistrarPoc::getVisibleInDomainRdapAsAbuse).findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
-3
@@ -38,10 +38,10 @@ import java.util.Optional;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
/**
|
||||
* Console action for editing fields on a registrar that are visible in WHOIS/RDAP.
|
||||
* Console action for editing fields on a registrar that are visible in RDAP.
|
||||
*
|
||||
* <p>This doesn't cover many of the registrar fields but rather only those that are visible in
|
||||
* WHOIS/RDAP and don't have any other obvious means of edit.
|
||||
* <p>This doesn't cover many of the registrar fields but rather only those that are visible in RDAP
|
||||
* and don't have any other obvious means of edit.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.DEFAULT,
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// Copyright 2021 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.whois;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.whois.WhoisMetrics.WhoisMetric;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
|
||||
/**
|
||||
* Dagger base module for the whois package.
|
||||
*
|
||||
* <p>Provides whois objects common to both the normal ({@link WhoisModule}) and non-caching ({@link
|
||||
* NonCachingWhoisModule}) implementations.
|
||||
*/
|
||||
@Module
|
||||
class BaseWhoisModule {
|
||||
|
||||
@Provides
|
||||
@SuppressWarnings("CloseableProvides")
|
||||
static Reader provideHttpInputReader(HttpServletRequest req) {
|
||||
try {
|
||||
return req.getReader();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a {@link WhoisMetrics.WhoisMetric.Builder} with the startTimestamp already
|
||||
* initialized.
|
||||
*/
|
||||
@Provides
|
||||
static WhoisMetric.Builder provideEppMetricBuilder(Clock clock) {
|
||||
return WhoisMetric.builderForRequest(clock);
|
||||
}
|
||||
}
|
||||
@@ -1,121 +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.whois;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.isBlockedByBsa;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCache;
|
||||
import static google.registry.model.tld.Tlds.findTldForName;
|
||||
import static google.registry.model.tld.Tlds.getTlds;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Verify;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.tld.Tld;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Represents a WHOIS lookup on a domain name (i.e. SLD). */
|
||||
public class DomainLookupCommand implements WhoisCommand {
|
||||
|
||||
private static final String ERROR_PREFIX = "Domain";
|
||||
|
||||
@VisibleForTesting final InternetDomainName domainName;
|
||||
|
||||
private final boolean fullOutput;
|
||||
private final boolean cached;
|
||||
private final String whoisRedactedEmailText;
|
||||
private final String domainBlockedByBsaTemplate;
|
||||
|
||||
public DomainLookupCommand(
|
||||
InternetDomainName domainName,
|
||||
boolean fullOutput,
|
||||
boolean cached,
|
||||
String whoisRedactedEmailText,
|
||||
String domainBlockedByBsaTemplate) {
|
||||
this.domainName = checkNotNull(domainName, "domainOrHostName");
|
||||
this.fullOutput = fullOutput;
|
||||
this.cached = cached;
|
||||
this.whoisRedactedEmailText = whoisRedactedEmailText;
|
||||
this.domainBlockedByBsaTemplate = domainBlockedByBsaTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final WhoisResponse executeQuery(final DateTime now) throws WhoisException {
|
||||
Optional<InternetDomainName> tld = findTldForName(domainName);
|
||||
// Google Registry Policy: Do not return records under TLDs for which we're not
|
||||
// authoritative.
|
||||
if (tld.isEmpty() || !getTlds().contains(tld.get().toString())) {
|
||||
throw new WhoisException(now, SC_NOT_FOUND, ERROR_PREFIX + " not found.");
|
||||
}
|
||||
// Include `getResponse` and `isBlockedByBsa` in one transaction to reduce latency.
|
||||
// Must pass the exceptions outside to throw.
|
||||
ResponseOrException result =
|
||||
replicaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
final Optional<WhoisResponse> response = getResponse(domainName, now);
|
||||
if (response.isPresent()) {
|
||||
return ResponseOrException.of(response.get());
|
||||
}
|
||||
|
||||
String label = domainName.parts().get(0);
|
||||
String tldStr = tld.get().toString();
|
||||
if (isBlockedByBsa(label, Tld.get(tldStr), now)) {
|
||||
return ResponseOrException.of(
|
||||
new WhoisException(
|
||||
now,
|
||||
SC_NOT_FOUND,
|
||||
String.format(domainBlockedByBsaTemplate, domainName)));
|
||||
}
|
||||
|
||||
return ResponseOrException.of(
|
||||
new WhoisException(now, SC_NOT_FOUND, ERROR_PREFIX + " not found."));
|
||||
});
|
||||
return result.returnOrThrow();
|
||||
}
|
||||
|
||||
private Optional<WhoisResponse> getResponse(InternetDomainName domainName, DateTime now) {
|
||||
Optional<Domain> domainResource =
|
||||
cached
|
||||
? loadByForeignKeyByCache(Domain.class, domainName.toString(), now)
|
||||
: loadByForeignKey(Domain.class, domainName.toString(), now);
|
||||
return domainResource.map(
|
||||
domain -> new DomainWhoisResponse(domain, fullOutput, whoisRedactedEmailText, now));
|
||||
}
|
||||
|
||||
record ResponseOrException(
|
||||
Optional<WhoisResponse> whoisResponse, Optional<WhoisException> exception) {
|
||||
|
||||
WhoisResponse returnOrThrow() throws WhoisException {
|
||||
Verify.verify(
|
||||
whoisResponse().isPresent() || exception().isPresent(),
|
||||
"Response and exception must not both be missing.");
|
||||
return whoisResponse().orElseThrow(() -> exception().get());
|
||||
}
|
||||
|
||||
static ResponseOrException of(WhoisResponse response) {
|
||||
return new ResponseOrException(Optional.of(response), Optional.empty());
|
||||
}
|
||||
|
||||
static ResponseOrException of(WhoisException exception) {
|
||||
return new ResponseOrException(Optional.empty(), Optional.of(exception));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,208 +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.whois;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static google.registry.util.CollectionUtils.isNullOrEmpty;
|
||||
import static google.registry.xml.UtcDateTimeAdapter.getFormattedString;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.adapters.EnumToAttributeAdapter.EppEnum;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactPhoneNumber;
|
||||
import google.registry.model.contact.PostalInfo;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.DesignatedContact.Type;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Represents a WHOIS response to a domain query. */
|
||||
final class DomainWhoisResponse extends WhoisResponseImpl {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
/** Prefix for status value URLs. */
|
||||
private static final String ICANN_STATUS_URL_PREFIX = "https://icann.org/epp#";
|
||||
|
||||
/** Message required to be appended to all domain WHOIS responses. */
|
||||
private static final String ICANN_AWIP_INFO_MESSAGE =
|
||||
"For more information on Whois status codes, please visit https://icann.org/epp\r\n";
|
||||
|
||||
/** Domain which was the target of this WHOIS command. */
|
||||
private final Domain domain;
|
||||
|
||||
/** Whether the full WHOIS output is to be displayed. */
|
||||
private final boolean fullOutput;
|
||||
|
||||
/** When fullOutput is false, the text to display for the registrant's email fields. */
|
||||
private final String whoisRedactedEmailText;
|
||||
|
||||
/** Creates new WHOIS domain response on the given domain. */
|
||||
DomainWhoisResponse(
|
||||
Domain domain, boolean fullOutput, String whoisRedactedEmailText, DateTime timestamp) {
|
||||
super(timestamp);
|
||||
this.domain = checkNotNull(domain, "domain");
|
||||
this.fullOutput = fullOutput;
|
||||
this.whoisRedactedEmailText = whoisRedactedEmailText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WhoisResponseResults getResponse(final boolean preferUnicode, String disclaimer) {
|
||||
Optional<Registrar> registrarOptional =
|
||||
Registrar.loadByRegistrarIdCached(domain.getCurrentSponsorRegistrarId());
|
||||
checkState(
|
||||
registrarOptional.isPresent(),
|
||||
"Could not load registrar %s",
|
||||
domain.getCurrentSponsorRegistrarId());
|
||||
Registrar registrar = registrarOptional.get();
|
||||
Optional<RegistrarPoc> abuseContact =
|
||||
registrar.getPocsFromReplica().stream()
|
||||
.filter(RegistrarPoc::getVisibleInDomainWhoisAsAbuse)
|
||||
.findFirst();
|
||||
return WhoisResponseResults.create(
|
||||
new DomainEmitter()
|
||||
.emitField("Domain Name", maybeFormatHostname(domain.getDomainName(), preferUnicode))
|
||||
.emitField("Registry Domain ID", domain.getRepoId())
|
||||
.emitField("Registrar WHOIS Server", registrar.getWhoisServer())
|
||||
.emitField("Registrar URL", registrar.getUrl())
|
||||
.emitFieldIfDefined("Updated Date", getFormattedString(domain.getLastEppUpdateTime()))
|
||||
.emitField("Creation Date", getFormattedString(domain.getCreationTime()))
|
||||
.emitField(
|
||||
"Registry Expiry Date", getFormattedString(domain.getRegistrationExpirationTime()))
|
||||
.emitField("Registrar", registrar.getRegistrarName())
|
||||
.emitField("Registrar IANA ID", Objects.toString(registrar.getIanaIdentifier(), ""))
|
||||
// Email address is a required field for registrar contacts. Therefore as long as there
|
||||
// is an abuse contact, we can get an email address from it.
|
||||
.emitField(
|
||||
"Registrar Abuse Contact Email",
|
||||
abuseContact.map(RegistrarPoc::getEmailAddress).orElse(""))
|
||||
.emitField(
|
||||
"Registrar Abuse Contact Phone",
|
||||
abuseContact.map(RegistrarPoc::getPhoneNumber).orElse(""))
|
||||
.emitStatusValues(domain.getStatusValues(), domain.getGracePeriods())
|
||||
// TODO(mcilwain): Investigate if the WHOIS spec requires us to always output REDACTED
|
||||
// text in WHOIS even if the contact is not present, and if so, do so.
|
||||
.emitContact("Registrant", domain.getRegistrant(), preferUnicode)
|
||||
.emitContact("Admin", getContactReference(Type.ADMIN), preferUnicode)
|
||||
.emitContact("Tech", getContactReference(Type.TECH), preferUnicode)
|
||||
.emitContact("Billing", getContactReference(Type.BILLING), preferUnicode)
|
||||
.emitSet(
|
||||
"Name Server",
|
||||
domain.loadNameserverHostNames(),
|
||||
hostName -> maybeFormatHostname(hostName, preferUnicode))
|
||||
.emitField(
|
||||
"DNSSEC", isNullOrEmpty(domain.getDsData()) ? "unsigned" : "signedDelegation")
|
||||
.emitWicfLink()
|
||||
.emitLastUpdated(getTimestamp())
|
||||
.emitAwipMessage()
|
||||
.emitFooter(disclaimer)
|
||||
.toString(),
|
||||
1);
|
||||
}
|
||||
|
||||
/** Returns the contact of the given type. */
|
||||
private Optional<VKey<Contact>> getContactReference(Type type) {
|
||||
Optional<DesignatedContact> contactOfType =
|
||||
domain.getContacts().stream().filter(d -> d.getType() == type).findFirst();
|
||||
return contactOfType.map(DesignatedContact::getContactKey);
|
||||
}
|
||||
|
||||
/** Output emitter with logic for domains. */
|
||||
class DomainEmitter extends Emitter<DomainEmitter> {
|
||||
DomainEmitter emitPhone(
|
||||
String contactType, String title, @Nullable ContactPhoneNumber phoneNumber) {
|
||||
if (phoneNumber == null) {
|
||||
return this;
|
||||
}
|
||||
return emitFieldIfDefined(
|
||||
ImmutableList.of(contactType, title), phoneNumber.getPhoneNumber(), fullOutput)
|
||||
.emitFieldIfDefined(
|
||||
ImmutableList.of(contactType, title, "Ext"), phoneNumber.getExtension(), fullOutput);
|
||||
}
|
||||
|
||||
/** Emit the contact entry of the given type. */
|
||||
DomainEmitter emitContact(
|
||||
String contactType, Optional<VKey<Contact>> contact, boolean preferUnicode) {
|
||||
if (contact.isEmpty()) {
|
||||
return this;
|
||||
}
|
||||
// If we refer to a contact that doesn't exist, that's a bug. It means referential integrity
|
||||
// has somehow been broken. We skip the rest of this contact, but log it to hopefully bring it
|
||||
// someone's attention.
|
||||
Contact contact1 = EppResource.loadByCache(contact.get());
|
||||
if (contact1 == null) {
|
||||
logger.atSevere().log(
|
||||
"(BUG) Broken reference found from domain %s to contact %s.",
|
||||
domain.getDomainName(), contact);
|
||||
return this;
|
||||
}
|
||||
PostalInfo postalInfo =
|
||||
chooseByUnicodePreference(
|
||||
preferUnicode,
|
||||
contact1.getLocalizedPostalInfo(),
|
||||
contact1.getInternationalizedPostalInfo());
|
||||
// ICANN Consistent Labeling & Display policy requires that this be the ROID.
|
||||
emitField(ImmutableList.of("Registry", contactType, "ID"), contact1.getRepoId(), fullOutput);
|
||||
if (postalInfo != null) {
|
||||
emitFieldIfDefined(ImmutableList.of(contactType, "Name"), postalInfo.getName(), fullOutput);
|
||||
emitFieldIfDefined(
|
||||
ImmutableList.of(contactType, "Organization"),
|
||||
postalInfo.getOrg(),
|
||||
fullOutput || contactType.equals("Registrant"));
|
||||
emitAddress(contactType, postalInfo.getAddress(), fullOutput);
|
||||
}
|
||||
emitPhone(contactType, "Phone", contact1.getVoiceNumber());
|
||||
emitPhone(contactType, "Fax", contact1.getFaxNumber());
|
||||
String emailFieldContent = fullOutput ? contact1.getEmailAddress() : whoisRedactedEmailText;
|
||||
emitField(ImmutableList.of(contactType, "Email"), emailFieldContent);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Emits status values and grace periods as a set, in the AWIP format. */
|
||||
DomainEmitter emitStatusValues(
|
||||
Set<StatusValue> statusValues, Set<GracePeriod> gracePeriods) {
|
||||
ImmutableSet.Builder<EppEnum> combinedStatuses = new ImmutableSet.Builder<>();
|
||||
combinedStatuses.addAll(statusValues);
|
||||
for (GracePeriod gracePeriod : gracePeriods) {
|
||||
combinedStatuses.add(gracePeriod.getType());
|
||||
}
|
||||
return emitSet(
|
||||
"Domain Status",
|
||||
combinedStatuses.build(),
|
||||
status -> {
|
||||
String xmlName = status.getXmlName();
|
||||
return String.format("%s %s%s", xmlName, ICANN_STATUS_URL_PREFIX, xmlName);
|
||||
});
|
||||
}
|
||||
|
||||
/** Emits the message that AWIP requires accompany all domain WHOIS responses. */
|
||||
DomainEmitter emitAwipMessage() {
|
||||
return emitRawLine(ICANN_AWIP_INFO_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +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.whois;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCache;
|
||||
import static google.registry.model.tld.Tlds.findTldForName;
|
||||
import static google.registry.model.tld.Tlds.getTlds;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.model.host.Host;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Represents a WHOIS lookup on a nameserver based on its hostname. */
|
||||
public class NameserverLookupByHostCommand implements WhoisCommand {
|
||||
|
||||
private static final String ERROR_PREFIX = "Nameserver";
|
||||
|
||||
@VisibleForTesting final InternetDomainName hostName;
|
||||
|
||||
boolean cached;
|
||||
|
||||
NameserverLookupByHostCommand(InternetDomainName hostName, boolean cached) {
|
||||
this.hostName = checkNotNull(hostName, "hostName");
|
||||
this.cached = cached;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final WhoisResponse executeQuery(final DateTime now) throws WhoisException {
|
||||
Optional<InternetDomainName> tld = findTldForName(hostName);
|
||||
// Google Registry Policy: Do not return records under TLDs for which we're not authoritative.
|
||||
if (tld.isPresent() && getTlds().contains(tld.get().toString())) {
|
||||
final Optional<WhoisResponse> response = getResponse(hostName, now);
|
||||
if (response.isPresent()) {
|
||||
return response.get();
|
||||
}
|
||||
}
|
||||
throw new WhoisException(now, SC_NOT_FOUND, ERROR_PREFIX + " not found.");
|
||||
}
|
||||
|
||||
private Optional<WhoisResponse> getResponse(InternetDomainName hostName, DateTime now) {
|
||||
Optional<Host> host =
|
||||
cached
|
||||
? loadByForeignKeyByCache(Host.class, hostName.toString(), now)
|
||||
: loadByForeignKey(Host.class, hostName.toString(), now);
|
||||
return host.map(h -> new NameserverWhoisResponse(h, now));
|
||||
}
|
||||
}
|
||||
@@ -1,84 +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.whois;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.tld.Tlds;
|
||||
import java.net.InetAddress;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* Represents a WHOIS lookup for a nameserver based on its IP.
|
||||
*
|
||||
* <p>Both IPv4 and IPv6 addresses are supported. Unlike other WHOIS commands, this is an eventually
|
||||
* consistent query.
|
||||
*
|
||||
* <p><b>Note:</b> There may be multiple nameservers with the same IP.
|
||||
*/
|
||||
final class NameserverLookupByIpCommand implements WhoisCommand {
|
||||
|
||||
@VisibleForTesting
|
||||
final InetAddress ipAddress;
|
||||
|
||||
NameserverLookupByIpCommand(InetAddress ipAddress) {
|
||||
this.ipAddress = checkNotNull(ipAddress, "ipAddress");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public WhoisResponse executeQuery(DateTime now) throws WhoisException {
|
||||
Iterable<Host> hostsFromDb;
|
||||
hostsFromDb =
|
||||
replicaTm()
|
||||
.transact(
|
||||
() ->
|
||||
// We cannot query @Convert-ed fields in HQL, so we must use native Postgres.
|
||||
replicaTm()
|
||||
.getEntityManager()
|
||||
/*
|
||||
* Using array_operator <@ (contained-by) with gin index on inet_address.
|
||||
* Without gin index, this is slightly slower than the alternative form of
|
||||
* ':address = ANY(inet_address)'.
|
||||
*/
|
||||
.createNativeQuery(
|
||||
"SELECT * From \"Host\" WHERE "
|
||||
+ "ARRAY[ CAST(:address AS TEXT) ] <@ inet_addresses AND "
|
||||
+ "deletion_time > CAST(:now AS timestamptz)",
|
||||
Host.class)
|
||||
.setParameter("address", InetAddresses.toAddrString(ipAddress))
|
||||
.setParameter("now", now.toString())
|
||||
.getResultList());
|
||||
ImmutableList<Host> hosts =
|
||||
Streams.stream(hostsFromDb)
|
||||
.filter(
|
||||
host ->
|
||||
Tlds.findTldForName(InternetDomainName.from(host.getHostName())).isPresent())
|
||||
.collect(toImmutableList());
|
||||
if (hosts.isEmpty()) {
|
||||
throw new WhoisException(now, SC_NOT_FOUND, "No nameservers found.");
|
||||
}
|
||||
return new NameserverWhoisResponse(hosts, now);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user