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

Compare commits

...

8 Commits

Author SHA1 Message Date
Lai Jiang edbca15bf4 Remove generics from TransferData (#1787)
`TransferData` is currently a generic class with a complicated type parameter that designate the `Builder` class of its concrete subclass, on order to facilitate returning the said `Builder` from an instance loosely typed to the superclass (`TransferData`) itself.

While this works, in most all places that a `TransferData` is used, the raw, un-generic type is declared, resulting a lot of warnings, not to mention the fact that type safety not actually checked when raw type is used.

In this PR, we make it so that the concrete `Builder` is returned through a protected abstract method that is implemented by the subclasses. The type information therefore no longer needs to be embedded in the superclass type signature, and reflection is not necessary to create the `Builder` either. Overall, it makes `TransferData` a much cleaner class without the messiness of generics.
2022-09-15 14:07:38 -04:00
sarahcaseybot 5f41adf843 Flyway file for autogenerated PackagePromotion id fix (#1789)
* Flyway file for autogenerated PackagePromotion id fix

* Actually include the flyway file
2022-09-15 13:28:46 -04:00
Lai Jiang e21f64b745 Delete EppResourceIndex and EppResourceIndexBucket (#1774) 2022-09-15 10:50:22 -04:00
sarahcaseybot 0dee97934a Prevent creation of package domains for more than 1 year (#1786)
* Prevent creation of package domains for more than 1 year

* Fix docs test
2022-09-14 14:49:56 -04:00
gbrodman 1070173264 Load, project, and save in one txn in ResaveAERP (#1780) 2022-09-13 15:59:49 -04:00
Pavlo Tkach b9a3c0cd96 Add dry run test for remove package token (#1782) 2022-09-13 11:20:53 -04:00
sarahcaseybot 120456d138 Increase dns update failure max retry count (#1781) 2022-09-12 16:17:31 -04:00
gbrodman 66736d52f0 Add a cookie-based OAuth2 authenticator (#1761)
This uses the GoogleIdTokenVerifier to verify ID tokens passed in
(presumably from a front end) via cookies. This isn't used anywhere yet
but it will be used for front-end API calls for the new console.
2022-09-12 15:03:05 -04:00
41 changed files with 1438 additions and 1321 deletions
@@ -44,7 +44,7 @@ import javax.inject.Inject;
/**
* Hard deletes load-test Contacts, Hosts, their subordinate history entries, and the associated
* ForeignKey and EppResourceIndex entities.
* ForeignKey entities.
*
* <p>This only deletes contacts and hosts, NOT domains. To delete domains, use {@link
* DeleteProberDataAction} and pass it the TLD(s) that the load test domains were created on. Note
@@ -53,7 +53,7 @@ import org.joda.time.Duration;
/**
* Deletes all prober {@link Domain}s and their subordinate history entries, poll messages, and
* billing events, along with their ForeignKeyDomainIndex and EppResourceIndex entities.
* billing events, along with their ForeignKeyDomainIndex entities.
*/
@Action(
service = Action.Service.BACKEND,
@@ -14,11 +14,14 @@
package google.registry.beam.resave;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static org.apache.beam.sdk.values.TypeDescriptors.integers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import google.registry.beam.common.RegistryJpaIO;
import google.registry.beam.common.RegistryJpaIO.Read;
import google.registry.model.EppResource;
@@ -27,7 +30,7 @@ import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainBase;
import google.registry.model.host.Host;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
import google.registry.persistence.transaction.CriteriaQueryBuilder;
import google.registry.persistence.VKey;
import google.registry.util.DateTimeUtils;
import java.io.Serializable;
import java.util.concurrent.ThreadLocalRandom;
@@ -69,7 +72,7 @@ public class ResaveAllEppResourcesPipeline implements Serializable {
* multiple times, and to avoid projecting and resaving the same domain multiple times.
*/
private static final String DOMAINS_TO_PROJECT_QUERY =
"FROM Domain d WHERE (d.transferData.transferStatus = 'PENDING' AND"
"SELECT repoId FROM Domain d WHERE (d.transferData.transferStatus = 'PENDING' AND"
+ " d.transferData.pendingTransferExpirationTime < current_timestamp()) OR"
+ " (d.registrationExpirationTime < current_timestamp() AND d.deletionTime ="
+ " (:END_OF_TIME)) OR (EXISTS (SELECT 1 FROM GracePeriod gp WHERE gp.domainRepoId ="
@@ -99,13 +102,13 @@ public class ResaveAllEppResourcesPipeline implements Serializable {
/** Projects to the current time and saves any contacts with expired transfers. */
private void fastResaveContacts(Pipeline pipeline) {
Read<Contact, Contact> read =
Read<String, String> repoIdRead =
RegistryJpaIO.read(
"FROM Contact WHERE transferData.transferStatus = 'PENDING' AND"
"SELECT repoId FROM Contact WHERE transferData.transferStatus = 'PENDING' AND"
+ " transferData.pendingTransferExpirationTime < current_timestamp()",
Contact.class,
c -> c);
projectAndResaveResources(pipeline, Contact.class, read);
String.class,
r -> r);
projectAndResaveResources(pipeline, Contact.class, repoIdRead);
}
/**
@@ -116,61 +119,72 @@ public class ResaveAllEppResourcesPipeline implements Serializable {
* DomainBase#cloneProjectedAtTime(DateTime)}.
*/
private void fastResaveDomains(Pipeline pipeline) {
Read<Domain, Domain> read =
Read<String, String> repoIdRead =
RegistryJpaIO.read(
DOMAINS_TO_PROJECT_QUERY,
ImmutableMap.of("END_OF_TIME", DateTimeUtils.END_OF_TIME),
Domain.class,
d -> d);
projectAndResaveResources(pipeline, Domain.class, read);
String.class,
r -> r);
projectAndResaveResources(pipeline, Domain.class, repoIdRead);
}
/** Projects all resources to the current time and saves them. */
private <T extends EppResource> void forceResaveAllResources(Pipeline pipeline, Class<T> clazz) {
Read<T, T> read = RegistryJpaIO.read(() -> CriteriaQueryBuilder.create(clazz).build());
projectAndResaveResources(pipeline, clazz, read);
Read<String, String> repoIdRead =
RegistryJpaIO.read(
// Note: cannot use SQL parameters for the table name
String.format("SELECT repoId FROM %s", clazz.getSimpleName()), String.class, r -> r);
projectAndResaveResources(pipeline, clazz, repoIdRead);
}
/** Projects and re-saves the result of the provided {@link Read}. */
/** Projects and re-saves all resources with repo IDs provided by the {@link Read}. */
private <T extends EppResource> void projectAndResaveResources(
Pipeline pipeline, Class<T> clazz, Read<?, T> read) {
Pipeline pipeline, Class<T> clazz, Read<?, String> repoIdRead) {
int numShards = options.getSqlWriteShards();
int batchSize = options.getSqlWriteBatchSize();
String className = clazz.getSimpleName();
pipeline
.apply("Read " + className, read)
.apply("Read " + className, repoIdRead)
.apply(
"Shard data for class" + className,
WithKeys.<Integer, T>of(e -> ThreadLocalRandom.current().nextInt(numShards))
WithKeys.<Integer, String>of(e -> ThreadLocalRandom.current().nextInt(numShards))
.withKeyType(integers()))
.apply(
"Group into batches for class" + className,
GroupIntoBatches.<Integer, T>ofSize(batchSize).withShardedKey())
.apply("Map " + className + " to now", ParDo.of(new BatchedProjectionFunction<>()))
GroupIntoBatches.<Integer, String>ofSize(batchSize).withShardedKey())
.apply(
"Write transformed " + className,
RegistryJpaIO.<EppResource>write()
.withName("Write transformed " + className)
.withBatchSize(batchSize)
.withShards(numShards));
"Load, map, and save " + className,
ParDo.of(new BatchedLoadProjectAndSaveFunction(clazz)));
}
private static class BatchedProjectionFunction<T extends EppResource>
extends DoFn<KV<ShardedKey<Integer>, Iterable<T>>, EppResource> {
/** Function that loads, projects, and saves resources all in the same transaction. */
private static class BatchedLoadProjectAndSaveFunction
extends DoFn<KV<ShardedKey<Integer>, Iterable<String>>, Void> {
private final Class<? extends EppResource> clazz;
private BatchedLoadProjectAndSaveFunction(Class<? extends EppResource> clazz) {
this.clazz = clazz;
}
@ProcessElement
public void processElement(
@Element KV<ShardedKey<Integer>, Iterable<T>> element,
OutputReceiver<EppResource> outputReceiver) {
@Element KV<ShardedKey<Integer>, Iterable<String>> element,
OutputReceiver<Void> outputReceiver) {
jpaTm()
.transact(
() ->
element
.getValue()
.forEach(
resource ->
outputReceiver.output(
resource.cloneProjectedAtTime(jpaTm().getTransactionTime()))));
() -> {
DateTime now = jpaTm().getTransactionTime();
ImmutableList<VKey<? extends EppResource>> keys =
Streams.stream(element.getValue())
.map(repoId -> VKey.create(clazz, repoId))
.collect(toImmutableList());
ImmutableList<EppResource> mappedResources =
jpaTm().loadByKeys(keys).values().stream()
.map(r -> r.cloneProjectedAtTime(now))
.collect(toImmutableList());
jpaTm().putAll(mappedResources);
});
}
}
@@ -1482,11 +1482,6 @@ public final class RegistryConfig {
return CONFIG_SETTINGS.get().registryPolicy.defaultRegistrarWhoisServer;
}
/** Returns the number of {@code EppResourceIndex} buckets to be used. */
public static int getEppResourceIndexBucketCount() {
return CONFIG_SETTINGS.get().datastore.eppResourceIndexBucketsNum;
}
/** Returns the base retry duration that gets doubled after each failure within {@code Ofy}. */
public static Duration getBaseOfyRetryDuration() {
return Duration.millis(CONFIG_SETTINGS.get().datastore.baseOfyRetryMillis);
@@ -108,7 +108,6 @@ public class RegistryConfigSettings {
/** Configuration for Cloud Datastore. */
public static class Datastore {
public int eppResourceIndexBucketsNum;
public int baseOfyRetryMillis;
}
@@ -183,10 +183,6 @@ registryPolicy:
requireSslCertificates: true
datastore:
# Number of EPP resource index buckets in Datastore. Dont change after
# initial install.
eppResourceIndexBucketsNum: 997
# Milliseconds that Objectify waits to retry a Datastore transaction (this
# doubles after each failure).
baseOfyRetryMillis: 100
@@ -81,7 +81,7 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
// tasks.
public static final String APP_ENGINE_RETRY_HEADER = "X-AppEngine-TaskRetryCount";
public static final String CLOUD_TASKS_RETRY_HEADER = "X-CloudTasks-TaskRetryCount";
public static final int RETRIES_BEFORE_PERMANENT_FAILURE = 10;
public static final int RETRIES_BEFORE_PERMANENT_FAILURE = 20;
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -23,7 +23,6 @@ import static google.registry.model.IdService.allocateId;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.config.RegistryConfig.Config;
import google.registry.flows.EppException;
import google.registry.flows.ExtensionManager;
@@ -40,7 +39,6 @@ import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.eppoutput.CreateData.ContactCreateData;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import javax.inject.Inject;
@@ -95,11 +93,7 @@ public final class ContactCreateFlow implements TransactionalFlow {
.setType(HistoryEntry.Type.CONTACT_CREATE)
.setXmlBytes(null) // We don't want to store contact details in the history entry.
.setContact(newContact);
tm().insertAll(
ImmutableSet.of(
newContact,
historyBuilder.build(),
EppResourceIndex.create(Key.create(newContact))));
tm().insertAll(ImmutableSet.of(newContact, historyBuilder.build()));
return responseBuilder
.setResData(ContactCreateData.create(newContact.getContactId(), now))
.build();
@@ -59,7 +59,6 @@ import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InternetDomainName;
import com.googlecode.objectify.Key;
import google.registry.dns.DnsQueue;
import google.registry.flows.EppException;
import google.registry.flows.EppException.CommandUseErrorException;
@@ -105,7 +104,6 @@ import google.registry.model.eppinput.EppInput;
import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.eppoutput.CreateData.DomainCreateData;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
import google.registry.model.poll.PollMessage;
import google.registry.model.poll.PollMessage.Autorenew;
@@ -153,6 +151,7 @@ import org.joda.time.Duration;
* @error {@link DomainCreateFlow.MustHaveSignedMarksInCurrentPhaseException}
* @error {@link DomainCreateFlow.NoGeneralRegistrationsInCurrentPhaseException}
* @error {@link DomainCreateFlow.NoTrademarkedRegistrationsBeforeSunriseException}
* @error {@link DomainCreateFlow.PackageDomainRegisteredForTooManyYearsException}
* @error {@link DomainCreateFlow.SignedMarksOnlyDuringSunriseException}
* @error {@link DomainFlowTmchUtils.NoMarksFoundMatchingDomainException}
* @error {@link DomainFlowTmchUtils.FoundMarkNotYetValidException}
@@ -391,6 +390,9 @@ public final class DomainCreateFlow implements TransactionalFlow {
.build();
if (allocationToken.isPresent()
&& allocationToken.get().getTokenType().equals(TokenType.PACKAGE)) {
if (years > 1) {
throw new PackageDomainRegisteredForTooManyYearsException(allocationToken.get().getToken());
}
domain =
domain.asBuilder().setCurrentPackageToken(allocationToken.get().createVKey()).build();
}
@@ -400,10 +402,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
entitiesToSave.add(
createNameCollisionOneTimePollMessage(targetId, domainHistory, registrarId, now));
}
entitiesToSave.add(
domain,
domainHistory,
EppResourceIndex.create(Key.create(domain)));
entitiesToSave.add(domain, domainHistory);
if (allocationToken.isPresent()
&& TokenType.SINGLE_USE.equals(allocationToken.get().getTokenType())) {
entitiesToSave.add(
@@ -755,4 +754,14 @@ public final class DomainCreateFlow implements TransactionalFlow {
ANCHOR_TENANT_CREATE_VALID_YEARS, invalidYears));
}
}
/** Package domain registered for too many years. */
static class PackageDomainRegisteredForTooManyYearsException extends CommandUseErrorException {
public PackageDomainRegisteredForTooManyYearsException(String token) {
super(
String.format(
"The package token %s cannot be used to register names for longer than 1 year.",
token));
}
}
}
@@ -274,7 +274,7 @@ public final class DomainTransferUtils {
* renewal, we must issue a cancellation for the autorenew, so that the losing registrar will not
* be charged (essentially, the gaining registrar takes on the cost of the year of registration
* that the autorenew just added). But, if the superuser extension is used to request a transfer
* without an additional year then the gaining registrar is not charged for the one year renewal
* without an additional year then the gaining registrar is not charged for the one-year renewal
* and the losing registrar still needs to be charged for the auto-renew.
*
* <p>For details on the policy justification, see b/19430703#comment17 and <a
@@ -27,7 +27,6 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import static google.registry.util.CollectionUtils.isNullOrEmpty;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.config.RegistryConfig.Config;
import google.registry.dns.DnsQueue;
import google.registry.flows.EppException;
@@ -49,7 +48,6 @@ import google.registry.model.eppoutput.EppResponse;
import google.registry.model.host.Host;
import google.registry.model.host.HostCommand.Create;
import google.registry.model.host.HostHistory;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import java.util.Optional;
import javax.inject.Inject;
@@ -106,7 +104,7 @@ public final class HostCreateFlow implements TransactionalFlow {
DateTime now = tm().getTransactionTime();
verifyResourceDoesNotExist(Host.class, targetId, now, registrarId);
// The superordinate domain of the host object if creating an in-bailiwick host, or null if
// creating an external host. This is looked up before we actually create the Host object so
// creating an external host. This is looked up before we actually create the Host object, so
// we can detect error conditions earlier.
Optional<Domain> superordinateDomain =
lookupSuperordinateDomain(validateHostName(targetId), now);
@@ -130,11 +128,7 @@ public final class HostCreateFlow implements TransactionalFlow {
.setSuperordinateDomain(superordinateDomain.map(Domain::createVKey).orElse(null))
.build();
historyBuilder.setType(HOST_CREATE).setHost(newHost);
ImmutableSet<ImmutableObject> entitiesToSave =
ImmutableSet.of(
newHost,
historyBuilder.build(),
EppResourceIndex.create(Key.create(newHost)));
ImmutableSet<ImmutableObject> entitiesToSave = ImmutableSet.of(newHost, historyBuilder.build());
if (superordinateDomain.isPresent()) {
tm().update(
superordinateDomain
@@ -152,14 +146,14 @@ public final class HostCreateFlow implements TransactionalFlow {
/** Subordinate hosts must have an ip address. */
static class SubordinateHostMustHaveIpException extends RequiredParameterMissingException {
public SubordinateHostMustHaveIpException() {
SubordinateHostMustHaveIpException() {
super("Subordinate hosts must have an ip address");
}
}
/** External hosts must not have ip addresses. */
static class UnexpectedExternalHostIpException extends ParameterValueRangeErrorException {
public UnexpectedExternalHostIpException() {
UnexpectedExternalHostIpException() {
super("External hosts must not have ip addresses");
}
}
@@ -23,8 +23,6 @@ import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.host.Host;
import google.registry.model.host.HostHistory;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.EppResourceIndexBucket;
import google.registry.model.reporting.HistoryEntry;
/** Sets of classes of the Objectify-registered entities in use throughout the model. */
@@ -38,8 +36,6 @@ public final class EntityClasses {
ContactHistory.class,
Domain.class,
DomainHistory.class,
EppResourceIndex.class,
EppResourceIndexBucket.class,
GaeUserIdConverter.class,
HistoryEntry.class,
Host.class,
@@ -1,79 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.index;
import static google.registry.util.TypeUtils.instantiate;
import com.google.common.annotations.VisibleForTesting;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.BackupGroupRoot;
import google.registry.model.EppResource;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.annotations.ReportedOn;
import google.registry.persistence.VKey;
/** An index that allows for quick enumeration of all EppResource entities (e.g. via map reduce). */
@ReportedOn
@Entity
@DeleteAfterMigration
public class EppResourceIndex extends BackupGroupRoot {
@Id String id;
@Parent Key<EppResourceIndexBucket> bucket;
/** Although this field holds a {@link Key} it is named "reference" for historical reasons. */
Key<? extends EppResource> reference;
@Index String kind;
public String getId() {
return id;
}
public String getKind() {
return kind;
}
public Key<? extends EppResource> getKey() {
return reference;
}
@VisibleForTesting
public Key<EppResourceIndexBucket> getBucket() {
return bucket;
}
@VisibleForTesting
public static <T extends EppResource> EppResourceIndex create(
Key<EppResourceIndexBucket> bucket, Key<T> resourceKey) {
EppResourceIndex instance = instantiate(EppResourceIndex.class);
instance.reference = resourceKey;
instance.kind = resourceKey.getKind();
// creates a web-safe key string, this value is never used
// TODO(b/211785379): remove unused id
instance.id = VKey.from(resourceKey).stringify();
instance.bucket = bucket;
return instance;
}
public static <T extends EppResource> EppResourceIndex create(Key<T> resourceKey) {
return create(EppResourceIndexBucket.getBucketKey(resourceKey), resourceKey);
}
}
@@ -1,68 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.index;
import static google.registry.config.RegistryConfig.getEppResourceIndexBucketCount;
import com.google.common.collect.ImmutableList;
import com.google.common.hash.Hashing;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.annotations.VirtualEntity;
/** A virtual entity to represent buckets to which EppResourceIndex objects are randomly added. */
@Entity
@VirtualEntity
@DeleteAfterMigration
public class EppResourceIndexBucket extends ImmutableObject {
@SuppressWarnings("unused")
@Id
private long bucketId;
/**
* Deterministic function that returns a bucket id based on the resource's roid.
* NB: At the moment, nothing depends on this being deterministic, so we have the ability to
* change the number of buckets and utilize a random distribution once we do.
*/
private static long getBucketIdFromEppResource(Key<? extends EppResource> resourceKey) {
int numBuckets = getEppResourceIndexBucketCount();
// IDs can't be 0, so add 1 to the hash.
return Hashing.consistentHash(resourceKey.getName().hashCode(), numBuckets) + 1L;
}
/** Gets a bucket key as a function of an EppResource to be indexed. */
public static Key<EppResourceIndexBucket> getBucketKey(Key<? extends EppResource> resourceKey) {
return Key.create(EppResourceIndexBucket.class, getBucketIdFromEppResource(resourceKey));
}
/** Gets the specified numbered bucket key. */
public static Key<EppResourceIndexBucket> getBucketKey(int bucketId) {
return Key.create(EppResourceIndexBucket.class, bucketId);
}
/** Returns the keys to all buckets. */
public static Iterable<Key<EppResourceIndexBucket>> getAllBuckets() {
ImmutableList.Builder<Key<EppResourceIndexBucket>> builder = new ImmutableList.Builder<>();
for (int bucketId = 1; bucketId <= getEppResourceIndexBucketCount(); bucketId++) {
builder.add(getBucketKey(bucketId));
}
return builder.build();
}
}
@@ -18,7 +18,7 @@ import javax.persistence.Embeddable;
/** Transfer data for contact. */
@Embeddable
public class ContactTransferData extends TransferData<ContactTransferData.Builder> {
public class ContactTransferData extends TransferData {
public static final ContactTransferData EMPTY = new ContactTransferData();
@Override
@@ -26,6 +26,11 @@ public class ContactTransferData extends TransferData<ContactTransferData.Builde
return EMPTY.equals(this);
}
@Override
protected Builder createEmptyBuilder() {
return new Builder();
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
@@ -34,7 +34,7 @@ import org.joda.time.DateTime;
/** Transfer data for domain. */
@Embeddable
public class DomainTransferData extends TransferData<DomainTransferData.Builder> {
public class DomainTransferData extends TransferData {
public static final DomainTransferData EMPTY = new DomainTransferData();
/**
@@ -107,7 +107,7 @@ public class DomainTransferData extends TransferData<DomainTransferData.Builder>
@Override
public Builder copyConstantFieldsToBuilder() {
return super.copyConstantFieldsToBuilder().setTransferPeriod(transferPeriod);
return ((Builder) super.copyConstantFieldsToBuilder()).setTransferPeriod(transferPeriod);
}
public Period getTransferPeriod() {
@@ -157,6 +157,11 @@ public class DomainTransferData extends TransferData<DomainTransferData.Builder>
return EMPTY.equals(this);
}
@Override
protected Builder createEmptyBuilder() {
return new Builder();
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
@@ -27,7 +27,6 @@ import google.registry.model.eppcommon.Trid;
import google.registry.model.poll.PollMessage;
import google.registry.persistence.VKey;
import google.registry.util.NullIgnoringCollectionBuilder;
import google.registry.util.TypeUtils.TypeInstantiator;
import java.util.Set;
import javax.annotation.Nullable;
import javax.persistence.AttributeOverride;
@@ -41,10 +40,7 @@ import javax.persistence.MappedSuperclass;
* are implicitly transferred with their superordinate domain.
*/
@MappedSuperclass
public abstract class TransferData<
B extends
TransferData.Builder<? extends TransferData<?>, ? extends TransferData.Builder<?, ?>>>
extends BaseTransferObject implements Buildable {
public abstract class TransferData extends BaseTransferObject implements Buildable {
/** The transaction id of the most recent transfer request (or null if there never was one). */
@Embedded
@@ -102,6 +98,8 @@ public abstract class TransferData<
@Override
public abstract Builder<?, ?> asBuilder();
protected abstract Builder<?, ?> createEmptyBuilder();
/**
* 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.
@@ -116,8 +114,8 @@ public abstract class TransferData<
* <li>transferPeriod
* </ul>
*/
public B copyConstantFieldsToBuilder() {
B newBuilder = new TypeInstantiator<B>(getClass()) {}.instantiate();
public Builder<?, ?> copyConstantFieldsToBuilder() {
Builder<?, ?> newBuilder = createEmptyBuilder();
newBuilder
.setTransferRequestTrid(transferRequestTrid)
.setTransferRequestTime(transferRequestTime)
@@ -129,7 +127,7 @@ public abstract class TransferData<
/** Maps serverApproveEntities set to the individual fields. */
static void mapServerApproveEntitiesToFields(
Set<VKey<? extends TransferServerApproveEntity>> serverApproveEntities,
TransferData<?> transferData) {
TransferData transferData) {
if (isNullOrEmpty(serverApproveEntities)) {
transferData.pollMessageId1 = null;
transferData.pollMessageId2 = null;
@@ -161,7 +159,7 @@ public abstract class TransferData<
}
/** Builder for {@link TransferData} because it is immutable. */
public abstract static class Builder<T extends TransferData<?>, B extends Builder<T, B>>
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. */
@@ -39,7 +39,7 @@ import google.registry.monitoring.whitebox.StackdriverModule;
import google.registry.persistence.PersistenceModule;
import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.rde.JSchModule;
import google.registry.request.Modules.Jackson2Module;
import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.NetHttpTransportModule;
import google.registry.request.Modules.UrlConnectionServiceModule;
import google.registry.request.Modules.UrlFetchServiceModule;
@@ -67,7 +67,7 @@ import javax.inject.Singleton;
GroupsModule.class,
GroupssettingsModule.class,
JSchModule.class,
Jackson2Module.class,
GsonModule.class,
KeyModule.class,
KeyringModule.class,
KmsModule.class,
@@ -32,7 +32,7 @@ import google.registry.keyring.kms.KmsModule;
import google.registry.module.frontend.FrontendRequestComponent.FrontendRequestComponentModule;
import google.registry.monitoring.whitebox.StackdriverModule;
import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.request.Modules.Jackson2Module;
import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.NetHttpTransportModule;
import google.registry.request.Modules.UserServiceModule;
import google.registry.request.auth.AuthModule;
@@ -56,7 +56,7 @@ import javax.inject.Singleton;
FrontendRequestComponentModule.class,
GroupsModule.class,
GroupssettingsModule.class,
Jackson2Module.class,
GsonModule.class,
KeyModule.class,
KeyringModule.class,
KmsModule.class,
@@ -32,7 +32,7 @@ import google.registry.module.pubapi.PubApiRequestComponent.PubApiRequestCompone
import google.registry.monitoring.whitebox.StackdriverModule;
import google.registry.persistence.PersistenceModule;
import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.request.Modules.Jackson2Module;
import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.NetHttpTransportModule;
import google.registry.request.Modules.UserServiceModule;
import google.registry.request.auth.AuthModule;
@@ -51,7 +51,7 @@ import javax.inject.Singleton;
DummyKeyringModule.class,
GroupsModule.class,
GroupssettingsModule.class,
Jackson2Module.class,
GsonModule.class,
KeyModule.class,
KeyringModule.class,
KmsModule.class,
@@ -33,7 +33,7 @@ import google.registry.keyring.kms.KmsModule;
import google.registry.module.tools.ToolsRequestComponent.ToolsRequestComponentModule;
import google.registry.monitoring.whitebox.StackdriverModule;
import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.request.Modules.Jackson2Module;
import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.NetHttpTransportModule;
import google.registry.request.Modules.UserServiceModule;
import google.registry.request.auth.AuthModule;
@@ -54,7 +54,7 @@ import javax.inject.Singleton;
DriveModule.class,
GroupsModule.class,
GroupssettingsModule.class,
Jackson2Module.class,
GsonModule.class,
KeyModule.class,
KeyringModule.class,
KmsModule.class,
@@ -30,7 +30,6 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import google.registry.model.ImmutableObject;
import google.registry.model.index.EppResourceIndex;
import google.registry.persistence.JpaRetries;
import google.registry.persistence.VKey;
import google.registry.util.Clock;
@@ -74,14 +73,6 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final Retrier retrier = new Retrier(new SystemSleeper(), 3);
// The entity of classes in this set will be simply ignored when passed to modification
// operations, i.e. insert, put, update and delete. This is to help maintain a single code path
// when we switch from ofy to tm() for the database migration as we don't need have a condition
// to exclude the Datastore specific entities when the underlying tm() is jpaTm().
// TODO(b/176108270): Remove this property after database migration.
private static final ImmutableSet<Class<? extends ImmutableObject>> IGNORED_ENTITY_CLASSES =
ImmutableSet.of(EppResourceIndex.class);
// EntityManagerFactory is thread safe.
private final EntityManagerFactory emf;
private final Clock clock;
@@ -267,9 +258,6 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
@Override
public void insert(Object entity) {
checkArgumentNotNull(entity, "entity must be specified");
if (isEntityOfIgnoredClass(entity)) {
return;
}
assertInTransaction();
transactionInfo.get().insertObject(entity);
}
@@ -289,9 +277,6 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
@Override
public void put(Object entity) {
checkArgumentNotNull(entity, "entity must be specified");
if (isEntityOfIgnoredClass(entity)) {
return;
}
assertInTransaction();
transactionInfo.get().updateObject(entity);
}
@@ -315,9 +300,6 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
@Override
public void update(Object entity) {
checkArgumentNotNull(entity, "entity must be specified");
if (isEntityOfIgnoredClass(entity)) {
return;
}
assertInTransaction();
checkArgument(exists(entity), "Given entity does not exist");
transactionInfo.get().updateObject(entity);
@@ -472,9 +454,6 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
private int internalDelete(VKey<?> key) {
checkArgumentNotNull(key, "key must be specified");
assertInTransaction();
if (IGNORED_ENTITY_CLASSES.contains(key.getKind())) {
return 0;
}
EntityType<?> entityType = getEntityType(key.getKind());
ImmutableSet<EntityId> entityIds = getEntityIdsFromSqlKey(entityType, key.getSqlKey());
String sql =
@@ -498,9 +477,6 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
@Override
public <T> T delete(T entity) {
checkArgumentNotNull(entity, "entity must be specified");
if (isEntityOfIgnoredClass(entity)) {
return entity;
}
assertInTransaction();
T managedEntity = entity;
if (!getEntityManager().contains(entity)) {
@@ -549,10 +525,6 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
}
}
private static boolean isEntityOfIgnoredClass(Object entity) {
return IGNORED_ENTITY_CLASSES.contains(entity.getClass());
}
private static ImmutableSet<EntityId> getEntityIdsFromEntity(
EntityType<?> entityType, Object entity) {
if (entityType.hasSingleIdAttribute()) {
@@ -615,12 +587,12 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
}
/** Gets the field definition from clazz or any superclass. */
private static Field getField(Class clazz, String fieldName) throws NoSuchFieldException {
private static Field getField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
try {
// Note that we have to use getDeclaredField() for this, getField() just finds public fields.
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
Class base = clazz.getSuperclass();
Class<?> base = clazz.getSuperclass();
if (base != null) {
return getField(base, fieldName);
} else {
@@ -48,7 +48,6 @@ import google.registry.model.common.Cursor.CursorType;
import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
import google.registry.model.host.Host;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.rde.RdeMode;
import google.registry.model.registrar.Registrar;
import google.registry.persistence.PersistenceModule.JpaTransactionManagerType;
@@ -169,13 +168,6 @@ import org.joda.time.Duration;
* the initial query for the history entry running at {@code READ_COMMITTED} transaction isolation
* level.
*
* <p>This is also true in Datastore because:
*
* <ol>
* <li>{@code EppResource} queries are strongly consistent thanks to {@link EppResourceIndex}
* <li>{@code EppResource} entities are rewinded to the point-in-time of the watermark
* </ol>
*
* <p>Here's what's not deterministic:
*
* <ul>
@@ -19,7 +19,7 @@ import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;
import com.google.appengine.api.users.UserService;
@@ -63,17 +63,12 @@ public final class Modules {
}
}
/**
* Dagger module that causes the Jackson2 JSON parser to be used for Google APIs requests.
*
* <p>Jackson1 and GSON can also satisfy the {@link JsonFactory} interface, but we've decided to
* go with Jackson2, since it's what's used in the public examples for using Google APIs.
*/
/** Dagger module that causes the Google GSON parser to be used for Google APIs requests. */
@Module
public static final class Jackson2Module {
public static final class GsonModule {
@Provides
static JsonFactory provideJsonFactory() {
return JacksonFactory.getDefaultInstance();
return GsonFactory.getDefaultInstance();
}
}
@@ -14,11 +14,17 @@
package google.registry.request.auth;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.appengine.api.oauth.OAuthService;
import com.google.appengine.api.oauth.OAuthServiceFactory;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import dagger.Module;
import dagger.Provides;
import google.registry.config.RegistryConfig.Config;
import javax.inject.Singleton;
/**
* Dagger module for authentication routines.
@@ -29,8 +35,9 @@ public class AuthModule {
/** Provides the custom authentication mechanisms (including OAuth). */
@Provides
ImmutableList<AuthenticationMechanism> provideApiAuthenticationMechanisms(
OAuthAuthenticationMechanism oauthAuthenticationMechanism) {
return ImmutableList.of(oauthAuthenticationMechanism);
OAuthAuthenticationMechanism oauthAuthenticationMechanism,
CookieOAuth2AuthenticationMechanism cookieOAuth2AuthenticationMechanism) {
return ImmutableList.of(oauthAuthenticationMechanism, cookieOAuth2AuthenticationMechanism);
}
/** Provides the OAuthService instance. */
@@ -38,4 +45,15 @@ public class AuthModule {
OAuthService provideOauthService() {
return OAuthServiceFactory.getOAuthService();
}
@Provides
@Singleton
GoogleIdTokenVerifier provideGoogleIdTokenVerifier(
@Config("allowedOauthClientIds") ImmutableSet<String> allowedOauthClientIds,
NetHttpTransport httpTransport,
JsonFactory jsonFactory) {
return new GoogleIdTokenVerifier.Builder(httpTransport, jsonFactory)
.setAudience(allowedOauthClientIds)
.build();
}
}
@@ -0,0 +1,89 @@
// Copyright 2022 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.request.auth;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.common.flogger.FluentLogger;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
/**
* A way to authenticate HTTP requests using OAuth2 ID tokens stored in cookies.
*
* <p>This is generic to Google Single-Sign-On and doesn't have any ties with Google App Engine.
*/
public class CookieOAuth2AuthenticationMechanism implements AuthenticationMechanism {
private static final String ID_TOKEN_COOKIE_NAME = "idToken";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final GoogleIdTokenVerifier googleIdTokenVerifier;
@Inject
public CookieOAuth2AuthenticationMechanism(GoogleIdTokenVerifier googleIdTokenVerifier) {
this.googleIdTokenVerifier = googleIdTokenVerifier;
}
@Override
public AuthResult authenticate(HttpServletRequest request) {
String rawIdToken = getRawIdTokenFromCookie(request);
if (rawIdToken == null) {
return AuthResult.NOT_AUTHENTICATED;
}
GoogleIdToken googleIdToken;
try {
googleIdToken = googleIdTokenVerifier.verify(rawIdToken);
} catch (IOException | GeneralSecurityException e) {
logger.atInfo().withCause(e).log("Error when verifying access token");
return AuthResult.NOT_AUTHENTICATED;
}
// A null token means the provided ID token was invalid or expired
if (googleIdToken == null) {
logger.atInfo().log("Token %s failed validation", rawIdToken);
return AuthResult.NOT_AUTHENTICATED;
}
String emailAddress = googleIdToken.getPayload().getEmail();
Optional<User> maybeUser = UserDao.loadUser(emailAddress);
if (!maybeUser.isPresent()) {
logger.atInfo().log("No user found for email address %s", emailAddress);
return AuthResult.NOT_AUTHENTICATED;
}
return AuthResult.create(AuthLevel.USER, UserAuthInfo.create(maybeUser.get()));
}
@Nullable
private String getRawIdTokenFromCookie(HttpServletRequest request) {
if (request.getCookies() == null) {
logger.atInfo().log("No cookies passed in request");
return null;
}
for (Cookie cookie : request.getCookies()) {
if (cookie.getName().equals(ID_TOKEN_COOKIE_NAME)) {
return cookie.getValue();
}
}
logger.atInfo().log("No ID token cookie");
return null;
}
}
@@ -36,7 +36,7 @@ import google.registry.persistence.PersistenceModule.ReadOnlyReplicaJpaTm;
import google.registry.persistence.transaction.JpaTransactionManager;
import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.rde.RdeModule;
import google.registry.request.Modules.Jackson2Module;
import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.UrlConnectionServiceModule;
import google.registry.request.Modules.UrlFetchServiceModule;
import google.registry.request.Modules.UserServiceModule;
@@ -65,7 +65,7 @@ import javax.inject.Singleton;
CloudTasksUtilsModule.class,
DummyKeyringModule.class,
DnsUpdateWriterModule.class,
Jackson2Module.class,
GsonModule.class,
KeyModule.class,
KeyringModule.class,
KmsModule.class,
@@ -18,7 +18,6 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadByEntitiesIfPresent;
import static google.registry.testing.DatabaseHelper.loadByEntity;
@@ -30,12 +29,10 @@ import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.persistSimpleResource;
import static google.registry.testing.TaskQueueHelper.assertDnsTasksEnqueued;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.time.DateTimeZone.UTC;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.config.RegistryEnvironment;
import google.registry.dns.DnsQueue;
import google.registry.model.ImmutableObject;
@@ -43,8 +40,6 @@ import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.tld.Registry;
@@ -279,8 +274,7 @@ class DeleteProberDataActionTest {
}
/**
* Persists and returns a domain and a descendant history entry, billing event, and poll message,
* along with the ForeignKeyIndex and EppResourceIndex.
* Persists and returns a domain and a descendant history entry, billing event, and poll message.
*/
private static Set<ImmutableObject> persistDomainAndDescendants(String fqdn) {
Domain domain = persistDeletedDomain(fqdn, DELETION_TIME);
@@ -318,11 +312,6 @@ class DeleteProberDataActionTest {
.add(historyEntry)
.add(billingEvent)
.add(pollMessage);
if (tm().isOfy()) {
builder
.add(ForeignKeyIndex.load(Domain.class, fqdn, START_OF_TIME))
.add(loadByEntity(EppResourceIndex.create(Key.create(domain))));
}
return builder.build();
}
@@ -23,6 +23,7 @@ import static google.registry.dns.DnsModule.PARAM_LOCK_INDEX;
import static google.registry.dns.DnsModule.PARAM_NUM_PUBLISH_LOCKS;
import static google.registry.dns.DnsModule.PARAM_PUBLISH_TASK_ENQUEUED;
import static google.registry.dns.DnsModule.PARAM_REFRESH_REQUEST_CREATED;
import static google.registry.dns.PublishDnsUpdatesAction.RETRIES_BEFORE_PERMANENT_FAILURE;
import static google.registry.request.RequestParameters.PARAM_TLD;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
@@ -392,10 +393,13 @@ public class PublishDnsUpdatesActionTest {
}
@Test
void testTaskFailsAfterTenRetries_doesNotRetry() {
void testTaskFailsAfterMaxRetries_doesNotRetry() {
action =
createAction(
"xn--q9jyb4c", ImmutableSet.of(), ImmutableSet.of("ns1.example.xn--q9jyb4c"), 10);
"xn--q9jyb4c",
ImmutableSet.of(),
ImmutableSet.of("ns1.example.xn--q9jyb4c"),
RETRIES_BEFORE_PERMANENT_FAILURE);
doThrow(new RuntimeException()).when(dnsWriter).commit();
action.run();
cloudTasksHelper.assertNoTasksEnqueued(DNS_PUBLISH_PUSH_QUEUE_NAME);
@@ -405,7 +409,11 @@ public class PublishDnsUpdatesActionTest {
@Test
void testDomainDnsUpdateRetriesExhausted_emailIsSent() {
action =
createAction("xn--q9jyb4c", ImmutableSet.of("example.xn--q9jyb4c"), ImmutableSet.of(), 10);
createAction(
"xn--q9jyb4c",
ImmutableSet.of("example.xn--q9jyb4c"),
ImmutableSet.of(),
RETRIES_BEFORE_PERMANENT_FAILURE);
doThrow(new RuntimeException()).when(dnsWriter).commit();
action.run();
ArgumentCaptor<EmailMessage> contentCaptor = ArgumentCaptor.forClass(EmailMessage.class);
@@ -423,7 +431,10 @@ public class PublishDnsUpdatesActionTest {
void testHostDnsUpdateRetriesExhausted_emailIsSent() {
action =
createAction(
"xn--q9jyb4c", ImmutableSet.of(), ImmutableSet.of("ns1.example.xn--q9jyb4c"), 10);
"xn--q9jyb4c",
ImmutableSet.of(),
ImmutableSet.of("ns1.example.xn--q9jyb4c"),
RETRIES_BEFORE_PERMANENT_FAILURE);
doThrow(new RuntimeException()).when(dnsWriter).commit();
action.run();
ArgumentCaptor<EmailMessage> contentCaptor = ArgumentCaptor.forClass(EmailMessage.class);
@@ -85,6 +85,7 @@ import google.registry.flows.domain.DomainCreateFlow.AnchorTenantCreatePeriodExc
import google.registry.flows.domain.DomainCreateFlow.MustHaveSignedMarksInCurrentPhaseException;
import google.registry.flows.domain.DomainCreateFlow.NoGeneralRegistrationsInCurrentPhaseException;
import google.registry.flows.domain.DomainCreateFlow.NoTrademarkedRegistrationsBeforeSunriseException;
import google.registry.flows.domain.DomainCreateFlow.PackageDomainRegisteredForTooManyYearsException;
import google.registry.flows.domain.DomainCreateFlow.RenewalPriceInfo;
import google.registry.flows.domain.DomainCreateFlow.SignedMarksOnlyDuringSunriseException;
import google.registry.flows.domain.DomainFlowTmchUtils.FoundMarkExpiredException;
@@ -3142,11 +3143,40 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
persistContactsAndHosts();
setEppInput(
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "1"));
runFlowAssertResponse(
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "example.tld")));
loadFile(
"domain_create_response_wildcard.xml",
new ImmutableMap.Builder<String, String>()
.put("DOMAIN", "example.tld")
.put("CRDATE", "1999-04-03T22:00:00.0Z")
.put("EXDATE", "2000-04-03T22:00:00.0Z")
.build()));
Domain domain = reloadResourceByForeignKey();
assertThat(domain.getCurrentPackageToken()).isPresent();
Truth8.assertThat(domain.getCurrentPackageToken()).hasValue(token.createVKey());
}
@Test
void testFailure_packageToken_registrationTooLong() throws Exception {
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setRenewalPriceBehavior(SPECIFIED)
.build());
persistContactsAndHosts();
setEppInput(
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
EppException thrown =
assertThrows(PackageDomainRegisteredForTooManyYearsException.class, this::runFlow);
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"The package token abc123 cannot be used to register names for longer than 1 year.");
}
}
@@ -1294,4 +1294,32 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
Domain domain = reloadResourceByForeignKey();
Truth8.assertThat(domain.getCurrentPackageToken()).isEmpty();
}
@Test
void testDryRunRemovePackageToken() throws Exception {
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setRenewalPriceBehavior(SPECIFIED)
.build());
persistDomain(SPECIFIED, Money.of(USD, 2));
persistResource(
reloadResourceByForeignKey()
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.build());
setEppInput(
"domain_renew_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2", "TOKEN", "__REMOVEPACKAGE__"));
dryRunFlowAssertResponse(
loadFile(
"domain_renew_response.xml",
ImmutableMap.of("DOMAIN", "example.tld", "EXDATE", "2002-04-03T22:00:00.0Z")));
}
}
@@ -21,8 +21,6 @@ import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.host.Host;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.EppResourceIndexBucket;
import google.registry.model.reporting.HistoryEntry;
import google.registry.testing.TestObject;
import org.junit.jupiter.api.Test;
@@ -43,11 +41,8 @@ public class ClassPathManagerTest {
assertThat(ClassPathManager.getClass("Host")).isEqualTo(Host.class);
assertThat(ClassPathManager.getClass("Contact")).isEqualTo(Contact.class);
assertThat(ClassPathManager.getClass("GaeUserIdConverter")).isEqualTo(GaeUserIdConverter.class);
assertThat(ClassPathManager.getClass("EppResourceIndexBucket"))
.isEqualTo(EppResourceIndexBucket.class);
assertThat(ClassPathManager.getClass("Domain")).isEqualTo(Domain.class);
assertThat(ClassPathManager.getClass("HistoryEntry")).isEqualTo(HistoryEntry.class);
assertThat(ClassPathManager.getClass("EppResourceIndex")).isEqualTo(EppResourceIndex.class);
}
@Test
@@ -84,11 +79,8 @@ public class ClassPathManagerTest {
assertThat(ClassPathManager.getClassName(Contact.class)).isEqualTo("Contact");
assertThat(ClassPathManager.getClassName(GaeUserIdConverter.class))
.isEqualTo("GaeUserIdConverter");
assertThat(ClassPathManager.getClassName(EppResourceIndexBucket.class))
.isEqualTo("EppResourceIndexBucket");
assertThat(ClassPathManager.getClassName(Domain.class)).isEqualTo("Domain");
assertThat(ClassPathManager.getClassName(HistoryEntry.class)).isEqualTo("HistoryEntry");
assertThat(ClassPathManager.getClassName(EppResourceIndex.class)).isEqualTo("EppResourceIndex");
}
@Test
@@ -0,0 +1,107 @@
// Copyright 2022 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.request.auth;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static org.mockito.Mockito.when;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.json.webtoken.JsonWebSignature.Header;
import com.google.common.truth.Truth8;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.persistence.transaction.JpaTestExtensions;
import java.security.GeneralSecurityException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
/** Tests for {@link CookieOAuth2AuthenticationMechanism}. */
@ExtendWith(MockitoExtension.class)
public class CookieOAuth2AuthenticationMechanismTest {
@RegisterExtension
public final JpaTestExtensions.JpaUnitTestExtension jpaExtension =
new JpaTestExtensions.Builder().withEntityClass(User.class).buildUnitTestExtension();
@Mock private GoogleIdTokenVerifier tokenVerifier;
@Mock private HttpServletRequest request;
private GoogleIdToken token;
private CookieOAuth2AuthenticationMechanism authenticationMechanism;
@BeforeEach
void beforeEach() {
authenticationMechanism = new CookieOAuth2AuthenticationMechanism(tokenVerifier);
Payload payload = new Payload();
payload.setEmail("email@email.com");
payload.setSubject("gaiaId");
token = new GoogleIdToken(new Header(), payload, new byte[0], new byte[0]);
}
@Test
void testSuccess_validUser() throws Exception {
User user =
new User.Builder()
.setEmailAddress("email@email.com")
.setGaiaId("gaiaId")
.setUserRoles(
new UserRoles.Builder().setIsAdmin(true).setGlobalRole(GlobalRole.FTE).build())
.build();
insertInDb(user);
when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("idToken", "asdf")});
when(tokenVerifier.verify("asdf")).thenReturn(token);
AuthResult authResult = authenticationMechanism.authenticate(request);
assertThat(authResult.isAuthenticated()).isTrue();
Truth8.assertThat(authResult.userAuthInfo()).isPresent();
Truth8.assertThat(authResult.userAuthInfo().get().consoleUser()).hasValue(user);
}
@Test
void testFailure_noCookie() {
when(request.getCookies()).thenReturn(new Cookie[0]);
assertThat(authenticationMechanism.authenticate(request).isAuthenticated()).isFalse();
}
@Test
void testFailure_badToken() throws Exception {
when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("idToken", "asdf")});
when(tokenVerifier.verify("asdf")).thenReturn(null);
assertThat(authenticationMechanism.authenticate(request).isAuthenticated()).isFalse();
}
@Test
void testFailure_errorVerifyingToken() throws Exception {
when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("idToken", "asdf")});
when(tokenVerifier.verify("asdf")).thenThrow(new GeneralSecurityException("hi"));
assertThat(authenticationMechanism.authenticate(request).isAuthenticated()).isFalse();
}
@Test
void testFailure_goodTokenButUnknownUser() throws Exception {
when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("idToken", "asdf")});
when(tokenVerifier.verify("asdf")).thenReturn(token);
assertThat(authenticationMechanism.authenticate(request).isAuthenticated()).isFalse();
}
}
@@ -51,7 +51,6 @@ import static org.joda.money.CurrencyUnit.USD;
import com.google.common.base.Ascii;
import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -59,7 +58,6 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.net.InetAddresses;
import com.googlecode.objectify.Key;
import google.registry.dns.writer.VoidDnsWriter;
import google.registry.model.Buildable;
import google.registry.model.EppResource;
@@ -88,8 +86,6 @@ import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppcommon.Trid;
import google.registry.model.host.Host;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.EppResourceIndexBucket;
import google.registry.model.poll.PollMessage;
import google.registry.model.pricing.StaticPremiumListPricingEngine;
import google.registry.model.registrar.Registrar;
@@ -119,6 +115,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
@@ -127,7 +124,7 @@ import org.joda.time.DateTimeZone;
import org.joda.time.Duration;
/** Static utils for setting up test resources. */
public class DatabaseHelper {
public final class DatabaseHelper {
// If the clock is defined, it will always be advanced by one millsecond after a transaction.
private static FakeClock clock;
@@ -321,10 +318,8 @@ public class DatabaseHelper {
/** Persists a domain and enqueues a LORDN task of the appropriate type for it. */
public static Domain persistDomainAndEnqueueLordn(final Domain domain) {
final Domain persistedDomain = persistResource(domain);
/**
* Calls {@link LordnTaskUtils#enqueueDomainTask} wrapped in a transaction so that the
* transaction time is set correctly.
*/
// Calls {@link LordnTaskUtils#enqueueDomainTask} wrapped in a transaction so that the
// transaction time is set correctly.
tm().transactNew(() -> LordnTaskUtils.enqueueDomainTask(persistedDomain));
maybeAdvanceClock();
return persistedDomain;
@@ -995,8 +990,7 @@ public class DatabaseHelper {
saver.accept(resource);
if (resource instanceof EppResource) {
EppResource eppResource = (EppResource) resource;
persistEppResourceExtras(
eppResource, EppResourceIndex.create(Key.create(eppResource)), saver);
persistEppResourceExtras(eppResource, saver);
}
} else {
tm().put(resource);
@@ -1004,11 +998,10 @@ public class DatabaseHelper {
}
private static <R extends EppResource> void persistEppResourceExtras(
R resource, EppResourceIndex index, Consumer<ImmutableObject> saver) {
R resource, Consumer<ImmutableObject> saver) {
assertWithMessage("Cannot persist an EppResource with a missing repoId in tests")
.that(resource.getRepoId())
.isNotEmpty();
saver.accept(index);
}
/** Persists an object in the DB for tests. */
@@ -1026,25 +1019,6 @@ public class DatabaseHelper {
return tm().transact(() -> tm().loadByEntity(resource));
}
/** Persists an EPP resource with the {@link EppResourceIndex} always going into bucket one. */
public static <R extends EppResource> R persistEppResourceInFirstBucket(final R resource) {
final EppResourceIndex eppResourceIndex =
EppResourceIndex.create(Key.create(EppResourceIndexBucket.class, 1), Key.create(resource));
tm().transact(
() -> {
if (tm().isOfy()) {
Consumer<ImmutableObject> saver = tm()::put;
saver.accept(resource);
persistEppResourceExtras(resource, eppResourceIndex, saver);
} else {
tm().put(resource);
}
});
maybeAdvanceClock();
tm().clearSessionCache();
return tm().transact(() -> tm().loadByEntity(resource));
}
/** Persists the specified resources to the DB. */
public static <R extends ImmutableObject> void persistResources(final Iterable<R> resources) {
for (R resource : resources) {
@@ -1070,7 +1044,7 @@ public class DatabaseHelper {
* <p><b>Warning:</b> If you call this multiple times in a single test, you need to inject Ofy's
* clock field and forward it by a millisecond between each subsequent call.
*
* @see #persistResource(Object)
* @see #persistResource(ImmutableObject)
*/
public static <R extends EppResource> R persistEppResource(final R resource) {
checkState(!tm().inTransaction());
@@ -1095,7 +1069,7 @@ public class DatabaseHelper {
}
/**
* Returns all of the history entries that are parented off the given EppResource, casted to the
* Returns all of the history entries that are parented off the given EppResource, cast to the
* corresponding subclass.
*/
public static <T extends HistoryEntry> List<T> getHistoryEntries(
@@ -1116,7 +1090,7 @@ public class DatabaseHelper {
/**
* Returns all of the history entries that are parented off the given EppResource with the given
* type and casted to the corresponding subclass.
* type and cast to the corresponding subclass.
*/
public static <T extends HistoryEntry> ImmutableList<T> getHistoryEntriesOfType(
EppResource resource, final HistoryEntry.Type type, Class<T> subclazz) {
@@ -1135,8 +1109,8 @@ public class DatabaseHelper {
}
/**
* Returns the only history entry of the given type, casted to the corresponding subtype, and
* throws an AssertionError if there are zero or more than one.
* Returns the only history entry of the given type, cast to the corresponding subtype, and throws
* an AssertionError if there are zero or more than one.
*/
public static <T extends HistoryEntry> T getOnlyHistoryEntryOfType(
EppResource resource, final HistoryEntry.Type type, Class<T> subclazz) {
@@ -138,15 +138,6 @@ class google.registry.model.host.HostHistory {
java.lang.String reason;
org.joda.time.DateTime modificationTime;
}
class google.registry.model.index.EppResourceIndex {
@Id java.lang.String id;
@Parent com.googlecode.objectify.Key<google.registry.model.index.EppResourceIndexBucket> bucket;
com.googlecode.objectify.Key<? extends google.registry.model.EppResource> reference;
java.lang.String kind;
}
class google.registry.model.index.EppResourceIndexBucket {
@Id long bucketId;
}
class google.registry.model.reporting.HistoryEntry {
@Id java.lang.Long id;
@Parent com.googlecode.objectify.Key<? extends google.registry.model.EppResource> parent;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1
View File
@@ -125,3 +125,4 @@ V124__add_console_user.sql
V125__create_package_promotion.sql
V126__drop_autorenew_poll_message_history_id_column_in_domain_table.sql
V127__fix_user.sql
V128__fix_package_promotion_id.sql
@@ -0,0 +1,23 @@
-- Copyright 2022 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.
CREATE SEQUENCE "Package_promotion_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE "Package_promotion_id_seq" OWNED BY "PackagePromotion".package_promotion_id;
ALTER TABLE "PackagePromotion" ALTER COLUMN package_promotion_id SET DEFAULT nextval('"Package_promotion_id_seq"'::regclass);
@@ -661,6 +661,25 @@ CREATE TABLE public."PackagePromotion" (
);
--
-- Name: Package_promotion_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public."Package_promotion_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: Package_promotion_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public."Package_promotion_id_seq" OWNED BY public."PackagePromotion".package_promotion_id;
--
-- Name: PollMessage; Type: TABLE; Schema: public; Owner: -
--
@@ -1104,6 +1123,13 @@ ALTER TABLE ONLY public."ClaimsList" ALTER COLUMN revision_id SET DEFAULT nextva
ALTER TABLE ONLY public."DomainTransactionRecord" ALTER COLUMN id SET DEFAULT nextval('public."DomainTransactionRecord_id_seq"'::regclass);
--
-- Name: PackagePromotion package_promotion_id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."PackagePromotion" ALTER COLUMN package_promotion_id SET DEFAULT nextval('public."Package_promotion_id_seq"'::regclass);
--
-- Name: PremiumList revision_id; Type: DEFAULT; Schema: public; Owner: -
--
+1
View File
@@ -311,6 +311,7 @@ An EPP flow that creates a new domain resource.
* Registrar is not logged in.
* The current registry phase allows registrations only with signed marks.
* The current registry phase does not allow for general registrations.
* Package domain registered for too many years.
* Signed marks are only allowed during sunrise.
* An allocation token was provided that is invalid for premium domains.
* 2003