mirror of
https://github.com/google/nomulus
synced 2026-07-26 10:03:23 +00:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7171a326b | ||
|
|
c3eae7b76f | ||
|
|
2687181045 | ||
|
|
68750569db | ||
|
|
028e5cc958 | ||
|
|
853e571d01 | ||
|
|
9b79f5af2c |
@@ -36,16 +36,16 @@ import { RegistrarGuard } from './registrar/registrar.guard';
|
||||
import SecurityComponent from './settings/security/security.component';
|
||||
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
|
||||
import { EmptyRegistrar } from './registrar/emptyRegistrar.component';
|
||||
import { RegistrarSelectorComponent } from './registrar/registrar-selector.component';
|
||||
import { RegistrarSelectorComponent } from './registrar/registrarSelector.component';
|
||||
import { GlobalLoaderService } from './shared/services/globalLoader.service';
|
||||
import { ContactWidgetComponent } from './home/widgets/contact-widget.component';
|
||||
import { PromotionsWidgetComponent } from './home/widgets/promotions-widget.component';
|
||||
import { TldsWidgetComponent } from './home/widgets/tlds-widget.component';
|
||||
import { ResourcesWidgetComponent } from './home/widgets/resources-widget.component';
|
||||
import { EppWidgetComponent } from './home/widgets/epp-widget.component';
|
||||
import { BillingWidgetComponent } from './home/widgets/billing-widget.component';
|
||||
import { DomainsWidgetComponent } from './home/widgets/domains-widget.component';
|
||||
import { SettingsWidgetComponent } from './home/widgets/settings-widget.component';
|
||||
import { ContactWidgetComponent } from './home/widgets/contactWidget.component';
|
||||
import { PromotionsWidgetComponent } from './home/widgets/promotionsWidget.component';
|
||||
import { TldsWidgetComponent } from './home/widgets/tldsWidget.component';
|
||||
import { ResourcesWidgetComponent } from './home/widgets/resourcesWidget.component';
|
||||
import { EppWidgetComponent } from './home/widgets/eppWidget.component';
|
||||
import { BillingWidgetComponent } from './home/widgets/billingWidget.component';
|
||||
import { DomainsWidgetComponent } from './home/widgets/domainsWidget.component';
|
||||
import { SettingsWidgetComponent } from './home/widgets/settingsWidget.component';
|
||||
import { UserDataService } from './shared/services/userData.service';
|
||||
import WhoisComponent from './settings/whois/whois.component';
|
||||
import { SnackBarModule } from './snackbar.module';
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ import { RegistrarService } from 'src/app/registrar/registrar.service';
|
||||
|
||||
@Component({
|
||||
selector: '[app-billing-widget]',
|
||||
templateUrl: './billing-widget.component.html',
|
||||
templateUrl: './billingWidget.component.html',
|
||||
})
|
||||
export class BillingWidgetComponent {
|
||||
constructor(public registrarService: RegistrarService) {}
|
||||
+1
-1
@@ -17,7 +17,7 @@ import { UserDataService } from 'src/app/shared/services/userData.service';
|
||||
|
||||
@Component({
|
||||
selector: '[app-contact-widget]',
|
||||
templateUrl: './contact-widget.component.html',
|
||||
templateUrl: './contactWidget.component.html',
|
||||
})
|
||||
export class ContactWidgetComponent {
|
||||
constructor(public userDataService: UserDataService) {}
|
||||
+1
-1
@@ -18,7 +18,7 @@ import { DomainListComponent } from 'src/app/domains/domainList.component';
|
||||
|
||||
@Component({
|
||||
selector: '[app-domains-widget]',
|
||||
templateUrl: './domains-widget.component.html',
|
||||
templateUrl: './domainsWidget.component.html',
|
||||
})
|
||||
export class DomainsWidgetComponent {
|
||||
constructor(private router: Router) {}
|
||||
+1
-1
@@ -16,7 +16,7 @@ import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: '[app-epp-widget]',
|
||||
templateUrl: './epp-widget.component.html',
|
||||
templateUrl: './eppWidget.component.html',
|
||||
})
|
||||
export class EppWidgetComponent {
|
||||
constructor() {}
|
||||
+1
-1
@@ -16,7 +16,7 @@ import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: '[app-promotions-widget]',
|
||||
templateUrl: './promotions-widget.component.html',
|
||||
templateUrl: './promotionsWidget.component.html',
|
||||
})
|
||||
export class PromotionsWidgetComponent {
|
||||
constructor() {}
|
||||
+1
-1
@@ -17,7 +17,7 @@ import { UserDataService } from 'src/app/shared/services/userData.service';
|
||||
|
||||
@Component({
|
||||
selector: '[app-resources-widget]',
|
||||
templateUrl: './resources-widget.component.html',
|
||||
templateUrl: './resourcesWidget.component.html',
|
||||
})
|
||||
export class ResourcesWidgetComponent {
|
||||
constructor(public userDataService: UserDataService) {}
|
||||
+1
-1
@@ -21,7 +21,7 @@ import { SettingsComponent } from 'src/app/settings/settings.component';
|
||||
|
||||
@Component({
|
||||
selector: '[app-settings-widget]',
|
||||
templateUrl: './settings-widget.component.html',
|
||||
templateUrl: './settingsWidget.component.html',
|
||||
})
|
||||
export class SettingsWidgetComponent {
|
||||
constructor(private router: Router) {}
|
||||
+1
-1
@@ -16,7 +16,7 @@ import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: '[app-tlds-widget]',
|
||||
templateUrl: './tlds-widget.component.html',
|
||||
templateUrl: './tldsWidget.component.html',
|
||||
})
|
||||
export class TldsWidgetComponent {
|
||||
constructor() {}
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { RegistrarSelectorComponent } from './registrar-selector.component';
|
||||
import { RegistrarSelectorComponent } from './registrarSelector.component';
|
||||
|
||||
describe('RegistrarSelectorComponent', () => {
|
||||
let component: RegistrarSelectorComponent;
|
||||
+2
-2
@@ -21,8 +21,8 @@ const MOBILE_LAYOUT_BREAKPOINT = '(max-width: 599px)';
|
||||
|
||||
@Component({
|
||||
selector: 'app-registrar-selector',
|
||||
templateUrl: './registrar-selector.component.html',
|
||||
styleUrls: ['./registrar-selector.component.scss'],
|
||||
templateUrl: './registrarSelector.component.html',
|
||||
styleUrls: ['./registrarSelector.component.scss'],
|
||||
})
|
||||
export class RegistrarSelectorComponent implements OnInit {
|
||||
protected isMobile: boolean = false;
|
||||
@@ -76,7 +76,7 @@ class ContactDetailsEventsResponder {
|
||||
|
||||
@Component({
|
||||
selector: 'app-contact-details-dialog',
|
||||
templateUrl: 'contact-details.component.html',
|
||||
templateUrl: 'contactDetails.component.html',
|
||||
styleUrls: ['./contact.component.scss'],
|
||||
})
|
||||
export class ContactDetailsDialogComponent {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
@use "@angular/material" as mat;
|
||||
@import "app/registrar/registrar-selector.component.scss";
|
||||
@import "app/registrar/registrarSelector.component.scss";
|
||||
|
||||
html,
|
||||
body {
|
||||
|
||||
@@ -1397,6 +1397,47 @@ public final class RegistryConfig {
|
||||
return config.bulkPricingPackageMonitoring.bulkPricingPackageDomainLimitUpgradeEmailBody;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("bsaGcsBucket")
|
||||
public static String provideBsaGcsBucket(@Config("projectId") String projectId) {
|
||||
return projectId + "-bsa";
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("bsaChecksumAlgorithm")
|
||||
public static String provideBsaChecksumAlgorithm(RegistryConfigSettings config) {
|
||||
return config.bsa.bsaChecksumAlgorithm;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("bsaLockLeaseExpiry")
|
||||
public static Duration provideBsaLockLeaseExpiry(RegistryConfigSettings config) {
|
||||
return Duration.standardMinutes(config.bsa.bsaLockLeaseExpiryMinutes);
|
||||
}
|
||||
|
||||
/** Returns the desired interval between successive BSA downloads. */
|
||||
@Provides
|
||||
@Config("bsaDownloadInterval")
|
||||
public static Duration provideBsaDownloadInterval(RegistryConfigSettings config) {
|
||||
return Duration.standardMinutes(config.bsa.bsaDownloadIntervalMinutes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum period when a BSA download can be skipped due to the checksum-based
|
||||
* equality check with the previous download.
|
||||
*/
|
||||
@Provides
|
||||
@Config("bsaMaxNopInterval")
|
||||
public static Duration provideBsaMaxNopInterval(RegistryConfigSettings config) {
|
||||
return Duration.standardHours(config.bsa.bsaMaxNopIntervalHours);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("bsaLabelTxnBatchSize")
|
||||
public static int provideBsaLabelTxnBatchSize(RegistryConfigSettings config) {
|
||||
return config.bsa.bsaLabelTxnBatchSize;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("bsaAuthUrl")
|
||||
public static String provideBsaAuthUrl(RegistryConfigSettings config) {
|
||||
@@ -1415,6 +1456,27 @@ public final class RegistryConfig {
|
||||
return ImmutableMap.copyOf(config.bsa.dataUrls);
|
||||
}
|
||||
|
||||
/** Provides the BSA Http endpoint for reporting order processing status. */
|
||||
@Provides
|
||||
@Config("bsaOrderStatusUrl")
|
||||
public static String provideBsaOrderStatusUrls(RegistryConfigSettings config) {
|
||||
return config.bsa.orderStatusUrl;
|
||||
}
|
||||
|
||||
/** Provides the BSA Http endpoint for reporting new unblockable domains. */
|
||||
@Provides
|
||||
@Config("bsaAddUnblockableDomainsUrl")
|
||||
public static String provideBsaAddUnblockableDomainsUrls(RegistryConfigSettings config) {
|
||||
return String.format("%s?%s", config.bsa.unblockableDomainsUrl, "action=add");
|
||||
}
|
||||
|
||||
/** Provides the BSA Http endpoint for reporting domains that have become blockable. */
|
||||
@Provides
|
||||
@Config("bsaRemoveUnblockableDomainsUrl")
|
||||
public static String provideBsaRemoveUnblockableDomainsUrls(RegistryConfigSettings config) {
|
||||
return String.format("%s?%s", config.bsa.unblockableDomainsUrl, "action=remove");
|
||||
}
|
||||
|
||||
private static String formatComments(String text) {
|
||||
return Splitter.on('\n').omitEmptyStrings().trimResults().splitToList(text).stream()
|
||||
.map(s -> "# " + s)
|
||||
|
||||
@@ -267,8 +267,15 @@ public class RegistryConfigSettings {
|
||||
|
||||
/** Configurations for integration with Brand Safety Alliance (BSA) API. */
|
||||
public static class Bsa {
|
||||
public String bsaChecksumAlgorithm;
|
||||
public int bsaLockLeaseExpiryMinutes;
|
||||
public int bsaDownloadIntervalMinutes;
|
||||
public int bsaMaxNopIntervalHours;
|
||||
public int bsaLabelTxnBatchSize;
|
||||
public String authUrl;
|
||||
public int authTokenExpirySeconds;
|
||||
public Map<String, String> dataUrls;
|
||||
public String orderStatusUrl;
|
||||
public String unblockableDomainsUrl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,3 +617,7 @@ bsa:
|
||||
dataUrls:
|
||||
"BLOCK": "https://"
|
||||
"BLOCK_PLUS": "https://"
|
||||
# Http endpoint for reporting order processing status
|
||||
orderStatusUrl: "https://"
|
||||
# Http endpoint for reporting changes in the set of unblockable domains.
|
||||
unblockableDomainsUrl: "https://"
|
||||
|
||||
@@ -75,14 +75,6 @@ public class EppRequestHandler {
|
||||
&& eppOutput.getResponse().getResult().getCode() == SUCCESS_AND_CLOSE) {
|
||||
response.setHeader(ProxyHttpHeaders.EPP_SESSION, "close");
|
||||
}
|
||||
// If a login request returns a success, a logged-in header is added to the response to inform
|
||||
// the proxy that it is no longer necessary to send the full client certificate to the backend
|
||||
// for this connection.
|
||||
if (eppOutput.isResponse()
|
||||
&& eppOutput.getResponse().isLoginResponse()
|
||||
&& eppOutput.isSuccess()) {
|
||||
response.setHeader(ProxyHttpHeaders.LOGGED_IN, "true");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.atWarning().withCause(e).log("handleEppCommand general exception.");
|
||||
response.setStatus(SC_BAD_REQUEST);
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.google.common.net.InetAddresses;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.flows.EppException.AuthenticationErrorException;
|
||||
import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.flows.certs.CertificateChecker.InsecureCertificateException;
|
||||
@@ -66,11 +67,11 @@ public class TlsCredentials implements TransportCredentials {
|
||||
public TlsCredentials(
|
||||
@Config("requireSslCertificates") boolean requireSslCertificates,
|
||||
@Header(ProxyHttpHeaders.CERTIFICATE_HASH) Optional<String> clientCertificateHash,
|
||||
@Header(ProxyHttpHeaders.IP_ADDRESS) Optional<String> clientAddress,
|
||||
Optional<InetAddress> clientInetAddr,
|
||||
CertificateChecker certificateChecker) {
|
||||
this.requireSslCertificates = requireSslCertificates;
|
||||
this.clientCertificateHash = clientCertificateHash;
|
||||
this.clientInetAddr = clientAddress.map(TlsCredentials::parseInetAddress);
|
||||
this.clientInetAddr = clientInetAddr;
|
||||
this.certificateChecker = certificateChecker;
|
||||
}
|
||||
|
||||
@@ -104,18 +105,25 @@ public class TlsCredentials implements TransportCredentials {
|
||||
}
|
||||
// In the rare unexpected case that the client inet address wasn't passed along at all, then
|
||||
// by default deny access.
|
||||
if (clientInetAddr.isPresent()) {
|
||||
for (CidrAddressBlock cidrAddressBlock : ipAddressAllowList) {
|
||||
if (cidrAddressBlock.contains(clientInetAddr.get())) {
|
||||
// IP address is in allow list; return early.
|
||||
return;
|
||||
}
|
||||
if (!clientInetAddr.isPresent()) {
|
||||
logger.atWarning().log(
|
||||
"Authentication error: Missing IP address for registrar %s.", registrar.getRegistrarId());
|
||||
throw new BadRegistrarIpAddressException(clientInetAddr);
|
||||
}
|
||||
for (CidrAddressBlock cidrAddressBlock : ipAddressAllowList) {
|
||||
if (cidrAddressBlock.contains(clientInetAddr.get())) {
|
||||
// IP address is in allow list; return early.
|
||||
return;
|
||||
}
|
||||
}
|
||||
logger.atInfo().log(
|
||||
logger.atWarning().log(
|
||||
"Authentication error: IP address %s is not allow-listed for registrar %s; allow list is:"
|
||||
+ " %s",
|
||||
clientInetAddr, registrar.getRegistrarId(), ipAddressAllowList);
|
||||
clientInetAddr,
|
||||
registrar.getRegistrarId(),
|
||||
RegistryEnvironment.get() == RegistryEnvironment.PRODUCTION
|
||||
? "redacted in production"
|
||||
: ipAddressAllowList);
|
||||
throw new BadRegistrarIpAddressException(clientInetAddr);
|
||||
}
|
||||
|
||||
@@ -232,7 +240,7 @@ public class TlsCredentials implements TransportCredentials {
|
||||
? String.format(
|
||||
"Registrar IP address %s is not in stored allow list",
|
||||
clientInetAddr.get().getHostAddress())
|
||||
: "Registrar IP address is not in stored allow list");
|
||||
: "Registrar IP address is missing");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,9 +257,14 @@ public class TlsCredentials implements TransportCredentials {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Header(ProxyHttpHeaders.IP_ADDRESS)
|
||||
static Optional<String> provideIpAddress(HttpServletRequest req) {
|
||||
return extractOptionalHeader(req, ProxyHttpHeaders.IP_ADDRESS);
|
||||
static Optional<InetAddress> provideIpAddress(HttpServletRequest req) {
|
||||
Optional<String> clientAddress = extractOptionalHeader(req, ProxyHttpHeaders.IP_ADDRESS);
|
||||
Optional<String> fallbackClientAddress =
|
||||
extractOptionalHeader(req, ProxyHttpHeaders.IP_ADDRESS);
|
||||
Optional<InetAddress> clientInetAddr = clientAddress.map(TlsCredentials::parseInetAddress);
|
||||
return clientInetAddr.isPresent()
|
||||
? clientInetAddr
|
||||
: fallbackClientAddress.map(TlsCredentials::parseInetAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import static com.google.common.collect.Sets.difference;
|
||||
import static com.google.common.collect.Sets.union;
|
||||
import static google.registry.config.RegistryConfig.getEppResourceCachingDuration;
|
||||
import static google.registry.config.RegistryConfig.getEppResourceMaxCachedEntries;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
@@ -357,13 +358,13 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
|
||||
|
||||
@Override
|
||||
public EppResource load(VKey<? extends EppResource> key) {
|
||||
return tm().reTransact(() -> tm().loadByKey(key));
|
||||
return replicaTm().reTransact(() -> replicaTm().loadByKey(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<VKey<? extends EppResource>, EppResource> loadAll(
|
||||
Iterable<? extends VKey<? extends EppResource>> keys) {
|
||||
return tm().reTransact(() -> tm().loadByKeys(keys));
|
||||
return replicaTm().reTransact(() -> replicaTm().loadByKeys(keys));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -165,10 +165,11 @@ public final class ForeignKeyUtils {
|
||||
* foreign keys should not use this cache.
|
||||
*
|
||||
* <p>Note that here the key of the {@link LoadingCache} is of type {@code VKey<? extends
|
||||
* EppResource>}, but they are not legal {VKey}s to {@link EppResource}s, whose keys are the SQL
|
||||
* primary keys, i.e. the {@code repoId}s. Instead, their keys are the foreign keys used to query
|
||||
* the database. We use {@link VKey} here because it is a convenient composite class that contains
|
||||
* both the resource type and the foreign key, which are needed to for the query and caching.
|
||||
* EppResource>}, but they are not legal {@link VKey}s to {@link EppResource}s, whose keys are the
|
||||
* SQL primary keys, i.e. the {@code repoId}s. Instead, their keys are the foreign keys used to
|
||||
* query the database. We use {@link VKey} here because it is a convenient composite class that
|
||||
* contains both the resource type and the foreign key, which are needed to for the query and
|
||||
* caching.
|
||||
*
|
||||
* <p>Also note that the value type of this cache is {@link Optional} because the foreign keys in
|
||||
* question are coming from external commands, and thus don't necessarily represent entities in
|
||||
|
||||
@@ -45,7 +45,7 @@ import org.joda.money.Money;
|
||||
* {@link PremiumList} object in SQL, and caching these entries so that future lookups can be
|
||||
* quicker.
|
||||
*/
|
||||
public class PremiumListDao {
|
||||
public final class PremiumListDao {
|
||||
|
||||
/**
|
||||
* In-memory cache for premium lists.
|
||||
@@ -102,7 +102,7 @@ public class PremiumListDao {
|
||||
/**
|
||||
* Returns the most recent revision of the PremiumList with the specified name, if it exists.
|
||||
*
|
||||
* <p>Note that this does not load <code>PremiumList.labelsToPrices</code>! If you need to check
|
||||
* <p>Note that this does not load {@code PremiumList.labelsToPrices}! If you need to check
|
||||
* prices, use {@link #getPremiumPrice}.
|
||||
*/
|
||||
public static Optional<PremiumList> getLatestRevision(String premiumListName) {
|
||||
@@ -169,7 +169,7 @@ public class PremiumListDao {
|
||||
}
|
||||
|
||||
private static Optional<PremiumList> getLatestRevisionUncached(String premiumListName) {
|
||||
return tm().transact(
|
||||
return tm().reTransact(
|
||||
() ->
|
||||
tm().query(
|
||||
"FROM PremiumList WHERE name = :name ORDER BY revisionId DESC",
|
||||
@@ -197,10 +197,10 @@ public class PremiumListDao {
|
||||
|
||||
/**
|
||||
* Loads the price for the given revisionId + label combination. Note that this does a database
|
||||
* retrieval so it should only be done in a cached context.
|
||||
* retrieval, so it should only be done in a cached context.
|
||||
*/
|
||||
static Optional<BigDecimal> getPriceForLabelUncached(RevisionIdAndLabel revisionIdAndLabel) {
|
||||
return tm().transact(
|
||||
return tm().reTransact(
|
||||
() ->
|
||||
tm().query(
|
||||
"SELECT pe.price FROM PremiumEntry pe WHERE pe.revisionId = :revisionId"
|
||||
|
||||
@@ -47,7 +47,7 @@ public class ReservedListDao {
|
||||
* exists.
|
||||
*/
|
||||
public static Optional<ReservedList> getLatestRevision(String reservedListName) {
|
||||
return tm().transact(
|
||||
return tm().reTransact(
|
||||
() ->
|
||||
tm().query(
|
||||
"FROM ReservedList WHERE revisionId IN "
|
||||
|
||||
@@ -65,7 +65,7 @@ public class ClaimsListDao {
|
||||
* doesn't exist.
|
||||
*/
|
||||
private static ClaimsList getUncached() {
|
||||
return tm().transact(
|
||||
return tm().reTransact(
|
||||
() -> {
|
||||
Long revisionId =
|
||||
tm().query("SELECT MAX(revisionId) FROM ClaimsList", Long.class)
|
||||
|
||||
@@ -267,7 +267,7 @@ public abstract class PersistenceModule {
|
||||
name -> overrides.put(HIKARI_DS_CLOUD_SQL_INSTANCE, name));
|
||||
overrides.put(
|
||||
Environment.ISOLATION, TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ.name());
|
||||
return new JpaTransactionManagerImpl(create(overrides), clock);
|
||||
return new JpaTransactionManagerImpl(create(overrides), clock, true);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -283,7 +283,7 @@ public abstract class PersistenceModule {
|
||||
name -> overrides.put(HIKARI_DS_CLOUD_SQL_INSTANCE, name));
|
||||
overrides.put(
|
||||
Environment.ISOLATION, TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ.name());
|
||||
return new JpaTransactionManagerImpl(create(overrides), clock);
|
||||
return new JpaTransactionManagerImpl(create(overrides), clock, true);
|
||||
}
|
||||
|
||||
/** Constructs the {@link EntityManagerFactory} instance. */
|
||||
|
||||
+11
-1
@@ -85,13 +85,19 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
// EntityManagerFactory is thread safe.
|
||||
private final EntityManagerFactory emf;
|
||||
private final Clock clock;
|
||||
private final boolean readOnly;
|
||||
|
||||
private static final ThreadLocal<TransactionInfo> transactionInfo =
|
||||
ThreadLocal.withInitial(TransactionInfo::new);
|
||||
|
||||
public JpaTransactionManagerImpl(EntityManagerFactory emf, Clock clock) {
|
||||
public JpaTransactionManagerImpl(EntityManagerFactory emf, Clock clock, boolean readOnly) {
|
||||
this.emf = emf;
|
||||
this.clock = clock;
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
|
||||
public JpaTransactionManagerImpl(EntityManagerFactory emf, Clock clock) {
|
||||
this(emf, clock, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -200,6 +206,10 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
try {
|
||||
txn.begin();
|
||||
txnInfo.start(clock);
|
||||
if (readOnly) {
|
||||
getEntityManager().createNativeQuery("SET TRANSACTION READ ONLY").executeUpdate();
|
||||
logger.atInfo().log("Using read-only SQL replica");
|
||||
}
|
||||
if (isolationLevel != null && isolationLevel != getDefaultTransactionIsolationLevel()) {
|
||||
getEntityManager()
|
||||
.createNativeQuery(
|
||||
|
||||
@@ -463,8 +463,7 @@ abstract class CreateOrUpdateTldCommand extends MutatingCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkReservedListValidityForTld(String tld, Set<String> reservedListNames)
|
||||
throws UnsupportedEncodingException {
|
||||
private void checkReservedListValidityForTld(String tld, Set<String> reservedListNames) {
|
||||
ImmutableList.Builder<String> builder = new ImmutableList.Builder<>();
|
||||
for (String reservedListName : reservedListNames) {
|
||||
if (!reservedListName.startsWith("common_") && !reservedListName.startsWith(tld + "_")) {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.tools;
|
||||
|
||||
import static google.registry.util.DiffUtils.prettyPrintEntityDeepDiff;
|
||||
import static google.registry.util.ListNamingUtils.convertFilePathToName;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
@@ -46,17 +47,27 @@ final class UpdateReservedListCommand extends CreateOrUpdateReservedListCommand
|
||||
.setReservedListMapFromLines(allLines)
|
||||
.setShouldPublish(shouldPublish);
|
||||
reservedList = updated.build();
|
||||
// only call stageEntityChange if there are changes in entries
|
||||
|
||||
if (!existingReservedList
|
||||
.getReservedListEntries()
|
||||
.equals(reservedList.getReservedListEntries())) {
|
||||
return String.format(
|
||||
"Update reserved list for %s?\nOld list: %s\n New list: %s",
|
||||
name,
|
||||
outputReservedListEntries(existingReservedList),
|
||||
outputReservedListEntries(reservedList));
|
||||
boolean shouldPublishChanged =
|
||||
existingReservedList.getShouldPublish() != reservedList.getShouldPublish();
|
||||
boolean reservedListEntriesChanged =
|
||||
!existingReservedList
|
||||
.getReservedListEntries()
|
||||
.equals(reservedList.getReservedListEntries());
|
||||
if (!shouldPublishChanged && !reservedListEntriesChanged) {
|
||||
return "No entity changes to apply.";
|
||||
}
|
||||
return "No entity changes to apply.";
|
||||
String result = String.format("Update reserved list for %s?\n", name);
|
||||
if (shouldPublishChanged) {
|
||||
result +=
|
||||
String.format(
|
||||
"shouldPublish: %s -> %s\n",
|
||||
existingReservedList.getShouldPublish(), reservedList.getShouldPublish());
|
||||
}
|
||||
if (reservedListEntriesChanged) {
|
||||
result +=
|
||||
prettyPrintEntityDeepDiff(
|
||||
existingReservedList.getReservedListEntries(), reservedList.getReservedListEntries());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import static google.registry.util.X509Utils.loadCertificate;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import google.registry.flows.TlsCredentials;
|
||||
import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
@@ -85,7 +86,7 @@ final class ValidateLoginCredentialsCommand implements Command {
|
||||
new TlsCredentials(
|
||||
true,
|
||||
Optional.ofNullable(clientCertificateHash),
|
||||
Optional.ofNullable(clientIpAddress),
|
||||
Optional.ofNullable(InetAddresses.forString(clientIpAddress)),
|
||||
certificateChecker)
|
||||
.validate(registrar, password);
|
||||
checkState(
|
||||
|
||||
@@ -19,6 +19,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
|
||||
|
||||
import com.google.api.client.http.HttpStatusCodes;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.model.CreateAutoTimestamp;
|
||||
@@ -33,6 +34,7 @@ import google.registry.ui.server.registrar.JsonGetAction;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import javax.persistence.TypedQuery;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Returns a (paginated) list of domains for a particular registrar. */
|
||||
@@ -49,6 +51,8 @@ public class ConsoleDomainListAction implements JsonGetAction {
|
||||
private static final String DOMAIN_QUERY_TEMPLATE =
|
||||
"FROM Domain WHERE currentSponsorRegistrarId = :registrarId AND deletionTime >"
|
||||
+ " :deletedAfterTime AND creationTime <= :createdBeforeTime";
|
||||
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;
|
||||
@@ -58,6 +62,7 @@ public class ConsoleDomainListAction implements JsonGetAction {
|
||||
private final int pageNumber;
|
||||
private final int resultsPerPage;
|
||||
private final Optional<Long> totalResults;
|
||||
private final Optional<String> searchTerm;
|
||||
|
||||
@Inject
|
||||
public ConsoleDomainListAction(
|
||||
@@ -68,7 +73,8 @@ public class ConsoleDomainListAction implements JsonGetAction {
|
||||
@Parameter("checkpointTime") Optional<DateTime> checkpointTime,
|
||||
@Parameter("pageNumber") Optional<Integer> pageNumber,
|
||||
@Parameter("resultsPerPage") Optional<Integer> resultsPerPage,
|
||||
@Parameter("totalResults") Optional<Long> totalResults) {
|
||||
@Parameter("totalResults") Optional<Long> totalResults,
|
||||
@Parameter("searchTerm") Optional<String> searchTerm) {
|
||||
this.authResult = authResult;
|
||||
this.response = response;
|
||||
this.gson = gson;
|
||||
@@ -77,6 +83,7 @@ public class ConsoleDomainListAction implements JsonGetAction {
|
||||
this.pageNumber = pageNumber.orElse(0);
|
||||
this.resultsPerPage = resultsPerPage.orElse(DEFAULT_RESULTS_PER_PAGE);
|
||||
this.totalResults = totalResults;
|
||||
this.searchTerm = searchTerm;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,13 +117,13 @@ public class ConsoleDomainListAction implements JsonGetAction {
|
||||
long actualTotalResults =
|
||||
totalResults.orElseGet(
|
||||
() ->
|
||||
tm().query("SELECT COUNT(*) " + DOMAIN_QUERY_TEMPLATE, Long.class)
|
||||
createCountQuery()
|
||||
.setParameter("registrarId", registrarId)
|
||||
.setParameter("createdBeforeTime", checkpointTimestamp)
|
||||
.setParameter("deletedAfterTime", checkpoint)
|
||||
.getSingleResult());
|
||||
List<Domain> domains =
|
||||
tm().query(DOMAIN_QUERY_TEMPLATE + " ORDER BY creationTime DESC", Domain.class)
|
||||
createDomainQuery()
|
||||
.setParameter("registrarId", registrarId)
|
||||
.setParameter("createdBeforeTime", checkpointTimestamp)
|
||||
.setParameter("deletedAfterTime", checkpoint)
|
||||
@@ -127,6 +134,26 @@ public class ConsoleDomainListAction implements JsonGetAction {
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_OK);
|
||||
}
|
||||
|
||||
/** Creates the query to get the total number of matching domains, interpolating as necessary. */
|
||||
private TypedQuery<Long> createCountQuery() {
|
||||
String queryString = "SELECT COUNT(*) " + DOMAIN_QUERY_TEMPLATE;
|
||||
if (searchTerm.isPresent() && !searchTerm.get().isEmpty()) {
|
||||
return tm().query(queryString + SEARCH_TERM_QUERY, Long.class)
|
||||
.setParameter("searchTerm", String.format("%%%s%%", Ascii.toLowerCase(searchTerm.get())));
|
||||
}
|
||||
return tm().query(queryString, Long.class);
|
||||
}
|
||||
|
||||
/** Creates the query to retrieve the matching domains themselves, interpolating as necessary. */
|
||||
private TypedQuery<Domain> createDomainQuery() {
|
||||
if (searchTerm.isPresent() && !searchTerm.get().isEmpty()) {
|
||||
return tm().query(
|
||||
DOMAIN_QUERY_TEMPLATE + SEARCH_TERM_QUERY + ORDER_BY_STATEMENT, Domain.class)
|
||||
.setParameter("searchTerm", String.format("%%%s%%", Ascii.toLowerCase(searchTerm.get())));
|
||||
}
|
||||
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);
|
||||
|
||||
@@ -226,4 +226,10 @@ public final class RegistrarConsoleModule {
|
||||
public static Optional<Long> provideTotalResults(HttpServletRequest req) {
|
||||
return extractOptionalParameter(req, "totalResults").map(Long::valueOf);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("searchTerm")
|
||||
public static Optional<String> provideSearchTerm(HttpServletRequest req) {
|
||||
return extractOptionalParameter(req, "searchTerm");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import static google.registry.util.X509Utils.getCertificateHash;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
@@ -66,7 +67,7 @@ class EppLoginTlsTest extends EppTestCase {
|
||||
new TlsCredentials(
|
||||
true,
|
||||
Optional.ofNullable(clientCertificateHash),
|
||||
Optional.of("192.168.1.100:54321"),
|
||||
Optional.of(InetAddresses.forString("192.168.1.100")),
|
||||
certificateChecker));
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ import google.registry.persistence.VKey;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeHttpSession;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.util.ProxyHttpHeaders;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
@@ -126,10 +125,6 @@ public class EppTestCase {
|
||||
setUpSession();
|
||||
FakeResponse response = executeXmlCommand(input);
|
||||
|
||||
// Check that the logged-in header was added to the response
|
||||
assertThat(response.getHeaders())
|
||||
.isEqualTo(ImmutableMap.of(ProxyHttpHeaders.LOGGED_IN, "true"));
|
||||
|
||||
verifyAndReturnOutput(response.getPayload(), expectedOutput, inputFilename, outputFilename);
|
||||
}
|
||||
|
||||
@@ -146,10 +141,6 @@ public class EppTestCase {
|
||||
setUpSession();
|
||||
FakeResponse response = executeXmlCommand(input);
|
||||
|
||||
// Checks that the Logged-In header is not in the response. If testing the login command, use
|
||||
// assertLoginCommandAndResponse instead of this method.
|
||||
assertThat(response.getHeaders()).doesNotContainEntry(ProxyHttpHeaders.LOGGED_IN, "true");
|
||||
|
||||
return verifyAndReturnOutput(
|
||||
response.getPayload(), expectedOutput, inputFilename, outputFilename);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import com.google.common.testing.TestLogHandler;
|
||||
import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
@@ -186,7 +187,10 @@ class FlowRunnerTest {
|
||||
void testRun_loggingStatement_tlsCredentials() throws Exception {
|
||||
flowRunner.credentials =
|
||||
new TlsCredentials(
|
||||
true, Optional.of("abc123def"), Optional.of("127.0.0.1"), certificateChecker);
|
||||
true,
|
||||
Optional.of("abc123def"),
|
||||
Optional.of(InetAddresses.forString("127.0.0.1")),
|
||||
certificateChecker);
|
||||
flowRunner.run(eppMetricBuilder);
|
||||
assertThat(Splitter.on("\n\t").split(findFirstLogMessageByPrefix(handler, "EPP Command\n\t")))
|
||||
.contains("TlsCredentials{clientCertificateHash=abc123def," + " clientAddress=/127.0.0.1}");
|
||||
|
||||
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import google.registry.flows.TlsCredentials.BadRegistrarIpAddressException;
|
||||
import google.registry.flows.TlsCredentials.MissingRegistrarCertificateException;
|
||||
import google.registry.flows.TlsCredentials.RegistrarCertificateNotConfiguredException;
|
||||
@@ -71,7 +72,11 @@ final class TlsCredentialsTest {
|
||||
@Test
|
||||
void testClientCertificateAndHash_missing() {
|
||||
TlsCredentials tls =
|
||||
new TlsCredentials(true, Optional.empty(), Optional.of("192.168.1.1"), certificateChecker);
|
||||
new TlsCredentials(
|
||||
true,
|
||||
Optional.empty(),
|
||||
Optional.of(InetAddresses.forString("192.168.1.1")),
|
||||
certificateChecker);
|
||||
persistResource(
|
||||
loadRegistrar("TheRegistrar")
|
||||
.asBuilder()
|
||||
@@ -83,10 +88,13 @@ final class TlsCredentialsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_missingIpAddress_doesntAllowAccess() {
|
||||
void test_wrongIpAddress_doesntAllowAccess() {
|
||||
TlsCredentials tls =
|
||||
new TlsCredentials(
|
||||
false, Optional.of("certHash"), Optional.of("127.0.0.1"), certificateChecker);
|
||||
false,
|
||||
Optional.of("certHash"),
|
||||
Optional.of(InetAddresses.forString("127.0.0.1")),
|
||||
certificateChecker);
|
||||
persistResource(
|
||||
loadRegistrar("TheRegistrar")
|
||||
.asBuilder()
|
||||
@@ -104,11 +112,33 @@ final class TlsCredentialsTest {
|
||||
.isEqualTo("Registrar IP address 127.0.0.1 is not in stored allow list");
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_missingIpAddress_doesntAllowAccess() {
|
||||
TlsCredentials tls =
|
||||
new TlsCredentials(false, Optional.of("certHash"), Optional.empty(), certificateChecker);
|
||||
persistResource(
|
||||
loadRegistrar("TheRegistrar")
|
||||
.asBuilder()
|
||||
.setClientCertificate(SAMPLE_CERT, clock.nowUtc())
|
||||
.setIpAddressAllowList(ImmutableSet.of(CidrAddressBlock.create("3.5.8.13")))
|
||||
.build());
|
||||
|
||||
BadRegistrarIpAddressException thrown =
|
||||
assertThrows(
|
||||
BadRegistrarIpAddressException.class,
|
||||
() -> tls.validate(Registrar.loadByRegistrarId("TheRegistrar").get(), "password"));
|
||||
|
||||
assertThat(thrown).hasMessageThat().isEqualTo("Registrar IP address is missing");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClientCertificate_notConfigured() {
|
||||
TlsCredentials tls =
|
||||
new TlsCredentials(
|
||||
true, Optional.of("hash"), Optional.of("192.168.1.1"), certificateChecker);
|
||||
true,
|
||||
Optional.of("hash"),
|
||||
Optional.of(InetAddresses.forString("192.168.1.1")),
|
||||
certificateChecker);
|
||||
persistResource(loadRegistrar("TheRegistrar").asBuilder().build());
|
||||
assertThrows(
|
||||
RegistrarCertificateNotConfiguredException.class,
|
||||
@@ -119,7 +149,10 @@ final class TlsCredentialsTest {
|
||||
void test_validateCertificateHash_canBeConfiguredToBypassCerts() throws Exception {
|
||||
TlsCredentials tls =
|
||||
new TlsCredentials(
|
||||
false, Optional.of("certHash"), Optional.of("192.168.1.1"), certificateChecker);
|
||||
false,
|
||||
Optional.of("certHash"),
|
||||
Optional.of(InetAddresses.forString("192.168.1.1")),
|
||||
certificateChecker);
|
||||
persistResource(
|
||||
loadRegistrar("TheRegistrar")
|
||||
.asBuilder()
|
||||
@@ -134,7 +167,10 @@ final class TlsCredentialsTest {
|
||||
void test_validateCertificateHash_passWithFailOverCerticate() throws Exception {
|
||||
TlsCredentials tls =
|
||||
new TlsCredentials(
|
||||
false, Optional.of(SAMPLE_CERT_HASH), Optional.of("192.168.1.1"), certificateChecker);
|
||||
false,
|
||||
Optional.of(SAMPLE_CERT_HASH),
|
||||
Optional.of(InetAddresses.forString("192.168.1.1")),
|
||||
certificateChecker);
|
||||
persistResource(
|
||||
loadRegistrar("TheRegistrar")
|
||||
.asBuilder()
|
||||
|
||||
@@ -30,6 +30,7 @@ import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.testing.CertificateSamples;
|
||||
import google.registry.util.CidrAddressBlock;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -42,10 +43,14 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
|
||||
Optional.of(CertificateSamples.SAMPLE_CERT3_HASH);
|
||||
private static final Optional<String> BAD_CERT_HASH =
|
||||
Optional.of(CertificateSamples.SAMPLE_CERT2_HASH);
|
||||
private static final Optional<String> GOOD_IP = Optional.of("192.168.1.1");
|
||||
private static final Optional<String> BAD_IP = Optional.of("1.1.1.1");
|
||||
private static final Optional<String> GOOD_IPV6 = Optional.of("2001:db8::1");
|
||||
private static final Optional<String> BAD_IPV6 = Optional.of("2001:db8::2");
|
||||
private static final Optional<InetAddress> GOOD_IP =
|
||||
Optional.of(InetAddresses.forString("192.168.1.1"));
|
||||
private static final Optional<InetAddress> BAD_IP =
|
||||
Optional.of(InetAddresses.forString("1.1.1.1"));
|
||||
private static final Optional<InetAddress> GOOD_IPV6 =
|
||||
Optional.of(InetAddresses.forString("2001:db8::1"));
|
||||
private static final Optional<InetAddress> BAD_IPV6 =
|
||||
Optional.of(InetAddresses.forString("2001:db8::2"));
|
||||
private final CertificateChecker certificateChecker =
|
||||
new CertificateChecker(
|
||||
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
|
||||
@@ -59,8 +64,7 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
|
||||
protected Registrar.Builder getRegistrarBuilder() {
|
||||
return super.getRegistrarBuilder()
|
||||
.setClientCertificate(GOOD_CERT.get(), DateTime.now(UTC))
|
||||
.setIpAddressAllowList(
|
||||
ImmutableList.of(CidrAddressBlock.create(InetAddresses.forString(GOOD_IP.get()), 32)));
|
||||
.setIpAddressAllowList(ImmutableList.of(CidrAddressBlock.create(GOOD_IP.get(), 32)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -129,7 +133,7 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
|
||||
CidrAddressBlock.create(InetAddresses.forString("192.168.1.1"), 32),
|
||||
CidrAddressBlock.create(InetAddresses.forString("2001:db8::1"), 128)))
|
||||
.build());
|
||||
credentials = new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, certificateChecker);
|
||||
credentials = new TlsCredentials(true, GOOD_CERT_HASH, Optional.empty(), certificateChecker);
|
||||
doFailingTest("login_valid.xml", BadRegistrarIpAddressException.class);
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -219,10 +219,10 @@ public abstract class JpaTransactionManagerExtension
|
||||
recreateSchema();
|
||||
}
|
||||
JpaTransactionManagerImpl txnManager = new JpaTransactionManagerImpl(emf, clock);
|
||||
JpaTransactionManagerImpl readOnlyTxnManager = new JpaTransactionManagerImpl(emf, clock, true);
|
||||
cachedTm = TransactionManagerFactory.tm();
|
||||
TransactionManagerFactory.setJpaTm(Suppliers.ofInstance(txnManager));
|
||||
TransactionManagerFactory.setReplicaJpaTm(
|
||||
Suppliers.ofInstance(new ReplicaSimulatingJpaTransactionManager(txnManager)));
|
||||
TransactionManagerFactory.setReplicaJpaTm(Suppliers.ofInstance(readOnlyTxnManager));
|
||||
// Reset SQL Sequence based id allocation so that ids are deterministic in tests.
|
||||
TransactionManagerFactory.tm()
|
||||
.transact(
|
||||
|
||||
+39
@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.persistence.PersistenceModule.TransactionIsolationLevel.TRANSACTION_READ_COMMITTED;
|
||||
import static google.registry.persistence.PersistenceModule.TransactionIsolationLevel.TRANSACTION_READ_UNCOMMITTED;
|
||||
import static google.registry.persistence.PersistenceModule.TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.testing.DatabaseHelper.assertDetachedFromEntityManager;
|
||||
import static google.registry.testing.DatabaseHelper.existsInDb;
|
||||
@@ -107,6 +108,44 @@ class JpaTransactionManagerImplTest {
|
||||
assertCompanyExist("Bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void transact_replica_failureOnWrite() {
|
||||
assertPersonEmpty();
|
||||
assertCompanyEmpty();
|
||||
DatabaseException thrown =
|
||||
assertThrows(
|
||||
DatabaseException.class,
|
||||
() ->
|
||||
replicaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
insertPerson(10);
|
||||
}));
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
.contains("cannot execute INSERT in a read-only transaction");
|
||||
}
|
||||
|
||||
@Test
|
||||
void transact_replica_successOnRead() {
|
||||
assertPersonEmpty();
|
||||
assertCompanyEmpty();
|
||||
tm().transact(
|
||||
() -> {
|
||||
insertPerson(10);
|
||||
});
|
||||
replicaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
EntityManager em = replicaTm().getEntityManager();
|
||||
Integer maybeAge =
|
||||
(Integer)
|
||||
em.createNativeQuery("SELECT age FROM Person WHERE age = 10")
|
||||
.getSingleResult();
|
||||
assertThat(maybeAge).isEqualTo(10);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void transact_setIsolationLevel() {
|
||||
// If not specified, run at the default isolation level.
|
||||
|
||||
-289
@@ -1,289 +0,0 @@
|
||||
// Copyright 2022 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.persistence.transaction;
|
||||
|
||||
import static com.google.common.base.Throwables.throwIfUnchecked;
|
||||
import static google.registry.persistence.transaction.DatabaseException.throwIfSqlException;
|
||||
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.stream.Stream;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.Query;
|
||||
import javax.persistence.TypedQuery;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* A {@link JpaTransactionManager} that simulates a read-only replica SQL instance.
|
||||
*
|
||||
* <p>We accomplish this by delegating all calls to the standard transaction manager except for
|
||||
* calls that start transactions. For these, we create a transaction like normal but set it to READ
|
||||
* ONLY mode before doing any work. This is similar to how the read-only Postgres replica works; it
|
||||
* treats all transactions as read-only transactions.
|
||||
*/
|
||||
public class ReplicaSimulatingJpaTransactionManager implements JpaTransactionManager {
|
||||
|
||||
private final JpaTransactionManager delegate;
|
||||
|
||||
public ReplicaSimulatingJpaTransactionManager(JpaTransactionManager delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teardown() {
|
||||
delegate.teardown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionIsolationLevel getDefaultTransactionIsolationLevel() {
|
||||
return delegate.getDefaultTransactionIsolationLevel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionIsolationLevel getCurrentTransactionIsolationLevel() {
|
||||
return delegate.getCurrentTransactionIsolationLevel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityManager getStandaloneEntityManager() {
|
||||
return delegate.getStandaloneEntityManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityManager getEntityManager() {
|
||||
return delegate.getEntityManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> TypedQuery<T> query(String sqlString, Class<T> resultClass) {
|
||||
return delegate.query(sqlString, resultClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> TypedQuery<T> criteriaQuery(CriteriaQuery<T> criteriaQuery) {
|
||||
return delegate.criteriaQuery(criteriaQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query query(String sqlString) {
|
||||
return delegate.query(sqlString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inTransaction() {
|
||||
return delegate.inTransaction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertInTransaction() {
|
||||
delegate.assertInTransaction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T transact(Callable<T> work, TransactionIsolationLevel isolationLevel) {
|
||||
if (inTransaction()) {
|
||||
try {
|
||||
return work.call();
|
||||
} catch (Exception e) {
|
||||
throwIfSqlException(e);
|
||||
throwIfUnchecked(e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return delegate.transact(
|
||||
() -> {
|
||||
delegate
|
||||
.getEntityManager()
|
||||
.createNativeQuery("SET TRANSACTION READ ONLY")
|
||||
.executeUpdate();
|
||||
return work.call();
|
||||
},
|
||||
isolationLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T reTransact(Callable<T> work) {
|
||||
return transact(work);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T transact(Callable<T> work) {
|
||||
return transact(work, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transact(ThrowingRunnable work, TransactionIsolationLevel isolationLevel) {
|
||||
transact(
|
||||
() -> {
|
||||
work.run();
|
||||
return null;
|
||||
},
|
||||
isolationLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reTransact(ThrowingRunnable work) {
|
||||
transact(work);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transact(ThrowingRunnable work) {
|
||||
transact(work, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DateTime getTransactionTime() {
|
||||
return delegate.getTransactionTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(Object entity) {
|
||||
delegate.insert(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertAll(ImmutableCollection<?> entities) {
|
||||
delegate.insertAll(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertAll(ImmutableObject... entities) {
|
||||
delegate.insertAll(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(Object entity) {
|
||||
delegate.put(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(ImmutableObject... entities) {
|
||||
delegate.putAll(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(ImmutableCollection<?> entities) {
|
||||
delegate.putAll(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Object entity) {
|
||||
delegate.update(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAll(ImmutableCollection<?> entities) {
|
||||
delegate.updateAll(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAll(ImmutableObject... entities) {
|
||||
delegate.updateAll(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean exists(VKey<T> key) {
|
||||
return delegate.exists(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(Object entity) {
|
||||
return delegate.exists(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> loadByKeyIfPresent(VKey<T> key) {
|
||||
return delegate.loadByKeyIfPresent(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ImmutableMap<VKey<? extends T>, T> loadByKeysIfPresent(
|
||||
Iterable<? extends VKey<? extends T>> vKeys) {
|
||||
return delegate.loadByKeysIfPresent(vKeys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ImmutableList<T> loadByEntitiesIfPresent(Iterable<T> entities) {
|
||||
return delegate.loadByEntitiesIfPresent(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T loadByKey(VKey<T> key) {
|
||||
return delegate.loadByKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ImmutableMap<VKey<? extends T>, T> loadByKeys(
|
||||
Iterable<? extends VKey<? extends T>> vKeys) {
|
||||
return delegate.loadByKeys(vKeys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T loadByEntity(T entity) {
|
||||
return delegate.loadByEntity(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ImmutableList<T> loadByEntities(Iterable<T> entities) {
|
||||
return delegate.loadByEntities(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ImmutableList<T> loadAllOf(Class<T> clazz) {
|
||||
return delegate.loadAllOf(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Stream<T> loadAllOfStream(Class<T> clazz) {
|
||||
return delegate.loadAllOfStream(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> loadSingleton(Class<T> clazz) {
|
||||
return delegate.loadSingleton(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(VKey<?> key) {
|
||||
delegate.delete(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Iterable<? extends VKey<?>> vKeys) {
|
||||
delegate.delete(vKeys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T delete(T entity) {
|
||||
return delegate.delete(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> QueryComposer<T> createQueryComposer(Class<T> entity) {
|
||||
return delegate.createQueryComposer(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void assertDelete(VKey<T> key) {
|
||||
delegate.assertDelete(key);
|
||||
}
|
||||
}
|
||||
+1
@@ -53,6 +53,7 @@ abstract class CreateOrUpdateReservedListCommandTestCase<
|
||||
.write("sdfgagmsdgs,sdfgsd\nasdf234tafgs,asdfaw\n\n");
|
||||
reservedTermsPath = reservedTermsFile.getPath();
|
||||
invalidReservedTermsPath = invalidReservedTermsFile.getPath();
|
||||
command.printStream = System.out;
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -66,6 +66,8 @@ class UpdateReservedListCommandTest
|
||||
assertThat(ReservedList.get("xn--q9jyb4c_common-reserved")).isPresent();
|
||||
ReservedList reservedList = ReservedList.get("xn--q9jyb4c_common-reserved").get();
|
||||
assertThat(reservedList.getShouldPublish()).isFalse();
|
||||
assertInStdout("Update reserved list for xn--q9jyb4c_common-reserved?");
|
||||
assertInStdout("shouldPublish: true -> false");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -85,6 +87,10 @@ class UpdateReservedListCommandTest
|
||||
assertThat(reservedList.getReservationInList("baddies")).hasValue(FULLY_BLOCKED);
|
||||
assertThat(reservedList.getReservationInList("ford")).hasValue(FULLY_BLOCKED);
|
||||
assertThat(reservedList.getReservationInList("helicopter")).isEmpty();
|
||||
assertInStdout("Update reserved list for xn--q9jyb4c_common-reserved?");
|
||||
assertInStdout("helicopter: helicopter,FULLY_BLOCKED -> null");
|
||||
assertInStdout("baddies: null -> baddies,FULLY_BLOCKED");
|
||||
assertInStdout("ford: null -> ford,FULLY_BLOCKED # random comment");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -127,11 +133,13 @@ class UpdateReservedListCommandTest
|
||||
// CreateOrUpdateReservedListCommandTestCases.java
|
||||
UpdateReservedListCommand command = new UpdateReservedListCommand();
|
||||
command.input = Paths.get(reservedTermsPath);
|
||||
command.shouldPublish = false;
|
||||
command.init();
|
||||
|
||||
assertThat(command.prompt()).contains("Update reserved list for xn--q9jyb4c_common-reserved?");
|
||||
assertThat(command.prompt()).contains("Old list: [(helicopter,FULLY_BLOCKED)]");
|
||||
assertThat(command.prompt())
|
||||
.contains("New list: [(baddies,FULLY_BLOCKED), (ford,FULLY_BLOCKED # random comment)]");
|
||||
assertThat(command.prompt()).contains("shouldPublish: true -> false");
|
||||
assertThat(command.prompt()).contains("helicopter: helicopter,FULLY_BLOCKED -> null");
|
||||
assertThat(command.prompt()).contains("baddies: null -> baddies,FULLY_BLOCKED");
|
||||
assertThat(command.prompt()).contains("ford: null -> ford,FULLY_BLOCKED # random comment");
|
||||
}
|
||||
}
|
||||
|
||||
+51
-14
@@ -21,6 +21,7 @@ import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistDomainAsDeleted;
|
||||
|
||||
import com.google.api.client.http.HttpStatusCodes;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.model.EppResourceUtils;
|
||||
import google.registry.model.console.GlobalRole;
|
||||
@@ -90,7 +91,7 @@ public class ConsoleDomainListActionTest {
|
||||
@Test
|
||||
void testSuccess_pages() {
|
||||
// Two pages of results should go in reverse chronological order
|
||||
ConsoleDomainListAction action = createAction("TheRegistrar", null, 0, 5, null);
|
||||
ConsoleDomainListAction action = createAction("TheRegistrar", null, 0, 5, null, null);
|
||||
action.run();
|
||||
DomainListResult result = GSON.fromJson(response.getPayload(), DomainListResult.class);
|
||||
assertThat(result.domains.stream().map(Domain::getDomainName).collect(toImmutableList()))
|
||||
@@ -98,7 +99,7 @@ public class ConsoleDomainListActionTest {
|
||||
assertThat(result.totalResults).isEqualTo(10);
|
||||
|
||||
// Now do the second page
|
||||
action = createAction("TheRegistrar", result.checkpointTime, 1, 5, 10L);
|
||||
action = createAction("TheRegistrar", result.checkpointTime, 1, 5, 10L, null);
|
||||
action.run();
|
||||
result = GSON.fromJson(response.getPayload(), DomainListResult.class);
|
||||
assertThat(result.domains.stream().map(Domain::getDomainName).collect(toImmutableList()))
|
||||
@@ -107,7 +108,7 @@ public class ConsoleDomainListActionTest {
|
||||
|
||||
@Test
|
||||
void testSuccess_partialPage() {
|
||||
ConsoleDomainListAction action = createAction("TheRegistrar", null, 1, 8, null);
|
||||
ConsoleDomainListAction action = createAction("TheRegistrar", null, 1, 8, null, null);
|
||||
action.run();
|
||||
DomainListResult result = GSON.fromJson(response.getPayload(), DomainListResult.class);
|
||||
assertThat(result.domains.stream().map(Domain::getDomainName).collect(toImmutableList()))
|
||||
@@ -116,7 +117,7 @@ public class ConsoleDomainListActionTest {
|
||||
|
||||
@Test
|
||||
void testSuccess_checkpointTime_createdBefore() {
|
||||
ConsoleDomainListAction action = createAction("TheRegistrar", null, 0, 10, null);
|
||||
ConsoleDomainListAction action = createAction("TheRegistrar", null, 0, 10, null, null);
|
||||
action.run();
|
||||
|
||||
DomainListResult result = GSON.fromJson(response.getPayload(), DomainListResult.class);
|
||||
@@ -127,7 +128,7 @@ public class ConsoleDomainListActionTest {
|
||||
persistActiveDomain("newdomain.tld", clock.nowUtc());
|
||||
|
||||
// Even though we persisted a new domain, the old checkpoint should return no more results
|
||||
action = createAction("TheRegistrar", result.checkpointTime, 1, 10, null);
|
||||
action = createAction("TheRegistrar", result.checkpointTime, 1, 10, null, null);
|
||||
action.run();
|
||||
result = GSON.fromJson(response.getPayload(), DomainListResult.class);
|
||||
assertThat(result.domains).isEmpty();
|
||||
@@ -136,7 +137,7 @@ public class ConsoleDomainListActionTest {
|
||||
|
||||
@Test
|
||||
void testSuccess_checkpointTime_deletion() {
|
||||
ConsoleDomainListAction action = createAction("TheRegistrar", null, 0, 5, null);
|
||||
ConsoleDomainListAction action = createAction("TheRegistrar", null, 0, 5, null, null);
|
||||
action.run();
|
||||
DomainListResult result = GSON.fromJson(response.getPayload(), DomainListResult.class);
|
||||
|
||||
@@ -146,16 +147,50 @@ public class ConsoleDomainListActionTest {
|
||||
persistDomainAsDeleted(toDelete, clock.nowUtc());
|
||||
|
||||
// Second page should include the domain that is now deleted due to the checkpoint time
|
||||
action = createAction("TheRegistrar", result.checkpointTime, 1, 5, null);
|
||||
action = createAction("TheRegistrar", result.checkpointTime, 1, 5, null, null);
|
||||
action.run();
|
||||
result = GSON.fromJson(response.getPayload(), DomainListResult.class);
|
||||
assertThat(result.domains.stream().map(Domain::getDomainName).collect(toImmutableList()))
|
||||
.containsExactly("4exists.tld", "3exists.tld", "2exists.tld", "1exists.tld", "0exists.tld");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_searchTerm_oneMatch() {
|
||||
ConsoleDomainListAction action = createAction("TheRegistrar", null, 0, 5, null, "0");
|
||||
action.run();
|
||||
DomainListResult result = GSON.fromJson(response.getPayload(), DomainListResult.class);
|
||||
assertThat(Iterables.getOnlyElement(result.domains).getDomainName()).isEqualTo("0exists.tld");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_searchTerm_returnsNone() {
|
||||
ConsoleDomainListAction action = createAction("TheRegistrar", null, 0, 5, null, "deleted");
|
||||
action.run();
|
||||
DomainListResult result = GSON.fromJson(response.getPayload(), DomainListResult.class);
|
||||
assertThat(result.domains).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_searchTerm_caseInsensitive() {
|
||||
ConsoleDomainListAction action = createAction("TheRegistrar", null, 0, 5, null, "eXiStS");
|
||||
action.run();
|
||||
DomainListResult result = GSON.fromJson(response.getPayload(), DomainListResult.class);
|
||||
assertThat(result.domains).hasSize(5);
|
||||
assertThat(result.totalResults).isEqualTo(10);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_searchTerm_tld() {
|
||||
ConsoleDomainListAction action = createAction("TheRegistrar", null, 0, 5, null, "tld");
|
||||
action.run();
|
||||
DomainListResult result = GSON.fromJson(response.getPayload(), DomainListResult.class);
|
||||
assertThat(result.domains).hasSize(5);
|
||||
assertThat(result.totalResults).isEqualTo(10);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPartialSuccess_pastEnd() {
|
||||
ConsoleDomainListAction action = createAction("TheRegistrar", null, 5, 5, null);
|
||||
ConsoleDomainListAction action = createAction("TheRegistrar", null, 5, 5, null, null);
|
||||
action.run();
|
||||
DomainListResult result = GSON.fromJson(response.getPayload(), DomainListResult.class);
|
||||
assertThat(result.domains).isEmpty();
|
||||
@@ -163,13 +198,13 @@ public class ConsoleDomainListActionTest {
|
||||
|
||||
@Test
|
||||
void testFailure_invalidResultsPerPage() {
|
||||
ConsoleDomainListAction action = createAction("TheRegistrar", null, 0, 0, null);
|
||||
ConsoleDomainListAction action = createAction("TheRegistrar", null, 0, 0, null, null);
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_BAD_REQUEST);
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo("Results per page must be between 1 and 500 inclusive");
|
||||
|
||||
action = createAction("TheRegistrar", null, 0, 501, null);
|
||||
action = createAction("TheRegistrar", null, 0, 501, null, null);
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_BAD_REQUEST);
|
||||
assertThat(response.getPayload())
|
||||
@@ -178,14 +213,14 @@ public class ConsoleDomainListActionTest {
|
||||
|
||||
@Test
|
||||
void testFailure_invalidPageNumber() {
|
||||
ConsoleDomainListAction action = createAction("TheRegistrar", null, -1, 10, null);
|
||||
ConsoleDomainListAction action = createAction("TheRegistrar", null, -1, 10, null, null);
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_BAD_REQUEST);
|
||||
assertThat(response.getPayload()).isEqualTo("Page number must be non-negative");
|
||||
}
|
||||
|
||||
private ConsoleDomainListAction createAction(String registrarId) {
|
||||
return createAction(registrarId, null, null, null, null);
|
||||
return createAction(registrarId, null, null, null, null, null);
|
||||
}
|
||||
|
||||
private ConsoleDomainListAction createAction(
|
||||
@@ -193,7 +228,8 @@ public class ConsoleDomainListActionTest {
|
||||
@Nullable DateTime checkpointTime,
|
||||
@Nullable Integer pageNumber,
|
||||
@Nullable Integer resultsPerPage,
|
||||
@Nullable Long totalResults) {
|
||||
@Nullable Long totalResults,
|
||||
@Nullable String searchTerm) {
|
||||
response = new FakeResponse();
|
||||
AuthResult authResult =
|
||||
AuthResult.createUser(
|
||||
@@ -210,6 +246,7 @@ public class ConsoleDomainListActionTest {
|
||||
Optional.ofNullable(checkpointTime),
|
||||
Optional.ofNullable(pageNumber),
|
||||
Optional.ofNullable(resultsPerPage),
|
||||
Optional.ofNullable(totalResults));
|
||||
Optional.ofNullable(totalResults),
|
||||
Optional.ofNullable(searchTerm));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +124,7 @@ public class EppServiceHandler extends HttpsRelayServiceHandler {
|
||||
.headers()
|
||||
.set(ProxyHttpHeaders.CERTIFICATE_HASH, sslClientCertificateHash)
|
||||
.set(ProxyHttpHeaders.IP_ADDRESS, clientAddress)
|
||||
.set(ProxyHttpHeaders.FALLBACK_IP_ADDRESS, clientAddress)
|
||||
.set(HttpHeaderNames.CONTENT_TYPE, EPP_CONTENT_TYPE)
|
||||
.set(HttpHeaderNames.ACCEPT, EPP_CONTENT_TYPE);
|
||||
return request;
|
||||
|
||||
@@ -100,7 +100,8 @@ public final class TestUtils {
|
||||
.set(HttpHeaderNames.CONTENT_TYPE, "application/epp+xml")
|
||||
.set("accept", "application/epp+xml")
|
||||
.set(ProxyHttpHeaders.CERTIFICATE_HASH, sslClientCertificateHash)
|
||||
.set(ProxyHttpHeaders.IP_ADDRESS, clientAddress);
|
||||
.set(ProxyHttpHeaders.IP_ADDRESS, clientAddress)
|
||||
.set(ProxyHttpHeaders.FALLBACK_IP_ADDRESS, clientAddress);
|
||||
if (cookies.length != 0) {
|
||||
request.headers().set("cookie", ClientCookieEncoder.STRICT.encode(cookies));
|
||||
}
|
||||
|
||||
@@ -19,30 +19,25 @@ import com.google.common.net.HttpHeaders;
|
||||
/** Utility class of HTTP header names used for HTTP calls between Nomulus and the proxy. */
|
||||
public final class ProxyHttpHeaders {
|
||||
|
||||
/**
|
||||
* HTTP header name used to pass a full SSL certificate from the proxy to Nomulus.
|
||||
*
|
||||
* <p>This header contains the SSL certificate encoded to a string. It is used to pass the client
|
||||
* certificate used for login to Nomulus for validation.
|
||||
*/
|
||||
public static final String FULL_CERTIFICATE = "X-SSL-Full-Certificate";
|
||||
|
||||
/** HTTP header name used to pass the certificate hash from the proxy to Nomulus. */
|
||||
public static final String CERTIFICATE_HASH = "X-SSL-Certificate";
|
||||
|
||||
/**
|
||||
* HTTP header name passed from Nomulus to proxy to indicate that a client has successfully logged
|
||||
* in.
|
||||
*/
|
||||
public static final String LOGGED_IN = "Logged-In";
|
||||
|
||||
/**
|
||||
* HTTP header name passed from Nomulus to proxy to indicate that an EPP session should be closed.
|
||||
*/
|
||||
public static final String EPP_SESSION = "Epp-Session";
|
||||
|
||||
/** HTTP header name used to pass the client IP address from the proxy to Nomulus. */
|
||||
public static final String IP_ADDRESS = HttpHeaders.X_FORWARDED_FOR;
|
||||
public static final String IP_ADDRESS = "Nomulus-Client-Address";
|
||||
|
||||
/**
|
||||
* Fallback HTTP header name used to pass the client IP address from the proxy to Nomulus.
|
||||
*
|
||||
* <p>Note that Java 17's servlet implementation (at least on App Engine) injects some seemingly
|
||||
* unrelated addresses into this header. We only use this as a fallback so the proxy can
|
||||
* transition to use the above header that should not be interfered with.
|
||||
*/
|
||||
public static final String FALLBACK_IP_ADDRESS = HttpHeaders.X_FORWARDED_FOR;
|
||||
|
||||
private ProxyHttpHeaders() {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user