Refactor lock/unlock commands to use Registry Locks (#390)

* Refactor lock/unlock commands to use Registry Locks

* CR responses

* Remove unnecessary XML test files

* Add tests

* Respond to CR

* Refactor further the creation and verification of locks

* remove isUnlocked

* Responses to CR

* Fix tests

* Add admin-override back to unlocking

* Add TODO

* Fix imports
This commit is contained in:
gbrodman 2020-01-23 10:22:56 -05:00 committed by GitHub
parent 02e7106262
commit 7df3d85243
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 907 additions and 224 deletions

View file

@ -30,7 +30,7 @@ public final class RegistryLockDao {
* code (there may be two instances of the same code in the database--one after lock object
* creation and one after verification.
*/
public static RegistryLock getByVerificationCode(String verificationCode) {
public static Optional<RegistryLock> getByVerificationCode(String verificationCode) {
return jpaTm()
.transact(
() -> {
@ -42,9 +42,8 @@ public final class RegistryLockDao {
Long.class)
.setParameter("verificationCode", verificationCode)
.getSingleResult();
// TODO(gbrodman): Don't throw NPE here. Maybe NoResultException fits better?
checkNotNull(revisionId, "No registry lock with this code");
return em.find(RegistryLock.class, revisionId);
return Optional.ofNullable(revisionId)
.map(revision -> em.find(RegistryLock.class, revision));
});
}
@ -67,8 +66,8 @@ public final class RegistryLockDao {
}
/**
* Returns the most recent lock object for a given repo ID (i.e. a domain) or empty if this domain
* hasn't been locked before.
* Returns the most recent lock object for a given domain specified by repo ID, or empty if this
* domain hasn't been locked before.
*/
public static Optional<RegistryLock> getMostRecentByRepoId(String repoId) {
return jpaTm()
@ -86,6 +85,28 @@ public final class RegistryLockDao {
.findFirst());
}
/**
* Returns the most recent verified lock object for a given domain specified by repo ID, or empty
* if no lock has ever been finalized for this domain. This is different from {@link
* #getMostRecentByRepoId(String)} in that it only returns verified locks.
*/
public static Optional<RegistryLock> getMostRecentVerifiedLockByRepoId(String repoId) {
return jpaTm()
.transact(
() ->
jpaTm()
.getEntityManager()
.createQuery(
"SELECT lock FROM RegistryLock lock WHERE lock.repoId = :repoId AND"
+ " lock.lockCompletionTimestamp IS NOT NULL ORDER BY lock.revisionId"
+ " DESC",
RegistryLock.class)
.setParameter("repoId", repoId)
.setMaxResults(1)
.getResultStream()
.findFirst());
}
public static RegistryLock save(RegistryLock registryLock) {
checkNotNull(registryLock, "Null registry lock cannot be saved");
return jpaTm().transact(() -> jpaTm().getEntityManager().merge(registryLock));

View file

@ -15,6 +15,7 @@
package google.registry.schema.domain;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.util.DateTimeUtils.isBeforeOrAt;
import static google.registry.util.DateTimeUtils.toZonedDateTime;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
@ -22,6 +23,7 @@ import google.registry.model.Buildable;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.ImmutableObject;
import google.registry.model.UpdateAutoTimestamp;
import google.registry.util.Clock;
import google.registry.util.DateTimeUtils;
import java.time.ZonedDateTime;
import java.util.Optional;
@ -182,6 +184,20 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
return lockCompletionTimestamp != null && unlockCompletionTimestamp == null;
}
/** Returns true iff the lock was requested >= 1 hour ago and has not been verified. */
public boolean isLockRequestExpired(Clock clock) {
return !getLockCompletionTimestamp().isPresent()
&& isBeforeOrAt(getLockRequestTimestamp(), clock.nowUtc().minusHours(1));
}
/** Returns true iff the unlock was requested >= 1 hour ago and has not been verified. */
public boolean isUnlockRequestExpired(Clock clock) {
Optional<DateTime> unlockRequestTimestamp = getUnlockRequestTimestamp();
return unlockRequestTimestamp.isPresent()
&& !getUnlockCompletionTimestamp().isPresent()
&& isBeforeOrAt(unlockRequestTimestamp.get(), clock.nowUtc().minusHours(1));
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));

View file

@ -0,0 +1,269 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.EppResourceUtils.loadByForeignKeyCached;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STATUSES;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.googlecode.objectify.Key;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.domain.DomainBase;
import google.registry.model.registry.Registry;
import google.registry.model.registry.RegistryLockDao;
import google.registry.model.reporting.HistoryEntry;
import google.registry.schema.domain.RegistryLock;
import google.registry.util.Clock;
import java.util.Optional;
import java.util.UUID;
import javax.annotation.Nullable;
/**
* Utility class for validating and applying {@link RegistryLock}s.
*
* <p>For both locks and unlocks, a lock must be requested via the createRegistry*Requst methods
* then verified through the verifyAndApply* methods. These methods will verify that the domain in
* question is in a lock/unlockable state and will return the lock object.
*/
public final class DomainLockUtils {
private DomainLockUtils() {}
public static RegistryLock createRegistryLockRequest(
String domainName,
String registrarId,
@Nullable String registrarPocId,
boolean isAdmin,
Clock clock) {
DomainBase domainBase = getDomain(domainName, clock);
verifyDomainNotLocked(domainBase);
// Multiple pending actions are not allowed
RegistryLockDao.getMostRecentByRepoId(domainBase.getRepoId())
.ifPresent(
previousLock ->
checkArgument(
previousLock.isLockRequestExpired(clock)
|| previousLock.getUnlockCompletionTimestamp().isPresent(),
"A pending or completed lock action already exists for %s",
previousLock.getDomainName()));
RegistryLock lock =
new RegistryLock.Builder()
.setVerificationCode(UUID.randomUUID().toString())
.setDomainName(domainName)
.setRepoId(domainBase.getRepoId())
.setRegistrarId(registrarId)
.setRegistrarPocId(registrarPocId)
.isSuperuser(isAdmin)
.build();
return RegistryLockDao.save(lock);
}
public static RegistryLock createRegistryUnlockRequest(
String domainName, String registrarId, boolean isAdmin, Clock clock) {
DomainBase domainBase = getDomain(domainName, clock);
Optional<RegistryLock> lockOptional =
RegistryLockDao.getMostRecentVerifiedLockByRepoId(domainBase.getRepoId());
RegistryLock.Builder newLockBuilder;
if (isAdmin) {
// Admins should always be able to unlock domains in case we get in a bad state
// TODO(b/147411297): Remove the admin checks / failsafes once we have migrated existing
// locked domains to have lock objects
newLockBuilder =
lockOptional
.map(RegistryLock::asBuilder)
.orElse(
new RegistryLock.Builder()
.setRepoId(domainBase.getRepoId())
.setDomainName(domainName)
.setLockCompletionTimestamp(clock.nowUtc())
.setRegistrarId(registrarId));
} else {
verifyDomainLocked(domainBase);
RegistryLock lock =
lockOptional.orElseThrow(
() ->
new IllegalArgumentException(
String.format("No lock object for domain %s", domainName)));
checkArgument(
lock.isLocked(), "Lock object for domain %s is not currently locked", domainName);
checkArgument(
!lock.getUnlockRequestTimestamp().isPresent() || lock.isUnlockRequestExpired(clock),
"A pending unlock action already exists for %s",
domainName);
checkArgument(
lock.getRegistrarId().equals(registrarId),
"Lock object does not have registrar ID %s",
registrarId);
checkArgument(
!lock.isSuperuser(), "Non-admin user cannot unlock admin-locked domain %s", domainName);
newLockBuilder = lock.asBuilder();
}
RegistryLock newLock =
newLockBuilder
.setVerificationCode(UUID.randomUUID().toString())
.isSuperuser(isAdmin)
.setUnlockRequestTimestamp(clock.nowUtc())
.setRegistrarId(registrarId)
.build();
return RegistryLockDao.save(newLock);
}
public static RegistryLock verifyAndApplyLock(
String verificationCode, boolean isAdmin, Clock clock) {
return jpaTm()
.transact(
() -> {
RegistryLock lock = getByVerificationCode(verificationCode);
checkArgument(
!lock.getLockCompletionTimestamp().isPresent(),
"Domain %s is already locked",
lock.getDomainName());
checkArgument(
!lock.isLockRequestExpired(clock),
"The pending lock has expired; please try again");
checkArgument(
!lock.isSuperuser() || isAdmin, "Non-admin user cannot complete admin lock");
RegistryLock newLock =
RegistryLockDao.save(
lock.asBuilder().setLockCompletionTimestamp(clock.nowUtc()).build());
tm().transact(() -> applyLockStatuses(newLock, clock));
return newLock;
});
}
public static RegistryLock verifyAndApplyUnlock(
String verificationCode, boolean isAdmin, Clock clock) {
return jpaTm()
.transact(
() -> {
RegistryLock lock = getByVerificationCode(verificationCode);
checkArgument(
!lock.getUnlockCompletionTimestamp().isPresent(),
"Domain %s is already unlocked",
lock.getDomainName());
checkArgument(
!lock.isUnlockRequestExpired(clock),
"The pending unlock has expired; please try again");
checkArgument(
isAdmin || !lock.isSuperuser(), "Non-admin user cannot complete admin unlock");
RegistryLock newLock =
RegistryLockDao.save(
lock.asBuilder().setUnlockCompletionTimestamp(clock.nowUtc()).build());
tm().transact(() -> removeLockStatuses(newLock, isAdmin, clock));
return newLock;
});
}
private static void verifyDomainNotLocked(DomainBase domainBase) {
checkArgument(
!domainBase.getStatusValues().containsAll(REGISTRY_LOCK_STATUSES),
"Domain %s is already locked",
domainBase.getFullyQualifiedDomainName());
}
private static void verifyDomainLocked(DomainBase domainBase) {
checkArgument(
!Sets.intersection(domainBase.getStatusValues(), REGISTRY_LOCK_STATUSES).isEmpty(),
"Domain %s is already unlocked",
domainBase.getFullyQualifiedDomainName());
}
private static DomainBase getDomain(String domainName, Clock clock) {
return loadByForeignKeyCached(DomainBase.class, domainName, clock.nowUtc())
.orElseThrow(
() -> new IllegalArgumentException(String.format("Unknown domain %s", domainName)));
}
private static RegistryLock getByVerificationCode(String verificationCode) {
return RegistryLockDao.getByVerificationCode(verificationCode)
.orElseThrow(
() ->
new IllegalArgumentException(
String.format("Invalid verification code %s", verificationCode)));
}
private static void applyLockStatuses(RegistryLock lock, Clock clock) {
DomainBase domain = getDomain(lock.getDomainName(), clock);
verifyDomainNotLocked(domain);
DomainBase newDomain =
domain
.asBuilder()
.setStatusValues(
ImmutableSet.copyOf(Sets.union(domain.getStatusValues(), REGISTRY_LOCK_STATUSES)))
.build();
saveEntities(newDomain, lock, clock);
}
private static void removeLockStatuses(RegistryLock lock, boolean isAdmin, Clock clock) {
DomainBase domain = getDomain(lock.getDomainName(), clock);
if (!isAdmin) {
verifyDomainLocked(domain);
}
DomainBase newDomain =
domain
.asBuilder()
.setStatusValues(
ImmutableSet.copyOf(
Sets.difference(domain.getStatusValues(), REGISTRY_LOCK_STATUSES)))
.build();
saveEntities(newDomain, lock, clock);
}
private static void saveEntities(DomainBase domain, RegistryLock lock, Clock clock) {
String reason = "Lock or unlock of a domain through a RegistryLock operation";
HistoryEntry historyEntry =
new HistoryEntry.Builder()
.setClientId(domain.getCurrentSponsorClientId())
.setBySuperuser(lock.isSuperuser())
.setRequestedByRegistrar(!lock.isSuperuser())
.setType(HistoryEntry.Type.DOMAIN_UPDATE)
.setModificationTime(clock.nowUtc())
.setParent(Key.create(domain))
.setReason(reason)
.build();
ofy().save().entities(domain, historyEntry);
if (!lock.isSuperuser()) { // admin actions shouldn't affect billing
BillingEvent.OneTime oneTime =
new BillingEvent.OneTime.Builder()
.setReason(Reason.SERVER_STATUS)
.setTargetId(domain.getForeignKey())
.setClientId(domain.getCurrentSponsorClientId())
.setCost(Registry.get(domain.getTld()).getServerStatusChangeCost())
.setEventTime(clock.nowUtc())
.setBillingTime(clock.nowUtc())
.setParent(historyEntry)
.build();
ofy().save().entity(oneTime);
}
}
}

View file

@ -14,25 +14,19 @@
package google.registry.tools;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import static org.joda.time.DateTimeZone.UTC;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.template.soy.data.SoyMapData;
import google.registry.model.domain.DomainBase;
import google.registry.model.eppcommon.StatusValue;
import google.registry.tools.soy.DomainUpdateSoyInfo;
import java.util.Optional;
import google.registry.schema.domain.RegistryLock;
import org.joda.time.DateTime;
/**
* A command to registry lock domain names via EPP.
* A command to registry lock domain names.
*
* <p>A registry lock consists of server-side statuses preventing deletes, updates, and transfers.
*/
@ -42,41 +36,35 @@ public class LockDomainCommand extends LockOrUnlockDomainCommand {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Override
protected void initMutatingEppToolCommand() {
protected ImmutableSet<String> getRelevantDomains() {
// Project all domains as of the same time so that argument order doesn't affect behavior.
DateTime now = DateTime.now(UTC);
DateTime now = clock.nowUtc();
ImmutableSet.Builder<String> relevantDomains = new ImmutableSet.Builder<>();
for (String domain : getDomains()) {
Optional<DomainBase> domainBase = loadByForeignKey(DomainBase.class, domain, now);
checkArgumentPresent(domainBase, "Domain '%s' does not exist or is deleted", domain);
DomainBase domainBase =
loadByForeignKey(DomainBase.class, domain, now)
.orElseThrow(
() ->
new IllegalArgumentException(
String.format("Domain '%s' does not exist or is deleted", domain)));
ImmutableSet<StatusValue> statusesToAdd =
Sets.difference(REGISTRY_LOCK_STATUSES, domainBase.get().getStatusValues())
.immutableCopy();
Sets.difference(REGISTRY_LOCK_STATUSES, domainBase.getStatusValues()).immutableCopy();
if (statusesToAdd.isEmpty()) {
logger.atInfo().log("Domain '%s' is already locked and needs no updates.", domain);
continue;
}
setSoyTemplate(DomainUpdateSoyInfo.getInstance(), DomainUpdateSoyInfo.DOMAINUPDATE);
addSoyRecord(
clientId,
new SoyMapData(
"domain", domain,
"add", true,
"addNameservers", ImmutableList.of(),
"addAdmins", ImmutableList.of(),
"addTechs", ImmutableList.of(),
"addStatuses",
statusesToAdd.stream().map(StatusValue::getXmlName).collect(toImmutableList()),
"remove", false,
"removeNameservers", ImmutableList.of(),
"removeAdmins", ImmutableList.of(),
"removeTechs", ImmutableList.of(),
"removeStatuses", ImmutableList.of(),
"change", false,
"secdns", false,
"addDsRecords", ImmutableList.of(),
"removeDsRecords", ImmutableList.of(),
"removeAllDsRecords", false));
relevantDomains.add(domain);
}
return relevantDomains.build();
}
@Override
protected RegistryLock createLock(String domain) {
return DomainLockUtils.createRegistryLockRequest(domain, clientId, null, true, clock);
}
@Override
protected void finalizeLockOrUnlockRequest(RegistryLock lock) {
DomainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), true, clock);
}
}

View file

@ -22,16 +22,23 @@ import static google.registry.util.CollectionUtils.findDuplicates;
import com.beust.jcommander.Parameter;
import com.google.common.base.Joiner;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.eppcommon.StatusValue;
import google.registry.schema.domain.RegistryLock;
import google.registry.util.Clock;
import java.util.List;
import javax.inject.Inject;
/** Shared base class for commands to registry lock or unlock a domain via EPP. */
public abstract class LockOrUnlockDomainCommand extends MutatingEppToolCommand {
public abstract class LockOrUnlockDomainCommand extends ConfirmingCommand
implements CommandWithCloudSql {
protected static final ImmutableSet<StatusValue> REGISTRY_LOCK_STATUSES =
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
static final ImmutableSet<StatusValue> REGISTRY_LOCK_STATUSES =
ImmutableSet.of(
SERVER_DELETE_PROHIBITED, SERVER_TRANSFER_PROHIBITED, SERVER_UPDATE_PROHIBITED);
@ -48,23 +55,52 @@ public abstract class LockOrUnlockDomainCommand extends MutatingEppToolCommand {
@Config("registryAdminClientId")
String registryAdminClientId;
@Inject Clock clock;
protected ImmutableSet<String> relevantDomains = ImmutableSet.of();
protected ImmutableSet<String> getDomains() {
return ImmutableSet.copyOf(mainParameters);
}
@Override
protected void initEppToolCommand() throws Exception {
// Superuser status is required to update registry lock statuses.
superuser = true;
protected void init() {
// Default clientId to the registry registrar account if otherwise unspecified.
if (clientId == null) {
clientId = registryAdminClientId;
}
String duplicates = Joiner.on(", ").join(findDuplicates(mainParameters));
checkArgument(duplicates.isEmpty(), "Duplicate domain arguments found: '%s'", duplicates);
initMutatingEppToolCommand();
System.out.println(
"== ENSURE THAT YOU HAVE AUTHENTICATED THE REGISTRAR BEFORE RUNNING THIS COMMAND ==");
relevantDomains = getRelevantDomains();
}
@Override
protected String execute() {
int failures = 0;
for (String domain : relevantDomains) {
try {
RegistryLock lock = createLock(domain);
finalizeLockOrUnlockRequest(lock);
} catch (Throwable t) {
Throwable rootCause = Throwables.getRootCause(t);
logger.atSevere().withCause(rootCause).log("Error when (un)locking domain %s.", domain);
failures++;
}
}
if (failures == 0) {
return String.format("Successfully locked/unlocked %d domains.", relevantDomains.size());
} else {
return String.format(
"Successfully locked/unlocked %d domains with %d failures.",
relevantDomains.size() - failures, failures);
}
}
protected abstract ImmutableSet<String> getRelevantDomains();
protected abstract RegistryLock createLock(String domain);
protected abstract void finalizeLockOrUnlockRequest(RegistryLock lock);
}

View file

@ -14,25 +14,19 @@
package google.registry.tools;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import static org.joda.time.DateTimeZone.UTC;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.template.soy.data.SoyMapData;
import google.registry.model.domain.DomainBase;
import google.registry.model.eppcommon.StatusValue;
import google.registry.tools.soy.DomainUpdateSoyInfo;
import java.util.Optional;
import google.registry.schema.domain.RegistryLock;
import org.joda.time.DateTime;
/**
* A command to registry unlock domain names via EPP.
* A command to registry unlock domain names.
*
* <p>A registry lock consists of server-side statuses preventing deletes, updates, and transfers.
*/
@ -42,41 +36,35 @@ public class UnlockDomainCommand extends LockOrUnlockDomainCommand {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Override
protected void initMutatingEppToolCommand() {
protected ImmutableSet<String> getRelevantDomains() {
// Project all domains as of the same time so that argument order doesn't affect behavior.
DateTime now = DateTime.now(UTC);
DateTime now = clock.nowUtc();
ImmutableSet.Builder<String> relevantDomains = new ImmutableSet.Builder<>();
for (String domain : getDomains()) {
Optional<DomainBase> domainBase = loadByForeignKey(DomainBase.class, domain, now);
checkArgumentPresent(domainBase, "Domain '%s' does not exist or is deleted", domain);
DomainBase domainBase =
loadByForeignKey(DomainBase.class, domain, now)
.orElseThrow(
() ->
new IllegalArgumentException(
String.format("Domain '%s' does not exist or is deleted", domain)));
ImmutableSet<StatusValue> statusesToRemove =
Sets.intersection(domainBase.get().getStatusValues(), REGISTRY_LOCK_STATUSES)
.immutableCopy();
Sets.intersection(domainBase.getStatusValues(), REGISTRY_LOCK_STATUSES).immutableCopy();
if (statusesToRemove.isEmpty()) {
logger.atInfo().log("Domain '%s' is already unlocked and needs no updates.", domain);
continue;
}
setSoyTemplate(DomainUpdateSoyInfo.getInstance(), DomainUpdateSoyInfo.DOMAINUPDATE);
addSoyRecord(
clientId,
new SoyMapData(
"domain", domain,
"add", false,
"addNameservers", ImmutableList.of(),
"addAdmins", ImmutableList.of(),
"addTechs", ImmutableList.of(),
"addStatuses", ImmutableList.of(),
"remove", true,
"removeNameservers", ImmutableList.of(),
"removeAdmins", ImmutableList.of(),
"removeTechs", ImmutableList.of(),
"removeStatuses",
statusesToRemove.stream().map(StatusValue::getXmlName).collect(toImmutableList()),
"change", false,
"secdns", false,
"addDsRecords", ImmutableList.of(),
"removeDsRecords", ImmutableList.of(),
"removeAllDsRecords", false));
relevantDomains.add(domain);
}
return relevantDomains.build();
}
@Override
protected RegistryLock createLock(String domain) {
return DomainLockUtils.createRegistryUnlockRequest(domain, clientId, true, clock);
}
@Override
protected void finalizeLockOrUnlockRequest(RegistryLock lock) {
DomainLockUtils.verifyAndApplyUnlock(lock.getVerificationCode(), true, clock);
}
}

View file

@ -44,23 +44,13 @@ public final class RegistryLockDaoTest {
public void testSaveAndLoad_success() {
RegistryLock lock = createLock();
RegistryLockDao.save(lock);
RegistryLock fromDatabase = RegistryLockDao.getByVerificationCode(lock.getVerificationCode());
RegistryLock fromDatabase =
RegistryLockDao.getByVerificationCode(lock.getVerificationCode()).get();
assertThat(fromDatabase.getDomainName()).isEqualTo(lock.getDomainName());
assertThat(fromDatabase.getVerificationCode()).isEqualTo(lock.getVerificationCode());
assertThat(fromDatabase.getLastUpdateTimestamp()).isEqualTo(jpaRule.getTxnClock().nowUtc());
}
@Test
public void testSaveAndLoad_failure_differentCode() {
RegistryLock lock = createLock();
RegistryLockDao.save(lock);
NullPointerException thrown =
assertThrows(
NullPointerException.class,
() -> RegistryLockDao.getByVerificationCode(UUID.randomUUID().toString()));
assertThat(thrown).hasMessageThat().isEqualTo("No registry lock with this code");
}
@Test
public void testSaveTwiceAndLoad_returnsLatest() {
RegistryLock lock = createLock();
@ -70,7 +60,7 @@ public final class RegistryLockDaoTest {
.transact(
() -> {
RegistryLock updatedLock =
RegistryLockDao.getByVerificationCode(lock.getVerificationCode());
RegistryLockDao.getByVerificationCode(lock.getVerificationCode()).get();
RegistryLockDao.save(
updatedLock
.asBuilder()
@ -81,7 +71,7 @@ public final class RegistryLockDaoTest {
.transact(
() -> {
RegistryLock fromDatabase =
RegistryLockDao.getByVerificationCode(lock.getVerificationCode());
RegistryLockDao.getByVerificationCode(lock.getVerificationCode()).get();
assertThat(fromDatabase.getLockCompletionTimestamp().get())
.isEqualTo(jpaRule.getTxnClock().nowUtc());
assertThat(fromDatabase.getLastUpdateTimestamp())
@ -100,7 +90,8 @@ public final class RegistryLockDaoTest {
.setUnlockCompletionTimestamp(jpaRule.getTxnClock().nowUtc())
.build());
RegistryLockDao.save(lock);
RegistryLock fromDatabase = RegistryLockDao.getByVerificationCode(lock.getVerificationCode());
RegistryLock fromDatabase =
RegistryLockDao.getByVerificationCode(lock.getVerificationCode()).get();
assertThat(fromDatabase.getUnlockRequestTimestamp())
.isEqualTo(Optional.of(jpaRule.getTxnClock().nowUtc()));
assertThat(fromDatabase.getUnlockCompletionTimestamp())
@ -119,7 +110,7 @@ public final class RegistryLockDaoTest {
.transact(
() -> {
RegistryLock fromDatabase =
RegistryLockDao.getByVerificationCode(lock.getVerificationCode());
RegistryLockDao.getByVerificationCode(lock.getVerificationCode()).get();
assertThat(fromDatabase.getLockCompletionTimestamp())
.isEqualTo(Optional.of(jpaRule.getTxnClock().nowUtc()));
});
@ -130,6 +121,11 @@ public final class RegistryLockDaoTest {
assertThrows(NullPointerException.class, () -> RegistryLockDao.save(null));
}
@Test
public void getLock_unknownCode() {
assertThat(RegistryLockDao.getByVerificationCode("hi").isPresent()).isFalse();
}
@Test
public void testLoad_lockedDomains_byRegistrarId() {
RegistryLock lock =
@ -180,6 +176,28 @@ public final class RegistryLockDaoTest {
assertThat(RegistryLockDao.getMostRecentByRepoId("nonexistent").isPresent()).isFalse();
}
@Test
public void testLoad_verified_byRepoId() {
RegistryLock completedLock =
createLock().asBuilder().setLockCompletionTimestamp(jpaRule.getTxnClock().nowUtc()).build();
RegistryLockDao.save(completedLock);
jpaRule.getTxnClock().advanceOneMilli();
RegistryLock inProgressLock = createLock();
RegistryLockDao.save(inProgressLock);
Optional<RegistryLock> mostRecent = RegistryLockDao.getMostRecentVerifiedLockByRepoId("repoId");
assertThat(mostRecent.isPresent()).isTrue();
assertThat(mostRecent.get().isLocked()).isTrue();
}
@Test
public void testLoad_verified_byRepoId_empty() {
RegistryLockDao.save(createLock());
Optional<RegistryLock> mostRecent = RegistryLockDao.getMostRecentVerifiedLockByRepoId("repoId");
assertThat(mostRecent.isPresent()).isFalse();
}
private RegistryLock createLock() {
return new RegistryLock.Builder()
.setRepoId("repoId")

View file

@ -22,6 +22,9 @@ import google.registry.schema.tld.PremiumListUtilsTest;
import google.registry.schema.tld.ReservedListDaoTest;
import google.registry.schema.tmch.ClaimsListDaoTest;
import google.registry.tools.CreateReservedListCommandTest;
import google.registry.tools.DomainLockUtilsTest;
import google.registry.tools.LockDomainCommandTest;
import google.registry.tools.UnlockDomainCommandTest;
import google.registry.tools.UpdateReservedListCommandTest;
import google.registry.tools.server.CreatePremiumListActionTest;
import google.registry.tools.server.UpdatePremiumListActionTest;
@ -42,14 +45,17 @@ import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({
ClaimsListDaoTest.class,
CreatePremiumListActionTest.class,
CreateReservedListCommandTest.class,
CursorDaoTest.class,
CreatePremiumListActionTest.class,
DomainLockUtilsTest.class,
LockDomainCommandTest.class,
PremiumListDaoTest.class,
PremiumListUtilsTest.class,
RegistryLockDaoTest.class,
RegistryLockGetActionTest.class,
ReservedListDaoTest.class,
UnlockDomainCommandTest.class,
UpdatePremiumListActionTest.class,
UpdateReservedListCommandTest.class
})

View file

@ -948,7 +948,7 @@ public class DatastoreHelper {
* Returns all of the history entries that are parented off the given EppResource with the given
* type.
*/
public static List<HistoryEntry> getHistoryEntriesOfType(
public static ImmutableList<HistoryEntry> getHistoryEntriesOfType(
EppResource resource, final HistoryEntry.Type type) {
return getHistoryEntries(resource)
.stream()

View file

@ -0,0 +1,361 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.testing.DatastoreHelper.createTlds;
import static google.registry.testing.DatastoreHelper.getHistoryEntriesOfType;
import static google.registry.testing.DatastoreHelper.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatastoreHelper.newDomainBase;
import static google.registry.testing.DatastoreHelper.persistActiveHost;
import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STATUSES;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableList;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.domain.DomainBase;
import google.registry.model.host.HostResource;
import google.registry.model.registry.Registry;
import google.registry.model.registry.RegistryLockDao;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationTestRule;
import google.registry.schema.domain.RegistryLock;
import google.registry.testing.AppEngineRule;
import google.registry.testing.DatastoreHelper;
import google.registry.testing.FakeClock;
import google.registry.testing.UserInfo;
import java.util.Set;
import java.util.stream.Collectors;
import org.joda.time.Duration;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link google.registry.tools.DomainLockUtils}. */
@RunWith(JUnit4.class)
public final class DomainLockUtilsTest {
private static final String DOMAIN_NAME = "example.tld";
private static final String POC_ID = "marla.singer@example.com";
@Rule
public final AppEngineRule appEngineRule =
AppEngineRule.builder()
.withDatastore()
.withUserService(UserInfo.create(POC_ID, "12345"))
.build();
@Rule
public final JpaIntegrationTestRule jpaRule =
new JpaTestRules.Builder().buildIntegrationTestRule();
private final FakeClock clock = jpaRule.getTxnClock();
private DomainBase domain;
@Before
public void setup() {
createTlds("tld", "net");
HostResource host = persistActiveHost("ns1.example.net");
domain = persistResource(newDomainBase(DOMAIN_NAME, host));
}
@Test
public void testSuccess_createLock() {
DomainLockUtils.createRegistryLockRequest(DOMAIN_NAME, "TheRegistrar", POC_ID, false, clock);
}
@Test
public void testSuccess_createUnlock() {
RegistryLock lock =
DomainLockUtils.createRegistryLockRequest(
DOMAIN_NAME, "TheRegistrar", POC_ID, false, clock);
DomainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), false, clock);
DomainLockUtils.createRegistryUnlockRequest(DOMAIN_NAME, "TheRegistrar", false, clock);
}
@Test
public void testSuccess_createUnlock_adminUnlockingAdmin() {
RegistryLock lock =
DomainLockUtils.createRegistryLockRequest(DOMAIN_NAME, "TheRegistrar", null, true, clock);
DomainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), true, clock);
DomainLockUtils.createRegistryUnlockRequest(DOMAIN_NAME, "TheRegistrar", true, clock);
}
@Test
public void testSuccess_createLock_previousLockExpired() {
DomainLockUtils.createRegistryLockRequest(DOMAIN_NAME, "TheRegistrar", POC_ID, false, clock);
clock.advanceBy(Duration.standardDays(1));
DomainLockUtils.createRegistryLockRequest(DOMAIN_NAME, "TheRegistrar", POC_ID, false, clock);
}
@Test
public void testSuccess_applyLockDomain() {
RegistryLock lock =
DomainLockUtils.createRegistryLockRequest(
DOMAIN_NAME, "TheRegistrar", POC_ID, false, clock);
DomainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), false, clock);
assertThat(reloadDomain().getStatusValues()).containsExactlyElementsIn(REGISTRY_LOCK_STATUSES);
HistoryEntry historyEntry = getOnlyHistoryEntryOfType(domain, HistoryEntry.Type.DOMAIN_UPDATE);
assertThat(historyEntry.getRequestedByRegistrar()).isTrue();
assertThat(historyEntry.getBySuperuser()).isFalse();
assertThat(historyEntry.getReason())
.isEqualTo("Lock or unlock of a domain through a RegistryLock operation");
assertBillingEvent(historyEntry);
}
@Test
public void testSuccess_applyUnlockDomain() {
RegistryLock lock =
DomainLockUtils.createRegistryLockRequest(
DOMAIN_NAME, "TheRegistrar", POC_ID, false, clock);
DomainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), false, clock);
RegistryLock unlock =
DomainLockUtils.createRegistryUnlockRequest(DOMAIN_NAME, "TheRegistrar", false, clock);
DomainLockUtils.verifyAndApplyUnlock(unlock.getVerificationCode(), false, clock);
assertThat(reloadDomain().getStatusValues()).containsNoneIn(REGISTRY_LOCK_STATUSES);
ImmutableList<HistoryEntry> historyEntries =
getHistoryEntriesOfType(domain, HistoryEntry.Type.DOMAIN_UPDATE);
assertThat(historyEntries.size()).isEqualTo(2);
historyEntries.forEach(
entry -> {
assertThat(entry.getRequestedByRegistrar()).isTrue();
assertThat(entry.getBySuperuser()).isFalse();
assertThat(entry.getReason())
.isEqualTo("Lock or unlock of a domain through a RegistryLock operation");
});
assertBillingEvents(historyEntries);
}
@Test
public void testSuccess_applyAdminLock_onlyHistoryEntry() {
RegistryLock lock =
DomainLockUtils.createRegistryLockRequest(DOMAIN_NAME, "TheRegistrar", null, true, clock);
DomainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), true, clock);
HistoryEntry historyEntry = getOnlyHistoryEntryOfType(domain, HistoryEntry.Type.DOMAIN_UPDATE);
assertThat(historyEntry.getRequestedByRegistrar()).isFalse();
assertThat(historyEntry.getBySuperuser()).isTrue();
DatastoreHelper.assertNoBillingEvents();
}
@Test
public void testFailure_createUnlock_alreadyPendingUnlock() {
RegistryLock lock =
DomainLockUtils.createRegistryLockRequest(
DOMAIN_NAME, "TheRegistrar", POC_ID, false, clock);
DomainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), false, clock);
DomainLockUtils.createRegistryUnlockRequest(DOMAIN_NAME, "TheRegistrar", false, clock);
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
DomainLockUtils.createRegistryUnlockRequest(
DOMAIN_NAME, "TheRegistrar", false, clock)))
.hasMessageThat()
.isEqualTo("A pending unlock action already exists for example.tld");
}
@Test
public void testFailure_createUnlock_nonAdminUnlockingAdmin() {
RegistryLock lock =
DomainLockUtils.createRegistryLockRequest(DOMAIN_NAME, "TheRegistrar", null, true, clock);
DomainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), true, clock);
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
DomainLockUtils.createRegistryUnlockRequest(
DOMAIN_NAME, "TheRegistrar", false, clock)))
.hasMessageThat()
.isEqualTo("Non-admin user cannot unlock admin-locked domain example.tld");
}
@Test
public void testFailure_createLock_unknownDomain() {
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
DomainLockUtils.createRegistryLockRequest(
"asdf.tld", "TheRegistrar", POC_ID, false, clock)))
.hasMessageThat()
.isEqualTo("Unknown domain asdf.tld");
}
@Test
public void testFailure_createLock_alreadyPendingLock() {
DomainLockUtils.createRegistryLockRequest(DOMAIN_NAME, "TheRegistrar", POC_ID, false, clock);
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
DomainLockUtils.createRegistryLockRequest(
DOMAIN_NAME, "TheRegistrar", POC_ID, false, clock)))
.hasMessageThat()
.isEqualTo("A pending or completed lock action already exists for example.tld");
}
@Test
public void testFailure_createLock_alreadyLocked() {
persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
DomainLockUtils.createRegistryLockRequest(
DOMAIN_NAME, "TheRegistrar", POC_ID, false, clock)))
.hasMessageThat()
.isEqualTo("Domain example.tld is already locked");
}
@Test
public void testFailure_createUnlock_alreadyUnlocked() {
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
DomainLockUtils.createRegistryUnlockRequest(
DOMAIN_NAME, "TheRegistrar", false, clock)))
.hasMessageThat()
.isEqualTo("Domain example.tld is already unlocked");
}
@Test
public void testFailure_applyLock_alreadyApplied() {
RegistryLock lock =
DomainLockUtils.createRegistryLockRequest(
DOMAIN_NAME, "TheRegistrar", POC_ID, false, clock);
DomainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), false, clock);
domain = reloadDomain();
assertThat(
assertThrows(
IllegalArgumentException.class,
() -> DomainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), false, clock)))
.hasMessageThat()
.isEqualTo("Domain example.tld is already locked");
assertNoDomainChanges();
}
@Test
public void testFailure_applyLock_expired() {
RegistryLock lock =
DomainLockUtils.createRegistryLockRequest(
DOMAIN_NAME, "TheRegistrar", POC_ID, false, clock);
clock.advanceBy(Duration.standardDays(1));
assertThat(
assertThrows(
IllegalArgumentException.class,
() -> DomainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), true, clock)))
.hasMessageThat()
.isEqualTo("The pending lock has expired; please try again");
assertNoDomainChanges();
}
@Test
public void testFailure_applyLock_nonAdmin_applyAdminLock() {
RegistryLock lock =
DomainLockUtils.createRegistryLockRequest(DOMAIN_NAME, "TheRegistrar", null, true, clock);
assertThat(
assertThrows(
IllegalArgumentException.class,
() -> DomainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), false, clock)))
.hasMessageThat()
.isEqualTo("Non-admin user cannot complete admin lock");
assertNoDomainChanges();
}
@Test
public void testFailure_applyUnlock_alreadyUnlocked() {
RegistryLock lock =
DomainLockUtils.createRegistryLockRequest(
DOMAIN_NAME, "TheRegistrar", POC_ID, false, clock);
DomainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), false, clock);
RegistryLock unlock =
DomainLockUtils.createRegistryUnlockRequest(DOMAIN_NAME, "TheRegistrar", false, clock);
DomainLockUtils.verifyAndApplyUnlock(unlock.getVerificationCode(), false, clock);
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
DomainLockUtils.verifyAndApplyUnlock(
unlock.getVerificationCode(), false, clock)))
.hasMessageThat()
.isEqualTo("Domain example.tld is already unlocked");
assertNoDomainChanges();
}
@Test
public void testFailure_applyLock_alreadyLocked() {
RegistryLock lock =
DomainLockUtils.createRegistryLockRequest(
DOMAIN_NAME, "TheRegistrar", POC_ID, false, clock);
String verificationCode = lock.getVerificationCode();
// reload to pick up modification times, etc
lock = RegistryLockDao.getByVerificationCode(verificationCode).get();
domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
assertThat(
assertThrows(
IllegalArgumentException.class,
() -> DomainLockUtils.verifyAndApplyLock(verificationCode, false, clock)))
.hasMessageThat()
.isEqualTo("Domain example.tld is already locked");
// Failure during Datastore portion shouldn't affect the SQL object
RegistryLock afterAction =
RegistryLockDao.getByVerificationCode(lock.getVerificationCode()).get();
assertThat(afterAction).isEqualTo(lock);
assertNoDomainChanges();
}
private DomainBase reloadDomain() {
return ofy().load().entity(domain).now();
}
private void assertNoDomainChanges() {
assertThat(reloadDomain()).isEqualTo(domain);
}
private void assertBillingEvent(HistoryEntry historyEntry) {
assertBillingEvents(ImmutableList.of(historyEntry));
}
private void assertBillingEvents(ImmutableList<HistoryEntry> historyEntries) {
Set<BillingEvent> expectedEvents =
historyEntries.stream()
.map(
entry ->
new BillingEvent.OneTime.Builder()
.setReason(Reason.SERVER_STATUS)
.setTargetId(domain.getForeignKey())
.setClientId(domain.getCurrentSponsorClientId())
.setCost(Registry.get(domain.getTld()).getServerStatusChangeCost())
.setEventTime(clock.nowUtc())
.setBillingTime(clock.nowUtc())
.setParent(entry)
.build())
.collect(Collectors.toSet());
DatastoreHelper.assertBillingEvents(expectedEvents);
}
}

