mirror of
https://github.com/google/nomulus
synced 2026-08-06 15:26:12 +00:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6868b771b | ||
|
|
f34aec8b56 | ||
|
|
b27b077638 | ||
|
|
0e8cd75a58 | ||
|
|
2a1748ba9c | ||
|
|
f4889191a4 | ||
|
|
9eddecf70f | ||
|
|
d4bcff0c31 | ||
|
|
62065f88fb |
@@ -185,7 +185,8 @@ public class CheckApiAction implements Runnable {
|
||||
}
|
||||
|
||||
private boolean checkExists(String domainString, DateTime now) {
|
||||
return !ForeignKeyUtils.loadCached(Domain.class, ImmutableList.of(domainString), now).isEmpty();
|
||||
return !ForeignKeyUtils.loadByCache(Domain.class, ImmutableList.of(domainString), now)
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
private Optional<String> checkReserved(InternetDomainName domainName) {
|
||||
|
||||
@@ -432,7 +432,7 @@ public final class DomainCheckFlow implements TransactionalFlow {
|
||||
.filter(existingDomains::containsKey)
|
||||
.collect(toImmutableMap(d -> d, existingDomains::get));
|
||||
ImmutableMap<VKey<? extends EppResource>, EppResource> loadedDomains =
|
||||
EppResource.loadCached(ImmutableList.copyOf(existingDomainsToLoad.values()));
|
||||
EppResource.loadByCacheIfEnabled(ImmutableList.copyOf(existingDomainsToLoad.values()));
|
||||
return ImmutableMap.copyOf(
|
||||
Maps.transformEntries(existingDomainsToLoad, (k, v) -> (Domain) loadedDomains.get(v)));
|
||||
}
|
||||
|
||||
@@ -417,7 +417,7 @@ public class DomainFlowUtils {
|
||||
contacts.stream().map(DesignatedContact::getContactKey).forEach(keysToLoad::add);
|
||||
registrant.ifPresent(keysToLoad::add);
|
||||
keysToLoad.addAll(nameservers);
|
||||
verifyNotInPendingDelete(EppResource.loadCached(keysToLoad.build()).values());
|
||||
verifyNotInPendingDelete(EppResource.loadByCacheIfEnabled(keysToLoad.build()).values());
|
||||
}
|
||||
|
||||
private static void verifyNotInPendingDelete(Iterable<EppResource> resources)
|
||||
|
||||
@@ -404,7 +404,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
|
||||
* <p>Don't use this unless you really need it for performance reasons, and be sure that you are
|
||||
* OK with the trade-offs in loss of transactional consistency.
|
||||
*/
|
||||
public static ImmutableMap<VKey<? extends EppResource>, EppResource> loadCached(
|
||||
public static ImmutableMap<VKey<? extends EppResource>, EppResource> loadByCacheIfEnabled(
|
||||
Iterable<VKey<? extends EppResource>> keys) {
|
||||
if (!RegistryConfig.isEppResourceCachingEnabled()) {
|
||||
return tm().reTransact(() -> tm().loadByKeys(keys));
|
||||
@@ -413,15 +413,12 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a given EppResource by its key using the cache (if enabled).
|
||||
* Loads a given EppResource by its key using the cache.
|
||||
*
|
||||
* <p>Don't use this unless you really need it for performance reasons, and be sure that you are
|
||||
* OK with the trade-offs in loss of transactional consistency.
|
||||
* <p>This method ignores the `isEppResourceCachingEnabled` config setting. It is reserved for use
|
||||
* cases that can tolerate slightly stale data, e.g., RDAP queries.
|
||||
*/
|
||||
public static <T extends EppResource> T loadCached(VKey<T> key) {
|
||||
if (!RegistryConfig.isEppResourceCachingEnabled()) {
|
||||
return tm().reTransact(() -> tm().loadByKey(key));
|
||||
}
|
||||
public static <T extends EppResource> T loadByCache(VKey<T> key) {
|
||||
// Safe to cast because loading a Key<T> returns an entity of type T.
|
||||
@SuppressWarnings("unchecked")
|
||||
T resource = (T) cacheEppResources.get(key);
|
||||
|
||||
@@ -16,6 +16,7 @@ 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;
|
||||
@@ -40,6 +41,7 @@ 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;
|
||||
@@ -109,12 +111,12 @@ public final class EppResourceUtils {
|
||||
*/
|
||||
public static <T extends EppResource> Optional<T> loadByForeignKey(
|
||||
Class<T> clazz, String foreignKey, DateTime now) {
|
||||
return loadByForeignKeyHelper(clazz, foreignKey, now, false);
|
||||
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.
|
||||
* 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".
|
||||
@@ -134,20 +136,36 @@ public final class EppResourceUtils {
|
||||
* @param foreignKey id to match
|
||||
* @param now the current logical time to project resources at
|
||||
*/
|
||||
public static <T extends EppResource> Optional<T> loadByForeignKeyCached(
|
||||
public static <T extends EppResource> Optional<T> loadByForeignKeyByCacheIfEnabled(
|
||||
Class<T> clazz, String foreignKey, DateTime now) {
|
||||
return loadByForeignKeyHelper(
|
||||
clazz, foreignKey, now, RegistryConfig.isEppResourceCachingEnabled());
|
||||
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(
|
||||
Class<T> clazz, String foreignKey, DateTime now, boolean useCache) {
|
||||
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.loadCached(clazz, ImmutableList.of(foreignKey), now).get(foreignKey)
|
||||
? 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) {
|
||||
@@ -155,10 +173,10 @@ public final class EppResourceUtils {
|
||||
}
|
||||
T resource =
|
||||
useCache
|
||||
? EppResource.loadCached(key)
|
||||
? 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.
|
||||
: tm().reTransact(() -> tm().loadByKeyIfPresent(key).orElse(null));
|
||||
: txnManager.reTransact(() -> txnManager.loadByKeyIfPresent(key).orElse(null));
|
||||
if (resource == null || isAtOrAfter(now, resource.getDeletionTime())) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@@ -204,11 +204,25 @@ 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>> loadCached(
|
||||
public static <E extends EppResource> ImmutableMap<String, VKey<E>> loadByCacheIfEnabled(
|
||||
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
|
||||
if (!RegistryConfig.isEppResourceCachingEnabled()) {
|
||||
return load(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
|
||||
.getAll(foreignKeys.stream().map(fk -> VKey.create(clazz, fk)).collect(toImmutableList()))
|
||||
.entrySet()
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
// 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.console;
|
||||
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.CreateAutoTimestamp;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.persistence.WithVKey;
|
||||
import jakarta.persistence.AttributeOverride;
|
||||
import jakarta.persistence.AttributeOverrides;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.Id;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* Represents a password reset request of some type.
|
||||
*
|
||||
* <p>Password reset requests must be performed within an hour of the time that they were requested,
|
||||
* as well as requiring that the requester and the fulfiller have the proper respective permissions.
|
||||
*/
|
||||
@Entity
|
||||
@WithVKey(String.class)
|
||||
public class PasswordResetRequest extends ImmutableObject implements Buildable {
|
||||
|
||||
public enum Type {
|
||||
EPP,
|
||||
REGISTRY_LOCK
|
||||
}
|
||||
|
||||
@Id private String verificationCode;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Enumerated(EnumType.STRING)
|
||||
Type type;
|
||||
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(
|
||||
name = "creationTime",
|
||||
column = @Column(name = "requestTime", nullable = false))
|
||||
})
|
||||
CreateAutoTimestamp requestTime = CreateAutoTimestamp.create(null);
|
||||
|
||||
@Column(nullable = false)
|
||||
String requester;
|
||||
|
||||
@Column DateTime fulfillmentTime;
|
||||
|
||||
@Column(nullable = false)
|
||||
String destinationEmail;
|
||||
|
||||
@Column(nullable = false)
|
||||
String registrarId;
|
||||
|
||||
public String getVerificationCode() {
|
||||
return verificationCode;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public DateTime getRequestTime() {
|
||||
return requestTime.getTimestamp();
|
||||
}
|
||||
|
||||
public String getRequester() {
|
||||
return requester;
|
||||
}
|
||||
|
||||
public Optional<DateTime> getFulfillmentTime() {
|
||||
return Optional.ofNullable(fulfillmentTime);
|
||||
}
|
||||
|
||||
public String getDestinationEmail() {
|
||||
return destinationEmail;
|
||||
}
|
||||
|
||||
public String getRegistrarId() {
|
||||
return registrarId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
}
|
||||
|
||||
/** Builder for constructing immutable {@link PasswordResetRequest} objects. */
|
||||
public static class Builder extends Buildable.Builder<PasswordResetRequest> {
|
||||
|
||||
public Builder() {}
|
||||
|
||||
private Builder(PasswordResetRequest instance) {
|
||||
super(instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PasswordResetRequest build() {
|
||||
checkArgumentNotNull(getInstance().type, "Type must be specified");
|
||||
checkArgumentNotNull(getInstance().requester, "Requester must be specified");
|
||||
checkArgumentNotNull(getInstance().destinationEmail, "Destination email must be specified");
|
||||
checkArgumentNotNull(getInstance().registrarId, "Registrar ID must be specified");
|
||||
getInstance().verificationCode = UUID.randomUUID().toString();
|
||||
return super.build();
|
||||
}
|
||||
|
||||
public Builder setType(Type type) {
|
||||
getInstance().type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setRequester(String requester) {
|
||||
getInstance().requester = requester;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDestinationEmail(String destinationEmail) {
|
||||
getInstance().destinationEmail = destinationEmail;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setRegistrarId(String registrarId) {
|
||||
getInstance().registrarId = registrarId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setFulfillmentTime(DateTime fulfillmentTime) {
|
||||
getInstance().fulfillmentTime = fulfillmentTime;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -441,7 +441,8 @@ public class DomainCommand {
|
||||
private static <T extends EppResource> ImmutableMap<String, VKey<T>> loadByForeignKeysCached(
|
||||
final Set<String> foreignKeys, final Class<T> clazz, final DateTime now)
|
||||
throws InvalidReferencesException {
|
||||
ImmutableMap<String, VKey<T>> fks = ForeignKeyUtils.loadCached(clazz, foreignKeys, now);
|
||||
ImmutableMap<String, VKey<T>> fks =
|
||||
ForeignKeyUtils.loadByCacheIfEnabled(clazz, foreignKeys, now);
|
||||
if (!fks.keySet().equals(foreignKeys)) {
|
||||
throw new InvalidReferencesException(
|
||||
clazz, ImmutableSet.copyOf(difference(foreignKeys, fks.keySet())));
|
||||
|
||||
@@ -27,6 +27,7 @@ import static com.google.common.io.BaseEncoding.base64;
|
||||
import static google.registry.config.RegistryConfig.getDefaultRegistrarWhoisServer;
|
||||
import static google.registry.model.CacheUtils.memoizeWithShortExpiration;
|
||||
import static google.registry.model.tld.Tlds.assertTldsExist;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableSortedCopy;
|
||||
@@ -62,6 +63,7 @@ import google.registry.model.tld.Tld.TldType;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.converter.CidrBlockListUserType;
|
||||
import google.registry.persistence.converter.CurrencyToStringMapUserType;
|
||||
import google.registry.persistence.transaction.TransactionManager;
|
||||
import google.registry.util.CidrAddressBlock;
|
||||
import google.registry.util.PasswordUtils;
|
||||
import jakarta.mail.internet.AddressException;
|
||||
@@ -576,7 +578,20 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
* address.
|
||||
*/
|
||||
public ImmutableSortedSet<RegistrarPoc> getContacts() {
|
||||
return getContactPocs().stream()
|
||||
return getContactPocs(tm()).stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(toImmutableSortedSet(CONTACT_EMAIL_COMPARATOR));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all {@link RegistrarPoc} objects for this registrar sorted by their email
|
||||
* address.
|
||||
*
|
||||
* <p>This method queries the replica database. It is reserved for use cases that can tolerate
|
||||
* slightly stale data.
|
||||
*/
|
||||
public ImmutableSortedSet<RegistrarPoc> getContactsFromReplica() {
|
||||
return getContactPocs(replicaTm()).stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(toImmutableSortedSet(CONTACT_EMAIL_COMPARATOR));
|
||||
}
|
||||
@@ -586,7 +601,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
* their email address.
|
||||
*/
|
||||
public ImmutableSortedSet<RegistrarPoc> getContactsOfType(final RegistrarPoc.Type type) {
|
||||
return getContactPocs().stream()
|
||||
return getContactPocs(tm()).stream()
|
||||
.filter(Objects::nonNull)
|
||||
.filter((@Nullable RegistrarPoc contact) -> contact.getTypes().contains(type))
|
||||
.collect(toImmutableSortedSet(CONTACT_EMAIL_COMPARATOR));
|
||||
@@ -600,13 +615,8 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
return getContacts().stream().filter(RegistrarPoc::getVisibleInDomainWhoisAsAbuse).findFirst();
|
||||
}
|
||||
|
||||
private ImmutableSet<RegistrarPoc> getContactPocs() {
|
||||
return tm().transact(
|
||||
() ->
|
||||
tm().query("FROM RegistrarPoc WHERE registrarId = :registrarId", RegistrarPoc.class)
|
||||
.setParameter("registrarId", registrarId)
|
||||
.getResultStream()
|
||||
.collect(toImmutableSet()));
|
||||
private ImmutableList<RegistrarPoc> getContactPocs(TransactionManager txnManager) {
|
||||
return txnManager.transact(() -> RegistrarPoc.loadForRegistrar(registrarId));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,6 +27,7 @@ import static google.registry.util.PasswordUtils.hashPassword;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.gson.annotations.Expose;
|
||||
@@ -36,6 +37,7 @@ import google.registry.model.JsonMapBuilder;
|
||||
import google.registry.model.Jsonifiable;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.transaction.QueryComposer;
|
||||
import google.registry.util.PasswordUtils;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
@@ -432,6 +434,12 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
|
||||
}
|
||||
}
|
||||
|
||||
public static ImmutableList<RegistrarPoc> loadForRegistrar(String registrarId) {
|
||||
return tm().createQueryComposer(RegistrarPoc.class)
|
||||
.where("registrarId", QueryComposer.Comparator.EQ, registrarId)
|
||||
.list();
|
||||
}
|
||||
|
||||
/** Class to represent the composite primary key for {@link RegistrarPoc} entity. */
|
||||
@VisibleForTesting
|
||||
public static class RegistrarPocId extends ImmutableObject implements Serializable {
|
||||
|
||||
@@ -55,7 +55,7 @@ import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* Base RDAP (new WHOIS) action for all requests.
|
||||
* Base RDAP action for all requests.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc9082">RFC 9082: Registration Data Access Protocol
|
||||
* (RDAP) Query Format</a>
|
||||
@@ -138,7 +138,7 @@ public abstract class RdapActionBase implements Runnable {
|
||||
// RFC7480 4.2 - servers receiving an RDAP request return an entity with a Content-Type header
|
||||
// containing the RDAP-specific JSON media type.
|
||||
response.setContentType(RESPONSE_MEDIA_TYPE);
|
||||
// RDAP Technical Implementation Guide 1.13 - when responding to RDAP valid requests, we MUST
|
||||
// RDAP Technical Implementation Guide 1.14 - when responding to RDAP valid requests, we MUST
|
||||
// include the Access-Control-Allow-Origin, which MUST be "*" unless otherwise specified.
|
||||
response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
||||
try {
|
||||
|
||||
@@ -26,7 +26,7 @@ import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for RDAP autonomous system number requests.
|
||||
* RDAP action for RDAP autonomous system number requests.
|
||||
*
|
||||
* <p>This feature is not implemented because it's only necessary for <i>address</i> registries like
|
||||
* ARIN, not domain registries.
|
||||
|
||||
@@ -41,14 +41,13 @@ final class RdapDataStructures {
|
||||
// Conformance to RFC 9083
|
||||
jsonArray.add("rdap_level_0");
|
||||
|
||||
// Conformance to the RDAP Response Profile V2.1
|
||||
// Conformance to the RDAP Response Profile V2.2 (February 2024)
|
||||
// (see section 1.2)
|
||||
jsonArray.add("icann_rdap_response_profile_1");
|
||||
|
||||
// Conformance to the RDAP Technical Implementation Guide V2.2 (February 2024)
|
||||
// (see section 1.3)
|
||||
jsonArray.add("icann_rdap_response_profile_0");
|
||||
|
||||
// Conformance to the RDAP Technical Implementation Guide V2.1
|
||||
// (see section 1.14)
|
||||
jsonArray.add("icann_rdap_technical_implementation_guide_0");
|
||||
|
||||
jsonArray.add("icann_rdap_technical_implementation_guide_1");
|
||||
return jsonArray;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
package google.registry.rdap;
|
||||
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateDomainName;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyCached;
|
||||
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;
|
||||
@@ -36,7 +36,7 @@ import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
/** RDAP (new WHOIS) action for domain requests. */
|
||||
/** RDAP action for domain requests. */
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
path = "/rdap/domain/",
|
||||
@@ -65,7 +65,7 @@ public class RdapDomainAction extends RdapActionBase {
|
||||
}
|
||||
// The query string is not used; the RDAP syntax is /rdap/domain/mydomain.com.
|
||||
Optional<Domain> domain =
|
||||
loadByForeignKeyCached(
|
||||
loadByForeignKeyByCache(
|
||||
Domain.class,
|
||||
pathSearchString,
|
||||
shouldIncludeDeleted() ? START_OF_TIME : rdapJsonFormatter.getRequestTime());
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
package google.registry.rdap;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyCached;
|
||||
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;
|
||||
@@ -51,6 +51,7 @@ import google.registry.request.Parameter;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.NonFinalForTesting;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.persistence.Query;
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Comparator;
|
||||
@@ -60,17 +61,15 @@ import java.util.stream.Stream;
|
||||
import org.hibernate.Hibernate;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for domain search requests.
|
||||
* RDAP action for domain search requests.
|
||||
*
|
||||
* <p>All commands and responses conform to the RDAP spec as defined in RFCs 7480 through 7485.
|
||||
* <p>All commands and responses conform to the RDAP spec as defined in STD 95 and its RFCs.
|
||||
*
|
||||
* @see <a href="http://tools.ietf.org/html/rfc9082">RFC 9082: Registration Data Access Protocol
|
||||
* (RDAP) Query Format</a>
|
||||
* @see <a href="http://tools.ietf.org/html/rfc9083">RFC 9083: JSON Responses for the Registration
|
||||
* Data Access Protocol (RDAP)</a>
|
||||
*/
|
||||
// TODO: This isn't required by the RDAP Technical Implementation Guide, and hence should be
|
||||
// deleted, at least until it's actually required.
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
path = "/rdap/domains",
|
||||
@@ -184,7 +183,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
private DomainSearchResponse searchByDomainNameWithoutWildcard(
|
||||
final RdapSearchPattern partialStringQuery) {
|
||||
Optional<Domain> domain =
|
||||
loadByForeignKeyCached(
|
||||
loadByForeignKeyByCache(
|
||||
Domain.class, partialStringQuery.getInitialString(), getRequestTime());
|
||||
return makeSearchResults(
|
||||
shouldBeVisible(domain) ? ImmutableList.of(domain.get()) : ImmutableList.of());
|
||||
@@ -339,7 +338,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
Optional<String> desiredRegistrar = getDesiredRegistrar();
|
||||
if (desiredRegistrar.isPresent()) {
|
||||
Optional<Host> host =
|
||||
loadByForeignKeyCached(
|
||||
loadByForeignKeyByCache(
|
||||
Host.class,
|
||||
partialStringQuery.getInitialString(),
|
||||
shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
|
||||
@@ -364,7 +363,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
// through the subordinate hosts. This is more efficient, and lets us permit wildcard searches
|
||||
// with no initial string.
|
||||
Domain domain =
|
||||
loadByForeignKeyCached(
|
||||
loadByForeignKeyByCache(
|
||||
Domain.class,
|
||||
partialStringQuery.getSuffix(),
|
||||
shouldIncludeDeleted() ? START_OF_TIME : getRequestTime())
|
||||
@@ -381,7 +380,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
if (partialStringQuery.matches(fqhn)) {
|
||||
if (desiredRegistrar.isPresent()) {
|
||||
Optional<Host> host =
|
||||
loadByForeignKeyCached(
|
||||
loadByForeignKeyByCache(
|
||||
Host.class, fqhn, shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
|
||||
if (host.isPresent()
|
||||
&& desiredRegistrar
|
||||
@@ -442,7 +441,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
replicaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
jakarta.persistence.Query query =
|
||||
Query query =
|
||||
replicaTm()
|
||||
.getEntityManager()
|
||||
.createNativeQuery(queryBuilder.toString())
|
||||
|
||||
@@ -37,14 +37,12 @@ import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for entity (contact and registrar) requests. the ICANN operational
|
||||
* profile dictates that the "handle" for registrars is to be the IANA registrar ID:
|
||||
* RDAP action for entity (contact and registrar) requests. the ICANN operational profile dictates
|
||||
* that the "handle" for registrars is to be the IANA registrar ID:
|
||||
*
|
||||
* <p>2.8.3. Registries MUST support lookup for entities with the registrar role within other
|
||||
* objects using the handle (as described in 3.1.5 of RFC 9082). The handle of the entity with the
|
||||
* registrar role MUST be equal to IANA Registrar ID. The entity with the registrar role in the RDAP
|
||||
* response MUST contain a publicIDs member to identify the IANA Registrar ID from the IANA’s
|
||||
* Registrar ID registry. The type value of the publicID object MUST be equal to IANA Registrar ID.
|
||||
* <p>2.4.1.Registry RDAP servers MUST support Registrar object lookup using an entity path request
|
||||
* for entities with the registrar role using the handle (as described in 3.1.5 of RFC9082) where
|
||||
* the handle of the entity with the registrar role is be [sic] equal to the IANA Registrar ID.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
@@ -104,7 +102,7 @@ public class RdapEntityAction extends RdapActionBase {
|
||||
// query, it MUST reply with 404 response code.
|
||||
//
|
||||
// Note we don't do RFC7480 5.3 - returning a different code if we wish to say "this info
|
||||
// exists but we don't want to show it to you", because we DON'T wish to say that.
|
||||
// exists, but we don't want to show it to you", because we DON'T wish to say that.
|
||||
throw new NotFoundException(pathSearchString + " not found");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,9 +49,9 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for entity (contact and registrar) search requests.
|
||||
* RDAP action for entity (contact and registrar) search requests.
|
||||
*
|
||||
* <p>All commands and responses conform to the RDAP spec as defined in RFCs 7480 through 7485.
|
||||
* <p>All commands and responses conform to the RDAP spec as defined in STD 95 and its RFCs.
|
||||
*
|
||||
* <p>The RDAP specification lumps contacts and registrars together and calls them "entities", which
|
||||
* is confusing for us, because "entity" means something else in SQL. But here, when we use the
|
||||
@@ -76,8 +76,6 @@ import java.util.Optional;
|
||||
* @see <a href="http://tools.ietf.org/html/rfc9083">RFC 9083: JSON Responses for the Registration
|
||||
* Data Access Protocol (RDAP)</a>
|
||||
*/
|
||||
// TODO: This isn't required by the RDAP Technical Implementation Guide, and hence should be
|
||||
// deleted, at least until it's actually required.
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
path = "/rdap/entities",
|
||||
|
||||
@@ -28,7 +28,7 @@ import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
/** RDAP (new WHOIS) action for help requests. */
|
||||
/** RDAP action for help requests. */
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
path = RdapHelpAction.PATH,
|
||||
|
||||
@@ -22,26 +22,18 @@ import google.registry.rdap.RdapDataStructures.Remark;
|
||||
/**
|
||||
* This file contains boilerplate required by the ICANN RDAP Profile.
|
||||
*
|
||||
* @see <a href="https://www.icann.org/resources/pages/rdap-operational-profile-2016-07-26-en">RDAP
|
||||
* Operational Profile for gTLD Registries and Registrars</a>
|
||||
* @see <a
|
||||
* href="https://itp.cdn.icann.org/en/files/registry-operators/rdap-response-profile-21feb24-en.pdf">
|
||||
* RDAP Response Profile</a>
|
||||
*/
|
||||
public class RdapIcannStandardInformation {
|
||||
|
||||
/** Required by ICANN RDAP Profile section 1.4.10. */
|
||||
private static final Notice CONFORMANCE_NOTICE =
|
||||
Notice.builder()
|
||||
.setDescription(
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and"
|
||||
+ " Registrars version 1.0")
|
||||
.build();
|
||||
|
||||
/** Required by ICANN RDAP Profile section 1.5.18. */
|
||||
/** Required by RDAP Response Profile section 2.6.3. */
|
||||
private static final Notice DOMAIN_STATUS_CODES_NOTICE =
|
||||
Notice.builder()
|
||||
.setTitle("Status Codes")
|
||||
.setDescription(
|
||||
"For more information on domain status codes, please visit"
|
||||
+ " https://icann.org/epp")
|
||||
"For more information on domain status codes, please visit https://icann.org/epp")
|
||||
.addLink(
|
||||
Link.builder()
|
||||
.setRel("glossary")
|
||||
@@ -50,7 +42,7 @@ public class RdapIcannStandardInformation {
|
||||
.build())
|
||||
.build();
|
||||
|
||||
/** Required by ICANN RDAP Response Profile section 2.11. */
|
||||
/** Required by RDAP Response Profile section 2.10. */
|
||||
private static final Notice INACCURACY_COMPLAINT_FORM_NOTICE =
|
||||
Notice.builder()
|
||||
.setTitle("RDDS Inaccuracy Complaint Form")
|
||||
@@ -79,28 +71,16 @@ public class RdapIcannStandardInformation {
|
||||
/** Boilerplate notices required by domain responses. */
|
||||
static final ImmutableList<Notice> DOMAIN_BOILERPLATE_NOTICES =
|
||||
ImmutableList.of(
|
||||
CONFORMANCE_NOTICE,
|
||||
// RDAP Response Profile 2.6.3
|
||||
DOMAIN_STATUS_CODES_NOTICE,
|
||||
// RDAP Response Profile 2.11
|
||||
// RDAP Response Profile 2.10
|
||||
INACCURACY_COMPLAINT_FORM_NOTICE);
|
||||
|
||||
/** Boilerplate notice for when a domain is blocked by BSA. */
|
||||
static final ImmutableList<Notice> DOMAIN_BLOCKED_BY_BSA_BOILERPLATE_NOTICES =
|
||||
ImmutableList.of(DOMAIN_BLOCKED_BY_BSA_NOTICE);
|
||||
|
||||
/** Boilerplate remarks required by nameserver and entity responses. */
|
||||
static final ImmutableList<Notice> NAMESERVER_AND_ENTITY_BOILERPLATE_NOTICES =
|
||||
ImmutableList.of(CONFORMANCE_NOTICE);
|
||||
|
||||
/**
|
||||
* Required by ICANN RDAP Profile section 1.4.9, as corrected by Gustavo Lozano of ICANN.
|
||||
*
|
||||
* <p>Also mentioned in the RDAP Technical Implementation Guide 3.6.
|
||||
*
|
||||
* @see <a href="http://mm.icann.org/pipermail/gtld-tech/2016-October/000822.html">Questions about
|
||||
* the ICANN RDAP Profile</a>
|
||||
*/
|
||||
/** Required by the RDAP Technical Implementation Guide 3.6. */
|
||||
static final Remark SUMMARY_DATA_REMARK =
|
||||
Remark.builder()
|
||||
.setTitle("Incomplete Data")
|
||||
@@ -109,14 +89,7 @@ public class RdapIcannStandardInformation {
|
||||
.setType(Remark.Type.OBJECT_TRUNCATED_UNEXPLAINABLE)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Required by ICANN RDAP Profile section 1.4.8, as corrected by Gustavo Lozano of ICANN.
|
||||
*
|
||||
* <p>Also mentioned in the RDAP Technical Implementation Guide 3.5.
|
||||
*
|
||||
* @see <a href="http://mm.icann.org/pipermail/gtld-tech/2016-October/000822.html">Questions about
|
||||
* the ICANN RDAP Profile</a>
|
||||
*/
|
||||
/** Required by the RDAP Technical Implementation Guide 3.5. */
|
||||
static final Notice TRUNCATED_RESULT_SET_NOTICE =
|
||||
Notice.builder()
|
||||
.setTitle("Search Policy")
|
||||
@@ -148,7 +121,9 @@ public class RdapIcannStandardInformation {
|
||||
/**
|
||||
* Included when requester is not logged in as the owner of the contact being returned.
|
||||
*
|
||||
* <p>Format required by ICANN RDAP Response Profile 15feb19 section 2.7.4.3.
|
||||
* <p>>Note: if we were keeping this around, we'd want/need to implement the <a
|
||||
* href="https://datatracker.ietf.org/doc/rfc9537/">official RDAP redaction spec</a> for contacts.
|
||||
* We are getting rid of contacts in 2025 though so this should be unnecessary.
|
||||
*/
|
||||
static final Remark CONTACT_PERSONAL_DATA_HIDDEN_DATA_REMARK =
|
||||
Remark.builder()
|
||||
@@ -169,10 +144,9 @@ public class RdapIcannStandardInformation {
|
||||
/**
|
||||
* Included in ALL contact responses, even if the user is authorized.
|
||||
*
|
||||
* <p>Format required by ICANN RDAP Response Profile 15feb19 section 2.7.5.3.
|
||||
*
|
||||
* <p>NOTE that unlike other redacted fields, there's no allowance to give the email to authorized
|
||||
* users or allow for registrar consent.
|
||||
* <p>>Note: if we were keeping this around, we'd want/need to implement the <a
|
||||
* href="https://datatracker.ietf.org/doc/rfc9537/">official RDAP redaction spec</a> for contacts.
|
||||
* We are getting rid of contacts in 2025 though so this should be unnecessary.
|
||||
*/
|
||||
static final Remark CONTACT_EMAIL_REDACTED_FOR_DOMAIN =
|
||||
Remark.builder()
|
||||
|
||||
@@ -26,7 +26,7 @@ import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for RDAP IP address requests.
|
||||
* RDAP action for RDAP IP address requests.
|
||||
*
|
||||
* <p>This feature is not implemented because it's only necessary for <i>address</i> registries like
|
||||
* ARIN, not domain registries.
|
||||
|
||||
@@ -23,20 +23,21 @@ import static google.registry.model.EppResourceUtils.isLinked;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static google.registry.util.CollectionUtils.union;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSetMultimap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import com.google.gson.JsonArray;
|
||||
import google.registry.config.RegistryConfig;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.CacheUtils;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.adapters.EnumToAttributeAdapter.EppEnum;
|
||||
import google.registry.model.contact.Contact;
|
||||
@@ -73,13 +74,11 @@ import google.registry.rdap.RdapObjectClasses.VcardArray;
|
||||
import google.registry.request.RequestServerName;
|
||||
import google.registry.util.Clock;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.persistence.Entity;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@@ -103,6 +102,16 @@ public class RdapJsonFormatter {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@VisibleForTesting
|
||||
record HistoryTimeAndRegistrar(DateTime modificationTime, String registrarId) {}
|
||||
|
||||
private static final LoadingCache<String, ImmutableMap<EventAction, HistoryTimeAndRegistrar>>
|
||||
DOMAIN_HISTORIES_BY_REPO_ID =
|
||||
CacheUtils.newCacheBuilder(RegistryConfig.getEppResourceCachingDuration())
|
||||
// Cache more than the EPP resource cache because we're only caching small objects
|
||||
.maximumSize(RegistryConfig.getEppResourceMaxCachedEntries() * 4L)
|
||||
.build(repoId -> getLastHistoryByType(repoId, Domain.class));
|
||||
|
||||
private DateTime requestTime = null;
|
||||
|
||||
@Inject
|
||||
@@ -212,7 +221,7 @@ public class RdapJsonFormatter {
|
||||
* Map of EPP event values to the RDAP equivalents.
|
||||
*
|
||||
* <p>Only has entries for optional events, either stated as optional in the RDAP Response Profile
|
||||
* 15feb19, or not mentioned at all but thought to be useful anyway.
|
||||
* section 2.3.2, or not mentioned at all but thought to be useful anyway.
|
||||
*
|
||||
* <p>Any required event should be added elsewhere, preferably without using HistoryEntries (so
|
||||
* that we don't need to load HistoryEntries for "summary" responses).
|
||||
@@ -283,8 +292,8 @@ public class RdapJsonFormatter {
|
||||
* Creates a JSON object for a {@link Domain}.
|
||||
*
|
||||
* <p>NOTE that domain searches aren't in the spec yet - they're in the RFC 9082 that describes
|
||||
* the query format, but they aren't in the RDAP Technical Implementation Guide 15feb19, meaning
|
||||
* we don't have to implement them yet and the RDAP Response Profile doesn't apply to them.
|
||||
* the query format, but they aren't in the RDAP Technical Implementation Guide, meaning we don't
|
||||
* have to implement them yet and the RDAP Response Profile doesn't apply to them.
|
||||
*
|
||||
* <p>We're implementing domain searches anyway, BUT we won't have the response for searches
|
||||
* conform to the RDAP Response Profile.
|
||||
@@ -298,9 +307,9 @@ public class RdapJsonFormatter {
|
||||
if (outputDataType != OutputDataType.FULL) {
|
||||
builder.remarksBuilder().add(RdapIcannStandardInformation.SUMMARY_DATA_REMARK);
|
||||
}
|
||||
// RDAP Response Profile 15feb19 section 2.1 discusses the domain name.
|
||||
// RDAP Response Profile section 2.1 discusses the domain name.
|
||||
builder.setLdhName(domain.getDomainName());
|
||||
// RDAP Response Profile 15feb19 section 2.2:
|
||||
// RDAP Response Profile section 2.2:
|
||||
// The domain handle MUST be the ROID
|
||||
builder.setHandle(domain.getRepoId());
|
||||
// If this is a summary (search result) - we'll return now. Since there's no requirement for
|
||||
@@ -308,9 +317,9 @@ public class RdapJsonFormatter {
|
||||
if (outputDataType == OutputDataType.SUMMARY) {
|
||||
return builder.build();
|
||||
}
|
||||
// RDAP Response Profile 15feb19 section 2.3.1:
|
||||
// RDAP Response Profile section 2.3.1:
|
||||
// The domain object in the RDAP response MUST contain the following events:
|
||||
// [registration, expiration, last update of RDAP database]
|
||||
// [registration, expiration]
|
||||
builder
|
||||
.eventsBuilder()
|
||||
.add(
|
||||
@@ -324,14 +333,18 @@ public class RdapJsonFormatter {
|
||||
.setEventAction(EventAction.EXPIRATION)
|
||||
.setEventDate(domain.getRegistrationExpirationTime())
|
||||
.build(),
|
||||
// RDAP response profile section 1.5:
|
||||
// The topmost object in the RDAP response MUST contain an event of "eventAction" type
|
||||
// "last update of RDAP database" with a value equal to the timestamp when the RDAP
|
||||
// database was last updated
|
||||
Event.builder()
|
||||
.setEventAction(EventAction.LAST_UPDATE_OF_RDAP_DATABASE)
|
||||
.setEventDate(getRequestTime())
|
||||
.build());
|
||||
// RDAP Response Profile 15feb19 section 2.3.2 discusses optional events. We add some of those
|
||||
// RDAP Response Profile section 2.3.2 discusses optional events. We add some of those
|
||||
// here. We also add a few others we find interesting.
|
||||
builder.eventsBuilder().addAll(makeOptionalEvents(domain));
|
||||
// RDAP Response Profile 15feb19 section 2.4.1:
|
||||
// RDAP Response Profile section 2.4.1:
|
||||
// The domain object in the RDAP response MUST contain an entity with the Registrar role.
|
||||
//
|
||||
// See {@link createRdapRegistrarEntity} for details of section 2.4 conformance
|
||||
@@ -369,8 +382,6 @@ public class RdapJsonFormatter {
|
||||
// RDAP Response Profile 2.6.3, must have a notice about statuses. That is in {@link
|
||||
// RdapIcannStandardInformation#domainBoilerplateNotices}
|
||||
|
||||
// Kick off the database loads of the nameservers that we will need, so it can load
|
||||
// asynchronously while we load and process the contacts.
|
||||
ImmutableSet<Host> loadedHosts =
|
||||
replicaTm()
|
||||
.transact(
|
||||
@@ -415,12 +426,12 @@ public class RdapJsonFormatter {
|
||||
}
|
||||
|
||||
// Add the nameservers to the data; the load was kicked off above for efficiency.
|
||||
// RDAP Response Profile 2.9: we MUST have the nameservers
|
||||
// RDAP Response Profile 2.8: we MUST have the nameservers
|
||||
for (Host host : HOST_RESOURCE_ORDERING.immutableSortedCopy(loadedHosts)) {
|
||||
builder.nameserversBuilder().add(createRdapNameserver(host, OutputDataType.INTERNAL));
|
||||
}
|
||||
|
||||
// RDAP Response Profile 2.10 - MUST contain a secureDns member including at least a
|
||||
// RDAP Response Profile 2.9 - MUST contain a secureDns member including at least a
|
||||
// delegationSigned element. Other elements (e.g. dsData) MUST be included if the domain name is
|
||||
// signed and the elements are stored in the Registry
|
||||
//
|
||||
@@ -445,13 +456,13 @@ public class RdapJsonFormatter {
|
||||
builder.remarksBuilder().add(RdapIcannStandardInformation.SUMMARY_DATA_REMARK);
|
||||
}
|
||||
|
||||
// We need the ldhName: RDAP Response Profile 2.9.1, 4.1
|
||||
// We need the ldhName: RDAP Response Profile 2.8.1, 4.1
|
||||
builder.setLdhName(host.getHostName());
|
||||
// Handle is optional, but if given it MUST be the ROID.
|
||||
// We will set it always as it's important as a "self link"
|
||||
builder.setHandle(host.getRepoId());
|
||||
|
||||
// Status is optional for internal Nameservers - RDAP Response Profile 2.9.2
|
||||
// Status is optional for internal Nameservers - RDAP Response Profile 2.8.2
|
||||
// It isn't mentioned at all anywhere else. So we can just not put it at all?
|
||||
//
|
||||
// To be safe, we'll put it on the "FULL" version anyway
|
||||
@@ -483,7 +494,7 @@ public class RdapJsonFormatter {
|
||||
|
||||
// For query responses - we MUST have all the ip addresses: RDAP Response Profile 4.2.
|
||||
//
|
||||
// However, it is optional for internal responses: RDAP Response Profile 2.9.2
|
||||
// However, it is optional for internal responses: RDAP Response Profile 2.8.2
|
||||
if (outputDataType != OutputDataType.INTERNAL) {
|
||||
for (InetAddress inetAddress : host.getInetAddresses()) {
|
||||
if (inetAddress instanceof Inet4Address) {
|
||||
@@ -501,7 +512,7 @@ public class RdapJsonFormatter {
|
||||
builder.entitiesBuilder().add(createRdapRegistrarEntity(registrar, OutputDataType.INTERNAL));
|
||||
}
|
||||
if (outputDataType != OutputDataType.INTERNAL) {
|
||||
// Rdap Response Profile 4.4, must have "last update of RDAP database" response. But this is
|
||||
// Rdap Response Profile 1.5, must have "last update of RDAP database" response. But this is
|
||||
// only for direct query responses and not for internal objects.
|
||||
builder.setLastUpdateOfRdapDatabaseEvent(
|
||||
Event.builder()
|
||||
@@ -526,10 +537,7 @@ public class RdapJsonFormatter {
|
||||
Contact contact, Iterable<RdapEntity.Role> roles, OutputDataType outputDataType) {
|
||||
RdapContactEntity.Builder contactBuilder = RdapContactEntity.builder();
|
||||
|
||||
// RDAP Response Profile 2.7.1, 2.7.3 - we MUST have the contacts. 2.7.4 discusses censoring of
|
||||
// fields we don't want to show (as opposed to not having contacts at all) because of GDPR etc.
|
||||
//
|
||||
// 2.8 allows for unredacted output for authorized people.
|
||||
// RDAP Response Profile 2.7.1, 2.7.3 - we MUST have the contacts
|
||||
boolean isAuthorized =
|
||||
rdapAuthorization.isAuthorizedForRegistrar(contact.getCurrentSponsorRegistrarId());
|
||||
|
||||
@@ -569,7 +577,7 @@ public class RdapJsonFormatter {
|
||||
.add(RdapIcannStandardInformation.CONTACT_EMAIL_REDACTED_FOR_DOMAIN);
|
||||
|
||||
if (outputDataType != OutputDataType.INTERNAL) {
|
||||
// Rdap Response Profile 2.7.6 must have "last update of RDAP database" response. But this is
|
||||
// Rdap Response Profile 1.5 must have "last update of RDAP database" response. But this is
|
||||
// only for direct query responses and not for internal objects. I'm not sure why it's in that
|
||||
// section at all...
|
||||
contactBuilder.setLastUpdateOfRdapDatabaseEvent(
|
||||
@@ -647,8 +655,8 @@ public class RdapJsonFormatter {
|
||||
* Creates a JSON object for a {@link Registrar}.
|
||||
*
|
||||
* <p>This object can be INTERNAL to the Domain and Nameserver responses, with requirements
|
||||
* discussed in the RDAP Response Profile 15feb19 sections 2.4 (internal to Domain) and 4.3
|
||||
* (internal to Namesever)
|
||||
* discussed in the RDAP Response Profile sections 2.4 (internal to Domain) and 4.3 (internal to
|
||||
* Namesever)
|
||||
*
|
||||
* @param registrar the registrar object from which the RDAP response
|
||||
* @param outputDataType whether to generate FULL, SUMMARY, or INTERNAL data.
|
||||
@@ -712,6 +720,15 @@ public class RdapJsonFormatter {
|
||||
builder.linksBuilder().add(makeSelfLink("entity", ianaIdentifier.toString()));
|
||||
}
|
||||
|
||||
// RDAP Response Profile 2.4.6: must have a links entry pointing to the registrar URL, with a
|
||||
// rel:about and a value containing the registrar RDAP base URL (if present)
|
||||
if (registrar.getUrl() != null) {
|
||||
Link.Builder registrarLinkBuilder =
|
||||
Link.builder().setHref(registrar.getUrl()).setRel("about").setType("text/html");
|
||||
registrar.getRdapBaseUrls().stream().findFirst().ifPresent(registrarLinkBuilder::setValue);
|
||||
builder.linksBuilder().add(registrarLinkBuilder.build());
|
||||
}
|
||||
|
||||
// There's no mention of the registrar STATUS in the RDAP Response Profile, so we'll only add it
|
||||
// for FULL response
|
||||
// We could probably not add it at all, but it could be useful for us internally
|
||||
@@ -737,10 +754,9 @@ public class RdapJsonFormatter {
|
||||
//
|
||||
// Write the minimum, meaning only ABUSE for INTERNAL registrars, nothing for SUMMARY and
|
||||
// everything for FULL.
|
||||
//
|
||||
if (outputDataType != OutputDataType.SUMMARY) {
|
||||
ImmutableList<RdapContactEntity> registrarContacts =
|
||||
registrar.getContacts().stream()
|
||||
registrar.getContactsFromReplica().stream()
|
||||
.map(RdapJsonFormatter::makeRdapJsonForRegistrarContact)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
@@ -758,7 +774,7 @@ public class RdapJsonFormatter {
|
||||
builder.entitiesBuilder().addAll(registrarContacts);
|
||||
}
|
||||
|
||||
// Rdap Response Profile 3.3, must have "last update of RDAP database" response. But this is
|
||||
// Rdap Response Profile 1.5, must have "last update of RDAP database" response. But this is
|
||||
// only for direct query responses and not for internal objects.
|
||||
if (outputDataType != OutputDataType.INTERNAL) {
|
||||
builder.setLastUpdateOfRdapDatabaseEvent(
|
||||
@@ -860,8 +876,18 @@ public class RdapJsonFormatter {
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ImmutableMap<EventAction, HistoryEntry> getLastHistoryEntryByType(EppResource resource) {
|
||||
HashMap<EventAction, HistoryEntry> lastEntryOfType = Maps.newHashMap();
|
||||
static ImmutableMap<EventAction, HistoryTimeAndRegistrar> getLastHistoryByType(
|
||||
EppResource eppResource) {
|
||||
if (eppResource instanceof Domain) {
|
||||
return DOMAIN_HISTORIES_BY_REPO_ID.get(eppResource.getRepoId());
|
||||
}
|
||||
return getLastHistoryByType(eppResource.getRepoId(), eppResource.getClass());
|
||||
}
|
||||
|
||||
private static ImmutableMap<EventAction, HistoryTimeAndRegistrar> getLastHistoryByType(
|
||||
String repoId, Class<? extends EppResource> resourceType) {
|
||||
ImmutableMap.Builder<EventAction, HistoryTimeAndRegistrar> lastEntryOfType =
|
||||
new ImmutableMap.Builder<>();
|
||||
// Events (such as transfer, but also create) can appear multiple times. We only want the last
|
||||
// time they appeared.
|
||||
//
|
||||
@@ -873,49 +899,48 @@ public class RdapJsonFormatter {
|
||||
// 2.3.2.3 An event of *eventAction* type *transfer*, with the last date and time that the
|
||||
// domain was transferred. The event of *eventAction* type *transfer* MUST be omitted if the
|
||||
// domain name has not been transferred since it was created.
|
||||
VKey<? extends EppResource> resourceVkey = resource.createVKey();
|
||||
Class<? extends HistoryEntry> historyClass =
|
||||
HistoryEntryDao.getHistoryClassFromParent(resourceVkey.getKind());
|
||||
String entityName = historyClass.getAnnotation(Entity.class).name();
|
||||
if (Strings.isNullOrEmpty(entityName)) {
|
||||
entityName = historyClass.getSimpleName();
|
||||
}
|
||||
String entityName = HistoryEntryDao.getHistoryClassFromParent(resourceType).getSimpleName();
|
||||
String jpql =
|
||||
GET_LAST_HISTORY_BY_TYPE_JPQL_TEMPLATE
|
||||
.replace("%entityName%", entityName)
|
||||
.replace("%repoIdValue%", resourceVkey.getKey().toString());
|
||||
Iterable<HistoryEntry> historyEntries =
|
||||
replicaTm()
|
||||
.transact(
|
||||
() ->
|
||||
replicaTm()
|
||||
.getEntityManager()
|
||||
.createQuery(jpql, HistoryEntry.class)
|
||||
.getResultList());
|
||||
for (HistoryEntry historyEntry : historyEntries) {
|
||||
EventAction rdapEventAction =
|
||||
HISTORY_ENTRY_TYPE_TO_RDAP_EVENT_ACTION_MAP.get(historyEntry.getType());
|
||||
// Only save the historyEntries if this is a type we care about.
|
||||
if (rdapEventAction == null) {
|
||||
continue;
|
||||
}
|
||||
lastEntryOfType.put(rdapEventAction, historyEntry);
|
||||
}
|
||||
return ImmutableMap.copyOf(lastEntryOfType);
|
||||
.replace("%repoIdValue%", repoId);
|
||||
replicaTm()
|
||||
.transact(
|
||||
() ->
|
||||
replicaTm()
|
||||
.getEntityManager()
|
||||
.createQuery(jpql, HistoryEntry.class)
|
||||
.getResultStream()
|
||||
.forEach(
|
||||
historyEntry -> {
|
||||
EventAction rdapEventAction =
|
||||
HISTORY_ENTRY_TYPE_TO_RDAP_EVENT_ACTION_MAP.get(
|
||||
historyEntry.getType());
|
||||
// Only save the entries if this is a type we care about.
|
||||
if (rdapEventAction != null) {
|
||||
lastEntryOfType.put(
|
||||
rdapEventAction,
|
||||
new HistoryTimeAndRegistrar(
|
||||
historyEntry.getModificationTime(),
|
||||
historyEntry.getRegistrarId()));
|
||||
}
|
||||
}));
|
||||
return lastEntryOfType.buildKeepingLast();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the list of optional events to list in domain, nameserver, or contact replies.
|
||||
*
|
||||
* <p>Only has entries for optional events that won't be shown in "SUMMARY" versions of these
|
||||
* objects. These are either stated as optional in the RDAP Response Profile 15feb19, or not
|
||||
* mentioned at all but thought to be useful anyway.
|
||||
* objects. These are either stated as optional in the RDAP Response Profile, or not mentioned at
|
||||
* all but thought to be useful anyway.
|
||||
*
|
||||
* <p>Any required event should be added elsewhere, preferably without using HistoryEntries (so
|
||||
* that we don't need to load HistoryEntries for "summary" responses).
|
||||
*/
|
||||
private ImmutableList<Event> makeOptionalEvents(EppResource resource) {
|
||||
ImmutableMap<EventAction, HistoryEntry> lastEntryOfType = getLastHistoryEntryByType(resource);
|
||||
ImmutableMap<EventAction, HistoryTimeAndRegistrar> lastHistoryOfType =
|
||||
getLastHistoryByType(resource);
|
||||
ImmutableList.Builder<Event> eventsBuilder = new ImmutableList.Builder<>();
|
||||
DateTime creationTime = resource.getCreationTime();
|
||||
DateTime lastChangeTime =
|
||||
@@ -923,12 +948,12 @@ public class RdapJsonFormatter {
|
||||
// The order of the elements is stable - it's the order in which the enum elements are defined
|
||||
// in EventAction
|
||||
for (EventAction rdapEventAction : EventAction.values()) {
|
||||
HistoryEntry historyEntry = lastEntryOfType.get(rdapEventAction);
|
||||
HistoryTimeAndRegistrar historyTimeAndRegistrar = lastHistoryOfType.get(rdapEventAction);
|
||||
// Check if there was any entry of this type
|
||||
if (historyEntry == null) {
|
||||
if (historyTimeAndRegistrar == null) {
|
||||
continue;
|
||||
}
|
||||
DateTime modificationTime = historyEntry.getModificationTime();
|
||||
DateTime modificationTime = historyTimeAndRegistrar.modificationTime();
|
||||
// We will ignore all events that happened before the "creation time", since these events are
|
||||
// from a "previous incarnation of the domain" (for a domain that was owned by someone,
|
||||
// deleted, and then bought by someone else)
|
||||
@@ -938,7 +963,7 @@ public class RdapJsonFormatter {
|
||||
eventsBuilder.add(
|
||||
Event.builder()
|
||||
.setEventAction(rdapEventAction)
|
||||
.setEventActor(historyEntry.getRegistrarId())
|
||||
.setEventActor(historyTimeAndRegistrar.registrarId())
|
||||
.setEventDate(modificationTime)
|
||||
.build());
|
||||
// The last change time might not be the lastEppUpdateTime, since some changes happen without
|
||||
@@ -947,29 +972,24 @@ public class RdapJsonFormatter {
|
||||
lastChangeTime = modificationTime;
|
||||
}
|
||||
}
|
||||
// RDAP Response Profile 15feb19 section 2.3.2.2:
|
||||
// RDAP Response Profile section 2.3.2.2:
|
||||
// The event of eventAction type last changed MUST be omitted if the domain name has not been
|
||||
// updated since it was created
|
||||
if (lastChangeTime.isAfter(creationTime)) {
|
||||
eventsBuilder.add(makeEvent(EventAction.LAST_CHANGED, null, lastChangeTime));
|
||||
// Creates an RDAP event object as defined by RFC 9083
|
||||
eventsBuilder.add(
|
||||
Event.builder()
|
||||
.setEventAction(EventAction.LAST_CHANGED)
|
||||
.setEventDate(lastChangeTime)
|
||||
.build());
|
||||
}
|
||||
return eventsBuilder.build();
|
||||
}
|
||||
|
||||
/** Creates an RDAP event object as defined by RFC 9083. */
|
||||
private static Event makeEvent(
|
||||
EventAction eventAction, @Nullable String eventActor, DateTime eventDate) {
|
||||
Event.Builder builder = Event.builder().setEventAction(eventAction).setEventDate(eventDate);
|
||||
if (eventActor != null) {
|
||||
builder.setEventActor(eventActor);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a vCard address entry: array of strings specifying the components of the address.
|
||||
*
|
||||
* <p>Rdap Response Profile 3.1.1: MUST contain the following fields: Street, City, Country Rdap
|
||||
* <p>RDAP Response Profile 3.1.1: MUST contain the following fields: Street, City, Country Rdap
|
||||
* Response Profile 3.1.2: optional fields: State/Province, Postal Code, Fax Number
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7095">RFC 7095: jCard: The JSON Format for
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
package google.registry.rdap;
|
||||
|
||||
import static google.registry.flows.host.HostFlowUtils.validateHostName;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyCached;
|
||||
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;
|
||||
@@ -33,7 +33,7 @@ import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
/** RDAP (new WHOIS) action for nameserver requests. */
|
||||
/** RDAP action for nameserver requests. */
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
path = "/rdap/nameserver/",
|
||||
@@ -48,7 +48,7 @@ public class RdapNameserverAction extends RdapActionBase {
|
||||
|
||||
@Override
|
||||
public RdapNameserver getJsonObjectForResource(String pathSearchString, boolean isHeadRequest) {
|
||||
// RDAP Technical Implementation Guide 2.2.1 - we must support A-label (Punycode) and U-label
|
||||
// RDAP Technical Implementation Guide 2.1.1 - we must support A-label (Punycode) and U-label
|
||||
// (Unicode) formats. canonicalizeName will transform Unicode to Punycode so we support both.
|
||||
pathSearchString = canonicalizeName(pathSearchString);
|
||||
// The RDAP syntax is /rdap/nameserver/ns1.mydomain.com.
|
||||
@@ -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 =
|
||||
loadByForeignKeyCached(
|
||||
loadByForeignKeyByCache(
|
||||
Host.class,
|
||||
pathSearchString,
|
||||
shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
package google.registry.rdap;
|
||||
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyCached;
|
||||
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;
|
||||
@@ -47,9 +47,9 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for nameserver search requests.
|
||||
* RDAP action for nameserver search requests.
|
||||
*
|
||||
* <p>All commands and responses conform to the RDAP spec as defined in RFCs 7480 through 7485.
|
||||
* <p>All commands and responses conform to the RDAP spec as defined in STD 95 and its RFCs.
|
||||
*
|
||||
* @see <a href="http://tools.ietf.org/html/rfc9082">RFC 9082: Registration Data Access Protocol
|
||||
* (RDAP) Query Format</a>
|
||||
@@ -159,7 +159,8 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
|
||||
.setIncompletenessWarningType(IncompletenessWarningType.COMPLETE);
|
||||
|
||||
Optional<Host> host =
|
||||
loadByForeignKeyCached(Host.class, partialStringQuery.getInitialString(), getRequestTime());
|
||||
loadByForeignKeyByCache(
|
||||
Host.class, partialStringQuery.getInitialString(), getRequestTime());
|
||||
|
||||
metricInformationBuilder.setNumHostsRetrieved(host.isPresent() ? 1 : 0);
|
||||
|
||||
@@ -175,7 +176,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
|
||||
private NameserverSearchResponse searchByNameUsingSuperordinateDomain(
|
||||
RdapSearchPattern partialStringQuery) {
|
||||
Optional<Domain> domain =
|
||||
loadByForeignKeyCached(Domain.class, partialStringQuery.getSuffix(), getRequestTime());
|
||||
loadByForeignKeyByCache(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,
|
||||
@@ -193,7 +194,7 @@ 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 = loadByForeignKeyCached(Host.class, fqhn, getRequestTime());
|
||||
Optional<Host> host = loadByForeignKeyByCache(Host.class, fqhn, getRequestTime());
|
||||
if (shouldBeVisible(host)) {
|
||||
hostList.add(host.get());
|
||||
if (hostList.size() > rdapResultSetMaxSize) {
|
||||
|
||||
@@ -45,11 +45,7 @@ import java.util.Optional;
|
||||
/** Object Classes defined in RFC 9083 section 5. */
|
||||
final class RdapObjectClasses {
|
||||
|
||||
/**
|
||||
* Temporary implementation of VCards.
|
||||
*
|
||||
* <p>Will create a better implementation soon.
|
||||
*/
|
||||
/** Rough implementation of VCards. */
|
||||
@RestrictJsonNames({})
|
||||
@AutoValue
|
||||
public abstract static class Vcard implements Jsonable {
|
||||
@@ -140,8 +136,8 @@ final class RdapObjectClasses {
|
||||
public enum BoilerplateType {
|
||||
DOMAIN(RdapIcannStandardInformation.DOMAIN_BOILERPLATE_NOTICES),
|
||||
DOMAIN_BLOCKED_BY_BSA(RdapIcannStandardInformation.DOMAIN_BLOCKED_BY_BSA_BOILERPLATE_NOTICES),
|
||||
NAMESERVER(RdapIcannStandardInformation.NAMESERVER_AND_ENTITY_BOILERPLATE_NOTICES),
|
||||
ENTITY(RdapIcannStandardInformation.NAMESERVER_AND_ENTITY_BOILERPLATE_NOTICES),
|
||||
NAMESERVER(ImmutableList.of()),
|
||||
ENTITY(ImmutableList.of()),
|
||||
OTHER(ImmutableList.of());
|
||||
|
||||
@SuppressWarnings("ImmutableEnumChecker") // immutable lists are, in fact, immutable
|
||||
@@ -173,8 +169,8 @@ final class RdapObjectClasses {
|
||||
* The Top Level JSON reply, Adds the required top-level boilerplate to a ReplyPayloadBase.
|
||||
*
|
||||
* <p>RFC 9083 specifies that the top-level object should include an entry indicating the
|
||||
* conformance level. ICANN RDAP spec for 15feb19 mandates several additional entries, in sections
|
||||
* 2.6.3, 2.11 of the Response Profile and 3.3, 3.5, of the Technical Implementation Guide.
|
||||
* conformance level. The RDAP spec mandates several additional entries, in sections 2.6.3, 2.10
|
||||
* of the Response Profile and 3.3, 3.5, of the Technical Implementation Guide.
|
||||
*/
|
||||
@AutoValue
|
||||
@RestrictJsonNames({})
|
||||
@@ -353,7 +349,7 @@ final class RdapObjectClasses {
|
||||
*
|
||||
* <p>Takes care of the name and unicode field.
|
||||
*
|
||||
* <p>See RDAP Response Profile 15feb19 sections 2.1 and 4.1.
|
||||
* <p>See RDAP Response Profile sections 2.1 and 4.1.
|
||||
*
|
||||
* <p>Note the ldhName field is only required for non-IDN names or IDN names when the query was an
|
||||
* A-label. It is optional for IDN names when the query was a U-label. Because we don't want to
|
||||
@@ -471,7 +467,7 @@ final class RdapObjectClasses {
|
||||
}
|
||||
|
||||
/**
|
||||
* an integer representing the signature lifetime in seconds to be used when creating the RRSIG
|
||||
* An integer representing the signature lifetime in seconds to be used when creating the RRSIG
|
||||
* DS record in the parent zone [RFC5910].
|
||||
*
|
||||
* <p>Note that although it isn't given as optional in RFC 9083, in RFC5910 it's mentioned as
|
||||
|
||||
@@ -44,7 +44,7 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Base RDAP (new WHOIS) action for domain, nameserver and entity search requests.
|
||||
* Base RDAP action for domain, nameserver and entity search requests.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc9082">RFC 9082: Registration Data Access Protocol
|
||||
* (RDAP) Query Format</a>
|
||||
@@ -155,7 +155,6 @@ public abstract class RdapSearchActionBase extends RdapActionBase {
|
||||
*/
|
||||
<T extends EppResource> RdapResultSet<T> getMatchingResources(
|
||||
CriteriaQueryBuilder<T> builder, boolean checkForVisibility, int querySizeLimit) {
|
||||
replicaTm().assertInTransaction();
|
||||
Optional<String> desiredRegistrar = getDesiredRegistrar();
|
||||
if (desiredRegistrar.isPresent()) {
|
||||
builder =
|
||||
|
||||
@@ -39,8 +39,8 @@ public final class RdapSearchPattern {
|
||||
/**
|
||||
* Pattern for allowed LDH searches.
|
||||
*
|
||||
* <p>Based on RFC 9082 4.1. Must contains only alphanumeric plus dots and hyphens. A single
|
||||
* whildcard asterix is allowed - but if exists must be the last character of a domain name label
|
||||
* <p>Based on RFC 9082 4.1. Must contain only alphanumeric plus dots and hyphens. A single
|
||||
* wildcard asterix is allowed - but if exists must be the last character of a domain name label
|
||||
* (so exam* and exam*.com are allowed, but exam*le.com isn't allowd)
|
||||
*
|
||||
* <p>The prefix is in group(1), and the suffix without the dot (if it exists) is in group(4). If
|
||||
@@ -123,7 +123,7 @@ public final class RdapSearchPattern {
|
||||
* Creates a SearchPattern using the provided domain search pattern in LDH format.
|
||||
*
|
||||
* <p>The domain search pattern can have a single wildcard asterix that can match 0 or more
|
||||
* charecters. If such an asterix exists - it must be at the end of a domain label.
|
||||
* characters. If such an asterix exists - it must be at the end of a domain label.
|
||||
*
|
||||
* @param searchQuery the string containing the partial match pattern
|
||||
* @throws UnprocessableEntityException if {@code pattern} does not meet the requirements of RFC
|
||||
@@ -150,7 +150,7 @@ public final class RdapSearchPattern {
|
||||
* Creates a SearchPattern using the provided domain search pattern in LDH or Unicode format.
|
||||
*
|
||||
* <p>The domain search pattern can have a single wildcard asterix that can match 0 or more
|
||||
* charecters. If such an asterix exists - it must be at the end of a domain label.
|
||||
* characters. If such an asterix exists - it must be at the end of a domain label.
|
||||
*
|
||||
* <p>In theory, according to RFC 9082 4.1 - we should make some checks about partial matching in
|
||||
* unicode queries. We don't, but we might want to just disable partial matches for unicode inputs
|
||||
|
||||
@@ -37,7 +37,7 @@ public final class RdapUtils {
|
||||
*
|
||||
* <p>Used for RDAP Technical Implementation Guide 2.4.2 - search of registrar by the fn element.
|
||||
*
|
||||
* <p>For convenience, we use case insensitive search.
|
||||
* <p>For convenience, we use case-insensitive search.
|
||||
*/
|
||||
static Optional<Registrar> getRegistrarByName(String registrarName) {
|
||||
return Streams.stream(Registrar.loadAllCached())
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.persistence.PersistenceModule;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.HttpException.InternalServerErrorException;
|
||||
@@ -36,7 +37,7 @@ import jakarta.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.security.GeneralSecurityException;
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVParser;
|
||||
@@ -72,7 +73,7 @@ public final class UpdateRegistrarRdapBaseUrlsAction implements Runnable {
|
||||
public void run() {
|
||||
try {
|
||||
ImmutableMap<String, String> ianaIdsToUrls = getIanaIdsToUrls();
|
||||
tm().transact(() -> processAllRegistrars(ianaIdsToUrls));
|
||||
processAllRegistrars(ianaIdsToUrls);
|
||||
} catch (Exception e) {
|
||||
throw new InternalServerErrorException("Error when retrieving RDAP base URL CSV file", e);
|
||||
}
|
||||
@@ -80,7 +81,14 @@ public final class UpdateRegistrarRdapBaseUrlsAction implements Runnable {
|
||||
|
||||
private static void processAllRegistrars(ImmutableMap<String, String> ianaIdsToUrls) {
|
||||
int nonUpdatedRegistrars = 0;
|
||||
for (Registrar registrar : Registrar.loadAll()) {
|
||||
// Split into multiple transactions to avoid load-save-reload conflicts. Re-building a registrar
|
||||
// requires a full (cached) load of all registrars to avoid IANA ID conflicts, so if multiple
|
||||
// registrars are modified in the same transaction, the second build call will fail.
|
||||
Iterable<Registrar> registrars =
|
||||
tm().transact(
|
||||
PersistenceModule.TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ,
|
||||
Registrar::loadAll);
|
||||
for (Registrar registrar : registrars) {
|
||||
// Only update REAL registrars
|
||||
if (registrar.getType() != Registrar.Type.REAL) {
|
||||
continue;
|
||||
@@ -100,7 +108,12 @@ public final class UpdateRegistrarRdapBaseUrlsAction implements Runnable {
|
||||
"Updating RDAP base URLs for registrar %s from %s to %s",
|
||||
registrar.getRegistrarId(), registrar.getRdapBaseUrls(), baseUrls);
|
||||
}
|
||||
tm().put(registrar.asBuilder().setRdapBaseUrls(baseUrls).build());
|
||||
tm().transact(
|
||||
() -> {
|
||||
// Reload inside a transaction to avoid race conditions
|
||||
Registrar reloadedRegistrar = tm().loadByEntity(registrar);
|
||||
tm().put(reloadedRegistrar.asBuilder().setRdapBaseUrls(baseUrls).build());
|
||||
});
|
||||
}
|
||||
}
|
||||
logger.atInfo().log("No change in RDAP base URLs for %d registrars", nonUpdatedRegistrars);
|
||||
@@ -108,9 +121,9 @@ public final class UpdateRegistrarRdapBaseUrlsAction implements Runnable {
|
||||
|
||||
private ImmutableMap<String, String> getIanaIdsToUrls()
|
||||
throws IOException, GeneralSecurityException {
|
||||
CSVParser csv;
|
||||
HttpURLConnection connection = urlConnectionService.createConnection(new URL(RDAP_IDS_URL));
|
||||
// Explictly set the accepted encoding, as we know Brotli causes us problems when talking to
|
||||
HttpURLConnection connection =
|
||||
urlConnectionService.createConnection(URI.create(RDAP_IDS_URL).toURL());
|
||||
// Explicitly set the accepted encoding, as we know Brotli causes us problems when talking to
|
||||
// ICANN.
|
||||
connection.setRequestProperty(ACCEPT_ENCODING, "gzip");
|
||||
String csvString;
|
||||
@@ -128,11 +141,11 @@ public final class UpdateRegistrarRdapBaseUrlsAction implements Runnable {
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
}
|
||||
csv =
|
||||
CSVParser csv =
|
||||
CSVFormat.Builder.create(CSVFormat.DEFAULT)
|
||||
.setHeader()
|
||||
.setSkipHeaderRecord(true)
|
||||
.build()
|
||||
.get()
|
||||
.parse(new StringReader(csvString));
|
||||
ImmutableMap.Builder<String, String> result = new ImmutableMap.Builder<>();
|
||||
for (CSVRecord record : csv) {
|
||||
|
||||
@@ -16,7 +16,7 @@ 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.loadByForeignKeyCached;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCacheIfEnabled;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STATUSES;
|
||||
|
||||
@@ -326,7 +326,7 @@ public final class DomainLockUtils {
|
||||
|
||||
private Domain getDomain(String domainName, String registrarId, DateTime now) {
|
||||
Domain domain =
|
||||
loadByForeignKeyCached(Domain.class, domainName, now)
|
||||
loadByForeignKeyByCacheIfEnabled(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(
|
||||
|
||||
@@ -251,11 +251,12 @@ final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
|
||||
if (undo) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder undoBuilder = new StringBuilder("UNDO COMMAND:\n\n)")
|
||||
.append("nomulus -e ")
|
||||
.append(RegistryToolEnvironment.get())
|
||||
.append(" uniform_rapid_suspension --undo --domain_name ")
|
||||
.append(domainName);
|
||||
StringBuilder undoBuilder =
|
||||
new StringBuilder("UNDO COMMAND:\n\n")
|
||||
.append("nomulus -e ")
|
||||
.append(RegistryToolEnvironment.get())
|
||||
.append(" uniform_rapid_suspension --undo --domain_name ")
|
||||
.append(domainName);
|
||||
if (!existingNameservers.isEmpty()) {
|
||||
undoBuilder.append(" --hosts ").append(Joiner.on(',').join(existingNameservers));
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ public class ConsoleDomainGetAction extends ConsoleApiAction {
|
||||
Optional<Domain> possibleDomain =
|
||||
tm().transact(
|
||||
() ->
|
||||
EppResourceUtils.loadByForeignKeyCached(
|
||||
EppResourceUtils.loadByForeignKeyByCacheIfEnabled(
|
||||
Domain.class, paramDomain, tm().getTransactionTime()));
|
||||
if (possibleDomain.isEmpty()) {
|
||||
consoleApiParams.response().setStatus(SC_NOT_FOUND);
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package google.registry.ui.server.console.settings;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.collect.Sets.difference;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
@@ -35,7 +34,6 @@ import google.registry.model.console.User;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.model.registrar.RegistrarPoc.Type;
|
||||
import google.registry.persistence.transaction.QueryComposer.Comparator;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Action.GkeService;
|
||||
@@ -77,14 +75,7 @@ public class ContactAction extends ConsoleApiAction {
|
||||
protected void getHandler(User user) {
|
||||
checkPermission(user, registrarId, ConsolePermission.VIEW_REGISTRAR_DETAILS);
|
||||
ImmutableList<RegistrarPoc> contacts =
|
||||
tm().transact(
|
||||
() ->
|
||||
tm()
|
||||
.createQueryComposer(RegistrarPoc.class)
|
||||
.where("registrarId", Comparator.EQ, registrarId)
|
||||
.stream()
|
||||
.collect(toImmutableList()));
|
||||
|
||||
tm().transact(() -> RegistrarPoc.loadForRegistrar(registrarId));
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(contacts));
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ 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.loadByForeignKeyCached;
|
||||
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;
|
||||
@@ -94,7 +94,7 @@ public class DomainLookupCommand implements WhoisCommand {
|
||||
private Optional<WhoisResponse> getResponse(InternetDomainName domainName, DateTime now) {
|
||||
Optional<Domain> domainResource =
|
||||
cached
|
||||
? loadByForeignKeyCached(Domain.class, domainName.toString(), now)
|
||||
? loadByForeignKeyByCache(Domain.class, domainName.toString(), now)
|
||||
: loadByForeignKey(Domain.class, domainName.toString(), now);
|
||||
return domainResource.map(
|
||||
domain -> new DomainWhoisResponse(domain, fullOutput, whoisRedactedEmailText, now));
|
||||
|
||||
@@ -81,7 +81,7 @@ final class DomainWhoisResponse extends WhoisResponseImpl {
|
||||
domain.getCurrentSponsorRegistrarId());
|
||||
Registrar registrar = registrarOptional.get();
|
||||
Optional<RegistrarPoc> abuseContact =
|
||||
registrar.getContacts().stream()
|
||||
registrar.getContactsFromReplica().stream()
|
||||
.filter(RegistrarPoc::getVisibleInDomainWhoisAsAbuse)
|
||||
.findFirst();
|
||||
return WhoisResponseResults.create(
|
||||
@@ -154,7 +154,7 @@ final class DomainWhoisResponse extends WhoisResponseImpl {
|
||||
// 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.loadCached(contact.get());
|
||||
Contact contact1 = EppResource.loadByCache(contact.get());
|
||||
if (contact1 == null) {
|
||||
logger.atSevere().log(
|
||||
"(BUG) Broken reference found from domain %s to contact %s.",
|
||||
|
||||
@@ -16,7 +16,7 @@ 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.loadByForeignKeyCached;
|
||||
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;
|
||||
@@ -57,7 +57,7 @@ public class NameserverLookupByHostCommand implements WhoisCommand {
|
||||
private Optional<WhoisResponse> getResponse(InternetDomainName hostName, DateTime now) {
|
||||
Optional<Host> host =
|
||||
cached
|
||||
? loadByForeignKeyCached(Host.class, hostName.toString(), now)
|
||||
? loadByForeignKeyByCache(Host.class, hostName.toString(), now)
|
||||
: loadByForeignKey(Host.class, hostName.toString(), now);
|
||||
return host.map(h -> new NameserverWhoisResponse(h, now));
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class RegistrarWhoisResponse extends WhoisResponseImpl {
|
||||
|
||||
@Override
|
||||
public WhoisResponseResults getResponse(boolean preferUnicode, String disclaimer) {
|
||||
Set<RegistrarPoc> contacts = registrar.getContacts();
|
||||
Set<RegistrarPoc> contacts = registrar.getContactsFromReplica();
|
||||
String plaintext =
|
||||
new RegistrarEmitter()
|
||||
.emitField("Registrar", registrar.getRegistrarName())
|
||||
|
||||
@@ -47,13 +47,14 @@
|
||||
<class>google.registry.model.billing.BillingRecurrence</class>
|
||||
<class>google.registry.model.common.Cursor</class>
|
||||
<class>google.registry.model.common.DnsRefreshRequest</class>
|
||||
<class>google.registry.model.common.FeatureFlag</class>
|
||||
<class>google.registry.model.console.ConsoleUpdateHistory</class>
|
||||
<class>google.registry.model.console.PasswordResetRequest</class>
|
||||
<class>google.registry.model.console.User</class>
|
||||
<class>google.registry.model.contact.ContactHistory</class>
|
||||
<class>google.registry.model.contact.Contact</class>
|
||||
<class>google.registry.model.domain.Domain</class>
|
||||
<class>google.registry.model.domain.DomainHistory</class>
|
||||
<class>google.registry.model.common.FeatureFlag</class>
|
||||
<class>google.registry.model.domain.GracePeriod</class>
|
||||
<class>google.registry.model.domain.GracePeriod$GracePeriodHistory</class>
|
||||
<class>google.registry.model.domain.secdns.DomainDsData</class>
|
||||
|
||||
@@ -36,27 +36,27 @@ public class EppResourceTest extends EntityTestCase {
|
||||
new TestCacheExtension.Builder().withEppResourceCache(Duration.ofDays(1)).build();
|
||||
|
||||
@Test
|
||||
void test_loadCached_ignoresContactChange() {
|
||||
void test_loadByCacheIfEnabled_ignoresContactChange() {
|
||||
Contact originalContact = persistActiveContact("contact123");
|
||||
assertThat(EppResource.loadCached(ImmutableList.of(originalContact.createVKey())))
|
||||
assertThat(EppResource.loadByCacheIfEnabled(ImmutableList.of(originalContact.createVKey())))
|
||||
.containsExactly(originalContact.createVKey(), originalContact);
|
||||
Contact modifiedContact =
|
||||
persistResource(originalContact.asBuilder().setEmailAddress("different@fake.lol").build());
|
||||
assertThat(EppResource.loadCached(ImmutableList.of(originalContact.createVKey())))
|
||||
assertThat(EppResource.loadByCacheIfEnabled(ImmutableList.of(originalContact.createVKey())))
|
||||
.containsExactly(originalContact.createVKey(), originalContact);
|
||||
assertThat(loadByForeignKey(Contact.class, "contact123", fakeClock.nowUtc()))
|
||||
.hasValue(modifiedContact);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_loadCached_ignoresHostChange() {
|
||||
void test_loadByCacheIfEnabled_ignoresHostChange() {
|
||||
Host originalHost = persistActiveHost("ns1.example.com");
|
||||
assertThat(EppResource.loadCached(ImmutableList.of(originalHost.createVKey())))
|
||||
assertThat(EppResource.loadByCacheIfEnabled(ImmutableList.of(originalHost.createVKey())))
|
||||
.containsExactly(originalHost.createVKey(), originalHost);
|
||||
Host modifiedHost =
|
||||
persistResource(
|
||||
originalHost.asBuilder().setLastTransferTime(fakeClock.nowUtc().minusDays(60)).build());
|
||||
assertThat(EppResource.loadCached(ImmutableList.of(originalHost.createVKey())))
|
||||
assertThat(EppResource.loadByCacheIfEnabled(ImmutableList.of(originalHost.createVKey())))
|
||||
.containsExactly(originalHost.createVKey(), originalHost);
|
||||
assertThat(loadByForeignKey(Host.class, "ns1.example.com", fakeClock.nowUtc()))
|
||||
.hasValue(modifiedHost);
|
||||
|
||||
@@ -121,7 +121,7 @@ class ForeignKeyUtilsTest {
|
||||
fakeClock.advanceOneMilli();
|
||||
Host newHost1 = persistActiveHost("ns1.example.com");
|
||||
assertThat(
|
||||
ForeignKeyUtils.loadCached(
|
||||
ForeignKeyUtils.loadByCacheIfEnabled(
|
||||
Host.class,
|
||||
ImmutableList.of("ns1.example.com", "ns2.example.com", "ns3.example.com"),
|
||||
fakeClock.nowUtc()))
|
||||
@@ -134,7 +134,7 @@ class ForeignKeyUtilsTest {
|
||||
Host host2 = persistActiveHost("ns2.example.com");
|
||||
persistResource(host2.asBuilder().setDeletionTime(fakeClock.nowUtc().minusDays(1)).build());
|
||||
assertThat(
|
||||
ForeignKeyUtils.loadCached(
|
||||
ForeignKeyUtils.loadByCacheIfEnabled(
|
||||
Host.class,
|
||||
ImmutableList.of("ns1.example.com", "ns2.example.com", "ns3.example.com"),
|
||||
fakeClock.nowUtc()))
|
||||
@@ -144,7 +144,7 @@ class ForeignKeyUtilsTest {
|
||||
persistActiveHost("ns1.example.com");
|
||||
// Even though a new host1 is now live, the cache still returns the VKey to the old one.
|
||||
assertThat(
|
||||
ForeignKeyUtils.loadCached(
|
||||
ForeignKeyUtils.loadByCacheIfEnabled(
|
||||
Host.class,
|
||||
ImmutableList.of("ns1.example.com", "ns2.example.com", "ns3.example.com"),
|
||||
fakeClock.nowUtc()))
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// 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.console;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import google.registry.model.EntityTestCase;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Tests for {@link PasswordResetRequest}. */
|
||||
public class PasswordResetRequestTest extends EntityTestCase {
|
||||
|
||||
PasswordResetRequestTest() {
|
||||
super(JpaEntityCoverageCheck.ENABLED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_persistence() {
|
||||
PasswordResetRequest request =
|
||||
new PasswordResetRequest.Builder()
|
||||
.setRequester("requestor@email.tld")
|
||||
.setDestinationEmail("destination@email.tld")
|
||||
.setType(PasswordResetRequest.Type.EPP)
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.build();
|
||||
String verificationCode = request.getVerificationCode();
|
||||
assertThat(verificationCode).isNotEmpty();
|
||||
persistResource(request);
|
||||
PasswordResetRequest fromDatabase =
|
||||
DatabaseHelper.loadByKey(VKey.create(PasswordResetRequest.class, verificationCode));
|
||||
assertAboutImmutableObjects().that(fromDatabase).isEqualExceptFields(request, "requestTime");
|
||||
assertThat(fromDatabase.getRequestTime()).isEqualTo(fakeClock.nowUtc());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_nullFields() {
|
||||
PasswordResetRequest.Builder builder = new PasswordResetRequest.Builder();
|
||||
assertThrows(IllegalArgumentException.class, builder::build);
|
||||
builder.setType(PasswordResetRequest.Type.EPP);
|
||||
assertThrows(IllegalArgumentException.class, builder::build);
|
||||
builder.setRequester("foobar@email.tld");
|
||||
assertThrows(IllegalArgumentException.class, builder::build);
|
||||
builder.setDestinationEmail("email@email.tld");
|
||||
assertThrows(IllegalArgumentException.class, builder::build);
|
||||
builder.setRegistrarId("TheRegistrar");
|
||||
builder.build();
|
||||
}
|
||||
}
|
||||
+2
@@ -379,6 +379,7 @@ public abstract class JpaTransactionManagerExtension
|
||||
.setPassword("foo-BAR2")
|
||||
.setPhoneNumber("+1.3334445555")
|
||||
.setPhonePasscode("12345")
|
||||
.setRdapBaseUrls(ImmutableSet.of("https://rdap.newregistrar.com/"))
|
||||
.setRegistryLockAllowed(false)
|
||||
.build();
|
||||
}
|
||||
@@ -393,6 +394,7 @@ public abstract class JpaTransactionManagerExtension
|
||||
.setPassword("password2")
|
||||
.setPhoneNumber("+1.2223334444")
|
||||
.setPhonePasscode("22222")
|
||||
.setRdapBaseUrls(ImmutableSet.of("https://rdap.theregistrar.com/"))
|
||||
.setRegistryLockAllowed(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -205,16 +205,6 @@ have failed to comply with these terms.",
|
||||
}
|
||||
JsonArray notices = jsonObject.getAsJsonArray("notices");
|
||||
notices.add(createTosNotice());
|
||||
notices.add(
|
||||
JsonParser.parseString(
|
||||
"""
|
||||
{
|
||||
"description": [
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars \
|
||||
version 1.0"
|
||||
]
|
||||
}
|
||||
"""));
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,12 +43,13 @@ final class RdapDataStructuresTest {
|
||||
@Test
|
||||
void testRdapConformance() {
|
||||
assertThat(RdapConformance.INSTANCE.toJson())
|
||||
.isEqualTo(createJson(
|
||||
"[",
|
||||
" 'rdap_level_0',",
|
||||
" 'icann_rdap_response_profile_0',",
|
||||
" 'icann_rdap_technical_implementation_guide_0'",
|
||||
"]"));
|
||||
.isEqualTo(
|
||||
createJson(
|
||||
"[",
|
||||
" 'rdap_level_0',",
|
||||
" 'icann_rdap_response_profile_1',",
|
||||
" 'icann_rdap_technical_implementation_guide_1'",
|
||||
"]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -462,12 +462,12 @@ class RdapJsonFormatterTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetLastHistoryEntryByType() {
|
||||
void testGetLastHistoryByType() {
|
||||
// Expected data are from "rdapjson_domain_summary.json"
|
||||
assertThat(
|
||||
Maps.transformValues(
|
||||
rdapJsonFormatter.getLastHistoryEntryByType(domainFull),
|
||||
HistoryEntry::getModificationTime))
|
||||
RdapJsonFormatter.getLastHistoryByType(domainFull),
|
||||
RdapJsonFormatter.HistoryTimeAndRegistrar::modificationTime))
|
||||
.containsExactlyEntriesIn(
|
||||
ImmutableMap.of(TRANSFER, DateTime.parse("1999-12-01T00:00:00.000Z")));
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import google.registry.model.common.CursorTest;
|
||||
import google.registry.model.common.DnsRefreshRequestTest;
|
||||
import google.registry.model.common.FeatureFlagTest;
|
||||
import google.registry.model.console.ConsoleUpdateHistoryTest;
|
||||
import google.registry.model.console.PasswordResetRequestTest;
|
||||
import google.registry.model.console.UserTest;
|
||||
import google.registry.model.contact.ContactTest;
|
||||
import google.registry.model.domain.DomainSqlTest;
|
||||
@@ -104,6 +105,7 @@ import org.junit.runner.RunWith;
|
||||
FeatureFlagTest.class,
|
||||
HostHistoryTest.class,
|
||||
LockTest.class,
|
||||
PasswordResetRequestTest.class,
|
||||
PollMessageTest.class,
|
||||
PremiumListDaoTest.class,
|
||||
RdeRevisionTest.class,
|
||||
|
||||
@@ -84,9 +84,7 @@ public final class FullFieldsTestEntityHelper {
|
||||
.setFaxNumber("+1.2125551213")
|
||||
.setEmailAddress("contact-us@example.com")
|
||||
.setWhoisServer("whois.example.com")
|
||||
.setRdapBaseUrls(
|
||||
ImmutableSet.of(
|
||||
"https://rdap.example.com/withSlash/", "https://rdap.example.com/withoutSlash"))
|
||||
.setRdapBaseUrls(ImmutableSet.of("https://rdap.example.com/withSlash/"))
|
||||
.setUrl("http://my.fake.url")
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ package google.registry.whois;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.bsa.persistence.BsaTestingUtils.persistBsaLabel;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyCached;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCacheIfEnabled;
|
||||
import static google.registry.model.registrar.Registrar.State.ACTIVE;
|
||||
import static google.registry.model.registrar.Registrar.Type.PDT;
|
||||
import static google.registry.model.tld.Tlds.getTlds;
|
||||
@@ -147,8 +147,9 @@ public class WhoisActionTest {
|
||||
persistResource(makeDomainWithRegistrar(registrar));
|
||||
persistSimpleResources(makeRegistrarPocs(registrar));
|
||||
// Populate the cache for both the domain and contact.
|
||||
Domain domain = loadByForeignKeyCached(Domain.class, "cat.lol", clock.nowUtc()).get();
|
||||
Contact contact = loadByForeignKeyCached(Contact.class, "5372808-ERL", clock.nowUtc()).get();
|
||||
Domain domain = loadByForeignKeyByCacheIfEnabled(Domain.class, "cat.lol", clock.nowUtc()).get();
|
||||
Contact contact =
|
||||
loadByForeignKeyByCacheIfEnabled(Contact.class, "5372808-ERL", clock.nowUtc()).get();
|
||||
// Make a change to the domain and contact that won't be seen because the cache will be hit.
|
||||
persistResource(domain.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
persistResource(
|
||||
@@ -280,7 +281,7 @@ public class WhoisActionTest {
|
||||
@Test
|
||||
void testRun_domainNotFound_usesCache() {
|
||||
// Populate the cache with the nonexistence of this domain.
|
||||
assertThat(loadByForeignKeyCached(Domain.class, "cat.lol", clock.nowUtc())).isEmpty();
|
||||
assertThat(loadByForeignKeyByCacheIfEnabled(Domain.class, "cat.lol", clock.nowUtc())).isEmpty();
|
||||
// Add a new valid cat.lol domain that won't be found because the cache will be hit instead.
|
||||
persistActiveDomain("cat.lol");
|
||||
newWhoisAction("domain cat.lol\r\n").run();
|
||||
@@ -435,7 +436,8 @@ public class WhoisActionTest {
|
||||
void testRun_nameserver_usesCache() {
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.xn--q9jyb4c", "1.2.3.4"));
|
||||
// Populate the cache.
|
||||
Host host = loadByForeignKeyCached(Host.class, "ns1.cat.xn--q9jyb4c", clock.nowUtc()).get();
|
||||
Host host =
|
||||
loadByForeignKeyByCacheIfEnabled(Host.class, "ns1.cat.xn--q9jyb4c", clock.nowUtc()).get();
|
||||
// Make a change to the persisted host that won't be seen because the cache will be hit.
|
||||
persistResource(
|
||||
host.asBuilder()
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "entity",
|
||||
"handle": "%CONTACT_HANDLE_1%",
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName" : "entity",
|
||||
"handle" : "",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName" : "entity",
|
||||
"handle" : "%CONTACT_HANDLE_1%",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName" : "entity",
|
||||
"handle" : "%CONTACT_HANDLE_1%",
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName" : "entity",
|
||||
"handle" : "",
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
"handle": "%DOMAIN_HANDLE_1%",
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"status": [
|
||||
"client delete prohibited",
|
||||
@@ -25,12 +25,6 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "%REQUEST_URL%"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -122,6 +116,12 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "self",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"vcardArray" : [
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"entities": [
|
||||
@@ -15,6 +15,12 @@
|
||||
"rel": "self",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/addgraceperiod.lol"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.theregistrar.com/"
|
||||
}
|
||||
],
|
||||
"publicIds": [
|
||||
@@ -128,6 +134,12 @@
|
||||
"rel": "self",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/addgraceperiod.lol"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.theregistrar.com/domain/addgraceperiod.lol",
|
||||
"rel": "related",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/addgraceperiod.lol"
|
||||
}
|
||||
],
|
||||
"nameservers": [
|
||||
|
||||
+14
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"entities": [
|
||||
@@ -15,6 +15,12 @@
|
||||
"rel": "self",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/autorenew.lol"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.theregistrar.com/"
|
||||
}
|
||||
],
|
||||
"publicIds": [
|
||||
@@ -132,6 +138,12 @@
|
||||
"rel": "self",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/autorenew.lol"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.theregistrar.com/domain/autorenew.lol",
|
||||
"rel": "related",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/autorenew.lol"
|
||||
}
|
||||
],
|
||||
"nameservers": [
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"handle": "%DOMAIN_HANDLE_1%",
|
||||
@@ -25,12 +25,6 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "https://example.tld/rdap/domains"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "https://example.tld/rdap/domains"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -105,6 +99,12 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "self",
|
||||
"value": "https://example.tld/rdap/domains"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"vcardArray" : [
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"ldhName": "%DOMAIN_PUNYCODE_NAME_1%",
|
||||
@@ -26,12 +26,6 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "%REQUEST_URL%"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -117,6 +111,12 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "self",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"vcardArray" : [
|
||||
|
||||
+14
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"entities": [
|
||||
@@ -15,6 +15,12 @@
|
||||
"rel": "self",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/renew.lol"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.theregistrar.com/"
|
||||
}
|
||||
],
|
||||
"publicIds": [
|
||||
@@ -128,6 +134,12 @@
|
||||
"rel": "self",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/renew.lol"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.theregistrar.com/domain/renew.lol",
|
||||
"rel": "related",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/renew.lol"
|
||||
}
|
||||
],
|
||||
"nameservers": [
|
||||
|
||||
+8
-8
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"handle": "%DOMAIN_HANDLE_1%",
|
||||
@@ -25,12 +25,6 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "https://example.tld/rdap/domain/cat.lol"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "https://example.tld/rdap/domain/cat.lol"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -112,6 +106,12 @@
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/cat.lol"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" : [
|
||||
|
||||
+8
-8
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"handle": "%DOMAIN_HANDLE_1%",
|
||||
@@ -25,12 +25,6 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "https://example.tld/rdap/domain/cat.lol"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "https://example.tld/rdap/domain/cat.lol"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -112,6 +106,12 @@
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/cat.lol"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" : [
|
||||
|
||||
+14
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"entities": [
|
||||
@@ -15,6 +15,12 @@
|
||||
"rel": "self",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/redemption.lol"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.theregistrar.com/"
|
||||
}
|
||||
],
|
||||
"publicIds": [
|
||||
@@ -128,6 +134,12 @@
|
||||
"rel": "self",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/redemption.lol"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.theregistrar.com/domain/redemption.lol",
|
||||
"rel": "related",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/redemption.lol"
|
||||
}
|
||||
],
|
||||
"nameservers": [
|
||||
|
||||
+8
-8
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"handle": "%DOMAIN_HANDLE_1%",
|
||||
@@ -25,12 +25,6 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "%REQUEST_URL%"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -112,6 +106,12 @@
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" : [
|
||||
|
||||
+14
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"entities": [
|
||||
@@ -15,6 +15,12 @@
|
||||
"rel": "self",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/transfer.lol"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.theregistrar.com/"
|
||||
}
|
||||
],
|
||||
"publicIds": [
|
||||
@@ -128,6 +134,12 @@
|
||||
"rel": "self",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/transfer.lol"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.theregistrar.com/domain/transfer.lol",
|
||||
"rel": "related",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/transfer.lol"
|
||||
}
|
||||
],
|
||||
"nameservers": [
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
"handle": "%DOMAIN_HANDLE_1%",
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"status": [
|
||||
"client delete prohibited",
|
||||
@@ -26,12 +26,6 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "%REQUEST_URL%"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -115,6 +109,12 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "self",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"vcardArray" : [
|
||||
|
||||
+8
-8
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"handle": "%DOMAIN_HANDLE_1%",
|
||||
@@ -26,12 +26,6 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "https://example.tld/rdap/domains"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "https://example.tld/rdap/domains"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -115,6 +109,12 @@
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domains"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" : [
|
||||
|
||||
@@ -83,8 +83,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -140,12 +140,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title" : "Status Codes",
|
||||
"description" :
|
||||
|
||||
+2
-8
@@ -84,8 +84,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -120,12 +120,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title" : "Status Codes",
|
||||
"description" :
|
||||
|
||||
+2
-8
@@ -84,8 +84,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -141,12 +141,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title" : "Status Codes",
|
||||
"description" :
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"domainSearchResults": [
|
||||
{
|
||||
@@ -82,9 +82,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": ["This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"]
|
||||
},
|
||||
{
|
||||
"title": "Status Codes",
|
||||
"description": ["For more information on domain status codes, please visit https://icann.org/epp"],
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices": [
|
||||
{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance" : [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance" : [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "nameserver",
|
||||
"handle": "%NAMESERVER_HANDLE_1%",
|
||||
@@ -39,6 +39,12 @@
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.theregistrar.com/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"status": [
|
||||
"active"
|
||||
@@ -36,6 +36,12 @@
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.theregistrar.com/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "nameserver",
|
||||
"handle": "%NAMESERVER_HANDLE_1%",
|
||||
@@ -42,6 +42,12 @@
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.theregistrar.com/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "nameserver",
|
||||
"handle": "%NAMESERVER_HANDLE_1%",
|
||||
@@ -40,6 +40,12 @@
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.theregistrar.com/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
|
||||
@@ -112,12 +112,6 @@
|
||||
],
|
||||
"title":"RDAP Terms of Service"
|
||||
},
|
||||
{
|
||||
"description":
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description":
|
||||
[
|
||||
@@ -150,7 +144,7 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -82,11 +82,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description":[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title":"Status Codes",
|
||||
"description":[
|
||||
@@ -116,7 +111,7 @@
|
||||
],
|
||||
"rdapConformance":[
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -101,8 +101,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -134,12 +134,6 @@
|
||||
"type": "text/html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -118,8 +118,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -153,12 +153,6 @@
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -71,8 +71,8 @@
|
||||
],
|
||||
"rdapConformance" : [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -106,12 +106,6 @@
|
||||
"value": "https://example.tld/rdap/nameservers"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -232,8 +232,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -267,12 +267,6 @@
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -91,8 +91,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -127,12 +127,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title" : "Status Codes",
|
||||
"description" :
|
||||
|
||||
@@ -136,8 +136,8 @@
|
||||
],
|
||||
"rdapConformance" : [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -171,12 +171,6 @@
|
||||
"value": "https://example.tld/rdap/nameservers"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -11,6 +11,12 @@
|
||||
"href": "https://example.tld/rdap/entity/301",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds":
|
||||
@@ -69,6 +75,12 @@
|
||||
"href": "https://example.tld/rdap/entity/302",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds":
|
||||
@@ -127,6 +139,12 @@
|
||||
"href": "https://example.tld/rdap/entity/303",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds":
|
||||
@@ -185,6 +203,12 @@
|
||||
"href": "https://example.tld/rdap/entity/304",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds":
|
||||
@@ -236,8 +260,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices":
|
||||
[
|
||||
@@ -271,12 +295,6 @@
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description":
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName" : "entity",
|
||||
"handle" : "%REGISTRAR_HANDLE_1%",
|
||||
@@ -15,6 +15,12 @@
|
||||
"href": "https://example.tld/rdap/entity/%REGISTRAR_HANDLE_1%",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName" : "entity",
|
||||
"handle" : "%REGISTRAR_HANDLE_1%",
|
||||
"links" : [
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"status" : ["%STATUS_1%"],
|
||||
"roles" : ["registrar"],
|
||||
"events": [
|
||||
|
||||
@@ -232,8 +232,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices":
|
||||
[
|
||||
@@ -288,12 +288,6 @@
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description":
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -136,8 +136,8 @@
|
||||
],
|
||||
"rdapConformance" : [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -192,12 +192,6 @@
|
||||
"value": "https://example.tld/rdap/nameservers"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -64,9 +64,9 @@
|
||||
"links" :
|
||||
[
|
||||
{
|
||||
"rel" : "self",
|
||||
"rel": "self",
|
||||
"href": "https://example.tld/rdap/entity/0002-ROID",
|
||||
"type" : "application/rdap+json",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
}
|
||||
],
|
||||
@@ -182,6 +182,12 @@
|
||||
"href": "https://example.tld/rdap/entity/301",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
@@ -233,8 +239,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -289,12 +295,6 @@
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -11,6 +11,12 @@
|
||||
"href": "https://example.tld/rdap/entity/301",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
@@ -69,6 +75,12 @@
|
||||
"href": "https://example.tld/rdap/entity/302",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
@@ -127,6 +139,12 @@
|
||||
"href": "https://example.tld/rdap/entity/303",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
@@ -185,6 +203,12 @@
|
||||
"href": "https://example.tld/rdap/entity/304",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
@@ -236,8 +260,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -292,12 +316,6 @@
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"key":"value","rdapConformance":["rdap_level_0","icann_rdap_response_profile_0","icann_rdap_technical_implementation_guide_0"],"notices":[{"title":"RDAP Terms of Service","links":[{"href":"https:\/\/www.example.tld\/about\/rdap\/tos.html","rel":"alternate","type":"text\/html","value":"http:\/\/myserver.example.com\/help\/tos"}],"description":["By querying our Domain Database, you are agreeing to comply with these terms so please read them carefully.","Any information provided is 'as is' without any guarantee of accuracy.","Please do not misuse the Domain Database. It is intended solely for query-based access.","Don't use the Domain Database to allow, enable, or otherwise support the transmission of mass unsolicited, commercial advertising or solicitations.","Don't access our Domain Database through the use of high volume, automated electronic processes that send queries or data to the systems of any ICANN-accredited registrar.","You may only use the information contained in the Domain Database for lawful purposes.","Do not compile, repackage, disseminate, or otherwise use the information contained in the Domain Database in its entirety, or in any substantial portion, without our prior written permission.","We may retain certain details about queries to our Domain Database for the purposes of detecting and preventing misuse.","We reserve the right to restrict or deny your access to the database if we suspect that you have failed to comply with these terms.","We reserve the right to modify this agreement at any time."]}]}
|
||||
{"key":"value","rdapConformance":["rdap_level_0","icann_rdap_response_profile_1","icann_rdap_technical_implementation_guide_1"],"notices":[{"title":"RDAP Terms of Service","links":[{"href":"https:\/\/www.example.tld\/about\/rdap\/tos.html","rel":"alternate","type":"text\/html","value":"http:\/\/myserver.example.com\/help\/tos"}],"description":["By querying our Domain Database, you are agreeing to comply with these terms so please read them carefully.","Any information provided is 'as is' without any guarantee of accuracy.","Please do not misuse the Domain Database. It is intended solely for query-based access.","Don't use the Domain Database to allow, enable, or otherwise support the transmission of mass unsolicited, commercial advertising or solicitations.","Don't access our Domain Database through the use of high volume, automated electronic processes that send queries or data to the systems of any ICANN-accredited registrar.","You may only use the information contained in the Domain Database for lawful purposes.","Do not compile, repackage, disseminate, or otherwise use the information contained in the Domain Database in its entirety, or in any substantial portion, without our prior written permission.","We may retain certain details about queries to our Domain Database for the purposes of detecting and preventing misuse.","We reserve the right to restrict or deny your access to the database if we suspect that you have failed to comply with these terms.","We reserve the right to modify this agreement at any time."]}]}
|
||||
|
||||
@@ -21,11 +21,6 @@
|
||||
"rel" : "related",
|
||||
"href" : "https://rdap.example.com/withSlash/domain/cat.xn--q9jyb4c",
|
||||
"type" : "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel" : "related",
|
||||
"href" : "https://rdap.example.com/withoutSlash/domain/cat.xn--q9jyb4c",
|
||||
"type" : "application/rdap+json"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -112,6 +107,12 @@
|
||||
"rel" : "self",
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
|
||||
@@ -21,11 +21,6 @@
|
||||
"href": "https://rdap.example.com/withSlash/domain/cat.xn--q9jyb4c",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.example.com/withoutSlash/domain/cat.xn--q9jyb4c",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -109,6 +104,12 @@
|
||||
"rel": "self",
|
||||
"href": "https://example.tld/rdap/entity/1",
|
||||
"type": "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds": [
|
||||
|
||||
@@ -22,11 +22,6 @@
|
||||
"href": "https://rdap.example.com/withSlash/domain/fish.xn--q9jyb4c",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.example.com/withoutSlash/domain/fish.xn--q9jyb4c",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -60,6 +55,12 @@
|
||||
"rel": "self",
|
||||
"href": "https://example.tld/rdap/entity/1",
|
||||
"type": "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds":
|
||||
|
||||
@@ -34,6 +34,12 @@
|
||||
"rel" : "self",
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
|
||||
@@ -30,6 +30,12 @@
|
||||
"rel": "self",
|
||||
"href": "https://example.tld/rdap/entity/1",
|
||||
"type": "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds":
|
||||
|
||||
@@ -30,6 +30,12 @@
|
||||
"rel": "self",
|
||||
"href": "https://example.tld/rdap/entity/1",
|
||||
"type": "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds":
|
||||
|
||||
@@ -29,6 +29,12 @@
|
||||
"rel" : "self",
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
|
||||
@@ -29,6 +29,12 @@
|
||||
"rel" : "self",
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user