mirror of
https://github.com/google/nomulus
synced 2026-09-21 07:24:26 +00:00
Allow superuser status to override EPP resource delete prohibited status (#2789)
This commit is contained in:
@@ -21,6 +21,7 @@ import static google.registry.xml.ValidationMode.STRICT;
|
|||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.flogger.FluentLogger;
|
import com.google.common.flogger.FluentLogger;
|
||||||
import google.registry.flows.EppException.CommandUseErrorException;
|
import google.registry.flows.EppException.CommandUseErrorException;
|
||||||
import google.registry.flows.EppException.ParameterValueRangeErrorException;
|
import google.registry.flows.EppException.ParameterValueRangeErrorException;
|
||||||
@@ -30,6 +31,7 @@ import google.registry.flows.custom.EntityChanges;
|
|||||||
import google.registry.model.EppResource;
|
import google.registry.model.EppResource;
|
||||||
import google.registry.model.adapters.CurrencyUnitAdapter.UnknownCurrencyException;
|
import google.registry.model.adapters.CurrencyUnitAdapter.UnknownCurrencyException;
|
||||||
import google.registry.model.eppcommon.EppXmlTransformer;
|
import google.registry.model.eppcommon.EppXmlTransformer;
|
||||||
|
import google.registry.model.eppcommon.StatusValue;
|
||||||
import google.registry.model.eppinput.EppInput.WrongProtocolVersionException;
|
import google.registry.model.eppinput.EppInput.WrongProtocolVersionException;
|
||||||
import google.registry.model.eppoutput.EppOutput;
|
import google.registry.model.eppoutput.EppOutput;
|
||||||
import google.registry.model.host.InetAddressAdapter.IpVersionMismatchException;
|
import google.registry.model.host.InetAddressAdapter.IpVersionMismatchException;
|
||||||
@@ -40,6 +42,9 @@ import java.util.List;
|
|||||||
/** Static utility functions for flows. */
|
/** Static utility functions for flows. */
|
||||||
public final class FlowUtils {
|
public final class FlowUtils {
|
||||||
|
|
||||||
|
public static final ImmutableSet<StatusValue> DELETE_PROHIBITED_STATUSES =
|
||||||
|
ImmutableSet.of(StatusValue.CLIENT_DELETE_PROHIBITED, StatusValue.SERVER_DELETE_PROHIBITED);
|
||||||
|
|
||||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
|
|
||||||
private FlowUtils() {}
|
private FlowUtils() {}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
package google.registry.flows.contact;
|
package google.registry.flows.contact;
|
||||||
|
|
||||||
|
import static google.registry.flows.FlowUtils.DELETE_PROHIBITED_STATUSES;
|
||||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||||
import static google.registry.flows.ResourceFlowUtils.checkLinkedDomains;
|
import static google.registry.flows.ResourceFlowUtils.checkLinkedDomains;
|
||||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||||
@@ -65,12 +66,6 @@ import org.joda.time.DateTime;
|
|||||||
@ReportingSpec(ActivityReportField.CONTACT_DELETE)
|
@ReportingSpec(ActivityReportField.CONTACT_DELETE)
|
||||||
public final class ContactDeleteFlow implements MutatingFlow {
|
public final class ContactDeleteFlow implements MutatingFlow {
|
||||||
|
|
||||||
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES =
|
|
||||||
ImmutableSet.of(
|
|
||||||
StatusValue.CLIENT_DELETE_PROHIBITED,
|
|
||||||
StatusValue.PENDING_DELETE,
|
|
||||||
StatusValue.SERVER_DELETE_PROHIBITED);
|
|
||||||
|
|
||||||
@Inject ExtensionManager extensionManager;
|
@Inject ExtensionManager extensionManager;
|
||||||
@Inject @RegistrarId String registrarId;
|
@Inject @RegistrarId String registrarId;
|
||||||
@Inject @TargetId String targetId;
|
@Inject @TargetId String targetId;
|
||||||
@@ -91,9 +86,10 @@ public final class ContactDeleteFlow implements MutatingFlow {
|
|||||||
DateTime now = tm().getTransactionTime();
|
DateTime now = tm().getTransactionTime();
|
||||||
checkLinkedDomains(targetId, now, Contact.class);
|
checkLinkedDomains(targetId, now, Contact.class);
|
||||||
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||||
verifyNoDisallowedStatuses(existingContact, DISALLOWED_STATUSES);
|
|
||||||
verifyOptionalAuthInfo(authInfo, existingContact);
|
verifyOptionalAuthInfo(authInfo, existingContact);
|
||||||
|
verifyNoDisallowedStatuses(existingContact, ImmutableSet.of(StatusValue.PENDING_DELETE));
|
||||||
if (!isSuperuser) {
|
if (!isSuperuser) {
|
||||||
|
verifyNoDisallowedStatuses(existingContact, DELETE_PROHIBITED_STATUSES);
|
||||||
verifyResourceOwnership(registrarId, existingContact);
|
verifyResourceOwnership(registrarId, existingContact);
|
||||||
}
|
}
|
||||||
// Handle pending transfers on contact deletion.
|
// Handle pending transfers on contact deletion.
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package google.registry.flows.domain;
|
|||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||||
import static google.registry.dns.DnsUtils.requestDomainDnsRefresh;
|
import static google.registry.dns.DnsUtils.requestDomainDnsRefresh;
|
||||||
|
import static google.registry.flows.FlowUtils.DELETE_PROHIBITED_STATUSES;
|
||||||
import static google.registry.flows.FlowUtils.createHistoryEntryId;
|
import static google.registry.flows.FlowUtils.createHistoryEntryId;
|
||||||
import static google.registry.flows.FlowUtils.persistEntityChanges;
|
import static google.registry.flows.FlowUtils.persistEntityChanges;
|
||||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||||
@@ -122,11 +123,6 @@ public final class DomainDeleteFlow implements MutatingFlow, SqlStatementLogging
|
|||||||
|
|
||||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
|
|
||||||
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES = ImmutableSet.of(
|
|
||||||
StatusValue.CLIENT_DELETE_PROHIBITED,
|
|
||||||
StatusValue.PENDING_DELETE,
|
|
||||||
StatusValue.SERVER_DELETE_PROHIBITED);
|
|
||||||
|
|
||||||
@Inject ExtensionManager extensionManager;
|
@Inject ExtensionManager extensionManager;
|
||||||
@Inject EppInput eppInput;
|
@Inject EppInput eppInput;
|
||||||
@Inject SessionMetadata sessionMetadata;
|
@Inject SessionMetadata sessionMetadata;
|
||||||
@@ -304,9 +300,10 @@ public final class DomainDeleteFlow implements MutatingFlow, SqlStatementLogging
|
|||||||
|
|
||||||
private void verifyDeleteAllowed(Domain existingDomain, Tld tld, DateTime now)
|
private void verifyDeleteAllowed(Domain existingDomain, Tld tld, DateTime now)
|
||||||
throws EppException {
|
throws EppException {
|
||||||
verifyNoDisallowedStatuses(existingDomain, DISALLOWED_STATUSES);
|
|
||||||
verifyOptionalAuthInfo(authInfo, existingDomain);
|
verifyOptionalAuthInfo(authInfo, existingDomain);
|
||||||
|
verifyNoDisallowedStatuses(existingDomain, ImmutableSet.of(StatusValue.PENDING_DELETE));
|
||||||
if (!isSuperuser) {
|
if (!isSuperuser) {
|
||||||
|
verifyNoDisallowedStatuses(existingDomain, DELETE_PROHIBITED_STATUSES);
|
||||||
verifyResourceOwnership(registrarId, existingDomain);
|
verifyResourceOwnership(registrarId, existingDomain);
|
||||||
verifyNotInPredelegation(tld, now);
|
verifyNotInPredelegation(tld, now);
|
||||||
checkAllowedAccessToTld(registrarId, tld.getTld().toString());
|
checkAllowedAccessToTld(registrarId, tld.getTld().toString());
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
package google.registry.flows.host;
|
package google.registry.flows.host;
|
||||||
|
|
||||||
import static google.registry.dns.DnsUtils.requestHostDnsRefresh;
|
import static google.registry.dns.DnsUtils.requestHostDnsRefresh;
|
||||||
|
import static google.registry.flows.FlowUtils.DELETE_PROHIBITED_STATUSES;
|
||||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||||
import static google.registry.flows.ResourceFlowUtils.checkLinkedDomains;
|
import static google.registry.flows.ResourceFlowUtils.checkLinkedDomains;
|
||||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||||
@@ -65,12 +66,6 @@ import org.joda.time.DateTime;
|
|||||||
@ReportingSpec(ActivityReportField.HOST_DELETE)
|
@ReportingSpec(ActivityReportField.HOST_DELETE)
|
||||||
public final class HostDeleteFlow implements MutatingFlow {
|
public final class HostDeleteFlow implements MutatingFlow {
|
||||||
|
|
||||||
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES =
|
|
||||||
ImmutableSet.of(
|
|
||||||
StatusValue.CLIENT_DELETE_PROHIBITED,
|
|
||||||
StatusValue.PENDING_DELETE,
|
|
||||||
StatusValue.SERVER_DELETE_PROHIBITED);
|
|
||||||
|
|
||||||
@Inject ExtensionManager extensionManager;
|
@Inject ExtensionManager extensionManager;
|
||||||
@Inject @RegistrarId String registrarId;
|
@Inject @RegistrarId String registrarId;
|
||||||
@Inject @TargetId String targetId;
|
@Inject @TargetId String targetId;
|
||||||
@@ -91,8 +86,9 @@ public final class HostDeleteFlow implements MutatingFlow {
|
|||||||
validateHostName(targetId);
|
validateHostName(targetId);
|
||||||
checkLinkedDomains(targetId, now, Host.class);
|
checkLinkedDomains(targetId, now, Host.class);
|
||||||
Host existingHost = loadAndVerifyExistence(Host.class, targetId, now);
|
Host existingHost = loadAndVerifyExistence(Host.class, targetId, now);
|
||||||
verifyNoDisallowedStatuses(existingHost, DISALLOWED_STATUSES);
|
verifyNoDisallowedStatuses(existingHost, ImmutableSet.of(StatusValue.PENDING_DELETE));
|
||||||
if (!isSuperuser) {
|
if (!isSuperuser) {
|
||||||
|
verifyNoDisallowedStatuses(existingHost, DELETE_PROHIBITED_STATUSES);
|
||||||
// Hosts transfer with their superordinate domains, so for hosts with a superordinate domain,
|
// Hosts transfer with their superordinate domains, so for hosts with a superordinate domain,
|
||||||
// the client id, needs to be read off of it.
|
// the client id, needs to be read off of it.
|
||||||
EppResource owningResource =
|
EppResource owningResource =
|
||||||
|
|||||||
@@ -182,6 +182,43 @@ class ContactDeleteFlowTest extends ResourceFlowTestCase<ContactDeleteFlow, Cont
|
|||||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccess_clientDeleteProhibited_superuser() throws Exception {
|
||||||
|
persistResource(
|
||||||
|
persistActiveContact(getUniqueIdFromCommand())
|
||||||
|
.asBuilder()
|
||||||
|
.addStatusValue(StatusValue.CLIENT_DELETE_PROHIBITED)
|
||||||
|
.build());
|
||||||
|
runFlowAssertResponse(
|
||||||
|
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("contact_delete_response.xml"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccess_serverDeleteProhibited_superuser() throws Exception {
|
||||||
|
persistResource(
|
||||||
|
persistActiveContact(getUniqueIdFromCommand())
|
||||||
|
.asBuilder()
|
||||||
|
.addStatusValue(StatusValue.SERVER_DELETE_PROHIBITED)
|
||||||
|
.build());
|
||||||
|
runFlowAssertResponse(
|
||||||
|
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("contact_delete_response.xml"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFailure_pendingDelete_superuser() throws Exception {
|
||||||
|
persistResource(
|
||||||
|
persistActiveContact(getUniqueIdFromCommand())
|
||||||
|
.asBuilder()
|
||||||
|
.addStatusValue(StatusValue.PENDING_DELETE)
|
||||||
|
.build());
|
||||||
|
assertAboutEppExceptions()
|
||||||
|
.that(
|
||||||
|
assertThrows(
|
||||||
|
ResourceStatusProhibitsOperationException.class,
|
||||||
|
() -> runFlow(CommitMode.LIVE, UserPrivileges.SUPERUSER)))
|
||||||
|
.marshalsToXml();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testFailure_unauthorizedClient() throws Exception {
|
void testFailure_unauthorizedClient() throws Exception {
|
||||||
sessionMetadata.setRegistrarId("NewRegistrar");
|
sessionMetadata.setRegistrarId("NewRegistrar");
|
||||||
|
|||||||
@@ -917,6 +917,52 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
|
|||||||
assertThat(thrown).hasMessageThat().contains("pendingDelete");
|
assertThat(thrown).hasMessageThat().contains("pendingDelete");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccess_clientDeleteProhibited_superuser() throws Exception {
|
||||||
|
eppRequestSource = EppRequestSource.TOOL;
|
||||||
|
setEppInput(
|
||||||
|
"domain_delete_superuser_extension.xml",
|
||||||
|
ImmutableMap.of("REDEMPTION_GRACE_PERIOD_DAYS", "15", "PENDING_DELETE_DAYS", "0"));
|
||||||
|
setUpSuccessfulTest();
|
||||||
|
|
||||||
|
persistResource(
|
||||||
|
domain.asBuilder().addStatusValue(StatusValue.CLIENT_DELETE_PROHIBITED).build());
|
||||||
|
clock.advanceOneMilli();
|
||||||
|
runFlowAssertResponse(
|
||||||
|
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("domain_delete_response_pending.xml"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccess_serverDeleteProhibited_superuser() throws Exception {
|
||||||
|
eppRequestSource = EppRequestSource.TOOL;
|
||||||
|
setEppInput(
|
||||||
|
"domain_delete_superuser_extension.xml",
|
||||||
|
ImmutableMap.of("REDEMPTION_GRACE_PERIOD_DAYS", "15", "PENDING_DELETE_DAYS", "0"));
|
||||||
|
setUpSuccessfulTest();
|
||||||
|
|
||||||
|
persistResource(
|
||||||
|
domain.asBuilder().addStatusValue(StatusValue.SERVER_DELETE_PROHIBITED).build());
|
||||||
|
clock.advanceOneMilli();
|
||||||
|
runFlowAssertResponse(
|
||||||
|
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("domain_delete_response_pending.xml"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFailure_pendingDelete_superuser() throws Exception {
|
||||||
|
eppRequestSource = EppRequestSource.TOOL;
|
||||||
|
setEppInput(
|
||||||
|
"domain_delete_superuser_extension.xml",
|
||||||
|
ImmutableMap.of("REDEMPTION_GRACE_PERIOD_DAYS", "15", "PENDING_DELETE_DAYS", "0"));
|
||||||
|
setUpSuccessfulTest();
|
||||||
|
persistResource(domain.asBuilder().addStatusValue(StatusValue.PENDING_DELETE).build());
|
||||||
|
|
||||||
|
ResourceStatusProhibitsOperationException thrown =
|
||||||
|
assertThrows(
|
||||||
|
ResourceStatusProhibitsOperationException.class,
|
||||||
|
() -> runFlow(CommitMode.LIVE, UserPrivileges.SUPERUSER));
|
||||||
|
assertThat(thrown).hasMessageThat().contains("pendingDelete");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_metadata() throws Exception {
|
void testSuccess_metadata() throws Exception {
|
||||||
eppRequestSource = EppRequestSource.TOOL;
|
eppRequestSource = EppRequestSource.TOOL;
|
||||||
|
|||||||
@@ -153,6 +153,43 @@ class HostDeleteFlowTest extends ResourceFlowTestCase<HostDeleteFlow, Host> {
|
|||||||
assertSqlDeleteSuccess();
|
assertSqlDeleteSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccess_clientDeleteProhibited_superuser() throws Exception {
|
||||||
|
persistResource(
|
||||||
|
persistActiveHost("ns1.example.tld")
|
||||||
|
.asBuilder()
|
||||||
|
.addStatusValue(StatusValue.CLIENT_DELETE_PROHIBITED)
|
||||||
|
.build());
|
||||||
|
runFlowAssertResponse(
|
||||||
|
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("host_delete_response.xml"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccess_serverDeleteProhibited_superuser() throws Exception {
|
||||||
|
persistResource(
|
||||||
|
persistActiveHost("ns1.example.tld")
|
||||||
|
.asBuilder()
|
||||||
|
.addStatusValue(StatusValue.SERVER_DELETE_PROHIBITED)
|
||||||
|
.build());
|
||||||
|
runFlowAssertResponse(
|
||||||
|
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("host_delete_response.xml"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFailure_pendingDelete_superuser() throws Exception {
|
||||||
|
persistResource(
|
||||||
|
persistActiveHost("ns1.example.tld")
|
||||||
|
.asBuilder()
|
||||||
|
.addStatusValue(StatusValue.PENDING_DELETE)
|
||||||
|
.build());
|
||||||
|
assertAboutEppExceptions()
|
||||||
|
.that(
|
||||||
|
assertThrows(
|
||||||
|
ResourceStatusProhibitsOperationException.class,
|
||||||
|
() -> runFlow(CommitMode.LIVE, UserPrivileges.SUPERUSER)))
|
||||||
|
.marshalsToXml();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_authorizedClientReadFromSuperordinate() throws Exception {
|
void testSuccess_authorizedClientReadFromSuperordinate() throws Exception {
|
||||||
sessionMetadata.setRegistrarId("TheRegistrar");
|
sessionMetadata.setRegistrarId("TheRegistrar");
|
||||||
|
|||||||
Reference in New Issue
Block a user