1
0
mirror of https://github.com/google/nomulus synced 2026-05-20 14:51:48 +00:00

Compare commits

...

5 Commits

Author SHA1 Message Date
Ben McIlwain
4b6ade0b14 Bring codebase up to more recent Java standards (#2422)
This includes using the new switch format (though IntelliJ does not yet
understand patterns including default so those aren't used), multiline strings,
replacing some unnecessary type declarations with <>, converting some classes to
records, replacing some Guava predicates with native Java code, and some other
miscellaneous Code Inspection fixes.
2024-05-01 20:48:38 +00:00
Pavlo Tkach
570618705e Allow console access for FTE globar role (#2419) 2024-05-01 16:19:29 +00:00
sarahcaseybot
e791608098 Add more indexes to speed up deleteProberDataAction (#2423)
This adds an index on transfer_billing_cancellation_id to Domain and superordinate_domain to Host. When tested on crash with the action limited to only delete 10,000 domains, before these indexes were added the action took about 2 hours to delete 10,000 domains. Once these indexes were added, the action was able to delete the 10,000 domains in a little under 2 minutes.
2024-05-01 15:44:08 +00:00
gbrodman
03b358726a Add Java classes for console history objects (#2350)
This also creates base classes for the objects contained within the
history classes, e.g. RegistrarBase. This is the same way that objects
stored in the HistoryEntry subclasses have base classes, e.g.
DomainBase.
2024-04-30 20:42:40 +00:00
gbrodman
d121f8f547 Generate fake XSRF token in FakeConsoleApiParams for tests (#2421) 2024-04-30 17:47:53 +00:00
244 changed files with 4654 additions and 3505 deletions

View File

@@ -175,14 +175,7 @@ public class TextDiffSubject extends Subject {
.orElse(0);
}
private static class SideBySideRowFormatter {
private final int maxExpectedLineLength;
private final int maxActualLineLength;
private SideBySideRowFormatter(int maxExpectedLineLength, int maxActualLineLength) {
this.maxExpectedLineLength = maxExpectedLineLength;
this.maxActualLineLength = maxActualLineLength;
}
private record SideBySideRowFormatter(int maxExpectedLineLength, int maxActualLineLength) {
public String formatRow(String expected, String actual, char padChar) {
return String.format(

View File

@@ -36,6 +36,7 @@ yarn-error.log
/libpeerconnection.log
testem.log
/typings
.nx/
# System files
.DS_Store

View File

@@ -33,6 +33,11 @@ export class BackendService {
error: HttpErrorResponse,
mockData?: Type
): Observable<Type> {
// This is a temporary redirect to the old console untill the new console
// is fully released and enabled
if (error.url && window.location.href.indexOf(error.url) < 0) {
window.location.href = error.url;
}
if (error.error instanceof Error) {
// A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', error.error.message);

View File

@@ -187,18 +187,12 @@ public class CheckBulkComplianceAction implements Runnable {
.getLastNotificationSent()
.map(sentDate -> Days.daysBetween(sentDate, clock.nowUtc()).getDays())
.orElse(Integer.MAX_VALUE);
if (daysSinceLastNotification < THIRTY_DAYS) {
// Don't send an email if notification was already sent within the last 30
// days
continue;
} else if (daysSinceLastNotification < FORTY_DAYS) {
// Send an upgrade email if last email was between 30 and 40 days ago
// Send a warning email if 30-39 days since last notification and an upgrade email if 40+ days
if (daysSinceLastNotification >= THIRTY_DAYS) {
sendActiveDomainOverageEmail(
/* warning= */ false, bulkPricingPackage, overageList.get(bulkPricingPackage));
} else {
// Send a warning email
sendActiveDomainOverageEmail(
/* warning= */ true, bulkPricingPackage, overageList.get(bulkPricingPackage));
/* warning= */ daysSinceLastNotification >= FORTY_DAYS,
bulkPricingPackage,
overageList.get(bulkPricingPackage));
}
}
}

View File

@@ -66,11 +66,15 @@ public class RelockDomainAction implements Runnable {
private static final Duration ONE_HOUR = Duration.standardHours(1);
private static final String RELOCK_SUCCESS_EMAIL_TEMPLATE =
"The domain %s was successfully re-locked.\n\nPlease contact support at %s if you have any "
+ "questions.";
"""
The domain %s was successfully re-locked.
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\n\nPlease contact "
+ "support at %s if you have any questions.";
"""
There was an error when automatically re-locking %s. Error message: %s
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";

View File

@@ -33,7 +33,7 @@ import google.registry.flows.certs.CertificateChecker;
import google.registry.groups.GmailClient;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.registrar.RegistrarPoc.Type;
import google.registry.model.registrar.RegistrarPocBase.Type;
import google.registry.request.Action;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
@@ -206,7 +206,7 @@ public class SendExpiringCertificateNotificationEmailAction implements Runnable
() -> {
Registrar.Builder newRegistrar = tm().loadByEntity(registrar).asBuilder();
switch (certificateType) {
case PRIMARY:
case PRIMARY -> {
newRegistrar.setLastExpiringCertNotificationSentDate(now);
tm().put(newRegistrar.build());
logger.atInfo().log(
@@ -215,8 +215,8 @@ public class SendExpiringCertificateNotificationEmailAction implements Runnable
DATE_FORMATTER.print(now),
certificateType.getDisplayName(),
registrar.getRegistrarName());
break;
case FAILOVER:
}
case FAILOVER -> {
newRegistrar.setLastExpiringFailoverCertNotificationSentDate(now);
tm().put(newRegistrar.build());
logger.atInfo().log(
@@ -225,13 +225,13 @@ public class SendExpiringCertificateNotificationEmailAction implements Runnable
DATE_FORMATTER.print(now),
certificateType.getDisplayName(),
registrar.getRegistrarName());
break;
default:
throw new IllegalArgumentException(
String.format(
"Unsupported certificate type: %s being passed in when updating "
+ "the last notification sent date to registrar %s.",
certificateType.toString(), registrar.getRegistrarName()));
}
default ->
throw new IllegalArgumentException(
String.format(
"Unsupported certificate type: %s being passed in when updating "
+ "the last notification sent date to registrar %s.",
certificateType.toString(), registrar.getRegistrarName()));
}
});
} catch (Exception e) {

View File

@@ -50,16 +50,17 @@ public class RegistryPipelineWorkerInitializer implements JvmInitializer {
environment.setup();
RegistryPipelineComponent registryPipelineComponent =
toRegistryPipelineComponent(registryOptions);
Lazy<JpaTransactionManager> transactionManagerLazy;
switch (registryOptions.getJpaTransactionManagerType()) {
case READ_ONLY_REPLICA:
transactionManagerLazy =
registryPipelineComponent.getReadOnlyReplicaJpaTransactionManager();
break;
case REGULAR:
default:
transactionManagerLazy = registryPipelineComponent.getJpaTransactionManager();
}
Lazy<JpaTransactionManager> transactionManagerLazy =
switch (registryOptions.getJpaTransactionManagerType()) {
case READ_ONLY_REPLICA ->
registryPipelineComponent.getReadOnlyReplicaJpaTransactionManager();
case REGULAR -> registryPipelineComponent.getJpaTransactionManager();
default ->
throw new IllegalStateException(
String.format(
"Unknown JPA transaction manager type: %s",
registryOptions.getJpaTransactionManagerType()));
};
TransactionManagerFactory.setJpaTmOnBeamWorker(transactionManagerLazy::get);
SystemPropertySetter.PRODUCTION_IMPL.setProperty(PROPERTY, "true");
}

View File

@@ -58,7 +58,7 @@ import google.registry.model.host.Host;
import google.registry.model.host.HostHistory;
import google.registry.model.rde.RdeMode;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.Type;
import google.registry.model.registrar.RegistrarBase.Type;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
@@ -688,27 +688,26 @@ public class RdePipeline implements Serializable {
protected abstract static class TupleTags {
protected static final TupleTag<KV<PendingDeposit, DepositFragment>> DOMAIN_FRAGMENTS =
new TupleTag<KV<PendingDeposit, DepositFragment>>() {};
new TupleTag<>() {};
protected static final TupleTag<KV<String, PendingDeposit>> REFERENCED_CONTACTS =
new TupleTag<KV<String, PendingDeposit>>() {};
new TupleTag<>() {};
protected static final TupleTag<KV<String, PendingDeposit>> REFERENCED_HOSTS =
new TupleTag<KV<String, PendingDeposit>>() {};
new TupleTag<>() {};
protected static final TupleTag<KV<String, KV<String, CoGbkResult>>> SUPERORDINATE_DOMAINS =
new TupleTag<KV<String, KV<String, CoGbkResult>>>() {};
new TupleTag<>() {};
protected static final TupleTag<KV<PendingDeposit, DepositFragment>> EXTERNAL_HOST_FRAGMENTS =
new TupleTag<KV<PendingDeposit, DepositFragment>>() {};
new TupleTag<>() {};
protected static final TupleTag<PendingDeposit> PENDING_DEPOSIT =
new TupleTag<PendingDeposit>() {};
protected static final TupleTag<PendingDeposit> PENDING_DEPOSIT = new TupleTag<>() {};
protected static final TupleTag<KV<String, CoGbkResult>> HOST_TO_PENDING_DEPOSIT =
new TupleTag<KV<String, CoGbkResult>>() {};
new TupleTag<>() {};
protected static final TupleTag<Long> REVISION_ID = new TupleTag<Long>() {};
protected static final TupleTag<Long> REVISION_ID = new TupleTag<>() {};
}
@Singleton

View File

@@ -62,7 +62,7 @@ class BsaDiffCreator {
this.gcsClient = gcsClient;
}
private <K, V extends Comparable> Multimap<K, V> listBackedMultiMap() {
private <K, V extends Comparable<?>> Multimap<K, V> listBackedMultiMap() {
return newListMultimap(newHashMap(), Lists::newArrayList);
}

View File

@@ -80,7 +80,7 @@ public class IdnChecker {
}
private static ImmutableMap<IdnTableEnum, ImmutableSet<Tld>> getIdnToTldMap(DateTime now) {
ImmutableMultimap.Builder<IdnTableEnum, Tld> idnToTldMap = new ImmutableMultimap.Builder();
var idnToTldMap = new ImmutableMultimap.Builder<IdnTableEnum, Tld>();
Tlds.getTldEntitiesOfType(TldType.REAL).stream()
.filter(tld -> isEnrolledWithBsa(tld, now))
.forEach(

View File

@@ -34,7 +34,7 @@ public record BlockOrder(long orderId, OrderType orderType) {
public static BlockOrder deserialize(String text) {
List<String> items = SPLITTER.splitToList(text);
try {
return create(Long.valueOf(items.get(0)), OrderType.valueOf(items.get(1)));
return create(Long.parseLong(items.get(0)), OrderType.valueOf(items.get(1)));
} catch (NumberFormatException ne) {
throw new IllegalArgumentException(text);
}

View File

@@ -96,10 +96,9 @@ class BsaDomainRefresh {
if (this == o) {
return true;
}
if (!(o instanceof BsaDomainRefresh)) {
if (!(o instanceof BsaDomainRefresh that)) {
return false;
}
BsaDomainRefresh that = (BsaDomainRefresh) o;
return Objects.equal(jobId, that.jobId)
&& Objects.equal(creationTime, that.creationTime)
&& Objects.equal(updateTime, that.updateTime)

View File

@@ -120,10 +120,9 @@ class BsaDownload {
if (this == o) {
return true;
}
if (!(o instanceof BsaDownload)) {
if (!(o instanceof BsaDownload that)) {
return false;
}
BsaDownload that = (BsaDownload) o;
return Objects.equal(creationTime, that.creationTime)
&& Objects.equal(updateTime, that.updateTime)
&& Objects.equal(blockListChecksums, that.blockListChecksums)
@@ -136,6 +135,6 @@ class BsaDownload {
}
static VKey<BsaDownload> vKey(long jobId) {
return VKey.create(BsaDownload.class, Long.valueOf(jobId));
return VKey.create(BsaDownload.class, jobId);
}
}

View File

@@ -61,10 +61,9 @@ final class BsaLabel {
if (this == o) {
return true;
}
if (!(o instanceof BsaLabel)) {
if (!(o instanceof BsaLabel label1)) {
return false;
}
BsaLabel label1 = (BsaLabel) o;
return Objects.equal(label, label1.label) && Objects.equal(creationTime, label1.creationTime);
}

View File

@@ -84,10 +84,9 @@ class BsaUnblockableDomain {
if (this == o) {
return true;
}
if (!(o instanceof BsaUnblockableDomain)) {
if (!(o instanceof BsaUnblockableDomain that)) {
return false;
}
BsaUnblockableDomain that = (BsaUnblockableDomain) o;
return Objects.equal(label, that.label)
&& Objects.equal(tld, that.tld)
&& reason == that.reason
@@ -142,10 +141,9 @@ class BsaUnblockableDomain {
if (this == o) {
return true;
}
if (!(o instanceof BsaUnblockableDomainId)) {
if (!(o instanceof BsaUnblockableDomainId that)) {
return false;
}
BsaUnblockableDomainId that = (BsaUnblockableDomainId) o;
return Objects.equal(label, that.label) && Objects.equal(tld, that.tld);
}

View File

@@ -72,7 +72,7 @@ public final class LabelDiffUpdates {
for (Map.Entry<LabelType, ImmutableList<BlockLabel>> entry :
labelsByType.entrySet()) {
switch (entry.getKey()) {
case CREATE:
case CREATE -> {
// With current Cloud SQL, label upsert throughput is about 200/second. If
// better performance is needed, consider bulk insert in native SQL.
tm().putAll(
@@ -86,8 +86,8 @@ public final class LabelDiffUpdates {
// cached BsaLabels. Eventually will be consistent.
nonBlockedDomains.addAll(
tallyUnblockableDomainsForNewLabels(entry.getValue(), idnChecker, now));
break;
case DELETE:
}
case DELETE -> {
ImmutableSet<String> deletedLabels =
entry.getValue().stream()
.filter(label -> isValidInAtLeastOneTld(label, idnChecker))
@@ -100,8 +100,8 @@ public final class LabelDiffUpdates {
"Only found %s entities among the %s labels: [%s]",
nDeleted, deletedLabels.size(), deletedLabels);
}
break;
case NEW_ORDER_ASSOCIATION:
}
case NEW_ORDER_ASSOCIATION -> {
ImmutableSet<String> affectedLabels =
entry.getValue().stream()
.filter(label -> isValidInAtLeastOneTld(label, idnChecker))
@@ -120,13 +120,12 @@ public final class LabelDiffUpdates {
Queries.queryBsaUnblockableDomainByLabels(affectedLabels)
.map(BsaUnblockableDomain::toUnblockableDomain)
.forEach(nonBlockedDomains::add);
for (BlockLabel label : entry.getValue()) {
getInvalidTldsForLabel(label, idnChecker)
.map(tld -> UnblockableDomain.of(label.label(), tld, Reason.INVALID))
.forEach(nonBlockedDomains::add);
}
break;
}
}
}
});

View File

@@ -103,7 +103,7 @@ public final class Queries {
static ImmutableList<BsaUnblockableDomain> batchReadUnblockables(
Optional<BsaUnblockableDomain> lastRead, int batchSize) {
return ImmutableList.copyOf(
return ImmutableList.<BsaUnblockableDomain>copyOf(
bsaQuery(
() ->
tm().getEntityManager()

View File

@@ -38,7 +38,7 @@ public final class DnsWriterProxy {
/**
* Returns the instance of {@link DnsWriter} by class name.
*
* If the DnsWriter doesn't belong to this TLD, will return null.
* <p>If the DnsWriter doesn't belong to this TLD, will return null.
*/
public DnsWriter getByClassNameForTld(String className, String tld) {
if (!Tld.get(tld).getDnsWriters().contains(className)) {

View File

@@ -50,6 +50,7 @@ import google.registry.model.domain.Domain;
import google.registry.model.host.Host;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.registrar.RegistrarPocBase;
import google.registry.model.tld.Tld;
import google.registry.request.Action;
import google.registry.request.Action.Service;
@@ -295,7 +296,7 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
ImmutableList<InternetAddress> recipients =
registrar.get().getContacts().stream()
.filter(c -> c.getTypes().contains(RegistrarPoc.Type.ADMIN))
.filter(c -> c.getTypes().contains(RegistrarPocBase.Type.ADMIN))
.map(RegistrarPoc::getEmailAddress)
.map(PublishDnsUpdatesAction::emailToInternetAddress)
.collect(toImmutableList());

View File

@@ -63,16 +63,15 @@ public final class RefreshDnsAction implements Runnable {
tm().transact(
() -> {
switch (type) {
case DOMAIN:
case DOMAIN -> {
loadAndVerifyExistence(Domain.class, domainOrHostName);
requestDomainDnsRefresh(domainOrHostName);
break;
case HOST:
}
case HOST -> {
verifyHostIsSubordinate(loadAndVerifyExistence(Host.class, domainOrHostName));
requestHostDnsRefresh(domainOrHostName);
break;
default:
throw new BadRequestException("Unsupported type: " + type);
}
default -> throw new BadRequestException("Unsupported type: " + type);
}
});
}

View File

@@ -55,14 +55,14 @@ import org.xbill.DNS.Type;
import org.xbill.DNS.Update;
/**
* A DnsWriter that implements the DNS UPDATE protocol as specified in
* <a href="https://tools.ietf.org/html/rfc2136">RFC 2136</a>. Publishes changes in the
* domain-registry to a (capable) external DNS server, sometimes called a "hidden master". DNS
* UPDATE messages are sent via a supplied "transport" class.
* A DnsWriter that implements the DNS UPDATE protocol as specified in <a
* href="https://tools.ietf.org/html/rfc2136">RFC 2136</a>. Publishes changes in the domain-registry
* to a (capable) external DNS server, sometimes called a "hidden master". DNS UPDATE messages are
* sent via a supplied "transport" class.
*
* On call to {@link #commit()}, a single UPDATE message is created containing the records required
* to "synchronize" the DNS with the current (at the time of processing) state of the registry, for
* the supplied domain/host.
* <p>On call to {@link #commit()}, a single UPDATE message is created containing the records
* required to "synchronize" the DNS with the current (at the time of processing) state of the
* registry, for the supplied domain/host.
*
* <p>The general strategy of the publish methods is to delete <em>all</em> resource records of any
* <em>type</em> that match the exact domain/host name supplied. And then for create/update cases,
@@ -73,8 +73,8 @@ import org.xbill.DNS.Update;
* <p>Only NS, DS, A, and AAAA records are published, and in particular no DNSSEC signing is done
* assuming that this will be done by a third party DNS provider.
*
* <p>Each commit call is treated as an atomic update to the DNS. If a commit fails an exception
* is thrown. The SOA record serial number is implicitly incremented by the server on each UPDATE
* <p>Each commit call is treated as an atomic update to the DNS. If a commit fails an exception is
* thrown. The SOA record serial number is implicitly incremented by the server on each UPDATE
* message, as required by RFC 2136. Care must be taken to make sure the SOA serial number does not
* go backwards if the entire TLD (zone) is "reset" to empty and republished.
*/

View File

@@ -33,6 +33,7 @@ import google.registry.groups.GroupsConnection;
import google.registry.groups.GroupsConnection.Role;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.registrar.RegistrarPocBase;
import google.registry.request.Action;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
@@ -99,7 +100,7 @@ public final class SyncGroupMembersAction implements Runnable {
* Returns the Google Groups email address for the given registrar ID and RegistrarContact.Type.
*/
public static String getGroupEmailAddressForContactType(
String registrarId, RegistrarPoc.Type type, String gSuiteDomainName) {
String registrarId, RegistrarPocBase.Type type, String gSuiteDomainName) {
// Take the registrar's ID, make it lowercase, and remove all characters that aren't
// alphanumeric, hyphens, or underscores.
return String.format(
@@ -174,7 +175,7 @@ public final class SyncGroupMembersAction implements Runnable {
Set<RegistrarPoc> registrarPocs = registrar.getContacts();
long totalAdded = 0;
long totalRemoved = 0;
for (final RegistrarPoc.Type type : RegistrarPoc.Type.values()) {
for (final RegistrarPocBase.Type type : RegistrarPocBase.Type.values()) {
groupKey =
getGroupEmailAddressForContactType(registrar.getRegistrarId(), type, gSuiteDomainName);
Set<String> currentMembers = groupsConnection.getMembersOfGroup(groupKey);

View File

@@ -17,13 +17,13 @@ package google.registry.export.sheet;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.model.common.Cursor.CursorType.SYNC_REGISTRAR_SHEET;
import static google.registry.model.registrar.RegistrarPoc.Type.ABUSE;
import static google.registry.model.registrar.RegistrarPoc.Type.ADMIN;
import static google.registry.model.registrar.RegistrarPoc.Type.BILLING;
import static google.registry.model.registrar.RegistrarPoc.Type.LEGAL;
import static google.registry.model.registrar.RegistrarPoc.Type.MARKETING;
import static google.registry.model.registrar.RegistrarPoc.Type.TECH;
import static google.registry.model.registrar.RegistrarPoc.Type.WHOIS;
import static google.registry.model.registrar.RegistrarPocBase.Type.ABUSE;
import static google.registry.model.registrar.RegistrarPocBase.Type.ADMIN;
import static google.registry.model.registrar.RegistrarPocBase.Type.BILLING;
import static google.registry.model.registrar.RegistrarPocBase.Type.LEGAL;
import static google.registry.model.registrar.RegistrarPocBase.Type.MARKETING;
import static google.registry.model.registrar.RegistrarPocBase.Type.TECH;
import static google.registry.model.registrar.RegistrarPocBase.Type.WHOIS;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
@@ -36,6 +36,7 @@ import google.registry.model.common.Cursor;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.registrar.RegistrarPocBase;
import google.registry.util.Clock;
import google.registry.util.DateTimeUtils;
import java.io.IOException;
@@ -173,7 +174,7 @@ class SyncRegistrarsSheet {
return result.toString();
}
private static Predicate<RegistrarPoc> byType(final RegistrarPoc.Type type) {
private static Predicate<RegistrarPoc> byType(final RegistrarPocBase.Type type) {
return contact -> contact.getTypes().contains(type);
}

View File

@@ -22,9 +22,9 @@ public interface TransportCredentials {
/**
* Check that these credentials are valid for the registrar and optionally check the password.
*
* Called by {@link google.registry.flows.session.LoginFlow LoginFlow} to check the transport
* credentials against the stored registrar's credentials. If they do not match, throw an
* {@link AuthenticationErrorException}.
* <p>Called by {@link google.registry.flows.session.LoginFlow LoginFlow} to check the transport
* credentials against the stored registrar's credentials. If they do not match, throw an {@link
* AuthenticationErrorException}.
*/
void validate(Registrar registrar, String password) throws AuthenticationErrorException;

View File

@@ -16,12 +16,10 @@ package google.registry.flows.domain;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.intersection;
import static com.google.common.collect.Sets.union;
@@ -95,6 +93,7 @@ import google.registry.model.domain.DomainCommand.Update;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.ForeignKeyedDesignatedContact;
import google.registry.model.domain.Period;
import google.registry.model.domain.Period.Unit;
import google.registry.model.domain.fee.BaseFee;
import google.registry.model.domain.fee.BaseFee.FeeType;
import google.registry.model.domain.fee.Credit;
@@ -121,9 +120,9 @@ import google.registry.model.domain.token.AllocationToken.RegistrationBehavior;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
import google.registry.model.host.Host;
import google.registry.model.poll.PollMessage;
import google.registry.model.poll.PollMessage.Autorenew;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.State;
import google.registry.model.registrar.RegistrarBase.State;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
@@ -206,7 +205,7 @@ public class DomainFlowUtils {
if (parts.size() <= 1) {
throw new BadDomainNamePartsCountException();
}
if (any(parts, equalTo(""))) {
if (parts.stream().anyMatch(String::isEmpty)) {
throw new EmptyDomainNamePartException();
}
validateFirstLabel(parts.get(0));
@@ -329,7 +328,7 @@ public class DomainFlowUtils {
/** Check if the registrar running the flow has access to the TLD in question. */
public static void checkAllowedAccessToTld(String registrarId, String tld) throws EppException {
if (!Registrar.loadByRegistrarIdCached(registrarId).get().getAllowedTlds().contains(tld)) {
throw new DomainFlowUtils.NotAuthorizedForTldException(tld);
throw new NotAuthorizedForTldException(tld);
}
}
@@ -344,7 +343,7 @@ public class DomainFlowUtils {
.get()
.getBillingAccountMap()
.containsKey(tld.getCurrency())) {
throw new DomainFlowUtils.MissingBillingAccountMapException(tld.getCurrency());
throw new MissingBillingAccountMapException(tld.getCurrency());
}
}
@@ -405,7 +404,7 @@ public class DomainFlowUtils {
/** We only allow specifying years in a period. */
static Period verifyUnitIsYears(Period period) throws EppException {
if (!checkNotNull(period).getUnit().equals(Period.Unit.YEARS)) {
if (!checkNotNull(period).getUnit().equals(Unit.YEARS)) {
throw new BadPeriodUnitException();
}
return period;
@@ -534,7 +533,7 @@ public class DomainFlowUtils {
public static boolean isReserved(InternetDomainName domainName, boolean isSunrise) {
ImmutableSet<ReservationType> types = getReservationTypes(domainName);
return !Sets.intersection(types, RESERVED_TYPES).isEmpty()
return !intersection(types, RESERVED_TYPES).isEmpty()
|| !(isSunrise || intersection(TYPES_ALLOWED_FOR_CREATE_ONLY_IN_SUNRISE, types).isEmpty());
}
@@ -601,8 +600,8 @@ public class DomainFlowUtils {
* Fills in a builder with the data needed for an autorenew poll message for this domain. This
* does not copy over the id of the current autorenew poll message.
*/
public static PollMessage.Autorenew.Builder newAutorenewPollMessage(Domain domain) {
return new PollMessage.Autorenew.Builder()
public static Autorenew.Builder newAutorenewPollMessage(Domain domain) {
return new Autorenew.Builder()
.setTargetId(domain.getDomainName())
.setRegistrarId(domain.getCurrentSponsorRegistrarId())
.setEventTime(domain.getRegistrationExpirationTime())
@@ -623,7 +622,7 @@ public class DomainFlowUtils {
BillingRecurrence existingBillingRecurrence,
DateTime newEndTime,
@Nullable HistoryEntryId historyId) {
Optional<PollMessage.Autorenew> autorenewPollMessage =
Optional<Autorenew> autorenewPollMessage =
tm().loadByKeyIfPresent(domain.getAutorenewPollMessage());
// Construct an updated autorenew poll message. If the autorenew poll message no longer exists,
@@ -632,7 +631,7 @@ public class DomainFlowUtils {
// message to be deleted), and then subsequently the transfer was canceled, rejected, or deleted
// (which would cause the poll message to be recreated here). In the latter case, the history id
// of the event that created the new poll message will also be used.
PollMessage.Autorenew updatedAutorenewPollMessage;
Autorenew updatedAutorenewPollMessage;
if (autorenewPollMessage.isPresent()) {
updatedAutorenewPollMessage =
autorenewPollMessage.get().asBuilder().setAutorenewEndTime(newEndTime).build();
@@ -706,7 +705,7 @@ public class DomainFlowUtils {
String feeClass = null;
ImmutableList<Fee> fees = ImmutableList.of();
switch (feeRequest.getCommandName()) {
case CREATE:
case CREATE -> {
// Don't return a create price for reserved names.
if (isReserved(domainName, isSunrise) && !isAvailable) {
feeClass = "reserved";
@@ -726,16 +725,16 @@ public class DomainFlowUtils {
allocationToken)
.getFees();
}
break;
case RENEW:
}
case RENEW -> {
builder.setAvailIfSupported(true);
fees =
pricingLogic
.getRenewPrice(
tld, domainNameString, now, years, billingRecurrence, allocationToken)
.getFees();
break;
case RESTORE:
}
case RESTORE -> {
// The minimum allowable period per the EPP spec is 1, so, strangely, 1 year still has to be
// passed in as the period for a restore even if the domain would *not* be renewed as part
// of a restore. This is fixed in RFC 8748 (which is a more recent version of the fee
@@ -751,21 +750,20 @@ public class DomainFlowUtils {
boolean isExpired =
domain.isPresent() && domain.get().getRegistrationExpirationTime().isBefore(now);
fees = pricingLogic.getRestorePrice(tld, domainNameString, now, isExpired).getFees();
break;
case TRANSFER:
}
case TRANSFER -> {
if (years != 1) {
throw new TransfersAreAlwaysForOneYearException();
}
builder.setAvailIfSupported(true);
fees =
pricingLogic.getTransferPrice(tld, domainNameString, now, billingRecurrence).getFees();
break;
case UPDATE:
}
case UPDATE -> {
builder.setAvailIfSupported(true);
fees = pricingLogic.getUpdatePrice(tld, domainNameString, now).getFees();
break;
default:
throw new UnknownFeeCommandException(feeRequest.getUnparsedCommandName());
}
default -> throw new UnknownFeeCommandException(feeRequest.getUnparsedCommandName());
}
if (feeClass == null) {

View File

@@ -138,14 +138,14 @@ public final class DomainPricingLogic {
isRenewCostPremiumPrice = domainPrices.isPremium();
} else {
switch (billingRecurrence.getRenewalPriceBehavior()) {
case DEFAULT:
case DEFAULT -> {
renewCost =
getDomainRenewCostWithDiscount(tld, domainPrices, dateTime, years, allocationToken);
isRenewCostPremiumPrice = domainPrices.isPremium();
break;
}
// if the renewal price behavior is specified, then the renewal price should be the same
// as the creation price, which is stored in the billing event as the renewal price
case SPECIFIED:
case SPECIFIED -> {
checkArgumentPresent(
billingRecurrence.getRenewalPrice(),
"Unexpected behavior: renewal price cannot be null when renewal behavior is"
@@ -153,20 +153,20 @@ public final class DomainPricingLogic {
// Don't apply allocation token to renewal price when SPECIFIED
renewCost = billingRecurrence.getRenewalPrice().get().multipliedBy(years);
isRenewCostPremiumPrice = false;
break;
}
// if the renewal price behavior is nonpremium, it means that the domain should be renewed
// at standard price of domains at the time, even if the domain is premium
case NONPREMIUM:
case NONPREMIUM -> {
renewCost =
getDomainCostWithDiscount(
false, years, allocationToken, tld.getStandardRenewCost(dateTime));
isRenewCostPremiumPrice = false;
break;
default:
throw new IllegalArgumentException(
String.format(
"Unknown RenewalPriceBehavior enum value: %s",
billingRecurrence.getRenewalPriceBehavior()));
}
default ->
throw new IllegalArgumentException(
String.format(
"Unknown RenewalPriceBehavior enum value: %s",
billingRecurrence.getRenewalPriceBehavior()));
}
}
return customLogic.customizeRenewPrice(

View File

@@ -124,22 +124,21 @@ public class FlowPicker {
}};
/** Poll flows have an {@link InnerCommand} of type {@link Poll}. */
private static final FlowProvider POLL_FLOW_PROVIDER = new FlowProvider() {
@Override
Class<? extends Flow> get(
EppInput eppInput, InnerCommand innerCommand, ResourceCommand resourceCommand) {
if (!(innerCommand instanceof Poll)) {
return null;
}
switch (((Poll) innerCommand).getPollOp()) {
case ACK:
return PollAckFlow.class;
case REQUEST:
return PollRequestFlow.class;
default:
return UnimplementedFlow.class;
}
}};
private static final FlowProvider POLL_FLOW_PROVIDER =
new FlowProvider() {
@Override
Class<? extends Flow> get(
EppInput eppInput, InnerCommand innerCommand, ResourceCommand resourceCommand) {
if (!(innerCommand instanceof Poll)) {
return null;
}
return switch (((Poll) innerCommand).getPollOp()) {
case ACK -> PollAckFlow.class;
case REQUEST -> PollRequestFlow.class;
default -> UnimplementedFlow.class;
};
}
};
/**
* The domain restore command is technically a domain {@literal <update>}, but logically a totally

View File

@@ -54,8 +54,7 @@ public final class PollFlowUtils {
if (pollMessage instanceof PollMessage.OneTime) {
// One-time poll messages are deleted once acked.
tm().delete(pollMessage.createVKey());
} else if (pollMessage instanceof PollMessage.Autorenew) {
PollMessage.Autorenew autorenewPollMessage = (PollMessage.Autorenew) pollMessage;
} else if (pollMessage instanceof PollMessage.Autorenew autorenewPollMessage) {
// Move the eventTime of this autorenew poll message forward by a year.
DateTime nextEventTime = autorenewPollMessage.getEventTime().plusYears(1);

View File

@@ -130,23 +130,22 @@ public final class PgpHelper {
while (keys.hasNext()) {
PGPPublicKey key = keys.next();
switch (want) {
case ENCRYPT:
case ENCRYPT -> {
if (key.isEncryptionKey()) {
return Optional.of(key);
}
break;
case SIGN:
}
case SIGN -> {
if (isSigningKey(key)) {
return Optional.of(key);
}
break;
case ENCRYPT_SIGN:
}
case ENCRYPT_SIGN -> {
if (key.isEncryptionKey() && isSigningKey(key)) {
return Optional.of(key);
}
break;
default:
throw new AssertionError();
}
default -> throw new AssertionError();
}
}
return Optional.empty();
@@ -154,14 +153,9 @@ public final class PgpHelper {
/** Returns {@code true} if this key can be used for signing. */
public static boolean isSigningKey(PGPPublicKey key) {
switch (key.getAlgorithm()) {
case RSA_GENERAL:
case RSA_SIGN:
case DSA:
case ELGAMAL_GENERAL:
return true;
default:
return false;
}
return switch (key.getAlgorithm()) {
case RSA_GENERAL, RSA_SIGN, DSA, ELGAMAL_GENERAL -> true;
default -> false;
};
}
}

View File

@@ -109,16 +109,18 @@ public class ModelUtils {
if (value != null && value.getClass().isArray()) {
// It's surprisingly difficult to convert arrays into lists if the array might be primitive.
final Object arrayValue = value;
value = new AbstractList<Object>() {
@Override
public Object get(int index) {
return Array.get(arrayValue, index);
}
value =
new AbstractList<>() {
@Override
public Object get(int index) {
return Array.get(arrayValue, index);
}
@Override
public int size() {
return Array.getLength(arrayValue);
}};
@Override
public int size() {
return Array.getLength(arrayValue);
}
};
}
values.put(field, value);
}

View File

@@ -0,0 +1,99 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.console;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.persistence.VKey;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.AttributeOverride;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Index;
import javax.persistence.Table;
/**
* A persisted history object representing an EPP action via the console.
*
* <p>In addition to the generic history fields (time, URL, etc.) we also persist a reference to the
* history entry so that we can refer to it if necessary.
*/
@Access(AccessType.FIELD)
@Entity
@Table(
indexes = {
@Index(columnList = "historyActingUser"),
@Index(columnList = "repoId"),
@Index(columnList = "revisionId")
})
public class ConsoleEppActionHistory extends ConsoleUpdateHistory {
@AttributeOverride(name = "repoId", column = @Column(nullable = false))
HistoryEntryId historyEntryId;
@Column(nullable = false)
Class<? extends HistoryEntry> historyEntryClass;
public HistoryEntryId getHistoryEntryId() {
return historyEntryId;
}
public Class<? extends HistoryEntry> getHistoryEntryClass() {
return historyEntryClass;
}
/** Creates a {@link VKey} instance for this entity. */
@Override
public VKey<ConsoleEppActionHistory> createVKey() {
return VKey.create(ConsoleEppActionHistory.class, getRevisionId());
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** Builder for the immutable UserUpdateHistory. */
public static class Builder
extends ConsoleUpdateHistory.Builder<ConsoleEppActionHistory, Builder> {
public Builder() {}
public Builder(ConsoleEppActionHistory instance) {
super(instance);
}
@Override
public ConsoleEppActionHistory build() {
checkArgumentNotNull(getInstance().historyEntryId, "History entry ID must be specified");
checkArgumentNotNull(
getInstance().historyEntryClass, "History entry class must be specified");
return super.build();
}
public Builder setHistoryEntryId(HistoryEntryId historyEntryId) {
getInstance().historyEntryId = historyEntryId;
return this;
}
public Builder setHistoryEntryClass(Class<? extends HistoryEntry> historyEntryClass) {
getInstance().historyEntryClass = historyEntryClass;
return this;
}
}
}

View File

@@ -0,0 +1,168 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.console;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.IdAllocation;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MappedSuperclass;
import org.joda.time.DateTime;
/**
* A record of a resource that was updated through the console.
*
* <p>This abstract class has several subclasses that (mostly) include the modified resource itself
* so that the entire object history is persisted to SQL.
*/
@Access(AccessType.FIELD)
@MappedSuperclass
public abstract class ConsoleUpdateHistory extends ImmutableObject implements Buildable {
public enum Type {
EPP_ACTION,
POC_CREATE,
POC_UPDATE,
POC_DELETE,
REGISTRAR_UPDATE,
USER_CREATE,
USER_DELETE,
USER_UPDATE
}
/** Autogenerated ID of this event. */
@Id
@IdAllocation
@Column(nullable = false, name = "historyRevisionId")
protected Long revisionId;
/** The user that performed the modification. */
@JoinColumn(name = "historyActingUser", referencedColumnName = "emailAddress", nullable = false)
@ManyToOne
User actingUser;
/** The URL of the action that was used to make the modification. */
@Column(nullable = false, name = "historyUrl")
String url;
/** The HTTP method (e.g. POST, PUT) used to make this modification. */
@Column(nullable = false, name = "historyMethod")
String method;
/** The raw body of the request that was used to make this modification. */
@Column(name = "historyRequestBody")
String requestBody;
/** The time at which the modification was mode. */
@Column(nullable = false, name = "historyModificationTime")
DateTime modificationTime;
/** The type of modification. */
@Column(nullable = false, name = "historyType")
@Enumerated(EnumType.STRING)
Type type;
public long getRevisionId() {
return revisionId;
}
public User getActingUser() {
return actingUser;
}
public String getUrl() {
return url;
}
public String getMethod() {
return method;
}
public String getRequestBody() {
return requestBody;
}
public DateTime getModificationTime() {
return modificationTime;
}
public Type getType() {
return type;
}
@Override
public abstract Builder<? extends ConsoleUpdateHistory, ?> asBuilder();
/** Builder for the immutable ConsoleUpdateHistory. */
public abstract static class Builder<
T extends ConsoleUpdateHistory, B extends ConsoleUpdateHistory.Builder<?, ?>>
extends GenericBuilder<T, B> {
protected Builder() {}
protected Builder(T instance) {
super(instance);
}
@Override
public T build() {
checkArgumentNotNull(getInstance().actingUser, "Acting user must be specified");
checkArgumentNotNull(getInstance().url, "URL must be specified");
checkArgumentNotNull(getInstance().method, "HTTP method must be specified");
checkArgumentNotNull(getInstance().modificationTime, "modificationTime must be specified");
checkArgumentNotNull(getInstance().type, "Console History type must be specified");
return super.build();
}
public B setActingUser(User actingUser) {
getInstance().actingUser = actingUser;
return thisCastToDerived();
}
public B setUrl(String url) {
getInstance().url = url;
return thisCastToDerived();
}
public B setMethod(String method) {
getInstance().method = method;
return thisCastToDerived();
}
public B setRequestBody(String requestBody) {
getInstance().requestBody = requestBody;
return thisCastToDerived();
}
public B setModificationTime(DateTime modificationTime) {
getInstance().modificationTime = modificationTime;
return thisCastToDerived();
}
public B setType(Type type) {
getInstance().type = type;
return thisCastToDerived();
}
}
}

View File

@@ -0,0 +1,99 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.console;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.registrar.RegistrarPocBase;
import google.registry.persistence.VKey;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Index;
import javax.persistence.PostLoad;
import javax.persistence.Table;
/**
* A persisted history object representing an update to a RegistrarPoc.
*
* <p>In addition to the generic history fields (time, URL, etc.) we also persist a copy of the
* modified RegistrarPoc object at this point in time.
*/
@Access(AccessType.FIELD)
@Entity
@Table(
indexes = {
@Index(columnList = "historyActingUser"),
@Index(columnList = "emailAddress"),
@Index(columnList = "registrarId")
})
public class RegistrarPocUpdateHistory extends ConsoleUpdateHistory {
RegistrarPocBase registrarPoc;
// These fields exist so that they can be populated in the SQL table
@Column(nullable = false)
String emailAddress;
@Column(nullable = false)
String registrarId;
public RegistrarPocBase getRegistrarPoc() {
return registrarPoc;
}
@PostLoad
void postLoad() {
registrarPoc.setEmailAddress(emailAddress);
registrarPoc.setRegistrarId(registrarId);
}
/** Creates a {@link VKey} instance for this entity. */
@Override
public VKey<RegistrarPocUpdateHistory> createVKey() {
return VKey.create(RegistrarPocUpdateHistory.class, getRevisionId());
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** Builder for the immutable UserUpdateHistory. */
public static class Builder
extends ConsoleUpdateHistory.Builder<RegistrarPocUpdateHistory, Builder> {
public Builder() {}
public Builder(RegistrarPocUpdateHistory instance) {
super(instance);
}
@Override
public RegistrarPocUpdateHistory build() {
checkArgumentNotNull(getInstance().registrarPoc, "Registrar POC must be specified");
return super.build();
}
public Builder setRegistrarPoc(RegistrarPoc registrarPoc) {
getInstance().registrarPoc = registrarPoc;
getInstance().registrarId = registrarPoc.getRegistrarId();
getInstance().emailAddress = registrarPoc.getEmailAddress();
return this;
}
}
}

View File

@@ -0,0 +1,89 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.console;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import google.registry.model.registrar.RegistrarBase;
import google.registry.persistence.VKey;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Index;
import javax.persistence.PostLoad;
import javax.persistence.Table;
/**
* A persisted history object representing an update to a Registrar.
*
* <p>In addition to the generic history fields (time, URL, etc.) we also persist a copy of the
* modified Registrar object at this point in time.
*/
@Access(AccessType.FIELD)
@Entity
@Table(indexes = {@Index(columnList = "historyActingUser"), @Index(columnList = "registrarId")})
public class RegistrarUpdateHistory extends ConsoleUpdateHistory {
RegistrarBase registrar;
// This field exists so that it exists in the SQL table
@Column(nullable = false)
@SuppressWarnings("unused")
private String registrarId;
public RegistrarBase getRegistrar() {
return registrar;
}
@PostLoad
void postLoad() {
registrar.setRegistrarId(registrarId);
}
/** Creates a {@link VKey} instance for this entity. */
@Override
public VKey<RegistrarUpdateHistory> createVKey() {
return VKey.create(RegistrarUpdateHistory.class, getRevisionId());
}
@Override
public Builder asBuilder() {
return new RegistrarUpdateHistory.Builder(clone(this));
}
/** Builder for the immutable UserUpdateHistory. */
public static class Builder
extends ConsoleUpdateHistory.Builder<RegistrarUpdateHistory, Builder> {
public Builder() {}
public Builder(RegistrarUpdateHistory instance) {
super(instance);
}
@Override
public RegistrarUpdateHistory build() {
checkArgumentNotNull(getInstance().registrar, "Registrar must be specified");
return super.build();
}
public Builder setRegistrar(RegistrarBase registrar) {
getInstance().registrar = registrar;
getInstance().registrarId = registrar.getRegistrarId();
return this;
}
}
}

View File

@@ -14,19 +14,11 @@
package google.registry.model.console;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.io.BaseEncoding.base64;
import static google.registry.model.registrar.Registrar.checkValidEmail;
import static google.registry.util.PasswordUtils.SALT_SUPPLIER;
import static google.registry.util.PasswordUtils.hashPassword;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import google.registry.model.Buildable;
import google.registry.model.UpdateAutoTimestampEntity;
import google.registry.persistence.VKey;
import google.registry.util.PasswordUtils;
import javax.persistence.Column;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
@@ -35,74 +27,17 @@ import javax.persistence.Index;
import javax.persistence.Table;
/** A console user, either a registry employee or a registrar partner. */
@Embeddable
@Entity
@Table(indexes = {@Index(columnList = "emailAddress", name = "user_email_address_idx")})
public class User extends UpdateAutoTimestampEntity implements Buildable {
public class User extends UserBase {
private static final long serialVersionUID = 6936728603828566721L;
/** Autogenerated unique ID of this user. */
@Override
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/** Email address of the user in question. */
@Column(nullable = false)
private String emailAddress;
/** Roles (which grant permissions) associated with this user. */
@Column(nullable = false)
private UserRoles userRoles;
/**
* 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;
@Access(AccessType.PROPERTY)
public Long getId() {
return id;
}
public String getEmailAddress() {
return emailAddress;
}
public UserRoles getUserRoles() {
return userRoles;
}
public boolean hasRegistryLockPassword() {
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);
}
/**
* Whether the user has the registry lock permission on any registrar or globally.
*
* <p>If so, they should be allowed to (re)set their registry lock password.
*/
public boolean hasAnyRegistryLockPermission() {
if (userRoles == null) {
return false;
}
if (userRoles.isAdmin() || userRoles.hasGlobalPermission(ConsolePermission.REGISTRY_LOCK)) {
return true;
}
return userRoles.getRegistrarRoles().values().stream()
.anyMatch(role -> role.hasPermission(ConsolePermission.REGISTRY_LOCK));
return super.getId();
}
@Override
@@ -116,7 +51,7 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
}
/** Builder for constructing immutable {@link User} objects. */
public static class Builder extends Buildable.Builder<User> {
public static class Builder extends UserBase.Builder<User, Builder> {
public Builder() {}
@@ -124,41 +59,5 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
super(user);
}
@Override
public User build() {
checkArgumentNotNull(getInstance().emailAddress, "Email address cannot be null");
checkArgumentNotNull(getInstance().userRoles, "User roles cannot be null");
return super.build();
}
public Builder setEmailAddress(String emailAddress) {
getInstance().emailAddress = checkValidEmail(emailAddress);
return this;
}
public Builder setUserRoles(UserRoles userRoles) {
checkArgumentNotNull(userRoles, "User roles cannot be null");
getInstance().userRoles = userRoles;
return this;
}
public Builder removeRegistryLockPassword() {
getInstance().registryLockPasswordHash = null;
getInstance().registryLockPasswordSalt = null;
return this;
}
public Builder setRegistryLockPassword(String registryLockPassword) {
checkArgument(
getInstance().hasAnyRegistryLockPermission(), "User has no registry lock permission");
checkArgument(
!getInstance().hasRegistryLockPassword(), "User already has a password, remove it first");
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);
return this;
}
}
}

View File

@@ -0,0 +1,178 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.console;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.io.BaseEncoding.base64;
import static google.registry.model.registrar.Registrar.checkValidEmail;
import static google.registry.util.PasswordUtils.SALT_SUPPLIER;
import static google.registry.util.PasswordUtils.hashPassword;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import google.registry.model.Buildable;
import google.registry.model.UpdateAutoTimestampEntity;
import google.registry.util.PasswordUtils;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;
/**
* A console user, either a registry employee or a registrar partner.
*
* <p>This class deliberately does not include an {@link Id} so that any foreign-keyed fields can
* refer to the proper parent entity's ID, whether we're storing this in the DB itself or as part of
* another entity.
*/
@Access(AccessType.FIELD)
@Embeddable
@MappedSuperclass
public class UserBase extends UpdateAutoTimestampEntity implements Buildable {
private static final long serialVersionUID = 6936728603828566721L;
/** Autogenerated unique ID of this user. */
@Transient private Long id;
/** Email address of the user in question. */
@Column(nullable = false)
String emailAddress;
/** Roles (which grant permissions) associated with this user. */
@Column(nullable = false)
UserRoles userRoles;
/**
* 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;
public Long getId() {
return id;
}
/**
* Sets the user ID.
*
* <p>This should only be used for restoring the user id of an object being loaded in a PostLoad
* method (effectively, when it is still under construction by Hibernate). In all other cases, the
* object should be regarded as immutable and changes should go through a Builder.
*
* <p>In addition to this special case use, this method must exist to satisfy Hibernate.
*/
@SuppressWarnings("unused")
void setId(Long id) {
this.id = id;
}
public String getEmailAddress() {
return emailAddress;
}
public UserRoles getUserRoles() {
return userRoles;
}
public boolean hasRegistryLockPassword() {
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);
}
/**
* Whether the user has the registry lock permission on any registrar or globally.
*
* <p>If so, they should be allowed to (re)set their registry lock password.
*/
public boolean hasAnyRegistryLockPermission() {
if (userRoles == null) {
return false;
}
if (userRoles.isAdmin() || userRoles.hasGlobalPermission(ConsolePermission.REGISTRY_LOCK)) {
return true;
}
return userRoles.getRegistrarRoles().values().stream()
.anyMatch(role -> role.hasPermission(ConsolePermission.REGISTRY_LOCK));
}
@Override
public Builder<? extends UserBase, ?> asBuilder() {
return new Builder<>(clone(this));
}
/** Builder for constructing immutable {@link UserBase} objects. */
public static class Builder<T extends UserBase, B extends Builder<T, B>>
extends GenericBuilder<T, B> {
public Builder() {}
public Builder(T abstractUser) {
super(abstractUser);
}
@Override
public T build() {
checkArgumentNotNull(getInstance().emailAddress, "Email address cannot be null");
checkArgumentNotNull(getInstance().userRoles, "User roles cannot be null");
return super.build();
}
public B setEmailAddress(String emailAddress) {
getInstance().emailAddress = checkValidEmail(emailAddress);
return thisCastToDerived();
}
public B setUserRoles(UserRoles userRoles) {
checkArgumentNotNull(userRoles, "User roles cannot be null");
getInstance().userRoles = userRoles;
return thisCastToDerived();
}
public B removeRegistryLockPassword() {
getInstance().registryLockPasswordHash = null;
getInstance().registryLockPasswordSalt = null;
return thisCastToDerived();
}
public B setRegistryLockPassword(String registryLockPassword) {
checkArgument(
getInstance().hasAnyRegistryLockPermission(), "User has no registry lock permission");
checkArgument(
!getInstance().hasRegistryLockPassword(), "User already has a password, remove it first");
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);
return thisCastToDerived();
}
}
}

View File

@@ -21,6 +21,8 @@ import com.google.common.collect.ImmutableMap;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import java.util.Map;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.EnumType;
@@ -32,6 +34,7 @@ import javax.persistence.Enumerated;
* <p>See <a href="https://go/nomulus-console-authz">go/nomulus-console-authz</a> for more
* information.
*/
@Access(AccessType.FIELD)
@Embeddable
public class UserRoles extends ImmutableObject implements Buildable {

View File

@@ -0,0 +1,86 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.console;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import google.registry.persistence.VKey;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Index;
import javax.persistence.PostLoad;
import javax.persistence.Table;
/**
* A persisted history object representing an update to a User.
*
* <p>In addition to the generic history fields (time, URL, etc.) we also persist a copy of the
* modified User object at this point in time.
*/
@Access(AccessType.FIELD)
@Entity
@Table(indexes = {@Index(columnList = "historyActingUser"), @Index(columnList = "emailAddress")})
public class UserUpdateHistory extends ConsoleUpdateHistory {
UserBase user;
// This field exists so that it's populated in the SQL table
@Column(nullable = false, name = "userId")
Long id;
public UserBase getUser() {
return user;
}
@PostLoad
void postLoad() {
user.setId(id);
}
/** Creates a {@link VKey} instance for this entity. */
@Override
public VKey<UserUpdateHistory> createVKey() {
return VKey.create(UserUpdateHistory.class, getRevisionId());
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** Builder for the immutable UserUpdateHistory. */
public static class Builder extends ConsoleUpdateHistory.Builder<UserUpdateHistory, Builder> {
public Builder() {}
public Builder(UserUpdateHistory instance) {
super(instance);
}
@Override
public UserUpdateHistory build() {
checkArgumentNotNull(getInstance().user, "User must be specified");
return super.build();
}
public Builder setUser(User user) {
getInstance().user = user;
getInstance().id = user.getId();
return this;
}
}
}

View File

@@ -61,7 +61,8 @@ import org.joda.time.DateTime;
@Index(columnList = "lordnPhase"),
@Index(columnList = "billing_recurrence_id"),
@Index(columnList = "transfer_billing_event_id"),
@Index(columnList = "transfer_billing_recurrence_id")
@Index(columnList = "transfer_billing_recurrence_id"),
@Index(columnList = "transfer_billing_cancellation_id")
})
@WithVKey(String.class)
@ExternalMessagingName("domain")

View File

@@ -659,22 +659,17 @@ public class DomainBase extends EppResource
contact.getType());
contactsDiscovered.add(contact.getType());
switch (contact.getType()) {
case BILLING:
billingContact = contact.getContactKey();
break;
case TECH:
techContact = contact.getContactKey();
break;
case ADMIN:
adminContact = contact.getContactKey();
break;
case REGISTRANT:
case BILLING -> billingContact = contact.getContactKey();
case TECH -> techContact = contact.getContactKey();
case ADMIN -> adminContact = contact.getContactKey();
case REGISTRANT -> {
if (includeRegistrant) {
registrantContact = contact.getContactKey();
}
break;
default:
throw new IllegalArgumentException("Unknown contact resource type: " + contact.getType());
}
default ->
throw new IllegalArgumentException(
"Unknown contact resource type: " + contact.getType());
}
}
}

View File

@@ -103,7 +103,7 @@ public enum GracePeriodStatus implements EppEnum {
/**
* Maps from xmlName to {@link GracePeriodStatus}.
*
* If no match is found for xmlName, null is returned.
* <p>If no match is found for xmlName, null is returned.
*/
@Nullable
public static GracePeriodStatus fromXmlName(String xmlName) {

View File

@@ -34,7 +34,7 @@ public class Result extends ImmutableObject {
* [RFC5321]. EPP uses four decimal digits to describe the success or failure of each EPP command.
* Each of the digits of the reply have special significance."
*
* "The first digit denotes command success or failure. The second digit denotes the response
* <p>"The first digit denotes command success or failure. The second digit denotes the response
* category, such as command syntax or security. The third and fourth digits provide explicit
* response detail within each response category."
*/

View File

@@ -49,6 +49,7 @@ import javax.persistence.Table;
@Index(columnList = "creationTime"),
@Index(columnList = "deletionTime"),
@Index(columnList = "currentSponsorRegistrarId"),
@Index(columnList = "superordinateDomain")
})
@ExternalMessagingName("host")
@WithVKey(String.class)

View File

@@ -529,8 +529,7 @@ public abstract class PollMessage extends ImmutableObject
// Set the identifier according to the TransferResponse type.
if (instance.transferResponse instanceof ContactTransferResponse) {
instance.contactId = ((ContactTransferResponse) instance.transferResponse).getContactId();
} else if (instance.transferResponse instanceof DomainTransferResponse) {
DomainTransferResponse response = (DomainTransferResponse) instance.transferResponse;
} else if (instance.transferResponse instanceof DomainTransferResponse response) {
instance.domainName = response.getDomainName();
instance.extendedRegistrationExpirationTime =
response.getExtendedRegistrationExpirationTime();

File diff suppressed because it is too large Load Diff

View File

@@ -14,35 +14,14 @@
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;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.gson.annotations.Expose;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.JsonMapBuilder;
import google.registry.model.Jsonifiable;
import google.registry.model.UnsafeSerializable;
import google.registry.model.registrar.RegistrarPoc.RegistrarPocId;
import google.registry.persistence.VKey;
import google.registry.util.PasswordUtils;
import java.io.Serializable;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
@@ -60,258 +39,20 @@ import javax.persistence.Table;
@Entity
@Table(indexes = @Index(columnList = "loginEmailAddress", name = "registrarpoc_login_email_idx"))
@IdClass(RegistrarPocId.class)
public class RegistrarPoc extends ImmutableObject implements Jsonifiable, UnsafeSerializable {
/**
* Registrar contacts types for partner communication tracking.
*
* <p><b>Note:</b> These types only matter to the registry. They are not meant to be used for
* WHOIS or RDAP results.
*/
public enum Type {
ABUSE("abuse", true),
ADMIN("primary", true),
BILLING("billing", true),
LEGAL("legal", true),
MARKETING("marketing", false),
TECH("technical", true),
WHOIS("whois-inquiry", true);
private final String displayName;
private final boolean required;
public String getDisplayName() {
return displayName;
}
public boolean isRequired() {
return required;
}
Type(String display, boolean required) {
displayName = display;
this.required = required;
}
}
/** The name of the contact. */
@Expose String name;
/**
* The contact email address of the contact.
*
* <p>This is different from the login email which is assgined to the regstrar and cannot be
* changed.
*/
@Expose @Id String emailAddress;
@Expose @Id 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;
/** The fax number of the contact. */
@Expose String faxNumber;
/**
* Multiple types are used to associate the registrar contact with various mailing groups. This
* data is internal to the registry.
*/
@Expose Set<Type> types;
/** A GAIA email address that was assigned to the registrar for console login purpose. */
String loginEmailAddress;
/**
* Whether this contact is publicly visible in WHOIS registrar query results as an Admin contact.
*/
@Expose boolean visibleInWhoisAsAdmin = false;
/**
* Whether this contact is publicly visible in WHOIS registrar query results as a Technical
* contact.
*/
@Expose boolean visibleInWhoisAsTech = false;
/**
* Whether this contact's phone number and email address is publicly visible in WHOIS domain query
* results as registrar abuse contact info.
*/
@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.
*/
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}
* set, and then saving the given {@code contacts}.
*
* <p>IMPORTANT NOTE: If you call this method then it is your responsibility to also persist the
* relevant Registrar entity with the {@link Registrar#contactsRequireSyncing} field set to true.
*/
public static void updateContacts(
final Registrar registrar, final ImmutableSet<RegistrarPoc> contacts) {
tm().transact(
() -> {
ImmutableSet<String> emailAddressesToKeep =
contacts.stream().map(RegistrarPoc::getEmailAddress).collect(toImmutableSet());
tm().query(
"DELETE FROM RegistrarPoc WHERE registrarId = :registrarId AND "
+ "emailAddress NOT IN :emailAddressesToKeep")
.setParameter("registrarId", registrar.getRegistrarId())
.setParameter("emailAddressesToKeep", emailAddressesToKeep)
.executeUpdate();
tm().putAll(contacts);
});
}
public String getName() {
return name;
}
@Access(AccessType.FIELD)
public class RegistrarPoc extends RegistrarPocBase {
@Id
@Access(AccessType.PROPERTY)
@Override
public String getEmailAddress() {
return emailAddress;
}
public Optional<String> getRegistryLockEmailAddress() {
return Optional.ofNullable(registryLockEmailAddress);
}
public String getPhoneNumber() {
return phoneNumber;
}
public String getFaxNumber() {
return faxNumber;
}
public ImmutableSortedSet<Type> getTypes() {
return nullToEmptyImmutableSortedCopy(types);
}
public boolean getVisibleInWhoisAsAdmin() {
return visibleInWhoisAsAdmin;
}
public boolean getVisibleInWhoisAsTech() {
return visibleInWhoisAsTech;
}
public boolean getVisibleInDomainWhoisAsAbuse() {
return visibleInDomainWhoisAsAbuse;
}
public String getLoginEmailAddress() {
return loginEmailAddress;
}
public Builder asBuilder() {
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.
*
* <p>The output will look something like this:
*
* <pre>{@code
* Some Person
* person@example.com
* Tel: +1.2125650666
* Types: [ADMIN, WHOIS]
* Visible in WHOIS as Admin contact: Yes
* Visible in WHOIS as Technical contact: No
* Registrar-Console access: Yes
* Login Email Address: person@registry.example
* }</pre>
*/
public String toStringMultilinePlainText() {
StringBuilder result = new StringBuilder(256);
result.append(getName()).append('\n');
result.append(getEmailAddress()).append('\n');
if (phoneNumber != null) {
result.append("Tel: ").append(getPhoneNumber()).append('\n');
}
if (faxNumber != null) {
result.append("Fax: ").append(getFaxNumber()).append('\n');
}
result.append("Types: ").append(getTypes()).append('\n');
result
.append("Visible in registrar WHOIS query as Admin contact: ")
.append(getVisibleInWhoisAsAdmin() ? "Yes" : "No")
.append('\n');
result
.append("Visible in registrar WHOIS query as Technical contact: ")
.append(getVisibleInWhoisAsTech() ? "Yes" : "No")
.append('\n');
result
.append(
"Phone number and email visible in domain WHOIS query as "
+ "Registrar Abuse contact info: ")
.append(getVisibleInDomainWhoisAsAbuse() ? "Yes" : "No")
.append('\n');
result
.append("Registrar-Console access: ")
.append(getLoginEmailAddress() != null ? "Yes" : "No")
.append('\n');
if (getLoginEmailAddress() != null) {
result.append("Login Email Address: ").append(getLoginEmailAddress()).append('\n');
}
return result.toString();
}
@Override
public Map<String, Object> toJsonMap() {
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("loginEmailAddress", loginEmailAddress)
.build();
@Id
@Access(AccessType.PROPERTY)
public String getRegistrarId() {
return registrarId;
}
@Override
@@ -319,6 +60,11 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
return VKey.create(RegistrarPoc.class, new RegistrarPocId(emailAddress, registrarId));
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** Class to represent the composite primary key for {@link RegistrarPoc} entity. */
@VisibleForTesting
public static class RegistrarPocId extends ImmutableObject implements Serializable {
@@ -336,112 +82,24 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
this.emailAddress = emailAddress;
this.registrarId = registrarId;
}
@Id
public String getEmailAddress() {
return emailAddress;
}
@Id
public String getRegistrarId() {
return registrarId;
}
}
/** A builder for constructing a {@link RegistrarPoc}, since it is immutable. */
public static class Builder extends Buildable.Builder<RegistrarPoc> {
public static class Builder extends RegistrarPocBase.Builder<RegistrarPoc, Builder> {
public Builder() {}
private Builder(RegistrarPoc instance) {
super(instance);
}
/** Build the registrar, nullifying empty fields. */
@Override
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());
}
public Builder setName(String name) {
getInstance().name = name;
return this;
}
public Builder setEmailAddress(String emailAddress) {
getInstance().emailAddress = emailAddress;
return this;
}
public Builder setRegistryLockEmailAddress(@Nullable String registryLockEmailAddress) {
getInstance().registryLockEmailAddress = registryLockEmailAddress;
return this;
}
public Builder setPhoneNumber(String phoneNumber) {
getInstance().phoneNumber = phoneNumber;
return this;
}
public Builder setRegistrarId(String registrarId) {
getInstance().registrarId = registrarId;
return this;
}
public Builder setRegistrar(Registrar registrar) {
getInstance().registrarId = registrar.getRegistrarId();
return this;
}
public Builder setFaxNumber(String faxNumber) {
getInstance().faxNumber = faxNumber;
return this;
}
public Builder setTypes(Iterable<Type> types) {
getInstance().types = ImmutableSet.copyOf(types);
return this;
}
public Builder setVisibleInWhoisAsAdmin(boolean visible) {
getInstance().visibleInWhoisAsAdmin = visible;
return this;
}
public Builder setVisibleInWhoisAsTech(boolean visible) {
getInstance().visibleInWhoisAsTech = visible;
return this;
}
public Builder setVisibleInDomainWhoisAsAbuse(boolean visible) {
getInstance().visibleInDomainWhoisAsAbuse = visible;
return this;
}
public Builder setLoginEmailAddress(String loginEmailAddress) {
getInstance().loginEmailAddress = loginEmailAddress;
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 Builder(RegistrarPoc registrarPoc) {
super(registrarPoc);
}
}
}

View File

@@ -0,0 +1,441 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.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.RegistrarBase.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.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.gson.annotations.Expose;
import google.registry.model.Buildable.GenericBuilder;
import google.registry.model.ImmutableObject;
import google.registry.model.JsonMapBuilder;
import google.registry.model.Jsonifiable;
import google.registry.model.UnsafeSerializable;
import google.registry.util.PasswordUtils;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Embeddable;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;
/**
* A contact for a Registrar. Note, equality, hashCode and comparable have been overridden to only
* enable key equality.
*
* <p>IMPORTANT NOTE: Any time that you change, update, or delete RegistrarContact entities, you
* *MUST* also modify the persisted Registrar entity with {@link Registrar#contactsRequireSyncing}
* set to true.
*
* <p>This class deliberately does not include an {@link Id} so that any foreign-keyed fields can
* refer to the proper parent entity's ID, whether we're storing this in the DB itself or as part of
* another entity.
*/
@Access(AccessType.FIELD)
@Embeddable
@MappedSuperclass
public class RegistrarPocBase extends ImmutableObject implements Jsonifiable, UnsafeSerializable {
/**
* Registrar contacts types for partner communication tracking.
*
* <p><b>Note:</b> These types only matter to the registry. They are not meant to be used for
* WHOIS or RDAP results.
*/
public enum Type {
ABUSE("abuse", true),
ADMIN("primary", true),
BILLING("billing", true),
LEGAL("legal", true),
MARKETING("marketing", false),
TECH("technical", true),
WHOIS("whois-inquiry", true);
private final String displayName;
private final boolean required;
public String getDisplayName() {
return displayName;
}
public boolean isRequired() {
return required;
}
Type(String display, boolean required) {
displayName = display;
this.required = required;
}
}
/** The name of the contact. */
@Expose String name;
/**
* The contact email address of the contact.
*
* <p>This is different from the login email which is assgined to the regstrar and cannot be
* changed.
*/
@Expose @Transient String emailAddress;
@Expose @Transient 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;
/** The fax number of the contact. */
@Expose String faxNumber;
/**
* Multiple types are used to associate the registrar contact with various mailing groups. This
* data is internal to the registry.
*/
@Expose Set<Type> types;
/** A GAIA email address that was assigned to the registrar for console login purpose. */
String loginEmailAddress;
/**
* Whether this contact is publicly visible in WHOIS registrar query results as an Admin contact.
*/
@Expose boolean visibleInWhoisAsAdmin = false;
/**
* Whether this contact is publicly visible in WHOIS registrar query results as a Technical
* contact.
*/
@Expose boolean visibleInWhoisAsTech = false;
/**
* Whether this contact's phone number and email address is publicly visible in WHOIS domain query
* results as registrar abuse contact info.
*/
@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.
*/
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}
* set, and then saving the given {@code contacts}.
*
* <p>IMPORTANT NOTE: If you call this method then it is your responsibility to also persist the
* relevant Registrar entity with the {@link Registrar#contactsRequireSyncing} field set to true.
*/
public static void updateContacts(
final Registrar registrar, final ImmutableSet<RegistrarPoc> contacts) {
tm().transact(
() -> {
ImmutableSet<String> emailAddressesToKeep =
contacts.stream().map(RegistrarPoc::getEmailAddress).collect(toImmutableSet());
tm().query(
"DELETE FROM RegistrarPoc WHERE registrarId = :registrarId AND "
+ "emailAddress NOT IN :emailAddressesToKeep")
.setParameter("registrarId", registrar.getRegistrarId())
.setParameter("emailAddressesToKeep", emailAddressesToKeep)
.executeUpdate();
tm().putAll(contacts);
});
}
public String getName() {
return name;
}
public String getEmailAddress() {
return emailAddress;
}
public Optional<String> getRegistryLockEmailAddress() {
return Optional.ofNullable(registryLockEmailAddress);
}
public String getPhoneNumber() {
return phoneNumber;
}
public String getFaxNumber() {
return faxNumber;
}
public ImmutableSortedSet<Type> getTypes() {
return nullToEmptyImmutableSortedCopy(types);
}
public boolean getVisibleInWhoisAsAdmin() {
return visibleInWhoisAsAdmin;
}
public boolean getVisibleInWhoisAsTech() {
return visibleInWhoisAsTech;
}
public boolean getVisibleInDomainWhoisAsAbuse() {
return visibleInDomainWhoisAsAbuse;
}
public String getLoginEmailAddress() {
return loginEmailAddress;
}
public Builder<? extends RegistrarPocBase, ?> asBuilder() {
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.
*
* <p>The output will look something like this:
*
* <pre>{@code
* Some Person
* person@example.com
* Tel: +1.2125650666
* Types: [ADMIN, WHOIS]
* Visible in WHOIS as Admin contact: Yes
* Visible in WHOIS as Technical contact: No
* Registrar-Console access: Yes
* Login Email Address: person@registry.example
* }</pre>
*/
public String toStringMultilinePlainText() {
StringBuilder result = new StringBuilder(256);
result.append(getName()).append('\n');
result.append(getEmailAddress()).append('\n');
if (phoneNumber != null) {
result.append("Tel: ").append(getPhoneNumber()).append('\n');
}
if (faxNumber != null) {
result.append("Fax: ").append(getFaxNumber()).append('\n');
}
result.append("Types: ").append(getTypes()).append('\n');
result
.append("Visible in registrar WHOIS query as Admin contact: ")
.append(getVisibleInWhoisAsAdmin() ? "Yes" : "No")
.append('\n');
result
.append("Visible in registrar WHOIS query as Technical contact: ")
.append(getVisibleInWhoisAsTech() ? "Yes" : "No")
.append('\n');
result
.append(
"Phone number and email visible in domain WHOIS query as "
+ "Registrar Abuse contact info: ")
.append(getVisibleInDomainWhoisAsAbuse() ? "Yes" : "No")
.append('\n');
result
.append("Registrar-Console access: ")
.append(getLoginEmailAddress() != null ? "Yes" : "No")
.append('\n');
if (getLoginEmailAddress() != null) {
result.append("Login Email Address: ").append(getLoginEmailAddress()).append('\n');
}
return result.toString();
}
@Override
public Map<String, Object> toJsonMap() {
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("loginEmailAddress", loginEmailAddress)
.build();
}
/**
* These methods set the email address and registrar ID
*
* <p>This should only be used for restoring the fields of an object being loaded in a PostLoad
* method (effectively, when it is still under construction by Hibernate). In all other cases, the
* object should be regarded as immutable and changes should go through a Builder.
*
* <p>In addition to this special case use, this method must exist to satisfy Hibernate.
*/
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public void setRegistrarId(String registrarId) {
this.registrarId = registrarId;
}
/** A builder for constructing a {@link RegistrarPoc}, since it is immutable. */
public static class Builder<T extends RegistrarPocBase, B extends Builder<T, B>>
extends GenericBuilder<T, B> {
public Builder() {}
protected Builder(T instance) {
super(instance);
}
/** Build the registrar, nullifying empty fields. */
@Override
public T 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());
}
public B setName(String name) {
getInstance().name = name;
return thisCastToDerived();
}
public B setEmailAddress(String emailAddress) {
getInstance().emailAddress = emailAddress;
return thisCastToDerived();
}
public B setRegistryLockEmailAddress(@Nullable String registryLockEmailAddress) {
getInstance().registryLockEmailAddress = registryLockEmailAddress;
return thisCastToDerived();
}
public B setPhoneNumber(String phoneNumber) {
getInstance().phoneNumber = phoneNumber;
return thisCastToDerived();
}
public B setRegistrarId(String registrarId) {
getInstance().registrarId = registrarId;
return thisCastToDerived();
}
public B setRegistrar(Registrar registrar) {
getInstance().registrarId = registrar.getRegistrarId();
return thisCastToDerived();
}
public B setFaxNumber(String faxNumber) {
getInstance().faxNumber = faxNumber;
return thisCastToDerived();
}
public B setTypes(Iterable<Type> types) {
getInstance().types = ImmutableSet.copyOf(types);
return thisCastToDerived();
}
public B setVisibleInWhoisAsAdmin(boolean visible) {
getInstance().visibleInWhoisAsAdmin = visible;
return thisCastToDerived();
}
public B setVisibleInWhoisAsTech(boolean visible) {
getInstance().visibleInWhoisAsTech = visible;
return thisCastToDerived();
}
public B setVisibleInDomainWhoisAsAbuse(boolean visible) {
getInstance().visibleInDomainWhoisAsAbuse = visible;
return thisCastToDerived();
}
public B setLoginEmailAddress(String loginEmailAddress) {
getInstance().loginEmailAddress = loginEmailAddress;
return thisCastToDerived();
}
public B setAllowedToSetRegistryLockPassword(boolean allowedToSetRegistryLockPassword) {
if (allowedToSetRegistryLockPassword) {
getInstance().registryLockPasswordSalt = null;
getInstance().registryLockPasswordHash = null;
}
getInstance().allowedToSetRegistryLockPassword = allowedToSetRegistryLockPassword;
return thisCastToDerived();
}
public B 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 thisCastToDerived();
}
}
}

View File

@@ -67,12 +67,13 @@ public enum ReservationType {
return messageForCheck;
}
private static final Ordering<ReservationType> ORDERING = new Ordering<ReservationType>() {
@Override
public int compare(ReservationType left, ReservationType right) {
return Integer.compare(left.ordinal(), right.ordinal());
}
};
private static final Ordering<ReservationType> ORDERING =
new Ordering<>() {
@Override
public int compare(ReservationType left, ReservationType right) {
return Integer.compare(left.ordinal(), right.ordinal());
}
};
/**
* Returns the {@code ReservationType} with the highest severity, used when a label has multiple

View File

@@ -60,17 +60,17 @@ public final class TmchCrl extends CrossTldSingleton {
}
/** ASCII-armored X.509 certificate revocation list. */
public final String getCrl() {
public String getCrl() {
return crl;
}
/** Returns the URL that the CRL was downloaded from. */
public final String getUrl() {
public String getUrl() {
return crl;
}
/** Time we last updated the Database with a newer ICANN CRL. */
public final DateTime getUpdated() {
public DateTime getUpdated() {
return updated;
}
}

View File

@@ -100,7 +100,7 @@ public class IntervalDescriptor extends AbstractTypeDescriptor<PGInterval>
@Override
public <X> ValueBinder<X> getBinder(JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicBinder<X>(javaTypeDescriptor, this) {
return new BasicBinder<>(javaTypeDescriptor, this) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
throws SQLException {
@@ -117,7 +117,7 @@ public class IntervalDescriptor extends AbstractTypeDescriptor<PGInterval>
@Override
public <X> ValueExtractor<X> getExtractor(JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicExtractor<X>(javaTypeDescriptor, this) {
return new BasicExtractor<>(javaTypeDescriptor, this) {
@Override
protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap(rs.getObject(name), options);

View File

@@ -14,7 +14,7 @@
package google.registry.persistence.converter;
import google.registry.model.registrar.RegistrarPoc.Type;
import google.registry.model.registrar.RegistrarPocBase.Type;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

View File

@@ -115,7 +115,7 @@ public class StringCollectionDescriptor extends AbstractTypeDescriptor<StringCol
@Override
public <X> ValueBinder<X> getBinder(JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicBinder<X>(javaTypeDescriptor, this) {
return new BasicBinder<>(javaTypeDescriptor, this) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
throws SQLException {
@@ -123,8 +123,7 @@ public class StringCollectionDescriptor extends AbstractTypeDescriptor<StringCol
st.setArray(index, null);
return;
}
if (value instanceof StringCollection) {
StringCollection stringCollection = (StringCollection) value;
if (value instanceof StringCollection stringCollection) {
if (stringCollection.getCollection() == null) {
st.setArray(index, null);
} else {
@@ -154,7 +153,7 @@ public class StringCollectionDescriptor extends AbstractTypeDescriptor<StringCol
@Override
public <X> ValueExtractor<X> getExtractor(JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicExtractor<X>(javaTypeDescriptor, this) {
return new BasicExtractor<>(javaTypeDescriptor, this) {
@Override
protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap(rs.getArray(name), options);

View File

@@ -105,7 +105,7 @@ public class StringMapDescriptor extends AbstractTypeDescriptor<StringMap>
@Override
public <X> ValueBinder<X> getBinder(JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicBinder<X>(javaTypeDescriptor, this) {
return new BasicBinder<>(javaTypeDescriptor, this) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
throws SQLException {
@@ -136,7 +136,7 @@ public class StringMapDescriptor extends AbstractTypeDescriptor<StringMap>
@Override
public <X> ValueExtractor<X> getExtractor(JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicExtractor<X>(javaTypeDescriptor, this) {
return new BasicExtractor<>(javaTypeDescriptor, this) {
@Override
protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap(rs.getObject(name), options);

View File

@@ -78,8 +78,7 @@ public class DatabaseException extends PersistenceException {
static String getSqlError(Throwable t) {
ImmutableList.Builder<String> errMessageBuilder = new ImmutableList.Builder<>();
do {
if (t instanceof SQLException) {
SQLException e = (SQLException) t;
if (t instanceof SQLException e) {
getSqlExceptionDetails(e).ifPresent(errMessageBuilder::add);
}
t = t.getCause();

View File

@@ -544,15 +544,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
return emf.getMetamodel().entity(clazz);
}
private static class EntityId {
private final String name;
private final Object value;
private EntityId(String name, Object value) {
this.name = name;
this.value = value;
}
}
private record EntityId(String name, Object value) {}
private static ImmutableSet<EntityId> getEntityIdsFromEntity(
EntityType<?> entityType, Object entity) {

View File

@@ -165,12 +165,9 @@ public class SecretManagerClientImpl implements SecretManagerClient {
return retrier.callWithRetry(callable, SecretManagerClientImpl::isRetryableException);
} catch (ApiException e) {
switch (e.getStatusCode().getCode()) {
case ALREADY_EXISTS:
throw new SecretAlreadyExistsException(e);
case NOT_FOUND:
throw new NoSuchSecretResourceException(e);
default:
throw new SecretManagerException(e);
case ALREADY_EXISTS -> throw new SecretAlreadyExistsException(e);
case NOT_FOUND -> throw new NoSuchSecretResourceException(e);
default -> throw new SecretManagerException(e);
}
}
}

View File

@@ -52,18 +52,13 @@ import org.joda.time.DateTime;
*
* <p>This implementation is geared towards RDAP replies, and hence has RDAP-specific quirks.
* Specifically:
*
* - Fields with empty arrays are not shown at all
*
* - VCards are a built-in special case (Not implemented yet)
*
* - DateTime conversion is specifically supported as if it were a primitive
*
* - Arrays are considered to be SETS rather than lists, meaning repeated values are removed and the
* order isn't guaranteed
*
* Usage:
*
* {@link JsonableElement}
* -----------------------
*
@@ -324,8 +319,7 @@ abstract class AbstractJsonableObject implements Jsonable {
/** Converts an Object to a JsonElement. */
private static JsonElement toJsonElement(String name, Member member, Object object) {
if (object instanceof Jsonable) {
Jsonable jsonable = (Jsonable) object;
if (object instanceof Jsonable jsonable) {
verifyAllowedJsonKeyName(name, member, jsonable.getClass());
return jsonable.toJson();
}
@@ -414,8 +408,7 @@ abstract class AbstractJsonableObject implements Jsonable {
object, "Member '%s' is null. If you want an optional member - use Optional", member);
// We ignore any Optional that are empty, as if they didn't exist at all
if (object instanceof Optional) {
Optional<?> optional = (Optional<?>) object;
if (object instanceof Optional<?> optional) {
if (optional.isEmpty()) {
return;
}

View File

@@ -79,7 +79,7 @@ public class RdapIcannStandardInformation {
/**
* Required by ICANN RDAP Profile section 1.4.9, as corrected by Gustavo Lozano of ICANN.
*
* Also mentioned in the RDAP Technical Implementation Guide 3.6.
* <p>Also mentioned in the RDAP Technical Implementation Guide 3.6.
*
* @see <a href="http://mm.icann.org/pipermail/gtld-tech/2016-October/000822.html">Questions about
* the ICANN RDAP Profile</a>
@@ -95,7 +95,7 @@ public class RdapIcannStandardInformation {
/**
* Required by ICANN RDAP Profile section 1.4.8, as corrected by Gustavo Lozano of ICANN.
*
* Also mentioned in the RDAP Technical Implementation Guide 3.5.
* <p>Also mentioned in the RDAP Technical Implementation Guide 3.5.
*
* @see <a href="http://mm.icann.org/pipermail/gtld-tech/2016-October/000822.html">Questions about
* the ICANN RDAP Profile</a>

View File

@@ -808,17 +808,12 @@ public class RdapJsonFormatter {
/** Converts a domain registry contact type into a role as defined by RFC 9083. */
private static RdapEntity.Role convertContactTypeToRdapRole(DesignatedContact.Type contactType) {
switch (contactType) {
case REGISTRANT:
return RdapEntity.Role.REGISTRANT;
case TECH:
return RdapEntity.Role.TECH;
case BILLING:
return RdapEntity.Role.BILLING;
case ADMIN:
return RdapEntity.Role.ADMIN;
}
throw new AssertionError();
return switch (contactType) {
case REGISTRANT -> RdapEntity.Role.REGISTRANT;
case TECH -> RdapEntity.Role.TECH;
case BILLING -> RdapEntity.Role.BILLING;
case ADMIN -> RdapEntity.Role.ADMIN;
};
}
/**

View File

@@ -47,7 +47,7 @@ final class RdapObjectClasses {
/**
* Temporary implementation of VCards.
*
* Will create a better implementation soon.
* <p>Will create a better implementation soon.
*/
@RestrictJsonNames({})
@AutoValue
@@ -146,7 +146,7 @@ final class RdapObjectClasses {
/**
* An object that can be used to create a TopLevelReply.
*
* All Actions need to return an object of this type.
* <p>All Actions need to return an object of this type.
*/
abstract static class ReplyPayloadBase extends AbstractJsonableObject {
final BoilerplateType boilerplateType;
@@ -173,15 +173,14 @@ final class RdapObjectClasses {
@JsonableElement("notices[]") abstract Notice aTosNotice();
@JsonableElement("notices") ImmutableList<Notice> boilerplateNotices() {
switch (aAreplyObject().boilerplateType) {
case DOMAIN:
return RdapIcannStandardInformation.domainBoilerplateNotices;
case NAMESERVER:
case ENTITY:
return RdapIcannStandardInformation.nameserverAndEntityBoilerplateNotices;
default: // things other than domains, nameservers and entities do not yet have boilerplate
return ImmutableList.of();
}
return switch (aAreplyObject().boilerplateType) {
case DOMAIN -> RdapIcannStandardInformation.domainBoilerplateNotices;
case NAMESERVER, ENTITY ->
RdapIcannStandardInformation.nameserverAndEntityBoilerplateNotices;
default -> // things other than domains, nameservers and entities do not yet have
// boilerplate
ImmutableList.of();
};
}
static TopLevelReplyObject create(ReplyPayloadBase replyObject, Notice tosNotice) {

View File

@@ -70,8 +70,7 @@ public class RdeFragmenter {
cache.put(WatermarkModePair.create(watermark, RdeMode.FULL), result);
cache.put(WatermarkModePair.create(watermark, RdeMode.THIN), result);
return result;
} else if (resource instanceof Host) {
Host host = (Host) resource;
} else if (resource instanceof Host host) {
result =
Optional.of(
host.isSubordinate()

View File

@@ -19,8 +19,9 @@ import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.ImmutableMap;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.State;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registrar.RegistrarBase;
import google.registry.model.registrar.RegistrarBase.State;
import google.registry.xjc.contact.XjcContactE164Type;
import google.registry.xjc.rderegistrar.XjcRdeRegistrar;
import google.registry.xjc.rderegistrar.XjcRdeRegistrarAddrType;
@@ -40,7 +41,7 @@ final class RegistrarToXjcConverter {
private static final String UNKNOWN_CC = "US";
/** A conversion map between internal Registrar states and external RDE states. */
private static final ImmutableMap<Registrar.State, XjcRdeRegistrarStatusType>
private static final ImmutableMap<RegistrarBase.State, XjcRdeRegistrarStatusType>
REGISTRAR_STATUS_CONVERSIONS =
ImmutableMap.of(
State.ACTIVE, XjcRdeRegistrarStatusType.OK,

View File

@@ -97,8 +97,7 @@ public final class CopyDetailReportsAction implements Runnable {
response.setPayload(String.format("Failure, encountered %s", e.getMessage()));
return;
}
ImmutableMap.Builder<String, Throwable> copyErrorsBuilder =
new ImmutableMap.Builder<String, Throwable>();
ImmutableMap.Builder<String, Throwable> copyErrorsBuilder = new ImmutableMap.Builder<>();
for (String detailReportName : detailReportObjectNames) {
// The standard report format is "invoice_details_yyyy-MM_registrarId_tld.csv
// TODO(larryruili): Determine a safer way of enforcing this.

View File

@@ -97,22 +97,22 @@ public class PublishInvoicesAction implements Runnable {
Job job = dataflow.projects().locations().jobs().get(projectId, jobRegion, jobId).execute();
String state = job.getCurrentState();
switch (state) {
case JOB_DONE:
case JOB_DONE -> {
logger.atInfo().log("Dataflow job %s finished successfully, publishing results.", jobId);
response.setStatus(SC_OK);
enqueueCopyDetailReportsTask();
emailUtils.emailOverallInvoice();
break;
case JOB_FAILED:
}
case JOB_FAILED -> {
logger.atSevere().log("Dataflow job %s finished unsuccessfully.", jobId);
response.setStatus(SC_NO_CONTENT);
emailUtils.sendAlertEmail(
String.format("Dataflow job %s ended in status failure.", jobId));
break;
default:
}
default -> {
logger.atInfo().log("Job in non-terminal state %s, retrying:", state);
response.setStatus(SC_SERVICE_UNAVAILABLE);
break;
}
}
} catch (IOException e) {
emailUtils.sendAlertEmail(String.format("Publish action failed due to %s", e.getMessage()));

View File

@@ -79,7 +79,7 @@ public class IcannHttpReporter {
/** Uploads {@code reportBytes} to ICANN, returning whether or not it succeeded. */
public boolean send(byte[] reportBytes, String reportFilename)
throws GeneralSecurityException, XmlException, IOException {
throws GeneralSecurityException, IOException {
validateReportFilename(reportFilename);
URL uploadUrl = makeUrl(reportFilename);
logger.atInfo().log(
@@ -153,17 +153,15 @@ public class IcannHttpReporter {
}
private String getUrlPrefix(ReportType reportType) {
switch (reportType) {
case TRANSACTIONS:
return icannTransactionsUrl;
case ACTIVITY:
return icannActivityUrl;
default:
throw new IllegalStateException(
String.format(
"Received invalid reportTypes! Expected ACTIVITY or TRANSACTIONS, got %s.",
reportType));
}
return switch (reportType) {
case TRANSACTIONS -> icannTransactionsUrl;
case ACTIVITY -> icannActivityUrl;
default ->
throw new IllegalStateException(
String.format(
"Received invalid reportTypes! Expected ACTIVITY or TRANSACTIONS, got %s.",
reportType));
};
}
}

View File

@@ -107,7 +107,7 @@ public class PublishSpec11ReportAction implements Runnable {
Job job = dataflow.projects().locations().jobs().get(projectId, jobRegion, jobId).execute();
String state = job.getCurrentState();
switch (state) {
case JOB_DONE:
case JOB_DONE -> {
logger.atInfo().log("Dataflow job %s finished successfully, publishing results.", jobId);
response.setStatus(SC_OK);
if (shouldSendMonthlySpec11Email()) {
@@ -125,18 +125,18 @@ public class PublishSpec11ReportAction implements Runnable {
response.setStatus(SC_NO_CONTENT);
}
}
break;
case JOB_FAILED:
}
case JOB_FAILED -> {
logger.atSevere().log("Dataflow job %s finished unsuccessfully.", jobId);
response.setStatus(SC_NO_CONTENT);
emailUtils.sendAlertEmail(
String.format("Spec11 Dataflow Pipeline Failure %s", date),
String.format("Spec11 %s job %s ended in status failure.", date, jobId));
break;
default:
}
default -> {
logger.atInfo().log("Job in non-terminal state %s, retrying:", state);
response.setStatus(SC_SERVICE_UNAVAILABLE);
break;
}
}
} catch (IOException | JSONException e) {
logger.atSevere().withCause(e).log("Failed to publish Spec11 reports.");

View File

@@ -37,8 +37,7 @@ public final class Modules {
static UrlConnectionService provideUrlConnectionService() {
return url -> {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
if (connection instanceof HttpsURLConnection) {
HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
if (connection instanceof HttpsURLConnection httpsConnection) {
SSLContext tls13Context = SSLContext.getInstance("TLSv1.3");
tls13Context.init(null, null, null);
httpsConnection.setSSLSocketFactory(tls13Context.getSocketFactory());

View File

@@ -28,6 +28,8 @@ import org.joda.time.DateTime;
*/
public interface Response {
void sendRedirect(String url) throws IOException;
/** Sets the HTTP status code. */
void setStatus(int status);

View File

@@ -32,6 +32,11 @@ public final class ResponseImpl implements Response {
this.rsp = rsp;
}
@Override
public void sendRedirect(String url) throws IOException {
rsp.sendRedirect(url);
}
@Override
public void setStatus(int status) {
rsp.setStatus(status);

View File

@@ -28,7 +28,7 @@ import dagger.Lazy;
import google.registry.config.RegistryConfig.Config;
import google.registry.groups.GroupsConnection;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.State;
import google.registry.model.registrar.RegistrarBase.State;
import google.registry.model.registrar.RegistrarPoc;
import java.util.Optional;
import javax.annotation.concurrent.Immutable;

View File

@@ -88,7 +88,7 @@ public class RequestAuthenticator {
AuthResult authResult;
switch (authMethod) {
// API-based user authentication mechanisms, such as OIDC.
case API:
case API -> {
for (AuthenticationMechanism authMechanism : apiAuthenticationMechanisms) {
authResult = authMechanism.authenticate(req);
if (authResult.isAuthenticated()) {
@@ -97,15 +97,15 @@ public class RequestAuthenticator {
return authResult;
}
}
break;
}
// Legacy authentication via UserService
case LEGACY:
case LEGACY -> {
authResult = legacyAuthenticationMechanism.authenticate(req);
if (authResult.isAuthenticated()) {
logger.atInfo().log("Authenticated via legacy auth: %s", authResult);
return authResult;
}
break;
}
}
}
logger.atInfo().log("No authentication found.");

View File

@@ -77,13 +77,12 @@ public final class Marksdb {
"No OpenPGP packets found in signature.\n%s",
dumpHex(signature)));
}
if (!(object instanceof PGPSignatureList)) {
if (!(object instanceof PGPSignatureList sigs)) {
throw new SignatureException(String.format(
"Expected PGPSignatureList packet but got %s\n%s",
object.getClass().getSimpleName(),
dumpHex(signature)));
}
PGPSignatureList sigs = (PGPSignatureList) object;
if (sigs.isEmpty()) {
throw new SignatureException(String.format(
"PGPSignatureList doesn't have a PGPSignature.\n%s",

View File

@@ -80,7 +80,7 @@ public final class TmchCertificateAuthority {
private static final LoadingCache<TmchCaMode, X509CRL> CRL_CACHE =
CacheUtils.newCacheBuilder(getSingletonCacheRefreshDuration())
.build(
new CacheLoader<TmchCaMode, X509CRL>() {
new CacheLoader<>() {
@Override
public X509CRL load(final TmchCaMode tmchCaMode) throws GeneralSecurityException {
Optional<TmchCrl> storedCrl = TmchCrl.get();

View File

@@ -158,11 +158,9 @@ public class TmchXmlSignature {
return null;
}
for (Object keyInfoChild : keyInfo.getContent()) {
if (keyInfoChild instanceof X509Data) {
X509Data x509Data = (X509Data) keyInfoChild;
if (keyInfoChild instanceof X509Data x509Data) {
for (Object x509DataChild : x509Data.getContent()) {
if (x509DataChild instanceof X509Certificate) {
X509Certificate cert = (X509Certificate) x509DataChild;
if (x509DataChild instanceof X509Certificate cert) {
try {
tmchCertificateAuthority.verify(cert);
} catch (SignatureException e) {

View File

@@ -150,7 +150,7 @@ public class AuthModule {
@Nullable @Config("credentialFilePath") String credentialFilePath) {
try {
if (credentialFilePath != null) {
return new String(Files.readAllBytes(Paths.get(credentialFilePath)), UTF_8);
return Files.readString(Paths.get(credentialFilePath));
} else {
return new Gson()
.toJson(

View File

@@ -15,7 +15,6 @@
package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Predicates.isNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.base.Verify.verify;
import static com.google.common.collect.ImmutableList.toImmutableList;
@@ -31,11 +30,12 @@ import com.google.common.collect.ImmutableSet;
import google.registry.flows.certs.CertificateChecker;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registrar.RegistrarBase;
import google.registry.tools.params.KeyValueMapParameter.CurrencyUnitToStringMap;
import google.registry.tools.params.OptionalLongParameter;
import google.registry.tools.params.OptionalPhoneNumberParameter;
import google.registry.tools.params.OptionalStringParameter;
import google.registry.tools.params.PathParameter;
import google.registry.tools.params.PathParameter.InputFile;
import google.registry.tools.params.StringListParameter;
import google.registry.util.CidrAddressBlock;
import java.nio.file.Files;
@@ -45,6 +45,7 @@ import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.inject.Inject;
@@ -59,16 +60,12 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
@Parameter(description = "Client identifier of the registrar account", required = true)
List<String> mainParameters;
@Parameter(
names = "--registrar_type",
description = "Type of the registrar")
Registrar.Type registrarType;
@Parameter(names = "--registrar_type", description = "Type of the registrar")
RegistrarBase.Type registrarType;
@Nullable
@Parameter(
names = "--registrar_state",
description = "Initial state of the registrar")
Registrar.State registrarState;
@Parameter(names = "--registrar_state", description = "Initial state of the registrar")
RegistrarBase.State registrarState;
@Parameter(
names = "--allowed_tlds",
@@ -132,7 +129,7 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
@Parameter(
names = "--cert_file",
description = "File containing client certificate (X.509 PEM)",
validateWith = PathParameter.InputFile.class)
validateWith = InputFile.class)
Path clientCertificateFilename;
@Parameter(
@@ -146,7 +143,7 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
@Parameter(
names = "--failover_cert_file",
description = "File containing failover client certificate (X.509 PEM)",
validateWith = PathParameter.InputFile.class)
validateWith = InputFile.class)
Path failoverClientCertificateFilename;
@Parameter(
@@ -296,24 +293,13 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
// On creates, fall back to ICANN referral email (if present).
builder.setEmailAddress(icannReferralEmail);
}
if (url != null) {
builder.setUrl(url.orElse(null));
}
if (phone != null) {
builder.setPhoneNumber(phone.orElse(null));
}
if (fax != null) {
builder.setFaxNumber(fax.orElse(null));
}
if (registrarType != null) {
builder.setType(registrarType);
}
if (registrarState != null) {
builder.setState(registrarState);
}
if (driveFolderId != null) {
builder.setDriveFolderId(driveFolderId.orElse(null));
}
Optional.ofNullable(url).ifPresent(u -> builder.setUrl(u.orElse(null)));
Optional.ofNullable(phone).ifPresent(p -> builder.setPhoneNumber(p.orElse(null)));
Optional.ofNullable(fax).ifPresent(f -> builder.setFaxNumber(f.orElse(null)));
Optional.ofNullable(registrarType).ifPresent(builder::setType);
Optional.ofNullable(registrarState).ifPresent(builder::setState);
Optional.ofNullable(driveFolderId).ifPresent(d -> builder.setDriveFolderId(d.orElse(null)));
if (!allowedTlds.isEmpty() || !addAllowedTlds.isEmpty()) {
checkModifyAllowedTlds(oldRegistrar);
}
@@ -341,7 +327,7 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
ipAllowList.stream().map(CidrAddressBlock::create).collect(toImmutableList()));
}
if (clientCertificateFilename != null) {
String asciiCert = new String(Files.readAllBytes(clientCertificateFilename), US_ASCII);
String asciiCert = Files.readString(clientCertificateFilename, US_ASCII);
// An empty certificate file is allowed in order to provide a functionality for removing an
// existing certificate without providing a replacement. An uploaded empty certificate file
// will prevent the registrar from being able to establish EPP connections.
@@ -364,16 +350,13 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
}
if (failoverClientCertificateFilename != null) {
String asciiCert =
new String(Files.readAllBytes(failoverClientCertificateFilename), US_ASCII);
String asciiCert = Files.readString(failoverClientCertificateFilename, US_ASCII);
if (!asciiCert.equals("")) {
certificateChecker.validateCertificate(asciiCert);
}
builder.setFailoverClientCertificate(asciiCert, now);
}
if (ianaId != null) {
builder.setIanaIdentifier(ianaId.orElse(null));
}
Optional.ofNullable(ianaId).ifPresent(i -> builder.setIanaIdentifier(i.orElse(null)));
Optional.ofNullable(poNumber).ifPresent(builder::setPoNumber);
if (billingAccountMap != null) {
LinkedHashMap<CurrencyUnit, String> newBillingAccountMap = new LinkedHashMap<>();
@@ -387,8 +370,8 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
}
List<Object> streetAddressFields = Arrays.asList(street, city, state, zip, countryCode);
checkArgument(
streetAddressFields.stream().anyMatch(isNull())
== streetAddressFields.stream().allMatch(isNull()),
streetAddressFields.stream().anyMatch(Objects::isNull)
== streetAddressFields.stream().allMatch(Objects::isNull),
"Must specify all fields of address");
if (street != null) {
// We always set the localized address for now. That should be safe to do since it supports

View File

@@ -18,7 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.collect.Iterables.getOnlyElement;
import static google.registry.model.registrar.Registrar.State.ACTIVE;
import static google.registry.model.registrar.RegistrarBase.State.ACTIVE;
import static google.registry.tools.RegistryToolEnvironment.PRODUCTION;
import static google.registry.tools.RegistryToolEnvironment.SANDBOX;
import static google.registry.tools.RegistryToolEnvironment.UNITTEST;

View File

@@ -61,14 +61,7 @@ abstract class EppToolCommand extends ConfirmingCommand implements CommandWithCo
private ServiceConnection connection;
static class XmlEppParameters {
final String clientId;
final String xml;
XmlEppParameters(String clientId, String xml) {
this.clientId = clientId;
this.xml = xml;
}
record XmlEppParameters(String clientId, String xml) {
@Override
public String toString() {

View File

@@ -62,7 +62,7 @@ final class GenerateDnsReportCommand implements Command {
@Override
public void run() throws Exception {
assertTldExists(tld);
Files.write(output, new Generator().generate().getBytes(US_ASCII));
Files.writeString(output, new Generator().generate(), US_ASCII);
}
private class Generator {

View File

@@ -55,58 +55,42 @@ final class GetKeyringSecretCommand implements Command {
Security.addProvider(new BouncyCastleProvider());
switch (keyringKeyName) {
case BRDA_RECEIVER_PUBLIC_KEY:
out.write(KeySerializer.serializePublicKey(keyring.getBrdaReceiverKey()));
break;
case BRDA_SIGNING_KEY_PAIR:
out.write(KeySerializer.serializeKeyPair(keyring.getBrdaSigningKey()));
break;
case BRDA_SIGNING_PUBLIC_KEY:
out.write(KeySerializer.serializePublicKey(keyring.getBrdaSigningKey().getPublicKey()));
break;
case BSA_API_KEY:
out.write(KeySerializer.serializeString(keyring.getBsaApiKey()));
break;
case ICANN_REPORTING_PASSWORD:
out.write(KeySerializer.serializeString(keyring.getIcannReportingPassword()));
break;
case SAFE_BROWSING_API_KEY:
out.write(KeySerializer.serializeString(keyring.getSafeBrowsingAPIKey()));
break;
case MARKSDB_DNL_LOGIN_AND_PASSWORD:
out.write(KeySerializer.serializeString(keyring.getMarksdbDnlLoginAndPassword()));
break;
case MARKSDB_LORDN_PASSWORD:
out.write(KeySerializer.serializeString(keyring.getMarksdbLordnPassword()));
break;
case MARKSDB_SMDRL_LOGIN_AND_PASSWORD:
out.write(KeySerializer.serializeString(keyring.getMarksdbSmdrlLoginAndPassword()));
break;
case RDE_RECEIVER_PUBLIC_KEY:
out.write(KeySerializer.serializePublicKey(keyring.getRdeReceiverKey()));
break;
case RDE_SIGNING_KEY_PAIR:
out.write(KeySerializer.serializeKeyPair(keyring.getRdeSigningKey()));
break;
case RDE_SIGNING_PUBLIC_KEY:
out.write(KeySerializer.serializePublicKey(keyring.getRdeSigningKey().getPublicKey()));
break;
case RDE_SSH_CLIENT_PRIVATE_KEY:
out.write(KeySerializer.serializeString(keyring.getRdeSshClientPrivateKey()));
break;
case RDE_SSH_CLIENT_PUBLIC_KEY:
out.write(KeySerializer.serializeString(keyring.getRdeSshClientPublicKey()));
break;
case RDE_STAGING_KEY_PAIR:
// Note that we're saving a key pair rather than just the private key because we can't
// serialize a private key on its own. See {@link KeySerializer}.
out.write(KeySerializer.serializeKeyPair(
new PGPKeyPair(
keyring.getRdeStagingEncryptionKey(), keyring.getRdeStagingDecryptionKey())));
break;
case RDE_STAGING_PUBLIC_KEY:
out.write(KeySerializer.serializePublicKey(keyring.getRdeStagingEncryptionKey()));
break;
case BRDA_RECEIVER_PUBLIC_KEY ->
out.write(KeySerializer.serializePublicKey(keyring.getBrdaReceiverKey()));
case BRDA_SIGNING_KEY_PAIR ->
out.write(KeySerializer.serializeKeyPair(keyring.getBrdaSigningKey()));
case BRDA_SIGNING_PUBLIC_KEY ->
out.write(KeySerializer.serializePublicKey(keyring.getBrdaSigningKey().getPublicKey()));
case BSA_API_KEY -> out.write(KeySerializer.serializeString(keyring.getBsaApiKey()));
case ICANN_REPORTING_PASSWORD ->
out.write(KeySerializer.serializeString(keyring.getIcannReportingPassword()));
case SAFE_BROWSING_API_KEY ->
out.write(KeySerializer.serializeString(keyring.getSafeBrowsingAPIKey()));
case MARKSDB_DNL_LOGIN_AND_PASSWORD ->
out.write(KeySerializer.serializeString(keyring.getMarksdbDnlLoginAndPassword()));
case MARKSDB_LORDN_PASSWORD ->
out.write(KeySerializer.serializeString(keyring.getMarksdbLordnPassword()));
case MARKSDB_SMDRL_LOGIN_AND_PASSWORD ->
out.write(KeySerializer.serializeString(keyring.getMarksdbSmdrlLoginAndPassword()));
case RDE_RECEIVER_PUBLIC_KEY ->
out.write(KeySerializer.serializePublicKey(keyring.getRdeReceiverKey()));
case RDE_SIGNING_KEY_PAIR ->
out.write(KeySerializer.serializeKeyPair(keyring.getRdeSigningKey()));
case RDE_SIGNING_PUBLIC_KEY ->
out.write(KeySerializer.serializePublicKey(keyring.getRdeSigningKey().getPublicKey()));
case RDE_SSH_CLIENT_PRIVATE_KEY ->
out.write(KeySerializer.serializeString(keyring.getRdeSshClientPrivateKey()));
case RDE_SSH_CLIENT_PUBLIC_KEY ->
out.write(KeySerializer.serializeString(keyring.getRdeSshClientPublicKey()));
case RDE_STAGING_KEY_PAIR ->
// Note that we're saving a key pair rather than just the private key because we can't
// serialize a private key on its own. See {@link KeySerializer}.
out.write(
KeySerializer.serializeKeyPair(
new PGPKeyPair(
keyring.getRdeStagingEncryptionKey(), keyring.getRdeStagingDecryptionKey())));
case RDE_STAGING_PUBLIC_KEY ->
out.write(KeySerializer.serializePublicKey(keyring.getRdeStagingEncryptionKey()));
}
}
}

View File

@@ -76,7 +76,7 @@ final class GhostrydeCommand implements Command {
Provider<PGPPrivateKey> rdeStagingDecryptionKey;
@Override
public final void run() throws Exception {
public void run() throws Exception {
checkArgument(encrypt ^ decrypt, "Please specify either --encrypt or --decrypt");
if (encrypt) {
checkArgumentNotNull(output, "--output path is required in --encrypt mode");

View File

@@ -54,7 +54,7 @@ public class GsonUtils {
if (!GsonPostProcessable.class.isAssignableFrom(type.getRawType())) {
return originalAdapter;
}
return new TypeAdapter<T>() {
return new TypeAdapter<>() {
@Override
public void write(JsonWriter out, T value) throws IOException {
originalAdapter.write(out, value);

View File

@@ -98,10 +98,9 @@ abstract class ListObjectsCommand implements CommandWithConnection {
if (obj == null) {
throw new VerifyException("Server returned no status");
}
if (!(obj instanceof String)) {
if (!(obj instanceof String status)) {
throw new VerifyException("Server returned non-string status");
}
String status = (String) obj;
// Handle errors.
if (status.equals("error")) {
obj = responseMap.get("error");

View File

@@ -52,12 +52,22 @@ final class LoginCommand implements Command {
url -> {
int remotePort = forwardingServerReceiver.getRemotePort();
System.out.printf(
"Please first run the following command in a separate terminal on your local "
+ "host:\n\n ssh -L %s:localhost:%s %s\n\n",
"""
Please first run the following command in a separate terminal on your local\
host:
ssh -L %s:localhost:%s %s
""",
port, remotePort, remoteHost);
System.out.printf(
"Please then open the following URL in your local browser and follow the"
+ " instructions:\n\n %s\n\n",
"""
Please then open the following URL in your local browser and follow the\
instructions:
%s
""",
url);
});
} else {

View File

@@ -159,15 +159,18 @@ public abstract class MutatingCommand extends ConfirmingCommand {
? ((ImmutableObject) existingEntity.get()).toDiffableFieldMap()
: ImmutableMap.of()));
switch (change.type) {
case CREATE:
case CREATE -> {
tm().insert(change.newEntity);
return;
case UPDATE:
}
case UPDATE -> {
tm().update(change.newEntity);
return;
case DELETE:
}
case DELETE -> {
tm().delete(change.vKey);
return;
}
}
throw new UnsupportedOperationException("Unknown entity change type: " + change.type);
}

View File

@@ -28,7 +28,7 @@ import javax.inject.Inject;
final class PendingEscrowCommand implements Command {
private static final Ordering<PendingDeposit> SORTER =
new Ordering<PendingDeposit>() {
new Ordering<>() {
@Override
public int compare(PendingDeposit left, PendingDeposit right) {
return ComparisonChain.start()
@@ -36,7 +36,8 @@ final class PendingEscrowCommand implements Command {
.compare(left.mode(), right.mode())
.compare(left.watermark(), right.watermark())
.result();
}};
}
};
@Inject
PendingDepositChecker checker;

View File

@@ -20,7 +20,6 @@ import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.util.CollectionUtils.nullToEmpty;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
@@ -30,6 +29,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.registrar.RegistrarPocBase;
import google.registry.tools.params.OptionalPhoneNumberParameter;
import google.registry.tools.params.PathParameter;
import google.registry.tools.params.StringListParameter;
@@ -167,7 +167,7 @@ final class RegistrarPocCommand extends MutatingCommand {
private static final ImmutableSet<Mode> MODES_REQUIRING_CONTACT_SYNC =
ImmutableSet.of(Mode.CREATE, Mode.UPDATE, Mode.DELETE);
@Nullable private ImmutableSet<RegistrarPoc.Type> contactTypes;
@Nullable private ImmutableSet<RegistrarPocBase.Type> contactTypes;
@Override
protected void init() throws Exception {
@@ -183,7 +183,7 @@ final class RegistrarPocCommand extends MutatingCommand {
} else {
contactTypes =
contactTypeNames.stream()
.map(Enums.stringConverter(RegistrarPoc.Type.class))
.map(Enums.stringConverter(RegistrarPocBase.Type.class))
.collect(toImmutableSet());
}
ImmutableSet<RegistrarPoc> contacts = registrar.getContacts();
@@ -193,16 +193,14 @@ final class RegistrarPocCommand extends MutatingCommand {
}
RegistrarPoc oldContact;
switch (mode) {
case LIST:
listContacts(contacts);
break;
case CREATE:
case LIST -> listContacts(contacts);
case CREATE -> {
stageEntityChange(null, createContact(registrar));
if (visibleInDomainWhoisAsAbuse != null && visibleInDomainWhoisAsAbuse) {
unsetOtherWhoisAbuseFlags(contacts, null);
}
break;
case UPDATE:
}
case UPDATE -> {
oldContact =
checkNotNull(
contactsMap.get(checkNotNull(email, "--email is required when --mode=UPDATE")),
@@ -218,8 +216,8 @@ final class RegistrarPocCommand extends MutatingCommand {
if (visibleInDomainWhoisAsAbuse != null && visibleInDomainWhoisAsAbuse) {
unsetOtherWhoisAbuseFlags(contacts, oldContact.getEmailAddress());
}
break;
case DELETE:
}
case DELETE -> {
oldContact =
checkNotNull(
contactsMap.get(checkNotNull(email, "--email is required when --mode=DELETE")),
@@ -229,9 +227,8 @@ final class RegistrarPocCommand extends MutatingCommand {
!oldContact.getVisibleInDomainWhoisAsAbuse(),
"Cannot delete the domain WHOIS abuse contact; set the flag on another contact first");
stageEntityChange(oldContact, null);
break;
default:
throw new AssertionError();
}
default -> throw new AssertionError();
}
if (MODES_REQUIRING_CONTACT_SYNC.contains(mode)) {
stageEntityChange(registrar, registrar.asBuilder().setContactsRequireSyncing(true).build());
@@ -243,7 +240,7 @@ final class RegistrarPocCommand extends MutatingCommand {
for (RegistrarPoc c : contacts) {
result.add(c.toStringMultilinePlainText());
}
Files.write(output, Joiner.on('\n').join(result).getBytes(UTF_8));
Files.writeString(output, Joiner.on('\n').join(result));
}
private RegistrarPoc createContact(Registrar registrar) {

View File

@@ -191,9 +191,10 @@ final class RegistryCli implements CommandRunner {
e.printStackTrace();
System.err.println("===================================================================");
System.err.println(
"This error is likely the result of having another instance of\n"
+ "nomulus running at the same time. Check your system, shut down\n"
+ "the other instance, and try again.");
"""
This error is likely the result of having another instance of
nomulus running at the same time. Check your system, shut down
the other instance, and try again.""");
System.err.println("===================================================================");
} else {
throw e;

View File

@@ -163,18 +163,12 @@ public class ServiceConnection {
}
public static URL getServer(Service service) {
switch (service) {
case DEFAULT:
return RegistryConfig.getDefaultServer();
case TOOLS:
return RegistryConfig.getToolsServer();
case BACKEND:
return RegistryConfig.getBackendServer();
case BSA:
return RegistryConfig.getBsaServer();
case PUBAPI:
return RegistryConfig.getPubapiServer();
}
throw new IllegalStateException("Unknown service: " + service);
return switch (service) {
case DEFAULT -> RegistryConfig.getDefaultServer();
case TOOLS -> RegistryConfig.getToolsServer();
case BACKEND -> RegistryConfig.getBackendServer();
case BSA -> RegistryConfig.getBsaServer();
case PUBAPI -> RegistryConfig.getPubapiServer();
};
}
}

View File

@@ -54,62 +54,48 @@ final class UpdateKeyringSecretCommand implements Command {
byte[] input = Files.readAllBytes(inputPath);
switch (keyringKeyName) {
case BRDA_RECEIVER_PUBLIC_KEY:
secretManagerKeyringUpdater.setBrdaReceiverPublicKey(deserializePublicKey(input));
break;
case BRDA_SIGNING_KEY_PAIR:
secretManagerKeyringUpdater.setBrdaSigningKey(deserializeKeyPair(input));
break;
case BRDA_SIGNING_PUBLIC_KEY:
throw new IllegalArgumentException(
"Can't update BRDA_SIGNING_PUBLIC_KEY directly."
+ " Must update public and private keys together using BRDA_SIGNING_KEY_PAIR.");
case BSA_API_KEY:
secretManagerKeyringUpdater.setBsaApiKey(deserializeString(input));
break;
case ICANN_REPORTING_PASSWORD:
secretManagerKeyringUpdater.setIcannReportingPassword(deserializeString(input));
break;
case MARKSDB_DNL_LOGIN_AND_PASSWORD:
secretManagerKeyringUpdater.setMarksdbDnlLoginAndPassword(deserializeString(input));
break;
case MARKSDB_LORDN_PASSWORD:
secretManagerKeyringUpdater.setMarksdbLordnPassword(deserializeString(input));
break;
case MARKSDB_SMDRL_LOGIN_AND_PASSWORD:
secretManagerKeyringUpdater.setMarksdbSmdrlLoginAndPassword(deserializeString(input));
break;
case RDE_RECEIVER_PUBLIC_KEY:
secretManagerKeyringUpdater.setRdeReceiverPublicKey(deserializePublicKey(input));
break;
case RDE_SIGNING_KEY_PAIR:
secretManagerKeyringUpdater.setRdeSigningKey(deserializeKeyPair(input));
break;
case RDE_SIGNING_PUBLIC_KEY:
throw new IllegalArgumentException(
"Can't update RDE_SIGNING_PUBLIC_KEY directly."
+ " Must update public and private keys together using RDE_SIGNING_KEY_PAIR.");
// Note that RDE_SSH_CLIENT public / private keys are slightly different than other key pairs,
// since they are just regular strings rather than {@link PGPKeyPair}s (because OpenSSH
// doesn't use PGP-style keys)
//
// Hence we can and need to update the private and public keys individually.
case RDE_SSH_CLIENT_PRIVATE_KEY:
secretManagerKeyringUpdater.setRdeSshClientPrivateKey(deserializeString(input));
break;
case RDE_SSH_CLIENT_PUBLIC_KEY:
secretManagerKeyringUpdater.setRdeSshClientPublicKey(deserializeString(input));
break;
case RDE_STAGING_KEY_PAIR:
secretManagerKeyringUpdater.setRdeStagingKey(deserializeKeyPair(input));
break;
case SAFE_BROWSING_API_KEY:
secretManagerKeyringUpdater.setSafeBrowsingAPIKey(deserializeString(input));
break;
case RDE_STAGING_PUBLIC_KEY:
throw new IllegalArgumentException(
"Can't update RDE_STAGING_PUBLIC_KEY directly."
+ " Must update public and private keys together using RDE_STAGING_KEY_PAIR.");
case BRDA_RECEIVER_PUBLIC_KEY ->
secretManagerKeyringUpdater.setBrdaReceiverPublicKey(deserializePublicKey(input));
case BRDA_SIGNING_KEY_PAIR ->
secretManagerKeyringUpdater.setBrdaSigningKey(deserializeKeyPair(input));
case BRDA_SIGNING_PUBLIC_KEY ->
throw new IllegalArgumentException(
"Can't update BRDA_SIGNING_PUBLIC_KEY directly."
+ " Must update public and private keys together using BRDA_SIGNING_KEY_PAIR.");
case BSA_API_KEY -> secretManagerKeyringUpdater.setBsaApiKey(deserializeString(input));
case ICANN_REPORTING_PASSWORD ->
secretManagerKeyringUpdater.setIcannReportingPassword(deserializeString(input));
case MARKSDB_DNL_LOGIN_AND_PASSWORD ->
secretManagerKeyringUpdater.setMarksdbDnlLoginAndPassword(deserializeString(input));
case MARKSDB_LORDN_PASSWORD ->
secretManagerKeyringUpdater.setMarksdbLordnPassword(deserializeString(input));
case MARKSDB_SMDRL_LOGIN_AND_PASSWORD ->
secretManagerKeyringUpdater.setMarksdbSmdrlLoginAndPassword(deserializeString(input));
case RDE_RECEIVER_PUBLIC_KEY ->
secretManagerKeyringUpdater.setRdeReceiverPublicKey(deserializePublicKey(input));
case RDE_SIGNING_KEY_PAIR ->
secretManagerKeyringUpdater.setRdeSigningKey(deserializeKeyPair(input));
case RDE_SIGNING_PUBLIC_KEY ->
throw new IllegalArgumentException(
"Can't update RDE_SIGNING_PUBLIC_KEY directly."
+ " Must update public and private keys together using RDE_SIGNING_KEY_PAIR.");
// Note that RDE_SSH_CLIENT public / private keys are slightly different than other key
// pairs, since they are just regular strings rather than {@link PGPKeyPair}s (because
// OpenSSH doesn't use PGP-style keys)
//
// Hence we can and need to update the private and public keys individually.
case RDE_SSH_CLIENT_PRIVATE_KEY ->
secretManagerKeyringUpdater.setRdeSshClientPrivateKey(deserializeString(input));
case RDE_SSH_CLIENT_PUBLIC_KEY ->
secretManagerKeyringUpdater.setRdeSshClientPublicKey(deserializeString(input));
case RDE_STAGING_KEY_PAIR ->
secretManagerKeyringUpdater.setRdeStagingKey(deserializeKeyPair(input));
case SAFE_BROWSING_API_KEY ->
secretManagerKeyringUpdater.setSafeBrowsingAPIKey(deserializeString(input));
case RDE_STAGING_PUBLIC_KEY ->
throw new IllegalArgumentException(
"Can't update RDE_STAGING_PUBLIC_KEY directly."
+ " Must update public and private keys together using RDE_STAGING_KEY_PAIR.");
}
secretManagerKeyringUpdater.update();

View File

@@ -60,11 +60,12 @@ public class RecreateBillingRecurrencesCommand extends ConfirmingCommand {
ImmutableList<BillingRecurrence> newRecurrences =
convertRecurrencesWithoutSaving(existingRecurrences);
return String.format(
"Create new BillingRecurrence(s)?\n"
+ "Existing recurrences:\n"
+ "%s\n"
+ "New recurrences:\n"
+ "%s",
"""
Create new BillingRecurrence(s)?
Existing recurrences:
%s
New recurrences:
%s""",
Joiner.on('\n').join(existingRecurrences), Joiner.on('\n').join(newRecurrences));
});
}

View File

@@ -24,7 +24,7 @@ import google.registry.config.RegistryConfig.Config;
import google.registry.groups.GroupsConnection;
import google.registry.groups.GroupsConnection.Role;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.registrar.RegistrarPocBase;
import google.registry.request.Action;
import google.registry.request.HttpException.BadRequestException;
import google.registry.request.HttpException.InternalServerErrorException;
@@ -64,7 +64,7 @@ public class CreateGroupsAction implements Runnable {
if (registrar == null) {
return;
}
List<RegistrarPoc.Type> types = asList(RegistrarPoc.Type.values());
List<RegistrarPocBase.Type> types = asList(RegistrarPocBase.Type.values());
// Concurrently create the groups for each RegistrarContact.Type, collecting the results from
// each call (which are either an Exception if it failed, or absent() if it succeeded).
List<Optional<Exception>> results =

View File

@@ -29,6 +29,7 @@ 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.model.registrar.RegistrarPocBase;
import google.registry.ui.forms.FormException;
import google.registry.ui.forms.FormField;
import google.registry.ui.forms.FormFieldException;
@@ -90,12 +91,6 @@ public final class RegistrarFormFields {
.matches(ASCII_PATTERN, ASCII_ERROR)
.build();
public static final FormField<String, Registrar.State> STATE_FIELD =
FormField.named("state")
.emptyToNull()
.asEnum(Registrar.State.class)
.build();
public static final FormField<List<String>, Set<String>> ALLOWED_TLDS_FIELD =
FormFields.LABEL.asBuilderNamed("allowedTlds")
.asSet()
@@ -106,14 +101,6 @@ public final class RegistrarFormFields {
.transform(RegistrarFormFields::parseHostname)
.build();
public static final FormField<Boolean, Boolean> BLOCK_PREMIUM_NAMES_FIELD =
FormField.named("blockPremiumNames", Boolean.class)
.build();
public static final FormField<String, String> DRIVE_FOLDER_ID_FIELD =
FormFields.XS_NORMALIZED_STRING.asBuilderNamed("driveFolderId")
.build();
public static final FormField<String, String> CLIENT_CERTIFICATE_HASH_FIELD =
FormField.named("clientCertificateHash")
.emptyToNull()
@@ -217,10 +204,10 @@ public final class RegistrarFormFields {
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 =
public static final FormField<String, Set<RegistrarPocBase.Type>> CONTACT_TYPES =
FormField.named("types")
.uppercased()
.asEnum(RegistrarPoc.Type.class)
.asEnum(RegistrarPocBase.Type.class)
.asSet(Splitter.on(',').omitEmptyStrings().trimResults())
.build();

View File

@@ -90,14 +90,11 @@ public final class SoyTemplateUtils {
private static ImmutableMap<String, String> getCssRenames(URL cssMap, URL cssMapDebug) {
try {
switch (ConsoleDebug.get()) {
case RAW:
return ImmutableMap.of(); // See firstNonNull() above for clarification.
case DEBUG:
return extractCssRenames(Resources.toString(cssMapDebug, UTF_8));
default:
return extractCssRenames(Resources.toString(cssMap, UTF_8));
}
return switch (ConsoleDebug.get()) {
case RAW -> ImmutableMap.of(); // See firstNonNull() above for clarification.
case DEBUG -> extractCssRenames(Resources.toString(cssMapDebug, UTF_8));
default -> extractCssRenames(Resources.toString(cssMap, UTF_8));
};
} catch (IOException e) {
throw new RuntimeException("Failed to load css map", e);
}

View File

@@ -17,10 +17,15 @@ package google.registry.ui.server.console;
import static google.registry.request.Action.Method.GET;
import com.google.api.client.http.HttpStatusCodes;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.User;
import google.registry.request.auth.AuthResult;
import google.registry.security.XsrfTokenManager;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.ui.server.registrar.ConsoleUiAction;
import google.registry.util.RegistryEnvironment;
import jakarta.servlet.http.Cookie;
import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;
@@ -35,11 +40,26 @@ public abstract class ConsoleApiAction implements Runnable {
@Override
public final void run() {
// Shouldn't be even possible because of Auth annotations on the various implementing classes
if (consoleApiParams.authResult().userAuthInfo().get().consoleUser().isEmpty()) {
AuthResult authResult = consoleApiParams.authResult();
if (authResult.userAuthInfo().isEmpty()
|| authResult.userAuthInfo().get().consoleUser().isEmpty()) {
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
return;
}
User user = consoleApiParams.authResult().userAuthInfo().get().consoleUser().get();
// This allows us to enable console to a selected cohort of users with release
// We can ignore it in tests
if (RegistryEnvironment.get() != RegistryEnvironment.UNITTEST
&& !GlobalRole.FTE.equals(user.getUserRoles().getGlobalRole())) {
try {
consoleApiParams.response().sendRedirect(ConsoleUiAction.PATH);
return;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (consoleApiParams.request().getMethod().equals(GET.toString())) {
getHandler(user);
} else {
@@ -75,4 +95,5 @@ public abstract class ConsoleApiAction implements Runnable {
}
return true;
}
}

View File

@@ -24,11 +24,8 @@ import google.registry.model.console.User;
import google.registry.model.domain.Domain;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.UserAuthInfo;
import google.registry.ui.server.registrar.JsonGetAction;
import google.registry.ui.server.registrar.ConsoleApiParams;
import java.util.Optional;
import javax.inject.Inject;
@@ -37,55 +34,41 @@ import javax.inject.Inject;
service = Action.Service.DEFAULT,
path = ConsoleDomainGetAction.PATH,
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public class ConsoleDomainGetAction implements JsonGetAction {
public class ConsoleDomainGetAction extends ConsoleApiAction {
public static final String PATH = "/console-api/domain";
private final AuthResult authResult;
private final Response response;
private final Gson gson;
private final String paramDomain;
@Inject
public ConsoleDomainGetAction(
AuthResult authResult,
Response response,
ConsoleApiParams consoleApiParams,
Gson gson,
@Parameter("consoleDomain") String paramDomain) {
this.authResult = authResult;
this.response = response;
this.gson = gson;
super(consoleApiParams);
this.paramDomain = paramDomain;
this.gson = gson;
}
@Override
public void run() {
if (!authResult.isAuthenticated() || authResult.userAuthInfo().isEmpty()) {
response.setStatus(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
return;
}
UserAuthInfo authInfo = authResult.userAuthInfo().get();
if (authInfo.consoleUser().isEmpty()) {
response.setStatus(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
return;
}
User user = authInfo.consoleUser().get();
protected void getHandler(User user) {
Optional<Domain> possibleDomain =
tm().transact(
() ->
EppResourceUtils.loadByForeignKeyCached(
Domain.class, paramDomain, tm().getTransactionTime()));
if (possibleDomain.isEmpty()) {
response.setStatus(HttpStatusCodes.STATUS_CODE_NOT_FOUND);
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_NOT_FOUND);
return;
}
Domain domain = possibleDomain.get();
if (!user.getUserRoles()
.hasPermission(domain.getCurrentSponsorRegistrarId(), ConsolePermission.DOWNLOAD_DOMAINS)) {
response.setStatus(HttpStatusCodes.STATUS_CODE_NOT_FOUND);
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_NOT_FOUND);
return;
}
response.setStatus(HttpStatusCodes.STATUS_CODE_OK);
response.setPayload(gson.toJson(domain));
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_OK);
consoleApiParams.response().setPayload(gson.toJson(domain));
}
}

View File

@@ -27,10 +27,8 @@ import google.registry.model.console.User;
import google.registry.model.domain.Domain;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthResult;
import google.registry.ui.server.registrar.JsonGetAction;
import google.registry.ui.server.registrar.ConsoleApiParams;
import java.util.List;
import java.util.Optional;
import javax.inject.Inject;
@@ -43,7 +41,7 @@ import org.joda.time.DateTime;
path = ConsoleDomainListAction.PATH,
method = Action.Method.GET,
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public class ConsoleDomainListAction implements JsonGetAction {
public class ConsoleDomainListAction extends ConsoleApiAction {
public static final String PATH = "/console-api/domain-list";
@@ -54,8 +52,6 @@ public class ConsoleDomainListAction implements JsonGetAction {
private static final String SEARCH_TERM_QUERY = " AND LOWER(domainName) LIKE :searchTerm";
private static final String ORDER_BY_STATEMENT = " ORDER BY creationTime DESC";
private final AuthResult authResult;
private final Response response;
private final Gson gson;
private final String registrarId;
private final Optional<DateTime> checkpointTime;
@@ -66,8 +62,7 @@ public class ConsoleDomainListAction implements JsonGetAction {
@Inject
public ConsoleDomainListAction(
AuthResult authResult,
Response response,
ConsoleApiParams consoleApiParams,
Gson gson,
@Parameter("registrarId") String registrarId,
@Parameter("checkpointTime") Optional<DateTime> checkpointTime,
@@ -75,8 +70,7 @@ public class ConsoleDomainListAction implements JsonGetAction {
@Parameter("resultsPerPage") Optional<Integer> resultsPerPage,
@Parameter("totalResults") Optional<Long> totalResults,
@Parameter("searchTerm") Optional<String> searchTerm) {
this.authResult = authResult;
this.response = response;
super(consoleApiParams);
this.gson = gson;
this.registrarId = registrarId;
this.checkpointTime = checkpointTime;
@@ -87,19 +81,20 @@ public class ConsoleDomainListAction implements JsonGetAction {
}
@Override
public void run() {
User user = authResult.userAuthInfo().get().consoleUser().get();
protected void getHandler(User user) {
if (!user.getUserRoles().hasPermission(registrarId, DOWNLOAD_DOMAINS)) {
response.setStatus(HttpStatusCodes.STATUS_CODE_FORBIDDEN);
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_FORBIDDEN);
return;
}
if (resultsPerPage < 1 || resultsPerPage > 500) {
writeBadRequest("Results per page must be between 1 and 500 inclusive");
setFailedResponse(
"Results per page must be between 1 and 500 inclusive",
HttpStatusCodes.STATUS_CODE_BAD_REQUEST);
return;
}
if (pageNumber < 0) {
writeBadRequest("Page number must be non-negative");
setFailedResponse(
"Page number must be non-negative", HttpStatusCodes.STATUS_CODE_BAD_REQUEST);
return;
}
@@ -130,8 +125,10 @@ public class ConsoleDomainListAction implements JsonGetAction {
.setFirstResult(numResultsToSkip)
.setMaxResults(resultsPerPage)
.getResultList();
response.setPayload(gson.toJson(new DomainListResult(domains, checkpoint, actualTotalResults)));
response.setStatus(HttpStatusCodes.STATUS_CODE_OK);
consoleApiParams
.response()
.setPayload(gson.toJson(new DomainListResult(domains, checkpoint, actualTotalResults)));
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_OK);
}
/** Creates the query to get the total number of matching domains, interpolating as necessary. */
@@ -154,11 +151,6 @@ public class ConsoleDomainListAction implements JsonGetAction {
return tm().query(DOMAIN_QUERY_TEMPLATE + ORDER_BY_STATEMENT, Domain.class);
}
private void writeBadRequest(String message) {
response.setPayload(message);
response.setStatus(HttpStatusCodes.STATUS_CODE_BAD_REQUEST);
}
/** Container result class that allows for pagination. */
@VisibleForTesting
static final class DomainListResult {

View File

@@ -22,6 +22,7 @@ import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.User;
import google.registry.request.Action;
import google.registry.request.auth.Auth;
import google.registry.security.XsrfTokenManager;
import google.registry.ui.server.registrar.ConsoleApiParams;
import jakarta.servlet.http.Cookie;
import javax.inject.Inject;
@@ -61,7 +62,7 @@ public class ConsoleUserDataAction extends ConsoleApiAction {
// for angular to read - https://angular.io/guide/http-security-xsrf-protection
Cookie xsrfCookie =
new Cookie(
consoleApiParams.xsrfTokenManager().X_CSRF_TOKEN,
XsrfTokenManager.X_CSRF_TOKEN,
consoleApiParams.xsrfTokenManager().generateToken(user.getEmailAddress()));
xsrfCookie.setSecure(true);
consoleApiParams.response().addCookie(xsrfCookie);

View File

@@ -27,16 +27,13 @@ import com.google.gson.Gson;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.State;
import google.registry.model.registrar.RegistrarBase.State;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthResult;
import google.registry.ui.server.registrar.JsonGetAction;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.util.StringGenerator;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Named;
@@ -46,50 +43,33 @@ import javax.inject.Named;
path = RegistrarsAction.PATH,
method = {GET, POST},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public class RegistrarsAction implements JsonGetAction {
public class RegistrarsAction extends ConsoleApiAction {
private static final int PASSWORD_LENGTH = 16;
private static final int PASSCODE_LENGTH = 5;
static final String PATH = "/console-api/registrars";
private final AuthResult authResult;
private final Response response;
private final Gson gson;
private final HttpServletRequest req;
private Optional<Registrar> registrar;
private StringGenerator passwordGenerator;
private StringGenerator passcodeGenerator;
@Inject
public RegistrarsAction(
HttpServletRequest req,
AuthResult authResult,
Response response,
ConsoleApiParams consoleApiParams,
Gson gson,
@Parameter("registrar") Optional<Registrar> registrar,
@Named("base58StringGenerator") StringGenerator passwordGenerator,
@Named("digitOnlyStringGenerator") StringGenerator passcodeGenerator) {
this.authResult = authResult;
this.response = response;
super(consoleApiParams);
this.gson = gson;
this.registrar = registrar;
this.req = req;
this.passcodeGenerator = passcodeGenerator;
this.passwordGenerator = passwordGenerator;
}
@Override
public void run() {
User user = authResult.userAuthInfo().get().consoleUser().get();
if (req.getMethod().equals(GET.toString())) {
getHandler(user);
} else {
postHandler(user);
}
}
private void getHandler(User user) {
protected void getHandler(User user) {
if (!user.getUserRoles().hasGlobalPermission(ConsolePermission.VIEW_REGISTRARS)) {
response.setStatus(HttpStatusCodes.STATUS_CODE_FORBIDDEN);
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_FORBIDDEN);
return;
}
ImmutableList<Registrar> registrars =
@@ -97,19 +77,20 @@ public class RegistrarsAction implements JsonGetAction {
.filter(r -> r.getType() == Registrar.Type.REAL)
.collect(ImmutableList.toImmutableList());
response.setPayload(gson.toJson(registrars));
response.setStatus(HttpStatusCodes.STATUS_CODE_OK);
consoleApiParams.response().setPayload(gson.toJson(registrars));
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_OK);
}
private void postHandler(User user) {
@Override
protected void postHandler(User user) {
if (!user.getUserRoles().isAdmin()) {
response.setStatus(HttpStatusCodes.STATUS_CODE_FORBIDDEN);
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_FORBIDDEN);
return;
}
if (registrar.isEmpty()) {
response.setStatus(HttpStatusCodes.STATUS_CODE_BAD_REQUEST);
response.setPayload(gson.toJson("'registrar' parameter is not present"));
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_BAD_REQUEST);
consoleApiParams.response().setPayload(gson.toJson("'registrar' parameter is not present"));
return;
}
@@ -171,11 +152,9 @@ public class RegistrarsAction implements JsonGetAction {
});
} catch (IllegalArgumentException e) {
response.setStatus(HttpStatusCodes.STATUS_CODE_BAD_REQUEST);
response.setPayload(gson.toJson(e.getMessage()));
setFailedResponse(e.getMessage(), HttpStatusCodes.STATUS_CODE_BAD_REQUEST);
} catch (Throwable e) {
response.setStatus(HttpStatusCodes.STATUS_CODE_SERVER_ERROR);
response.setPayload(gson.toJson(e.getMessage()));
setFailedResponse(e.getMessage(), HttpStatusCodes.STATUS_CODE_SERVER_ERROR);
}
}
}

Some files were not shown because too many files have changed in this diff Show More