1
0
mirror of https://github.com/google/nomulus synced 2026-06-09 16:33:02 +00:00

Compare commits

...

16 Commits

Author SHA1 Message Date
Weimin Yu 1f4cf5bdb6 Fix flyway invocation in Gradle script (#993)
* Fix flyway invocation in Gradle script

Script wrongly assumed that Flyway task is invoked if --environment is
set.

Bug was introduced in go/r3pr/940
2021-03-08 13:59:28 -05:00
gbrodman 4176f7dd9c Convert DomainTransferRejectFlow to use tm() methods (#977)
* Convert DomainTransferRejectFlow to use tm() methods

This change includes a few other necessary dependencies to converting
DomainTransferRejectFlowTest to be a dual-database test. Namely:

- The basic "use tm() instead of ofy()" and "branching database
selection on what were previously raw ofy queries"
- Modification of the PollMessage convertVKey methods to do what they
say they do
- Filling the generic pending / response fields in PollMessage based on what type of
poll message it is (this has to be done because SQL is not very good at
storing ambiguous superclasses)
- Setting the generic pending / repsonse fields in PollMessage upon
build
- Filling out the serverApproveEntities field in DomainTransferData with
all necessary poll messages / billing events that should be cancelled on
rejection
- Scattered changes in DatabaseHelper to make sure that we're saving and
loading entities correctly where we weren't before
2021-03-08 13:24:30 -05:00
Michael Muller e07139665e Disable whois caching in nomulus tool (#980)
* Disable whois caching in nomulus tool

The whois commands previously served output generated from cached EppResource
objects in most cases.  While this is entirely appropriate from server-side,
it is less useful when these commands are run from nomulus tool and, in fact,
when run from the "shell" command this results in changes that have been
applied from the shell not being visible from a subsequent "whois".  The
command may instead serve information on an earlier, cached version of the
resource instead of the latest version.

This implementation uses dagger for parameterization of cached/non-cached
modes.  I did consider the possibility of simply parameterizing the query
commands in all cases as discussed, however, having gone down the
daggerization path and having gotten it to work, I have to say I find this
approach preferrable.  There's really no case for identifying
cached/non-cached on a per-command basis and doing so would require
propagating the flag throughout all levels of the API and all callsites.

Tested: In addition to the new unit test which explicitly verifies the
caching/noncaching behavior of the new commands, tested the actual failing
sequence from "nomulus -e sandbox shell" and verified that the correct results
are served after a mutation.

* Fixed copyright year

* More copyright date fixes

* Added WhoisCommandFactoryTest to fragile tests

I suspect that this test has some contention with other tests, it's not clear
why.
2021-03-08 12:33:23 -05:00
Michael Muller b75eb0ad95 Replay Cloud SQL transactions against datastore (#738)
* Replay Cloud SQL transactions against datastore

Implement the ReplicateToDatastore cron job that will apply all Cloud SQL
transactions to the datastore.  The last transaction id is stored in a
LastSqlTransaction entity in datastore.

Note that this will not be activated in production until a) the cron
configuration is updated and b) the cloudSql.replicateTransactions flag is set
to true in the nomulus config file.

* Post-review changes

Fixed immutability issues with LastSqlTransaction, write a single transaction
at a time to datastore.

* Changes requested in review

* Get a batch of SQL transactions

Read a batch of SQL transactions at a time and then process them
transactionally against datastore.

* Bring this up-to-date with the codebase

* Changes requested in review

* Fixed date in copyright
2021-03-05 10:35:30 -05:00
Weimin Yu 63f8fcef18 Clean up Gradle Flyway tasks in :db (#990)
* Clean up Gradle Flyway tasks in :db

Simplified the command line by revising the semantics of some
properties.

Added examples of Flyway task invocations.

This script still uses the GCS file-based credential. We will migrate it
to the Secret Manager soon.
2021-03-04 19:58:09 -05:00
Michael Muller 8d563c4516 Add replay & compare to more flow tests (#975)
* Add replay & compare to more flow tests

Add replay and comparison testing to another batch of flow tests, apply fixes
as needed.
2021-03-04 08:25:47 -05:00
Lai Jiang a58c4a6492 Log forbidden HTTP request method at warning (#983)
* Log forbidden HTTP request method at warning

This seems like more reasonable. It will potential issues with how
requests are generated more discoverable in the log.
2021-03-03 21:02:06 -05:00
Weimin Yu e59475a5f6 Allow AppEngine deployment to qa environment (#986)
* Allow AppEngine deployment to qa environment
2021-03-03 19:31:08 -05:00
sarahcaseybot c35f92f54b Reject handshakes with bad TLS protocols and ciphers (#970)
* Reject handshakes with bad TLS protocols and ciphers

* Fix protocols

* make cipher suite list static and fix tests

* Delete unnecessary line

* Add start time configuration for enforcement

* small format fix

* Add multiple ciphersuite test

* fix gradle lint

* fix indentation
2021-03-03 15:47:42 -05:00
Ben McIlwain cd415fe846 Actually log the error in DeleteExpiredDomainsAction (#985)
* Actually log the error in DeleteExpiredDomainsAction
2021-03-03 15:21:13 -05:00
Lai Jiang 4ca2c11b20 Use fanout action to run ICANN report upload job in cron (#984)
GAE cron only issuse HTTP GET requests to the endpoint in question. This
particular only allows POSTs. As a result this cron job never succeeded.
This is not a big problem as this job is meant to catch up any
unforeseen upload failures and in case it needs to catch up but fails,
every month the staging job (which is enqueued corrected by cron) will
eventually catch everything to date.
2021-03-03 09:00:34 -05:00
Lai Jiang 4e44a98139 Update a few plugins for Java 11 compatibility (#966)
* Update a few plugins for Java 11 compatibility

Guice 5.0.1 is now compatible with Java 11. However we don't
directly depend on Guice. Rather Soy depends on Guice. So I added a
direct dependency on Guice 5.0 just before Soy in order to frontload Soy
and pull in the newer version.

Mockito 3.7.7 is now compatible with Java 11. The complication is that
we need to use the inline version of Mockito, which among other things
also allows mocking for final classes (hooray!). It will eventually
become the default Mockito mock maker but for now it needs to be
manually activated.

Note that the inline version now introduces another warning:

```
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
```

Which I think is WAI due to how the inline mock maker works. Waiting on
the author to confirm.

After these to changes the only illegal reflective access is caused by
App Engine SDK tools, which we will rid ourselves of when we migrate off
of GAE.

* Restore package-lock.json
2021-03-02 21:01:30 -05:00
gbrodman d4371a880e Add GetPremiumListCommand (#972)
* Add GetPremiumListCommand

When testing the premium list refactor, it would have been nice and
convenient to have this. Currently we have no way of inspecting premium
list contents that I'm aware of.
2021-03-02 19:59:12 -05:00
gbrodman 7312bc9e60 Add SQL searching to RdapEntitySearchAction and RdapSearchActionBase (#969)
- Adds a CriteriaQueryBuilder class that allows us to build
CriteriaQuery objects with sane and modular WHERE and ORDER BY clauses.
CriteriaQuery requires that all WHERE and ORDER BY clauses be specified
at the same time (else later ones will overwrite the earlier ones) so in
order to have a proper builder pattern we need to wait to build the
query object until we are done adding clauses.

- In addition, encapsulating the query logic in the CriteriaQueryBuilder
class means that we don't need to deal with the complicated Root/Path
branching, otherwise we'd have to keep track of CriteriaQuery and Root
objects everywhere.

- Added a REPLAYED_ENTITIES TransitionId that will represent all
replayed entities, e.g. EppResources. Also sets this, by default, to
always be CLOUD_SQL if we're using the SQL transaction manager in tests.

- Added branching logic in RdapEntitySearchAction based on that transition
ID that determines whether we do the existing ofy query logic or JPA
logic.
2021-03-02 13:13:55 -05:00
Michael Muller 83ca9d82df Add replay & compare to ContactCreateFlowTest test (#976)
* Add replay & compare to ContactCreateFlowTest test

This test exposed a write priority issue between ContactResource and
ContactHistory entries.
2021-03-02 11:50:03 -05:00
Lai Jiang 8724ef6c70 Upgrade to Gradle 6.8.3 (#979)
* Upgrade to Gradle 6.8.3
2021-03-01 21:11:06 -05:00
123 changed files with 2580 additions and 581 deletions
+2 -2
View File
@@ -121,7 +121,7 @@ task stage {
// App-engine environment configuration. We set up all of the variables in
// the root project.
def environments = ['production', 'sandbox', 'alpha', 'crash']
def environments = ['production', 'sandbox', 'alpha', 'crash', 'qa']
def gcpProject = null
@@ -155,7 +155,7 @@ def verifyDeploymentParams() {
System.err.println('-----------------------------------------------------------------')
throw new GradleException('Aborting. See prominent error above.')
} else if (gcpProject == null) {
def error = 'You must specify -P environment={alpha,crash}'
def error = 'You must specify -P environment={alpha,crash,qa}'
System.err.println("\033[33;1m${error}\033[0m")
throw GradleException("Aborting: ${error}")
}
+4 -3
View File
@@ -71,12 +71,13 @@ dependencies {
def deps = dependencyMap
compile deps['com.google.auth:google-auth-library-credentials']
compile deps['com.google.auth:google-auth-library-oauth2-http']
compile deps['com.google.cloud:google-cloud-core']
compile deps['com.google.guava:guava']
compile deps['com.google.auto.value:auto-value-annotations']
compile deps['com.google.cloud:google-cloud-core']
compile deps['com.google.cloud:google-cloud-storage']
compile deps['org.apache.commons:commons-text']
compile deps['com.google.guava:guava']
compile deps['com.google.protobuf:protobuf-java']
compile deps['com.google.template:soy']
compile deps['org.apache.commons:commons-text']
annotationProcessor deps['com.google.auto.value:auto-value']
testCompile deps['com.google.truth:truth']
testCompile deps['com.google.truth.extensions:truth-java8-extension']
@@ -33,7 +33,7 @@ com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
com.google.oauth-client:google-oauth-client:1.27.0
com.google.protobuf:protobuf-java-util:3.6.1
com.google.protobuf:protobuf-java:3.6.1
com.google.protobuf:protobuf-java:3.13.0
com.google.template:soy:2018-03-14
com.ibm.icu:icu4j:57.1
commons-codec:commons-codec:1.11
@@ -33,7 +33,7 @@ com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
com.google.oauth-client:google-oauth-client:1.27.0
com.google.protobuf:protobuf-java-util:3.6.1
com.google.protobuf:protobuf-java:3.6.1
com.google.protobuf:protobuf-java:3.13.0
com.google.template:soy:2018-03-14
com.ibm.icu:icu4j:57.1
commons-codec:commons-codec:1.11
@@ -33,7 +33,7 @@ com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
com.google.oauth-client:google-oauth-client:1.27.0
com.google.protobuf:protobuf-java-util:3.6.1
com.google.protobuf:protobuf-java:3.6.1
com.google.protobuf:protobuf-java:3.13.0
com.google.template:soy:2018-03-14
com.ibm.icu:icu4j:57.1
commons-codec:commons-codec:1.11
@@ -33,7 +33,7 @@ com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
com.google.oauth-client:google-oauth-client:1.27.0
com.google.protobuf:protobuf-java-util:3.6.1
com.google.protobuf:protobuf-java:3.6.1
com.google.protobuf:protobuf-java:3.13.0
com.google.template:soy:2018-03-14
com.ibm.icu:icu4j:57.1
commons-codec:commons-codec:1.11
@@ -33,7 +33,7 @@ com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
com.google.oauth-client:google-oauth-client:1.27.0
com.google.protobuf:protobuf-java-util:3.6.1
com.google.protobuf:protobuf-java:3.6.1
com.google.protobuf:protobuf-java:3.13.0
com.google.template:soy:2018-03-14
com.ibm.icu:icu4j:57.1
commons-codec:commons-codec:1.11
@@ -33,7 +33,7 @@ com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
com.google.oauth-client:google-oauth-client:1.27.0
com.google.protobuf:protobuf-java-util:3.6.1
com.google.protobuf:protobuf-java:3.6.1
com.google.protobuf:protobuf-java:3.13.0
com.google.template:soy:2018-03-14
com.google.truth.extensions:truth-java8-extension:1.0
com.google.truth:truth:1.0
@@ -50,8 +50,8 @@ javax.inject:javax.inject:1
javax.validation:validation-api:1.0.0.GA
joda-time:joda-time:2.9.2
junit:junit:4.12
net.bytebuddy:byte-buddy-agent:1.10.5
net.bytebuddy:byte-buddy:1.10.5
net.bytebuddy:byte-buddy-agent:1.10.19
net.bytebuddy:byte-buddy:1.10.19
org.apache.commons:commons-lang3:3.8.1
org.apache.commons:commons-text:1.6
org.apache.httpcomponents:httpclient:4.5.8
@@ -66,8 +66,8 @@ org.junit.jupiter:junit-jupiter-engine:5.6.2
org.junit.platform:junit-platform-commons:1.6.2
org.junit.platform:junit-platform-engine:1.6.2
org.junit:junit-bom:5.6.2
org.mockito:mockito-core:3.3.3
org.objenesis:objenesis:2.6
org.mockito:mockito-core:3.7.7
org.objenesis:objenesis:3.1
org.opentest4j:opentest4j:1.2.0
org.ow2.asm:asm-analysis:6.0
org.ow2.asm:asm-commons:6.0
@@ -33,7 +33,7 @@ com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
com.google.oauth-client:google-oauth-client:1.27.0
com.google.protobuf:protobuf-java-util:3.6.1
com.google.protobuf:protobuf-java:3.6.1
com.google.protobuf:protobuf-java:3.13.0
com.google.template:soy:2018-03-14
com.google.truth.extensions:truth-java8-extension:1.0
com.google.truth:truth:1.0
@@ -50,8 +50,8 @@ javax.inject:javax.inject:1
javax.validation:validation-api:1.0.0.GA
joda-time:joda-time:2.9.2
junit:junit:4.12
net.bytebuddy:byte-buddy-agent:1.10.5
net.bytebuddy:byte-buddy:1.10.5
net.bytebuddy:byte-buddy-agent:1.10.19
net.bytebuddy:byte-buddy:1.10.19
org.apache.commons:commons-lang3:3.8.1
org.apache.commons:commons-text:1.6
org.apache.httpcomponents:httpclient:4.5.8
@@ -66,8 +66,8 @@ org.junit.jupiter:junit-jupiter-engine:5.6.2
org.junit.platform:junit-platform-commons:1.6.2
org.junit.platform:junit-platform-engine:1.6.2
org.junit:junit-bom:5.6.2
org.mockito:mockito-core:3.3.3
org.objenesis:objenesis:2.6
org.mockito:mockito-core:3.7.7
org.objenesis:objenesis:3.1
org.opentest4j:opentest4j:1.2.0
org.ow2.asm:asm-analysis:6.0
org.ow2.asm:asm-commons:6.0
@@ -33,7 +33,7 @@ com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
com.google.oauth-client:google-oauth-client:1.27.0
com.google.protobuf:protobuf-java-util:3.6.1
com.google.protobuf:protobuf-java:3.6.1
com.google.protobuf:protobuf-java:3.13.0
com.google.template:soy:2018-03-14
com.google.truth.extensions:truth-java8-extension:1.0
com.google.truth:truth:1.0
@@ -50,8 +50,8 @@ javax.inject:javax.inject:1
javax.validation:validation-api:1.0.0.GA
joda-time:joda-time:2.9.2
junit:junit:4.12
net.bytebuddy:byte-buddy-agent:1.10.5
net.bytebuddy:byte-buddy:1.10.5
net.bytebuddy:byte-buddy-agent:1.10.19
net.bytebuddy:byte-buddy:1.10.19
org.apache.commons:commons-lang3:3.8.1
org.apache.commons:commons-text:1.6
org.apache.httpcomponents:httpclient:4.5.8
@@ -66,8 +66,8 @@ org.junit.jupiter:junit-jupiter-engine:5.6.2
org.junit.platform:junit-platform-commons:1.6.2
org.junit.platform:junit-platform-engine:1.6.2
org.junit:junit-bom:5.6.2
org.mockito:mockito-core:3.3.3
org.objenesis:objenesis:2.6
org.mockito:mockito-core:3.7.7
org.objenesis:objenesis:3.1
org.opentest4j:opentest4j:1.2.0
org.ow2.asm:asm-analysis:6.0
org.ow2.asm:asm-commons:6.0
@@ -33,7 +33,7 @@ com.google.inject:guice:4.1.0
com.google.j2objc:j2objc-annotations:1.3
com.google.oauth-client:google-oauth-client:1.27.0
com.google.protobuf:protobuf-java-util:3.6.1
com.google.protobuf:protobuf-java:3.6.1
com.google.protobuf:protobuf-java:3.13.0
com.google.template:soy:2018-03-14
com.google.truth.extensions:truth-java8-extension:1.0
com.google.truth:truth:1.0
@@ -50,8 +50,8 @@ javax.inject:javax.inject:1
javax.validation:validation-api:1.0.0.GA
joda-time:joda-time:2.9.2
junit:junit:4.12
net.bytebuddy:byte-buddy-agent:1.10.5
net.bytebuddy:byte-buddy:1.10.5
net.bytebuddy:byte-buddy-agent:1.10.19
net.bytebuddy:byte-buddy:1.10.19
org.apache.commons:commons-lang3:3.8.1
org.apache.commons:commons-text:1.6
org.apache.httpcomponents:httpclient:4.5.8
@@ -66,8 +66,8 @@ org.junit.jupiter:junit-jupiter-engine:5.6.2
org.junit.platform:junit-platform-commons:1.6.2
org.junit.platform:junit-platform-engine:1.6.2
org.junit:junit-bom:5.6.2
org.mockito:mockito-core:3.3.3
org.objenesis:objenesis:2.6
org.mockito:mockito-core:3.7.7
org.objenesis:objenesis:3.1
org.opentest4j:opentest4j:1.2.0
org.ow2.asm:asm-analysis:6.0
org.ow2.asm:asm-commons:6.0
@@ -0,0 +1 @@
mock-maker-inline
+3 -2
View File
@@ -111,8 +111,9 @@ PROPERTIES = [
# Cloud SQL properties
Property('dbServer',
'A registry environment name (e.g., "alpha") or a host[:port] '
'string'),
'Sets the target database of a Flyway task. This may be a '
'registry environment name (e.g., alpha) or the host[:port] '
'of a database that accepts direct IP access.'),
Property('dbName',
'Database name to use in connection.',
'postgres'),
+3
View File
@@ -207,6 +207,9 @@ PRESUBMITS = {
"ForeignKeyIndex.java",
"HistoryEntryDao.java",
"JpaTransactionManagerImpl.java",
# CriteriaQueryBuilder is a false positive
"CriteriaQueryBuilder.java",
"RdapSearchActionBase.java",
},
):
"The first String parameter to EntityManager.create(Native)Query "
+5
View File
@@ -76,6 +76,9 @@ def fragileTestPatterns = [
"google/registry/model/tmch/ClaimsListShardTest.*",
// Creates large object (64MBytes), occasionally throws OOM error.
"google/registry/model/server/KmsSecretRevisionTest.*",
// Changes cache timeouts and for some reason appears to have contention
// with other tests.
"google/registry/whois/WhoisCommandFactoryTest.*",
] + dockerIncompatibleTestPatterns
sourceSets {
@@ -344,6 +347,8 @@ dependencies {
jaxb deps['com.sun.xml.bind:jaxb-osgi']
// Dependency needed for soy to java compilation.
soy deps['com.google.inject:guice']
soy deps['com.google.protobuf:protobuf-java']
soy deps['com.google.template:soy']
// Dependencies needed for compiling stylesheets to javascript
+9 -4
View File
@@ -3,19 +3,24 @@
# This file is expected to be part of source control.
aopalliance:aopalliance:1.0
args4j:args4j:2.0.23
com.google.code.findbugs:jsr305:2.0.3
com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.7
com.google.common.html.types:types:1.0.4
com.google.guava:guava:21.0
com.google.errorprone:error_prone_annotations:2.3.4
com.google.guava:failureaccess:1.0.1
com.google.guava:guava:30.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.gwt:gwt-user:2.8.0-beta1
com.google.inject.extensions:guice-multibindings:4.1.0
com.google.inject:guice:4.1.0
com.google.protobuf:protobuf-java:3.3.0
com.google.inject:guice:5.0.1
com.google.j2objc:j2objc-annotations:1.3
com.google.protobuf:protobuf-java:3.13.0
com.google.template:soy:2018-03-14
com.ibm.icu:icu4j:57.1
javax.annotation:jsr250-api:1.0
javax.inject:javax.inject:1
javax.validation:validation-api:1.0.0.GA
org.checkerframework:checker-qual:3.5.0
org.json:json:20160212
org.ow2.asm:asm-analysis:6.0
org.ow2.asm:asm-commons:6.0
@@ -198,8 +198,8 @@ javax.xml.bind:jaxb-api:2.3.1
jline:jline:1.0
joda-time:joda-time:2.10.5
junit:junit:4.13.1
net.bytebuddy:byte-buddy-agent:1.10.5
net.bytebuddy:byte-buddy:1.10.17
net.bytebuddy:byte-buddy-agent:1.10.19
net.bytebuddy:byte-buddy:1.10.19
net.java.dev.jna:jna:5.5.0
org.apache.avro:avro:1.8.2
org.apache.beam:beam-model-fn-execution:2.27.0
@@ -273,11 +273,11 @@ org.junit.platform:junit-platform-runner:1.7.0
org.junit.platform:junit-platform-suite-api:1.7.0
org.junit:junit-bom:5.7.0
org.jvnet.staxex:stax-ex:1.8
org.mockito:mockito-core:3.3.3
org.mockito:mockito-junit-jupiter:3.3.3
org.mockito:mockito-core:3.7.7
org.mockito:mockito-junit-jupiter:3.7.7
org.mortbay.jetty:jetty-util:6.1.26
org.mortbay.jetty:jetty:6.1.26
org.objenesis:objenesis:2.6
org.objenesis:objenesis:3.1
org.opentest4j:opentest4j:1.2.0
org.ow2.asm:asm-analysis:8.0.1
org.ow2.asm:asm-commons:7.1
@@ -193,8 +193,8 @@ javax.xml.bind:jaxb-api:2.3.1
jline:jline:1.0
joda-time:joda-time:2.10.5
junit:junit:4.13.1
net.bytebuddy:byte-buddy-agent:1.10.5
net.bytebuddy:byte-buddy:1.10.17
net.bytebuddy:byte-buddy-agent:1.10.19
net.bytebuddy:byte-buddy:1.10.19
net.java.dev.jna:jna:5.5.0
org.apache.avro:avro:1.8.2
org.apache.beam:beam-model-fn-execution:2.27.0
@@ -267,11 +267,11 @@ org.junit.platform:junit-platform-runner:1.7.0
org.junit.platform:junit-platform-suite-api:1.7.0
org.junit:junit-bom:5.7.0
org.jvnet.staxex:stax-ex:1.8
org.mockito:mockito-core:3.3.3
org.mockito:mockito-junit-jupiter:3.3.3
org.mockito:mockito-core:3.7.7
org.mockito:mockito-junit-jupiter:3.7.7
org.mortbay.jetty:jetty-util:6.1.26
org.mortbay.jetty:jetty:6.1.26
org.objenesis:objenesis:2.6
org.objenesis:objenesis:3.1
org.opentest4j:opentest4j:1.2.0
org.ow2.asm:asm-analysis:8.0.1
org.ow2.asm:asm-commons:7.1
@@ -208,8 +208,8 @@ jline:jline:1.0
joda-time:joda-time:2.10.5
junit:junit:4.13.1
net.arnx:nashorn-promise:0.1.1
net.bytebuddy:byte-buddy-agent:1.10.5
net.bytebuddy:byte-buddy:1.10.17
net.bytebuddy:byte-buddy-agent:1.10.19
net.bytebuddy:byte-buddy:1.10.19
net.java.dev.jna:jna:5.5.0
org.apache.avro:avro:1.8.2
org.apache.beam:beam-model-fn-execution:2.27.0
@@ -285,11 +285,11 @@ org.junit.platform:junit-platform-runner:1.7.0
org.junit.platform:junit-platform-suite-api:1.7.0
org.junit:junit-bom:5.7.0
org.jvnet.staxex:stax-ex:1.8
org.mockito:mockito-core:3.3.3
org.mockito:mockito-junit-jupiter:3.3.3
org.mockito:mockito-core:3.7.7
org.mockito:mockito-junit-jupiter:3.7.7
org.mortbay.jetty:jetty-util:6.1.26
org.mortbay.jetty:jetty:6.1.26
org.objenesis:objenesis:2.6
org.objenesis:objenesis:3.1
org.opentest4j:opentest4j:1.2.0
org.ow2.asm:asm-analysis:8.0.1
org.ow2.asm:asm-commons:7.1
@@ -208,8 +208,8 @@ jline:jline:1.0
joda-time:joda-time:2.10.5
junit:junit:4.13.1
net.arnx:nashorn-promise:0.1.1
net.bytebuddy:byte-buddy-agent:1.10.5
net.bytebuddy:byte-buddy:1.10.17
net.bytebuddy:byte-buddy-agent:1.10.19
net.bytebuddy:byte-buddy:1.10.19
net.java.dev.jna:jna:5.5.0
org.apache.avro:avro:1.8.2
org.apache.beam:beam-model-fn-execution:2.27.0
@@ -285,11 +285,11 @@ org.junit.platform:junit-platform-runner:1.7.0
org.junit.platform:junit-platform-suite-api:1.7.0
org.junit:junit-bom:5.7.0
org.jvnet.staxex:stax-ex:1.8
org.mockito:mockito-core:3.3.3
org.mockito:mockito-junit-jupiter:3.3.3
org.mockito:mockito-core:3.7.7
org.mockito:mockito-junit-jupiter:3.7.7
org.mortbay.jetty:jetty-util:6.1.26
org.mortbay.jetty:jetty:6.1.26
org.objenesis:objenesis:2.6
org.objenesis:objenesis:3.1
org.opentest4j:opentest4j:1.2.0
org.ow2.asm:asm-analysis:8.0.1
org.ow2.asm:asm-commons:7.1
@@ -104,8 +104,9 @@ public class DeleteExpiredDomainsAction implements Runnable {
runLocked();
response.setStatus(SC_OK);
} catch (Exception e) {
logger.atSevere().withCause(e).log("Errored out during execution.");
response.setStatus(SC_INTERNAL_SERVER_ERROR);
response.setPayload("Encountered error; see GCP logs for full details.");
response.setPayload(String.format("Errored out with cause: %s", e));
}
return null;
};
@@ -280,7 +280,7 @@
</cron>
<cron>
<url><![CDATA[/_dr/task/icannReportingUpload]]></url>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/icannReportingUpload&runInEmpty]]></url>
<description>
Checks if the monthly ICANN reports have been successfully uploaded. If they have not, attempts to upload them again.
Most of the time, this job should not do anything since the uploads are triggered when the reports are staged.
@@ -31,6 +31,7 @@ import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.PremiumList.PremiumListEntry;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.request.Action;
import google.registry.request.Parameter;
@@ -141,7 +142,7 @@ public class ExportPremiumTermsAction implements Runnable {
PremiumListDualDao.exists(premiumListName), "Could not load premium list for " + tld);
SortedSet<String> premiumTerms =
Streams.stream(PremiumListDualDao.loadAllPremiumListEntries(premiumListName))
.map(entry -> Joiner.on(",").join(entry.getLabel(), entry.getValue()))
.map(PremiumListEntry::toString)
.collect(ImmutableSortedSet.toImmutableSortedSet(String::compareTo));
return Joiner.on("\n")
@@ -18,12 +18,11 @@ import static com.google.common.collect.Sets.intersection;
import static google.registry.model.EppResourceUtils.getLinkedDomainKeys;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.googlecode.objectify.Key;
import google.registry.flows.EppException.AuthorizationErrorException;
import google.registry.flows.EppException.InvalidAuthorizationInformationErrorException;
import google.registry.flows.EppException.ObjectDoesNotExistException;
@@ -185,16 +184,15 @@ public final class ResourceFlowUtils {
return;
}
// The roid should match one of the contacts.
Optional<Key<ContactResource>> foundContact =
Optional<VKey<ContactResource>> foundContact =
domain.getReferencedContacts().stream()
.map(VKey::getOfyKey)
.filter(key -> key.getName().equals(authRepoId))
.filter(key -> key.getOfyKey().getName().equals(authRepoId))
.findFirst();
if (!foundContact.isPresent()) {
throw new BadAuthInfoForResourceException();
}
// Check the authInfo against the contact.
verifyAuthInfo(authInfo, ofy().load().key(foundContact.get()).now());
verifyAuthInfo(authInfo, transactIfJpaTm(() -> tm().loadByKey(foundContact.get())));
}
/** Check that the given {@link AuthInfo} is valid for the given contact. */
@@ -25,6 +25,9 @@ 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;
import static google.registry.model.DatabaseMigrationUtils.getPrimaryDatabase;
import static google.registry.model.common.DatabaseTransitionSchedule.PrimaryDatabase.DATASTORE;
import static google.registry.model.common.DatabaseTransitionSchedule.TransitionId.REPLAYED_ENTITIES;
import static google.registry.model.domain.DomainBase.MAX_REGISTRATION_YEARS;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.registry.Registries.findTldForName;
@@ -38,6 +41,7 @@ import static google.registry.model.registry.label.ReservationType.FULLY_BLOCKED
import static google.registry.model.registry.label.ReservationType.NAME_COLLISION;
import static google.registry.model.registry.label.ReservationType.RESERVED_FOR_ANCHOR_TENANT;
import static google.registry.model.registry.label.ReservationType.RESERVED_FOR_SPECIFIC_USE;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.pricing.PricingEngineProxy.isDomainPremium;
import static google.registry.util.CollectionUtils.nullToEmpty;
@@ -88,6 +92,7 @@ import google.registry.model.domain.DomainCommand.Create;
import google.registry.model.domain.DomainCommand.CreateOrUpdate;
import google.registry.model.domain.DomainCommand.InvalidReferencesException;
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.fee.BaseFee;
@@ -355,22 +360,23 @@ public class DomainFlowUtils {
static void validateNoDuplicateContacts(Set<DesignatedContact> contacts)
throws ParameterValuePolicyErrorException {
ImmutableMultimap<Type, Key<ContactResource>> contactsByType =
ImmutableMultimap<Type, VKey<ContactResource>> contactsByType =
contacts.stream()
.collect(
toImmutableSetMultimap(
DesignatedContact::getType, contact -> contact.getContactKey().getOfyKey()));
DesignatedContact::getType, contact -> contact.getContactKey()));
// If any contact type has multiple contacts:
if (contactsByType.asMap().values().stream().anyMatch(v -> v.size() > 1)) {
// Find the duplicates.
Map<Type, Collection<Key<ContactResource>>> dupeKeysMap =
Map<Type, Collection<VKey<ContactResource>>> dupeKeysMap =
Maps.filterEntries(contactsByType.asMap(), e -> e.getValue().size() > 1);
ImmutableList<Key<ContactResource>> dupeKeys =
ImmutableList<VKey<ContactResource>> dupeKeys =
dupeKeysMap.values().stream().flatMap(Collection::stream).collect(toImmutableList());
// Load the duplicates in one batch.
Map<Key<ContactResource>, ContactResource> dupeContacts = ofy().load().keys(dupeKeys);
ImmutableMultimap.Builder<Type, Key<ContactResource>> typesMap =
Map<VKey<? extends ContactResource>, ContactResource> dupeContacts =
tm().loadByKeys(dupeKeys);
ImmutableMultimap.Builder<Type, VKey<ContactResource>> typesMap =
new ImmutableMultimap.Builder<>();
dupeKeysMap.forEach(typesMap::putAll);
// Create an error message showing the type and contact IDs of the duplicates.
@@ -537,13 +543,13 @@ public class DomainFlowUtils {
// If the resultant autorenew poll message would have no poll messages to deliver, then just
// delete it. Otherwise save it with the new end time.
if (isAtOrAfter(updatedAutorenewPollMessage.getEventTime(), newEndTime)) {
autorenewPollMessage.ifPresent(autorenew -> ofy().delete().entity(autorenew));
autorenewPollMessage.ifPresent(autorenew -> tm().delete(autorenew));
} else {
ofy().save().entity(updatedAutorenewPollMessage);
tm().put(updatedAutorenewPollMessage);
}
Recurring recurring = tm().loadByKey(domain.getAutorenewBillingEvent());
ofy().save().entity(recurring.asBuilder().setRecurrenceEndTime(newEndTime).build());
tm().put(recurring.asBuilder().setRecurrenceEndTime(newEndTime).build());
}
/**
@@ -1045,18 +1051,11 @@ public class DomainFlowUtils {
Duration maxSearchPeriod,
final ImmutableSet<TransactionReportField> cancelableFields) {
List<HistoryEntry> recentHistoryEntries =
ofy()
.load()
.type(HistoryEntry.class)
.ancestor(domainBase)
.filter("modificationTime >=", now.minus(maxSearchPeriod))
.order("modificationTime")
.list();
Optional<HistoryEntry> entryToCancel =
List<? extends HistoryEntry> recentHistoryEntries =
findRecentHistoryEntries(domainBase, now, maxSearchPeriod);
Optional<? extends HistoryEntry> entryToCancel =
Streams.findLast(
recentHistoryEntries
.stream()
recentHistoryEntries.stream()
.filter(
historyEntry -> {
// Look for add and renew transaction records that have yet to be reported
@@ -1082,6 +1081,28 @@ public class DomainFlowUtils {
return recordsBuilder.build();
}
private static List<? extends HistoryEntry> findRecentHistoryEntries(
DomainBase domainBase, DateTime now, Duration maxSearchPeriod) {
if (getPrimaryDatabase(REPLAYED_ENTITIES).equals(DATASTORE)) {
return ofy()
.load()
.type(HistoryEntry.class)
.ancestor(domainBase)
.filter("modificationTime >=", now.minus(maxSearchPeriod))
.order("modificationTime")
.list();
} else {
return jpaTm()
.getEntityManager()
.createQuery(
"FROM DomainHistory WHERE modificationTime >= :beginning "
+ "ORDER BY modificationTime ASC",
DomainHistory.class)
.setParameter("beginning", now.minus(maxSearchPeriod))
.getResultList();
}
}
/** Resource linked to this domain does not exist. */
static class LinkedResourcesDoNotExistException extends ObjectDoesNotExistException {
public LinkedResourcesDoNotExistException(Class<?> type, ImmutableSet<String> resourceIds) {
@@ -25,7 +25,6 @@ import static google.registry.flows.domain.DomainFlowUtils.updateAutorenewRecurr
import static google.registry.flows.domain.DomainTransferUtils.createGainingTransferPollMessage;
import static google.registry.flows.domain.DomainTransferUtils.createTransferResponse;
import static google.registry.model.ResourceTransferUtils.denyPendingTransfer;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.reporting.DomainTransactionRecord.TransactionReportField.TRANSFER_NACKED;
import static google.registry.model.reporting.DomainTransactionRecord.TransactionReportField.TRANSFER_SUCCESSFUL;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
@@ -41,7 +40,6 @@ import google.registry.flows.FlowModule.Superuser;
import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.model.ImmutableObject;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppcommon.AuthInfo;
@@ -102,11 +100,11 @@ public final class DomainTransferRejectFlow implements TransactionalFlow {
}
DomainBase newDomain =
denyPendingTransfer(existingDomain, TransferStatus.CLIENT_REJECTED, now, clientId);
ofy().save().<ImmutableObject>entities(
newDomain,
historyEntry,
createGainingTransferPollMessage(
targetId, newDomain.getTransferData(), null, historyEntry));
tm().putAll(
newDomain,
historyEntry,
createGainingTransferPollMessage(
targetId, newDomain.getTransferData(), null, historyEntry));
// Reopen the autorenew event and poll message that we closed for the implicit transfer. This
// may end up recreating the poll message if it was deleted upon the transfer request.
updateAutorenewRecurrenceEndTime(existingDomain, END_OF_TIME);
@@ -52,6 +52,7 @@ import google.registry.model.tmch.ClaimsListShard;
import google.registry.model.tmch.ClaimsListShard.ClaimsListRevision;
import google.registry.model.tmch.ClaimsListShard.ClaimsListSingleton;
import google.registry.model.tmch.TmchCrl;
import google.registry.schema.replay.LastSqlTransaction;
/** Sets of classes of the Objectify-registered entities in use throughout the model. */
public final class EntityClasses {
@@ -90,6 +91,7 @@ public final class EntityClasses {
HostResource.class,
KmsSecret.class,
KmsSecretRevision.class,
LastSqlTransaction.class,
Lock.class,
PollMessage.class,
PollMessage.Autorenew.class,
@@ -118,9 +118,7 @@ public final class ResourceTransferUtils {
if (resource.getStatusValues().contains(StatusValue.PENDING_TRANSFER)) {
TransferData oldTransferData = resource.getTransferData();
tm().delete(oldTransferData.getServerApproveEntities());
ofy()
.save()
.entity(
tm().put(
new PollMessage.OneTime.Builder()
.setClientId(oldTransferData.getGainingClientId())
.setEventTime(now)
@@ -62,6 +62,8 @@ public class DatabaseTransitionSchedule extends ImmutableObject implements Datas
DOMAIN_LABEL_LISTS,
/** The schedule for the migration of the {@link SignedMarkRevocationList} entity. */
SIGNED_MARK_REVOCATION_LIST,
/** The schedule for all asynchronously-replayed entities, ones not dually-written. */
REPLAYED_ENTITIES,
}
/**
@@ -131,6 +131,11 @@ public class DatastoreTransactionManager implements TransactionManager {
saveEntity(entity);
}
@Override
public void putAll(Object... entities) {
syncIfTransactionless(getOfy().save().entities(entities));
}
@Override
public void putAll(ImmutableCollection<?> entities) {
syncIfTransactionless(getOfy().save().entities(entities));
@@ -41,9 +41,9 @@ public class EntityWritePriorities {
*/
static final ImmutableMap<String, Integer> CLASS_PRIORITIES =
ImmutableMap.of(
"ContactResource", -15,
"HistoryEntry", -10,
"AllocationToken", -9,
"ContactResource", 5,
"DomainBase", 10);
// The beginning of the range of priority numbers reserved for delete. This must be greater than
@@ -116,7 +116,7 @@ public class Ofy {
return ofy().getTransaction() != null;
}
void assertInTransaction() {
public void assertInTransaction() {
checkState(inTransaction(), "Must be called in a transaction");
}
@@ -359,8 +359,13 @@ public abstract class PollMessage extends ImmutableObject
}
/** Converts an unspecialized VKey&lt;PollMessage&gt; to a VKey of the derived class. */
public static @Nullable VKey<OneTime> convertVKey(@Nullable VKey<OneTime> key) {
return key == null ? null : VKey.create(OneTime.class, key.getSqlKey(), key.getOfyKey());
public static @Nullable VKey<OneTime> convertVKey(@Nullable VKey<? extends PollMessage> key) {
if (key == null) {
return null;
}
Key<OneTime> ofyKey =
Key.create(key.getOfyKey().getParent(), OneTime.class, key.getOfyKey().getId());
return VKey.create(OneTime.class, key.getSqlKey(), ofyKey);
}
@Override
@@ -383,6 +388,7 @@ public abstract class PollMessage extends ImmutableObject
@OnLoad
void onLoad() {
super.onLoad();
// Take the Objectify-specific fields and map them to the SQL-specific fields, if applicable
if (!isNullOrEmpty(contactPendingActionNotificationResponses)) {
pendingActionNotificationResponse = contactPendingActionNotificationResponses.get(0);
}
@@ -390,36 +396,70 @@ public abstract class PollMessage extends ImmutableObject
contactId = contactTransferResponses.get(0).getContactId();
transferResponse = contactTransferResponses.get(0);
}
if (!isNullOrEmpty(domainPendingActionNotificationResponses)) {
pendingActionNotificationResponse = domainPendingActionNotificationResponses.get(0);
}
if (!isNullOrEmpty(domainTransferResponses)) {
fullyQualifiedDomainName = domainTransferResponses.get(0).getFullyQualifiedDomainName();
transferResponse = domainTransferResponses.get(0);
extendedRegistrationExpirationTime =
domainTransferResponses.get(0).getExtendedRegistrationExpirationTime();
}
}
@Override
@PostLoad
void postLoad() {
super.postLoad();
// Take the SQL-specific fields and map them to the Objectify-specific fields, if applicable
if (pendingActionNotificationResponse != null) {
contactPendingActionNotificationResponses =
ImmutableList.of(
ContactPendingActionNotificationResponse.create(
pendingActionNotificationResponse.nameOrId.value,
pendingActionNotificationResponse.getActionResult(),
pendingActionNotificationResponse.getTrid(),
pendingActionNotificationResponse.processedDate));
if (contactId != null) {
contactPendingActionNotificationResponses =
ImmutableList.of(
ContactPendingActionNotificationResponse.create(
pendingActionNotificationResponse.nameOrId.value,
pendingActionNotificationResponse.getActionResult(),
pendingActionNotificationResponse.getTrid(),
pendingActionNotificationResponse.processedDate));
} else if (fullyQualifiedDomainName != null) {
domainPendingActionNotificationResponses =
ImmutableList.of(
DomainPendingActionNotificationResponse.create(
pendingActionNotificationResponse.nameOrId.value,
pendingActionNotificationResponse.getActionResult(),
pendingActionNotificationResponse.getTrid(),
pendingActionNotificationResponse.processedDate));
}
}
if (contactId != null && transferResponse != null) {
// The transferResponse is currently an unspecialized TransferResponse instance, create a
// ContactTransferResponse so that the value is consistently specialized and store it in the
// list representation for datastore.
transferResponse =
new ContactTransferResponse.Builder()
.setContactId(contactId)
.setGainingClientId(transferResponse.getGainingClientId())
.setLosingClientId(transferResponse.getLosingClientId())
.setTransferStatus(transferResponse.getTransferStatus())
.setTransferRequestTime(transferResponse.getTransferRequestTime())
.setPendingTransferExpirationTime(
transferResponse.getPendingTransferExpirationTime())
.build();
contactTransferResponses = ImmutableList.of((ContactTransferResponse) transferResponse);
if (transferResponse != null) {
// The transferResponse is currently an unspecialized TransferResponse instance, create the
// appropriate subclass so that the value is consistently specialized
if (contactId != null) {
transferResponse =
new ContactTransferResponse.Builder()
.setContactId(contactId)
.setGainingClientId(transferResponse.getGainingClientId())
.setLosingClientId(transferResponse.getLosingClientId())
.setTransferStatus(transferResponse.getTransferStatus())
.setTransferRequestTime(transferResponse.getTransferRequestTime())
.setPendingTransferExpirationTime(
transferResponse.getPendingTransferExpirationTime())
.build();
contactTransferResponses = ImmutableList.of((ContactTransferResponse) transferResponse);
} else if (fullyQualifiedDomainName != null) {
transferResponse =
new DomainTransferResponse.Builder()
.setFullyQualifiedDomainName(fullyQualifiedDomainName)
.setGainingClientId(transferResponse.getGainingClientId())
.setLosingClientId(transferResponse.getLosingClientId())
.setTransferStatus(transferResponse.getTransferStatus())
.setTransferRequestTime(transferResponse.getTransferRequestTime())
.setPendingTransferExpirationTime(
transferResponse.getPendingTransferExpirationTime())
.setExtendedRegistrationExpirationTime(extendedRegistrationExpirationTime)
.build();
domainTransferResponses = ImmutableList.of((DomainTransferResponse) transferResponse);
}
}
}
@@ -441,10 +481,7 @@ public abstract class PollMessage extends ImmutableObject
.filter(ContactPendingActionNotificationResponse.class::isInstance)
.map(ContactPendingActionNotificationResponse.class::cast)
.collect(toImmutableList()));
if (getInstance().contactPendingActionNotificationResponses != null) {
getInstance().pendingActionNotificationResponse =
getInstance().contactPendingActionNotificationResponses.get(0);
}
getInstance().contactTransferResponses =
forceEmptyToNull(
responseData
@@ -452,10 +489,6 @@ public abstract class PollMessage extends ImmutableObject
.filter(ContactTransferResponse.class::isInstance)
.map(ContactTransferResponse.class::cast)
.collect(toImmutableList()));
if (getInstance().contactTransferResponses != null) {
getInstance().contactId = getInstance().contactTransferResponses.get(0).getContactId();
getInstance().transferResponse = getInstance().contactTransferResponses.get(0);
}
getInstance().domainPendingActionNotificationResponses =
forceEmptyToNull(
@@ -471,13 +504,36 @@ public abstract class PollMessage extends ImmutableObject
.filter(DomainTransferResponse.class::isInstance)
.map(DomainTransferResponse.class::cast)
.collect(toImmutableList()));
getInstance().hostPendingActionNotificationResponses =
forceEmptyToNull(
responseData
.stream()
responseData.stream()
.filter(HostPendingActionNotificationResponse.class::isInstance)
.map(HostPendingActionNotificationResponse.class::cast)
.collect(toImmutableList()));
// Set the generic pending-action field as appropriate
if (getInstance().contactPendingActionNotificationResponses != null) {
getInstance().pendingActionNotificationResponse =
getInstance().contactPendingActionNotificationResponses.get(0);
} else if (getInstance().domainPendingActionNotificationResponses != null) {
getInstance().pendingActionNotificationResponse =
getInstance().domainPendingActionNotificationResponses.get(0);
} else if (getInstance().hostPendingActionNotificationResponses != null) {
getInstance().pendingActionNotificationResponse =
getInstance().hostPendingActionNotificationResponses.get(0);
}
// Set the generic transfer response field as appropriate
if (getInstance().contactTransferResponses != null) {
getInstance().contactId = getInstance().contactTransferResponses.get(0).getContactId();
getInstance().transferResponse = getInstance().contactTransferResponses.get(0);
} else if (getInstance().domainTransferResponses != null) {
getInstance().fullyQualifiedDomainName =
getInstance().domainTransferResponses.get(0).getFullyQualifiedDomainName();
getInstance().transferResponse = getInstance().domainTransferResponses.get(0);
getInstance().extendedRegistrationExpirationTime =
getInstance().domainTransferResponses.get(0).getExtendedRegistrationExpirationTime();
}
return this;
}
}
@@ -518,8 +574,13 @@ public abstract class PollMessage extends ImmutableObject
}
/** Converts an unspecialized VKey&lt;PollMessage&gt; to a VKey of the derived class. */
public static @Nullable VKey<Autorenew> convertVKey(VKey<Autorenew> key) {
return key == null ? null : VKey.create(Autorenew.class, key.getSqlKey(), key.getOfyKey());
public static @Nullable VKey<Autorenew> convertVKey(VKey<? extends PollMessage> key) {
if (key == null) {
return null;
}
Key<Autorenew> ofyKey =
Key.create(key.getOfyKey().getParent(), Autorenew.class, key.getOfyKey().getId());
return VKey.create(Autorenew.class, key.getSqlKey(), ofyKey);
}
@Override
@@ -208,6 +208,12 @@ public final class PremiumList extends BaseDomainLabelList<Money, PremiumList.Pr
return price;
}
@Override
public String toString() {
// Don't include the comment so that we can use this when exporting the premium list
return String.format("%s,%s", label, price);
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
@@ -14,7 +14,10 @@
package google.registry.model.transfer;
import static google.registry.util.CollectionUtils.forceEmptyToNull;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.AlsoLoad;
import com.googlecode.objectify.annotation.Embed;
@@ -34,6 +37,7 @@ import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.PostLoad;
import org.joda.time.DateTime;
/** Transfer data for domain. */
@@ -214,6 +218,28 @@ public class DomainTransferData extends TransferData<DomainTransferData.Builder>
return serverApproveAutorenewPollMessageHistoryId;
}
@PostLoad
@Override
void postLoad() {
// The superclass's serverApproveEntities should include the billing events if present
super.postLoad();
ImmutableSet.Builder<VKey<? extends TransferServerApproveEntity>> serverApproveEntitiesBuilder =
new ImmutableSet.Builder<>();
if (serverApproveEntities != null) {
serverApproveEntitiesBuilder.addAll(serverApproveEntities);
}
if (serverApproveBillingEvent != null) {
serverApproveEntitiesBuilder.add(serverApproveBillingEvent);
}
if (serverApproveAutorenewEvent != null) {
serverApproveEntitiesBuilder.add(serverApproveAutorenewEvent);
}
if (serverApproveAutorenewPollMessage != null) {
serverApproveEntitiesBuilder.add(serverApproveAutorenewPollMessage);
}
serverApproveEntities = forceEmptyToNull(serverApproveEntitiesBuilder.build());
}
@Override
public boolean isEmpty() {
return EMPTY.equals(this);
@@ -167,7 +167,7 @@ public abstract class TransferData<
return;
}
Key<? extends EppResource> eppKey;
if (getClass().equals(DomainBase.class)) {
if (getClass().equals(DomainTransferData.class)) {
eppKey = Key.create(DomainBase.class, repoId);
} else {
eppKey = Key.create(ContactResource.class, repoId);
@@ -0,0 +1,97 @@
// Copyright 2021 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 google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
/**
* An extension of {@link CriteriaQuery} that uses a Builder-style pattern when adding "WHERE"
* and/or "ORDER BY" clauses.
*
* <p>{@link CriteriaQuery}, as is, requires that all clauses must be passed in at once -- if one
* calls "WHERE" multiple times, the later call overwrites the earlier call.
*/
public class CriteriaQueryBuilder<T> {
/** Functional interface that defines the 'where' operator, e.g. {@link CriteriaBuilder#equal}. */
public interface WhereClause<U> {
Predicate predicate(Expression<U> expression, U object);
}
/** Functional interface that defines the order-by operator, e.g. {@link CriteriaBuilder#asc}. */
public interface OrderByClause<U> {
Order order(Expression<U> expression);
}
private final CriteriaQuery<T> query;
private final Root<T> root;
private final ImmutableList.Builder<Predicate> predicates = new ImmutableList.Builder<>();
private final ImmutableList.Builder<Order> orders = new ImmutableList.Builder<>();
private CriteriaQueryBuilder(CriteriaQuery<T> query, Root<T> root) {
this.query = query;
this.root = root;
}
/** Adds a WHERE clause to the query, given the specified operation, field, and value. */
public <V> CriteriaQueryBuilder<T> where(WhereClause<V> whereClause, String fieldName, V value) {
Expression<V> expression = root.get(fieldName);
return where(whereClause.predicate(expression, value));
}
/** Adds a WHERE clause to the query specifying that a value must be in the given collection. */
public CriteriaQueryBuilder<T> whereFieldIsIn(String fieldName, Collection<?> values) {
return where(root.get(fieldName).in(values));
}
/** Orders the result by the given operation applied to the given field. */
public <U> CriteriaQueryBuilder<T> orderBy(OrderByClause<U> orderByClause, String fieldName) {
Expression<U> expression = root.get(fieldName);
return orderBy(orderByClause.order(expression));
}
/** Builds and returns the query, applying all WHERE and ORDER BY clauses at once. */
public CriteriaQuery<T> build() {
Predicate[] predicateArray = predicates.build().toArray(new Predicate[0]);
return query.where(predicateArray).orderBy(orders.build());
}
private CriteriaQueryBuilder<T> where(Predicate predicate) {
predicates.add(predicate);
return this;
}
private CriteriaQueryBuilder<T> orderBy(Order order) {
orders.add(order);
return this;
}
/** Creates a query builder that will SELECT from the given class. */
public static <T> CriteriaQueryBuilder<T> create(Class<T> clazz) {
CriteriaQuery<T> query = jpaTm().getEntityManager().getCriteriaBuilder().createQuery(clazz);
Root<T> root = query.from(clazz);
query = query.select(root);
return new CriteriaQueryBuilder<>(query, root);
}
}
@@ -288,6 +288,15 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
transactionInfo.get().addUpdate(toPersist);
}
@Override
public void putAll(Object... entities) {
checkArgumentNotNull(entities, "entities must be specified");
assertInTransaction();
for (Object entity : entities) {
put(entity);
}
}
@Override
public void putAll(ImmutableCollection<?> entities) {
checkArgumentNotNull(entities, "entities must be specified");
@@ -86,7 +86,7 @@ public class Transaction extends ImmutableObject implements Buildable {
}
}
static Transaction deserialize(byte[] serializedTransaction) throws IOException {
public static Transaction deserialize(byte[] serializedTransaction) throws IOException {
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(serializedTransaction));
// Verify that the data is what we expect.
@@ -34,9 +34,9 @@ public class TransactionEntity implements SqlEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
long id;
private long id;
byte[] contents;
private byte[] contents;
TransactionEntity() {}
@@ -48,4 +48,12 @@ public class TransactionEntity implements SqlEntity {
public Optional<DatastoreEntity> toDatastoreEntity() {
return Optional.empty(); // Not persisted in Datastore per se
}
public long getId() {
return id;
}
public byte[] getContents() {
return contents;
}
}
@@ -123,7 +123,10 @@ public interface TransactionManager {
/** Persists a new entity or update the existing entity in the database. */
void put(Object entity);
/** Persists all new entities or update the existing entities in the database. */
/** Persists all new entities or updates the existing entities in the database. */
void putAll(Object... entities);
/** Persists all new entities or updates the existing entities in the database. */
void putAll(ImmutableCollection<?> entities);
/**
@@ -17,6 +17,7 @@ package google.registry.rdap;
import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Actions.getPathForAction;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
@@ -28,7 +29,10 @@ import com.google.common.net.MediaType;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.DatabaseMigrationUtils;
import google.registry.model.EppResource;
import google.registry.model.common.DatabaseTransitionSchedule.PrimaryDatabase;
import google.registry.model.common.DatabaseTransitionSchedule.TransitionId;
import google.registry.model.registrar.Registrar;
import google.registry.rdap.RdapMetrics.EndpointType;
import google.registry.rdap.RdapObjectClasses.ErrorResponse;
@@ -256,4 +260,11 @@ public abstract class RdapActionBase implements Runnable {
DateTime getRequestTime() {
return rdapJsonFormatter.getRequestTime();
}
static boolean isDatastore() {
return tm().transact(
() ->
DatabaseMigrationUtils.getPrimaryDatabase(TransitionId.REPLAYED_ENTITIES)
.equals(PrimaryDatabase.DATASTORE));
}
}
@@ -15,7 +15,9 @@
package google.registry.rdap;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.rdap.RdapUtils.getRegistrarByIanaIdentifier;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD;
@@ -29,6 +31,9 @@ import com.google.common.primitives.Longs;
import com.googlecode.objectify.cmd.Query;
import google.registry.model.contact.ContactResource;
import google.registry.model.registrar.Registrar;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.CriteriaQueryBuilder;
import google.registry.rdap.RdapAuthorization.Role;
import google.registry.rdap.RdapJsonFormatter.OutputDataType;
import google.registry.rdap.RdapMetrics.EndpointType;
import google.registry.rdap.RdapMetrics.SearchType;
@@ -112,7 +117,6 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
/** Parses the parameters and calls the appropriate search function. */
@Override
public EntitySearchResponse getSearchResponse(boolean isHeadRequest) {
// RDAP syntax example: /rdap/entities?fn=Bobby%20Joe*.
if (Booleans.countTrue(fnParam.isPresent(), handleParam.isPresent()) != 1) {
throw new BadRequestException("You must specify either fn=XXXX or handle=YYYY");
@@ -214,9 +218,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
// Don't allow wildcard suffixes when searching for entities.
if (partialStringQuery.getHasWildcard() && (partialStringQuery.getSuffix() != null)) {
throw new UnprocessableEntityException(
partialStringQuery.getHasWildcard()
? "Suffixes not allowed in wildcard entity name searches"
: "Suffixes not allowed when searching for deleted entities");
"Suffixes not allowed in wildcard entity name searches");
}
// For wildcards, make sure the initial string is long enough, except in the special case of
// searching for all registrars, where we aren't worried about inefficient searches.
@@ -225,9 +227,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
&& (partialStringQuery.getInitialString().length()
< RdapSearchPattern.MIN_INITIAL_STRING_LENGTH)) {
throw new UnprocessableEntityException(
partialStringQuery.getHasWildcard()
? "Initial search string required in wildcard entity name searches"
: "Initial search string required when searching for deleted entities");
"Initial search string required in wildcard entity name searches");
}
// Get the registrar matches. If we have a registrar cursor, weed out registrars up to and
// including the one we ended with last time. We can skip registrars if subtype is CONTACTS.
@@ -262,18 +262,39 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|| (cursorType == CursorType.REGISTRAR)) {
resultSet = RdapResultSet.create(ImmutableList.of());
} else {
Query<ContactResource> query =
queryItems(
ContactResource.class,
"searchName",
partialStringQuery,
cursorQueryString, // if we get this far, and there's a cursor, it must be a contact
DeletedItemHandling.EXCLUDE,
rdapResultSetMaxSize + 1);
if (rdapAuthorization.role() != RdapAuthorization.Role.ADMINISTRATOR) {
query = query.filter("currentSponsorClientId in", rdapAuthorization.clientIds());
if (isDatastore()) {
Query<ContactResource> query =
queryItems(
ContactResource.class,
"searchName",
partialStringQuery,
cursorQueryString, // if we get here and there's a cursor, it must be a contact
DeletedItemHandling.EXCLUDE,
rdapResultSetMaxSize + 1);
if (!rdapAuthorization.role().equals(Role.ADMINISTRATOR)) {
query = query.filter("currentSponsorClientId in", rdapAuthorization.clientIds());
}
resultSet = getMatchingResources(query, false, rdapResultSetMaxSize + 1);
} else {
resultSet =
jpaTm()
.transact(
() -> {
CriteriaQueryBuilder<ContactResource> builder =
queryItemsSql(
ContactResource.class,
"searchName",
partialStringQuery,
cursorQueryString,
DeletedItemHandling.EXCLUDE);
if (!rdapAuthorization.role().equals(Role.ADMINISTRATOR)) {
builder =
builder.whereFieldIsIn(
"currentSponsorClientId", rdapAuthorization.clientIds());
}
return getMatchingResourcesSql(builder, false, rdapResultSetMaxSize + 1);
});
}
resultSet = getMatchingResources(query, false, rdapResultSetMaxSize + 1);
}
}
return makeSearchResults(resultSet, registrars, QueryType.FULL_NAME);
@@ -303,15 +324,15 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
if (subtype == Subtype.REGISTRARS) {
contactResourceList = ImmutableList.of();
} else {
ContactResource contactResource =
ofy()
.load()
.type(ContactResource.class)
.id(partialStringQuery.getInitialString())
.now();
Optional<ContactResource> contactResource =
transactIfJpaTm(
() ->
tm().loadByKeyIfPresent(
VKey.create(
ContactResource.class, partialStringQuery.getInitialString())));
contactResourceList =
((contactResource != null) && shouldBeVisible(contactResource))
? ImmutableList.of(contactResource)
(contactResource.isPresent() && shouldBeVisible(contactResource.get()))
? ImmutableList.of(contactResource.get())
: ImmutableList.of();
}
ImmutableList<Registrar> registrarList;
@@ -365,16 +386,31 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
if (subtype == Subtype.REGISTRARS) {
contactResultSet = RdapResultSet.create(ImmutableList.of());
} else {
contactResultSet =
getMatchingResources(
queryItemsByKey(
ContactResource.class,
partialStringQuery,
cursorQueryString,
getDeletedItemHandling(),
querySizeLimit),
shouldIncludeDeleted(),
querySizeLimit);
if (isDatastore()) {
contactResultSet =
getMatchingResources(
queryItemsByKey(
ContactResource.class,
partialStringQuery,
cursorQueryString,
getDeletedItemHandling(),
querySizeLimit),
shouldIncludeDeleted(),
querySizeLimit);
} else {
contactResultSet =
jpaTm()
.transact(
() ->
getMatchingResourcesSql(
queryItemsByKeySql(
ContactResource.class,
partialStringQuery,
cursorQueryString,
getDeletedItemHandling()),
shouldIncludeDeleted(),
querySizeLimit));
}
}
return makeSearchResults(contactResultSet, registrars, QueryType.HANDLE);
}
@@ -16,6 +16,7 @@ package google.registry.rdap;
import static com.google.common.base.Charsets.UTF_8;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import com.google.common.collect.ImmutableList;
@@ -24,6 +25,7 @@ import com.googlecode.objectify.Key;
import com.googlecode.objectify.cmd.Query;
import google.registry.model.EppResource;
import google.registry.model.registrar.Registrar;
import google.registry.persistence.transaction.CriteriaQueryBuilder;
import google.registry.rdap.RdapMetrics.EndpointType;
import google.registry.rdap.RdapMetrics.WildcardType;
import google.registry.rdap.RdapSearchResults.BaseSearchResponse;
@@ -40,8 +42,10 @@ import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import javax.inject.Inject;
import javax.persistence.criteria.CriteriaBuilder;
/**
* Base RDAP (new WHOIS) action for domain, nameserver and entity search requests.
@@ -161,14 +165,63 @@ public abstract class RdapSearchActionBase extends RdapActionBase {
if (desiredRegistrar.isPresent()) {
query = query.filter("currentSponsorClientId", desiredRegistrar.get());
}
if (!checkForVisibility) {
return RdapResultSet.create(query.list());
List<T> queryResult = query.list();
if (checkForVisibility) {
return filterResourcesByVisibility(queryResult, querySizeLimit);
} else {
return RdapResultSet.create(queryResult);
}
}
/**
* In Cloud SQL, builds and runs the given query, and checks for permissioning if necessary.
*
* @param builder a query builder that represents the various SELECT FROM, WHERE, ORDER BY and
* (etc) clauses that make up this SQL query
* @param checkForVisibility true if the results should be checked to make sure they are visible;
* normally this should be equal to the shouldIncludeDeleted setting, but in cases where the
* query could not check deletion status (due to Datastore limitations such as the limit of
* one field queried for inequality, for instance), it may need to be set to true even when
* not including deleted records
* @param querySizeLimit the maximum number of items the query is expected to return, usually
* because the limit has been set
* @return an {@link RdapResultSet} object containing the list of resources and an incompleteness
* warning flag, which is set to MIGHT_BE_INCOMPLETE iff any resources were excluded due to
* lack of visibility, and the resulting list of resources is less than the maximum allowable,
* and the number of items returned by the query is greater than or equal to the maximum
* number we might have expected
*/
<T extends EppResource> RdapResultSet<T> getMatchingResourcesSql(
CriteriaQueryBuilder<T> builder, boolean checkForVisibility, int querySizeLimit) {
jpaTm().assertInTransaction();
Optional<String> desiredRegistrar = getDesiredRegistrar();
if (desiredRegistrar.isPresent()) {
builder =
builder.where(
jpaTm().getEntityManager().getCriteriaBuilder()::equal,
"currentSponsorClientId",
desiredRegistrar.get());
}
List<T> queryResult =
jpaTm()
.getEntityManager()
.createQuery(builder.build())
.setMaxResults(querySizeLimit)
.getResultList();
if (checkForVisibility) {
return filterResourcesByVisibility(queryResult, querySizeLimit);
} else {
return RdapResultSet.create(queryResult);
}
}
private <T extends EppResource> RdapResultSet<T> filterResourcesByVisibility(
List<T> queryResult, int querySizeLimit) {
// If we are including deleted resources, we need to check that we're authorized for each one.
List<T> resources = new ArrayList<>();
int numResourcesQueried = 0;
boolean someExcluded = false;
for (T resource : query) {
for (T resource : queryResult) {
if (shouldBeVisible(resource)) {
resources.add(resource);
} else {
@@ -268,8 +321,8 @@ public abstract class RdapSearchActionBase extends RdapActionBase {
// to be (more) sure we got everything.
int getStandardQuerySizeLimit() {
return shouldIncludeDeleted()
? (RESULT_SET_SIZE_SCALING_FACTOR * (rdapResultSetMaxSize + 1))
: (rdapResultSetMaxSize + 1);
? (RESULT_SET_SIZE_SCALING_FACTOR * (rdapResultSetMaxSize + 1))
: (rdapResultSetMaxSize + 1);
}
/**
@@ -311,9 +364,10 @@ public abstract class RdapSearchActionBase extends RdapActionBase {
query = query.filter(filterField, partialStringQuery.getInitialString());
} else {
// Ignore the suffix; the caller will need to filter on the suffix, if any.
query = query
.filter(filterField + " >=", partialStringQuery.getInitialString())
.filter(filterField + " <", partialStringQuery.getNextInitialString());
query =
query
.filter(filterField + " >=", partialStringQuery.getInitialString())
.filter(filterField + " <", partialStringQuery.getNextInitialString());
}
if (cursorString.isPresent()) {
query = query.filter(filterField + " >", cursorString.get());
@@ -321,6 +375,59 @@ public abstract class RdapSearchActionBase extends RdapActionBase {
return setOtherQueryAttributes(query, deletedItemHandling, resultSetMaxSize);
}
/**
* In Cloud SQL, handles prefix searches in cases where, if we need to filter out deleted items,
* there are no pending deletes.
*
* <p>In such cases, it is sufficient to check whether {@code deletionTime} is equal to {@code
* END_OF_TIME}, because any other value means it has already been deleted. This allows us to use
* an equality query for the deletion time.
*
* @param clazz the type of resource to be queried
* @param filterField the database field of interest
* @param partialStringQuery the details of the search string; if there is no wildcard, an
* equality query is used; if there is a wildcard, a range query is used instead; the initial
* string should not be empty, and any search suffix will be ignored, so the caller must
* filter the results if a suffix is specified
* @param cursorString if a cursor is present, this parameter should specify the cursor string, to
* skip any results up to and including the string; empty() if there is no cursor
* @param deletedItemHandling whether to include or exclude deleted items
* @return a {@link CriteriaQueryBuilder} object representing the query so far
*/
static <T extends EppResource> CriteriaQueryBuilder<T> queryItemsSql(
Class<T> clazz,
String filterField,
RdapSearchPattern partialStringQuery,
Optional<String> cursorString,
DeletedItemHandling deletedItemHandling) {
jpaTm().assertInTransaction();
if (partialStringQuery.getInitialString().length()
< RdapSearchPattern.MIN_INITIAL_STRING_LENGTH) {
throw new UnprocessableEntityException(
String.format(
"Initial search string must be at least %d characters",
RdapSearchPattern.MIN_INITIAL_STRING_LENGTH));
}
CriteriaBuilder criteriaBuilder = jpaTm().getEntityManager().getCriteriaBuilder();
CriteriaQueryBuilder<T> builder = CriteriaQueryBuilder.create(clazz);
if (partialStringQuery.getHasWildcard()) {
builder =
builder.where(
criteriaBuilder::like,
filterField,
String.format("%s%%", partialStringQuery.getInitialString()));
} else {
// no wildcard means we use a standard equals query
builder =
builder.where(criteriaBuilder::equal, filterField, partialStringQuery.getInitialString());
}
if (cursorString.isPresent()) {
builder = builder.where(criteriaBuilder::greaterThan, filterField, cursorString.get());
}
builder = builder.orderBy(criteriaBuilder::asc, filterField);
return setDeletedItemHandlingSql(builder, deletedItemHandling);
}
/**
* Handles searches using a simple string rather than an {@link RdapSearchPattern}.
*
@@ -331,9 +438,9 @@ public abstract class RdapSearchActionBase extends RdapActionBase {
* @param filterField the database field of interest
* @param queryString the search string
* @param cursorField the field which should be compared to the cursor string, or empty() if the
* key should be compared to a key created from the cursor string
* key should be compared to a key created from the cursor string
* @param cursorString if a cursor is present, this parameter should specify the cursor string, to
* skip any results up to and including the string; empty() if there is no cursor
* skip any results up to and including the string; empty() if there is no cursor
* @param deletedItemHandling whether to include or exclude deleted items
* @param resultSetMaxSize the maximum number of results to return
* @return the query object
@@ -363,6 +470,50 @@ public abstract class RdapSearchActionBase extends RdapActionBase {
return setOtherQueryAttributes(query, deletedItemHandling, resultSetMaxSize);
}
/**
* In Cloud SQL, handles searches using a simple string rather than an {@link RdapSearchPattern}.
*
* <p>Since the filter is not an inequality, we can support also checking a cursor string against
* a different field (which involves an inequality on that field).
*
* @param clazz the type of resource to be queried
* @param filterField the database field of interest
* @param queryString the search string
* @param cursorField the field which should be compared to the cursor string, or empty() if the
* key should be compared to a key created from the cursor string
* @param cursorString if a cursor is present, this parameter should specify the cursor string, to
* skip any results up to and including the string; empty() if there is no cursor
* @param deletedItemHandling whether to include or exclude deleted items
* @return a {@link CriteriaQueryBuilder} object representing the query so far
*/
static <T extends EppResource> CriteriaQueryBuilder<T> queryItemsSql(
Class<T> clazz,
String filterField,
String queryString,
Optional<String> cursorField,
Optional<String> cursorString,
DeletedItemHandling deletedItemHandling) {
if (queryString.length() < RdapSearchPattern.MIN_INITIAL_STRING_LENGTH) {
throw new UnprocessableEntityException(
String.format(
"Initial search string must be at least %d characters",
RdapSearchPattern.MIN_INITIAL_STRING_LENGTH));
}
jpaTm().assertInTransaction();
CriteriaQueryBuilder<T> builder = CriteriaQueryBuilder.create(clazz);
CriteriaBuilder criteriaBuilder = jpaTm().getEntityManager().getCriteriaBuilder();
builder = builder.where(criteriaBuilder::equal, filterField, queryString);
if (cursorString.isPresent()) {
if (cursorField.isPresent()) {
builder =
builder.where(criteriaBuilder::greaterThan, cursorField.get(), cursorString.get());
} else {
builder = builder.where(criteriaBuilder::greaterThan, "repoId", cursorString.get());
}
}
return setDeletedItemHandlingSql(builder, deletedItemHandling);
}
/** Handles searches where the field to be searched is the key. */
static <T extends EppResource> Query<T> queryItemsByKey(
Class<T> clazz,
@@ -382,9 +533,10 @@ public abstract class RdapSearchActionBase extends RdapActionBase {
query = query.filterKey("=", Key.create(clazz, partialStringQuery.getInitialString()));
} else {
// Ignore the suffix; the caller will need to filter on the suffix, if any.
query = query
.filterKey(">=", Key.create(clazz, partialStringQuery.getInitialString()))
.filterKey("<", Key.create(clazz, partialStringQuery.getNextInitialString()));
query =
query
.filterKey(">=", Key.create(clazz, partialStringQuery.getInitialString()))
.filterKey("<", Key.create(clazz, partialStringQuery.getNextInitialString()));
}
if (cursorString.isPresent()) {
query = query.filterKey(">", Key.create(clazz, cursorString.get()));
@@ -392,6 +544,26 @@ public abstract class RdapSearchActionBase extends RdapActionBase {
return setOtherQueryAttributes(query, deletedItemHandling, resultSetMaxSize);
}
/** In Cloud SQL, handles searches where the field to be searched is the key. */
static <T extends EppResource> CriteriaQueryBuilder<T> queryItemsByKeySql(
Class<T> clazz,
RdapSearchPattern partialStringQuery,
Optional<String> cursorString,
DeletedItemHandling deletedItemHandling) {
jpaTm().assertInTransaction();
return queryItemsSql(clazz, "repoId", partialStringQuery, cursorString, deletedItemHandling);
}
static <T extends EppResource> CriteriaQueryBuilder<T> setDeletedItemHandlingSql(
CriteriaQueryBuilder<T> builder, DeletedItemHandling deletedItemHandling) {
if (!Objects.equals(deletedItemHandling, DeletedItemHandling.INCLUDE)) {
builder =
builder.where(
jpaTm().getEntityManager().getCriteriaBuilder()::equal, "deletionTime", END_OF_TIME);
}
return builder;
}
static <T extends EppResource> Query<T> setOtherQueryAttributes(
Query<T> query, DeletedItemHandling deletedItemHandling, int resultSetMaxSize) {
if (deletedItemHandling != DeletedItemHandling.INCLUDE) {
@@ -135,7 +135,7 @@ public class RequestHandler<C> {
return;
}
if (!route.get().isMethodAllowed(method)) {
logger.atInfo().log("Method %s not allowed for: %s", method, path);
logger.atWarning().log("Method %s not allowed for: %s", method, path);
rsp.sendError(SC_METHOD_NOT_ALLOWED);
return;
}
@@ -0,0 +1,71 @@
// Copyright 2021 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.schema.replay;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.ofy.ObjectifyService.ofy;
import com.google.common.annotations.VisibleForTesting;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import google.registry.model.ImmutableObject;
/** Datastore entity to keep track of the last SQL transaction imported into the datastore. */
@Entity
public class LastSqlTransaction extends ImmutableObject implements DatastoreOnlyEntity {
/** The key for this singleton. */
public static final Key<LastSqlTransaction> KEY = Key.create(LastSqlTransaction.class, 1);
@SuppressWarnings("unused")
@Id
private long id = 1;
private long transactionId;
LastSqlTransaction() {}
@VisibleForTesting
LastSqlTransaction(long newTransactionId) {
transactionId = newTransactionId;
}
LastSqlTransaction cloneWithNewTransactionId(long transactionId) {
checkArgument(
transactionId > this.transactionId,
"New transaction id (%s) must be greater than the current transaction id (%s)",
transactionId,
this.transactionId);
return new LastSqlTransaction(transactionId);
}
long getTransactionId() {
return transactionId;
}
/**
* Loads the instance.
*
* <p>Must be called within an Ofy transaction.
*
* <p>Creates a new instance of the singleton if it is not already present in Cloud Datastore,
*/
static LastSqlTransaction load() {
ofy().assertInTransaction();
LastSqlTransaction result = ofy().load().key(KEY).now();
return result == null ? new LastSqlTransaction() : result;
}
}
@@ -0,0 +1,136 @@
// Copyright 2021 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.schema.replay;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import static google.registry.request.Action.Method.GET;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import google.registry.persistence.transaction.Transaction;
import google.registry.persistence.transaction.TransactionEntity;
import google.registry.request.Action;
import google.registry.request.auth.Auth;
import java.io.IOException;
import java.util.List;
import javax.persistence.NoResultException;
/** Cron task to replicate from Cloud SQL to datastore. */
@Action(
service = Action.Service.BACKEND,
path = ReplicateToDatastoreAction.PATH,
method = GET,
automaticallyPrintOk = true,
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
class ReplicateToDatastoreAction implements Runnable {
public static final String PATH = "/_dr/cron/replicateToDatastore";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/**
* Number of transactions to fetch from SQL. The rationale for 200 is that we're processing these
* every minute and our production instance currently does about 2 mutations per second, so this
* should generally be enough to scoop up all of the transactions for the past minute.
*/
public static final int BATCH_SIZE = 200;
@VisibleForTesting
List<TransactionEntity> getTransactionBatch() {
// Get the next batch of transactions that we haven't replicated.
LastSqlTransaction lastSqlTxnBeforeBatch = ofyTm().transact(() -> LastSqlTransaction.load());
try {
return jpaTm()
.transact(
() ->
jpaTm()
.getEntityManager()
.createQuery(
"SELECT txn FROM TransactionEntity txn WHERE id >"
+ " :lastId ORDER BY id")
.setParameter("lastId", lastSqlTxnBeforeBatch.getTransactionId())
.setMaxResults(BATCH_SIZE)
.getResultList());
} catch (NoResultException e) {
return ImmutableList.of();
}
}
/**
* Apply a transaction to datastore, returns true if there was a fatal error and the batch should
* be aborted.
*/
@VisibleForTesting
boolean applyTransaction(TransactionEntity txnEntity) {
logger.atInfo().log("Applying a single transaction Cloud SQL -> Cloud Datastore");
return ofyTm()
.transact(
() -> {
// Reload the last transaction id, which could possibly have changed.
LastSqlTransaction lastSqlTxn = LastSqlTransaction.load();
long nextTxnId = lastSqlTxn.getTransactionId() + 1;
if (nextTxnId < txnEntity.getId()) {
// We're missing a transaction. This is bad. Transaction ids are supposed to
// increase monotonically, so we abort rather than applying anything out of
// order.
logger.atSevere().log(
"Missing transaction: last transaction id = %s, next available transaction "
+ "= %s",
nextTxnId - 1, txnEntity.getId());
return true;
} else if (nextTxnId > txnEntity.getId()) {
// We've already replayed this transaction. This shouldn't happen, as GAE cron
// is supposed to avoid overruns and this action shouldn't be executed from any
// other context, but it's not harmful as we can just ignore the transaction. Log
// it so that we know about it and move on.
logger.atWarning().log(
"Ignoring transaction %s, which appears to have already been applied.",
txnEntity.getId());
return false;
}
logger.atInfo().log("Applying transaction %s to Cloud Datastore", txnEntity.getId());
// At this point, we know txnEntity is the correct next transaction, so write it
// to datastore.
try {
Transaction.deserialize(txnEntity.getContents()).writeToDatastore();
} catch (IOException e) {
throw new RuntimeException("Error during transaction deserialization.", e);
}
// Write the updated last transaction id to datastore as part of this datastore
// transaction.
ofy().save().entity(lastSqlTxn.cloneWithNewTransactionId(nextTxnId));
logger.atInfo().log(
"Finished applying single transaction Cloud SQL -> Cloud Datastore");
return false;
});
}
@Override
public void run() {
// TODO(b/181758163): Deal with objects that don't exist in Cloud SQL, e.g. ForeignKeyIndex,
// EppResourceIndex.
logger.atInfo().log("Processing transaction replay batch Cloud SQL -> Cloud Datastore");
for (TransactionEntity txnEntity : getTransactionBatch()) {
if (applyTransaction(txnEntity)) {
break;
}
}
logger.atInfo().log("Done processing transaction replay batch Cloud SQL -> Cloud Datastore");
}
}
@@ -0,0 +1,49 @@
// Copyright 2021 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.tools;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.Streams;
import google.registry.model.registry.label.PremiumList.PremiumListEntry;
import google.registry.model.registry.label.PremiumListDualDao;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/** Retrieves and prints one or more premium lists. */
@Parameters(separators = " =", commandDescription = "Show one or more premium lists")
public class GetPremiumListCommand implements CommandWithRemoteApi {
@Parameter(description = "Name(s) of the premium list(s) to retrieve", required = true)
private List<String> mainParameters;
@Override
public void run() throws Exception {
for (String premiumListName : mainParameters) {
if (PremiumListDualDao.exists(premiumListName)) {
System.out.printf(
"%s:\n%s\n",
premiumListName,
Streams.stream(PremiumListDualDao.loadAllPremiumListEntries(premiumListName))
.sorted(Comparator.comparing(PremiumListEntry::getLabel))
.map(PremiumListEntry::toString)
.collect(Collectors.joining("\n")));
} else {
System.out.println(String.format("No list found with name %s.", premiumListName));
}
}
}
}
@@ -76,6 +76,7 @@ public final class RegistryTool {
.put("get_host", GetHostCommand.class)
.put("get_keyring_secret", GetKeyringSecretCommand.class)
.put("get_operation_status", GetOperationStatusCommand.class)
.put("get_premium_list", GetPremiumListCommand.class)
.put("get_registrar", GetRegistrarCommand.class)
.put("get_resource_by_key", GetResourceByKeyCommand.class)
.put("get_routing_map", GetRoutingMapCommand.class)
@@ -43,7 +43,7 @@ import google.registry.request.Modules.UrlFetchTransportModule;
import google.registry.request.Modules.UserServiceModule;
import google.registry.tools.AuthModule.LocalCredentialModule;
import google.registry.util.UtilsModule;
import google.registry.whois.WhoisModule;
import google.registry.whois.NonCachingWhoisModule;
import javax.annotation.Nullable;
import javax.inject.Singleton;
@@ -81,7 +81,7 @@ import javax.inject.Singleton;
UserServiceModule.class,
UtilsModule.class,
VoidDnsWriterModule.class,
WhoisModule.class
NonCachingWhoisModule.class
})
interface RegistryToolComponent {
void inject(AckPollMessagesCommand command);
@@ -0,0 +1,52 @@
// Copyright 2021 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.whois;
import dagger.Module;
import dagger.Provides;
import google.registry.util.Clock;
import google.registry.whois.WhoisMetrics.WhoisMetric;
import java.io.IOException;
import java.io.Reader;
import javax.servlet.http.HttpServletRequest;
/**
* Dagger base module for the whois package.
*
* <p>Provides whois objects common to both the normal ({@link WhoisModule}) and non-caching ({@link
* NonCachingWhoisModule}) implementations.
*/
@Module
class BaseWhoisModule {
@Provides
@SuppressWarnings("CloseableProvides")
static Reader provideHttpInputReader(HttpServletRequest req) {
try {
return req.getReader();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Provides a {@link WhoisMetrics.WhoisMetric.Builder} with the startTimestamp already
* initialized.
*/
@Provides
static WhoisMetric.Builder provideEppMetricBuilder(Clock clock) {
return WhoisMetric.builderForRequest(clock);
}
}
@@ -14,6 +14,7 @@
package google.registry.whois;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.EppResourceUtils.loadByForeignKeyCached;
import com.google.common.net.InternetDomainName;
@@ -25,20 +26,27 @@ import org.joda.time.DateTime;
public class DomainLookupCommand extends DomainOrHostLookupCommand {
private final boolean fullOutput;
private final boolean cached;
private final String whoisRedactedEmailText;
public DomainLookupCommand(
InternetDomainName domainName,
boolean fullOutput,
boolean cached,
String whoisRedactedEmailText) {
super(domainName, "Domain");
this.fullOutput = fullOutput;
this.cached = cached;
this.whoisRedactedEmailText = whoisRedactedEmailText;
}
@Override
protected Optional<WhoisResponse> getResponse(InternetDomainName domainName, DateTime now) {
return loadByForeignKeyCached(DomainBase.class, domainName.toString(), now)
.map(domain -> new DomainWhoisResponse(domain, fullOutput, whoisRedactedEmailText, now));
Optional<DomainBase> domainResource =
cached
? loadByForeignKeyCached(DomainBase.class, domainName.toString(), now)
: loadByForeignKey(DomainBase.class, domainName.toString(), now);
return domainResource.map(
domain -> new DomainWhoisResponse(domain, fullOutput, whoisRedactedEmailText, now));
}
}
@@ -14,6 +14,7 @@
package google.registry.whois;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.EppResourceUtils.loadByForeignKeyCached;
import com.google.common.net.InternetDomainName;
@@ -24,13 +25,19 @@ import org.joda.time.DateTime;
/** Represents a WHOIS lookup on a nameserver based on its hostname. */
public class NameserverLookupByHostCommand extends DomainOrHostLookupCommand {
NameserverLookupByHostCommand(InternetDomainName hostName) {
boolean cached;
NameserverLookupByHostCommand(InternetDomainName hostName, boolean cached) {
super(hostName, "Nameserver");
this.cached = cached;
}
@Override
protected Optional<WhoisResponse> getResponse(InternetDomainName hostName, DateTime now) {
return loadByForeignKeyCached(HostResource.class, hostName.toString(), now)
.map(host -> new NameserverWhoisResponse(host, now));
Optional<HostResource> hostResource =
cached
? loadByForeignKeyCached(HostResource.class, hostName.toString(), now)
: loadByForeignKey(HostResource.class, hostName.toString(), now);
return hostResource.map(host -> new NameserverWhoisResponse(host, now));
}
}
@@ -0,0 +1,39 @@
// Copyright 2021 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.whois;
import dagger.Module;
import dagger.Provides;
import google.registry.config.RegistryConfig.Config;
/**
* Whois module for systems that require that we not cache EPP resources (e.g. the nomulus tool).
*
* <h3>Dependencies</h3>
*
* <ul>
* <li>{@link google.registry.request.RequestModule RequestModule}
* </ul>
*
* @see "google.registry.tools.RegistryToolComponent"
*/
@Module
public final class NonCachingWhoisModule extends BaseWhoisModule {
@Provides
@Config("whoisCommandFactory")
static WhoisCommandFactory provideWhoisCommandFactory() {
return WhoisCommandFactory.createNonCached();
}
}
@@ -39,61 +39,66 @@ final class RegistrarLookupCommand implements WhoisCommand {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/** True if the command should return cached responses. */
private boolean cached;
/**
* Cache of a map from a stripped-down (letters and digits only) name to the registrar. This map
* includes only active, publicly visible registrars, because the others should be invisible to
* WHOIS.
*/
private static final Supplier<Map<String, Registrar>> REGISTRAR_BY_NORMALIZED_NAME_CACHE =
memoizeWithShortExpiration(
() -> {
Map<String, Registrar> map = new HashMap<>();
// Use the normalized registrar name as a key, and ignore inactive and hidden
// registrars.
for (Registrar registrar : Registrar.loadAllCached()) {
if (!registrar.isLiveAndPubliclyVisible() || registrar.getRegistrarName() == null) {
continue;
}
String normalized = normalizeRegistrarName(registrar.getRegistrarName());
if (map.put(normalized, registrar) != null) {
logger.atWarning().log(
"%s appeared as a normalized registrar name for more than one registrar.",
normalized);
}
}
// Use the normalized registrar name without its last word as a key, assuming there are
// multiple words in the name. This allows searches without LLC or INC, etc. Only insert
// if there isn't already a mapping for this string, so that if there's a registrar with
// a
// two word name (Go Daddy) and no business-type suffix and another registrar with just
// that first word as its name (Go), the latter will win.
for (Registrar registrar : ImmutableList.copyOf(map.values())) {
if (registrar.getRegistrarName() == null) {
continue;
}
List<String> words =
Splitter.on(CharMatcher.whitespace()).splitToList(registrar.getRegistrarName());
if (words.size() > 1) {
String normalized =
normalizeRegistrarName(Joiner.on("").join(words.subList(0, words.size() - 1)));
map.putIfAbsent(normalized, registrar);
}
}
return ImmutableMap.copyOf(map);
});
memoizeWithShortExpiration(RegistrarLookupCommand::loadRegistrarMap);
@VisibleForTesting
final String registrarName;
RegistrarLookupCommand(String registrarName) {
RegistrarLookupCommand(String registrarName, boolean cached) {
checkArgument(!isNullOrEmpty(registrarName), "registrarName");
this.registrarName = registrarName;
this.cached = cached;
}
static Map<String, Registrar> loadRegistrarMap() {
Map<String, Registrar> map = new HashMap<>();
// Use the normalized registrar name as a key, and ignore inactive and hidden
// registrars.
for (Registrar registrar : Registrar.loadAll()) {
if (!registrar.isLiveAndPubliclyVisible() || registrar.getRegistrarName() == null) {
continue;
}
String normalized = normalizeRegistrarName(registrar.getRegistrarName());
if (map.put(normalized, registrar) != null) {
logger.atWarning().log(
"%s appeared as a normalized registrar name for more than one registrar.", normalized);
}
}
// Use the normalized registrar name without its last word as a key, assuming there are
// multiple words in the name. This allows searches without LLC or INC, etc. Only insert
// if there isn't already a mapping for this string, so that if there's a registrar with
// a
// two word name (Go Daddy) and no business-type suffix and another registrar with just
// that first word as its name (Go), the latter will win.
for (Registrar registrar : ImmutableList.copyOf(map.values())) {
if (registrar.getRegistrarName() == null) {
continue;
}
List<String> words =
Splitter.on(CharMatcher.whitespace()).splitToList(registrar.getRegistrarName());
if (words.size() > 1) {
String normalized =
normalizeRegistrarName(Joiner.on("").join(words.subList(0, words.size() - 1)));
map.putIfAbsent(normalized, registrar);
}
}
return ImmutableMap.copyOf(map);
}
@Override
public WhoisResponse executeQuery(DateTime now) throws WhoisException {
Registrar registrar =
REGISTRAR_BY_NORMALIZED_NAME_CACHE.get().get(normalizeRegistrarName(registrarName));
Map<String, Registrar> registrars =
cached ? REGISTRAR_BY_NORMALIZED_NAME_CACHE.get() : loadRegistrarMap();
Registrar registrar = registrars.get(normalizeRegistrarName(registrarName));
// If a registrar is in the cache, we know it must be active and publicly visible.
if (registrar == null) {
throw new WhoisException(now, SC_NOT_FOUND, "No registrar found.");
@@ -26,10 +26,30 @@ import java.net.InetAddress;
*/
public class WhoisCommandFactory {
/** True if the commands should return cached responses. */
private boolean cached = true;
/** Public default constructor, needed so we can construct this from the class name. */
public WhoisCommandFactory() {}
private WhoisCommandFactory(boolean cached) {
this.cached = cached;
}
/** Create a command factory that does not rely on entity caches. */
static WhoisCommandFactory createNonCached() {
return new WhoisCommandFactory(false);
}
/** Create a command factory that may rely on entity caches. */
static WhoisCommandFactory createCached() {
return new WhoisCommandFactory(true);
}
/** Returns a new {@link WhoisCommand} to perform a domain lookup on the specified domain name. */
public WhoisCommand domainLookup(
InternetDomainName domainName, boolean fullOutput, String whoisRedactedEmailText) {
return new DomainLookupCommand(domainName, fullOutput, whoisRedactedEmailText);
return new DomainLookupCommand(domainName, fullOutput, cached, whoisRedactedEmailText);
}
/**
@@ -43,7 +63,7 @@ public class WhoisCommandFactory {
* Returns a new {@link WhoisCommand} to perform a nameserver lookup on the specified host name.
*/
public WhoisCommand nameserverLookupByHost(InternetDomainName hostName) {
return new NameserverLookupByHostCommand(hostName);
return new NameserverLookupByHostCommand(hostName, cached);
}
/**
@@ -51,6 +71,6 @@ public class WhoisCommandFactory {
* name.
*/
public WhoisCommand registrarLookup(String registrar) {
return new RegistrarLookupCommand(registrar);
return new RegistrarLookupCommand(registrar, cached);
}
}
@@ -20,11 +20,6 @@ import static google.registry.util.TypeUtils.instantiate;
import dagger.Module;
import dagger.Provides;
import google.registry.config.RegistryConfig.Config;
import google.registry.util.Clock;
import google.registry.whois.WhoisMetrics.WhoisMetric;
import java.io.IOException;
import java.io.Reader;
import javax.servlet.http.HttpServletRequest;
/**
* Dagger module for the whois package.
@@ -38,31 +33,11 @@ import javax.servlet.http.HttpServletRequest;
* @see "google.registry.module.frontend.FrontendComponent"
*/
@Module
public final class WhoisModule {
@Provides
@SuppressWarnings("CloseableProvides")
static Reader provideHttpInputReader(HttpServletRequest req) {
try {
return req.getReader();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public final class WhoisModule extends BaseWhoisModule {
@Provides
@Config("whoisCommandFactory")
static WhoisCommandFactory provideWhoisCommandFactory(
@Config("whoisCommandFactoryClass") String factoryClass) {
return instantiate(getClassFromString(factoryClass, WhoisCommandFactory.class));
}
/**
* Provides a {@link WhoisMetrics.WhoisMetric.Builder} with the startTimestamp already
* initialized.
*/
@Provides
static WhoisMetric.Builder provideEppMetricBuilder(Clock clock) {
return WhoisMetric.builderForRequest(clock);
}
}
@@ -194,7 +194,7 @@ class WhoisReader {
if (!tld.isPresent()) {
// This target is not under any configured TLD, so just try it as a registrar name.
logger.atInfo().log("Attempting registrar lookup using %s as a registrar", arg1);
return new RegistrarLookupCommand(arg1);
return commandFactory.registrarLookup(arg1);
}
// If the target is exactly one level above the TLD, then this is a second level domain
@@ -25,12 +25,19 @@ import google.registry.flows.ResourceCheckFlowTestCase;
import google.registry.flows.exceptions.TooManyResourceChecksException;
import google.registry.model.contact.ContactResource;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.ReplayExtension;
import google.registry.testing.TestOfyAndSql;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link ContactCheckFlow}. */
@DualDatabaseTest
class ContactCheckFlowTest extends ResourceCheckFlowTestCase<ContactCheckFlow, ContactResource> {
@Order(value = Order.DEFAULT - 2)
@RegisterExtension
final ReplayExtension replayExtension = ReplayExtension.createWithCompare(clock);
ContactCheckFlowTest() {
setEppInput("contact_check.xml");
}
@@ -33,13 +33,20 @@ import google.registry.flows.exceptions.ResourceAlreadyExistsForThisClientExcept
import google.registry.flows.exceptions.ResourceCreateContentionException;
import google.registry.model.contact.ContactResource;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.ReplayExtension;
import google.registry.testing.TestOfyAndSql;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link ContactCreateFlow}. */
@DualDatabaseTest
class ContactCreateFlowTest extends ResourceFlowTestCase<ContactCreateFlow, ContactResource> {
@Order(value = Order.DEFAULT - 2)
@RegisterExtension
final ReplayExtension replayExtension = ReplayExtension.createWithCompare(clock);
ContactCreateFlowTest() {
setEppInput("contact_create.xml");
clock.setTo(DateTime.parse("1999-04-03T22:00:00.0Z"));
@@ -38,13 +38,20 @@ import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppcommon.Trid;
import google.registry.model.reporting.HistoryEntry;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.ReplayExtension;
import google.registry.testing.TestOfyAndSql;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link ContactDeleteFlow}. */
@DualDatabaseTest
class ContactDeleteFlowTest extends ResourceFlowTestCase<ContactDeleteFlow, ContactResource> {
@Order(value = Order.DEFAULT - 2)
@RegisterExtension
final ReplayExtension replayExtension = ReplayExtension.createWithCompare(clock);
@BeforeEach
void initFlowTest() {
setEppInput("contact_delete.xml");
@@ -39,13 +39,20 @@ import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
import google.registry.model.eppcommon.PresenceMarker;
import google.registry.model.eppcommon.StatusValue;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.ReplayExtension;
import google.registry.testing.TestOfyAndSql;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link ContactInfoFlow}. */
@DualDatabaseTest
class ContactInfoFlowTest extends ResourceFlowTestCase<ContactInfoFlow, ContactResource> {
@Order(value = Order.DEFAULT - 2)
@RegisterExtension
final ReplayExtension replayExtension = ReplayExtension.createWithCompare(clock);
ContactInfoFlowTest() {
setEppInput("contact_info.xml");
}
@@ -38,14 +38,21 @@ import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferResponse;
import google.registry.model.transfer.TransferStatus;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.ReplayExtension;
import google.registry.testing.TestOfyAndSql;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link ContactTransferCancelFlow}. */
@DualDatabaseTest
class ContactTransferCancelFlowTest
extends ContactTransferFlowTestCase<ContactTransferCancelFlow, ContactResource> {
@Order(value = Order.DEFAULT - 2)
@RegisterExtension
final ReplayExtension replayExtension = ReplayExtension.createWithCompare(clock);
@BeforeEach
void setUp() {
this.setEppInput("contact_transfer_cancel.xml");
@@ -40,14 +40,21 @@ import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferResponse;
import google.registry.model.transfer.TransferStatus;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.ReplayExtension;
import google.registry.testing.TestOfyAndSql;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link ContactTransferRejectFlow}. */
@DualDatabaseTest
class ContactTransferRejectFlowTest
extends ContactTransferFlowTestCase<ContactTransferRejectFlow, ContactResource> {
@Order(value = Order.DEFAULT - 2)
@RegisterExtension
final ReplayExtension replayExtension = ReplayExtension.createWithCompare(clock);
@BeforeEach
void setUp() {
setEppInput("contact_transfer_reject.xml");
@@ -52,15 +52,22 @@ import google.registry.model.reporting.HistoryEntry;
import google.registry.model.transfer.ContactTransferData;
import google.registry.model.transfer.TransferStatus;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.ReplayExtension;
import google.registry.testing.TestOfyAndSql;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link ContactTransferRequestFlow}. */
@DualDatabaseTest
class ContactTransferRequestFlowTest
extends ContactTransferFlowTestCase<ContactTransferRequestFlow, ContactResource> {
@Order(value = Order.DEFAULT - 2)
@RegisterExtension
final ReplayExtension replayExtension = ReplayExtension.createWithCompare(clock);
ContactTransferRequestFlowTest() {
// We need the transfer to happen at exactly this time in order for the response to match up.
clock.setTo(DateTime.parse("2000-06-08T22:00:00.0Z"));
@@ -71,16 +71,29 @@ import google.registry.model.registry.Registry;
import google.registry.model.registry.Registry.TldState;
import google.registry.model.registry.label.ReservedList;
import google.registry.model.reporting.HistoryEntry;
import google.registry.testing.ReplayExtension;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link DomainCheckFlow}. */
class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, DomainBase> {
@Order(value = Order.DEFAULT - 3)
@RegisterExtension
final SetClockExtension setClockExtension = new SetClockExtension();
@Order(value = Order.DEFAULT - 2)
@RegisterExtension
final ReplayExtension replayExtension = ReplayExtension.createWithCompare(clock);
DomainCheckFlowTest() {
setEppInput("domain_check_one_tld.xml");
}
@@ -1338,4 +1351,12 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
.build());
}
class SetClockExtension implements BeforeEachCallback {
@Override
public void beforeEach(ExtensionContext context) {
// Kick the clock back to before the earliest date that we set the clock to.
clock.setTo(DateTime.parse("2009-01-01T10:00:00Z"));
}
}
}
@@ -36,13 +36,20 @@ import google.registry.flows.exceptions.TooManyResourceChecksException;
import google.registry.model.domain.DomainBase;
import google.registry.model.registry.Registry;
import google.registry.model.registry.Registry.TldState;
import google.registry.testing.ReplayExtension;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link DomainClaimsCheckFlow}. */
public class DomainClaimsCheckFlowTest
extends ResourceFlowTestCase<DomainClaimsCheckFlow, DomainBase> {
@Order(value = Order.DEFAULT - 2)
@RegisterExtension
final ReplayExtension replayExtension = ReplayExtension.createWithCompare(clock);
DomainClaimsCheckFlowTest() {
setEppInput("domain_check_claims.xml");
}
@@ -26,6 +26,7 @@ import static google.registry.testing.DatabaseHelper.persistActiveHost;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
import static google.registry.testing.TestDataHelper.updateSubstitutions;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -45,7 +46,8 @@ import google.registry.flows.domain.DomainFlowUtils.FeeChecksDontSupportPhasesEx
import google.registry.flows.domain.DomainFlowUtils.RestoresAreAlwaysForOneYearException;
import google.registry.flows.domain.DomainFlowUtils.TransfersAreAlwaysForOneYearException;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.billing.BillingEvent.Flag;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.contact.ContactAuthInfo;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DesignatedContact;
@@ -63,13 +65,26 @@ import google.registry.model.registry.Registry;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.ReplayExtension;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link DomainInfoFlow}. */
class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase> {
@Order(value = Order.DEFAULT - 3)
@RegisterExtension
final SetClockExtension setClockExtension = new SetClockExtension();
@Order(value = Order.DEFAULT - 2)
@RegisterExtension
final ReplayExtension replayExtension = ReplayExtension.createWithCompare(clock);
/**
* The domain_info_fee.xml default substitutions common to most tests.
*
@@ -93,7 +108,6 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
void setup() {
setEppInput("domain_info.xml");
sessionMetadata.setClientId("NewRegistrar");
clock.setTo(DateTime.parse("2005-03-03T22:00:00.000Z"));
createTld("tld");
persistResource(AppEngineExtension.makeRegistrar1().asBuilder().setClientId("ClientZ").build());
}
@@ -149,7 +163,10 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
}
private void doSuccessfulTest(
String expectedXmlFilename, boolean inactive, ImmutableMap<String, String> substitutions)
String expectedXmlFilename,
boolean inactive,
ImmutableMap<String, String> substitutions,
boolean expectHistoryAndBilling)
throws Exception {
assertTransactionalFlow(false);
String expected =
@@ -158,12 +175,20 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
expected = expected.replaceAll("\"ok\"", "\"inactive\"");
}
runFlowAssertResponse(expected);
assertNoHistory();
assertNoBillingEvents();
if (!expectHistoryAndBilling) {
assertNoHistory();
assertNoBillingEvents();
}
}
private void doSuccessfulTest(
String expectedXmlFilename, boolean inactive, ImmutableMap<String, String> substitutions)
throws Exception {
doSuccessfulTest(expectedXmlFilename, inactive, substitutions, false);
}
private void doSuccessfulTest(String expectedXmlFilename, boolean inactive) throws Exception {
doSuccessfulTest(expectedXmlFilename, inactive, ImmutableMap.of());
doSuccessfulTest(expectedXmlFilename, inactive, ImmutableMap.of(), false);
}
private void doSuccessfulTest(String expectedXmlFilename) throws Exception {
@@ -309,7 +334,11 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
.asBuilder()
.addGracePeriod(
GracePeriod.create(
gracePeriodStatus, domain.getRepoId(), clock.nowUtc().plusDays(1), "foo", null))
gracePeriodStatus,
domain.getRepoId(),
clock.nowUtc().plusDays(1),
"TheRegistrar",
null))
.setCreationClientId("NewRegistrar")
.setCreationTimeForTest(DateTime.parse("2003-11-26T22:00:00.0Z"))
.setRegistrationExpirationTime(DateTime.parse("2005-11-26T22:00:00.0Z"))
@@ -328,10 +357,25 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
@Test
void testSuccess_autoRenewGracePeriod() throws Exception {
persistTestEntities(false);
Key<HistoryEntry> historyEntry =
Key.create(domain.createVKey().getOfyKey(), HistoryEntry.class, 67890);
VKey<BillingEvent.Recurring> recurringVKey =
VKey.from(Key.create(historyEntry, Recurring.class, 12345));
HistoryEntry historyEntry =
persistResource(
new HistoryEntry.Builder()
.setParent(domain)
.setType(HistoryEntry.Type.DOMAIN_CREATE)
.setModificationTime(clock.nowUtc())
.build());
BillingEvent.Recurring renewEvent =
persistResource(
new BillingEvent.Recurring.Builder()
.setReason(Reason.RENEW)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setTargetId(getUniqueIdFromCommand())
.setClientId("TheRegistrar")
.setEventTime(clock.nowUtc())
.setRecurrenceEndTime(END_OF_TIME)
.setParent(historyEntry)
.build());
VKey<BillingEvent.Recurring> recurringVKey = renewEvent.createVKey();
// Add an AUTO_RENEW grace period to the saved resource.
persistResource(
domain
@@ -341,10 +385,10 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
GracePeriodStatus.AUTO_RENEW,
domain.getRepoId(),
clock.nowUtc().plusDays(1),
"foo",
"TheRegistrar",
recurringVKey))
.build());
doSuccessfulTest("domain_info_response_autorenewperiod.xml", false);
doSuccessfulTest("domain_info_response_autorenewperiod.xml", false, ImmutableMap.of(), true);
}
@Test
@@ -360,7 +404,7 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
GracePeriodStatus.REDEMPTION,
domain.getRepoId(),
clock.nowUtc().plusDays(1),
"foo",
"TheRegistrar",
null))
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
.build());
@@ -379,7 +423,7 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
GracePeriodStatus.RENEW,
domain.getRepoId(),
clock.nowUtc().plusDays(1),
"foo",
"TheRegistrar",
null))
.build());
doSuccessfulTest("domain_info_response_renewperiod.xml", false);
@@ -397,14 +441,14 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
GracePeriodStatus.RENEW,
domain.getRepoId(),
clock.nowUtc().plusDays(1),
"foo",
"TheRegistrar",
null))
.addGracePeriod(
GracePeriod.create(
GracePeriodStatus.RENEW,
domain.getRepoId(),
clock.nowUtc().plusDays(2),
"foo",
"TheRegistrar",
null))
.build());
doSuccessfulTest("domain_info_response_renewperiod.xml", false);
@@ -422,7 +466,7 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
GracePeriodStatus.TRANSFER,
domain.getRepoId(),
clock.nowUtc().plusDays(1),
"foo",
"TheRegistrar",
null))
.build());
doSuccessfulTest("domain_info_response_transferperiod.xml", false);
@@ -450,14 +494,14 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
GracePeriodStatus.ADD,
domain.getRepoId(),
clock.nowUtc().plusDays(1),
"foo",
"TheRegistrar",
null))
.addGracePeriod(
GracePeriod.create(
GracePeriodStatus.RENEW,
domain.getRepoId(),
clock.nowUtc().plusDays(2),
"foo",
"TheRegistrar",
null))
.build());
doSuccessfulTest("domain_info_response_stackedaddrenewperiod.xml", false);
@@ -475,7 +519,7 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
GracePeriodStatus.ADD,
domain.getRepoId(),
clock.nowUtc().plusDays(1),
"foo",
"TheRegistrar",
null))
.setDsData(
ImmutableSet.of(
@@ -929,4 +973,12 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
assertIcannReportingActivityFieldLogged("srs-dom-info");
assertTldsFieldLogged("tld");
}
class SetClockExtension implements BeforeEachCallback {
@Override
public void beforeEach(ExtensionContext context) {
// Kick the clock back to before the earliest date that we set the clock to.
clock.setTo(DateTime.parse("2005-03-03T22:00:00.000Z"));
}
}
}
@@ -70,16 +70,23 @@ import google.registry.model.registry.Registry;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
import google.registry.model.reporting.HistoryEntry;
import google.registry.testing.ReplayExtension;
import java.util.Map;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link DomainRestoreRequestFlow}. */
class DomainRestoreRequestFlowTest
extends ResourceFlowTestCase<DomainRestoreRequestFlow, DomainBase> {
@Order(value = Order.DEFAULT - 2)
@RegisterExtension
final ReplayExtension replayExtension = ReplayExtension.createWithCompare(clock);
private static final ImmutableMap<String, String> FEE_06_MAP =
ImmutableMap.of("FEE_VERSION", "0.6", "FEE_NS", "fee", "CURRENCY", "USD");
private static final ImmutableMap<String, String> FEE_11_MAP =
@@ -99,11 +106,12 @@ class DomainRestoreRequestFlowTest
}
void persistPendingDeleteDomain(DateTime expirationTime) throws Exception {
DomainBase domain = newDomainBase(getUniqueIdFromCommand());
DomainBase domain = persistResource(newDomainBase(getUniqueIdFromCommand()));
HistoryEntry historyEntry =
persistResource(
new HistoryEntry.Builder()
.setType(HistoryEntry.Type.DOMAIN_DELETE)
.setModificationTime(clock.nowUtc())
.setParent(domain)
.build());
persistResource(
@@ -116,7 +124,7 @@ class DomainRestoreRequestFlowTest
GracePeriodStatus.REDEMPTION,
domain.getRepoId(),
clock.nowUtc().plusDays(1),
"foo",
"TheRegistrar",
null))
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
.setDeletePollMessage(
@@ -23,11 +23,11 @@ import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_CREATE;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_TRANSFER_REJECT;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_TRANSFER_REQUEST;
import static google.registry.testing.DatabaseHelper.assertBillingEvents;
import static google.registry.testing.DatabaseHelper.deleteResource;
import static google.registry.testing.DatabaseHelper.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatabaseHelper.getOnlyPollMessage;
import static google.registry.testing.DatabaseHelper.getPollMessages;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistDomainAsDeleted;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DomainBaseSubject.assertAboutDomains;
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
@@ -56,12 +56,14 @@ import google.registry.model.reporting.HistoryEntry;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferResponse;
import google.registry.model.transfer.TransferStatus;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link DomainTransferRejectFlow}. */
@DualDatabaseTest
class DomainTransferRejectFlowTest
extends DomainTransferFlowTestCase<DomainTransferRejectFlow, DomainBase> {
@@ -151,31 +153,31 @@ class DomainTransferRejectFlowTest
runFlow();
}
@Test
@TestOfyAndSql
void testSuccess() throws Exception {
doSuccessfulTest("domain_transfer_reject.xml", "domain_transfer_reject_response.xml");
}
@Test
@TestOfyAndSql
void testDryRun() throws Exception {
setEppInput("domain_transfer_reject.xml");
eppLoader.replaceAll("JD1234-REP", contact.getRepoId());
dryRunFlowAssertResponse(loadFile("domain_transfer_reject_response.xml"));
}
@Test
@TestOfyAndSql
void testSuccess_domainAuthInfo() throws Exception {
doSuccessfulTest(
"domain_transfer_reject_domain_authinfo.xml", "domain_transfer_reject_response.xml");
}
@Test
@TestOfyAndSql
void testSuccess_contactAuthInfo() throws Exception {
doSuccessfulTest(
"domain_transfer_reject_contact_authinfo.xml", "domain_transfer_reject_response.xml");
}
@Test
@TestOfyAndSql
void testFailure_notAuthorizedForTld() {
persistResource(
loadRegistrar("TheRegistrar").asBuilder().setAllowedTlds(ImmutableSet.of()).build());
@@ -188,7 +190,7 @@ class DomainTransferRejectFlowTest
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testSuccess_superuserNotAuthorizedForTld() throws Exception {
persistResource(
loadRegistrar("TheRegistrar").asBuilder().setAllowedTlds(ImmutableSet.of()).build());
@@ -196,7 +198,7 @@ class DomainTransferRejectFlowTest
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("domain_transfer_reject_response.xml"));
}
@Test
@TestOfyAndSql
void testFailure_badContactPassword() {
// Change the contact's password so it does not match the password in the file.
contact =
@@ -212,7 +214,7 @@ class DomainTransferRejectFlowTest
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_badDomainPassword() {
// Change the domain's password so it does not match the password in the file.
domain =
@@ -228,7 +230,7 @@ class DomainTransferRejectFlowTest
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_neverBeenTransferred() {
changeTransferStatus(null);
EppException thrown =
@@ -237,7 +239,7 @@ class DomainTransferRejectFlowTest
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_clientApproved() {
changeTransferStatus(TransferStatus.CLIENT_APPROVED);
EppException thrown =
@@ -246,7 +248,7 @@ class DomainTransferRejectFlowTest
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_clientRejected() {
changeTransferStatus(TransferStatus.CLIENT_REJECTED);
EppException thrown =
@@ -255,7 +257,7 @@ class DomainTransferRejectFlowTest
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_clientCancelled() {
changeTransferStatus(TransferStatus.CLIENT_CANCELLED);
EppException thrown =
@@ -264,7 +266,7 @@ class DomainTransferRejectFlowTest
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_serverApproved() {
changeTransferStatus(TransferStatus.SERVER_APPROVED);
EppException thrown =
@@ -273,7 +275,7 @@ class DomainTransferRejectFlowTest
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_serverCancelled() {
changeTransferStatus(TransferStatus.SERVER_CANCELLED);
EppException thrown =
@@ -282,7 +284,7 @@ class DomainTransferRejectFlowTest
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_gainingClient() {
setClientIdForFlow("NewRegistrar");
EppException thrown =
@@ -291,7 +293,7 @@ class DomainTransferRejectFlowTest
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_unrelatedClient() {
setClientIdForFlow("ClientZ");
EppException thrown =
@@ -300,7 +302,7 @@ class DomainTransferRejectFlowTest
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_deletedDomain() throws Exception {
domain =
persistResource(domain.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
@@ -310,9 +312,9 @@ class DomainTransferRejectFlowTest
assertThat(thrown).hasMessageThat().contains(String.format("(%s)", getUniqueIdFromCommand()));
}
@Test
@TestOfyAndSql
void testFailure_nonexistentDomain() throws Exception {
deleteResource(domain);
persistDomainAsDeleted(domain, clock.nowUtc());
ResourceDoesNotExistException thrown =
assertThrows(
ResourceDoesNotExistException.class, () -> doFailingTest("domain_transfer_reject.xml"));
@@ -322,7 +324,7 @@ class DomainTransferRejectFlowTest
// NB: No need to test pending delete status since pending transfers will get cancelled upon
// entering pending delete phase. So it's already handled in that test case.
@Test
@TestOfyAndSql
void testIcannActivityReportField_getsLogged() throws Exception {
runFlow();
assertIcannReportingActivityFieldLogged("srs-dom-transfer-reject");
@@ -338,7 +340,7 @@ class DomainTransferRejectFlowTest
.build());
}
@Test
@TestOfyAndSql
void testIcannTransactionRecord_noRecordsToCancel() throws Exception {
setUpGracePeriodDurations();
runFlow();
@@ -348,7 +350,7 @@ class DomainTransferRejectFlowTest
.containsExactly(DomainTransactionRecord.create("tld", clock.nowUtc(), TRANSFER_NACKED, 1));
}
@Test
@TestOfyAndSql
void testIcannTransactionRecord_cancelsPreviousRecords() throws Exception {
setUpGracePeriodDurations();
DomainTransactionRecord previousSuccessRecord =
@@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.truth.Correspondence;
import com.google.common.truth.Correspondence.BinaryPredicate;
@@ -50,6 +51,11 @@ public final class ImmutableObjectSubject extends Subject {
this.actual = actual;
}
public void isEqualExceptFields(
@Nullable ImmutableObject expected, Iterable<String> ignoredFields) {
isEqualExceptFields(expected, Iterables.toArray(ignoredFields, String.class));
}
public void isEqualExceptFields(@Nullable ImmutableObject expected, String... ignoredFields) {
if (actual == null) {
assertThat(expected).isNull();
@@ -15,6 +15,7 @@
package google.registry.model.domain;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.flows.domain.DomainTransferUtils.createPendingTransferData;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.testing.DatabaseHelper.createTld;
@@ -34,11 +35,13 @@ import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.common.EntityGroupRoot;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DesignatedContact.Type;
import google.registry.model.domain.Period.Unit;
import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppcommon.Trid;
import google.registry.model.host.HostResource;
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.HistoryEntry;
@@ -54,6 +57,7 @@ import org.joda.money.Money;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
/** Verify that we can store/retrieve DomainBase objects from a SQL database. */
@DualDatabaseTest
@@ -617,15 +621,15 @@ public class DomainBaseSqlTest {
.setParent(historyEntry)
.build();
DomainTransferData transferData =
new DomainTransferData.Builder()
.setServerApproveBillingEvent(
createLegacyVKey(BillingEvent.OneTime.class, oneTimeBillingEvent.getId()))
.setServerApproveAutorenewEvent(
createLegacyVKey(BillingEvent.Recurring.class, billEvent.getId()))
.setServerApproveAutorenewPollMessage(
createLegacyVKey(
PollMessage.Autorenew.class, autorenewPollMessage.getId()))
.build();
createPendingTransferData(
new DomainTransferData.Builder()
.setTransferRequestTrid(Trid.create("foo", "bar"))
.setTransferRequestTime(fakeClock.nowUtc())
.setGainingClientId("registrar2")
.setLosingClientId("registrar1")
.setPendingTransferExpirationTime(fakeClock.nowUtc().plusDays(1)),
ImmutableSet.of(oneTimeBillingEvent, billEvent, autorenewPollMessage),
Period.create(0, Unit.YEARS));
gracePeriods =
ImmutableSet.of(
GracePeriod.create(
@@ -708,10 +712,17 @@ public class DomainBaseSqlTest {
// Fix the original creation timestamp (this gets initialized on first write)
DomainBase org = domain.asBuilder().setCreationTime(thatDomain.getCreationTime()).build();
String[] moreExcepts = Arrays.copyOf(excepts, excepts.length + 1);
moreExcepts[moreExcepts.length - 1] = "updateTimestamp";
ImmutableList<String> moreExcepts =
new ImmutableList.Builder<String>()
.addAll(Arrays.asList(excepts))
.add("updateTimestamp")
.add("transferData")
.build();
// Note that the equality comparison forces a lazy load of all fields.
assertAboutImmutableObjects().that(thatDomain).isEqualExceptFields(org, moreExcepts);
// Transfer data cannot be directly compared due to serverApproveEtities inequalities
assertAboutImmutableObjects()
.that(domain.getTransferData())
.isEqualExceptFields(org.getTransferData(), "serverApproveEntities");
}
}
@@ -0,0 +1,202 @@
// Copyright 2021 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.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import com.google.common.collect.ImmutableList;
import google.registry.model.ImmutableObject;
import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestExtension;
import google.registry.testing.FakeClock;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.criteria.CriteriaQuery;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Tests for {@link CriteriaQueryBuilder}. */
class CriteriaQueryBuilderTest {
private final FakeClock fakeClock = new FakeClock();
private CriteriaQueryBuilderTestEntity entity1 =
new CriteriaQueryBuilderTestEntity("name1", "data");
private CriteriaQueryBuilderTestEntity entity2 =
new CriteriaQueryBuilderTestEntity("name2", "zztz");
private CriteriaQueryBuilderTestEntity entity3 = new CriteriaQueryBuilderTestEntity("zzz", "aaa");
@RegisterExtension
final JpaUnitTestExtension jpaExtension =
new JpaTestRules.Builder()
.withClock(fakeClock)
.withEntityClass(CriteriaQueryBuilderTestEntity.class)
.buildUnitTestRule();
@BeforeEach
void beforeEach() {
jpaTm().transact(() -> jpaTm().putAll(ImmutableList.of(entity1, entity2, entity3)));
}
@Test
void testSuccess_noWhereClause() {
assertThat(
jpaTm()
.transact(
() ->
jpaTm()
.getEntityManager()
.createQuery(
CriteriaQueryBuilder.create(CriteriaQueryBuilderTestEntity.class)
.build())
.getResultList()))
.containsExactly(entity1, entity2, entity3)
.inOrder();
}
@Test
void testSuccess_where_exactlyOne() {
List<CriteriaQueryBuilderTestEntity> result =
jpaTm()
.transact(
() -> {
CriteriaQuery<CriteriaQueryBuilderTestEntity> query =
CriteriaQueryBuilder.create(CriteriaQueryBuilderTestEntity.class)
.where(
jpaTm().getEntityManager().getCriteriaBuilder()::equal,
"data",
"zztz")
.build();
return jpaTm().getEntityManager().createQuery(query).getResultList();
});
assertThat(result).containsExactly(entity2);
}
@Test
void testSuccess_where_like_oneResult() {
List<CriteriaQueryBuilderTestEntity> result =
jpaTm()
.transact(
() -> {
CriteriaQuery<CriteriaQueryBuilderTestEntity> query =
CriteriaQueryBuilder.create(CriteriaQueryBuilderTestEntity.class)
.where(
jpaTm().getEntityManager().getCriteriaBuilder()::like, "data", "a%")
.build();
return jpaTm().getEntityManager().createQuery(query).getResultList();
});
assertThat(result).containsExactly(entity3);
}
@Test
void testSuccess_where_like_twoResults() {
List<CriteriaQueryBuilderTestEntity> result =
jpaTm()
.transact(
() -> {
CriteriaQuery<CriteriaQueryBuilderTestEntity> query =
CriteriaQueryBuilder.create(CriteriaQueryBuilderTestEntity.class)
.where(
jpaTm().getEntityManager().getCriteriaBuilder()::like, "data", "%a%")
.build();
return jpaTm().getEntityManager().createQuery(query).getResultList();
});
assertThat(result).containsExactly(entity1, entity3).inOrder();
}
@Test
void testSuccess_multipleWheres() {
List<CriteriaQueryBuilderTestEntity> result =
jpaTm()
.transact(
() -> {
CriteriaQuery<CriteriaQueryBuilderTestEntity> query =
CriteriaQueryBuilder.create(CriteriaQueryBuilderTestEntity.class)
// first "where" matches 1 and 3
.where(
jpaTm().getEntityManager().getCriteriaBuilder()::like, "data", "%a%")
// second "where" matches 1 and 2
.where(
jpaTm().getEntityManager().getCriteriaBuilder()::like, "data", "%t%")
.build();
return jpaTm().getEntityManager().createQuery(query).getResultList();
});
assertThat(result).containsExactly(entity1);
}
@Test
void testSuccess_where_in_oneResult() {
List<CriteriaQueryBuilderTestEntity> result =
jpaTm()
.transact(
() -> {
CriteriaQuery<CriteriaQueryBuilderTestEntity> query =
CriteriaQueryBuilder.create(CriteriaQueryBuilderTestEntity.class)
.whereFieldIsIn("data", ImmutableList.of("aaa", "bbb"))
.build();
return jpaTm().getEntityManager().createQuery(query).getResultList();
});
assertThat(result).containsExactly(entity3).inOrder();
}
@Test
void testSuccess_where_in_twoResults() {
List<CriteriaQueryBuilderTestEntity> result =
jpaTm()
.transact(
() -> {
CriteriaQuery<CriteriaQueryBuilderTestEntity> query =
CriteriaQueryBuilder.create(CriteriaQueryBuilderTestEntity.class)
.whereFieldIsIn("data", ImmutableList.of("aaa", "bbb", "data"))
.build();
return jpaTm().getEntityManager().createQuery(query).getResultList();
});
assertThat(result).containsExactly(entity1, entity3).inOrder();
}
@Test
void testSuccess_orderBy() {
List<CriteriaQueryBuilderTestEntity> result =
jpaTm()
.transact(
() -> {
CriteriaQuery<CriteriaQueryBuilderTestEntity> query =
CriteriaQueryBuilder.create(CriteriaQueryBuilderTestEntity.class)
.orderBy(jpaTm().getEntityManager().getCriteriaBuilder()::asc, "data")
.where(
jpaTm().getEntityManager().getCriteriaBuilder()::like, "data", "%a%")
.build();
return jpaTm().getEntityManager().createQuery(query).getResultList();
});
assertThat(result).containsExactly(entity3, entity1).inOrder();
}
@Entity(name = "CriteriaQueryBuilderTestEntity")
private static class CriteriaQueryBuilderTestEntity extends ImmutableObject {
@Id private String name;
@SuppressWarnings("unused")
private String data;
private CriteriaQueryBuilderTestEntity() {}
private CriteriaQueryBuilderTestEntity(String name, String data) {
this.name = name;
this.data = data;
}
}
}
@@ -112,7 +112,7 @@ class TransactionTest {
});
TransactionEntity txnEnt =
jpaTm().transact(() -> jpaTm().loadByKey(VKey.createSql(TransactionEntity.class, 1L)));
Transaction txn = Transaction.deserialize(txnEnt.contents);
Transaction txn = Transaction.deserialize(txnEnt.getContents());
txn.writeToDatastore();
ofyTm()
.transact(
@@ -43,14 +43,16 @@ import google.registry.model.reporting.HistoryEntry;
import google.registry.rdap.RdapMetrics.EndpointType;
import google.registry.rdap.RdapMetrics.SearchType;
import google.registry.rdap.RdapSearchResults.IncompletenessWarningType;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.FakeResponse;
import google.registry.testing.TestOfyAndSql;
import java.net.URLDecoder;
import java.util.Optional;
import javax.annotation.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link RdapEntitySearchAction}. */
@DualDatabaseTest
class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySearchAction> {
RdapEntitySearchActionTest() {
@@ -209,9 +211,9 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
.asBuilder()
.setRepoId(String.format("%04d-ROID", i))
.build();
resourcesBuilder.add(contact);
resourcesBuilder.add(makeHistoryEntry(
contact, HistoryEntry.Type.CONTACT_CREATE, null, "created", clock.nowUtc()));
resourcesBuilder.add(contact);
}
persistResources(resourcesBuilder.build());
for (int i = 1; i <= numRegistrars; i++) {
@@ -386,7 +388,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
}
}
@Test
@TestOfyAndSql
void testInvalidPath_rejected() {
action.requestPath = actionPath + "/path";
action.run();
@@ -394,7 +396,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(Optional.empty(), 400);
}
@Test
@TestOfyAndSql
void testInvalidRequest_rejected() {
action.run();
assertThat(parseJsonObject(response.getPayload()))
@@ -405,7 +407,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(Optional.empty(), 400);
}
@Test
@TestOfyAndSql
void testNameMatch_suffixRejected() {
assertThat(generateActualJsonWithFullName("exam*ple"))
.isEqualTo(
@@ -417,7 +419,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(Optional.empty(), 422);
}
@Test
@TestOfyAndSql
void testHandleMatch_suffixRejected() {
assertThat(generateActualJsonWithHandle("exam*ple"))
.isEqualTo(
@@ -429,7 +431,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(Optional.empty(), 422);
}
@Test
@TestOfyAndSql
void testMultipleWildcards_rejected() {
assertThat(generateActualJsonWithHandle("*.*"))
.isEqualTo(
@@ -441,7 +443,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(Optional.empty(), 422);
}
@Test
@TestOfyAndSql
void testNoCharactersToMatch_rejected() {
rememberWildcardType("*");
assertThat(generateActualJsonWithHandle("*"))
@@ -453,7 +455,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(Optional.empty(), 422);
}
@Test
@TestOfyAndSql
void testFewerThanTwoCharactersToMatch_rejected() {
rememberWildcardType("a*");
assertThat(generateActualJsonWithHandle("a*"))
@@ -465,7 +467,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(Optional.empty(), 422);
}
@Test
@TestOfyAndSql
void testInvalidSubtype_rejected() {
action.subtypeParam = Optional.of("Space Aliens");
assertThat(generateActualJsonWithFullName("Blinky (赤ベイ)"))
@@ -478,14 +480,14 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(Optional.empty(), 400);
}
@Test
@TestOfyAndSql
void testNameMatchContact_found() {
login("2-RegistrarTest");
runSuccessfulNameTestWithBlinky("Blinky (赤ベイ)", "rdap_contact.json");
verifyMetrics(1);
}
@Test
@TestOfyAndSql
void testNameMatchContact_found_subtypeAll() {
login("2-RegistrarTest");
action.subtypeParam = Optional.of("aLl");
@@ -493,7 +495,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(1);
}
@Test
@TestOfyAndSql
void testNameMatchContact_found_subtypeContacts() {
login("2-RegistrarTest");
action.subtypeParam = Optional.of("cONTACTS");
@@ -501,7 +503,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(1);
}
@Test
@TestOfyAndSql
void testNameMatchContact_notFound_subtypeRegistrars() {
login("2-RegistrarTest");
action.subtypeParam = Optional.of("Registrars");
@@ -509,7 +511,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchContact_found_specifyingSameRegistrar() {
login("2-RegistrarTest");
action.registrarParam = Optional.of("2-RegistrarTest");
@@ -517,7 +519,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(1);
}
@Test
@TestOfyAndSql
void testNameMatchContact_notFound_specifyingOtherRegistrar() {
login("2-RegistrarTest");
action.registrarParam = Optional.of("2-RegistrarInact");
@@ -525,7 +527,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchContact_found_asAdministrator() {
loginAsAdmin();
rememberWildcardType("Blinky (赤ベイ)");
@@ -533,27 +535,27 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(1);
}
@Test
@TestOfyAndSql
void testNameMatchContact_notFound_notLoggedIn() {
runNotFoundNameTest("Blinky (赤ベイ)");
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchContact_notFound_loggedInAsOtherRegistrar() {
login("2-Registrar");
runNotFoundNameTest("Blinky (赤ベイ)");
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchContact_found_wildcard() {
login("2-RegistrarTest");
runSuccessfulNameTestWithBlinky("Blinky*", "rdap_contact.json");
verifyMetrics(1);
}
@Test
@TestOfyAndSql
void testNameMatchContact_found_wildcardSpecifyingSameRegistrar() {
login("2-RegistrarTest");
action.registrarParam = Optional.of("2-RegistrarTest");
@@ -561,7 +563,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(1);
}
@Test
@TestOfyAndSql
void testNameMatchContact_notFound_wildcardSpecifyingOtherRegistrar() {
login("2-RegistrarTest");
action.registrarParam = Optional.of("2-RegistrarInact");
@@ -569,7 +571,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchContact_found_wildcardBoth() {
login("2-RegistrarTest");
rememberWildcardType("Blin*");
@@ -579,14 +581,14 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(2);
}
@Test
@TestOfyAndSql
void testNameMatchContact_notFound_deleted() {
login("2-RegistrarTest");
runNotFoundNameTest("Cl*");
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchContact_notFound_deletedWhenLoggedInAsOtherRegistrar() {
login("2-RegistrarTest");
action.includeDeletedParam = Optional.of(true);
@@ -594,7 +596,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchContact_notFound_deletedWhenLoggedInAsSameRegistrar() {
login("2-Registrar");
action.includeDeletedParam = Optional.of(true);
@@ -602,7 +604,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchContact_notFound_deletedWhenLoggedInAsAdmin() {
loginAsAdmin();
action.includeDeletedParam = Optional.of(true);
@@ -610,7 +612,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchRegistrar_found() {
login("2-RegistrarTest");
runSuccessfulNameTest(
@@ -618,7 +620,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchRegistrar_found_subtypeAll() {
login("2-RegistrarTest");
action.subtypeParam = Optional.of("all");
@@ -627,7 +629,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchRegistrar_found_subtypeRegistrars() {
login("2-RegistrarTest");
action.subtypeParam = Optional.of("REGISTRARS");
@@ -636,7 +638,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchRegistrar_notFound_subtypeContacts() {
login("2-RegistrarTest");
action.subtypeParam = Optional.of("contacts");
@@ -644,7 +646,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchRegistrar_found_specifyingSameRegistrar() {
action.registrarParam = Optional.of("2-Registrar");
runSuccessfulNameTest(
@@ -652,25 +654,26 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchRegistrar_notFound_specifyingDifferentRegistrar() {
action.registrarParam = Optional.of("2-RegistrarTest");
runNotFoundNameTest("Yes Virginia <script>");
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchContacts_nonTruncated() {
login("2-RegistrarTest");
createManyContactsAndRegistrars(4, 0, registrarTest);
rememberWildcardType("Entity *");
// JsonObject foo = generateActualJsonWithFullName("Entity *");
assertThat(generateActualJsonWithFullName("Entity *"))
.isEqualTo(generateExpectedJson("rdap_nontruncated_contacts.json"));
assertThat(response.getStatus()).isEqualTo(200);
verifyMetrics(4);
}
@Test
@TestOfyAndSql
void testNameMatchContacts_truncated() {
login("2-RegistrarTest");
createManyContactsAndRegistrars(5, 0, registrarTest);
@@ -683,7 +686,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(5, IncompletenessWarningType.TRUNCATED);
}
@Test
@TestOfyAndSql
void testNameMatchContacts_reallyTruncated() {
login("2-RegistrarTest");
createManyContactsAndRegistrars(9, 0, registrarTest);
@@ -697,7 +700,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(5, IncompletenessWarningType.TRUNCATED);
}
@Test
@TestOfyAndSql
void testNameMatchContacts_cursorNavigation() throws Exception {
login("2-RegistrarTest");
createManyContactsAndRegistrars(9, 0, registrarTest);
@@ -716,7 +719,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
"Entity 9"));
}
@Test
@TestOfyAndSql
void testNameMatchRegistrars_nonTruncated() {
createManyContactsAndRegistrars(0, 4, registrarTest);
rememberWildcardType("Entity *");
@@ -726,7 +729,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchRegistrars_truncated() {
createManyContactsAndRegistrars(0, 5, registrarTest);
rememberWildcardType("Entity *");
@@ -738,7 +741,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(0, IncompletenessWarningType.TRUNCATED);
}
@Test
@TestOfyAndSql
void testNameMatchRegistrars_reallyTruncated() {
createManyContactsAndRegistrars(0, 9, registrarTest);
rememberWildcardType("Entity *");
@@ -750,7 +753,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(0, IncompletenessWarningType.TRUNCATED);
}
@Test
@TestOfyAndSql
void testNameMatchRegistrars_cursorNavigation() throws Exception {
createManyContactsAndRegistrars(0, 13, registrarTest);
checkCursorNavigation(
@@ -772,7 +775,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
"Entity 9"));
}
@Test
@TestOfyAndSql
void testNameMatchRegistrars_cursorNavigationThroughAll() throws Exception {
createManyContactsAndRegistrars(0, 13, registrarTest);
action.subtypeParam = Optional.of("registrars");
@@ -798,7 +801,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
"Yes Virginia <script>"));
}
@Test
@TestOfyAndSql
void testNameMatchMix_truncated() {
login("2-RegistrarTest");
createManyContactsAndRegistrars(3, 3, registrarTest);
@@ -811,7 +814,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(3, IncompletenessWarningType.TRUNCATED);
}
@Test
@TestOfyAndSql
void testNameMatchMix_cursorNavigation() throws Exception {
login("2-RegistrarTest");
createManyContactsAndRegistrars(3, 3, registrarTest);
@@ -827,7 +830,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
"Entity 6"));
}
@Test
@TestOfyAndSql
void testNameMatchMix_subtypeContacts() {
login("2-RegistrarTest");
action.subtypeParam = Optional.of("contacts");
@@ -839,7 +842,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(4);
}
@Test
@TestOfyAndSql
void testNameMatchMix_subtypeRegistrars() {
login("2-RegistrarTest");
action.subtypeParam = Optional.of("registrars");
@@ -849,13 +852,13 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchRegistrar_notFound_inactive() {
runNotFoundNameTest("No Way");
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchRegistrar_notFound_inactiveAsDifferentRegistrar() {
action.includeDeletedParam = Optional.of(true);
login("2-Registrar");
@@ -863,7 +866,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchRegistrar_found_inactiveAsSameRegistrar() {
action.includeDeletedParam = Optional.of(true);
login("2-RegistrarInact");
@@ -871,7 +874,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchRegistrar_found_inactiveAsAdmin() {
action.includeDeletedParam = Optional.of(true);
loginAsAdmin();
@@ -879,13 +882,13 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchRegistrar_notFound_test() {
runNotFoundNameTest("Da Test Registrar");
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchRegistrar_notFound_testAsDifferentRegistrar() {
action.includeDeletedParam = Optional.of(true);
login("2-Registrar");
@@ -893,7 +896,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchRegistrar_found_testAsSameRegistrar() {
action.includeDeletedParam = Optional.of(true);
login("2-RegistrarTest");
@@ -902,7 +905,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(0);
}
@Test
@TestOfyAndSql
void testNameMatchRegistrar_found_testAsAdmin() {
action.includeDeletedParam = Optional.of(true);
loginAsAdmin();
@@ -911,14 +914,14 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(0);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_found() {
login("2-RegistrarTest");
runSuccessfulHandleTestWithBlinky("2-ROID", "rdap_contact.json");
verifyMetrics(1);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_found_subtypeAll() {
login("2-RegistrarTest");
action.subtypeParam = Optional.of("all");
@@ -926,7 +929,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(1);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_found_subtypeContacts() {
login("2-RegistrarTest");
action.subtypeParam = Optional.of("contacts");
@@ -934,7 +937,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(1);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_notFound_subtypeRegistrars() {
login("2-RegistrarTest");
action.subtypeParam = Optional.of("reGistrars");
@@ -942,28 +945,28 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_found_specifyingSameRegistrar() {
action.registrarParam = Optional.of("2-RegistrarTest");
runSuccessfulHandleTestWithBlinky("2-ROID", "rdap_contact_no_personal_data_with_remark.json");
verifyMetrics(1);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_notFound_specifyingDifferentRegistrar() {
action.registrarParam = Optional.of("2-Registrar");
runNotFoundHandleTest("2-ROID");
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_notFound_deleted() {
login("2-RegistrarTest");
runNotFoundHandleTest("6-ROID");
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_notFound_deletedWhenLoggedInAsOtherRegistrar() {
login("2-RegistrarTest");
action.includeDeletedParam = Optional.of(true);
@@ -971,7 +974,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(1);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_found_deletedWhenLoggedInAsSameRegistrar() {
login("2-Registrar");
action.includeDeletedParam = Optional.of(true);
@@ -985,7 +988,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(1);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_found_deletedWhenLoggedInAsAdmin() {
loginAsAdmin();
action.includeDeletedParam = Optional.of(true);
@@ -999,14 +1002,14 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(1);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_notFound_deletedWildcard() {
login("2-RegistrarTest");
runNotFoundHandleTest("6-ROI*");
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_notFound_deletedWildcardWhenLoggedInAsOtherRegistrar() {
login("2-RegistrarTest");
action.includeDeletedParam = Optional.of(true);
@@ -1014,7 +1017,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(1);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_found_deletedWildcardWhenLoggedInAsSameRegistrar() {
login("2-Registrar");
action.includeDeletedParam = Optional.of(true);
@@ -1028,7 +1031,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(1);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_found_deletedWildcardWhenLoggedInAsAdmin() {
loginAsAdmin();
action.includeDeletedParam = Optional.of(true);
@@ -1042,48 +1045,48 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(1);
}
@Test
@TestOfyAndSql
void testHandleMatchRegistrar_found() {
runSuccessfulHandleTest("20", "20", "Yes Virginia <script>", "rdap_registrar.json");
verifyMetrics(0);
}
@Test
@TestOfyAndSql
void testHandleMatchRegistrar_found_subtypeAll() {
action.subtypeParam = Optional.of("all");
runSuccessfulHandleTest("20", "20", "Yes Virginia <script>", "rdap_registrar.json");
verifyMetrics(0);
}
@Test
@TestOfyAndSql
void testHandleMatchRegistrar_found_subtypeRegistrars() {
action.subtypeParam = Optional.of("registrars");
runSuccessfulHandleTest("20", "20", "Yes Virginia <script>", "rdap_registrar.json");
verifyMetrics(0);
}
@Test
@TestOfyAndSql
void testHandleMatchRegistrar_notFound_subtypeContacts() {
action.subtypeParam = Optional.of("contacts");
runNotFoundHandleTest("20");
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testHandleMatchRegistrar_found_specifyingSameRegistrar() {
action.registrarParam = Optional.of("2-Registrar");
runSuccessfulHandleTest("20", "20", "Yes Virginia <script>", "rdap_registrar.json");
verifyMetrics(0);
}
@Test
@TestOfyAndSql
void testHandleMatchRegistrar_notFound_specifyingDifferentRegistrar() {
action.registrarParam = Optional.of("2-RegistrarTest");
runNotFoundHandleTest("20");
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_found_wildcardWithResultSetSizeOne() {
login("2-RegistrarTest");
action.rdapResultSetMaxSize = 1;
@@ -1091,14 +1094,14 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(1);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_found_wildcard() {
login("2-RegistrarTest");
runSuccessfulHandleTestWithBlinky("2-RO*", "rdap_contact.json");
verifyMetrics(1);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_found_wildcardSpecifyingSameRegistrar() {
action.registrarParam = Optional.of("2-RegistrarTest");
login("2-RegistrarTest");
@@ -1106,7 +1109,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(1);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_notFound_wildcardSpecifyingDifferentRegistrar() {
action.registrarParam = Optional.of("2-Registrar");
login("2-RegistrarTest");
@@ -1114,20 +1117,20 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_found_deleted() {
login("2-RegistrarTest");
runSuccessfulHandleTestWithBlinky("2-RO*", "rdap_contact.json");
verifyMetrics(1);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_notFound_wildcard() {
runNotFoundHandleTest("20*");
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testHandleMatchContact_cursorNavigationWithFullLastPage() throws Exception {
login("2-RegistrarTest");
createManyContactsAndRegistrars(12, 0, registrarTest);
@@ -1150,7 +1153,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
"Entity 12"));
}
@Test
@TestOfyAndSql
void testHandleMatchContact_cursorNavigationWithPartialLastPage() throws Exception {
login("2-RegistrarTest");
createManyContactsAndRegistrars(13, 0, registrarTest);
@@ -1174,13 +1177,13 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
"Entity 13"));
}
@Test
@TestOfyAndSql
void testHandleMatchRegistrar_notFound_wildcard() {
runNotFoundHandleTest("3test*");
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testHandleMatchRegistrars_cursorNavigationThroughAll() throws Exception {
createManyContactsAndRegistrars(0, 13, registrarTest);
action.subtypeParam = Optional.of("registrars");
@@ -1206,7 +1209,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
"Yes Virginia <script>"));
}
@Test
@TestOfyAndSql
void testHandleMatchMix_found_truncated() {
createManyContactsAndRegistrars(30, 0, registrarTest);
rememberWildcardType("00*");
@@ -1216,13 +1219,13 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(5, IncompletenessWarningType.TRUNCATED);
}
@Test
@TestOfyAndSql
void testHandleMatchRegistrar_notFound_inactive() {
runNotFoundHandleTest("21");
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testHandleMatchRegistrar_notFound_inactiveAsDifferentRegistrar() {
action.includeDeletedParam = Optional.of(true);
login("2-Registrar");
@@ -1230,7 +1233,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyErrorMetrics(0);
}
@Test
@TestOfyAndSql
void testHandleMatchRegistrar_found_inactiveAsSameRegistrar() {
action.includeDeletedParam = Optional.of(true);
login("2-RegistrarInact");
@@ -1238,7 +1241,7 @@ class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEntitySear
verifyMetrics(0);
}
@Test
@TestOfyAndSql
void testHandleMatchRegistrar_found_inactiveAsAdmin() {
action.includeDeletedParam = Optional.of(true);
loginAsAdmin();
@@ -0,0 +1,188 @@
// Copyright 2021 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.schema.replay;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import static google.registry.testing.LogsSubject.assertAboutLogs;
import com.google.common.testing.TestLogHandler;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import google.registry.config.RegistryConfig;
import google.registry.model.ImmutableObject;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.TransactionEntity;
import google.registry.testing.AppEngineExtension;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
public class ReplicateToDatastoreActionTest {
@RegisterExtension
public final AppEngineExtension appEngine =
AppEngineExtension.builder()
.withDatastoreAndCloudSql()
.withOfyTestEntities(TestEntity.class)
.withJpaUnitTestEntities(TestEntity.class)
.build();
ReplicateToDatastoreAction task = new ReplicateToDatastoreAction();
TestLogHandler logHandler;
public ReplicateToDatastoreActionTest() {}
@BeforeEach
public void setUp() {
RegistryConfig.overrideCloudSqlReplicateTransactions(true);
logHandler = new TestLogHandler();
Logger.getLogger(ReplicateToDatastoreAction.class.getCanonicalName()).addHandler(logHandler);
}
@AfterEach
public void tearDown() {
RegistryConfig.overrideCloudSqlReplicateTransactions(false);
}
@Test
public void testReplication() {
TestEntity foo = new TestEntity("foo");
TestEntity bar = new TestEntity("bar");
TestEntity baz = new TestEntity("baz");
jpaTm()
.transact(
() -> {
jpaTm().insert(foo);
jpaTm().insert(bar);
});
task.run();
assertThat(ofyTm().transact(() -> ofyTm().loadByKey(foo.key()))).isEqualTo(foo);
assertThat(ofyTm().transact(() -> ofyTm().loadByKey(bar.key()))).isEqualTo(bar);
assertThat(ofyTm().transact(() -> ofyTm().loadByKeyIfPresent(baz.key())).isPresent()).isFalse();
jpaTm()
.transact(
() -> {
jpaTm().delete(bar.key());
jpaTm().insert(baz);
});
task.run();
assertThat(ofyTm().transact(() -> ofyTm().loadByKeyIfPresent(bar.key()).isPresent())).isFalse();
assertThat(ofyTm().transact(() -> ofyTm().loadByKey(baz.key()))).isEqualTo(baz);
}
@Test
public void testReplayFromLastTxn() {
TestEntity foo = new TestEntity("foo");
TestEntity bar = new TestEntity("bar");
// Write a transaction containing "foo".
jpaTm().transact(() -> jpaTm().insert(foo));
task.run();
// Verify that it propagated to datastore, then remove "foo" directly from datastore.
assertThat(ofyTm().transact(() -> ofyTm().loadByKey(foo.key()))).isEqualTo(foo);
ofyTm().transact(() -> ofyTm().delete(foo.key()));
// Write "bar"
jpaTm().transact(() -> jpaTm().insert(bar));
task.run();
// If we replayed only the most recent transaction, we should have "bar" but not "foo".
assertThat(ofyTm().transact(() -> ofyTm().loadByKey(bar.key()))).isEqualTo(bar);
assertThat(ofyTm().transact(() -> ofyTm().loadByKeyIfPresent(foo.key()).isPresent())).isFalse();
}
@Test
public void testUnintentionalConcurrency() {
TestEntity foo = new TestEntity("foo");
TestEntity bar = new TestEntity("bar");
// Write a transaction and run just the batch fetch.
jpaTm().transact(() -> jpaTm().insert(foo));
List<TransactionEntity> txns1 = task.getTransactionBatch();
assertThat(txns1).hasSize(1);
// Write a second transaction and do another batch fetch.
jpaTm().transact(() -> jpaTm().insert(bar));
List<TransactionEntity> txns2 = task.getTransactionBatch();
assertThat(txns2).hasSize(2);
// Apply the first batch.
assertThat(task.applyTransaction(txns1.get(0))).isFalse();
// Remove the foo record so we can ensure that this transaction doesn't get doublle-played.
ofyTm().transact(() -> ofyTm().delete(foo.key()));
// Apply the second batch.
for (TransactionEntity txn : txns2) {
assertThat(task.applyTransaction(txn)).isFalse();
}
// Verify that the first transaction didn't get replayed but the second one did.
assertThat(ofyTm().transact(() -> ofyTm().loadByKeyIfPresent(foo.key()).isPresent())).isFalse();
assertThat(ofyTm().transact(() -> ofyTm().loadByKey(bar.key()))).isEqualTo(bar);
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(
Level.WARNING, "Ignoring transaction 1, which appears to have already been applied.");
}
@Test
public void testMissingTransactions() {
// Write a transaction (should have a transaction id of 1).
TestEntity foo = new TestEntity("foo");
jpaTm().transact(() -> jpaTm().insert(foo));
// Force the last transaction id back to -1 so that we look for transaction 0.
ofyTm().transact(() -> ofyTm().insert(new LastSqlTransaction(-1)));
List<TransactionEntity> txns = task.getTransactionBatch();
assertThat(txns).hasSize(1);
assertThat(task.applyTransaction(txns.get(0))).isTrue();
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(
Level.SEVERE,
"Missing transaction: last transaction id = -1, next available transaction = 1");
}
@Entity(name = "ReplicationTestEntity")
@javax.persistence.Entity(name = "TestEntity")
private static class TestEntity extends ImmutableObject {
@Id @javax.persistence.Id private String name;
private TestEntity() {}
private TestEntity(String name) {
this.name = name;
}
public VKey<TestEntity> key() {
return VKey.create(TestEntity.class, name, Key.create(this));
}
}
}
@@ -17,10 +17,12 @@ package google.registry.testing;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.io.Files.asCharSink;
import static com.google.common.truth.Truth.assertWithMessage;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.persistSimpleResources;
import static google.registry.testing.DualDatabaseTestInvocationContextProvider.injectTmForDualDatabaseTest;
import static google.registry.testing.DualDatabaseTestInvocationContextProvider.restoreTmAfterDualDatabaseTest;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import static google.registry.util.ResourceUtils.readResourceUtf8;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -40,10 +42,16 @@ import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.ObjectifyFilter;
import google.registry.model.common.DatabaseTransitionSchedule;
import google.registry.model.common.DatabaseTransitionSchedule.PrimaryDatabase;
import google.registry.model.common.DatabaseTransitionSchedule.PrimaryDatabaseTransition;
import google.registry.model.common.DatabaseTransitionSchedule.TransitionId;
import google.registry.model.common.TimedTransitionProperty;
import google.registry.model.ofy.ObjectifyService;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.State;
@@ -390,6 +398,14 @@ public final class AppEngineExtension implements BeforeEachCallback, AfterEachCa
loadInitialData();
}
} else {
// If we're using SQL, set replayed entities to use SQL
DatabaseTransitionSchedule schedule =
DatabaseTransitionSchedule.create(
TransitionId.REPLAYED_ENTITIES,
TimedTransitionProperty.fromValueMap(
ImmutableSortedMap.of(START_OF_TIME, PrimaryDatabase.CLOUD_SQL),
PrimaryDatabaseTransition.class));
tm().transactNew(() -> ofy().saveWithoutBackup().entity(schedule).now());
if (withCloudSql && !withJpaUnitTest && !withoutCannedData) {
loadInitialData();
}
@@ -326,8 +326,7 @@ public class DatabaseHelper {
* Returns a persisted domain that is the passed-in domain modified to be deleted at the specified
* time.
*/
public static DomainBase persistDomainAsDeleted(
DomainBase domain, DateTime deletionTime) {
public static DomainBase persistDomainAsDeleted(DomainBase domain, DateTime deletionTime) {
return persistResource(domain.asBuilder().setDeletionTime(deletionTime).build());
}
@@ -347,7 +346,7 @@ public class DatabaseHelper {
}
public static ReservedList persistReservedList(
String listName, boolean shouldPublish, String... lines) {
String listName, boolean shouldPublish, String... lines) {
ReservedList reservedList =
new ReservedList.Builder()
.setName(listName)
@@ -512,10 +511,7 @@ public class DatabaseHelper {
}
public static BillingEvent.OneTime createBillingEventForTransfer(
DomainBase domain,
HistoryEntry historyEntry,
DateTime costLookupTime,
DateTime eventTime) {
DomainBase domain, HistoryEntry historyEntry, DateTime costLookupTime, DateTime eventTime) {
return new BillingEvent.OneTime.Builder()
.setReason(Reason.TRANSFER)
.setTargetId(domain.getDomainName())
@@ -530,10 +526,7 @@ public class DatabaseHelper {
}
public static ContactResource persistContactWithPendingTransfer(
ContactResource contact,
DateTime requestTime,
DateTime expirationTime,
DateTime now) {
ContactResource contact, DateTime requestTime, DateTime expirationTime, DateTime now) {
HistoryEntry historyEntryContactTransfer =
persistResource(
new HistoryEntry.Builder()
@@ -587,26 +580,29 @@ public class DatabaseHelper {
String domainName = String.format("%s.%s", label, tld);
String repoId = generateNewDomainRoid(tld);
DomainBase domain =
new DomainBase.Builder()
.setRepoId(repoId)
.setDomainName(domainName)
.setPersistedCurrentSponsorClientId("TheRegistrar")
.setCreationClientId("TheRegistrar")
.setCreationTimeForTest(creationTime)
.setRegistrationExpirationTime(expirationTime)
.setRegistrant(contact.createVKey())
.setContacts(
ImmutableSet.of(
DesignatedContact.create(Type.ADMIN, contact.createVKey()),
DesignatedContact.create(Type.TECH, contact.createVKey())))
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("fooBAR")))
.addGracePeriod(
GracePeriod.create(GracePeriodStatus.ADD, repoId, now.plusDays(10), "foo", null))
.build();
HistoryEntry historyEntryDomainCreate =
persistResource(
new HistoryEntry.Builder()
new DomainBase.Builder()
.setRepoId(repoId)
.setDomainName(domainName)
.setPersistedCurrentSponsorClientId("TheRegistrar")
.setCreationClientId("TheRegistrar")
.setCreationTimeForTest(creationTime)
.setRegistrationExpirationTime(expirationTime)
.setRegistrant(contact.createVKey())
.setContacts(
ImmutableSet.of(
DesignatedContact.create(Type.ADMIN, contact.createVKey()),
DesignatedContact.create(Type.TECH, contact.createVKey())))
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("fooBAR")))
.addGracePeriod(
GracePeriod.create(
GracePeriodStatus.ADD, repoId, now.plusDays(10), "TheRegistrar", null))
.build());
DomainHistory historyEntryDomainCreate =
persistResource(
new DomainHistory.Builder()
.setType(HistoryEntry.Type.DOMAIN_CREATE)
.setModificationTime(now)
.setParent(domain)
.build());
BillingEvent.Recurring autorenewEvent =
@@ -643,18 +639,17 @@ public class DatabaseHelper {
DateTime requestTime,
DateTime expirationTime,
DateTime extendedRegistrationExpirationTime) {
HistoryEntry historyEntryDomainTransfer =
DomainHistory historyEntryDomainTransfer =
persistResource(
new HistoryEntry.Builder()
new DomainHistory.Builder()
.setType(HistoryEntry.Type.DOMAIN_TRANSFER_REQUEST)
.setModificationTime(tm().transact(() -> tm().getTransactionTime()))
.setParent(domain)
.build());
BillingEvent.OneTime transferBillingEvent = persistResource(createBillingEventForTransfer(
domain,
historyEntryDomainTransfer,
requestTime,
expirationTime));
BillingEvent.OneTime transferBillingEvent =
persistResource(
createBillingEventForTransfer(
domain, historyEntryDomainTransfer, requestTime, expirationTime));
BillingEvent.Recurring gainingClientAutorenewEvent =
persistResource(
new BillingEvent.Recurring.Builder()
@@ -678,18 +673,18 @@ public class DatabaseHelper {
.build());
// Modify the existing autorenew event to reflect the pending transfer.
persistResource(
tm().loadByKey(domain.getAutorenewBillingEvent())
.asBuilder()
.setRecurrenceEndTime(expirationTime)
.build());
transactIfJpaTm(
() ->
tm().loadByKey(domain.getAutorenewBillingEvent())
.asBuilder()
.setRecurrenceEndTime(expirationTime)
.build()));
// Update the end time of the existing autorenew poll message. We must delete it if it has no
// events left in it.
PollMessage.Autorenew autorenewPollMessage = tm().loadByKey(domain.getAutorenewPollMessage());
PollMessage.Autorenew autorenewPollMessage =
transactIfJpaTm(() -> tm().loadByKey(domain.getAutorenewPollMessage()));
if (autorenewPollMessage.getEventTime().isBefore(expirationTime)) {
persistResource(
autorenewPollMessage.asBuilder()
.setAutorenewEndTime(expirationTime)
.build());
persistResource(autorenewPollMessage.asBuilder().setAutorenewEndTime(expirationTime).build());
} else {
deleteResource(autorenewPollMessage);
}
@@ -928,11 +923,8 @@ public class DatabaseHelper {
}
public static PollMessage getOnlyPollMessage(
String clientId,
DateTime now,
Class<? extends PollMessage> subType) {
return getPollMessages(clientId, now)
.stream()
String clientId, DateTime now, Class<? extends PollMessage> subType) {
return getPollMessages(clientId, now).stream()
.filter(subType::isInstance)
.map(subType::cast)
.collect(onlyElement());
@@ -957,10 +949,7 @@ public class DatabaseHelper {
return createDomainRepoId(ObjectifyService.allocateId(), tld);
}
/**
* Returns a newly allocated, globally unique contact/host repoId of the format
* HEX_TLD-ROID.
*/
/** Returns a newly allocated, globally unique contact/host repoId of the format HEX_TLD-ROID. */
public static String generateNewContactHostRoid() {
return createRepoId(ObjectifyService.allocateId(), getContactAndHostRoidSuffix());
}
@@ -1131,8 +1120,7 @@ public class DatabaseHelper {
*/
public static ImmutableList<HistoryEntry> getHistoryEntriesOfType(
EppResource resource, final HistoryEntry.Type type) {
return getHistoryEntries(resource)
.stream()
return getHistoryEntries(resource).stream()
.filter(entry -> entry.getType() == type)
.collect(toImmutableList());
}
@@ -1143,7 +1131,7 @@ public class DatabaseHelper {
*/
public static HistoryEntry getOnlyHistoryEntryOfType(
EppResource resource, final HistoryEntry.Type type) {
List<HistoryEntry> historyEntries = getHistoryEntriesOfType(resource, type);
List<HistoryEntry> historyEntries = getHistoryEntriesOfType(resource, type);
assertThat(historyEntries).hasSize(1);
return historyEntries.get(0);
}
@@ -1151,13 +1139,16 @@ public class DatabaseHelper {
private static HistoryEntry.Type getHistoryEntryType(EppResource resource) {
if (resource instanceof ContactResource) {
return resource.getRepoId() != null
? HistoryEntry.Type.CONTACT_CREATE : HistoryEntry.Type.CONTACT_UPDATE;
? HistoryEntry.Type.CONTACT_CREATE
: HistoryEntry.Type.CONTACT_UPDATE;
} else if (resource instanceof HostResource) {
return resource.getRepoId() != null
? HistoryEntry.Type.HOST_CREATE : HistoryEntry.Type.HOST_UPDATE;
? HistoryEntry.Type.HOST_CREATE
: HistoryEntry.Type.HOST_UPDATE;
} else if (resource instanceof DomainBase) {
return resource.getRepoId() != null
? HistoryEntry.Type.DOMAIN_CREATE : HistoryEntry.Type.DOMAIN_UPDATE;
? HistoryEntry.Type.DOMAIN_CREATE
: HistoryEntry.Type.DOMAIN_UPDATE;
} else {
throw new AssertionError();
}
@@ -1215,7 +1206,7 @@ public class DatabaseHelper {
tm().clearSessionCache();
}
/** Force the create and update timestamps to get written into the resource. **/
/** Force the create and update timestamps to get written into the resource. */
public static <R> R cloneAndSetAutoTimestamps(final R resource) {
R result;
if (tm().isOfy()) {
@@ -0,0 +1,71 @@
// Copyright 2021 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.tools;
import static google.registry.testing.DatabaseHelper.createTld;
import static org.junit.Assert.assertThrows;
import com.beust.jcommander.ParameterException;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import org.junit.jupiter.api.BeforeEach;
@DualDatabaseTest
public class GetPremiumListCommandTest extends CommandTestCase<GetPremiumListCommand> {
private static final String BASE_LIST_CONTENTS =
"tld:\n"
+ "aluminum,USD 11.00\n"
+ "brass,USD 20.00\n"
+ "copper,USD 15.00\n"
+ "diamond,USD 1000000.00\n"
+ "gold,USD 24317.00\n"
+ "iridium,USD 13117.00\n"
+ "palladium,USD 877.00\n"
+ "platinum,USD 87741.00\n"
+ "rhodium,USD 88415.00\n"
+ "rich,USD 100.00\n"
+ "richer,USD 1000.00\n"
+ "silver,USD 588.00\n";
@BeforeEach
void beforeEach() {
createTld("tld");
}
@TestOfyAndSql
void testSuccess_list() throws Exception {
runCommand("tld");
assertStdoutIs(BASE_LIST_CONTENTS);
}
@TestOfyAndSql
void testSuccess_onlyOneExists() throws Exception {
runCommand("tld", "nonexistent");
assertStdoutIs(BASE_LIST_CONTENTS + "No list found with name nonexistent.\n");
}
@TestOfyAndSql
void testFailure_nonexistent() throws Exception {
runCommand("nonexistent", "othernonexistent");
assertStdoutIs(
"No list found with name nonexistent.\nNo list found with name othernonexistent.\n");
}
@TestOfyAndSql
void testFailure_noArgs() {
assertThrows(ParameterException.class, this::runCommand);
}
}
@@ -96,7 +96,7 @@ public class WhoisActionTest {
whoisAction.input = new StringReader(input);
whoisAction.response = response;
whoisAction.whoisReader =
new WhoisReader(new WhoisCommandFactory(), "Please contact registrar");
new WhoisReader(WhoisCommandFactory.createCached(), "Please contact registrar");
whoisAction.whoisMetrics = new WhoisMetrics();
whoisAction.metricBuilder = WhoisMetric.builderForRequest(clock);
whoisAction.disclaimer =
@@ -0,0 +1,240 @@
// Copyright 2021 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.whois;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.newDomainBase;
import static google.registry.testing.DatabaseHelper.newHostResource;
import static google.registry.testing.DatabaseHelper.newRegistry;
import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InternetDomainName;
import google.registry.config.RegistryConfig;
import google.registry.model.domain.DomainBase;
import google.registry.model.host.HostResource;
import google.registry.model.registrar.Registrar;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.FakeClock;
import google.registry.testing.TestCacheExtension;
import java.net.InetAddress;
import org.joda.time.Duration;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
class WhoisCommandFactoryTest {
FakeClock clock = new FakeClock();
@RegisterExtension
final AppEngineExtension appEngine =
AppEngineExtension.builder().withDatastoreAndCloudSql().withClock(clock).build();
@RegisterExtension
final TestCacheExtension testCacheExtension =
new TestCacheExtension.Builder().withEppResourceCache(Duration.millis(1000000000)).build();
WhoisCommandFactory noncachedFactory = WhoisCommandFactory.createNonCached();
WhoisCommandFactory cachedFactory = WhoisCommandFactory.createCached();
DomainBase domain;
HostResource host;
Registrar otherRegistrar;
int origSingletonCacheRefreshSeconds;
@BeforeEach
void setUp() throws Exception {
persistResource(newRegistry("tld", "TLD"));
host =
newHostResource("ns.example.tld")
.asBuilder()
.setInetAddresses(ImmutableSet.of(InetAddress.getByName("1.2.3.4")))
.build();
persistResource(host);
domain = newDomainBase("example.tld", host);
persistResource(domain);
otherRegistrar = persistNewRegistrar("OtherRegistrar");
otherRegistrar =
persistResource(
otherRegistrar
.asBuilder()
.setState(Registrar.State.ACTIVE)
.setPhoneNumber("+1.2223334444")
.build());
// In addition to the TestCacheExtension, we have to set a long singleton cache timeout.
RegistryConfig.CONFIG_SETTINGS.get().caching.singletonCacheRefreshSeconds = 1000000;
}
@AfterEach
void tearDown() {
// Restore the singleton cache timeout. For some reason, this doesn't work if we store the
// original value in an instance variable (I suspect there may be some overlap in test
// execution) so just restore to zero.
RegistryConfig.CONFIG_SETTINGS.get().caching.singletonCacheRefreshSeconds = 0;
}
@Test
void testNonCached_NameserverLookupByHostCommand() throws Exception {
WhoisResponse response =
noncachedFactory
.nameserverLookupByHost(InternetDomainName.from("ns.example.tld"))
.executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Registrar: The Registrar");
// Note that we can't use persistResource() for these as that clears the cache.
tm().transact(
() ->
tm().put(
host.asBuilder()
.setPersistedCurrentSponsorClientId("OtherRegistrar")
.build()));
response =
noncachedFactory
.nameserverLookupByHost(InternetDomainName.from("ns.example.tld"))
.executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Registrar: OtherRegistrar name");
}
@Test
void testCached_NameserverLookupByHostCommand() throws Exception {
WhoisResponse response =
cachedFactory
.nameserverLookupByHost(InternetDomainName.from("ns.example.tld"))
.executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Registrar: The Registrar");
tm().transact(
() ->
tm().put(
host.asBuilder()
.setPersistedCurrentSponsorClientId("OtherRegistrar")
.build()));
response =
cachedFactory
.nameserverLookupByHost(InternetDomainName.from("ns.example.tld"))
.executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Registrar: The Registrar");
}
@Test
void testNonCached_DomainLookupCommand() throws Exception {
WhoisResponse response =
noncachedFactory
.domainLookup(InternetDomainName.from("example.tld"), true, "REDACTED")
.executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Registrar: The Registrar");
tm().transact(
() ->
tm().put(
domain
.asBuilder()
.setPersistedCurrentSponsorClientId("OtherRegistrar")
.build()));
response =
noncachedFactory
.domainLookup(InternetDomainName.from("example.tld"), true, "REDACTED")
.executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Registrar: OtherRegistrar name");
}
@Test
void testCached_DomainLookupCommand() throws Exception {
WhoisResponse response =
cachedFactory
.domainLookup(InternetDomainName.from("example.tld"), true, "REDACTED")
.executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Registrar: The Registrar");
tm().transact(
() ->
tm().put(
domain
.asBuilder()
.setPersistedCurrentSponsorClientId("OtherRegistrar")
.build()));
response =
cachedFactory
.domainLookup(InternetDomainName.from("example.tld"), true, "REDACTED")
.executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Registrar: The Registrar");
}
@Test
void testNonCached_RegistrarLookupCommand() throws Exception {
WhoisResponse response =
noncachedFactory.registrarLookup("OtherRegistrar").executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Phone Number: +1.2223334444");
tm().transact(
() -> tm().put(otherRegistrar.asBuilder().setPhoneNumber("+1.2345677890").build()));
response = noncachedFactory.registrarLookup("OtherRegistrar").executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Phone Number: +1.2345677890");
}
@Test
void testCached_RegistrarLookupCommand() throws Exception {
WhoisResponse response =
cachedFactory.registrarLookup("OtherRegistrar").executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Phone Number: +1.2223334444");
tm().transact(
() -> tm().put(otherRegistrar.asBuilder().setPhoneNumber("+1.2345677890").build()));
response = cachedFactory.registrarLookup("OtherRegistrar").executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Phone Number: +1.2223334444");
}
@Test
void testNonCached_NameserverLookupByIpCommand() throws Exception {
// Note that this lookup currently doesn't cache the hosts, so there's no point in testing the
// "cached" case. This test is here so that it will fail if anyone adds caching.
WhoisResponse response =
noncachedFactory
.nameserverLookupByIp(InetAddress.getByName("1.2.3.4"))
.executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Registrar: The Registrar");
tm().transact(
() ->
tm().put(
host.asBuilder()
.setPersistedCurrentSponsorClientId("OtherRegistrar")
.build()));
response =
noncachedFactory
.nameserverLookupByIp(InetAddress.getByName("1.2.3.4"))
.executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Registrar: OtherRegistrar");
}
}
@@ -78,7 +78,7 @@ class WhoisHttpActionTest {
whoisAction.requestPath = WhoisHttpAction.PATH + pathInfo;
whoisAction.response = response;
whoisAction.whoisReader =
new WhoisReader(new WhoisCommandFactory(), "Please contact registrar");
new WhoisReader(WhoisCommandFactory.createCached(), "Please contact registrar");
whoisAction.whoisMetrics = new WhoisMetrics();
whoisAction.metricBuilder = WhoisMetric.builderForRequest(clock);
whoisAction.disclaimer =
@@ -48,7 +48,7 @@ class WhoisReaderTest {
@SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
<T> T readCommand(String commandStr) throws Exception {
return (T)
new WhoisReader(new WhoisCommandFactory(), "Please contact registrar")
new WhoisReader(WhoisCommandFactory.createCached(), "Please contact registrar")
.readCommand(new StringReader(commandStr), false, clock.nowUtc());
}
@@ -13,6 +13,7 @@ HistoryEntry
HostResource
KmsSecret
KmsSecretRevision
LastSqlTransaction
Modification
OneTime
PollMessage
@@ -28,7 +28,7 @@
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">13.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-03T10:00:00.000Z">50.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-03T10:00:00.002Z">50.00</fee:fee>
<fee:date>2010-01-02T13:22:21Z</fee:date>
<fee:notAfter>2010-01-03T10:00:00.000Z</fee:notAfter>
</fee:command>
@@ -40,7 +40,7 @@
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">13.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-03T10:00:00.000Z">50.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-03T10:00:00.002Z">50.00</fee:fee>
<fee:date>2010-01-02T13:22:21Z</fee:date>
<fee:notAfter>2010-01-03T10:00:00.000Z</fee:notAfter>
</fee:command>
@@ -52,7 +52,7 @@
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">13.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-03T10:00:00.000Z">50.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-03T10:00:00.002Z">50.00</fee:fee>
<fee:date>2010-01-02T13:22:21Z</fee:date>
<fee:notAfter>2010-01-03T10:00:00.000Z</fee:notAfter>
</fee:command>
@@ -25,7 +25,7 @@
<fee:command>create</fee:command>
<fee:period unit="y">1</fee:period>
<fee:fee description="create">13.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.000Z">100.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.002Z">100.00</fee:fee>
</fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>example3.tld</fee:name>
@@ -33,7 +33,7 @@
<fee:command>create</fee:command>
<fee:period unit="y">2</fee:period>
<fee:fee description="create">26.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.000Z">100.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.002Z">100.00</fee:fee>
</fee:cd>
</fee:chkData>
</extension>
@@ -28,7 +28,7 @@
<fee:currency>USD</fee:currency>
<fee:period unit="y">1</fee:period>
<fee:fee description="create">13.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.000Z">100.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.002Z">100.00</fee:fee>
</fee:cd>
<fee:cd avail="1">
<fee:object>
@@ -38,7 +38,7 @@
<fee:currency>USD</fee:currency>
<fee:period unit="y">1</fee:period>
<fee:fee description="create">13.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.000Z">100.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.002Z">100.00</fee:fee>
</fee:cd>
<fee:cd avail="1">
<fee:object>
@@ -48,7 +48,7 @@
<fee:currency>USD</fee:currency>
<fee:period unit="y">1</fee:period>
<fee:fee description="create">13.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.000Z">100.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.002Z">100.00</fee:fee>
</fee:cd>
</fee:chkData>
</extension>
@@ -28,7 +28,7 @@
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">13.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.000Z">100.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.002Z">100.00</fee:fee>
<fee:notAfter>2010-01-02T10:00:00.000Z</fee:notAfter>
</fee:command>
</fee:cd>
@@ -39,7 +39,7 @@
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">13.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.000Z">100.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.002Z">100.00</fee:fee>
<fee:notAfter>2010-01-02T10:00:00.000Z</fee:notAfter>
</fee:command>
</fee:cd>
@@ -50,7 +50,7 @@
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">13.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.000Z">100.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.002Z">100.00</fee:fee>
<fee:notAfter>2010-01-02T10:00:00.000Z</fee:notAfter>
</fee:command>
</fee:cd>
@@ -19,7 +19,7 @@
<fee:command>create</fee:command>
<fee:period unit="y">1</fee:period>
<fee:fee description="create">100.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.000Z">100.00
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.002Z">100.00
</fee:fee>
<fee:class>premium</fee:class>
</fee:cd>
@@ -916,3 +916,7 @@ class google.registry.persistence.DomainHistoryVKey {
java.lang.Long historyRevisionId;
java.lang.String repoId;
}
class google.registry.schema.replay.LastSqlTransaction {
@Id long id;
long transactionId;
}
@@ -0,0 +1 @@
mock-maker-inline
+15 -8
View File
@@ -20,8 +20,6 @@ plugins {
ext {
Set restrictedDbEnv =
[ 'sandbox', 'production' ].asUnmodifiable()
Set allDbEnv =
[ 'alpha', 'crash' ].plus(restrictedDbEnv).asUnmodifiable()
def dbServerProperty = 'dbServer'
def dbNameProperty = 'dbName'
@@ -57,12 +55,12 @@ ext {
}
getJdbcAccessInfo = {
if (allDbEnv.contains(dbServer)) {
if (rootProject.projects.keySet().contains(dbServer)) {
return getSocketFactoryAccessInfo(dbServer)
} else if (!dbServer.isEmpty()) {
return getAccessInfoByHostPort(dbServer)
} else {
// Not connecting to a database. Return a dummy object for Flyway config.
// Not running flyway tasks. Return a dummy object for Flyway config.
return [ url: '', user: '', password: '' ]
}
}
@@ -74,10 +72,8 @@ ext {
// production. The role parameter may be superuser. (More roles will be added
// later).
getCloudSqlCredential = { env, role ->
def devProject = project.hasProperty('devProject')
? project.getProperty('devProject') : rootProject.devProject
def gcpProject = project.hasProperty('gcpProject')
? project.getProperty('gcpProject') : rootProject.gcpProject
def devProject = rootProject.devProject
def gcpProject = rootProject.projects[env]
def keyProject = env in restrictedDbEnv? devProject : gcpProject
def command =
"""gsutil cp \
@@ -137,6 +133,17 @@ publishing {
}
}
// Adds flyway tasks such as: flywayInfo, flywayValidate, flywayMigrate (
// deploying the schema in local repository), and flywayClean (dropping all data
// in the database). The latter two commands are disallowed in environments
// listed in ext.restrictedDbEnv.
//
// Examples:
// Get info in alpha: nom_build :db:flywayInfo --dbServer=alpha
// Deploy schema to a local test instance and override the database name:
// nom_build :db:flywayMigrate --dbServer=localhost:5432 --dbName=not-default \
// --dbUser=... --dbPassword=...
flyway {
def accessInfo = project.ext.getJdbcAccessInfo()
+3 -2
View File
@@ -73,6 +73,7 @@ ext {
'com.google.http-client:google-http-client-appengine:1.34.1',
'com.google.http-client:google-http-client-jackson2:1.34.1',
'com.google.http-client:google-http-client:1.34.1',
'com.google.inject:guice:5.0.1',
'com.google.javascript:closure-compiler:v20190301',
'com.google.monitoring-client:contrib:1.0.7',
'com.google.monitoring-client:metrics:1.0.7',
@@ -157,8 +158,8 @@ ext {
'org.joda:joda-money:1.0.1',
'org.json:json:20160810',
'org.jsoup:jsoup:1.13.1',
'org.mockito:mockito-core:3.3.3',
'org.mockito:mockito-junit-jupiter:3.3.3',
'org.mockito:mockito-core:3.7.7',
'org.mockito:mockito-junit-jupiter:3.7.7',
'org.mortbay.jetty:jetty:6.1.26',
'org.postgresql:postgresql:42.2.18',
'org.seleniumhq.selenium:selenium-api:3.141.59',
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+3
View File
@@ -28,6 +28,7 @@ dependencies {
compile deps['org.bouncycastle:bcpkix-jdk15on']
compile deps['org.bouncycastle:bcprov-jdk15on']
compile project(':util')
compile project(':common')
runtime deps['com.google.flogger:flogger-system-backend']
runtime deps['io.netty:netty-tcnative-boringssl-static']
@@ -38,9 +39,11 @@ dependencies {
testCompile deps['org.junit.jupiter:junit-jupiter-params']
testCompile deps['org.bouncycastle:bcpkix-jdk15on']
testCompile deps['org.bouncycastle:bcprov-jdk15on']
testCompile project(path: ':common', configuration: 'testing')
annotationProcessor deps['com.google.dagger:dagger-compiler']
testAnnotationProcessor deps['com.google.dagger:dagger-compiler']
compile 'joda-time:joda-time:2.9.2'
}
// Make testing artifacts available to be depended up on by other projects.
@@ -19,6 +19,7 @@ import static google.registry.util.X509Utils.getCertificateHash;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import google.registry.util.Clock;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler.Sharable;
@@ -29,6 +30,7 @@ import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.SupportedCipherSuiteFilter;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.Future;
@@ -41,6 +43,7 @@ import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.util.function.Supplier;
import javax.net.ssl.SSLSession;
import org.joda.time.DateTime;
/**
* Adds a server side SSL handler to the channel pipeline.
@@ -66,6 +69,29 @@ public class SslServerInitializer<C extends Channel> extends ChannelInitializer<
public static final AttributeKey<Promise<X509Certificate>> CLIENT_CERTIFICATE_PROMISE_KEY =
AttributeKey.valueOf("CLIENT_CERTIFICATE_PROMISE_KEY");
/**
* The list of cipher suites that are currently acceptable to create a successful handshake.
*
* <p>This list includes all of the current TLS1.3 ciphers and a collection of TLS1.2 ciphers with
* no known security vulnerabilities. Note that OpenSSL uses a separate nomenclature for the
* ciphers internally but the IANA names listed here will be transparently translated by the
* OpenSSL provider (if used), so there is no need to include the OpenSSL name variants here. More
* information about these cipher suites and their OpenSSL names can be found at ciphersuite.info.
*/
private static final ImmutableList ALLOWED_TLS_CIPHERS =
ImmutableList.of(
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_AES_128_CCM_SHA256",
"TLS_AES_128_CCM_8_SHA256");
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final boolean requireClientCert;
// TODO(jianglai): Always validate client certs (if required).
@@ -76,13 +102,19 @@ public class SslServerInitializer<C extends Channel> extends ChannelInitializer<
private final Supplier<PrivateKey> privateKeySupplier;
private final Supplier<ImmutableList<X509Certificate>> certificatesSupplier;
private final ImmutableList<String> supportedSslVersions;
// TODO(sarahbot): Remove this variable and its check after enforcement start date has passed.
private final ImmutableList<String> oldSupportedSslVersions;
private final DateTime enforcementStartTime;
private final Clock clock;
public SslServerInitializer(
boolean requireClientCert,
boolean validateClientCert,
SslProvider sslProvider,
Supplier<PrivateKey> privateKeySupplier,
Supplier<ImmutableList<X509Certificate>> certificatesSupplier) {
Supplier<ImmutableList<X509Certificate>> certificatesSupplier,
DateTime enforcementStartTime,
Clock clock) {
logger.atInfo().log("Server SSL Provider: %s", sslProvider);
checkArgument(
requireClientCert || !validateClientCert,
@@ -94,10 +126,16 @@ public class SslServerInitializer<C extends Channel> extends ChannelInitializer<
this.certificatesSupplier = certificatesSupplier;
this.supportedSslVersions =
sslProvider == SslProvider.OPENSSL
? ImmutableList.of("TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1")
// JDK support for TLS 1.3 won't be available until 2020-07-14 at the earliest.
? ImmutableList.of("TLSv1.3", "TLSv1.2")
// JDK support for TLS 1.3 won't be available until 2021-04-20 at the earliest.
// See: https://java.com/en/jre-jdk-cryptoroadmap.html
: ImmutableList.of("TLSv1.2");
this.oldSupportedSslVersions =
sslProvider == SslProvider.OPENSSL
? ImmutableList.of("TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1")
: ImmutableList.of("TLSv1.2", "TLSv1.1", "TLSv1");
this.enforcementStartTime = enforcementStartTime;
this.clock = clock;
}
@Override
@@ -109,8 +147,15 @@ public class SslServerInitializer<C extends Channel> extends ChannelInitializer<
.sslProvider(sslProvider)
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.clientAuth(requireClientCert ? ClientAuth.REQUIRE : ClientAuth.NONE)
.protocols(supportedSslVersions)
.protocols(
enforcementStartTime.isBefore(clock.nowUtc())
? supportedSslVersions
: oldSupportedSslVersions)
.ciphers(
enforcementStartTime.isBefore(clock.nowUtc()) ? ALLOWED_TLS_CIPHERS : null,
SupportedCipherSuiteFilter.INSTANCE)
.build();
logger.atInfo().log("Available Cipher Suites: %s", sslContext.cipherSuites());
SslHandler sslHandler = sslContext.newHandler(channel.alloc());
if (requireClientCert) {
@@ -23,6 +23,7 @@ import static google.registry.networking.handler.SslServerInitializer.CLIENT_CER
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import google.registry.testing.FakeClock;
import google.registry.util.SelfSignedCaCertificate;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
@@ -42,13 +43,16 @@ import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.Instant;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Stream;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
import org.joda.time.DateTime;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@@ -97,19 +101,29 @@ class SslServerInitializerTest {
validateClientCert,
sslProvider,
Suppliers.ofInstance(privateKey),
Suppliers.ofInstance(ImmutableList.copyOf(certificates)));
Suppliers.ofInstance(ImmutableList.copyOf(certificates)),
DateTime.parse("2021-04-01T16:00:00Z"),
new FakeClock(DateTime.parse("2021-05-01T16:00:00Z")));
}
private ChannelHandler getClientHandler(
SslProvider sslProvider,
X509Certificate trustedCertificate,
PrivateKey privateKey,
X509Certificate certificate) {
X509Certificate certificate,
String protocol,
List<String> cipher) {
return new ChannelInitializer<LocalChannel>() {
@Override
protected void initChannel(LocalChannel ch) throws Exception {
SslContextBuilder sslContextBuilder =
SslContextBuilder.forClient().trustManager(trustedCertificate).sslProvider(sslProvider);
SslContextBuilder.forClient()
.trustManager(trustedCertificate)
.sslProvider(sslProvider)
.ciphers(cipher);
if (protocol != null) {
sslContextBuilder.protocols(protocol);
}
if (privateKey != null && certificate != null) {
sslContextBuilder.keyManager(privateKey, certificate);
}
@@ -127,6 +141,14 @@ class SslServerInitializerTest {
};
}
private ChannelHandler getClientHandler(
SslProvider sslProvider,
X509Certificate trustedCertificate,
PrivateKey privateKey,
X509Certificate certificate) {
return getClientHandler(sslProvider, trustedCertificate, privateKey, certificate, null, null);
}
@ParameterizedTest
@MethodSource("provideTestCombinations")
void testSuccess_swappedInitializerWithSslHandler(SslProvider sslProvider) throws Exception {
@@ -137,7 +159,9 @@ class SslServerInitializerTest {
false,
sslProvider,
Suppliers.ofInstance(ssc.key()),
Suppliers.ofInstance(ImmutableList.of(ssc.cert())));
Suppliers.ofInstance(ImmutableList.of(ssc.cert())),
DateTime.parse("2021-04-01T16:00:00Z"),
new FakeClock(DateTime.parse("2021-05-01T16:00:00Z")));
EmbeddedChannel channel = new EmbeddedChannel();
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(sslServerInitializer);
@@ -172,6 +196,174 @@ class SslServerInitializerTest {
assertThat(sslSession.getPeerCertificates()).asList().containsExactly(serverSsc.cert());
}
@ParameterizedTest
@MethodSource("provideTestCombinations")
void testFailure_cipherNotAccepted(SslProvider sslProvider) throws Exception {
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST);
LocalAddress localAddress = new LocalAddress("CIPHER_NOT_ACCEPTED_" + sslProvider);
nettyExtension.setUpServer(
localAddress, getServerHandler(true, true, sslProvider, serverSsc.key(), serverSsc.cert()));
SelfSignedCaCertificate clientSsc =
SelfSignedCaCertificate.create(
"CLIENT",
Date.from(Instant.now().minus(Duration.ofDays(2))),
Date.from(Instant.now().plus(Duration.ofDays(1))));
nettyExtension.setUpClient(
localAddress,
getClientHandler(
sslProvider,
serverSsc.cert(),
clientSsc.key(),
clientSsc.cert(),
"TLSv1.2",
Collections.singletonList("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA")));
verifySslException(
nettyExtension.getServerChannel(),
channel -> channel.attr(CLIENT_CERTIFICATE_PROMISE_KEY).get().get(),
SSLHandshakeException.class);
}
@ParameterizedTest
@MethodSource("provideTestCombinations")
void testSuccess_someCiphersNotAccepted(SslProvider sslProvider) throws Exception {
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST);
LocalAddress localAddress = new LocalAddress("SOME_CIPHERS_NOT_ACCEPTED_" + sslProvider);
nettyExtension.setUpServer(
localAddress,
new SslServerInitializer<LocalChannel>(
true,
true,
sslProvider,
Suppliers.ofInstance(serverSsc.key()),
Suppliers.ofInstance(ImmutableList.of(serverSsc.cert())),
DateTime.parse("2021-04-01T16:00:00Z"),
new FakeClock(DateTime.parse("2021-05-01T16:00:00Z"))));
SelfSignedCaCertificate clientSsc =
SelfSignedCaCertificate.create(
"CLIENT",
Date.from(Instant.now().minus(Duration.ofDays(2))),
Date.from(Instant.now().plus(Duration.ofDays(1))));
nettyExtension.setUpClient(
localAddress,
getClientHandler(
sslProvider,
serverSsc.cert(),
clientSsc.key(),
clientSsc.cert(),
"TLSv1.2",
ImmutableList.of(
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", // Only accepted cipher
"TLS_RSA_WITH_AES_256_CBC_SHA")));
SSLSession sslSession = setUpSslChannel(nettyExtension.getClientChannel(), serverSsc.cert());
nettyExtension.assertThatMessagesWork();
assertThat(sslSession.getLocalCertificates()).asList().containsExactly(clientSsc.cert());
assertThat(sslSession.getPeerCertificates()).asList().containsExactly(serverSsc.cert());
assertThat(sslSession.getCipherSuite()).isEqualTo("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
}
@ParameterizedTest
@MethodSource("provideTestCombinations")
void testSuccess_cipherNotAccepted_beforeEnforcementDate(SslProvider sslProvider)
throws Exception {
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST);
LocalAddress localAddress = new LocalAddress("CIPHER_ACCEPTED_BEFORE_DATE_" + sslProvider);
nettyExtension.setUpServer(
localAddress,
new SslServerInitializer<LocalChannel>(
true,
true,
sslProvider,
Suppliers.ofInstance(serverSsc.key()),
Suppliers.ofInstance(ImmutableList.of(serverSsc.cert())),
DateTime.parse("2021-04-01T16:00:00Z"),
new FakeClock(DateTime.parse("2021-03-01T16:00:00Z"))));
SelfSignedCaCertificate clientSsc =
SelfSignedCaCertificate.create(
"CLIENT",
Date.from(Instant.now().minus(Duration.ofDays(2))),
Date.from(Instant.now().plus(Duration.ofDays(1))));
nettyExtension.setUpClient(
localAddress,
getClientHandler(
sslProvider,
serverSsc.cert(),
clientSsc.key(),
clientSsc.cert(),
"TLSv1.2",
Collections.singletonList("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA")));
SSLSession sslSession = setUpSslChannel(nettyExtension.getClientChannel(), serverSsc.cert());
nettyExtension.assertThatMessagesWork();
assertThat(sslSession.getLocalCertificates()).asList().containsExactly(clientSsc.cert());
assertThat(sslSession.getPeerCertificates()).asList().containsExactly(serverSsc.cert());
}
@ParameterizedTest
@MethodSource("provideTestCombinations")
void testFailure_protocolNotAccepted(SslProvider sslProvider) throws Exception {
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST);
LocalAddress localAddress = new LocalAddress("PROTOCOL_NOT_ACCEPTED_" + sslProvider);
nettyExtension.setUpServer(
localAddress, getServerHandler(true, true, sslProvider, serverSsc.key(), serverSsc.cert()));
SelfSignedCaCertificate clientSsc =
SelfSignedCaCertificate.create(
"CLIENT",
Date.from(Instant.now().minus(Duration.ofDays(2))),
Date.from(Instant.now().plus(Duration.ofDays(1))));
nettyExtension.setUpClient(
localAddress,
getClientHandler(
sslProvider, serverSsc.cert(), clientSsc.key(), clientSsc.cert(), "TLSv1.1", null));
verifySslException(
nettyExtension.getServerChannel(),
channel -> channel.attr(CLIENT_CERTIFICATE_PROMISE_KEY).get().get(),
SSLHandshakeException.class);
}
@ParameterizedTest
@MethodSource("provideTestCombinations")
void testSuccess_protocolNotAccepted_beforeEnforcementDate(SslProvider sslProvider)
throws Exception {
SelfSignedCaCertificate serverSsc = SelfSignedCaCertificate.create(SSL_HOST);
LocalAddress localAddress = new LocalAddress("PROTOCOL_ACCEPTED_BEFORE_DATE_" + sslProvider);
nettyExtension.setUpServer(
localAddress,
new SslServerInitializer<LocalChannel>(
true,
true,
sslProvider,
Suppliers.ofInstance(serverSsc.key()),
Suppliers.ofInstance(ImmutableList.of(serverSsc.cert())),
DateTime.parse("2021-04-01T16:00:00Z"),
new FakeClock(DateTime.parse("2021-03-01T16:00:00Z"))));
SelfSignedCaCertificate clientSsc =
SelfSignedCaCertificate.create(
"CLIENT",
Date.from(Instant.now().minus(Duration.ofDays(2))),
Date.from(Instant.now().plus(Duration.ofDays(1))));
nettyExtension.setUpClient(
localAddress,
getClientHandler(
sslProvider, serverSsc.cert(), clientSsc.key(), clientSsc.cert(), "TLSv1.1", null));
SSLSession sslSession = setUpSslChannel(nettyExtension.getClientChannel(), serverSsc.cert());
nettyExtension.assertThatMessagesWork();
assertThat(sslSession.getLocalCertificates()).asList().containsExactly(clientSsc.cert());
assertThat(sslSession.getPeerCertificates()).asList().containsExactly(serverSsc.cert());
}
@ParameterizedTest
@MethodSource("provideTestCombinations")
void testFailure_clientCertExpired(SslProvider sslProvider) throws Exception {
@@ -48,8 +48,8 @@ javax.mail:mail:1.4
javax.xml.bind:jaxb-api:2.3.0
joda-time:joda-time:2.9.2
junit:junit:4.12
net.bytebuddy:byte-buddy-agent:1.10.5
net.bytebuddy:byte-buddy:1.10.5
net.bytebuddy:byte-buddy-agent:1.10.19
net.bytebuddy:byte-buddy:1.10.19
org.apache.httpcomponents:httpclient:4.5.11
org.apache.httpcomponents:httpcore:4.4.13
org.apiguardian:apiguardian-api:1.1.0
@@ -64,8 +64,8 @@ org.junit.jupiter:junit-jupiter-params:5.6.2
org.junit.platform:junit-platform-commons:1.6.2
org.junit.platform:junit-platform-engine:1.6.2
org.junit:junit-bom:5.6.2
org.mockito:mockito-core:3.3.3
org.objenesis:objenesis:2.6
org.mockito:mockito-core:3.7.7
org.objenesis:objenesis:3.1
org.opentest4j:opentest4j:1.2.0
org.yaml:snakeyaml:1.17
xerces:xmlParserAPIs:2.6.2
@@ -48,8 +48,8 @@ javax.mail:mail:1.4
javax.xml.bind:jaxb-api:2.3.0
joda-time:joda-time:2.9.2
junit:junit:4.12
net.bytebuddy:byte-buddy-agent:1.10.5
net.bytebuddy:byte-buddy:1.10.5
net.bytebuddy:byte-buddy-agent:1.10.19
net.bytebuddy:byte-buddy:1.10.19
org.apache.httpcomponents:httpclient:4.5.11
org.apache.httpcomponents:httpcore:4.4.13
org.apiguardian:apiguardian-api:1.1.0
@@ -64,8 +64,8 @@ org.junit.jupiter:junit-jupiter-params:5.6.2
org.junit.platform:junit-platform-commons:1.6.2
org.junit.platform:junit-platform-engine:1.6.2
org.junit:junit-bom:5.6.2
org.mockito:mockito-core:3.3.3
org.objenesis:objenesis:2.6
org.mockito:mockito-core:3.7.7
org.objenesis:objenesis:3.1
org.opentest4j:opentest4j:1.2.0
org.yaml:snakeyaml:1.17
xerces:xmlParserAPIs:2.6.2
@@ -48,8 +48,8 @@ javax.mail:mail:1.4
javax.xml.bind:jaxb-api:2.3.0
joda-time:joda-time:2.9.2
junit:junit:4.12
net.bytebuddy:byte-buddy-agent:1.10.5
net.bytebuddy:byte-buddy:1.10.5
net.bytebuddy:byte-buddy-agent:1.10.19
net.bytebuddy:byte-buddy:1.10.19
org.apache.httpcomponents:httpclient:4.5.11
org.apache.httpcomponents:httpcore:4.4.13
org.apiguardian:apiguardian-api:1.1.0
@@ -64,8 +64,8 @@ org.junit.jupiter:junit-jupiter-params:5.6.2
org.junit.platform:junit-platform-commons:1.6.2
org.junit.platform:junit-platform-engine:1.6.2
org.junit:junit-bom:5.6.2
org.mockito:mockito-core:3.3.3
org.objenesis:objenesis:2.6
org.mockito:mockito-core:3.7.7
org.objenesis:objenesis:3.1
org.opentest4j:opentest4j:1.2.0
org.yaml:snakeyaml:1.17
xerces:xmlParserAPIs:2.6.2

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