mirror of
https://github.com/google/nomulus
synced 2026-09-19 22:44:26 +00:00
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:
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user