1
0
mirror of https://github.com/google/nomulus synced 2026-05-18 13:51:45 +00:00

Compare commits

...

31 Commits

Author SHA1 Message Date
Ben McIlwain
df8ce38796 Rename whitelist -> allow list (#635)
* Rename whitelist -> allow list

* Merge branch 'master' into allowlist-denylist
2020-06-18 18:36:05 -04:00
Weimin Yu
382c8014de Fix flakiness caused by ofy entity name conflict (#636)
* Fix flakiness caused by ofy entity name conflict

Overrode the 'kind' of two test entities to prevent name conflicts.
Tests are flaky because of this.

Added a check in AppEngineRuleTest for conflicting kinds.
2020-06-18 13:14:10 -04:00
Ben McIlwain
57113b4746 Show price of reserved domains when using matching allocation token (#632)
* Show price of reserved domains when using matching allocation token

When the registrar passes the fee extension, this shows the price of the domain
on a check command for reserved domains if the provided allocation token is a
match. Of course, the price is already always displayed on non-reserved names
(regardless of whether the specific provided token is a match or not).

This affects domain checks only; the price is already always displayed on domain
creates because you already by definition have access to register the domain in
question.
2020-06-18 11:57:22 -04:00
Weimin Yu
d43564172f Refactor pipline for Datastore backup loading (#628)
* Refactor pipline for Datastore backup loading

Refactored pipeline transforms.

Added testing utilities that handles assertions better.

Investigated and documented challenges in serializing Ofy entities
without side effects.
2020-06-17 22:10:14 -04:00
gbrodman
69a1d04c18 Remove 'fullyQualified' from host and domain names (#631)
* Remove 'fullyQualified' from host and domain names

We don't actually enforce that these are properly fully-qualified
(there's no dot at the end) and we specifically use the term "label
name" when talking about labels.

Note: this doesn't convert FQDN -> DN (et al) in at least two types of
cases:
1. When the term is part of the XML schema
2. When the term is used by some external system, e.g. SafeBrowsing API

* Add TODO to rename fields
2020-06-17 16:19:26 -04:00
Michael Muller
19395def5b Implement a persistable Transaction object (#614)
* Implement a persistable Transaction object

Implement Transaction, which encapsulates a sequence of datastore mutations
that can be serialized and written to the Cloud SQL Transaction table and
subsequently replayed to Datastore from a backend cron job.

* Changes requested in review

* Add a mujtation count to the persisted format
2020-06-17 14:16:48 -04:00
Shicong Huang
2f600e3e69 Implement remaining methods in JpaTransactionManager (#633) 2020-06-17 12:39:34 -04:00
Michael Muller
31841ccc55 Fix cookie processing for RDAP URL update (#630)
* Fix cookie processing for RDAP URL update

The existing code only does cookie processing on the _first_ Set-Cookie
header.  Therefore, if the "id" cookie used for authentication is defined in
anything other than the first Set-Cookie header (as it now is), we don't find
it.

Replace the cookie processing stanza with a line that processes all cookies in
all Set-Cookie headers.
2020-06-16 15:07:13 -04:00
gbrodman
4f37c65af5 Fix versioning semantic merge conflict (#629) 2020-06-16 12:51:28 -04:00
gbrodman
47178d4fb5 Add HostBase and HostHistory classes (#587)
* Add proof of concept for HostBase and HostHistory classes

* Use a PROPERTY accessor for @Ids

* Add an unused setter method for Hibernate's sake

* Refactor HostHistory

* Some responses to CR

* Fix relationship and test

* Manually manage the foreign keys for HostHistory

* Protect HostBase's builder and use text for the enum type

* Add responses to CR

- Add javadocs
- Create an ID sequence for host history objects

* Don't try to set the ID

* Use a Long and remove the setter

* Add some comments and rename a couple fields

* Don't change Datastore schema

* Use Long in the Datastore schema

* Add new createVKey method

* Add comments and rename fields

* Rename v27->v31 and regenerate the golden

* Fix superordinateDomain and inetAddresses in HostHistory

* V31 -> V32

* Fix SQL files that got messed up in the merge

* Configure and use a manually-created history ID sequence

* Add three more indices to HostHistory
2020-06-16 11:47:17 -04:00
Shicong Huang
26e2a51180 Refactor TransferData to remove unused fields in Contact table (#623)
* Add DomainTransferData and ContactTransferData

* Refactor TransferData to remove unused fields in Contact table

* Add scope for TransferData's type parameter
2020-06-16 10:42:57 -04:00
Michael Muller
21f2f38ad1 Allow class-specific creation of symmetric VKeys (#625)
* Allow class-specific creation of symmetrict VKeys

When translating from a datastore Key to a VKey, see if the "kind" class
contains a createVKey(com.googlecode.objectify.Key) static method and if it
does, use it to construct a symmetric VKey instead of simply creating an
objectify-sided asymmetric VKey.

As a test case for this, implement the createVKey() static function for
DomainBase.  Also, create unit tests for VKeyTranslatorFactory, which
continues to house the functionality.
2020-06-15 11:35:03 -04:00
Lai Jiang
0e04f6ca5b Upgrade to Gradle 6.5 (#627) 2020-06-15 10:57:29 -04:00
Ben McIlwain
4be70c8509 Refactor Fee handling so that each fee knows if it's premium (#626)
* Refactor Fee handling so that each fee knows if it's premium

This is a noop for now, as the new isPremium boolean isn't yet used by anything,
but it will be used in follow-up PRs to add additional fee information using the
fee extension (see: b/157621273).  Specifically what we're trying to do here is
return <fee:command name="create" standard="1"> (using the finalized version of
the fee extension) when an entire command has no premium fee associated with
it. And in the current earlier versions of the fee extension that we support,
we'll want to display the correct fee amount and class for creates/checks on
reserved domains when a valid allocation token is passed. This also needs the
isPremium information.

There are no testing implications yet because isPremium isn't exposed anywhere,
but there will definitely be lots of test changes once it's feeding into EPP-
visible changes.

* Rename things, add method Javadoc

* Apply formatting
2020-06-12 16:43:02 -04:00
Ben McIlwain
cf1448bca8 Restore the original expiration time on domain restore (#601)
* Restore the original expiration time on domain restore

Except if that time is now in the past, then add a year to it.

* Apply auto-formatter changes to fix my local build

* Merge branch 'master' into restore-expiry-date

* Fix reversed comments
2020-06-12 14:33:49 -04:00
gbrodman
f62473542f Use the requested server host when creating the registry lock verification URL (#624)
* Use the server host when creating the registry lock verification URL

The app doesn't know about any external configuration that may point to
this app, so there's no way of finding out that, for instance,
registry.google points to the app. Thus, we have to use what the user
gives us so that, in our case, the registry-lock verification
emails can point to https://registry.google/registry-lock-verify instead
of https://domain-registry.appspot.com/registry-lock-verify. The former
is used by clients / users to authenticate, and unfortunately
authenticating on registry.google does not give authentication to
domain-registry.apspot.com.

Tested using the RDAP code that uses getServerName() -- in that case, if
you access registry.google/rdap/<>, it uses registry.google in the URLs
but if you use domain-registry.appspot.com/rdap/<>, it uses
domain-registry.appspot.com in the URLs.

Relatedly, frontend_config_prod-appengine.asciiproto in Piper
is what configures registry.google to point to
domain-registry.appspot.com
2020-06-12 10:11:53 -04:00
Lai Jiang
484173b659 Log client certificate type and length (#622)
* Log client certificate type and length

It appears that most client certificates are RSA certs, but we should
make sure that is indeed the case. Print out the strength of the cert
if it is RSA.

Also adds supports for TLS 1.3 and print out the supported cipher suites.

* Add a comment about zero length certificate

* Make length of non-RSA keys -1

* Don't use TLS 1.3 if JDK SSL provider is used
2020-06-11 17:11:40 -04:00
Weimin Yu
d3fd826dc1 Load CommitLog into BEAM pipeline (#618)
* Load CommitLog into BEAM pipeline

Created tools that can generate CommitLogs on a test
datastore.

Defined BEAM transforms that load from CommitLog files and
added a few simple tests.

This is work-in-progress. Next step is to build a consistent
backup by merging Datastore exports and CommitLogs.
2020-06-11 11:38:53 -04:00
gbrodman
1c62728886 Rename V30 -> V31 to avoid duplicates (#621) 2020-06-10 16:08:31 -04:00
Lai Jiang
b5d3186e67 Update Netty to the latest version (#620)
* Upgrade to the latest version of Netty

* Update lock files
2020-06-10 16:08:11 -04:00
gbrodman
b4dfec5fd5 Rename client_id to registrar_id in SQL (#619)
We'll eventually want to shift everything over to using registrar_id and
registrarId rather than client_id and clientId but for the sake of the
Datastore schema and existing code, we won't change the Java identifier
for now. Once we're completely and only on SQL, we can rename the Java
field easily.
2020-06-10 15:11:27 -04:00
gbrodman
40b14fb695 Create a converter for sets of InetAddresses and use it in HostResource (#612)
* Create a converter for sets of inetAddresses and use it in HostResource

This can just be a set of strings where each string represents an
address;  there's no need for it to be a separate table. This allows
for simplification of the SQL schema.

* Regenerate golden SQL file after renaming v28 -> v29

* Add more tests and rename a typo in the file

* Refactor common test code and use tm methods

* Use JUnit5 API

* Rename test entity
2020-06-10 13:04:20 -04:00
Shicong Huang
fdac686250 Add columns for TransferData in Domain and Contact (#577)
* Add columns for TransferData in Domain and Contact

* Rename flyway file and foreign key

* Rebase on master and address comment

* Compileable commit

* Fix unit test

* Refactor TransferServerApproveEntity

* Use tm().delete(vkeys)

* Rename transfer_period fields

* Rename client_id to registrar_id

* Rebase on master

* Resolve comment

* Rebase on master
2020-06-09 16:39:55 -04:00
Shicong Huang
f0765dc893 Make JpaUnitTestRule not depend on the nomulus schema (#613) 2020-06-09 14:51:13 -04:00
Lai Jiang
2995bb03fd Update Javadoc URL (#615) 2020-06-09 10:25:56 -04:00
gbrodman
0f415f78a6 Use the correct text VKey for HostResource's superordinateDomain (#608)
* Store the superordinateDomain reference as a VKey rather than Key

This is a reference to a Domain object, so we should store it as a VKey
in reference to the Domain table. This should not affect any business
logic, but rather will allow us to set up the SQL tables for
HostResource et al. properly.
2020-06-08 12:21:51 -04:00
gbrodman
b324fb98d3 Only use asymmetric VKeys for EPP resources (#611)
Given that we currently have no way of reconstituting a symmetric key
from the asymmetric key (or at least, we don't have a 100% reliable way
of doing so) it's best to keep keys as asymmetrical, referring to the
correct database. That way, we don't get situations where we cannot
compare equality of two keys due to one being asymmetrical and one being
symmetrical.
2020-06-05 16:55:12 -04:00
Lai Jiang
d27fe8ead5 Fix a typo (#610) 2020-06-05 15:53:17 -04:00
Lai Jiang
da65a38782 Add a GCB job to build and publish javadoc (#609) 2020-06-05 13:00:15 -04:00
Legina Chen
5a1f3d0376 Remove platformType and threatEntryMetaData fields from ThreatMatch (#607)
* Remove platformType and threatEntryMetaData fields from ThreatMatch

* Run google-java-format on both files

* Add test for removal of unnecessary fields

* Removed unnecessary fields from Spec11PipelineTest.testEndToEndPipeline_generatesExpectedFiles

* Added style check

* Fix typo
2020-06-05 09:00:07 -07:00
Shicong Huang
b1241b98b2 Generate sql schema for PollMessage (#582)
* Generate sql schema for PollMessage

* Rework columns and resolve comments

* Fix datastore schema
2020-06-04 18:24:59 -04:00
284 changed files with 6088 additions and 2398 deletions

View File

@@ -33,7 +33,7 @@ running system:
* View the source code for the [GAE app](https://github.com/google/nomulus/tree/master/core/src/main/java/google/registry)
and for the [GKE proxy](https://github.com/google/nomulus/tree/master/proxy/src/main/java/google/registry)
* [Other docs](https://github.com/google/nomulus/tree/master/docs)
* [Javadoc](https://nomulus.foo/javadoc/latest/)
* [Javadoc](https://javadoc.nomulus.foo/)
* [Nomulus discussion
group](https://groups.google.com/forum/#!forum/nomulus-discuss), for any
other questions

View File

@@ -458,6 +458,7 @@ task javadoc(type: Javadoc) {
source javadocSource
classpath = files(javadocClasspath)
destinationDir = file("${buildDir}/docs/javadoc")
options.encoding = "UTF-8"
// In a lot of places we don't write @return so suppress warnings about that.
options.addBooleanOption('Xdoclint:all,-missing', true)
options.addBooleanOption("-allow-script-in-comments",true)
@@ -467,4 +468,4 @@ task javadoc(type: Javadoc) {
tasks.build.dependsOn(tasks.javadoc)
javadocDependentTasks.each { tasks.javadoc.shouldRunAfter(it) }
javadocDependentTasks.each { tasks.javadoc.dependsOn(it) }

View File

@@ -0,0 +1,66 @@
// Copyright 2020 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.backup;
import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.ApiProxy.Environment;
import com.google.common.collect.ImmutableMap;
import java.io.Closeable;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Sets up a placeholder {@link Environment} on a non-AppEngine platform so that Datastore Entities
* can be deserialized. See {@code DatastoreEntityExtension} in test source for more information.
*/
public class AppEngineEnvironment implements Closeable {
private static final Environment PLACEHOLDER_ENV = createAppEngineEnvironment();
private boolean isPlaceHolderNeeded;
public AppEngineEnvironment() {
isPlaceHolderNeeded = ApiProxy.getCurrentEnvironment() == null;
// isPlaceHolderNeeded may be true when we are invoked in a test with AppEngineRule.
if (isPlaceHolderNeeded) {
ApiProxy.setEnvironmentForCurrentThread(PLACEHOLDER_ENV);
}
}
@Override
public void close() {
if (isPlaceHolderNeeded) {
ApiProxy.setEnvironmentForCurrentThread(null);
}
}
/** Returns a placeholder {@link Environment} that can return hardcoded AppId and Attributes. */
private static Environment createAppEngineEnvironment() {
return (Environment)
Proxy.newProxyInstance(
Environment.class.getClassLoader(),
new Class[] {Environment.class},
(Object proxy, Method method, Object[] args) -> {
switch (method.getName()) {
case "getAppId":
return "PlaceholderAppId";
case "getAttributes":
return ImmutableMap.<String, Object>of();
default:
throw new UnsupportedOperationException(method.getName());
}
});
}
}

View File

@@ -0,0 +1,95 @@
// Copyright 2020 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.backup;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.backup.BackupUtils.createDeserializingIterator;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import google.registry.model.ImmutableObject;
import google.registry.model.ofy.CommitLogCheckpoint;
import google.registry.model.ofy.CommitLogManifest;
import google.registry.model.ofy.CommitLogMutation;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.Iterator;
import java.util.stream.Stream;
/**
* Helpers for reading CommitLog records from a file.
*
* <p>This class is adapted from {@link RestoreCommitLogsAction}, and will be used in the initial
* population of the Cloud SQL database.
*/
public final class CommitLogImports {
private CommitLogImports() {}
/**
* Returns entities in an {@code inputStream} (from a single CommitLog file) as an {@link
* ImmutableList} of {@link VersionedEntity} records. Upon completion the {@code inputStream} is
* closed.
*
* <p>The returned list may be empty, since CommitLogs are written at fixed intervals regardless
* if actual changes exist.
*
* <p>A CommitLog file starts with a {@link CommitLogCheckpoint}, followed by (repeated)
* subsequences of [{@link CommitLogManifest}, [{@link CommitLogMutation}] ...]. Each subsequence
* represents the changes in one transaction. The {@code CommitLogManifest} contains deleted
* entity keys, whereas each {@code CommitLogMutation} contains one whole entity.
*/
public static ImmutableList<VersionedEntity> loadEntities(InputStream inputStream) {
try (AppEngineEnvironment appEngineEnvironment = new AppEngineEnvironment();
InputStream input = new BufferedInputStream(inputStream)) {
Iterator<ImmutableObject> commitLogs = createDeserializingIterator(input);
checkState(commitLogs.hasNext());
checkState(commitLogs.next() instanceof CommitLogCheckpoint);
return Streams.stream(commitLogs)
.map(
e ->
e instanceof CommitLogManifest
? VersionedEntity.fromManifest((CommitLogManifest) e)
: Stream.of(VersionedEntity.fromMutation((CommitLogMutation) e)))
.flatMap(s -> s)
.collect(ImmutableList.toImmutableList());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/** Covenience method that adapts {@link #loadEntities(InputStream)} to a {@link File}. */
public static ImmutableList<VersionedEntity> loadEntities(File commitLogFile) {
try {
return loadEntities(new FileInputStream(commitLogFile));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Covenience method that adapts {@link #loadEntities(InputStream)} to a {@link
* ReadableByteChannel}.
*/
public static ImmutableList<VersionedEntity> loadEntities(ReadableByteChannel channel) {
return loadEntities(Channels.newInputStream(channel));
}
}

View File

@@ -0,0 +1,198 @@
// Copyright 2020 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.backup;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityTranslator;
import com.google.appengine.api.datastore.Key;
import com.google.auto.value.AutoValue;
import com.google.auto.value.extension.memoized.Memoized;
import google.registry.model.ofy.CommitLogManifest;
import google.registry.model.ofy.CommitLogMutation;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Stream;
import javax.annotation.Nullable;
/**
* A Datastore {@link Entity Entity's} serialized state with timestamp. The intended use case is a
* multi-stage pipeline where an Entity's Java form is not needed in most stages.
*
* <p>For a new or updated Entity, its serialized bytes are stored along with its Datastore {@link
* Key}. For a deleted entity, only its Datastore {@link Key} is stored, and the {@link
* #entityProtoBytes} field is left unset.
*
* <p>Storing raw bytes is motivated by two factors. First, since I/O is frequent and the Java
* objects are rarely needed in our target use case, storing raw bytes is the most efficient
* approach. More importantly, due to our data model and our customization of {@link
* google.registry.model.ofy.ObjectifyService ObjectifyService}, it is challenging to implement a
* serializer for Objectify entities that preserves the value of all properties. Without such
* serializers, Objectify entities cannot be used in a pipeline.
*
* <p>Objectify entities do not implement {@link Serializable}, serialization of such objects is as
* follows:
*
* <ul>
* <li>Convert an Objectify entity to a Datastore {@link Entity}: {@code
* ofy().save().toEntity(..)}
* <li>Entity is serializable, but the more efficient approach is to convert an Entity to a
* ProtocolBuffer ({@link com.google.storage.onestore.v3.OnestoreEntity.EntityProto}) and then
* to raw bytes.
* </ul>
*
* <p>When the first conversion above is applied to an Objectify entity, a property value in the
* output may differ from the input in two situations:
*
* <ul>
* <li>If a property is of an assign-on-persist data type, e.g., {@link
* google.registry.model.UpdateAutoTimestamp}.
* <li>If it is related to CommitLog management, e.g., {@link google.registry.model.EppResource
* EppResource.revisions}.
* </ul>
*
* <p>Working around the side effects caused by our customization is difficult. Any solution would
* likely rely on Objectify's stack of context. However, many Objectify invocations in our code base
* are hardcoded to call the customized version of ObjectifyService, rendering Objectify's stack
* useless.
*
* <p>For now, this inability to use Objectify entities in pipelines is mostly a testing problem: we
* can not perform {@link org.apache.beam.sdk.testing.PAssert BEAM pipeline assertions} on Objectify
* entities. {@code InitSqlTestUtils.assertContainsExactlyElementsIn} is an example of a workaround.
*
* <p>Note that {@link Optional java.util.Optional} is not serializable, therefore cannot be used as
* property type in this class.
*/
@AutoValue
public abstract class VersionedEntity implements Serializable {
private static final long serialVersionUID = 1L;
public abstract long commitTimeMills();
/** The {@link Key} of the {@link Entity}. */
public abstract Key key();
/** Serialized form of the {@link Entity}. This property is {@code null} for a deleted Entity. */
@Nullable
abstract ImmutableBytes entityProtoBytes();
@Memoized
public Optional<Entity> getEntity() {
return Optional.ofNullable(entityProtoBytes())
.map(ImmutableBytes::getBytes)
.map(EntityTranslator::createFromPbBytes);
}
public boolean isDelete() {
return entityProtoBytes() == null;
}
/**
* Converts deleted entity keys in {@code manifest} into a {@link Stream} of {@link
* VersionedEntity VersionedEntities}. See {@link CommitLogImports#loadEntities} for more
* information.
*/
public static Stream<VersionedEntity> fromManifest(CommitLogManifest manifest) {
long commitTimeMillis = manifest.getCommitTime().getMillis();
return manifest.getDeletions().stream()
.map(com.googlecode.objectify.Key::getRaw)
.map(key -> builder().commitTimeMills(commitTimeMillis).key(key).build());
}
/* Converts a {@link CommitLogMutation} to a {@link VersionedEntity}. */
public static VersionedEntity fromMutation(CommitLogMutation mutation) {
return from(
com.googlecode.objectify.Key.create(mutation).getParent().getId(),
mutation.getEntityProtoBytes());
}
public static VersionedEntity from(long commitTimeMillis, byte[] entityProtoBytes) {
return builder()
.entityProtoBytes(entityProtoBytes)
.key(EntityTranslator.createFromPbBytes(entityProtoBytes).getKey())
.commitTimeMills(commitTimeMillis)
.build();
}
static Builder builder() {
return new AutoValue_VersionedEntity.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder commitTimeMills(long commitTimeMillis);
abstract Builder entityProtoBytes(ImmutableBytes bytes);
public abstract Builder key(Key key);
public abstract VersionedEntity build();
public Builder entityProtoBytes(byte[] bytes) {
return entityProtoBytes(new ImmutableBytes(bytes));
}
}
/**
* Wraps a byte array and prevents it from being modified by its original owner.
*
* <p>While this class seems an overkill, it exists for two reasons:
*
* <ul>
* <li>It is easier to override the {@link #equals} method here (for value-equivalence check)
* than to override the AutoValue-generated {@code equals} method.
* <li>To appease the style checker, which forbids arrays as AutoValue property.
* </ul>
*/
static final class ImmutableBytes implements Serializable {
private static final long serialVersionUID = 1L;
private final byte[] bytes;
ImmutableBytes(byte[] bytes) {
this.bytes = Arrays.copyOf(bytes, bytes.length);
}
/**
* Returns the saved byte array. Invocation is restricted to trusted callers, who must not
* modify the array.
*/
byte[] getBytes() {
return bytes;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ImmutableBytes)) {
return false;
}
ImmutableBytes that = (ImmutableBytes) o;
// Do not use Objects.equals, which checks reference identity instead of data in array.
return Arrays.equals(bytes, that.bytes);
}
@Override
public int hashCode() {
// Do not use Objects.hashCode, which hashes the reference, not the data in array.
return Arrays.hashCode(bytes);
}
}
}

View File

@@ -347,7 +347,7 @@ public class DeleteContactsAndHostsAction implements Runnable {
String resourceClientId = resource.getPersistedCurrentSponsorClientId();
if (resource instanceof HostResource && ((HostResource) resource).isSubordinate()) {
resourceClientId =
ofy().load().key(((HostResource) resource).getSuperordinateDomain()).now()
tm().load(((HostResource) resource).getSuperordinateDomain())
.cloneProjectedAtTime(now)
.getCurrentSponsorClientId();
}
@@ -426,7 +426,7 @@ public class DeleteContactsAndHostsAction implements Runnable {
if (resource instanceof HostResource) {
return ImmutableList.of(
HostPendingActionNotificationResponse.create(
((HostResource) resource).getFullyQualifiedHostName(), deleteAllowed, trid, now));
((HostResource) resource).getHostName(), deleteAllowed, trid, now));
} else if (resource instanceof ContactResource) {
return ImmutableList.of(
ContactPendingActionNotificationResponse.create(
@@ -465,11 +465,12 @@ public class DeleteContactsAndHostsAction implements Runnable {
} else if (existingResource instanceof HostResource) {
HostResource host = (HostResource) existingResource;
if (host.isSubordinate()) {
dnsQueue.addHostRefreshTask(host.getFullyQualifiedHostName());
ofy().save().entity(
ofy().load().key(host.getSuperordinateDomain()).now().asBuilder()
.removeSubordinateHost(host.getFullyQualifiedHostName())
.build());
dnsQueue.addHostRefreshTask(host.getHostName());
tm().saveNewOrUpdate(
tm().load(host.getSuperordinateDomain())
.asBuilder()
.removeSubordinateHost(host.getHostName())
.build());
}
} else {
throw new IllegalStateException(

View File

@@ -177,7 +177,7 @@ public class DeleteProberDataAction implements Runnable {
return;
}
String domainName = domain.getFullyQualifiedDomainName();
String domainName = domain.getDomainName();
if (domainName.equals("nic." + domain.getTld())) {
getContext().incrementCounter("skipped, NIC domain");
return;
@@ -246,28 +246,32 @@ public class DeleteProberDataAction implements Runnable {
}
private void softDeleteDomain(final DomainBase domain) {
tm().transactNew(() -> {
DomainBase deletedDomain = domain
.asBuilder()
.setDeletionTime(tm().getTransactionTime())
.setStatusValues(null)
.build();
HistoryEntry historyEntry = new HistoryEntry.Builder()
.setParent(domain)
.setType(DOMAIN_DELETE)
.setModificationTime(tm().getTransactionTime())
.setBySuperuser(true)
.setReason("Deletion of prober data")
.setClientId(registryAdminClientId)
.build();
// Note that we don't bother handling grace periods, billing events, pending transfers,
// poll messages, or auto-renews because these will all be hard-deleted the next time the
// mapreduce runs anyway.
ofy().save().entities(deletedDomain, historyEntry);
updateForeignKeyIndexDeletionTime(deletedDomain);
dnsQueue.addDomainRefreshTask(deletedDomain.getFullyQualifiedDomainName());
}
);
tm().transactNew(
() -> {
DomainBase deletedDomain =
domain
.asBuilder()
.setDeletionTime(tm().getTransactionTime())
.setStatusValues(null)
.build();
HistoryEntry historyEntry =
new HistoryEntry.Builder()
.setParent(domain)
.setType(DOMAIN_DELETE)
.setModificationTime(tm().getTransactionTime())
.setBySuperuser(true)
.setReason("Deletion of prober data")
.setClientId(registryAdminClientId)
.build();
// Note that we don't bother handling grace periods, billing events, pending
// transfers,
// poll messages, or auto-renews because these will all be hard-deleted the next
// time the
// mapreduce runs anyway.
ofy().save().entities(deletedDomain, historyEntry);
updateForeignKeyIndexDeletionTime(deletedDomain);
dnsQueue.addDomainRefreshTask(deletedDomain.getDomainName());
});
}
}
}

View File

@@ -216,11 +216,11 @@ public class RefreshDnsOnHostRenameAction implements Runnable {
}
if (referencingHostKey != null) {
retrier.callWithRetry(
() -> dnsQueue.addDomainRefreshTask(domain.getFullyQualifiedDomainName()),
() -> dnsQueue.addDomainRefreshTask(domain.getDomainName()),
TransientFailureException.class);
logger.atInfo().log(
"Enqueued DNS refresh for domain %s referenced by host %s.",
domain.getFullyQualifiedDomainName(), referencingHostKey);
domain.getDomainName(), referencingHostKey);
getContext().incrementCounter("domains refreshed");
} else {
getContext().incrementCounter("domains not refreshed");

View File

@@ -96,7 +96,7 @@ public class RelockDomainAction implements Runnable {
String message =
String.format(
"Domain %s is already manually relocked, skipping automated relock.",
domain.getFullyQualifiedDomainName());
domain.getDomainName());
logger.atInfo().log(message);
// SC_NO_CONTENT (204) skips retry -- see the comment below
response.setStatus(SC_NO_CONTENT);
@@ -144,7 +144,7 @@ public class RelockDomainAction implements Runnable {
private void verifyDomainAndLockState(RegistryLock oldLock, DomainBase domain) {
// Domain shouldn't be deleted or have a pending transfer/delete
String domainName = domain.getFullyQualifiedDomainName();
String domainName = domain.getDomainName();
checkArgument(
!DateTimeUtils.isAtOrAfter(jpaTm().getTransactionTime(), domain.getDeletionTime()),
"Domain %s has been deleted",

View File

@@ -15,10 +15,12 @@
package google.registry.beam.initsql;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import org.joda.time.DateTime;
/**
* Helpers for determining the fully qualified paths to Nomulus backup files. A backup consists of a
@@ -30,13 +32,9 @@ public final class BackupPaths {
private static final String WILDCARD_CHAR = "*";
private static final String EXPORT_PATTERN_TEMPLATE = "%s/all_namespaces/kind_%s/input-%s";
/**
* Regex pattern that captures the kind string in a file name. Datastore places no restrictions on
* what characters may be used in a kind string.
*/
private static final String FILENAME_TO_KIND_REGEX = ".+/all_namespaces/kind_(.+)/input-.+";
private static final Pattern FILENAME_TO_KIND_PATTERN = Pattern.compile(FILENAME_TO_KIND_REGEX);
public static final String COMMIT_LOG_NAME_PREFIX = "commit_diff_until_";
private static final String COMMIT_LOG_PATTERN_TEMPLATE = "%s/" + COMMIT_LOG_NAME_PREFIX + "*";
/**
* Returns a regex pattern that matches all Datastore export files of a given {@code kind}.
@@ -50,6 +48,21 @@ public final class BackupPaths {
return String.format(EXPORT_PATTERN_TEMPLATE, exportDir, kind, WILDCARD_CHAR);
}
/**
* Returns an {@link ImmutableList} of regex patterns that match all Datastore export files of the
* given {@code kinds}.
*
* @param exportDir path to the top directory of a Datastore export
* @param kinds all entity 'kinds' to be matched
*/
public static ImmutableList<String> getExportFilePatterns(
String exportDir, Iterable<String> kinds) {
checkNotNull(kinds, "kinds");
return Streams.stream(kinds)
.map(kind -> getExportFileNamePattern(exportDir, kind))
.collect(ImmutableList.toImmutableList());
}
/**
* Returns the fully qualified path of a Datastore export file with the given {@code kind} and
* {@code shard}.
@@ -65,22 +78,16 @@ public final class BackupPaths {
return String.format(EXPORT_PATTERN_TEMPLATE, exportDir, kind, Integer.toString(shard));
}
/**
* Returns the 'kind' of entity stored in a file based on the file name.
*
* <p>This method poses low risk and greatly simplifies the implementation of some transforms in
* {@link ExportLoadingTransforms}.
*
* @see ExportLoadingTransforms
*/
public static String getKindFromFileName(String fileName) {
/** Returns an {@link ImmutableList} of regex patterns that match all CommitLog files. */
public static ImmutableList<String> getCommitLogFilePatterns(String commitLogDir) {
return ImmutableList.of(String.format(COMMIT_LOG_PATTERN_TEMPLATE, commitLogDir));
}
/** Gets the Commit timestamp from a CommitLog file name. */
public static DateTime getCommitLogTimestamp(String fileName) {
checkArgument(!isNullOrEmpty(fileName), "Null or empty fileName.");
Matcher matcher = FILENAME_TO_KIND_PATTERN.matcher(fileName);
checkArgument(
matcher.matches(),
"Illegal file name %s, should match %s.",
fileName,
FILENAME_TO_KIND_REGEX);
return matcher.group(1);
int start = fileName.lastIndexOf(COMMIT_LOG_NAME_PREFIX);
checkArgument(start >= 0, "Illegal file name %s.", fileName);
return DateTime.parse(fileName.substring(start + COMMIT_LOG_NAME_PREFIX.length()));
}
}

View File

@@ -1,117 +0,0 @@
// Copyright 2020 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.beam.initsql;
import static google.registry.beam.initsql.BackupPaths.getExportFileNamePattern;
import static google.registry.beam.initsql.BackupPaths.getKindFromFileName;
import static org.apache.beam.sdk.values.TypeDescriptors.kvs;
import static org.apache.beam.sdk.values.TypeDescriptors.strings;
import com.google.common.collect.ImmutableList;
import google.registry.tools.LevelDbLogReader;
import java.io.IOException;
import java.util.Collection;
import org.apache.beam.sdk.coders.StringUtf8Coder;
import org.apache.beam.sdk.io.Compression;
import org.apache.beam.sdk.io.FileIO;
import org.apache.beam.sdk.io.FileIO.ReadableFile;
import org.apache.beam.sdk.io.fs.EmptyMatchTreatment;
import org.apache.beam.sdk.io.fs.MatchResult.Metadata;
import org.apache.beam.sdk.transforms.Create;
import org.apache.beam.sdk.transforms.DoFn;
import org.apache.beam.sdk.transforms.MapElements;
import org.apache.beam.sdk.transforms.PTransform;
import org.apache.beam.sdk.transforms.ParDo;
import org.apache.beam.sdk.values.KV;
import org.apache.beam.sdk.values.PBegin;
import org.apache.beam.sdk.values.PCollection;
import org.apache.beam.sdk.values.TypeDescriptor;
/**
* {@link PTransform Pipeline transforms} for loading from Datastore export files. They are all part
* of a transformation that loads raw records from a Datastore export, and are broken apart for
* testing.
*
* <p>We drop the 'kind' information in {@link #getDatastoreExportFilePatterns} and recover it later
* using the file paths. Although we could have kept it by passing around {@link KV key-value
* pairs}, the code would be more complicated, especially in {@link #loadDataFromFiles()}.
*/
public class ExportLoadingTransforms {
/**
* Returns a {@link PTransform transform} that can generate a collection of patterns that match
* all Datastore export files of the given {@code kinds}.
*/
public static PTransform<PBegin, PCollection<String>> getDatastoreExportFilePatterns(
String exportDir, Collection<String> kinds) {
return Create.of(
kinds.stream()
.map(kind -> getExportFileNamePattern(exportDir, kind))
.collect(ImmutableList.toImmutableList()))
.withCoder(StringUtf8Coder.of());
}
/**
* Returns a {@link PTransform} from file name patterns to file {@link Metadata Metadata records}.
*/
public static PTransform<PCollection<String>, PCollection<Metadata>> getFilesByPatterns() {
return new PTransform<PCollection<String>, PCollection<Metadata>>() {
@Override
public PCollection<Metadata> expand(PCollection<String> input) {
return input.apply(FileIO.matchAll().withEmptyMatchTreatment(EmptyMatchTreatment.DISALLOW));
}
};
}
/**
* Returns a {@link PTransform} from file {@link Metadata} to {@link KV Key-Value pairs} with
* entity 'kind' as key and raw record as value.
*/
public static PTransform<PCollection<Metadata>, PCollection<KV<String, byte[]>>>
loadDataFromFiles() {
return new PTransform<PCollection<Metadata>, PCollection<KV<String, byte[]>>>() {
@Override
public PCollection<KV<String, byte[]>> expand(PCollection<Metadata> input) {
return input
.apply(FileIO.readMatches().withCompression(Compression.UNCOMPRESSED))
.apply(
MapElements.into(kvs(strings(), TypeDescriptor.of(ReadableFile.class)))
.via(file -> KV.of(getKindFromFileName(file.getMetadata().toString()), file)))
.apply("Load One LevelDb File", ParDo.of(new LoadOneFile()));
}
};
}
/**
* Reads a LevelDb file and converts each raw record into a {@link KV pair} of kind and bytes.
*
* <p>LevelDb files are not seekable because a large object may span multiple blocks. If a
* sequential read fails, the file needs to be retried from the beginning.
*/
private static class LoadOneFile extends DoFn<KV<String, ReadableFile>, KV<String, byte[]>> {
@ProcessElement
public void processElement(
@Element KV<String, ReadableFile> kv, OutputReceiver<KV<String, byte[]>> output) {
try {
LevelDbLogReader.from(kv.getValue().open())
.forEachRemaining(record -> output.output(KV.of(kv.getKey(), record)));
} catch (IOException e) {
// Let the pipeline retry the whole file.
throw new RuntimeException(e);
}
}
}
}

View File

@@ -0,0 +1,195 @@
// Copyright 2020 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.beam.initsql;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static google.registry.beam.initsql.BackupPaths.getCommitLogTimestamp;
import static google.registry.beam.initsql.BackupPaths.getExportFilePatterns;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static google.registry.util.DateTimeUtils.isBeforeOrAt;
import avro.shaded.com.google.common.collect.Iterators;
import com.google.common.annotations.VisibleForTesting;
import google.registry.backup.CommitLogImports;
import google.registry.backup.VersionedEntity;
import google.registry.tools.LevelDbLogReader;
import java.util.Collection;
import java.util.Iterator;
import org.apache.beam.sdk.coders.StringUtf8Coder;
import org.apache.beam.sdk.io.Compression;
import org.apache.beam.sdk.io.FileIO;
import org.apache.beam.sdk.io.FileIO.ReadableFile;
import org.apache.beam.sdk.io.fs.EmptyMatchTreatment;
import org.apache.beam.sdk.io.fs.MatchResult.Metadata;
import org.apache.beam.sdk.transforms.Create;
import org.apache.beam.sdk.transforms.DoFn;
import org.apache.beam.sdk.transforms.PTransform;
import org.apache.beam.sdk.transforms.ParDo;
import org.apache.beam.sdk.transforms.ProcessFunction;
import org.apache.beam.sdk.values.PBegin;
import org.apache.beam.sdk.values.PCollection;
import org.joda.time.DateTime;
/**
* {@link PTransform Pipeline transforms} used in pipelines that load from both Datastore export
* files and Nomulus CommitLog files.
*/
public final class Transforms {
private Transforms() {}
/**
* The commitTimestamp assigned to all entities loaded from a Datastore export file. The exact
* value does not matter, but it must be lower than the timestamps of real CommitLog records.
*/
@VisibleForTesting static final long EXPORT_ENTITY_TIME_STAMP = START_OF_TIME.getMillis();
/**
* Returns a {@link PTransform transform} that can generate a collection of patterns that match
* all Datastore CommitLog files.
*/
public static PTransform<PBegin, PCollection<String>> getCommitLogFilePatterns(
String commitLogDir) {
return toStringPCollection(BackupPaths.getCommitLogFilePatterns(commitLogDir));
}
/**
* Returns a {@link PTransform transform} that can generate a collection of patterns that match
* all Datastore export files of the given {@code kinds}.
*/
public static PTransform<PBegin, PCollection<String>> getDatastoreExportFilePatterns(
String exportDir, Collection<String> kinds) {
return toStringPCollection(getExportFilePatterns(exportDir, kinds));
}
/**
* Returns a {@link PTransform} from file name patterns to file {@link Metadata Metadata records}.
*/
public static PTransform<PCollection<String>, PCollection<Metadata>> getFilesByPatterns() {
return new PTransform<PCollection<String>, PCollection<Metadata>>() {
@Override
public PCollection<Metadata> expand(PCollection<String> input) {
return input.apply(FileIO.matchAll().withEmptyMatchTreatment(EmptyMatchTreatment.DISALLOW));
}
};
}
/**
* Returns CommitLog files with timestamps between {@code fromTime} (inclusive) and {@code
* endTime} (exclusive).
*/
public static PTransform<PCollection<? extends String>, PCollection<String>>
filterCommitLogsByTime(DateTime fromTime, DateTime toTime) {
return ParDo.of(new FilterCommitLogFileByTime(fromTime, toTime));
}
/** Returns a {@link PTransform} from file {@link Metadata} to {@link VersionedEntity}. */
public static PTransform<PCollection<Metadata>, PCollection<VersionedEntity>>
loadExportDataFromFiles() {
return processFiles(
new BackupFileReader(
file ->
Iterators.transform(
LevelDbLogReader.from(file.open()),
(byte[] bytes) -> VersionedEntity.from(EXPORT_ENTITY_TIME_STAMP, bytes))));
}
/** Returns a {@link PTransform} from file {@link Metadata} to {@link VersionedEntity}. */
public static PTransform<PCollection<Metadata>, PCollection<VersionedEntity>>
loadCommitLogsFromFiles() {
return processFiles(
new BackupFileReader(file -> CommitLogImports.loadEntities(file.open()).iterator()));
}
/**
* Returns a {@link PTransform} that produces a {@link PCollection} containing all elements in the
* given {@link Iterable}.
*/
static PTransform<PBegin, PCollection<String>> toStringPCollection(Iterable<String> strings) {
return Create.of(strings).withCoder(StringUtf8Coder.of());
}
/**
* Returns a {@link PTransform} from file {@link Metadata} to {@link VersionedEntity} using
* caller-provided {@code transformer}.
*/
static PTransform<PCollection<Metadata>, PCollection<VersionedEntity>> processFiles(
DoFn<ReadableFile, VersionedEntity> transformer) {
return new PTransform<PCollection<Metadata>, PCollection<VersionedEntity>>() {
@Override
public PCollection<VersionedEntity> expand(PCollection<Metadata> input) {
return input
.apply(FileIO.readMatches().withCompression(Compression.UNCOMPRESSED))
.apply(transformer.getClass().getSimpleName(), ParDo.of(transformer));
// TODO(weiminyu): reshuffle to enable dynamic work rebalance per beam dev guide
}
};
}
private static class FilterCommitLogFileByTime extends DoFn<String, String> {
private final DateTime fromTime;
private final DateTime toTime;
public FilterCommitLogFileByTime(DateTime fromTime, DateTime toTime) {
checkNotNull(fromTime, "fromTime");
checkNotNull(toTime, "toTime");
checkArgument(
fromTime.isBefore(toTime),
"Invalid time range: fromTime (%s) is before endTime (%s)",
fromTime,
toTime);
this.fromTime = fromTime;
this.toTime = toTime;
}
@ProcessElement
public void processElement(@Element String fileName, OutputReceiver<String> out) {
DateTime timestamp = getCommitLogTimestamp(fileName);
if (isBeforeOrAt(fromTime, timestamp) && timestamp.isBefore(toTime)) {
out.output(fileName);
}
}
}
/**
* Reads from a Datastore backup file and converts its content into {@link VersionedEntity
* VersionedEntities}.
*
* <p>The input file may be either a LevelDb file from a Datastore export or a CommitLog file
* generated by the Nomulus server. In either case, the file contains variable-length records and
* must be read sequentially from the beginning. If the read fails, the file needs to be retried
* from the beginning.
*/
private static class BackupFileReader extends DoFn<ReadableFile, VersionedEntity> {
private final ProcessFunction<ReadableFile, Iterator<VersionedEntity>> reader;
private BackupFileReader(ProcessFunction<ReadableFile, Iterator<VersionedEntity>> reader) {
this.reader = reader;
}
@ProcessElement
public void processElement(@Element ReadableFile file, OutputReceiver<VersionedEntity> out) {
try {
reader.apply(file).forEachRemaining(out::output);
} catch (Exception e) {
// Let the pipeline use default retry strategy on the whole file. For GCP Dataflow this
// means retrying up to 4 times (may include other files grouped with this one), and failing
// the pipeline if no success.
throw new RuntimeException(e);
}
}
}
}

View File

@@ -24,22 +24,10 @@ import org.json.JSONObject;
public abstract class ThreatMatch implements Serializable {
private static final String THREAT_TYPE_FIELD = "threatType";
private static final String PLATFORM_TYPE_FIELD = "platformType";
private static final String METADATA_FIELD = "threatEntryMetadata";
private static final String DOMAIN_NAME_FIELD = "fullyQualifiedDomainName";
/** Returns what kind of threat it is (malware, phishing etc.) */
public abstract String threatType();
/** Returns what platforms it affects (Windows, Linux etc.) */
abstract String platformType();
/**
* Returns a String representing a JSON Object containing arbitrary metadata associated with this
* threat, or "NONE" if there is no metadata to retrieve.
*
* <p>This ideally would be a {@link JSONObject} type, but can't be due to serialization
* requirements.
*/
abstract String metadata();
/** Returns the fully qualified domain name [SLD].[TLD] of the matched threat. */
public abstract String fullyQualifiedDomainName();
@@ -52,29 +40,19 @@ public abstract class ThreatMatch implements Serializable {
static ThreatMatch create(JSONObject threatMatchJSON, String fullyQualifiedDomainName)
throws JSONException {
return new AutoValue_ThreatMatch(
threatMatchJSON.getString(THREAT_TYPE_FIELD),
threatMatchJSON.getString(PLATFORM_TYPE_FIELD),
threatMatchJSON.has(METADATA_FIELD)
? threatMatchJSON.getJSONObject(METADATA_FIELD).toString()
: "NONE",
fullyQualifiedDomainName);
threatMatchJSON.getString(THREAT_TYPE_FIELD), fullyQualifiedDomainName);
}
/** Returns a {@link JSONObject} representing a subset of this object's data. */
JSONObject toJSON() throws JSONException {
return new JSONObject()
.put(THREAT_TYPE_FIELD, threatType())
.put(PLATFORM_TYPE_FIELD, platformType())
.put(METADATA_FIELD, metadata())
.put(DOMAIN_NAME_FIELD, fullyQualifiedDomainName());
}
/** Parses a {@link JSONObject} and returns an equivalent {@link ThreatMatch}. */
public static ThreatMatch fromJSON(JSONObject threatMatch) throws JSONException {
return new AutoValue_ThreatMatch(
threatMatch.getString(THREAT_TYPE_FIELD),
threatMatch.getString(PLATFORM_TYPE_FIELD),
threatMatch.getString(METADATA_FIELD),
threatMatch.getString(DOMAIN_NAME_FIELD));
threatMatch.getString(THREAT_TYPE_FIELD), threatMatch.getString(DOMAIN_NAME_FIELD));
}
}

View File

@@ -119,35 +119,31 @@ public class DnsQueue {
.param(PARAM_TLD, tld));
}
/**
* Adds a task to the queue to refresh the DNS information for the specified subordinate host.
*/
public TaskHandle addHostRefreshTask(String fullyQualifiedHostName) {
Optional<InternetDomainName> tld =
Registries.findTldForName(InternetDomainName.from(fullyQualifiedHostName));
checkArgument(tld.isPresent(),
String.format("%s is not a subordinate host to a known tld", fullyQualifiedHostName));
return addToQueue(TargetType.HOST, fullyQualifiedHostName, tld.get().toString(), Duration.ZERO);
/** Adds a task to the queue to refresh the DNS information for the specified subordinate host. */
public TaskHandle addHostRefreshTask(String hostName) {
Optional<InternetDomainName> tld = Registries.findTldForName(InternetDomainName.from(hostName));
checkArgument(
tld.isPresent(), String.format("%s is not a subordinate host to a known tld", hostName));
return addToQueue(TargetType.HOST, hostName, tld.get().toString(), Duration.ZERO);
}
/** Enqueues a task to refresh DNS for the specified domain now. */
public TaskHandle addDomainRefreshTask(String fullyQualifiedDomainName) {
return addDomainRefreshTask(fullyQualifiedDomainName, Duration.ZERO);
public TaskHandle addDomainRefreshTask(String domainName) {
return addDomainRefreshTask(domainName, Duration.ZERO);
}
/** Enqueues a task to refresh DNS for the specified domain at some point in the future. */
public TaskHandle addDomainRefreshTask(String fullyQualifiedDomainName, Duration countdown) {
public TaskHandle addDomainRefreshTask(String domainName, Duration countdown) {
return addToQueue(
TargetType.DOMAIN,
fullyQualifiedDomainName,
assertTldExists(getTldFromDomainName(fullyQualifiedDomainName)),
domainName,
assertTldExists(getTldFromDomainName(domainName)),
countdown);
}
/** Adds a task to the queue to refresh the DNS information for the specified zone. */
public TaskHandle addZoneRefreshTask(String fullyQualifiedZoneName) {
return addToQueue(
TargetType.ZONE, fullyQualifiedZoneName, fullyQualifiedZoneName, Duration.ZERO);
public TaskHandle addZoneRefreshTask(String zoneName) {
return addToQueue(TargetType.ZONE, zoneName, zoneName, Duration.ZERO);
}
/**

View File

@@ -89,7 +89,7 @@ public final class RefreshDnsAction implements Runnable {
private static void verifyHostIsSubordinate(HostResource host) {
if (!host.isSubordinate()) {
throw new BadRequestException(
String.format("%s isn't a subordinate hostname", host.getFullyQualifiedHostName()));
String.format("%s isn't a subordinate hostname", host.getHostName()));
}
}
}

View File

@@ -154,7 +154,7 @@ public class CloudDnsWriter extends BaseDnsWriter {
}
// Construct NS records (if any).
Set<String> nameserverData = domainBase.get().loadNameserverFullyQualifiedHostNames();
Set<String> nameserverData = domainBase.get().loadNameserverHostNames();
Set<String> subordinateHosts = domainBase.get().getSubordinateHosts();
if (!nameserverData.isEmpty()) {
HashSet<String> nsRrData = new HashSet<>();

View File

@@ -189,7 +189,7 @@ public class DnsUpdateWriter extends BaseDnsWriter {
for (DelegationSignerData signerData : domain.getDsData()) {
DSRecord dsRecord =
new DSRecord(
toAbsoluteName(domain.getFullyQualifiedDomainName()),
toAbsoluteName(domain.getDomainName()),
DClass.IN,
dnsDefaultDsTtl.getStandardSeconds(),
signerData.getKeyTag(),
@@ -215,8 +215,7 @@ public class DnsUpdateWriter extends BaseDnsWriter {
private void addInBailiwickNameServerSet(DomainBase domain, Update update) {
for (String hostName :
intersection(
domain.loadNameserverFullyQualifiedHostNames(), domain.getSubordinateHosts())) {
intersection(domain.loadNameserverHostNames(), domain.getSubordinateHosts())) {
Optional<HostResource> host = loadByForeignKey(HostResource.class, hostName, clock.nowUtc());
checkState(host.isPresent(), "Host %s cannot be loaded", hostName);
update.add(makeAddressSet(host.get()));
@@ -226,10 +225,10 @@ public class DnsUpdateWriter extends BaseDnsWriter {
private RRset makeNameServerSet(DomainBase domain) {
RRset nameServerSet = new RRset();
for (String hostName : domain.loadNameserverFullyQualifiedHostNames()) {
for (String hostName : domain.loadNameserverHostNames()) {
NSRecord record =
new NSRecord(
toAbsoluteName(domain.getFullyQualifiedDomainName()),
toAbsoluteName(domain.getDomainName()),
DClass.IN,
dnsDefaultNsTtl.getStandardSeconds(),
toAbsoluteName(hostName));
@@ -244,7 +243,7 @@ public class DnsUpdateWriter extends BaseDnsWriter {
if (address instanceof Inet4Address) {
ARecord record =
new ARecord(
toAbsoluteName(host.getFullyQualifiedHostName()),
toAbsoluteName(host.getHostName()),
DClass.IN,
dnsDefaultATtl.getStandardSeconds(),
address);
@@ -260,7 +259,7 @@ public class DnsUpdateWriter extends BaseDnsWriter {
if (address instanceof Inet6Address) {
AAAARecord record =
new AAAARecord(
toAbsoluteName(host.getFullyQualifiedHostName()),
toAbsoluteName(host.getHostName()),
DClass.IN,
dnsDefaultATtl.getStandardSeconds(),
address);

View File

@@ -284,7 +284,7 @@
<description>
Checks if the monthly ICANN reports have been successfully uploaded. If they have not, attempts to upload them again.
Most of the time, this job should not do anything since the uploads are triggered when the reports are staged.
However, in the event that an upload failed for any reason (e.g. ICANN server is down, IP whitelist issues),
However, in the event that an upload failed for any reason (e.g. ICANN server is down, IP allow list issues),
this cron job will continue to retry uploads daily until they succeed.
</description>
<schedule>every day 15:00</schedule>

View File

@@ -107,7 +107,7 @@ public class ExportDomainListsAction implements Runnable {
@Override
public void map(DomainBase domain) {
if (realTlds.contains(domain.getTld()) && isActive(domain, exportTime)) {
emit(domain.getTld(), domain.getFullyQualifiedDomainName());
emit(domain.getTld(), domain.getDomainName());
getContext().incrementCounter(String.format("domains in tld %s", domain.getTld()));
}
}

View File

@@ -84,8 +84,7 @@ class SyncRegistrarsSheet {
public int compare(Registrar left, Registrar right) {
return left.getClientId().compareTo(right.getClientId());
}
}.immutableSortedCopy(Registrar.loadAllCached())
.stream()
}.immutableSortedCopy(Registrar.loadAllCached()).stream()
.filter(
registrar ->
registrar.getType() == Registrar.Type.REAL
@@ -149,7 +148,7 @@ class SyncRegistrarsSheet {
builder.put("allowedTlds", convert(registrar.getAllowedTlds()));
builder.put("whoisServer", convert(registrar.getWhoisServer()));
builder.put("blockPremiumNames", convert(registrar.getBlockPremiumNames()));
builder.put("ipAddressWhitelist", convert(registrar.getIpAddressWhitelist()));
builder.put("ipAddressAllowList", convert(registrar.getIpAddressAllowList()));
builder.put("url", convert(registrar.getUrl()));
builder.put("referralUrl", convert(registrar.getUrl()));
builder.put("icannReferralEmail", convert(registrar.getIcannReferralEmail()));

View File

@@ -37,7 +37,7 @@ import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
/**
* Container and validation for TLS certificate and ip-whitelisting.
* Container and validation for TLS certificate and IP-allow-listing.
*
* <p>Credentials are based on the following headers:
*
@@ -48,7 +48,7 @@ import javax.servlet.http.HttpServletRequest;
* band.
* <dt>X-Forwarded-For
* <dd>This field should contain the host and port of the connecting client. It is validated
* during an EPP login command against an IP whitelist that is transmitted out of band.
* during an EPP login command against an IP allow list that is transmitted out of band.
* </dl>
*/
public class TlsCredentials implements TransportCredentials {
@@ -85,27 +85,28 @@ public class TlsCredentials implements TransportCredentials {
}
/**
* Verifies {@link #clientInetAddr} is in CIDR whitelist associated with {@code registrar}.
* Verifies {@link #clientInetAddr} is in CIDR allow list associated with {@code registrar}.
*
* @throws BadRegistrarIpAddressException If IP address is not in the whitelist provided
* @throws BadRegistrarIpAddressException If IP address is not in the allow list provided
*/
private void validateIp(Registrar registrar) throws AuthenticationErrorException {
ImmutableList<CidrAddressBlock> ipWhitelist = registrar.getIpAddressWhitelist();
if (ipWhitelist.isEmpty()) {
ImmutableList<CidrAddressBlock> ipAddressAllowList = registrar.getIpAddressAllowList();
if (ipAddressAllowList.isEmpty()) {
logger.atInfo().log(
"Skipping IP whitelist check because %s doesn't have an IP whitelist",
"Skipping IP allow list check because %s doesn't have an IP allow list",
registrar.getClientId());
return;
}
for (CidrAddressBlock cidrAddressBlock : ipWhitelist) {
for (CidrAddressBlock cidrAddressBlock : ipAddressAllowList) {
if (cidrAddressBlock.contains(clientInetAddr)) {
// IP address is in whitelist; return early.
// IP address is in allow list; return early.
return;
}
}
logger.atInfo().log(
"Authentication error: IP address %s is not whitelisted for registrar %s; whitelist is: %s",
clientInetAddr, registrar.getClientId(), ipWhitelist);
"Authentication error: IP address %s is not allow-listed for registrar %s; allow list is:"
+ " %s",
clientInetAddr, registrar.getClientId(), ipAddressAllowList);
throw new BadRegistrarIpAddressException();
}
@@ -180,10 +181,10 @@ public class TlsCredentials implements TransportCredentials {
}
}
/** Registrar IP address is not in stored whitelist. */
/** Registrar IP address is not in stored allow list. */
public static class BadRegistrarIpAddressException extends AuthenticationErrorException {
public BadRegistrarIpAddressException() {
super("Registrar IP address is not in stored whitelist");
super("Registrar IP address is not in stored allow list");
}
}

View File

@@ -97,7 +97,7 @@ public final class ContactTransferApproveFlow implements TransactionalFlow {
ofy().save().<Object>entities(newContact, historyEntry, gainingPollMessage);
// Delete the billing event and poll messages that were written in case the transfer would have
// been implicitly server approved.
ofy().delete().keys(existingContact.getTransferData().getServerApproveEntities());
tm().delete(existingContact.getTransferData().getServerApproveEntities());
return responseBuilder
.setResData(createTransferResponse(targetId, newContact.getTransferData()))
.build();

View File

@@ -93,7 +93,7 @@ public final class ContactTransferCancelFlow implements TransactionalFlow {
ofy().save().<Object>entities(newContact, historyEntry, losingPollMessage);
// Delete the billing event and poll messages that were written in case the transfer would have
// been implicitly server approved.
ofy().delete().keys(existingContact.getTransferData().getServerApproveEntities());
tm().delete(existingContact.getTransferData().getServerApproveEntities());
return responseBuilder
.setResData(createTransferResponse(targetId, newContact.getTransferData()))
.build();

View File

@@ -90,7 +90,7 @@ public final class ContactTransferRejectFlow implements TransactionalFlow {
ofy().save().<Object>entities(newContact, historyEntry, gainingPollMessage);
// Delete the billing event and poll messages that were written in case the transfer would have
// been implicitly server approved.
ofy().delete().keys(existingContact.getTransferData().getServerApproveEntities());
tm().delete(existingContact.getTransferData().getServerApproveEntities());
return responseBuilder
.setResData(createTransferResponse(targetId, newContact.getTransferData()))
.build();

View File

@@ -46,7 +46,7 @@ import google.registry.model.eppoutput.EppResponse;
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.ContactTransferData;
import google.registry.model.transfer.TransferStatus;
import java.util.Optional;
import javax.inject.Inject;
@@ -112,27 +112,30 @@ public final class ContactTransferRequestFlow implements TransactionalFlow {
.setParent(Key.create(existingContact))
.build();
DateTime transferExpirationTime = now.plus(automaticTransferLength);
TransferData serverApproveTransferData = new TransferData.Builder()
.setTransferRequestTime(now)
.setTransferRequestTrid(trid)
.setGainingClientId(gainingClientId)
.setLosingClientId(losingClientId)
.setPendingTransferExpirationTime(transferExpirationTime)
.setTransferStatus(TransferStatus.SERVER_APPROVED)
.build();
ContactTransferData serverApproveTransferData =
new ContactTransferData.Builder()
.setTransferRequestTime(now)
.setTransferRequestTrid(trid)
.setGainingClientId(gainingClientId)
.setLosingClientId(losingClientId)
.setPendingTransferExpirationTime(transferExpirationTime)
.setTransferStatus(TransferStatus.SERVER_APPROVED)
.build();
// If the transfer is server approved, this message will be sent to the losing registrar. */
PollMessage serverApproveLosingPollMessage =
createLosingTransferPollMessage(targetId, serverApproveTransferData, historyEntry);
// If the transfer is server approved, this message will be sent to the gaining registrar. */
PollMessage serverApproveGainingPollMessage =
createGainingTransferPollMessage(targetId, serverApproveTransferData, historyEntry);
TransferData pendingTransferData = serverApproveTransferData.asBuilder()
.setTransferStatus(TransferStatus.PENDING)
.setServerApproveEntities(
ImmutableSet.of(
Key.create(serverApproveGainingPollMessage),
Key.create(serverApproveLosingPollMessage)))
.build();
ContactTransferData pendingTransferData =
serverApproveTransferData
.asBuilder()
.setTransferStatus(TransferStatus.PENDING)
.setServerApproveEntities(
ImmutableSet.of(
serverApproveGainingPollMessage.createVKey(),
serverApproveLosingPollMessage.createVKey()))
.build();
// When a transfer is requested, a poll message is created to notify the losing registrar.
PollMessage requestPollMessage =
createLosingTransferPollMessage(targetId, pendingTransferData, historyEntry).asBuilder()

View File

@@ -163,7 +163,8 @@ public final class DomainCheckFlow implements Flow {
clientId,
now));
ImmutableList.Builder<DomainCheck> checks = new ImmutableList.Builder<>();
ImmutableList.Builder<DomainCheck> checksBuilder = new ImmutableList.Builder<>();
ImmutableSet.Builder<String> availableDomains = new ImmutableSet.Builder<>();
ImmutableMap<String, TldState> tldStates =
Maps.toMap(seenTlds, tld -> Registry.get(tld).getTldState(now));
ImmutableMap<InternetDomainName, String> domainCheckResults =
@@ -180,13 +181,19 @@ public final class DomainCheckFlow implements Flow {
domainCheckResults,
tldStates,
allocationToken);
checks.add(DomainCheck.create(!message.isPresent(), targetId, message.orElse(null)));
boolean isAvailable = !message.isPresent();
checksBuilder.add(DomainCheck.create(isAvailable, targetId, message.orElse(null)));
if (isAvailable) {
availableDomains.add(targetId);
}
}
BeforeResponseReturnData responseData =
flowCustomLogic.beforeResponse(
BeforeResponseParameters.newBuilder()
.setDomainChecks(checks.build())
.setResponseExtensions(getResponseExtensions(domainNames, now, allocationToken))
.setDomainChecks(checksBuilder.build())
.setResponseExtensions(
getResponseExtensions(
domainNames, availableDomains.build(), now, allocationToken))
.setAsOfDate(now)
.build());
return responseBuilder
@@ -221,6 +228,7 @@ public final class DomainCheckFlow implements Flow {
/** Handle the fee check extension. */
private ImmutableList<? extends ResponseExtension> getResponseExtensions(
ImmutableMap<String, InternetDomainName> domainNames,
ImmutableSet<String> availableDomains,
DateTime now,
Optional<AllocationToken> allocationToken)
throws EppException {
@@ -242,7 +250,8 @@ public final class DomainCheckFlow implements Flow {
feeCheck.getCurrency(),
now,
pricingLogic,
allocationToken);
allocationToken,
availableDomains.contains(domainName));
responseItems.add(builder.setDomainNameIfSupported(domainName).build());
}
}

View File

@@ -181,7 +181,7 @@ import org.joda.time.Duration;
* @error {@link DomainFlowUtils.MissingRegistrantException}
* @error {@link DomainFlowUtils.MissingTechnicalContactException}
* @error {@link DomainFlowUtils.NameserversNotAllowedForTldException}
* @error {@link DomainFlowUtils.NameserversNotSpecifiedForTldWithNameserverWhitelistException}
* @error {@link DomainFlowUtils.NameserversNotSpecifiedForTldWithNameserverAllowListException}
* @error {@link DomainFlowUtils.PremiumNameBlockedException}
* @error {@link DomainFlowUtils.RegistrantNotAllowedException}
* @error {@link DomainFlowUtils.RegistrarMustBeActiveForThisOperationException}
@@ -354,7 +354,7 @@ public class DomainCreateFlow implements TransactionalFlow {
.setDsData(secDnsCreate.isPresent() ? secDnsCreate.get().getDsData() : null)
.setRegistrant(command.getRegistrant())
.setAuthInfo(command.getAuthInfo())
.setFullyQualifiedDomainName(targetId)
.setDomainName(targetId)
.setNameservers(
(ImmutableSet<VKey<HostResource>>)
command.getNameservers().stream().collect(toImmutableSet()))
@@ -598,7 +598,7 @@ public class DomainCreateFlow implements TransactionalFlow {
private void enqueueTasks(
DomainBase newDomain, boolean hasSignedMarks, boolean hasClaimsNotice) {
if (newDomain.shouldPublishToDns()) {
dnsQueue.addDomainRefreshTask(newDomain.getFullyQualifiedDomainName());
dnsQueue.addDomainRefreshTask(newDomain.getDomainName());
}
if (hasClaimsNotice || hasSignedMarks) {
LordnTaskUtils.enqueueDomainBaseTask(newDomain);

View File

@@ -242,7 +242,7 @@ public final class DomainDeleteFlow implements TransactionalFlow {
// If there's a pending transfer, the gaining client's autorenew billing
// event and poll message will already have been deleted in
// ResourceDeleteFlow since it's listed in serverApproveEntities.
dnsQueue.addDomainRefreshTask(existingDomain.getFullyQualifiedDomainName());
dnsQueue.addDomainRefreshTask(existingDomain.getDomainName());
entitiesToSave.add(newDomain, historyEntry);
EntityChanges entityChanges = flowCustomLogic.beforeSave(
@@ -339,7 +339,7 @@ public final class DomainDeleteFlow implements TransactionalFlow {
.setResponseData(
ImmutableList.of(
DomainPendingActionNotificationResponse.create(
existingDomain.getFullyQualifiedDomainName(), true, trid, deletionTime)))
existingDomain.getDomainName(), true, trid, deletionTime)))
.setParent(historyEntry)
.build();
}

View File

@@ -338,11 +338,11 @@ public class DomainFlowUtils {
static void validateNameserversCountForTld(String tld, InternetDomainName domainName, int count)
throws EppException {
// For TLDs with a nameserver whitelist, all domains must have at least 1 nameserver.
ImmutableSet<String> tldNameserversWhitelist =
// For TLDs with a nameserver allow list, all domains must have at least 1 nameserver.
ImmutableSet<String> tldNameserversAllowList =
Registry.get(tld).getAllowedFullyQualifiedHostNames();
if (!tldNameserversWhitelist.isEmpty() && count == 0) {
throw new NameserversNotSpecifiedForTldWithNameserverWhitelistException(
if (!tldNameserversAllowList.isEmpty() && count == 0) {
throw new NameserversNotSpecifiedForTldWithNameserverAllowListException(
domainName.toString());
}
if (count > MAX_NAMESERVERS_PER_DOMAIN) {
@@ -398,21 +398,21 @@ public class DomainFlowUtils {
static void validateRegistrantAllowedOnTld(String tld, String registrantContactId)
throws RegistrantNotAllowedException {
ImmutableSet<String> whitelist = Registry.get(tld).getAllowedRegistrantContactIds();
// Empty whitelist or null registrantContactId are ignored.
ImmutableSet<String> allowedRegistrants = Registry.get(tld).getAllowedRegistrantContactIds();
// Empty allow list or null registrantContactId are ignored.
if (registrantContactId != null
&& !whitelist.isEmpty()
&& !whitelist.contains(registrantContactId)) {
&& !allowedRegistrants.isEmpty()
&& !allowedRegistrants.contains(registrantContactId)) {
throw new RegistrantNotAllowedException(registrantContactId);
}
}
static void validateNameserversAllowedOnTld(String tld, Set<String> fullyQualifiedHostNames)
throws EppException {
ImmutableSet<String> whitelist = Registry.get(tld).getAllowedFullyQualifiedHostNames();
ImmutableSet<String> allowedHostNames = Registry.get(tld).getAllowedFullyQualifiedHostNames();
Set<String> hostnames = nullToEmpty(fullyQualifiedHostNames);
if (!whitelist.isEmpty()) { // Empty whitelist is ignored.
Set<String> disallowedNameservers = difference(hostnames, whitelist);
if (!allowedHostNames.isEmpty()) { // Empty allow list is ignored.
Set<String> disallowedNameservers = difference(hostnames, allowedHostNames);
if (!disallowedNameservers.isEmpty()) {
throw new NameserversNotAllowedForTldException(disallowedNameservers);
}
@@ -489,7 +489,7 @@ public class DomainFlowUtils {
return new BillingEvent.Recurring.Builder()
.setReason(Reason.RENEW)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setTargetId(domain.getFullyQualifiedDomainName())
.setTargetId(domain.getDomainName())
.setClientId(domain.getCurrentSponsorClientId())
.setEventTime(domain.getRegistrationExpirationTime());
}
@@ -500,7 +500,7 @@ public class DomainFlowUtils {
*/
public static PollMessage.Autorenew.Builder newAutorenewPollMessage(DomainBase domain) {
return new PollMessage.Autorenew.Builder()
.setTargetId(domain.getFullyQualifiedDomainName())
.setTargetId(domain.getDomainName())
.setClientId(domain.getCurrentSponsorClientId())
.setEventTime(domain.getRegistrationExpirationTime())
.setMsg("Domain was auto-renewed.");
@@ -555,7 +555,8 @@ public class DomainFlowUtils {
@Nullable CurrencyUnit topLevelCurrency,
DateTime currentDate,
DomainPricingLogic pricingLogic,
Optional<AllocationToken> allocationToken)
Optional<AllocationToken> allocationToken,
boolean isAvailable)
throws EppException {
DateTime now = currentDate;
// Use the custom effective date specified in the fee check request, if there is one.
@@ -587,7 +588,8 @@ public class DomainFlowUtils {
ImmutableList<Fee> fees = ImmutableList.of();
switch (feeRequest.getCommandName()) {
case CREATE:
if (isReserved(domain, isSunrise)) { // Don't return a create price for reserved names.
// Don't return a create price for reserved names.
if (isReserved(domain, isSunrise) && !isAvailable) {
builder.setClass("reserved"); // Override whatever class we've set above.
builder.setAvailIfSupported(false);
builder.setReasonIfSupported("reserved");
@@ -690,7 +692,7 @@ public class DomainFlowUtils {
List<Fee> fees = feeCommand.get().getFees();
// The schema guarantees that at least one fee will be present.
checkState(!fees.isEmpty());
BigDecimal total = BigDecimal.ZERO;
BigDecimal total = zeroInCurrency(feeCommand.get().getCurrency());
for (Fee fee : fees) {
if (!fee.hasDefaultAttributes()) {
throw new UnsupportedFeeAttributeException();
@@ -938,6 +940,16 @@ public class DomainFlowUtils {
}
}
/**
* Returns zero for a specific currency.
*
* <p>{@link BigDecimal} has a concept of significant figures, so zero is not always zero. E.g.
* zero in USD is 0.00, whereas zero in Yen is 0, and zero in Dinars is 0.000 (!).
*/
static BigDecimal zeroInCurrency(CurrencyUnit currencyUnit) {
return Money.of(currencyUnit, BigDecimal.ZERO).getAmount();
}
/**
* Check that if there's a claims notice it's on the claims list, and that if there's not one it's
* not on the claims list.
@@ -1371,32 +1383,32 @@ public class DomainFlowUtils {
}
}
/** Registrant is not whitelisted for this TLD. */
/** Registrant is not allow-listed for this TLD. */
public static class RegistrantNotAllowedException extends StatusProhibitsOperationException {
public RegistrantNotAllowedException(String contactId) {
super(String.format("Registrant with id %s is not whitelisted for this TLD", contactId));
super(String.format("Registrant with id %s is not allow-listed for this TLD", contactId));
}
}
/** Nameservers are not whitelisted for this TLD. */
/** Nameservers are not allow-listed for this TLD. */
public static class NameserversNotAllowedForTldException
extends StatusProhibitsOperationException {
public NameserversNotAllowedForTldException(Set<String> fullyQualifiedHostNames) {
super(
String.format(
"Nameservers '%s' are not whitelisted for this TLD",
"Nameservers '%s' are not allow-listed for this TLD",
Joiner.on(',').join(fullyQualifiedHostNames)));
}
}
/** Nameservers not specified for domain on TLD with nameserver whitelist. */
public static class NameserversNotSpecifiedForTldWithNameserverWhitelistException
/** Nameservers not specified for domain on TLD with nameserver allow list. */
public static class NameserversNotSpecifiedForTldWithNameserverAllowListException
extends StatusProhibitsOperationException {
public NameserversNotSpecifiedForTldWithNameserverWhitelistException(String domain) {
public NameserversNotSpecifiedForTldWithNameserverAllowListException(String domain) {
super(
String.format(
"At least one nameserver must be specified for domain %s"
+ " on a TLD with nameserver whitelist",
+ " on a TLD with nameserver allow list",
domain));
}
}

View File

@@ -107,7 +107,7 @@ public final class DomainInfoFlow implements Flow {
// This is a policy decision that is left up to us by the rfcs.
DomainInfoData.Builder infoBuilder =
DomainInfoData.newBuilder()
.setFullyQualifiedDomainName(domain.getFullyQualifiedDomainName())
.setFullyQualifiedDomainName(domain.getDomainName())
.setRepoId(domain.getRepoId())
.setCurrentSponsorClientId(domain.getCurrentSponsorClientId())
.setRegistrant(tm().load(domain.getRegistrant()).getContactId());
@@ -118,12 +118,9 @@ public final class DomainInfoFlow implements Flow {
infoBuilder
.setStatusValues(domain.getStatusValues())
.setContacts(loadForeignKeyedDesignatedContacts(domain.getContacts()))
.setNameservers(hostsRequest.requestDelegated()
? domain.loadNameserverFullyQualifiedHostNames()
: null)
.setSubordinateHosts(hostsRequest.requestSubordinate()
? domain.getSubordinateHosts()
: null)
.setNameservers(hostsRequest.requestDelegated() ? domain.loadNameserverHostNames() : null)
.setSubordinateHosts(
hostsRequest.requestSubordinate() ? domain.getSubordinateHosts() : null)
.setCreationClientId(domain.getCreationClientId())
.setCreationTime(domain.getCreationTime())
.setLastEppUpdateClientId(domain.getLastEppUpdateClientId())
@@ -164,7 +161,8 @@ public final class DomainInfoFlow implements Flow {
null,
now,
pricingLogic,
Optional.empty());
Optional.empty(),
false);
extensions.add(builder.build());
}
return extensions.build();

View File

@@ -14,8 +14,9 @@
package google.registry.flows.domain;
import static google.registry.flows.domain.DomainFlowUtils.zeroInCurrency;
import static google.registry.pricing.PricingEngineProxy.getDomainFeeClass;
import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost;
import static google.registry.pricing.PricingEngineProxy.getPricesForDomainName;
import com.google.common.net.InternetDomainName;
import google.registry.flows.EppException;
@@ -33,7 +34,6 @@ import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.pricing.PremiumPricingEngine.DomainPrices;
import google.registry.model.registry.Registry;
import google.registry.pricing.PricingEngineProxy;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Optional;
@@ -64,21 +64,27 @@ public final class DomainPricingLogic {
public FeesAndCredits getCreatePrice(
Registry registry,
String domainName,
DateTime date,
DateTime dateTime,
int years,
boolean isAnchorTenant,
Optional<AllocationToken> allocationToken)
throws EppException {
CurrencyUnit currency = registry.getCurrency();
BaseFee createFeeOrCredit;
// Domain create cost is always zero for anchor tenants
Money domainCreateCost =
isAnchorTenant
? Money.of(currency, BigDecimal.ZERO)
: getDomainCreateCostWithDiscount(domainName, date, years, allocationToken);
BaseFee createFeeOrCredit = Fee.create(domainCreateCost.getAmount(), FeeType.CREATE);
if (isAnchorTenant) {
createFeeOrCredit = Fee.create(zeroInCurrency(currency), FeeType.CREATE, false);
} else {
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime);
Money domainCreateCost =
getDomainCreateCostWithDiscount(domainPrices, years, allocationToken);
createFeeOrCredit =
Fee.create(domainCreateCost.getAmount(), FeeType.CREATE, domainPrices.isPremium());
}
// Create fees for the cost and the EAP fee, if any.
Fee eapFee = registry.getEapFeeFor(date);
Fee eapFee = registry.getEapFeeFor(dateTime);
FeesAndCredits.Builder feesBuilder =
new FeesAndCredits.Builder().setCurrency(currency).addFeeOrCredit(createFeeOrCredit);
// Don't charge anchor tenants EAP fees.
@@ -92,7 +98,7 @@ public final class DomainPricingLogic {
.setFeesAndCredits(feesBuilder.build())
.setRegistry(registry)
.setDomainName(InternetDomainName.from(domainName))
.setAsOfDate(date)
.setAsOfDate(dateTime)
.setYears(years)
.build());
}
@@ -100,69 +106,73 @@ public final class DomainPricingLogic {
/** Returns a new renew price for the pricer. */
@SuppressWarnings("unused")
public FeesAndCredits getRenewPrice(
Registry registry,
String domainName,
DateTime date,
int years)
throws EppException {
Money renewCost = getDomainRenewCost(domainName, date, years);
Registry registry, String domainName, DateTime dateTime, int years) throws EppException {
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime);
BigDecimal renewCost = domainPrices.getRenewCost().multipliedBy(years).getAmount();
return customLogic.customizeRenewPrice(
RenewPriceParameters.newBuilder()
.setFeesAndCredits(
new FeesAndCredits.Builder()
.setCurrency(registry.getCurrency())
.addFeeOrCredit(Fee.create(renewCost.getAmount(), FeeType.RENEW))
.addFeeOrCredit(Fee.create(renewCost, FeeType.RENEW, domainPrices.isPremium()))
.build())
.setRegistry(registry)
.setDomainName(InternetDomainName.from(domainName))
.setAsOfDate(date)
.setAsOfDate(dateTime)
.setYears(years)
.build());
}
/** Returns a new restore price for the pricer. */
public FeesAndCredits getRestorePrice(Registry registry, String domainName, DateTime date)
public FeesAndCredits getRestorePrice(Registry registry, String domainName, DateTime dateTime)
throws EppException {
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime);
FeesAndCredits feesAndCredits =
new FeesAndCredits.Builder()
.setCurrency(registry.getCurrency())
.addFeeOrCredit(
Fee.create(getDomainRenewCost(domainName, date, 1).getAmount(), FeeType.RENEW))
Fee.create(
domainPrices.getRenewCost().getAmount(),
FeeType.RENEW,
domainPrices.isPremium()))
.addFeeOrCredit(
Fee.create(registry.getStandardRestoreCost().getAmount(), FeeType.RESTORE))
Fee.create(registry.getStandardRestoreCost().getAmount(), FeeType.RESTORE, false))
.build();
return customLogic.customizeRestorePrice(
RestorePriceParameters.newBuilder()
.setFeesAndCredits(feesAndCredits)
.setRegistry(registry)
.setDomainName(InternetDomainName.from(domainName))
.setAsOfDate(date)
.setAsOfDate(dateTime)
.build());
}
/** Returns a new transfer price for the pricer. */
public FeesAndCredits getTransferPrice(Registry registry, String domainName, DateTime date)
public FeesAndCredits getTransferPrice(Registry registry, String domainName, DateTime dateTime)
throws EppException {
Money renewCost = getDomainRenewCost(domainName, date, 1);
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime);
return customLogic.customizeTransferPrice(
TransferPriceParameters.newBuilder()
.setFeesAndCredits(
new FeesAndCredits.Builder()
.setCurrency(registry.getCurrency())
.addFeeOrCredit(Fee.create(renewCost.getAmount(), FeeType.RENEW))
.addFeeOrCredit(
Fee.create(
domainPrices.getRenewCost().getAmount(),
FeeType.RENEW,
domainPrices.isPremium()))
.build())
.setRegistry(registry)
.setDomainName(InternetDomainName.from(domainName))
.setAsOfDate(date)
.setAsOfDate(dateTime)
.build());
}
/** Returns a new update price for the pricer. */
public FeesAndCredits getUpdatePrice(Registry registry, String domainName, DateTime date)
public FeesAndCredits getUpdatePrice(Registry registry, String domainName, DateTime dateTime)
throws EppException {
CurrencyUnit currency = registry.getCurrency();
BaseFee feeOrCredit =
Fee.create(Money.zero(registry.getCurrency()).getAmount(), FeeType.UPDATE);
BaseFee feeOrCredit = Fee.create(zeroInCurrency(currency), FeeType.UPDATE, false);
return customLogic.customizeUpdatePrice(
UpdatePriceParameters.newBuilder()
.setFeesAndCredits(
@@ -172,19 +182,19 @@ public final class DomainPricingLogic {
.build())
.setRegistry(registry)
.setDomainName(InternetDomainName.from(domainName))
.setAsOfDate(date)
.setAsOfDate(dateTime)
.build());
}
/** Returns the fee class for a given domain and date. */
public Optional<String> getFeeClass(String domainName, DateTime date) {
return getDomainFeeClass(domainName, date);
public Optional<String> getFeeClass(String domainName, DateTime dateTime) {
return getDomainFeeClass(domainName, dateTime);
}
/** Returns the domain create cost with allocation-token-related discounts applied. */
private Money getDomainCreateCostWithDiscount(
String domainName, DateTime date, int years, Optional<AllocationToken> allocationToken)
DomainPrices domainPrices, int years, Optional<AllocationToken> allocationToken)
throws EppException {
DomainPrices domainPrices = PricingEngineProxy.getPricesForDomainName(domainName, date);
if (allocationToken.isPresent()
&& allocationToken.get().getDiscountFraction() != 0.0
&& domainPrices.isPremium()) {

View File

@@ -211,8 +211,7 @@ public final class DomainRenewFlow implements TransactionalFlow {
BeforeResponseParameters.newBuilder()
.setDomain(newDomain)
.setResData(DomainRenewData.create(targetId, newExpirationTime))
.setResponseExtensions(
createResponseExtensions(feesAndCredits.getTotalCost(), feeRenew))
.setResponseExtensions(createResponseExtensions(feesAndCredits, feeRenew))
.build());
return responseBuilder
.setResData(responseData.resData())
@@ -270,14 +269,19 @@ public final class DomainRenewFlow implements TransactionalFlow {
}
private ImmutableList<FeeTransformResponseExtension> createResponseExtensions(
Money renewCost, Optional<FeeRenewCommandExtension> feeRenew) {
FeesAndCredits feesAndCredits, Optional<FeeRenewCommandExtension> feeRenew) {
return feeRenew.isPresent()
? ImmutableList.of(
feeRenew
.get()
.createResponseBuilder()
.setCurrency(renewCost.getCurrencyUnit())
.setFees(ImmutableList.of(Fee.create(renewCost.getAmount(), FeeType.RENEW)))
.setCurrency(feesAndCredits.getCurrency())
.setFees(
ImmutableList.of(
Fee.create(
feesAndCredits.getRenewCost().getAmount(),
FeeType.RENEW,
feesAndCredits.hasPremiumFeesOfType(FeeType.RENEW))))
.build())
: ImmutableList.of();
}

View File

@@ -143,24 +143,30 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
verifyRestoreAllowed(command, existingDomain, feeUpdate, feesAndCredits, now);
HistoryEntry historyEntry = buildHistoryEntry(existingDomain, now);
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
entitiesToSave.addAll(
createRestoreAndRenewBillingEvents(
historyEntry, feesAndCredits.getRestoreCost(), feesAndCredits.getRenewCost(), now));
// We don't preserve the original expiration time of the domain when we restore, since doing so
// would require us to know if they received a grace period refund when they deleted the domain,
// and to charge them for that again. Instead, we just say that all restores get a fresh year of
// registration and bill them for that accordingly.
DateTime newExpirationTime = now.plusYears(1);
BillingEvent.Recurring autorenewEvent = newAutorenewBillingEvent(existingDomain)
.setEventTime(newExpirationTime)
.setRecurrenceEndTime(END_OF_TIME)
.setParent(historyEntry)
.build();
PollMessage.Autorenew autorenewPollMessage = newAutorenewPollMessage(existingDomain)
.setEventTime(newExpirationTime)
.setAutorenewEndTime(END_OF_TIME)
.setParent(historyEntry)
.build();
// Restore the expiration time on the deleted domain, except if that's already passed, then add
// a year and bill for it immediately, with no grace period.
DateTime newExpirationTime = existingDomain.getRegistrationExpirationTime();
if (newExpirationTime.isBefore(now)) {
entitiesToSave.add(createRenewBillingEvent(historyEntry, feesAndCredits.getRenewCost(), now));
newExpirationTime = newExpirationTime.plusYears(1);
}
// Always bill for the restore itself.
entitiesToSave.add(
createRestoreBillingEvent(historyEntry, feesAndCredits.getRestoreCost(), now));
BillingEvent.Recurring autorenewEvent =
newAutorenewBillingEvent(existingDomain)
.setEventTime(newExpirationTime)
.setRecurrenceEndTime(END_OF_TIME)
.setParent(historyEntry)
.build();
PollMessage.Autorenew autorenewPollMessage =
newAutorenewPollMessage(existingDomain)
.setEventTime(newExpirationTime)
.setAutorenewEndTime(END_OF_TIME)
.setParent(historyEntry)
.build();
DomainBase newDomain =
performRestore(
existingDomain, newExpirationTime, autorenewEvent, autorenewPollMessage, now, clientId);
@@ -168,11 +174,9 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
entitiesToSave.add(newDomain, historyEntry, autorenewEvent, autorenewPollMessage);
ofy().save().entities(entitiesToSave.build());
ofy().delete().key(existingDomain.getDeletePollMessage());
dnsQueue.addDomainRefreshTask(existingDomain.getFullyQualifiedDomainName());
dnsQueue.addDomainRefreshTask(existingDomain.getDomainName());
return responseBuilder
.setExtensions(
createResponseExtensions(
feesAndCredits.getRestoreCost(), feesAndCredits.getRenewCost(), feeUpdate))
.setExtensions(createResponseExtensions(feesAndCredits, feeUpdate))
.build();
}
@@ -212,18 +216,6 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
validateFeeChallenge(targetId, now, feeUpdate, feesAndCredits);
}
private ImmutableSet<BillingEvent.OneTime> createRestoreAndRenewBillingEvents(
HistoryEntry historyEntry, Money restoreCost, Money renewCost, DateTime now) {
// Bill for the restore.
BillingEvent.OneTime restoreEvent = createRestoreBillingEvent(historyEntry, restoreCost, now);
// Create a new autorenew billing event and poll message starting at the new expiration time.
// Also bill for the 1 year cost of a domain renew. This is to avoid registrants being able to
// game the system for premium names by renewing, deleting, and then restoring to get a free
// year. Note that this billing event has no grace period; it is effective immediately.
BillingEvent.OneTime renewEvent = createRenewBillingEvent(historyEntry, renewCost, now);
return ImmutableSet.of(restoreEvent, renewEvent);
}
private static DomainBase performRestore(
DomainBase existingDomain,
DateTime newExpirationTime,
@@ -271,17 +263,23 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
}
private static ImmutableList<FeeTransformResponseExtension> createResponseExtensions(
Money restoreCost, Money renewCost, Optional<FeeUpdateCommandExtension> feeUpdate) {
FeesAndCredits feesAndCredits, Optional<FeeUpdateCommandExtension> feeUpdate) {
return feeUpdate.isPresent()
? ImmutableList.of(
feeUpdate
.get()
.createResponseBuilder()
.setCurrency(restoreCost.getCurrencyUnit())
.setCurrency(feesAndCredits.getCurrency())
.setFees(
ImmutableList.of(
Fee.create(restoreCost.getAmount(), FeeType.RESTORE),
Fee.create(renewCost.getAmount(), FeeType.RENEW)))
Fee.create(
feesAndCredits.getRestoreCost().getAmount(),
FeeType.RESTORE,
feesAndCredits.hasPremiumFeesOfType(FeeType.RESTORE)),
Fee.create(
feesAndCredits.getRenewCost().getAmount(),
FeeType.RENEW,
feesAndCredits.hasPremiumFeesOfType(FeeType.RENEW))))
.build())
: ImmutableList.of();
}

View File

@@ -58,7 +58,7 @@ import google.registry.model.registry.Registry;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferStatus;
import java.util.Optional;
import javax.inject.Inject;
@@ -112,7 +112,7 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
if (!isSuperuser) {
checkAllowedAccessToTld(clientId, tld);
}
TransferData transferData = existingDomain.getTransferData();
DomainTransferData transferData = existingDomain.getTransferData();
String gainingClientId = transferData.getGainingClientId();
Registry registry = Registry.get(existingDomain.getTld());
HistoryEntry historyEntry = buildHistoryEntry(existingDomain, registry, now, gainingClientId);
@@ -214,7 +214,7 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
ofy().save().entities(entitiesToSave.build());
// Delete the billing event and poll messages that were written in case the transfer would have
// been implicitly server approved.
ofy().delete().keys(existingDomain.getTransferData().getServerApproveEntities());
tm().delete(existingDomain.getTransferData().getServerApproveEntities());
return responseBuilder
.setResData(createTransferResponse(
targetId, newDomain.getTransferData(), newDomain.getRegistrationExpirationTime()))

View File

@@ -110,7 +110,7 @@ public final class DomainTransferCancelFlow implements TransactionalFlow {
updateAutorenewRecurrenceEndTime(existingDomain, END_OF_TIME);
// Delete the billing event and poll messages that were written in case the transfer would have
// been implicitly server approved.
ofy().delete().keys(existingDomain.getTransferData().getServerApproveEntities());
tm().delete(existingDomain.getTransferData().getServerApproveEntities());
return responseBuilder
.setResData(createTransferResponse(targetId, newDomain.getTransferData(), null))
.build();

View File

@@ -32,7 +32,7 @@ import google.registry.model.domain.DomainBase;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferStatus;
import google.registry.util.Clock;
import java.util.Optional;
@@ -74,7 +74,7 @@ public final class DomainTransferQueryFlow implements Flow {
verifyOptionalAuthInfo(authInfo, domain);
// Most of the fields on the transfer response are required, so there's no way to return valid
// XML if the object has never been transferred (and hence the fields aren't populated).
TransferData transferData = domain.getTransferData();
DomainTransferData transferData = domain.getTransferData();
if (transferData.getTransferStatus() == null) {
throw new NoTransferHistoryToQueryException();
}

View File

@@ -112,7 +112,7 @@ public final class DomainTransferRejectFlow implements TransactionalFlow {
updateAutorenewRecurrenceEndTime(existingDomain, END_OF_TIME);
// Delete the billing event and poll messages that were written in case the transfer would have
// been implicitly server approved.
ofy().delete().keys(existingDomain.getTransferData().getServerApproveEntities());
tm().delete(existingDomain.getTransferData().getServerApproveEntities());
return responseBuilder
.setResData(createTransferResponse(targetId, newDomain.getTransferData(), null))
.build();

View File

@@ -69,7 +69,7 @@ import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferData.TransferServerApproveEntity;
import google.registry.model.transfer.TransferResponse.DomainTransferResponse;
import google.registry.model.transfer.TransferStatus;
@@ -198,9 +198,9 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
feesAndCredits.map(FeesAndCredits::getTotalCost),
now);
// Create the transfer data that represents the pending transfer.
TransferData pendingTransferData =
DomainTransferData pendingTransferData =
createPendingTransferData(
new TransferData.Builder()
new DomainTransferData.Builder()
.setTransferRequestTrid(trid)
.setTransferRequestTime(now)
.setGainingClientId(gainingClientId)

View File

@@ -20,7 +20,6 @@ import static google.registry.util.DateTimeUtils.END_OF_TIME;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Flag;
import google.registry.model.billing.BillingEvent.Reason;
@@ -33,10 +32,12 @@ import google.registry.model.poll.PendingActionNotificationResponse.DomainPendin
import google.registry.model.poll.PollMessage;
import google.registry.model.registry.Registry;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferData.TransferServerApproveEntity;
import google.registry.model.transfer.TransferResponse.DomainTransferResponse;
import google.registry.model.transfer.TransferStatus;
import google.registry.persistence.VKey;
import java.util.Optional;
import javax.annotation.Nullable;
import org.joda.money.Money;
@@ -48,41 +49,38 @@ import org.joda.time.DateTime;
public final class DomainTransferUtils {
/** Sets up {@link TransferData} for a domain with links to entities for server approval. */
public static TransferData createPendingTransferData(
TransferData.Builder transferDataBuilder,
public static DomainTransferData createPendingTransferData(
DomainTransferData.Builder transferDataBuilder,
ImmutableSet<TransferServerApproveEntity> serverApproveEntities,
Period transferPeriod) {
ImmutableSet.Builder<Key<? extends TransferServerApproveEntity>> serverApproveEntityKeys =
ImmutableSet.Builder<VKey<? extends TransferServerApproveEntity>> serverApproveEntityKeys =
new ImmutableSet.Builder<>();
for (TransferServerApproveEntity entity : serverApproveEntities) {
serverApproveEntityKeys.add(Key.create(entity));
serverApproveEntityKeys.add(entity.createVKey());
}
if (transferPeriod.getValue() != 0) {
// Unless superuser sets period to 0, add a transfer billing event.
transferDataBuilder.setServerApproveBillingEvent(
Key.create(
serverApproveEntities
.stream()
.filter(BillingEvent.OneTime.class::isInstance)
.map(BillingEvent.OneTime.class::cast)
.collect(onlyElement())));
serverApproveEntities.stream()
.filter(BillingEvent.OneTime.class::isInstance)
.map(BillingEvent.OneTime.class::cast)
.collect(onlyElement())
.createVKey());
}
return transferDataBuilder
.setTransferStatus(TransferStatus.PENDING)
.setServerApproveAutorenewEvent(
Key.create(
serverApproveEntities
.stream()
.filter(BillingEvent.Recurring.class::isInstance)
.map(BillingEvent.Recurring.class::cast)
.collect(onlyElement())))
serverApproveEntities.stream()
.filter(BillingEvent.Recurring.class::isInstance)
.map(BillingEvent.Recurring.class::cast)
.collect(onlyElement())
.createVKey())
.setServerApproveAutorenewPollMessage(
Key.create(
serverApproveEntities
.stream()
.filter(PollMessage.Autorenew.class::isInstance)
.map(PollMessage.Autorenew.class::cast)
.collect(onlyElement())))
serverApproveEntities.stream()
.filter(PollMessage.Autorenew.class::isInstance)
.map(PollMessage.Autorenew.class::cast)
.collect(onlyElement())
.createVKey())
.setServerApproveEntities(serverApproveEntityKeys.build())
.setTransferPeriod(transferPeriod)
.build();
@@ -111,10 +109,10 @@ public final class DomainTransferUtils {
String gainingClientId,
Optional<Money> transferCost,
DateTime now) {
String targetId = existingDomain.getFullyQualifiedDomainName();
String targetId = existingDomain.getDomainName();
// Create a TransferData for the server-approve case to use for the speculative poll messages.
TransferData serverApproveTransferData =
new TransferData.Builder()
DomainTransferData serverApproveTransferData =
new DomainTransferData.Builder()
.setTransferRequestTrid(trid)
.setTransferRequestTime(now)
.setGainingClientId(gainingClientId)

View File

@@ -57,6 +57,7 @@ import google.registry.flows.custom.DomainUpdateFlowCustomLogic.AfterValidationP
import google.registry.flows.custom.DomainUpdateFlowCustomLogic.BeforeSaveParameters;
import google.registry.flows.custom.EntityChanges;
import google.registry.flows.domain.DomainFlowUtils.MissingRegistrantException;
import google.registry.flows.domain.DomainFlowUtils.NameserversNotSpecifiedForTldWithNameserverAllowListException;
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Reason;
@@ -109,7 +110,7 @@ import org.joda.time.DateTime;
* @error {@link DomainFlowUtils.MissingTechnicalContactException}
* @error {@link DomainFlowUtils.MissingRegistrantException}
* @error {@link DomainFlowUtils.NameserversNotAllowedForTldException}
* @error {@link DomainFlowUtils.NameserversNotSpecifiedForTldWithNameserverWhitelistException}
* @error {@link NameserversNotSpecifiedForTldWithNameserverAllowListException}
* @error {@link DomainFlowUtils.NotAuthorizedForTldException}
* @error {@link DomainFlowUtils.RegistrantNotAllowedException}
* @error {@link DomainFlowUtils.SecDnsAllUsageException}
@@ -265,7 +266,7 @@ public final class DomainUpdateFlow implements TransactionalFlow {
validateDsData(newDomain.getDsData());
validateNameserversCountForTld(
newDomain.getTld(),
InternetDomainName.from(newDomain.getFullyQualifiedDomainName()),
InternetDomainName.from(newDomain.getDomainName()),
newDomain.getNameservers().size());
}

View File

@@ -15,6 +15,7 @@
package google.registry.flows.domain;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.flows.domain.DomainFlowUtils.zeroInCurrency;
import static google.registry.util.CollectionUtils.nullToEmpty;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
@@ -27,6 +28,7 @@ import google.registry.model.domain.fee.BaseFee;
import google.registry.model.domain.fee.BaseFee.FeeType;
import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.Fee;
import java.math.BigDecimal;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
@@ -39,26 +41,30 @@ public class FeesAndCredits extends ImmutableObject implements Buildable {
private ImmutableList<Credit> credits;
private Money getTotalCostForType(FeeType type) {
Money result = Money.zero(currency);
checkArgumentNotNull(type);
for (Fee fee : fees) {
if (fee.getType() == type) {
result = result.plus(fee.getCost());
}
}
return result;
return Money.of(
currency,
fees.stream()
.filter(f -> f.getType() == type)
.map(BaseFee::getCost)
.reduce(zeroInCurrency(currency), BigDecimal::add));
}
public boolean hasPremiumFeesOfType(FeeType type) {
return fees.stream().filter(f -> f.getType() == type).anyMatch(BaseFee::isPremium);
}
/** Returns the total cost of all fees and credits for the event. */
public Money getTotalCost() {
Money result = Money.zero(currency);
for (Fee fee : fees) {
result = result.plus(fee.getCost());
}
for (Credit credit : credits) {
result = result.plus(credit.getCost());
}
return result;
return Money.of(
currency,
Streams.concat(fees.stream(), credits.stream())
.map(BaseFee::getCost)
.reduce(zeroInCurrency(currency), BigDecimal::add));
}
public boolean hasAnyPremiumFees() {
return fees.stream().anyMatch(BaseFee::isPremium);
}
/** Returns the create cost for the event. */

View File

@@ -125,10 +125,10 @@ public final class HostCreateFlow implements TransactionalFlow {
new HostResource.Builder()
.setCreationClientId(clientId)
.setPersistedCurrentSponsorClientId(clientId)
.setFullyQualifiedHostName(targetId)
.setHostName(targetId)
.setInetAddresses(command.getInetAddresses())
.setRepoId(createRepoId(ObjectifyService.allocateId(), roidSuffix))
.setSuperordinateDomain(superordinateDomain.map(Key::create).orElse(null))
.setSuperordinateDomain(superordinateDomain.map(DomainBase::createVKey).orElse(null))
.build();
historyBuilder
.setType(HistoryEntry.Type.HOST_CREATE)

View File

@@ -96,8 +96,7 @@ public final class HostDeleteFlow implements TransactionalFlow {
// the client id, needs to be read off of it.
EppResource owningResource =
existingHost.isSubordinate()
? ofy().load().key(existingHost.getSuperordinateDomain()).now()
.cloneProjectedAtTime(now)
? tm().load(existingHost.getSuperordinateDomain()).cloneProjectedAtTime(now)
: existingHost;
verifyResourceOwnership(clientId, owningResource);
}

View File

@@ -18,7 +18,7 @@ import static google.registry.flows.FlowUtils.validateClientIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
import static google.registry.flows.host.HostFlowUtils.validateHostName;
import static google.registry.model.EppResourceUtils.isLinked;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
@@ -77,7 +77,7 @@ public final class HostInfoFlow implements Flow {
// there is no superordinate domain, the host's own values for these fields will be correct.
if (host.isSubordinate()) {
DomainBase superordinateDomain =
ofy().load().key(host.getSuperordinateDomain()).now().cloneProjectedAtTime(now);
tm().load(host.getSuperordinateDomain()).cloneProjectedAtTime(now);
hostInfoDataBuilder
.setCurrentSponsorClientId(superordinateDomain.getCurrentSponsorClientId())
.setLastTransferTime(host.computeLastTransferTime(superordinateDomain));
@@ -90,16 +90,17 @@ public final class HostInfoFlow implements Flow {
.setLastTransferTime(host.getLastTransferTime());
}
return responseBuilder
.setResData(hostInfoDataBuilder
.setFullyQualifiedHostName(host.getFullyQualifiedHostName())
.setRepoId(host.getRepoId())
.setStatusValues(statusValues.build())
.setInetAddresses(host.getInetAddresses())
.setCreationClientId(host.getCreationClientId())
.setCreationTime(host.getCreationTime())
.setLastEppUpdateClientId(host.getLastEppUpdateClientId())
.setLastEppUpdateTime(host.getLastEppUpdateTime())
.build())
.setResData(
hostInfoDataBuilder
.setFullyQualifiedHostName(host.getHostName())
.setRepoId(host.getRepoId())
.setStatusValues(statusValues.build())
.setInetAddresses(host.getInetAddresses())
.setCreationClientId(host.getCreationClientId())
.setCreationTime(host.getCreationTime())
.setLastEppUpdateClientId(host.getLastEppUpdateClientId())
.setLastEppUpdateTime(host.getLastEppUpdateTime())
.build())
.build();
}
}

View File

@@ -60,6 +60,7 @@ import google.registry.model.host.HostResource;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.persistence.VKey;
import java.util.Objects;
import java.util.Optional;
import javax.inject.Inject;
@@ -136,9 +137,10 @@ public final class HostUpdateFlow implements TransactionalFlow {
boolean isHostRename = suppliedNewHostName != null;
String oldHostName = targetId;
String newHostName = firstNonNull(suppliedNewHostName, oldHostName);
DomainBase oldSuperordinateDomain = existingHost.isSubordinate()
? ofy().load().key(existingHost.getSuperordinateDomain()).now().cloneProjectedAtTime(now)
: null;
DomainBase oldSuperordinateDomain =
existingHost.isSubordinate()
? tm().load(existingHost.getSuperordinateDomain()).cloneProjectedAtTime(now)
: null;
// Note that lookupSuperordinateDomain calls cloneProjectedAtTime on the domain for us.
Optional<DomainBase> newSuperordinateDomain =
lookupSuperordinateDomain(validateHostName(newHostName), now);
@@ -153,8 +155,8 @@ public final class HostUpdateFlow implements TransactionalFlow {
AddRemove remove = command.getInnerRemove();
checkSameValuesNotAddedAndRemoved(add.getStatusValues(), remove.getStatusValues());
checkSameValuesNotAddedAndRemoved(add.getInetAddresses(), remove.getInetAddresses());
Key<DomainBase> newSuperordinateDomainKey =
newSuperordinateDomain.map(Key::create).orElse(null);
VKey<DomainBase> newSuperordinateDomainKey =
newSuperordinateDomain.map(DomainBase::createVKey).orElse(null);
// If the superordinateDomain field is changing, set the lastSuperordinateChange to now.
DateTime lastSuperordinateChange =
Objects.equals(newSuperordinateDomainKey, existingHost.getSuperordinateDomain())
@@ -173,19 +175,21 @@ public final class HostUpdateFlow implements TransactionalFlow {
newSuperordinateDomain.isPresent()
? newSuperordinateDomain.get().getCurrentSponsorClientId()
: owningResource.getPersistedCurrentSponsorClientId();
HostResource newHost = existingHost.asBuilder()
.setFullyQualifiedHostName(newHostName)
.addStatusValues(add.getStatusValues())
.removeStatusValues(remove.getStatusValues())
.addInetAddresses(add.getInetAddresses())
.removeInetAddresses(remove.getInetAddresses())
.setLastEppUpdateTime(now)
.setLastEppUpdateClientId(clientId)
.setSuperordinateDomain(newSuperordinateDomainKey)
.setLastSuperordinateChange(lastSuperordinateChange)
.setLastTransferTime(lastTransferTime)
.setPersistedCurrentSponsorClientId(newPersistedClientId)
.build();
HostResource newHost =
existingHost
.asBuilder()
.setHostName(newHostName)
.addStatusValues(add.getStatusValues())
.removeStatusValues(remove.getStatusValues())
.addInetAddresses(add.getInetAddresses())
.removeInetAddresses(remove.getInetAddresses())
.setLastEppUpdateTime(now)
.setLastEppUpdateClientId(clientId)
.setSuperordinateDomain(newSuperordinateDomainKey)
.setLastSuperordinateChange(lastSuperordinateChange)
.setLastTransferTime(lastTransferTime)
.setPersistedCurrentSponsorClientId(newPersistedClientId)
.build();
verifyHasIpsIffIsExternal(command, existingHost, newHost);
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
entitiesToSave.add(newHost);
@@ -261,14 +265,14 @@ public final class HostUpdateFlow implements TransactionalFlow {
// Only update DNS for subordinate hosts. External hosts have no glue to write, so they
// are only written as NS records from the referencing domain.
if (existingHost.isSubordinate()) {
dnsQueue.addHostRefreshTask(existingHost.getFullyQualifiedHostName());
dnsQueue.addHostRefreshTask(existingHost.getHostName());
}
// In case of a rename, there are many updates we need to queue up.
if (((Update) resourceCommand).getInnerChange().getFullyQualifiedHostName() != null) {
// If the renamed host is also subordinate, then we must enqueue an update to write the new
// glue.
if (newHost.isSubordinate()) {
dnsQueue.addHostRefreshTask(newHost.getFullyQualifiedHostName());
dnsQueue.addHostRefreshTask(newHost.getHostName());
}
// We must also enqueue updates for all domains that use this host as their nameserver so
// that their NS records can be updated to point at the new name.
@@ -280,28 +284,28 @@ public final class HostUpdateFlow implements TransactionalFlow {
if (existingHost.isSubordinate()
&& newHost.isSubordinate()
&& Objects.equals(
existingHost.getSuperordinateDomain(),
newHost.getSuperordinateDomain())) {
ofy().save().entity(
ofy().load().key(existingHost.getSuperordinateDomain()).now().asBuilder()
.removeSubordinateHost(existingHost.getFullyQualifiedHostName())
.addSubordinateHost(newHost.getFullyQualifiedHostName())
.build());
existingHost.getSuperordinateDomain(), newHost.getSuperordinateDomain())) {
tm().saveNewOrUpdate(
tm().load(existingHost.getSuperordinateDomain())
.asBuilder()
.removeSubordinateHost(existingHost.getHostName())
.addSubordinateHost(newHost.getHostName())
.build());
return;
}
if (existingHost.isSubordinate()) {
ofy().save().entity(
ofy().load().key(existingHost.getSuperordinateDomain()).now()
.asBuilder()
.removeSubordinateHost(existingHost.getFullyQualifiedHostName())
.build());
tm().saveNewOrUpdate(
tm().load(existingHost.getSuperordinateDomain())
.asBuilder()
.removeSubordinateHost(existingHost.getHostName())
.build());
}
if (newHost.isSubordinate()) {
ofy().save().entity(
ofy().load().key(newHost.getSuperordinateDomain()).now()
.asBuilder()
.addSubordinateHost(newHost.getFullyQualifiedHostName())
.build());
tm().saveNewOrUpdate(
tm().load(newHost.getSuperordinateDomain())
.asBuilder()
.addSubordinateHost(newHost.getHostName())
.build());
}
}

View File

@@ -48,6 +48,8 @@ import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.stream.StreamSupport;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;
@@ -56,23 +58,29 @@ import org.joda.time.Duration;
/** An EPP entity object (i.e. a domain, contact, or host). */
@MappedSuperclass
@Access(AccessType.FIELD) // otherwise it'll use the default if the repoId (property)
public abstract class EppResource extends BackupGroupRoot implements Buildable {
/**
* Unique identifier in the registry for this resource.
*
* <p>This is in the (\w|_){1,80}-\w{1,8} format specified by RFC 5730 for roidType.
*
* @see <a href="https://tools.ietf.org/html/rfc5730">RFC 5730</a>
*/
@Id @javax.persistence.Id String repoId;
@Id
// not persisted so that we can store these in references to other objects. Subclasses that wish
// to use this as the primary key should create a getter method annotated with @Id
@Transient
String repoId;
/** The ID of the registrar that is currently sponsoring this resource. */
@Index
@Column(nullable = false)
@Column(name = "currentSponsorRegistrarId", nullable = false)
String currentSponsorClientId;
/** The ID of the registrar that created this resource. */
@Column(nullable = false)
@Column(name = "creationRegistrarId", nullable = false)
String creationClientId;
/**
@@ -82,6 +90,7 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
* edits; it only includes EPP-visible modifications such as {@literal <update>}. Can be null if
* the resource has never been modified.
*/
@Column(name = "lastEppUpdateRegistrarId")
String lastEppUpdateClientId;
/** The time when this resource was created. */
@@ -137,6 +146,12 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
return repoId;
}
// Hibernate needs this to populate the repo ID, but no one else should ever use it
@SuppressWarnings("UnusedMethod")
private void setRepoId(String repoId) {
this.repoId = repoId;
}
public final DateTime getCreationTime() {
return creationTime.getTimestamp();
}
@@ -192,8 +207,8 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
public interface ForeignKeyedEppResource {}
/** An interface for resources that have transfer data. */
public interface ResourceWithTransferData {
TransferData getTransferData();
public interface ResourceWithTransferData<T extends TransferData> {
T getTransferData();
/**
* The time that this resource was last transferred.
@@ -204,15 +219,16 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
}
/** An interface for builders of resources that have transfer data. */
public interface BuilderWithTransferData<B extends BuilderWithTransferData<B>> {
B setTransferData(TransferData transferData);
public interface BuilderWithTransferData<
T extends TransferData, B extends BuilderWithTransferData<T, B>> {
B setTransferData(T transferData);
/** Set the time when this resource was transferred. */
B setLastTransferTime(DateTime lastTransferTime);
}
/** Abstract builder for {@link EppResource} types. */
public abstract static class Builder<T extends EppResource, B extends Builder<?, ?>>
public abstract static class Builder<T extends EppResource, B extends Builder<T, B>>
extends GenericBuilder<T, B> {
/** Create a {@link Builder} wrapping a new instance. */

View File

@@ -39,6 +39,7 @@ import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.ofy.CommitLogManifest;
import google.registry.model.ofy.CommitLogMutation;
import google.registry.model.registry.Registry;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferStatus;
import java.util.List;
@@ -222,17 +223,23 @@ public final class EppResourceUtils {
}
/** Process an automatic transfer on a resource. */
public static <B extends EppResource.Builder<?, B> & BuilderWithTransferData<B>>
public static <
T extends TransferData,
B extends EppResource.Builder<?, B> & BuilderWithTransferData<T, B>>
void setAutomaticTransferSuccessProperties(B builder, TransferData transferData) {
checkArgument(TransferStatus.PENDING.equals(transferData.getTransferStatus()));
builder.removeStatusValue(StatusValue.PENDING_TRANSFER)
.setTransferData(transferData.asBuilder()
.setTransferStatus(TransferStatus.SERVER_APPROVED)
.setServerApproveEntities(null)
.setServerApproveBillingEvent(null)
.setServerApproveAutorenewEvent(null)
.setServerApproveAutorenewPollMessage(null)
.build())
TransferData.Builder transferDataBuilder = transferData.asBuilder();
transferDataBuilder.setTransferStatus(TransferStatus.SERVER_APPROVED);
transferDataBuilder.setServerApproveEntities(null);
if (transferData instanceof DomainTransferData) {
((DomainTransferData.Builder) transferDataBuilder)
.setServerApproveBillingEvent(null)
.setServerApproveAutorenewEvent(null)
.setServerApproveAutorenewPollMessage(null);
}
builder
.removeStatusValue(StatusValue.PENDING_TRANSFER)
.setTransferData((T) transferDataBuilder.build())
.setLastTransferTime(transferData.getPendingTransferExpirationTime())
.setPersistedCurrentSponsorClientId(transferData.getGainingClientId());
}
@@ -245,10 +252,11 @@ public final class EppResourceUtils {
* </ul>
*/
public static <
T extends EppResource & ResourceWithTransferData,
B extends EppResource.Builder<?, B> & BuilderWithTransferData<B>>
void projectResourceOntoBuilderAtTime(T resource, B builder, DateTime now) {
TransferData transferData = resource.getTransferData();
T extends TransferData,
E extends EppResource & ResourceWithTransferData<T>,
B extends EppResource.Builder<?, B> & BuilderWithTransferData<T, B>>
void projectResourceOntoBuilderAtTime(E resource, B builder, DateTime now) {
T transferData = resource.getTransferData();
// If there's a pending transfer that has expired, process it.
DateTime expirationTime = transferData.getPendingTransferExpirationTime();
if (TransferStatus.PENDING.equals(transferData.getTransferStatus())

View File

@@ -57,17 +57,17 @@ import org.joda.time.Duration;
* <p>This includes the TLDs (Registries), Registrars, and the RegistrarContacts that can access the
* web console.
*
* This class is basically a "builder" for the parameters needed to generate the OT&amp;E entities.
* Nothing is created until you call {@link #buildAndPersist}.
* <p>This class is basically a "builder" for the parameters needed to generate the OT&amp;E
* entities. Nothing is created until you call {@link #buildAndPersist}.
*
* Usage example:
* <p>Usage example:
*
* <pre> {@code
* <pre>{@code
* OteAccountBuilder.forClientId("example")
* .addContact("contact@email.com") // OPTIONAL
* .setPassword("password") // OPTIONAL
* .setCertificateHash(certificateHash) // OPTIONAL
* .setIpWhitelist(ImmutableList.of("1.1.1.1", "2.2.2.0/24")) // OPTIONAL
* .setIpAllowList(ImmutableList.of("1.1.1.1", "2.2.2.0/24")) // OPTIONAL
* .buildAndPersist();
* }</pre>
*/
@@ -221,11 +221,11 @@ public final class OteAccountBuilder {
return transformRegistrars(builder -> builder.setClientCertificate(asciiCert, now));
}
/** Sets the IP whitelist to all the OT&amp;E Registrars. */
public OteAccountBuilder setIpWhitelist(Collection<String> ipWhitelist) {
ImmutableList<CidrAddressBlock> ipAddressWhitelist =
ipWhitelist.stream().map(CidrAddressBlock::create).collect(toImmutableList());
return transformRegistrars(builder -> builder.setIpAddressWhitelist(ipAddressWhitelist));
/** Sets the IP allow list to all the OT&amp;E Registrars. */
public OteAccountBuilder setIpAllowList(Collection<String> ipAllowList) {
ImmutableList<CidrAddressBlock> ipAddressAllowList =
ipAllowList.stream().map(CidrAddressBlock::create).collect(toImmutableList());
return transformRegistrars(builder -> builder.setIpAddressAllowList(ipAddressAllowList));
}
/**

View File

@@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -35,6 +36,7 @@ import google.registry.model.poll.PendingActionNotificationResponse.ContactPendi
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferResponse;
import google.registry.model.transfer.TransferResponse.ContactTransferResponse;
@@ -62,12 +64,13 @@ public final class ResourceTransferUtils {
if (eppResource instanceof ContactResource) {
builder = new ContactTransferResponse.Builder().setContactId(eppResource.getForeignKey());
} else {
DomainTransferData domainTransferData = (DomainTransferData) transferData;
builder =
new DomainTransferResponse.Builder()
.setFullyQualifiedDomainName(eppResource.getForeignKey())
.setExtendedRegistrationExpirationTime(
ADD_EXDATE_STATUSES.contains(transferData.getTransferStatus())
? transferData.getTransferredRegistrationExpirationTime()
ADD_EXDATE_STATUSES.contains(domainTransferData.getTransferStatus())
? domainTransferData.getTransferredRegistrationExpirationTime()
: null);
}
builder.setGainingClientId(transferData.getGainingClientId())
@@ -114,7 +117,7 @@ public final class ResourceTransferUtils {
R resource, R newResource, DateTime now, HistoryEntry historyEntry) {
if (resource.getStatusValues().contains(StatusValue.PENDING_TRANSFER)) {
TransferData oldTransferData = resource.getTransferData();
ofy().delete().keys(oldTransferData.getServerApproveEntities());
tm().delete(oldTransferData.getServerApproveEntities());
ofy()
.save()
.entity(
@@ -141,23 +144,25 @@ public final class ResourceTransferUtils {
*/
private static <
R extends EppResource & ResourceWithTransferData,
B extends EppResource.Builder<R, B> & BuilderWithTransferData<B>>
B extends EppResource.Builder<R, B> & BuilderWithTransferData<TransferData, B>>
B resolvePendingTransfer(R resource, TransferStatus transferStatus, DateTime now) {
checkArgument(
resource.getStatusValues().contains(StatusValue.PENDING_TRANSFER),
"Resource is not in pending transfer status.");
checkArgument(
!TransferData.EMPTY.equals(resource.getTransferData()),
"No old transfer data to resolve.");
checkArgument(!resource.getTransferData().isEmpty(), "No old transfer data to resolve.");
@SuppressWarnings("unchecked")
B builder = (B) resource.asBuilder();
return builder
.removeStatusValue(StatusValue.PENDING_TRANSFER)
.setTransferData(
resource.getTransferData().copyConstantFieldsToBuilder()
.setTransferStatus(transferStatus)
.setPendingTransferExpirationTime(checkNotNull(now))
.build());
(TransferData)
resource
.getTransferData()
.copyConstantFieldsToBuilder()
.setTransferStatus(transferStatus)
.setPendingTransferExpirationTime(checkNotNull(now))
.build());
}
/**
@@ -170,7 +175,7 @@ public final class ResourceTransferUtils {
*/
public static <
R extends EppResource & ResourceWithTransferData,
B extends EppResource.Builder<R, B> & BuilderWithTransferData<B>>
B extends EppResource.Builder<R, B> & BuilderWithTransferData<TransferData, B>>
R approvePendingTransfer(R resource, TransferStatus transferStatus, DateTime now) {
checkArgument(transferStatus.isApproved(), "Not an approval transfer status");
B builder = resolvePendingTransfer(resource, transferStatus, now);

View File

@@ -116,7 +116,7 @@ public abstract class BillingEvent extends ImmutableObject
/** The registrar to bill. */
@Index
@Column(nullable = false)
@Column(name = "registrarId", nullable = false)
String clientId;
/** Revision id of the entry in DomainHistory table that ths bill belongs to. */
@@ -267,7 +267,7 @@ public abstract class BillingEvent extends ImmutableObject
@javax.persistence.Entity(name = "BillingEvent")
@javax.persistence.Table(
indexes = {
@javax.persistence.Index(columnList = "clientId"),
@javax.persistence.Index(columnList = "registrarId"),
@javax.persistence.Index(columnList = "eventTime"),
@javax.persistence.Index(columnList = "billingTime"),
@javax.persistence.Index(columnList = "syntheticCreationTime"),
@@ -345,6 +345,7 @@ public abstract class BillingEvent extends ImmutableObject
return Optional.ofNullable(allocationToken);
}
@Override
public VKey<OneTime> createVKey() {
return VKey.createOfy(getClass(), Key.create(this));
}
@@ -439,7 +440,7 @@ public abstract class BillingEvent extends ImmutableObject
@javax.persistence.Entity(name = "BillingRecurrence")
@javax.persistence.Table(
indexes = {
@javax.persistence.Index(columnList = "clientId"),
@javax.persistence.Index(columnList = "registrarId"),
@javax.persistence.Index(columnList = "eventTime"),
@javax.persistence.Index(columnList = "recurrenceEndTime"),
@javax.persistence.Index(columnList = "recurrence_time_of_year")
@@ -482,6 +483,7 @@ public abstract class BillingEvent extends ImmutableObject
return recurrenceTimeOfYear;
}
@Override
public VKey<Recurring> createVKey() {
return VKey.createOfy(getClass(), Key.create(this));
}
@@ -529,7 +531,7 @@ public abstract class BillingEvent extends ImmutableObject
@javax.persistence.Entity(name = "BillingCancellation")
@javax.persistence.Table(
indexes = {
@javax.persistence.Index(columnList = "clientId"),
@javax.persistence.Index(columnList = "registrarId"),
@javax.persistence.Index(columnList = "eventTime"),
@javax.persistence.Index(columnList = "billingTime")
})
@@ -603,6 +605,7 @@ public abstract class BillingEvent extends ImmutableObject
return builder.build();
}
@Override
public VKey<Cancellation> createVKey() {
return VKey.createOfy(getClass(), Key.create(this));
}
@@ -682,6 +685,11 @@ public abstract class BillingEvent extends ImmutableObject
return new Builder(clone(this));
}
@Override
public VKey<Modification> createVKey() {
return VKey.createOfy(this.getClass(), Key.create(this));
}
/**
* Create a new Modification billing event which is a refund of the given OneTime billing event
* and that is parented off the given HistoryEntry.

View File

@@ -35,9 +35,9 @@ import java.util.List;
import org.joda.time.DateTime;
/**
* Shared entity for date cursors. This type supports both "scoped" cursors (i.e. per resource
* of a given type, such as a TLD) and global (i.e. one per environment) cursors, defined internally
* as scoped on {@link EntityGroupRoot}.
* Shared entity for date cursors. This type supports both "scoped" cursors (i.e. per resource of a
* given type, such as a TLD) and global (i.e. one per environment) cursors, defined internally as
* scoped on {@link EntityGroupRoot}.
*/
@Entity
public class Cursor extends ImmutableObject implements DatastoreEntity {

View File

@@ -27,14 +27,14 @@ import google.registry.schema.replay.SqlEntity;
* reasons.
*
* <p>This exists as a storage place for common configuration options and global settings that
* aren't updated too frequently. Entities in this entity group are usually cached upon load. The
* aren't updated too frequently. Entities in this entity group are usually cached upon load. The
* reason this common entity group exists is because it enables strongly consistent queries and
* updates across this seldomly updated data. This shared entity group also helps cut down on
* a potential ballooning in the number of entity groups enlisted in transactions.
* updates across this seldomly updated data. This shared entity group also helps cut down on a
* potential ballooning in the number of entity groups enlisted in transactions.
*
* <p>Historically, each TLD used to have a separate namespace, and all entities for a TLD were in
* a single EntityGroupRoot for that TLD. Hence why there was a "cross-tld" entity group -- it was
* the entity group for the single namespace where global data applicable for all TLDs lived.
* <p>Historically, each TLD used to have a separate namespace, and all entities for a TLD were in a
* single EntityGroupRoot for that TLD. Hence why there was a "cross-tld" entity group -- it was the
* entity group for the single namespace where global data applicable for all TLDs lived.
*/
@Entity
public class EntityGroupRoot extends BackupGroupRoot implements DatastoreEntity {

View File

@@ -30,18 +30,19 @@ import google.registry.model.EppResource.ResourceWithTransferData;
import google.registry.model.annotations.ExternalMessagingName;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.contact.PostalInfo.Type;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.ContactTransferData;
import google.registry.persistence.VKey;
import google.registry.persistence.WithStringVKey;
import google.registry.schema.replay.DatastoreAndSqlEntity;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlElement;
import org.joda.time.DateTime;
@@ -57,13 +58,14 @@ import org.joda.time.DateTime;
name = "Contact",
indexes = {
@javax.persistence.Index(columnList = "creationTime"),
@javax.persistence.Index(columnList = "currentSponsorClientId"),
@javax.persistence.Index(columnList = "currentSponsorRegistrarId"),
@javax.persistence.Index(columnList = "deletionTime"),
@javax.persistence.Index(columnList = "contactId", unique = true),
@javax.persistence.Index(columnList = "searchName")
})
@ExternalMessagingName("contact")
@WithStringVKey
@Access(AccessType.FIELD)
public class ContactResource extends EppResource
implements DatastoreAndSqlEntity, ForeignKeyedEppResource, ResourceWithTransferData {
@@ -171,8 +173,7 @@ public class ContactResource extends EppResource
ContactAuthInfo authInfo;
/** Data about any pending or past transfers on this contact. */
// TODO(b/153363295): Figure out how to persist transfer data
@Transient TransferData transferData;
ContactTransferData transferData;
/**
* The time that this resource was last transferred.
@@ -203,6 +204,13 @@ public class ContactResource extends EppResource
return VKey.createOfy(ContactResource.class, Key.create(this));
}
@Override
@javax.persistence.Id
@Access(AccessType.PROPERTY)
public String getRepoId() {
return super.getRepoId();
}
public String getContactId() {
return contactId;
}
@@ -244,8 +252,8 @@ public class ContactResource extends EppResource
}
@Override
public final TransferData getTransferData() {
return Optional.ofNullable(transferData).orElse(TransferData.EMPTY);
public final ContactTransferData getTransferData() {
return Optional.ofNullable(transferData).orElse(ContactTransferData.EMPTY);
}
@Override
@@ -287,7 +295,7 @@ public class ContactResource extends EppResource
/** A builder for constructing {@link ContactResource}, since it is immutable. */
public static class Builder extends EppResource.Builder<ContactResource, Builder>
implements BuilderWithTransferData<Builder> {
implements BuilderWithTransferData<ContactTransferData, Builder> {
public Builder() {}
@@ -352,7 +360,7 @@ public class ContactResource extends EppResource
}
@Override
public Builder setTransferData(TransferData transferData) {
public Builder setTransferData(ContactTransferData transferData) {
getInstance().transferData = transferData;
return this;
}
@@ -382,7 +390,7 @@ public class ContactResource extends EppResource
public ContactResource build() {
ContactResource instance = getInstance();
// If TransferData is totally empty, set it to null.
if (TransferData.EMPTY.equals(instance.transferData)) {
if (ContactTransferData.EMPTY.equals(instance.transferData)) {
setTransferData(null);
}
// Set the searchName using the internationalized and localized postal info names.

View File

@@ -63,9 +63,10 @@ import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.HostResource;
import google.registry.model.poll.PollMessage;
import google.registry.model.registry.Registry;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferStatus;
import google.registry.persistence.VKey;
import google.registry.persistence.WithStringVKey;
import google.registry.schema.replay.DatastoreAndSqlEntity;
import google.registry.util.CollectionUtils;
import java.util.HashSet;
@@ -74,6 +75,8 @@ import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import javax.annotation.Nullable;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
@@ -101,14 +104,18 @@ import org.joda.time.Interval;
name = "Domain",
indexes = {
@javax.persistence.Index(columnList = "creationTime"),
@javax.persistence.Index(columnList = "currentSponsorClientId"),
@javax.persistence.Index(columnList = "currentSponsorRegistrarId"),
@javax.persistence.Index(columnList = "deletionTime"),
@javax.persistence.Index(columnList = "fullyQualifiedDomainName"),
@javax.persistence.Index(columnList = "domainName"),
@javax.persistence.Index(columnList = "tld")
})
@WithStringVKey
@ExternalMessagingName("domain")
@Access(AccessType.FIELD)
public class DomainBase extends EppResource
implements DatastoreAndSqlEntity, ForeignKeyedEppResource, ResourceWithTransferData {
implements DatastoreAndSqlEntity,
ForeignKeyedEppResource,
ResourceWithTransferData<DomainTransferData> {
/** The max number of years that a domain can be registered for, as set by ICANN policy. */
public static final int MAX_REGISTRATION_YEARS = 10;
@@ -130,7 +137,10 @@ public class DomainBase extends EppResource
*
* @invariant fullyQualifiedDomainName == fullyQualifiedDomainName.toLowerCase(Locale.ENGLISH)
*/
@Index String fullyQualifiedDomainName;
// TODO(b/158858642): Rename this to domainName when we are off Datastore
@Column(name = "domainName")
@Index
String fullyQualifiedDomainName;
/** The top level domain this is under, dernormalized from {@link #fullyQualifiedDomainName}. */
@Index
@@ -251,7 +261,7 @@ public class DomainBase extends EppResource
String smdId;
/** Data about any pending or past transfers on this domain. */
@Transient TransferData transferData;
DomainTransferData transferData;
/**
* The time that this resource was last transferred.
@@ -291,6 +301,13 @@ public class DomainBase extends EppResource
allContacts = contactsBuilder.build();
}
@Override
@javax.persistence.Id
@Access(AccessType.PROPERTY)
public String getRepoId() {
return super.getRepoId();
}
public ImmutableSet<String> getSubordinateHosts() {
return nullToEmptyImmutableCopy(subordinateHosts);
}
@@ -320,8 +337,8 @@ public class DomainBase extends EppResource
}
@Override
public TransferData getTransferData() {
return Optional.ofNullable(transferData).orElse(TransferData.EMPTY);
public DomainTransferData getTransferData() {
return Optional.ofNullable(transferData).orElse(DomainTransferData.EMPTY);
}
@Override
@@ -334,7 +351,7 @@ public class DomainBase extends EppResource
return fullyQualifiedDomainName;
}
public String getFullyQualifiedDomainName() {
public String getDomainName() {
return fullyQualifiedDomainName;
}
@@ -400,7 +417,7 @@ public class DomainBase extends EppResource
@Override
public DomainBase cloneProjectedAtTime(final DateTime now) {
TransferData transferData = getTransferData();
DomainTransferData transferData = getTransferData();
DateTime transferExpirationTime = transferData.getPendingTransferExpirationTime();
// If there's a pending transfer that has expired, handle it.
@@ -436,8 +453,14 @@ public class DomainBase extends EppResource
.setRegistrationExpirationTime(expirationDate)
// Set the speculatively-written new autorenew events as the domain's autorenew
// events.
.setAutorenewBillingEvent(transferData.getServerApproveAutorenewEvent())
.setAutorenewPollMessage(transferData.getServerApproveAutorenewPollMessage());
.setAutorenewBillingEvent(
transferData.getServerApproveAutorenewEvent() == null
? null
: transferData.getServerApproveAutorenewEvent().getOfyKey())
.setAutorenewPollMessage(
transferData.getServerApproveAutorenewPollMessage() == null
? null
: transferData.getServerApproveAutorenewPollMessage().getOfyKey());
if (transferData.getTransferPeriod().getValue() == 1) {
// Set the grace period using a key to the prescheduled transfer billing event. Not using
// GracePeriod.forBillingEvent() here in order to avoid the actual Datastore fetch.
@@ -448,7 +471,9 @@ public class DomainBase extends EppResource
transferExpirationTime.plus(
Registry.get(getTld()).getTransferGracePeriodLength()),
transferData.getGainingClientId(),
transferData.getServerApproveBillingEvent())));
transferData.getServerApproveBillingEvent() == null
? null
: transferData.getServerApproveBillingEvent().getOfyKey())));
} else {
// There won't be a billing event, so we don't need a grace period
builder.setGracePeriods(ImmutableSet.of());
@@ -530,13 +555,13 @@ public class DomainBase extends EppResource
}
/** Loads and returns the fully qualified host names of all linked nameservers. */
public ImmutableSortedSet<String> loadNameserverFullyQualifiedHostNames() {
public ImmutableSortedSet<String> loadNameserverHostNames() {
return ofy()
.load()
.keys(getNameservers().stream().map(VKey::getOfyKey).collect(toImmutableSet()))
.values()
.stream()
.map(HostResource::getFullyQualifiedHostName)
.map(HostResource::getHostName)
.collect(toImmutableSortedSet(Ordering.natural()));
}
@@ -618,6 +643,10 @@ public class DomainBase extends EppResource
return VKey.create(DomainBase.class, getRepoId(), Key.create(this));
}
public static VKey<DomainBase> createVKey(Key key) {
return VKey.create(DomainBase.class, key.getName(), key);
}
/** Predicate to determine if a given {@link DesignatedContact} is the registrant. */
private static final Predicate<DesignatedContact> IS_REGISTRANT =
(DesignatedContact contact) -> DesignatedContact.Type.REGISTRANT.equals(contact.type);
@@ -630,7 +659,7 @@ public class DomainBase extends EppResource
/** A builder for constructing {@link DomainBase}, since it is immutable. */
public static class Builder extends EppResource.Builder<DomainBase, Builder>
implements BuilderWithTransferData<Builder> {
implements BuilderWithTransferData<DomainTransferData, Builder> {
public Builder() {}
@@ -642,7 +671,7 @@ public class DomainBase extends EppResource
public DomainBase build() {
DomainBase instance = getInstance();
// If TransferData is totally empty, set it to null.
if (TransferData.EMPTY.equals(getInstance().transferData)) {
if (DomainTransferData.EMPTY.equals(getInstance().transferData)) {
setTransferData(null);
}
// A DomainBase has status INACTIVE if there are no nameservers.
@@ -652,8 +681,7 @@ public class DomainBase extends EppResource
removeStatusValue(StatusValue.INACTIVE);
}
checkArgumentNotNull(
emptyToNull(instance.fullyQualifiedDomainName), "Missing fullyQualifiedDomainName");
checkArgumentNotNull(emptyToNull(instance.fullyQualifiedDomainName), "Missing domainName");
if (instance.getRegistrant() == null
&& instance.allContacts.stream().anyMatch(IS_REGISTRANT)) {
throw new IllegalArgumentException("registrant is null but is in allContacts");
@@ -663,11 +691,11 @@ public class DomainBase extends EppResource
return super.build();
}
public Builder setFullyQualifiedDomainName(String fullyQualifiedDomainName) {
public Builder setDomainName(String domainName) {
checkArgument(
fullyQualifiedDomainName.equals(canonicalizeDomainName(fullyQualifiedDomainName)),
domainName.equals(canonicalizeDomainName(domainName)),
"Domain name must be in puny-coded, lower-case form");
getInstance().fullyQualifiedDomainName = fullyQualifiedDomainName;
getInstance().fullyQualifiedDomainName = domainName;
return thisCastToDerived();
}
@@ -816,7 +844,7 @@ public class DomainBase extends EppResource
}
@Override
public Builder setTransferData(TransferData transferData) {
public Builder setTransferData(DomainTransferData transferData) {
getInstance().transferData = transferData;
return thisCastToDerived();
}

View File

@@ -24,6 +24,7 @@ import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingEvent;
import google.registry.model.domain.rgp.GracePeriodStatus;
import javax.annotation.Nullable;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import org.joda.time.DateTime;
@@ -51,6 +52,7 @@ public class GracePeriod extends ImmutableObject {
DateTime expirationTime;
/** The registrar to bill. */
@Column(name = "registrarId")
String clientId;
/**

View File

@@ -16,6 +16,8 @@ package google.registry.model.domain;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.ImmutableObject;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlValue;
@@ -25,6 +27,7 @@ import javax.xml.bind.annotation.XmlValue;
@javax.persistence.Embeddable
public class Period extends ImmutableObject {
@Enumerated(EnumType.STRING)
@XmlAttribute
Unit unit;

View File

@@ -104,6 +104,8 @@ public abstract class BaseFee extends ImmutableObject {
@XmlTransient Range<DateTime> validDateRange;
@XmlTransient boolean isPremium;
public String getDescription() {
return description;
}
@@ -120,6 +122,11 @@ public abstract class BaseFee extends ImmutableObject {
return firstNonNull(refundable, true);
}
/** Returns whether the fee in question is a premium price. */
public boolean isPremium() {
return isPremium;
}
/**
* According to the fee extension specification, a fee must always be non-negative, while a credit
* must always be negative. Essentially, they are the same thing, just with different sign.

View File

@@ -31,25 +31,33 @@ import org.joda.time.DateTime;
public class Fee extends BaseFee {
/** Creates a Fee for the given cost and type with the default description. */
public static Fee create(BigDecimal cost, FeeType type, Object... descriptionArgs) {
public static Fee create(
BigDecimal cost, FeeType type, boolean isPremium, Object... descriptionArgs) {
checkArgumentNotNull(type, "Must specify the type of the fee");
return createWithCustomDescription(cost, type, type.renderDescription(descriptionArgs));
return createWithCustomDescription(
cost, type, isPremium, type.renderDescription(descriptionArgs));
}
/** Creates a Fee for the given cost, type, and valid date range with the default description. */
public static Fee create(
BigDecimal cost, FeeType type, Range<DateTime> validDateRange, Object... descriptionArgs) {
Fee instance = create(cost, type, descriptionArgs);
BigDecimal cost,
FeeType type,
boolean isPremium,
Range<DateTime> validDateRange,
Object... descriptionArgs) {
Fee instance = create(cost, type, isPremium, descriptionArgs);
instance.validDateRange = validDateRange;
return instance;
}
/** Creates a Fee for the given cost and type with a custom description. */
public static Fee createWithCustomDescription(BigDecimal cost, FeeType type, String description) {
private static Fee createWithCustomDescription(
BigDecimal cost, FeeType type, boolean isPremium, String description) {
Fee instance = new Fee();
instance.cost = checkNotNull(cost);
checkArgument(instance.cost.signum() >= 0);
checkArgument(instance.cost.signum() >= 0, "Cost must be a positive number");
instance.type = checkNotNull(type);
instance.isPremium = isPremium;
instance.description = description;
return instance;
}

View File

@@ -56,7 +56,7 @@ public class FeeCheckCommandExtensionV11 extends ImmutableObject
/** The period to check. */
Period period;
/** The class to check. */
/** The fee class to check. */
@XmlElement(name = "class")
String feeClass;

View File

@@ -22,6 +22,7 @@ import com.google.common.collect.ImmutableSet;
import google.registry.model.EppResource;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainBase;
import google.registry.model.host.HostBase;
import google.registry.model.host.HostResource;
import google.registry.model.translators.EnumToAttributeAdapter.EppEnum;
import google.registry.model.translators.StatusValueAdapter;
@@ -127,7 +128,7 @@ public enum StatusValue implements EppEnum {
/** Enum to help clearly list which resource types a status value is allowed to be present on. */
private enum AllowedOn {
ALL(ContactResource.class, DomainBase.class, HostResource.class),
ALL(ContactResource.class, DomainBase.class, HostBase.class, HostResource.class),
NONE,
DOMAINS(DomainBase.class);

View File

@@ -0,0 +1,233 @@
// Copyright 2020 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.host;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.union;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.condition.IfNull;
import google.registry.model.EppResource;
import google.registry.model.domain.DomainBase;
import google.registry.model.transfer.TransferData;
import google.registry.persistence.VKey;
import java.net.InetAddress;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.MappedSuperclass;
import org.joda.time.DateTime;
/**
* A persistable Host resource including mutable and non-mutable fields.
*
* <p>A host's {@link TransferData} is stored on the superordinate domain. Non-subordinate hosts
* don't carry a full set of TransferData; all they have is lastTransferTime.
*
* <p>This class deliberately does not include an {@link javax.persistence.Id} so that any
* foreign-keyed fields can refer to the proper parent entity's ID, whether we're storing this in
* the DB itself or as part of another entity
*
* @see <a href="https://tools.ietf.org/html/rfc5732">RFC 5732</a>
*/
@MappedSuperclass
@Embeddable
@Access(AccessType.FIELD)
public class HostBase extends EppResource {
/**
* Fully qualified hostname, which is a unique identifier for this host.
*
* <p>This is only unique in the sense that for any given lifetime specified as the time range
* from (creationTime, deletionTime) there can only be one host in Datastore with this name.
* However, there can be many hosts with the same name and non-overlapping lifetimes.
*/
// TODO(b/158858642): Rename this to hostName when we are off Datastore
@Index
@Column(name = "hostName")
String fullyQualifiedHostName;
/** IP Addresses for this host. Can be null if this is an external host. */
@Index Set<InetAddress> inetAddresses;
/** The superordinate domain of this host, or null if this is an external host. */
@Index
@IgnoreSave(IfNull.class)
@DoNotHydrate
VKey<DomainBase> superordinateDomain;
/**
* The time that this resource was last transferred.
*
* <p>Can be null if the resource has never been transferred.
*/
DateTime lastTransferTime;
/**
* The most recent time that the {@link #superordinateDomain} field was changed.
*
* <p>This should be updated whenever the superordinate domain changes, including when it is set
* to null. This field will be null for new hosts that have never experienced a change of
* superordinate domain.
*/
DateTime lastSuperordinateChange;
public String getHostName() {
return fullyQualifiedHostName;
}
public VKey<DomainBase> getSuperordinateDomain() {
return superordinateDomain;
}
public boolean isSubordinate() {
return superordinateDomain != null;
}
public ImmutableSet<InetAddress> getInetAddresses() {
return nullToEmptyImmutableCopy(inetAddresses);
}
public DateTime getLastTransferTime() {
return lastTransferTime;
}
public DateTime getLastSuperordinateChange() {
return lastSuperordinateChange;
}
@Override
public String getForeignKey() {
return fullyQualifiedHostName;
}
@Override
public VKey<? extends EppResource> createVKey() {
return VKey.createOfy(HostBase.class, Key.create(this));
}
@Deprecated
@Override
public HostBase cloneProjectedAtTime(DateTime now) {
return this;
}
@Override
public Builder asBuilder() {
return new Builder<>(clone(this));
}
/**
* Compute the correct last transfer time for this host given its loaded superordinate domain.
*
* <p>Hosts can move between superordinate domains, so to know which lastTransferTime is correct
* we need to know if the host was attached to this superordinate the last time that the
* superordinate was transferred. If the last superordinate change was before this time, then the
* host was attached to this superordinate domain during that transfer.
*
* <p>If the host is not subordinate the domain can be null and we just return last transfer time.
*
* @param superordinateDomain the loaded superordinate domain, which must match the key in the
* {@link #superordinateDomain} field. Passing it as a parameter allows the caller to control
* the degree of consistency used to load it.
*/
public DateTime computeLastTransferTime(@Nullable DomainBase superordinateDomain) {
if (!isSubordinate()) {
checkArgument(superordinateDomain == null);
return getLastTransferTime();
}
checkArgument(
superordinateDomain != null
&& superordinateDomain.createVKey().equals(getSuperordinateDomain()));
DateTime lastSuperordinateChange =
Optional.ofNullable(getLastSuperordinateChange()).orElse(getCreationTime());
DateTime lastTransferOfCurrentSuperordinate =
Optional.ofNullable(superordinateDomain.getLastTransferTime()).orElse(START_OF_TIME);
return lastSuperordinateChange.isBefore(lastTransferOfCurrentSuperordinate)
? superordinateDomain.getLastTransferTime()
: getLastTransferTime();
}
/** A builder for constructing {@link HostBase}, since it is immutable. */
protected static class Builder<T extends HostBase, B extends Builder<T, B>>
extends EppResource.Builder<T, B> {
public Builder() {}
protected Builder(T instance) {
super(instance);
}
// Strangely, if we don't add these @Overrides the methods return an EppResource.Builder
// even though we parameterize it with B in both cases anyway.
@Override
public B setRepoId(String repoId) {
return super.setRepoId(repoId);
}
@Override
public T build() {
return super.build();
}
public B setHostName(String hostName) {
checkArgument(
hostName.equals(canonicalizeDomainName(hostName)),
"Host name must be in puny-coded, lower-case form");
getInstance().fullyQualifiedHostName = hostName;
return thisCastToDerived();
}
public B setInetAddresses(ImmutableSet<InetAddress> inetAddresses) {
getInstance().inetAddresses = inetAddresses;
return thisCastToDerived();
}
public B setLastSuperordinateChange(DateTime lastSuperordinateChange) {
getInstance().lastSuperordinateChange = lastSuperordinateChange;
return thisCastToDerived();
}
public B addInetAddresses(ImmutableSet<InetAddress> inetAddresses) {
return setInetAddresses(
ImmutableSet.copyOf(union(getInstance().getInetAddresses(), inetAddresses)));
}
public B removeInetAddresses(ImmutableSet<InetAddress> inetAddresses) {
return setInetAddresses(
ImmutableSet.copyOf(difference(getInstance().getInetAddresses(), inetAddresses)));
}
public B setSuperordinateDomain(VKey<DomainBase> superordinateDomain) {
getInstance().superordinateDomain = superordinateDomain;
return thisCastToDerived();
}
public B setLastTransferTime(DateTime lastTransferTime) {
getInstance().lastTransferTime = lastTransferTime;
return thisCastToDerived();
}
}
}

View File

@@ -0,0 +1,90 @@
// Copyright 2020 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.host;
import com.googlecode.objectify.Key;
import google.registry.model.EppResource;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey;
import javax.persistence.Column;
import javax.persistence.Entity;
/**
* A persisted history entry representing an EPP modification to a host.
*
* <p>In addition to the general history fields (e.g. action time, registrar ID) we also persist a
* copy of the host entity at this point in time. We persist a raw {@link HostBase} so that the
* foreign-keyed fields in that class can refer to this object.
*/
@Entity
@javax.persistence.Table(
indexes = {
@javax.persistence.Index(columnList = "creationTime"),
@javax.persistence.Index(columnList = "historyRegistrarId"),
@javax.persistence.Index(columnList = "hostName"),
@javax.persistence.Index(columnList = "historyType"),
@javax.persistence.Index(columnList = "historyModificationTime")
})
public class HostHistory extends HistoryEntry {
// Store HostBase instead of HostResource so we don't pick up its @Id
HostBase hostBase;
@Column(nullable = false)
VKey<HostResource> hostRepoId;
/** The state of the {@link HostBase} object at this point in time. */
public HostBase getHostBase() {
return hostBase;
}
/** The key to the {@link google.registry.model.host.HostResource} this is based off of. */
public VKey<HostResource> getHostRepoId() {
return hostRepoId;
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
public static class Builder extends HistoryEntry.Builder<HostHistory, Builder> {
public Builder() {}
public Builder(HostHistory instance) {
super(instance);
}
public Builder setHostBase(HostBase hostBase) {
getInstance().hostBase = hostBase;
return this;
}
public Builder setHostResourceId(VKey<HostResource> hostRepoId) {
getInstance().hostRepoId = hostRepoId;
hostRepoId.maybeGetOfyKey().ifPresent(parent -> getInstance().parent = parent);
return this;
}
// We can remove this once all HistoryEntries are converted to History objects
@Override
public Builder setParent(Key<? extends EppResource> parent) {
super.setParent(parent);
getInstance().hostRepoId = VKey.createOfy(HostResource.class, (Key<HostResource>) parent);
return this;
}
}
}

View File

@@ -14,113 +14,36 @@
package google.registry.model.host;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.union;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.condition.IfNull;
import google.registry.model.EppResource;
import google.registry.model.EppResource.ForeignKeyedEppResource;
import google.registry.model.annotations.ExternalMessagingName;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.domain.DomainBase;
import google.registry.model.transfer.TransferData;
import google.registry.persistence.VKey;
import google.registry.persistence.WithStringVKey;
import google.registry.schema.replay.DatastoreAndSqlEntity;
import java.net.InetAddress;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import javax.persistence.ElementCollection;
import org.joda.time.DateTime;
import javax.persistence.Access;
import javax.persistence.AccessType;
/**
* A persistable Host resource including mutable and non-mutable fields.
*
* <p>A host's {@link TransferData} is stored on the superordinate domain. Non-subordinate hosts
* don't carry a full set of TransferData; all they have is lastTransferTime.
*
* @see <a href="https://tools.ietf.org/html/rfc5732">RFC 5732</a>
* <p>The {@link javax.persistence.Id} of the HostResource is the repoId.
*/
@ReportedOn
@Entity
@javax.persistence.Entity
@ExternalMessagingName("host")
@WithStringVKey
public class HostResource extends EppResource
@Access(AccessType.FIELD) // otherwise it'll use the default if the repoId (property)
public class HostResource extends HostBase
implements DatastoreAndSqlEntity, ForeignKeyedEppResource {
/**
* Fully qualified hostname, which is a unique identifier for this host.
*
* <p>This is only unique in the sense that for any given lifetime specified as the time range
* from (creationTime, deletionTime) there can only be one host in Datastore with this name.
* However, there can be many hosts with the same name and non-overlapping lifetimes.
*/
@Index
String fullyQualifiedHostName;
/** IP Addresses for this host. Can be null if this is an external host. */
@Index @ElementCollection Set<InetAddress> inetAddresses;
/** The superordinate domain of this host, or null if this is an external host. */
@Index
@IgnoreSave(IfNull.class)
@DoNotHydrate
Key<DomainBase> superordinateDomain;
/**
* The time that this resource was last transferred.
*
* <p>Can be null if the resource has never been transferred.
*/
DateTime lastTransferTime;
/**
* The most recent time that the {@link #superordinateDomain} field was changed.
*
* <p>This should be updated whenever the superordinate domain changes, including when it is set
* to null. This field will be null for new hosts that have never experienced a change of
* superordinate domain.
*/
DateTime lastSuperordinateChange;
public String getFullyQualifiedHostName() {
return fullyQualifiedHostName;
}
public Key<DomainBase> getSuperordinateDomain() {
return superordinateDomain;
}
public boolean isSubordinate() {
return superordinateDomain != null;
}
public ImmutableSet<InetAddress> getInetAddresses() {
return nullToEmptyImmutableCopy(inetAddresses);
}
public DateTime getLastTransferTime() {
return lastTransferTime;
}
public DateTime getLastSuperordinateChange() {
return lastSuperordinateChange;
}
@Override
public String getForeignKey() {
return fullyQualifiedHostName;
@javax.persistence.Id
@Access(AccessType.PROPERTY) // to tell it to use the non-default property-as-ID
public String getRepoId() {
return super.getRepoId();
}
@Override
@@ -129,92 +52,17 @@ public class HostResource extends EppResource
return VKey.createOfy(HostResource.class, Key.create(this));
}
@Deprecated
@Override
public HostResource cloneProjectedAtTime(DateTime now) {
return this;
}
/**
* Compute the correct last transfer time for this host given its loaded superordinate domain.
*
* <p>Hosts can move between superordinate domains, so to know which lastTransferTime is correct
* we need to know if the host was attached to this superordinate the last time that the
* superordinate was transferred. If the last superordinate change was before this time, then the
* host was attached to this superordinate domain during that transfer.
*
* <p>If the host is not subordinate the domain can be null and we just return last transfer time.
*
* @param superordinateDomain the loaded superordinate domain, which must match the key in
* the {@link #superordinateDomain} field. Passing it as a parameter allows the caller to
* control the degree of consistency used to load it.
*/
public DateTime computeLastTransferTime(@Nullable DomainBase superordinateDomain) {
if (!isSubordinate()) {
checkArgument(superordinateDomain == null);
return getLastTransferTime();
}
checkArgument(
superordinateDomain != null
&& Key.create(superordinateDomain).equals(getSuperordinateDomain()));
DateTime lastSuperordinateChange =
Optional.ofNullable(getLastSuperordinateChange()).orElse(getCreationTime());
DateTime lastTransferOfCurrentSuperordinate =
Optional.ofNullable(superordinateDomain.getLastTransferTime()).orElse(START_OF_TIME);
return lastSuperordinateChange.isBefore(lastTransferOfCurrentSuperordinate)
? superordinateDomain.getLastTransferTime()
: getLastTransferTime();
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** A builder for constructing {@link HostResource}, since it is immutable. */
public static class Builder extends EppResource.Builder<HostResource, Builder> {
public static class Builder extends HostBase.Builder<HostResource, Builder> {
public Builder() {}
private Builder(HostResource instance) {
super(instance);
}
public Builder setFullyQualifiedHostName(String fullyQualifiedHostName) {
checkArgument(
fullyQualifiedHostName.equals(canonicalizeDomainName(fullyQualifiedHostName)),
"Host name must be in puny-coded, lower-case form");
getInstance().fullyQualifiedHostName = fullyQualifiedHostName;
return this;
}
public Builder setInetAddresses(ImmutableSet<InetAddress> inetAddresses) {
getInstance().inetAddresses = inetAddresses;
return this;
}
public Builder setLastSuperordinateChange(DateTime lastSuperordinateChange) {
getInstance().lastSuperordinateChange = lastSuperordinateChange;
return this;
}
public Builder addInetAddresses(ImmutableSet<InetAddress> inetAddresses) {
return setInetAddresses(ImmutableSet.copyOf(
union(getInstance().getInetAddresses(), inetAddresses)));
}
public Builder removeInetAddresses(ImmutableSet<InetAddress> inetAddresses) {
return setInetAddresses(ImmutableSet.copyOf(
difference(getInstance().getInetAddresses(), inetAddresses)));
}
public Builder setSuperordinateDomain(Key<DomainBase> superordinateDomain) {
getInstance().superordinateDomain = superordinateDomain;
return this;
}
public Builder setLastTransferTime(DateTime lastTransferTime) {
getInstance().lastTransferTime = lastTransferTime;
return this;
}
}
}

View File

@@ -44,10 +44,9 @@ import org.joda.time.DateTime;
/**
* Root for a random commit log bucket.
*
* <p>This is used to shard {@link CommitLogManifest} objects into
* {@link RegistryConfig#getCommitLogBucketCount() N} entity groups. This increases
* transaction throughput, while maintaining the ability to perform strongly-consistent ancestor
* queries.
* <p>This is used to shard {@link CommitLogManifest} objects into {@link
* RegistryConfig#getCommitLogBucketCount() N} entity groups. This increases transaction throughput,
* while maintaining the ability to perform strongly-consistent ancestor queries.
*
* @see <a href="https://cloud.google.com/appengine/articles/scaling/contention">Avoiding Datastore
* contention</a>

View File

@@ -38,11 +38,11 @@ import org.joda.time.DateTime;
* Entity representing a point-in-time consistent view of Datastore, based on commit logs.
*
* <p>Conceptually, this entity consists of two pieces of information: the checkpoint "wall" time
* and a set of bucket checkpoint times. The former is the ID for this checkpoint (constrained
* to be unique upon checkpoint creation) and also represents the approximate wall time of the
* consistent Datastore view this checkpoint represents. The latter is really a mapping from
* bucket ID to timestamp, where the timestamp dictates the upper bound (inclusive) on commit logs
* from that bucket to include when restoring Datastore to this checkpoint.
* and a set of bucket checkpoint times. The former is the ID for this checkpoint (constrained to be
* unique upon checkpoint creation) and also represents the approximate wall time of the consistent
* Datastore view this checkpoint represents. The latter is really a mapping from bucket ID to
* timestamp, where the timestamp dictates the upper bound (inclusive) on commit logs from that
* bucket to include when restoring Datastore to this checkpoint.
*/
@Entity
@NotBackedUp(reason = Reason.COMMIT_LOGS)

View File

@@ -28,9 +28,7 @@ import google.registry.schema.replay.DatastoreEntity;
import google.registry.schema.replay.SqlEntity;
import org.joda.time.DateTime;
/**
* Singleton parent entity for all commit log checkpoints.
*/
/** Singleton parent entity for all commit log checkpoints. */
@Entity
@NotBackedUp(reason = Reason.COMMIT_LOGS)
public class CommitLogCheckpointRoot extends ImmutableObject implements DatastoreEntity {

View File

@@ -23,6 +23,7 @@ import static google.registry.util.CollectionUtils.union;
import com.google.appengine.api.datastore.DatastoreFailureException;
import com.google.appengine.api.datastore.DatastoreTimeoutException;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.taskqueue.TransientFailureException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
@@ -365,6 +366,16 @@ public class Ofy {
return Key.create(info.bucketKey, CommitLogManifest.class, info.transactionTime.getMillis());
}
/** Convert an entity POJO to a datastore Entity. */
public Entity toEntity(Object pojo) {
return ofy().save().toEntity(pojo);
}
/** Convert a datastore entity to a POJO. */
public Object toPojo(Entity entity) {
return ofy().load().fromEntity(entity);
}
/**
* Returns the @Entity-annotated base class for an object that is either an {@code Key<?>} or an
* object of an entity class registered with Objectify.

View File

@@ -19,6 +19,7 @@ import com.googlecode.objectify.annotation.Embed;
import google.registry.model.ImmutableObject;
import google.registry.model.eppcommon.Trid;
import google.registry.model.eppoutput.EppResponse.ResponseData;
import javax.persistence.Embeddable;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@@ -29,11 +30,12 @@ import org.joda.time.DateTime;
/** The {@link ResponseData} returned when completing a pending action on a domain. */
@XmlTransient
public abstract class PendingActionNotificationResponse
extends ImmutableObject implements ResponseData {
@Embeddable
public class PendingActionNotificationResponse extends ImmutableObject implements ResponseData {
/** The inner name type that contains a name and the result boolean. */
@Embed
@Embeddable
static class NameOrId extends ImmutableObject {
@XmlValue
String value;

View File

@@ -25,6 +25,7 @@ import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.EntitySubclass;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.Buildable;
@@ -39,10 +40,24 @@ import google.registry.model.poll.PendingActionNotificationResponse.DomainPendin
import google.registry.model.poll.PendingActionNotificationResponse.HostPendingActionNotificationResponse;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.transfer.TransferData.TransferServerApproveEntity;
import google.registry.model.transfer.TransferResponse;
import google.registry.model.transfer.TransferResponse.ContactTransferResponse;
import google.registry.model.transfer.TransferResponse.DomainTransferResponse;
import google.registry.persistence.VKey;
import google.registry.persistence.WithLongVKey;
import java.util.List;
import java.util.Optional;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Embedded;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Transient;
import org.joda.time.DateTime;
/**
@@ -68,28 +83,52 @@ import org.joda.time.DateTime;
@Entity
@ReportedOn
@ExternalMessagingName("message")
@javax.persistence.Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type")
@javax.persistence.Table(
indexes = {
@javax.persistence.Index(columnList = "registrar_id"),
@javax.persistence.Index(columnList = "eventTime")
})
public abstract class PollMessage extends ImmutableObject
implements Buildable, TransferServerApproveEntity {
/** Entity id. */
@Id
long id;
@javax.persistence.Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "poll_message_id")
Long id;
@Parent
@DoNotHydrate
Key<HistoryEntry> parent;
@Parent @DoNotHydrate @Transient Key<HistoryEntry> parent;
/** The registrar that this poll message will be delivered to. */
@Index
@Column(name = "registrar_id", nullable = false)
String clientId;
/** The time when the poll message should be delivered. May be in the future. */
@Index
@Column(nullable = false)
DateTime eventTime;
/** Human readable message that will be returned with this poll message. */
@Column(name = "message")
String msg;
@Ignore String domainRepoId;
@Ignore String contactRepoId;
@Ignore String hostRepoId;
@Ignore Long domainRevisionId;
@Ignore Long contactRevisionId;
@Ignore Long hostRevisionId;
public Key<HistoryEntry> getParentKey() {
return parent;
}
@@ -112,6 +151,9 @@ public abstract class PollMessage extends ImmutableObject
public abstract ImmutableList<ResponseData> getResponseData();
@Override
public abstract VKey<? extends PollMessage> createVKey();
/** Override Buildable.asBuilder() to give this method stronger typing. */
@Override
public abstract Builder<?, ?> asBuilder();
@@ -180,15 +222,83 @@ public abstract class PollMessage extends ImmutableObject
* <p>One-time poll messages are deleted from Datastore once they have been delivered and ACKed.
*/
@EntitySubclass(index = false)
@javax.persistence.Entity
@DiscriminatorValue("ONE_TIME")
@WithLongVKey
public static class OneTime extends PollMessage {
// Response data. Objectify cannot persist a base class type, so we must have a separate field
// to hold every possible derived type of ResponseData that we might store.
@Transient
List<ContactPendingActionNotificationResponse> contactPendingActionNotificationResponses;
List<ContactTransferResponse> contactTransferResponses;
@Transient List<ContactTransferResponse> contactTransferResponses;
@Transient
List<DomainPendingActionNotificationResponse> domainPendingActionNotificationResponses;
List<DomainTransferResponse> domainTransferResponses;
List<HostPendingActionNotificationResponse> hostPendingActionNotificationResponses;
@Transient List<DomainTransferResponse> domainTransferResponses;
@Transient List<HostPendingActionNotificationResponse> hostPendingActionNotificationResponses;
@Ignore
@Embedded
@AttributeOverrides({
@AttributeOverride(
name = "nameOrId.value",
column = @Column(name = "pending_action_response_name_or_id")),
@AttributeOverride(
name = "nameOrId.actionResult",
column = @Column(name = "pending_action_response_action_result")),
@AttributeOverride(
name = "trid.serverTransactionId",
column = @Column(name = "pending_action_response_server_txn_id")),
@AttributeOverride(
name = "trid.clientTransactionId",
column = @Column(name = "pending_action_response_client_txn_id")),
@AttributeOverride(
name = "processedDate",
column = @Column(name = "pending_action_response_processed_date"))
})
PendingActionNotificationResponse pendingActionNotificationResponse;
@Ignore
@Embedded
@AttributeOverrides({
@AttributeOverride(
name = "transferStatus",
column = @Column(name = "transfer_response_transfer_status")),
@AttributeOverride(
name = "gainingClientId",
column = @Column(name = "transfer_response_gaining_registrar_id")),
@AttributeOverride(
name = "transferRequestTime",
column = @Column(name = "transfer_response_transfer_request_time")),
@AttributeOverride(
name = "losingClientId",
column = @Column(name = "transfer_response_losing_registrar_id")),
@AttributeOverride(
name = "pendingTransferExpirationTime",
column = @Column(name = "transfer_response_pending_transfer_expiration_time"))
})
TransferResponse transferResponse;
@Ignore
@Column(name = "transfer_response_domain_name")
String fullyQualifiedDomainName;
@Ignore
@Column(name = "transfer_response_domain_expiration_time")
DateTime extendedRegistrationExpirationTime;
@Ignore
@Column(name = "transfer_response_contact_id")
String contactId;
@Override
public VKey<OneTime> createVKey() {
return VKey.createOfy(this.getClass(), Key.create(this));
}
@Override
public Builder asBuilder() {
@@ -265,9 +375,13 @@ public abstract class PollMessage extends ImmutableObject
* happens.
*/
@EntitySubclass(index = false)
@javax.persistence.Entity
@DiscriminatorValue("AUTORENEW")
@WithLongVKey
public static class Autorenew extends PollMessage {
/** The target id of the autorenew event. */
@Column(name = "autorenew_domain_name")
String targetId;
/** The autorenew recurs annually between {@link #eventTime} and this time. */
@@ -282,6 +396,11 @@ public abstract class PollMessage extends ImmutableObject
return autorenewEndTime;
}
@Override
public VKey<Autorenew> createVKey() {
return VKey.createOfy(this.getClass(), Key.create(this));
}
@Override
public ImmutableList<ResponseData> getResponseData() {
// Note that the event time is when the auto-renew occured, so the expiration time in the

View File

@@ -236,7 +236,7 @@ public class Registrar extends ImmutableObject
*/
@Id
@javax.persistence.Id
@Column(name = "client_id", nullable = false)
@Column(name = "registrarId", nullable = false)
String clientIdentifier;
/**
@@ -296,7 +296,9 @@ public class Registrar extends ImmutableObject
/** Base64 encoded SHA256 hash of {@link #failoverClientCertificate}. */
String failoverClientCertificateHash;
/** A whitelist of netmasks (in CIDR notation) which the client is allowed to connect from. */
/** An allow list of netmasks (in CIDR notation) which the client is allowed to connect from. */
// TODO: Rename to ipAddressAllowList once Cloud SQL migration is complete.
@Column(name = "ip_address_allow_list")
List<CidrAddressBlock> ipAddressWhitelist;
/** A hashed password for EPP access. The hash is a base64 encoded SHA256 string. */
@@ -553,7 +555,7 @@ public class Registrar extends ImmutableObject
return failoverClientCertificateHash;
}
public ImmutableList<CidrAddressBlock> getIpAddressWhitelist() {
public ImmutableList<CidrAddressBlock> getIpAddressAllowList() {
return nullToEmptyImmutableCopy(ipAddressWhitelist);
}
@@ -674,7 +676,7 @@ public class Registrar extends ImmutableObject
.put("phoneNumber", phoneNumber)
.put("phonePasscode", phonePasscode)
.putListOfStrings("allowedTlds", getAllowedTlds())
.putListOfStrings("ipAddressWhitelist", ipAddressWhitelist)
.putListOfStrings("ipAddressAllowList", getIpAddressAllowList())
.putListOfJsonObjects("contacts", getContacts())
.put("registryLockAllowed", registryLockAllowed)
.build();
@@ -853,8 +855,8 @@ public class Registrar extends ImmutableObject
return this;
}
public Builder setIpAddressWhitelist(Iterable<CidrAddressBlock> ipAddressWhitelist) {
getInstance().ipAddressWhitelist = ImmutableList.copyOf(ipAddressWhitelist);
public Builder setIpAddressAllowList(Iterable<CidrAddressBlock> ipAddressAllowList) {
getInstance().ipAddressWhitelist = ImmutableList.copyOf(ipAddressAllowList);
return this;
}

View File

@@ -431,10 +431,10 @@ public class Registry extends ImmutableObject implements Buildable {
/** The end of the claims period (at or after this time, claims no longer applies). */
DateTime claimsPeriodEnd = END_OF_TIME;
/** A whitelist of clients allowed to be used on domains on this TLD (ignored if empty). */
/** An allow list of clients allowed to be used on domains on this TLD (ignored if empty). */
Set<String> allowedRegistrantContactIds;
/** A whitelist of hosts allowed to be used on domains on this TLD (ignored if empty). */
/** An allow list of hosts allowed to be used on domains on this TLD (ignored if empty). */
Set<String> allowedFullyQualifiedHostNames;
public String getTldStr() {
@@ -579,6 +579,8 @@ public class Registry extends ImmutableObject implements Buildable {
return Fee.create(
eapFeeSchedule.getValueAtTime(now).getAmount(),
FeeType.EAP,
// An EAP fee counts as premium so the domain's overall Fee doesn't show as standard-priced.
true,
validPeriod,
validPeriod.upperEndpoint());
}

View File

@@ -248,7 +248,7 @@ public final class PremiumList extends BaseDomainLabelList<Money, PremiumList.Pr
}
/**
* A premium list entry entity, persisted to Datastore. Each instance represents the price of a
* A premium list entry entity, persisted to Datastore. Each instance represents the price of a
* single label on a given TLD.
*/
@ReportedOn

View File

@@ -62,8 +62,8 @@ import org.joda.time.DateTime;
*/
@Entity
public final class ReservedList
extends BaseDomainLabelList<ReservationType, ReservedList.ReservedListEntry> implements
DatastoreEntity {
extends BaseDomainLabelList<ReservationType, ReservedList.ReservedListEntry>
implements DatastoreEntity {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();

View File

@@ -32,11 +32,22 @@ import google.registry.model.domain.Period;
import google.registry.model.eppcommon.Trid;
import java.util.Set;
import javax.annotation.Nullable;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.MappedSuperclass;
import javax.persistence.SequenceGenerator;
import javax.persistence.Transient;
import org.joda.time.DateTime;
/** A record of an EPP command that mutated a resource. */
@ReportedOn
@Entity
@MappedSuperclass
public class HistoryEntry extends ImmutableObject implements Buildable {
/** Represents the type of history entry. */
@@ -60,8 +71,8 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
@Deprecated
DOMAIN_ALLOCATE,
/**
* Used for domain registration autorenews explicitly logged by
* {@link google.registry.batch.ExpandRecurringBillingEventsAction}.
* Used for domain registration autorenews explicitly logged by {@link
* google.registry.batch.ExpandRecurringBillingEventsAction}.
*/
DOMAIN_AUTORENEW,
DOMAIN_CREATE,
@@ -89,14 +100,22 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
}
/** The autogenerated id of this event. */
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "HistorySequenceGenerator")
@SequenceGenerator(
name = "HistorySequenceGenerator",
sequenceName = "history_id_sequence",
allocationSize = 1)
@Id
long id;
@javax.persistence.Id
@Column(name = "historyRevisionId")
Long id;
/** The resource this event mutated. */
@Parent
Key<? extends EppResource> parent;
@Parent @Transient protected Key<? extends EppResource> parent;
/** The type of history entry. */
@Column(nullable = false, name = "historyType")
@Enumerated(EnumType.STRING)
Type type;
/**
@@ -104,17 +123,21 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
* be null for all other types.
*/
@IgnoreSave(IfNull.class)
@Transient // domain-specific
Period period;
/** The actual EPP xml of the command, stored as bytes to be agnostic of encoding. */
@Column(nullable = false, name = "historyXmlBytes")
byte[] xmlBytes;
/** The time the command occurred, represented by the ofy transaction time.*/
/** The time the command occurred, represented by the ofy transaction time. */
@Index
@Column(nullable = false, name = "historyModificationTime")
DateTime modificationTime;
/** The id of the registrar that sent the command. */
@Index
@Column(name = "historyRegistrarId")
String clientId;
/**
@@ -124,18 +147,31 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
* sending the EPP transfer command is the gaining party). For approves and rejects, the other
* registrar is the gaining party.
*/
@Transient // domain-specific
String otherClientId;
/** Transaction id that made this change, or null if the entry was not created by a flow. */
@Nullable Trid trid;
@Nullable
@AttributeOverrides({
@AttributeOverride(
name = "clientTransactionId",
column = @Column(name = "historyClientTransactionId")),
@AttributeOverride(
name = "serverTransactionId",
column = @Column(name = "historyServerTransactionId"))
})
Trid trid;
/** Whether this change was created by a superuser. */
@Column(nullable = false, name = "historyBySuperuser")
boolean bySuperuser;
/** Reason for the change. */
@Column(nullable = false, name = "historyReason")
String reason;
/** Whether this change was requested by a registrar. */
@Column(nullable = false, name = "historyRequestedByRegistrar")
Boolean requestedByRegistrar;
/**
@@ -145,6 +181,7 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
* also be empty if the HistoryEntry refers to an EPP mutation that does not affect domain
* transaction counts (such as contact or host mutations).
*/
@Transient // domain-specific
Set<DomainTransactionRecord> domainTransactionRecords;
public Key<? extends EppResource> getParent() {
@@ -176,7 +213,8 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
}
/** Returns the TRID, which may be null if the entry was not created by a normal flow. */
@Nullable public Trid getTrid() {
@Nullable
public Trid getTrid() {
return trid;
}
@@ -202,77 +240,84 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
}
/** A builder for {@link HistoryEntry} since it is immutable */
public static class Builder extends Buildable.Builder<HistoryEntry> {
public static class Builder<T extends HistoryEntry, B extends Builder<?, ?>>
extends GenericBuilder<T, B> {
public Builder() {}
public Builder(HistoryEntry instance) {
public Builder(T instance) {
super(instance);
}
public Builder setParent(EppResource parent) {
@Override
public T build() {
return super.build();
}
public B setParent(EppResource parent) {
getInstance().parent = Key.create(parent);
return this;
return thisCastToDerived();
}
public Builder setParent(Key<? extends EppResource> parentKey) {
getInstance().parent = parentKey;
return this;
// Until we move completely to SQL, override this in subclasses (e.g. HostHistory) to set VKeys
public B setParent(Key<? extends EppResource> parent) {
getInstance().parent = parent;
return thisCastToDerived();
}
public Builder setType(Type type) {
public B setType(Type type) {
getInstance().type = type;
return this;
return thisCastToDerived();
}
public Builder setPeriod(Period period) {
public B setPeriod(Period period) {
getInstance().period = period;
return this;
return thisCastToDerived();
}
public Builder setXmlBytes(byte[] xmlBytes) {
public B setXmlBytes(byte[] xmlBytes) {
getInstance().xmlBytes = xmlBytes;
return this;
return thisCastToDerived();
}
public Builder setModificationTime(DateTime modificationTime) {
public B setModificationTime(DateTime modificationTime) {
getInstance().modificationTime = modificationTime;
return this;
return thisCastToDerived();
}
public Builder setClientId(String clientId) {
public B setClientId(String clientId) {
getInstance().clientId = clientId;
return this;
return thisCastToDerived();
}
public Builder setOtherClientId(String otherClientId) {
public B setOtherClientId(String otherClientId) {
getInstance().otherClientId = otherClientId;
return this;
return thisCastToDerived();
}
public Builder setTrid(Trid trid) {
public B setTrid(Trid trid) {
getInstance().trid = trid;
return this;
return thisCastToDerived();
}
public Builder setBySuperuser(boolean bySuperuser) {
public B setBySuperuser(boolean bySuperuser) {
getInstance().bySuperuser = bySuperuser;
return this;
return thisCastToDerived();
}
public Builder setReason(String reason) {
public B setReason(String reason) {
getInstance().reason = reason;
return this;
return thisCastToDerived();
}
public Builder setRequestedByRegistrar(Boolean requestedByRegistrar) {
public B setRequestedByRegistrar(Boolean requestedByRegistrar) {
getInstance().requestedByRegistrar = requestedByRegistrar;
return this;
return thisCastToDerived();
}
public Builder setDomainTransactionRecords(
public B setDomainTransactionRecords(
ImmutableSet<DomainTransactionRecord> domainTransactionRecords) {
getInstance().domainTransactionRecords = domainTransactionRecords;
return this;
return thisCastToDerived();
}
}
}

View File

@@ -327,8 +327,8 @@ public class ClaimsListShard extends ImmutableObject implements DatastoreEntity
}
/**
* Serves as the coordinating claims list singleton linking to the {@link ClaimsListRevision}
* that is live.
* Serves as the coordinating claims list singleton linking to the {@link ClaimsListRevision} that
* is live.
*/
@Entity
@NotBackedUp(reason = Reason.EXTERNALLY_SOURCED)

View File

@@ -16,6 +16,9 @@ package google.registry.model.transfer;
import google.registry.model.Buildable.GenericBuilder;
import google.registry.model.ImmutableObject;
import javax.persistence.Column;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.MappedSuperclass;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
@@ -31,10 +34,12 @@ public abstract class BaseTransferObject extends ImmutableObject {
* will always be non-null.
*/
@XmlElement(name = "trStatus")
@Enumerated(EnumType.STRING)
TransferStatus transferStatus;
/** The gaining registrar of the current or last transfer. Can be null if never transferred. */
@XmlElement(name = "reID")
@Column(name = "transfer_gaining_registrar_id")
String gainingClientId;
/** The time that the last transfer was requested. Can be null if never transferred. */
@@ -43,6 +48,7 @@ public abstract class BaseTransferObject extends ImmutableObject {
/** The losing registrar of the current or last transfer. Can be null if never transferred. */
@XmlElement(name = "acID")
@Column(name = "transfer_losing_registrar_id")
String losingClientId;
/**
@@ -51,6 +57,7 @@ public abstract class BaseTransferObject extends ImmutableObject {
* this holds the time that the last pending transfer ended. Can be null if never transferred.
*/
@XmlElement(name = "acDate")
@Column(name = "transfer_pending_expiration_time")
DateTime pendingTransferExpirationTime;
public TransferStatus getTransferStatus() {
@@ -114,5 +121,10 @@ public abstract class BaseTransferObject extends ImmutableObject {
getInstance().pendingTransferExpirationTime = pendingTransferExpirationTime;
return thisCastToDerived();
}
@Override
public T build() {
return super.build();
}
}
}

View File

@@ -0,0 +1,48 @@
// Copyright 2020 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.transfer;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Unindex;
import javax.persistence.Embeddable;
/** Transfer data for contact. */
@Embed
@Unindex
@Embeddable
public class ContactTransferData extends TransferData<ContactTransferData.Builder> {
public static final ContactTransferData EMPTY = new ContactTransferData();
@Override
public boolean isEmpty() {
return EMPTY.equals(this);
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
public static class Builder
extends TransferData.Builder<ContactTransferData, ContactTransferData.Builder> {
/** Create a {@link ContactTransferData.Builder} wrapping a new instance. */
public Builder() {}
/** Create a {@link ContactTransferData.Builder} wrapping the given instance. */
private Builder(ContactTransferData instance) {
super(instance);
}
}
}

View File

@@ -0,0 +1,186 @@
// Copyright 2020 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.transfer;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.annotation.Unindex;
import com.googlecode.objectify.condition.IfNull;
import google.registry.model.billing.BillingEvent;
import google.registry.model.domain.Period;
import google.registry.model.domain.Period.Unit;
import google.registry.model.poll.PollMessage;
import google.registry.persistence.VKey;
import javax.annotation.Nullable;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import org.joda.time.DateTime;
/** Transfer data for domain. */
@Embed
@Unindex
@Embeddable
public class DomainTransferData extends TransferData<DomainTransferData.Builder> {
public static final DomainTransferData EMPTY = new DomainTransferData();
/**
* The period to extend the registration upon completion of the transfer.
*
* <p>By default, domain transfers are for one year. This can be changed to zero by using the
* superuser EPP extension.
*/
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "unit", column = @Column(name = "transfer_renew_period_unit")),
@AttributeOverride(name = "value", column = @Column(name = "transfer_renew_period_value"))
})
Period transferPeriod = Period.create(1, Unit.YEARS);
/**
* The registration expiration time resulting from the approval - speculative or actual - of the
* most recent transfer request, applicable for domains only.
*
* <p>For pending transfers, this is the expiration time that will take effect under a projected
* server approval. For approved transfers, this is the actual expiration time of the domain as of
* the moment of transfer completion. For rejected or cancelled transfers, this field will be
* reset to null.
*
* <p>Note that even when this field is set, it does not necessarily mean that the post-transfer
* domain has a new expiration time. Superuser transfers may not include a bundled 1 year renewal
* at all, or even when a renewal is bundled, for a transfer during the autorenew grace period the
* bundled renewal simply subsumes the recent autorenewal, resulting in the same expiration time.
*/
// TODO(b/36405140): backfill this field for existing domains to which it should apply.
@Column(name = "transfer_registration_expiration_time")
DateTime transferredRegistrationExpirationTime;
@Ignore
@Column(name = "transfer_billing_cancellation_id")
Long billingCancellationId;
/**
* The regular one-time billing event that will be charged for a server-approved transfer.
*
* <p>This field should be null if there is not currently a pending transfer or if the object
* being transferred is not a domain.
*
* <p>TODO(b/158230654) Remove unused columns for TransferData in Contact table.
*/
@IgnoreSave(IfNull.class)
@Column(name = "transfer_billing_event_id")
VKey<BillingEvent.OneTime> serverApproveBillingEvent;
/**
* The autorenew billing event that should be associated with this resource after the transfer.
*
* <p>This field should be null if there is not currently a pending transfer or if the object
* being transferred is not a domain.
*/
@IgnoreSave(IfNull.class)
@Column(name = "transfer_billing_recurrence_id")
VKey<BillingEvent.Recurring> serverApproveAutorenewEvent;
/**
* The autorenew poll message that should be associated with this resource after the transfer.
*
* <p>This field should be null if there is not currently a pending transfer or if the object
* being transferred is not a domain.
*/
@IgnoreSave(IfNull.class)
@Column(name = "transfer_autorenew_poll_message_id")
VKey<PollMessage.Autorenew> serverApproveAutorenewPollMessage;
@Override
public Builder copyConstantFieldsToBuilder() {
return super.copyConstantFieldsToBuilder().setTransferPeriod(this.transferPeriod);
}
public Period getTransferPeriod() {
return transferPeriod;
}
@Nullable
public DateTime getTransferredRegistrationExpirationTime() {
return transferredRegistrationExpirationTime;
}
@Nullable
public VKey<BillingEvent.OneTime> getServerApproveBillingEvent() {
return serverApproveBillingEvent;
}
@Nullable
public VKey<BillingEvent.Recurring> getServerApproveAutorenewEvent() {
return serverApproveAutorenewEvent;
}
@Nullable
public VKey<PollMessage.Autorenew> getServerApproveAutorenewPollMessage() {
return serverApproveAutorenewPollMessage;
}
@Override
public boolean isEmpty() {
return EMPTY.equals(this);
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
public static class Builder extends TransferData.Builder<DomainTransferData, Builder> {
/** Create a {@link DomainTransferData.Builder} wrapping a new instance. */
public Builder() {}
/** Create a {@link Builder} wrapping the given instance. */
private Builder(DomainTransferData instance) {
super(instance);
}
public Builder setTransferPeriod(Period transferPeriod) {
getInstance().transferPeriod = transferPeriod;
return this;
}
public Builder setTransferredRegistrationExpirationTime(
DateTime transferredRegistrationExpirationTime) {
getInstance().transferredRegistrationExpirationTime = transferredRegistrationExpirationTime;
return this;
}
public Builder setServerApproveBillingEvent(
VKey<BillingEvent.OneTime> serverApproveBillingEvent) {
getInstance().serverApproveBillingEvent = serverApproveBillingEvent;
return this;
}
public Builder setServerApproveAutorenewEvent(
VKey<BillingEvent.Recurring> serverApproveAutorenewEvent) {
getInstance().serverApproveAutorenewEvent = serverApproveAutorenewEvent;
return this;
}
public Builder setServerApproveAutorenewPollMessage(
VKey<PollMessage.Autorenew> serverApproveAutorenewPollMessage) {
getInstance().serverApproveAutorenewPollMessage = serverApproveAutorenewPollMessage;
return this;
}
}
}

View File

@@ -17,62 +17,43 @@ package google.registry.model.transfer;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.annotation.Unindex;
import com.googlecode.objectify.condition.IfNull;
import google.registry.model.Buildable;
import google.registry.model.EppResource;
import google.registry.model.billing.BillingEvent;
import google.registry.model.domain.Period;
import google.registry.model.domain.Period.Unit;
import google.registry.model.eppcommon.Trid;
import google.registry.model.poll.PollMessage;
import google.registry.persistence.VKey;
import google.registry.util.TypeUtils.TypeInstantiator;
import java.util.Set;
import javax.annotation.Nullable;
import javax.persistence.ElementCollection;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embedded;
import org.joda.time.DateTime;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;
/**
* Common transfer data for {@link EppResource} types. Only applies to domains and contacts; hosts
* are implicitly transferred with their superordinate domain.
*/
@Embed
@Unindex
@javax.persistence.Embeddable
public class TransferData extends BaseTransferObject implements Buildable {
public static final TransferData EMPTY = new TransferData();
@MappedSuperclass
public abstract class TransferData<
B extends TransferData.Builder<? extends TransferData, ? extends TransferData.Builder>>
extends BaseTransferObject implements Buildable {
/** The transaction id of the most recent transfer request (or null if there never was one). */
@Embedded Trid transferRequestTrid;
/**
* The period to extend the registration upon completion of the transfer.
*
* <p>By default, domain transfers are for one year. This can be changed to zero by using the
* superuser EPP extension.
*/
@Embedded Period transferPeriod = Period.create(1, Unit.YEARS);
/**
* The registration expiration time resulting from the approval - speculative or actual - of the
* most recent transfer request, applicable for domains only.
*
* <p>For pending transfers, this is the expiration time that will take effect under a projected
* server approval. For approved transfers, this is the actual expiration time of the domain as
* of the moment of transfer completion. For rejected or cancelled transfers, this field will be
* reset to null.
*
* <p>Note that even when this field is set, it does not necessarily mean that the post-transfer
* domain has a new expiration time. Superuser transfers may not include a bundled 1 year renewal
* at all, or even when a renewal is bundled, for a transfer during the autorenew grace period the
* bundled renewal simply subsumes the recent autorenewal, resulting in the same expiration time.
*/
// TODO(b/36405140): backfill this field for existing domains to which it should apply.
DateTime transferredRegistrationExpirationTime;
@Embedded
@AttributeOverrides({
@AttributeOverride(
name = "serverTransactionId",
column = @Column(name = "transfer_server_txn_id")),
@AttributeOverride(
name = "clientTransactionId",
column = @Column(name = "transfer_client_txn_id"))
})
Trid transferRequestTrid;
/**
* The billing event and poll messages associated with a server-approved transfer.
@@ -83,80 +64,40 @@ public class TransferData extends BaseTransferObject implements Buildable {
* pending transfer is explicitly approved, rejected or cancelled, the referenced entities should
* be deleted.
*/
@Transient
@IgnoreSave(IfNull.class)
@ElementCollection
Set<Key<? extends TransferServerApproveEntity>> serverApproveEntities;
Set<VKey<? extends TransferServerApproveEntity>> serverApproveEntities;
/**
* The regular one-time billing event that will be charged for a server-approved transfer.
*
* <p>This field should be null if there is not currently a pending transfer or if the object
* being transferred is not a domain.
*/
@IgnoreSave(IfNull.class)
Key<BillingEvent.OneTime> serverApproveBillingEvent;
// The following 3 fields are the replacement for serverApproveEntities in Cloud SQL.
// TODO(shicong): Add getter/setter for these 3 fields and use them in the application code.
@Ignore
@Column(name = "transfer_gaining_poll_message_id")
Long gainingTransferPollMessageId;
/**
* The autorenew billing event that should be associated with this resource after the transfer.
*
* <p>This field should be null if there is not currently a pending transfer or if the object
* being transferred is not a domain.
*/
@IgnoreSave(IfNull.class)
Key<BillingEvent.Recurring> serverApproveAutorenewEvent;
@Ignore
@Column(name = "transfer_losing_poll_message_id")
Long losingTransferPollMessageId;
/**
* The autorenew poll message that should be associated with this resource after the transfer.
*
* <p>This field should be null if there is not currently a pending transfer or if the object
* being transferred is not a domain.
*/
@IgnoreSave(IfNull.class)
Key<PollMessage.Autorenew> serverApproveAutorenewPollMessage;
public abstract boolean isEmpty();
@Nullable
public Trid getTransferRequestTrid() {
return transferRequestTrid;
}
public Period getTransferPeriod() {
return transferPeriod;
}
@Nullable
public DateTime getTransferredRegistrationExpirationTime() {
return transferredRegistrationExpirationTime;
}
public ImmutableSet<Key<? extends TransferServerApproveEntity>> getServerApproveEntities() {
public ImmutableSet<VKey<? extends TransferServerApproveEntity>> getServerApproveEntities() {
return nullToEmptyImmutableCopy(serverApproveEntities);
}
@Nullable
public Key<BillingEvent.OneTime> getServerApproveBillingEvent() {
return serverApproveBillingEvent;
}
@Nullable
public Key<BillingEvent.Recurring> getServerApproveAutorenewEvent() {
return serverApproveAutorenewEvent;
}
@Nullable
public Key<PollMessage.Autorenew> getServerApproveAutorenewPollMessage() {
return serverApproveAutorenewPollMessage;
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
public abstract Builder asBuilder();
/**
* Returns a fresh Builder populated only with the constant fields of this TransferData, i.e.
* those that are fixed and unchanging throughout the transfer process.
*
* <p>These fields are:
*
* <ul>
* <li>transferRequestTrid
* <li>transferRequestTime
@@ -165,64 +106,43 @@ public class TransferData extends BaseTransferObject implements Buildable {
* <li>transferPeriod
* </ul>
*/
public Builder copyConstantFieldsToBuilder() {
return new Builder()
public B copyConstantFieldsToBuilder() {
B newBuilder = new TypeInstantiator<B>(getClass()) {}.instantiate();
newBuilder
// .setTransferPeriod(this.transferPeriod)
.setTransferRequestTrid(this.transferRequestTrid)
.setTransferRequestTime(this.transferRequestTime)
.setGainingClientId(this.gainingClientId)
.setLosingClientId(this.losingClientId)
.setTransferPeriod(this.transferPeriod);
.setLosingClientId(this.losingClientId);
return newBuilder;
}
/** Builder for {@link TransferData} because it is immutable. */
public static class Builder extends BaseTransferObject.Builder<TransferData, Builder> {
public abstract static class Builder<T extends TransferData, B extends Builder<T, B>>
extends BaseTransferObject.Builder<T, B> {
/** Create a {@link Builder} wrapping a new instance. */
public Builder() {}
/** Create a {@link Builder} wrapping the given instance. */
private Builder(TransferData instance) {
protected Builder(T instance) {
super(instance);
}
public Builder setTransferRequestTrid(Trid transferRequestTrid) {
public B setTransferRequestTrid(Trid transferRequestTrid) {
getInstance().transferRequestTrid = transferRequestTrid;
return this;
return thisCastToDerived();
}
public Builder setTransferPeriod(Period transferPeriod) {
getInstance().transferPeriod = transferPeriod;
return this;
}
public Builder setTransferredRegistrationExpirationTime(
DateTime transferredRegistrationExpirationTime) {
getInstance().transferredRegistrationExpirationTime = transferredRegistrationExpirationTime;
return this;
}
public Builder setServerApproveEntities(
ImmutableSet<Key<? extends TransferServerApproveEntity>> serverApproveEntities) {
public B setServerApproveEntities(
ImmutableSet<VKey<? extends TransferServerApproveEntity>> serverApproveEntities) {
getInstance().serverApproveEntities = serverApproveEntities;
return this;
return thisCastToDerived();
}
public Builder setServerApproveBillingEvent(
Key<BillingEvent.OneTime> serverApproveBillingEvent) {
getInstance().serverApproveBillingEvent = serverApproveBillingEvent;
return this;
}
public Builder setServerApproveAutorenewEvent(
Key<BillingEvent.Recurring> serverApproveAutorenewEvent) {
getInstance().serverApproveAutorenewEvent = serverApproveAutorenewEvent;
return this;
}
public Builder setServerApproveAutorenewPollMessage(
Key<PollMessage.Autorenew> serverApproveAutorenewPollMessage) {
getInstance().serverApproveAutorenewPollMessage = serverApproveAutorenewPollMessage;
return this;
@Override
public T build() {
return super.build();
}
}
@@ -230,5 +150,7 @@ public class TransferData extends BaseTransferObject implements Buildable {
* Marker interface for objects that are written in anticipation of a server approval, and
* therefore need to be deleted under any other outcome.
*/
public interface TransferServerApproveEntity {}
public interface TransferServerApproveEntity {
VKey<? extends TransferServerApproveEntity> createVKey();
}
}

View File

@@ -17,6 +17,7 @@ package google.registry.model.transfer;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.EppResource;
import google.registry.model.eppoutput.EppResponse.ResponseData;
import javax.persistence.Embeddable;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
@@ -28,7 +29,8 @@ import org.joda.time.DateTime;
* are common to all transfer responses; derived classes add resource specific fields.
*/
@XmlTransient
public abstract class TransferResponse extends BaseTransferObject implements ResponseData {
@Embeddable
public class TransferResponse extends BaseTransferObject implements ResponseData {
/** An adapter to output the XML in response to a transfer command on a domain. */
@Embed

View File

@@ -15,6 +15,7 @@
package google.registry.model.translators;
import static com.google.common.base.Functions.identity;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static google.registry.model.EntityClasses.ALL_CLASSES;
@@ -22,6 +23,8 @@ import com.google.appengine.api.datastore.Key;
import com.google.common.collect.ImmutableMap;
import com.googlecode.objectify.annotation.EntitySubclass;
import google.registry.persistence.VKey;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Translator factory for VKey.
@@ -45,17 +48,46 @@ public class VKeyTranslatorFactory extends AbstractSimpleTranslatorFactory<VKey,
super(VKey.class);
}
/** Create a VKey from a raw datastore key. */
public static VKey<?> createVKey(Key datastoreKey) {
return createVKey(com.googlecode.objectify.Key.create(datastoreKey));
}
/** Create a VKey from an objectify Key. */
public static <T> VKey<T> createVKey(com.googlecode.objectify.Key<T> key) {
if (key == null) {
return null;
}
// Try to create the VKey from its reference type.
Class clazz = CLASS_REGISTRY.get(key.getKind());
checkArgument(clazz != null, "Unknown Key type: %s", key.getKind());
try {
Method createVKeyMethod =
clazz.getDeclaredMethod("createVKey", com.googlecode.objectify.Key.class);
return (VKey<T>) createVKeyMethod.invoke(null, new Object[] {key});
} catch (NoSuchMethodException e) {
// Revert to an ofy vkey for now. TODO(mmuller): remove this when all classes with VKeys have
// converters.
return VKey.createOfy(clazz, key);
} catch (IllegalAccessException | InvocationTargetException e) {
// If we have a createVKey(Key) method with incorrect permissions or that is non-static, this
// is probably an error so let's reported.
throw new RuntimeException(e);
}
}
/** Create a VKey from a URL-safe string representation. */
public static VKey<?> createVKey(String urlSafe) {
return createVKey(com.googlecode.objectify.Key.create(urlSafe));
}
@Override
public SimpleTranslator<VKey, Key> createTranslator() {
return new SimpleTranslator<VKey, Key>() {
@Override
public VKey loadValue(Key datastoreValue) {
// TODO(mmuller): we need to call a method on refClass to also reconstitute the SQL key.
return datastoreValue == null
? null
: VKey.createOfy(
CLASS_REGISTRY.get(datastoreValue.getKind()),
com.googlecode.objectify.Key.create(datastoreValue));
return createVKey(datastoreValue);
}
@Override

View File

@@ -17,7 +17,9 @@ package google.registry.persistence;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.googlecode.objectify.Key;
import google.registry.model.ImmutableObject;
import java.io.Serializable;
import java.util.Optional;
/**
@@ -26,7 +28,9 @@ import java.util.Optional;
* <p>A VKey instance must contain both the JPA primary key for the referenced entity class and the
* objectify key for the object.
*/
public class VKey<T> extends ImmutableObject {
public class VKey<T> extends ImmutableObject implements Serializable {
private static final long serialVersionUID = -5291472863840231240L;
// The primary key for the referenced entity.
private final Object primaryKey;
@@ -57,6 +61,23 @@ public class VKey<T> extends ImmutableObject {
return new VKey(kind, ofyKey, null);
}
/**
* Creates a {@link VKey} which only contains the ofy primary key by specifying the id of the
* {@link Key}.
*/
public static <T> VKey<T> createOfy(Class<? extends T> kind, long id) {
return createOfy(kind, Key.create(kind, id));
}
/**
* Creates a {@link VKey} which only contains the ofy primary key by specifying the name of the
* {@link Key}.
*/
public static <T> VKey<T> createOfy(Class<? extends T> kind, String name) {
checkArgumentNotNull(kind, "name must not be null");
return createOfy(kind, Key.create(kind, name));
}
/** Creates a {@link VKey} which only contains both sql and ofy primary key. */
public static <T> VKey<T> create(
Class<? extends T> kind, Object sqlKey, com.googlecode.objectify.Key ofyKey) {

View File

@@ -0,0 +1,33 @@
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.persistence.converter;
import com.google.common.net.InetAddresses;
import java.net.InetAddress;
import javax.persistence.Converter;
@Converter(autoApply = true)
public class InetAddressSetConverter extends StringSetConverterBase<InetAddress> {
@Override
String toString(InetAddress element) {
return InetAddresses.toAddrString(element);
}
@Override
InetAddress fromString(String value) {
return InetAddresses.forString(value);
}
}

View File

@@ -0,0 +1,37 @@
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.persistence.converter;
import google.registry.model.transfer.TransferData.TransferServerApproveEntity;
import google.registry.persistence.VKey;
import java.util.Set;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
/** {@link AttributeConverter} for {@link Set}. */
@Converter(autoApply = true)
public class TransferServerApproveEntitySetConverter
extends StringSetConverterBase<VKey<? extends TransferServerApproveEntity>> {
@Override
String toString(VKey<? extends TransferServerApproveEntity> element) {
return String.valueOf(element.getSqlKey());
}
@Override
VKey<? extends TransferServerApproveEntity> fromString(String value) {
return VKey.createSql(TransferServerApproveEntity.class, Long.parseLong(value));
}
}

View File

@@ -122,32 +122,35 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
@Override
public <T> T transactNew(Supplier<T> work) {
// TODO(shicong): Implements the functionality to start a new transaction.
throw new UnsupportedOperationException();
return transact(work);
}
@Override
public void transactNew(Runnable work) {
// TODO(shicong): Implements the functionality to start a new transaction.
throw new UnsupportedOperationException();
transact(work);
}
@Override
public <T> T transactNewReadOnly(Supplier<T> work) {
// TODO(shicong): Implements read only transaction.
throw new UnsupportedOperationException();
return transact(
() -> {
getEntityManager().createNativeQuery("SET TRANSACTION READ ONLY").executeUpdate();
return work.get();
});
}
@Override
public void transactNewReadOnly(Runnable work) {
// TODO(shicong): Implements read only transaction.
throw new UnsupportedOperationException();
transactNewReadOnly(
() -> {
work.run();
return null;
});
}
@Override
public <T> T doTransactionless(Supplier<T> work) {
// TODO(shicong): Implements doTransactionless.
throw new UnsupportedOperationException();
return transact(work);
}
@Override

View File

@@ -0,0 +1,254 @@
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.persistence.transaction;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityTranslator;
import com.google.common.collect.ImmutableList;
import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.ofy.ObjectifyService;
import google.registry.persistence.VKey;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* A SQL transaction that can be serialized and stored in its own table.
*
* <p>Transaction is used to store transactions committed to Cloud SQL in a Transaction table during
* the second phase of our migration, during which time we will be asynchronously replaying Cloud
* SQL transactions to datastore.
*
* <p>TODO(mmuller): Use these from {@link TransactionManager} to store the contents of an SQL
* transaction for asynchronous propagation to datastore. Implement a cron endpoint that reads them
* from the Transaction table and calls writeToDatastore().
*/
public class Transaction extends ImmutableObject implements Buildable {
// Version id for persisted objects. Use the creation date for the value, as it's reasonably
// unique and inherently informative.
private static final int VERSION_ID = 20200604;
private transient ImmutableList<Mutation> mutations;
/** Write the entire transaction to the datastore in a datastore transaction. */
public void writeToDatastore() {
tm().transact(
() -> {
for (Mutation mutation : mutations) {
mutation.writeToDatastore();
}
});
}
/** Serialize a transaction to a byte array. */
public byte[] serialize() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
// Write the transaction version id. This serves as both a version id and a "magic number" to
// protect us against trying to deserialize some random byte array.
out.writeInt(VERSION_ID);
// Write all of the mutations, preceded by their count.
out.writeInt(mutations.size());
for (Mutation mutation : mutations) {
mutation.serializeTo(out);
}
out.close();
return baos.toByteArray();
} catch (IOException e) {
throw new IllegalArgumentException();
}
}
static Transaction deserialize(byte[] serializedTransaction) throws IOException {
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(serializedTransaction));
// Verify that the data is what we expect.
int version = in.readInt();
checkArgument(
version == VERSION_ID, "Invalid version id. Expected %s but got %s", VERSION_ID, version);
Transaction.Builder builder = new Transaction.Builder();
int mutationCount = in.readInt();
for (int i = 0; i < mutationCount; ++i) {
try {
builder.add(Mutation.deserializeFrom(in));
} catch (EOFException e) {
throw new RuntimeException("Serialized transaction terminated prematurely", e);
}
}
if (in.read() != -1) {
throw new RuntimeException("Unread data at the end of a serialized transaction.");
}
return builder.build();
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
public static class Builder extends GenericBuilder<Transaction, Builder> {
ImmutableList.Builder listBuilder = new ImmutableList.Builder();
Builder() {}
protected Builder(Transaction instance) {
super(instance);
}
public Builder addUpdate(Object entity) {
checkNotNull(entity);
listBuilder.add(new Update(entity));
return thisCastToDerived();
}
public Builder addDelete(VKey<?> key) {
checkNotNull(key);
listBuilder.add(new Delete(key));
return thisCastToDerived();
}
/** Adds a mutation (mainly intended for serialization) */
Builder add(Mutation mutation) {
checkNotNull(mutation);
listBuilder.add(mutation);
return thisCastToDerived();
}
@Override
public Transaction build() {
getInstance().mutations = listBuilder.build();
return super.build();
}
}
/** Base class for database record mutations. */
public abstract static class Mutation {
enum Type {
UPDATE,
DELETE
};
/** Write the changes in the mutation to the datastore. */
public abstract void writeToDatastore();
/** Serialize the mutation to the output stream. */
public abstract void serializeTo(ObjectOutputStream out) throws IOException;
/** Deserialize a mutation from the input stream. */
public static Mutation deserializeFrom(ObjectInputStream in) throws IOException {
try {
Type type = (Type) in.readObject();
switch (type) {
case UPDATE:
return Update.deserializeFrom(in);
case DELETE:
return Delete.deserializeFrom(in);
default:
throw new IllegalArgumentException("Unknown enum value: " + type);
}
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(e);
}
}
}
/**
* Record update.
*
* <p>Note that we don't have to distinguish between add and update, since this is for replay into
* the datastore which makes no such distinction.
*
* <p>Update serializes its entity using Objectify serialization.
*/
public static class Update extends Mutation {
private Object entity;
Update(Object entity) {
this.entity = entity;
}
@Override
public void writeToDatastore() {
ofyTm().saveNewOrUpdate(entity);
}
@Override
public void serializeTo(ObjectOutputStream out) throws IOException {
out.writeObject(Type.UPDATE);
Entity realEntity = ObjectifyService.ofy().toEntity(entity);
EntityProto proto = EntityTranslator.convertToPb(realEntity);
out.write(VERSION_ID);
proto.writeDelimitedTo(out);
}
public static Update deserializeFrom(ObjectInputStream in) throws IOException {
EntityProto proto = new EntityProto();
proto.parseDelimitedFrom(in);
return new Update(ObjectifyService.ofy().toPojo(EntityTranslator.createFromPb(proto)));
}
}
/**
* Record deletion.
*
* <p>Delete serializes its VKey using Java native serialization.
*/
public static class Delete extends Mutation {
private final VKey<?> key;
Delete(VKey<?> key) {
this.key = key;
}
@Override
public void writeToDatastore() {
ofyTm().delete(key);
}
@Override
public void serializeTo(ObjectOutputStream out) throws IOException {
out.writeObject(Type.DELETE);
// Java object serialization works for this.
out.writeObject(key);
}
public static Delete deserializeFrom(ObjectInputStream in) throws IOException {
try {
return new Delete((VKey) in.readObject());
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(e);
}
}
}
}

View File

@@ -424,8 +424,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
// and fetch all domains, to make sure that we can return the first domains in alphabetical
// order.
ImmutableSortedSet.Builder<DomainBase> domainSetBuilder =
ImmutableSortedSet.orderedBy(
Comparator.comparing(DomainBase::getFullyQualifiedDomainName));
ImmutableSortedSet.orderedBy(Comparator.comparing(DomainBase::getDomainName));
int numHostKeysSearched = 0;
for (List<VKey<HostResource>> chunk : Iterables.partition(hostKeys, 30)) {
numHostKeysSearched += chunk.size();
@@ -444,8 +443,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
Stream<DomainBase> stream = Streams.stream(query).filter(domain -> isAuthorized(domain));
if (cursorString.isPresent()) {
stream =
stream.filter(
domain -> (domain.getFullyQualifiedDomainName().compareTo(cursorString.get()) > 0));
stream.filter(domain -> (domain.getDomainName().compareTo(cursorString.get()) > 0));
}
stream.forEach(domainSetBuilder::add);
}
@@ -495,7 +493,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
.setIncompletenessWarningType(incompletenessWarningType);
Optional<String> newCursor = Optional.empty();
for (DomainBase domain : Iterables.limit(domains, rdapResultSetMaxSize)) {
newCursor = Optional.of(domain.getFullyQualifiedDomainName());
newCursor = Optional.of(domain.getDomainName());
builder
.domainSearchResultsBuilder()
.add(rdapJsonFormatter.createRdapDomain(domain, outputDataType));

View File

@@ -221,7 +221,7 @@ public class RdapJsonFormatter {
/** Sets the ordering for hosts; just use the fully qualified host name. */
private static final Ordering<HostResource> HOST_RESOURCE_ORDERING =
Ordering.natural().onResultOf(HostResource::getFullyQualifiedHostName);
Ordering.natural().onResultOf(HostResource::getHostName);
/** Sets the ordering for designated contacts; order them in a fixed order by contact type. */
private static final Ordering<DesignatedContact> DESIGNATED_CONTACT_ORDERING =
@@ -266,12 +266,12 @@ public class RdapJsonFormatter {
*/
RdapDomain createRdapDomain(DomainBase domainBase, OutputDataType outputDataType) {
RdapDomain.Builder builder = RdapDomain.builder();
builder.linksBuilder().add(makeSelfLink("domain", domainBase.getFullyQualifiedDomainName()));
builder.linksBuilder().add(makeSelfLink("domain", domainBase.getDomainName()));
if (outputDataType != OutputDataType.FULL) {
builder.remarksBuilder().add(RdapIcannStandardInformation.SUMMARY_DATA_REMARK);
}
// RDAP Response Profile 15feb19 section 2.1 discusses the domain name.
builder.setLdhName(domainBase.getFullyQualifiedDomainName());
builder.setLdhName(domainBase.getDomainName());
// RDAP Response Profile 15feb19 section 2.2:
// The domain handle MUST be the ROID
builder.setHandle(domainBase.getRepoId());
@@ -313,9 +313,7 @@ public class RdapJsonFormatter {
// RDAP Technical Implementation Guide 3.2: must have link to the registrar's RDAP URL for this
// domain, with rel=related.
for (String registrarRdapBase : registrar.getRdapBaseUrls()) {
String href =
makeServerRelativeUrl(
registrarRdapBase, "domain", domainBase.getFullyQualifiedDomainName());
String href = makeServerRelativeUrl(registrarRdapBase, "domain", domainBase.getDomainName());
builder
.linksBuilder()
.add(
@@ -336,7 +334,7 @@ public class RdapJsonFormatter {
if (status.isEmpty()) {
logger.atWarning().log(
"Domain %s (ROID %s) doesn't have any status",
domainBase.getFullyQualifiedDomainName(), domainBase.getRepoId());
domainBase.getDomainName(), domainBase.getRepoId());
}
// RDAP Response Profile 2.6.3, must have a notice about statuses. That is in {@link
// RdapIcannStandardInformation#domainBoilerplateNotices}
@@ -409,15 +407,13 @@ public class RdapJsonFormatter {
*/
RdapNameserver createRdapNameserver(HostResource hostResource, OutputDataType outputDataType) {
RdapNameserver.Builder builder = RdapNameserver.builder();
builder
.linksBuilder()
.add(makeSelfLink("nameserver", hostResource.getFullyQualifiedHostName()));
builder.linksBuilder().add(makeSelfLink("nameserver", hostResource.getHostName()));
if (outputDataType != OutputDataType.FULL) {
builder.remarksBuilder().add(RdapIcannStandardInformation.SUMMARY_DATA_REMARK);
}
// We need the ldhName: RDAP Response Profile 2.9.1, 4.1
builder.setLdhName(hostResource.getFullyQualifiedHostName());
builder.setLdhName(hostResource.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(hostResource.getRepoId());
@@ -433,10 +429,7 @@ public class RdapJsonFormatter {
statuses.add(StatusValue.LINKED);
}
if (hostResource.isSubordinate()
&& ofy()
.load()
.key(hostResource.getSuperordinateDomain())
.now()
&& tm().load(hostResource.getSuperordinateDomain())
.cloneProjectedAtTime(getRequestTime())
.getStatusValues()
.contains(StatusValue.PENDING_TRANSFER)) {

View File

@@ -269,10 +269,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
Optional<String> newCursor = Optional.empty();
for (HostResource host : Iterables.limit(hosts, rdapResultSetMaxSize)) {
newCursor =
Optional.of(
(cursorType == CursorType.NAME)
? host.getFullyQualifiedHostName()
: host.getRepoId());
Optional.of((cursorType == CursorType.NAME) ? host.getHostName() : host.getRepoId());
builder
.nameserverSearchResultsBuilder()
.add(rdapJsonFormatter.createRdapNameserver(host, outputDataType));

View File

@@ -60,7 +60,7 @@ import javax.inject.Inject;
* <p>It is a "login/query/logout" system where you login using the ICANN Reporting credentials, get
* a cookie you then send to get the list and finally logout.
*
* <p>For clarity, this is how one would contact this endpoint "manually", from a whitelisted IP
* <p>For clarity, this is how one would contact this endpoint "manually", from an allow-listed IP
* server:
*
* <p>$ curl [base]/login -I --user [tld]_ry:[password]
@@ -101,7 +101,8 @@ public final class UpdateRegistrarRdapBaseUrlsAction implements Runnable {
HttpResponse response = request.execute();
Optional<HttpCookie> idCookie =
HttpCookie.parse(response.getHeaders().getFirstHeaderStringValue("Set-Cookie")).stream()
response.getHeaders().getHeaderStringValues("Set-Cookie").stream()
.flatMap(value -> HttpCookie.parse(value).stream())
.filter(cookie -> cookie.getName().equals(COOKIE_ID))
.findAny();
checkState(

View File

@@ -36,7 +36,6 @@ import google.registry.xjc.eppcom.XjcEppcomTrStatusType;
import google.registry.xjc.rdecontact.XjcRdeContact;
import google.registry.xjc.rdecontact.XjcRdeContactElement;
import google.registry.xjc.rdecontact.XjcRdeContactTransferDataType;
import java.util.Objects;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
@@ -102,7 +101,7 @@ final class ContactResourceToXjcConverter {
// required before an automated response action will be taken by
// the registry. For all other status types, the value identifies
// the date and time when the request was completed.
if (!Objects.equals(model.getTransferData(), TransferData.EMPTY)) {
if (!model.getTransferData().isEmpty()) {
bean.setTrnData(convertTransferData(model.getTransferData()));
}

View File

@@ -28,6 +28,7 @@ import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.rde.RdeMode;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferData;
import google.registry.persistence.VKey;
import google.registry.util.Idn;
@@ -61,7 +62,7 @@ final class DomainBaseToXjcConverter {
// o A <name> element that contains the fully qualified name of the
// domain name object.
bean.setName(model.getFullyQualifiedDomainName());
bean.setName(model.getDomainName());
// o A <roid> element that contains the repository object identifier
// assigned to the domain name object when it was created.
@@ -69,7 +70,7 @@ final class DomainBaseToXjcConverter {
// o An OPTIONAL <uName> element that contains the name of the domain
// name in Unicode character set. It MUST be provided if available.
bean.setUName(Idn.toUnicode(model.getFullyQualifiedDomainName()));
bean.setUName(Idn.toUnicode(model.getDomainName()));
// o An OPTIONAL <idnTableId> element that references the IDN Table
// used for the IDN. This corresponds to the "id" attribute of the
@@ -142,7 +143,7 @@ final class DomainBaseToXjcConverter {
// it is that with host attributes, you inline the nameserver data
// on each domain; with host objects, you normalize the nameserver
// data to a separate EPP object.
ImmutableSet<String> linkedNameserverHostNames = model.loadNameserverFullyQualifiedHostNames();
ImmutableSet<String> linkedNameserverHostNames = model.loadNameserverHostNames();
if (!linkedNameserverHostNames.isEmpty()) {
XjcDomainNsType nameservers = new XjcDomainNsType();
for (String hostName : linkedNameserverHostNames) {
@@ -153,7 +154,7 @@ final class DomainBaseToXjcConverter {
switch (mode) {
case FULL:
String domainName = model.getFullyQualifiedDomainName();
String domainName = model.getDomainName();
// o Zero or more OPTIONAL <rgpStatus> element to represent
// "pendingDelete" sub-statuses, including "redemptionPeriod",
@@ -234,7 +235,7 @@ final class DomainBaseToXjcConverter {
// * An OPTIONAL <exDate> element that contains the end of the
// domain name object's validity period (expiry date) if the
// transfer caused or causes a change in the validity period.
if (!model.getTransferData().equals(TransferData.EMPTY)) {
if (!model.getTransferData().isEmpty()) {
// Temporary check to make sure that there really was a transfer. A bug caused spurious
// empty transfer records to get generated for deleted domains.
// TODO(b/33289763): remove the hasGainingAndLosingRegistrars check in February 2017
@@ -258,7 +259,7 @@ final class DomainBaseToXjcConverter {
}
/** Converts {@link TransferData} to {@link XjcRdeDomainTransferDataType}. */
private static XjcRdeDomainTransferDataType convertTransferData(TransferData model) {
private static XjcRdeDomainTransferDataType convertTransferData(DomainTransferData model) {
XjcRdeDomainTransferDataType bean = new XjcRdeDomainTransferDataType();
bean.setTrStatus(
XjcEppcomTrStatusType.fromValue(model.getTransferStatus().getXmlName()));

View File

@@ -17,7 +17,6 @@ package google.registry.rde;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.net.InetAddresses;
import com.googlecode.objectify.Key;
import google.registry.model.domain.DomainBase;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.HostResource;
@@ -35,9 +34,8 @@ import org.joda.time.DateTime;
final class HostResourceToXjcConverter {
/** Converts a subordinate {@link HostResource} to {@link XjcRdeHostElement}. */
static XjcRdeHostElement convertSubordinate(
HostResource host, DomainBase superordinateDomain) {
checkArgument(Key.create(superordinateDomain).equals(host.getSuperordinateDomain()));
static XjcRdeHostElement convertSubordinate(HostResource host, DomainBase superordinateDomain) {
checkArgument(superordinateDomain.createVKey().equals(host.getSuperordinateDomain()));
return new XjcRdeHostElement(convertSubordinateHost(host, superordinateDomain));
}
@@ -70,7 +68,7 @@ final class HostResourceToXjcConverter {
private static XjcRdeHost convertHostCommon(
HostResource model, String clientId, DateTime lastTransferTime) {
XjcRdeHost bean = new XjcRdeHost();
bean.setName(model.getFullyQualifiedHostName());
bean.setName(model.getHostName());
bean.setRoid(model.getRepoId());
bean.setCrDate(model.getCreationTime());
bean.setUpDate(model.getLastEppUpdateTime());

View File

@@ -18,6 +18,7 @@ import static com.google.common.base.Strings.nullToEmpty;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.model.EppResourceUtils.loadAtPointInTime;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.appengine.tools.mapreduce.Mapper;
import com.google.auto.value.AutoValue;
@@ -186,13 +187,16 @@ public final class RdeStagingMapper extends Mapper<EppResource, PendingDeposit,
return result;
} else if (resource instanceof HostResource) {
HostResource host = (HostResource) resource;
result = Optional.of(host.isSubordinate()
? marshaller.marshalSubordinateHost(
host,
// Note that loadAtPointInTime() does cloneProjectedAtTime(watermark) for us.
loadAtPointInTime(
ofy().load().key(host.getSuperordinateDomain()).now(), watermark).now())
: marshaller.marshalExternalHost(host));
result =
Optional.of(
host.isSubordinate()
? marshaller.marshalSubordinateHost(
host,
// Note that loadAtPointInTime() does cloneProjectedAtTime(watermark) for
// us.
loadAtPointInTime(tm().load(host.getSuperordinateDomain()), watermark)
.now())
: marshaller.marshalExternalHost(host));
cache.put(WatermarkModePair.create(watermark, RdeMode.FULL), result);
cache.put(WatermarkModePair.create(watermark, RdeMode.THIN), result);
return result;

View File

@@ -266,15 +266,15 @@ public final class IcannReportingUploadAction implements Runnable {
private static final String ICANN_UPLOAD_PERMANENT_ERROR_MESSAGE =
"A report for that month already exists, the cut-off date already passed";
/** Don't retry when the IP address isn't whitelisted, as retries go through the same IP. */
private static final Pattern ICANN_UPLOAD_WHITELIST_ERROR =
/** Don't retry when the IP address isn't allow-listed, as retries go through the same IP. */
private static final Pattern ICANN_UPLOAD_ALLOW_LIST_ERROR =
Pattern.compile("Your IP address .+ is not allowed to connect");
/** Predicate to retry uploads on IOException, so long as they aren't non-retryable errors. */
private static boolean isUploadFailureRetryable(Throwable e) {
return (e instanceof IOException)
&& !e.getMessage().contains(ICANN_UPLOAD_PERMANENT_ERROR_MESSAGE)
&& !ICANN_UPLOAD_WHITELIST_ERROR.matcher(e.getMessage()).matches();
&& !ICANN_UPLOAD_ALLOW_LIST_ERROR.matcher(e.getMessage()).matches();
}
private void emailUploadResults(ImmutableMap<String, Boolean> reportSummary) {

View File

@@ -59,13 +59,11 @@ public enum Auth {
/**
* Allows anyone access, as long as they use OAuth to authenticate.
*
* Also allows access from App Engine task-queue. Note that OAuth client ID still needs to be
* whitelisted in the config file for OAuth-based authentication to succeed.
* <p>Also allows access from App Engine task-queue. Note that OAuth client ID still needs to be
* allow-listed in the config file for OAuth-based authentication to succeed.
*/
AUTH_PUBLIC_OR_INTERNAL(
ImmutableList.of(AuthMethod.INTERNAL, AuthMethod.API),
AuthLevel.APP,
UserPolicy.PUBLIC),
ImmutableList.of(AuthMethod.INTERNAL, AuthMethod.API), AuthLevel.APP, UserPolicy.PUBLIC),
/**
* Allows only admins or App Engine task-queue access.

View File

@@ -69,7 +69,7 @@ public final class LordnTaskUtils {
return Joiner.on(',')
.join(
domain.getRepoId(),
domain.getFullyQualifiedDomainName(),
domain.getDomainName(),
domain.getSmdId(),
getIanaIdentifier(domain.getCreationClientId()),
transactionTime); // Used as creation time.
@@ -80,7 +80,7 @@ public final class LordnTaskUtils {
return Joiner.on(',')
.join(
domain.getRepoId(),
domain.getFullyQualifiedDomainName(),
domain.getDomainName(),
domain.getLaunchNotice().getNoticeId().getTcnId(),
getIanaIdentifier(domain.getCreationClientId()),
transactionTime, // Used as creation time.

View File

@@ -153,9 +153,9 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
Path failoverClientCertificateFilename;
@Parameter(
names = "--ip_whitelist",
description = "Comma-delimited list of IP ranges. An empty string clears the whitelist.")
List<String> ipWhitelist = new ArrayList<>();
names = "--ip_allow_list",
description = "Comma-delimited list of IP ranges. An empty string clears the allow list.")
List<String> ipAllowList = new ArrayList<>();
@Nullable
@Parameter(
@@ -343,16 +343,16 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
}
builder.setAllowedTlds(allowedTldsBuilder.build());
}
if (!ipWhitelist.isEmpty()) {
ImmutableList.Builder<CidrAddressBlock> ipWhitelistBuilder = new ImmutableList.Builder<>();
if (!(ipWhitelist.size() == 1 && ipWhitelist.get(0).contains("null"))) {
for (String ipRange : ipWhitelist) {
if (!ipAllowList.isEmpty()) {
ImmutableList.Builder<CidrAddressBlock> ipAllowListBuilder = new ImmutableList.Builder<>();
if (!(ipAllowList.size() == 1 && ipAllowList.get(0).contains("null"))) {
for (String ipRange : ipAllowList) {
if (!ipRange.isEmpty()) {
ipWhitelistBuilder.add(CidrAddressBlock.create(ipRange));
ipAllowListBuilder.add(CidrAddressBlock.create(ipRange));
}
}
}
builder.setIpAddressWhitelist(ipWhitelistBuilder.build());
builder.setIpAddressAllowList(ipAllowListBuilder.build());
}
if (clientCertificateFilename != null) {
String asciiCert = new String(Files.readAllBytes(clientCertificateFilename), US_ASCII);

View File

@@ -293,14 +293,14 @@ public final class DomainLockUtils {
checkArgument(
!domainBase.getStatusValues().containsAll(REGISTRY_LOCK_STATUSES),
"Domain %s is already locked",
domainBase.getFullyQualifiedDomainName());
domainBase.getDomainName());
}
private static void verifyDomainLocked(DomainBase domainBase) {
checkArgument(
!Sets.intersection(domainBase.getStatusValues(), REGISTRY_LOCK_STATUSES).isEmpty(),
"Domain %s is already unlocked",
domainBase.getFullyQualifiedDomainName());
domainBase.getDomainName());
}
private static DomainBase getDomain(String domainName, DateTime now) {

View File

@@ -95,7 +95,7 @@ final class GenerateDnsReportCommand implements CommandWithRemoteApi {
private void write(DomainBase domain) {
ImmutableList<String> nameservers =
ImmutableList.sortedCopyOf(domain.loadNameserverFullyQualifiedHostNames());
ImmutableList.sortedCopyOf(domain.loadNameserverHostNames());
ImmutableList<Map<String, ?>> dsData =
domain
.getDsData()
@@ -109,7 +109,7 @@ final class GenerateDnsReportCommand implements CommandWithRemoteApi {
"digest", base16().encode(dsData1.getDigest())))
.collect(toImmutableList());
ImmutableMap.Builder<String, Object> mapBuilder = new ImmutableMap.Builder<>();
mapBuilder.put("domain", domain.getFullyQualifiedDomainName());
mapBuilder.put("domain", domain.getDomainName());
if (!nameservers.isEmpty()) {
mapBuilder.put("nameservers", nameservers);
}
@@ -127,9 +127,8 @@ final class GenerateDnsReportCommand implements CommandWithRemoteApi {
.map(InetAddress::getHostAddress)
.sorted()
.collect(toImmutableList());
ImmutableMap<String, ?> map = ImmutableMap.of(
"host", nameserver.getFullyQualifiedHostName(),
"ips", ipAddresses);
ImmutableMap<String, ?> map =
ImmutableMap.of("host", nameserver.getHostName(), "ips", ipAddresses);
writeJson(map);
}

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