View file

@ -16,6 +16,7 @@ package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.eppcommon.StatusValue.SERVER_TRANSFER_PROHIBITED;
import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.newDomainBase;
import static google.registry.testing.DatastoreHelper.persistActiveDomain;
import static google.registry.testing.DatastoreHelper.persistNewRegistrar;
@ -24,55 +25,75 @@ import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STAT
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import google.registry.model.domain.DomainBase;
import google.registry.model.registrar.Registrar.Type;
import google.registry.model.registry.RegistryLockDao;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationTestRule;
import google.registry.testing.FakeClock;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
/** Unit tests for {@link LockDomainCommand}. */
public class LockDomainCommandTest extends EppToolCommandTestCase<LockDomainCommand> {
public class LockDomainCommandTest extends CommandTestCase<LockDomainCommand> {
@Rule
public final JpaIntegrationTestRule jpaRule =
new JpaTestRules.Builder().buildIntegrationTestRule();
@Before
public void before() {
eppVerifier.expectSuperuser();
persistNewRegistrar("adminreg", "Admin Registrar", Type.REAL, 693L);
createTld("tld");
command.registryAdminClientId = "adminreg";
command.clock = new FakeClock();
}
@Test
public void testSuccess_sendsCorrectEppXml() throws Exception {
persistActiveDomain("example.tld");
public void testSuccess_locksDomain() throws Exception {
DomainBase domain = persistActiveDomain("example.tld");
runCommandForced("--client=NewRegistrar", "example.tld");
eppVerifier.verifySent("domain_lock.xml", ImmutableMap.of("DOMAIN", "example.tld"));
assertThat(reloadResource(domain).getStatusValues())
.containsAtLeastElementsIn(REGISTRY_LOCK_STATUSES);
}
@Test
public void testSuccess_partiallyUpdatesStatuses() throws Exception {
persistResource(
newDomainBase("example.tld")
.asBuilder()
.addStatusValue(SERVER_TRANSFER_PROHIBITED)
.build());
DomainBase domain =
persistResource(
newDomainBase("example.tld")
.asBuilder()
.addStatusValue(SERVER_TRANSFER_PROHIBITED)
.build());
runCommandForced("--client=NewRegistrar", "example.tld");
eppVerifier.verifySent("domain_lock_partial_statuses.xml");
assertThat(reloadResource(domain).getStatusValues())
.containsAtLeastElementsIn(REGISTRY_LOCK_STATUSES);
}
@Test
public void testSuccess_manyDomains() throws Exception {
// Create 26 domains -- one more than the number of entity groups allowed in a transaction (in
// case that was going to be the failure point).
List<String> domains = new ArrayList<>();
List<DomainBase> domains = new ArrayList<>();
for (int n = 0; n < 26; n++) {
String domain = String.format("domain%d.tld", n);
persistActiveDomain(domain);
domains.add(domain);
domains.add(persistActiveDomain(domain));
}
runCommandForced(
ImmutableList.<String>builder().add("--client=NewRegistrar").addAll(domains).build());
for (String domain : domains) {
eppVerifier.verifySent("domain_lock.xml", ImmutableMap.of("DOMAIN", domain));
ImmutableList.<String>builder()
.add("--client=NewRegistrar")
.addAll(
domains.stream()
.map(DomainBase::getFullyQualifiedDomainName)
.collect(Collectors.toList()))
.build());
for (DomainBase domain : domains) {
assertThat(reloadResource(domain).getStatusValues())
.containsAtLeastElementsIn(REGISTRY_LOCK_STATUSES);
}
}
@ -87,21 +108,22 @@ public class LockDomainCommandTest extends EppToolCommandTestCase<LockDomainComm
@Test
public void testSuccess_alreadyLockedDomain_performsNoAction() throws Exception {
persistResource(
newDomainBase("example.tld")
.asBuilder()
.addStatusValues(REGISTRY_LOCK_STATUSES)
.build());
DomainBase domain =
persistResource(
newDomainBase("example.tld")
.asBuilder()
.addStatusValues(REGISTRY_LOCK_STATUSES)
.build());
runCommandForced("--client=NewRegistrar", "example.tld");
assertThat(reloadResource(domain)).isEqualTo(domain);
}
@Test
public void testSuccess_defaultsToAdminRegistrar_ifUnspecified() throws Exception {
persistActiveDomain("example.tld");
DomainBase domain = persistActiveDomain("example.tld");
runCommandForced("example.tld");
eppVerifier
.expectClientId("adminreg")
.verifySent("domain_lock.xml", ImmutableMap.of("DOMAIN", "example.tld"));
assertThat(RegistryLockDao.getMostRecentByRepoId(domain.getRepoId()).get().getRegistrarId())
.isEqualTo("adminreg");
}
@Test

View file

@ -16,75 +16,95 @@ package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.eppcommon.StatusValue.SERVER_DELETE_PROHIBITED;
import static google.registry.model.eppcommon.StatusValue.SERVER_TRANSFER_PROHIBITED;
import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED;
import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.newDomainBase;
import static google.registry.testing.DatastoreHelper.persistActiveDomain;
import static google.registry.testing.DatastoreHelper.persistNewRegistrar;
import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STATUSES;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import google.registry.model.domain.DomainBase;
import google.registry.model.registrar.Registrar.Type;
import google.registry.model.registry.RegistryLockDao;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationTestRule;
import google.registry.schema.domain.RegistryLock;
import google.registry.testing.FakeClock;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
/** Unit tests for {@link UnlockDomainCommand}. */
public class UnlockDomainCommandTest extends EppToolCommandTestCase<UnlockDomainCommand> {
public class UnlockDomainCommandTest extends CommandTestCase<UnlockDomainCommand> {
@Rule
public final JpaIntegrationTestRule jpaRule =
new JpaTestRules.Builder().buildIntegrationTestRule();
@Before
public void before() {
eppVerifier.expectSuperuser();
persistNewRegistrar("adminreg", "Admin Registrar", Type.REAL, 693L);
createTld("tld");
command.registryAdminClientId = "adminreg";
command.clock = new FakeClock();
}
private static void persistLockedDomain(String domainName) {
persistResource(
newDomainBase(domainName)
.asBuilder()
.addStatusValues(
ImmutableSet.of(
SERVER_DELETE_PROHIBITED, SERVER_TRANSFER_PROHIBITED, SERVER_UPDATE_PROHIBITED))
.build());
private DomainBase persistLockedDomain(String domainName, String registrarId) {
DomainBase domain = persistResource(newDomainBase(domainName));
RegistryLock lock =
DomainLockUtils.createRegistryLockRequest(
domainName, registrarId, null, true, command.clock);
DomainLockUtils.verifyAndApplyLock(lock.getVerificationCode(), true, command.clock);
return reloadResource(domain);
}
@Test
public void testSuccess_sendsCorrectEppXml() throws Exception {
persistLockedDomain("example.tld");
public void testSuccess_unlocksDomain() throws Exception {
DomainBase domain = persistLockedDomain("example.tld", "NewRegistrar");
runCommandForced("--client=NewRegistrar", "example.tld");
eppVerifier.verifySent("domain_unlock.xml", ImmutableMap.of("DOMAIN", "example.tld"));
assertThat(reloadResource(domain).getStatusValues()).containsNoneIn(REGISTRY_LOCK_STATUSES);
}
@Test
public void testSuccess_partiallyUpdatesStatuses() throws Exception {
persistResource(
newDomainBase("example.tld")
.asBuilder()
.addStatusValues(ImmutableSet.of(SERVER_DELETE_PROHIBITED, SERVER_UPDATE_PROHIBITED))
.build());
DomainBase domain = persistLockedDomain("example.tld", "NewRegistrar");
domain =
persistResource(
domain
.asBuilder()
.setStatusValues(
ImmutableSet.of(SERVER_DELETE_PROHIBITED, SERVER_UPDATE_PROHIBITED))
.build());
runCommandForced("--client=NewRegistrar", "example.tld");
eppVerifier.verifySent("domain_unlock_partial_statuses.xml");
assertThat(reloadResource(domain).getStatusValues()).containsNoneIn(REGISTRY_LOCK_STATUSES);
}
@Test
public void testSuccess_manyDomains() throws Exception {
// Create 26 domains -- one more than the number of entity groups allowed in a transaction (in
// case that was going to be the failure point).
List<String> domains = new ArrayList<>();
List<DomainBase> domains = new ArrayList<>();
for (int n = 0; n < 26; n++) {
String domain = String.format("domain%d.tld", n);
persistLockedDomain(domain);
domains.add(domain);
domains.add(persistLockedDomain(domain, "NewRegistrar"));
}
runCommandForced(
ImmutableList.<String>builder().add("--client=NewRegistrar").addAll(domains).build());
for (String domain : domains) {
eppVerifier.verifySent("domain_unlock.xml", ImmutableMap.of("DOMAIN", domain));
ImmutableList.<String>builder()
.add("--client=NewRegistrar")
.addAll(
domains.stream()
.map(DomainBase::getFullyQualifiedDomainName)
.collect(Collectors.toList()))
.build());
for (DomainBase domain : domains) {
assertThat(reloadResource(domain).getStatusValues()).containsNoneIn(REGISTRY_LOCK_STATUSES);
}
}
@ -99,17 +119,17 @@ public class UnlockDomainCommandTest extends EppToolCommandTestCase<UnlockDomain
@Test
public void testSuccess_alreadyUnlockedDomain_performsNoAction() throws Exception {
persistActiveDomain("example.tld");
DomainBase domain = persistActiveDomain("example.tld");
runCommandForced("--client=NewRegistrar", "example.tld");
assertThat(reloadResource(domain)).isEqualTo(domain);
}
@Test
public void testSuccess_defaultsToAdminRegistrar_ifUnspecified() throws Exception {
persistLockedDomain("example.tld");
DomainBase domain = persistLockedDomain("example.tld", "NewRegistrar");
runCommandForced("example.tld");
eppVerifier
.expectClientId("adminreg")
.verifySent("domain_unlock.xml", ImmutableMap.of("DOMAIN", "example.tld"));
assertThat(RegistryLockDao.getMostRecentByRepoId(domain.getRepoId()).get().getRegistrarId())
.isEqualTo("adminreg");
}
@Test

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>%DOMAIN%</domain:name>
<domain:add>
<domain:status s="serverDeleteProhibited"/>
<domain:status s="serverTransferProhibited"/>
<domain:status s="serverUpdateProhibited"/>
</domain:add>
</domain:update>
</update>
<clTRID>RegistryTool</clTRID>
</command>
</epp>

View file

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
<domain:add>
<domain:status s="serverDeleteProhibited"/>
<domain:status s="serverUpdateProhibited"/>
</domain:add>
</domain:update>
</update>
<clTRID>RegistryTool</clTRID>
</command>
</epp>

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>%DOMAIN%</domain:name>
<domain:rem>
<domain:status s="serverDeleteProhibited"/>
<domain:status s="serverTransferProhibited"/>
<domain:status s="serverUpdateProhibited"/>
</domain:rem>
</domain:update>
</update>
<clTRID>RegistryTool</clTRID>
</command>
</epp>

View file

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
<domain:rem>
<domain:status s="serverDeleteProhibited"/>
<domain:status s="serverUpdateProhibited"/>
</domain:rem>
</domain:update>
</update>
<clTRID>RegistryTool</clTRID>
</command>
</epp>