mirror of
https://github.com/google/nomulus
synced 2025-12-23 06:15:42 +00:00
Refactor console domain actions to exist in separate files (#2638)
This means that we're not storing everything in one file, otherwise it quickly becomes unwieldy
This commit is contained in:
@@ -109,7 +109,6 @@ import google.registry.tools.server.RefreshDnsForAllDomainsAction;
|
||||
import google.registry.tools.server.ToolsServerModule;
|
||||
import google.registry.tools.server.UpdateUserGroupAction;
|
||||
import google.registry.tools.server.VerifyOteAction;
|
||||
import google.registry.ui.server.console.ConsoleBulkDomainAction;
|
||||
import google.registry.ui.server.console.ConsoleDomainGetAction;
|
||||
import google.registry.ui.server.console.ConsoleDomainListAction;
|
||||
import google.registry.ui.server.console.ConsoleDumDownloadAction;
|
||||
@@ -122,6 +121,7 @@ import google.registry.ui.server.console.ConsoleUpdateRegistrarAction;
|
||||
import google.registry.ui.server.console.ConsoleUserDataAction;
|
||||
import google.registry.ui.server.console.ConsoleUsersAction;
|
||||
import google.registry.ui.server.console.RegistrarsAction;
|
||||
import google.registry.ui.server.console.domains.ConsoleBulkDomainAction;
|
||||
import google.registry.ui.server.console.settings.ContactAction;
|
||||
import google.registry.ui.server.console.settings.SecurityAction;
|
||||
import google.registry.ui.server.console.settings.WhoisRegistrarFieldsAction;
|
||||
|
||||
@@ -25,7 +25,6 @@ import google.registry.monitoring.whitebox.WhiteboxModule;
|
||||
import google.registry.request.RequestComponentBuilder;
|
||||
import google.registry.request.RequestModule;
|
||||
import google.registry.request.RequestScope;
|
||||
import google.registry.ui.server.console.ConsoleBulkDomainAction;
|
||||
import google.registry.ui.server.console.ConsoleDomainGetAction;
|
||||
import google.registry.ui.server.console.ConsoleDomainListAction;
|
||||
import google.registry.ui.server.console.ConsoleDumDownloadAction;
|
||||
@@ -38,6 +37,7 @@ import google.registry.ui.server.console.ConsoleUpdateRegistrarAction;
|
||||
import google.registry.ui.server.console.ConsoleUserDataAction;
|
||||
import google.registry.ui.server.console.ConsoleUsersAction;
|
||||
import google.registry.ui.server.console.RegistrarsAction;
|
||||
import google.registry.ui.server.console.domains.ConsoleBulkDomainAction;
|
||||
import google.registry.ui.server.console.settings.ContactAction;
|
||||
import google.registry.ui.server.console.settings.SecurityAction;
|
||||
import google.registry.ui.server.console.settings.WhoisRegistrarFieldsAction;
|
||||
|
||||
@@ -136,7 +136,7 @@ abstract class EppToolCommand extends ConfirmingCommand implements CommandWithCo
|
||||
params.put("dryRun", dryRun);
|
||||
params.put("clientId", command.clientId);
|
||||
params.put("superuser", superuser);
|
||||
params.put("xml", URLEncoder.encode(command.xml, UTF_8.toString()));
|
||||
params.put("xml", URLEncoder.encode(command.xml, UTF_8));
|
||||
String requestBody =
|
||||
Joiner.on('&').withKeyValueSeparator("=").join(filterValues(params, Objects::nonNull));
|
||||
responses.add(
|
||||
|
||||
@@ -1,235 +0,0 @@
|
||||
// Copyright 2024 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.ui.server.console;
|
||||
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.escape.Escaper;
|
||||
import com.google.common.xml.XmlEscapers;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.flows.EppController;
|
||||
import google.registry.flows.EppRequestSource;
|
||||
import google.registry.flows.PasswordOnlyTransportCredentials;
|
||||
import google.registry.flows.StatelessRequestSessionMetadata;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.eppcommon.ProtocolDefinition;
|
||||
import google.registry.model.eppoutput.EppOutput;
|
||||
import google.registry.model.eppoutput.Result;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.OptionalJsonPayload;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.auth.Auth;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Console endpoint to perform the same action to a list of domains.
|
||||
*
|
||||
* <p>All requests must include the {@link BulkAction} to perform as well as a {@link
|
||||
* BulkDomainList} of domains on which to apply the action. The remaining contents of the request
|
||||
* body depend on the type of action -- some requests may require more data than others.
|
||||
*/
|
||||
@Action(
|
||||
service = Action.GaeService.DEFAULT,
|
||||
gkeService = Action.GkeService.CONSOLE,
|
||||
path = ConsoleBulkDomainAction.PATH,
|
||||
method = Action.Method.POST,
|
||||
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
|
||||
public class ConsoleBulkDomainAction extends ConsoleApiAction {
|
||||
|
||||
public static final String PATH = "/console-api/bulk-domain";
|
||||
|
||||
private static Escaper XML_ESCAPER = XmlEscapers.xmlContentEscaper();
|
||||
|
||||
public enum BulkAction {
|
||||
DELETE,
|
||||
SUSPEND
|
||||
}
|
||||
|
||||
/** All requests must include at least a list of domain names on which to perform the action. */
|
||||
public record BulkDomainList(@Expose List<String> domainList) {}
|
||||
|
||||
public record BulkDomainDeleteRequest(@Expose String reason) {}
|
||||
|
||||
public record BulkDomainSuspendRequest(@Expose String reason) {}
|
||||
|
||||
private static final String DOMAIN_DELETE_XML =
|
||||
"""
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<command>
|
||||
<delete>
|
||||
<domain:delete
|
||||
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
|
||||
<domain:name>%DOMAIN_NAME%</domain:name>
|
||||
</domain:delete>
|
||||
</delete>
|
||||
<extension>
|
||||
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
|
||||
<metadata:reason>%REASON%</metadata:reason>
|
||||
<metadata:requestedByRegistrar>true</metadata:requestedByRegistrar>
|
||||
</metadata:metadata>
|
||||
</extension>
|
||||
<clTRID>RegistryConsole</clTRID>
|
||||
</command>
|
||||
</epp>""";
|
||||
|
||||
private static final String DOMAIN_SUSPEND_XML =
|
||||
"""
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<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_NAME%</domain:name>
|
||||
<domain:add>
|
||||
<domain:status s="serverDeleteProhibited" lang="en"></domain:status>
|
||||
<domain:status s="serverHold" lang="en"></domain:status>
|
||||
<domain:status s="serverRenewProhibited" lang="en"></domain:status>
|
||||
<domain:status s="serverTransferProhibited" lang="en"></domain:status>
|
||||
<domain:status s="serverUpdateProhibited" lang="en"></domain:status>
|
||||
</domain:add>
|
||||
<domain:rem></domain:rem>
|
||||
</domain:update>
|
||||
</update>
|
||||
<extension>
|
||||
<metadata:metadata
|
||||
xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
|
||||
<metadata:reason>Console suspension: %REASON%</metadata:reason>
|
||||
<metadata:requestedByRegistrar>false</metadata:requestedByRegistrar>
|
||||
</metadata:metadata>
|
||||
</extension>
|
||||
<clTRID>RegistryTool</clTRID>
|
||||
</command>
|
||||
</epp>""";
|
||||
|
||||
private final EppController eppController;
|
||||
private final String registrarId;
|
||||
private final String bulkDomainAction;
|
||||
private final Optional<JsonElement> optionalJsonPayload;
|
||||
|
||||
@Inject
|
||||
public ConsoleBulkDomainAction(
|
||||
ConsoleApiParams consoleApiParams,
|
||||
EppController eppController,
|
||||
@Parameter("registrarId") String registrarId,
|
||||
@Parameter("bulkDomainAction") String bulkDomainAction,
|
||||
@OptionalJsonPayload Optional<JsonElement> optionalJsonPayload) {
|
||||
super(consoleApiParams);
|
||||
this.eppController = eppController;
|
||||
this.registrarId = registrarId;
|
||||
this.bulkDomainAction = bulkDomainAction;
|
||||
this.optionalJsonPayload = optionalJsonPayload;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postHandler(User user) {
|
||||
// Temporary flag while testing
|
||||
if (!user.getUserRoles().isAdmin()) {
|
||||
consoleApiParams.response().setStatus(SC_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
BulkAction bulkAction = BulkAction.valueOf(bulkDomainAction);
|
||||
JsonElement jsonPayload =
|
||||
optionalJsonPayload.orElseThrow(
|
||||
() -> new IllegalArgumentException("Bulk action payload must be present"));
|
||||
BulkDomainList domainList = consoleApiParams.gson().fromJson(jsonPayload, BulkDomainList.class);
|
||||
checkPermission(user, registrarId, ConsolePermission.EXECUTE_EPP_COMMANDS);
|
||||
ImmutableMap<String, ConsoleEppOutput> result =
|
||||
switch (bulkAction) {
|
||||
case DELETE -> handleBulkDelete(jsonPayload, domainList, user);
|
||||
case SUSPEND -> handleBulkSuspend(jsonPayload, domainList, user);
|
||||
};
|
||||
// Front end should parse situations where only some commands worked
|
||||
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(result));
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
}
|
||||
|
||||
private ImmutableMap<String, ConsoleEppOutput> handleBulkDelete(
|
||||
JsonElement jsonPayload, BulkDomainList domainList, User user) {
|
||||
String reason =
|
||||
consoleApiParams.gson().fromJson(jsonPayload, BulkDomainDeleteRequest.class).reason;
|
||||
return runCommandOverDomains(
|
||||
domainList,
|
||||
DOMAIN_DELETE_XML,
|
||||
new ImmutableMap.Builder<String, String>().put("REASON", reason),
|
||||
user);
|
||||
}
|
||||
|
||||
private ImmutableMap<String, ConsoleEppOutput> handleBulkSuspend(
|
||||
JsonElement jsonPayload, BulkDomainList domainList, User user) {
|
||||
String reason =
|
||||
consoleApiParams.gson().fromJson(jsonPayload, BulkDomainSuspendRequest.class).reason;
|
||||
return runCommandOverDomains(
|
||||
domainList,
|
||||
DOMAIN_SUSPEND_XML,
|
||||
new ImmutableMap.Builder<String, String>().put("REASON", reason),
|
||||
user);
|
||||
}
|
||||
|
||||
/** Runs the provided XML template and substitutions over a provided list of domains. */
|
||||
private ImmutableMap<String, ConsoleEppOutput> runCommandOverDomains(
|
||||
BulkDomainList domainList,
|
||||
String xmlTemplate,
|
||||
ImmutableMap.Builder<String, String> replacements,
|
||||
User user) {
|
||||
return domainList.domainList.stream()
|
||||
.collect(
|
||||
toImmutableMap(
|
||||
d -> d,
|
||||
d ->
|
||||
executeEpp(
|
||||
fillSubstitutions(xmlTemplate, replacements.put("DOMAIN_NAME", d)), user)));
|
||||
}
|
||||
|
||||
private ConsoleEppOutput executeEpp(String xml, User user) {
|
||||
return ConsoleEppOutput.fromEppOutput(
|
||||
eppController.handleEppCommand(
|
||||
new StatelessRequestSessionMetadata(
|
||||
registrarId, ProtocolDefinition.getVisibleServiceExtensionUris()),
|
||||
new PasswordOnlyTransportCredentials(),
|
||||
EppRequestSource.CONSOLE,
|
||||
false,
|
||||
user.getUserRoles().isAdmin(),
|
||||
xml.getBytes(UTF_8)));
|
||||
}
|
||||
|
||||
/** Fills the provided XML template with the replacement values, including escaping the values. */
|
||||
private String fillSubstitutions(
|
||||
String xmlTemplate, ImmutableMap.Builder<String, String> replacements) {
|
||||
String xml = xmlTemplate;
|
||||
for (Map.Entry<String, String> entry : replacements.buildKeepingLast().entrySet()) {
|
||||
xml = xml.replaceAll("%" + entry.getKey() + "%", XML_ESCAPER.escape(entry.getValue()));
|
||||
}
|
||||
return xml;
|
||||
}
|
||||
|
||||
public record ConsoleEppOutput(@Expose String message, @Expose int responseCode) {
|
||||
static ConsoleEppOutput fromEppOutput(EppOutput eppOutput) {
|
||||
Result result = eppOutput.getResponse().getResult();
|
||||
return new ConsoleEppOutput(result.getMsg(), result.getCode().code);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright 2024 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.ui.server.console.domains;
|
||||
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.flows.EppController;
|
||||
import google.registry.flows.EppRequestSource;
|
||||
import google.registry.flows.PasswordOnlyTransportCredentials;
|
||||
import google.registry.flows.StatelessRequestSessionMetadata;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.eppcommon.ProtocolDefinition;
|
||||
import google.registry.model.eppoutput.EppOutput;
|
||||
import google.registry.model.eppoutput.Result;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.OptionalJsonPayload;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.ui.server.console.ConsoleApiAction;
|
||||
import google.registry.ui.server.console.ConsoleApiParams;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Console endpoint to perform the same action to a list of domains.
|
||||
*
|
||||
* <p>All requests must include the {@link ConsoleDomainActionType.BulkAction} to perform as well as
|
||||
* a {@link BulkDomainList} of domains on which to apply the action. The remaining contents of the
|
||||
* request body depend on the type of action -- some requests may require more data than others.
|
||||
*/
|
||||
@Action(
|
||||
service = Action.GaeService.DEFAULT,
|
||||
gkeService = Action.GkeService.CONSOLE,
|
||||
path = ConsoleBulkDomainAction.PATH,
|
||||
method = Action.Method.POST,
|
||||
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
|
||||
public class ConsoleBulkDomainAction extends ConsoleApiAction {
|
||||
|
||||
public static final String PATH = "/console-api/bulk-domain";
|
||||
|
||||
/** All requests must include at least a list of domain names on which to perform the action. */
|
||||
public record BulkDomainList(@Expose List<String> domainList) {}
|
||||
|
||||
private final EppController eppController;
|
||||
private final String registrarId;
|
||||
private final String bulkDomainAction;
|
||||
private final Optional<JsonElement> optionalJsonPayload;
|
||||
|
||||
@Inject
|
||||
public ConsoleBulkDomainAction(
|
||||
ConsoleApiParams consoleApiParams,
|
||||
EppController eppController,
|
||||
@Parameter("registrarId") String registrarId,
|
||||
@Parameter("bulkDomainAction") String bulkDomainAction,
|
||||
@OptionalJsonPayload Optional<JsonElement> optionalJsonPayload) {
|
||||
super(consoleApiParams);
|
||||
this.eppController = eppController;
|
||||
this.registrarId = registrarId;
|
||||
this.bulkDomainAction = bulkDomainAction;
|
||||
this.optionalJsonPayload = optionalJsonPayload;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postHandler(User user) {
|
||||
// Temporary flag while testing
|
||||
if (!user.getUserRoles().isAdmin()) {
|
||||
consoleApiParams.response().setStatus(SC_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
JsonElement jsonPayload =
|
||||
optionalJsonPayload.orElseThrow(
|
||||
() -> new IllegalArgumentException("Bulk action payload must be present"));
|
||||
BulkDomainList domainList = consoleApiParams.gson().fromJson(jsonPayload, BulkDomainList.class);
|
||||
ConsoleDomainActionType actionType =
|
||||
ConsoleDomainActionType.parseActionType(bulkDomainAction, jsonPayload);
|
||||
|
||||
checkPermission(user, registrarId, actionType.getNecessaryPermission());
|
||||
|
||||
ImmutableMap<String, ConsoleEppOutput> result =
|
||||
domainList.domainList.stream()
|
||||
.collect(
|
||||
toImmutableMap(d -> d, d -> executeEpp(actionType.getXmlContentsToRun(d), user)));
|
||||
// Front end should parse situations where only some commands worked
|
||||
consoleApiParams.response().setPayload(consoleApiParams.gson().toJson(result));
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
}
|
||||
|
||||
private ConsoleEppOutput executeEpp(String xml, User user) {
|
||||
return ConsoleEppOutput.fromEppOutput(
|
||||
eppController.handleEppCommand(
|
||||
new StatelessRequestSessionMetadata(
|
||||
registrarId, ProtocolDefinition.getVisibleServiceExtensionUris()),
|
||||
new PasswordOnlyTransportCredentials(),
|
||||
EppRequestSource.CONSOLE,
|
||||
false,
|
||||
user.getUserRoles().isAdmin(),
|
||||
xml.getBytes(UTF_8)));
|
||||
}
|
||||
|
||||
public record ConsoleEppOutput(@Expose String message, @Expose int responseCode) {
|
||||
static ConsoleEppOutput fromEppOutput(EppOutput eppOutput) {
|
||||
Result result = eppOutput.getResponse().getResult();
|
||||
return new ConsoleEppOutput(result.getMsg(), result.getCode().code);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2025 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.ui.server.console.domains;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.JsonElement;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
|
||||
/** An action that will run a delete EPP command on the given domain. */
|
||||
public class ConsoleBulkDomainDeleteActionType implements ConsoleDomainActionType {
|
||||
|
||||
private static final String DOMAIN_DELETE_XML =
|
||||
"""
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<command>
|
||||
<delete>
|
||||
<domain:delete
|
||||
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
|
||||
<domain:name>%DOMAIN_NAME%</domain:name>
|
||||
</domain:delete>
|
||||
</delete>
|
||||
<extension>
|
||||
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
|
||||
<metadata:reason>%REASON%</metadata:reason>
|
||||
<metadata:requestedByRegistrar>true</metadata:requestedByRegistrar>
|
||||
</metadata:metadata>
|
||||
</extension>
|
||||
<clTRID>RegistryConsole</clTRID>
|
||||
</command>
|
||||
</epp>""";
|
||||
|
||||
private final String reason;
|
||||
|
||||
public ConsoleBulkDomainDeleteActionType(JsonElement jsonElement) {
|
||||
this.reason = jsonElement.getAsJsonObject().get("reason").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getXmlContentsToRun(String domainName) {
|
||||
return ConsoleDomainActionType.fillSubstitutions(
|
||||
DOMAIN_DELETE_XML, ImmutableMap.of("DOMAIN_NAME", domainName, "REASON", reason));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConsolePermission getNecessaryPermission() {
|
||||
return ConsolePermission.EXECUTE_EPP_COMMANDS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright 2025 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.ui.server.console.domains;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.JsonElement;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
|
||||
/** An action that will suspend the given domain, assigning all 5 server*Prohibited statuses. */
|
||||
public class ConsoleBulkDomainSuspendActionType implements ConsoleDomainActionType {
|
||||
|
||||
private static final String DOMAIN_SUSPEND_XML =
|
||||
"""
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<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_NAME%</domain:name>
|
||||
<domain:add>
|
||||
<domain:status s="serverDeleteProhibited" lang="en"></domain:status>
|
||||
<domain:status s="serverHold" lang="en"></domain:status>
|
||||
<domain:status s="serverRenewProhibited" lang="en"></domain:status>
|
||||
<domain:status s="serverTransferProhibited" lang="en"></domain:status>
|
||||
<domain:status s="serverUpdateProhibited" lang="en"></domain:status>
|
||||
</domain:add>
|
||||
<domain:rem></domain:rem>
|
||||
</domain:update>
|
||||
</update>
|
||||
<extension>
|
||||
<metadata:metadata
|
||||
xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
|
||||
<metadata:reason>Console suspension: %REASON%</metadata:reason>
|
||||
<metadata:requestedByRegistrar>false</metadata:requestedByRegistrar>
|
||||
</metadata:metadata>
|
||||
</extension>
|
||||
<clTRID>RegistryConsole</clTRID>
|
||||
</command>
|
||||
</epp>""";
|
||||
|
||||
private final String reason;
|
||||
|
||||
public ConsoleBulkDomainSuspendActionType(JsonElement jsonElement) {
|
||||
this.reason = jsonElement.getAsJsonObject().get("reason").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getXmlContentsToRun(String domainName) {
|
||||
return ConsoleDomainActionType.fillSubstitutions(
|
||||
DOMAIN_SUSPEND_XML, ImmutableMap.of("DOMAIN_NAME", domainName, "REASON", reason));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConsolePermission getNecessaryPermission() {
|
||||
return ConsolePermission.SUSPEND_DOMAIN;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2025 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.ui.server.console.domains;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.escape.Escaper;
|
||||
import com.google.common.xml.XmlEscapers;
|
||||
import com.google.gson.JsonElement;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A type of EPP action to perform on domain(s), run by the {@link ConsoleBulkDomainAction}.
|
||||
*
|
||||
* <p>Each {@link BulkAction} defines the class that implements that action, including the EPP XML
|
||||
* that will be run and the permission required.
|
||||
*/
|
||||
public interface ConsoleDomainActionType {
|
||||
|
||||
enum BulkAction {
|
||||
DELETE(ConsoleBulkDomainDeleteActionType.class),
|
||||
SUSPEND(ConsoleBulkDomainSuspendActionType.class);
|
||||
|
||||
private final Class<? extends ConsoleDomainActionType> actionClass;
|
||||
|
||||
BulkAction(Class<? extends ConsoleDomainActionType> actionClass) {
|
||||
this.actionClass = actionClass;
|
||||
}
|
||||
|
||||
public Class<? extends ConsoleDomainActionType> getActionClass() {
|
||||
return actionClass;
|
||||
}
|
||||
}
|
||||
|
||||
Escaper XML_ESCAPER = XmlEscapers.xmlContentEscaper();
|
||||
|
||||
static String fillSubstitutions(String xmlTemplate, ImmutableMap<String, String> replacements) {
|
||||
String xml = xmlTemplate;
|
||||
for (Map.Entry<String, String> entry : replacements.entrySet()) {
|
||||
xml = xml.replaceAll("%" + entry.getKey() + "%", XML_ESCAPER.escape(entry.getValue()));
|
||||
}
|
||||
return xml;
|
||||
}
|
||||
|
||||
String getXmlContentsToRun(String domainName);
|
||||
|
||||
ConsolePermission getNecessaryPermission();
|
||||
|
||||
static ConsoleDomainActionType parseActionType(String bulkDomainAction, JsonElement jsonElement) {
|
||||
BulkAction bulkAction = BulkAction.valueOf(bulkDomainAction);
|
||||
try {
|
||||
return bulkAction.getActionClass().getConstructor(JsonElement.class).newInstance(jsonElement);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e); // shouldn't happen
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.ui.server.console;
|
||||
package google.registry.ui.server.console.domains;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_OPTIONAL;
|
||||
@@ -49,6 +49,7 @@ import google.registry.testing.ConsoleApiParamsUtils;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.tools.GsonUtils;
|
||||
import google.registry.ui.server.console.ConsoleApiParams;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -166,13 +167,13 @@ public class ConsoleBulkDomainActionTest {
|
||||
|
||||
@Test
|
||||
void testFailure_badActionString() {
|
||||
ConsoleBulkDomainAction action = createAction("bad", null);
|
||||
ConsoleBulkDomainAction action = createAction("bad", GSON.toJsonTree(ImmutableMap.of()));
|
||||
action.run();
|
||||
assertThat(fakeResponse.getStatus()).isEqualTo(SC_BAD_REQUEST);
|
||||
assertThat(fakeResponse.getPayload())
|
||||
.isEqualTo(
|
||||
"No enum constant"
|
||||
+ " google.registry.ui.server.console.ConsoleBulkDomainAction.BulkAction.bad");
|
||||
+ " google.registry.ui.server.console.domains.ConsoleDomainActionType.BulkAction.bad");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -186,7 +187,8 @@ public class ConsoleBulkDomainActionTest {
|
||||
@Test
|
||||
void testFailure_noPermission() {
|
||||
JsonElement payload =
|
||||
GSON.toJsonTree(ImmutableMap.of("domainList", ImmutableList.of("domain.tld")));
|
||||
GSON.toJsonTree(
|
||||
ImmutableMap.of("domainList", ImmutableList.of("domain.tld"), "reason", "reason"));
|
||||
ConsoleBulkDomainAction action =
|
||||
createAction(
|
||||
"DELETE",
|
||||
Reference in New Issue
Block a user