mirror of
https://github.com/google/nomulus
synced 2026-07-26 18:12:38 +00:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06299ccb86 | ||
|
|
732c30b359 | ||
|
|
ee5a2d3916 | ||
|
|
2b5643df4c | ||
|
|
6bbd7a2290 | ||
|
|
77ab80f3dc | ||
|
|
5e1cd0120f | ||
|
|
0167dad85f |
@@ -25,7 +25,10 @@ export class RegistrarSelectorComponent {
|
||||
registrarInput = signal<string>(this.registrarService.registrarId());
|
||||
filteredOptions?: string[];
|
||||
allRegistrarIds = computed(() =>
|
||||
this.registrarService.registrars().map((r) => r.registrarId)
|
||||
this.registrarService
|
||||
.registrars()
|
||||
.map((r) => r.registrarId)
|
||||
.sort()
|
||||
);
|
||||
|
||||
constructor(protected registrarService: RegistrarService) {
|
||||
|
||||
@@ -58,6 +58,8 @@ def fragileTestPatterns = [
|
||||
// Changes cache timeouts and for some reason appears to have contention
|
||||
// with other tests.
|
||||
"google/registry/whois/WhoisCommandFactoryTest.*",
|
||||
// Breaks random other tests when running with standardTests.
|
||||
"google/registry/bsa/UploadBsaUnavailableDomainsActionTest.*",
|
||||
// Currently changes a global configuration parameter that for some reason
|
||||
// results in timestamp inversions for other tests. TODO(mmuller): fix.
|
||||
"google/registry/flows/host/HostInfoFlowTest.*",
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package google.registry.batch;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STATUSES;
|
||||
@@ -30,8 +29,6 @@ import google.registry.groups.GmailClient;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.RegistryLock;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.model.tld.RegistryLockDao;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.request.Action;
|
||||
@@ -70,12 +67,14 @@ public class RelockDomainAction implements Runnable {
|
||||
"""
|
||||
The domain %s was successfully re-locked.
|
||||
|
||||
Please contact support at %s if you have any questions.""";
|
||||
Please contact support at %s if you have any questions.\
|
||||
""";
|
||||
private static final String RELOCK_NON_RETRYABLE_FAILURE_EMAIL_TEMPLATE =
|
||||
"""
|
||||
There was an error when automatically re-locking %s. Error message: %s
|
||||
|
||||
Please contact support at %s if you have any questions.""";
|
||||
Please contact support at %s if you have any questions.\
|
||||
""";
|
||||
private static final String RELOCK_TRANSIENT_FAILURE_EMAIL_TEMPLATE =
|
||||
"There was an unexpected error when automatically re-locking %s. We will continue retrying "
|
||||
+ "the lock for five hours. Please contact support at %s if you have any questions";
|
||||
@@ -171,7 +170,7 @@ public class RelockDomainAction implements Runnable {
|
||||
domainLockUtils.administrativelyApplyLock(
|
||||
oldLock.getDomainName(),
|
||||
oldLock.getRegistrarId(),
|
||||
oldLock.getRegistrarPocId(),
|
||||
oldLock.getRegistryLockEmail(),
|
||||
oldLock.isSuperuser());
|
||||
logger.atInfo().log("Re-locked domain %s.", oldLock.getDomainName());
|
||||
response.setStatus(SC_OK);
|
||||
@@ -221,7 +220,7 @@ public class RelockDomainAction implements Runnable {
|
||||
EmailMessage.newBuilder()
|
||||
.setBody(body)
|
||||
.setSubject(String.format("Error re-locking domain %s", oldLock.getDomainName()))
|
||||
.setRecipients(getEmailRecipients(oldLock.getRegistrarId()))
|
||||
.setRecipients(ImmutableSet.of(getEmailRecipient(oldLock)))
|
||||
.build());
|
||||
}
|
||||
|
||||
@@ -250,7 +249,7 @@ public class RelockDomainAction implements Runnable {
|
||||
EmailMessage.newBuilder()
|
||||
.setBody(body)
|
||||
.setSubject(String.format("Successful re-lock of domain %s", oldLock.getDomainName()))
|
||||
.setRecipients(getEmailRecipients(oldLock.getRegistrarId()))
|
||||
.setRecipients(ImmutableSet.of(getEmailRecipient(oldLock)))
|
||||
.build());
|
||||
}
|
||||
|
||||
@@ -261,7 +260,7 @@ public class RelockDomainAction implements Runnable {
|
||||
// For an unexpected failure, notify both the lock-enabled contacts and our alerting email
|
||||
ImmutableSet<InternetAddress> allRecipients =
|
||||
new ImmutableSet.Builder<InternetAddress>()
|
||||
.addAll(getEmailRecipients(oldLock.getRegistrarId()))
|
||||
.add(getEmailRecipient(oldLock))
|
||||
.add(alertRecipientAddress)
|
||||
.build();
|
||||
gmailClient.sendEmail(
|
||||
@@ -281,31 +280,12 @@ public class RelockDomainAction implements Runnable {
|
||||
.build());
|
||||
}
|
||||
|
||||
private ImmutableSet<InternetAddress> getEmailRecipients(String registrarId) {
|
||||
Registrar registrar =
|
||||
Registrar.loadByRegistrarIdCached(registrarId)
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalStateException(String.format("Unknown registrar %s", registrarId)));
|
||||
|
||||
ImmutableSet<String> registryLockEmailAddresses =
|
||||
registrar.getContacts().stream()
|
||||
.filter(RegistrarPoc::isRegistryLockAllowed)
|
||||
.map(RegistrarPoc::getRegistryLockEmailAddress)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.collect(toImmutableSet());
|
||||
|
||||
ImmutableSet.Builder<InternetAddress> builder = new ImmutableSet.Builder<>();
|
||||
// can't use streams due to the 'throws' in the InternetAddress constructor
|
||||
for (String registryLockEmailAddress : registryLockEmailAddresses) {
|
||||
try {
|
||||
builder.add(new InternetAddress(registryLockEmailAddress));
|
||||
} catch (AddressException e) {
|
||||
// This shouldn't stop any other emails going out, so swallow it
|
||||
logger.atWarning().log("Invalid email address '%s'.", registryLockEmailAddress);
|
||||
}
|
||||
private InternetAddress getEmailRecipient(RegistryLock lock) {
|
||||
try {
|
||||
return new InternetAddress(lock.getRegistryLockEmail());
|
||||
} catch (AddressException e) {
|
||||
// this really shouldn't happen
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,10 +215,12 @@ public class BsaValidateAction implements Runnable {
|
||||
if (Objects.equals(expectedReason, domain.reason())) {
|
||||
return Optional.empty();
|
||||
}
|
||||
if (isRegistered || domain.reason().equals(Reason.REGISTERED)) {
|
||||
if (isStalenessAllowed(isRegistered, activeDomains.get(domain.domainName()))) {
|
||||
// Registered name still reported with other reasons: Don't report if registration is recent.
|
||||
// Note that staleness is not tolerated if deregistered name is still reported as registered:
|
||||
// in this case we do not have the VKey on hand, and it is not worth the effort to find it
|
||||
// out.
|
||||
if (isRegistered && isStalenessAllowed(activeDomains.get(domain.domainName()))) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
return Optional.of(
|
||||
String.format(
|
||||
@@ -228,15 +230,10 @@ public class BsaValidateAction implements Runnable {
|
||||
domain.reason()));
|
||||
}
|
||||
|
||||
boolean isStalenessAllowed(boolean isNewDomain, VKey<Domain> domainVKey) {
|
||||
boolean isStalenessAllowed(VKey<Domain> domainVKey) {
|
||||
Domain domain = bsaQuery(() -> replicaTm().loadByKey(domainVKey));
|
||||
var now = clock.nowUtc();
|
||||
if (isNewDomain) {
|
||||
return domain.getCreationTime().plus(maxStaleness).isAfter(now);
|
||||
} else {
|
||||
return domain.getDeletionTime().isBefore(now)
|
||||
&& domain.getDeletionTime().plus(maxStaleness).isAfter(now);
|
||||
}
|
||||
return domain.getCreationTime().plus(maxStaleness).isAfter(now);
|
||||
}
|
||||
|
||||
/** Returns unique labels across all block lists in the download specified by {@code jobName}. */
|
||||
|
||||
@@ -25,16 +25,16 @@ import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.cloud.storage.BlobId;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.hash.Hasher;
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.google.common.io.ByteSource;
|
||||
import google.registry.bsa.api.BsaCredential;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.gcs.GcsUtils;
|
||||
@@ -47,10 +47,13 @@ import google.registry.request.auth.Auth;
|
||||
import google.registry.util.Clock;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.io.Writer;
|
||||
import java.util.Optional;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
@@ -60,14 +63,17 @@ import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okio.BufferedSink;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* Daily action that uploads unavailable domain names on applicable TLDs to BSA.
|
||||
*
|
||||
* <p>The upload is a single zipped text file containing combined details for all BSA-enrolled TLDs.
|
||||
* The text is a newline-delimited list of punycoded fully qualified domain names, and contains all
|
||||
* domains on each TLD that are registered and/or reserved.
|
||||
* The text is a newline-delimited list of punycoded fully qualified domain names with a trailing
|
||||
* newline at the end, and contains all domains on each TLD that are registered and/or reserved.
|
||||
*
|
||||
* <p>The file is also uploaded to GCS to preserve it as a record for ourselves.
|
||||
*/
|
||||
@@ -118,7 +124,7 @@ public class UploadBsaUnavailableDomainsAction implements Runnable {
|
||||
// TODO(mcilwain): Implement a date Cursor, have the cronjob run frequently, and short-circuit
|
||||
// the run if the daily upload is already completed.
|
||||
DateTime runTime = clock.nowUtc();
|
||||
String unavailableDomains = Joiner.on("\n").join(getUnavailableDomains(runTime));
|
||||
ImmutableSortedSet<String> unavailableDomains = getUnavailableDomains(runTime);
|
||||
if (unavailableDomains.isEmpty()) {
|
||||
logger.atWarning().log("No unavailable domains found; terminating.");
|
||||
emailSender.sendNotification(
|
||||
@@ -136,12 +142,16 @@ public class UploadBsaUnavailableDomainsAction implements Runnable {
|
||||
}
|
||||
|
||||
/** Uploads the unavailable domains list to GCS in the unavailable domains bucket. */
|
||||
boolean uploadToGcs(String unavailableDomains, DateTime runTime) {
|
||||
boolean uploadToGcs(ImmutableSortedSet<String> unavailableDomains, DateTime runTime) {
|
||||
logger.atInfo().log("Uploading unavailable names file to GCS in bucket %s", gcsBucket);
|
||||
BlobId blobId = BlobId.of(gcsBucket, createFilename(runTime));
|
||||
// `gcsUtils.openOutputStream` returns a buffered stream
|
||||
try (OutputStream gcsOutput = gcsUtils.openOutputStream(blobId);
|
||||
Writer osWriter = new OutputStreamWriter(gcsOutput, US_ASCII)) {
|
||||
osWriter.write(unavailableDomains);
|
||||
for (var domainName : unavailableDomains) {
|
||||
osWriter.write(domainName);
|
||||
osWriter.write("\n");
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
logger.atSevere().withCause(e).log(
|
||||
@@ -150,10 +160,14 @@ public class UploadBsaUnavailableDomainsAction implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
boolean uploadToBsa(String unavailableDomains, DateTime runTime) {
|
||||
boolean uploadToBsa(ImmutableSortedSet<String> unavailableDomains, DateTime runTime) {
|
||||
try {
|
||||
byte[] gzippedContents = gzipUnavailableDomains(unavailableDomains);
|
||||
String sha512Hash = ByteSource.wrap(gzippedContents).hash(Hashing.sha512()).toString();
|
||||
Hasher sha512Hasher = Hashing.sha512().newHasher();
|
||||
unavailableDomains.stream()
|
||||
.map(name -> name + "\n")
|
||||
.forEachOrdered(line -> sha512Hasher.putString(line, UTF_8));
|
||||
String sha512Hash = sha512Hasher.hash().toString();
|
||||
|
||||
String filename = createFilename(runTime);
|
||||
OkHttpClient client = new OkHttpClient().newBuilder().build();
|
||||
|
||||
@@ -169,7 +183,9 @@ public class UploadBsaUnavailableDomainsAction implements Runnable {
|
||||
.addFormDataPart(
|
||||
"file",
|
||||
String.format("%s.gz", filename),
|
||||
RequestBody.create(gzippedContents, MediaType.parse("application/octet-stream")))
|
||||
new StreamingRequestBody(
|
||||
gzippedStream(unavailableDomains),
|
||||
MediaType.parse("application/octet-stream")))
|
||||
.build();
|
||||
|
||||
Request request =
|
||||
@@ -196,15 +212,6 @@ public class UploadBsaUnavailableDomainsAction implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] gzipUnavailableDomains(String unavailableDomains) throws IOException {
|
||||
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
|
||||
try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream)) {
|
||||
gzipOutputStream.write(unavailableDomains.getBytes(US_ASCII));
|
||||
}
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static String createFilename(DateTime runTime) {
|
||||
return String.format("unavailable_domains_%s.txt", runTime.toString());
|
||||
}
|
||||
@@ -280,4 +287,65 @@ public class UploadBsaUnavailableDomainsAction implements Runnable {
|
||||
private static String toDomain(String domainLabel, Tld tld) {
|
||||
return String.format("%s.%s", domainLabel, tld.getTldStr());
|
||||
}
|
||||
|
||||
private InputStream gzippedStream(ImmutableSortedSet<String> unavailableDomains)
|
||||
throws IOException {
|
||||
PipedInputStream inputStream = new PipedInputStream();
|
||||
PipedOutputStream outputStream = new PipedOutputStream(inputStream);
|
||||
|
||||
new Thread(
|
||||
() -> {
|
||||
try {
|
||||
gzipUnavailableDomains(outputStream, unavailableDomains);
|
||||
} catch (Throwable e) {
|
||||
logger.atSevere().withCause(e).log("Failed to gzip unavailable domains.");
|
||||
try {
|
||||
// This will cause the next read to throw an IOException.
|
||||
inputStream.close();
|
||||
} catch (IOException ignore) {
|
||||
// Won't happen for `PipedInputStream.close()`
|
||||
}
|
||||
}
|
||||
})
|
||||
.start();
|
||||
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
private void gzipUnavailableDomains(
|
||||
PipedOutputStream outputStream, ImmutableSortedSet<String> unavailableDomains)
|
||||
throws IOException {
|
||||
// `GZIPOutputStream` is buffered.
|
||||
try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream)) {
|
||||
for (String name : unavailableDomains) {
|
||||
var line = name + "\n";
|
||||
gzipOutputStream.write(line.getBytes(US_ASCII));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class StreamingRequestBody extends RequestBody {
|
||||
private final BufferedInputStream inputStream;
|
||||
private final MediaType mediaType;
|
||||
|
||||
StreamingRequestBody(InputStream inputStream, MediaType mediaType) {
|
||||
this.inputStream = new BufferedInputStream(inputStream);
|
||||
this.mediaType = mediaType;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public MediaType contentType() {
|
||||
return mediaType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(@NotNull BufferedSink bufferedSink) throws IOException {
|
||||
byte[] buffer = new byte[2048];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
bufferedSink.write(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1591,6 +1591,26 @@ public final class RegistryConfig {
|
||||
return CONFIG_SETTINGS.get().caching.eppResourceMaxCachedEntries;
|
||||
}
|
||||
|
||||
/** Returns if we have enabled caching for User Authentication */
|
||||
public static boolean getUserAuthCachingEnabled() {
|
||||
return CONFIG_SETTINGS.get().caching.userAuthCachingEnabled;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void overrideIsUserAuthCachingEnabledForTesting(boolean enabled) {
|
||||
CONFIG_SETTINGS.get().caching.userAuthCachingEnabled = enabled;
|
||||
}
|
||||
|
||||
/** Returns the expiry duration for the user authentication cache. */
|
||||
public static java.time.Duration getUserAuthCachingDuration() {
|
||||
return java.time.Duration.ofSeconds(CONFIG_SETTINGS.get().caching.userAuthCachingSeconds);
|
||||
}
|
||||
|
||||
/** Returns the maximum number of entries in user authentication cache. */
|
||||
public static int getUserAuthMaxCachedEntries() {
|
||||
return CONFIG_SETTINGS.get().caching.userAuthMaxCachedEntries;
|
||||
}
|
||||
|
||||
/** Returns the amount of time that a particular claims list should be cached. */
|
||||
public static java.time.Duration getClaimsListCacheDuration() {
|
||||
return java.time.Duration.ofSeconds(CONFIG_SETTINGS.get().caching.claimsListCachingSeconds);
|
||||
|
||||
@@ -161,6 +161,9 @@ public class RegistryConfigSettings {
|
||||
public int eppResourceCachingSeconds;
|
||||
public int eppResourceMaxCachedEntries;
|
||||
public int claimsListCachingSeconds;
|
||||
public boolean userAuthCachingEnabled;
|
||||
public int userAuthCachingSeconds;
|
||||
public int userAuthMaxCachedEntries;
|
||||
}
|
||||
|
||||
/** Configuration for ICANN monthly reporting. */
|
||||
|
||||
@@ -326,6 +326,20 @@ caching:
|
||||
# long duration is acceptable because claims lists don't change frequently.
|
||||
claimsListCachingSeconds: 21600 # six hours
|
||||
|
||||
#-- User Authentication Cache Settings --#
|
||||
|
||||
# Whether to cache User objects during OIDC token authentication to reduce database load.
|
||||
# This helps mitigate high QPS from frequent hello commands and session-less requests.
|
||||
userAuthCachingEnabled: true
|
||||
|
||||
# The duration in seconds for which a User object is cached after being loaded.
|
||||
# A short duration is recommended to avoid stale data.
|
||||
userAuthCachingSeconds: 60
|
||||
|
||||
# The maximum number of User objects to store in the cache per pod.
|
||||
# This helps limit the memory footprint of the cache.
|
||||
userAuthMaxCachedEntries: 200
|
||||
|
||||
# Note: Only allowedServiceAccountEmails and oauthClientId should be configured.
|
||||
# Other fields are related to OAuth-based authentication and will be removed.
|
||||
auth:
|
||||
|
||||
@@ -101,8 +101,15 @@ public final class RegistryLock extends UpdateAutoTimestampEntity implements Bui
|
||||
@Column(nullable = false)
|
||||
private String registrarId;
|
||||
|
||||
/** The POC that performed the action, or null if it was a superuser. */
|
||||
@Expose private String registrarPocId;
|
||||
/**
|
||||
* The email address of the user that performed the action, or null if it was a superuser.
|
||||
*
|
||||
* <p>Note: this is misnamed in the database due to historical reasons, where we used the
|
||||
* registrar POC ID as the email address rather than a separate specialized field.
|
||||
*/
|
||||
@Column(name = "registrarPocId")
|
||||
@Expose
|
||||
private String registryLockEmail;
|
||||
|
||||
/** When the lock is first requested. */
|
||||
@AttributeOverrides({
|
||||
@@ -161,8 +168,8 @@ public final class RegistryLock extends UpdateAutoTimestampEntity implements Bui
|
||||
return registrarId;
|
||||
}
|
||||
|
||||
public String getRegistrarPocId() {
|
||||
return registrarPocId;
|
||||
public String getRegistryLockEmail() {
|
||||
return registryLockEmail;
|
||||
}
|
||||
|
||||
public DateTime getLockRequestTime() {
|
||||
@@ -255,7 +262,7 @@ public final class RegistryLock extends UpdateAutoTimestampEntity implements Bui
|
||||
checkArgumentNotNull(getInstance().registrarId, "Registrar ID cannot be null");
|
||||
checkArgumentNotNull(getInstance().verificationCode, "Verification code cannot be null");
|
||||
checkArgument(
|
||||
getInstance().registrarPocId != null || getInstance().isSuperuser,
|
||||
getInstance().registryLockEmail != null || getInstance().isSuperuser,
|
||||
"Registrar POC ID must be provided if superuser is false");
|
||||
return super.build();
|
||||
}
|
||||
@@ -275,8 +282,8 @@ public final class RegistryLock extends UpdateAutoTimestampEntity implements Bui
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setRegistrarPocId(String registrarPocId) {
|
||||
getInstance().registrarPocId = registrarPocId;
|
||||
public Builder setRegistryLockEmail(String registryLockEmail) {
|
||||
getInstance().registryLockEmail = registryLockEmail;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,16 +14,11 @@
|
||||
|
||||
package google.registry.model.registrar;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.io.BaseEncoding.base64;
|
||||
import static google.registry.model.registrar.Registrar.checkValidEmail;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableSortedCopy;
|
||||
import static google.registry.util.PasswordUtils.SALT_SUPPLIER;
|
||||
import static google.registry.util.PasswordUtils.hashPassword;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
@@ -38,7 +33,6 @@ import google.registry.model.Jsonifiable;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.transaction.QueryComposer;
|
||||
import google.registry.util.PasswordUtils;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
@@ -47,9 +41,7 @@ import jakarta.persistence.Id;
|
||||
import jakarta.persistence.IdClass;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A contact for a Registrar. Note, equality, hashCode and comparable have been overridden to only
|
||||
@@ -107,9 +99,6 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
|
||||
|
||||
@Id @Expose public String registrarId;
|
||||
|
||||
/** External email address of this contact used for registry lock confirmations. */
|
||||
String registryLockEmailAddress;
|
||||
|
||||
/** The voice number of the contact. */
|
||||
@Expose String phoneNumber;
|
||||
|
||||
@@ -147,22 +136,10 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
|
||||
@Expose
|
||||
boolean visibleInDomainWhoisAsAbuse = false;
|
||||
|
||||
/**
|
||||
* Whether the contact is allowed to set their registry lock password through the registrar
|
||||
* console. This will be set to false on contact creation and when the user sets a password.
|
||||
*/
|
||||
/** Legacy field, around until we can remove the non-null constraint and the column from SQL. */
|
||||
@Column(nullable = false)
|
||||
boolean allowedToSetRegistryLockPassword = false;
|
||||
|
||||
/**
|
||||
* A hashed password that exists iff this contact is registry-lock-enabled. The hash is a base64
|
||||
* encoded SHA256 string.
|
||||
*/
|
||||
String registryLockPasswordHash;
|
||||
|
||||
/** Randomly generated hash salt. */
|
||||
String registryLockPasswordSalt;
|
||||
|
||||
/**
|
||||
* Helper to update the contacts associated with a Registrar. This requires querying for the
|
||||
* existing contacts, deleting existing contacts that are not part of the given {@code contacts}
|
||||
@@ -197,10 +174,6 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
|
||||
return emailAddress;
|
||||
}
|
||||
|
||||
public Optional<String> getRegistryLockEmailAddress() {
|
||||
return Optional.ofNullable(registryLockEmailAddress);
|
||||
}
|
||||
|
||||
public String getPhoneNumber() {
|
||||
return phoneNumber;
|
||||
}
|
||||
@@ -229,24 +202,6 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
|
||||
return new Builder(clone(this));
|
||||
}
|
||||
|
||||
public boolean isAllowedToSetRegistryLockPassword() {
|
||||
return allowedToSetRegistryLockPassword;
|
||||
}
|
||||
|
||||
public boolean isRegistryLockAllowed() {
|
||||
return !isNullOrEmpty(registryLockPasswordHash) && !isNullOrEmpty(registryLockPasswordSalt);
|
||||
}
|
||||
|
||||
public boolean verifyRegistryLockPassword(String registryLockPassword) {
|
||||
if (isNullOrEmpty(registryLockPassword)
|
||||
|| isNullOrEmpty(registryLockPasswordSalt)
|
||||
|| isNullOrEmpty(registryLockPasswordHash)) {
|
||||
return false;
|
||||
}
|
||||
return PasswordUtils.verifyPassword(
|
||||
registryLockPassword, registryLockPasswordHash, registryLockPasswordSalt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation that's human friendly.
|
||||
*
|
||||
@@ -296,15 +251,12 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
|
||||
return new JsonMapBuilder()
|
||||
.put("name", name)
|
||||
.put("emailAddress", emailAddress)
|
||||
.put("registryLockEmailAddress", registryLockEmailAddress)
|
||||
.put("phoneNumber", phoneNumber)
|
||||
.put("faxNumber", faxNumber)
|
||||
.put("types", getTypes().stream().map(Object::toString).collect(joining(",")))
|
||||
.put("visibleInWhoisAsAdmin", visibleInWhoisAsAdmin)
|
||||
.put("visibleInWhoisAsTech", visibleInWhoisAsTech)
|
||||
.put("visibleInDomainWhoisAsAbuse", visibleInDomainWhoisAsAbuse)
|
||||
.put("allowedToSetRegistryLockPassword", allowedToSetRegistryLockPassword)
|
||||
.put("registryLockAllowed", isRegistryLockAllowed())
|
||||
.put("id", getId())
|
||||
.build();
|
||||
}
|
||||
@@ -344,14 +296,6 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
|
||||
public RegistrarPoc build() {
|
||||
checkNotNull(getInstance().registrarId, "Registrar ID cannot be null");
|
||||
checkValidEmail(getInstance().emailAddress);
|
||||
// Check allowedToSetRegistryLockPassword here because if we want to allow the user to set
|
||||
// a registry lock password, we must also set up the correct registry lock email concurrently
|
||||
// or beforehand.
|
||||
if (getInstance().allowedToSetRegistryLockPassword) {
|
||||
checkArgument(
|
||||
!isNullOrEmpty(getInstance().registryLockEmailAddress),
|
||||
"Registry lock email must not be null if allowing registry lock access");
|
||||
}
|
||||
return cloneEmptyToNull(super.build());
|
||||
}
|
||||
|
||||
@@ -365,11 +309,6 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setRegistryLockEmailAddress(@Nullable String registryLockEmailAddress) {
|
||||
getInstance().registryLockEmailAddress = registryLockEmailAddress;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPhoneNumber(String phoneNumber) {
|
||||
getInstance().phoneNumber = phoneNumber;
|
||||
return this;
|
||||
@@ -409,28 +348,6 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
|
||||
getInstance().visibleInDomainWhoisAsAbuse = visible;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAllowedToSetRegistryLockPassword(boolean allowedToSetRegistryLockPassword) {
|
||||
if (allowedToSetRegistryLockPassword) {
|
||||
getInstance().registryLockPasswordSalt = null;
|
||||
getInstance().registryLockPasswordHash = null;
|
||||
}
|
||||
getInstance().allowedToSetRegistryLockPassword = allowedToSetRegistryLockPassword;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setRegistryLockPassword(String registryLockPassword) {
|
||||
checkArgument(
|
||||
getInstance().allowedToSetRegistryLockPassword,
|
||||
"Not allowed to set registry lock password for this contact");
|
||||
checkArgument(
|
||||
!isNullOrEmpty(registryLockPassword), "Registry lock password was null or empty");
|
||||
byte[] salt = SALT_SUPPLIER.get();
|
||||
getInstance().registryLockPasswordSalt = base64().encode(salt);
|
||||
getInstance().registryLockPasswordHash = hashPassword(registryLockPassword, salt);
|
||||
getInstance().allowedToSetRegistryLockPassword = false;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public static ImmutableList<RegistrarPoc> loadForRegistrar(String registrarId) {
|
||||
|
||||
+49
-2
@@ -15,14 +15,20 @@
|
||||
package google.registry.request.auth;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static google.registry.config.RegistryConfig.getUserAuthCachingDuration;
|
||||
import static google.registry.config.RegistryConfig.getUserAuthCachingEnabled;
|
||||
import static google.registry.config.RegistryConfig.getUserAuthMaxCachedEntries;
|
||||
import static google.registry.model.CacheUtils.newCacheBuilder;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import com.google.api.client.json.webtoken.JsonWebSignature;
|
||||
import com.google.auth.oauth2.TokenVerifier.VerificationException;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.config.RegistryConfig;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.persistence.VKey;
|
||||
@@ -71,6 +77,40 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
|
||||
this.tokenVerifier = tokenVerifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* An in-memory cache for User entities, built using the project's standard utility.
|
||||
*
|
||||
* <p>This cache reduces database load by temporarily storing User objects after they are fetched.
|
||||
* It is configured to cache negative results (i.e., when a user is not found) to prevent repeated
|
||||
* lookups for invalid users. The cache's behavior (enabled, expiry, size) is controlled by
|
||||
* settings in {@link RegistryConfig}.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static LoadingCache<String, Optional<User>> userCache =
|
||||
newCacheBuilder(getUserAuthCachingDuration())
|
||||
.maximumSize(getUserAuthMaxCachedEntries())
|
||||
.build(OidcTokenAuthenticationMechanism::loadUser);
|
||||
|
||||
/**
|
||||
* A loader function that defines how to fetch a User from the database on a cache miss.
|
||||
*
|
||||
* <p>This is the single point of entry to the database for this authentication flow. It will only
|
||||
* be invoked by the cache when a requested user is not already in memory.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static Optional<User> loadUser(String email) {
|
||||
VKey<User> userVKey = VKey.create(User.class, email);
|
||||
return tm().transact(() -> tm().loadByKeyIfPresent(userVKey));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void setCacheForTesting(LoadingCache<String, Optional<User>> cache) {
|
||||
checkState(
|
||||
RegistryEnvironment.get() == RegistryEnvironment.UNITTEST,
|
||||
"Cannot set cache outside of a test environment");
|
||||
OidcTokenAuthenticationMechanism.userCache = cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthResult authenticate(HttpServletRequest request) {
|
||||
if (RegistryEnvironment.get().equals(RegistryEnvironment.UNITTEST)
|
||||
@@ -112,8 +152,15 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
|
||||
logger.atInfo().log("No email address from the OIDC token:\n%s", token.getPayload());
|
||||
return AuthResult.NOT_AUTHENTICATED;
|
||||
}
|
||||
Optional<User> maybeUser =
|
||||
tm().transact(() -> tm().loadByKeyIfPresent(VKey.create(User.class, email)));
|
||||
Optional<User> maybeUser;
|
||||
if (getUserAuthCachingEnabled()) {
|
||||
// If caching is ON, use the cache.
|
||||
maybeUser = userCache.get(email);
|
||||
} else {
|
||||
// If caching is OFF, fall back to the original direct database call.
|
||||
maybeUser = loadUser(email);
|
||||
}
|
||||
|
||||
stopwatch.tick("OidcTokenAuthenticationMechanism maybeUser loaded");
|
||||
if (maybeUser.isPresent()) {
|
||||
return AuthResult.createUser(maybeUser.get());
|
||||
|
||||
@@ -74,11 +74,12 @@ public final class DomainLockUtils {
|
||||
* <p>The lock will not be applied until {@link #verifyVerificationCode} is called.
|
||||
*/
|
||||
public RegistryLock saveNewRegistryLockRequest(
|
||||
String domainName, String registrarId, @Nullable String registrarPocId, boolean isAdmin) {
|
||||
String domainName, String registrarId, @Nullable String registryLockEmail, boolean isAdmin) {
|
||||
return tm().transact(
|
||||
() ->
|
||||
RegistryLockDao.save(
|
||||
createLockBuilder(domainName, registrarId, registrarPocId, isAdmin).build()));
|
||||
createLockBuilder(domainName, registrarId, registryLockEmail, isAdmin)
|
||||
.build()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,13 +130,13 @@ public final class DomainLockUtils {
|
||||
* the case of relocks, isAdmin is determined by the previous lock.
|
||||
*/
|
||||
public RegistryLock administrativelyApplyLock(
|
||||
String domainName, String registrarId, @Nullable String registrarPocId, boolean isAdmin) {
|
||||
String domainName, String registrarId, @Nullable String registryLockEmail, boolean isAdmin) {
|
||||
return tm().transact(
|
||||
() -> {
|
||||
DateTime now = tm().getTransactionTime();
|
||||
RegistryLock newLock =
|
||||
RegistryLockDao.save(
|
||||
createLockBuilder(domainName, registrarId, registrarPocId, isAdmin)
|
||||
createLockBuilder(domainName, registrarId, registryLockEmail, isAdmin)
|
||||
.setLockCompletionTime(now)
|
||||
.build());
|
||||
applyLockStatuses(newLock, now, isAdmin);
|
||||
@@ -235,7 +236,7 @@ public final class DomainLockUtils {
|
||||
}
|
||||
|
||||
private RegistryLock.Builder createLockBuilder(
|
||||
String domainName, String registrarId, @Nullable String registrarPocId, boolean isAdmin) {
|
||||
String domainName, String registrarId, @Nullable String registryLockEmail, boolean isAdmin) {
|
||||
DateTime now = tm().getTransactionTime();
|
||||
Domain domain = getDomain(domainName, registrarId, now);
|
||||
verifyDomainNotLocked(domain, isAdmin);
|
||||
@@ -255,7 +256,7 @@ public final class DomainLockUtils {
|
||||
.setDomainName(domainName)
|
||||
.setRepoId(domain.getRepoId())
|
||||
.setRegistrarId(registrarId)
|
||||
.setRegistrarPocId(registrarPocId)
|
||||
.setRegistryLockEmail(registryLockEmail)
|
||||
.isSuperuser(isAdmin);
|
||||
}
|
||||
|
||||
|
||||
@@ -85,12 +85,6 @@ final class RegistrarPocCommand extends MutatingCommand {
|
||||
+ " and will be used as the console login email, if --login_email is not specified.")
|
||||
String email;
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = "--registry_lock_email",
|
||||
description = "Email address used for registry lock confirmation emails")
|
||||
String registryLockEmail;
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = "--phone",
|
||||
@@ -132,15 +126,6 @@ final class RegistrarPocCommand extends MutatingCommand {
|
||||
arity = 1)
|
||||
private Boolean visibleInDomainWhoisAsAbuse;
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = "--allowed_to_set_registry_lock_password",
|
||||
description =
|
||||
"Allow this contact to set their registry lock password in the console,"
|
||||
+ " enabling registry lock",
|
||||
arity = 1)
|
||||
private Boolean allowedToSetRegistryLockPassword;
|
||||
|
||||
@Parameter(
|
||||
names = {"-o", "--output"},
|
||||
description = "Output file when --mode=LIST",
|
||||
@@ -235,9 +220,6 @@ final class RegistrarPocCommand extends MutatingCommand {
|
||||
builder.setRegistrar(registrar);
|
||||
builder.setName(name);
|
||||
builder.setEmailAddress(email);
|
||||
if (!isNullOrEmpty(registryLockEmail)) {
|
||||
builder.setRegistryLockEmailAddress(registryLockEmail);
|
||||
}
|
||||
if (phone != null) {
|
||||
builder.setPhoneNumber(phone.orElse(null));
|
||||
}
|
||||
@@ -255,9 +237,6 @@ final class RegistrarPocCommand extends MutatingCommand {
|
||||
if (visibleInDomainWhoisAsAbuse != null) {
|
||||
builder.setVisibleInDomainWhoisAsAbuse(visibleInDomainWhoisAsAbuse);
|
||||
}
|
||||
if (allowedToSetRegistryLockPassword != null) {
|
||||
builder.setAllowedToSetRegistryLockPassword(allowedToSetRegistryLockPassword);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@@ -269,9 +248,6 @@ final class RegistrarPocCommand extends MutatingCommand {
|
||||
if (!isNullOrEmpty(name)) {
|
||||
builder.setName(name);
|
||||
}
|
||||
if (!isNullOrEmpty(registryLockEmail)) {
|
||||
builder.setRegistryLockEmailAddress(registryLockEmail);
|
||||
}
|
||||
if (phone != null) {
|
||||
builder.setPhoneNumber(phone.orElse(null));
|
||||
}
|
||||
@@ -290,9 +266,6 @@ final class RegistrarPocCommand extends MutatingCommand {
|
||||
if (visibleInDomainWhoisAsAbuse != null) {
|
||||
builder.setVisibleInDomainWhoisAsAbuse(visibleInDomainWhoisAsAbuse);
|
||||
}
|
||||
if (allowedToSetRegistryLockPassword != null) {
|
||||
builder.setAllowedToSetRegistryLockPassword(allowedToSetRegistryLockPassword);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import static google.registry.util.DomainNameUtils.canonicalizeHostname;
|
||||
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
@@ -29,7 +28,6 @@ import com.google.re2j.Pattern;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarAddress;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.ui.forms.FormException;
|
||||
import google.registry.ui.forms.FormField;
|
||||
import google.registry.ui.forms.FormFieldException;
|
||||
import google.registry.ui.forms.FormFields;
|
||||
@@ -38,7 +36,6 @@ import google.registry.util.X509Utils;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -192,14 +189,6 @@ public final class RegistrarFormFields {
|
||||
public static final FormField<String, String> CONTACT_FAX_NUMBER_FIELD =
|
||||
FormFields.PHONE_NUMBER.asBuilderNamed("faxNumber").build();
|
||||
|
||||
public static final FormField<Object, Boolean> CONTACT_ALLOWED_TO_SET_REGISTRY_LOCK_PASSWORD =
|
||||
FormField.named("allowedToSetRegistryLockPassword", Object.class)
|
||||
.transform(Boolean.class, b -> Boolean.valueOf(Objects.toString(b)))
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> CONTACT_REGISTRY_LOCK_PASSWORD_FIELD =
|
||||
FormFields.NAME.asBuilderNamed("registryLockPassword").build();
|
||||
|
||||
public static final FormField<String, Set<RegistrarPoc.Type>> CONTACT_TYPES =
|
||||
FormField.named("types")
|
||||
.uppercased()
|
||||
@@ -369,8 +358,6 @@ public final class RegistrarFormFields {
|
||||
private static void applyRegistrarContactArgs(RegistrarPoc.Builder builder, Map<String, ?> args) {
|
||||
builder.setName(CONTACT_NAME_FIELD.extractUntyped(args).orElse(null));
|
||||
builder.setEmailAddress(CONTACT_EMAIL_ADDRESS_FIELD.extractUntyped(args).orElse(null));
|
||||
builder.setRegistryLockEmailAddress(
|
||||
REGISTRY_LOCK_EMAIL_ADDRESS_FIELD.extractUntyped(args).orElse(null));
|
||||
builder.setVisibleInWhoisAsAdmin(
|
||||
CONTACT_VISIBLE_IN_WHOIS_AS_ADMIN_FIELD.extractUntyped(args).orElse(false));
|
||||
builder.setVisibleInWhoisAsTech(
|
||||
@@ -380,23 +367,5 @@ public final class RegistrarFormFields {
|
||||
builder.setPhoneNumber(CONTACT_PHONE_NUMBER_FIELD.extractUntyped(args).orElse(null));
|
||||
builder.setFaxNumber(CONTACT_FAX_NUMBER_FIELD.extractUntyped(args).orElse(null));
|
||||
builder.setTypes(CONTACT_TYPES.extractUntyped(args).orElse(ImmutableSet.of()));
|
||||
// The parser is inconsistent with whether it retrieves boolean values as strings or booleans.
|
||||
// As a result, use a potentially-redundant converter that can deal with both.
|
||||
builder.setAllowedToSetRegistryLockPassword(
|
||||
CONTACT_ALLOWED_TO_SET_REGISTRY_LOCK_PASSWORD.extractUntyped(args).orElse(false));
|
||||
|
||||
// Registry lock password does not need to be set every time
|
||||
CONTACT_REGISTRY_LOCK_PASSWORD_FIELD
|
||||
.extractUntyped(args)
|
||||
.ifPresent(
|
||||
password -> {
|
||||
if (!Strings.isNullOrEmpty(password)) {
|
||||
if (password.length() < 8) {
|
||||
throw new FormException(
|
||||
"Registry lock password must be at least 8 characters long");
|
||||
}
|
||||
builder.setRegistryLockPassword(password);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +53,10 @@ import java.util.Optional;
|
||||
public class RegistrarsAction extends ConsoleApiAction {
|
||||
private static final int PASSWORD_LENGTH = 16;
|
||||
private static final int PASSCODE_LENGTH = 5;
|
||||
private static final ImmutableList<Registrar.Type> allowedRegistrarTypes =
|
||||
ImmutableList.of(Registrar.Type.REAL, Registrar.Type.OTE);
|
||||
private static final ImmutableSet<Registrar.Type> TYPES_ALLOWED_FOR_USERS =
|
||||
ImmutableSet.of(Registrar.Type.REAL, Registrar.Type.OTE);
|
||||
private static final ImmutableSet<Registrar.Type> TYPES_ALLOWED_FOR_ADMINS =
|
||||
ImmutableSet.of(Registrar.Type.INTERNAL, Registrar.Type.REAL, Registrar.Type.OTE);
|
||||
private static final String SQL_TEMPLATE =
|
||||
"""
|
||||
SELECT * FROM "Registrar"
|
||||
@@ -80,6 +82,8 @@ public class RegistrarsAction extends ConsoleApiAction {
|
||||
@Override
|
||||
protected void getHandler(User user) {
|
||||
if (user.getUserRoles().hasGlobalPermission(ConsolePermission.VIEW_REGISTRARS)) {
|
||||
ImmutableSet<Registrar.Type> allowedRegistrarTypes =
|
||||
user.getUserRoles().isAdmin() ? TYPES_ALLOWED_FOR_ADMINS : TYPES_ALLOWED_FOR_USERS;
|
||||
ImmutableList<Registrar> registrars =
|
||||
Streams.stream(Registrar.loadAll())
|
||||
.filter(r -> allowedRegistrarTypes.contains(r.getType()))
|
||||
@@ -94,6 +98,7 @@ public class RegistrarsAction extends ConsoleApiAction {
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(toImmutableSet());
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Registrar> registrars =
|
||||
tm().transact(
|
||||
() ->
|
||||
|
||||
@@ -45,7 +45,6 @@ import google.registry.ui.server.console.ConsoleApiAction;
|
||||
import google.registry.ui.server.console.ConsoleApiParams;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
@@ -246,7 +245,6 @@ public class ContactAction extends ConsoleApiAction {
|
||||
throw new ContactRequirementException(
|
||||
"An abuse contact visible in domain WHOIS query must be designated");
|
||||
}
|
||||
checkContactRegistryLockRequirements(existingContacts, updatedContacts);
|
||||
}
|
||||
|
||||
private static void enforcePrimaryContactRestrictions(
|
||||
@@ -266,69 +264,6 @@ public class ContactAction extends ConsoleApiAction {
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkContactRegistryLockRequirements(
|
||||
ImmutableSet<RegistrarPoc> existingContacts, ImmutableSet<RegistrarPoc> updatedContacts) {
|
||||
// Any contact(s) with new passwords must be allowed to set them
|
||||
for (RegistrarPoc updatedContact : updatedContacts) {
|
||||
if (updatedContact.isRegistryLockAllowed()
|
||||
|| updatedContact.isAllowedToSetRegistryLockPassword()) {
|
||||
RegistrarPoc existingContact =
|
||||
existingContacts.stream()
|
||||
.filter(
|
||||
contact -> contact.getEmailAddress().equals(updatedContact.getEmailAddress()))
|
||||
.findFirst()
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new FormException(
|
||||
"Cannot set registry lock password directly on new contact"));
|
||||
// Can't modify registry lock email address
|
||||
if (!Objects.equals(
|
||||
updatedContact.getRegistryLockEmailAddress(),
|
||||
existingContact.getRegistryLockEmailAddress())) {
|
||||
throw new FormException("Cannot modify registryLockEmailAddress through the UI");
|
||||
}
|
||||
if (updatedContact.isRegistryLockAllowed()) {
|
||||
// the password must have been set before or the user was allowed to set it now
|
||||
if (!existingContact.isAllowedToSetRegistryLockPassword()
|
||||
&& !existingContact.isRegistryLockAllowed()) {
|
||||
throw new FormException("Registrar contact not allowed to set registry lock password");
|
||||
}
|
||||
}
|
||||
if (updatedContact.isAllowedToSetRegistryLockPassword()) {
|
||||
if (!existingContact.isAllowedToSetRegistryLockPassword()) {
|
||||
throw new FormException(
|
||||
"Cannot modify isAllowedToSetRegistryLockPassword through the UI");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Any previously-existing contacts with registry lock enabled cannot be deleted
|
||||
existingContacts.stream()
|
||||
.filter(RegistrarPoc::isRegistryLockAllowed)
|
||||
.forEach(
|
||||
contact -> {
|
||||
Optional<RegistrarPoc> updatedContactOptional =
|
||||
updatedContacts.stream()
|
||||
.filter(
|
||||
updatedContact ->
|
||||
updatedContact.getEmailAddress().equals(contact.getEmailAddress()))
|
||||
.findFirst();
|
||||
if (updatedContactOptional.isEmpty()) {
|
||||
throw new FormException(
|
||||
String.format(
|
||||
"Cannot delete the contact %s that has registry lock enabled",
|
||||
contact.getEmailAddress()));
|
||||
}
|
||||
if (!updatedContactOptional.get().isRegistryLockAllowed()) {
|
||||
throw new FormException(
|
||||
String.format(
|
||||
"Cannot remove the ability to use registry lock on the contact %s",
|
||||
contact.getEmailAddress()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the registrar contact whose phone number and email address is visible in domain WHOIS
|
||||
* query as abuse contact (if any).
|
||||
|
||||
@@ -68,7 +68,7 @@ public class RelockDomainActionTest {
|
||||
|
||||
private static final String DOMAIN_NAME = "example.tld";
|
||||
private static final String CLIENT_ID = "TheRegistrar";
|
||||
private static final String POC_ID = "marla.singer@example.com";
|
||||
private static final String LOCK_EMAIL_ADDRESS = "Marla.Singer.RegistryLock@crr.com";
|
||||
|
||||
private final FakeResponse response = new FakeResponse();
|
||||
private final FakeClock clock = new FakeClock(DateTime.parse("2015-05-18T12:34:56Z"));
|
||||
@@ -94,7 +94,9 @@ public class RelockDomainActionTest {
|
||||
Host host = persistActiveHost("ns1.example.net");
|
||||
domain = persistResource(DatabaseHelper.newDomain(DOMAIN_NAME, host));
|
||||
|
||||
oldLock = domainLockUtils.administrativelyApplyLock(DOMAIN_NAME, CLIENT_ID, POC_ID, false);
|
||||
oldLock =
|
||||
domainLockUtils.administrativelyApplyLock(
|
||||
DOMAIN_NAME, CLIENT_ID, LOCK_EMAIL_ADDRESS, false);
|
||||
assertThat(loadByEntity(domain).getStatusValues())
|
||||
.containsAtLeastElementsIn(REGISTRY_LOCK_STATUSES);
|
||||
oldLock =
|
||||
@@ -255,9 +257,10 @@ public class RelockDomainActionTest {
|
||||
.setSubject("Successful re-lock of domain example.tld")
|
||||
.setBody(
|
||||
"""
|
||||
The domain example.tld was successfully re-locked.
|
||||
The domain example.tld was successfully re-locked.
|
||||
|
||||
Please contact support at support@example.com if you have any questions.""")
|
||||
Please contact support at support@example.com if you have any questions.\
|
||||
""")
|
||||
.setRecipients(
|
||||
ImmutableSet.of(new InternetAddress("Marla.Singer.RegistryLock@crr.com")))
|
||||
.build();
|
||||
@@ -268,9 +271,10 @@ public class RelockDomainActionTest {
|
||||
String expectedBody =
|
||||
String.format(
|
||||
"""
|
||||
There was an error when automatically re-locking example.tld. Error message: %s
|
||||
There was an error when automatically re-locking example.tld. Error message: %s
|
||||
|
||||
Please contact support at support@example.com if you have any questions.""",
|
||||
Please contact support at support@example.com if you have any questions.\
|
||||
""",
|
||||
exceptionMessage);
|
||||
assertFailureEmailWithBody(
|
||||
expectedBody, ImmutableSet.of(new InternetAddress("Marla.Singer.RegistryLock@crr.com")));
|
||||
|
||||
@@ -24,7 +24,6 @@ import static google.registry.bsa.persistence.BsaTestingUtils.persistDownloadSch
|
||||
import static google.registry.bsa.persistence.BsaTestingUtils.persistUnblockableDomain;
|
||||
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.persistResource;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -218,7 +217,8 @@ public class BsaValidateActionTest {
|
||||
test1,1;2
|
||||
test2,3
|
||||
""";
|
||||
String blockPlusContent = """
|
||||
String blockPlusContent =
|
||||
"""
|
||||
domainLabel,orderIDs
|
||||
test2,4
|
||||
""";
|
||||
@@ -239,7 +239,7 @@ public class BsaValidateActionTest {
|
||||
persistBsaLabel("label");
|
||||
Domain domain = persistActiveDomain("label.app", fakeClock.nowUtc());
|
||||
fakeClock.advanceBy(MAX_STALENESS.minus(Duration.standardSeconds(1)));
|
||||
assertThat(action.isStalenessAllowed(true, domain.createVKey())).isTrue();
|
||||
assertThat(action.isStalenessAllowed(domain.createVKey())).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -247,23 +247,7 @@ public class BsaValidateActionTest {
|
||||
persistBsaLabel("label");
|
||||
Domain domain = persistActiveDomain("label.app", fakeClock.nowUtc());
|
||||
fakeClock.advanceBy(MAX_STALENESS);
|
||||
assertThat(action.isStalenessAllowed(true, domain.createVKey())).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isStalenessAllowed_deletedDomain_allowed() {
|
||||
persistBsaLabel("label");
|
||||
Domain domain = persistDeletedDomain("label.app", fakeClock.nowUtc());
|
||||
fakeClock.advanceBy(MAX_STALENESS.minus(Duration.standardSeconds(1)));
|
||||
assertThat(action.isStalenessAllowed(false, domain.createVKey())).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isStalenessAllowed_deletedDomain_notAllowed() {
|
||||
persistBsaLabel("label");
|
||||
Domain domain = persistDeletedDomain("label.app", fakeClock.nowUtc());
|
||||
fakeClock.advanceBy(MAX_STALENESS);
|
||||
assertThat(action.isStalenessAllowed(false, domain.createVKey())).isFalse();
|
||||
assertThat(action.isStalenessAllowed(domain.createVKey())).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -405,10 +389,11 @@ public class BsaValidateActionTest {
|
||||
assertThat(message.body())
|
||||
.isEqualTo(
|
||||
"""
|
||||
Most recent download is 2023-11-09t020857.880z.
|
||||
Most recent download is 2023-11-09t020857.880z.
|
||||
|
||||
Error line 1.
|
||||
Error line 2""");
|
||||
Error line 1.
|
||||
Error line 2\
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -20,13 +20,24 @@ import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistDeletedDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistReservedList;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.LogsSubject.assertAboutLogs;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static google.registry.util.NetworkUtils.pickUnusedPort;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.concurrent.Executors.newSingleThreadExecutor;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import com.google.cloud.storage.BlobId;
|
||||
import com.google.cloud.storage.contrib.nio.testing.LocalStorageHelper;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.net.HostAndPort;
|
||||
import com.google.common.testing.TestLogHandler;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.bsa.api.BsaCredential;
|
||||
import google.registry.gcs.GcsUtils;
|
||||
import google.registry.model.tld.Tld;
|
||||
@@ -35,9 +46,25 @@ import google.registry.model.tld.label.ReservedList;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.request.UrlConnectionService;
|
||||
import google.registry.server.Route;
|
||||
import google.registry.server.TestServer;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.annotation.MultipartConfig;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.Part;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -102,13 +129,112 @@ public class UploadBsaUnavailableDomainsActionTest {
|
||||
BlobId existingFile =
|
||||
BlobId.of(BUCKET, String.format("unavailable_domains_%s.txt", clock.nowUtc()));
|
||||
String blockList = new String(gcsUtils.readBytesFrom(existingFile), UTF_8);
|
||||
assertThat(blockList).isEqualTo("ace.tld\nflagrant.tld\nfoobar.tld\njimmy.tld\ntine.tld");
|
||||
assertThat(blockList).isEqualTo("ace.tld\nflagrant.tld\nfoobar.tld\njimmy.tld\ntine.tld\n");
|
||||
assertThat(blockList).doesNotContain("not-blocked.tld");
|
||||
|
||||
// This test currently fails in the upload-to-bsa step.
|
||||
verify(emailSender, times(1))
|
||||
.sendNotification("BSA daily upload completed with errors", "Please see logs for details.");
|
||||
}
|
||||
|
||||
// TODO(mcilwain): Add test of BSA API upload as well.
|
||||
@Test
|
||||
void uploadToBsaTest() throws Exception {
|
||||
TestLogHandler logHandler = new TestLogHandler();
|
||||
Logger loggerToIntercept =
|
||||
Logger.getLogger(UploadBsaUnavailableDomainsAction.class.getCanonicalName());
|
||||
loggerToIntercept.addHandler(logHandler);
|
||||
|
||||
persistActiveDomain("foobar.tld");
|
||||
persistActiveDomain("ace.tld");
|
||||
persistDeletedDomain("not-blocked.tld", clock.nowUtc().minusDays(1));
|
||||
|
||||
var testServer = startTestServer();
|
||||
action.apiUrl = testServer.getUrl("/upload").toURI().toString();
|
||||
try {
|
||||
action.run();
|
||||
} finally {
|
||||
testServer.stop();
|
||||
}
|
||||
String dataSent = "ace.tld\nflagrant.tld\nfoobar.tld\njimmy.tld\ntine.tld\n";
|
||||
String checkSum = Hashing.sha512().hashString(dataSent, UTF_8).toString();
|
||||
String expectedResponse =
|
||||
"Received response with code 200 from server: "
|
||||
+ String.format("Checksum: [%s]\n%s\n", checkSum, dataSent);
|
||||
assertAboutLogs().that(logHandler).hasLogAtLevelWithMessage(Level.INFO, expectedResponse);
|
||||
verify(emailSender, times(1)).sendNotification("BSA daily upload completed successfully", "");
|
||||
}
|
||||
|
||||
private TestServer startTestServer() throws Exception {
|
||||
TestServer testServer =
|
||||
new TestServer(
|
||||
HostAndPort.fromParts(InetAddress.getLocalHost().getHostAddress(), pickUnusedPort()),
|
||||
ImmutableMap.of(),
|
||||
ImmutableList.of(Route.route("/upload", Servelet.class)));
|
||||
testServer.start();
|
||||
newSingleThreadExecutor()
|
||||
.execute(
|
||||
() -> {
|
||||
try {
|
||||
while (true) {
|
||||
testServer.process();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// Expected
|
||||
}
|
||||
});
|
||||
return testServer;
|
||||
}
|
||||
|
||||
@MultipartConfig(
|
||||
location = "", // Directory for storing uploaded files. Use default when blank
|
||||
maxFileSize = 10485760L, // 10MB
|
||||
maxRequestSize = 20971520L, // 20MB
|
||||
fileSizeThreshold = 1048576 // Save in memory if file size < 1MB
|
||||
)
|
||||
public static class Servelet extends HttpServlet {
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
String checkSum = null;
|
||||
String content = null;
|
||||
try {
|
||||
for (Part part : req.getParts()) {
|
||||
switch (part.getName()) {
|
||||
case "zone" -> checkSum = readChecksum(part);
|
||||
case "file" -> content = readGzipped(part);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.atInfo().withCause(e).log("");
|
||||
}
|
||||
int status = checkSum == null || content == null ? 400 : 200;
|
||||
resp.setStatus(status);
|
||||
resp.setContentType("text/plain");
|
||||
try (PrintWriter writer = resp.getWriter()) {
|
||||
writer.printf("Checksum: [%s]\n%s\n", checkSum, content);
|
||||
}
|
||||
}
|
||||
|
||||
private String readChecksum(Part part) {
|
||||
try (InputStream is = part.getInputStream()) {
|
||||
return new Gson()
|
||||
.fromJson(new String(ByteStreams.toByteArray(is), UTF_8), Map.class)
|
||||
.getOrDefault("checkSum", "Not found")
|
||||
.toString();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String readGzipped(Part part) {
|
||||
try (InputStream is = part.getInputStream();
|
||||
GZIPInputStream gis = new GZIPInputStream(is)) {
|
||||
return new String(ByteStreams.toByteArray(gis), UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-3
@@ -434,11 +434,8 @@ public abstract class JpaTransactionManagerExtension
|
||||
.setRegistrar(makeRegistrar2())
|
||||
.setName("Marla Singer")
|
||||
.setEmailAddress("Marla.Singer@crr.com")
|
||||
.setRegistryLockEmailAddress("Marla.Singer.RegistryLock@crr.com")
|
||||
.setPhoneNumber("+1.2128675309")
|
||||
.setTypes(ImmutableSet.of(RegistrarPoc.Type.TECH))
|
||||
.setAllowedToSetRegistryLockPassword(true)
|
||||
.setRegistryLockPassword("hi")
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
+84
-1
@@ -16,13 +16,20 @@ package google.registry.request.auth;
|
||||
|
||||
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.config.RegistryConfig.getUserAuthCachingDuration;
|
||||
import static google.registry.config.RegistryConfig.getUserAuthMaxCachedEntries;
|
||||
import static google.registry.request.auth.AuthModule.BEARER_PREFIX;
|
||||
import static google.registry.request.auth.AuthModule.IAP_HEADER_NAME;
|
||||
import static google.registry.testing.DatabaseHelper.createAdminUser;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload;
|
||||
import com.google.api.client.json.webtoken.JsonWebSignature;
|
||||
import com.google.api.client.json.webtoken.JsonWebSignature.Header;
|
||||
@@ -33,7 +40,9 @@ import dagger.Component;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.config.CredentialModule.ApplicationDefaultCredential;
|
||||
import google.registry.config.RegistryConfig;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.CacheUtils;
|
||||
import google.registry.model.console.GlobalRole;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserRoles;
|
||||
@@ -44,6 +53,7 @@ import google.registry.request.auth.OidcTokenAuthenticationMechanism.RegularOidc
|
||||
import google.registry.util.GoogleCredentialsBundle;
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -54,6 +64,9 @@ public class OidcTokenAuthenticationMechanismTest {
|
||||
|
||||
private static final String rawToken = "this-token";
|
||||
private static final String email = "user@email.test";
|
||||
|
||||
private static final String unknownEmail = "bad-guy@evil.real";
|
||||
|
||||
private static final String gaiaId = "gaia-id";
|
||||
private static final ImmutableSet<String> serviceAccounts =
|
||||
ImmutableSet.of("service@email.test", "email@service.goog");
|
||||
@@ -75,6 +88,12 @@ public class OidcTokenAuthenticationMechanismTest {
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() throws Exception {
|
||||
// 1. Create a brand new cache.
|
||||
LoadingCache<String, Optional<User>> testCache =
|
||||
CacheUtils.newCacheBuilder(getUserAuthCachingDuration())
|
||||
.maximumSize(getUserAuthMaxCachedEntries())
|
||||
.build(OidcTokenAuthenticationMechanism::loadUser);
|
||||
OidcTokenAuthenticationMechanism.setCacheForTesting(testCache);
|
||||
payload.setEmail(email);
|
||||
payload.setSubject(gaiaId);
|
||||
user = createAdminUser(email);
|
||||
@@ -154,7 +173,7 @@ public class OidcTokenAuthenticationMechanismTest {
|
||||
|
||||
@Test
|
||||
void testAuthenticate_unknownEmailAddress() throws Exception {
|
||||
payload.setEmail("bad-guy@evil.real");
|
||||
payload.setEmail(unknownEmail);
|
||||
authResult = authenticationMechanism.authenticate(request);
|
||||
assertThat(authResult).isEqualTo(AuthResult.NOT_AUTHENTICATED);
|
||||
}
|
||||
@@ -189,6 +208,62 @@ public class OidcTokenAuthenticationMechanismTest {
|
||||
authenticationMechanism = component.regularOidcAuthenticationMechanism();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAuthenticate_ExistentUser_isCached() {
|
||||
// Arrange: Create a spy of the actual cache object.
|
||||
// A spy calls the real methods of the object while allowing us to verify interactions.
|
||||
LoadingCache<String, Optional<User>> spiedCache =
|
||||
spy(OidcTokenAuthenticationMechanism.userCache);
|
||||
OidcTokenAuthenticationMechanism.setCacheForTesting(spiedCache);
|
||||
|
||||
// Act: Call the authenticate method.
|
||||
authenticationMechanism.authenticate(request);
|
||||
|
||||
// Assert: Verify that the cache's "get" method was called exactly once.
|
||||
// This confirms the cache is being used without checking its internal stats.
|
||||
verify(spiedCache).get(email);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAuthenticate_nonExistentUser_isCached() {
|
||||
// Arrange: Use an email that is not in the test database.
|
||||
|
||||
payload.setEmail(unknownEmail);
|
||||
|
||||
LoadingCache<String, Optional<User>> spiedCache =
|
||||
spy(OidcTokenAuthenticationMechanism.userCache);
|
||||
OidcTokenAuthenticationMechanism.setCacheForTesting(spiedCache);
|
||||
// Act: Call the authenticate method.
|
||||
authenticationMechanism.authenticate(request);
|
||||
|
||||
// Assert: Verify that the cache's "get" method was called for the unverified email.
|
||||
// This confirms that we attempted to look up the unknown user in the cache.
|
||||
verify(spiedCache).get(unknownEmail);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAuthenticate_whenCacheIsDisabled_cacheIsNotUsed() {
|
||||
// Arrange: Explicitly disable the cache and create a spy.
|
||||
RegistryConfig.overrideIsUserAuthCachingEnabledForTesting(false);
|
||||
LoadingCache<String, Optional<User>> spiedCache =
|
||||
spy(OidcTokenAuthenticationMechanism.userCache);
|
||||
OidcTokenAuthenticationMechanism.setCacheForTesting(spiedCache);
|
||||
|
||||
// Act: Authenticate the user.
|
||||
AuthResult authResult = authenticationMechanism.authenticate(request);
|
||||
|
||||
// Assert: The authentication should still succeed because the code falls back
|
||||
// to the direct database call.
|
||||
assertThat(authResult.isAuthenticated()).isTrue();
|
||||
|
||||
// Assert: Crucially, verify that the cache's "get" method was NEVER called.
|
||||
// This proves the cache was correctly bypassed.
|
||||
verify(spiedCache, never()).get(any(String.class));
|
||||
|
||||
// Teardown: Restore the default setting for other tests.
|
||||
RegistryConfig.overrideIsUserAuthCachingEnabledForTesting(true);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Component(modules = {AuthModule.class, TestModule.class})
|
||||
interface TestComponent {
|
||||
@@ -234,4 +309,12 @@ public class OidcTokenAuthenticationMechanismTest {
|
||||
return GoogleCredentialsBundle.create(GoogleCredentials.newBuilder().build());
|
||||
}
|
||||
}
|
||||
|
||||
private void reinitializeCache() {
|
||||
OidcTokenAuthenticationMechanism.userCache =
|
||||
CacheUtils.newCacheBuilder(getUserAuthCachingDuration())
|
||||
.maximumSize(getUserAuthMaxCachedEntries())
|
||||
.recordStats()
|
||||
.build(OidcTokenAuthenticationMechanism::loadUser);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ class RegistrarPocTest {
|
||||
.setRegistrar(testRegistrar)
|
||||
.setName("Judith Registrar")
|
||||
.setEmailAddress("judith.doe@example.com")
|
||||
.setRegistryLockEmailAddress("judith.doe@external.com")
|
||||
.setPhoneNumber("+1.2125650000")
|
||||
.setFaxNumber("+1.2125650001")
|
||||
.setTypes(ImmutableSet.of(WHOIS))
|
||||
|
||||
@@ -26,10 +26,16 @@ import com.google.common.net.HostAndPort;
|
||||
import com.google.common.util.concurrent.SimpleTimeLimiter;
|
||||
import google.registry.util.RegistryEnvironment;
|
||||
import google.registry.util.UrlChecker;
|
||||
import jakarta.servlet.MultipartConfigElement;
|
||||
import jakarta.servlet.annotation.MultipartConfig;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.FutureTask;
|
||||
@@ -49,6 +55,8 @@ import org.eclipse.jetty.server.ServerConnector;
|
||||
* {@link #stop()} methods. However, a {@link #process()} method was added, which is used to process
|
||||
* requests made to servlets (not static files) in the calling thread.
|
||||
*
|
||||
* <p>A servlet that expects multi-part requests should be annotated with {@link MultipartConfig}.
|
||||
*
|
||||
* <p><b>Note:</b> This server is intended for development purposes. For the love all that is good,
|
||||
* do not make this public-facing.
|
||||
*
|
||||
@@ -70,6 +78,7 @@ public final class TestServer {
|
||||
private final HostAndPort urlAddress;
|
||||
private final Server server = new Server();
|
||||
private final BlockingQueue<FutureTask<Void>> requestQueue = new LinkedBlockingDeque<>();
|
||||
private List<Path> multiPartTmpDirs = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates a new instance, but does not begin serving.
|
||||
@@ -134,6 +143,13 @@ public final class TestServer {
|
||||
},
|
||||
SHUTDOWN_TIMEOUT_MS,
|
||||
TimeUnit.MILLISECONDS);
|
||||
for (var dir : multiPartTmpDirs) {
|
||||
try {
|
||||
Files.delete(dir);
|
||||
} catch (Exception e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throwIfUnchecked(e);
|
||||
throw new RuntimeException(e);
|
||||
@@ -161,7 +177,27 @@ public final class TestServer {
|
||||
StaticResourceServlet.configureServletHolder(holder, runfile.getKey(), runfile.getValue());
|
||||
}
|
||||
for (Route route : routes) {
|
||||
context.addServlet(wrapServlet(route.servletClass()), route.path());
|
||||
holder = context.addServlet(wrapServlet(route.servletClass()), route.path());
|
||||
MultipartConfig multipartConfig = route.servletClass().getAnnotation(MultipartConfig.class);
|
||||
if (multipartConfig != null) {
|
||||
try {
|
||||
var location = multipartConfig.location();
|
||||
if (location == null || location.isBlank()) {
|
||||
Path tmpDir = Files.createTempDirectory("TestServer_");
|
||||
multiPartTmpDirs.add(tmpDir);
|
||||
location = tmpDir.toString();
|
||||
}
|
||||
MultipartConfigElement multipartConfigElement =
|
||||
new MultipartConfigElement(
|
||||
location,
|
||||
multipartConfig.maxFileSize(),
|
||||
multipartConfig.maxRequestSize(),
|
||||
multipartConfig.fileSizeThreshold());
|
||||
holder.getRegistration().setMultipartConfig(multipartConfigElement);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
holder = context.addServlet(DefaultServlet.class, "/*");
|
||||
holder.setInitParameter("aliases", "1");
|
||||
|
||||
@@ -291,7 +291,7 @@ public final class DomainLockUtilsTest {
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setRepoId(domain.getRepoId())
|
||||
.isSuperuser(false)
|
||||
.setRegistrarPocId(POC_ID)
|
||||
.setRegistryLockEmail(POC_ID)
|
||||
.build());
|
||||
clock.advanceOneMilli();
|
||||
RegistryLock resultLock =
|
||||
@@ -477,7 +477,7 @@ public final class DomainLockUtilsTest {
|
||||
.setRepoId("repoId")
|
||||
.setRelockDuration(standardHours(6))
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setRegistrarPocId("someone@example.com")
|
||||
.setRegistryLockEmail("someone@example.com")
|
||||
.setVerificationCode("hi")
|
||||
.build());
|
||||
domainLockUtils.enqueueDomainRelock(lock.getRelockDuration().get(), lock.getRevisionId(), 0);
|
||||
@@ -504,7 +504,7 @@ public final class DomainLockUtilsTest {
|
||||
.setDomainName("example.tld")
|
||||
.setRepoId("repoId")
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setRegistrarPocId("someone@example.com")
|
||||
.setRegistryLockEmail("someone@example.com")
|
||||
.setVerificationCode("hi")
|
||||
.build());
|
||||
IllegalArgumentException thrown =
|
||||
|
||||
@@ -92,7 +92,6 @@ class RegistrarPocCommandTest extends CommandTestCase<RegistrarPocCommand> {
|
||||
"--mode=UPDATE",
|
||||
"--name=Judith Registrar",
|
||||
"--email=judith.doe@example.com",
|
||||
"--registry_lock_email=judith.doe@external.com",
|
||||
"--phone=+1.2125650000",
|
||||
"--fax=+1.2125650001",
|
||||
"--contact_type=WHOIS",
|
||||
@@ -108,7 +107,6 @@ class RegistrarPocCommandTest extends CommandTestCase<RegistrarPocCommand> {
|
||||
.setRegistrar(registrar)
|
||||
.setName("Judith Registrar")
|
||||
.setEmailAddress("judith.doe@example.com")
|
||||
.setRegistryLockEmailAddress("judith.doe@external.com")
|
||||
.setPhoneNumber("+1.2125650000")
|
||||
.setFaxNumber("+1.2125650001")
|
||||
.setTypes(ImmutableSet.of(WHOIS))
|
||||
@@ -255,7 +253,6 @@ class RegistrarPocCommandTest extends CommandTestCase<RegistrarPocCommand> {
|
||||
"--mode=CREATE",
|
||||
"--name=Jim Doe",
|
||||
"--email=jim.doe@example.com",
|
||||
"--registry_lock_email=jim.doe@external.com",
|
||||
"--contact_type=ADMIN,ABUSE",
|
||||
"--visible_in_whois_as_admin=true",
|
||||
"--visible_in_whois_as_tech=false",
|
||||
@@ -269,7 +266,6 @@ class RegistrarPocCommandTest extends CommandTestCase<RegistrarPocCommand> {
|
||||
.setRegistrar(registrar)
|
||||
.setName("Jim Doe")
|
||||
.setEmailAddress("jim.doe@example.com")
|
||||
.setRegistryLockEmailAddress("jim.doe@external.com")
|
||||
.setTypes(ImmutableSet.of(ADMIN, ABUSE))
|
||||
.setVisibleInWhoisAsAdmin(true)
|
||||
.setVisibleInWhoisAsTech(false)
|
||||
@@ -318,87 +314,6 @@ class RegistrarPocCommandTest extends CommandTestCase<RegistrarPocCommand> {
|
||||
assertThat(loadRegistrar("NewRegistrar").getContactsRequireSyncing()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreate_setAllowedToSetRegistryLockPassword() throws Exception {
|
||||
runCommandForced(
|
||||
"--mode=CREATE",
|
||||
"--name=Jim Doe",
|
||||
"--email=jim.doe@example.com",
|
||||
"--registry_lock_email=jim.doe.registry.lock@example.com",
|
||||
"--allowed_to_set_registry_lock_password=true",
|
||||
"NewRegistrar");
|
||||
RegistrarPoc registrarPoc = loadRegistrar("NewRegistrar").getContacts().asList().get(1);
|
||||
assertThat(registrarPoc.isAllowedToSetRegistryLockPassword()).isTrue();
|
||||
registrarPoc.asBuilder().setRegistryLockPassword("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdate_setAllowedToSetRegistryLockPassword() throws Exception {
|
||||
Registrar registrar = loadRegistrar("NewRegistrar");
|
||||
RegistrarPoc registrarPoc =
|
||||
persistResource(
|
||||
new RegistrarPoc.Builder()
|
||||
.setRegistrar(registrar)
|
||||
.setName("Jim Doe")
|
||||
.setEmailAddress("jim.doe@example.com")
|
||||
.build());
|
||||
assertThat(registrarPoc.isAllowedToSetRegistryLockPassword()).isFalse();
|
||||
|
||||
// First, try (and fail) to set the password directly
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> registrarPoc.asBuilder().setRegistryLockPassword("foo"));
|
||||
|
||||
// Next, try (and fail) to allow registry lock without a registry lock email
|
||||
assertThat(
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
runCommandForced(
|
||||
"--mode=UPDATE",
|
||||
"--email=jim.doe@example.com",
|
||||
"--allowed_to_set_registry_lock_password=true",
|
||||
"NewRegistrar")))
|
||||
.hasMessageThat()
|
||||
.isEqualTo("Registry lock email must not be null if allowing registry lock access");
|
||||
|
||||
// Next, include the email and it should succeed
|
||||
runCommandForced(
|
||||
"--mode=UPDATE",
|
||||
"--email=jim.doe@example.com",
|
||||
"--registry_lock_email=jim.doe.registry.lock@example.com",
|
||||
"--allowed_to_set_registry_lock_password=true",
|
||||
"NewRegistrar");
|
||||
RegistrarPoc newContact = reloadResource(registrarPoc);
|
||||
assertThat(newContact.isAllowedToSetRegistryLockPassword()).isTrue();
|
||||
// should be allowed to set the password now
|
||||
newContact.asBuilder().setRegistryLockPassword("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdate_setAllowedToSetRegistryLockPassword_removesOldPassword() throws Exception {
|
||||
Registrar registrar = loadRegistrar("NewRegistrar");
|
||||
RegistrarPoc registrarPoc =
|
||||
persistResource(
|
||||
new RegistrarPoc.Builder()
|
||||
.setRegistrar(registrar)
|
||||
.setName("Jim Doe")
|
||||
.setEmailAddress("jim.doe@example.com")
|
||||
.setRegistryLockEmailAddress("jim.doe.registry.lock@example.com")
|
||||
.setAllowedToSetRegistryLockPassword(true)
|
||||
.setRegistryLockPassword("hi")
|
||||
.build());
|
||||
assertThat(registrarPoc.verifyRegistryLockPassword("hi")).isTrue();
|
||||
assertThat(registrarPoc.verifyRegistryLockPassword("hello")).isFalse();
|
||||
runCommandForced(
|
||||
"--mode=UPDATE",
|
||||
"--email=jim.doe@example.com",
|
||||
"--allowed_to_set_registry_lock_password=true",
|
||||
"NewRegistrar");
|
||||
registrarPoc = reloadResource(registrarPoc);
|
||||
assertThat(registrarPoc.verifyRegistryLockPassword("hi")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreate_failure_badEmail() {
|
||||
IllegalArgumentException thrown =
|
||||
|
||||
+18
-16
@@ -75,7 +75,8 @@ public class ConsoleRegistryLockActionTest extends ConsoleActionBaseTestCase {
|
||||
Note: this code will expire in one hour.
|
||||
|
||||
https://registrarconsole.tld/console/#/registry-lock-verify?lockVerificationCode=\
|
||||
123456789ABCDEFGHJKLMNPQRSTUVWXY""";
|
||||
123456789ABCDEFGHJKLMNPQRSTUVWXY\
|
||||
""";
|
||||
|
||||
@Mock GmailClient gmailClient;
|
||||
private ConsoleRegistryLockAction action;
|
||||
@@ -112,8 +113,8 @@ public class ConsoleRegistryLockActionTest extends ConsoleActionBaseTestCase {
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo(
|
||||
"""
|
||||
[{"domainName":"example.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
|
||||
"""
|
||||
[{"domainName":"example.test","registryLockEmail":"johndoe@theregistrar.com","lockRequestTime":\
|
||||
{"creationTime":"2024-04-15T00:00:00.000Z"},"unlockRequestTime":"null","lockCompletionTime":\
|
||||
"2024-04-15T00:00:00.000Z","unlockCompletionTime":"null","isSuperuser":false}]\
|
||||
""");
|
||||
@@ -127,7 +128,7 @@ public class ConsoleRegistryLockActionTest extends ConsoleActionBaseTestCase {
|
||||
.setDomainName("expired.test")
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setVerificationCode("123456789ABCDEFGHJKLMNPQRSTUVWXY")
|
||||
.setRegistrarPocId("johndoe@theregistrar.com")
|
||||
.setRegistryLockEmail("johndoe@theregistrar.com")
|
||||
.build();
|
||||
saveRegistryLock(expiredLock);
|
||||
RegistryLock expiredUnlock =
|
||||
@@ -136,7 +137,7 @@ public class ConsoleRegistryLockActionTest extends ConsoleActionBaseTestCase {
|
||||
.setDomainName("expiredunlock.test")
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setVerificationCode("123456789ABCDEFGHJKLMNPQRSTUVWXY")
|
||||
.setRegistrarPocId("johndoe@theregistrar.com")
|
||||
.setRegistryLockEmail("johndoe@theregistrar.com")
|
||||
.setLockCompletionTime(clock.nowUtc())
|
||||
.setUnlockRequestTime(clock.nowUtc())
|
||||
.build();
|
||||
@@ -149,7 +150,7 @@ public class ConsoleRegistryLockActionTest extends ConsoleActionBaseTestCase {
|
||||
.setDomainName("example.test")
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setVerificationCode("123456789ABCDEFGHJKLMNPQRSTUVWXY")
|
||||
.setRegistrarPocId("johndoe@theregistrar.com")
|
||||
.setRegistryLockEmail("johndoe@theregistrar.com")
|
||||
.setLockCompletionTime(clock.nowUtc())
|
||||
.build();
|
||||
clock.advanceOneMilli();
|
||||
@@ -168,7 +169,7 @@ public class ConsoleRegistryLockActionTest extends ConsoleActionBaseTestCase {
|
||||
.setDomainName("pending.test")
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setVerificationCode("111111111ABCDEFGHJKLMNPQRSTUVWXY")
|
||||
.setRegistrarPocId("johndoe@theregistrar.com")
|
||||
.setRegistryLockEmail("johndoe@theregistrar.com")
|
||||
.build();
|
||||
|
||||
RegistryLock incompleteUnlock =
|
||||
@@ -177,7 +178,7 @@ public class ConsoleRegistryLockActionTest extends ConsoleActionBaseTestCase {
|
||||
.setDomainName("incompleteunlock.test")
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setVerificationCode("123456789ABCDEFGHJKLMNPQRSTUVWXY")
|
||||
.setRegistrarPocId("johndoe@theregistrar.com")
|
||||
.setRegistryLockEmail("johndoe@theregistrar.com")
|
||||
.setLockCompletionTime(clock.nowUtc())
|
||||
.setUnlockRequestTime(clock.nowUtc())
|
||||
.build();
|
||||
@@ -187,7 +188,7 @@ public class ConsoleRegistryLockActionTest extends ConsoleActionBaseTestCase {
|
||||
.setRepoId("repoId")
|
||||
.setDomainName("unlocked.test")
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setRegistrarPocId("johndoe@theregistrar.com")
|
||||
.setRegistryLockEmail("johndoe@theregistrar.com")
|
||||
.setVerificationCode("123456789ABCDEFGHJKLMNPQRSTUUUUU")
|
||||
.setLockCompletionTime(clock.nowUtc())
|
||||
.setUnlockRequestTime(clock.nowUtc())
|
||||
@@ -206,26 +207,27 @@ public class ConsoleRegistryLockActionTest extends ConsoleActionBaseTestCase {
|
||||
// locks or completed unlocks
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo(
|
||||
"""
|
||||
"""
|
||||
[{"domainName":"adminexample.test","lockRequestTime":{"creationTime":"2024-04-16T00:00:00.001Z"},\
|
||||
"unlockRequestTime":"null","lockCompletionTime":"2024-04-16T00:00:00.001Z","unlockCompletionTime":\
|
||||
"null","isSuperuser":true},\
|
||||
\
|
||||
{"domainName":"example.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
|
||||
{"domainName":"example.test","registryLockEmail":"johndoe@theregistrar.com","lockRequestTime":\
|
||||
{"creationTime":"2024-04-16T00:00:00.001Z"},"unlockRequestTime":"null","lockCompletionTime":\
|
||||
"2024-04-16T00:00:00.000Z","unlockCompletionTime":"null","isSuperuser":false},\
|
||||
\
|
||||
{"domainName":"expiredunlock.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
|
||||
{"domainName":"expiredunlock.test","registryLockEmail":"johndoe@theregistrar.com","lockRequestTime":\
|
||||
{"creationTime":"2024-04-15T00:00:00.000Z"},"unlockRequestTime":"2024-04-15T00:00:00.000Z",\
|
||||
"lockCompletionTime":"2024-04-15T00:00:00.000Z","unlockCompletionTime":"null","isSuperuser":false},\
|
||||
\
|
||||
{"domainName":"incompleteunlock.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
|
||||
{"domainName":"incompleteunlock.test","registryLockEmail":"johndoe@theregistrar.com","lockRequestTime":\
|
||||
{"creationTime":"2024-04-16T00:00:00.001Z"},"unlockRequestTime":"2024-04-16T00:00:00.001Z",\
|
||||
"lockCompletionTime":"2024-04-16T00:00:00.001Z","unlockCompletionTime":"null","isSuperuser":false},\
|
||||
\
|
||||
{"domainName":"pending.test","registrarPocId":"johndoe@theregistrar.com","lockRequestTime":\
|
||||
{"domainName":"pending.test","registryLockEmail":"johndoe@theregistrar.com","lockRequestTime":\
|
||||
{"creationTime":"2024-04-16T00:00:00.001Z"},"unlockRequestTime":"null","lockCompletionTime":"null",\
|
||||
"unlockCompletionTime":"null","isSuperuser":false}]""");
|
||||
"unlockCompletionTime":"null","isSuperuser":false}]\
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -500,7 +502,7 @@ public class ConsoleRegistryLockActionTest extends ConsoleActionBaseTestCase {
|
||||
.setRepoId(defaultDomain.getRepoId())
|
||||
.setDomainName(defaultDomain.getDomainName())
|
||||
.setRegistrarId(defaultDomain.getCurrentSponsorRegistrarId())
|
||||
.setRegistrarPocId("johndoe@theregistrar.com")
|
||||
.setRegistryLockEmail("johndoe@theregistrar.com")
|
||||
.setVerificationCode("123456789ABCDEFGHJKLMNPQRSTUUUUU");
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -187,7 +187,7 @@ public class ConsoleRegistryLockVerifyActionTest extends ConsoleActionBaseTestCa
|
||||
.setRepoId(defaultDomain.getRepoId())
|
||||
.setDomainName(defaultDomain.getDomainName())
|
||||
.setRegistrarId(defaultDomain.getCurrentSponsorRegistrarId())
|
||||
.setRegistrarPocId("johndoe@theregistrar.com")
|
||||
.setRegistryLockEmail("johndoe@theregistrar.com")
|
||||
.setVerificationCode(DEFAULT_CODE);
|
||||
}
|
||||
|
||||
|
||||
+21
-33
@@ -284,39 +284,27 @@ class ContactActionTest extends ConsoleActionBaseTestCase {
|
||||
"Registrar New Registrar (registrarId) updated in registry unittest"
|
||||
+ " environment")
|
||||
.setBody(
|
||||
"The following changes were made in registry unittest environment to the"
|
||||
+ " registrar registrarId by admin fte@email.tld:\n"
|
||||
+ "\n"
|
||||
+ "contacts:\n"
|
||||
+ " ADDED:\n"
|
||||
+ " {id="
|
||||
+ id
|
||||
+ ", name=Test Registrar 2,"
|
||||
+ " emailAddress=incorrect@example.com, registrarId=registrarId,"
|
||||
+ " registryLockEmailAddress=null, phoneNumber=+1.1234567890,"
|
||||
+ " faxNumber=+1.1234567891, types=[TECH],"
|
||||
+ " visibleInWhoisAsAdmin=false, visibleInWhoisAsTech=true,"
|
||||
+ " visibleInDomainWhoisAsAbuse=false,"
|
||||
+ " allowedToSetRegistryLockPassword=false}\n"
|
||||
+ " REMOVED:\n"
|
||||
+ " {id="
|
||||
+ id
|
||||
+ ", name=Test Registrar 2, emailAddress=test.registrar2@example.com,"
|
||||
+ " registrarId=registrarId, registryLockEmailAddress=null,"
|
||||
+ " phoneNumber=+1.1234567890, faxNumber=+1.1234567891, types=[TECH],"
|
||||
+ " visibleInWhoisAsAdmin=false,"
|
||||
+ " visibleInWhoisAsTech=true, visibleInDomainWhoisAsAbuse=false,"
|
||||
+ " allowedToSetRegistryLockPassword=false}\n"
|
||||
+ " FINAL CONTENTS:\n"
|
||||
+ " {id="
|
||||
+ id
|
||||
+ ", name=Test Registrar 2,"
|
||||
+ " emailAddress=incorrect@example.com, registrarId=registrarId,"
|
||||
+ " registryLockEmailAddress=null, phoneNumber=+1.1234567890,"
|
||||
+ " faxNumber=+1.1234567891, types=[TECH],"
|
||||
+ " visibleInWhoisAsAdmin=false, visibleInWhoisAsTech=true,"
|
||||
+ " visibleInDomainWhoisAsAbuse=false,"
|
||||
+ " allowedToSetRegistryLockPassword=false}\n")
|
||||
"""
|
||||
The following changes were made in registry unittest environment to the registrar registrarId \
|
||||
by admin fte@email.tld:
|
||||
|
||||
contacts:
|
||||
ADDED:
|
||||
{id=5, name=Test Registrar 2, emailAddress=incorrect@example.com, registrarId=registrarId, \
|
||||
phoneNumber=+1.1234567890, faxNumber=+1.1234567891, types=[TECH], visibleInWhoisAsAdmin=false, \
|
||||
visibleInWhoisAsTech=true, visibleInDomainWhoisAsAbuse=false, \
|
||||
allowedToSetRegistryLockPassword=false}
|
||||
REMOVED:
|
||||
{id=5, name=Test Registrar 2, emailAddress=test.registrar2@example.com, \
|
||||
registrarId=registrarId, phoneNumber=+1.1234567890, faxNumber=+1.1234567891, types=[TECH], \
|
||||
visibleInWhoisAsAdmin=false, visibleInWhoisAsTech=true, visibleInDomainWhoisAsAbuse=false, \
|
||||
allowedToSetRegistryLockPassword=false}
|
||||
FINAL CONTENTS:
|
||||
{id=5, name=Test Registrar 2, emailAddress=incorrect@example.com, registrarId=registrarId, \
|
||||
phoneNumber=+1.1234567890, faxNumber=+1.1234567891, types=[TECH], visibleInWhoisAsAdmin=false, \
|
||||
visibleInWhoisAsTech=true, visibleInDomainWhoisAsAbuse=false, \
|
||||
allowedToSetRegistryLockPassword=false}
|
||||
""")
|
||||
.setRecipients(ImmutableList.of(new InternetAddress("notification@test.example")))
|
||||
.build());
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -194,3 +194,4 @@ V193__password_reset_request.sql
|
||||
V194__password_reset_request_registrar.sql
|
||||
V195__registrar_poc_id.sql
|
||||
V196__tld_expiry_access_period_enabled.sql
|
||||
V197__poc_rlock_drop_not_null.sql
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
-- Copyright 2025 The Nomulus Authors. All Rights Reserved.
|
||||
--
|
||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
-- you may not use this file except in compliance with the License.
|
||||
-- You may obtain a copy of the License at
|
||||
--
|
||||
-- http://www.apache.org/licenses/LICENSE-2.0
|
||||
--
|
||||
-- Unless required by applicable law or agreed to in writing, software
|
||||
-- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
-- See the License for the specific language governing permissions and
|
||||
-- limitations under the License.
|
||||
|
||||
-- In order to remove the field from the Java class it needs to be nullable
|
||||
ALTER table "RegistrarPoc" ALTER column allowed_to_set_registry_lock_password DROP NOT NULL;
|
||||
@@ -688,9 +688,6 @@
|
||||
id bigint,
|
||||
name text,
|
||||
phone_number text,
|
||||
registry_lock_email_address text,
|
||||
registry_lock_password_hash text,
|
||||
registry_lock_password_salt text,
|
||||
types text[],
|
||||
visible_in_domain_whois_as_abuse boolean not null,
|
||||
visible_in_whois_as_admin boolean not null,
|
||||
|
||||
@@ -1009,7 +1009,7 @@ CREATE TABLE public."Registrar" (
|
||||
|
||||
CREATE TABLE public."RegistrarPoc" (
|
||||
email_address text NOT NULL,
|
||||
allowed_to_set_registry_lock_password boolean NOT NULL,
|
||||
allowed_to_set_registry_lock_password boolean,
|
||||
fax_number text,
|
||||
name text,
|
||||
phone_number text,
|
||||
|
||||
@@ -379,9 +379,9 @@
|
||||
"type": "string",
|
||||
"description": "Name of the domain."
|
||||
},
|
||||
"registrarPocId": {
|
||||
"registryLockEmail": {
|
||||
"type": "string",
|
||||
"description": "Registrar point of contact ID."
|
||||
"description": "Email address of the requester."
|
||||
},
|
||||
"lockRequestTime": {
|
||||
"type": "object",
|
||||
|
||||
@@ -15,7 +15,6 @@ spec:
|
||||
spec:
|
||||
serviceAccountName: nomulus
|
||||
nodeSelector:
|
||||
cloud.google.com/compute-class: "Performance"
|
||||
cloud.google.com/machine-family: c4
|
||||
containers:
|
||||
- name: backend
|
||||
@@ -25,9 +24,15 @@ spec:
|
||||
name: http
|
||||
resources:
|
||||
requests:
|
||||
# explicit pod-slots 0 is required in order to downgrade node
|
||||
# class from performance, which has implicit pod-slots 1
|
||||
cloud.google.com/pod-slots: 0
|
||||
cpu: "500m"
|
||||
memory: "1Gi"
|
||||
limits:
|
||||
# explicit pod-slots 0 is required in order to downgrade node
|
||||
# class from performance, which has implicit pod-slots 1
|
||||
cloud.google.com/pod-slots: 0
|
||||
cpu: "1000m"
|
||||
memory: "1.5Gi"
|
||||
args: [ENVIRONMENT]
|
||||
|
||||
@@ -15,7 +15,6 @@ spec:
|
||||
spec:
|
||||
serviceAccountName: nomulus
|
||||
nodeSelector:
|
||||
cloud.google.com/compute-class: "Performance"
|
||||
cloud.google.com/machine-family: c4
|
||||
containers:
|
||||
- name: frontend
|
||||
@@ -25,9 +24,15 @@ spec:
|
||||
name: http
|
||||
resources:
|
||||
requests:
|
||||
# explicit pod-slots 0 is required in order to downgrade node
|
||||
# class from performance, which has implicit pod-slots 1
|
||||
cloud.google.com/pod-slots: 0
|
||||
cpu: "600m"
|
||||
memory: "1Gi"
|
||||
limits:
|
||||
# explicit pod-slots 0 is required in order to downgrade node
|
||||
# class from performance, which has implicit pod-slots 1
|
||||
cloud.google.com/pod-slots: 0
|
||||
cpu: "1000m"
|
||||
memory: "2Gi"
|
||||
args: [ENVIRONMENT]
|
||||
@@ -53,9 +58,15 @@ spec:
|
||||
name: epp
|
||||
resources:
|
||||
requests:
|
||||
# explicit pod-slots 0 is required in order to downgrade node
|
||||
# class from performance, which has implicit pod-slots 1
|
||||
cloud.google.com/pod-slots: 0
|
||||
cpu: "600m"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
# explicit pod-slots 0 is required in order to downgrade node
|
||||
# class from performance, which has implicit pod-slots 1
|
||||
cloud.google.com/pod-slots: 0
|
||||
cpu: "1000m"
|
||||
memory: "1Gi"
|
||||
args: [--env, PROXY_ENV, --log, --local]
|
||||
@@ -88,7 +99,7 @@ spec:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: frontend
|
||||
minReplicas: 8
|
||||
minReplicas: 12
|
||||
maxReplicas: 16
|
||||
metrics:
|
||||
- type: Resource
|
||||
|
||||
@@ -119,6 +119,7 @@ public class SslClientInitializer<C extends Channel> extends ChannelInitializer<
|
||||
sslContextBuilder
|
||||
.build()
|
||||
.newHandler(channel.alloc(), hostProvider.apply(channel), portProvider.apply(channel));
|
||||
sslHandler.setHandshakeTimeoutMillis(20000);
|
||||
|
||||
// Enable hostname verification.
|
||||
SSLEngine sslEngine = sslHandler.engine();
|
||||
|
||||
+11
-9
@@ -139,6 +139,8 @@ public class SslServerInitializer<C extends Channel> extends ChannelInitializer<
|
||||
|
||||
logger.atInfo().log("Available Cipher Suites: %s", sslContext.cipherSuites());
|
||||
SslHandler sslHandler = sslContext.newHandler(channel.alloc());
|
||||
sslHandler.setHandshakeTimeoutMillis(20000);
|
||||
|
||||
if (requireClientCert) {
|
||||
Promise<X509Certificate> clientCertificatePromise = channel.eventLoop().newPromise();
|
||||
Future<Channel> unusedFuture =
|
||||
@@ -159,15 +161,15 @@ public class SslServerInitializer<C extends Channel> extends ChannelInitializer<
|
||||
}
|
||||
logger.atInfo().log(
|
||||
"""
|
||||
--SSL Information--
|
||||
Client Certificate Hash: %s
|
||||
SSL Protocol: %s
|
||||
Cipher Suite: %s
|
||||
Not Before: %s
|
||||
Not After: %s
|
||||
Client Certificate Type: %s
|
||||
Client Certificate Length: %s
|
||||
""",
|
||||
--SSL Information--
|
||||
Client Certificate Hash: %s
|
||||
SSL Protocol: %s
|
||||
Cipher Suite: %s
|
||||
Not Before: %s
|
||||
Not After: %s
|
||||
Client Certificate Type: %s
|
||||
Client Certificate Length: %s
|
||||
""",
|
||||
getCertificateHash(clientCertificate),
|
||||
sslSession.getProtocol(),
|
||||
sslSession.getCipherSuite(),
|
||||
|
||||
@@ -33,7 +33,6 @@ do
|
||||
--project "${project}" --zone "${parts[1]}"
|
||||
sed s/GCP_PROJECT/${project}/g "./kubernetes/proxy-deployment-${environment}.yaml" | \
|
||||
kubectl apply -f -
|
||||
kubectl apply -f "./kubernetes/proxy-limit-range.yaml" --force
|
||||
kubectl apply -f "./kubernetes/proxy-service.yaml" --force
|
||||
# Alpha does not have canary
|
||||
if [[ ${environment} != "alpha" ]]; then
|
||||
|
||||
@@ -33,6 +33,13 @@ spec:
|
||||
port: health-check
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
resources:
|
||||
requests:
|
||||
cpu: "400m"
|
||||
memory: "350Mi"
|
||||
limits:
|
||||
cpu: "600m"
|
||||
memory: "512Mi"
|
||||
imagePullPolicy: Always
|
||||
args: ["--env", "production_canary"]
|
||||
env:
|
||||
|
||||
@@ -33,6 +33,13 @@ spec:
|
||||
port: health-check
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
resources:
|
||||
requests:
|
||||
cpu: "400m"
|
||||
memory: "350Mi"
|
||||
limits:
|
||||
cpu: "600m"
|
||||
memory: "512Mi"
|
||||
imagePullPolicy: Always
|
||||
args: ["--env", "production"]
|
||||
env:
|
||||
|
||||
@@ -33,6 +33,13 @@ spec:
|
||||
port: health-check
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
resources:
|
||||
requests:
|
||||
cpu: "400m"
|
||||
memory: "350Mi"
|
||||
limits:
|
||||
cpu: "600m"
|
||||
memory: "512Mi"
|
||||
imagePullPolicy: Always
|
||||
args: ["--env", "sandbox_canary", "--log"]
|
||||
env:
|
||||
|
||||
@@ -33,6 +33,13 @@ spec:
|
||||
port: health-check
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
resources:
|
||||
requests:
|
||||
cpu: "400m"
|
||||
memory: "350Mi"
|
||||
limits:
|
||||
cpu: "600m"
|
||||
memory: "512Mi"
|
||||
imagePullPolicy: Always
|
||||
args: ["--env", "sandbox", "--log"]
|
||||
env:
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: LimitRange
|
||||
metadata:
|
||||
name: resource-limits
|
||||
namespace: default
|
||||
spec:
|
||||
limits:
|
||||
- type: Container
|
||||
default:
|
||||
cpu: "300m"
|
||||
memory: "512Mi"
|
||||
defaultRequest:
|
||||
cpu: "100m"
|
||||
memory: "350Mi"
|
||||
Reference in New Issue
Block a user