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.ToolsServerModule;
|
||||||
import google.registry.tools.server.UpdateUserGroupAction;
|
import google.registry.tools.server.UpdateUserGroupAction;
|
||||||
import google.registry.tools.server.VerifyOteAction;
|
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.ConsoleDomainGetAction;
|
||||||
import google.registry.ui.server.console.ConsoleDomainListAction;
|
import google.registry.ui.server.console.ConsoleDomainListAction;
|
||||||
import google.registry.ui.server.console.ConsoleDumDownloadAction;
|
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.ConsoleUserDataAction;
|
||||||
import google.registry.ui.server.console.ConsoleUsersAction;
|
import google.registry.ui.server.console.ConsoleUsersAction;
|
||||||
import google.registry.ui.server.console.RegistrarsAction;
|
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.ContactAction;
|
||||||
import google.registry.ui.server.console.settings.SecurityAction;
|
import google.registry.ui.server.console.settings.SecurityAction;
|
||||||
import google.registry.ui.server.console.settings.WhoisRegistrarFieldsAction;
|
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.RequestComponentBuilder;
|
||||||
import google.registry.request.RequestModule;
|
import google.registry.request.RequestModule;
|
||||||
import google.registry.request.RequestScope;
|
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.ConsoleDomainGetAction;
|
||||||
import google.registry.ui.server.console.ConsoleDomainListAction;
|
import google.registry.ui.server.console.ConsoleDomainListAction;
|
||||||
import google.registry.ui.server.console.ConsoleDumDownloadAction;
|
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.ConsoleUserDataAction;
|
||||||
import google.registry.ui.server.console.ConsoleUsersAction;
|
import google.registry.ui.server.console.ConsoleUsersAction;
|
||||||
import google.registry.ui.server.console.RegistrarsAction;
|
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.ContactAction;
|
||||||
import google.registry.ui.server.console.settings.SecurityAction;
|
import google.registry.ui.server.console.settings.SecurityAction;
|
||||||
import google.registry.ui.server.console.settings.WhoisRegistrarFieldsAction;
|
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("dryRun", dryRun);
|
||||||
params.put("clientId", command.clientId);
|
params.put("clientId", command.clientId);
|
||||||
params.put("superuser", superuser);
|
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 =
|
String requestBody =
|
||||||
Joiner.on('&').withKeyValueSeparator("=").join(filterValues(params, Objects::nonNull));
|
Joiner.on('&').withKeyValueSeparator("=").join(filterValues(params, Objects::nonNull));
|
||||||
responses.add(
|
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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// 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 com.google.common.truth.Truth.assertThat;
|
||||||
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_OPTIONAL;
|
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.FakeClock;
|
||||||
import google.registry.testing.FakeResponse;
|
import google.registry.testing.FakeResponse;
|
||||||
import google.registry.tools.GsonUtils;
|
import google.registry.tools.GsonUtils;
|
||||||
|
import google.registry.ui.server.console.ConsoleApiParams;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
@@ -166,13 +167,13 @@ public class ConsoleBulkDomainActionTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testFailure_badActionString() {
|
void testFailure_badActionString() {
|
||||||
ConsoleBulkDomainAction action = createAction("bad", null);
|
ConsoleBulkDomainAction action = createAction("bad", GSON.toJsonTree(ImmutableMap.of()));
|
||||||
action.run();
|
action.run();
|
||||||
assertThat(fakeResponse.getStatus()).isEqualTo(SC_BAD_REQUEST);
|
assertThat(fakeResponse.getStatus()).isEqualTo(SC_BAD_REQUEST);
|
||||||
assertThat(fakeResponse.getPayload())
|
assertThat(fakeResponse.getPayload())
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
"No enum constant"
|
"No enum constant"
|
||||||
+ " google.registry.ui.server.console.ConsoleBulkDomainAction.BulkAction.bad");
|
+ " google.registry.ui.server.console.domains.ConsoleDomainActionType.BulkAction.bad");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -186,7 +187,8 @@ public class ConsoleBulkDomainActionTest {
|
|||||||
@Test
|
@Test
|
||||||
void testFailure_noPermission() {
|
void testFailure_noPermission() {
|
||||||
JsonElement payload =
|
JsonElement payload =
|
||||||
GSON.toJsonTree(ImmutableMap.of("domainList", ImmutableList.of("domain.tld")));
|
GSON.toJsonTree(
|
||||||
|
ImmutableMap.of("domainList", ImmutableList.of("domain.tld"), "reason", "reason"));
|
||||||
ConsoleBulkDomainAction action =
|
ConsoleBulkDomainAction action =
|
||||||
createAction(
|
createAction(
|
||||||
"DELETE",
|
"DELETE",
|
||||||
@@ -14,4 +14,4 @@ CONSOLE /console-api/settings/contacts ContactAction GET,
|
|||||||
CONSOLE /console-api/settings/security SecurityAction POST n USER PUBLIC
|
CONSOLE /console-api/settings/security SecurityAction POST n USER PUBLIC
|
||||||
CONSOLE /console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC
|
CONSOLE /console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC
|
||||||
CONSOLE /console-api/userdata ConsoleUserDataAction GET n USER PUBLIC
|
CONSOLE /console-api/userdata ConsoleUserDataAction GET n USER PUBLIC
|
||||||
CONSOLE /console-api/users ConsoleUsersAction GET,POST,DELETE,PUT n USER PUBLIC
|
CONSOLE /console-api/users ConsoleUsersAction GET,POST,DELETE,PUT n USER PUBLIC
|
||||||
|
|||||||
@@ -80,4 +80,4 @@ CONSOLE /console-api/settings/contacts ContactAction
|
|||||||
CONSOLE /console-api/settings/security SecurityAction POST n USER PUBLIC
|
CONSOLE /console-api/settings/security SecurityAction POST n USER PUBLIC
|
||||||
CONSOLE /console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC
|
CONSOLE /console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC
|
||||||
CONSOLE /console-api/userdata ConsoleUserDataAction GET n USER PUBLIC
|
CONSOLE /console-api/userdata ConsoleUserDataAction GET n USER PUBLIC
|
||||||
CONSOLE /console-api/users ConsoleUsersAction GET,POST,DELETE,PUT n USER PUBLIC
|
CONSOLE /console-api/users ConsoleUsersAction GET,POST,DELETE,PUT n USER PUBLIC
|
||||||
|
|||||||
Reference in New Issue
Block a user