mirror of
https://github.com/google/nomulus
synced 2026-07-07 00:27:12 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d9a857133a | |||
| c7a27061d8 | |||
| 7766db36a7 | |||
| eda0f7ad7c | |||
| 67527f1560 | |||
| 4aeba6e3f7 | |||
| d6f1f5894b | |||
| 47ad569cb0 | |||
| 06934daf94 | |||
| 9a032e4bb9 | |||
| 0ab612ab23 | |||
| 403c7ad275 |
@@ -21,27 +21,23 @@ clean {
|
||||
|
||||
task npmInstallDeps(type: Exec) {
|
||||
workingDir "${consoleDir}/"
|
||||
executable 'npm'
|
||||
args 'i', '--no-audit', '--no-fund', '--loglevel=error'
|
||||
commandLine 'sh', '-c', 'npm i --no-audit --no-fund --loglevel=error'
|
||||
}
|
||||
|
||||
task runConsoleWebappLocally(type: Exec) {
|
||||
workingDir "${consoleDir}/"
|
||||
executable 'npm'
|
||||
args 'run', 'start:dev'
|
||||
commandLine 'sh', '-c', 'npm run start:dev'
|
||||
}
|
||||
|
||||
task runConsoleWebappUnitTests(type: Exec) {
|
||||
workingDir "${consoleDir}/"
|
||||
executable 'npm'
|
||||
args 'run', 'test'
|
||||
commandLine 'sh', '-c', 'npm run test'
|
||||
}
|
||||
|
||||
task buildConsoleWebapp(type: Exec) {
|
||||
workingDir "${consoleDir}/"
|
||||
executable 'npx'
|
||||
def configuration = project.getProperty('configuration')
|
||||
args 'ng', 'build', '--base-href=/console/', "--configuration=${configuration}", "--output-path=staged/dist"
|
||||
commandLine 'sh', '-c', "npx ng build --base-href=/console/ --configuration=${configuration} --output-path=staged/dist"
|
||||
doFirst {
|
||||
println "Building console for environment: ${configuration}"
|
||||
}
|
||||
@@ -52,8 +48,7 @@ task buildConsoleForAll() {}
|
||||
def createConsoleTask = { env ->
|
||||
project.tasks.register("buildConsoleFor${env.capitalize()}", Exec) {
|
||||
workingDir "${consoleDir}/"
|
||||
executable 'npx'
|
||||
args 'ng', 'build', '--base-href=/console/', "--configuration=${env}"
|
||||
commandLine 'sh', '-c', "npx ng build --base-href=/console/ --configuration=${env}"
|
||||
doFirst {
|
||||
println "Building console for environment: ${env}"
|
||||
}
|
||||
@@ -91,14 +86,12 @@ tasks.buildConsoleWebapp.mustRunAfter(tasks.buildConsoleForProduction)
|
||||
|
||||
task applyFormatting(type: Exec) {
|
||||
workingDir "${consoleDir}/"
|
||||
executable 'npm'
|
||||
args 'run', 'prettify'
|
||||
commandLine 'sh', '-c', 'npm run prettify'
|
||||
}
|
||||
|
||||
task checkFormatting(type: Exec) {
|
||||
workingDir "${consoleDir}/"
|
||||
executable 'npm'
|
||||
args 'run', 'prettify:check'
|
||||
commandLine 'sh', '-c', 'npm run prettify:check'
|
||||
}
|
||||
|
||||
tasks.buildConsoleWebapp.dependsOn(tasks.npmInstallDeps)
|
||||
|
||||
@@ -75,7 +75,7 @@ export class RegistrarDetailsComponent implements OnInit {
|
||||
}
|
||||
|
||||
checkOteStatus() {
|
||||
this.router.navigate(['ote-status/', this.registrarInEdit.registrarId], {
|
||||
this.router.navigate(['ote-status', this.registrarInEdit.registrarId], {
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.flows.picker;
|
||||
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableTable;
|
||||
@@ -255,6 +256,17 @@ public class FlowPicker {
|
||||
if (innerCommand == null && !(eppInput.getCommandWrapper() instanceof Hello)) {
|
||||
throw new MissingCommandException();
|
||||
}
|
||||
if (innerCommand instanceof ResourceCommandWrapper resourceCommandWrapper) {
|
||||
ResourceCommand resourceCommand = resourceCommandWrapper.getResourceCommand();
|
||||
if (resourceCommand != null) {
|
||||
String wrapperName = innerCommand.getClass().getSimpleName();
|
||||
String commandName = resourceCommand.getClass().getSimpleName();
|
||||
if (!wrapperName.equals(commandName)) {
|
||||
throw new MismatchedCommandException(
|
||||
Ascii.toLowerCase(wrapperName), Ascii.toLowerCase(commandName));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Try the FlowProviders until we find a match. The order matters because it's possible to
|
||||
// match multiple FlowProviders and so more specific matches are tried first.
|
||||
for (FlowProvider flowProvider : FLOW_PROVIDERS) {
|
||||
@@ -279,4 +291,14 @@ public class FlowPicker {
|
||||
super("Command missing");
|
||||
}
|
||||
}
|
||||
|
||||
/** Command wrapper and inner resource command do not match. */
|
||||
static class MismatchedCommandException extends SyntaxErrorException {
|
||||
public MismatchedCommandException(String wrapperName, String commandName) {
|
||||
super(
|
||||
String.format(
|
||||
"EPP command wrapper <%s> does not match resource command <%s>",
|
||||
wrapperName, commandName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,12 +158,12 @@ public class DomainCommand {
|
||||
throws InvalidReferencesException, ParameterValuePolicyErrorException {
|
||||
Create clone = clone(this);
|
||||
clone.nameservers = linkHosts(nullSafeImmutableCopy(clone.nameserverHostNames), now);
|
||||
if (registrantContactId != null) {
|
||||
throw new RegistrantProhibitedException();
|
||||
}
|
||||
if (!isNullOrEmpty(foreignKeyedDesignatedContacts)) {
|
||||
throw new ContactsProhibitedException();
|
||||
}
|
||||
if (registrantContactId != null) {
|
||||
throw new RegistrantProhibitedException();
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,8 @@ import google.registry.model.domain.token.VKeyConverter_AllocationToken;
|
||||
import google.registry.model.tld.label.PremiumList;
|
||||
import google.registry.model.tld.label.ReservedList;
|
||||
import google.registry.persistence.EntityCallbacksListener.RecursivePostPersist;
|
||||
import google.registry.persistence.EntityCallbacksListener.RecursivePostRemove;
|
||||
import google.registry.persistence.EntityCallbacksListener.RecursivePostUpdate;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.converter.AllocationTokenVkeyListUserType;
|
||||
import google.registry.persistence.converter.BillingCostTransitionUserType;
|
||||
@@ -219,9 +221,12 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
|
||||
* Invalidates the cache entry.
|
||||
*
|
||||
* <p>This is called automatically when the tld is saved. One should also call it when a tld is
|
||||
* deleted.
|
||||
* deleted. This only affects the pod-local cache so most pods won't catch it, but it's still the
|
||||
* right thing to do.
|
||||
*/
|
||||
@RecursivePostPersist
|
||||
@RecursivePostRemove
|
||||
@RecursivePostUpdate
|
||||
public void invalidateInCache() {
|
||||
CACHE.invalidate(tldStr);
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ import google.registry.ui.server.console.settings.SecurityAction;
|
||||
ToolsServerModule.class,
|
||||
WhiteboxModule.class
|
||||
})
|
||||
interface RequestComponent {
|
||||
public interface RequestComponent {
|
||||
FlowComponent.Builder flowComponentBuilder();
|
||||
|
||||
BrdaCopyAction brdaCopyAction();
|
||||
|
||||
@@ -14,11 +14,9 @@
|
||||
|
||||
package google.registry.request;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Streams;
|
||||
import java.util.Comparator;
|
||||
@@ -64,18 +62,6 @@ public class RouterDisplayHelper {
|
||||
return formatRoutes(Router.extractRoutesFromComponent(componentClass).values());
|
||||
}
|
||||
|
||||
public static ImmutableList<String> extractHumanReadableRoutesWithWrongService(
|
||||
Class<?> componentClass, Action.Service expectedService) {
|
||||
return Router.extractRoutesFromComponent(componentClass).values().stream()
|
||||
.filter(route -> route.action().service() != expectedService)
|
||||
.map(
|
||||
route ->
|
||||
String.format(
|
||||
"%s (%s%s)",
|
||||
route.actionClass(), route.action().service(), route.action().path()))
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
private static String getFormatString(Map<String, Integer> columnWidths) {
|
||||
return String.format(
|
||||
FORMAT,
|
||||
|
||||
@@ -14,12 +14,14 @@
|
||||
|
||||
package google.registry.tmch;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.request.UrlConnectionUtils.getResponseBytes;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_NO_CONTENT;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.io.ByteSource;
|
||||
import google.registry.request.Action;
|
||||
@@ -62,6 +64,8 @@ public final class NordnVerifyAction implements Runnable {
|
||||
static final String NORDN_URL_PARAM = "nordnUrl";
|
||||
static final String NORDN_LOG_ID_PARAM = "nordnLogId";
|
||||
|
||||
private static final String MARKSDB_URL_BEGINNING = "ry.marksdb.org";
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@Inject LordnRequestInitializer lordnRequestInitializer;
|
||||
@@ -104,6 +108,12 @@ public final class NordnVerifyAction implements Runnable {
|
||||
*/
|
||||
@VisibleForTesting
|
||||
LordnLog verify() throws IOException, GeneralSecurityException {
|
||||
String host = Ascii.toLowerCase(url.getHost());
|
||||
checkArgument(
|
||||
host.startsWith(MARKSDB_URL_BEGINNING),
|
||||
"URL %s must start with %s",
|
||||
url,
|
||||
MARKSDB_URL_BEGINNING);
|
||||
logger.atInfo().log("LORDN verify task %s: Sending request to URL %s", actionLogId, url);
|
||||
HttpURLConnection connection = urlConnectionService.createConnection(url);
|
||||
lordnRequestInitializer.initialize(connection, tld);
|
||||
|
||||
@@ -25,6 +25,7 @@ import google.registry.request.HttpException.BadRequestException;
|
||||
import google.registry.request.Parameter;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
|
||||
@@ -53,7 +54,7 @@ public final class TmchModule {
|
||||
@Parameter(NordnVerifyAction.NORDN_URL_PARAM)
|
||||
static URL provideNordnUrl(HttpServletRequest req) {
|
||||
try {
|
||||
return new URL(extractRequiredParameter(req, NordnVerifyAction.NORDN_URL_PARAM));
|
||||
return URI.create(extractRequiredParameter(req, NordnVerifyAction.NORDN_URL_PARAM)).toURL();
|
||||
} catch (MalformedURLException e) {
|
||||
throw new BadRequestException("Bad URL: " + NordnVerifyAction.NORDN_URL_PARAM);
|
||||
}
|
||||
|
||||
@@ -14,27 +14,17 @@
|
||||
|
||||
package google.registry.tools;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import google.registry.module.RequestComponent;
|
||||
import google.registry.request.RouterDisplayHelper;
|
||||
|
||||
/** Generates the routing map file used for unit testing. */
|
||||
@Parameters(commandDescription = "Generate a routing map file")
|
||||
final class GetRoutingMapCommand implements Command {
|
||||
|
||||
@Parameter(
|
||||
names = {"-c", "--class"},
|
||||
description =
|
||||
"Request component class (e.g. google.registry.module.backend.BackendRequestComponent)"
|
||||
+ " for which routing map should be generated",
|
||||
required = true
|
||||
)
|
||||
private String serviceClassName;
|
||||
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
System.out.println(
|
||||
RouterDisplayHelper.extractHumanReadableRoutesFromComponent(
|
||||
Class.forName(serviceClassName)));
|
||||
RouterDisplayHelper.extractHumanReadableRoutesFromComponent(RequestComponent.class));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ public class RefreshDnsForAllDomainsAction implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
checkArgument(!tlds.isEmpty(), "Must specify TLDs to refresh");
|
||||
assertTldsExist(tlds);
|
||||
checkArgument(batchSize > 0, "Must specify a positive number for batch size");
|
||||
logger.atInfo().log("Enqueueing DNS refresh tasks for TLDs %s.", tlds);
|
||||
|
||||
@@ -37,6 +37,7 @@ import com.google.gson.annotations.Expose;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.ConsoleUpdateHistory;
|
||||
import google.registry.model.console.GlobalRole;
|
||||
import google.registry.model.console.RegistrarRole;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserRoles;
|
||||
@@ -149,10 +150,15 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
throw new BadRequestException("Total users amount per registrar is limited to 4");
|
||||
}
|
||||
|
||||
User userToAppend = verifyUserExists(this.userData.get().emailAddress);
|
||||
if (userToAppend.getUserRoles().isAdmin()
|
||||
|| !userToAppend.getUserRoles().getGlobalRole().equals(GlobalRole.NONE)) {
|
||||
throw new BadRequestException(
|
||||
"Cannot append a global administrator or user with a global role to a registrar");
|
||||
}
|
||||
|
||||
updateUserRegistrarRoles(
|
||||
this.userData.get().emailAddress,
|
||||
registrarId,
|
||||
requestRoleToAllowedRoles(this.userData.get().role));
|
||||
userToAppend, registrarId, requestRoleToAllowedRoles(this.userData.get().role));
|
||||
|
||||
sendConfirmationEmail(registrarId, this.userData.get().emailAddress, "Added existing user");
|
||||
consoleApiParams.response().setStatus(SC_OK);
|
||||
@@ -164,7 +170,14 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
}
|
||||
|
||||
String email = this.userData.get().emailAddress;
|
||||
User updatedUser = updateUserRegistrarRoles(email, registrarId, null);
|
||||
User userToDelete = verifyUserExists(email);
|
||||
if (userToDelete.getUserRoles().isAdmin()
|
||||
|| !userToDelete.getUserRoles().getGlobalRole().equals(GlobalRole.NONE)) {
|
||||
throw new BadRequestException(
|
||||
"Cannot delete a global administrator or user with a global role");
|
||||
}
|
||||
|
||||
User updatedUser = updateUserRegistrarRoles(userToDelete, registrarId, null);
|
||||
|
||||
// User has no registrars assigned
|
||||
if (updatedUser.getUserRoles().getRegistrarRoles().isEmpty()) {
|
||||
@@ -251,7 +264,7 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
}
|
||||
|
||||
updateUserRegistrarRoles(
|
||||
this.userData.get().emailAddress,
|
||||
verifyUserExists(this.userData.get().emailAddress),
|
||||
registrarId,
|
||||
requestRoleToAllowedRoles(this.userData.get().role));
|
||||
|
||||
@@ -297,9 +310,8 @@ public class ConsoleUsersAction extends ConsoleApiAction {
|
||||
return false;
|
||||
}
|
||||
|
||||
private User updateUserRegistrarRoles(String email, String registrarId, RegistrarRole newRole) {
|
||||
private User updateUserRegistrarRoles(User user, String registrarId, RegistrarRole newRole) {
|
||||
Map<String, RegistrarRole> updatedRegistrarRoles;
|
||||
User user = verifyUserExists(email);
|
||||
if (newRole == null) {
|
||||
updatedRegistrarRoles =
|
||||
user.getUserRoles().getRegistrarRoles().entrySet().stream()
|
||||
|
||||
@@ -115,6 +115,10 @@ public class XmlTransformer {
|
||||
// Prevent XXE attacks.
|
||||
xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
|
||||
xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
|
||||
xmlInputFactory.setXMLResolver(
|
||||
(publicID, systemID, baseURI, namespace) -> {
|
||||
throw new XMLStreamException("Entity resolution disabled.");
|
||||
});
|
||||
return xmlInputFactory;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,8 @@ Child elements of the <create> command.
|
||||
minOccurs="0"/>
|
||||
<element name="registrant" type="eppcom:clIDType"
|
||||
minOccurs="0"/>
|
||||
<element name="contact" type="domain:contactType"
|
||||
minOccurs="0" maxOccurs="unbounded"/>
|
||||
<element name="authInfo" type="domain:authInfoType"/>
|
||||
</sequence>
|
||||
</complexType>
|
||||
@@ -98,6 +100,22 @@ If attributes, addresses are optional and follow the
|
||||
structure defined in the host mapping.
|
||||
-->
|
||||
|
||||
<complexType name="contactType">
|
||||
<simpleContent>
|
||||
<extension base="eppcom:clIDType">
|
||||
<attribute name="type" type="domain:contactAttrType"/>
|
||||
</extension>
|
||||
</simpleContent>
|
||||
</complexType>
|
||||
|
||||
<simpleType name="contactAttrType">
|
||||
<restriction base="token">
|
||||
<enumeration value="admin"/>
|
||||
<enumeration value="billing"/>
|
||||
<enumeration value="tech"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
|
||||
<complexType name="authInfoType">
|
||||
<choice>
|
||||
<element name="pw" type="eppcom:pwAuthInfoType"/>
|
||||
@@ -198,6 +216,8 @@ Data elements that can be added or removed.
|
||||
<sequence>
|
||||
<element name="ns" type="domain:nsType"
|
||||
minOccurs="0"/>
|
||||
<element name="contact" type="domain:contactType"
|
||||
minOccurs="0" maxOccurs="unbounded"/>
|
||||
<element name="status" type="domain:statusType"
|
||||
minOccurs="0" maxOccurs="11"/>
|
||||
</sequence>
|
||||
@@ -299,6 +319,8 @@ Child response elements.
|
||||
minOccurs="0" maxOccurs="11"/>
|
||||
<element name="registrant" type="eppcom:clIDType"
|
||||
minOccurs="0"/>
|
||||
<element name="contact" type="domain:contactType"
|
||||
minOccurs="0" maxOccurs="unbounded"/>
|
||||
<element name="ns" type="domain:nsType"
|
||||
minOccurs="0"/>
|
||||
<element name="host" type="eppcom:labelType"
|
||||
|
||||
@@ -60,6 +60,9 @@
|
||||
maxOccurs="unbounded"/>
|
||||
<element name="registrant"
|
||||
type="eppcom:clIDType" minOccurs="0"/>
|
||||
<element name="contact"
|
||||
type="domain:contactType"
|
||||
minOccurs="0" maxOccurs="unbounded"/>
|
||||
<element name="ns"
|
||||
type="domain:nsType" minOccurs="0"/>
|
||||
<element name="clID"
|
||||
|
||||
@@ -116,4 +116,13 @@ class EppXmlSanitizerTest {
|
||||
String sanitizedXml = sanitizeEppXml(inputXml.getBytes(UTF_16LE));
|
||||
assertThat(sanitizedXml).isEqualTo(inputXml);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSanitize_withDtd_returnsBase64() {
|
||||
String inputXml = "<!DOCTYPE foo [<!ENTITY xxe SYSTEM \"file:///etc/passwd\">]><pw>&xxe;</pw>";
|
||||
byte[] inputXmlBytes = inputXml.getBytes(UTF_8);
|
||||
// Since DTDs are disabled, parsing should fail and fallback to base64 encoding of input.
|
||||
String expectedBase64 = Base64.getMimeEncoder().encodeToString(inputXmlBytes);
|
||||
assertThat(sanitizeEppXml(inputXmlBytes).trim()).isEqualTo(expectedBase64.trim());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,6 @@ import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppException.UnimplementedExtensionException;
|
||||
import google.registry.flows.EppRequestSource;
|
||||
import google.registry.flows.ExtensionManager.UndeclaredServiceExtensionException;
|
||||
import google.registry.flows.FlowUtils;
|
||||
import google.registry.flows.FlowUtils.NotLoggedInException;
|
||||
import google.registry.flows.FlowUtils.UnknownCurrencyEppException;
|
||||
import google.registry.flows.ResourceFlowTestCase;
|
||||
@@ -144,6 +143,7 @@ import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTok
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForRegistrarException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.NonexistentAllocationTokenException;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.flows.exceptions.OnlyToolCanPassMetadataException;
|
||||
import google.registry.flows.exceptions.ResourceCreateContentionException;
|
||||
import google.registry.model.billing.BillingBase;
|
||||
@@ -1937,8 +1937,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
void testFailure_minimumDataset_noRegistrantButSomeOtherContactTypes() throws Exception {
|
||||
setEppInput("domain_create_other_contact_types.xml");
|
||||
persistHosts();
|
||||
EppException thrown =
|
||||
assertThrows(FlowUtils.GenericXmlSyntaxErrorException.class, this::runFlow);
|
||||
EppException thrown = assertThrows(ContactsProhibitedException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,6 @@ import google.registry.config.RegistryConfig;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppException.UnimplementedExtensionException;
|
||||
import google.registry.flows.EppRequestSource;
|
||||
import google.registry.flows.FlowUtils;
|
||||
import google.registry.flows.FlowUtils.NotLoggedInException;
|
||||
import google.registry.flows.ResourceFlowTestCase;
|
||||
import google.registry.flows.ResourceFlowUtils.AddExistingValueException;
|
||||
@@ -90,6 +89,7 @@ import google.registry.flows.domain.DomainFlowUtils.SecDnsAllUsageException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.TooManyDsRecordsException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.TooManyNameserversException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.UrgentAttributeNotSupportedException;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.flows.exceptions.OnlyToolCanPassMetadataException;
|
||||
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
|
||||
import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException;
|
||||
@@ -283,8 +283,10 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
// This EPP adds a new technical contact mak21 that wasn't already present.
|
||||
setEppInput("domain_update_empty_registrant.xml");
|
||||
persistReferencedEntities();
|
||||
persistDomain();
|
||||
// Fails because the update adds some new contacts, although the registrant has been removed.
|
||||
assertThrows(FlowUtils.GenericXmlSyntaxErrorException.class, this::persistDomain);
|
||||
EppException thrown = assertThrows(ContactsProhibitedException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
private void modifyDomainToHave13Nameservers() throws Exception {
|
||||
|
||||
@@ -27,6 +27,7 @@ import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
import static google.registry.testing.HostSubject.assertAboutHosts;
|
||||
import static google.registry.util.DateTimeUtils.minusDays;
|
||||
import static google.registry.util.DateTimeUtils.plusDays;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@@ -42,6 +43,8 @@ import google.registry.flows.host.HostFlowUtils.HostNameNotLowerCaseException;
|
||||
import google.registry.flows.host.HostFlowUtils.HostNameNotNormalizedException;
|
||||
import google.registry.flows.host.HostFlowUtils.HostNameNotPunyCodedException;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.reporting.HistoryEntry.Type;
|
||||
@@ -311,6 +314,30 @@ class HostDeleteFlowTest extends ResourceFlowTestCase<HostDeleteFlow, Host> {
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_failfastWhenLinkedToDomainInRedemption() throws Exception {
|
||||
createTld("tld");
|
||||
Host host = persistActiveHost("ns1.example.tld");
|
||||
Domain domain = persistResource(DatabaseHelper.newDomain("example.tld"));
|
||||
persistResource(
|
||||
domain
|
||||
.asBuilder()
|
||||
.setNameservers(ImmutableSet.of(host.createVKey()))
|
||||
.setDeletionTime(plusDays(clock.now(), 35))
|
||||
.addGracePeriod(
|
||||
GracePeriod.create(
|
||||
GracePeriodStatus.REDEMPTION,
|
||||
domain.getRepoId(),
|
||||
plusDays(clock.now(), 1),
|
||||
"TheRegistrar",
|
||||
null))
|
||||
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
|
||||
.build());
|
||||
|
||||
EppException thrown = assertThrows(ResourceToDeleteIsReferencedException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_nonLowerCaseHostname() {
|
||||
setEppInput("host_delete.xml", ImmutableMap.of("HOSTNAME", "NS1.EXAMPLE.NET"));
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright 2026 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.flows.picker;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import google.registry.flows.Flow;
|
||||
import google.registry.flows.domain.DomainCheckFlow;
|
||||
import google.registry.flows.domain.DomainCreateFlow;
|
||||
import google.registry.model.domain.DomainCommand;
|
||||
import google.registry.model.eppcommon.EppXmlTransformer;
|
||||
import google.registry.model.eppinput.EppInput;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link FlowPicker}. */
|
||||
class FlowPickerTest {
|
||||
|
||||
@Test
|
||||
void testGetFlowClass_matchingWrapperAndCommand_returnsFlow() throws Exception {
|
||||
EppInput checkEppInput = EppInput.create(EppInput.Check.create(new DomainCommand.Check()));
|
||||
Class<? extends Flow> checkFlow = FlowPicker.getFlowClass(checkEppInput);
|
||||
assertThat(checkFlow).isEqualTo(DomainCheckFlow.class);
|
||||
|
||||
EppInput createEppInput = EppInput.create(EppInput.Create.create(new DomainCommand.Create()));
|
||||
Class<? extends Flow> createFlow = FlowPicker.getFlowClass(createEppInput);
|
||||
assertThat(createFlow).isEqualTo(DomainCreateFlow.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetFlowClass_mismatchedWrapperAndCommand_throwsSyntaxErrorException() {
|
||||
EppInput mismatchedEppInput1 =
|
||||
EppInput.create(EppInput.Check.create(new DomainCommand.Create()));
|
||||
assertThat(
|
||||
assertThrows(
|
||||
FlowPicker.MismatchedCommandException.class,
|
||||
() -> FlowPicker.getFlowClass(mismatchedEppInput1)))
|
||||
.hasMessageThat()
|
||||
.isEqualTo("EPP command wrapper <check> does not match resource command <create>");
|
||||
|
||||
EppInput mismatchedEppInput2 =
|
||||
EppInput.create(EppInput.Create.create(new DomainCommand.Check()));
|
||||
assertThat(
|
||||
assertThrows(
|
||||
FlowPicker.MismatchedCommandException.class,
|
||||
() -> FlowPicker.getFlowClass(mismatchedEppInput2)))
|
||||
.hasMessageThat()
|
||||
.contains("EPP command wrapper <create> does not match resource command <check>");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetFlowClass_mismatchedXml_throwsMismatchedCommandException() throws Exception {
|
||||
String mismatchedXml =
|
||||
"""
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<command>
|
||||
<check>
|
||||
<domain:create xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
|
||||
<domain:name>example.com</domain:name>
|
||||
<domain:authInfo>
|
||||
<domain:pw>fooBAR123</domain:pw>
|
||||
</domain:authInfo>
|
||||
</domain:create>
|
||||
</check>
|
||||
<clTRID>ABC-12345</clTRID>
|
||||
</command>
|
||||
</epp>
|
||||
""";
|
||||
|
||||
// Verify JAXB successfully unmarshals the mismatched XML without throwing any errors
|
||||
EppInput eppInput = EppXmlTransformer.unmarshal(EppInput.class, mismatchedXml.getBytes(UTF_8));
|
||||
assertThat(eppInput).isNotNull();
|
||||
|
||||
// Verify that FlowPicker intercepts the unmarshalled input and blocks it
|
||||
assertThat(
|
||||
assertThrows(
|
||||
FlowPicker.MismatchedCommandException.class,
|
||||
() -> FlowPicker.getFlowClass(eppInput)))
|
||||
.hasMessageThat()
|
||||
.contains("EPP command wrapper <check> does not match resource command <create>");
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@ package google.registry.model.domain;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveHost;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import google.registry.flows.FlowUtils;
|
||||
import google.registry.flows.domain.DomainFlowUtils.RegistrantProhibitedException;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.ResourceCommandTestCase;
|
||||
import google.registry.model.eppinput.EppInput;
|
||||
import google.registry.model.eppinput.EppInput.ResourceCommandWrapper;
|
||||
@@ -88,9 +88,10 @@ class DomainCommandTest extends ResourceCommandTestCase {
|
||||
void testCreate_cloneAndLinkReferences_failsWithContacts() throws Exception {
|
||||
persistActiveHost("ns1.example.net");
|
||||
persistActiveHost("ns2.example.net");
|
||||
DomainCommand.Create create =
|
||||
(DomainCommand.Create) loadEppResourceCommand("domain_create_with_contacts.xml");
|
||||
assertThrows(
|
||||
FlowUtils.GenericXmlSyntaxErrorException.class,
|
||||
() -> loadEppResourceCommand("domain_create_with_contacts.xml"));
|
||||
ContactsProhibitedException.class, () -> create.cloneAndLinkReferences(fakeClock.now()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -138,9 +139,10 @@ class DomainCommandTest extends ResourceCommandTestCase {
|
||||
void testUpdate_cloneAndLinkReferences_failsWithContacts() throws Exception {
|
||||
persistActiveHost("ns1.example.com");
|
||||
persistActiveHost("ns2.example.com");
|
||||
DomainCommand.Update update =
|
||||
(DomainCommand.Update) loadEppResourceCommand("domain_update_with_contacts.xml");
|
||||
assertThrows(
|
||||
FlowUtils.GenericXmlSyntaxErrorException.class,
|
||||
() -> loadEppResourceCommand("domain_update_with_contacts.xml"));
|
||||
ContactsProhibitedException.class, () -> update.cloneAndLinkReferences(fakeClock.now()));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -37,7 +37,7 @@ import google.registry.testing.FakeUrlConnectionService;
|
||||
import google.registry.util.UrlConnectionException;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -88,7 +88,8 @@ public final class UpdateRegistrarRdapBaseUrlsActionTest {
|
||||
private void assertCorrectRequestSent() throws Exception {
|
||||
assertThat(urlConnectionService.getConnectedUrls())
|
||||
.containsExactly(
|
||||
new URL("https://www.iana.org/assignments/registrar-ids/registrar-ids-1.csv"));
|
||||
URI.create("https://www.iana.org/assignments/registrar-ids/registrar-ids-1.csv")
|
||||
.toURL());
|
||||
verify(connection).setRequestProperty("Accept-Encoding", "gzip");
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationT
|
||||
import google.registry.testing.FakeUrlConnectionService;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
@@ -74,7 +74,7 @@ class IcannHttpReporterTest {
|
||||
assertThat(reporter.send(FAKE_PAYLOAD, "test-transactions-201706.csv")).isTrue();
|
||||
|
||||
assertThat(urlConnectionService.getConnectedUrls())
|
||||
.containsExactly(new URL("https://fake-transactions.url/test/2017-06"));
|
||||
.containsExactly(URI.create("https://fake-transactions.url/test/2017-06").toURL());
|
||||
String userPass = "test_ry:fakePass";
|
||||
String expectedAuth =
|
||||
String.format("Basic %s", BaseEncoding.base64().encode(StringUtils.getBytesUtf8(userPass)));
|
||||
@@ -88,7 +88,7 @@ class IcannHttpReporterTest {
|
||||
assertThat(reporter.send(FAKE_PAYLOAD, "xn--abc123-transactions-201706.csv")).isTrue();
|
||||
|
||||
assertThat(urlConnectionService.getConnectedUrls())
|
||||
.containsExactly(new URL("https://fake-transactions.url/xn--abc123/2017-06"));
|
||||
.containsExactly(URI.create("https://fake-transactions.url/xn--abc123/2017-06").toURL());
|
||||
String userPass = "xn--abc123_ry:fakePass";
|
||||
String expectedAuth =
|
||||
String.format("Basic %s", BaseEncoding.base64().encode(StringUtils.getBytesUtf8(userPass)));
|
||||
|
||||
@@ -55,7 +55,7 @@ public class GoldenFileTestHelper {
|
||||
|
||||
public static GoldenFileTestHelper assertThatRoutesFromComponent(Class<?> component) {
|
||||
return assertThat(RouterDisplayHelper.extractHumanReadableRoutesFromComponent(component))
|
||||
.createdByNomulusCommand("get_routing_map -c " + component.getName());
|
||||
.createdByNomulusCommand("get_routing_map");
|
||||
}
|
||||
|
||||
public GoldenFileTestHelper createdByNomulusCommand(String nomulusCommand) {
|
||||
|
||||
@@ -57,7 +57,7 @@ import google.registry.util.UrlConnectionException;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.security.SecureRandom;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
@@ -249,7 +249,7 @@ class NordnUploadActionTest {
|
||||
.setRequestProperty(eq(CONTENT_TYPE), startsWith("multipart/form-data; boundary="));
|
||||
verify(httpUrlConnection).setRequestMethod("POST");
|
||||
assertThat(httpUrlConnection.getURL())
|
||||
.isEqualTo(new URL("http://127.0.0.1/LORDN/tld/" + phase));
|
||||
.isEqualTo(URI.create("http://127.0.0.1/LORDN/tld/" + phase).toURL());
|
||||
assertThat(connectionOutputStream.toString(UTF_8)).contains(csv);
|
||||
verifyColumnCleared(domain1);
|
||||
verifyColumnCleared(domain2);
|
||||
|
||||
@@ -38,7 +38,7 @@ import google.registry.testing.FakeResponse;
|
||||
import google.registry.testing.FakeUrlConnectionService;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -49,33 +49,35 @@ class NordnVerifyActionTest {
|
||||
|
||||
private static final String LOG_ACCEPTED =
|
||||
"""
|
||||
1,2012-08-16T02:15:00.0Z,2012-08-16T00:00:00.0Z,0000000000000478Nzs+3VMkR8ckuUynOLmyeqTmZQSbzDuf/R50n2n5QX4=,accepted,no-warnings,1
|
||||
roid,result-code
|
||||
SH8013-REP,2000""";
|
||||
1,2012-08-16T02:15:00.0Z,2012-08-16T00:00:00.0Z,0000000000000478Nzs+3VMkR8ckuUynOLmyeqTmZQSbzDuf/R50n2n5QX4=,accepted,no-warnings,1
|
||||
roid,result-code
|
||||
SH8013-REP,2000\
|
||||
""";
|
||||
|
||||
private static final String LOG_REJECTED =
|
||||
"""
|
||||
1,2012-08-16T02:15:00.0Z,2012-08-16T00:00:00.0Z,0000000000000478Nzs+3VMkR8ckuUynOLmyeqTmZQSbzDuf/R50n2n5QX4=,rejected,no-warnings,1
|
||||
roid,result-code
|
||||
SH8013-REP,2001""";
|
||||
1,2012-08-16T02:15:00.0Z,2012-08-16T00:00:00.0Z,0000000000000478Nzs+3VMkR8ckuUynOLmyeqTmZQSbzDuf/R50n2n5QX4=,rejected,no-warnings,1
|
||||
roid,result-code
|
||||
SH8013-REP,2001\
|
||||
""";
|
||||
|
||||
private static final String LOG_WARNINGS =
|
||||
"""
|
||||
1,2012-08-16T02:15:00.0Z,2012-08-16T00:00:00.0Z,0000000000000478Nzs+3VMkR8ckuUynOLmyeqTmZQSbzDuf/R50n2n5QX4=,accepted,warnings-present,3
|
||||
roid,result-code
|
||||
SH8013-REP,2001
|
||||
lulz-roid,3609
|
||||
sabokitty-roid,3610
|
||||
""";
|
||||
1,2012-08-16T02:15:00.0Z,2012-08-16T00:00:00.0Z,0000000000000478Nzs+3VMkR8ckuUynOLmyeqTmZQSbzDuf/R50n2n5QX4=,accepted,warnings-present,3
|
||||
roid,result-code
|
||||
SH8013-REP,2001
|
||||
lulz-roid,3609
|
||||
sabokitty-roid,3610
|
||||
""";
|
||||
|
||||
private static final String LOG_ERRORS =
|
||||
"""
|
||||
1,2012-08-16T02:15:00.0Z,2012-08-16T00:00:00.0Z,0000000000000478Nzs+3VMkR8ckuUynOLmyeqTmZQSbzDuf/R50n2n5QX4=,accepted,warnings-present,3
|
||||
roid,result-code
|
||||
SH8013-REP,2000
|
||||
lulz-roid,4601
|
||||
bogpog,4611
|
||||
""";
|
||||
1,2012-08-16T02:15:00.0Z,2012-08-16T00:00:00.0Z,0000000000000478Nzs+3VMkR8ckuUynOLmyeqTmZQSbzDuf/R50n2n5QX4=,accepted,warnings-present,3
|
||||
roid,result-code
|
||||
SH8013-REP,2000
|
||||
lulz-roid,4601
|
||||
bogpog,4611
|
||||
""";
|
||||
|
||||
@RegisterExtension
|
||||
final JpaIntegrationTestExtension jpa =
|
||||
@@ -101,14 +103,15 @@ class NordnVerifyActionTest {
|
||||
.thenReturn(new ByteArrayInputStream(LOG_ACCEPTED.getBytes(UTF_8)));
|
||||
action.lordnRequestInitializer = lordnRequestInitializer;
|
||||
action.response = response;
|
||||
action.url = new URL("http://127.0.0.1/blobio");
|
||||
action.url = URI.create("http://ry.marksdb.org/blobio").toURL();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("DirectInvocationOnMock")
|
||||
void testSuccess_sendHttpRequest_urlIsCorrect() throws Exception {
|
||||
action.run();
|
||||
assertThat(httpUrlConnection.getURL()).isEqualTo(new URL("http://127.0.0.1/blobio"));
|
||||
assertThat(httpUrlConnection.getURL())
|
||||
.isEqualTo(URI.create("http://ry.marksdb.org/blobio").toURL());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -162,4 +165,22 @@ class NordnVerifyActionTest {
|
||||
ConflictException thrown = assertThrows(ConflictException.class, action::run);
|
||||
assertThat(thrown).hasMessageThat().contains("Not ready");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_badUrl() throws Exception {
|
||||
action.url = URI.create("http://example.com/blobio").toURL();
|
||||
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, action::run);
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
.isEqualTo("URL http://example.com/blobio must start with ry.marksdb.org");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("DirectInvocationOnMock")
|
||||
void testSuccess_uppercaseUrl() throws Exception {
|
||||
action.url = URI.create("http://RY.MARKSDB.ORG/blobio").toURL();
|
||||
action.run();
|
||||
assertThat(httpUrlConnection.getURL())
|
||||
.isEqualTo(URI.create("http://RY.MARKSDB.ORG/blobio").toURL());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import static org.mockito.Mockito.when;
|
||||
import google.registry.config.RegistryConfig.ConfigModule.TmchCaMode;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CRLException;
|
||||
import java.security.cert.CertificateNotYetValidException;
|
||||
@@ -39,7 +39,7 @@ class TmchCrlActionTest extends TmchActionTestCase {
|
||||
TmchCrlAction action = new TmchCrlAction();
|
||||
action.marksdb = marksdb;
|
||||
action.tmchCertificateAuthority = new TmchCertificateAuthority(tmchCaMode, clock);
|
||||
action.tmchCrlUrl = new URL("https://sloth.lol/tmch.crl");
|
||||
action.tmchCrlUrl = URI.create("https://sloth.lol/tmch.crl").toURL();
|
||||
return action;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ class TmchCrlActionTest extends TmchActionTestCase {
|
||||
newTmchCrlAction(TmchCaMode.PILOT).run();
|
||||
verify(httpUrlConnection).getInputStream();
|
||||
assertThat(urlConnectionService.getConnectedUrls())
|
||||
.containsExactly(new URL("https://sloth.lol/tmch.crl"));
|
||||
.containsExactly(URI.create("https://sloth.lol/tmch.crl").toURL());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+15
@@ -24,6 +24,7 @@ import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistDeletedDomain;
|
||||
import static google.registry.util.DateTimeUtils.minusYears;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -151,4 +152,18 @@ public class RefreshDnsForAllDomainsActionTest {
|
||||
action.run();
|
||||
assertDnsRequestsWithRequestTime(clock.now(), 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_runAction_emptyTlds_throwsException() {
|
||||
action =
|
||||
new RefreshDnsForAllDomainsAction(
|
||||
response,
|
||||
ImmutableSet.of(),
|
||||
Optional.of(10),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
new Random());
|
||||
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, action::run);
|
||||
assertThat(thrown).hasMessageThat().isEqualTo("Must specify TLDs to refresh");
|
||||
}
|
||||
}
|
||||
|
||||
+37
@@ -29,13 +29,20 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSetMultimap;
|
||||
import google.registry.flows.PasswordOnlyTransportCredentials;
|
||||
import google.registry.model.console.ConsoleUpdateHistory;
|
||||
import google.registry.model.console.RegistrarRole;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserRoles;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.RequestModule;
|
||||
import google.registry.request.auth.AuthResult;
|
||||
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
|
||||
import google.registry.testing.ConsoleApiParamsUtils;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.ui.server.console.ConsoleEppPasswordAction.EppPasswordData;
|
||||
import google.registry.util.EmailMessage;
|
||||
import jakarta.mail.internet.AddressException;
|
||||
@@ -123,6 +130,36 @@ class ConsoleEppPasswordActionTest extends ConsoleActionBaseTestCase {
|
||||
assertThat(history.getDescription()).hasValue("TheRegistrar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_noPermission() throws IOException {
|
||||
User user =
|
||||
persistResource(
|
||||
new User.Builder()
|
||||
.setEmailAddress("no.permission@example.tld")
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder()
|
||||
.setRegistrarRoles(
|
||||
ImmutableMap.of("TheRegistrar", RegistrarRole.ACCOUNT_MANAGER))
|
||||
.build())
|
||||
.build());
|
||||
ConsoleEppPasswordAction action =
|
||||
createAction(user, "TheRegistrar", "foobar", "randomPassword", "randomPassword");
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_FORBIDDEN);
|
||||
}
|
||||
|
||||
private ConsoleEppPasswordAction createAction(
|
||||
User user,
|
||||
String registrarId,
|
||||
String oldPassword,
|
||||
String newPassword,
|
||||
String newPasswordRepeat)
|
||||
throws IOException {
|
||||
consoleApiParams = ConsoleApiParamsUtils.createFake(AuthResult.createUser(user));
|
||||
response = (FakeResponse) consoleApiParams.response();
|
||||
return createAction(registrarId, oldPassword, newPassword, newPasswordRepeat);
|
||||
}
|
||||
|
||||
private ConsoleEppPasswordAction createAction(
|
||||
String registrarId, String oldPassword, String newPassword, String newPasswordRepeat)
|
||||
throws IOException {
|
||||
|
||||
@@ -407,6 +407,103 @@ class ConsoleUsersActionTest extends ConsoleActionBaseTestCase {
|
||||
.contains("Can't update user not associated with registrarId TheRegistrar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_appendUser() throws IOException {
|
||||
User user = DatabaseHelper.createAdminUser("email@email.com");
|
||||
AuthResult authResult = AuthResult.createUser(user);
|
||||
ConsoleUsersAction action =
|
||||
createAction(
|
||||
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
|
||||
Optional.of("POST"),
|
||||
Optional.of(
|
||||
new UserData("test3@test.com", null, RegistrarRole.TECH_CONTACT.name(), null)));
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
User appendedUser = DatabaseHelper.loadByKey(VKey.create(User.class, "test3@test.com"));
|
||||
assertThat(appendedUser.getUserRoles().getRegistrarRoles().get("TheRegistrar"))
|
||||
.isEqualTo(RegistrarRole.TECH_CONTACT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appendUser_globalAdmin() throws IOException {
|
||||
User user = DatabaseHelper.createAdminUser("email@email.com");
|
||||
AuthResult authResult = AuthResult.createUser(user);
|
||||
DatabaseHelper.persistResource(
|
||||
new User.Builder()
|
||||
.setEmailAddress("globaladmin@test.com")
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder().setIsAdmin(true).setGlobalRole(GlobalRole.NONE).build())
|
||||
.build());
|
||||
|
||||
ConsoleUsersAction action =
|
||||
createAction(
|
||||
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
|
||||
Optional.of("POST"),
|
||||
Optional.of(
|
||||
new UserData(
|
||||
"globaladmin@test.com", null, RegistrarRole.TECH_CONTACT.name(), null)));
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_BAD_REQUEST);
|
||||
assertThat(response.getPayload())
|
||||
.contains("Cannot append a global administrator or user with a global role");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appendUser_globalRole() throws IOException {
|
||||
User user = DatabaseHelper.createAdminUser("email@email.com");
|
||||
AuthResult authResult = AuthResult.createUser(user);
|
||||
DatabaseHelper.persistResource(
|
||||
new User.Builder()
|
||||
.setEmailAddress("support@test.com")
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder()
|
||||
.setIsAdmin(false)
|
||||
.setGlobalRole(GlobalRole.SUPPORT_AGENT)
|
||||
.build())
|
||||
.build());
|
||||
|
||||
ConsoleUsersAction action =
|
||||
createAction(
|
||||
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
|
||||
Optional.of("POST"),
|
||||
Optional.of(
|
||||
new UserData("support@test.com", null, RegistrarRole.TECH_CONTACT.name(), null)));
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_BAD_REQUEST);
|
||||
assertThat(response.getPayload())
|
||||
.contains("Cannot append a global administrator or user with a global role");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_deleteUser_globalAdmin() throws IOException {
|
||||
User user = DatabaseHelper.createAdminUser("email@email.com");
|
||||
AuthResult authResult = AuthResult.createUser(user);
|
||||
// Historically associated global admin
|
||||
DatabaseHelper.persistResource(
|
||||
new User.Builder()
|
||||
.setEmailAddress("globaladmin@test.com")
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder()
|
||||
.setIsAdmin(true)
|
||||
.setGlobalRole(GlobalRole.NONE)
|
||||
.setRegistrarRoles(
|
||||
ImmutableMap.of("TheRegistrar", RegistrarRole.PRIMARY_CONTACT))
|
||||
.build())
|
||||
.build());
|
||||
|
||||
ConsoleUsersAction action =
|
||||
createAction(
|
||||
Optional.of(ConsoleApiParamsUtils.createFake(authResult)),
|
||||
Optional.of("DELETE"),
|
||||
Optional.of(
|
||||
new UserData(
|
||||
"globaladmin@test.com", null, RegistrarRole.ACCOUNT_MANAGER.toString(), null)));
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_BAD_REQUEST);
|
||||
assertThat(response.getPayload())
|
||||
.contains("Cannot delete a global administrator or user with a global role");
|
||||
}
|
||||
|
||||
private ConsoleUsersAction createAction(
|
||||
Optional<ConsoleApiParams> maybeConsoleApiParams,
|
||||
Optional<String> method,
|
||||
|
||||
@@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import google.registry.util.UrlChecker;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
@@ -55,11 +56,12 @@ class DockerWebDriverExtension implements BeforeAllCallback, AfterAllCallback {
|
||||
URL url;
|
||||
try {
|
||||
url =
|
||||
new URL(
|
||||
String.format(
|
||||
"http://%s:%d",
|
||||
container.getContainerIpAddress(),
|
||||
container.getMappedPort(CHROME_DRIVER_SERVICE_PORT)));
|
||||
URI.create(
|
||||
String.format(
|
||||
"http://%s:%d",
|
||||
container.getContainerIpAddress(),
|
||||
container.getMappedPort(CHROME_DRIVER_SERVICE_PORT)))
|
||||
.toURL();
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
<domain:hostObj>ns.fake-domain.tld</domain:hostObj>
|
||||
</domain:ns>
|
||||
<domain:registrant>google-mon</domain:registrant>
|
||||
<domain:contact type="admin">google-mon</domain:contact>
|
||||
<domain:contact type="tech">google-mon</domain:contact>
|
||||
<domain:authInfo>
|
||||
<domain:pw>insecure</domain:pw>
|
||||
</domain:authInfo>
|
||||
|
||||
@@ -185,7 +185,7 @@ public class TokenStore {
|
||||
if (Duration.between(availableTokens.timestamp(), clock.now())
|
||||
.compareTo(config.getRefreshPeriod())
|
||||
>= 0) {
|
||||
tokensMap.remove(user);
|
||||
tokensMap.remove(user, availableTokens);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user