1
0
mirror of https://github.com/google/nomulus synced 2026-07-10 18:13:02 +00:00

Compare commits

...

33 Commits

Author SHA1 Message Date
Ben McIlwain dbb4092680 Don't retry permanent failures when uploading ICANN monthly reports (#328)
* Don't retry permanent failures when uploading ICANN monthly reports

There are two kinds of permanent failures that this checks for that we know will
never succeed, so it makes no sense to continue retrying 11 more times before
moving onto the next file to upload. These errors are:

1.
com.google.api.client.http.HttpResponseException: 403
Your IP address xx.xx.xx.xx is not allowed to connect

2.
com.google.api.client.http.HttpResponseException: 400
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><response xmlns="urn:ietf:params:xml:ns:iirdea-1.0"><result code="2002"><msg>A report for that month already exists, the cut-off date already passed.</msg><description>Date: 2019-09</description></result></response>

In order to implement this new functionality, this commit also adds a new way to
call Retriable that allows specifying the isRetryable Predicate (which is quite
useful).
2019-10-25 13:54:47 -04:00
Weimin Yu af3223d086 Add a test task for all schema-dependent tests (#325)
Such tests are used in server-schema compatibility tests.

Also added a test that verifies that all eligible tests
are included.
2019-10-25 13:25:01 -04:00
Shicong Huang 2d7f80baaf Fix issues related to Cloud SQL connection (#321)
* Reenable JpaTransactionManager for Alpha and Crash

* Make UploadClaimsListCommand implement CommandWithCloudSql

* Fix wrong call to get password

* Use Cloud SQL Socket library to provision TransactionManager

* Change to use dataSource configs
2019-10-25 12:40:43 -04:00
gbrodman d4d5d6a570 Add another test contact for Registry Lock testing (#324)
* Add another test contact for Registry Lock testing

Previously, we only had two contacts -- one per registrar. This PR adds
a second, registry-lock-enabled, contact to one registrar for two
reasons:

1. For registry-lock-related testing, we'd like to be able to test both
positively and negatively, making sure that the permissions work the way
they should
2. In general, the UI tests should include the case where we have
multiple contacts in the same registrar. Previously, this was never the
case in tests.

* Merge remote-tracking branch 'origin/master' into addTestContact
2019-10-25 10:24:59 -04:00
gbrodman 53c0be6537 Don't destroy existing registry lock passwords in contacts (#317)
* Don't destroy existing registry lock passwords in contacts

The existing code assumes that the "contacts" segment of the form
contains an exact representation of the registrar contacts. This breaks
when we have a contact with an existing registry lock password because
we don't want to keep passing around that password in plain text (we
never store it in plain text)

This PR changes the code so that instead of assuming the contact is
provided in its entirety, we load the contact from storage first
(matching by email address) if it exists. We then set the required
fields from the JSON object, and set the password optionally if it was
provided.

Alternatives:
- Create a separate RegistrarContactPassword object with a
RegistrarContact parent. This increases complexity significantly since
we'd be adding a parent-child relationship and adding more objects to
Datastore during the transition to SQL. It also doesn't completely solve
the problem of "When should we set the password?" because the password
field still must be part of the same form.
- Rearrange the UI so that the password is set as part of a completely
separate form with a separate submit action. This would be possible but
is sub-optimal for two reasons. First, we are trying to not re-engineer
the web console as much as possible since we're likely starting it from
scratch before too long anyway. Second, we want the
lock-password-setting to be part of the standard contact modification
workflow.

* Responses to CR

* Actually we need to allow "removal" of fields

* Remove optional

* one-statement building the contacts
2019-10-24 20:18:37 -04:00
gbrodman 63bb2dd79b Don't include password hash + salt in visible diffs (#322)
We don't want to override toDiffableFieldMap because (per the javadoc)
that is supposed to contain sensitive information. So, we should just
remove it before sending it out.
2019-10-23 10:57:46 -07:00
gbrodman 8278b5409e Add a registrarId index to RegistryLock (#312)
* Add a registrarId index to RegistryLock

* Merge remote-tracking branch 'origin/master' into getByRegistrar

* Responses to CR
2019-10-23 06:51:20 -07:00
gbrodman f98b0f8739 Use merge instead of persist for RegistryLockDao (#310)
* Use merge instead of persist for RegistryLockDao

* CR responses
2019-10-22 12:21:32 -07:00
gbrodman 128fde16c3 Fix security alerts by upgrading (#318)
Note: this will be incomplete until Puppeteer releases a new version as
we await their https-proxy-agent version bump
2019-10-21 14:17:03 -07:00
Lai Jiang 45f7c89efd Upgrade to Gradle 5.6.3 (#315) 2019-10-18 15:13:46 -07:00
Weimin Yu 13dc758747 Use base64-encoded SQL credentials (#314)
* Use base64-encoded SQL credentials

Encode Cloud SQL credential files on gcs with base64,
to be consistent with our Cloud Build practices.

Also renamed a property that specifies where to publish
the schema jar. New name is schema_publish_repo.
2019-10-18 11:48:40 -04:00
Shicong Huang 85c11ca889 Use a single database container for all tests (#313) 2019-10-15 15:09:03 -04:00
sarahcaseybot 340bf40642 Update Spec11 email template (#308)
* Changes to Spec11 notice email.

* Fix lines that were too long
2019-10-14 12:23:28 -04:00
Michael Muller 53ece5eda4 Enable filtering across all test tasks (#311)
The segregated test targets in core break the --tests filter.  Fix this by
defining a "testFilter" property and creating the FilteringTest task type that
applies it to the property set by "--tests".
2019-10-11 14:24:01 -04:00
Shicong Huang 6c220567c8 Write ClaimsList to Cloud SQL (#223)
* Rewrite ClaimsListShard with new API

* Write ClaimsList to Cloud SQL

* Add creationTimestamp
2019-10-11 12:31:34 -04:00
Weimin Yu c3e3a1353b Allow schema-push to all env with Flyway (#309)
* Make Flyway schema task work with prod and sandbox

Also renamed the 'superuser' role to 'admin' since
we do not own super user in Cloud SQL.

* Allow pushing schema to all env with Flyway

Desktop schema push to production is needed in the short term.
Long-termly we need to decide if this should be kept for glass
breaking

Schema push to sandbox and production requires interactiveconfirmation.

Also fixed a typo in initialize_roles.sql.
2019-10-10 16:32:21 -04:00
Ben McIlwain ce480a5191 Add Bloom filters to the Cloud SQL PremiumList schema (#306)
* Add Bloom filters to the Cloud SQL PremiumList schema

They are slightly different from the existing Bloom filters stored in Datastore
in that they now use an ASCII String encoding rather than the more generic
CharSequence, and there is no maximum size (whereas we previously had to live
within the 1 MB max entity size for Datastore).
2019-10-09 17:06:42 -04:00
Weimin Yu f2a2b2d2e2 Modify Cloud SQL user management scripts (#302)
* Modify Cloud SQL user management scripts

Create readonly and readwrite roles that may be granted to users.
Also configured default privileges for tables created in the future.

Made sure arbitrary users may not create database or tables.

* Modify Cloud SQL user management scripts

Create readonly and readwrite roles that may be granted to users.
Also configured default privileges for tables created in the future.

Made sure arbitrary users may not create database or tables.
2019-10-09 16:02:42 -04:00
gbrodman 906b054f4b Load persistence.xml classes before adding test entities (#307)
* Load persistence.xml classes before adding test entities

* Also use persistence.xml in GenerateSqlSchemaCommand

* Add exception message

* remove duplicate line
2019-10-09 15:15:04 -04:00
Shicong Huang a694e247cd Add support for nomulus tool to connect to Cloud SQL (#303) 2019-10-09 10:30:35 -04:00
Shicong Huang 022f397cd9 Implement ZonedDateTimeConverter (#287)
* Implement ZonedDateTimeConverter

* Use dedicated TestEntity for ZonedDateTimeConverterTest
2019-10-08 16:46:07 -04:00
Ben McIlwain 5dc058ec99 Automatically apply JPA type converters (#305)
* Automatically apply JPA type converters

* Include converters in tests and schema generation too
2019-10-08 13:16:39 -04:00
Ben McIlwain 0fd7cf29b5 Make it clear that registered domain lists are also exported to Drive (#304)
* Make it clear that registered domain lists are also exported to Drive
2019-10-08 13:15:55 -04:00
Ben McIlwain bc7f3546c7 Add initial support for persisting premium lists to Cloud SQL (#285)
* Add initial support for persisting premium lists to Cloud SQL

This adds support to the `nomulus create_premium_list` command only; support for
`nomulus update_premium_list` will be in a subsequent PR.

The design goals for this PR were:
1. Do not change the existing codepaths for premium lists at all, especially not
   on the read path.
2. Write premium lists to Cloud SQL only if requested (i.e. not by default), and
   write to Datastore first so as to not be blocked by errors with Cloud SQL.
3. Reuse existing codepaths to the maximum possible extent (e.g. don't yet
   re-implement premium list parsing; take advantage of the existing logic), but
   also ...
4. Some duplication is OK, since the existing Datastore path will be deleted
   once this migration is complete, leaving only the codepaths for Cloud SQL.

* Refactor out common logic

* Add DAO test

* Add tests for parsing premium lists

* Use containsExactly

* Code review changes

* Format

* Re-generate schema

* Fix column names

* Make some tests pass

* Add SQL migration scripts

* Fix test errors
2019-10-08 11:47:22 -04:00
Ben McIlwain 658f61bd8f Fix errors in DB update README file (#301)
* Fix errors in DB update README file
2019-10-07 15:55:53 -04:00
Michael Muller 8ca8fff387 Refactor environment -> project map out of build (#300)
Move the project mapping into its own file so that it can be more easily
overriden.
2019-10-07 13:34:49 -04:00
Michael Muller 8bcfb1802e Add an explanation to dummied-out JPA init (#299)
* Add an explanation to dummied-out JPA init

Add a more elaborate explanation of why actual JpaTransactionManager
initialization was removed from the factory.
2019-10-07 12:47:24 -04:00
gbrodman a259dee986 Add a DAO for RegistryLock objects (#290)
* Add a DAO for RegistryLock objects

* Add an index on verification code and remove old file

* Move to v4

* Use camelCase in index names

* Javadoc fixes

* Allow alteration of RegistryLock objects in-place

* save, load-modify, read in separate transactions

* Change the creation timestamp to be a CreateAutoTimestamp
2019-10-07 11:24:08 -04:00
Michael Muller ebc8d54f94 Add persistence.xml to the war files (#293)
* Add persistence.xml to the war files
* Always use the DummyJpaTransactionManager

Use the DJTM until we get all of the dependencies set up for all of the
environments.

This shouldn't affect any of the unit tests, these use the
JpaTransactionManagerRule to set up a local database and connection.

This fixes the App Engine build.
2019-10-06 21:32:27 -04:00
sarahcaseybot e9ce389269 Implement UpdateAutoTimestampConverter (#298)
* Implement a JPA-based converter for UpdateAutoTimestamp, allowing us to persist instances of this class.
2019-10-04 16:45:32 -04:00
Michael Muller e07be9780e Implement CreateAutoTimestampConverter (#282)
* Implement CreateAutoTimestampConverter

Implement a JPA-based converter for CreateAutoTimestamp, allowing us to
persist instances of this class.

Note that converters appear to be required to convert to and from database
types that are generally known to JDBC.  For example, conversion to Timestamp
works, conversion to OffsetDateTime does not (even though this works through
the JDBC interface directly).
2019-10-04 16:31:14 -04:00
Michael Muller 31b5abb9a1 Improve error handling for environment property (#295)
* Improve error handling for environment property

Improve the error messages that we get for a bad or missing environment
property.  Move the property processing into the main build so that we do it
only once and configure all of the appengine deployment tasks to check that a
project has been defined and print a friendly error if it hasn't.

Note that even if the check isn't configured, this change prevents deployment
because the gcpProject will be set to null.  It just won't print as useful an
error message.

Tested: ran appengineDeployAll with and without the environment property, ran
"build" to verify that we don't get any complaints for non-deployment targets.

* Changes for review.

* Changes for review

* Changes for review.

* Changes for review.
2019-10-04 13:23:36 -04:00
Michael Muller aa4e242a34 Give JpaTransactionManagerRule more parameters (#292)
* Give JpaTransactionManagerRule more parameters

Allow users of the rule to add annotated classes and properties, both useful
for testing.

* Change in response to review.

* Changes for review.

* Move test EntityManagerFactory create method

Move the test create method into the JpaTransactionManagerRuleTest.

* Remove nomulus SQL dialect from G.S.S.Command

Remove NomulusPostgreSQLDialect from GenerateSqlSchemaCommand (it has been
moved to its own top-level class).
2019-10-03 16:14:28 -04:00
105 changed files with 2907 additions and 2286 deletions
+22 -3
View File
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
apply from: "${rootDir.path}/projects.gradle"
apply plugin: 'war'
def environment = rootProject.environment
@@ -26,14 +25,19 @@ project.convention.plugins['war'].webAppDirName =
apply plugin: 'com.google.cloud.tools.appengine'
def coreResourcesDir = "${rootDir}/core/build/resources/main"
// Get the web.xml file for the service.
war {
webInf {
from "../../core/src/main/java/google/registry/env/common/${project.name}/WEB-INF"
from("${coreResourcesDir}/META-INF/persistence.xml") {
into "classes/META-INF"
}
}
}
def coreResourcesDir = "${rootDir}/core/build/resources/main"
war {
from("${coreResourcesDir}/google/registry/ui/html") {
include "*.html"
@@ -68,6 +72,10 @@ appengine {
if (!rootProject.prodOrSandboxEnv) {
version = 'GCLOUD_CONFIG'
}
// Don't set gcpProject directly, it gets overriden in ./build.gradle.
// Do -P environment={crash,alpha} instead. For sandbox/production,
// use Spinnaker.
projectId = gcpProject
}
}
@@ -92,4 +100,15 @@ explodeWar.doLast {
rootProject.deploy.dependsOn appengineDeployAll
rootProject.stage.dependsOn appengineStage
appengineDeployAll.dependsOn rootProject.verifyDeployment
// Impose verification for all of the deployment tasks. We haven't found a
// better way to do this other than to apply to each of them independently.
// If a new task gets added, it will still fail if "environment" is not defined
// because gcpProject is null. We just won't get as friendly an error message.
appengineDeployAll.configure rootProject.verifyDeploymentConfig
appengineDeploy.configure rootProject.verifyDeploymentConfig
appengineDeployCron.configure rootProject.verifyDeploymentConfig
appengineDeployDispatch.configure rootProject.verifyDeploymentConfig
appengineDeployDos.configure rootProject.verifyDeploymentConfig
appengineDeployIndex.configure rootProject.verifyDeploymentConfig
appengineDeployQueue.configure rootProject.verifyDeploymentConfig
+50 -10
View File
@@ -109,21 +109,61 @@ task deploy {
description = 'Deploys all services to App Engine.'
}
task verifyDeployment {
group = 'deployment'
description = 'Ensure that one cannot deploy to production or sandbox.'
doFirst {
if (rootProject.prodOrSandboxEnv) {
throw new GradleException("Cannot deploy to production or sandbox.");
}
}
}
task stage {
group = 'deployment'
description = 'Generates application directories for all services.'
}
// App-engine environment configuration. We set up all of the variables in
// the root project.
def environments = ['production', 'sandbox', 'alpha', 'crash']
def gcpProject = null
apply from: "${rootDir.path}/projects.gradle"
if (environment == '') {
// Keep the project null, this will prevent deployment. Set the
// environment to "alpha" because other code needs this property to
// explode the war file.
environment = 'alpha'
} else if (environment != 'production' && environment != 'sandbox') {
gcpProject = projects[environment]
if (gcpProject == null) {
throw new GradleException("-Penvironment must be one of " +
"${projects.keySet()}.")
}
}
rootProject.ext.environment = environment
rootProject.ext.gcpProject = gcpProject
rootProject.ext.prodOrSandboxEnv = environment in ['production', 'sandbox']
// Function to verify that the deployment parameters have been set.
def verifyDeploymentParams() {
if (prodOrSandboxEnv) {
// Do not deploy to prod or sandbox. Print a prominent error in bright red.
System.err.println('\033[31;1m-----------------------------------------------------------------')
System.err.println('*** DANGER WILL ROBINSON!')
System.err.println('*** You may not deploy to production or sandbox from gradle. Do a')
System.err.println('*** release from Spinnaker, see deployment playbook.')
System.err.println('-----------------------------------------------------------------')
throw new GradleException('Aborting. See prominent error above.')
} else if (gcpProject == null) {
def error = 'You must specify -P environment={alpha,crash}'
System.err.println("\033[33;1m${error}\033[0m")
throw GradleException("Aborting: ${error}")
}
}
// Closure that we can just drop into all of our deployment tasks.
rootProject.ext.verifyDeploymentConfig = {
doFirst { verifyDeploymentParams() }
}
// Subproject configuration.
allprojects {
// Skip no-op project
if (project.name == 'services') return
@@ -16,7 +16,7 @@ com.jcraft:jsch:0.1.54
com.jcraft:jzlib:1.1.1
com.netflix.nebula:gradle-lint-plugin:10.4.2
com.netflix.nebula:nebula-gradle-interop:1.0.11
com.netflix.nebula:nebula-test:7.3.0
com.netflix.nebula:nebula-test:7.4.0
commons-codec:commons-codec:1.9
commons-io:commons-io:2.5
commons-lang:commons-lang:2.6
@@ -41,7 +41,7 @@ org.apache.maven:maven-builder-support:3.6.2
org.apache.maven:maven-model-builder:3.6.2
org.apache.maven:maven-model:3.6.2
org.codehaus.gpars:gpars:1.2.1
org.codehaus.groovy:groovy-all:2.4.9
org.codehaus.groovy:groovy-all:2.4.15
org.codehaus.groovy:groovy-ant:2.1.8
org.codehaus.groovy:groovy-groovydoc:2.1.8
org.codehaus.groovy:groovy-templates:2.1.8
@@ -65,4 +65,4 @@ org.multiverse:multiverse-core:0.7.0
org.objenesis:objenesis:2.4
org.ow2.asm:asm:7.0
org.slf4j:slf4j-api:1.7.2
org.spockframework:spock-core:1.1-groovy-2.4-rc-4
org.spockframework:spock-core:1.3-groovy-2.4
@@ -90,6 +90,9 @@
{
"moduleLicense": "The BSD License"
},
{
"moduleLicense": "The New BSD License"
},
{
"moduleLicense": "The PostgreSQL License"
},
@@ -198,6 +201,9 @@
{
"moduleLicense": "The MIT license"
},
{
"moduleLicense": "The MIT License (MIT)"
},
{
"moduleLicense": "The PostgreSQL License"
},
+46 -12
View File
@@ -192,7 +192,7 @@ dependencies {
compile deps['com.jcraft:jsch']
testCompile deps['com.thoughtworks.qdox:qdox']
compile deps['dnsjava:dnsjava']
runtime deps['org.glassfish.jaxb:jaxb-runtime']
testCompile deps['io.github.classgraph:classgraph']
testCompile deps['javax.annotation:jsr250-api']
compile deps['javax.inject:javax.inject']
compile deps['javax.mail:mail']
@@ -218,6 +218,7 @@ dependencies {
compile deps['org.bouncycastle:bcpg-jdk15on']
testCompile deps['org.bouncycastle:bcpkix-jdk15on']
compile deps['org.bouncycastle:bcprov-jdk15on']
runtime deps['org.glassfish.jaxb:jaxb-runtime']
compile deps['org.joda:joda-money']
compile deps['org.json:json']
testCompile deps['org.mortbay.jetty:jetty']
@@ -558,10 +559,38 @@ artifacts {
testRuntime testJar
}
task fragileTest(type: Test) {
/**
* We have to break out the test suites because some of the tests conflict
* with one another, but unfortunately this breaks the "--tests" flag. The
* --tests flag only applies to the task named on the command line (usually
* just "test"), not for all tasks of type "Test".
*
* As a better solution, FilteringTest sets testNameIncludePatterns (the
* internal property that --tests sets) from the value of the "testFilter"
* property, allowing us to filter across all the tests in core without
* explicitly specifying a test task or causing errors because there are no
* matching tests in the main task.
*
* To use it, define "testFilter" to be a comma-separated collection of class
* names (wildcards are allowed):
*
* ./gradlew test -P testFilter=*.FooBar,google.registry.tools.ShellCommandTest
*/
class FilteringTest extends Test {
void setTests(List<String> tests) {
// Common exclude pattern. See README in parent directory for explanation.
exclude "**/*TestCase.*", "**/*TestSuite.*"
include tests
if (project.testFilter) {
testNameIncludePatterns = project.testFilter.split(',')
}
}
}
task fragileTest(type: FilteringTest) {
// Common exclude pattern. See README in parent directory for explanation.
exclude "**/*TestCase.*", "**/*TestSuite.*"
include fragileTestPatterns
tests = fragileTestPatterns
if (rootProject.findProperty("skipDockerIncompatibleTests") == "true") {
exclude dockerIncompatibleTestPatterns
@@ -571,15 +600,22 @@ task fragileTest(type: Test) {
forkEvery 1
}
task outcastTest(type: Test) {
// Common exclude pattern. See README in parent directory for explanation.
exclude "**/*TestCase.*", "**/*TestSuite.*"
include outcastTestPatterns
task outcastTest(type: FilteringTest) {
tests = outcastTestPatterns
// Sets the maximum number of test executors that may exist at the same time.
maxParallelForks 5
}
// Dedicated test suite for schema-dependent tests.
task sqlIntegrationTest(type: Test) {
include 'google/registry/schema/integration/SqlIntegrationTestSuite.*'
// Copied from FilteringTest. Not inheriting b/c it excludes TestSuites.
if (project.testFilter) {
testNameIncludePatterns = project.testFilter.split(',')
}
}
task findGoldenImages(type: JavaExec) {
classpath = sourceSets.test.runtimeClasspath
main = 'google.registry.webdriver.GoldenImageFinder'
@@ -630,10 +666,8 @@ task registryTool(type: JavaExec) {
}
}
task generateGoldenImages(type: Test) {
// Common exclude pattern. See README in parent directory for explanation.
exclude "**/*TestCase.*", "**/*TestSuite.*"
include "**/webdriver/*"
task generateGoldenImages(type: FilteringTest) {
tests = ["**/webdriver/*"]
// Sets the maximum number of test executors that may exist at the same time.
maxParallelForks 5
@@ -130,6 +130,7 @@ commons-codec:commons-codec:1.11
commons-logging:commons-logging:1.2
dnsjava:dnsjava:2.1.7
io.dropwizard.metrics:metrics-core:3.1.2
io.github.classgraph:classgraph:4.8.52
io.grpc:grpc-all:1.17.1
io.grpc:grpc-alts:1.17.1
io.grpc:grpc-auth:1.17.1
@@ -129,6 +129,7 @@ commons-codec:commons-codec:1.11
commons-logging:commons-logging:1.2
dnsjava:dnsjava:2.1.7
io.dropwizard.metrics:metrics-core:3.1.2
io.github.classgraph:classgraph:4.8.52
io.grpc:grpc-all:1.17.1
io.grpc:grpc-alts:1.17.1
io.grpc:grpc-auth:1.17.1
@@ -141,6 +141,7 @@ commons-codec:commons-codec:1.11
commons-logging:commons-logging:1.2
dnsjava:dnsjava:2.1.7
io.dropwizard.metrics:metrics-core:3.1.2
io.github.classgraph:classgraph:4.8.52
io.grpc:grpc-all:1.17.1
io.grpc:grpc-alts:1.17.1
io.grpc:grpc-auth:1.17.1
@@ -141,6 +141,7 @@ commons-codec:commons-codec:1.11
commons-logging:commons-logging:1.2
dnsjava:dnsjava:2.1.7
io.dropwizard.metrics:metrics-core:3.1.2
io.github.classgraph:classgraph:4.8.52
io.grpc:grpc-all:1.17.1
io.grpc:grpc-alts:1.17.1
io.grpc:grpc-auth:1.17.1
@@ -144,7 +144,7 @@
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportDomainLists&runInEmpty]]></url>
<description>
This job exports lists of all active domain names to Google Cloud Storage.
This job exports lists of all active domain names to Google Drive and Google Cloud Storage.
</description>
<schedule>every 12 hours synchronized</schedule>
<target>backend</target>
@@ -112,7 +112,7 @@
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportDomainLists&runInEmpty]]></url>
<description>
This job exports lists of all active domain names to Google Cloud Storage.
This job exports lists of all active domain names to Google Drive and Google Cloud Storage.
</description>
<schedule>every 12 hours synchronized</schedule>
<target>backend</target>
@@ -56,7 +56,7 @@ import javax.inject.Inject;
import org.joda.time.DateTime;
/**
* A mapreduce that exports the list of active domains on all real TLDs to Google Cloud Storage.
* A mapreduce that exports the list of active domains on all real TLDs to Google Drive and GCS.
*
* <p>Each TLD's active domain names are exported as a newline-delimited flat text file with the
* name TLD.txt into the domain-lists bucket. Note that this overwrites the files in place.
@@ -0,0 +1,68 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.registry;
import static com.google.common.base.Preconditions.checkNotNull;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import com.google.common.collect.ImmutableList;
import google.registry.schema.domain.RegistryLock;
import javax.persistence.EntityManager;
/** Data access object for {@link google.registry.schema.domain.RegistryLock}. */
public final class RegistryLockDao {
/**
* Returns the most recent version of the {@link RegistryLock} referred to by the verification
* code (there may be two instances of the same code in the database--one after lock object
* creation and one after verification.
*/
public static RegistryLock getByVerificationCode(String verificationCode) {
return jpaTm()
.transact(
() -> {
EntityManager em = jpaTm().getEntityManager();
Long revisionId =
em.createQuery(
"SELECT MAX(revisionId) FROM RegistryLock WHERE verificationCode ="
+ " :verificationCode",
Long.class)
.setParameter("verificationCode", verificationCode)
.getSingleResult();
checkNotNull(revisionId, "No registry lock with this code");
return em.find(RegistryLock.class, revisionId);
});
}
public static ImmutableList<RegistryLock> getByRegistrarId(String registrarId) {
return jpaTm()
.transact(
() ->
ImmutableList.copyOf(
jpaTm()
.getEntityManager()
.createQuery(
"SELECT lock FROM RegistryLock lock WHERE"
+ " lock.registrarId = :registrarId",
RegistryLock.class)
.setParameter("registrarId", registrarId)
.getResultList()));
}
public static RegistryLock save(RegistryLock registryLock) {
checkNotNull(registryLock, "Null registry lock cannot be saved");
return jpaTm().transact(() -> jpaTm().getEntityManager().merge(registryLock));
}
}
@@ -0,0 +1,72 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.tmch;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import com.google.common.flogger.FluentLogger;
import google.registry.schema.tmch.ClaimsList;
import javax.persistence.EntityManager;
/** Data access object for {@link ClaimsList}. */
public class ClaimsListDao {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static void save(ClaimsList claimsList) {
jpaTm().transact(() -> jpaTm().getEntityManager().persist(claimsList));
}
/**
* Try to save the given {@link ClaimsList} into Cloud SQL. If the save fails, the error will be
* logged but no exception will be thrown.
*
* <p>This method is used during the dual-write phase of database migration as Datastore is still
* the authoritative database.
*/
public static void trySave(ClaimsList claimsList) {
try {
ClaimsListDao.save(claimsList);
logger.atInfo().log(
"Inserted %,d claims into Cloud SQL, created at %s",
claimsList.getLabelsToKeys().size(), claimsList.getTmdbGenerationTime());
} catch (Throwable e) {
logger.atSevere().withCause(e).log("Error inserting claims into Cloud SQL");
}
}
/**
* Returns the current revision of the {@link ClaimsList} in Cloud SQL. Throws exception if there
* is no claims in the table.
*/
public static ClaimsList getCurrent() {
return jpaTm()
.transact(
() -> {
EntityManager em = jpaTm().getEntityManager();
Long revisionId =
em.createQuery("SELECT MAX(revisionId) FROM ClaimsList", Long.class)
.getSingleResult();
return em.createQuery(
"FROM ClaimsList cl LEFT JOIN FETCH cl.labelsToKeys WHERE cl.revisionId ="
+ " :revisionId",
ClaimsList.class)
.setParameter("revisionId", revisionId)
.getSingleResult();
});
}
private ClaimsListDao() {}
}
@@ -217,8 +217,7 @@ public class ClaimsListShard extends ImmutableObject {
});
}
public static ClaimsListShard create(
DateTime creationTime, ImmutableMap<String, String> labelsToKeys) {
public static ClaimsListShard create(DateTime creationTime, Map<String, String> labelsToKeys) {
ClaimsListShard instance = new ClaimsListShard();
instance.id = allocateId();
instance.creationTime = checkNotNull(creationTime);
@@ -15,10 +15,7 @@
package google.registry.model.transaction;
import com.google.common.flogger.FluentLogger;
import google.registry.persistence.PersistenceModule.AppEngineEmf;
import google.registry.util.Clock;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
@@ -26,7 +23,6 @@ import javax.persistence.PersistenceException;
import org.joda.time.DateTime;
/** Implementation of {@link JpaTransactionManager} for JPA compatible database. */
@Singleton
public class JpaTransactionManagerImpl implements JpaTransactionManager {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -39,8 +35,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
private final ThreadLocal<TransactionInfo> transactionInfo =
ThreadLocal.withInitial(TransactionInfo::new);
@Inject
JpaTransactionManagerImpl(@AppEngineEmf EntityManagerFactory emf, Clock clock) {
public JpaTransactionManagerImpl(EntityManagerFactory emf, Clock clock) {
this.emf = emf;
this.clock = clock;
}
@@ -14,9 +14,13 @@
package google.registry.model.transaction;
import static google.registry.config.RegistryEnvironment.ALPHA;
import static google.registry.config.RegistryEnvironment.CRASH;
import com.google.appengine.api.utils.SystemProperty;
import com.google.appengine.api.utils.SystemProperty.Environment.Value;
import com.google.common.annotations.VisibleForTesting;
import google.registry.config.RegistryEnvironment;
import google.registry.model.ofy.DatastoreTransactionManager;
import google.registry.persistence.DaggerPersistenceComponent;
@@ -30,8 +34,8 @@ public class TransactionManagerFactory {
private TransactionManagerFactory() {}
private static JpaTransactionManager createJpaTransactionManager() {
if (SystemProperty.environment.value() == Value.Production) {
return DaggerPersistenceComponent.create().jpaTransactionManager();
if (shouldEnableJpaTm() && isInAppEngine()) {
return DaggerPersistenceComponent.create().appEngineJpaTransactionManager();
} else {
return DummyJpaTransactionManager.create();
}
@@ -44,6 +48,34 @@ public class TransactionManagerFactory {
return new DatastoreTransactionManager(null);
}
/**
* Sets jpaTm to the implementation for Nomulus tool. Note that this method should be only used by
* {@link google.registry.tools.RegistryCli} to initialize jpaTm.
*/
public static void initForTool() {
if (shouldEnableJpaTm()) {
jpaTm = DaggerPersistenceComponent.create().nomulusToolJpaTransactionManager();
}
}
// TODO(shicong): Enable JpaTm for all environments and remove this function
private static boolean shouldEnableJpaTm() {
return RegistryEnvironment.get() == ALPHA || RegistryEnvironment.get() == CRASH;
}
/**
* This function uses App Engine API to determine if the current runtime environment is App
* Engine.
*
* @see <a
* href="https://cloud.google.com/appengine/docs/standard/java/javadoc/com/google/appengine/api/utils/SystemProperty">App
* Engine API public doc</a>
*/
private static boolean isInAppEngine() {
// SystemProperty.environment.value() returns null if the current runtime is local JVM
return SystemProperty.environment.value() == Value.Production;
}
/** Returns {@link TransactionManager} instance. */
public static TransactionManager tm() {
return TM;
@@ -51,11 +83,6 @@ public class TransactionManagerFactory {
/** Returns {@link JpaTransactionManager} instance. */
public static JpaTransactionManager jpaTm() {
// TODO: Returns corresponding TransactionManager based on the runtime environment.
// We have 3 kinds of runtime environment:
// 1. App Engine
// 2. Local JVM used by nomulus tool
// 3. Unit test
return jpaTm;
}
}
@@ -0,0 +1,59 @@
// Copyright 2019 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;
import static com.google.common.base.Charsets.US_ASCII;
import static com.google.common.hash.Funnels.stringFunnel;
import com.google.common.hash.BloomFilter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import javax.annotation.Nullable;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
/** JPA converter for ASCII String {@link BloomFilter}s. */
@Converter(autoApply = true)
public class BloomFilterConverter implements AttributeConverter<BloomFilter<String>, byte[]> {
@Override
@Nullable
public byte[] convertToDatabaseColumn(@Nullable BloomFilter<String> entity) {
if (entity == null) {
return null;
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
entity.writeTo(bos);
} catch (IOException e) {
throw new UncheckedIOException("Error saving Bloom filter data", e);
}
return bos.toByteArray();
}
@Override
@Nullable
public BloomFilter<String> convertToEntityAttribute(@Nullable byte[] columnValue) {
if (columnValue == null) {
return null;
}
try {
return BloomFilter.readFrom(new ByteArrayInputStream(columnValue), stringFunnel(US_ASCII));
} catch (IOException e) {
throw new UncheckedIOException("Error loading Bloom filter data", e);
}
}
}
@@ -0,0 +1,49 @@
// Copyright 2019 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;
import static com.google.common.base.MoreObjects.firstNonNull;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import google.registry.model.CreateAutoTimestamp;
import google.registry.util.DateTimeUtils;
import java.sql.Timestamp;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import javax.annotation.Nullable;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import org.joda.time.DateTime;
/** JPA converter to for storing/retrieving CreateAutoTimestamp objects. */
@Converter(autoApply = true)
public class CreateAutoTimestampConverter
implements AttributeConverter<CreateAutoTimestamp, Timestamp> {
@Override
public Timestamp convertToDatabaseColumn(CreateAutoTimestamp entity) {
DateTime dateTime = firstNonNull(entity.getTimestamp(), jpaTm().getTransactionTime());
return Timestamp.from(DateTimeUtils.toZonedDateTime(dateTime).toInstant());
}
@Override
@Nullable
public CreateAutoTimestamp convertToEntityAttribute(@Nullable Timestamp columnValue) {
if (columnValue == null) {
return null;
}
ZonedDateTime zdt = ZonedDateTime.ofInstant(columnValue.toInstant(), ZoneOffset.UTC);
return CreateAutoTimestamp.create(DateTimeUtils.toJodaDateTime(zdt));
}
}
@@ -0,0 +1,27 @@
// Copyright 2019 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;
import java.sql.Types;
import org.hibernate.dialect.PostgreSQL95Dialect;
/** Nomulus mapping rules for column types in Postgresql. */
public class NomulusPostgreSQLDialect extends PostgreSQL95Dialect {
public NomulusPostgreSQLDialect() {
super();
registerColumnType(Types.VARCHAR, "text");
registerColumnType(Types.TIMESTAMP_WITH_TIMEZONE, "timestamptz");
registerColumnType(Types.TIMESTAMP, "timestamptz");
}
}
@@ -18,7 +18,9 @@ import dagger.Component;
import google.registry.config.CredentialModule;
import google.registry.config.RegistryConfig.ConfigModule;
import google.registry.keyring.kms.KmsModule;
import google.registry.model.transaction.JpaTransactionManagerImpl;
import google.registry.model.transaction.JpaTransactionManager;
import google.registry.persistence.PersistenceModule.AppEngineJpaTm;
import google.registry.persistence.PersistenceModule.NomulusToolJpaTm;
import google.registry.util.UtilsModule;
import javax.inject.Singleton;
import javax.persistence.EntityManagerFactory;
@@ -34,5 +36,10 @@ import javax.persistence.EntityManagerFactory;
UtilsModule.class
})
public interface PersistenceComponent {
JpaTransactionManagerImpl jpaTransactionManager();
@AppEngineJpaTm
JpaTransactionManager appEngineJpaTransactionManager();
@NomulusToolJpaTm
JpaTransactionManager nomulusToolJpaTransactionManager();
}
@@ -29,11 +29,14 @@ import dagger.Module;
import dagger.Provides;
import google.registry.config.RegistryConfig.Config;
import google.registry.keyring.kms.KmsKeyring;
import google.registry.model.transaction.JpaTransactionManager;
import google.registry.model.transaction.JpaTransactionManagerImpl;
import google.registry.util.Clock;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Qualifier;
import javax.inject.Singleton;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import org.hibernate.cfg.Environment;
@@ -48,6 +51,10 @@ public class PersistenceModule {
public static final String HIKARI_MAXIMUM_POOL_SIZE = "hibernate.hikari.maximumPoolSize";
public static final String HIKARI_IDLE_TIMEOUT = "hibernate.hikari.idleTimeout";
public static final String HIKARI_DS_SOCKET_FACTORY = "hibernate.hikari.dataSource.socketFactory";
public static final String HIKARI_DS_CLOUD_SQL_INSTANCE =
"hibernate.hikari.dataSource.cloudSqlInstance";
@Provides
@DefaultHibernateConfigs
public static ImmutableMap<String, String> providesDefaultDatabaseConfigs() {
@@ -61,6 +68,8 @@ public class PersistenceModule {
// SessionFactory is created. Setting it to 'none' to turn off the feature.
properties.put(Environment.HBM2DDL_AUTO, "none");
// Hibernate converts any date to this timezone when writing to the database.
properties.put(Environment.JDBC_TIME_ZONE, "UTC");
properties.put(
Environment.PHYSICAL_NAMING_STRATEGY, NomulusNamingStrategy.class.getCanonicalName());
@@ -70,28 +79,50 @@ public class PersistenceModule {
properties.put(HIKARI_MINIMUM_IDLE, getHibernateHikariMinimumIdle());
properties.put(HIKARI_MAXIMUM_POOL_SIZE, getHibernateHikariMaximumPoolSize());
properties.put(HIKARI_IDLE_TIMEOUT, getHibernateHikariIdleTimeout());
properties.put(Environment.DIALECT, NomulusPostgreSQLDialect.class.getName());
return properties.build();
}
@Provides
@AppEngineEmf
public static EntityManagerFactory providesAppEngineEntityManagerFactory(
@Singleton
@PartialCloudSqlConfigs
public static ImmutableMap<String, String> providesPartialCloudSqlConfigs(
@Config("cloudSqlJdbcUrl") String jdbcUrl,
@Config("cloudSqlUsername") String username,
@Config("cloudSqlInstanceConnectionName") String instanceConnectionName,
KmsKeyring kmsKeyring,
@DefaultHibernateConfigs ImmutableMap<String, String> defaultConfigs) {
String password = kmsKeyring.getCloudSqlPassword();
HashMap<String, String> overrides = Maps.newHashMap(defaultConfigs);
// For Java users, the Cloud SQL JDBC Socket Factory can provide authenticated connections.
// See https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory for details.
overrides.put("socketFactory", "com.google.cloud.sql.postgres.SocketFactory");
overrides.put("cloudSqlInstance", instanceConnectionName);
overrides.put(Environment.URL, jdbcUrl);
overrides.put(HIKARI_DS_SOCKET_FACTORY, "com.google.cloud.sql.postgres.SocketFactory");
overrides.put(HIKARI_DS_CLOUD_SQL_INSTANCE, instanceConnectionName);
return ImmutableMap.copyOf(overrides);
}
EntityManagerFactory emf = create(jdbcUrl, username, password, ImmutableMap.copyOf(overrides));
Runtime.getRuntime().addShutdownHook(new Thread(emf::close));
return emf;
@Provides
@Singleton
@AppEngineJpaTm
public static JpaTransactionManager providesAppEngineJpaTm(
@Config("cloudSqlUsername") String username,
KmsKeyring kmsKeyring,
@PartialCloudSqlConfigs ImmutableMap<String, String> cloudSqlConfigs,
Clock clock) {
HashMap<String, String> overrides = Maps.newHashMap(cloudSqlConfigs);
overrides.put(Environment.USER, username);
overrides.put(Environment.PASS, kmsKeyring.getCloudSqlPassword());
return new JpaTransactionManagerImpl(create(overrides), clock);
}
@Provides
@Singleton
@NomulusToolJpaTm
public static JpaTransactionManager providesNomulusToolJpaTm(
@Config("toolsCloudSqlUsername") String username,
KmsKeyring kmsKeyring,
@PartialCloudSqlConfigs ImmutableMap<String, String> cloudSqlConfigs,
Clock clock) {
HashMap<String, String> overrides = Maps.newHashMap(cloudSqlConfigs);
overrides.put(Environment.USER, username);
overrides.put(Environment.PASS, kmsKeyring.getToolsCloudSqlPassword());
return new JpaTransactionManagerImpl(create(overrides), clock);
}
/** Constructs the {@link EntityManagerFactory} instance. */
@@ -102,25 +133,42 @@ public class PersistenceModule {
properties.put(Environment.URL, jdbcUrl);
properties.put(Environment.USER, username);
properties.put(Environment.PASS, password);
return create(ImmutableMap.copyOf(properties));
}
private static EntityManagerFactory create(Map<String, String> properties) {
// If there are no annotated classes, we can create the EntityManagerFactory from the generic
// method. Otherwise we have to use a more tailored approach. Note that this adds to the set
// of annotated classes defined in the configuration, it does not override them.
EntityManagerFactory emf =
Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME, properties);
Persistence.createEntityManagerFactory(
PERSISTENCE_UNIT_NAME, ImmutableMap.copyOf(properties));
checkState(
emf != null,
"Persistence.createEntityManagerFactory() returns a null EntityManagerFactory");
return emf;
}
/** Dagger qualifier for the {@link EntityManagerFactory} used for App Engine application. */
/** Dagger qualifier for {@link JpaTransactionManager} used for App Engine application. */
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface AppEngineEmf {}
@interface AppEngineJpaTm {}
/** Dagger qualifier for {@link JpaTransactionManager} used for Nomulus tool. */
@Qualifier
@Documented
@interface NomulusToolJpaTm {}
/** Dagger qualifier for the partial Cloud SQL configs. */
@Qualifier
@Documented
@interface PartialCloudSqlConfigs {}
/** Dagger qualifier for the default Hibernate configurations. */
// TODO(shicong): Change annotations in this class to none public or put them in a top level
// package
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface DefaultHibernateConfigs {}
}
@@ -0,0 +1,46 @@
// Copyright 2019 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;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import google.registry.model.UpdateAutoTimestamp;
import google.registry.util.DateTimeUtils;
import java.sql.Timestamp;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import javax.annotation.Nullable;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
/** JPA converter for storing/retrieving UpdateAutoTimestamp objects. */
@Converter(autoApply = true)
public class UpdateAutoTimestampConverter
implements AttributeConverter<UpdateAutoTimestamp, Timestamp> {
@Override
public Timestamp convertToDatabaseColumn(UpdateAutoTimestamp entity) {
return Timestamp.from(DateTimeUtils.toZonedDateTime(jpaTm().getTransactionTime()).toInstant());
}
@Override
@Nullable
public UpdateAutoTimestamp convertToEntityAttribute(@Nullable Timestamp columnValue) {
if (columnValue == null) {
return null;
}
ZonedDateTime zdt = ZonedDateTime.ofInstant(columnValue.toInstant(), ZoneOffset.UTC);
return UpdateAutoTimestamp.create(DateTimeUtils.toJodaDateTime(zdt));
}
}
@@ -0,0 +1,48 @@
// Copyright 2019 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;
import java.sql.Timestamp;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import javax.annotation.Nullable;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
/**
* JPA converter to for storing/retrieving {@link ZonedDateTime} objects.
*
* <p>Hibernate provides a default converter for {@link ZonedDateTime}, but it converts timestamp to
* a non-normalized format, e.g., 2019-09-01T01:01:01Z will be converted to
* 2019-09-01T01:01:01Z[UTC]. This converter solves that problem by explicitly calling {@link
* ZoneId#normalized()} to normalize the zone id.
*/
@Converter(autoApply = true)
public class ZonedDateTimeConverter implements AttributeConverter<ZonedDateTime, Timestamp> {
@Override
@Nullable
public Timestamp convertToDatabaseColumn(@Nullable ZonedDateTime attribute) {
return attribute == null ? null : Timestamp.from(attribute.toInstant());
}
@Override
@Nullable
public ZonedDateTime convertToEntityAttribute(@Nullable Timestamp dbData) {
return dbData == null
? null
: ZonedDateTime.ofInstant(dbData.toInstant(), ZoneId.of("UTC").normalized());
}
}
@@ -39,6 +39,7 @@ import google.registry.util.Retrier;
import google.registry.util.SendEmailService;
import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.mail.internet.InternetAddress;
@@ -102,7 +103,7 @@ public final class IcannReportingUploadAction implements Runnable {
final byte[] payload = readBytesFromGcs(gcsFilename);
return icannReporter.send(payload, reportFilename);
},
IOException.class);
IcannReportingUploadAction::isUploadFailureRetryable);
} catch (RuntimeException e) {
logger.atWarning().withCause(e).log("Upload to %s failed.", gcsFilename);
}
@@ -115,6 +116,21 @@ public final class IcannReportingUploadAction implements Runnable {
String.format("OK, attempted uploading %d reports", manifestedFiles.size()));
}
/** Don't retry when reports are already uploaded or can't be uploaded. */
private static final String ICANN_UPLOAD_PERMANENT_ERROR_MESSAGE =
"A report for that month already exists, the cut-off date already passed.";
/** Don't retry when the IP address isn't whitelisted, as retries go through the same IP. */
private static final Pattern ICANN_UPLOAD_WHITELIST_ERROR =
Pattern.compile("Your IP address .+ is not allowed to connect");
/** Predicate to retry uploads on IOException, so long as they aren't non-retryable errors. */
private static boolean isUploadFailureRetryable(Throwable e) {
return (e instanceof IOException)
&& !e.getMessage().contains(ICANN_UPLOAD_PERMANENT_ERROR_MESSAGE)
&& !ICANN_UPLOAD_WHITELIST_ERROR.matcher(e.getMessage()).matches();
}
private void emailUploadResults(ImmutableMap<String, Boolean> reportSummary) {
String subject = String.format(
"ICANN Monthly report upload summary: %d/%d succeeded",
@@ -15,11 +15,11 @@
package google.registry.schema.domain;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.util.DateTimeUtils.toJodaDateTime;
import static google.registry.util.DateTimeUtils.toZonedDateTime;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import google.registry.model.Buildable;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.ImmutableObject;
import google.registry.util.DateTimeUtils;
import java.time.ZonedDateTime;
@@ -56,14 +56,18 @@ import org.joda.time.DateTime;
* Unique constraint to get around Hibernate's failure to handle auto-increment field in
* composite primary key.
*
* <p>Note: because of this index, physical columns must be declared in the {@link Column}
* annotations for {@link RegistryLock#revisionId} and {@link RegistryLock#repoId} fields.
* <p>Note: indexes use the camelCase version of the field names because the {@link
* google.registry.persistence.NomulusNamingStrategy} does not translate the field name into the
* snake_case column name until the write itself.
*/
indexes =
@Index(
name = "idx_registry_lock_repo_id_revision_id",
columnList = "repo_id, revision_id",
unique = true))
indexes = {
@Index(
name = "idx_registry_lock_repo_id_revision_id",
columnList = "repoId, revisionId",
unique = true),
@Index(name = "idx_registry_lock_verification_code", columnList = "verificationCode"),
@Index(name = "idx_registry_lock_registrar_id", columnList = "registrarId")
})
public final class RegistryLock extends ImmutableObject implements Buildable {
/** Describes the action taken by the user. */
@@ -74,11 +78,11 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "revision_id", nullable = false)
@Column(nullable = false)
private Long revisionId;
/** EPP repo ID of the domain in question. */
@Column(name = "repo_id", nullable = false)
@Column(nullable = false)
private String repoId;
// TODO (b/140568328): remove this when everything is in Cloud SQL and we can join on "domain"
@@ -104,7 +108,7 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
/** Creation timestamp is when the lock/unlock is first requested. */
@Column(nullable = false)
private ZonedDateTime creationTimestamp;
private CreateAutoTimestamp creationTimestamp = CreateAutoTimestamp.create(null);
/**
* Completion timestamp is when the user has verified the lock/unlock, when this object de facto
@@ -148,7 +152,7 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
}
public DateTime getCreationTimestamp() {
return toJodaDateTime(creationTimestamp);
return creationTimestamp.getTimestamp();
}
/** Returns the completion timestamp, or empty if this lock has not been completed yet. */
@@ -168,6 +172,10 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
return revisionId;
}
public void setCompletionTimestamp(DateTime dateTime) {
this.completionTimestamp = toZonedDateTime(dateTime);
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
@@ -187,7 +195,6 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
checkArgumentNotNull(getInstance().domainName, "Domain name cannot be null");
checkArgumentNotNull(getInstance().registrarId, "Registrar ID cannot be null");
checkArgumentNotNull(getInstance().action, "Action cannot be null");
checkArgumentNotNull(getInstance().creationTimestamp, "Creation timestamp cannot be null");
checkArgumentNotNull(getInstance().verificationCode, "Verification codecannot be null");
checkArgument(
getInstance().registrarPocId != null || getInstance().isSuperuser,
@@ -220,8 +227,8 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
return this;
}
public Builder setCreationTimestamp(DateTime creationTimestamp) {
getInstance().creationTimestamp = toZonedDateTime(creationTimestamp);
public Builder setCreationTimestamp(CreateAutoTimestamp creationTimestamp) {
getInstance().creationTimestamp = creationTimestamp;
return this;
}
@@ -14,10 +14,14 @@
package google.registry.schema.tld;
import static com.google.common.base.Charsets.US_ASCII;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.hash.Funnels.stringFunnel;
import com.google.common.collect.ImmutableMap;
import com.google.common.hash.BloomFilter;
import google.registry.model.CreateAutoTimestamp;
import java.math.BigDecimal;
import java.time.ZonedDateTime;
import java.util.Map;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
@@ -26,10 +30,12 @@ import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.MapKeyColumn;
import javax.persistence.Table;
import org.joda.money.CurrencyUnit;
import org.joda.time.DateTime;
/**
* A list of premium prices for domain names.
@@ -40,47 +46,55 @@ import org.joda.money.CurrencyUnit;
* This is fine though, because we only use the list with the highest revisionId.
*/
@Entity
@Table(name = "PremiumList")
@Table(indexes = {@Index(columnList = "name", name = "premiumlist_name_idx")})
public class PremiumList {
@Column(nullable = false)
private String name;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "revision_id")
@Column(nullable = false)
private Long revisionId;
@Column(name = "creation_timestamp", nullable = false)
private ZonedDateTime creationTimestamp;
@Column(nullable = false)
private CreateAutoTimestamp creationTimestamp = CreateAutoTimestamp.create(null);
@Column(name = "currency", nullable = false)
@Column(nullable = false)
private CurrencyUnit currency;
@ElementCollection
@CollectionTable(
name = "PremiumEntry",
joinColumns = @JoinColumn(name = "revision_id", referencedColumnName = "revision_id"))
@MapKeyColumn(name = "domain_label")
joinColumns = @JoinColumn(name = "revisionId", referencedColumnName = "revisionId"))
@MapKeyColumn(name = "domainLabel")
@Column(name = "price", nullable = false)
private Map<String, BigDecimal> labelsToPrices;
private PremiumList(
ZonedDateTime creationTimestamp,
CurrencyUnit currency,
Map<String, BigDecimal> labelsToPrices) {
this.creationTimestamp = creationTimestamp;
@Column(nullable = false)
private BloomFilter<String> bloomFilter;
private PremiumList(String name, CurrencyUnit currency, Map<String, BigDecimal> labelsToPrices) {
this.name = name;
this.currency = currency;
this.labelsToPrices = labelsToPrices;
// ASCII is used for the charset because all premium list domain labels are stored punycoded.
this.bloomFilter = BloomFilter.create(stringFunnel(US_ASCII), labelsToPrices.size());
labelsToPrices.keySet().forEach(this.bloomFilter::put);
}
// Hibernate requires this default constructor.
private PremiumList() {}
// TODO(mcilwain): Change creationTimestamp to Joda DateTime.
/** Constructs a {@link PremiumList} object. */
public static PremiumList create(
ZonedDateTime creationTimestamp,
CurrencyUnit currency,
Map<String, BigDecimal> labelsToPrices) {
return new PremiumList(creationTimestamp, currency, labelsToPrices);
String name, CurrencyUnit currency, Map<String, BigDecimal> labelsToPrices) {
return new PremiumList(name, currency, labelsToPrices);
}
/** Returns the name of the premium list, which is usually also a TLD string. */
public String getName() {
return name;
}
/** Returns the ID of this revision, or throws if null. */
@@ -91,12 +105,23 @@ public class PremiumList {
}
/** Returns the creation time of this revision of the premium list. */
public ZonedDateTime getCreationTimestamp() {
return creationTimestamp;
public DateTime getCreationTimestamp() {
return creationTimestamp.getTimestamp();
}
/** Returns a {@link Map} of domain labels to prices. */
public Map<String, BigDecimal> getLabelsToPrices() {
return labelsToPrices;
public ImmutableMap<String, BigDecimal> getLabelsToPrices() {
return ImmutableMap.copyOf(labelsToPrices);
}
/**
* Returns a Bloom filter to determine whether a label might be premium, or is definitely not.
*
* <p>If the domain label might be premium, then the next step is to check for the existence of a
* corresponding row in the PremiumListEntry table. Otherwise, we know for sure it's not premium,
* and no DB load is required.
*/
public BloomFilter<String> getBloomFilter() {
return bloomFilter;
}
}
@@ -0,0 +1,56 @@
// Copyright 2019 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.tld;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
/** Data access object class for {@link PremiumList}. */
public class PremiumListDao {
/** Persist a new premium list to Cloud SQL. */
public static void saveNew(PremiumList premiumList) {
jpaTm()
.transact(
() -> {
checkArgument(
!checkExists(premiumList.getName()),
"A premium list of this name already exists: %s.",
premiumList.getName());
jpaTm().getEntityManager().persist(premiumList);
});
}
/**
* Returns whether the premium list of the given name exists.
*
* <p>This means that at least one premium list revision must exist for the given name.
*/
public static boolean checkExists(String premiumListName) {
return jpaTm()
.transact(
() ->
jpaTm()
.getEntityManager()
.createQuery("SELECT 1 FROM PremiumList WHERE name = :name", Integer.class)
.setParameter("name", premiumListName)
.setMaxResults(1)
.getResultList()
.size()
> 0);
}
private PremiumListDao() {}
}
@@ -15,7 +15,11 @@
package google.registry.schema.tmch;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.util.DateTimeUtils.toJodaDateTime;
import static google.registry.util.DateTimeUtils.toZonedDateTime;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.ImmutableObject;
import java.time.ZonedDateTime;
import java.util.Map;
import java.util.Optional;
@@ -29,6 +33,7 @@ import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.MapKeyColumn;
import javax.persistence.Table;
import org.joda.time.DateTime;
/**
* A list of TMCH claims labels and their associated claims keys.
@@ -40,26 +45,29 @@ import javax.persistence.Table;
* highest {@link #revisionId}.
*/
@Entity
@Table(name = "ClaimsList")
public class ClaimsList {
@Table
public class ClaimsList extends ImmutableObject {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "revision_id")
@Column
private Long revisionId;
@Column(name = "creation_timestamp", nullable = false)
private ZonedDateTime creationTimestamp;
@Column(nullable = false)
private CreateAutoTimestamp creationTimestamp = CreateAutoTimestamp.create(null);
@Column(nullable = false)
private ZonedDateTime tmdbGenerationTime;
@ElementCollection
@CollectionTable(
name = "ClaimsEntry",
joinColumns = @JoinColumn(name = "revision_id", referencedColumnName = "revision_id"))
@MapKeyColumn(name = "domain_label", nullable = false)
@Column(name = "claim_key", nullable = false)
joinColumns = @JoinColumn(name = "revisionId", referencedColumnName = "revisionId"))
@MapKeyColumn(name = "domainLabel", nullable = false)
@Column(name = "claimKey", nullable = false)
private Map<String, String> labelsToKeys;
private ClaimsList(ZonedDateTime creationTimestamp, Map<String, String> labelsToKeys) {
this.creationTimestamp = creationTimestamp;
private ClaimsList(ZonedDateTime tmdbGenerationTime, Map<String, String> labelsToKeys) {
this.tmdbGenerationTime = tmdbGenerationTime;
this.labelsToKeys = labelsToKeys;
}
@@ -67,9 +75,8 @@ public class ClaimsList {
private ClaimsList() {}
/** Constructs a {@link ClaimsList} object. */
public static ClaimsList create(
ZonedDateTime creationTimestamp, Map<String, String> labelsToKeys) {
return new ClaimsList(creationTimestamp, labelsToKeys);
public static ClaimsList create(DateTime creationTimestamp, Map<String, String> labelsToKeys) {
return new ClaimsList(toZonedDateTime(creationTimestamp), labelsToKeys);
}
/** Returns the revision id of this claims list, or throws exception if it is null. */
@@ -79,9 +86,14 @@ public class ClaimsList {
return revisionId;
}
/** Returns the TMDB generation time of this claims list. */
public DateTime getTmdbGenerationTime() {
return toJodaDateTime(tmdbGenerationTime);
}
/** Returns the creation time of this claims list. */
public ZonedDateTime getCreationTimestamp() {
return creationTimestamp;
public DateTime getCreationTimestamp() {
return creationTimestamp.getTimestamp();
}
/** Returns an {@link Map} mapping domain label to its lookup key. */
@@ -18,7 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import google.registry.model.tmch.ClaimsListShard;
import google.registry.schema.tmch.ClaimsList;
import java.util.List;
import org.joda.time.DateTime;
@@ -34,11 +34,11 @@ import org.joda.time.DateTime;
public class ClaimsListParser {
/**
* Converts the lines from the DNL CSV file into a {@link ClaimsListShard} object.
* Converts the lines from the DNL CSV file into a {@link ClaimsList} object.
*
* <p>Please note that this does <b>not</b> insert the object into Datastore.
*/
public static ClaimsListShard parse(List<String> lines) {
public static ClaimsList parse(List<String> lines) {
ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>();
// First line: <version>,<DNL List creation datetime>
@@ -74,6 +74,6 @@ public class ClaimsListParser {
builder.put(label, lookupKey);
}
return ClaimsListShard.create(creationTime, builder.build());
return ClaimsList.create(creationTime, builder.build());
}
}
@@ -18,9 +18,11 @@ import static google.registry.request.Action.Method.POST;
import com.google.common.flogger.FluentLogger;
import google.registry.keyring.api.KeyModule.Key;
import google.registry.model.tmch.ClaimsListDao;
import google.registry.model.tmch.ClaimsListShard;
import google.registry.request.Action;
import google.registry.request.auth.Auth;
import google.registry.schema.tmch.ClaimsList;
import java.io.IOException;
import java.security.SignatureException;
import java.util.List;
@@ -54,10 +56,14 @@ public final class TmchDnlAction implements Runnable {
} catch (SignatureException | IOException | PGPException e) {
throw new RuntimeException(e);
}
ClaimsListShard claims = ClaimsListParser.parse(lines);
claims.save();
ClaimsList claims = ClaimsListParser.parse(lines);
ClaimsListShard claimsListShard =
ClaimsListShard.create(claims.getTmdbGenerationTime(), claims.getLabelsToKeys());
claimsListShard.save();
logger.atInfo().log(
"Inserted %,d claims into Datastore, created at %s",
claims.size(), claims.getCreationTime());
claimsListShard.size(), claimsListShard.getCreationTime());
ClaimsListDao.trySave(claims);
}
}
@@ -0,0 +1,23 @@
// Copyright 2019 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;
/**
* Marker interface for commands that use Cloud Sql.
*
* <p>Just implementing this is sufficient to use Cloud Sql; {@link RegistryTool} will install it as
* needed.
*/
interface CommandWithCloudSql extends CommandWithRemoteApi {}
@@ -16,6 +16,7 @@ package google.registry.tools;
import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.security.JsonHttp.JSON_SAFETY_PREFIX;
import static google.registry.tools.server.CreateOrUpdatePremiumListAction.ALSO_CLOUD_SQL_PARAM;
import static google.registry.tools.server.CreateOrUpdatePremiumListAction.INPUT_PARAM;
import static google.registry.tools.server.CreateOrUpdatePremiumListAction.NAME_PARAM;
import static google.registry.util.ListNamingUtils.convertFilePathToName;
@@ -57,6 +58,12 @@ abstract class CreateOrUpdatePremiumListCommand extends ConfirmingCommand
required = true)
Path inputFile;
@Parameter(
names = {"--also_cloud_sql"},
description =
"Persist premium list to Cloud SQL in addition to Datastore; defaults to false.")
boolean alsoCloudSql;
protected AppEngineConnection connection;
protected int inputLineCount;
@@ -67,7 +74,7 @@ abstract class CreateOrUpdatePremiumListCommand extends ConfirmingCommand
abstract String getCommandPath();
ImmutableMap<String, ?> getParameterMap() {
ImmutableMap<String, String> getParameterMap() {
return ImmutableMap.of();
}
@@ -88,14 +95,15 @@ abstract class CreateOrUpdatePremiumListCommand extends ConfirmingCommand
@Override
public String execute() throws Exception {
ImmutableMap.Builder<String, Object> params = new ImmutableMap.Builder<>();
ImmutableMap.Builder<String, String> params = new ImmutableMap.Builder<>();
params.put(NAME_PARAM, name);
params.put(ALSO_CLOUD_SQL_PARAM, Boolean.toString(alsoCloudSql));
String inputFileContents = new String(Files.readAllBytes(inputFile), UTF_8);
String requestBody =
Joiner.on('&').withKeyValueSeparator("=").join(
ImmutableMap.of(INPUT_PARAM, URLEncoder.encode(inputFileContents, UTF_8.toString())));
ImmutableMap<String, ?> extraParams = getParameterMap();
ImmutableMap<String, String> extraParams = getParameterMap();
if (extraParams != null) {
params.putAll(extraParams);
}
@@ -110,7 +118,7 @@ abstract class CreateOrUpdatePremiumListCommand extends ConfirmingCommand
// TODO(user): refactor this behavior into a better general-purpose
// response validation that can be re-used across the new client/server commands.
String extractServerResponse(String response) {
private String extractServerResponse(String response) {
Map<String, Object> responseMap = toMap(JSONValue.parse(stripJsonPrefix(response)));
// TODO(user): consider using jart's FormField Framework.
@@ -127,7 +135,7 @@ abstract class CreateOrUpdatePremiumListCommand extends ConfirmingCommand
}
// TODO(user): figure out better place to put this method to make it re-usable
static String stripJsonPrefix(String json) {
private static String stripJsonPrefix(String json) {
Verify.verify(json.startsWith(JSON_SAFETY_PREFIX));
return json.substring(JSON_SAFETY_PREFIX.length());
}
@@ -36,12 +36,11 @@ public class CreatePremiumListCommand extends CreateOrUpdatePremiumListCommand {
}
@Override
ImmutableMap<String, ?> getParameterMap() {
ImmutableMap<String, String> getParameterMap() {
if (override) {
return ImmutableMap.of("override", override);
return ImmutableMap.of("override", "true");
} else {
return ImmutableMap.of();
}
}
}
@@ -19,32 +19,23 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.eppcommon.Trid;
import google.registry.model.transfer.BaseTransferObject;
import google.registry.model.transfer.TransferData;
import google.registry.persistence.NomulusNamingStrategy;
import google.registry.schema.domain.RegistryLock;
import google.registry.schema.tld.PremiumList;
import google.registry.schema.tmch.ClaimsList;
import google.registry.persistence.NomulusPostgreSQLDialect;
import google.registry.persistence.PersistenceModule;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.Types;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.PostgreSQL95Dialect;
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
import org.hibernate.jpa.boot.internal.PersistenceXmlParser;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.hibernate.tool.schema.TargetType;
import org.joda.time.Period;
import org.testcontainers.containers.PostgreSQLContainer;
/**
@@ -57,22 +48,6 @@ import org.testcontainers.containers.PostgreSQLContainer;
@Parameters(separators = " =", commandDescription = "Generate PostgreSQL schema.")
public class GenerateSqlSchemaCommand implements Command {
// TODO(mmuller): These should be read from persistence.xml so we don't need to maintain two
// separate lists of all SQL table classes.
private static final ImmutableSet<Class> SQL_TABLE_CLASSES =
ImmutableSet.of(
BaseTransferObject.class,
ClaimsList.class,
DelegationSignerData.class,
DesignatedContact.class,
DomainBase.class,
GracePeriod.class,
Period.class,
PremiumList.class,
RegistryLock.class,
TransferData.class,
Trid.class);
@VisibleForTesting
public static final String DB_OPTIONS_CLASH =
"Database host and port may not be specified along with the option to start a "
@@ -159,7 +134,9 @@ public class GenerateSqlSchemaCommand implements Command {
MetadataSources metadata =
new MetadataSources(new StandardServiceRegistryBuilder().applySettings(settings).build());
SQL_TABLE_CLASSES.forEach(metadata::addAnnotatedClass);
addAnnotatedClasses(metadata, settings);
SchemaExport schemaExport = new SchemaExport();
schemaExport.setHaltOnError(true);
schemaExport.setFormat(true);
@@ -199,12 +176,29 @@ public class GenerateSqlSchemaCommand implements Command {
}
}
/** Nomulus mapping rules for column types in Postgresql. */
public static class NomulusPostgreSQLDialect extends PostgreSQL95Dialect {
public NomulusPostgreSQLDialect() {
super();
registerColumnType(Types.VARCHAR, "text");
registerColumnType(Types.TIMESTAMP, "timestamptz");
private void addAnnotatedClasses(MetadataSources metadata, Map<String, String> settings) {
ParsedPersistenceXmlDescriptor descriptor =
PersistenceXmlParser.locatePersistenceUnits(settings).stream()
.filter(unit -> PersistenceModule.PERSISTENCE_UNIT_NAME.equals(unit.getName()))
.findFirst()
.orElseThrow(
() ->
new IllegalArgumentException(
String.format(
"Could not find persistence unit with name %s",
PersistenceModule.PERSISTENCE_UNIT_NAME)));
List<String> classNames = descriptor.getManagedClassNames();
for (String className : classNames) {
try {
Class<?> clazz = Class.forName(className);
metadata.addAnnotatedClass(clazz);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(
String.format(
"Could not load class with name %s present in persistence.xml", className),
e);
}
}
}
}
@@ -31,6 +31,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import google.registry.config.RegistryConfig;
import google.registry.model.ofy.ObjectifyService;
import google.registry.model.transaction.TransactionManagerFactory;
import google.registry.tools.AuthModule.LoginRequiredException;
import google.registry.tools.params.ParameterFactory;
import java.io.ByteArrayInputStream;
@@ -210,6 +211,8 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
}
// CommandWithRemoteApis need to have the remote api installed to work.
// CommandWithCloudSql extends CommandWithRemoteApi so the command will also get the remote
// api installed. This is because the DB password is stored in Datastore.
if (command instanceof CommandWithRemoteApi) {
if (installer == null) {
installer = new RemoteApiInstaller();
@@ -233,6 +236,10 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
ofy().clearSessionCache();
}
if (command instanceof CommandWithCloudSql) {
TransactionManagerFactory.initForTool();
}
command.run();
}
@@ -21,7 +21,9 @@ import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.base.Joiner;
import com.google.common.io.Files;
import google.registry.model.tmch.ClaimsListDao;
import google.registry.model.tmch.ClaimsListShard;
import google.registry.schema.tmch.ClaimsList;
import google.registry.tmch.ClaimsListParser;
import java.io.File;
import java.io.IOException;
@@ -30,14 +32,19 @@ import java.util.List;
/** A command to upload a {@link ClaimsListShard}. */
@Parameters(separators = " =", commandDescription = "Manually upload a new claims list file")
final class UploadClaimsListCommand extends ConfirmingCommand implements CommandWithRemoteApi {
final class UploadClaimsListCommand extends ConfirmingCommand implements CommandWithCloudSql {
@Parameter(description = "Claims list filename")
private List<String> mainParameters = new ArrayList<>();
@Parameter(
names = {"--also_cloud_sql"},
description = "Persist claims list to Cloud SQL in addition to Datastore; defaults to false.")
boolean alsoCloudSql;
private String claimsListFilename;
private ClaimsListShard claimsList;
private ClaimsList claimsList;
@Override
protected void init() throws IOException {
@@ -56,7 +63,10 @@ final class UploadClaimsListCommand extends ConfirmingCommand implements Command
@Override
public String execute() {
claimsList.save();
ClaimsListShard.create(claimsList.getTmdbGenerationTime(), claimsList.getLabelsToKeys()).save();
if (alsoCloudSql) {
ClaimsListDao.trySave(claimsList);
}
return String.format("Successfully uploaded claims list %s", claimsListFilename);
}
}
@@ -14,17 +14,28 @@
package google.registry.tools.server;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.flogger.LazyArgs.lazy;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.flogger.FluentLogger;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumList.PremiumListEntry;
import google.registry.request.JsonResponse;
import google.registry.request.Parameter;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.joda.money.CurrencyUnit;
/**
* Abstract base class for actions that update premium lists.
*/
/** Abstract base class for actions that update premium lists. */
public abstract class CreateOrUpdatePremiumListAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -33,24 +44,70 @@ public abstract class CreateOrUpdatePremiumListAction implements Runnable {
public static final String NAME_PARAM = "name";
public static final String INPUT_PARAM = "inputData";
public static final String ALSO_CLOUD_SQL_PARAM = "alsoCloudSql";
@Inject JsonResponse response;
@Inject @Parameter("premiumListName") String name;
@Inject @Parameter(INPUT_PARAM) String inputData;
@Inject
@Parameter("premiumListName")
String name;
@Inject
@Parameter(INPUT_PARAM)
String inputData;
@Inject
@Parameter(ALSO_CLOUD_SQL_PARAM)
boolean alsoCloudSql;
@Override
public void run() {
try {
savePremiumList();
saveToDatastore();
} catch (IllegalArgumentException e) {
logger.atInfo().withCause(e).log(
"Usage error in attempting to save premium list from nomulus tool command");
response.setPayload(ImmutableMap.of("error", e.toString(), "status", "error"));
return;
} catch (Exception e) {
logger.atSevere().withCause(e).log(
"Unexpected error saving premium list from nomulus tool command");
"Unexpected error saving premium list to Datastore from nomulus tool command");
response.setPayload(ImmutableMap.of("error", e.toString(), "status", "error"));
return;
}
if (alsoCloudSql) {
try {
saveToCloudSql();
} catch (Throwable e) {
logger.atSevere().withCause(e).log(
"Unexpected error saving premium list to Cloud SQL from nomulus tool command");
response.setPayload(ImmutableMap.of("error", e.toString(), "status", "error"));
return;
}
}
}
google.registry.schema.tld.PremiumList parseInputToPremiumList() {
List<String> inputDataPreProcessed =
Splitter.on('\n').omitEmptyStrings().splitToList(inputData);
ImmutableMap<String, PremiumListEntry> prices =
new PremiumList.Builder().setName(name).build().parse(inputDataPreProcessed);
ImmutableSet<CurrencyUnit> currencies =
prices.values().stream()
.map(e -> e.getValue().getCurrencyUnit())
.distinct()
.collect(toImmutableSet());
checkArgument(
currencies.size() == 1,
"The Cloud SQL schema requires exactly one currency, but got: %s",
ImmutableSortedSet.copyOf(currencies));
CurrencyUnit currency = Iterables.getOnlyElement(currencies);
Map<String, BigDecimal> priceAmounts =
Maps.transformValues(prices, ple -> ple.getValue().getAmount());
return google.registry.schema.tld.PremiumList.create(name, currency, priceAmounts);
}
/** Logs the premium list data at INFO, truncated if too long. */
@@ -64,6 +121,9 @@ public abstract class CreateOrUpdatePremiumListAction implements Runnable {
: (inputData.substring(0, MAX_LOGGING_PREMIUM_LIST_LENGTH) + "<truncated>")));
}
/** Creates a new premium list or updates an existing one. */
protected abstract void savePremiumList();
/** Saves the premium list to Datastore. */
protected abstract void saveToDatastore();
/** Saves the premium list to Cloud SQL. */
protected abstract void saveToCloudSql();
}
@@ -27,6 +27,7 @@ import google.registry.model.registry.label.PremiumList;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.schema.tld.PremiumListDao;
import java.util.List;
import javax.inject.Inject;
@@ -50,7 +51,7 @@ public class CreatePremiumListAction extends CreateOrUpdatePremiumListAction {
@Inject CreatePremiumListAction() {}
@Override
protected void savePremiumList() {
protected void saveToDatastore() {
checkArgument(
!doesPremiumListExist(name), "A premium list of this name already exists: %s.", name);
if (!override) {
@@ -71,4 +72,22 @@ public class CreatePremiumListAction extends CreateOrUpdatePremiumListAction {
logger.atInfo().log(message);
response.setPayload(ImmutableMap.of("status", "success", "message", message));
}
@Override
protected void saveToCloudSql() {
if (!override) {
assertTldExists(name);
}
logger.atInfo().log("Saving premium list to Cloud SQL for TLD %s", name);
// TODO(mcilwain): Call logInputData() here once Datastore persistence is removed.
google.registry.schema.tld.PremiumList premiumList = parseInputToPremiumList();
PremiumListDao.saveNew(premiumList);
String message =
String.format(
"Saved premium list %s with %d entries", name, premiumList.getLabelsToPrices().size());
logger.atInfo().log(message);
// TODO(mcilwain): Call response.setPayload(...) here once Datastore persistence is removed.
}
}
@@ -60,6 +60,12 @@ public class ToolsServerModule {
return extractRequiredParameter(req, CreatePremiumListAction.INPUT_PARAM);
}
@Provides
@Parameter("alsoCloudSql")
static boolean provideAlsoCloudSql(HttpServletRequest req) {
return extractBooleanParameter(req, CreatePremiumListAction.ALSO_CLOUD_SQL_PARAM);
}
@Provides
@Parameter("premiumListName")
static String provideName(HttpServletRequest req) {
@@ -46,7 +46,7 @@ public class UpdatePremiumListAction extends CreateOrUpdatePremiumListAction {
@Inject UpdatePremiumListAction() {}
@Override
protected void savePremiumList() {
protected void saveToDatastore() {
Optional<PremiumList> existingPremiumList = PremiumList.getUncached(name);
checkArgument(
existingPremiumList.isPresent(),
@@ -67,4 +67,11 @@ public class UpdatePremiumListAction extends CreateOrUpdatePremiumListAction {
logger.atInfo().log(message);
response.setPayload(ImmutableMap.of("status", "success", "message", message));
}
// TODO(mcilwain): Implement this in a subsequent PR.
@Override
protected void saveToCloudSql() {
throw new UnsupportedOperationException(
"Updating of premium lists in Cloud SQL is not supported yet");
}
}
@@ -21,7 +21,9 @@ import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import com.google.common.base.Ascii;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InternetDomainName;
import com.google.re2j.Pattern;
import google.registry.model.registrar.Registrar;
@@ -194,12 +196,10 @@ public final class RegistrarFormFields {
FormField.named("visibleInDomainWhoisAsAbuse", Boolean.class).build();
public static final FormField<String, String> CONTACT_PHONE_NUMBER_FIELD =
FormFields.PHONE_NUMBER.asBuilder()
.build();
FormFields.PHONE_NUMBER.asBuilder().build();
public static final FormField<String, String> CONTACT_FAX_NUMBER_FIELD =
FormFields.PHONE_NUMBER.asBuilderNamed("faxNumber")
.build();
FormFields.PHONE_NUMBER.asBuilderNamed("faxNumber").build();
public static final FormField<String, String> CONTACT_GAE_USER_ID_FIELD =
FormFields.NAME.asBuilderNamed("gaeUserId").build();
@@ -217,11 +217,8 @@ public final class RegistrarFormFields {
.asSet(Splitter.on(',').omitEmptyStrings().trimResults())
.build();
public static final FormField<List<Map<String, ?>>, List<RegistrarContact.Builder>>
CONTACTS_FIELD = FormField.mapNamed("contacts")
.transform(RegistrarContact.Builder.class, RegistrarFormFields::toRegistrarContactBuilder)
.asList()
.build();
public static final FormField<List<Map<String, ?>>, List<Map<String, ?>>> CONTACTS_AS_MAPS =
FormField.mapNamed("contacts").asList().build();
public static final FormField<List<String>, List<String>> I18N_STREET_FIELD =
FormFields.XS_NORMALIZED_STRING.asBuilderNamed("street")
@@ -344,33 +341,60 @@ public final class RegistrarFormFields {
}
}
private static @Nullable RegistrarContact.Builder toRegistrarContactBuilder(
@Nullable Map<String, ?> args) {
public static ImmutableList<RegistrarContact.Builder> getRegistrarContactBuilders(
ImmutableSet<RegistrarContact> existingContacts, @Nullable Map<String, ?> args) {
if (args == null) {
return null;
return ImmutableList.of();
}
RegistrarContact.Builder builder = new RegistrarContact.Builder();
CONTACT_NAME_FIELD.extractUntyped(args).ifPresent(builder::setName);
CONTACT_EMAIL_ADDRESS_FIELD.extractUntyped(args).ifPresent(builder::setEmailAddress);
CONTACT_VISIBLE_IN_WHOIS_AS_ADMIN_FIELD
.extractUntyped(args)
.ifPresent(builder::setVisibleInWhoisAsAdmin);
CONTACT_VISIBLE_IN_WHOIS_AS_TECH_FIELD
.extractUntyped(args)
.ifPresent(builder::setVisibleInWhoisAsTech);
PHONE_AND_EMAIL_VISIBLE_IN_DOMAIN_WHOIS_AS_ABUSE_FIELD
.extractUntyped(args)
.ifPresent(builder::setVisibleInDomainWhoisAsAbuse);
CONTACT_PHONE_NUMBER_FIELD.extractUntyped(args).ifPresent(builder::setPhoneNumber);
CONTACT_FAX_NUMBER_FIELD.extractUntyped(args).ifPresent(builder::setFaxNumber);
CONTACT_TYPES.extractUntyped(args).ifPresent(builder::setTypes);
CONTACT_GAE_USER_ID_FIELD.extractUntyped(args).ifPresent(builder::setGaeUserId);
CONTACT_ALLOWED_TO_SET_REGISTRY_LOCK_PASSWORD
.extractUntyped(args)
.ifPresent(builder::setAllowedToSetRegistryLockPassword);
Optional<List<Map<String, ?>>> contactsAsMaps = CONTACTS_AS_MAPS.extractUntyped(args);
if (!contactsAsMaps.isPresent()) {
return ImmutableList.of();
}
ImmutableList.Builder<RegistrarContact.Builder> result = new ImmutableList.Builder<>();
for (Map<String, ?> contactAsMap : contactsAsMaps.get()) {
String emailAddress =
CONTACT_EMAIL_ADDRESS_FIELD
.extractUntyped(contactAsMap)
.orElseThrow(
() -> new IllegalArgumentException("Contacts from UI must have email addresses"));
// Start with a new builder if the contact didn't previously exist
RegistrarContact.Builder contactBuilder =
existingContacts.stream()
.filter(rc -> rc.getEmailAddress().equals(emailAddress))
.findFirst()
.map(RegistrarContact::asBuilder)
.orElse(new RegistrarContact.Builder());
applyRegistrarContactArgs(contactBuilder, contactAsMap);
result.add(contactBuilder);
}
return result.build();
}
private static void applyRegistrarContactArgs(
RegistrarContact.Builder builder, Map<String, ?> args) {
builder.setName(CONTACT_NAME_FIELD.extractUntyped(args).orElse(null));
builder.setEmailAddress(CONTACT_EMAIL_ADDRESS_FIELD.extractUntyped(args).orElse(null));
builder.setVisibleInWhoisAsAdmin(
CONTACT_VISIBLE_IN_WHOIS_AS_ADMIN_FIELD.extractUntyped(args).orElse(false));
builder.setVisibleInWhoisAsTech(
CONTACT_VISIBLE_IN_WHOIS_AS_TECH_FIELD.extractUntyped(args).orElse(false));
builder.setVisibleInDomainWhoisAsAbuse(
PHONE_AND_EMAIL_VISIBLE_IN_DOMAIN_WHOIS_AS_ABUSE_FIELD.extractUntyped(args).orElse(false));
builder.setPhoneNumber(CONTACT_PHONE_NUMBER_FIELD.extractUntyped(args).orElse(null));
builder.setFaxNumber(CONTACT_FAX_NUMBER_FIELD.extractUntyped(args).orElse(null));
builder.setTypes(CONTACT_TYPES.extractUntyped(args).orElse(ImmutableSet.of()));
builder.setGaeUserId(CONTACT_GAE_USER_ID_FIELD.extractUntyped(args).orElse(null));
builder.setAllowedToSetRegistryLockPassword(
CONTACT_ALLOWED_TO_SET_REGISTRY_LOCK_PASSWORD.extractUntyped(args).orElse(false));
// Registry lock password does not need to be set every time
CONTACT_REGISTRY_LOCK_PASSWORD_FIELD
.extractUntyped(args)
.ifPresent(builder::setRegistryLockPassword);
return builder;
.ifPresent(
password -> {
if (!Strings.isNullOrEmpty(password)) {
builder.setRegistryLockPassword(password);
}
});
}
}
@@ -60,7 +60,6 @@ import google.registry.util.CollectionUtils;
import google.registry.util.DiffUtils;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -175,8 +174,7 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
}
private RegistrarResult update(final Map<String, ?> args, String clientId) {
return tm()
.transact(
return tm().transact(
() -> {
// We load the registrar here rather than outside of the transaction - to make
// sure we have the latest version. This one is loaded inside the transaction, so it's
@@ -215,7 +213,8 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
updatedRegistrar = checkAndUpdateAdminControlledFields(updatedRegistrar, args);
// read the contacts from the request.
ImmutableSet<RegistrarContact> updatedContacts = readContacts(registrar, args);
ImmutableSet<RegistrarContact> updatedContacts =
readContacts(registrar, contacts, args);
// Save the updated contacts
if (!updatedContacts.equals(contacts)) {
@@ -240,11 +239,18 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
});
}
private Map<String, Object> expandRegistrarWithContacts(Iterable<RegistrarContact> contacts,
Registrar registrar) {
private Map<String, Object> expandRegistrarWithContacts(
Iterable<RegistrarContact> contacts, Registrar registrar) {
ImmutableSet<Map<String, Object>> expandedContacts =
Streams.stream(contacts)
.map(RegistrarContact::toDiffableFieldMap)
// Note: per the javadoc, toDiffableFieldMap includes sensitive data but we don't want
// to display it here
.peek(
map -> {
map.remove("registryLockPasswordHash");
map.remove("registryLockPasswordSalt");
})
.collect(toImmutableSet());
// Use LinkedHashMap here to preserve ordering; null values mean we can't use ImmutableMap.
LinkedHashMap<String, Object> result = new LinkedHashMap<>(registrar.toDiffableFieldMap());
@@ -377,16 +383,10 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
/** Reads the contacts from the supplied args. */
public static ImmutableSet<RegistrarContact> readContacts(
Registrar registrar, Map<String, ?> args) {
ImmutableSet.Builder<RegistrarContact> contacts = new ImmutableSet.Builder<>();
Optional<List<RegistrarContact.Builder>> builders =
RegistrarFormFields.CONTACTS_FIELD.extractUntyped(args);
if (builders.isPresent()) {
builders.get().forEach(c -> contacts.add(c.setParent(registrar).build()));
}
return contacts.build();
Registrar registrar, ImmutableSet<RegistrarContact> existingContacts, Map<String, ?> args) {
return RegistrarFormFields.getRegistrarContactBuilders(existingContacts, args).stream()
.map(builder -> builder.setParent(registrar).build())
.collect(toImmutableSet());
}
/**
@@ -452,8 +452,17 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
() ->
new FormException(
"Not allowed to set registry lock password directly on new contact"));
if (!existingContact.isAllowedToSetRegistryLockPassword()) {
throw new FormException("Registrar contact not allowed to set registry lock password");
if (updatedContact.isRegistryLockAllowed()) {
// the password must have been set before or the user was allowed to set it now
if (!existingContact.isAllowedToSetRegistryLockPassword()
&& !existingContact.isRegistryLockAllowed()) {
throw new FormException("Registrar contact not allowed to set registry lock password");
}
}
if (updatedContact.isAllowedToSetRegistryLockPassword()) {
if (!existingContact.isAllowedToSetRegistryLockPassword()) {
throw new FormException("Cannot set isAllowedToSetRegistryLockPassword through UI");
}
}
}
}
@@ -32,6 +32,12 @@
<class>google.registry.model.transfer.TransferData</class>
<class>google.registry.model.eppcommon.Trid</class>
<!-- Customized type converters -->
<class>google.registry.persistence.BloomFilterConverter</class>
<class>google.registry.persistence.CreateAutoTimestampConverter</class>
<class>google.registry.persistence.UpdateAutoTimestampConverter</class>
<class>google.registry.persistence.ZonedDateTimeConverter</class>
<!-- TODO(weiminyu): check out application-layer validation. -->
<validation-mode>NONE</validation-mode>
</persistence-unit>
@@ -37,17 +37,18 @@
{/call}
<p>Please work with the registrant to mitigate any security issues and have the
domains delisted.</p>
domains delisted. If you believe that any of the domains were reported in error, or are still
receiving reports for issues that have been remediated,
please <a href="https://safebrowsing.google.com/safebrowsing/report_error/?hl=en">submit a
request</a> to have the site reviewed.</p>
{call .resourceList}
{param resources: $resources /}
{/call}
<p>You will continue to receive a monthly summary of all domains managed by your registrar
that remain on our lists of potential security threats. You will additionally receive a daily
notice when any new domains that are added to these lists. Once the registrant has resolved
the security issues and followed the steps to have his or her domain reviewed and delisted
it will automatically be removed from our monthly reporting.</p>
that remain on our lists of potential security threats. You will also receive a daily
notice when any new domains are added to these lists.</p>
<p>If you have any questions regarding this notice, please contact {$replyToEmail}.</p>
{/template}
@@ -79,11 +80,14 @@
{param resources: $resources /}
{/call}
<p>If you believe that any of the domains were reported in error, or are still receiving
reports for issues that have been remediated,
please <a href="https://safebrowsing.google.com/safebrowsing/report_error/?hl=en">submit
a request</a> to have the site reviewed.</p>
<p>You will continue to receive daily notices when new domains managed by your registrar
are flagged for abuse, as well as a monthly summary of all of your domains under management
that remain flagged for abuse. Once the registrant has resolved the security issues and
followed the steps to have his or her domain reviewed and delisted it will automatically
be removed from our reporting.</p>
that remain flagged for abuse.</p>
<p>If you would like to change the email to which these notices are sent please update your
abuse contact using your registrar portal account.</p>
@@ -0,0 +1,138 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.registry;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.testing.JUnitBackports.assertThrows;
import google.registry.model.transaction.JpaTransactionManagerRule;
import google.registry.schema.domain.RegistryLock;
import google.registry.schema.domain.RegistryLock.Action;
import google.registry.testing.AppEngineRule;
import java.util.Optional;
import java.util.UUID;
import javax.persistence.PersistenceException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link RegistryLockDao}. */
@RunWith(JUnit4.class)
public final class RegistryLockDaoTest {
@Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build();
@Rule
public final JpaTransactionManagerRule jpaTmRule =
new JpaTransactionManagerRule.Builder().build();
@Test
public void testSaveAndLoad_success() {
RegistryLock lock = createLock();
RegistryLockDao.save(lock);
RegistryLock fromDatabase = RegistryLockDao.getByVerificationCode(lock.getVerificationCode());
assertThat(fromDatabase.getDomainName()).isEqualTo(lock.getDomainName());
assertThat(fromDatabase.getVerificationCode()).isEqualTo(lock.getVerificationCode());
}
@Test
public void testSaveAndLoad_failure_differentCode() {
RegistryLock lock = createLock();
RegistryLockDao.save(lock);
PersistenceException exception =
assertThrows(
PersistenceException.class,
() -> RegistryLockDao.getByVerificationCode(UUID.randomUUID().toString()));
assertThat(exception)
.hasCauseThat()
.hasMessageThat()
.isEqualTo("No registry lock with this code");
assertThat(exception).hasCauseThat().isInstanceOf(NullPointerException.class);
}
@Test
public void testSaveTwiceAndLoad_returnsLatest() {
RegistryLock lock = createLock();
jpaTm().transact(() -> RegistryLockDao.save(lock));
jpaTmRule.getTxnClock().advanceOneMilli();
jpaTm()
.transact(
() -> {
RegistryLock updatedLock =
RegistryLockDao.getByVerificationCode(lock.getVerificationCode());
updatedLock.setCompletionTimestamp(jpaTmRule.getTxnClock().nowUtc());
RegistryLockDao.save(updatedLock);
});
jpaTm()
.transact(
() -> {
RegistryLock fromDatabase =
RegistryLockDao.getByVerificationCode(lock.getVerificationCode());
assertThat(fromDatabase.getCompletionTimestamp().get())
.isEqualTo(jpaTmRule.getTxnClock().nowUtc());
});
}
@Test
public void testUpdateLock_usingSamePrimaryKey() {
RegistryLock lock = RegistryLockDao.save(createLock());
jpaTmRule.getTxnClock().advanceOneMilli();
RegistryLock updatedLock =
lock.asBuilder().setCompletionTimestamp(jpaTmRule.getTxnClock().nowUtc()).build();
jpaTm().transact(() -> RegistryLockDao.save(updatedLock));
jpaTm()
.transact(
() -> {
RegistryLock fromDatabase =
RegistryLockDao.getByVerificationCode(lock.getVerificationCode());
assertThat(fromDatabase.getCompletionTimestamp())
.isEqualTo(Optional.of(jpaTmRule.getTxnClock().nowUtc()));
});
}
@Test
public void testFailure_saveNull() {
assertThrows(NullPointerException.class, () -> RegistryLockDao.save(null));
}
@Test
public void testLoad_byRegistrarId() {
RegistryLock lock = createLock();
RegistryLock secondLock = createLock().asBuilder().setDomainName("otherexample.test").build();
RegistryLockDao.save(lock);
RegistryLockDao.save(secondLock);
assertThat(
RegistryLockDao.getByRegistrarId("TheRegistrar").stream()
.map(RegistryLock::getDomainName)
.collect(toImmutableSet()))
.containsExactly("example.test", "otherexample.test");
assertThat(RegistryLockDao.getByRegistrarId("nonexistent")).isEmpty();
}
private RegistryLock createLock() {
return new RegistryLock.Builder()
.setRepoId("repoId")
.setDomainName("example.test")
.setRegistrarId("TheRegistrar")
.setAction(Action.LOCK)
.setVerificationCode(UUID.randomUUID().toString())
.isSuperuser(true)
.build();
}
}
@@ -0,0 +1,95 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.tmch;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.JUnitBackports.assertThrows;
import com.google.common.collect.ImmutableMap;
import google.registry.model.transaction.JpaTransactionManagerRule;
import google.registry.schema.tmch.ClaimsList;
import google.registry.testing.FakeClock;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link ClaimsListDao}. */
@RunWith(JUnit4.class)
public class ClaimsListDaoTest {
private FakeClock fakeClock = new FakeClock();
@Rule
public final JpaTransactionManagerRule jpaTmRule =
new JpaTransactionManagerRule.Builder().build();
@Test
public void trySave_insertsClaimsListSuccessfully() {
ClaimsList claimsList =
ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2"));
ClaimsListDao.trySave(claimsList);
ClaimsList insertedClaimsList = ClaimsListDao.getCurrent();
assertClaimsListEquals(claimsList, insertedClaimsList);
assertThat(insertedClaimsList.getCreationTimestamp())
.isEqualTo(jpaTmRule.getTxnClock().nowUtc());
}
@Test
public void trySave_noExceptionThrownWhenSaveFail() {
ClaimsList claimsList =
ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2"));
ClaimsListDao.trySave(claimsList);
ClaimsList insertedClaimsList = ClaimsListDao.getCurrent();
assertClaimsListEquals(claimsList, insertedClaimsList);
// Save ClaimsList with existing revisionId should fail because revisionId is the primary key.
ClaimsListDao.trySave(insertedClaimsList);
}
@Test
public void trySave_claimsListWithNoEntries() {
ClaimsList claimsList = ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of());
ClaimsListDao.trySave(claimsList);
ClaimsList insertedClaimsList = ClaimsListDao.getCurrent();
assertClaimsListEquals(claimsList, insertedClaimsList);
assertThat(insertedClaimsList.getLabelsToKeys()).isEmpty();
}
@Test
public void getCurrent_throwsNoResultExceptionIfTableIsEmpty() {
PersistenceException thrown =
assertThrows(PersistenceException.class, () -> ClaimsListDao.getCurrent());
assertThat(thrown).hasCauseThat().isInstanceOf(NoResultException.class);
}
@Test
public void getCurrent_returnsLatestClaims() {
ClaimsList oldClaimsList =
ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2"));
ClaimsList newClaimsList =
ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label3", "key3", "label4", "key4"));
ClaimsListDao.trySave(oldClaimsList);
ClaimsListDao.trySave(newClaimsList);
assertClaimsListEquals(newClaimsList, ClaimsListDao.getCurrent());
}
private void assertClaimsListEquals(ClaimsList left, ClaimsList right) {
assertThat(left.getRevisionId()).isEqualTo(right.getRevisionId());
assertThat(left.getTmdbGenerationTime()).isEqualTo(right.getTmdbGenerationTime());
assertThat(left.getLabelsToKeys()).isEqualTo(right.getLabelsToKeys());
}
}
@@ -15,15 +15,31 @@
package google.registry.model.transaction;
import static org.joda.time.DateTimeZone.UTC;
import static org.testcontainers.containers.PostgreSQLContainer.POSTGRESQL_PORT;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.io.Resources;
import google.registry.persistence.PersistenceModule;
import google.registry.testing.FakeClock;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.persistence.EntityManagerFactory;
import org.hibernate.cfg.Environment;
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
import org.hibernate.jpa.boot.internal.PersistenceXmlParser;
import org.hibernate.jpa.boot.spi.Bootstrap;
import org.joda.time.DateTime;
import org.junit.rules.ExternalResource;
import org.junit.rules.RuleChain;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.testcontainers.containers.JdbcDatabaseContainer;
import org.testcontainers.containers.PostgreSQLContainer;
@@ -37,35 +53,57 @@ import org.testcontainers.containers.PostgreSQLContainer;
*/
public class JpaTransactionManagerRule extends ExternalResource {
private static final String SCHEMA_GOLDEN_SQL = "sql/schema/nomulus.golden.sql";
private static final String DB_CLEANUP_SQL =
"google/registry/model/transaction/cleanup_database.sql";
private static final String MANAGEMENT_DB_NAME = "management";
private static final String POSTGRES_DB_NAME = "postgres";
private final DateTime now = DateTime.now(UTC);
private final FakeClock clock = new FakeClock(now);
private final String initScript;
private JdbcDatabaseContainer database;
private final String initScriptPath;
private final ImmutableList<Class> extraEntityClasses;
private final ImmutableMap userProperties;
private static final JdbcDatabaseContainer database = create();
private EntityManagerFactory emf;
private JpaTransactionManager cachedTm;
private JpaTransactionManagerRule(String initScript) {
this.initScript = initScript;
private JpaTransactionManagerRule(
String initScriptPath,
ImmutableList<Class> extraEntityClasses,
ImmutableMap<String, String> userProperties) {
this.initScriptPath = initScriptPath;
this.extraEntityClasses = extraEntityClasses;
this.userProperties = userProperties;
}
/** Wraps {@link JpaTransactionManagerRule} in a {@link PostgreSQLContainer}. */
@Override
public Statement apply(Statement base, Description description) {
database = new PostgreSQLContainer().withInitScript(initScript);
return RuleChain.outerRule(database)
.around(JpaTransactionManagerRule.super::apply)
.apply(base, description);
private static JdbcDatabaseContainer create() {
PostgreSQLContainer container = new PostgreSQLContainer().withDatabaseName(MANAGEMENT_DB_NAME);
container.start();
Runtime.getRuntime().addShutdownHook(new Thread(() -> container.close()));
return container;
}
@Override
public void before() {
public void before() throws Exception {
executeSql(MANAGEMENT_DB_NAME, DB_CLEANUP_SQL);
executeSql(POSTGRES_DB_NAME, initScriptPath);
ImmutableMap properties = PersistenceModule.providesDefaultDatabaseConfigs();
if (!userProperties.isEmpty()) {
// If there are user properties, create a new properties object with these added.
ImmutableMap.Builder builder = properties.builder();
builder.putAll(userProperties);
properties = builder.build();
}
emf =
PersistenceModule.create(
database.getJdbcUrl(),
createEntityManagerFactory(
getJdbcUrlFor(POSTGRES_DB_NAME),
database.getUsername(),
database.getPassword(),
PersistenceModule.providesDefaultDatabaseConfigs());
properties,
extraEntityClasses);
JpaTransactionManagerImpl txnManager = new JpaTransactionManagerImpl(emf, clock);
cachedTm = TransactionManagerFactory.jpaTm;
TransactionManagerFactory.jpaTm = txnManager;
@@ -80,6 +118,65 @@ public class JpaTransactionManagerRule extends ExternalResource {
cachedTm = null;
}
private void executeSql(String dbName, String sqlScriptPath) {
try (Connection conn = createConnection(dbName)) {
String sqlScript = Resources.toString(Resources.getResource(sqlScriptPath), Charsets.UTF_8);
conn.createStatement().execute(sqlScript);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private String getJdbcUrlFor(String dbName) {
// Disable Postgres driver use of java.util.logging to reduce noise at startup time
return "jdbc:postgresql://"
+ database.getContainerIpAddress()
+ ":"
+ database.getMappedPort(POSTGRESQL_PORT)
+ "/"
+ dbName
+ "?loggerLevel=OFF";
}
private Connection createConnection(String dbName) {
final Properties info = new Properties();
info.put("user", database.getUsername());
info.put("password", database.getPassword());
final Driver jdbcDriverInstance = database.getJdbcDriverInstance();
try {
return jdbcDriverInstance.connect(getJdbcUrlFor(dbName), info);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/** Constructs the {@link EntityManagerFactory} instance. */
private static EntityManagerFactory createEntityManagerFactory(
String jdbcUrl,
String username,
String password,
ImmutableMap<String, String> configs,
ImmutableList<Class> extraEntityClasses) {
HashMap<String, String> properties = Maps.newHashMap(configs);
properties.put(Environment.URL, jdbcUrl);
properties.put(Environment.USER, username);
properties.put(Environment.PASS, password);
ParsedPersistenceXmlDescriptor descriptor =
PersistenceXmlParser.locatePersistenceUnits(properties).stream()
.filter(unit -> PersistenceModule.PERSISTENCE_UNIT_NAME.equals(unit.getName()))
.findFirst()
.orElseThrow(
() ->
new IllegalArgumentException(
String.format(
"Could not find persistence unit with name %s",
PersistenceModule.PERSISTENCE_UNIT_NAME)));
extraEntityClasses.stream().map(Class::getName).forEach(descriptor::addClasses);
return Bootstrap.getEntityManagerFactoryBuilder(descriptor, properties).build();
}
/** Returns the {@link FakeClock} used by the underlying {@link JpaTransactionManagerImpl}. */
public FakeClock getTxnClock() {
return clock;
@@ -88,6 +185,8 @@ public class JpaTransactionManagerRule extends ExternalResource {
/** Builder for {@link JpaTransactionManagerRule}. */
public static class Builder {
private String initScript;
private List<Class> extraEntityClasses = new ArrayList<Class>();
private Map<String, String> userProperties = new HashMap<String, String>();
/**
* Sets the SQL script to be used to initialize the database. If not set,
@@ -98,12 +197,27 @@ public class JpaTransactionManagerRule extends ExternalResource {
return this;
}
/** Adds annotated class(es) to the known entities for the database. */
public Builder withEntityClass(Class... classes) {
this.extraEntityClasses.addAll(ImmutableSet.copyOf(classes));
return this;
}
/** Adds the specified property to those used to initialize the transaction manager. */
public Builder withProperty(String name, String value) {
this.userProperties.put(name, value);
return this;
}
/** Builds a {@link JpaTransactionManagerRule} instance. */
public JpaTransactionManagerRule build() {
if (initScript == null) {
initScript = SCHEMA_GOLDEN_SQL;
}
return new JpaTransactionManagerRule(initScript);
return new JpaTransactionManagerRule(
initScript,
ImmutableList.copyOf(extraEntityClasses),
ImmutableMap.copyOf(userProperties));
}
}
}
@@ -18,8 +18,12 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.testing.JUnitBackports.assertThrows;
import google.registry.model.ImmutableObject;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.PersistenceException;
import org.hibernate.cfg.Environment;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,7 +35,10 @@ public class JpaTransactionManagerRuleTest {
@Rule
public final JpaTransactionManagerRule jpaTmRule =
new JpaTransactionManagerRule.Builder().build();
new JpaTransactionManagerRule.Builder()
.withEntityClass(TestEntity.class)
.withProperty(Environment.HBM2DDL_AUTO, "update")
.build();
@Test
public void verifiesRuleWorks() {
@@ -56,4 +63,28 @@ public class JpaTransactionManagerRuleTest {
assertThat(results).isEmpty();
});
}
@Test
public void testExtraParameters() {
// This test verifies that 1) withEntityClass() has registered TestEntity and 2) The table
// has been created, implying withProperty(HBM2DDL_AUTO, "update") worked.
TestEntity original = new TestEntity("key", "value");
jpaTm().transact(() -> jpaTm().getEntityManager().persist(original));
TestEntity retrieved =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "key"));
assertThat(retrieved).isEqualTo(original);
}
@Entity(name = "TestEntity") // Specify name to avoid nested class naming issues.
static class TestEntity extends ImmutableObject {
@Id String key;
String value;
TestEntity(String key, String value) {
this.key = key;
this.value = value;
}
TestEntity() {}
}
}
@@ -0,0 +1,67 @@
// Copyright 2019 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;
import static com.google.common.base.Charsets.US_ASCII;
import static com.google.common.hash.Funnels.stringFunnel;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import com.google.common.collect.ImmutableSet;
import com.google.common.hash.BloomFilter;
import google.registry.model.ImmutableObject;
import google.registry.model.transaction.JpaTransactionManagerRule;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.cfg.Environment;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class BloomFilterConverterTest {
@Rule
public final JpaTransactionManagerRule jpaTmRule =
new JpaTransactionManagerRule.Builder()
.withEntityClass(TestEntity.class)
.withProperty(Environment.HBM2DDL_AUTO, "update")
.build();
@Test
public void roundTripConversion_returnsSameBloomFilter() {
BloomFilter<String> bloomFilter = BloomFilter.create(stringFunnel(US_ASCII), 3);
ImmutableSet.of("foo", "bar", "baz").forEach(bloomFilter::put);
TestEntity entity = new TestEntity(bloomFilter);
jpaTm().transact(() -> jpaTm().getEntityManager().persist(entity));
TestEntity persisted =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.bloomFilter).isEqualTo(bloomFilter);
}
@Entity(name = "TestEntity") // Override entity name to avoid the nested class reference.
public static class TestEntity extends ImmutableObject {
@Id String name = "id";
BloomFilter<String> bloomFilter;
public TestEntity() {}
public TestEntity(BloomFilter<String> bloomFilter) {
this.bloomFilter = bloomFilter;
}
}
}
@@ -0,0 +1,78 @@
// Copyright 2019 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;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.ImmutableObject;
import google.registry.model.transaction.JpaTransactionManagerRule;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.cfg.Environment;
import org.joda.time.DateTime;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class CreateAutoTimestampConverterTest {
@Rule
public final JpaTransactionManagerRule jpaTmRule =
new JpaTransactionManagerRule.Builder()
.withEntityClass(TestEntity.class)
.withProperty(Environment.HBM2DDL_AUTO, "update")
.build();
@Test
public void testTypeConversion() {
CreateAutoTimestamp ts = CreateAutoTimestamp.create(DateTime.parse("2019-09-9T11:39:00Z"));
TestEntity ent = new TestEntity("myinst", ts);
jpaTm().transact(() -> jpaTm().getEntityManager().persist(ent));
TestEntity result =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "myinst"));
assertThat(result).isEqualTo(new TestEntity("myinst", ts));
}
@Test
public void testAutoInitialization() {
CreateAutoTimestamp ts = CreateAutoTimestamp.create(null);
TestEntity ent = new TestEntity("autoinit", ts);
jpaTm().transact(() -> jpaTm().getEntityManager().persist(ent));
TestEntity result =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "autoinit"));
assertThat(result.cat.getTimestamp()).isEqualTo(jpaTmRule.getTxnClock().nowUtc());
}
@Entity(name = "TestEntity") // Override entity name to avoid the nested class reference.
public static class TestEntity extends ImmutableObject {
@Id String name;
CreateAutoTimestamp cat;
public TestEntity() {}
public TestEntity(String name, CreateAutoTimestamp cat) {
this.name = name;
this.cat = cat;
}
}
}
@@ -0,0 +1,89 @@
// Copyright 2019 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;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import google.registry.model.ImmutableObject;
import google.registry.model.UpdateAutoTimestamp;
import google.registry.model.transaction.JpaTransactionManagerRule;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.cfg.Environment;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class UpdateAutoTimestampConverterTest {
@Rule
public final JpaTransactionManagerRule jpaTmRule =
new JpaTransactionManagerRule.Builder()
.withEntityClass(TestEntity.class)
.withProperty(Environment.HBM2DDL_AUTO, "update")
.build();
@Test
public void testTypeConversion() {
TestEntity ent = new TestEntity("myinst", null);
jpaTm().transact(() -> jpaTm().getEntityManager().persist(ent));
TestEntity result =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "myinst"));
assertThat(result.name).isEqualTo("myinst");
assertThat(result.uat.getTimestamp()).isEqualTo(jpaTmRule.getTxnClock().nowUtc());
}
@Test
public void testTimeChangesOnSubsequentTransactions() {
TestEntity ent1 = new TestEntity("myinst1", null);
jpaTm().transact(() -> jpaTm().getEntityManager().persist(ent1));
TestEntity result1 =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "myinst1"));
jpaTmRule.getTxnClock().advanceOneMilli();
TestEntity ent2 = new TestEntity("myinst2", result1.uat);
jpaTm().transact(() -> jpaTm().getEntityManager().persist(ent2));
TestEntity result2 =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "myinst2"));
assertThat(result1.uat.getTimestamp()).isNotEqualTo(result2.uat.getTimestamp());
assertThat(result2.uat.getTimestamp()).isEqualTo(jpaTmRule.getTxnClock().nowUtc());
}
@Entity(name = "TestEntity") // Override entity name to avoid the nested class reference.
public static class TestEntity extends ImmutableObject {
@Id String name;
UpdateAutoTimestamp uat;
public TestEntity() {}
public TestEntity(String name, UpdateAutoTimestamp uat) {
this.name = name;
this.uat = uat;
}
}
}
@@ -0,0 +1,121 @@
// Copyright 2019 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;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import google.registry.model.ImmutableObject;
import google.registry.model.transaction.JpaTransactionManagerRule;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.ZonedDateTime;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.cfg.Environment;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link ZonedDateTimeConverter}. */
@RunWith(JUnit4.class)
public class ZonedDateTimeConverterTest {
@Rule
public final JpaTransactionManagerRule jpaTmRule =
new JpaTransactionManagerRule.Builder()
.withEntityClass(TestEntity.class)
.withProperty(Environment.HBM2DDL_AUTO, "update")
.build();
private final ZonedDateTimeConverter converter = new ZonedDateTimeConverter();
@Test
public void convertToDatabaseColumn_returnsNullIfInputIsNull() {
assertThat(converter.convertToDatabaseColumn(null)).isNull();
}
@Test
public void convertToDatabaseColumn_convertsCorrectly() {
ZonedDateTime zonedDateTime = ZonedDateTime.parse("2019-09-01T01:01:01Z");
assertThat(converter.convertToDatabaseColumn(zonedDateTime).toInstant())
.isEqualTo(zonedDateTime.toInstant());
}
@Test
public void convertToEntityAttribute_returnsNullIfInputIsNull() {
assertThat(converter.convertToEntityAttribute(null)).isNull();
}
@Test
public void convertToEntityAttribute_convertsCorrectly() {
ZonedDateTime zonedDateTime = ZonedDateTime.parse("2019-09-01T01:01:01Z");
Instant instant = zonedDateTime.toInstant();
assertThat(converter.convertToEntityAttribute(Timestamp.from(instant)))
.isEqualTo(zonedDateTime);
}
@Test
public void converter_generatesTimestampWithNormalizedZone() {
ZonedDateTime zdt = ZonedDateTime.parse("2019-09-01T01:01:01Z");
TestEntity entity = new TestEntity("normalized_utc_time", zdt);
jpaTm().transact(() -> jpaTm().getEntityManager().persist(entity));
TestEntity retrievedEntity =
jpaTm()
.transact(
() -> jpaTm().getEntityManager().find(TestEntity.class, "normalized_utc_time"));
assertThat(retrievedEntity.zdt.toString()).isEqualTo("2019-09-01T01:01:01Z");
}
@Test
public void converter_convertsNonNormalizedZoneCorrectly() {
ZonedDateTime zdt = ZonedDateTime.parse("2019-09-01T01:01:01Z[UTC]");
TestEntity entity = new TestEntity("non_normalized_utc_time", zdt);
jpaTm().transact(() -> jpaTm().getEntityManager().persist(entity));
TestEntity retrievedEntity =
jpaTm()
.transact(
() -> jpaTm().getEntityManager().find(TestEntity.class, "non_normalized_utc_time"));
assertThat(retrievedEntity.zdt.toString()).isEqualTo("2019-09-01T01:01:01Z");
}
@Test
public void converter_convertsNonUtcZoneCorrectly() {
ZonedDateTime zdt = ZonedDateTime.parse("2019-09-01T01:01:01+05:00");
TestEntity entity = new TestEntity("new_york_time", zdt);
jpaTm().transact(() -> jpaTm().getEntityManager().persist(entity));
TestEntity retrievedEntity =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "new_york_time"));
assertThat(retrievedEntity.zdt.toString()).isEqualTo("2019-08-31T20:01:01Z");
}
@Entity(name = "TestEntity") // Override entity name to avoid the nested class reference.
private static class TestEntity extends ImmutableObject {
@Id String name;
ZonedDateTime zdt;
public TestEntity() {}
public TestEntity(String name, ZonedDateTime zdt) {
this.name = name;
this.zdt = zdt;
}
}
}
@@ -35,6 +35,7 @@ import google.registry.testing.FakeClock;
import google.registry.testing.FakeLockHandler;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -54,9 +55,11 @@ public class EscrowTaskRunnerTest {
private final EscrowTask task = mock(EscrowTask.class);
private final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01TZ"));
private DateTimeZone previousDateTimeZone;
private EscrowTaskRunner runner;
private Registry registry;
@Before
public void before() {
createTld("lol");
@@ -64,9 +67,15 @@ public class EscrowTaskRunnerTest {
runner = new EscrowTaskRunner();
runner.clock = clock;
runner.lockHandler = new FakeLockHandler(true);
previousDateTimeZone = DateTimeZone.getDefault();
DateTimeZone.setDefault(DateTimeZone.forID("America/New_York")); // Make sure UTC stuff works.
}
@After
public void after() {
DateTimeZone.setDefault(previousDateTimeZone);
}
@Test
public void testRun_cursorIsToday_advancesCursorToTomorrow() throws Exception {
clock.setTo(DateTime.parse("2006-06-06T00:30:00Z"));
@@ -154,6 +154,45 @@ public class IcannReportingUploadActionTest {
new InternetAddress("recipient@example.com"),
new InternetAddress("sender@example.com")));
}
@Test
public void testFailure_quicklySkipsOverNonRetryableUploadException() throws Exception {
runTest_nonRetryableException(
new IOException(
"<msg>A report for that month already exists, the cut-off date already"
+ " passed.</msg>"));
}
@Test
public void testFailure_quicklySkipsOverIpWhitelistException() throws Exception {
runTest_nonRetryableException(
new IOException("Your IP address 25.147.130.158 is not allowed to connect"));
}
private void runTest_nonRetryableException(Exception nonRetryableException) throws Exception {
IcannReportingUploadAction action = createAction();
when(mockReporter.send(PAYLOAD_FAIL, "a-activity-201706.csv"))
.thenThrow(nonRetryableException)
.thenThrow(
new AssertionError(
"This should never be thrown because the previous exception isn't retryable"));
action.run();
verify(mockReporter, times(1)).send(PAYLOAD_FAIL, "a-activity-201706.csv");
verify(mockReporter).send(PAYLOAD_SUCCESS, "test-transactions-201706.csv");
verifyNoMoreInteractions(mockReporter);
assertThat(((FakeResponse) action.response).getPayload())
.isEqualTo("OK, attempted uploading 2 reports");
verify(emailService)
.sendEmail(
EmailMessage.create(
"ICANN Monthly report upload summary: 1/2 succeeded",
"Report Filename - Upload status:\n"
+ "test-transactions-201706.csv - SUCCESS\n"
+ "a-activity-201706.csv - FAILURE",
new InternetAddress("recipient@example.com"),
new InternetAddress("sender@example.com")));
}
@Test
public void testFail_FileNotFound() throws Exception {
IcannReportingUploadAction action = createAction();
@@ -61,63 +61,40 @@ public class Spec11EmailUtilsTest {
private static final ImmutableList<String> FAKE_RESOURCES = ImmutableList.of("foo");
private static final String DAILY_EMAIL_FORMAT =
"Dear registrar partner,"
+ ""
+ "<p>Super Cool Registry conducts a daily analysis of all domains registered in its "
+ "TLDs to identify potential security concerns. On 2018-07-15, the following domains "
+ "that your registrar manages were flagged for potential security concerns:</p>"
+ ""
+ "<table>"
+ "<tr><th>Domain Name</th><th>Threat Type</th></tr>"
+ "%s"
+ "</table>"
+ "<p><b>Please communicate these findings to the registrant and work with the "
+ "registrant to mitigate any security issues and have the domains delisted.</b></p>"
+ ""
"Dear registrar partner,<p>Super Cool Registry conducts a daily analysis of all domains"
+ " registered in its TLDs to identify potential security concerns. On 2018-07-15, the"
+ " following domains that your registrar manages were flagged for potential security"
+ " concerns:</p><table><tr><th>Domain Name</th><th>Threat Type</th></tr>%s"
+ "</table><p><b>Please communicate these findings to the registrant and work with the"
+ " registrant to mitigate any security issues and have the domains delisted.</b></p>"
+ "Some helpful resources for getting off a blocked list include:"
+ "<ul><li>foo</li></ul><p>"
+ ""
+ "You will continue to receive daily notices when new domains managed by your registrar "
+ "are flagged for abuse, as well as a monthly summary of all of your domains under "
+ "management that remain flagged for abuse. Once the registrant has resolved the "
+ "security issues and followed the steps to have his or her domain reviewed and "
+ "delisted it will automatically be removed from our reporting.</p>"
+ ""
+ "<p>If you would like to change the email to which these notices are sent please "
+ "update your abuse contact using your registrar portal account.</p>"
+ ""
+ "<p>If you have any questions regarding this notice, please contact "
+ "abuse@test.com.</p>";
+ "<ul><li>foo</li></ul><p>If you believe that any of the domains were reported in"
+ " error, or are still receiving reports for issues that have been remediated, please"
+ " <a href=\"https://safebrowsing.google.com/safebrowsing/report_error/?hl=en\">submit"
+ " a request</a> to have the site reviewed.</p><p>You will continue to receive daily"
+ " notices when new domains managed by your registrar are flagged for abuse, as well as"
+ " a monthly summary of all of your domains under management that remain flagged for"
+ " abuse.</p><p>If you would like to change the email to which these notices are sent"
+ " please update your abuse contact using your registrar portal account.</p><p>If you"
+ " have any questions regarding this notice, please contact abuse@test.com.</p>";
private static final String MONTHLY_EMAIL_FORMAT =
"Dear registrar partner,"
+ ""
+ "<p>Super Cool Registry previously notified you when the following "
+ "domains managed by your registrar were flagged for potential security concerns.</p>"
+ "<p>The following domains that you manage continue to be flagged by our analysis "
+ "for potential security concerns. This may be because the registrants have not "
+ "completed the requisite steps to mitigate the potential security abuse and/or have "
+ "it reviewed and delisted.</p>"
+ ""
+ "<table>"
+ "<tr><th>Domain Name</th><th>Threat Type</th></tr>"
+ "%s"
+ "</table>"
+ ""
+ "<p>Please work with the registrant to mitigate any security issues "
+ "and have the domains delisted.</p>"
+ ""
+ "Some helpful resources for getting off a blocked list include:"
+ "<ul><li>foo</li></ul><p>"
+ ""
+ "You will continue to receive a monthly summary of all domains managed by your "
+ "registrar that remain on our lists of potential security threats. You will "
+ "additionally receive a daily notice when any new domains that are added to these "
+ "lists. Once the registrant has resolved the security issues and followed the steps to "
+ "have his or her domain reviewed and delisted it will automatically be removed from "
+ "our monthly reporting.</p>"
+ ""
+ "<p>If you have any questions regarding this notice, please contact "
+ "abuse@test.com.</p>";
"Dear registrar partner,<p>Super Cool Registry previously notified you when the following"
+ " domains managed by your registrar were flagged for potential security"
+ " concerns.</p><p>The following domains that you manage continue to be flagged by our"
+ " analysis for potential security concerns. This may be because the registrants have"
+ " not completed the requisite steps to mitigate the potential security abuse and/or"
+ " have it reviewed and delisted.</p><table><tr><th>Domain Name</th><th>Threat"
+ " Type</th></tr>%s</table><p>Please work with the registrant to mitigate any security"
+ " issues and have the domains delisted. If you believe that any of the domains were"
+ " reported in error, or are still receiving reports for issues that have been"
+ " remediated, please <a"
+ " href=\"https://safebrowsing.google.com/safebrowsing/report_error/?hl=en\">submit a"
+ " request</a> to have the site reviewed.</p>Some helpful resources for getting off a"
+ " blocked list include:<ul><li>foo</li></ul><p>You will continue to receive a monthly"
+ " summary of all domains managed by your registrar that remain on our lists of"
+ " potential security threats. You will also receive a daily notice when any new"
+ " domains are added to these lists.</p><p>If you have any questions regarding this"
+ " notice, please contact abuse@test.com.</p>";
@Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build();
@@ -0,0 +1,81 @@
// Copyright 2018 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.integration;
import static com.google.common.truth.Truth.assertWithMessage;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import google.registry.model.transaction.JpaTransactionManagerRule;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ScanResult;
import java.lang.reflect.Field;
import java.util.stream.Stream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.junit.runners.Suite.SuiteClasses;
/**
* Verifies that all tests that depends on the Cloud SQL schema are included in the project's
* sqlIntegrationTest suite. Names of the test classes is set to the 'test.sqlIntergrationTests'
* system property as a comma-separated string.
*
* <p>A test is deemed dependent on the SQL schema iff it has a field with type {@link
* JpaTransactionManagerRule}.
*/
// TODO(weiminyu): consider generating a TestSuite class instead.
@RunWith(JUnit4.class)
public class SqlIntegrationMembershipTest {
@Test
public void sqlIntegrationMembershipComplete() {
ImmutableSet<String> sqlDependentTests;
try (ScanResult scanResult =
new ClassGraph()
.enableAnnotationInfo()
.whitelistPackages("google.registry")
.scan()) {
sqlDependentTests =
scanResult.getClassesWithAnnotation(RunWith.class.getName()).stream()
.filter(clazz -> clazz.getSimpleName().endsWith("Test"))
.map(clazz -> clazz.loadClass())
.filter(SqlIntegrationMembershipTest::isSqlDependent)
.map(Class::getName)
.collect(ImmutableSet.toImmutableSet());
}
ImmutableSet<String> declaredTests =
Stream.of(SqlIntegrationTestSuite.class.getAnnotation(SuiteClasses.class).value())
.map(Class::getName)
.collect(ImmutableSet.toImmutableSet());
SetView<String> undeclaredTests = Sets.difference(sqlDependentTests, declaredTests);
assertWithMessage(
"Undeclared sql-dependent tests found. "
+ "Please add them to SqlIntegrationTestSuite.java.")
.that(undeclaredTests)
.isEmpty();
SetView<String> unnecessaryDeclarations = Sets.difference(declaredTests, sqlDependentTests);
assertWithMessage("Found tests that should not be included in SqlIntegrationTestSuite.java.")
.that(unnecessaryDeclarations)
.isEmpty();
}
private static boolean isSqlDependent(Class<?> testClass) {
return Stream.of(testClass.getDeclaredFields())
.map(Field::getType)
.anyMatch(JpaTransactionManagerRule.class::equals);
}
}
@@ -0,0 +1,52 @@
// Copyright 2018 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.integration;
import google.registry.model.registry.RegistryLockDaoTest;
import google.registry.model.tmch.ClaimsListDaoTest;
import google.registry.model.transaction.JpaTransactionManagerImplTest;
import google.registry.model.transaction.JpaTransactionManagerRuleTest;
import google.registry.persistence.BloomFilterConverterTest;
import google.registry.persistence.CreateAutoTimestampConverterTest;
import google.registry.persistence.UpdateAutoTimestampConverterTest;
import google.registry.persistence.ZonedDateTimeConverterTest;
import google.registry.schema.tld.PremiumListDaoTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
/**
* Groups all tests that may depends on Cloud SQL schema. They will be run for server-schema
* compatibility check.
*
* <p>Schema dependency is approximated by the use of {@link
* google.registry.model.transaction.JpaTransactionManagerRule}.
*
* @see SqlIntegrationMembershipTest
*/
// TODO(weiminyu): refactor JpaTransactionManagerRule to eliminate false positives.
@RunWith(Suite.class)
@SuiteClasses({
BloomFilterConverterTest.class,
ClaimsListDaoTest.class,
CreateAutoTimestampConverterTest.class,
JpaTransactionManagerImplTest.class,
JpaTransactionManagerRuleTest.class,
PremiumListDaoTest.class,
RegistryLockDaoTest.class,
UpdateAutoTimestampConverterTest.class,
ZonedDateTimeConverterTest.class
})
public class SqlIntegrationTestSuite {}
@@ -0,0 +1,89 @@
// Copyright 2019 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.tld;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.testing.JUnitBackports.assertThrows;
import com.google.common.collect.ImmutableMap;
import google.registry.model.transaction.JpaTransactionManagerRule;
import java.math.BigDecimal;
import javax.persistence.PersistenceException;
import org.joda.money.CurrencyUnit;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link PremiumListDao}. */
@RunWith(JUnit4.class)
public class PremiumListDaoTest {
@Rule
public final JpaTransactionManagerRule jpaTmRule =
new JpaTransactionManagerRule.Builder().build();
private static final ImmutableMap<String, BigDecimal> TEST_PRICES =
ImmutableMap.of(
"silver",
BigDecimal.valueOf(10.23),
"gold",
BigDecimal.valueOf(1305.47),
"palladium",
BigDecimal.valueOf(1552.78));
@Test
public void saveNew_worksSuccessfully() {
PremiumList premiumList = PremiumList.create("testname", CurrencyUnit.USD, TEST_PRICES);
PremiumListDao.saveNew(premiumList);
jpaTm()
.transact(
() -> {
PremiumList persistedList =
jpaTm()
.getEntityManager()
.createQuery(
"SELECT pl FROM PremiumList pl WHERE pl.name = :name", PremiumList.class)
.setParameter("name", "testname")
.getSingleResult();
assertThat(persistedList.getLabelsToPrices()).containsExactlyEntriesIn(TEST_PRICES);
assertThat(persistedList.getCreationTimestamp())
.isEqualTo(jpaTmRule.getTxnClock().nowUtc());
});
}
@Test
public void saveNew_throwsWhenPremiumListAlreadyExists() {
PremiumListDao.saveNew(PremiumList.create("testlist", CurrencyUnit.USD, TEST_PRICES));
PersistenceException thrown =
assertThrows(
PersistenceException.class,
() ->
PremiumListDao.saveNew(
PremiumList.create("testlist", CurrencyUnit.USD, TEST_PRICES)));
assertThat(thrown)
.hasCauseThat()
.hasMessageThat()
.contains("A premium list of this name already exists");
}
@Test
public void checkExists_worksSuccessfully() {
assertThat(PremiumListDao.checkExists("testlist")).isFalse();
PremiumListDao.saveNew(PremiumList.create("testlist", CurrencyUnit.USD, TEST_PRICES));
assertThat(PremiumListDao.checkExists("testlist")).isTrue();
}
}
@@ -0,0 +1,50 @@
// Copyright 2019 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.tld;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.hash.BloomFilter;
import java.math.BigDecimal;
import org.joda.money.CurrencyUnit;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link PremiumList}. */
@RunWith(JUnit4.class)
public class PremiumListTest {
private static final ImmutableMap<String, BigDecimal> TEST_PRICES =
ImmutableMap.of(
"silver",
BigDecimal.valueOf(10.23),
"gold",
BigDecimal.valueOf(1305.47),
"palladium",
BigDecimal.valueOf(1552.78));
@Test
public void bloomFilter_worksCorrectly() {
BloomFilter<String> bloomFilter =
PremiumList.create("testname", CurrencyUnit.USD, TEST_PRICES).getBloomFilter();
ImmutableSet.of("silver", "gold", "palladium")
.forEach(l -> assertThat(bloomFilter.mightContain(l)).isTrue());
ImmutableSet.of("dirt", "pyrite", "zirconia")
.forEach(l -> assertThat(bloomFilter.mightContain(l)).isFalse());
}
}
@@ -193,6 +193,7 @@ public final class AppEngineRule extends ExternalResource {
.setPassword("foo-BAR2")
.setPhoneNumber("+1.3334445555")
.setPhonePasscode("12345")
.setRegistryLockAllowed(false)
.build();
}
@@ -206,6 +207,7 @@ public final class AppEngineRule extends ExternalResource {
.setPassword("password2")
.setPhoneNumber("+1.2223334444")
.setPhonePasscode("22222")
.setRegistryLockAllowed(true)
.build();
}
@@ -241,6 +243,19 @@ public final class AppEngineRule extends ExternalResource {
.build();
}
public static RegistrarContact makeRegistrarContact3() {
return new RegistrarContact.Builder()
.setParent(makeRegistrar2())
.setName("Marla Singer")
.setEmailAddress("Marla.Singer@crr.com")
.setPhoneNumber("+1.2128675309")
.setTypes(ImmutableSet.of(RegistrarContact.Type.TECH))
.setGaeUserId(THE_REGISTRAR_GAE_USER_ID)
.setAllowedToSetRegistryLockPassword(true)
.setRegistryLockPassword("hi")
.build();
}
/** Hack to make sure AppEngineRule is always wrapped in a TemporaryFolder rule. */
@Override
public Statement apply(Statement base, Description description) {
@@ -412,6 +427,10 @@ public final class AppEngineRule extends ExternalResource {
public static void loadInitialData() {
persistSimpleResources(
ImmutableList.of(
makeRegistrar1(), makeRegistrarContact1(), makeRegistrar2(), makeRegistrarContact2()));
makeRegistrar1(),
makeRegistrarContact1(),
makeRegistrar2(),
makeRegistrarContact2(),
makeRegistrarContact3()));
}
}
@@ -67,7 +67,13 @@ public class CreatePremiumListCommandTest<C extends CreatePremiumListCommand>
verifySentParams(
connection,
servletPath,
ImmutableMap.of("name", "foo", "inputData", generateInputData(premiumTermsPath)));
ImmutableMap.of(
"name",
"foo",
"inputData",
generateInputData(premiumTermsPath),
"alsoCloudSql",
"false"));
}
@Test
@@ -78,7 +84,28 @@ public class CreatePremiumListCommandTest<C extends CreatePremiumListCommand>
connection,
servletPath,
ImmutableMap.of(
"name", "example_premium_terms", "inputData", generateInputData(premiumTermsPath)));
"name",
"example_premium_terms",
"inputData",
generateInputData(premiumTermsPath),
"alsoCloudSql",
"false"));
}
@Test
public void testRun_alsoCloudSql() throws Exception {
runCommandForced("-i=" + premiumTermsPath, "-n=foo", "--also_cloud_sql");
assertInStdout("Successfully");
verifySentParams(
connection,
servletPath,
ImmutableMap.of(
"name",
"foo",
"inputData",
generateInputData(premiumTermsPath),
"alsoCloudSql",
"true"));
}
@Test
@@ -21,6 +21,7 @@ import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.loadRegistrar;
import com.google.common.collect.ImmutableSortedSet;
import google.registry.model.ImmutableObject;
import google.registry.model.ofy.CommitLogManifest;
import google.registry.model.ofy.CommitLogMutation;
@@ -62,13 +63,16 @@ public class ResaveEnvironmentEntitiesCommandTest
transform(
ofy().load().type(CommitLogMutation.class).list(),
mutation -> ofy().load().fromEntity(mutation.getEntity()));
ImmutableSortedSet<RegistrarContact> theRegistrarContacts =
loadRegistrar("TheRegistrar").getContacts();
assertThat(savedEntities)
.containsExactly(
// The Registrars and RegistrarContacts are created by AppEngineRule.
loadRegistrar("TheRegistrar"),
loadRegistrar("NewRegistrar"),
Registry.get("tld"),
getOnlyElement(loadRegistrar("TheRegistrar").getContacts()),
theRegistrarContacts.first(),
theRegistrarContacts.last(),
getOnlyElement(loadRegistrar("NewRegistrar").getContacts()));
}
@@ -57,7 +57,13 @@ public class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
verifySentParams(
connection,
servletPath,
ImmutableMap.of("name", "foo", "inputData", generateInputData(premiumTermsPath)));
ImmutableMap.of(
"name",
"foo",
"inputData",
generateInputData(premiumTermsPath),
"alsoCloudSql",
"false"));
}
@Test
@@ -68,6 +74,11 @@ public class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
connection,
servletPath,
ImmutableMap.of(
"name", "example_premium_terms", "inputData", generateInputData(premiumTermsPath)));
"name",
"example_premium_terms",
"inputData",
generateInputData(premiumTermsPath),
"alsoCloudSql",
"false"));
}
}
@@ -0,0 +1,70 @@
// Copyright 2017 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.server;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.testing.JUnitBackports.assertThrows;
import google.registry.schema.tld.PremiumList;
import google.registry.testing.AppEngineRule;
import google.registry.testing.FakeJsonResponse;
import java.math.BigDecimal;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link CreateOrUpdatePremiumListAction}. */
@RunWith(JUnit4.class)
public class CreateOrUpdatePremiumListActionTest {
@Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build();
private CreatePremiumListAction action;
private FakeJsonResponse response;
@Before
public void init() {
action = new CreatePremiumListAction();
response = new FakeJsonResponse();
action.response = response;
action.name = "testlist";
}
@Test
public void parseInputToPremiumList_works() {
action.inputData = "foo,USD 99.50\n" + "bar,USD 30\n" + "baz,USD 10\n";
PremiumList premiumList = action.parseInputToPremiumList();
assertThat(premiumList.getName()).isEqualTo("testlist");
assertThat(premiumList.getLabelsToPrices())
.containsExactly("foo", twoDigits(99.50), "bar", twoDigits(30), "baz", twoDigits(10));
}
@Test
public void parseInputToPremiumList_throwsOnInconsistentCurrencies() {
action.inputData = "foo,USD 99.50\n" + "bar,USD 30\n" + "baz,JPY 990\n";
IllegalArgumentException thrown =
assertThrows(IllegalArgumentException.class, () -> action.parseInputToPremiumList());
assertThat(thrown)
.hasMessageThat()
.isEqualTo("The Cloud SQL schema requires exactly one currency, but got: [JPY, USD]");
}
private static BigDecimal twoDigits(double num) {
return BigDecimal.valueOf((long) (num * 100.0), 2);
}
}
@@ -188,6 +188,33 @@ public class ContactSettingsTest extends RegistrarSettingsActionTestCase {
@Test
public void testSuccess_setRegistryLockPassword() {
addPasswordToTechContact();
techContact = Iterables.getOnlyElement(loadRegistrar(CLIENT_ID).getContactsOfType(Type.TECH));
assertThat(techContact.verifyRegistryLockPassword("hi")).isTrue();
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
}
@Test
public void testSuccess_setRegistryLockPassword_notOverriddenLater() {
addPasswordToTechContact();
techContact = Iterables.getOnlyElement(loadRegistrar(CLIENT_ID).getContactsOfType(Type.TECH));
assertThat(techContact.verifyRegistryLockPassword("hi")).isTrue();
techContact = Iterables.getOnlyElement(loadRegistrar(CLIENT_ID).getContactsOfType(Type.TECH));
Map<String, Object> techContactMap = techContact.toJsonMap();
techContactMap.put("name", "Some Other Name");
Map<String, Object> reqJson = loadRegistrar(CLIENT_ID).toJsonMap();
reqJson.put(
"contacts",
ImmutableList.of(AppEngineRule.makeRegistrarContact2().toJsonMap(), techContactMap));
Map<String, Object> response =
action.handleJsonRequest(ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", reqJson));
assertThat(response).containsAtLeastEntriesIn(ImmutableMap.of("status", "SUCCESS"));
techContact = Iterables.getOnlyElement(loadRegistrar(CLIENT_ID).getContactsOfType(Type.TECH));
assertThat(techContact.verifyRegistryLockPassword("hi")).isTrue();
}
private void addPasswordToTechContact() {
techContact =
persistResource(techContact.asBuilder().setAllowedToSetRegistryLockPassword(true).build());
Map<String, Object> contactMap = techContact.toJsonMap();
@@ -199,9 +226,6 @@ public class ContactSettingsTest extends RegistrarSettingsActionTestCase {
Map<String, Object> response =
action.handleJsonRequest(ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", reqJson));
assertThat(response).containsAtLeastEntriesIn(ImmutableMap.of("status", "SUCCESS"));
techContact = Iterables.getOnlyElement(loadRegistrar(CLIENT_ID).getContactsOfType(Type.TECH));
assertThat(techContact.verifyRegistryLockPassword("hi")).isTrue();
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
}
@Test
@@ -275,7 +299,7 @@ public class ContactSettingsTest extends RegistrarSettingsActionTestCase {
"results",
ImmutableList.of(),
"message",
"Registrar contact not allowed to set registry lock password");
"Cannot set isAllowedToSetRegistryLockPassword through UI");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormException");
}
}
@@ -35,6 +35,7 @@ import org.junit.Test;
import org.junit.rules.Timeout;
import org.junit.runner.RunWith;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
/** WebDriver tests for Registrar Console UI. */
@RunWith(RepeatableRunner.class)
@@ -78,6 +79,12 @@ public class RegistrarConsoleWebTest extends WebDriverTestCase {
assertThat(driver.waitForElement(By.id(eltId)).isDisplayed()).isFalse();
}
/** Checks that searching with the given By produces at least one element with the given text. */
void assertEltTextPresent(By by, String toFind) {
assertThat(driver.findElements(by).stream().map(WebElement::getText).anyMatch(toFind::equals))
.isTrue();
}
@Test
public void testEditButtonsVisibility_owner() throws Throwable {
driver.get(server.getUrl("/registrar#whois-settings"));
@@ -190,9 +197,11 @@ public class RegistrarConsoleWebTest extends WebDriverTestCase {
ImmutableList<RegistrarContact> contacts =
server.runInAppEngineEnvironment(
() -> loadRegistrar("TheRegistrar").getContacts().asList());
assertEltText("contacts[0].name", contacts.get(0).getName());
assertEltText("contacts[0].emailAddress", contacts.get(0).getEmailAddress());
assertEltText("contacts[0].phoneNumber", contacts.get(0).getPhoneNumber());
for (RegistrarContact contact : contacts) {
assertEltTextPresent(By.id("contacts[0].name"), contact.getName());
assertEltTextPresent(By.id("contacts[0].emailAddress"), contact.getEmailAddress());
assertEltTextPresent(By.id("contacts[0].phoneNumber"), contact.getPhoneNumber());
}
}
@Test
@@ -0,0 +1,16 @@
-- Copyright 2019 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.
DROP DATABASE IF EXISTS postgres;
CREATE DATABASE postgres;
@@ -11,9 +11,10 @@ emailAddress: the.registrar@example.com -> thase@the.registrar
url: http://my.fake.url -> http://my.new.url
contacts:
ADDED:
{parent=Key<?>(EntityGroupRoot("cross-tld")/Registrar("TheRegistrar")), name=Extra Terrestrial, emailAddress=etphonehome@example.com, phoneNumber=+1.2345678901, faxNumber=null, types=[ADMIN, BILLING, TECH, WHOIS], gaeUserId=null, visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false, registryLockPasswordHash=null, registryLockPasswordSalt=null}
{parent=Key<?>(EntityGroupRoot("cross-tld")/Registrar("TheRegistrar")), name=Extra Terrestrial, emailAddress=etphonehome@example.com, phoneNumber=+1.2345678901, faxNumber=null, types=[ADMIN, BILLING, TECH, WHOIS], gaeUserId=null, visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false}
REMOVED:
{parent=Key<?>(EntityGroupRoot("cross-tld")/Registrar("TheRegistrar")), name=John Doe, emailAddress=johndoe@theregistrar.com, phoneNumber=+1.1234567890, faxNumber=null, types=[ADMIN], gaeUserId=31337, visibleInWhoisAsAdmin=false, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false, registryLockPasswordHash=null, registryLockPasswordSalt=null},
{parent=Key<?>(EntityGroupRoot("cross-tld")/Registrar("TheRegistrar")), name=Jian-Yang, emailAddress=jyang@bachman.accelerator, phoneNumber=+1.1234567890, faxNumber=null, types=[TECH], gaeUserId=null, visibleInWhoisAsAdmin=false, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false, registryLockPasswordHash=null, registryLockPasswordSalt=null}
{parent=Key<?>(EntityGroupRoot("cross-tld")/Registrar("TheRegistrar")), name=Marla Singer, emailAddress=Marla.Singer@crr.com, phoneNumber=+1.2128675309, faxNumber=null, types=[TECH], gaeUserId=31337, visibleInWhoisAsAdmin=false, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false},
{parent=Key<?>(EntityGroupRoot("cross-tld")/Registrar("TheRegistrar")), name=John Doe, emailAddress=johndoe@theregistrar.com, phoneNumber=+1.1234567890, faxNumber=null, types=[ADMIN], gaeUserId=31337, visibleInWhoisAsAdmin=false, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false},
{parent=Key<?>(EntityGroupRoot("cross-tld")/Registrar("TheRegistrar")), name=Jian-Yang, emailAddress=jyang@bachman.accelerator, phoneNumber=+1.1234567890, faxNumber=null, types=[TECH], gaeUserId=null, visibleInWhoisAsAdmin=false, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false}
FINAL CONTENTS:
{parent=Key<?>(EntityGroupRoot("cross-tld")/Registrar("TheRegistrar")), name=Extra Terrestrial, emailAddress=etphonehome@example.com, phoneNumber=+1.2345678901, faxNumber=null, types=[ADMIN, BILLING, TECH, WHOIS], gaeUserId=null, visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false, registryLockPasswordHash=null, registryLockPasswordSalt=null}
{parent=Key<?>(EntityGroupRoot("cross-tld")/Registrar("TheRegistrar")), name=Extra Terrestrial, emailAddress=etphonehome@example.com, phoneNumber=+1.2345678901, faxNumber=null, types=[ADMIN, BILLING, TECH, WHOIS], gaeUserId=null, visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 50 KiB

+25 -8
View File
@@ -3,6 +3,23 @@
This project contains Nomulus's Cloud SQL schema and schema-deployment
utilities.
### Database Roles and Privileges
Nomulus uses the 'postgres' database in the 'public' schema. The following
users/roles are defined:
* postgres: the initial user is used for admin and schema deployment.
* In Cloud SQL, we do not control superusers. The initial 'postgres' user
is a regular user with create-role/create-db privileges. Therefore,
it is not possible to separate admin user and schema-deployment user.
* readwrite is a role with read-write privileges on all data tables and
sequences. However, it does not have write access to admin tables. Nor
can it create new tables.
* The Registry server user is granted this role.
* readonly is a role with SELECT privileges on all tables.
* Reporting job user and individual human readers may be granted
this role.
### Schema DDL Scripts
Currently we use Flyway for schema deployment. Versioned incremental update
@@ -12,18 +29,18 @@ changes not yet deployed are pushed.
Below are the steps to submit a schema change:
* Define the incremental DDL script that would update the existing schema to
the new one.
* Add the script to the src/main/resource/flyway folder. Its name should
follow the V{id}__{description text}.sql, where {id} is a number that is
higher than all existing scripts in that folder. Also note that it is a
1. Write the incremental DDL script that makes your changes to the existing
schema. It should be stored in a new file in the
`db/src/main/resources/sql/flyway` folder using the naming pattern
`V{id}__{description text}.sql`, where `{id}` is the next highest number
following the existing scripts in that folder. Also note that it is a
**double** underscore in the naming pattern.
* Run the `:db:test` task from the Gradle root project. The SchemaTest will
2. Run the `:db:test` task from the Gradle root project. The SchemaTest will
fail because the new schema does not match the golden file.
* Copy db/build/resources/test/testcontainer/mount/dump.txt to the golden file
3. Copy db/build/resources/test/testcontainer/mount/dump.txt to the golden file
(db/src/main/resources/sql/schema/nomulus.golden.sql). Diff it against the
old version and verify that all changes are expected.
* Rerun the `:db:test` task. This time all tests should pass.
4. Rerun the `:db:test` task. This time all tests should pass.
Relevant files (under db/src/main/resources/sql/schema/):
+49 -12
View File
@@ -12,18 +12,45 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import org.gradle.api.internal.tasks.userinput.UserInputHandler
plugins {
id "org.flywaydb.flyway" version "6.0.1"
id 'maven-publish'
}
ext {
Set restrictedDbEnv =
[ 'sandbox', 'production' ].asUnmodifiable()
Set allDbEnv =
[ 'alpha', 'crash' ].plus(restrictedDbEnv).asUnmodifiable()
def dbServerProperty = 'dbServer'
def dbNameProperty = 'dbName'
def dbServer = findProperty(dbServerProperty)
def dbServer = findProperty(dbServerProperty).toString().toLowerCase()
def dbName = findProperty(dbNameProperty)
reconfirmRestrictedDbEnv = {
if (!restrictedDbEnv.contains(dbServer)) {
return
}
// For restricted environments, ask the user to type again to confirm.
// The following statement uses Gradle internal API to get around the
// missing console bug when Gradle Daemon is in use. Another option is
// to use the ant.input task. For details please refer to
// https://github.com/gradle/gradle/issues/1251.
def dbServerAgain = services.get(UserInputHandler.class).askQuestion(
"""\
Are you sure? Operating on ${dbServer} from desktop is unsafe.
Please type '${dbServer}' again to proceed: """.stripIndent(),
'').trim()
if (dbServer != dbServerAgain) {
throw new RuntimeException(
"Failed to confirm for restricted database environment. Operation aborted.")
}
}
getAccessInfoByHostPort = { hostAndPort ->
return [
url: "jdbc:postgresql://${hostAndPort}/${dbName}",
@@ -31,8 +58,8 @@ ext {
password: findProperty('dbPassword')]
}
getSocketFactoryAccessInfo = {
def cred = getCloudSqlCredential('alpha', 'superuser').split(' ')
getSocketFactoryAccessInfo = { env ->
def cred = getCloudSqlCredential(env, 'admin').split(' ')
def sqlInstance = cred[0]
return [
url: """\
@@ -46,11 +73,10 @@ ext {
}
getJdbcAccessInfo = {
switch (dbServer.toString().toLowerCase()) {
case 'alpha':
return getSocketFactoryAccessInfo()
default:
return getAccessInfoByHostPort(dbServer)
if (allDbEnv.contains(dbServer)) {
return getSocketFactoryAccessInfo(dbServer)
} else {
return getAccessInfoByHostPort(dbServer)
}
}
@@ -62,13 +88,17 @@ ext {
// later).
getCloudSqlCredential = { env, role ->
env = env == 'production' ? '' : "-${env}"
def keyProject = env == '-crash'
? 'domain-registry-crash-kms-keys'
: "domain-registry${env}-keys"
def command =
"""gsutil cp \
gs://domain-registry${env}-cloudsql-credentials/${role}.enc - | \
gs://domain-registry${env}-cloudsql-credentials/${role}_credential.enc - | \
base64 -d | \
gcloud kms decrypt --location global --keyring nomulus \
--key sql-credentials-on-gcs-key --plaintext-file=- \
--ciphertext-file=- \
--project=domain-registry${env}-keys"""
--project=${keyProject}"""
return execInBash(command, '/tmp')
}
@@ -89,11 +119,11 @@ artifacts {
publishing {
repositories {
maven {
url project.schema_jar_repo
url project.schema_publish_repo
}
}
publications {
schemaOrmPublication(MavenPublication) {
sqlSchemaPublication(MavenPublication) {
groupId 'google.registry'
artifactId 'schema'
version project.schema_version
@@ -114,6 +144,13 @@ flyway {
locations = [ "classpath:sql/flyway" ]
}
tasks.flywayMigrate.dependsOn(
tasks.create('confirmMigrateOnRestrictedDb') {
doLast {
project.ext.reconfirmRestrictedDbEnv()
}
})
dependencies {
def deps = rootProject.dependencyMap
@@ -0,0 +1,16 @@
-- Copyright 2019 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.
create index if not exists idx_registry_lock_verification_code ON "RegistryLock"
using btree (verification_code);
@@ -12,19 +12,6 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
CREATE TABLE "RegistryLock" (
revision_id BIGSERIAL NOT NULL,
action TEXT NOT NULL,
completion_timestamp TIMESTAMPTZ,
creation_timestamp TIMESTAMPTZ NOT NULL,
domain_name TEXT NOT NULL,
is_superuser BOOLEAN NOT NULL,
registrar_id TEXT NOT NULL,
registrar_poc_id TEXT,
repo_id TEXT NOT NULL,
verification_code TEXT NOT NULL,
PRIMARY KEY (revision_id)
);
alter table "PremiumList" add column if not exists name text not null;
ALTER TABLE IF EXISTS "RegistryLock"
ADD CONSTRAINT idx_registry_lock_repo_id_revision_id UNIQUE (repo_id, revision_id);
create index if not exists premiumlist_name_idx ON "PremiumList" (name);
@@ -0,0 +1,15 @@
-- Copyright 2019 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.
alter table "PremiumList" add column if not exists bloom_filter bytea not null;
@@ -0,0 +1,15 @@
-- Copyright 2019 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.
alter table "ClaimsList" add column if not exists tmdb_generation_time timestamp with time zone not null;
@@ -0,0 +1,16 @@
-- Copyright 2019 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.
create index if not exists idx_registry_lock_registrar_id ON "RegistryLock"
using btree (registrar_id);
@@ -22,6 +22,7 @@
create table "ClaimsList" (
revision_id bigserial not null,
creation_timestamp timestamptz not null,
tmdb_generation_time timestamptz not null,
primary key (revision_id)
);
@@ -130,8 +131,10 @@
create table "PremiumList" (
revision_id bigserial not null,
bloom_filter bytea not null,
creation_timestamp timestamptz not null,
currency bytea not null,
name text not null,
primary key (revision_id)
);
@@ -157,6 +160,9 @@
alter table if exists "Domain_GracePeriod"
add constraint UK_4ps2u4y8i5r91wu2n1x2xea28 unique (grace_periods_id);
create index premiumlist_name_idx on "PremiumList" (name);
create index idx_registry_lock_verification_code on "RegistryLock" (verification_code);
create index idx_registry_lock_registrar_id on "RegistryLock" (registrar_id);
alter table if exists "RegistryLock"
add constraint idx_registry_lock_repo_id_revision_id unique (repo_id, revision_id);
@@ -50,7 +50,8 @@ CREATE TABLE public."ClaimsEntry" (
CREATE TABLE public."ClaimsList" (
revision_id bigint NOT NULL,
creation_timestamp timestamp with time zone NOT NULL
creation_timestamp timestamp with time zone NOT NULL,
tmdb_generation_time timestamp with time zone NOT NULL
);
@@ -91,7 +92,9 @@ CREATE TABLE public."PremiumEntry" (
CREATE TABLE public."PremiumList" (
revision_id bigint NOT NULL,
creation_timestamp timestamp with time zone NOT NULL,
currency bytea NOT NULL
currency bytea NOT NULL,
name text NOT NULL,
bloom_filter bytea NOT NULL
);
@@ -220,6 +223,27 @@ ALTER TABLE ONLY public."RegistryLock"
ADD CONSTRAINT idx_registry_lock_repo_id_revision_id UNIQUE (repo_id, revision_id);
--
-- Name: idx_registry_lock_registrar_id; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX idx_registry_lock_registrar_id ON public."RegistryLock" USING btree (registrar_id);
--
-- Name: idx_registry_lock_verification_code; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX idx_registry_lock_verification_code ON public."RegistryLock" USING btree (verification_code);
--
-- Name: premiumlist_name_idx; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX premiumlist_name_idx ON public."PremiumList" USING btree (name);
--
-- Name: ClaimsEntry fk6sc6at5hedffc0nhdcab6ivuq; Type: FK CONSTRAINT; Schema: public; Owner: -
--
@@ -12,10 +12,12 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- Script to create a user with read-only permission to all tables.
-- Script to create a user with read-only permission to all tables. The
-- initialize_roles.sql script creates the readonly role used here.
-- Comment out line below if user already exists:
CREATE USER :username ENCRYPTED PASSWORD :'password';
GRANT CONNECT ON DATABASE postgres TO :username;
GRANT USAGE ON SCHEMA public TO :username;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO :username;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO :username;
-- Comment out line above and uncomment line below if user has been created
-- from Cloud Dashboard:
-- ALTER USER :username NOCREATEDB NOCREATEROLE;
GRANT readonly TO :username;
@@ -13,11 +13,12 @@
-- limitations under the License.
--
-- Script to create a user with read-write permission to all tables (except for
-- WRITE permissions to flyway_schema_history).
-- WRITE permissions to flyway_schema_history). The initialize_roles.sql script
-- creates the readwrite role used here.
-- Comment out line below if user already exists:
CREATE USER :username ENCRYPTED PASSWORD :'password';
GRANT CONNECT ON DATABASE postgres TO :username;
GRANT USAGE ON SCHEMA public TO :username;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO :username;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO :username;
REVOKE INSERT, UPDATE, DELETE ON TABLE public.flyway_schema_history FROM :username;
-- Comment out line above and uncomment line below if user has been created
-- from Cloud Dashboard:
-- ALTER USER :username NOCREATEDB NOCREATEROLE;
GRANT readwrite TO :username;
@@ -14,9 +14,7 @@
--
-- Script to delete a user from the database.
REVOKE ALL PRIVILEGES ON DATABASE postgres FROM :username;
REVOKE ALL PRIVILEGES ON SCHEMA public FROM :username;
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM :username;
REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public FROM :username;
REVOKE ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public FROM :username;
-- Ignore warnings like :username is not a member of role readonly/write.
REVOKE readonly FROM :username;
REVOKE readwrite FROM :username;
DROP USER :username;
@@ -0,0 +1,48 @@
-- Copyright 2019 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.
--
-- Initializes roles and their privileges in the postgres database in Cloud SQL.
-- This script should run once under the **'postgres'** user before any other
-- roles or users are created.
-- Prevent backdoor grants through the implicit 'public' role.
REVOKE ALL PRIVILEGES ON SCHEMA public from public;
CREATE ROLE readonly;
GRANT CONNECT ON DATABASE postgres TO readonly;
GRANT USAGE ON SCHEMA public TO readonly;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO readonly;
ALTER DEFAULT PRIVILEGES
IN SCHEMA public
FOR USER postgres
GRANT USAGE, SELECT ON SEQUENCES TO readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;
ALTER DEFAULT PRIVILEGES
IN SCHEMA public
FOR USER postgres
GRANT SELECT ON TABLES TO readonly;
CREATE ROLE readwrite;
GRANT CONNECT ON DATABASE postgres TO readwrite;
GRANT USAGE ON SCHEMA public TO readwrite;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO readwrite;
ALTER DEFAULT PRIVILEGES
IN SCHEMA public
FOR USER postgres
GRANT USAGE, SELECT ON SEQUENCES TO readwrite;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO readwrite;
ALTER DEFAULT PRIVILEGES
IN SCHEMA public
FOR USER postgres
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO readwrite;
@@ -12,11 +12,9 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- Script to create a user with read-write permission to schema 'public' and
-- all tables.
-- Removes write privileges to Flyway admin table from roles.
-- This script is run once under 'postgres' after initialize_roles.sql
-- has been run AND the initial schema deployment by Flyway is done.
CREATE USER :username ENCRYPTED PASSWORD :'password';
GRANT CONNECT ON DATABASE postgres TO :username;
GRANT ALL PRIVILEGES ON SCHEMA public TO :username;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO :username;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO :username;
REVOKE INSERT, UPDATE, DELETE ON TABLE public.flyway_schema_history FROM readonly;
REVOKE INSERT, UPDATE, DELETE ON TABLE public.flyway_schema_history FROM readwrite;
+1
View File
@@ -79,6 +79,7 @@ ext {
'com.sun.xml.bind:jaxb-xjc:2.2.11',
'com.thoughtworks.qdox:qdox:1.12.1',
'dnsjava:dnsjava:2.1.7',
'io.github.classgraph:classgraph:4.8.52',
'io.github.java-diff-utils:java-diff-utils:4.0',
'io.netty:netty-buffer:4.1.31.Final',
'io.netty:netty-codec:4.1.31.Final',
+7 -1
View File
@@ -8,6 +8,12 @@ verboseTestOutput=false
flowDocsFile=
enableDependencyLocking=true
# Comma separated list of test patterns, if specified run only these.
testFilter=
# GAE Environment for deployment and staging.
environment=
# Cloud SQL properties
# A registry environment name (e.g., 'alpha') or a host[:port] string
@@ -18,5 +24,5 @@ dbPassword=
# Maven repository of the Cloud SQL schema jar, which contains the
# SQL DDL scripts.
schema_jar_repo=
schema_publish_repo=
schema_version=
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+195 -1835
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -21,10 +21,10 @@
},
"devDependencies": {
"jasmine-core": "3.4.0",
"karma": "3.0.0",
"karma": "^4.4.1",
"karma-chrome-launcher": "2.2.0",
"karma-closure": "0.1.3",
"karma-jasmine": "2.0.1",
"puppeteer": "1.17.0"
"puppeteer": "1.20.0"
}
}
@@ -141,6 +141,7 @@ commons-codec:commons-codec:1.11
commons-logging:commons-logging:1.2
dnsjava:dnsjava:2.1.7
io.dropwizard.metrics:metrics-core:3.1.2
io.github.classgraph:classgraph:4.8.52
io.grpc:grpc-all:1.17.1
io.grpc:grpc-alts:1.17.1
io.grpc:grpc-auth:1.17.1
@@ -130,6 +130,7 @@ commons-codec:commons-codec:1.11
commons-logging:commons-logging:1.2
dnsjava:dnsjava:2.1.7
io.dropwizard.metrics:metrics-core:3.1.2
io.github.classgraph:classgraph:4.8.52
io.grpc:grpc-all:1.17.1
io.grpc:grpc-alts:1.17.1
io.grpc:grpc-auth:1.17.1
@@ -141,6 +141,7 @@ commons-codec:commons-codec:1.11
commons-logging:commons-logging:1.2
dnsjava:dnsjava:2.1.7
io.dropwizard.metrics:metrics-core:3.1.2
io.github.classgraph:classgraph:4.8.52
io.grpc:grpc-all:1.17.1
io.grpc:grpc-alts:1.17.1
io.grpc:grpc-auth:1.17.1
@@ -141,6 +141,7 @@ commons-codec:commons-codec:1.11
commons-logging:commons-logging:1.2
dnsjava:dnsjava:2.1.7
io.dropwizard.metrics:metrics-core:3.1.2
io.github.classgraph:classgraph:4.8.52
io.grpc:grpc-all:1.17.1
io.grpc:grpc-alts:1.17.1
io.grpc:grpc-auth:1.17.1
+6 -22
View File
@@ -12,26 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
def environments = ['production', 'sandbox', 'alpha', 'crash']
// Mapping from environment names to GCP projects.
// Replace the values with the names of your deployment environments.
def projects = ['production': 'domain-registry',
'sandbox' : 'domain-registry-sandbox',
'alpha' : 'domain-registry-alpha',
'crash' : 'domain-registry-crash']
def environment = rootProject.findProperty("environment")
if (environment == null) {
environment = 'production'
}
def gcpProject = projects[environment]
if (gcpProject == null) {
throw new GradleException("-Penvironment must be one of ${environments}.")
}
rootProject.ext.environment = environment
rootProject.ext.gcpProject = gcpProject
rootProject.ext.prodOrSandboxEnv = ['production', 'sandbox'].contains(environment)
rootProject.ext.projects = ['production': 'your-production-project',
'sandbox' : 'your-sandbox-project',
'alpha' : 'your-alpha-project',
'crash' : 'your-crash-project']
@@ -141,6 +141,7 @@ commons-codec:commons-codec:1.11
commons-logging:commons-logging:1.2
dnsjava:dnsjava:2.1.7
io.dropwizard.metrics:metrics-core:3.1.2
io.github.classgraph:classgraph:4.8.52
io.grpc:grpc-all:1.17.1
io.grpc:grpc-alts:1.17.1
io.grpc:grpc-auth:1.17.1
@@ -130,6 +130,7 @@ commons-codec:commons-codec:1.11
commons-logging:commons-logging:1.2
dnsjava:dnsjava:2.1.7
io.dropwizard.metrics:metrics-core:3.1.2
io.github.classgraph:classgraph:4.8.52
io.grpc:grpc-all:1.17.1
io.grpc:grpc-alts:1.17.1
io.grpc:grpc-auth:1.17.1
@@ -141,6 +141,7 @@ commons-codec:commons-codec:1.11
commons-logging:commons-logging:1.2
dnsjava:dnsjava:2.1.7
io.dropwizard.metrics:metrics-core:3.1.2
io.github.classgraph:classgraph:4.8.52
io.grpc:grpc-all:1.17.1
io.grpc:grpc-alts:1.17.1
io.grpc:grpc-auth:1.17.1
@@ -141,6 +141,7 @@ commons-codec:commons-codec:1.11
commons-logging:commons-logging:1.2
dnsjava:dnsjava:2.1.7
io.dropwizard.metrics:metrics-core:3.1.2
io.github.classgraph:classgraph:4.8.52
io.grpc:grpc-all:1.17.1
io.grpc:grpc-alts:1.17.1
io.grpc:grpc-auth:1.17.1
@@ -141,6 +141,7 @@ commons-codec:commons-codec:1.11
commons-logging:commons-logging:1.2
dnsjava:dnsjava:2.1.7
io.dropwizard.metrics:metrics-core:3.1.2
io.github.classgraph:classgraph:4.8.52
io.grpc:grpc-all:1.17.1
io.grpc:grpc-alts:1.17.1
io.grpc:grpc-auth:1.17.1

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