mirror of
https://github.com/google/nomulus
synced 2026-07-07 16:46:56 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d9a857133a | |||
| c7a27061d8 | |||
| 7766db36a7 | |||
| eda0f7ad7c | |||
| 67527f1560 | |||
| 4aeba6e3f7 | |||
| d6f1f5894b | |||
| 47ad569cb0 | |||
| 06934daf94 | |||
| 9a032e4bb9 | |||
| 0ab612ab23 | |||
| 403c7ad275 | |||
| 11d625b837 | |||
| 6a47287da7 | |||
| cdc0ffe831 | |||
| 7c23413d83 | |||
| fe222bbdcf | |||
| f770f6a46d | |||
| e071f5579c | |||
| 6080cd2f7a | |||
| 90f583910e | |||
| a85bf5c30a | |||
| dcfe939c38 | |||
| ae61922318 | |||
| 84e97aa2db | |||
| 4df4bf1489 | |||
| 9faabb0f1d | |||
| ea9d717378 | |||
| d1769b29ef |
@@ -94,7 +94,7 @@ Do not wait for the user to tell you to improve the skills; it is your responsib
|
||||
|
||||
# Gemini Engineering Guide: Nomulus Codebase
|
||||
|
||||
This document captures high-level architectural patterns, lessons learned from large-scale refactorings (like the Joda-Time to `java.time` migration), and specific instructions to avoid common pitfalls in this environment.
|
||||
This document captures high-level architectural patterns, lessons learned from large-scale refactorings, and specific instructions to avoid common pitfalls in this environment.
|
||||
|
||||
## 🏛 Architecture Overview
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Nomulus is an open source, scalable, cloud-based service for operating
|
||||
[top-level domains](https://en.wikipedia.org/wiki/Top-level_domain) (TLDs). It
|
||||
is the authoritative source for the TLDs that it runs, meaning that it is
|
||||
responsible for tracking domain name ownership and handling registrations,
|
||||
renewals, availability checks, and WHOIS requests. End-user registrants (i.e.,
|
||||
renewals, availability checks, and RDAP requests. End-user registrants (i.e.,
|
||||
people or companies that want to register a domain name) use an intermediate
|
||||
domain name registrar acting on their behalf to interact with the registry.
|
||||
|
||||
@@ -97,7 +97,7 @@ Nomulus has the following capabilities:
|
||||
for details), and an implementation based on
|
||||
[Google Cloud Secret Manager](https://cloud.google.com/security/products/secret-manager) is
|
||||
available.
|
||||
* **TPC Proxy**: Nomulus is built on top of the [Jetty](https://jetty.org/)
|
||||
* **TCP Proxy**: Nomulus is built on top of the [Jetty](https://jetty.org/)
|
||||
container that implements the [Jakarta Servlet](https://jakarta.ee/specifications/servlet/)
|
||||
specification and only serves HTTP/S traffic. A proxy to translate raw TCP traffic (e.g., EPP)
|
||||
to and from HTTP is provided.
|
||||
|
||||
@@ -9,15 +9,14 @@ expected to change.
|
||||
|
||||
## Deployment
|
||||
|
||||
The webapp is deployed with the nomulus default service war to GKE.
|
||||
During nomulus default service war build task, gradle script triggers the
|
||||
following:
|
||||
The webapp is deployed as part of the default Nomulus GKE service image.
|
||||
During the image build task, the Gradle script triggers the following:
|
||||
|
||||
1) Console webapp build script `buildConsoleWebapp`, which installs
|
||||
dependencies, assembles a compiled ts -> js, minified, optimized static
|
||||
artifact (html, css, js)
|
||||
2) Artifact assembled in step 1 then gets copied to core project web artifact
|
||||
location, so that it can be deployed with the rest of the core webapp
|
||||
2) Artifact assembled in step 1 then gets copied to the jetty webapp resource
|
||||
location, so that it can be staged inside the default GKE service container.
|
||||
|
||||
## Development server
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -48,7 +48,11 @@ interface DomainData {
|
||||
selector: 'app-response-dialog',
|
||||
template: `
|
||||
<h2 mat-dialog-title>{{ data.title }}</h2>
|
||||
<mat-dialog-content [innerHTML]="data.content" />
|
||||
<mat-dialog-content>
|
||||
@for (line of data.content; track line) {
|
||||
<div>{{ line }}</div>
|
||||
}
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button (click)="onClose()">Close</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -59,7 +63,7 @@ export class ResponseDialogComponent {
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<ReasonDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
public data: { title: string; content: string }
|
||||
public data: { title: string; content: string[] }
|
||||
) {}
|
||||
|
||||
onClose(): void {
|
||||
@@ -312,11 +316,13 @@ export class DomainListComponent {
|
||||
this.dialog.open(ResponseDialogComponent, {
|
||||
data: {
|
||||
title: 'Domain Deletion Results',
|
||||
content: `Successfully deleted - ${successCount} domain(s)<br/>Failed to delete - ${failureCount} domain(s)<br/>${
|
||||
content: [
|
||||
`Successfully deleted - ${successCount} domain(s)`,
|
||||
`Failed to delete - ${failureCount} domain(s)`,
|
||||
failureCount
|
||||
? 'Some domains could not be deleted due to ongoing processes or server errors. '
|
||||
: ''
|
||||
}Please check the table for more information.`,
|
||||
? 'Some domains could not be deleted due to ongoing processes or server errors. Please check the table for more information.'
|
||||
: 'Please check the table for more information.',
|
||||
],
|
||||
},
|
||||
});
|
||||
this.selection.clear();
|
||||
|
||||
@@ -97,10 +97,9 @@
|
||||
@for (column of columns; track column.columnDef) {
|
||||
<mat-list-item role="listitem">
|
||||
<span class="console-app__list-key">{{ column.header }} </span>
|
||||
<span
|
||||
class="console-app__list-value"
|
||||
[innerHTML]="column.cell(registrarInEdit).replace('<br/>', ' ')"
|
||||
></span>
|
||||
<span class="console-app__list-value">{{
|
||||
column.cell(registrarInEdit)
|
||||
}}</span>
|
||||
</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -49,10 +49,9 @@
|
||||
<mat-header-cell *matHeaderCellDef>
|
||||
{{ column.header }}
|
||||
</mat-header-cell>
|
||||
<mat-cell
|
||||
*matCellDef="let row"
|
||||
[innerHTML]="column.cell(row)"
|
||||
></mat-cell>
|
||||
<mat-cell *matCellDef="let row" style="white-space: pre-wrap">{{
|
||||
column.cell(row)
|
||||
}}</mat-cell>
|
||||
</ng-container>
|
||||
}
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
|
||||
@@ -56,7 +56,7 @@ export const columns = [
|
||||
cell: (record: Registrar) =>
|
||||
`${Object.entries(record.billingAccountMap || {}).reduce(
|
||||
(acc, [key, val]) => {
|
||||
return `${acc}${key}=${val}<br/>`;
|
||||
return `${acc}${key}=${val}\n`;
|
||||
},
|
||||
''
|
||||
)}`,
|
||||
|
||||
@@ -25,7 +25,18 @@
|
||||
@for (column of columns; track column) {
|
||||
<ng-container [matColumnDef]="column.columnDef">
|
||||
<mat-header-cell *matHeaderCellDef> {{ column.header }} </mat-header-cell>
|
||||
<mat-cell *matCellDef="let row" [innerHTML]="column.cell(row)"></mat-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
@if (column.columnDef === 'name') {
|
||||
<div class="contact__name-column">
|
||||
<div class="contact__name-column-title">{{ row.name }}</div>
|
||||
<div class="contact__name-column-roles">
|
||||
{{ row.userFriendlyTypes.join(" • ") }}
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
{{ column.cell(row) }}
|
||||
}
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
}
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
|
||||
@@ -34,14 +34,7 @@ export default class ContactComponent {
|
||||
{
|
||||
columnDef: 'name',
|
||||
header: 'Name',
|
||||
cell: (contact: ViewReadyContact) => `
|
||||
<div class="contact__name-column">
|
||||
<div class="contact__name-column-title">${contact.name}</div>
|
||||
<div class="contact__name-column-roles">${contact.userFriendlyTypes.join(
|
||||
' • '
|
||||
)}</div>
|
||||
</div>
|
||||
`,
|
||||
cell: (contact: ViewReadyContact) => `${contact.name}`,
|
||||
},
|
||||
{
|
||||
columnDef: 'emailAddress',
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<mat-header-cell *matHeaderCellDef>
|
||||
{{ column.header }}
|
||||
</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row" [innerHTML]="column.cell(row)"></mat-cell>
|
||||
<mat-cell *matCellDef="let row">{{ column.cell(row) }}</mat-cell>
|
||||
</ng-container>
|
||||
}
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
|
||||
@@ -25,10 +25,20 @@ import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import io.protostuff.Input;
|
||||
import io.protostuff.LinkedBuffer;
|
||||
import io.protostuff.Output;
|
||||
import io.protostuff.Pipe;
|
||||
import io.protostuff.ProtostuffIOUtil;
|
||||
import io.protostuff.Schema;
|
||||
import io.protostuff.WireFormat;
|
||||
import io.protostuff.runtime.DefaultIdStrategy;
|
||||
import io.protostuff.runtime.Delegate;
|
||||
import io.protostuff.runtime.RuntimeSchema;
|
||||
import java.io.IOException;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Optional;
|
||||
import redis.clients.jedis.AbstractPipeline;
|
||||
@@ -52,11 +62,20 @@ public class SimplifiedJedisClient {
|
||||
Domain.class, "d_",
|
||||
Host.class, "h_");
|
||||
|
||||
/** We need to inform Protostuff of the custom {@link InetAddress} delegates. */
|
||||
private static DefaultIdStrategy createIdStrategy() {
|
||||
DefaultIdStrategy strategy = new DefaultIdStrategy();
|
||||
strategy.registerDelegate(new GenericInetAddressDelegate<>(InetAddress.class));
|
||||
strategy.registerDelegate(new GenericInetAddressDelegate<>(Inet4Address.class));
|
||||
strategy.registerDelegate(new GenericInetAddressDelegate<>(Inet6Address.class));
|
||||
return strategy;
|
||||
}
|
||||
|
||||
private static final ImmutableMap<Class<? extends EppResource>, Schema<? extends EppResource>>
|
||||
VALUE_SCHEMAS =
|
||||
ImmutableMap.of(
|
||||
Domain.class, RuntimeSchema.getSchema(Domain.class),
|
||||
Host.class, RuntimeSchema.getSchema(Host.class));
|
||||
Host.class, RuntimeSchema.getSchema(Host.class, createIdStrategy()));
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@@ -151,4 +170,46 @@ public class SimplifiedJedisClient {
|
||||
checkArgument(VALUE_SCHEMAS.containsKey(clazz), "Unknown class type %s", clazz);
|
||||
return (Schema<V>) VALUE_SCHEMAS.get(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom Protostuff {@link Delegate} for {@link InetAddress} and its subclasses.
|
||||
*
|
||||
* <p>This is required in Java 17+ because Protostuff's default runtime schema serialization
|
||||
* relies on reflection. Since {@link InetAddress} is part of the encapsulated {@code java.base}
|
||||
* module, reflective access is restricted and throws {@link
|
||||
* java.lang.reflect.InaccessibleObjectException}.
|
||||
*
|
||||
* <p>This delegate serializes the IP address as a raw byte array using {@link
|
||||
* InetAddress#getAddress()} and reconstructs it using {@link InetAddress#getByAddress(byte[])}
|
||||
*/
|
||||
private record GenericInetAddressDelegate<T extends InetAddress>(Class<T> clazz)
|
||||
implements Delegate<T> {
|
||||
|
||||
@Override
|
||||
public WireFormat.FieldType getFieldType() {
|
||||
return WireFormat.FieldType.BYTES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> typeClass() {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public T readFrom(Input input) throws IOException {
|
||||
return (T) InetAddress.getByAddress(input.readByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(Output output, int number, T value, boolean repeated) throws IOException {
|
||||
output.writeByteArray(number, value.getAddress(), repeated);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated)
|
||||
throws IOException {
|
||||
output.writeByteArray(number, input.readByteArray(), repeated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,6 +173,18 @@ public final class RegistryConfig {
|
||||
return config.registrarConsole.supportEmailAddress;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("consoleHistoryQueryLimit")
|
||||
public static int provideConsoleHistoryQueryLimit(RegistryConfigSettings config) {
|
||||
return config.registrarConsole.historyQueryLimit;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("consoleBulkDomainActionLimit")
|
||||
public static int provideConsoleBulkDomainActionLimit(RegistryConfigSettings config) {
|
||||
return config.registrarConsole.bulkDomainActionLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* The DUM file name, used as a file name base for DUM csv file
|
||||
*
|
||||
|
||||
@@ -181,6 +181,8 @@ public class RegistryConfigSettings {
|
||||
public String supportPhoneNumber;
|
||||
public String supportEmailAddress;
|
||||
public String technicalDocsUrl;
|
||||
public int historyQueryLimit;
|
||||
public int bulkDomainActionLimit;
|
||||
}
|
||||
|
||||
/** Configuration for monitoring. */
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<queue>
|
||||
<name>dns-refresh</name>
|
||||
<max-dispatches-per-second>100</max-dispatches-per-second>
|
||||
<max-retry-duration>82800s</max-retry-duration>
|
||||
</queue>
|
||||
|
||||
<!-- Queue for publishing DNS updates in batches. -->
|
||||
@@ -29,6 +30,7 @@
|
||||
<min-backoff>30s</min-backoff>
|
||||
<max-backoff>1800s</max-backoff>
|
||||
<max-doublings>0</max-doublings>
|
||||
<max-retry-duration>82800s</max-retry-duration>
|
||||
</queue>
|
||||
|
||||
<!-- Queue for uploading RDE deposits to the escrow provider. -->
|
||||
@@ -59,6 +61,7 @@
|
||||
<queue>
|
||||
<name>async-host-rename</name>
|
||||
<max-dispatches-per-second>1</max-dispatches-per-second>
|
||||
<max-retry-duration>82800s</max-retry-duration>
|
||||
</queue>
|
||||
|
||||
<!-- Queue for tasks that wait for a Beam pipeline to complete (i.e. Spec11 and invoicing). -->
|
||||
@@ -110,11 +113,12 @@
|
||||
<max-attempts>3</max-attempts>
|
||||
</queue>
|
||||
|
||||
<!-- <!– Queue for async actions that should be run at some point in the future. –>-->
|
||||
<!-- Queue for async actions that should be run at some point in the future.-->
|
||||
<queue>
|
||||
<name>async-actions</name>
|
||||
<max-dispatches-per-second>1</max-dispatches-per-second>
|
||||
<max-concurrent-dispatches>5</max-concurrent-dispatches>
|
||||
<max-retry-duration>82800s</max-retry-duration>
|
||||
</queue>
|
||||
|
||||
</entries>
|
||||
|
||||
@@ -380,6 +380,12 @@ registrarConsole:
|
||||
# URL linking to directory of technical support docs on the registry.
|
||||
technicalDocsUrl: http://example.com/your_support_docs/
|
||||
|
||||
# Maximum number of history records returned in a single query.
|
||||
historyQueryLimit: 500
|
||||
|
||||
# Maximum number of domains allowed in a single bulk action.
|
||||
bulkDomainActionLimit: 500
|
||||
|
||||
monitoring:
|
||||
# Max queries per second for the Google Cloud Monitoring V3 (aka Stackdriver)
|
||||
# API. The limit can be adjusted by contacting Cloud Support.
|
||||
|
||||
-10
@@ -266,16 +266,6 @@
|
||||
<schedule>0 15 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/wipeOutContactHistoryPii]]></url>
|
||||
<name>wipeOutContactHistoryPii</name>
|
||||
<description>
|
||||
This job runs weekly to wipe out PII fields of ContactHistory entities
|
||||
that have been in the database for a certain period of time.
|
||||
</description>
|
||||
<schedule>0 15 * * 1</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/bsaDownload]]></url>
|
||||
<name>bsaDownload</name>
|
||||
|
||||
-10
@@ -155,16 +155,6 @@
|
||||
<schedule>*/1 * * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/wipeOutContactHistoryPii]]></url>
|
||||
<name>wipeOutContactHistoryPii</name>
|
||||
<description>
|
||||
This job runs weekly to wipe out PII fields of ContactHistory entities
|
||||
that have been in the database for a certain period of time.
|
||||
</description>
|
||||
<schedule>0 15 * * 1</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/bsaDownload]]></url>
|
||||
<name>bsaDownload</name>
|
||||
|
||||
@@ -44,6 +44,8 @@ import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@@ -134,7 +136,9 @@ public final class ResourceFlowUtils {
|
||||
}
|
||||
String authPassword = authInfo.getPw().getValue();
|
||||
String domainPassword = domain.getAuthInfo().getPw().getValue();
|
||||
if (!domainPassword.equals(authPassword)) {
|
||||
if (!MessageDigest.isEqual(
|
||||
authPassword.getBytes(StandardCharsets.UTF_8),
|
||||
domainPassword.getBytes(StandardCharsets.UTF_8))) {
|
||||
throw new BadAuthInfoForResourceException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,6 +389,10 @@ public class DomainFlowUtils {
|
||||
if (alg > 255 || alg < 0) {
|
||||
return true;
|
||||
}
|
||||
if (DomainDsData.FORBIDDEN_ALGORITHMS.contains(alg)
|
||||
&& FeatureFlag.isActiveNow(FORBID_INSECURE_ALGORITHMS_RFC_9904)) {
|
||||
return true;
|
||||
}
|
||||
// Algorithms that are reserved or unassigned will just return a string representation of their
|
||||
// integer wire value.
|
||||
String algorithm = Algorithm.string(alg);
|
||||
|
||||
@@ -283,7 +283,7 @@ public final class DomainPricingLogic {
|
||||
return tld.getStandardRenewCost(dateTime).multipliedBy(years);
|
||||
}
|
||||
if (token.getRenewalPriceBehavior().equals(RenewalPriceBehavior.SPECIFIED)) {
|
||||
return token.getRenewalPrice().get();
|
||||
return token.getRenewalPrice().get().multipliedBy(years);
|
||||
}
|
||||
}
|
||||
return getDomainCostWithDiscount(
|
||||
@@ -328,12 +328,13 @@ public final class DomainPricingLogic {
|
||||
// Apply the allocation token discount, if applicable.
|
||||
if (token.getDiscountPrice().isPresent()
|
||||
&& tld.getCurrency().equals(token.getDiscountPrice().get().getCurrencyUnit())) {
|
||||
int nonDiscountedYears = Math.max(0, years - token.getDiscountYears());
|
||||
int discountedYears = Math.min(years, token.getDiscountYears());
|
||||
int nonDiscountedYears = years - discountedYears;
|
||||
totalDomainFlowCost =
|
||||
token
|
||||
.getDiscountPrice()
|
||||
.get()
|
||||
.multipliedBy(token.getDiscountYears())
|
||||
.multipliedBy(discountedYears)
|
||||
.plus(subsequentYearCost.orElse(firstYearCost).multipliedBy(nonDiscountedYears));
|
||||
} else if (token.getDiscountFraction() > 0) {
|
||||
int discountedYears = Math.min(years, token.getDiscountYears());
|
||||
|
||||
@@ -116,15 +116,21 @@ public class HostFlowUtils {
|
||||
if (inetAddresses == null) {
|
||||
return;
|
||||
}
|
||||
if (inetAddresses.stream().anyMatch(InetAddress::isLoopbackAddress)) {
|
||||
throw new LoopbackIpNotValidForHostException();
|
||||
for (InetAddress inetAddress : inetAddresses) {
|
||||
if (inetAddress.isLoopbackAddress()
|
||||
|| inetAddress.isLinkLocalAddress()
|
||||
|| inetAddress.isSiteLocalAddress()
|
||||
|| inetAddress.isAnyLocalAddress()
|
||||
|| inetAddress.isMulticastAddress()) {
|
||||
throw new IpAddressNotRoutableException(inetAddress.getHostAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Loopback IPs are not valid for hosts. */
|
||||
static class LoopbackIpNotValidForHostException extends ParameterValuePolicyErrorException {
|
||||
public LoopbackIpNotValidForHostException() {
|
||||
super("Loopback IPs are not valid for hosts");
|
||||
/** IP address is not a public, routable address. */
|
||||
static class IpAddressNotRoutableException extends ParameterValuePolicyErrorException {
|
||||
public IpAddressNotRoutableException(String ipAddress) {
|
||||
super(String.format("IP address %s is not a public, globally routable address", ipAddress));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,9 @@ public class PasswordResetRequest extends ImmutableObject implements Buildable {
|
||||
checkArgumentNotNull(getInstance().requester, "Requester must be specified");
|
||||
checkArgumentNotNull(getInstance().destinationEmail, "Destination email must be specified");
|
||||
checkArgumentNotNull(getInstance().registrarId, "Registrar ID must be specified");
|
||||
getInstance().verificationCode = UUID.randomUUID().toString();
|
||||
if (getInstance().verificationCode == null) {
|
||||
getInstance().verificationCode = UUID.randomUUID().toString();
|
||||
}
|
||||
return super.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.model.domain.secdns;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import jakarta.persistence.Access;
|
||||
@@ -26,6 +27,7 @@ import jakarta.xml.bind.annotation.XmlTransient;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
import jakarta.xml.bind.annotation.adapters.HexBinaryAdapter;
|
||||
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
import org.xbill.DNS.DNSSEC.Algorithm;
|
||||
|
||||
/** Base class for {@link DomainDsData} and {@link DomainDsDataHistory}. */
|
||||
@MappedSuperclass
|
||||
@@ -33,6 +35,16 @@ import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
@XmlType(propOrder = {"keyTag", "algorithm", "digestType", "digest"})
|
||||
public abstract class DomainDsDataBase extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
// A set of algorithms that we do not allow. See RFC 9904 for more details.
|
||||
public static final ImmutableSet<Integer> FORBIDDEN_ALGORITHMS =
|
||||
ImmutableSet.of(
|
||||
Algorithm.RSAMD5,
|
||||
Algorithm.RSASHA1,
|
||||
Algorithm.RSA_NSEC3_SHA1,
|
||||
Algorithm.DSA,
|
||||
Algorithm.DSA_NSEC3_SHA1,
|
||||
Algorithm.ECC_GOST);
|
||||
|
||||
@XmlTransient @Transient @Insignificant String domainRepoId;
|
||||
|
||||
/** The identifier for this particular key in the domain. */
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -21,10 +21,8 @@ import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
import com.google.cloud.storage.BlobId;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.net.MediaType;
|
||||
@@ -41,6 +39,8 @@ import jakarta.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** Copy all registrar detail reports in a given bucket's subdirectory from GCS to Drive. */
|
||||
@Action(
|
||||
@@ -54,6 +54,9 @@ public final class CopyDetailReportsAction implements Runnable {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private static final Pattern FILENAME_PATTERN =
|
||||
Pattern.compile("^invoice_details_[0-9]{4}-[0-9]{2}_(.+)_.+\\.csv$");
|
||||
|
||||
private final String billingBucket;
|
||||
private final String invoiceDirectoryPrefix;
|
||||
private final DriveConnection driveConnection;
|
||||
@@ -101,8 +104,13 @@ public final class CopyDetailReportsAction implements Runnable {
|
||||
new ImmutableMultimap.Builder<>();
|
||||
for (String detailReportName : detailReportObjectNames) {
|
||||
// The standard report format is "invoice_details_yyyy-MM_registrarId_tld.csv
|
||||
// TODO(larryruili): Determine a safer way of enforcing this.
|
||||
String registrarId = Iterables.get(Splitter.on('_').split(detailReportName), 3);
|
||||
Matcher matcher = FILENAME_PATTERN.matcher(detailReportName);
|
||||
if (!matcher.matches()) {
|
||||
logger.atWarning().log(
|
||||
"Detail report filename '%s' does not match the expected pattern.", detailReportName);
|
||||
continue;
|
||||
}
|
||||
String registrarId = matcher.group(1);
|
||||
Optional<Registrar> registrar = Registrar.loadByRegistrarId(registrarId);
|
||||
if (registrar.isEmpty()) {
|
||||
logger.atWarning().log(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -43,6 +43,7 @@ import jakarta.inject.Qualifier;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Supplier;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -88,8 +89,8 @@ public class AuthModule {
|
||||
TokenVerifier provideIapTokenVerifier(
|
||||
@Config("projectIdNumber") long projectIdNumber,
|
||||
@Named("backendServiceIdMap") Supplier<ImmutableMap<String, Long>> backendServiceIdMap) {
|
||||
com.google.auth.oauth2.TokenVerifier.Builder tokenVerifierBuilder =
|
||||
com.google.auth.oauth2.TokenVerifier.newBuilder().setIssuer(IAP_ISSUER_URL);
|
||||
ConcurrentHashMap<String, com.google.auth.oauth2.TokenVerifier> tokenVerifiers =
|
||||
new ConcurrentHashMap<>();
|
||||
return (String service, String token) -> {
|
||||
Long backendServiceId = backendServiceIdMap.get().get(service);
|
||||
checkNotNull(
|
||||
@@ -98,7 +99,15 @@ public class AuthModule {
|
||||
service,
|
||||
backendServiceIdMap);
|
||||
String audience = String.format(IAP_AUDIENCE_FORMAT, projectIdNumber, backendServiceId);
|
||||
return tokenVerifierBuilder.setAudience(audience).build().verify(token);
|
||||
com.google.auth.oauth2.TokenVerifier verifier =
|
||||
tokenVerifiers.computeIfAbsent(
|
||||
audience,
|
||||
aud ->
|
||||
com.google.auth.oauth2.TokenVerifier.newBuilder()
|
||||
.setIssuer(IAP_ISSUER_URL)
|
||||
.setAudience(aud)
|
||||
.build());
|
||||
return verifier.verify(token);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.google.common.hash.Hashing;
|
||||
import google.registry.model.server.ServerSecret;
|
||||
import google.registry.util.Clock;
|
||||
import jakarta.inject.Inject;
|
||||
import java.security.MessageDigest;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
@@ -102,7 +103,7 @@ public final class XsrfTokenManager {
|
||||
}
|
||||
// Reconstruct the token to verify validity.
|
||||
String reconstructedToken = encodeToken(ServerSecret.get().asBytes(), email, timestampMillis);
|
||||
if (!token.equals(reconstructedToken)) {
|
||||
if (!MessageDigest.isEqual(token.getBytes(UTF_8), reconstructedToken.getBytes(UTF_8))) {
|
||||
logger.atWarning().log(
|
||||
"Reconstructed XSRF mismatch (got != expected): %s != %s", token, reconstructedToken);
|
||||
return false;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package google.registry.tools;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
|
||||
|
||||
import com.beust.jcommander.IStringConverter;
|
||||
@@ -46,7 +47,7 @@ record DsRecord(int keyTag, int alg, int digestType, String digest) {
|
||||
String.format("DS record has an invalid digest length: %s", digest));
|
||||
}
|
||||
|
||||
if (DomainFlowUtils.algorithmIsInvalid(alg)) {
|
||||
if (tm().reTransact(() -> DomainFlowUtils.algorithmIsInvalid(alg))) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("DS record uses an unrecognized algorithm: %d", alg));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -105,7 +105,8 @@ public abstract class ConsoleApiAction implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkPermission(User user, String registrarId, ConsolePermission permission) {
|
||||
protected static void checkPermission(
|
||||
User user, String registrarId, ConsolePermission permission) {
|
||||
if (!user.getUserRoles().hasPermission(registrarId, permission)) {
|
||||
throw new ConsolePermissionForbiddenException(
|
||||
String.format(
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.flows.EppException.AuthenticationErrorException;
|
||||
import google.registry.flows.PasswordOnlyTransportCredentials;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.ConsoleUpdateHistory;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
@@ -84,6 +85,8 @@ public class ConsoleEppPasswordAction extends ConsoleApiAction {
|
||||
eppRequestBody.newPassword().equals(eppRequestBody.newPasswordRepeat()),
|
||||
"New password fields don't match");
|
||||
|
||||
checkPermission(user, eppRequestBody.registrarId(), ConsolePermission.CONFIGURE_EPP_CONNECTION);
|
||||
|
||||
Registrar registrar;
|
||||
try {
|
||||
registrar = registrarAccessor.getRegistrar(eppRequestBody.registrarId());
|
||||
|
||||
@@ -62,17 +62,20 @@ public class ConsoleHistoryDataAction extends ConsoleApiAction {
|
||||
private final Optional<String> consoleUserEmail;
|
||||
|
||||
private final String supportEmail;
|
||||
private final int historyQueryLimit;
|
||||
|
||||
@Inject
|
||||
public ConsoleHistoryDataAction(
|
||||
ConsoleApiParams consoleApiParams,
|
||||
@Config("supportEmail") String supportEmail,
|
||||
@Config("consoleHistoryQueryLimit") int historyQueryLimit,
|
||||
@Parameter("registrarId") String registrarId,
|
||||
@Parameter("consoleUserEmail") Optional<String> consoleUserEmail) {
|
||||
super(consoleApiParams);
|
||||
this.registrarId = registrarId;
|
||||
this.consoleUserEmail = consoleUserEmail;
|
||||
this.supportEmail = supportEmail;
|
||||
this.historyQueryLimit = historyQueryLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -117,6 +120,7 @@ public class ConsoleHistoryDataAction extends ConsoleApiAction {
|
||||
.createNativeQuery(SQL_REGISTRAR_HISTORY, ConsoleUpdateHistory.class)
|
||||
.setParameter("registrarId", registrarId)
|
||||
.setHint("org.hibernate.fetchSize", 1000)
|
||||
.setMaxResults(historyQueryLimit)
|
||||
.getResultList());
|
||||
|
||||
List<ConsoleUpdateHistory> formattedHistoryList =
|
||||
|
||||
@@ -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()
|
||||
|
||||
+18
-7
@@ -86,7 +86,7 @@ public class PasswordResetRequestAction extends ConsoleApiAction {
|
||||
"Must provide registry lock email to reset");
|
||||
requiredPermission = ConsolePermission.MANAGE_USERS;
|
||||
destinationEmail = passwordResetRequestData.registryLockEmail;
|
||||
checkUserExistsWithRegistryLockEmail(destinationEmail);
|
||||
checkUserExistsWithRegistryLockEmail(destinationEmail, registrarId);
|
||||
emailSubject = "Registry lock password reset request";
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Unknown type " + type);
|
||||
@@ -121,12 +121,23 @@ public class PasswordResetRequestAction extends ConsoleApiAction {
|
||||
.sendEmail(EmailMessage.create(emailSubject, body, destinationAddress));
|
||||
}
|
||||
|
||||
static User checkUserExistsWithRegistryLockEmail(String destinationEmail) {
|
||||
return tm().createQueryComposer(User.class)
|
||||
.where("registryLockEmailAddress", QueryComposer.Comparator.EQ, destinationEmail)
|
||||
.first()
|
||||
.orElseThrow(
|
||||
() -> new IllegalArgumentException("Unknown user with lock email " + destinationEmail));
|
||||
static User checkUserExistsWithRegistryLockEmail(String destinationEmail, String registrarId) {
|
||||
User targetUser =
|
||||
tm().createQueryComposer(User.class)
|
||||
.where("registryLockEmailAddress", QueryComposer.Comparator.EQ, destinationEmail)
|
||||
.first()
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalArgumentException(
|
||||
"Unknown user with lock email " + destinationEmail));
|
||||
|
||||
// Prevent IDOR: Ensure the resolved user has the right permission
|
||||
checkArgument(
|
||||
targetUser.getUserRoles().hasPermission(registrarId, ConsolePermission.REGISTRY_LOCK),
|
||||
"User %s does not have permission REGISTRY_LOCK on registrar %s",
|
||||
targetUser.getEmailAddress(),
|
||||
registrarId);
|
||||
return targetUser;
|
||||
}
|
||||
|
||||
private String getAdminPocEmail(String registrarId) {
|
||||
|
||||
+12
-2
@@ -98,7 +98,9 @@ public class PasswordResetVerifyAction extends ConsoleApiAction {
|
||||
}
|
||||
|
||||
private void handleRegistryLockPasswordReset(PasswordResetRequest request) {
|
||||
User affectedUser = checkUserExistsWithRegistryLockEmail(request.getDestinationEmail());
|
||||
User affectedUser =
|
||||
checkUserExistsWithRegistryLockEmail(
|
||||
request.getDestinationEmail(), request.getRegistrarId());
|
||||
tm().put(
|
||||
affectedUser
|
||||
.asBuilder()
|
||||
@@ -119,7 +121,15 @@ public class PasswordResetVerifyAction extends ConsoleApiAction {
|
||||
ConsolePermission requiredVerifyPermission =
|
||||
switch (request.getType()) {
|
||||
case EPP -> ConsolePermission.MANAGE_USERS;
|
||||
case REGISTRY_LOCK -> ConsolePermission.REGISTRY_LOCK;
|
||||
case REGISTRY_LOCK -> {
|
||||
checkArgument(
|
||||
user.getRegistryLockEmailAddress()
|
||||
.map(address -> address.equals(request.getDestinationEmail()))
|
||||
.orElse(false),
|
||||
"User %s has the wrong registry lock email address",
|
||||
user.getEmailAddress());
|
||||
yield ConsolePermission.REGISTRY_LOCK;
|
||||
}
|
||||
};
|
||||
checkPermission(user, request.getRegistrarId(), requiredVerifyPermission);
|
||||
if (plusHours(request.getRequestTime(), 1).isBefore(tm().getTxTime())) {
|
||||
|
||||
+12
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.ui.server.console.domains;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
@@ -22,6 +23,7 @@ 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.config.RegistryConfig.Config;
|
||||
import google.registry.flows.EppController;
|
||||
import google.registry.flows.EppRequestSource;
|
||||
import google.registry.flows.PasswordOnlyTransportCredentials;
|
||||
@@ -64,11 +66,13 @@ public class ConsoleBulkDomainAction extends ConsoleApiAction {
|
||||
private final String registrarId;
|
||||
private final String bulkDomainAction;
|
||||
private final Optional<JsonElement> optionalJsonPayload;
|
||||
private final int bulkDomainActionLimit;
|
||||
|
||||
@Inject
|
||||
public ConsoleBulkDomainAction(
|
||||
ConsoleApiParams consoleApiParams,
|
||||
EppController eppController,
|
||||
@Config("consoleBulkDomainActionLimit") int bulkDomainActionLimit,
|
||||
@Parameter("registrarId") String registrarId,
|
||||
@Parameter("bulkDomainAction") String bulkDomainAction,
|
||||
@OptionalJsonPayload Optional<JsonElement> optionalJsonPayload) {
|
||||
@@ -77,6 +81,7 @@ public class ConsoleBulkDomainAction extends ConsoleApiAction {
|
||||
this.registrarId = registrarId;
|
||||
this.bulkDomainAction = bulkDomainAction;
|
||||
this.optionalJsonPayload = optionalJsonPayload;
|
||||
this.bulkDomainActionLimit = bulkDomainActionLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -85,6 +90,13 @@ public class ConsoleBulkDomainAction extends ConsoleApiAction {
|
||||
optionalJsonPayload.orElseThrow(
|
||||
() -> new IllegalArgumentException("Bulk action payload must be present"));
|
||||
BulkDomainList domainList = consoleApiParams.gson().fromJson(jsonPayload, BulkDomainList.class);
|
||||
checkArgument(
|
||||
domainList.domainList != null && !domainList.domainList.isEmpty(),
|
||||
"Domain list cannot be empty");
|
||||
checkArgument(
|
||||
domainList.domainList.size() <= bulkDomainActionLimit,
|
||||
"Cannot process more than %s domains in a single bulk action",
|
||||
bulkDomainActionLimit);
|
||||
ConsoleDomainActionType actionType =
|
||||
ConsoleDomainActionType.parseActionType(bulkDomainAction, jsonPayload);
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -19,6 +19,7 @@ import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableO
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveHost;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveSubordinateHost;
|
||||
import static google.registry.testing.DatabaseHelper.persistDeletedDomain;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -67,7 +68,8 @@ public class SimplifiedJedisClientTest {
|
||||
|
||||
@Test
|
||||
void testClient_roundTrip_host() {
|
||||
Host host = persistActiveHost("ns1.example.tld");
|
||||
Domain domain = persistActiveDomain("example.tld");
|
||||
Host host = persistActiveSubordinateHost("ns1.example.tld", domain);
|
||||
SimplifiedJedisClient client = createJedisClient();
|
||||
client.set(new SimplifiedJedisClient.JedisResource<>("repoId1", host));
|
||||
assertThat(client.get(Host.class, "repoId1")).hasValue(host);
|
||||
|
||||
@@ -21,8 +21,8 @@ import static google.registry.model.common.FeatureFlag.FeatureStatus.INACTIVE;
|
||||
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.testing.DatabaseHelper.persistFeatureFlag;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.util.DateTimeUtils.START_INSTANT;
|
||||
import static google.registry.util.DateTimeUtils.plusDays;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
@@ -35,10 +35,8 @@ import com.google.cloud.storage.BlobId;
|
||||
import com.google.cloud.storage.StorageException;
|
||||
import com.google.cloud.storage.contrib.nio.testing.LocalStorageHelper;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.gcs.GcsUtils;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
@@ -80,7 +78,7 @@ class ExportDomainListsActionTest {
|
||||
action.gcsUtils = gcsUtils;
|
||||
action.clock = clock;
|
||||
action.driveConnection = driveConnection;
|
||||
persistFeatureFlag(INACTIVE);
|
||||
persistFeatureFlag(INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS, INACTIVE);
|
||||
}
|
||||
|
||||
private void verifyExportedToDrive(String folderId, String filename, String domains)
|
||||
@@ -110,7 +108,7 @@ class ExportDomainListsActionTest {
|
||||
|
||||
@Test
|
||||
void test_outputsOnlyActiveDomains_csv() throws Exception {
|
||||
persistFeatureFlag(ACTIVE);
|
||||
persistFeatureFlag(INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS, ACTIVE);
|
||||
persistActiveDomain("onetwo.tld");
|
||||
persistActiveDomain("rudnitzky.tld");
|
||||
persistDeletedDomain("mortuary.tld", Instant.parse("2001-03-14T10:11:12Z"));
|
||||
@@ -144,7 +142,7 @@ class ExportDomainListsActionTest {
|
||||
|
||||
@Test
|
||||
void test_outputsOnlyDomainsOnRealTlds_csv() throws Exception {
|
||||
persistFeatureFlag(ACTIVE);
|
||||
persistFeatureFlag(INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS, ACTIVE);
|
||||
persistActiveDomain("onetwo.tld");
|
||||
persistActiveDomain("rudnitzky.tld");
|
||||
persistActiveDomain("wontgo.testtld");
|
||||
@@ -164,7 +162,7 @@ class ExportDomainListsActionTest {
|
||||
|
||||
@Test
|
||||
void test_outputIncludesDeletionTimes_forPendingDeletes_notRdemption() throws Exception {
|
||||
persistFeatureFlag(ACTIVE);
|
||||
persistFeatureFlag(INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS, ACTIVE);
|
||||
// Domains pending delete (meaning the 5 day period, not counting the 30 day redemption period)
|
||||
// should include their pending deletion date
|
||||
persistActiveDomain("active.tld");
|
||||
@@ -227,7 +225,7 @@ class ExportDomainListsActionTest {
|
||||
|
||||
@Test
|
||||
void test_outputsDomainsFromDifferentTldsToMultipleFiles_csv() throws Exception {
|
||||
persistFeatureFlag(ACTIVE);
|
||||
persistFeatureFlag(INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS, ACTIVE);
|
||||
createTld("tldtwo");
|
||||
persistResource(Tld.get("tldtwo").asBuilder().setDriveFolderId("hooray").build());
|
||||
|
||||
@@ -255,13 +253,4 @@ class ExportDomainListsActionTest {
|
||||
// tldthree does not have a drive id, so no export to drive is performed.
|
||||
verifyNoMoreInteractions(driveConnection);
|
||||
}
|
||||
|
||||
private void persistFeatureFlag(FeatureFlag.FeatureStatus status) {
|
||||
persistResource(
|
||||
new FeatureFlag()
|
||||
.asBuilder()
|
||||
.setFeatureName(INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS)
|
||||
.setStatusMap(ImmutableSortedMap.of(START_INSTANT, status))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.newHost;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveHost;
|
||||
import static google.registry.testing.DatabaseHelper.persistFeatureFlag;
|
||||
import static google.registry.testing.DatabaseHelper.persistReservedList;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.DomainSubject.assertAboutDomains;
|
||||
@@ -80,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;
|
||||
@@ -143,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;
|
||||
@@ -151,7 +152,6 @@ import google.registry.model.billing.BillingBase.Reason;
|
||||
import google.registry.model.billing.BillingBase.RenewalPriceBehavior;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.billing.BillingRecurrence;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.common.FeatureFlag.FeatureStatus;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
@@ -798,7 +798,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
.hasExactlyDsData(
|
||||
DomainDsData.create(
|
||||
12345,
|
||||
3,
|
||||
8,
|
||||
2,
|
||||
base16()
|
||||
.decode("D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A"))
|
||||
@@ -968,11 +968,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
void testFailure_secDnsSha1DigestType() throws Exception {
|
||||
setEppInput("domain_create_dsdata_sha1.xml");
|
||||
persistHosts();
|
||||
DatabaseHelper.persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(FORBID_INSECURE_ALGORITHMS_RFC_9904)
|
||||
.setStatusMap(ImmutableSortedMap.of(START_INSTANT, FeatureStatus.ACTIVE))
|
||||
.build());
|
||||
persistFeatureFlag(FORBID_INSECURE_ALGORITHMS_RFC_9904, FeatureStatus.ACTIVE);
|
||||
EppException thrown = assertThrows(InvalidDsRecordException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
@@ -981,18 +977,14 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
void testSuccess_secDnsSha1_flagInactive() throws Exception {
|
||||
setEppInput("domain_create_dsdata_sha1.xml");
|
||||
persistHosts();
|
||||
DatabaseHelper.persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(FORBID_INSECURE_ALGORITHMS_RFC_9904)
|
||||
.setStatusMap(ImmutableSortedMap.of(START_INSTANT, FeatureStatus.INACTIVE))
|
||||
.build());
|
||||
persistFeatureFlag(FORBID_INSECURE_ALGORITHMS_RFC_9904, FeatureStatus.INACTIVE);
|
||||
doSuccessfulTest("tld");
|
||||
Domain domain = reloadResourceByForeignKey();
|
||||
assertAboutDomains()
|
||||
.that(domain)
|
||||
.hasExactlyDsData(
|
||||
DomainDsData.create(
|
||||
12345, 3, 1, base16().decode("49FD46E6C4B45C55D4AC49FD46E6C4B45C55D4AC"))
|
||||
12345, 8, 1, base16().decode("49FD46E6C4B45C55D4AC49FD46E6C4B45C55D4AC"))
|
||||
.cloneWithDomainRepoId(domain.getRepoId()));
|
||||
}
|
||||
|
||||
@@ -1004,6 +996,34 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_secDnsForbiddenAlgorithm() throws Exception {
|
||||
setEppInput("domain_create_dsdata_forbidden_algorithm.xml");
|
||||
persistHosts();
|
||||
persistFeatureFlag(FORBID_INSECURE_ALGORITHMS_RFC_9904, FeatureStatus.ACTIVE);
|
||||
EppException thrown = assertThrows(InvalidDsRecordException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_secDnsForbiddenAlgorithm_flagInactive() throws Exception {
|
||||
setEppInput("domain_create_dsdata_forbidden_algorithm.xml");
|
||||
persistHosts();
|
||||
persistFeatureFlag(FORBID_INSECURE_ALGORITHMS_RFC_9904, FeatureStatus.INACTIVE);
|
||||
doSuccessfulTest("tld");
|
||||
Domain domain = reloadResourceByForeignKey();
|
||||
assertAboutDomains()
|
||||
.that(domain)
|
||||
.hasExactlyDsData(
|
||||
DomainDsData.create(
|
||||
12345,
|
||||
1,
|
||||
2,
|
||||
base16()
|
||||
.decode("D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A"))
|
||||
.cloneWithDomainRepoId(domain.getRepoId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongExtension() {
|
||||
setEppInput("domain_create_wrong_extension.xml");
|
||||
@@ -1917,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();
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,8 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, Domain> {
|
||||
"UNIT", "y");
|
||||
|
||||
private static final Pattern OK_PATTERN = Pattern.compile("\"ok\"");
|
||||
private static final String SHA_256_DIGEST =
|
||||
"D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A";
|
||||
|
||||
private Host host1;
|
||||
private Host host2;
|
||||
@@ -314,8 +316,7 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, Domain> {
|
||||
domain
|
||||
.asBuilder()
|
||||
.setDsData(
|
||||
ImmutableSet.of(
|
||||
DomainDsData.create(12345, 3, 1, base16().decode("49FD46E6C4B45C55D4AC"))))
|
||||
ImmutableSet.of(DomainDsData.create(12345, 8, 2, base16().decode(SHA_256_DIGEST))))
|
||||
.setNameservers(ImmutableSet.of(host1.createVKey(), host3.createVKey()))
|
||||
.build());
|
||||
doSuccessfulTest("domain_info_response_dsdata.xml", false);
|
||||
@@ -519,8 +520,7 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, Domain> {
|
||||
"TheRegistrar",
|
||||
null))
|
||||
.setDsData(
|
||||
ImmutableSet.of(
|
||||
DomainDsData.create(12345, 3, 1, base16().decode("49FD46E6C4B45C55D4AC"))))
|
||||
ImmutableSet.of(DomainDsData.create(12345, 8, 2, base16().decode(SHA_256_DIGEST))))
|
||||
.build());
|
||||
doSuccessfulTest("domain_info_response_dsdata_addperiod.xml", false);
|
||||
}
|
||||
|
||||
@@ -203,6 +203,29 @@ public class DomainPricingLogicTest {
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDomainCreatePrice_discountPriceAllocationToken_oneYearCreate_moreDiscountYears()
|
||||
throws EppException {
|
||||
AllocationToken allocationToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc123_more_discount")
|
||||
.setTokenType(SINGLE_USE)
|
||||
.setDomainName("default.example")
|
||||
.setDiscountPrice(Money.of(USD, 5))
|
||||
.setDiscountYears(2)
|
||||
.setRegistrationBehavior(RegistrationBehavior.DEFAULT)
|
||||
.build());
|
||||
assertThat(
|
||||
domainPricingLogic.getCreatePrice(
|
||||
tld, "default.example", clock.now(), 1, false, false, Optional.of(allocationToken)))
|
||||
.isEqualTo(
|
||||
new FeesAndCredits.Builder()
|
||||
.setCurrency(USD)
|
||||
.addFeeOrCredit(Fee.create(new BigDecimal("5.00"), CREATE, false))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDomainRenewPrice_oneYear_standardDomain_noBilling_isStandardPrice()
|
||||
throws EppException {
|
||||
@@ -1093,4 +1116,23 @@ public class DomainPricingLogicTest {
|
||||
.getRenewCost())
|
||||
.isEqualTo(Money.of(USD, 5));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDomainRenewPrice_specifiedToken_multiYear() throws Exception {
|
||||
AllocationToken allocationToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc123_multi")
|
||||
.setTokenType(SINGLE_USE)
|
||||
.setDomainName("premium.example")
|
||||
.setRenewalPriceBehavior(SPECIFIED)
|
||||
.setRenewalPrice(Money.of(USD, 5))
|
||||
.build());
|
||||
assertThat(
|
||||
domainPricingLogic
|
||||
.getRenewPrice(
|
||||
tld, "premium.example", clock.now(), 5, null, Optional.of(allocationToken))
|
||||
.getRenewCost())
|
||||
.isEqualTo(Money.of(USD, 25));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveHost;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveSubordinateHost;
|
||||
import static google.registry.testing.DatabaseHelper.persistDeletedDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistFeatureFlag;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.DomainSubject.assertAboutDomains;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
@@ -66,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;
|
||||
@@ -89,13 +89,13 @@ 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;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.billing.BillingBase.Reason;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.common.FeatureFlag.FeatureStatus;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainAuthInfo;
|
||||
@@ -130,7 +130,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
private static final ImmutableMap<String, String> OTHER_DSDATA_TEMPLATE_MAP =
|
||||
ImmutableMap.of(
|
||||
"KEY_TAG", "12346",
|
||||
"ALG", "3",
|
||||
"ALG", "8",
|
||||
"DIGEST_TYPE", "2",
|
||||
"DIGEST", SHA_256_DIGEST);
|
||||
|
||||
@@ -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 {
|
||||
@@ -459,9 +461,9 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
doSecDnsSuccessfulTest(
|
||||
"domain_update_dsdata_add.xml",
|
||||
null,
|
||||
ImmutableSet.of(DomainDsData.create(12346, 3, 2, base16().decode(SHA_256_DIGEST))),
|
||||
ImmutableSet.of(DomainDsData.create(12346, 8, 2, base16().decode(SHA_256_DIGEST))),
|
||||
ImmutableMap.of(
|
||||
"KEY_TAG", "12346", "ALG", "3", "DIGEST_TYPE", "2", "DIGEST", SHA_256_DIGEST),
|
||||
"KEY_TAG", "12346", "ALG", "8", "DIGEST_TYPE", "2", "DIGEST", SHA_256_DIGEST),
|
||||
true);
|
||||
}
|
||||
|
||||
@@ -471,9 +473,9 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
"domain_update_dsdata_add.xml",
|
||||
ImmutableSet.of(SOME_DSDATA),
|
||||
ImmutableSet.of(
|
||||
SOME_DSDATA, DomainDsData.create(12346, 3, 2, base16().decode(SHA_256_DIGEST))),
|
||||
SOME_DSDATA, DomainDsData.create(12346, 8, 2, base16().decode(SHA_256_DIGEST))),
|
||||
ImmutableMap.of(
|
||||
"KEY_TAG", "12346", "ALG", "3", "DIGEST_TYPE", "2", "DIGEST", SHA_256_DIGEST),
|
||||
"KEY_TAG", "12346", "ALG", "8", "DIGEST_TYPE", "2", "DIGEST", SHA_256_DIGEST),
|
||||
true);
|
||||
}
|
||||
|
||||
@@ -648,7 +650,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
union(
|
||||
commonDsData,
|
||||
ImmutableSet.of(
|
||||
DomainDsData.create(12346, 3, 2, base16().decode(SHA_256_DIGEST))))),
|
||||
DomainDsData.create(12346, 8, 2, base16().decode(SHA_256_DIGEST))))),
|
||||
true);
|
||||
}
|
||||
|
||||
@@ -657,7 +659,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
doSecDnsSuccessfulTest(
|
||||
"domain_update_dsdata_rem.xml",
|
||||
ImmutableSet.of(
|
||||
SOME_DSDATA, DomainDsData.create(12346, 3, 2, base16().decode(SHA_256_DIGEST))),
|
||||
SOME_DSDATA, DomainDsData.create(12346, 8, 2, base16().decode(SHA_256_DIGEST))),
|
||||
ImmutableSet.of(SOME_DSDATA),
|
||||
true);
|
||||
}
|
||||
@@ -668,7 +670,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
doSecDnsSuccessfulTest(
|
||||
"domain_update_dsdata_rem_all.xml",
|
||||
ImmutableSet.of(
|
||||
SOME_DSDATA, DomainDsData.create(12346, 3, 2, base16().decode(SHA_256_DIGEST))),
|
||||
SOME_DSDATA, DomainDsData.create(12346, 8, 2, base16().decode(SHA_256_DIGEST))),
|
||||
ImmutableSet.of(),
|
||||
true);
|
||||
}
|
||||
@@ -678,9 +680,9 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
doSecDnsSuccessfulTest(
|
||||
"domain_update_dsdata_add_rem.xml",
|
||||
ImmutableSet.of(
|
||||
SOME_DSDATA, DomainDsData.create(12345, 3, 2, base16().decode(SHA_256_DIGEST))),
|
||||
SOME_DSDATA, DomainDsData.create(12345, 8, 2, base16().decode(SHA_256_DIGEST))),
|
||||
ImmutableSet.of(
|
||||
SOME_DSDATA, DomainDsData.create(12346, 3, 2, base16().decode(SHA_256_DIGEST))),
|
||||
SOME_DSDATA, DomainDsData.create(12346, 8, 2, base16().decode(SHA_256_DIGEST))),
|
||||
true);
|
||||
}
|
||||
|
||||
@@ -703,12 +705,12 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
union(
|
||||
commonDsData,
|
||||
ImmutableSet.of(
|
||||
DomainDsData.create(12345, 3, 2, base16().decode(SHA_256_DIGEST))))),
|
||||
DomainDsData.create(12345, 8, 2, base16().decode(SHA_256_DIGEST))))),
|
||||
ImmutableSet.copyOf(
|
||||
union(
|
||||
commonDsData,
|
||||
ImmutableSet.of(
|
||||
DomainDsData.create(12346, 3, 2, base16().decode(SHA_256_DIGEST))))),
|
||||
DomainDsData.create(12346, 8, 2, base16().decode(SHA_256_DIGEST))))),
|
||||
true);
|
||||
}
|
||||
|
||||
@@ -893,11 +895,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
"DIGEST_TYPE", "1",
|
||||
"DIGEST", "A94A8FE5CCB19BA61C4C0873D391E987982FBBD3"));
|
||||
persistResource(DatabaseHelper.newDomain(getUniqueIdFromCommand()));
|
||||
DatabaseHelper.persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(FORBID_INSECURE_ALGORITHMS_RFC_9904)
|
||||
.setStatusMap(ImmutableSortedMap.of(START_INSTANT, FeatureStatus.ACTIVE))
|
||||
.build());
|
||||
persistFeatureFlag(FORBID_INSECURE_ALGORITHMS_RFC_9904, FeatureStatus.ACTIVE);
|
||||
EppException thrown = assertThrows(InvalidDsRecordException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
@@ -912,11 +910,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
"DIGEST_TYPE", "1",
|
||||
"DIGEST", "A94A8FE5CCB19BA61C4C0873D391E987982FBBD3"));
|
||||
persistResource(DatabaseHelper.newDomain(getUniqueIdFromCommand()));
|
||||
DatabaseHelper.persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(FORBID_INSECURE_ALGORITHMS_RFC_9904)
|
||||
.setStatusMap(ImmutableSortedMap.of(START_INSTANT, FeatureStatus.INACTIVE))
|
||||
.build());
|
||||
persistFeatureFlag(FORBID_INSECURE_ALGORITHMS_RFC_9904, FeatureStatus.INACTIVE);
|
||||
runFlow();
|
||||
Domain domain = reloadResourceByForeignKey();
|
||||
assertAboutDomains()
|
||||
@@ -990,6 +984,70 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_secDnsForbiddenAlgorithm() throws Exception {
|
||||
setEppInput("domain_update_dsdata_add.xml", OTHER_DSDATA_TEMPLATE_MAP);
|
||||
persistResource(
|
||||
DatabaseHelper.newDomain(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.setDsData(
|
||||
ImmutableSet.of(DomainDsData.create(1, 1, 2, base16().decode(SHA_256_DIGEST))))
|
||||
.build());
|
||||
persistFeatureFlag(FORBID_INSECURE_ALGORITHMS_RFC_9904, FeatureStatus.ACTIVE);
|
||||
EppException thrown = assertThrows(InvalidDsRecordException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_secDnsForbiddenAlgorithm_flagInactive() throws Exception {
|
||||
setEppInput("domain_update_dsdata_add.xml", OTHER_DSDATA_TEMPLATE_MAP);
|
||||
persistResource(
|
||||
DatabaseHelper.newDomain(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.setDsData(
|
||||
ImmutableSet.of(DomainDsData.create(1, 1, 2, base16().decode(SHA_256_DIGEST))))
|
||||
.build());
|
||||
persistFeatureFlag(FORBID_INSECURE_ALGORITHMS_RFC_9904, FeatureStatus.INACTIVE);
|
||||
runFlow();
|
||||
Domain domain = reloadResourceByForeignKey();
|
||||
assertAboutDomains()
|
||||
.that(domain)
|
||||
.hasExactlyDsData(
|
||||
DomainDsData.create(1, 1, 2, base16().decode(SHA_256_DIGEST)),
|
||||
DomainDsData.create(12346, 8, 2, base16().decode(SHA_256_DIGEST)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_unrelatedUpdate_existingForbiddenAlgorithm() throws Exception {
|
||||
persistReferencedEntities();
|
||||
persistResource(
|
||||
persistDomain()
|
||||
.asBuilder()
|
||||
.setDsData(
|
||||
ImmutableSet.of(DomainDsData.create(1, 1, 2, base16().decode(SHA_256_DIGEST))))
|
||||
.build());
|
||||
persistFeatureFlag(FORBID_INSECURE_ALGORITHMS_RFC_9904, FeatureStatus.ACTIVE);
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(InvalidDsRecordException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_unrelatedUpdate_existingForbiddenAlgorithm_flagInactive() throws Exception {
|
||||
persistReferencedEntities();
|
||||
persistResource(
|
||||
persistDomain()
|
||||
.asBuilder()
|
||||
.setDsData(
|
||||
ImmutableSet.of(DomainDsData.create(1, 1, 2, base16().decode(SHA_256_DIGEST))))
|
||||
.build());
|
||||
runFlow();
|
||||
Domain updatedDomain = reloadResourceByForeignKey();
|
||||
assertAboutDomains()
|
||||
.that(updatedDomain)
|
||||
.hasExactlyDsData(DomainDsData.create(1, 1, 2, base16().decode(SHA_256_DIGEST)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_secDnsMultipleInvalidAlgorithms() throws Exception {
|
||||
setEppInput("domain_update_dsdata_add.xml", OTHER_DSDATA_TEMPLATE_MAP);
|
||||
|
||||
@@ -48,7 +48,7 @@ import google.registry.flows.host.HostFlowUtils.HostNameNotPunyCodedException;
|
||||
import google.registry.flows.host.HostFlowUtils.HostNameTooLongException;
|
||||
import google.registry.flows.host.HostFlowUtils.HostNameTooShallowException;
|
||||
import google.registry.flows.host.HostFlowUtils.InvalidHostNameException;
|
||||
import google.registry.flows.host.HostFlowUtils.LoopbackIpNotValidForHostException;
|
||||
import google.registry.flows.host.HostFlowUtils.IpAddressNotRoutableException;
|
||||
import google.registry.flows.host.HostFlowUtils.SuperordinateDomainDoesNotExistException;
|
||||
import google.registry.flows.host.HostFlowUtils.SuperordinateDomainInPendingDeleteException;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
@@ -354,22 +354,62 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, Host> {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_localhostInetAddress_ipv4() {
|
||||
void testFailure_loopbackInetAddress_ipv4() {
|
||||
createTld("tld");
|
||||
persistActiveDomain("example.tld");
|
||||
setEppHostCreateInput("ns1.example.tld", "<host:addr ip=\"v4\">127.0.0.1</host:addr>");
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(LoopbackIpNotValidForHostException.class, this::runFlow))
|
||||
.that(assertThrows(IpAddressNotRoutableException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_localhostInetAddress_ipv6() {
|
||||
void testFailure_loopbackInetAddress_ipv6() {
|
||||
createTld("tld");
|
||||
persistActiveDomain("example.tld");
|
||||
setEppHostCreateInput("ns1.example.tld", "<host:addr ip=\"v6\">::1</host:addr>");
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(LoopbackIpNotValidForHostException.class, this::runFlow))
|
||||
.that(assertThrows(IpAddressNotRoutableException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_linkLocalInetAddress_ipv4() {
|
||||
createTld("tld");
|
||||
persistActiveDomain("example.tld");
|
||||
setEppHostCreateInput("ns1.example.tld", "<host:addr ip=\"v4\">169.254.1.1</host:addr>");
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(IpAddressNotRoutableException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_linkLocalInetAddress_ipv6() {
|
||||
createTld("tld");
|
||||
persistActiveDomain("example.tld");
|
||||
setEppHostCreateInput("ns1.example.tld", "<host:addr ip=\"v6\">fe80::1</host:addr>");
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(IpAddressNotRoutableException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_privateInetAddress_ipv4() {
|
||||
createTld("tld");
|
||||
persistActiveDomain("example.tld");
|
||||
setEppHostCreateInput("ns1.example.tld", "<host:addr ip=\"v4\">192.168.1.1</host:addr>");
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(IpAddressNotRoutableException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_anyLocalInetAddress_ipv4() {
|
||||
createTld("tld");
|
||||
persistActiveDomain("example.tld");
|
||||
setEppHostCreateInput("ns1.example.tld", "<host:addr ip=\"v4\">0.0.0.0</host:addr>");
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(IpAddressNotRoutableException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -65,7 +65,7 @@ import google.registry.flows.host.HostFlowUtils.HostNameNotNormalizedException;
|
||||
import google.registry.flows.host.HostFlowUtils.HostNameNotPunyCodedException;
|
||||
import google.registry.flows.host.HostFlowUtils.HostNameTooLongException;
|
||||
import google.registry.flows.host.HostFlowUtils.HostNameTooShallowException;
|
||||
import google.registry.flows.host.HostFlowUtils.LoopbackIpNotValidForHostException;
|
||||
import google.registry.flows.host.HostFlowUtils.IpAddressNotRoutableException;
|
||||
import google.registry.flows.host.HostFlowUtils.SuperordinateDomainDoesNotExistException;
|
||||
import google.registry.flows.host.HostFlowUtils.SuperordinateDomainInPendingDeleteException;
|
||||
import google.registry.flows.host.HostUpdateFlow.CannotAddIpToExternalHostException;
|
||||
@@ -1391,24 +1391,68 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Host> {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_localhostInetAddress_ipv4() throws Exception {
|
||||
void testFailure_loopbackInetAddress_ipv4() throws Exception {
|
||||
createTld("tld");
|
||||
persistActiveSubordinateHost(oldHostName(), persistActiveDomain("example.tld"));
|
||||
setEppHostUpdateInput(
|
||||
"ns1.example.tld", "ns2.example.tld", "<host:addr ip=\"v4\">127.0.0.1</host:addr>", null);
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(LoopbackIpNotValidForHostException.class, this::runFlow))
|
||||
.that(assertThrows(IpAddressNotRoutableException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_localhostInetAddress_ipv6() throws Exception {
|
||||
void testFailure_loopbackInetAddress_ipv6() throws Exception {
|
||||
createTld("tld");
|
||||
persistActiveSubordinateHost(oldHostName(), persistActiveDomain("example.tld"));
|
||||
setEppHostUpdateInput(
|
||||
"ns1.example.tld", "ns2.example.tld", "<host:addr ip=\"v6\">::1</host:addr>", null);
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(LoopbackIpNotValidForHostException.class, this::runFlow))
|
||||
.that(assertThrows(IpAddressNotRoutableException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_linkLocalInetAddress_ipv4() throws Exception {
|
||||
createTld("tld");
|
||||
persistActiveSubordinateHost(oldHostName(), persistActiveDomain("example.tld"));
|
||||
setEppHostUpdateInput(
|
||||
"ns1.example.tld", "ns2.example.tld", "<host:addr ip=\"v4\">169.254.1.1</host:addr>", null);
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(IpAddressNotRoutableException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_linkLocalInetAddress_ipv6() throws Exception {
|
||||
createTld("tld");
|
||||
persistActiveSubordinateHost(oldHostName(), persistActiveDomain("example.tld"));
|
||||
setEppHostUpdateInput(
|
||||
"ns1.example.tld", "ns2.example.tld", "<host:addr ip=\"v6\">fe80::1</host:addr>", null);
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(IpAddressNotRoutableException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_privateInetAddress_ipv4() throws Exception {
|
||||
createTld("tld");
|
||||
persistActiveSubordinateHost(oldHostName(), persistActiveDomain("example.tld"));
|
||||
setEppHostUpdateInput(
|
||||
"ns1.example.tld", "ns2.example.tld", "<host:addr ip=\"v4\">192.168.1.1</host:addr>", null);
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(IpAddressNotRoutableException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_anyLocalInetAddress_ipv4() throws Exception {
|
||||
createTld("tld");
|
||||
persistActiveSubordinateHost(oldHostName(), persistActiveDomain("example.tld"));
|
||||
setEppHostUpdateInput(
|
||||
"ns1.example.tld", "ns2.example.tld", "<host:addr ip=\"v4\">0.0.0.0</host:addr>", null);
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(IpAddressNotRoutableException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
|
||||
@@ -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>");
|
||||
}
|
||||
}
|
||||
@@ -20,13 +20,12 @@ import static google.registry.model.common.FeatureFlag.FeatureName.PROHIBIT_CONT
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.testing.DatabaseHelper.deleteResource;
|
||||
import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.persistFeatureFlag;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
import static google.registry.util.DateTimeUtils.START_INSTANT;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppException.UnimplementedExtensionException;
|
||||
import google.registry.flows.EppException.UnimplementedObjectServiceException;
|
||||
@@ -39,7 +38,6 @@ import google.registry.flows.session.LoginFlow.BadRegistrarIdException;
|
||||
import google.registry.flows.session.LoginFlow.RegistrarAccountNotActiveException;
|
||||
import google.registry.flows.session.LoginFlow.TooManyFailedLoginsException;
|
||||
import google.registry.flows.session.LoginFlow.UnsupportedLanguageException;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.common.FeatureFlag.FeatureStatus;
|
||||
import google.registry.model.eppoutput.EppOutput;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
@@ -61,11 +59,7 @@ public abstract class LoginFlowTestCase extends FlowTestCase<LoginFlow> {
|
||||
sessionMetadata.setRegistrarId(null); // Don't implicitly log in (all other flows need to).
|
||||
registrar = loadRegistrar("NewRegistrar");
|
||||
registrarBuilder = registrar.asBuilder();
|
||||
persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(PROHIBIT_CONTACT_OBJECTS_ON_LOGIN)
|
||||
.setStatusMap(ImmutableSortedMap.of(START_INSTANT, FeatureStatus.ACTIVE))
|
||||
.build());
|
||||
persistFeatureFlag(PROHIBIT_CONTACT_OBJECTS_ON_LOGIN, FeatureStatus.ACTIVE);
|
||||
}
|
||||
|
||||
// Can't inline this since it may be overridden in subclasses.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -283,15 +283,15 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
|
||||
private JsonObject generateExpectedJsonForTwoDomainsNsReply() {
|
||||
return jsonFileBuilder()
|
||||
.addDomain("cat.example", "F-EXAMPLE")
|
||||
.addDomain("cat.lol", "6-LOL")
|
||||
.addDomain("cat.example", domainCatExample.getRepoId())
|
||||
.addDomain("cat.lol", domainCatLol.getRepoId())
|
||||
.load("rdap_domains_two.json");
|
||||
}
|
||||
|
||||
private JsonObject generateExpectedJsonForTwoDomainsCatStarReplySql() {
|
||||
return jsonFileBuilder()
|
||||
.addDomain("cat.lol", "6-LOL")
|
||||
.addDomain("cat2.lol", "B-LOL")
|
||||
.addDomain("cat.lol", domainCatLol.getRepoId())
|
||||
.addDomain("cat2.lol", domainCatLol2.getRepoId())
|
||||
.load("rdap_domains_two.json");
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
minusMonths(clock.now(), 6)));
|
||||
}
|
||||
|
||||
private void createManyDomainsAndHosts(
|
||||
private ImmutableList<Domain> createManyDomainsAndHosts(
|
||||
int numActiveDomains, int numTotalDomainsPerActiveDomain, int numHosts) {
|
||||
ImmutableSet.Builder<VKey<Host>> hostKeysBuilder = new ImmutableSet.Builder<>();
|
||||
ImmutableSet.Builder<String> subordinateHostnamesBuilder = new ImmutableSet.Builder<>();
|
||||
@@ -340,7 +340,7 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
}
|
||||
domainsBuilder.add(builder.build());
|
||||
}
|
||||
persistResources(domainsBuilder.build());
|
||||
return persistResources(domainsBuilder.build());
|
||||
}
|
||||
|
||||
private void checkNumberOfDomainsInResult(JsonObject obj, int expected) {
|
||||
@@ -353,10 +353,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
requestType,
|
||||
queryString,
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.lol", "6-LOL")
|
||||
.addDomain("cat.lol", domainCatLol.getRepoId())
|
||||
.addRegistrar("Yes Virginia <script>")
|
||||
.addNameserver("ns1.cat.lol", "2-ROID")
|
||||
.addNameserver("ns2.cat.lol", "4-ROID")
|
||||
.addNameserver("ns1.cat.lol", hostNs1CatLol.getRepoId())
|
||||
.addNameserver("ns2.cat.lol", hostNs2CatLol.getRepoId())
|
||||
.setNextQuery(queryString)
|
||||
.load(filename));
|
||||
}
|
||||
@@ -367,10 +367,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
requestType,
|
||||
queryString,
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat2.lol", "B-LOL")
|
||||
.addDomain("cat2.lol", domainCatLol2.getRepoId())
|
||||
.addRegistrar("Yes Virginia <script>")
|
||||
.addNameserver("ns1.cat.example", "7-ROID")
|
||||
.addNameserver("ns2.dog.lol", "9-ROID")
|
||||
.addNameserver("ns1.cat.example", hostNameToHostMap.get("ns1.cat.example").getRepoId())
|
||||
.addNameserver("ns2.dog.lol", hostNameToHostMap.get("ns2.dog.lol").getRepoId())
|
||||
.setNextQuery(queryString)
|
||||
.load(filename));
|
||||
}
|
||||
@@ -679,10 +679,11 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
RequestType.NAME,
|
||||
"cat.example",
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.example", "F-EXAMPLE")
|
||||
.addDomain("cat.example", domainCatExample.getRepoId())
|
||||
.addRegistrar("St. John Chrysostom")
|
||||
.addNameserver("ns1.cat.lol", "2-ROID")
|
||||
.addNameserver("ns2.external.tld", "D-ROID")
|
||||
.addNameserver("ns1.cat.lol", hostNs1CatLol.getRepoId())
|
||||
.addNameserver(
|
||||
"ns2.external.tld", hostNameToHostMap.get("ns2.external.tld").getRepoId())
|
||||
.load("rdap_domain.json"));
|
||||
verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(1L));
|
||||
}
|
||||
@@ -693,10 +694,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
RequestType.NAME,
|
||||
"cat.みんな",
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.みんな", "15-Q9JYB4C")
|
||||
.addDomain("cat.みんな", domainIdn.getRepoId())
|
||||
.addRegistrar("みんな")
|
||||
.addNameserver("ns1.cat.みんな", "11-ROID")
|
||||
.addNameserver("ns2.cat.みんな", "13-ROID")
|
||||
.addNameserver("ns1.cat.みんな", hostNameToHostMap.get("ns1.cat.xn--q9jyb4c").getRepoId())
|
||||
.addNameserver("ns2.cat.みんな", hostNameToHostMap.get("ns2.cat.xn--q9jyb4c").getRepoId())
|
||||
.load("rdap_domain_unicode_with_unicode_nameservers.json"));
|
||||
// The unicode gets translated to ASCII before getting parsed into a search pattern.
|
||||
metricPrefixLength = 15;
|
||||
@@ -709,10 +710,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
RequestType.NAME,
|
||||
"cat.xn--q9jyb4c",
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.みんな", "15-Q9JYB4C")
|
||||
.addDomain("cat.みんな", domainIdn.getRepoId())
|
||||
.addRegistrar("みんな")
|
||||
.addNameserver("ns1.cat.みんな", "11-ROID")
|
||||
.addNameserver("ns2.cat.みんな", "13-ROID")
|
||||
.addNameserver("ns1.cat.みんな", hostNameToHostMap.get("ns1.cat.xn--q9jyb4c").getRepoId())
|
||||
.addNameserver("ns2.cat.みんな", hostNameToHostMap.get("ns2.cat.xn--q9jyb4c").getRepoId())
|
||||
.load("rdap_domain_unicode_with_unicode_nameservers.json"));
|
||||
verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(1L));
|
||||
}
|
||||
@@ -723,10 +724,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
RequestType.NAME,
|
||||
"cat.1.test",
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.1.test", "1B-1TEST")
|
||||
.addDomain("cat.1.test", domainMultipart.getRepoId())
|
||||
.addRegistrar("1.test")
|
||||
.addNameserver("ns1.cat.1.test", "17-ROID")
|
||||
.addNameserver("ns2.cat.2.test", "19-ROID")
|
||||
.addNameserver("ns1.cat.1.test", hostNameToHostMap.get("ns1.cat.1.test").getRepoId())
|
||||
.addNameserver("ns2.cat.2.test", hostNameToHostMap.get("ns2.cat.2.test").getRepoId())
|
||||
.load("rdap_domain.json"));
|
||||
verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(1L));
|
||||
}
|
||||
@@ -737,10 +738,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
RequestType.NAME,
|
||||
"ca*.1.test",
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.1.test", "1B-1TEST")
|
||||
.addDomain("cat.1.test", domainMultipart.getRepoId())
|
||||
.addRegistrar("1.test")
|
||||
.addNameserver("ns1.cat.1.test", "17-ROID")
|
||||
.addNameserver("ns2.cat.2.test", "19-ROID")
|
||||
.addNameserver("ns1.cat.1.test", hostNameToHostMap.get("ns1.cat.1.test").getRepoId())
|
||||
.addNameserver("ns2.cat.2.test", hostNameToHostMap.get("ns2.cat.2.test").getRepoId())
|
||||
.load("rdap_domain.json"));
|
||||
verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(1L));
|
||||
}
|
||||
@@ -813,10 +814,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
.that(generateActualJson(RequestType.NAME, "cat.*"))
|
||||
.isEqualTo(
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.1.test", "1B-1TEST")
|
||||
.addDomain("cat.example", "F-EXAMPLE")
|
||||
.addDomain("cat.lol", "6-LOL")
|
||||
.addDomain("cat.みんな", "15-Q9JYB4C")
|
||||
.addDomain("cat.1.test", domainMultipart.getRepoId())
|
||||
.addDomain("cat.example", domainCatExample.getRepoId())
|
||||
.addDomain("cat.lol", domainCatLol.getRepoId())
|
||||
.addDomain("cat.みんな", domainIdn.getRepoId())
|
||||
.load("rdap_domains_four_with_one_unicode.json"));
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(4L));
|
||||
@@ -851,10 +852,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
.that(generateActualJson(RequestType.NAME, "cat*"))
|
||||
.isEqualTo(
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.1.test", "1B-1TEST")
|
||||
.addDomain("cat.example", "F-EXAMPLE")
|
||||
.addDomain("cat.lol", "6-LOL")
|
||||
.addDomain("cat.みんな", "15-Q9JYB4C")
|
||||
.addDomain("cat.1.test", domainMultipart.getRepoId())
|
||||
.addDomain("cat.example", domainCatExample.getRepoId())
|
||||
.addDomain("cat.lol", domainCatLol.getRepoId())
|
||||
.addDomain("cat.みんな", domainIdn.getRepoId())
|
||||
.setNextQuery("name=cat*&cursor=Y2F0LnhuLS1xOWp5YjRj")
|
||||
.load("rdap_domains_four_with_one_unicode_truncated.json"));
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
@@ -970,15 +971,15 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
// This is not exactly desired behavior, but expected: There are enough domains to fill a full
|
||||
// result set, but there are so many deleted domains that we run out of patience before we work
|
||||
// our way through all of them.
|
||||
createManyDomainsAndHosts(4, 50, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(4, 50, 2);
|
||||
rememberWildcardType("domain*.lol");
|
||||
assertAboutJson()
|
||||
.that(generateActualJson(RequestType.NAME, "domain*.lol"))
|
||||
.isEqualTo(
|
||||
jsonFileBuilder()
|
||||
.addDomain("domain100.lol", "8E-LOL")
|
||||
.addDomain("domain150.lol", "5C-LOL")
|
||||
.addDomain("domain200.lol", "2A-LOL")
|
||||
.addDomain("domain100.lol", domains.get(100).getRepoId())
|
||||
.addDomain("domain150.lol", domains.get(50).getRepoId())
|
||||
.addDomain("domain200.lol", domains.get(0).getRepoId())
|
||||
.addDomain("domainunused.lol", "unused-LOL")
|
||||
.load("rdap_incomplete_domain_result_set.json"));
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
@@ -990,28 +991,28 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
|
||||
@Test
|
||||
void testDomainMatch_nontruncatedResultsSet() {
|
||||
createManyDomainsAndHosts(4, 1, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(4, 1, 2);
|
||||
runSuccessfulTestWithFourDomains(
|
||||
RequestType.NAME,
|
||||
"domain*.lol",
|
||||
"2D-LOL",
|
||||
"2C-LOL",
|
||||
"2B-LOL",
|
||||
"2A-LOL",
|
||||
domains.get(3).getRepoId(),
|
||||
domains.get(2).getRepoId(),
|
||||
domains.get(1).getRepoId(),
|
||||
domains.get(0).getRepoId(),
|
||||
"rdap_nontruncated_domains.json");
|
||||
verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(4L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDomainMatch_truncatedResultsSet() {
|
||||
createManyDomainsAndHosts(5, 1, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(5, 1, 2);
|
||||
runSuccessfulTestWithFourDomains(
|
||||
RequestType.NAME,
|
||||
"domain*.lol",
|
||||
"2E-LOL",
|
||||
"2D-LOL",
|
||||
"2C-LOL",
|
||||
"2B-LOL",
|
||||
domains.get(4).getRepoId(),
|
||||
domains.get(3).getRepoId(),
|
||||
domains.get(2).getRepoId(),
|
||||
domains.get(1).getRepoId(),
|
||||
"name=domain*.lol&cursor=ZG9tYWluNC5sb2w%3D",
|
||||
"rdap_domains_four_truncated.json");
|
||||
verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(5L), IncompletenessWarningType.TRUNCATED);
|
||||
@@ -1019,16 +1020,16 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
|
||||
@Test
|
||||
void testDomainMatch_tldSearchOrderedProperly_sql() {
|
||||
createManyDomainsAndHosts(4, 1, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(4, 1, 2);
|
||||
rememberWildcardType("*.lol");
|
||||
assertAboutJson()
|
||||
.that(generateActualJson(RequestType.NAME, "*.lol"))
|
||||
.isEqualTo(
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.lol", "6-LOL")
|
||||
.addDomain("cat2.lol", "B-LOL")
|
||||
.addDomain("domain1.lol", "2D-LOL")
|
||||
.addDomain("domain2.lol", "2C-LOL")
|
||||
.addDomain("cat.lol", domainCatLol.getRepoId())
|
||||
.addDomain("cat2.lol", domainCatLol2.getRepoId())
|
||||
.addDomain("domain1.lol", domains.get(3).getRepoId())
|
||||
.addDomain("domain2.lol", domains.get(2).getRepoId())
|
||||
.setNextQuery("name=*.lol&cursor=ZG9tYWluMi5sb2w%3D")
|
||||
.load("rdap_domains_four_truncated.json"));
|
||||
verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(5L), IncompletenessWarningType.TRUNCATED);
|
||||
@@ -1038,14 +1039,14 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
void testDomainMatch_reallyTruncatedResultsSet() {
|
||||
// Don't use 10 or more domains for this test, because domain10.lol will come before
|
||||
// domain2.lol, and you'll get the wrong domains in the result set.
|
||||
createManyDomainsAndHosts(9, 1, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(9, 1, 2);
|
||||
runSuccessfulTestWithFourDomains(
|
||||
RequestType.NAME,
|
||||
"domain*.lol",
|
||||
"32-LOL",
|
||||
"31-LOL",
|
||||
"30-LOL",
|
||||
"2F-LOL",
|
||||
domains.get(8).getRepoId(),
|
||||
domains.get(7).getRepoId(),
|
||||
domains.get(6).getRepoId(),
|
||||
domains.get(5).getRepoId(),
|
||||
"name=domain*.lol&cursor=ZG9tYWluNC5sb2w%3D",
|
||||
"rdap_domains_four_truncated.json");
|
||||
verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(5L), IncompletenessWarningType.TRUNCATED);
|
||||
@@ -1053,16 +1054,16 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
|
||||
@Test
|
||||
void testDomainMatch_truncatedResultsAfterMultipleChunks_sql() {
|
||||
createManyDomainsAndHosts(5, 6, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(5, 6, 2);
|
||||
rememberWildcardType("domain*.lol");
|
||||
assertAboutJson()
|
||||
.that(generateActualJson(RequestType.NAME, "domain*.lol"))
|
||||
.isEqualTo(
|
||||
jsonFileBuilder()
|
||||
.addDomain("domain12.lol", "3C-LOL")
|
||||
.addDomain("domain18.lol", "36-LOL")
|
||||
.addDomain("domain24.lol", "30-LOL")
|
||||
.addDomain("domain30.lol", "2A-LOL")
|
||||
.addDomain("domain12.lol", domains.get(18).getRepoId())
|
||||
.addDomain("domain18.lol", domains.get(12).getRepoId())
|
||||
.addDomain("domain24.lol", domains.get(6).getRepoId())
|
||||
.addDomain("domain30.lol", domains.get(0).getRepoId())
|
||||
.setNextQuery("name=domain*.lol&cursor=ZG9tYWluMzAubG9s")
|
||||
.load("rdap_domains_four_truncated.json"));
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
@@ -1261,10 +1262,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
RequestType.NS_LDH_NAME,
|
||||
"ns1.cat.xn--q9jyb4c",
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.みんな", "15-Q9JYB4C")
|
||||
.addDomain("cat.みんな", domainIdn.getRepoId())
|
||||
.addRegistrar("みんな")
|
||||
.addNameserver("ns1.cat.みんな", "11-ROID")
|
||||
.addNameserver("ns2.cat.みんな", "13-ROID")
|
||||
.addNameserver("ns1.cat.みんな", hostNameToHostMap.get("ns1.cat.xn--q9jyb4c").getRepoId())
|
||||
.addNameserver("ns2.cat.みんな", hostNameToHostMap.get("ns2.cat.xn--q9jyb4c").getRepoId())
|
||||
.load("rdap_domain_unicode_with_unicode_nameservers.json"));
|
||||
verifyMetrics(SearchType.BY_NAMESERVER_NAME, 1, 1);
|
||||
}
|
||||
@@ -1275,10 +1276,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
RequestType.NS_LDH_NAME,
|
||||
"ns1.cat.1.test",
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.1.test", "1B-1TEST")
|
||||
.addDomain("cat.1.test", domainMultipart.getRepoId())
|
||||
.addRegistrar("1.test")
|
||||
.addNameserver("ns1.cat.1.test", "17-ROID")
|
||||
.addNameserver("ns2.cat.2.test", "19-ROID")
|
||||
.addNameserver("ns1.cat.1.test", hostNameToHostMap.get("ns1.cat.1.test").getRepoId())
|
||||
.addNameserver("ns2.cat.2.test", hostNameToHostMap.get("ns2.cat.2.test").getRepoId())
|
||||
.load("rdap_domain.json"));
|
||||
verifyMetrics(SearchType.BY_NAMESERVER_NAME, 1, 1);
|
||||
}
|
||||
@@ -1289,10 +1290,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
RequestType.NS_LDH_NAME,
|
||||
"ns*.cat.1.test",
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.1.test", "1B-1TEST")
|
||||
.addDomain("cat.1.test", domainMultipart.getRepoId())
|
||||
.addRegistrar("1.test")
|
||||
.addNameserver("ns1.cat.1.test", "17-ROID")
|
||||
.addNameserver("ns2.cat.2.test", "19-ROID")
|
||||
.addNameserver("ns1.cat.1.test", hostNameToHostMap.get("ns1.cat.1.test").getRepoId())
|
||||
.addNameserver("ns2.cat.2.test", hostNameToHostMap.get("ns2.cat.2.test").getRepoId())
|
||||
.load("rdap_domain.json"));
|
||||
verifyMetrics(SearchType.BY_NAMESERVER_NAME, 1, 1);
|
||||
}
|
||||
@@ -1430,28 +1431,28 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
|
||||
@Test
|
||||
void testNameserverMatch_nontruncatedResultsSet() {
|
||||
createManyDomainsAndHosts(4, 1, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(4, 1, 2);
|
||||
runSuccessfulTestWithFourDomains(
|
||||
RequestType.NS_LDH_NAME,
|
||||
"ns1.domain1.lol",
|
||||
"2D-LOL",
|
||||
"2C-LOL",
|
||||
"2B-LOL",
|
||||
"2A-LOL",
|
||||
domains.get(3).getRepoId(),
|
||||
domains.get(2).getRepoId(),
|
||||
domains.get(1).getRepoId(),
|
||||
domains.get(0).getRepoId(),
|
||||
"rdap_nontruncated_domains.json");
|
||||
verifyMetrics(SearchType.BY_NAMESERVER_NAME, Optional.of(4L), Optional.of(1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNameserverMatch_truncatedResultsSet() {
|
||||
createManyDomainsAndHosts(5, 1, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(5, 1, 2);
|
||||
runSuccessfulTestWithFourDomains(
|
||||
RequestType.NS_LDH_NAME,
|
||||
"ns1.domain1.lol",
|
||||
"2E-LOL",
|
||||
"2D-LOL",
|
||||
"2C-LOL",
|
||||
"2B-LOL",
|
||||
domains.get(4).getRepoId(),
|
||||
domains.get(3).getRepoId(),
|
||||
domains.get(2).getRepoId(),
|
||||
domains.get(1).getRepoId(),
|
||||
"nsLdhName=ns1.domain1.lol&cursor=ZG9tYWluNC5sb2w%3D",
|
||||
"rdap_domains_four_truncated.json");
|
||||
verifyMetrics(
|
||||
@@ -1463,14 +1464,14 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
|
||||
@Test
|
||||
void testNameserverMatch_reallyTruncatedResultsSet() {
|
||||
createManyDomainsAndHosts(9, 1, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(9, 1, 2);
|
||||
runSuccessfulTestWithFourDomains(
|
||||
RequestType.NS_LDH_NAME,
|
||||
"ns1.domain1.lol",
|
||||
"32-LOL",
|
||||
"31-LOL",
|
||||
"30-LOL",
|
||||
"2F-LOL",
|
||||
domains.get(8).getRepoId(),
|
||||
domains.get(7).getRepoId(),
|
||||
domains.get(6).getRepoId(),
|
||||
domains.get(5).getRepoId(),
|
||||
"nsLdhName=ns1.domain1.lol&cursor=ZG9tYWluNC5sb2w%3D",
|
||||
"rdap_domains_four_truncated.json");
|
||||
verifyMetrics(
|
||||
@@ -1484,16 +1485,16 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
void testNameserverMatch_duplicatesNotTruncated() {
|
||||
// 36 nameservers for each of 4 domains; these should translate into two fetches, which should
|
||||
// not trigger the truncation warning because all the domains will be duplicates.
|
||||
createManyDomainsAndHosts(4, 1, 36);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(4, 1, 36);
|
||||
rememberWildcardType("ns*.domain1.lol");
|
||||
assertAboutJson()
|
||||
.that(generateActualJson(RequestType.NS_LDH_NAME, "ns*.domain1.lol"))
|
||||
.isEqualTo(
|
||||
jsonFileBuilder()
|
||||
.addDomain("domain1.lol", "71-LOL")
|
||||
.addDomain("domain2.lol", "70-LOL")
|
||||
.addDomain("domain3.lol", "6F-LOL")
|
||||
.addDomain("domain4.lol", "6E-LOL")
|
||||
.addDomain("domain1.lol", domains.get(3).getRepoId())
|
||||
.addDomain("domain2.lol", domains.get(2).getRepoId())
|
||||
.addDomain("domain3.lol", domains.get(1).getRepoId())
|
||||
.addDomain("domain4.lol", domains.get(0).getRepoId())
|
||||
.load("rdap_nontruncated_domains.json"));
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
verifyMetrics(SearchType.BY_NAMESERVER_NAME, Optional.of(4L), Optional.of(36L));
|
||||
@@ -1501,14 +1502,14 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
|
||||
@Test
|
||||
void testNameserverMatch_incompleteResultsSet() {
|
||||
createManyDomainsAndHosts(2, 1, 41);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(2, 1, 41);
|
||||
rememberWildcardType("ns*.domain1.lol");
|
||||
assertAboutJson()
|
||||
.that(generateActualJson(RequestType.NS_LDH_NAME, "ns*.domain1.lol"))
|
||||
.isEqualTo(
|
||||
jsonFileBuilder()
|
||||
.addDomain("domain1.lol", "79-LOL")
|
||||
.addDomain("domain2.lol", "78-LOL")
|
||||
.addDomain("domain1.lol", domains.get(1).getRepoId())
|
||||
.addDomain("domain2.lol", domains.get(0).getRepoId())
|
||||
.load("rdap_incomplete_domains.json"));
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
verifyMetrics(
|
||||
@@ -1641,10 +1642,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
.isEqualTo(
|
||||
wrapInSearchReply(
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.lol", "6-LOL")
|
||||
.addDomain("cat.lol", domainCatLol.getRepoId())
|
||||
.addRegistrar("Yes Virginia <script>")
|
||||
.addNameserver("ns1.cat.lol", "2-ROID")
|
||||
.addNameserver("ns2.cat.lol", "4-ROID")
|
||||
.addNameserver("ns1.cat.lol", hostNs1CatLol.getRepoId())
|
||||
.addNameserver("ns2.cat.lol", hostNs2CatLol.getRepoId())
|
||||
.load("rdap_domain.json")));
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
verifyMetrics(SearchType.BY_NAMESERVER_ADDRESS, 1, 1);
|
||||
@@ -1667,28 +1668,28 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
|
||||
@Test
|
||||
void testAddressMatch_nontruncatedResultsSet() {
|
||||
createManyDomainsAndHosts(4, 1, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(4, 1, 2);
|
||||
runSuccessfulTestWithFourDomains(
|
||||
RequestType.NS_IP,
|
||||
"5.5.5.1",
|
||||
"2D-LOL",
|
||||
"2C-LOL",
|
||||
"2B-LOL",
|
||||
"2A-LOL",
|
||||
domains.get(3).getRepoId(),
|
||||
domains.get(2).getRepoId(),
|
||||
domains.get(1).getRepoId(),
|
||||
domains.get(0).getRepoId(),
|
||||
"rdap_nontruncated_domains.json");
|
||||
verifyMetrics(SearchType.BY_NAMESERVER_ADDRESS, 4, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddressMatch_truncatedResultsSet() {
|
||||
createManyDomainsAndHosts(5, 1, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(5, 1, 2);
|
||||
runSuccessfulTestWithFourDomains(
|
||||
RequestType.NS_IP,
|
||||
"5.5.5.1",
|
||||
"2E-LOL",
|
||||
"2D-LOL",
|
||||
"2C-LOL",
|
||||
"2B-LOL",
|
||||
domains.get(4).getRepoId(),
|
||||
domains.get(3).getRepoId(),
|
||||
domains.get(2).getRepoId(),
|
||||
domains.get(1).getRepoId(),
|
||||
"nsIp=5.5.5.1&cursor=ZG9tYWluNC5sb2w%3D",
|
||||
"rdap_domains_four_truncated.json");
|
||||
verifyMetrics(
|
||||
@@ -1700,14 +1701,14 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
|
||||
@Test
|
||||
void testAddressMatch_reallyTruncatedResultsSet() {
|
||||
createManyDomainsAndHosts(9, 1, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(9, 1, 2);
|
||||
runSuccessfulTestWithFourDomains(
|
||||
RequestType.NS_IP,
|
||||
"5.5.5.1",
|
||||
"32-LOL",
|
||||
"31-LOL",
|
||||
"30-LOL",
|
||||
"2F-LOL",
|
||||
domains.get(8).getRepoId(),
|
||||
domains.get(7).getRepoId(),
|
||||
domains.get(6).getRepoId(),
|
||||
domains.get(5).getRepoId(),
|
||||
"nsIp=5.5.5.1&cursor=ZG9tYWluNC5sb2w%3D",
|
||||
"rdap_domains_four_truncated.json");
|
||||
verifyMetrics(
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -248,4 +248,38 @@ class CopyDetailReportsActionTest {
|
||||
action.run();
|
||||
verifyNoInteractions(driveConnection);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_registrarIdWithUnderscores() throws IOException {
|
||||
persistResource(
|
||||
loadRegistrar("TheRegistrar")
|
||||
.asBuilder()
|
||||
.setRegistrarId("The_Registrar")
|
||||
.setDriveFolderId("0B-99999")
|
||||
.build());
|
||||
|
||||
gcsUtils.createFromBytes(
|
||||
BlobId.of("test-bucket", "results/invoice_details_2017-10_The_Registrar_test.csv"),
|
||||
"hello,world\n1,2".getBytes(UTF_8));
|
||||
|
||||
action.run();
|
||||
verify(driveConnection)
|
||||
.createOrUpdateFile(
|
||||
"invoice_details_2017-10_The_Registrar_test.csv",
|
||||
MediaType.CSV_UTF_8,
|
||||
"0B-99999",
|
||||
"hello,world\n1,2".getBytes(UTF_8));
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_invalidFilenamePattern_skipped() throws IOException {
|
||||
gcsUtils.createFromBytes(
|
||||
BlobId.of("test-bucket", "results/invoice_details_2017-10.csv"),
|
||||
"hello,world\n1,2".getBytes(UTF_8));
|
||||
|
||||
action.run();
|
||||
verifyNoInteractions(driveConnection);
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -69,6 +69,9 @@ import google.registry.model.billing.BillingCancellation;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.billing.BillingRecurrence;
|
||||
import google.registry.model.common.DnsRefreshRequest;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.common.FeatureFlag.FeatureName;
|
||||
import google.registry.model.common.FeatureFlag.FeatureStatus;
|
||||
import google.registry.model.console.GlobalRole;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserRoles;
|
||||
@@ -682,6 +685,32 @@ public final class DatabaseHelper {
|
||||
return newRegistrars.build();
|
||||
}
|
||||
|
||||
/** Persists and returns a {@link FeatureFlag} with a single status starting from the epoch. */
|
||||
public static FeatureFlag persistFeatureFlag(FeatureName featureName, FeatureStatus status) {
|
||||
return persistFeatureFlag(featureName, ImmutableSortedMap.of(START_INSTANT, status));
|
||||
}
|
||||
|
||||
/** Persists and returns a {@link FeatureFlag} with an initial status and one transition. */
|
||||
public static FeatureFlag persistFeatureFlag(
|
||||
FeatureName featureName,
|
||||
FeatureStatus initialStatus,
|
||||
Instant transitionTime,
|
||||
FeatureStatus status) {
|
||||
return persistFeatureFlag(
|
||||
featureName,
|
||||
ImmutableSortedMap.<Instant, FeatureStatus>naturalOrder()
|
||||
.put(START_INSTANT, initialStatus)
|
||||
.put(transitionTime, status)
|
||||
.build());
|
||||
}
|
||||
|
||||
/** Persists and returns a {@link FeatureFlag} with a custom status map. */
|
||||
public static FeatureFlag persistFeatureFlag(
|
||||
FeatureName featureName, ImmutableSortedMap<Instant, FeatureStatus> statusMap) {
|
||||
return persistResource(
|
||||
new FeatureFlag.Builder().setFeatureName(featureName).setStatusMap(statusMap).build());
|
||||
}
|
||||
|
||||
public static Iterable<BillingBase> getBillingEvents() {
|
||||
return tm().transact(
|
||||
() ->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -19,7 +19,7 @@ import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATAS
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.TEST_FEATURE;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureStatus.ACTIVE;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureStatus.INACTIVE;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.DatabaseHelper.persistFeatureFlag;
|
||||
import static google.registry.util.DateTimeUtils.START_INSTANT;
|
||||
import static google.registry.util.DateTimeUtils.plusWeeks;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
@@ -27,7 +27,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import com.beust.jcommander.ParameterException;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.common.FeatureFlag.FeatureStatus;
|
||||
import google.registry.model.common.TimedTransitionProperty;
|
||||
import google.registry.testing.FakeClock;
|
||||
import java.time.Instant;
|
||||
@@ -81,14 +80,7 @@ public class ConfigureFeatureFlagCommandTest extends CommandTestCase<ConfigureFe
|
||||
|
||||
@Test
|
||||
void testUpdate() throws Exception {
|
||||
persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(TEST_FEATURE)
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.<Instant, FeatureStatus>naturalOrder()
|
||||
.put(START_INSTANT, INACTIVE)
|
||||
.build())
|
||||
.build());
|
||||
persistFeatureFlag(TEST_FEATURE, INACTIVE);
|
||||
|
||||
Instant featureStart = plusWeeks(clock.now(), 6);
|
||||
assertThat(FeatureFlag.get(TEST_FEATURE).getStatusMap())
|
||||
@@ -110,14 +102,7 @@ public class ConfigureFeatureFlagCommandTest extends CommandTestCase<ConfigureFe
|
||||
|
||||
@Test
|
||||
void testConfigure_multipleFlags() throws Exception {
|
||||
persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(TEST_FEATURE)
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.<Instant, FeatureStatus>naturalOrder()
|
||||
.put(START_INSTANT, INACTIVE)
|
||||
.build())
|
||||
.build());
|
||||
persistFeatureFlag(TEST_FEATURE, INACTIVE);
|
||||
|
||||
Instant featureStart = plusWeeks(clock.now(), 6);
|
||||
assertThat(FeatureFlag.get(TEST_FEATURE).getStatusMap())
|
||||
@@ -179,14 +164,7 @@ public class ConfigureFeatureFlagCommandTest extends CommandTestCase<ConfigureFe
|
||||
|
||||
@Test
|
||||
void testUpdate_invalidStatusMap() throws Exception {
|
||||
persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(TEST_FEATURE)
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.<Instant, FeatureStatus>naturalOrder()
|
||||
.put(START_INSTANT, INACTIVE)
|
||||
.build())
|
||||
.build());
|
||||
persistFeatureFlag(TEST_FEATURE, INACTIVE);
|
||||
|
||||
Instant featureStart = plusWeeks(clock.now(), 6);
|
||||
assertThat(FeatureFlag.get(TEST_FEATURE).getStatusMap())
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
package google.registry.tools;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.FORBID_INSECURE_ALGORITHMS_RFC_9904;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.persistFeatureFlag;
|
||||
import static google.registry.testing.DatabaseHelper.persistPremiumList;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static org.joda.money.CurrencyUnit.JPY;
|
||||
@@ -25,6 +27,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import com.beust.jcommander.ParameterException;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.dns.writer.VoidDnsWriter;
|
||||
import google.registry.model.common.FeatureFlag.FeatureStatus;
|
||||
import google.registry.model.pricing.StaticPremiumListPricingEngine;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.tld.label.PremiumListDao;
|
||||
@@ -49,9 +52,9 @@ class CreateDomainCommandTest extends EppToolCommandTestCase<CreateDomainCommand
|
||||
"--period=1",
|
||||
"--nameservers=ns1.zdns.google,ns2.zdns.google,ns3.zdns.google,ns4.zdns.google",
|
||||
"--password=2fooBAR",
|
||||
"--ds_records=1 2 2 9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08,4 5 2"
|
||||
"--ds_records=1 2 2 9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08,4 8 2"
|
||||
+ " D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A",
|
||||
"--ds_records=60485 5 2 D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A",
|
||||
"--ds_records=60485 8 2 D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A",
|
||||
"example.tld");
|
||||
eppVerifier.verifySent("domain_create_complete.xml");
|
||||
}
|
||||
@@ -63,9 +66,9 @@ class CreateDomainCommandTest extends EppToolCommandTestCase<CreateDomainCommand
|
||||
"--period=1",
|
||||
"--nameservers=NS1.zdns.google,ns2.ZDNS.google,ns3.zdns.gOOglE,ns4.zdns.google",
|
||||
"--password=2fooBAR",
|
||||
"--ds_records=1 2 2 9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08,4 5 2"
|
||||
"--ds_records=1 2 2 9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08,4 8 2"
|
||||
+ " D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A",
|
||||
"--ds_records=60485 5 2 D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A",
|
||||
"--ds_records=60485 8 2 D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A",
|
||||
"example.tld");
|
||||
eppVerifier.verifySent("domain_create_complete.xml");
|
||||
}
|
||||
@@ -77,9 +80,9 @@ class CreateDomainCommandTest extends EppToolCommandTestCase<CreateDomainCommand
|
||||
"--period=1",
|
||||
"--nameservers=ns[1-4].zdns.google",
|
||||
"--password=2fooBAR",
|
||||
"--ds_records=1 2 2 9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08,4 5 2"
|
||||
"--ds_records=1 2 2 9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08,4 8 2"
|
||||
+ " D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A",
|
||||
"--ds_records=60485 5 2 D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A",
|
||||
"--ds_records=60485 8 2 D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A",
|
||||
"example.tld");
|
||||
eppVerifier.verifySent("domain_create_complete.xml");
|
||||
}
|
||||
@@ -91,9 +94,9 @@ class CreateDomainCommandTest extends EppToolCommandTestCase<CreateDomainCommand
|
||||
"--period=1",
|
||||
"--nameservers=NS[1-4].zdns.google",
|
||||
"--password=2fooBAR",
|
||||
"--ds_records=1 2 2 9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08,4 5 2"
|
||||
"--ds_records=1 2 2 9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08,4 8 2"
|
||||
+ " D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A",
|
||||
"--ds_records=60485 5 2 D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A",
|
||||
"--ds_records=60485 8 2 D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A",
|
||||
"example.tld");
|
||||
eppVerifier.verifySent("domain_create_complete.xml");
|
||||
}
|
||||
@@ -279,6 +282,21 @@ class CreateDomainCommandTest extends EppToolCommandTestCase<CreateDomainCommand
|
||||
assertThat(thrown).hasMessageThat().isEqualTo("DS record uses an unrecognized digest type: 3");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_forbiddenAlgorithm() {
|
||||
persistFeatureFlag(FORBID_INSECURE_ALGORITHMS_RFC_9904, FeatureStatus.ACTIVE);
|
||||
IllegalArgumentException thrown =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
runCommandForced(
|
||||
"--client=NewRegistrar",
|
||||
"--ds_records=1 1 2"
|
||||
+ " D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A",
|
||||
"example.tld"));
|
||||
assertThat(thrown).hasMessageThat().isEqualTo("DS record uses an unrecognized algorithm: 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_invalidDigestLength() {
|
||||
IllegalArgumentException thrown =
|
||||
|
||||
@@ -18,10 +18,8 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.TEST_FEATURE;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureStatus.ACTIVE;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.util.DateTimeUtils.START_INSTANT;
|
||||
import static google.registry.testing.DatabaseHelper.persistFeatureFlag;
|
||||
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -30,12 +28,7 @@ public class DeleteFeatureFlagCommandTest extends CommandTestCase<DeleteFeatureF
|
||||
|
||||
@Test
|
||||
void testSimpleSuccess() throws Exception {
|
||||
persistResource(
|
||||
new FeatureFlag()
|
||||
.asBuilder()
|
||||
.setFeatureName(TEST_FEATURE)
|
||||
.setStatusMap(ImmutableSortedMap.of(START_INSTANT, ACTIVE))
|
||||
.build());
|
||||
persistFeatureFlag(TEST_FEATURE, ACTIVE);
|
||||
assertThat(tm().transact(() -> FeatureFlag.isActiveNow(TEST_FEATURE))).isTrue();
|
||||
runCommandForced("TEST_FEATURE");
|
||||
assertThat(FeatureFlag.getUncached(TEST_FEATURE)).isEmpty();
|
||||
|
||||
@@ -19,7 +19,7 @@ import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATAS
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.TEST_FEATURE;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureStatus.ACTIVE;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureStatus.INACTIVE;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.DatabaseHelper.persistFeatureFlag;
|
||||
import static google.registry.util.DateTimeUtils.START_INSTANT;
|
||||
import static google.registry.util.DateTimeUtils.plusWeeks;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
@@ -27,7 +27,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import com.beust.jcommander.ParameterException;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import google.registry.model.EntityYamlUtils;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.common.FeatureFlag.FeatureFlagNotFoundException;
|
||||
import google.registry.model.common.FeatureFlag.FeatureStatus;
|
||||
import google.registry.testing.FakeClock;
|
||||
@@ -47,15 +46,7 @@ public class GetFeatureFlagCommandTest extends CommandTestCase<GetFeatureFlagCom
|
||||
|
||||
@Test
|
||||
void testSuccess() throws Exception {
|
||||
persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(TEST_FEATURE)
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.<Instant, FeatureStatus>naturalOrder()
|
||||
.put(START_INSTANT, INACTIVE)
|
||||
.put(plusWeeks(clock.now(), 8), ACTIVE)
|
||||
.build())
|
||||
.build());
|
||||
persistFeatureFlag(TEST_FEATURE, INACTIVE, plusWeeks(clock.now(), 8), ACTIVE);
|
||||
runCommand("TEST_FEATURE");
|
||||
assertInStdout(
|
||||
"""
|
||||
@@ -68,24 +59,13 @@ public class GetFeatureFlagCommandTest extends CommandTestCase<GetFeatureFlagCom
|
||||
|
||||
@Test
|
||||
void testSuccess_multipleArguments() throws Exception {
|
||||
persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(TEST_FEATURE)
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.<Instant, FeatureStatus>naturalOrder()
|
||||
.put(START_INSTANT, INACTIVE)
|
||||
.put(plusWeeks(clock.now(), 8), ACTIVE)
|
||||
.build())
|
||||
.build());
|
||||
persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(MINIMUM_DATASET_CONTACTS_OPTIONAL)
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.<Instant, FeatureStatus>naturalOrder()
|
||||
.put(START_INSTANT, INACTIVE)
|
||||
.put(plusWeeks(clock.now(), 3), ACTIVE)
|
||||
.put(plusWeeks(clock.now(), 6), INACTIVE)
|
||||
.build())
|
||||
persistFeatureFlag(TEST_FEATURE, INACTIVE, plusWeeks(clock.now(), 8), ACTIVE);
|
||||
persistFeatureFlag(
|
||||
MINIMUM_DATASET_CONTACTS_OPTIONAL,
|
||||
ImmutableSortedMap.<Instant, FeatureStatus>naturalOrder()
|
||||
.put(START_INSTANT, INACTIVE)
|
||||
.put(plusWeeks(clock.now(), 3), ACTIVE)
|
||||
.put(plusWeeks(clock.now(), 6), INACTIVE)
|
||||
.build());
|
||||
runCommand("TEST_FEATURE", "MINIMUM_DATASET_CONTACTS_OPTIONAL");
|
||||
assertInStdout(
|
||||
@@ -114,15 +94,7 @@ public class GetFeatureFlagCommandTest extends CommandTestCase<GetFeatureFlagCom
|
||||
|
||||
@Test
|
||||
void testFailure_oneFlagDoesNotExist() {
|
||||
persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(TEST_FEATURE)
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.<Instant, FeatureStatus>naturalOrder()
|
||||
.put(START_INSTANT, INACTIVE)
|
||||
.put(plusWeeks(clock.now(), 8), ACTIVE)
|
||||
.build())
|
||||
.build());
|
||||
persistFeatureFlag(TEST_FEATURE, INACTIVE, plusWeeks(clock.now(), 8), ACTIVE);
|
||||
assertThrows(
|
||||
FeatureFlagNotFoundException.class,
|
||||
() -> runCommand("TEST_FEATURE", "MINIMUM_DATASET_CONTACTS_OPTIONAL"));
|
||||
|
||||
@@ -18,14 +18,13 @@ import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATAS
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.TEST_FEATURE;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureStatus.ACTIVE;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureStatus.INACTIVE;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.DatabaseHelper.persistFeatureFlag;
|
||||
import static google.registry.testing.TestDataHelper.loadFile;
|
||||
import static google.registry.util.DateTimeUtils.START_INSTANT;
|
||||
import static google.registry.util.DateTimeUtils.plusWeeks;
|
||||
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import google.registry.model.EntityYamlUtils;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.common.FeatureFlag.FeatureStatus;
|
||||
import java.time.Instant;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -41,49 +40,23 @@ public class ListFeatureFlagsCommandTest extends CommandTestCase<ListFeatureFlag
|
||||
|
||||
@Test
|
||||
void testSuccess_oneFlag() throws Exception {
|
||||
persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(TEST_FEATURE)
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.<Instant, FeatureStatus>naturalOrder()
|
||||
.put(START_INSTANT, INACTIVE)
|
||||
.put(plusWeeks(fakeClock.now(), 8), ACTIVE)
|
||||
.build())
|
||||
.build());
|
||||
persistFeatureFlag(TEST_FEATURE, INACTIVE, plusWeeks(fakeClock.now(), 8), ACTIVE);
|
||||
runCommand();
|
||||
assertInStdout(loadFile(getClass(), "oneFlag.yaml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_success_manyFlags() throws Exception {
|
||||
persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(TEST_FEATURE)
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.<Instant, FeatureStatus>naturalOrder()
|
||||
.put(START_INSTANT, INACTIVE)
|
||||
.put(plusWeeks(fakeClock.now(), 8), ACTIVE)
|
||||
.build())
|
||||
.build());
|
||||
persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(MINIMUM_DATASET_CONTACTS_OPTIONAL)
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.<Instant, FeatureStatus>naturalOrder()
|
||||
.put(START_INSTANT, INACTIVE)
|
||||
.put(plusWeeks(fakeClock.now(), 1), ACTIVE)
|
||||
.put(plusWeeks(fakeClock.now(), 8), INACTIVE)
|
||||
.put(plusWeeks(fakeClock.now(), 10), ACTIVE)
|
||||
.build())
|
||||
.build());
|
||||
persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(MINIMUM_DATASET_CONTACTS_PROHIBITED)
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.<Instant, FeatureStatus>naturalOrder()
|
||||
.put(START_INSTANT, ACTIVE)
|
||||
.build())
|
||||
persistFeatureFlag(TEST_FEATURE, INACTIVE, plusWeeks(fakeClock.now(), 8), ACTIVE);
|
||||
persistFeatureFlag(
|
||||
MINIMUM_DATASET_CONTACTS_OPTIONAL,
|
||||
ImmutableSortedMap.<Instant, FeatureStatus>naturalOrder()
|
||||
.put(START_INSTANT, INACTIVE)
|
||||
.put(plusWeeks(fakeClock.now(), 1), ACTIVE)
|
||||
.put(plusWeeks(fakeClock.now(), 8), INACTIVE)
|
||||
.put(plusWeeks(fakeClock.now(), 10), ACTIVE)
|
||||
.build());
|
||||
persistFeatureFlag(MINIMUM_DATASET_CONTACTS_PROHIBITED, ACTIVE);
|
||||
runCommand();
|
||||
assertInStdout(loadFile(getClass(), "threeFlags.yaml"));
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package google.registry.tools;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.FORBID_INSECURE_ALGORITHMS_RFC_9904;
|
||||
import static google.registry.model.domain.rgp.GracePeriodStatus.AUTO_RENEW;
|
||||
import static google.registry.model.eppcommon.StatusValue.PENDING_DELETE;
|
||||
import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED;
|
||||
@@ -22,6 +23,7 @@ import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_CREATE;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveHost;
|
||||
import static google.registry.testing.DatabaseHelper.persistFeatureFlag;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.TestLogHandlerUtils.assertLogMessage;
|
||||
import static google.registry.testing.TestLogHandlerUtils.assertNoLogMessage;
|
||||
@@ -36,6 +38,7 @@ import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.billing.BillingBase.Flag;
|
||||
import google.registry.model.billing.BillingBase.Reason;
|
||||
import google.registry.model.billing.BillingRecurrence;
|
||||
import google.registry.model.common.FeatureFlag.FeatureStatus;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
@@ -535,6 +538,21 @@ class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomainCommand
|
||||
assertThat(thrown).hasMessageThat().isEqualTo("DS record uses an unrecognized digest type: 3");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_forbiddenDsRecordAlgorithm() {
|
||||
persistFeatureFlag(FORBID_INSECURE_ALGORITHMS_RFC_9904, FeatureStatus.ACTIVE);
|
||||
IllegalArgumentException thrown =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
runCommandForced(
|
||||
"--client=NewRegistrar",
|
||||
"--add_ds_records=1 1 2"
|
||||
+ " D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A",
|
||||
"example.tld"));
|
||||
assertThat(thrown).hasMessageThat().isEqualTo("DS record uses an unrecognized algorithm: 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_invalidDigestLength() {
|
||||
IllegalArgumentException thrown =
|
||||
|
||||
+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 {
|
||||
|
||||
+29
-1
@@ -23,6 +23,8 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonParser;
|
||||
import google.registry.model.console.ConsoleUpdateHistory;
|
||||
import google.registry.model.console.RegistrarRole;
|
||||
import google.registry.model.console.User;
|
||||
@@ -162,6 +164,27 @@ class ConsoleHistoryDataActionTest extends ConsoleActionBaseTestCase {
|
||||
assertThat(response.getPayload()).isEqualTo("[]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_limitsResults() {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
DatabaseHelper.persistResource(
|
||||
new ConsoleUpdateHistory.Builder()
|
||||
.setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE)
|
||||
.setDescription("TheRegistrar|some detail " + i)
|
||||
.setActingUser(fteUser)
|
||||
.setUrl("https://test.com")
|
||||
.setMethod("GET")
|
||||
.setModificationTime(clock.now())
|
||||
.build());
|
||||
}
|
||||
ConsoleHistoryDataAction action =
|
||||
createAction(AuthResult.createUser(fteUser), "TheRegistrar", Optional.empty(), 5);
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
JsonArray payload = JsonParser.parseString(response.getPayload()).getAsJsonArray();
|
||||
assertThat(payload.size()).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_getByRegistrar_noPermission() {
|
||||
ConsoleHistoryDataAction action =
|
||||
@@ -192,10 +215,15 @@ class ConsoleHistoryDataActionTest extends ConsoleActionBaseTestCase {
|
||||
|
||||
private ConsoleHistoryDataAction createAction(
|
||||
AuthResult authResult, String registrarId, Optional<String> consoleUserEmail) {
|
||||
return createAction(authResult, registrarId, consoleUserEmail, 500);
|
||||
}
|
||||
|
||||
private ConsoleHistoryDataAction createAction(
|
||||
AuthResult authResult, String registrarId, Optional<String> consoleUserEmail, int limit) {
|
||||
consoleApiParams = ConsoleApiParamsUtils.createFake(authResult);
|
||||
when(consoleApiParams.request().getMethod()).thenReturn("GET");
|
||||
response = (FakeResponse) consoleApiParams.response();
|
||||
return new ConsoleHistoryDataAction(
|
||||
consoleApiParams, SUPPORT_EMAIL, registrarId, consoleUserEmail);
|
||||
consoleApiParams, SUPPORT_EMAIL, limit, registrarId, consoleUserEmail);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
+25
@@ -158,6 +158,31 @@ public class PasswordResetRequestActionTest extends ConsoleActionBaseTestCase {
|
||||
.isEqualTo("Unknown user with lock email nonexistent@email.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_registryLock_userNoLockPermission() throws Exception {
|
||||
DatabaseHelper.persistResource(
|
||||
new User.Builder()
|
||||
.setEmailAddress("email@registry.tld")
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder()
|
||||
.setRegistrarRoles(
|
||||
ImmutableMap.of("TheRegistrar", RegistrarRole.ACCOUNT_MANAGER))
|
||||
.build())
|
||||
.setRegistryLockEmailAddress("registrylock@theregistrar.com")
|
||||
.build());
|
||||
PasswordResetRequestAction action =
|
||||
createAction(
|
||||
PasswordResetRequest.Type.REGISTRY_LOCK,
|
||||
"TheRegistrar",
|
||||
"registrylock@theregistrar.com");
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo(
|
||||
"User email@registry.tld does not have permission REGISTRY_LOCK on registrar"
|
||||
+ " TheRegistrar");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("Enable when testing is done in sandbox and isAdmin check is removed")
|
||||
void testFailure_epp_noPermission() throws Exception {
|
||||
|
||||
+52
@@ -85,6 +85,16 @@ public class PasswordResetVerifyActionTest extends ConsoleActionBaseTestCase {
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_post_replay() throws Exception {
|
||||
createAction("POST", verificationCode, "newPassword1").run();
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
|
||||
|
||||
// Attempting to reuse the same code should fail
|
||||
createAction("POST", verificationCode, "newPassword2").run();
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_get_invalidVerificationCode() throws Exception {
|
||||
createAction("GET", "invalid", null).run();
|
||||
@@ -147,6 +157,48 @@ public class PasswordResetVerifyActionTest extends ConsoleActionBaseTestCase {
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_post_lock_differentRegistryLockEmail() throws Exception {
|
||||
User user =
|
||||
persistResource(
|
||||
new User.Builder()
|
||||
.setEmailAddress("anotheruser@example.tld")
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder()
|
||||
.setRegistrarRoles(
|
||||
ImmutableMap.of(
|
||||
"TheRegistrar", RegistrarRole.ACCOUNT_MANAGER_WITH_REGISTRY_LOCK))
|
||||
.build())
|
||||
.setRegistryLockEmailAddress("anotherregistrylock@theregistrar.com")
|
||||
.build());
|
||||
verificationCode = saveRequest(PasswordResetRequest.Type.REGISTRY_LOCK).getVerificationCode();
|
||||
createAction(user, "POST", verificationCode, "newPassword").run();
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo("User anotheruser@example.tld has the wrong registry lock email address");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_get_lock_differentRegistryLockEmail() throws Exception {
|
||||
User user =
|
||||
persistResource(
|
||||
new User.Builder()
|
||||
.setEmailAddress("anotheruser@example.tld")
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder()
|
||||
.setRegistrarRoles(
|
||||
ImmutableMap.of(
|
||||
"TheRegistrar", RegistrarRole.ACCOUNT_MANAGER_WITH_REGISTRY_LOCK))
|
||||
.build())
|
||||
.setRegistryLockEmailAddress("anotherregistrylock@theregistrar.com")
|
||||
.build());
|
||||
verificationCode = saveRequest(PasswordResetRequest.Type.REGISTRY_LOCK).getVerificationCode();
|
||||
createAction(user, "GET", verificationCode, null).run();
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo("User anotheruser@example.tld has the wrong registry lock email address");
|
||||
}
|
||||
|
||||
private User createTechUser() {
|
||||
return new User.Builder()
|
||||
.setEmailAddress("tech@example.tld")
|
||||
|
||||
+34
-3
@@ -45,6 +45,7 @@ import google.registry.testing.ConsoleApiParamsUtils;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.ui.server.console.ConsoleActionBaseTestCase;
|
||||
import google.registry.ui.server.console.ConsoleApiParams;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -173,7 +174,9 @@ public class ConsoleBulkDomainActionTest extends ConsoleActionBaseTestCase {
|
||||
|
||||
@Test
|
||||
void testFailure_badActionString() {
|
||||
ConsoleBulkDomainAction action = createAction("bad", GSON.toJsonTree(ImmutableMap.of()));
|
||||
ConsoleBulkDomainAction action =
|
||||
createAction(
|
||||
"bad", GSON.toJsonTree(ImmutableMap.of("domainList", ImmutableList.of("domain.tld"))));
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_BAD_REQUEST);
|
||||
assertThat(response.getPayload())
|
||||
@@ -190,6 +193,29 @@ public class ConsoleBulkDomainActionTest extends ConsoleActionBaseTestCase {
|
||||
assertThat(response.getPayload()).isEqualTo("Bulk action payload must be present");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_listTooLarge() {
|
||||
JsonElement payload =
|
||||
GSON.toJsonTree(
|
||||
ImmutableMap.of(
|
||||
"domainList", Collections.nCopies(6, "domain.tld"), "reason", "reason"));
|
||||
ConsoleBulkDomainAction action = createAction("DELETE", payload, fteUser, 5);
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_BAD_REQUEST);
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo("Cannot process more than 5 domains in a single bulk action");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_emptyList() {
|
||||
JsonElement payload =
|
||||
GSON.toJsonTree(ImmutableMap.of("domainList", ImmutableList.of(), "reason", "reason"));
|
||||
ConsoleBulkDomainAction action = createAction("DELETE", payload);
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_BAD_REQUEST);
|
||||
assertThat(response.getPayload()).isEqualTo("Domain list cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_noPermission() {
|
||||
JsonElement payload =
|
||||
@@ -232,15 +258,20 @@ public class ConsoleBulkDomainActionTest extends ConsoleActionBaseTestCase {
|
||||
// }
|
||||
|
||||
private ConsoleBulkDomainAction createAction(String action, JsonElement payload) {
|
||||
return createAction(action, payload, fteUser);
|
||||
return createAction(action, payload, fteUser, 500);
|
||||
}
|
||||
|
||||
private ConsoleBulkDomainAction createAction(String action, JsonElement payload, User user) {
|
||||
return createAction(action, payload, user, 500);
|
||||
}
|
||||
|
||||
private ConsoleBulkDomainAction createAction(
|
||||
String action, JsonElement payload, User user, int limit) {
|
||||
AuthResult authResult = AuthResult.createUser(user);
|
||||
ConsoleApiParams params = ConsoleApiParamsUtils.createFake(authResult);
|
||||
when(params.request().getMethod()).thenReturn("POST");
|
||||
response = (FakeResponse) params.response();
|
||||
return new ConsoleBulkDomainAction(
|
||||
params, eppController, "TheRegistrar", action, Optional.ofNullable(payload));
|
||||
params, eppController, limit, "TheRegistrar", action, Optional.ofNullable(payload));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ for more information on using webdriver.
|
||||
2. Missing golden images
|
||||
* If you added a new test using screenshot comparison, you have to generate
|
||||
the golden image for that test in advance and copy it to
|
||||
[goldens/](https://github.com/google/nomulus/tree/master/core/src/test/java/google/registry/webdriver/goldens)
|
||||
[goldens/](https://github.com/google/nomulus/tree/master/core/src/test/resources/google/registry/webdriver/goldens)
|
||||
folder. There
|
||||
is an auxiliary Gradle build task to help with this, and here are some examples:
|
||||
```shell
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<secDNS:maxSigLife>604800</secDNS:maxSigLife>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12345</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
|
||||
+8
-8
@@ -21,49 +21,49 @@
|
||||
xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12345</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>2</secDNS:digestType>
|
||||
<secDNS:digest>D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12346</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>2</secDNS:digestType>
|
||||
<secDNS:digest>D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12347</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>2</secDNS:digestType>
|
||||
<secDNS:digest>D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12348</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>2</secDNS:digestType>
|
||||
<secDNS:digest>D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12349</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>2</secDNS:digestType>
|
||||
<secDNS:digest>D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12350</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>2</secDNS:digestType>
|
||||
<secDNS:digest>D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12351</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>2</secDNS:digestType>
|
||||
<secDNS:digest>D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12352</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>2</secDNS:digestType>
|
||||
<secDNS:digest>D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
|
||||
+9
-9
@@ -21,55 +21,55 @@
|
||||
xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12345</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12346</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12347</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12348</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12349</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12350</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12351</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12352</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12353</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
|
||||
+6
-6
@@ -27,19 +27,19 @@
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12346</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12347</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12348</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
@@ -51,19 +51,19 @@
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12350</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12351</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12352</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
|
||||
+8
-8
@@ -21,49 +21,49 @@
|
||||
xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12345</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>100</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12346</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>3</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12347</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12348</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12349</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12350</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12351</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12352</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<command>
|
||||
<create>
|
||||
<domain:create
|
||||
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
|
||||
<domain:name>example.tld</domain:name>
|
||||
<domain:period unit="y">2</domain:period>
|
||||
<domain:ns>
|
||||
<domain:hostObj>ns1.example.net</domain:hostObj>
|
||||
<domain:hostObj>ns2.example.net</domain:hostObj>
|
||||
</domain:ns>
|
||||
<domain:authInfo>
|
||||
<domain:pw>2fooBAR</domain:pw>
|
||||
</domain:authInfo>
|
||||
</domain:create>
|
||||
</create>
|
||||
<extension>
|
||||
<secDNS:create
|
||||
xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12345</secDNS:keyTag>
|
||||
<secDNS:alg>1</secDNS:alg>
|
||||
<secDNS:digestType>2</secDNS:digestType>
|
||||
<secDNS:digest>D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
</secDNS:create>
|
||||
</extension>
|
||||
<clTRID>ABC-12345</clTRID>
|
||||
</command>
|
||||
</epp>
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12345</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>2</secDNS:digestType>
|
||||
<secDNS:digest>D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12345</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
<secDNS:add>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12345</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
|
||||
+3
-3
@@ -34,9 +34,9 @@
|
||||
xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12345</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>2</secDNS:digestType>
|
||||
<secDNS:digest>D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
</secDNS:infData>
|
||||
</extension>
|
||||
|
||||
+3
-3
@@ -33,9 +33,9 @@
|
||||
xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12345</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>2</secDNS:digestType>
|
||||
<secDNS:digest>D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
</secDNS:infData>
|
||||
<rgp:infData xmlns:rgp="urn:ietf:params:xml:ns:rgp-1.0">
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@
|
||||
<secDNS:rem>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12345</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>2</secDNS:digestType>
|
||||
<secDNS:digest>D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
@@ -22,7 +22,7 @@
|
||||
<secDNS:add>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12346</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>2</secDNS:digestType>
|
||||
<secDNS:digest>D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@
|
||||
<secDNS:rem>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12345</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>A94A8FE5CCB19BA61C4C0873D391E987982FBBD3</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
@@ -22,7 +22,7 @@
|
||||
<secDNS:add>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12345</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>A94A8FE5CCB19BA61C4C0873D391E987982FBBD3</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<secDNS:rem>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12346</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>2</secDNS:digestType>
|
||||
<secDNS:digest>D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
<secDNS:add>
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>12346</secDNS:keyTag>
|
||||
<secDNS:alg>3</secDNS:alg>
|
||||
<secDNS:alg>8</secDNS:alg>
|
||||
<secDNS:digestType>1</secDNS:digestType>
|
||||
<secDNS:digest>38EC35D5B3A34B44C39B</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user