diff --git a/.gitignore b/.gitignore index 64fc08d9a..d379d1566 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,14 @@ autogenerated/ # Python Ignores *.pyc + +###################################################################### +# Gradle Ignores + +/gradle/.gradle +/gradle/gradle +/gradle/gradlew +/gradle/gradlew.bat +/gradle/**/WEB-INF +/gradle/**/build + diff --git a/docs/install.md b/docs/install.md index 5d2ac22d3..36b46c09c 100644 --- a/docs/install.md +++ b/docs/install.md @@ -7,8 +7,8 @@ This document covers the steps necessary to download, build, and deploy Nomulus. You will need the following programs installed on your local machine: * A recent version of the [Java 8 JDK][java-jdk8]. -* [Bazel build system](http://bazel.io/) (version [0.17.1][bazel-version] - works as of 2018-09-14). +* [Bazel build system](http://bazel.io/) (version [0.17.2][bazel-version] + works as of 2018-10-03). * [Google App Engine SDK for Java][app-engine-sdk], and configure aliases to to the `gcloud` and `appcfg.sh` utilities (you'll use them a lot). * [Git](https://git-scm.com/) version control system. @@ -181,4 +181,4 @@ See the [first steps tutorial](./first-steps-tutorial.md) for more information. [app-engine-sdk]: https://cloud.google.com/appengine/docs/java/download [java-jdk8]: http://www.oracle.com/technetwork/java/javase/downloads -[bazel-version]: https://github.com/bazelbuild/bazel/releases/download/0.17.1/bazel-0.17.1-installer-linux-x86_64.sh +[bazel-version]: https://github.com/bazelbuild/bazel/releases/download/0.17.2/bazel-0.17.2-installer-linux-x86_64.sh diff --git a/gradle/README.md b/gradle/README.md new file mode 100644 index 000000000..62c7a557c --- /dev/null +++ b/gradle/README.md @@ -0,0 +1,63 @@ +This folder contains experimental Gradle scripts as an alternative to Bazel for +the open-source Nomulus project. These are work-in-progress and are expected to +evolve in the near future. + +All testing is done with Gradle v4.10.2. + +## Current status + +Currently there are two sub-projects, third_party, which contains the +back-ported JUnit 4.13 code; and core, which contains all Nomulus source code. +Gradle can be used to compile and run all Java tests. + +Gradle is configured to use the directory containing this file as root, but use +the existing Nomulus source tree. + +Dependencies are mostly the same as in Bazel, with a few exceptions: + +* org.slf4j:slf4j-simple is added to provide a logging implementation in + tests. Bazel does not need this. +* com.googlecode.java-diff-utils:diffutils is not included. Bazel needs it for + Truth's equality check, but Gradle works fine without it. +* jaxb 2.2.11 is used instead of 2.3 in Bazel, since the latter breaks the + ant.xjc task. The problem is reportedly fixed in jaxb 2.4. +* The other dependencies are copied from Nomulus' repository.bzl config. + * We still need to verify if there are unused dependencies. + * Many dependencies are behind their latest versions. + +### Notable Issues + +Only single-threaded test execution is allowed, due to race condition over +global resources, such as the local Datastore instance, or updates to the System +properties. This is a new problem with Gradle, which does not provide as much +test isolation as Bazel. We are exploring solutions to this problem. + +Test suites (RdeTestSuite and TmchTestSuite) are ignored to avoid duplicate +execution of tests. Neither suite performs any shared test setup routine, so it +is easier to exclude the suite classes than individual test classes. + +Since Gradle does not support hierarchical build files, all file sets (e.g., +resources) must be declared at the top, in root project config or the +sub-project configs. + +## Initial Setup + +Install Gradle on your local host, then run the following commands from this +directory: + +```shell +# One-time command to add gradle wrapper: +gradle wrapper + +# Start the build: +./gradlew build +``` + +From now on, use './gradlew build' or './gradlew test' to build and test your +changes. + +To upgrade to a new Gradle version for this project, use: + +```shell +gradle wrapper --gradle-version version-number +``` diff --git a/gradle/build.gradle b/gradle/build.gradle new file mode 100644 index 000000000..9c8b11b2c --- /dev/null +++ b/gradle/build.gradle @@ -0,0 +1,29 @@ +buildscript { + repositories { + jcenter() + mavenCentral() + + maven { + url 'https://plugins.gradle.org/m2/' + } + } +} + +allprojects { + repositories { + jcenter() + mavenCentral() + flatDir { + // The objectify jar that comes with Nomulus. + dirs "${rootDir}/../third_party/objectify/v4_1" + } + } + + // Single version across all projects for now. + version = '1.0' + + // Java plugin: + apply plugin: 'java' +} + + diff --git a/gradle/core/build.gradle b/gradle/core/build.gradle new file mode 100644 index 000000000..c9745215c --- /dev/null +++ b/gradle/core/build.gradle @@ -0,0 +1,366 @@ +plugins { + id 'java-library' +} + +def javaDir = "${rootDir}/../java" +def javatestsDir = "${rootDir}/../javatests" + +def generatedDir = "${project.buildDir}/generated-sources" + +sourceSets { + main { + java { + srcDirs = [ + "${javaDir}", + "${generatedDir}" + ] + } + resources { + srcDirs = [ + "${javaDir}" + ] + exclude '**/*.java', '**/*.xjb' + } + } + test { + java { + srcDirs = [ + "${javatestsDir}", + "${generatedDir}" + ] + } + resources { + srcDirs = [ + "${javatestsDir}" + ] + exclude '**/*.java', '**/*.xsd', '**/*.xjb' + } + } +} + +configurations { + css + jaxb + soy +} + + +// Relevant canned dependency labels: +// - implementation: Dependencies to be included in release distribution. +// - compileOnly: Dependencies used at compile time only for production code. They will not be +// included in release. +// - testImplementation: Dependencies needed for testing only. +dependencies { + implementation 'com.beust:jcommander:1.48' + implementation 'com.fasterxml.jackson.core:jackson-core:2.8.5' + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.8.0' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.8.5' + implementation 'com.google.api-client:google-api-client:1.22.0' + implementation 'com.google.api-client:google-api-client-appengine:1.22.0' + implementation 'com.google.api-client:google-api-client-jackson2:1.20.0' + implementation 'com.google.monitoring-client:metrics:1.0.4' + implementation 'com.google.monitoring-client:stackdriver:1.0.4' + implementation 'com.google.api-client:google-api-client-java6:1.20.0' + implementation 'com.google.api-client:google-api-client-servlet:1.22.0' + implementation 'com.google.apis:google-api-services-admin-directory:directory_v1-rev72-1.22.0' + implementation 'com.google.apis:google-api-services-bigquery:v2-rev325-1.22.0' + implementation 'com.google.apis:google-api-services-clouddebugger:v2-rev8-1.22.0' + implementation 'com.google.apis:google-api-services-cloudkms:v1-rev12-1.22.0' + implementation 'com.google.apis:google-api-services-cloudresourcemanager:v1-rev6-1.22.0' + implementation 'com.google.apis:google-api-services-dataflow:v1b3-rev196-1.22.0' + implementation 'com.google.apis:google-api-services-dns:v2beta1-rev6-1.22.0' + implementation 'com.google.apis:google-api-services-drive:v2-rev160-1.19.1' + implementation 'com.google.apis:google-api-services-groupssettings:v1-rev60-1.22.0' + implementation 'com.google.apis:google-api-services-monitoring:v3-rev11-1.22.0' + implementation 'com.google.apis:google-api-services-sheets:v4-rev483-1.22.0' + implementation 'com.google.apis:google-api-services-storage:v1-rev86-1.22.0' + // TODO(b/71631624): change appengine:appengine-api-1.0-sdk to testCompileOnly after + // BillingEmailUtilsTest.java is fixed. + implementation 'com.google.appengine:appengine-api-1.0-sdk:1.9.48' + implementation 'com.google.appengine:appengine-api-labs:1.9.48' + implementation 'com.google.appengine:appengine-api-stubs:1.9.48' + implementation 'com.google.appengine.tools:appengine-gcs-client:0.6' + implementation 'com.google.appengine.tools:appengine-mapreduce:0.8.5' + implementation 'com.google.appengine.tools:appengine-pipeline:0.2.13' + implementation 'com.google.appengine:appengine-tools-sdk:1.9.48' + implementation 'com.google.auth:google-auth-library-credentials:0.7.1' + implementation 'com.google.auth:google-auth-library-oauth2-http:0.7.1' + implementation 'com.google.auto:auto-common:0.8' + implementation 'com.google.auto.factory:auto-factory:1.0-beta3' + implementation 'com.google.auto.value:auto-value-annotations:1.6.2' + implementation 'com.google.cloud.bigdataoss:gcsio:1.4.5' + implementation 'com.google.cloud.bigdataoss:util:1.4.5' + implementation 'com.google.code.findbugs:jsr305:3.0.2' + implementation 'com.google.dagger:dagger:2.15' + implementation 'com.google.dagger:dagger-producers:2.15' + implementation 'com.google.errorprone:error_prone_annotations:2.1.3' + implementation 'com.google.errorprone:javac-shaded:9-dev-r4023-3' + implementation 'com.google.flogger:flogger:0.1' + implementation 'com.google.flogger:flogger-system-backend:0.1' + implementation 'com.google.gdata:core:1.47.1' + implementation 'com.google.googlejavaformat:google-java-format:1.4' + implementation 'com.google.guava:guava:25.1-jre' + implementation 'com.google.http-client:google-http-client:1.22.0' + implementation 'com.google.http-client:google-http-client-appengine:1.22.0' + implementation 'com.google.http-client:google-http-client-jackson2:1.22.0' + implementation 'com.google.oauth-client:google-oauth-client:1.22.0' + implementation 'com.google.oauth-client:google-oauth-client-appengine:1.22.0' + implementation 'com.google.oauth-client:google-oauth-client-java6:1.22.0' + implementation 'com.google.oauth-client:google-oauth-client-jetty:1.22.0' + implementation 'com.google.oauth-client:google-oauth-client-servlet:1.22.0' + implementation 'com.google.protobuf:protobuf-java:2.6.0' + implementation 'com.google.re2j:re2j:1.1' + implementation 'com.google.template:soy:2018-03-14' + implementation 'com.googlecode.charts4j:charts4j:1.3' + implementation 'com.googlecode.json-simple:json-simple:1.1.1' + implementation 'com.ibm.icu:icu4j:57.1' + implementation 'com.jcraft:jsch:0.1.53' + implementation 'com.jcraft:jzlib:1.1.3' + implementation 'com.squareup:javapoet:1.8.0' + implementation 'com.squareup:javawriter:2.5.1' + implementation 'com.sun.activation:javax.activation:1.2.0' + implementation 'com.thoughtworks.paranamer:paranamer:2.7' + implementation 'commons-codec:commons-codec:1.6' + implementation 'commons-logging:commons-logging:1.1.1' + implementation 'dnsjava:dnsjava:2.1.7' + implementation 'io.netty:netty-buffer:4.1.28.Final' + implementation 'io.netty:netty-codec:4.1.28.Final' + implementation 'io.netty:netty-codec-http:4.1.28.Final' + implementation 'io.netty:netty-common:4.1.28.Final' + implementation 'io.netty:netty-handler:4.1.28.Final' + implementation 'io.netty:netty-resolver:4.1.28.Final' + implementation 'io.netty:netty-tcnative:2.0.12.Final' + implementation 'io.netty:netty-tcnative-boringssl-static:2.0.12.Final' + implementation 'io.netty:netty-transport:4.1.28.Final' + implementation 'it.unimi.dsi:fastutil:6.5.16' + implementation 'javax.annotation:jsr250-api:1.0' + implementation 'javax.inject:javax.inject:1' + implementation 'javax.mail:mail:1.4' + implementation 'javax.servlet:servlet-api:2.5' + implementation 'javax.xml.bind:jaxb-api:2.3.0' + implementation 'javax.xml.soap:javax.xml.soap-api:1.4.0' + implementation 'jline:jline:1.0' + implementation 'joda-time:joda-time:2.3' + implementation 'org.apache.avro:avro:1.8.2' + implementation 'org.apache.beam:beam-runners-direct-java:2.2.0' + implementation 'org.apache.beam:beam-runners-google-cloud-dataflow-java:2.1.0' + implementation 'org.apache.beam:beam-sdks-common-runner-api:2.1.0' + implementation 'org.apache.beam:beam-sdks-java-core:2.2.0' + implementation 'org.apache.beam:beam-sdks-java-extensions-google-cloud-platform-core:2.1.0' + implementation 'org.apache.beam:beam-sdks-java-io-google-cloud-platform:2.2.0' + implementation 'org.apache.commons:commons-compress:1.8.1' + implementation 'org.apache.ftpserver:ftpserver-core:1.0.6' + implementation 'org.apache.httpcomponents:httpclient:4.5.2' + implementation 'org.apache.httpcomponents:httpcore:4.4.4' + implementation 'org.apache.mina:mina-core:2.0.4' + implementation 'org.apache.sshd:sshd-core:2.0.0' + implementation 'org.apache.sshd:sshd-scp:2.0.0' + implementation 'org.apache.sshd:sshd-sftp:2.0.0' + implementation 'org.apache.tomcat:servlet-api:6.0.45' + implementation 'org.apache.tomcat:tomcat-annotations-api:8.0.5' + implementation 'org.bouncycastle:bcpg-jdk15on:1.52' + implementation 'org.bouncycastle:bcpkix-jdk15on:1.52' + implementation 'org.bouncycastle:bcprov-jdk15on:1.52' + implementation 'org.codehaus.jackson:jackson-core-asl:1.9.13' + implementation 'org.codehaus.jackson:jackson-mapper-asl:1.9.13' + implementation 'org.joda:joda-money:0.10.0' + implementation 'org.json:json:20160810' + implementation 'org.khronos:opengl-api:gl1.1-android-2.1_r1' + implementation 'org.mortbay.jetty:jetty:6.1.26' + implementation 'org.mortbay.jetty:servlet-api:2.5-20081211' + implementation 'org.mortbay.jetty:jetty-util:6.1.26' + implementation 'org.slf4j:slf4j-api:1.7.16' + implementation 'org.tukaani:xz:1.5' + implementation 'org.xerial.snappy:snappy-java:1.1.4-M3' + implementation 'org.yaml:snakeyaml:1.17' + implementation 'xerces:xmlParserAPIs:2.6.2' + implementation 'xpp3:xpp3:1.1.4c' + // Custom-built objectify jar at commit ecd5165, included in Nomulus release. + implementation name: 'objectify-4.1.3' + + compileOnly 'com.google.appengine:appengine-remote-api:1.9.48' // Also testImplementation + compileOnly 'com.google.auto.service:auto-service:1.0-rc4' + compileOnly 'org.osgi:org.osgi.core:4.3.0' + + annotationProcessor 'com.google.auto.value:auto-value:1.6.2' + testAnnotationProcessor 'com.google.auto.value:auto-value:1.6.2' + annotationProcessor 'com.google.dagger:dagger-compiler:2.15' + testAnnotationProcessor 'com.google.dagger:dagger-compiler:2.15' + + testImplementation 'com.google.appengine:appengine-remote-api:1.9.48' // Also compileOnly + testImplementation 'com.google.appengine:appengine-testing:1.9.58' + testImplementation 'com.google.guava:guava-testlib:25.0-jre' + testImplementation 'com.google.monitoring-client:contrib:1.0.4' + testImplementation 'com.google.truth:truth:0.42' + testImplementation 'com.google.truth.extensions:truth-java8-extension:0.39' + testImplementation 'org.hamcrest:hamcrest-all:1.3' + testImplementation 'org.hamcrest:hamcrest-core:1.3' + testImplementation 'org.hamcrest:hamcrest-library:1.3' + testImplementation 'junit:junit:4.12' + testImplementation 'org.mockito:mockito-all:1.9.5' + testImplementation 'org.slf4j:slf4j-simple:1.7.16' // Not needed by Bazel + + testImplementation project(':third_party') + + // Dependencies needed for jaxb compilation. + // Use jaxb 2.2.11 because 2.3 is known to break the Ant task we use. + // TODO: upgrade jaxb versions to 2.4.0, already in beta by Sept 2018 + jaxb 'javax.xml.bind:jaxb-api:2.2.11' + jaxb 'com.sun.activation:javax.activation:1.2.0' + jaxb 'com.sun.xml.bind:jaxb-xjc:2.2.11' + jaxb 'com.sun.xml.bind:jaxb-impl:2.2.11' + jaxb 'com.sun.xml.bind:jaxb-osgi:2.2.11' + + // Dependency needed for soy to java compilation. + soy 'com.google.template:soy:2018-03-14' + + // Dependencies needed for compiling stylesheets to javascript + css 'com.google.closure-stylesheets:closure-stylesheets:1.5.0' + css 'args4j:args4j:2.0.26' +} + +task jaxbToJava() { + doLast { + file(generatedDir).mkdirs() + + // Temp dir to hold schema and bindings files. Files must be in the same directory because + // the bindings (.xjb) file does not declare relative paths to schema (.xsd) files. + def xjcTempSourceDir = file("${temporaryDir}/xjc") + xjcTempSourceDir.mkdirs() + ant.copy( + todir: "${xjcTempSourceDir}", + overwrite: true) { + fileSet( + dir: "${javaDir}/google/registry/xml/xsd", + includes: '**.xsd') + } + ant.copy( + todir: "${xjcTempSourceDir}", + overwrite: true, + file: + "${javaDir}/google/registry/xjc/bindings.xjb") + + ant.taskdef( + name: 'xjc', + classname: 'com.sun.tools.xjc.XJCTask', + classpath: configurations.jaxb.asPath) + ant.xjc( + destdir: "${generatedDir}", + binding: "${xjcTempSourceDir}/bindings.xjb", + removeOldOutput: 'yes', extension: 'true') { + project.fileTree( + dir: new File("$xjcTempSourceDir"), + include: ['**/*.xsd']) + .addToAntBuilder(ant, 'schema', FileCollection.AntType.FileSet) + // -npa: do not generate package-info.java files. They will be generated below. + arg(line: '-npa -quiet -extension') + } + exec { + workingDir "${generatedDir}" + + executable "${javaDir}/google/registry/xjc/make_pkginfo.sh" + args "${javaDir}/google/registry/xjc/package-info.java.in", + "${generatedDir}/google/registry/xjc" + } + } +} + +task soyToJava() { + ext.soyToJava = { javaPackage, outputDirectory, soyFiles -> + javaexec { + main = "com.google.template.soy.SoyParseInfoGenerator" + classpath configurations.soy + args "--javaPackage", "${javaPackage}", + "--outputDirectory", "${outputDirectory}", + "--javaClassNameSource", "filename", + "--allowExternalCalls", "true", + "--srcs", "${soyFiles.join(',')}" + } + } + + doLast { + + soyToJava('google.registry.tools.soy', "${generatedDir}/google/registry/tools/soy", + fileTree(dir: "${javaDir}/google/registry/tools/soy", include: ['**/*.soy'])) + + soyToJava('google.registry.ui.soy.registrar', + "${generatedDir}/google/registry/ui/soy/registrar", + fileTree(dir: "${javaDir}/google/registry/ui/soy/registrar", include: ['**/*.soy'])) + + soyToJava('google.registry.ui.soy', + "${generatedDir}/google/registry/ui/soy", + files { + file("${javaDir}/google/registry/ui/soy").listFiles() + }.filter { + it.name.endsWith(".soy") + }) + } +} + +task stylesheetsToJavascript { + ext.cssCompile = { outputName, debug, cssFiles -> + javaexec { + main = "com.google.common.css.compiler.commandline.ClosureCommandLineCompiler" + classpath configurations.css + + def argsBuffer = [ + "--output-file", "${outputName}.css", + "--output-source-map", "${outputName}.css.map", + "--input-orientation", "LTR", + "--output-orientation", "NOCHANGE", + "--output-renaming-map", "${outputName}.css.js", + "--output-renaming-map-format", "CLOSURE_COMPILED_SPLIT_HYPHENS" + ] + if (debug) { + argsBuffer.addAll(["--rename", "DEBUG", "--pretty-print"]) + } else { + argsBuffer.addAll(["--rename", "CLOSURE"]) + } + + argsBuffer.addAll(cssFiles) + args argsBuffer + } + } + + doLast { + def cssSourceDir = "${javaDir}/google/registry/ui/css" + def outputDir = "${project.buildDir}/resources/main/google/registry/ui/css" + file("${outputDir}").mkdirs() + def srcFiles = [ + "${cssSourceDir}/console.css", "${cssSourceDir}/contact-settings.css", + "${cssSourceDir}/contact-us.css", "${cssSourceDir}/dashboard.css", + "${cssSourceDir}/epp.css", "${cssSourceDir}/forms.css", + "${cssSourceDir}/kd_components.css", "${cssSourceDir}/registry.css", + "${cssSourceDir}/resources.css", "${cssSourceDir}/security-settings.css" + ] + cssCompile("${outputDir}/registrar_bin", false, srcFiles) + cssCompile("${outputDir}/registrar_dbg", true, srcFiles) + } +} + +compileJava.dependsOn jaxbToJava +compileJava.dependsOn soyToJava + +// stylesheetsToJavascript must happen after processResources, which wipes the resources folder +// before copying data into it. +stylesheetsToJavascript.dependsOn processResources +classes.dependsOn stylesheetsToJavascript + + +test { + // Test exclusion patterns: + // - *TestCase.java are inherited by concrete test classes. + // - *TestSuite.java are excluded to avoid duplicate execution of suite members. See README + // in this directory for more information. + exclude "**/*TestCase.*", "**/*TestSuite.*" + + // Use a single JVM to execute all tests. See README in this directory for more information. + maxParallelForks 1 + + // Use a single thread to execute all tests in a JVM. See README in this directory for more + // information. + forkEvery 1 + + // Uncomment to see test outputs in stdout. + //testLogging.showStandardStreams = true +} diff --git a/gradle/settings.gradle b/gradle/settings.gradle new file mode 100644 index 000000000..3a9c6015f --- /dev/null +++ b/gradle/settings.gradle @@ -0,0 +1,5 @@ +rootProject.name = 'nomulus' + +include 'third_party' +include 'core' + diff --git a/gradle/third_party/build.gradle b/gradle/third_party/build.gradle new file mode 100644 index 000000000..acaf8c817 --- /dev/null +++ b/gradle/third_party/build.gradle @@ -0,0 +1,14 @@ +plugins { + id 'java-library' +} + +sourceSets { + main { + java { + srcDirs = [ + "${rootDir}/../third_party/junit/" + ] + include '**/*.java' + } + } +} diff --git a/java/google/registry/beam/invoicing/InvoicingPipeline.java b/java/google/registry/beam/invoicing/InvoicingPipeline.java index 69c8c1f0f..6e3bfe632 100644 --- a/java/google/registry/beam/invoicing/InvoicingPipeline.java +++ b/java/google/registry/beam/invoicing/InvoicingPipeline.java @@ -34,6 +34,7 @@ import org.apache.beam.sdk.options.PipelineOptionsFactory; import org.apache.beam.sdk.options.ValueProvider; import org.apache.beam.sdk.options.ValueProvider.NestedValueProvider; import org.apache.beam.sdk.transforms.Count; +import org.apache.beam.sdk.transforms.Filter; import org.apache.beam.sdk.transforms.MapElements; import org.apache.beam.sdk.transforms.PTransform; import org.apache.beam.sdk.values.KV; @@ -141,6 +142,7 @@ public class InvoicingPipeline implements Serializable { "Map to invoicing key", MapElements.into(TypeDescriptor.of(InvoiceGroupingKey.class)) .via(BillingEvent::getInvoiceGroupingKey)) + .apply(Filter.by((InvoiceGroupingKey key) -> key.unitPrice() != 0)) .setCoder(new InvoiceGroupingKeyCoder()) .apply("Count occurrences", Count.perElement()) .apply( diff --git a/java/google/registry/config/RegistryConfig.java b/java/google/registry/config/RegistryConfig.java index 802ae4fd2..65f9883d5 100644 --- a/java/google/registry/config/RegistryConfig.java +++ b/java/google/registry/config/RegistryConfig.java @@ -24,16 +24,20 @@ import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.net.HostAndPort; import dagger.Module; import dagger.Provides; -import google.registry.config.RegistryConfigSettings.AppEngine.ToolsServiceUrl; +import google.registry.util.RandomStringGenerator; +import google.registry.util.StringGenerator; import google.registry.util.TaskQueueUtils; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.net.URI; import java.net.URL; +import java.security.NoSuchAlgorithmException; +import java.security.ProviderException; +import java.security.SecureRandom; import java.util.Optional; +import java.util.Random; import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.inject.Named; @@ -815,6 +819,7 @@ public final class RegistryConfig { * * @see google.registry.reporting.icann.ReportingEmailUtils * @see google.registry.reporting.billing.BillingEmailUtils + * @see google.registry.reporting.spec11.Spec11EmailUtils */ @Provides @Config("alertRecipientEmailAddress") @@ -827,13 +832,35 @@ public final class RegistryConfig { * * @see google.registry.reporting.icann.ReportingEmailUtils * @see google.registry.reporting.billing.BillingEmailUtils + * @see google.registry.reporting.spec11.Spec11EmailUtils */ - @Provides @Config("alertSenderEmailAddress") public static String provideAlertSenderEmailAddress( @Config("projectId") String projectId, RegistryConfigSettings config) { - return String.format("%s@%s", projectId, config.misc.alertEmailSenderDomain); + return String.format("%s-no-reply@%s", projectId, config.misc.alertEmailSenderDomain); + } + + /** + * Returns the email address to which spec 11 email should be replied. + * + * @see google.registry.reporting.spec11.Spec11EmailUtils + */ + @Provides + @Config("spec11ReplyToEmailAddress") + public static String provideSpec11ReplyToEmailAddress(RegistryConfigSettings config) { + return config.misc.spec11ReplyToEmailAddress; + } + + /** + * Returns the template for the body of the spec 11 email to the registrars. + * + * @see google.registry.reporting.spec11.Spec11EmailUtils + */ + @Provides + @Config("spec11EmailBodyTemplate") + public static String provideSpec11EmailBodyTemplate(RegistryConfigSettings config) { + return config.registryPolicy.spec11EmailBodyTemplate; } /** @@ -1021,6 +1048,12 @@ public final class RegistryConfig { return config.registryPolicy.greetingServerId; } + @Provides + @Config("activeKeyring") + public static String provideKeyring(RegistryConfigSettings config) { + return config.keyring.activeKeyring; + } + /** * The name to use for the Cloud KMS KeyRing containing encryption keys for Nomulus secrets. * @@ -1030,13 +1063,13 @@ public final class RegistryConfig { @Provides @Config("cloudKmsKeyRing") public static String provideCloudKmsKeyRing(RegistryConfigSettings config) { - return config.kms.keyringName; + return config.keyring.kms.keyringName; } @Provides @Config("cloudKmsProjectId") public static String provideCloudKmsProjectId(RegistryConfigSettings config) { - return config.kms.projectId; + return config.keyring.kms.projectId; } @Provides @@ -1061,7 +1094,7 @@ public final class RegistryConfig { @Provides @Config("premiumTermsExportDisclaimer") public static String providePremiumTermsExportDisclaimer(RegistryConfigSettings config) { - return formatComments(config.registryPolicy.reservedTermsExportDisclaimer); + return formatComments(config.registryPolicy.premiumTermsExportDisclaimer); } /** @@ -1186,6 +1219,12 @@ public final class RegistryConfig { return config.registryTool.clientSecretFilename; } + @Provides + @Config("rdapTos") + public static ImmutableList provideRdapTos(RegistryConfigSettings config) { + return ImmutableList.copyOf(Splitter.on('\n').split(config.registryPolicy.rdapTos)); + } + /** * Returns the help text to be used by RDAP. * @@ -1195,7 +1234,8 @@ public final class RegistryConfig { @Singleton @Provides @Config("rdapHelpMap") - public static ImmutableMap provideRdapHelpMap() { + public static ImmutableMap provideRdapHelpMap( + @Config("rdapTos") ImmutableList rdapTos) { return new ImmutableMap.Builder() .put("/", RdapNoticeDescriptor.builder() .setTitle("RDAP Help") @@ -1216,37 +1256,51 @@ public final class RegistryConfig { .build()) .put("/tos", RdapNoticeDescriptor.builder() .setTitle("RDAP Terms of Service") - .setDescription(ImmutableList.of( - "By querying our Domain Database as part of the RDAP pilot program (RDAP Domain" - + "Database), you are agreeing to comply with these terms, so please read" - + " them carefully.", - "Any information provided is 'as is' without any guarantee of accuracy.", - "Please do not misuse the RDAP Domain Database. It is intended solely for" - + " query-based access on an experimental basis and should not be used for or" - + " relied upon for any other purpose.", - "Don't use the RDAP Domain Database to allow, enable, or otherwise support the" - + " transmission of mass unsolicited, commercial advertising or" - + " solicitations.", - "Don't access our RDAP Domain Database through the use of high volume, automated" - + " electronic processes that send queries or data to the systems of any" - + " ICANN-accredited registrar.", - "You may only use the information contained in the RDAP Domain Database for" - + " lawful purposes.", - "Do not compile, repackage, disseminate, or otherwise use the information" - + " contained in the RDAP Domain Database in its entirety, or in any" - + " substantial portion, without our prior written permission.", - "We may retain certain details about queries to our RDAP Domain Database for the" - + " purposes of detecting and preventing misuse.", - "We reserve the right to restrict or deny your access to the RDAP Domain Database" - + " if we suspect that you have failed to comply with these terms.", - "We reserve the right to modify or discontinue our participation in the RDAP" - + " pilot program and suspend or terminate access to the RDAP Domain Database" - + " at any time and for any reason in our sole discretion.", - "We reserve the right to modify this agreement at any time.")) + .setDescription(rdapTos) .setLinkValueSuffix("help/tos") .build()) .build(); } + + /** + * Returns a singleton insecure random number generator that is fast. + * + *

This binding is intentionally qualified so that any requester must explicitly acknowledge + * that using an insecure random number generator is fine for its use case. + */ + @Singleton + @Provides + @Config("insecureRandom") + public static Random provideInsecureRandom() { + return new Random(); + } + + /** Returns a singleton secure random number generator this is slow. */ + @Singleton + @Provides + public static SecureRandom provideSecureRandom() { + try { + return SecureRandom.getInstance("NativePRNG"); + } catch (NoSuchAlgorithmException e) { + throw new ProviderException(e); + } + } + + /** Returns a singleton random string generator using Base58 encoding. */ + @Singleton + @Provides + @Config("base58StringGenerator") + public static StringGenerator provideBase58StringGenerator(SecureRandom secureRandom) { + return new RandomStringGenerator(StringGenerator.Alphabets.BASE_58, secureRandom); + } + + /** Returns a singleton random string generator using Base58 encoding. */ + @Singleton + @Provides + @Config("base64StringGenerator") + public static StringGenerator provideBase64StringGenerator(SecureRandom secureRandom) { + return new RandomStringGenerator(StringGenerator.Alphabets.BASE_64, secureRandom); + } } /** @@ -1295,14 +1349,44 @@ public final class RegistryConfig { return Duration.standardDays(30); } + public static boolean areServersLocal() { + return CONFIG_SETTINGS.get().appEngine.isLocal; + } + /** - * Returns the address of the Nomulus app HTTP server. + * Returns the address of the Nomulus app default HTTP server. * *

This is used by the {@code nomulus} tool to connect to the App Engine remote API. */ - public static HostAndPort getServer() { - ToolsServiceUrl url = CONFIG_SETTINGS.get().appEngine.toolsServiceUrl; - return HostAndPort.fromParts(url.hostName, url.port); + public static URL getDefaultServer() { + return makeUrl(CONFIG_SETTINGS.get().appEngine.defaultServiceUrl); + } + + /** + * Returns the address of the Nomulus app backend HTTP server. + * + *

This is used by the {@code nomulus} tool to connect to the App Engine remote API. + */ + public static URL getBackendServer() { + return makeUrl(CONFIG_SETTINGS.get().appEngine.backendServiceUrl); + } + + /** + * Returns the address of the Nomulus app tools HTTP server. + * + *

This is used by the {@code nomulus} tool to connect to the App Engine remote API. + */ + public static URL getToolsServer() { + return makeUrl(CONFIG_SETTINGS.get().appEngine.toolsServiceUrl); + } + + /** + * Returns the address of the Nomulus app pubapi HTTP server. + * + *

This is used by the {@code nomulus} tool to connect to the App Engine remote API. + */ + public static URL getPubapiServer() { + return makeUrl(CONFIG_SETTINGS.get().appEngine.pubapiServiceUrl); } /** Returns the amount of time a singleton should be cached, before expiring. */ @@ -1410,7 +1494,7 @@ public final class RegistryConfig { * change the contents of the YAML config files. */ @VisibleForTesting - static final Supplier CONFIG_SETTINGS = + public static final Supplier CONFIG_SETTINGS = memoize(YamlUtils::getConfigSettings); private static String formatComments(String text) { diff --git a/java/google/registry/config/RegistryConfigSettings.java b/java/google/registry/config/RegistryConfigSettings.java index 0aeabba1d..58561f0fa 100644 --- a/java/google/registry/config/RegistryConfigSettings.java +++ b/java/google/registry/config/RegistryConfigSettings.java @@ -34,19 +34,17 @@ public class RegistryConfigSettings { public Monitoring monitoring; public Misc misc; public Beam beam; - public Kms kms; + public Keyring keyring; public RegistryTool registryTool; /** Configuration options that apply to the entire App Engine project. */ public static class AppEngine { public String projectId; - public ToolsServiceUrl toolsServiceUrl; - - /** Configuration options for the tools service URL. */ - public static class ToolsServiceUrl { - public String hostName; - public int port; - } + public boolean isLocal; + public String defaultServiceUrl; + public String backendServiceUrl; + public String toolsServiceUrl; + public String pubapiServiceUrl; } /** Configuration options for OAuth settings for authenticating users. */ @@ -81,7 +79,6 @@ public class RegistryConfigSettings { public String greetingServerId; public List registrarChangesNotificationEmailAddresses; public String defaultRegistrarWhoisServer; - public String defaultRegistrarReferralUrl; public String tmchCaMode; public String tmchCrlUrl; public String tmchMarksDbUrl; @@ -90,6 +87,8 @@ public class RegistryConfigSettings { public String premiumTermsExportDisclaimer; public String reservedTermsExportDisclaimer; public String whoisDisclaimer; + public String rdapTos; + public String spec11EmailBodyTemplate; } /** Configuration for Cloud Datastore. */ @@ -99,12 +98,6 @@ public class RegistryConfigSettings { public int baseOfyRetryMillis; } - /** Configuration for Cloud KMS. */ - public static class Kms { - public String keyringName; - public String projectId; - } - /** Configuration for Apache Beam (Cloud Dataflow). */ public static class Beam { public String defaultJobZone; @@ -166,10 +159,23 @@ public class RegistryConfigSettings { public static class Misc { public String sheetExportId; public String alertRecipientEmailAddress; + public String spec11ReplyToEmailAddress; public String alertEmailSenderDomain; public int asyncDeleteDelaySeconds; } + /** Configuration for keyrings (used to store secrets outside of source). */ + public static class Keyring { + public String activeKeyring; + public Kms kms; + } + + /** Configuration for Cloud KMS. */ + public static class Kms { + public String keyringName; + public String projectId; + } + /** Configuration options for the registry tool. */ public static class RegistryTool { public String clientSecretFilename; diff --git a/java/google/registry/config/files/default-config.yaml b/java/google/registry/config/files/default-config.yaml index de850501e..9c4a37628 100644 --- a/java/google/registry/config/files/default-config.yaml +++ b/java/google/registry/config/files/default-config.yaml @@ -9,10 +9,13 @@ appEngine: # Globally unique App Engine project ID projectId: registry-project-id - # Hostname and port of the tools service for the project. - toolsServiceUrl: - hostName: localhost - port: 443 + # whether to use local/test credentials when connecting to the servers + isLocal: true + # URLs of the services for the project. + defaultServiceUrl: https://localhost + backendServiceUrl: https://localhost + toolsServiceUrl: https://localhost + pubapiServiceUrl: https://localhost gSuite: # Publicly accessible domain name of the running G Suite instance. @@ -103,6 +106,72 @@ registryPolicy: unlawful behavior. We reserve the right to restrict or deny your access to the WHOIS database, and may modify these terms at any time. + # RDAP Terms of Service text displayed at the /rdap/help/tos endpoint. + rdapTos: > + By querying our Domain Database as part of the RDAP pilot program (RDAP + Domain Database), you are agreeing to comply with these terms, so please + read them carefully. + + Any information provided is 'as is' without any guarantee of accuracy. + + Please do not misuse the RDAP Domain Database. It is intended solely for + query-based access on an experimental basis and should not be used for or + relied upon for any other purpose. + + Don't use the RDAP Domain Database to allow, enable, or otherwise support + the transmission of mass unsolicited, commercial advertising or + solicitations. + + Don't access our RDAP Domain Database through the use of high volume, + automated electronic processes that send queries or data to the systems + of any ICANN-accredited registrar. + + You may only use the information contained in the RDAP Domain Database for + lawful purposes. + + Do not compile, repackage, disseminate, or otherwise use the information + contained in the RDAP Domain Database in its entirety, or in any + substantial portion, without our prior written permission. + + We may retain certain details about queries to our RDAP Domain Database + for the purposes of detecting and preventing misuse. + + We reserve the right to restrict or deny your access to the RDAP Domain + Database if we suspect that you have failed to comply with these terms. + + We reserve the right to modify or discontinue our participation in the + RDAP pilot program and suspend or terminate access to the RDAP Domain + Database at any time and for any reason in our sole discretion. + + We reserve the right to modify this agreement at any time. + + # Body of the spec 11 email sent to registrars. + # Items in braces are to be replaced. + spec11EmailBodyTemplate: | + Dear registrar partner, + + The registry conducts periodic technical analyses of all domains registered + in its TLDs. As part of this analysis, the following domains that you + manage were flagged for potential security concerns: + + {LIST_OF_THREATS} + + Please communicate these findings to the registrant and work with the + registrant to mitigate any security issues and have the domains delisted. + + Some helpful sites for getting off a blocked list include: + + - Google Search Console (https://search.google.com/search-console/about) + -- includes information and tools for webmasters to learn about and + mitigate security threats and have their websites delisted + - first.org -- a registry of Computer Emergency Response Teams (CERTs) + that may be able to assist in mitigation + - stopbadware.org -- a non-profit anti-malware organization that provides + support and information for webmasters dealing with security threats + + If you have any questions regarding this notice, please contact + {REPLY_TO_EMAIL}. + datastore: # Number of commit log buckets in Datastore. Lowering this after initial # install risks losing up to a days' worth of differential backups. @@ -266,6 +335,10 @@ misc: # Address we send alert summary emails to. alertRecipientEmailAddress: email@example.com + # Address to which the Spec 11 emails to registrars should be replied. This needs + # to be a deliverable email address in case the registrars want to contact us. + spec11ReplyToEmailAddress: reply-to@example.com + # Domain for the email address we send alert summary emails from. alertEmailSenderDomain: appspotmail.com @@ -283,14 +356,19 @@ beam: # The default zone to run Apache Beam (Cloud Dataflow) jobs in. defaultJobZone: us-east1-c -kms: - # GCP project containing the KMS keyring. Should only be used for KMS in - # order to keep a simple locked down IAM configuration. - projectId: registry-kms-project-id +keyring: + # The name of the active keyring, either "KMS" or "Dummy". + activeKeyring: Dummy - # The name to use for the Cloud KMS KeyRing which will store encryption keys - # for Nomulus secrets. - keyringName: nomulus + # Configuration options specific to Google Cloud KMS. + kms: + # GCP project containing the KMS keyring. Should only be used for KMS in + # order to keep a simple locked down IAM configuration. + projectId: registry-kms-project-id + + # The name to use for the Cloud KMS KeyRing which will store encryption keys + # for Nomulus secrets. + keyringName: nomulus # Configuration options relevant to the "nomulus" registry tool. registryTool: diff --git a/java/google/registry/config/files/nomulus-config-production-sample.yaml b/java/google/registry/config/files/nomulus-config-production-sample.yaml index 4a891e1d9..9d2e9ca44 100644 --- a/java/google/registry/config/files/nomulus-config-production-sample.yaml +++ b/java/google/registry/config/files/nomulus-config-production-sample.yaml @@ -4,11 +4,14 @@ appEngine: projectId: placeholder - # The "tools-dot-" prefix is used on the project ID in this URL in order to - # get around an issue with double-wildcard SSL certs. - toolsServiceUrl: - hostName: tools-dot-placeholder.appspot.com - port: 443 + # Set to true if running against local servers (localhost) + isLocal: false + # The "-dot-" prefix is used on the project ID in this URL in order + # to get around an issue with double-wildcard SSL certs. + defaultServiceUrl: https://domain-registry-placeholder.appspot.com + backendServiceUrl: https://backend-dot-domain-registry-placeholder.appspot.com + toolsServiceUrl: https://tools-dot-domain-registry-placeholder.appspot.com + pubapiServiceUrl: https://pubapi-dot-domain-registry-placeholder.appspot.com gSuite: domainName: placeholder @@ -61,5 +64,7 @@ cloudDns: rootUrl: null servicePath: null -kms: - projectId: placeholder +keyring: + activeKeyring: KMS + kms: + projectId: placeholder diff --git a/java/google/registry/env/common/default/WEB-INF/queue.xml b/java/google/registry/env/common/default/WEB-INF/queue.xml index 3e14770b7..7b2cf9afb 100644 --- a/java/google/registry/env/common/default/WEB-INF/queue.xml +++ b/java/google/registry/env/common/default/WEB-INF/queue.xml @@ -192,17 +192,6 @@ - - - bigquery-streaming-metrics - 500/s - 500 - - 1 - 1m - - - retryable-cron-tasks diff --git a/java/google/registry/flows/EppController.java b/java/google/registry/flows/EppController.java index a98517a9f..7a43e3ff8 100644 --- a/java/google/registry/flows/EppController.java +++ b/java/google/registry/flows/EppController.java @@ -22,7 +22,6 @@ import static google.registry.flows.FlowReporter.extractTlds; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.google.common.flogger.FluentLogger; @@ -33,7 +32,6 @@ import google.registry.model.eppoutput.EppOutput; import google.registry.model.eppoutput.EppResponse; import google.registry.model.eppoutput.Result; import google.registry.model.eppoutput.Result.Code; -import google.registry.monitoring.whitebox.BigQueryMetricsEnqueuer; import google.registry.monitoring.whitebox.EppMetric; import java.util.Optional; import javax.inject.Inject; @@ -52,7 +50,6 @@ public final class EppController { @Inject FlowComponent.Builder flowComponentBuilder; @Inject EppMetric.Builder eppMetricBuilder; @Inject EppMetrics eppMetrics; - @Inject BigQueryMetricsEnqueuer bigQueryMetricsEnqueuer; @Inject ServerTridProvider serverTridProvider; @Inject EppController() {} @@ -65,7 +62,6 @@ public final class EppController { boolean isSuperuser, byte[] inputXmlBytes) { eppMetricBuilder.setClientId(Optional.ofNullable(sessionMetadata.getClientId())); - eppMetricBuilder.setPrivilegeLevel(isSuperuser ? "SUPERUSER" : "NORMAL"); try { EppInput eppInput; try { @@ -99,7 +95,6 @@ public final class EppController { e.getResult(), Trid.create(null, serverTridProvider.createServerTrid())); } if (!eppInput.getTargetIds().isEmpty()) { - eppMetricBuilder.setEppTarget(Joiner.on(',').join(eppInput.getTargetIds())); if (eppInput.isDomainResourceType()) { eppMetricBuilder.setTlds(extractTlds(eppInput.getTargetIds())); } @@ -122,7 +117,6 @@ public final class EppController { } finally { if (!isDryRun) { EppMetric metric = eppMetricBuilder.build(); - bigQueryMetricsEnqueuer.export(metric); eppMetrics.incrementEppRequests(metric); eppMetrics.recordProcessingTime(metric); } diff --git a/java/google/registry/flows/EppXmlSanitizer.java b/java/google/registry/flows/EppXmlSanitizer.java index 2113dc5dc..ee20afe71 100644 --- a/java/google/registry/flows/EppXmlSanitizer.java +++ b/java/google/registry/flows/EppXmlSanitizer.java @@ -14,14 +14,18 @@ package google.registry.flows; +import static com.google.common.base.Preconditions.checkState; + import com.google.common.base.CharMatcher; import com.google.common.collect.ImmutableSet; import com.google.common.flogger.FluentLogger; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Locale; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.xml.namespace.QName; @@ -32,6 +36,7 @@ import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.Characters; +import javax.xml.stream.events.StartDocument; import javax.xml.stream.events.XMLEvent; /** @@ -71,31 +76,47 @@ public class EppXmlSanitizer { private static final XMLEventFactory XML_EVENT_FACTORY = XMLEventFactory.newFactory(); /** - * Returns sanitized and pretty-printed EPP XML message. For malformed XML messages, - * base64-encoded raw bytes will be returned. + * Returns sanitized EPP XML message. For malformed XML messages, base64-encoded raw bytes will be + * returned. * - *

The xml version header {@code } is added to the result if the input - * does not already contain it. Also, an empty element will be formatted as {@code } - * instead of {@code }. + *

The output always begins with version and encoding declarations no matter if the input + * includes them. If encoding is not declared by input, UTF-8 will be used according to XML + * standard. + * + *

Also, an empty element will be formatted as {@code } instead of {@code }. */ public static String sanitizeEppXml(byte[] inputXmlBytes) { try { // Keep exactly one newline at end of sanitized string. - return CharMatcher.whitespace() - .trimTrailingFrom(new String(sanitize(inputXmlBytes), StandardCharsets.UTF_8)) - + "\n"; - } catch (XMLStreamException e) { + return CharMatcher.whitespace().trimTrailingFrom(sanitizeAndEncode(inputXmlBytes)) + "\n"; + } catch (XMLStreamException | UnsupportedEncodingException e) { logger.atWarning().withCause(e).log("Failed to sanitize EPP XML message."); return Base64.getMimeEncoder().encodeToString(inputXmlBytes); } } - private static byte[] sanitize(byte[] inputXmlBytes) throws XMLStreamException { + private static String sanitizeAndEncode(byte[] inputXmlBytes) + throws XMLStreamException, UnsupportedEncodingException { XMLEventReader xmlEventReader = XML_INPUT_FACTORY.createXMLEventReader(new ByteArrayInputStream(inputXmlBytes)); + if (!xmlEventReader.hasNext()) { + return ""; + } + + XMLEvent firstEvent = xmlEventReader.nextEvent(); + checkState(firstEvent.isStartDocument(), "Missing StartDocument"); + // Get input encoding for use in XMLEventWriter creation, so that sanitized XML preserves the + // encoding declaration. According to XML spec, UTF-8 is to be used unless input declares + // otherwise. Epp officially allows UTF-8 and UTF-16. + String inputEncoding = + Optional.ofNullable(((StartDocument) firstEvent).getCharacterEncodingScheme()) + .orElse(StandardCharsets.UTF_8.name()); + ByteArrayOutputStream outputXmlBytes = new ByteArrayOutputStream(); - XMLEventWriter xmlEventWriter = XML_OUTPUT_FACTORY.createXMLEventWriter(outputXmlBytes); + XMLEventWriter xmlEventWriter = + XML_OUTPUT_FACTORY.createXMLEventWriter(outputXmlBytes, inputEncoding); + xmlEventWriter.add(firstEvent); while (xmlEventReader.hasNext()) { XMLEvent xmlEvent = xmlEventReader.nextEvent(); @@ -118,7 +139,7 @@ public class EppXmlSanitizer { } } xmlEventWriter.flush(); - return outputXmlBytes.toByteArray(); + return outputXmlBytes.toString(inputEncoding); } private static String maskSensitiveData(String original) { diff --git a/java/google/registry/flows/FlowRunner.java b/java/google/registry/flows/FlowRunner.java index b830e4e23..8df7f0083 100644 --- a/java/google/registry/flows/FlowRunner.java +++ b/java/google/registry/flows/FlowRunner.java @@ -72,7 +72,6 @@ public class FlowRunner { } eppMetricBuilder.setCommandNameFromFlow(flowClass.getSimpleName()); if (!isTransactional) { - eppMetricBuilder.incrementAttempts(); EppOutput eppOutput = EppOutput.create(flowProvider.get().run()); if (flowClass.equals(LoginFlow.class)) { // In LoginFlow, clientId isn't known until after the flow executes, so save it then. @@ -84,7 +83,6 @@ public class FlowRunner { return ofy() .transact( () -> { - eppMetricBuilder.incrementAttempts(); try { EppOutput output = EppOutput.create(flowProvider.get().run()); if (isDryRun) { diff --git a/java/google/registry/flows/async/AsyncFlowMetrics.java b/java/google/registry/flows/async/AsyncFlowMetrics.java index c62fa4a8e..3cdda6e62 100644 --- a/java/google/registry/flows/async/AsyncFlowMetrics.java +++ b/java/google/registry/flows/async/AsyncFlowMetrics.java @@ -29,7 +29,6 @@ import com.google.monitoring.metrics.IncrementableMetric; import com.google.monitoring.metrics.LabelDescriptor; import com.google.monitoring.metrics.MetricRegistryImpl; import google.registry.util.Clock; -import google.registry.util.NonFinalForTesting; import javax.inject.Inject; import org.joda.time.DateTime; import org.joda.time.Duration; @@ -67,9 +66,8 @@ public class AsyncFlowMetrics { LabelDescriptor.create("operation_type", "The type of async flow operation."), LabelDescriptor.create("result", "The result of the async flow operation.")); - @NonFinalForTesting @VisibleForTesting - static IncrementableMetric asyncFlowOperationCounts = + static final IncrementableMetric asyncFlowOperationCounts = MetricRegistryImpl.getDefault() .newIncrementableMetric( "/async_flows/operations", @@ -77,9 +75,8 @@ public class AsyncFlowMetrics { "count", LABEL_DESCRIPTORS); - @NonFinalForTesting @VisibleForTesting - static EventMetric asyncFlowOperationProcessingTime = + static final EventMetric asyncFlowOperationProcessingTime = MetricRegistryImpl.getDefault() .newEventMetric( "/async_flows/processing_time", @@ -88,9 +85,8 @@ public class AsyncFlowMetrics { LABEL_DESCRIPTORS, DEFAULT_FITTER); - @NonFinalForTesting @VisibleForTesting - static EventMetric asyncFlowBatchSize = + static final EventMetric asyncFlowBatchSize = MetricRegistryImpl.getDefault() .newEventMetric( "/async_flows/batch_size", diff --git a/java/google/registry/flows/domain/DomainAllocateFlow.java b/java/google/registry/flows/domain/DomainAllocateFlow.java index c0e5a9b6e..3812805d6 100644 --- a/java/google/registry/flows/domain/DomainAllocateFlow.java +++ b/java/google/registry/flows/domain/DomainAllocateFlow.java @@ -88,7 +88,7 @@ import google.registry.model.reporting.DomainTransactionRecord; import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField; import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.IcannReportingTypes.ActivityReportField; -import google.registry.tmch.LordnTask; +import google.registry.tmch.LordnTaskUtils; import java.util.Optional; import java.util.Set; import javax.inject.Inject; @@ -385,13 +385,14 @@ public class DomainAllocateFlow implements TransactionalFlow { dnsQueue.get().addDomainRefreshTask(newDomain.getFullyQualifiedDomainName()); } if (allocateCreate.getSmdId() != null || allocateCreate.getNotice() != null) { - LordnTask.enqueueDomainResourceTask(newDomain); + LordnTaskUtils.enqueueDomainResourceTask(newDomain); } } private ImmutableList createResponseExtensions( DateTime now, Registry registry, int years) throws EppException { - FeesAndCredits feesAndCredits = pricingLogic.getCreatePrice(registry, targetId, now, years); + FeesAndCredits feesAndCredits = + pricingLogic.getCreatePrice(registry, targetId, now, years, false); Optional feeCreate = eppInput.getSingleExtension(FeeCreateCommandExtension.class); return feeCreate.isPresent() diff --git a/java/google/registry/flows/domain/DomainApplicationCreateFlow.java b/java/google/registry/flows/domain/DomainApplicationCreateFlow.java index 233ae2f2a..7cb9a8bf0 100644 --- a/java/google/registry/flows/domain/DomainApplicationCreateFlow.java +++ b/java/google/registry/flows/domain/DomainApplicationCreateFlow.java @@ -211,8 +211,11 @@ public final class DomainApplicationCreateFlow implements TransactionalFlow { checkAllowedAccessToTld(clientId, tld); } Registry registry = Registry.get(tld); + boolean isAnchorTenant = + isAnchorTenant(domainName, Optional.empty(), authInfo.getPw().getValue(), Optional.empty()); FeesAndCredits feesAndCredits = - pricingLogic.getCreatePrice(registry, targetId, now, command.getPeriod().getValue()); + pricingLogic.getCreatePrice( + registry, targetId, now, command.getPeriod().getValue(), isAnchorTenant); verifyUnitIsYears(command.getPeriod()); int years = command.getPeriod().getValue(); validateRegistrationPeriod(years); @@ -220,8 +223,6 @@ public final class DomainApplicationCreateFlow implements TransactionalFlow { LaunchCreateExtension launchCreate = eppInput.getSingleExtension(LaunchCreateExtension.class).get(); validateLaunchCreateExtension(launchCreate, registry, domainName, now); - boolean isAnchorTenant = - isAnchorTenant(domainName, Optional.empty(), authInfo.getPw().getValue(), Optional.empty()); // Superusers can create reserved domains, force creations on domains that require a claims // notice without specifying a claims key, and override blocks on registering premium domains. if (!isSuperuser) { diff --git a/java/google/registry/flows/domain/DomainCreateFlow.java b/java/google/registry/flows/domain/DomainCreateFlow.java index 71e16f879..09dce93b1 100644 --- a/java/google/registry/flows/domain/DomainCreateFlow.java +++ b/java/google/registry/flows/domain/DomainCreateFlow.java @@ -23,6 +23,7 @@ import static google.registry.flows.domain.DomainFlowUtils.cloneAndLinkReference import static google.registry.flows.domain.DomainFlowUtils.createFeeCreateResponse; import static google.registry.flows.domain.DomainFlowUtils.getReservationTypes; import static google.registry.flows.domain.DomainFlowUtils.isAnchorTenant; +import static google.registry.flows.domain.DomainFlowUtils.isReserved; import static google.registry.flows.domain.DomainFlowUtils.isValidReservedCreate; import static google.registry.flows.domain.DomainFlowUtils.validateCreateCommandContactsAndNameservers; import static google.registry.flows.domain.DomainFlowUtils.validateDomainAllowedOnCreateRestrictedTld; @@ -113,7 +114,7 @@ import google.registry.model.reporting.DomainTransactionRecord; import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField; import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.IcannReportingTypes.ActivityReportField; -import google.registry.tmch.LordnTask; +import google.registry.tmch.LordnTaskUtils; import java.util.Optional; import javax.inject.Inject; import org.joda.time.DateTime; @@ -308,7 +309,8 @@ public class DomainCreateFlow implements TransactionalFlow { .build()); Optional feeCreate = eppInput.getSingleExtension(FeeCreateCommandExtension.class); - FeesAndCredits feesAndCredits = pricingLogic.getCreatePrice(registry, targetId, now, years); + FeesAndCredits feesAndCredits = + pricingLogic.getCreatePrice(registry, targetId, now, years, isAnchorTenant); validateFeeChallenge(targetId, registry.getTldStr(), clientId, now, feeCreate, feesAndCredits); Optional secDnsCreate = validateSecDnsExtension(eppInput.getSingleExtension(SecDnsCreateExtension.class)); @@ -319,7 +321,14 @@ public class DomainCreateFlow implements TransactionalFlow { // Bill for the create. BillingEvent.OneTime createBillingEvent = createOneTimeBillingEvent( - registry, isAnchorTenant, isSunriseCreate, years, feesAndCredits, historyEntry, now); + registry, + isAnchorTenant, + isSunriseCreate, + isReserved(domainName, isSunriseCreate), + years, + feesAndCredits, + historyEntry, + now); // Create a new autorenew billing event and poll message starting at the expiration time. BillingEvent.Recurring autorenewBillingEvent = createAutorenewBillingEvent(historyEntry, registrationExpirationTime); @@ -506,6 +515,7 @@ public class DomainCreateFlow implements TransactionalFlow { Registry registry, boolean isAnchorTenant, boolean isSunriseCreate, + boolean isReserved, int years, FeesAndCredits feesAndCredits, HistoryEntry historyEntry, @@ -517,6 +527,10 @@ public class DomainCreateFlow implements TransactionalFlow { } if (isAnchorTenant) { flagsBuilder.add(Flag.ANCHOR_TENANT); + } else if (isReserved) { + // Don't add this flag if the domain is an anchor tenant (which are also reserved); only add + // it if it's reserved for other reasons. + flagsBuilder.add(Flag.RESERVED); } return new BillingEvent.OneTime.Builder() .setReason(Reason.CREATE) @@ -594,7 +608,7 @@ public class DomainCreateFlow implements TransactionalFlow { dnsQueue.addDomainRefreshTask(newDomain.getFullyQualifiedDomainName()); } if (hasClaimsNotice || hasSignedMarks) { - LordnTask.enqueueDomainResourceTask(newDomain); + LordnTaskUtils.enqueueDomainResourceTask(newDomain); } } diff --git a/java/google/registry/flows/domain/DomainFlowUtils.java b/java/google/registry/flows/domain/DomainFlowUtils.java index c03f1d81b..57ae9c3c8 100644 --- a/java/google/registry/flows/domain/DomainFlowUtils.java +++ b/java/google/registry/flows/domain/DomainFlowUtils.java @@ -32,7 +32,6 @@ import static google.registry.model.registry.label.ReservationType.RESERVED_FOR_ import static google.registry.model.registry.label.ReservationType.RESERVED_FOR_SPECIFIC_USE; import static google.registry.model.registry.label.ReservedList.getAllowedNameservers; import static google.registry.pricing.PricingEngineProxy.isDomainPremium; -import static google.registry.tldconfig.idn.IdnLabelValidator.findValidIdnTableForTld; import static google.registry.util.CollectionUtils.nullToEmpty; import static google.registry.util.DateTimeUtils.END_OF_TIME; import static google.registry.util.DateTimeUtils.isAtOrAfter; @@ -113,6 +112,7 @@ import google.registry.model.reporting.DomainTransactionRecord; import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField; import google.registry.model.reporting.HistoryEntry; import google.registry.model.tmch.ClaimsListShard; +import google.registry.tldconfig.idn.IdnLabelValidator; import google.registry.util.Idn; import java.math.BigDecimal; import java.util.Comparator; @@ -166,6 +166,10 @@ public class DomainFlowUtils { private static final CharMatcher ALLOWED_CHARS = CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('0', '9').or(CharMatcher.anyOf("-."))); + /** Default validator used to determine if an IDN name can be provisioned on a TLD. */ + private static final IdnLabelValidator IDN_LABEL_VALIDATOR = + IdnLabelValidator.createDefaultIdnLabelValidator(); + /** The maximum number of DS records allowed on a domain. */ private static final int MAX_DS_RECORDS_PER_DOMAIN = 8; @@ -239,7 +243,8 @@ public class DomainFlowUtils { public static String validateDomainNameWithIdnTables(InternetDomainName domainName) throws InvalidIdnDomainLabelException { Optional idnTableName = - findValidIdnTableForTld(domainName.parts().get(0), domainName.parent().toString()); + IDN_LABEL_VALIDATOR.findValidIdnTableForTld( + domainName.parts().get(0), domainName.parent().toString()); if (!idnTableName.isPresent()) { throw new InvalidIdnDomainLabelException(); } @@ -453,7 +458,7 @@ public class DomainFlowUtils { private static final ImmutableSet RESERVED_TYPES = ImmutableSet.of(RESERVED_FOR_SPECIFIC_USE, RESERVED_FOR_ANCHOR_TENANT, FULLY_BLOCKED); - private static boolean isReserved(InternetDomainName domainName, boolean isSunrise) { + static boolean isReserved(InternetDomainName domainName, boolean isSunrise) { ImmutableSet types = getReservationTypes(domainName); return !Sets.intersection(types, RESERVED_TYPES).isEmpty() || !(isSunrise || intersection(TYPES_ALLOWED_FOR_CREATE_ONLY_IN_SUNRISE, types).isEmpty()); @@ -621,7 +626,10 @@ public class DomainFlowUtils { builder.setReasonIfSupported("reserved"); } else { builder.setAvailIfSupported(true); - fees = pricingLogic.getCreatePrice(registry, domainNameString, now, years).getFees(); + // TODO(b/117145844): Once allocation token support for domain check flow is implemented, + // we should be able to calculate the correct price here. + fees = + pricingLogic.getCreatePrice(registry, domainNameString, now, years, false).getFees(); } break; case RENEW: diff --git a/java/google/registry/flows/domain/DomainPricingLogic.java b/java/google/registry/flows/domain/DomainPricingLogic.java index 4b4620ad0..22e900756 100644 --- a/java/google/registry/flows/domain/DomainPricingLogic.java +++ b/java/google/registry/flows/domain/DomainPricingLogic.java @@ -54,7 +54,8 @@ public final class DomainPricingLogic { /** Returns a new create price for the pricer. */ public FeesAndCredits getCreatePrice( - Registry registry, String domainName, DateTime date, int years) throws EppException { + Registry registry, String domainName, DateTime date, int years, boolean isAnchorTenant) + throws EppException { CurrencyUnit currency = registry.getCurrency(); // Get the vanilla create cost. @@ -65,7 +66,8 @@ public final class DomainPricingLogic { Fee eapFee = registry.getEapFeeFor(date); FeesAndCredits.Builder feesBuilder = new FeesAndCredits.Builder().setCurrency(currency).addFeeOrCredit(createFeeOrCredit); - if (!eapFee.hasZeroCost()) { + // Don't charge anchor tenants EAP fees. + if (!isAnchorTenant && !eapFee.hasZeroCost()) { feesBuilder.addFeeOrCredit(eapFee); } diff --git a/java/google/registry/keyring/BUILD b/java/google/registry/keyring/BUILD new file mode 100644 index 000000000..ec999ce7c --- /dev/null +++ b/java/google/registry/keyring/BUILD @@ -0,0 +1,21 @@ +package( + default_visibility = ["//visibility:public"], +) + +licenses(["notice"]) # Apache 2.0 + +java_library( + name = "keyring", + srcs = glob(["*.java"]), + deps = [ + "//java/google/registry/config", + "//java/google/registry/keyring/api", + "@com_google_code_findbugs_jsr305", + "@com_google_dagger", + "@com_google_flogger", + "@com_google_flogger_system_backend", + "@com_google_guava", + "@javax_inject", + "@org_bouncycastle_bcpg_jdk15on", + ], +) diff --git a/java/google/registry/keyring/kms/KeyringModule.java b/java/google/registry/keyring/KeyringModule.java similarity index 58% rename from java/google/registry/keyring/kms/KeyringModule.java rename to java/google/registry/keyring/KeyringModule.java index 7a9b53f80..28c525900 100644 --- a/java/google/registry/keyring/kms/KeyringModule.java +++ b/java/google/registry/keyring/KeyringModule.java @@ -1,4 +1,4 @@ -// Copyright 2017 The Nomulus Authors. All Rights Reserved. +// 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. @@ -12,11 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -package google.registry.keyring.kms; +package google.registry.keyring; + +import static com.google.common.base.Preconditions.checkState; import dagger.Module; import dagger.Provides; +import google.registry.config.RegistryConfig.Config; import google.registry.keyring.api.Keyring; +import java.util.Map; import javax.inject.Singleton; /** Dagger module for {@link Keyring} */ @@ -25,7 +29,13 @@ public final class KeyringModule { @Provides @Singleton - public static Keyring provideKeyring(KmsKeyring kmsKeyring) { - return kmsKeyring; + public static Keyring provideKeyring( + Map keyrings, @Config("activeKeyring") String activeKeyring) { + checkState( + keyrings.containsKey(activeKeyring), + "Invalid Keyring %s is configured; valid choices are %s", + activeKeyring, + keyrings.keySet()); + return keyrings.get(activeKeyring); } } diff --git a/java/google/registry/keyring/api/DummyKeyringModule.java b/java/google/registry/keyring/api/DummyKeyringModule.java index 9e35c14fe..9e2f2aec3 100644 --- a/java/google/registry/keyring/api/DummyKeyringModule.java +++ b/java/google/registry/keyring/api/DummyKeyringModule.java @@ -21,11 +21,15 @@ import static google.registry.keyring.api.PgpHelper.lookupKeyPair; import com.google.common.base.VerifyException; import com.google.common.io.ByteSource; import com.google.common.io.Resources; +import dagger.Binds; import dagger.Module; import dagger.Provides; +import dagger.multibindings.IntoMap; +import dagger.multibindings.StringKey; import java.io.IOException; import java.io.InputStream; import javax.annotation.concurrent.Immutable; +import javax.inject.Named; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; @@ -68,7 +72,9 @@ import org.bouncycastle.openpgp.bc.BcPGPSecretKeyRingCollection; */ @Module @Immutable -public final class DummyKeyringModule { +public abstract class DummyKeyringModule { + + public static final String NAME = "Dummy"; /** The contents of a dummy PGP public key stored in a file. */ private static final ByteSource PGP_PUBLIC_KEYRING = @@ -81,9 +87,15 @@ public final class DummyKeyringModule { /** The email address of the aforementioned PGP key. */ private static final String EMAIL_ADDRESS = "test-registry@example.com"; + @Binds + @IntoMap + @StringKey(NAME) + abstract Keyring provideKeyring(@Named("DummyKeyring") InMemoryKeyring keyring); + /** Always returns a {@link InMemoryKeyring} instance. */ @Provides - static Keyring provideKeyring() { + @Named("DummyKeyring") + static InMemoryKeyring provideDummyKeyring() { PGPKeyPair dummyKey; try (InputStream publicInput = PGP_PUBLIC_KEYRING.openStream(); InputStream privateInput = PGP_PRIVATE_KEYRING.openStream()) { @@ -112,4 +124,6 @@ public final class DummyKeyringModule { "not a real login", "not a real credential"); } + + private DummyKeyringModule() {} } diff --git a/java/google/registry/keyring/kms/KmsModule.java b/java/google/registry/keyring/kms/KmsModule.java index 1b63fff76..1c96ca50e 100644 --- a/java/google/registry/keyring/kms/KmsModule.java +++ b/java/google/registry/keyring/kms/KmsModule.java @@ -19,13 +19,23 @@ import com.google.api.services.cloudkms.v1.CloudKMS; import dagger.Binds; import dagger.Module; import dagger.Provides; +import dagger.multibindings.IntoMap; +import dagger.multibindings.StringKey; import google.registry.config.CredentialModule.DefaultCredential; import google.registry.config.RegistryConfig.Config; +import google.registry.keyring.api.Keyring; -/** Dagger module for Cloud KMS connection objects. */ +/** Dagger module for Cloud KMS. */ @Module public abstract class KmsModule { + public static final String NAME = "KMS"; + + @Binds + @IntoMap + @StringKey(NAME) + abstract Keyring provideKeyring(KmsKeyring keyring); + @Provides static CloudKMS provideKms( @DefaultCredential GoogleCredential credential, diff --git a/java/google/registry/model/billing/BillingEvent.java b/java/google/registry/model/billing/BillingEvent.java index 4d3c364dc..934eefd95 100644 --- a/java/google/registry/model/billing/BillingEvent.java +++ b/java/google/registry/model/billing/BillingEvent.java @@ -71,6 +71,14 @@ public abstract class BillingEvent extends ImmutableObject ANCHOR_TENANT, AUTO_RENEW, LANDRUSH, + /** + * This flag is used on create {@link OneTime} billing events for domains that were reserved. + * + *

This can happen when allocation tokens are used or superusers override a domain + * reservation. These cases can need special handling in billing/invoicing. Anchor tenants will + * never have this flag applied; they will have ANCHOR_TENANT instead. + */ + RESERVED, SUNRISE, /** * This flag will be added to any {@link OneTime} events that are created via, e.g., an diff --git a/java/google/registry/model/domain/token/AllocationToken.java b/java/google/registry/model/domain/token/AllocationToken.java index aa783b918..cc82cb6fb 100644 --- a/java/google/registry/model/domain/token/AllocationToken.java +++ b/java/google/registry/model/domain/token/AllocationToken.java @@ -41,7 +41,7 @@ public class AllocationToken extends BackupGroupRoot implements Buildable { @Id String token; /** The key of the history entry for which the token was used. Null if not yet used. */ - Key redemptionHistoryEntry; + @Nullable @Index Key redemptionHistoryEntry; /** The fully-qualified domain name that this token is limited to, if any. */ @Nullable @Index String domainName; diff --git a/java/google/registry/model/tmch/ClaimsListShard.java b/java/google/registry/model/tmch/ClaimsListShard.java index eeefc76c7..db507acdd 100644 --- a/java/google/registry/model/tmch/ClaimsListShard.java +++ b/java/google/registry/model/tmch/ClaimsListShard.java @@ -39,7 +39,6 @@ import google.registry.model.annotations.VirtualEntity; import google.registry.model.common.CrossTldSingleton; import google.registry.util.CollectionUtils; import google.registry.util.Concurrent; -import google.registry.util.NonFinalForTesting; import google.registry.util.Retrier; import google.registry.util.SystemSleeper; import java.util.HashMap; @@ -69,10 +68,8 @@ import org.joda.time.DateTime; @NotBackedUp(reason = Reason.EXTERNALLY_SOURCED) public class ClaimsListShard extends ImmutableObject { - /** The number of claims list entries to store per shard. Do not modify except for in tests. */ - @VisibleForTesting - @NonFinalForTesting - static int shardSize = 10000; + /** The number of claims list entries to store per shard. */ + private static final int SHARD_SIZE = 10000; @Id long id; @@ -165,6 +162,11 @@ public class ClaimsListShard extends ImmutableObject { * switching over to using them atomically, then deleting the old ones. */ public void save() { + save(SHARD_SIZE); + } + + @VisibleForTesting + void save(int shardSize) { // Figure out what the next versionId should be based on which ones already exist. final Key oldRevision = getCurrentRevision(); final Key parentKey = ClaimsListRevision.createKey(); diff --git a/java/google/registry/module/backend/BUILD b/java/google/registry/module/backend/BUILD index d4527f2ea..75aa46b18 100644 --- a/java/google/registry/module/backend/BUILD +++ b/java/google/registry/module/backend/BUILD @@ -22,6 +22,7 @@ java_library( "//java/google/registry/flows", "//java/google/registry/gcs", "//java/google/registry/groups", + "//java/google/registry/keyring", "//java/google/registry/keyring/api", "//java/google/registry/keyring/kms", "//java/google/registry/mapreduce", diff --git a/java/google/registry/module/backend/BackendComponent.java b/java/google/registry/module/backend/BackendComponent.java index 53073e658..9844eee40 100644 --- a/java/google/registry/module/backend/BackendComponent.java +++ b/java/google/registry/module/backend/BackendComponent.java @@ -27,19 +27,18 @@ import google.registry.gcs.GcsServiceModule; import google.registry.groups.DirectoryModule; import google.registry.groups.GroupsModule; import google.registry.groups.GroupssettingsModule; +import google.registry.keyring.KeyringModule; +import google.registry.keyring.api.DummyKeyringModule; import google.registry.keyring.api.KeyModule; import google.registry.keyring.kms.KmsModule; import google.registry.module.backend.BackendRequestComponent.BackendRequestComponentModule; import google.registry.monitoring.whitebox.StackdriverModule; import google.registry.rde.JSchModule; -import google.registry.request.Modules.AppIdentityCredentialModule; import google.registry.request.Modules.DatastoreServiceModule; -import google.registry.request.Modules.GoogleCredentialModule; import google.registry.request.Modules.Jackson2Module; import google.registry.request.Modules.NetHttpTransportModule; import google.registry.request.Modules.URLFetchServiceModule; import google.registry.request.Modules.UrlFetchTransportModule; -import google.registry.request.Modules.UseAppIdentityCredentialForGoogleApisModule; import google.registry.request.Modules.UserServiceModule; import google.registry.request.auth.AuthModule; import google.registry.util.AppEngineServiceUtilsImpl.AppEngineServiceUtilsModule; @@ -52,7 +51,6 @@ import javax.inject.Singleton; @Component( modules = { AppEngineServiceUtilsModule.class, - AppIdentityCredentialModule.class, AuthModule.class, BackendRequestComponentModule.class, BigqueryModule.class, @@ -60,15 +58,15 @@ import javax.inject.Singleton; CredentialModule.class, DatastoreServiceModule.class, DirectoryModule.class, - google.registry.keyring.api.DummyKeyringModule.class, + DummyKeyringModule.class, DriveModule.class, GcsServiceModule.class, - GoogleCredentialModule.class, GroupsModule.class, GroupssettingsModule.class, JSchModule.class, Jackson2Module.class, KeyModule.class, + KeyringModule.class, KmsModule.class, NetHttpTransportModule.class, SheetsServiceModule.class, @@ -77,7 +75,6 @@ import javax.inject.Singleton; SystemSleeperModule.class, URLFetchServiceModule.class, UrlFetchTransportModule.class, - UseAppIdentityCredentialForGoogleApisModule.class, UserServiceModule.class, VoidDnsWriterModule.class, }) diff --git a/java/google/registry/module/backend/BackendRequestComponent.java b/java/google/registry/module/backend/BackendRequestComponent.java index 0b5df3532..09c7fa8ab 100644 --- a/java/google/registry/module/backend/BackendRequestComponent.java +++ b/java/google/registry/module/backend/BackendRequestComponent.java @@ -53,7 +53,6 @@ import google.registry.export.sheet.SheetModule; import google.registry.export.sheet.SyncRegistrarsSheetAction; import google.registry.flows.async.AsyncFlowsModule; import google.registry.mapreduce.MapreduceModule; -import google.registry.monitoring.whitebox.MetricsExportAction; import google.registry.monitoring.whitebox.WhiteboxModule; import google.registry.rde.BrdaCopyAction; import google.registry.rde.RdeModule; @@ -136,7 +135,6 @@ interface BackendRequestComponent { IcannReportingStagingAction icannReportingStagingAction(); IcannReportingUploadAction icannReportingUploadAction(); LoadSnapshotAction loadSnapshotAction(); - MetricsExportAction metricsExportAction(); NordnUploadAction nordnUploadAction(); NordnVerifyAction nordnVerifyAction(); PublishDnsUpdatesAction publishDnsUpdatesAction(); diff --git a/java/google/registry/module/frontend/BUILD b/java/google/registry/module/frontend/BUILD index 1f99a1b17..589a82287 100644 --- a/java/google/registry/module/frontend/BUILD +++ b/java/google/registry/module/frontend/BUILD @@ -11,6 +11,7 @@ java_library( "//java/google/registry/config", "//java/google/registry/dns", "//java/google/registry/flows", + "//java/google/registry/keyring", "//java/google/registry/keyring/api", "//java/google/registry/keyring/kms", "//java/google/registry/monitoring/whitebox", diff --git a/java/google/registry/module/frontend/FrontendComponent.java b/java/google/registry/module/frontend/FrontendComponent.java index 263f4b6b7..1c96edc5b 100644 --- a/java/google/registry/module/frontend/FrontendComponent.java +++ b/java/google/registry/module/frontend/FrontendComponent.java @@ -21,16 +21,15 @@ import google.registry.config.CredentialModule; import google.registry.config.RegistryConfig.ConfigModule; import google.registry.flows.ServerTridProviderModule; import google.registry.flows.custom.CustomLogicFactoryModule; +import google.registry.keyring.KeyringModule; +import google.registry.keyring.api.DummyKeyringModule; import google.registry.keyring.api.KeyModule; import google.registry.keyring.kms.KmsModule; import google.registry.module.frontend.FrontendRequestComponent.FrontendRequestComponentModule; import google.registry.monitoring.whitebox.StackdriverModule; -import google.registry.request.Modules.AppIdentityCredentialModule; -import google.registry.request.Modules.GoogleCredentialModule; import google.registry.request.Modules.Jackson2Module; import google.registry.request.Modules.NetHttpTransportModule; import google.registry.request.Modules.UrlFetchTransportModule; -import google.registry.request.Modules.UseAppIdentityCredentialForGoogleApisModule; import google.registry.request.Modules.UserServiceModule; import google.registry.request.auth.AuthModule; import google.registry.ui.ConsoleDebug.ConsoleConfigModule; @@ -44,17 +43,16 @@ import javax.inject.Singleton; @Component( modules = { AppEngineServiceUtilsModule.class, - AppIdentityCredentialModule.class, AuthModule.class, ConfigModule.class, ConsoleConfigModule.class, CredentialModule.class, CustomLogicFactoryModule.class, - google.registry.keyring.api.DummyKeyringModule.class, + DummyKeyringModule.class, FrontendRequestComponentModule.class, - GoogleCredentialModule.class, Jackson2Module.class, KeyModule.class, + KeyringModule.class, KmsModule.class, NetHttpTransportModule.class, ServerTridProviderModule.class, @@ -62,7 +60,6 @@ import javax.inject.Singleton; SystemClockModule.class, SystemSleeperModule.class, UrlFetchTransportModule.class, - UseAppIdentityCredentialForGoogleApisModule.class, UserServiceModule.class, }) interface FrontendComponent { diff --git a/java/google/registry/module/frontend/FrontendRequestComponent.java b/java/google/registry/module/frontend/FrontendRequestComponent.java index 316da2b51..19c22964b 100644 --- a/java/google/registry/module/frontend/FrontendRequestComponent.java +++ b/java/google/registry/module/frontend/FrontendRequestComponent.java @@ -26,12 +26,14 @@ import google.registry.request.RequestComponentBuilder; import google.registry.request.RequestModule; import google.registry.request.RequestScope; import google.registry.ui.server.registrar.ConsoleUiAction; +import google.registry.ui.server.registrar.RegistrarConsoleModule; import google.registry.ui.server.registrar.RegistrarSettingsAction; /** Dagger component with per-request lifetime for "default" App Engine module. */ @RequestScope @Subcomponent( modules = { + RegistrarConsoleModule.class, DnsModule.class, EppTlsModule.class, RequestModule.class, diff --git a/java/google/registry/module/pubapi/BUILD b/java/google/registry/module/pubapi/BUILD index 9af5ff86d..e3d388311 100644 --- a/java/google/registry/module/pubapi/BUILD +++ b/java/google/registry/module/pubapi/BUILD @@ -11,6 +11,7 @@ java_library( "//java/google/registry/config", "//java/google/registry/dns", "//java/google/registry/flows", + "//java/google/registry/keyring", "//java/google/registry/keyring/api", "//java/google/registry/keyring/kms", "//java/google/registry/monitoring/whitebox", diff --git a/java/google/registry/module/pubapi/PubApiComponent.java b/java/google/registry/module/pubapi/PubApiComponent.java index da606f81b..ef5ffbc65 100644 --- a/java/google/registry/module/pubapi/PubApiComponent.java +++ b/java/google/registry/module/pubapi/PubApiComponent.java @@ -21,16 +21,15 @@ import google.registry.config.CredentialModule; import google.registry.config.RegistryConfig.ConfigModule; import google.registry.flows.ServerTridProviderModule; import google.registry.flows.custom.CustomLogicFactoryModule; +import google.registry.keyring.KeyringModule; +import google.registry.keyring.api.DummyKeyringModule; import google.registry.keyring.api.KeyModule; import google.registry.keyring.kms.KmsModule; import google.registry.module.pubapi.PubApiRequestComponent.PubApiRequestComponentModule; import google.registry.monitoring.whitebox.StackdriverModule; -import google.registry.request.Modules.AppIdentityCredentialModule; -import google.registry.request.Modules.GoogleCredentialModule; import google.registry.request.Modules.Jackson2Module; import google.registry.request.Modules.NetHttpTransportModule; import google.registry.request.Modules.UrlFetchTransportModule; -import google.registry.request.Modules.UseAppIdentityCredentialForGoogleApisModule; import google.registry.request.Modules.UserServiceModule; import google.registry.request.auth.AuthModule; import google.registry.util.AppEngineServiceUtilsImpl.AppEngineServiceUtilsModule; @@ -43,16 +42,15 @@ import javax.inject.Singleton; @Component( modules = { AppEngineServiceUtilsModule.class, - AppIdentityCredentialModule.class, AuthModule.class, ConfigModule.class, CredentialModule.class, CustomLogicFactoryModule.class, - google.registry.keyring.api.DummyKeyringModule.class, + DummyKeyringModule.class, PubApiRequestComponentModule.class, - GoogleCredentialModule.class, Jackson2Module.class, KeyModule.class, + KeyringModule.class, KmsModule.class, NetHttpTransportModule.class, ServerTridProviderModule.class, @@ -60,7 +58,6 @@ import javax.inject.Singleton; SystemClockModule.class, SystemSleeperModule.class, UrlFetchTransportModule.class, - UseAppIdentityCredentialForGoogleApisModule.class, UserServiceModule.class, }) interface PubApiComponent { diff --git a/java/google/registry/module/tools/BUILD b/java/google/registry/module/tools/BUILD index a76e62b89..a46e09e90 100644 --- a/java/google/registry/module/tools/BUILD +++ b/java/google/registry/module/tools/BUILD @@ -15,6 +15,7 @@ java_library( "//java/google/registry/flows", "//java/google/registry/gcs", "//java/google/registry/groups", + "//java/google/registry/keyring", "//java/google/registry/keyring/api", "//java/google/registry/keyring/kms", "//java/google/registry/loadtest", diff --git a/java/google/registry/module/tools/ToolsComponent.java b/java/google/registry/module/tools/ToolsComponent.java index 26495260a..605ddc95a 100644 --- a/java/google/registry/module/tools/ToolsComponent.java +++ b/java/google/registry/module/tools/ToolsComponent.java @@ -24,16 +24,15 @@ import google.registry.gcs.GcsServiceModule; import google.registry.groups.DirectoryModule; import google.registry.groups.GroupsModule; import google.registry.groups.GroupssettingsModule; +import google.registry.keyring.KeyringModule; +import google.registry.keyring.api.DummyKeyringModule; import google.registry.keyring.api.KeyModule; import google.registry.keyring.kms.KmsModule; import google.registry.module.tools.ToolsRequestComponent.ToolsRequestComponentModule; -import google.registry.request.Modules.AppIdentityCredentialModule; import google.registry.request.Modules.DatastoreServiceModule; -import google.registry.request.Modules.GoogleCredentialModule; import google.registry.request.Modules.Jackson2Module; import google.registry.request.Modules.NetHttpTransportModule; import google.registry.request.Modules.UrlFetchTransportModule; -import google.registry.request.Modules.UseAppIdentityCredentialForGoogleApisModule; import google.registry.request.Modules.UserServiceModule; import google.registry.request.auth.AuthModule; import google.registry.util.AppEngineServiceUtilsImpl.AppEngineServiceUtilsModule; @@ -46,21 +45,20 @@ import javax.inject.Singleton; @Component( modules = { AppEngineServiceUtilsModule.class, - AppIdentityCredentialModule.class, AuthModule.class, ConfigModule.class, CredentialModule.class, CustomLogicFactoryModule.class, DatastoreServiceModule.class, DirectoryModule.class, - google.registry.keyring.api.DummyKeyringModule.class, + DummyKeyringModule.class, DriveModule.class, GcsServiceModule.class, - GoogleCredentialModule.class, GroupsModule.class, GroupssettingsModule.class, Jackson2Module.class, KeyModule.class, + KeyringModule.class, KmsModule.class, NetHttpTransportModule.class, ServerTridProviderModule.class, @@ -68,7 +66,6 @@ import javax.inject.Singleton; SystemSleeperModule.class, ToolsRequestComponentModule.class, UrlFetchTransportModule.class, - UseAppIdentityCredentialForGoogleApisModule.class, UserServiceModule.class, }) interface ToolsComponent { diff --git a/java/google/registry/monitoring/whitebox/BigQueryMetric.java b/java/google/registry/monitoring/whitebox/BigQueryMetric.java deleted file mode 100644 index 70c9373ee..000000000 --- a/java/google/registry/monitoring/whitebox/BigQueryMetric.java +++ /dev/null @@ -1,36 +0,0 @@ -// 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.monitoring.whitebox; - -import com.google.api.services.bigquery.model.TableFieldSchema; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; - -/** - * A metric which can be encoded into a BigQuery row. - * - * @see BigQueryMetricsEnqueuer - */ -public interface BigQueryMetric { - - /** Get the BigQuery table name for this metric. */ - String getTableId(); - - /** Get the schema description for the BigQuery table. */ - ImmutableList getSchemaFields(); - - /** Get a map of the row values for this metric instance. */ - ImmutableMap getBigQueryRowEncoding(); -} diff --git a/java/google/registry/monitoring/whitebox/BigQueryMetricsEnqueuer.java b/java/google/registry/monitoring/whitebox/BigQueryMetricsEnqueuer.java deleted file mode 100644 index 54905098c..000000000 --- a/java/google/registry/monitoring/whitebox/BigQueryMetricsEnqueuer.java +++ /dev/null @@ -1,65 +0,0 @@ -// 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.monitoring.whitebox; - -import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withUrl; - -import com.google.appengine.api.taskqueue.Queue; -import com.google.appengine.api.taskqueue.TaskOptions; -import com.google.appengine.api.taskqueue.TransientFailureException; -import com.google.common.base.Supplier; -import com.google.common.flogger.FluentLogger; -import google.registry.util.AppEngineServiceUtils; -import java.util.Map.Entry; -import javax.inject.Inject; -import javax.inject.Named; - -/** - * A collector of metric information. Enqueues collected metrics to a task queue to be written to - * BigQuery asynchronously. - * - * @see MetricsExportAction - */ -public class BigQueryMetricsEnqueuer { - - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - - public static final String QUEUE_BIGQUERY_STREAMING_METRICS = "bigquery-streaming-metrics"; - - @Inject AppEngineServiceUtils appEngineServiceUtils; - @Inject @Named("insertIdGenerator") Supplier idGenerator; - @Inject @Named(QUEUE_BIGQUERY_STREAMING_METRICS) Queue queue; - - @Inject BigQueryMetricsEnqueuer() {} - - public void export(BigQueryMetric metric) { - try { - String hostname = appEngineServiceUtils.getCurrentVersionHostname("backend"); - TaskOptions opts = - withUrl(MetricsExportAction.PATH) - .header("Host", hostname) - .param("insertId", idGenerator.get()); - for (Entry entry : metric.getBigQueryRowEncoding().entrySet()) { - opts.param(entry.getKey(), entry.getValue()); - } - opts.param("tableId", metric.getTableId()); - queue.add(opts); - } catch (TransientFailureException e) { - // Log and swallow. We may drop some metrics here but this should be rare. - logger.atInfo().withCause(e).log( - "Transient error occurred while recording metric; metric dropped."); - } - } -} diff --git a/java/google/registry/monitoring/whitebox/EppMetric.java b/java/google/registry/monitoring/whitebox/EppMetric.java index 26e298a42..d328e1d56 100644 --- a/java/google/registry/monitoring/whitebox/EppMetric.java +++ b/java/google/registry/monitoring/whitebox/EppMetric.java @@ -15,44 +15,19 @@ package google.registry.monitoring.whitebox; import static com.google.common.base.Preconditions.checkArgument; -import static google.registry.bigquery.BigqueryUtils.toBigqueryTimestamp; -import com.google.api.services.bigquery.model.TableFieldSchema; import com.google.auto.value.AutoValue; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; -import google.registry.bigquery.BigqueryUtils.FieldType; import google.registry.model.eppoutput.Result.Code; import google.registry.model.registry.Registries; import google.registry.util.Clock; import java.util.Optional; import org.joda.time.DateTime; -/** - * A value class for recording attributes of an EPP metric. - * - * @see BigQueryMetricsEnqueuer - */ +/** A value class for recording attributes of an EPP metric. */ @AutoValue -public abstract class EppMetric implements BigQueryMetric { - - static final String TABLE_ID = "eppMetrics"; - static final ImmutableList SCHEMA_FIELDS = - ImmutableList.of( - new TableFieldSchema().setName("requestId").setType(FieldType.STRING.name()), - new TableFieldSchema().setName("startTime").setType(FieldType.TIMESTAMP.name()), - new TableFieldSchema().setName("endTime").setType(FieldType.TIMESTAMP.name()), - new TableFieldSchema().setName("commandName").setType(FieldType.STRING.name()), - new TableFieldSchema().setName("clientId").setType(FieldType.STRING.name()), - new TableFieldSchema().setName("tld").setType(FieldType.STRING.name()), - new TableFieldSchema().setName("privilegeLevel").setType(FieldType.STRING.name()), - new TableFieldSchema().setName("eppTarget").setType(FieldType.STRING.name()), - new TableFieldSchema().setName("eppStatus").setType(FieldType.INTEGER.name()), - new TableFieldSchema().setName("attempts").setType(FieldType.INTEGER.name())); - - public abstract String getRequestId(); +public abstract class EppMetric { public abstract DateTime getStartTimestamp(); @@ -64,55 +39,8 @@ public abstract class EppMetric implements BigQueryMetric { public abstract Optional getTld(); - public abstract Optional getPrivilegeLevel(); - - public abstract Optional getEppTarget(); - public abstract Optional getStatus(); - public abstract Integer getAttempts(); - - @Override - public String getTableId() { - return TABLE_ID; - } - - @Override - public ImmutableList getSchemaFields() { - return SCHEMA_FIELDS; - } - - @Override - public ImmutableMap getBigQueryRowEncoding() { - // Create map builder, start with required values - ImmutableMap.Builder map = - new ImmutableMap.Builder() - .put("requestId", getRequestId()) - .put("startTime", toBigqueryTimestamp(getStartTimestamp())) - .put("endTime", toBigqueryTimestamp(getEndTimestamp())) - .put("attempts", getAttempts().toString()); - // Populate optional values, if present - addOptional("commandName", getCommandName(), map); - addOptional("clientId", getClientId(), map); - addOptional("tld", getTld(), map); - addOptional("privilegeLevel", getPrivilegeLevel(), map); - addOptional("eppTarget", getEppTarget(), map); - if (getStatus().isPresent()) { - map.put("eppStatus", Integer.toString(getStatus().get().code)); - } - - return map.build(); - } - - /** - * Helper method to populate an {@link com.google.common.collect.ImmutableMap.Builder} with an - * {@link Optional} value if the value is {@link Optional#isPresent()}. - */ - private static void addOptional( - String key, Optional value, ImmutableMap.Builder map) { - value.ifPresent(t -> map.put(key, t.toString())); - } - /** Create an {@link EppMetric.Builder}. */ public static Builder builder() { return new AutoValue_EppMetric.Builder(); @@ -124,9 +52,8 @@ public abstract class EppMetric implements BigQueryMetric { * *

The start timestamp is recorded now, and the end timestamp at {@code build()}. */ - public static Builder builderForRequest(String requestId, Clock clock) { + public static Builder builderForRequest(Clock clock) { return builder() - .setRequestId(requestId) .setStartTimestamp(clock.nowUtc()) .setClock(clock); } @@ -135,14 +62,9 @@ public abstract class EppMetric implements BigQueryMetric { @AutoValue.Builder public abstract static class Builder { - /** Builder-only counter of the number of attempts, to support {@link #incrementAttempts()}. */ - private int attempts = 0; - /** Builder-only clock to support automatic recording of endTimestamp on {@link #build()}. */ private Clock clock = null; - abstract Builder setRequestId(String requestId); - abstract Builder setStartTimestamp(DateTime startTimestamp); abstract Builder setEndTimestamp(DateTime endTimestamp); @@ -191,19 +113,8 @@ public abstract class EppMetric implements BigQueryMetric { return this; } - public abstract Builder setPrivilegeLevel(String privilegeLevel); - - public abstract Builder setEppTarget(String eppTarget); - public abstract Builder setStatus(Code code); - abstract Builder setAttempts(Integer attempts); - - public Builder incrementAttempts() { - attempts++; - return this; - } - Builder setClock(Clock clock) { this.clock = clock; return this; @@ -216,7 +127,6 @@ public abstract class EppMetric implements BigQueryMetric { * current timestamp of the clock; otherwise end timestamp must have been previously set. */ public EppMetric build() { - setAttempts(attempts); if (clock != null) { setEndTimestamp(clock.nowUtc()); } diff --git a/java/google/registry/monitoring/whitebox/MetricsExportAction.java b/java/google/registry/monitoring/whitebox/MetricsExportAction.java deleted file mode 100644 index f5a802ff1..000000000 --- a/java/google/registry/monitoring/whitebox/MetricsExportAction.java +++ /dev/null @@ -1,104 +0,0 @@ -// 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.monitoring.whitebox; - -import static com.google.common.base.Predicates.in; -import static com.google.common.base.Predicates.not; -import static com.google.common.collect.Multimaps.filterKeys; -import static google.registry.request.Action.Method.POST; -import static java.util.stream.Collectors.joining; - -import com.google.api.services.bigquery.Bigquery; -import com.google.api.services.bigquery.model.TableDataInsertAllRequest; -import com.google.api.services.bigquery.model.TableDataInsertAllResponse; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableListMultimap; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.flogger.FluentLogger; -import google.registry.bigquery.CheckedBigquery; -import google.registry.config.RegistryConfig.Config; -import google.registry.request.Action; -import google.registry.request.Parameter; -import google.registry.request.ParameterMap; -import google.registry.request.auth.Auth; -import java.io.IOException; -import java.util.Map; -import javax.inject.Inject; - -/** Action for exporting metrics to BigQuery. */ -@Action( - path = MetricsExportAction.PATH, - method = POST, - auth = Auth.AUTH_INTERNAL_ONLY -) -public class MetricsExportAction implements Runnable { - - public static final String PATH = "/_dr/task/metrics"; - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - private static final String DATASET_ID = "metrics"; - private static final ImmutableSet SPECIAL_PARAMS = ImmutableSet.of("tableId", "insertId"); - - @Inject @Parameter("tableId") String tableId; - @Inject @Parameter("insertId") String insertId; - @Inject @Config("projectId") String projectId; - - @Inject CheckedBigquery checkedBigquery; - @Inject @ParameterMap ImmutableListMultimap parameters; - @Inject MetricsExportAction() {} - - /** Exports metrics to BigQuery. */ - @Override - public void run() { - try { - Bigquery bigquery = - checkedBigquery.ensureDataSetAndTableExist(projectId, DATASET_ID, tableId); - // Filter out the special parameters that the Action is called with. Everything that's left - // is returned in a Map that is suitable to pass to Bigquery as row data. - Map jsonRows = - ImmutableMap.copyOf( - filterKeys(parameters, not(in(SPECIAL_PARAMS))).entries()); - TableDataInsertAllResponse response = bigquery.tabledata() - .insertAll( - projectId, - DATASET_ID, - tableId, - new TableDataInsertAllRequest() - .setRows( - ImmutableList.of(new TableDataInsertAllRequest.Rows() - .setInsertId(insertId) - .setJson(jsonRows)))) - .execute(); - - if (response.getInsertErrors() != null && !response.getInsertErrors().isEmpty()) { - throw new RuntimeException( - response - .getInsertErrors() - .stream() - .map( - error -> { - try { - return error.toPrettyString(); - } catch (IOException e) { - return error.toString(); - } - }) - .collect(joining("\n"))); - } - } catch (Throwable e) { - logger.atWarning().withCause(e).log("Unknown error while exporting metrics to BigQuery."); - } - } -} diff --git a/java/google/registry/monitoring/whitebox/WhiteboxModule.java b/java/google/registry/monitoring/whitebox/WhiteboxModule.java index 2203964b4..1c67aa483 100644 --- a/java/google/registry/monitoring/whitebox/WhiteboxModule.java +++ b/java/google/registry/monitoring/whitebox/WhiteboxModule.java @@ -14,24 +14,9 @@ package google.registry.monitoring.whitebox; -import static com.google.appengine.api.taskqueue.QueueFactory.getQueue; -import static google.registry.monitoring.whitebox.BigQueryMetricsEnqueuer.QUEUE_BIGQUERY_STREAMING_METRICS; -import static google.registry.request.RequestParameters.extractRequiredParameter; - -import com.google.api.services.bigquery.model.TableFieldSchema; -import com.google.appengine.api.taskqueue.Queue; -import com.google.common.base.Supplier; -import com.google.common.collect.ImmutableList; import dagger.Module; import dagger.Provides; -import dagger.multibindings.IntoMap; -import dagger.multibindings.StringKey; -import google.registry.request.Parameter; -import google.registry.request.RequestLogId; import google.registry.util.Clock; -import java.util.UUID; -import javax.inject.Named; -import javax.servlet.http.HttpServletRequest; /** * Dagger module for injecting common settings for Whitebox tasks. @@ -39,46 +24,14 @@ import javax.servlet.http.HttpServletRequest; @Module public class WhiteboxModule { - @Provides - @IntoMap - @StringKey(EppMetric.TABLE_ID) - static ImmutableList provideEppMetricsSchema() { - return EppMetric.SCHEMA_FIELDS; - } - - @Provides - @Parameter("tableId") - static String provideTableId(HttpServletRequest req) { - return extractRequiredParameter(req, "tableId"); - } - - @Provides - @Parameter("insertId") - static String provideInsertId(HttpServletRequest req) { - return extractRequiredParameter(req, "insertId"); - } - - @Provides - @Named("insertIdGenerator") - static Supplier provideInsertIdGenerator() { - return () -> UUID.randomUUID().toString(); - } - /** Provides an EppMetric builder with the request ID and startTimestamp already initialized. */ @Provides - static EppMetric.Builder provideEppMetricBuilder( - @RequestLogId String requestLogId, Clock clock) { - return EppMetric.builderForRequest(requestLogId, clock); + static EppMetric.Builder provideEppMetricBuilder(Clock clock) { + return EppMetric.builderForRequest(clock); } @Provides static CheckApiMetric.Builder provideCheckApiMetricBuilder(Clock clock) { return CheckApiMetric.builder(clock); } - - @Provides - @Named(QUEUE_BIGQUERY_STREAMING_METRICS) - static Queue provideBigQueryStreamingMetricsQueue() { - return getQueue(QUEUE_BIGQUERY_STREAMING_METRICS); - } } diff --git a/java/google/registry/proxy/handler/QuotaHandler.java b/java/google/registry/proxy/handler/QuotaHandler.java index 1381d72c9..183e89a7b 100644 --- a/java/google/registry/proxy/handler/QuotaHandler.java +++ b/java/google/registry/proxy/handler/QuotaHandler.java @@ -156,7 +156,9 @@ public abstract class QuotaHandler extends ChannelInboundHandlerAdapter { public void channelInactive(ChannelHandlerContext ctx) { // If no reads occurred before the connection is inactive (for example when the handshake // is not successful), no quota is leased and therefore no return is needed. - if (quotaResponse != null) { + // Note that the quota response can be a failure, in which case no token was leased to us from + // the token store. Consequently no return is necessary. + if (quotaResponse != null && quotaResponse.success()) { Future unusedFuture = quotaManager.releaseQuota(QuotaRebate.create(quotaResponse)); } ctx.fireChannelInactive(); diff --git a/java/google/registry/proxy/terraform/modules/common.tf b/java/google/registry/proxy/terraform/modules/common.tf index 10e1a70af..ef46ba494 100644 --- a/java/google/registry/proxy/terraform/modules/common.tf +++ b/java/google/registry/proxy/terraform/modules/common.tf @@ -1,3 +1,4 @@ provider "google" { + version = ">= 1.13.0" project = "${var.proxy_project_name}" } diff --git a/java/google/registry/rdap/RdapActionBase.java b/java/google/registry/rdap/RdapActionBase.java index bfbc8e984..d1f448559 100644 --- a/java/google/registry/rdap/RdapActionBase.java +++ b/java/google/registry/rdap/RdapActionBase.java @@ -16,7 +16,6 @@ package google.registry.rdap; import static com.google.common.base.Charsets.UTF_8; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkState; import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.util.DateTimeUtils.END_OF_TIME; @@ -27,6 +26,7 @@ import static javax.servlet.http.HttpServletResponse.SC_OK; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.flogger.FluentLogger; import com.google.common.net.MediaType; import com.google.re2j.Pattern; @@ -48,7 +48,8 @@ import google.registry.request.RequestPath; import google.registry.request.Response; import google.registry.request.auth.AuthResult; import google.registry.request.auth.UserAuthInfo; -import google.registry.ui.server.registrar.SessionUtils; +import google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor; +import google.registry.util.Clock; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; @@ -57,7 +58,6 @@ import java.util.List; import java.util.Optional; import javax.annotation.Nullable; import javax.inject.Inject; -import javax.servlet.http.HttpServletRequest; import org.joda.time.DateTime; import org.json.simple.JSONValue; @@ -87,13 +87,13 @@ public abstract class RdapActionBase implements Runnable { INCLUDE } - @Inject HttpServletRequest request; @Inject Response response; + @Inject Clock clock; @Inject @RequestMethod Action.Method requestMethod; @Inject @RequestPath String requestPath; @Inject @FullServletPath String fullServletPath; @Inject AuthResult authResult; - @Inject SessionUtils sessionUtils; + @Inject AuthenticatedRegistrarAccessor registrarAccessor; @Inject RdapJsonFormatter rdapJsonFormatter; @Inject @Parameter("registrar") Optional registrarParam; @Inject @Parameter("includeDeleted") Optional includeDeletedParam; @@ -200,15 +200,12 @@ public abstract class RdapActionBase implements Runnable { if (userAuthInfo.isUserAdmin()) { return RdapAuthorization.ADMINISTRATOR_AUTHORIZATION; } - if (!sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)) { + ImmutableSet clientIds = registrarAccessor.getAllClientIdWithRoles().keySet(); + if (clientIds.isEmpty()) { + logger.atWarning().log("Couldn't find registrar for User %s.", authResult.userIdForLogging()); return RdapAuthorization.PUBLIC_AUTHORIZATION; } - String clientId = sessionUtils.getRegistrarClientId(request); - Optional registrar = Registrar.loadByClientIdCached(clientId); - if (!registrar.isPresent()) { - return RdapAuthorization.PUBLIC_AUTHORIZATION; - } - return RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, clientId); + return RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, clientIds); } /** Returns the registrar on which results should be filtered, or absent(). */ @@ -238,14 +235,9 @@ public abstract class RdapActionBase implements Runnable { if (userAuthInfo.isUserAdmin()) { return true; } - if (!sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)) { + if (registrarAccessor.getAllClientIdWithRoles().isEmpty()) { return false; } - String clientId = sessionUtils.getRegistrarClientId(request); - checkState( - Registrar.loadByClientIdCached(clientId).isPresent(), - "Registrar with clientId %s doesn't exist", - clientId); return true; } diff --git a/java/google/registry/rdap/RdapAuthorization.java b/java/google/registry/rdap/RdapAuthorization.java index b21c11e6b..991cf12a6 100644 --- a/java/google/registry/rdap/RdapAuthorization.java +++ b/java/google/registry/rdap/RdapAuthorization.java @@ -15,7 +15,7 @@ package google.registry.rdap; import com.google.auto.value.AutoValue; -import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import google.registry.model.ImmutableObject; /** Authorization information for RDAP data access. */ @@ -32,13 +32,13 @@ public abstract class RdapAuthorization extends ImmutableObject { public abstract Role role(); /** The registrar client IDs for which access is granted (used only if the role is REGISTRAR. */ - public abstract ImmutableList clientIds(); + public abstract ImmutableSet clientIds(); static RdapAuthorization create(Role role, String clientId) { - return new AutoValue_RdapAuthorization(role, ImmutableList.of(clientId)); + return new AutoValue_RdapAuthorization(role, ImmutableSet.of(clientId)); } - static RdapAuthorization create(Role role, ImmutableList clientIds) { + static RdapAuthorization create(Role role, ImmutableSet clientIds) { return new AutoValue_RdapAuthorization(role, clientIds); } @@ -54,9 +54,8 @@ public abstract class RdapAuthorization extends ImmutableObject { } public static final RdapAuthorization PUBLIC_AUTHORIZATION = - create(Role.PUBLIC, ImmutableList.of()); + create(Role.PUBLIC, ImmutableSet.of()); public static final RdapAuthorization ADMINISTRATOR_AUTHORIZATION = - create(Role.ADMINISTRATOR, ImmutableList.of()); + create(Role.ADMINISTRATOR, ImmutableSet.of()); } - diff --git a/java/google/registry/rdap/RdapDomainAction.java b/java/google/registry/rdap/RdapDomainAction.java index 3b55655ce..2563a3987 100644 --- a/java/google/registry/rdap/RdapDomainAction.java +++ b/java/google/registry/rdap/RdapDomainAction.java @@ -29,7 +29,6 @@ import google.registry.request.Action; import google.registry.request.HttpException.BadRequestException; import google.registry.request.HttpException.NotFoundException; import google.registry.request.auth.Auth; -import google.registry.util.Clock; import javax.inject.Inject; import org.joda.time.DateTime; @@ -44,8 +43,7 @@ public class RdapDomainAction extends RdapActionBase { public static final String PATH = "/rdap/domain/"; - @Inject Clock clock; - @Inject RdapDomainAction() {} + @Inject public RdapDomainAction() {} @Override public String getHumanReadableObjectTypeName() { diff --git a/java/google/registry/rdap/RdapDomainSearchAction.java b/java/google/registry/rdap/RdapDomainSearchAction.java index cc6ae752c..59c5ffac4 100644 --- a/java/google/registry/rdap/RdapDomainSearchAction.java +++ b/java/google/registry/rdap/RdapDomainSearchAction.java @@ -45,7 +45,6 @@ import google.registry.request.HttpException.NotFoundException; import google.registry.request.HttpException.UnprocessableEntityException; import google.registry.request.Parameter; import google.registry.request.auth.Auth; -import google.registry.util.Clock; import google.registry.util.Idn; import google.registry.util.NonFinalForTesting; import java.net.InetAddress; @@ -83,11 +82,10 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - @Inject Clock clock; @Inject @Parameter("name") Optional nameParam; @Inject @Parameter("nsLdhName") Optional nsLdhNameParam; @Inject @Parameter("nsIp") Optional nsIpParam; - @Inject RdapDomainSearchAction() {} + @Inject public RdapDomainSearchAction() {} @Override public String getHumanReadableObjectTypeName() { diff --git a/java/google/registry/rdap/RdapEntityAction.java b/java/google/registry/rdap/RdapEntityAction.java index 44f6d13ee..a88050b69 100644 --- a/java/google/registry/rdap/RdapEntityAction.java +++ b/java/google/registry/rdap/RdapEntityAction.java @@ -31,7 +31,6 @@ import google.registry.request.Action; import google.registry.request.HttpException.BadRequestException; import google.registry.request.HttpException.NotFoundException; import google.registry.request.auth.Auth; -import google.registry.util.Clock; import java.util.Optional; import javax.inject.Inject; import org.joda.time.DateTime; @@ -58,8 +57,7 @@ public class RdapEntityAction extends RdapActionBase { private static final Pattern ROID_PATTERN = Pattern.compile("[-_.a-zA-Z0-9]+"); - @Inject Clock clock; - @Inject RdapEntityAction() {} + @Inject public RdapEntityAction() {} @Override public String getHumanReadableObjectTypeName() { diff --git a/java/google/registry/rdap/RdapEntitySearchAction.java b/java/google/registry/rdap/RdapEntitySearchAction.java index 987063be1..85a42bc20 100644 --- a/java/google/registry/rdap/RdapEntitySearchAction.java +++ b/java/google/registry/rdap/RdapEntitySearchAction.java @@ -39,7 +39,6 @@ import google.registry.request.HttpException.NotFoundException; import google.registry.request.HttpException.UnprocessableEntityException; import google.registry.request.Parameter; import google.registry.request.auth.Auth; -import google.registry.util.Clock; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -84,11 +83,10 @@ public class RdapEntitySearchAction extends RdapSearchActionBase { public static final String PATH = "/rdap/entities"; - @Inject Clock clock; @Inject @Parameter("fn") Optional fnParam; @Inject @Parameter("handle") Optional handleParam; @Inject @Parameter("subtype") Optional subtypeParam; - @Inject RdapEntitySearchAction() {} + @Inject public RdapEntitySearchAction() {} private enum QueryType { FULL_NAME, diff --git a/java/google/registry/rdap/RdapHelpAction.java b/java/google/registry/rdap/RdapHelpAction.java index ab20cf83a..0f7a08158 100644 --- a/java/google/registry/rdap/RdapHelpAction.java +++ b/java/google/registry/rdap/RdapHelpAction.java @@ -23,7 +23,6 @@ import google.registry.rdap.RdapJsonFormatter.BoilerplateType; import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.request.Action; import google.registry.request.auth.Auth; -import google.registry.util.Clock; import javax.inject.Inject; /** RDAP (new WHOIS) action for help requests. */ @@ -37,8 +36,7 @@ public class RdapHelpAction extends RdapActionBase { public static final String PATH = "/rdap/help"; - @Inject Clock clock; - @Inject RdapHelpAction() {} + @Inject public RdapHelpAction() {} @Override public String getHumanReadableObjectTypeName() { diff --git a/java/google/registry/rdap/RdapJsonFormatter.java b/java/google/registry/rdap/RdapJsonFormatter.java index d8d1bc3be..0c2c1fd05 100644 --- a/java/google/registry/rdap/RdapJsonFormatter.java +++ b/java/google/registry/rdap/RdapJsonFormatter.java @@ -29,7 +29,6 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.common.collect.Ordering; import com.google.common.collect.Streams; -import com.google.common.flogger.FluentLogger; import com.google.common.net.InetAddresses; import com.googlecode.objectify.Key; import google.registry.config.RdapNoticeDescriptor; @@ -83,8 +82,6 @@ public class RdapJsonFormatter { @Inject @Config("rdapHelpMap") ImmutableMap rdapHelpMap; @Inject RdapJsonFormatter() {} - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - /** * What type of data to generate. Summary data includes only information about the object itself, * while full data includes associated items (e.g. for domains, full data includes the hosts, diff --git a/java/google/registry/rdap/RdapNameserverAction.java b/java/google/registry/rdap/RdapNameserverAction.java index 0d0e0f4a3..27e7e44f1 100644 --- a/java/google/registry/rdap/RdapNameserverAction.java +++ b/java/google/registry/rdap/RdapNameserverAction.java @@ -29,7 +29,6 @@ import google.registry.request.Action; import google.registry.request.HttpException.BadRequestException; import google.registry.request.HttpException.NotFoundException; import google.registry.request.auth.Auth; -import google.registry.util.Clock; import javax.inject.Inject; import org.joda.time.DateTime; @@ -44,8 +43,7 @@ public class RdapNameserverAction extends RdapActionBase { public static final String PATH = "/rdap/nameserver/"; - @Inject Clock clock; - @Inject RdapNameserverAction() {} + @Inject public RdapNameserverAction() {} @Override public String getHumanReadableObjectTypeName() { diff --git a/java/google/registry/rdap/RdapNameserverSearchAction.java b/java/google/registry/rdap/RdapNameserverSearchAction.java index faa2058ec..b36eddda5 100644 --- a/java/google/registry/rdap/RdapNameserverSearchAction.java +++ b/java/google/registry/rdap/RdapNameserverSearchAction.java @@ -38,7 +38,6 @@ import google.registry.request.HttpException.NotFoundException; import google.registry.request.HttpException.UnprocessableEntityException; import google.registry.request.Parameter; import google.registry.request.auth.Auth; -import google.registry.util.Clock; import google.registry.util.Idn; import java.net.InetAddress; import java.util.ArrayList; @@ -66,10 +65,9 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase { public static final String PATH = "/rdap/nameservers"; - @Inject Clock clock; @Inject @Parameter("name") Optional nameParam; @Inject @Parameter("ip") Optional ipParam; - @Inject RdapNameserverSearchAction() {} + @Inject public RdapNameserverSearchAction() {} @Override public String getHumanReadableObjectTypeName() { diff --git a/java/google/registry/rde/imports/RdeDomainImportAction.java b/java/google/registry/rde/imports/RdeDomainImportAction.java index 638f65dc9..97043d9d8 100644 --- a/java/google/registry/rde/imports/RdeDomainImportAction.java +++ b/java/google/registry/rde/imports/RdeDomainImportAction.java @@ -54,6 +54,7 @@ import google.registry.request.Action; import google.registry.request.Parameter; import google.registry.request.Response; import google.registry.request.auth.Auth; +import google.registry.util.StringGenerator; import google.registry.util.SystemClock; import google.registry.xjc.JaxbFragment; import google.registry.xjc.rdedomain.XjcRdeDomain; @@ -83,6 +84,7 @@ public class RdeDomainImportAction implements Runnable { protected final String importBucketName; protected final String importFileName; protected final Optional mapShards; + protected final StringGenerator stringGenerator; @Inject public RdeDomainImportAction( @@ -90,12 +92,14 @@ public class RdeDomainImportAction implements Runnable { Response response, @Config("rdeImportBucket") String importBucketName, @Parameter(PATH) String importFileName, - @Parameter(PARAM_MAP_SHARDS) Optional mapShards) { + @Parameter(PARAM_MAP_SHARDS) Optional mapShards, + @Config("base64StringGenerator") StringGenerator stringGenerator) { this.mrRunner = mrRunner; this.response = response; this.importBucketName = importBucketName; this.importFileName = importFileName; this.mapShards = mapShards; + this.stringGenerator = stringGenerator; } @Override @@ -122,7 +126,7 @@ public class RdeDomainImportAction implements Runnable { * Creates a new {@link RdeDomainImportMapper} */ private RdeDomainImportMapper createMapper() { - return new RdeDomainImportMapper(importBucketName); + return new RdeDomainImportMapper(importBucketName, stringGenerator); } /** Mapper to import domains from an escrow file. */ @@ -132,11 +136,13 @@ public class RdeDomainImportAction implements Runnable { private static final long serialVersionUID = -7645091075256589374L; private final String importBucketName; + private final StringGenerator stringGenerator; private transient RdeImportUtils importUtils; private transient DnsQueue dnsQueue; - public RdeDomainImportMapper(String importBucketName) { + public RdeDomainImportMapper(String importBucketName, StringGenerator stringGenerator) { this.importBucketName = importBucketName; + this.stringGenerator = stringGenerator; } private RdeImportUtils getImportUtils() { @@ -196,7 +202,7 @@ public class RdeDomainImportAction implements Runnable { createAutoRenewPollMessageForDomainImport(xjcDomain, historyEntry); DomainResource domain = XjcToDomainResourceConverter.convertDomain( - xjcDomain, autorenewBillingEvent, autorenewPollMessage); + xjcDomain, autorenewBillingEvent, autorenewPollMessage, stringGenerator); getDnsQueue().addDomainRefreshTask(domain.getFullyQualifiedDomainName()); // Keep a list of "extra objects" that need to be saved along with the domain // and add to it if necessary. diff --git a/java/google/registry/rde/imports/XjcToDomainResourceConverter.java b/java/google/registry/rde/imports/XjcToDomainResourceConverter.java index 563dbddae..e8dd07969 100644 --- a/java/google/registry/rde/imports/XjcToDomainResourceConverter.java +++ b/java/google/registry/rde/imports/XjcToDomainResourceConverter.java @@ -42,8 +42,6 @@ import google.registry.model.registry.Registries; import google.registry.model.registry.Registry; import google.registry.model.transfer.TransferData; import google.registry.model.transfer.TransferStatus; -import google.registry.util.NonFinalForTesting; -import google.registry.util.RandomStringGenerator; import google.registry.util.StringGenerator; import google.registry.util.XmlToEnumMapper; import google.registry.xjc.domain.XjcDomainContactType; @@ -54,27 +52,12 @@ import google.registry.xjc.rdedomain.XjcRdeDomainElement; import google.registry.xjc.rdedomain.XjcRdeDomainTransferDataType; import google.registry.xjc.rgp.XjcRgpStatusType; import google.registry.xjc.secdns.XjcSecdnsDsDataType; -import java.security.NoSuchAlgorithmException; -import java.security.ProviderException; -import java.security.SecureRandom; import java.util.function.Function; import org.joda.time.DateTime; /** Utility class that converts an {@link XjcRdeDomainElement} into a {@link DomainResource}. */ final class XjcToDomainResourceConverter extends XjcToEppResourceConverter { - @NonFinalForTesting - static StringGenerator stringGenerator = - new RandomStringGenerator(StringGenerator.Alphabets.BASE_64, getRandom()); - - static SecureRandom getRandom() { - try { - return SecureRandom.getInstance("NativePRNG"); - } catch (NoSuchAlgorithmException e) { - throw new ProviderException(e); - } - } - private static final XmlToEnumMapper TRANSFER_STATUS_MAPPER = XmlToEnumMapper.create(TransferStatus.values()); @@ -152,7 +135,8 @@ final class XjcToDomainResourceConverter extends XjcToEppResourceConverter { static DomainResource convertDomain( XjcRdeDomain domain, BillingEvent.Recurring autoRenewBillingEvent, - PollMessage.Autorenew autoRenewPollMessage) { + PollMessage.Autorenew autoRenewPollMessage, + StringGenerator stringGenerator) { GracePeriodConverter gracePeriodConverter = new GracePeriodConverter(domain, Key.create(autoRenewBillingEvent)); DomainResource.Builder builder = diff --git a/java/google/registry/reporting/icann/IcannReportingModule.java b/java/google/registry/reporting/icann/IcannReportingModule.java index 25114df4c..a11ba597b 100644 --- a/java/google/registry/reporting/icann/IcannReportingModule.java +++ b/java/google/registry/reporting/icann/IcannReportingModule.java @@ -25,7 +25,6 @@ import dagger.Provides; import google.registry.bigquery.BigqueryConnection; import google.registry.request.HttpException.BadRequestException; import google.registry.request.Parameter; -import google.registry.util.SendEmailService; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.util.Optional; @@ -115,11 +114,6 @@ public final class IcannReportingModule { } } - @Provides - static SendEmailService provideSendEmailService() { - return new SendEmailService(); - } - /** Dagger qualifier for the subdirectory we stage to/upload from. */ @Qualifier @Documented diff --git a/java/google/registry/reporting/spec11/Spec11EmailUtils.java b/java/google/registry/reporting/spec11/Spec11EmailUtils.java index 310afbd46..76b716446 100644 --- a/java/google/registry/reporting/spec11/Spec11EmailUtils.java +++ b/java/google/registry/reporting/spec11/Spec11EmailUtils.java @@ -47,8 +47,10 @@ public class Spec11EmailUtils { private final YearMonth yearMonth; private final String alertSenderAddress; private final String alertRecipientAddress; + private final String spec11ReplyToAddress; private final String reportingBucket; private final String spec11ReportDirectory; + private final String spec11EmailBodyTemplate; private final GcsUtils gcsUtils; private final Retrier retrier; @@ -58,6 +60,8 @@ public class Spec11EmailUtils { YearMonth yearMonth, @Config("alertSenderEmailAddress") String alertSenderAddress, @Config("alertRecipientEmailAddress") String alertRecipientAddress, + @Config("spec11ReplyToEmailAddress") String spec11ReplyToAddress, + @Config("spec11EmailBodyTemplate") String spec11EmailBodyTemplate, @Config("reportingBucket") String reportingBucket, @Spec11ReportDirectory String spec11ReportDirectory, GcsUtils gcsUtils, @@ -66,8 +70,10 @@ public class Spec11EmailUtils { this.yearMonth = yearMonth; this.alertSenderAddress = alertSenderAddress; this.alertRecipientAddress = alertRecipientAddress; + this.spec11ReplyToAddress = spec11ReplyToAddress; this.reportingBucket = reportingBucket; this.spec11ReportDirectory = spec11ReportDirectory; + this.spec11EmailBodyTemplate = spec11EmailBodyTemplate; this.gcsUtils = gcsUtils; this.retrier = retrier; } @@ -114,23 +120,24 @@ public class Spec11EmailUtils { JSONObject reportJSON = new JSONObject(line); String registrarEmail = reportJSON.getString(Spec11Pipeline.REGISTRAR_EMAIL_FIELD); JSONArray threatMatches = reportJSON.getJSONArray(Spec11Pipeline.THREAT_MATCHES_FIELD); - StringBuilder body = - new StringBuilder("Hello registrar partner,\n") - .append("We have detected problems with the following domains:\n"); + StringBuilder threatList = new StringBuilder(); for (int i = 0; i < threatMatches.length(); i++) { ThreatMatch threatMatch = ThreatMatch.fromJSON(threatMatches.getJSONObject(i)); - body.append( + threatList.append( String.format( "%s - %s\n", threatMatch.fullyQualifiedDomainName(), threatMatch.threatType())); } - body.append("At the moment, no action is required. This is purely informatory.") - .append("Regards,\nGoogle Registry\n"); + String body = + spec11EmailBodyTemplate + .replace("{REPLY_TO_EMAIL}", spec11ReplyToAddress) + .replace("{LIST_OF_THREATS}", threatList.toString()); Message msg = emailService.createMessage(); msg.setSubject( String.format("Google Registry Monthly Threat Detector [%s]", yearMonth.toString())); msg.setText(body.toString()); msg.setFrom(new InternetAddress(alertSenderAddress)); - msg.setRecipient(RecipientType.TO, new InternetAddress(registrarEmail)); + msg.addRecipient(RecipientType.TO, new InternetAddress(registrarEmail)); + msg.addRecipient(RecipientType.BCC, new InternetAddress(spec11ReplyToAddress)); emailService.sendMessage(msg); } diff --git a/java/google/registry/repositories.bzl b/java/google/registry/repositories.bzl index 202e7eb49..d05fb830a 100644 --- a/java/google/registry/repositories.bzl +++ b/java/google/registry/repositories.bzl @@ -72,6 +72,7 @@ def domain_registry_repositories( omit_com_google_googlejavaformat_google_java_format = False, omit_com_google_guava = False, omit_com_google_guava_testlib = False, + omit_com_google_gwt_user = False, omit_com_google_http_client = False, omit_com_google_http_client_appengine = False, omit_com_google_http_client_jackson2 = False, @@ -88,6 +89,7 @@ def domain_registry_repositories( omit_com_google_template_soy = False, omit_com_google_truth = False, omit_com_google_truth_extensions_truth_java8_extension = False, + omit_com_googlecode_java_diff_utils_diffutils = False, omit_com_googlecode_charts4j = False, omit_com_googlecode_json_simple = False, omit_com_ibm_icu_icu4j = False, @@ -117,6 +119,7 @@ def domain_registry_repositories( omit_javax_inject = False, omit_javax_mail = False, omit_javax_servlet_api = False, + omit_javax_validation_api = False, omit_javax_xml_bind_jaxb_api = False, omit_javax_xml_soap_api = False, omit_javax_xml_ws_jaxws_api = False, @@ -136,6 +139,8 @@ def domain_registry_repositories( omit_org_apache_httpcomponents_httpcore = False, omit_org_apache_mina_core = False, omit_org_apache_sshd_core = False, + omit_org_apache_sshd_scp = False, + omit_org_apache_sshd_sftp = False, omit_org_apache_tomcat_servlet_api = False, omit_org_apache_tomcat_annotations_api = False, omit_org_bouncycastle_bcpg_jdk15on = False, @@ -262,6 +267,8 @@ def domain_registry_repositories( com_google_guava() if not omit_com_google_guava_testlib: com_google_guava_testlib() + if not omit_com_google_gwt_user: + com_google_gwt_user() if not omit_com_google_http_client: com_google_http_client() if not omit_com_google_http_client_appengine: @@ -294,6 +301,8 @@ def domain_registry_repositories( com_google_truth() if not omit_com_google_truth_extensions_truth_java8_extension: com_google_truth_extensions_truth_java8_extension() + if not omit_com_googlecode_java_diff_utils_diffutils: + com_googlecode_java_diff_utils_diffutils() if not omit_com_googlecode_charts4j: com_googlecode_charts4j() if not omit_com_googlecode_json_simple: @@ -352,6 +361,8 @@ def domain_registry_repositories( javax_mail() if not omit_javax_servlet_api: javax_servlet_api() + if not omit_javax_validation_api: + javax_validation_api() if not omit_javax_xml_bind_jaxb_api: javax_xml_bind_jaxb_api() if not omit_javax_xml_soap_api: @@ -388,6 +399,10 @@ def domain_registry_repositories( org_apache_mina_core() if not omit_org_apache_sshd_core: org_apache_sshd_core() + if not omit_org_apache_sshd_scp: + org_apache_sshd_scp() + if not omit_org_apache_sshd_sftp: + org_apache_sshd_sftp() if not omit_org_apache_tomcat_servlet_api: org_apache_tomcat_servlet_api() if not omit_org_apache_tomcat_annotations_api: @@ -1342,6 +1357,23 @@ def com_google_guava_testlib(): ], ) +def com_google_gwt_user(): + java_import_external( + name = "com_google_gwt_user", + neverlink = 1, + licenses = ["notice"], # GWT Terms + jar_sha256 = "9f420f0d0c2f177d71cb1794b3be1418f9755f6e4181101af3951b8302b9556d", + jar_urls = [ + "http://maven.ibiblio.org/maven2/com/google/gwt/gwt-user/2.8.2/gwt-user-2.8.2.jar", + "http://repo1.maven.org/maven2/com/google/gwt/gwt-user/2.8.2/gwt-user-2.8.2.jar", + ], + deps = [ + "@javax_validation_api", + "@javax_servlet_api", + "@org_w3c_css_sac", + ], + ) + def com_google_http_client(): java_import_external( name = "com_google_http_client", @@ -1547,6 +1579,7 @@ def com_google_truth(): "@junit", "@com_google_auto_value", "@com_google_errorprone_error_prone_annotations", + "@com_googlecode_java_diff_utils_diffutils", ], ) @@ -1577,6 +1610,17 @@ def com_googlecode_charts4j(): licenses = ["notice"], # The MIT License ) +def com_googlecode_java_diff_utils_diffutils(): + java_import_external( + name = "com_googlecode_java_diff_utils_diffutils", + licenses = ["notice"], # The Apache Software License, Version 2.0 + jar_sha256 = "61ba4dc49adca95243beaa0569adc2a23aedb5292ae78aa01186fa782ebdc5c2", + jar_urls = [ + "http://maven.ibiblio.org/maven2/com/googlecode/java-diff-utils/diffutils/1.3.0/diffutils-1.3.0.jar", + "http://repo1.maven.org/maven2/com/googlecode/java-diff-utils/diffutils/1.3.0/diffutils-1.3.0.jar", + ], + ) + def com_googlecode_json_simple(): java_import_external( name = "com_googlecode_json_simple", @@ -1907,6 +1951,17 @@ def javax_servlet_api(): licenses = ["notice"], # Apache ) +def javax_validation_api(): + java_import_external( + name = "javax_validation_api", + licenses = ["notice"], # Apache License, Version 2.0 + jar_sha256 = "e459f313ebc6db2483f8ceaad39af07086361b474fa92e40f442e8de5d9895dc", + jar_urls = [ + "http://maven.ibiblio.org/maven2/javax/validation/validation-api/1.0.0.GA/validation-api-1.0.0.GA.jar", + "http://repo1.maven.org/maven2/javax/validation/validation-api/1.0.0.GA/validation-api-1.0.0.GA.jar", + ], + ) + def javax_xml_bind_jaxb_api(): java_import_external( name = "javax_xml_bind_jaxb_api", @@ -2195,15 +2250,51 @@ def org_apache_mina_core(): def org_apache_sshd_core(): java_import_external( name = "org_apache_sshd_core", - jar_sha256 = "5630fa11f7e2f7f5b6b7e6b9be06e476715dfb48db37998b4b7c3eea098d86ff", + # Apache 2.0 License + # http://www.apache.org/licenses/LICENSE-2.0 + # Apache License, Version 2.0 + # http://www.apache.org/licenses/LICENSE-2.0.txt + licenses = ["notice"], + jar_sha256 = "00c944fac00dec2e7ace4052e0a52c772ca3fa2653918bbcfadf7100df022e25", jar_urls = [ - "http://maven.ibiblio.org/maven2/org/apache/sshd/sshd-core/1.2.0/sshd-core-1.2.0.jar", - "http://repo1.maven.org/maven2/org/apache/sshd/sshd-core/1.2.0/sshd-core-1.2.0.jar", + "http://repo1.maven.org/maven2/org/apache/sshd/sshd-core/2.0.0/sshd-core-2.0.0.jar", + "http://maven.ibiblio.org/maven2/org/apache/sshd/sshd-core/2.0.0/sshd-core-2.0.0.jar", ], - licenses = ["notice"], # Apache 2.0 License deps = ["@org_slf4j_api"], ) +def org_apache_sshd_scp(): + java_import_external( + name = "org_apache_sshd_scp", + # Apache 2.0 License + # http://www.apache.org/licenses/LICENSE-2.0 + # Apache License, Version 2.0 + # http://www.apache.org/licenses/LICENSE-2.0.txt + licenses = ["notice"], + jar_sha256 = "ae32fcc16ab0a0ae04655b8832676b41199814184dc50028b3c6aa61053635ca", + jar_urls = [ + "http://repo1.maven.org/maven2/org/apache/sshd/sshd-scp/2.0.0/sshd-scp-2.0.0.jar", + "http://maven.ibiblio.org/maven2/org/apache/sshd/sshd-scp/2.0.0/sshd-scp-2.0.0.jar", + ], + deps = ["@org_apache_sshd_core"], + ) + +def org_apache_sshd_sftp(): + java_import_external( + name = "org_apache_sshd_sftp", + # Apache 2.0 License + # http://www.apache.org/licenses/LICENSE-2.0 + # Apache License, Version 2.0 + # http://www.apache.org/licenses/LICENSE-2.0.txt + licenses = ["notice"], + jar_sha256 = "0504af9a4afcaf61be9f0b56d3cfc76a9187a654e297bc57b7fa81aa76bb8cb0", + jar_urls = [ + "http://repo1.maven.org/maven2/org/apache/sshd/sshd-sftp/2.0.0/sshd-sftp-2.0.0.jar", + "http://maven.ibiblio.org/maven2/org/apache/sshd/sshd-sftp/2.0.0/sshd-sftp-2.0.0.jar", + ], + deps = ["@org_apache_sshd_core"], + ) + def org_apache_tomcat_servlet_api(): java_import_external( name = "org_apache_tomcat_servlet_api", diff --git a/java/google/registry/request/Modules.java b/java/google/registry/request/Modules.java index 5b9cdf0ff..af043552a 100644 --- a/java/google/registry/request/Modules.java +++ b/java/google/registry/request/Modules.java @@ -15,13 +15,9 @@ package google.registry.request; import static com.google.appengine.api.datastore.DatastoreServiceFactory.getDatastoreService; -import static java.nio.charset.StandardCharsets.UTF_8; import com.google.api.client.extensions.appengine.http.UrlFetchTransport; -import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; -import com.google.api.client.googleapis.extensions.appengine.auth.oauth2.AppIdentityCredential; import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; -import com.google.api.client.http.HttpRequestInitializer; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.JsonFactory; @@ -31,15 +27,8 @@ import com.google.appengine.api.urlfetch.URLFetchService; import com.google.appengine.api.urlfetch.URLFetchServiceFactory; import com.google.appengine.api.users.UserService; import com.google.appengine.api.users.UserServiceFactory; -import dagger.Binds; import dagger.Module; import dagger.Provides; -import google.registry.keyring.api.KeyModule.Key; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.Set; -import java.util.function.Function; -import javax.inject.Provider; import javax.inject.Singleton; /** Dagger modules for App Engine services and other vendor classes. */ @@ -120,88 +109,4 @@ public final class Modules { } } } - - /** - * Dagger module providing {@link AppIdentityCredential}. - * - *

This can be used to authenticate to Google APIs using the identity of your GAE app. - * - * @see UseAppIdentityCredentialForGoogleApisModule - */ - @Module - public static final class AppIdentityCredentialModule { - @Provides - static Function, AppIdentityCredential> provideAppIdentityCredential() { - return AppIdentityCredential::new; - } - } - - /** - * Dagger module causing Google APIs requests to be authorized with your GAE app identity. - * - *

You must also use the {@link AppIdentityCredentialModule}. - */ - @Module - public abstract static class UseAppIdentityCredentialForGoogleApisModule { - @Binds - abstract Function, ? extends HttpRequestInitializer> provideHttpRequestInitializer( - Function, AppIdentityCredential> credential); - } - - /** - * Module indicating Google API requests should be authorized with JSON {@link GoogleCredential}. - * - *

This is useful when configuring a component that runs the registry outside of the App Engine - * environment, for example, in a command line environment. - * - *

You must also use the {@link GoogleCredentialModule}. - */ - @Module - public abstract static class UseGoogleCredentialForGoogleApisModule { - @Binds - abstract Function, ? extends HttpRequestInitializer> provideHttpRequestInitializer( - Function, GoogleCredential> credential); - } - - /** - * Dagger module providing {@link GoogleCredential} from a JSON key file contents. - * - *

This satisfies the {@link HttpRequestInitializer} interface for authenticating Google APIs - * requests, just like {@link AppIdentityCredential}. - * - *

But we consider GAE authentication more desirable and easier to manage operations-wise. So - * this authentication method should only be used for the following situations: - * - *

    - *
  1. Locally-running programs (which aren't executing on the App Engine platform) - *
  2. Spreadsheet service (which can't use {@link AppIdentityCredential} due to an old library) - *
- * - * @see google.registry.keyring.api.Keyring#getJsonCredential() - */ - @Module - public static final class GoogleCredentialModule { - - @Provides - @Singleton - static GoogleCredential provideGoogleCredential( - NetHttpTransport netHttpTransport, - JsonFactory jsonFactory, - @Key("jsonCredential") String jsonCredential) { - try { - return GoogleCredential.fromStream( - new ByteArrayInputStream(jsonCredential.getBytes(UTF_8)), - netHttpTransport, - jsonFactory); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Provides - static Function, GoogleCredential> provideScopedGoogleCredential( - final Provider googleCredentialProvider) { - return scopes -> googleCredentialProvider.get().createScoped(scopes); - } - } } diff --git a/java/google/registry/request/auth/AuthResult.java b/java/google/registry/request/auth/AuthResult.java index d64b23c34..c2e4bc585 100644 --- a/java/google/registry/request/auth/AuthResult.java +++ b/java/google/registry/request/auth/AuthResult.java @@ -36,6 +36,16 @@ public abstract class AuthResult { return authLevel() != AuthLevel.NONE; } + public String userIdForLogging() { + return userAuthInfo() + .map( + userAuthInfo -> + String.format( + "%s %s", + userAuthInfo.isUserAdmin() ? "admin" : "user", userAuthInfo.user().getEmail())) + .orElse(""); + } + public static AuthResult create(AuthLevel authLevel) { return new AutoValue_AuthResult(authLevel, Optional.empty()); } diff --git a/java/google/registry/tldconfig/idn/IdnLabelValidator.java b/java/google/registry/tldconfig/idn/IdnLabelValidator.java index 2c9d842bd..c02fcee05 100644 --- a/java/google/registry/tldconfig/idn/IdnLabelValidator.java +++ b/java/google/registry/tldconfig/idn/IdnLabelValidator.java @@ -20,7 +20,6 @@ import static google.registry.tldconfig.idn.IdnTableEnum.JA; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import google.registry.util.Idn; -import google.registry.util.NonFinalForTesting; import java.util.Optional; /** Validates whether a given IDN label can be provisioned for a particular TLD. */ @@ -30,10 +29,20 @@ public final class IdnLabelValidator { private static final ImmutableList DEFAULT_IDN_TABLES = ImmutableList.of(EXTENDED_LATIN, JA); + private static final ImmutableMap> + DEFAULT_IDN_TABLE_LISTS_PER_TLD = + ImmutableMap.of("xn--q9jyb4c", ImmutableList.of(EXTENDED_LATIN, JA)); + /** Some TLDs have their own IDN tables, configured here. */ - @NonFinalForTesting - private static ImmutableMap> idnTableListsPerTld = - ImmutableMap.of("xn--q9jyb4c", ImmutableList.of(EXTENDED_LATIN, JA)); + private ImmutableMap> idnTableListsPerTld; + + IdnLabelValidator(ImmutableMap> indTableListsPerTld) { + this.idnTableListsPerTld = indTableListsPerTld; + } + + public static IdnLabelValidator createDefaultIdnLabelValidator() { + return new IdnLabelValidator(DEFAULT_IDN_TABLE_LISTS_PER_TLD); + } /** * Returns name of first matching {@link IdnTable} if domain label is valid for the given TLD. @@ -41,16 +50,14 @@ public final class IdnLabelValidator { *

A label is valid if it is considered valid by at least one configured IDN table for that * TLD. If no match is found, an absent value is returned. */ - public static Optional findValidIdnTableForTld(String label, String tld) { + public Optional findValidIdnTableForTld(String label, String tld) { String unicodeString = Idn.toUnicode(label); - for (IdnTableEnum idnTable - : Optional.ofNullable(idnTableListsPerTld.get(tld)).orElse(DEFAULT_IDN_TABLES)) { + for (IdnTableEnum idnTable : + Optional.ofNullable(idnTableListsPerTld.get(tld)).orElse(DEFAULT_IDN_TABLES)) { if (idnTable.getTable().isValidLabel(unicodeString)) { return Optional.of(idnTable.getTable().getName()); } } return Optional.empty(); } - - private IdnLabelValidator() {} } diff --git a/java/google/registry/tmch/LordnTask.java b/java/google/registry/tmch/LordnTaskUtils.java similarity index 65% rename from java/google/registry/tmch/LordnTask.java rename to java/google/registry/tmch/LordnTaskUtils.java index 27308ac36..f1b0b3453 100644 --- a/java/google/registry/tmch/LordnTask.java +++ b/java/google/registry/tmch/LordnTaskUtils.java @@ -15,36 +15,22 @@ package google.registry.tmch; import static com.google.appengine.api.taskqueue.QueueFactory.getQueue; -import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static google.registry.model.ofy.ObjectifyService.ofy; -import com.google.appengine.api.taskqueue.LeaseOptions; -import com.google.appengine.api.taskqueue.Queue; -import com.google.appengine.api.taskqueue.TaskHandle; import com.google.appengine.api.taskqueue.TaskOptions; import com.google.appengine.api.taskqueue.TaskOptions.Method; -import com.google.appengine.api.taskqueue.TransientFailureException; -import com.google.apphosting.api.DeadlineExceededException; import com.google.common.base.Joiner; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableList; -import com.google.common.util.concurrent.Uninterruptibles; import google.registry.model.domain.DomainResource; import google.registry.model.registrar.Registrar; -import google.registry.util.NonFinalForTesting; -import google.registry.util.TaskQueueUtils; -import java.util.List; import java.util.Optional; -import java.util.concurrent.TimeUnit; import org.joda.time.DateTime; -import org.joda.time.Duration; /** * Helper methods for creating tasks containing CSV line data in the lordn-sunrise and lordn-claims * queues based on DomainResource changes. */ -public class LordnTask { +public final class LordnTaskUtils { public static final String QUEUE_SUNRISE = "lordn-sunrise"; public static final String QUEUE_CLAIMS = "lordn-claims"; @@ -52,51 +38,6 @@ public class LordnTask { + "registration-datetime,ack-datetime,application-datetime"; public static final String COLUMNS_SUNRISE = "roid,domain-name,SMD-id,registrar-id," + "registration-datetime,application-datetime"; - private static final Duration LEASE_PERIOD = Duration.standardHours(1); - - @NonFinalForTesting - private static Long backOffMillis = 2000L; - - /** - * Converts a list of queue tasks, each containing a row of CSV data, into a single newline- - * delimited String. - */ - public static String convertTasksToCsv(List tasks, DateTime now, String columns) { - String header = String.format("1,%s,%d\n%s\n", now, tasks.size(), columns); - StringBuilder csv = new StringBuilder(header); - for (TaskHandle task : checkNotNull(tasks)) { - String payload = new String(task.getPayload()); - if (!Strings.isNullOrEmpty(payload)) { - csv.append(payload).append("\n"); - } - } - return csv.toString(); - } - - /** Leases and returns all tasks from the queue with the specified tag tld, in batches. */ - public static List loadAllTasks(Queue queue, String tld) { - ImmutableList.Builder allTasks = new ImmutableList.Builder<>(); - int numErrors = 0; - long backOff = backOffMillis; - while (true) { - try { - List tasks = queue.leaseTasks(LeaseOptions.Builder - .withTag(tld) - .leasePeriod(LEASE_PERIOD.getMillis(), TimeUnit.MILLISECONDS) - .countLimit(TaskQueueUtils.getBatchSize())); - allTasks.addAll(tasks); - if (tasks.isEmpty()) { - return allTasks.build(); - } - } catch (TransientFailureException | DeadlineExceededException e) { - if (++numErrors >= 3) { - throw new RuntimeException("Error leasing tasks", e); - } - Uninterruptibles.sleepUninterruptibly(backOff, TimeUnit.MILLISECONDS); - backOff *= 2; - } - } - } /** * Enqueues a task in the LORDN queue representing a line of CSV for LORDN export. @@ -156,4 +97,6 @@ public class LordnTask { // have null iana ids. return String.valueOf(registrar.get().getIanaIdentifier()); } + + private LordnTaskUtils() {} } diff --git a/java/google/registry/tmch/NordnUploadAction.java b/java/google/registry/tmch/NordnUploadAction.java index fb57c0adf..4632c20bb 100644 --- a/java/google/registry/tmch/NordnUploadAction.java +++ b/java/google/registry/tmch/NordnUploadAction.java @@ -19,21 +19,27 @@ import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withUrl; import static com.google.appengine.api.urlfetch.FetchOptions.Builder.validateCertificate; import static com.google.appengine.api.urlfetch.HTTPMethod.POST; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.net.HttpHeaders.LOCATION; import static com.google.common.net.MediaType.CSV_UTF_8; -import static google.registry.tmch.LordnTask.COLUMNS_CLAIMS; -import static google.registry.tmch.LordnTask.COLUMNS_SUNRISE; -import static google.registry.tmch.LordnTask.convertTasksToCsv; +import static google.registry.tmch.LordnTaskUtils.COLUMNS_CLAIMS; +import static google.registry.tmch.LordnTaskUtils.COLUMNS_SUNRISE; import static google.registry.util.UrlFetchUtils.getHeaderFirst; import static google.registry.util.UrlFetchUtils.setPayloadMultipart; +import static java.nio.charset.StandardCharsets.UTF_8; import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED; +import com.google.appengine.api.taskqueue.LeaseOptions; import com.google.appengine.api.taskqueue.Queue; import com.google.appengine.api.taskqueue.TaskHandle; import com.google.appengine.api.taskqueue.TaskOptions; +import com.google.appengine.api.taskqueue.TransientFailureException; import com.google.appengine.api.urlfetch.HTTPRequest; import com.google.appengine.api.urlfetch.HTTPResponse; import com.google.appengine.api.urlfetch.URLFetchService; +import com.google.apphosting.api.DeadlineExceededException; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; import com.google.common.flogger.FluentLogger; import google.registry.config.RegistryConfig.Config; import google.registry.request.Action; @@ -41,6 +47,7 @@ import google.registry.request.Parameter; import google.registry.request.RequestParameters; import google.registry.request.auth.Auth; import google.registry.util.Clock; +import google.registry.util.Retrier; import google.registry.util.TaskQueueUtils; import google.registry.util.UrlFetchException; import java.io.IOException; @@ -48,29 +55,30 @@ import java.net.URL; import java.util.List; import java.util.Optional; import java.util.Random; +import java.util.concurrent.TimeUnit; import javax.inject.Inject; import org.joda.time.DateTime; import org.joda.time.Duration; /** * Action that reads the NORDN pull queues, uploads claims and sunrise marks data to TMCH, and - * enqueues subsequent upload verification tasks. A unique actionLogId is generated and passed - * along to the verify action so that connected verify tasks can be identified by looking at logs. + * enqueues subsequent upload verification tasks. A unique actionLogId is generated and passed along + * to the verify action so that connected verify tasks can be identified by looking at logs. * * @see NordnVerifyAction */ @Action( - path = NordnUploadAction.PATH, - method = Action.Method.POST, - automaticallyPrintOk = true, - auth = Auth.AUTH_INTERNAL_ONLY -) + path = NordnUploadAction.PATH, + method = Action.Method.POST, + automaticallyPrintOk = true, + auth = Auth.AUTH_INTERNAL_ONLY) public final class NordnUploadAction implements Runnable { static final String PATH = "/_dr/task/nordnUpload"; static final String LORDN_PHASE_PARAM = "lordn-phase"; private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + private static final Duration LEASE_PERIOD = Duration.standardHours(1); /** * A unique (enough) id that is outputted in log lines to make it clear which log lines are @@ -80,6 +88,8 @@ public final class NordnUploadAction implements Runnable { private final String actionLogId = String.valueOf(1000000000 + new Random().nextInt(1000000000)); @Inject Clock clock; + @Inject Retrier retrier; + @Inject @Config("insecureRandom") Random random; @Inject LordnRequestInitializer lordnRequestInitializer; @Inject URLFetchService fetchService; @Inject @Config("tmchMarksdbUrl") String tmchMarksdbUrl; @@ -107,15 +117,54 @@ public final class NordnUploadAction implements Runnable { } } + /** + * Converts a list of queue tasks, each containing a row of CSV data, into a single newline- + * delimited String. + */ + static String convertTasksToCsv(List tasks, DateTime now, String columns) { + String header = String.format("1,%s,%d\n%s\n", now, tasks.size(), columns); + StringBuilder csv = new StringBuilder(header); + for (TaskHandle task : checkNotNull(tasks)) { + String payload = new String(task.getPayload(), UTF_8); + if (!Strings.isNullOrEmpty(payload)) { + csv.append(payload).append("\n"); + } + } + return csv.toString(); + } + + /** Leases and returns all tasks from the queue with the specified tag tld, in batches. */ + List loadAllTasks(Queue queue, String tld) { + ImmutableList.Builder allTasks = new ImmutableList.Builder<>(); + while (true) { + List tasks = + retrier.callWithRetry( + () -> + queue.leaseTasks( + LeaseOptions.Builder.withTag(tld) + .leasePeriod(LEASE_PERIOD.getMillis(), TimeUnit.MILLISECONDS) + .countLimit(TaskQueueUtils.getBatchSize())), + TransientFailureException.class, + DeadlineExceededException.class); + if (tasks.isEmpty()) { + return allTasks.build(); + } + allTasks.addAll(tasks); + } + } + private void processLordnTasks() throws IOException { checkArgument(phase.equals(PARAM_LORDN_PHASE_SUNRISE) || phase.equals(PARAM_LORDN_PHASE_CLAIMS), "Invalid phase specified to Nordn servlet: %s.", phase); DateTime now = clock.nowUtc(); - Queue queue = getQueue( - phase.equals(PARAM_LORDN_PHASE_SUNRISE) ? LordnTask.QUEUE_SUNRISE : LordnTask.QUEUE_CLAIMS); + Queue queue = + getQueue( + phase.equals(PARAM_LORDN_PHASE_SUNRISE) + ? LordnTaskUtils.QUEUE_SUNRISE + : LordnTaskUtils.QUEUE_CLAIMS); String columns = phase.equals(PARAM_LORDN_PHASE_SUNRISE) ? COLUMNS_SUNRISE : COLUMNS_CLAIMS; - List tasks = LordnTask.loadAllTasks(queue, tld); + List tasks = loadAllTasks(queue, tld); if (!tasks.isEmpty()) { String csvData = convertTasksToCsv(tasks, now, columns); uploadCsvToLordn(String.format("/LORDN/%s/%s", tld, phase), csvData); @@ -138,23 +187,26 @@ public final class NordnUploadAction implements Runnable { "LORDN upload task %s: Sending to URL: %s ; data: %s", actionLogId, url, csvData); HTTPRequest req = new HTTPRequest(new URL(url), POST, validateCertificate().setDeadline(60d)); lordnRequestInitializer.initialize(req, tld); - setPayloadMultipart(req, "file", "claims.csv", CSV_UTF_8, csvData); + setPayloadMultipart(req, "file", "claims.csv", CSV_UTF_8, csvData, random); HTTPResponse rsp = fetchService.fetch(req); logger.atInfo().log( "LORDN upload task %s response: HTTP response code %d, response data: %s", actionLogId, rsp.getResponseCode(), rsp.getContent()); if (rsp.getResponseCode() != SC_ACCEPTED) { throw new UrlFetchException( - String.format("LORDN upload task %s error: Failed to upload LORDN claims to MarksDB", - actionLogId), - req, rsp); + String.format( + "LORDN upload task %s error: Failed to upload LORDN claims to MarksDB", actionLogId), + req, + rsp); } Optional location = getHeaderFirst(rsp, LOCATION); if (!location.isPresent()) { throw new UrlFetchException( - String.format("LORDN upload task %s error: MarksDB failed to provide a Location header", + String.format( + "LORDN upload task %s error: MarksDB failed to provide a Location header", actionLogId), - req, rsp); + req, + rsp); } getQueue(NordnVerifyAction.QUEUE).add(makeVerifyTask(new URL(location.get()))); } diff --git a/java/google/registry/tmch/TmchCertificateAuthority.java b/java/google/registry/tmch/TmchCertificateAuthority.java index a3ec73f60..9db65809f 100644 --- a/java/google/registry/tmch/TmchCertificateAuthority.java +++ b/java/google/registry/tmch/TmchCertificateAuthority.java @@ -15,7 +15,7 @@ package google.registry.tmch; import static google.registry.config.RegistryConfig.ConfigModule.TmchCaMode.PILOT; -import static google.registry.config.RegistryConfig.getSingletonCachePersistDuration; +import static google.registry.config.RegistryConfig.ConfigModule.TmchCaMode.PRODUCTION; import static google.registry.config.RegistryConfig.getSingletonCacheRefreshDuration; import static google.registry.util.ResourceUtils.readResourceUtf8; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -23,17 +23,16 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableMap; import google.registry.config.RegistryConfig.Config; import google.registry.config.RegistryConfig.ConfigModule.TmchCaMode; import google.registry.model.tmch.TmchCrl; import google.registry.util.Clock; -import google.registry.util.NonFinalForTesting; -import google.registry.util.SystemClock; import google.registry.util.X509Utils; import java.security.GeneralSecurityException; +import java.security.cert.CertificateParsingException; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; -import java.util.concurrent.ExecutionException; import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.ThreadSafe; import javax.inject.Inject; @@ -58,9 +57,12 @@ public final class TmchCertificateAuthority { private static final String CRL_PILOT_FILE = "icann-tmch-pilot.crl"; private final TmchCaMode tmchCaMode; + private final Clock clock; - public @Inject TmchCertificateAuthority(@Config("tmchCaMode") TmchCaMode tmchCaMode) { + @Inject + public TmchCertificateAuthority(@Config("tmchCaMode") TmchCaMode tmchCaMode, Clock clock) { this.tmchCaMode = tmchCaMode; + this.clock = clock; } /** @@ -70,8 +72,8 @@ public final class TmchCertificateAuthority { * string into an X509CRL instance is expensive and should itself be cached. * *

Note that the stored CRL won't exist for tests, and on deployed environments will always - * correspond to the correct CRL for the given TMCH CA mode because {@link TmchCrlAction} can - * only persist the correct one for this given environment. + * correspond to the correct CRL for the given TMCH CA mode because {@link TmchCrlAction} can only + * persist the correct one for this given environment. */ private static final LoadingCache CRL_CACHE = CacheBuilder.newBuilder() @@ -89,37 +91,28 @@ public final class TmchCertificateAuthority { crlContents = storedCrl.getCrl(); } X509CRL crl = X509Utils.loadCrl(crlContents); - try { - crl.verify(ROOT_CACHE.get(tmchCaMode).getPublicKey()); - return crl; - } catch (ExecutionException e) { - if (e.getCause() instanceof GeneralSecurityException) { - throw (GeneralSecurityException) e.getCause(); - } else { - throw new RuntimeException("Unexpected exception while loading CRL", e); - } - } - }}); + crl.verify(ROOT_CERTS.get(tmchCaMode).getPublicKey()); + return crl; + } + }); - /** A cached function that loads the CRT from a jar resource. */ - private static final LoadingCache ROOT_CACHE = - CacheBuilder.newBuilder() - .expireAfterWrite(getSingletonCachePersistDuration().getMillis(), MILLISECONDS) - .build( - new CacheLoader() { - @Override - public X509Certificate load(final TmchCaMode tmchCaMode) - throws GeneralSecurityException { - String file = (tmchCaMode == PILOT) ? ROOT_CRT_PILOT_FILE : ROOT_CRT_FILE; - X509Certificate root = - X509Utils.loadCertificate( - readResourceUtf8(TmchCertificateAuthority.class, file)); - root.checkValidity(clock.nowUtc().toDate()); - return root; - }}); + /** CRTs from a jar resource. */ + private static final ImmutableMap ROOT_CERTS = + loadRootCertificates(); - @NonFinalForTesting - private static Clock clock = new SystemClock(); + private static ImmutableMap loadRootCertificates() { + try { + return ImmutableMap.of( + PILOT, + X509Utils.loadCertificate( + readResourceUtf8(TmchCertificateAuthority.class, ROOT_CRT_PILOT_FILE)), + PRODUCTION, + X509Utils.loadCertificate( + readResourceUtf8(TmchCertificateAuthority.class, ROOT_CRT_FILE))); + } catch (CertificateParsingException e) { + throw new RuntimeException(e); + } + } /** * Check that {@code cert} is signed by the ICANN TMCH CA root and not revoked. @@ -132,7 +125,7 @@ public final class TmchCertificateAuthority { */ public void verify(X509Certificate cert) throws GeneralSecurityException { synchronized (TmchCertificateAuthority.class) { - X509Utils.verifyCertificate(getRoot(), getCrl(), cert, clock.nowUtc().toDate()); + X509Utils.verifyCertificate(getAndValidateRoot(), getCrl(), cert, clock.nowUtc().toDate()); } } @@ -145,21 +138,26 @@ public final class TmchCertificateAuthority { * refreshes itself. * * @throws GeneralSecurityException for unsupported protocols, certs not signed by the TMCH, - * incorrect keys, and for invalid, old, not-yet-valid or revoked certificates. + * incorrect keys, and for invalid, old, not-yet-valid or revoked certificates. * @see X509Utils#verifyCrl */ public void updateCrl(String asciiCrl, String url) throws GeneralSecurityException { X509CRL crl = X509Utils.loadCrl(asciiCrl); - X509Utils.verifyCrl(getRoot(), getCrl(), crl, clock.nowUtc().toDate()); + X509Utils.verifyCrl(getAndValidateRoot(), getCrl(), crl, clock.nowUtc().toDate()); TmchCrl.set(asciiCrl, url); } - public X509Certificate getRoot() throws GeneralSecurityException { + public X509Certificate getAndValidateRoot() throws GeneralSecurityException { try { - return ROOT_CACHE.get(tmchCaMode); + X509Certificate root = ROOT_CERTS.get(tmchCaMode); + // The current production certificate expires on 2023-07-23. Future code monkey be reminded, + // if you are looking at this code because the next line throws an exception, ask ICANN for a + // new root certificate! (preferably before the current one expires...) + root.checkValidity(clock.nowUtc().toDate()); + return root; } catch (Exception e) { - if (e.getCause() instanceof GeneralSecurityException) { - throw (GeneralSecurityException) e.getCause(); + if (e instanceof GeneralSecurityException) { + throw (GeneralSecurityException) e; } else if (e instanceof RuntimeException) { throw (RuntimeException) e; } diff --git a/java/google/registry/tools/AppEngineConnection.java b/java/google/registry/tools/AppEngineConnection.java index cf4393a02..68ab47ddb 100644 --- a/java/google/registry/tools/AppEngineConnection.java +++ b/java/google/registry/tools/AppEngineConnection.java @@ -15,7 +15,6 @@ package google.registry.tools; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Suppliers.memoize; import static com.google.common.net.HttpHeaders.X_REQUESTED_WITH; import static com.google.common.net.MediaType.JSON_UTF_8; import static google.registry.security.JsonHttp.JSON_SAFETY_PREFIX; @@ -27,48 +26,55 @@ import com.google.api.client.http.HttpHeaders; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpResponse; -import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.io.CharStreams; -import com.google.common.net.HostAndPort; import com.google.common.net.MediaType; import com.google.re2j.Matcher; import com.google.re2j.Pattern; -import google.registry.security.XsrfTokenManager; -import google.registry.tools.CommandWithConnection.Connection; +import google.registry.config.RegistryConfig; import java.io.IOException; import java.io.InputStreamReader; +import java.net.URL; import java.util.Map; import javax.annotation.Nullable; import javax.inject.Inject; import org.json.simple.JSONValue; -/** An http connection to the appengine server. */ -class AppEngineConnection implements Connection { +/** + * An http connection to an appengine server. + * + *

By default - connects to the TOOLS service. To create a Connection to another service, call + * the {@link #withService} function. + */ +class AppEngineConnection { /** Pattern to heuristically extract title tag contents in HTML responses. */ private static final Pattern HTML_TITLE_TAG_PATTERN = Pattern.compile("(.*?)"); @Inject HttpRequestFactory requestFactory; - @Inject AppEngineConnectionFlags flags; - @Inject XsrfTokenManager xsrfTokenManager; + private final Service service; @Inject - AppEngineConnection() {} + AppEngineConnection() { + service = Service.TOOLS; + } - /** - * Memoized XSRF security token. - * - *

Computing this is expensive since it needs to load {@code ServerSecret} so do it once. - */ - private final Supplier xsrfToken = - memoize(() -> xsrfTokenManager.generateToken(getUserId())); + private AppEngineConnection(Service service, HttpRequestFactory requestFactory) { + this.service = service; + this.requestFactory = requestFactory; + } - @Override - public void prefetchXsrfToken() { - // Cause XSRF token to be fetched, and then stay resident in cache (since it's memoized). - xsrfToken.get(); + enum Service { + DEFAULT, + TOOLS, + BACKEND, + PUBAPI + } + + /** Returns a copy of this connection that talks to a different service. */ + public AppEngineConnection withService(Service service) { + return new AppEngineConnection(service, requestFactory); } /** Returns the contents of the title tag in the given HTML, or null if not found. */ @@ -85,7 +91,8 @@ class AppEngineConnection implements Connection { private String internalSend( String endpoint, Map params, MediaType contentType, @Nullable byte[] payload) throws IOException { - GenericUrl url = new GenericUrl(String.format("%s%s", getServerUrl(), endpoint)); + GenericUrl url = new GenericUrl(getServer()); + url.setRawPath(endpoint); url.putAll(params); HttpRequest request = (payload != null) @@ -120,23 +127,20 @@ class AppEngineConnection implements Connection { } } - // TODO(b/111123862): Rename this to sendPostRequest() - @Override - public String send(String endpoint, Map params, MediaType contentType, byte[] payload) + public String sendPostRequest( + String endpoint, Map params, MediaType contentType, byte[] payload) throws IOException { return internalSend(endpoint, params, contentType, checkNotNull(payload, "payload")); } - @Override public String sendGetRequest(String endpoint, Map params) throws IOException { return internalSend(endpoint, params, MediaType.PLAIN_TEXT_UTF_8, null); } - @Override @SuppressWarnings("unchecked") public Map sendJson(String endpoint, Map object) throws IOException { String response = - send( + sendPostRequest( endpoint, ImmutableMap.of(), JSON_UTF_8, @@ -144,22 +148,17 @@ class AppEngineConnection implements Connection { return (Map) JSONValue.parse(response.substring(JSON_SAFETY_PREFIX.length())); } - @Override - public String getServerUrl() { - return (isLocalhost() ? "http://" : "https://") + getServer().toString(); - } - - HostAndPort getServer() { - return flags.getServer().withDefaultPort(443); // Default to HTTPS port if unspecified. - } - - boolean isLocalhost() { - return flags.getServer().getHost().equals("localhost"); - } - - private String getUserId() { - return isLocalhost() - ? UserIdProvider.getTestUserId() - : UserIdProvider.getProdUserId(); + public URL getServer() { + switch (service) { + case DEFAULT: + return RegistryConfig.getDefaultServer(); + case TOOLS: + return RegistryConfig.getToolsServer(); + case BACKEND: + return RegistryConfig.getBackendServer(); + case PUBAPI: + return RegistryConfig.getPubapiServer(); + } + throw new IllegalStateException("Unknown service: " + service); } } diff --git a/java/google/registry/tools/AppEngineConnectionFlags.java b/java/google/registry/tools/AppEngineConnectionFlags.java deleted file mode 100644 index 020397883..000000000 --- a/java/google/registry/tools/AppEngineConnectionFlags.java +++ /dev/null @@ -1,63 +0,0 @@ -// 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; - -import com.beust.jcommander.Parameter; -import com.beust.jcommander.Parameters; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.net.HostAndPort; -import dagger.Module; -import dagger.Provides; -import google.registry.config.RegistryConfig; - -/** - * Class to contain the configuration flags for AppEngineConnection. - * - *

This is broken out into its own class to make it cleaner to extract these from the dagger - * module, where these values are injected. - */ -@Parameters(separators = " =") -class AppEngineConnectionFlags { - - @Parameter(names = "--server", description = "HOST[:PORT] to which remote commands are sent.") - private HostAndPort server = RegistryConfig.getServer(); - - /** Provided for testing. */ - @VisibleForTesting - AppEngineConnectionFlags(HostAndPort server) { - this.server = server; - } - - AppEngineConnectionFlags() {} - - HostAndPort getServer() { - return server; - } - - @Module - static class FlagsModule { - AppEngineConnectionFlags flags; - - FlagsModule(AppEngineConnectionFlags flags) { - this.flags = flags; - } - - @Provides - AppEngineConnectionFlags provideAppEngineConnectionFlags() { - return flags; - } - } -} - diff --git a/java/google/registry/tools/BUILD b/java/google/registry/tools/BUILD index 2c2327ef4..d4671b9b9 100644 --- a/java/google/registry/tools/BUILD +++ b/java/google/registry/tools/BUILD @@ -46,6 +46,7 @@ java_library( "//java/google/registry/export", "//java/google/registry/flows", "//java/google/registry/gcs", + "//java/google/registry/keyring", "//java/google/registry/keyring/api", "//java/google/registry/keyring/kms", "//java/google/registry/loadtest", @@ -69,7 +70,6 @@ java_library( "@com_google_api_client", "@com_google_apis_google_api_services_bigquery", "@com_google_apis_google_api_services_dns", - "@com_google_apis_google_api_services_monitoring", "@com_google_appengine_api_1_0_sdk", "@com_google_appengine_remote_api", "@com_google_appengine_remote_api//:link", @@ -81,8 +81,6 @@ java_library( "@com_google_guava", "@com_google_http_client", "@com_google_http_client_jackson2", - "@com_google_monitoring_client_metrics", - "@com_google_monitoring_client_stackdriver", "@com_google_oauth_client", "@com_google_oauth_client_java6", "@com_google_oauth_client_jetty", diff --git a/java/google/registry/tools/CommandWithConnection.java b/java/google/registry/tools/CommandWithConnection.java index 010afce8f..c53239724 100644 --- a/java/google/registry/tools/CommandWithConnection.java +++ b/java/google/registry/tools/CommandWithConnection.java @@ -14,30 +14,7 @@ package google.registry.tools; -import com.google.common.net.MediaType; -import java.io.IOException; -import java.util.Map; -import javax.annotation.Nullable; - /** A command that can send HTTP requests to a backend module. */ interface CommandWithConnection extends Command { - - /** An http connection to AppEngine. */ - interface Connection { - - void prefetchXsrfToken(); - - /** Send a POST request. TODO(mmuller): change to sendPostRequest() */ - String send( - String endpoint, Map params, MediaType contentType, @Nullable byte[] payload) - throws IOException; - - String sendGetRequest(String endpoint, Map params) throws IOException; - - Map sendJson(String endpoint, Map object) throws IOException; - - String getServerUrl(); - } - - void setConnection(Connection connection); + void setConnection(AppEngineConnection connection); } diff --git a/java/google/registry/tools/CreateAnchorTenantCommand.java b/java/google/registry/tools/CreateAnchorTenantCommand.java index 776a6cbcb..d7ceea2a6 100644 --- a/java/google/registry/tools/CreateAnchorTenantCommand.java +++ b/java/google/registry/tools/CreateAnchorTenantCommand.java @@ -25,6 +25,7 @@ import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.google.common.net.InternetDomainName; import com.google.template.soy.data.SoyMapData; +import google.registry.config.RegistryConfig.Config; import google.registry.tools.soy.CreateAnchorTenantSoyInfo; import google.registry.util.StringGenerator; import javax.inject.Inject; @@ -72,6 +73,7 @@ final class CreateAnchorTenantCommand extends MutatingEppToolCommand { private boolean fee; @Inject + @Config("base64StringGenerator") StringGenerator passwordGenerator; @Override diff --git a/java/google/registry/tools/CreateContactCommand.java b/java/google/registry/tools/CreateContactCommand.java index b73f697a0..fbd0f597d 100644 --- a/java/google/registry/tools/CreateContactCommand.java +++ b/java/google/registry/tools/CreateContactCommand.java @@ -20,6 +20,7 @@ import static com.google.common.base.Strings.isNullOrEmpty; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.google.template.soy.data.SoyMapData; +import google.registry.config.RegistryConfig.Config; import google.registry.tools.params.PhoneNumberParameter; import google.registry.tools.soy.ContactCreateSoyInfo; import google.registry.util.StringGenerator; @@ -103,6 +104,7 @@ final class CreateContactCommand extends MutatingEppToolCommand { private String password; @Inject + @Config("base64StringGenerator") StringGenerator passwordGenerator; private static final int PASSWORD_LENGTH = 16; diff --git a/java/google/registry/tools/CreateDomainCommand.java b/java/google/registry/tools/CreateDomainCommand.java index a183cf195..a41e81b4d 100644 --- a/java/google/registry/tools/CreateDomainCommand.java +++ b/java/google/registry/tools/CreateDomainCommand.java @@ -23,6 +23,7 @@ import static org.joda.time.DateTimeZone.UTC; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.google.template.soy.data.SoyMapData; +import google.registry.config.RegistryConfig.Config; import google.registry.model.pricing.PremiumPricingEngine.DomainPrices; import google.registry.tools.soy.DomainCreateSoyInfo; import google.registry.util.StringGenerator; @@ -46,6 +47,7 @@ final class CreateDomainCommand extends CreateOrUpdateDomainCommand private boolean forcePremiums; @Inject + @Config("base64StringGenerator") StringGenerator passwordGenerator; private static final int PASSWORD_LENGTH = 16; diff --git a/java/google/registry/tools/CreateOrUpdatePremiumListCommand.java b/java/google/registry/tools/CreateOrUpdatePremiumListCommand.java index 76f14d271..069bbfc68 100644 --- a/java/google/registry/tools/CreateOrUpdatePremiumListCommand.java +++ b/java/google/registry/tools/CreateOrUpdatePremiumListCommand.java @@ -57,11 +57,11 @@ abstract class CreateOrUpdatePremiumListCommand extends ConfirmingCommand required = true) Path inputFile; - protected Connection connection; + protected AppEngineConnection connection; protected int inputLineCount; @Override - public void setConnection(Connection connection) { + public void setConnection(AppEngineConnection connection) { this.connection = connection; } @@ -101,11 +101,9 @@ abstract class CreateOrUpdatePremiumListCommand extends ConfirmingCommand } // Call the server and get the response data - String response = connection.send( - getCommandPath(), - params.build(), - MediaType.FORM_DATA, - requestBody.getBytes(UTF_8)); + String response = + connection.sendPostRequest( + getCommandPath(), params.build(), MediaType.FORM_DATA, requestBody.getBytes(UTF_8)); return extractServerResponse(response); } diff --git a/java/google/registry/tools/CreateRegistrarCommand.java b/java/google/registry/tools/CreateRegistrarCommand.java index 0eb42e28e..06336b09f 100644 --- a/java/google/registry/tools/CreateRegistrarCommand.java +++ b/java/google/registry/tools/CreateRegistrarCommand.java @@ -50,10 +50,10 @@ final class CreateRegistrarCommand extends CreateOrUpdateRegistrarCommand arity = 1) boolean createGoogleGroups = true; - private Connection connection; + private AppEngineConnection connection; @Override - public void setConnection(Connection connection) { + public void setConnection(AppEngineConnection connection) { this.connection = connection; } diff --git a/java/google/registry/tools/CreateRegistrarGroupsCommand.java b/java/google/registry/tools/CreateRegistrarGroupsCommand.java index 72c190389..65bfbc133 100644 --- a/java/google/registry/tools/CreateRegistrarGroupsCommand.java +++ b/java/google/registry/tools/CreateRegistrarGroupsCommand.java @@ -41,10 +41,10 @@ public class CreateRegistrarGroupsCommand extends ConfirmingCommand private List registrars = new ArrayList<>(); - private Connection connection; + private AppEngineConnection connection; @Override - public void setConnection(Connection connection) { + public void setConnection(AppEngineConnection connection) { this.connection = connection; } @@ -66,8 +66,8 @@ public class CreateRegistrarGroupsCommand extends ConfirmingCommand } /** Calls the server endpoint to create groups for the specified registrar client id. */ - static void executeOnServer(Connection connection, String clientId) throws IOException { - connection.send( + static void executeOnServer(AppEngineConnection connection, String clientId) throws IOException { + connection.sendPostRequest( CreateGroupsAction.PATH, ImmutableMap.of(CreateGroupsAction.CLIENT_ID_PARAM, clientId), MediaType.PLAIN_TEXT_UTF_8, @@ -77,7 +77,7 @@ public class CreateRegistrarGroupsCommand extends ConfirmingCommand @Override protected String execute() throws IOException { for (Registrar registrar : registrars) { - connection.send( + connection.sendPostRequest( CreateGroupsAction.PATH, ImmutableMap.of(CreateGroupsAction.CLIENT_ID_PARAM, registrar.getClientId()), MediaType.PLAIN_TEXT_UTF_8, diff --git a/java/google/registry/tools/CurlCommand.java b/java/google/registry/tools/CurlCommand.java index 046a808c7..3804b8d48 100644 --- a/java/google/registry/tools/CurlCommand.java +++ b/java/google/registry/tools/CurlCommand.java @@ -22,11 +22,12 @@ import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.net.MediaType; +import google.registry.tools.AppEngineConnection.Service; import java.util.List; @Parameters(separators = " =", commandDescription = "Send an HTTP command to the nomulus server.") class CurlCommand implements CommandWithConnection { - private Connection connection; + private AppEngineConnection connection; // HTTP Methods that are acceptable for use as values for --method. public enum Method { @@ -62,8 +63,14 @@ class CurlCommand implements CommandWithConnection { + "absent, a GET request is sent.") private List data; + @Parameter( + names = {"--service"}, + description = "Which service to connect to", + required = true) + private Service service; + @Override - public void setConnection(Connection connection) { + public void setConnection(AppEngineConnection connection) { this.connection = connection; } @@ -77,12 +84,14 @@ class CurlCommand implements CommandWithConnection { throw new IllegalArgumentException("You may not specify a body for a get method."); } - // TODO(b/112315418): Make it possible to address any backend. + AppEngineConnection connectionToService = connection.withService(service); String response = (method == Method.GET) - ? connection.sendGetRequest(path, ImmutableMap.of()) - : connection.send( - path, ImmutableMap.of(), mimeType, + ? connectionToService.sendGetRequest(path, ImmutableMap.of()) + : connectionToService.sendPostRequest( + path, + ImmutableMap.of(), + mimeType, Joiner.on("&").join(data).getBytes(UTF_8)); System.out.println(response); } diff --git a/java/google/registry/tools/DefaultRequestFactoryModule.java b/java/google/registry/tools/DefaultRequestFactoryModule.java index ea2a055c0..4f75c894e 100644 --- a/java/google/registry/tools/DefaultRequestFactoryModule.java +++ b/java/google/registry/tools/DefaultRequestFactoryModule.java @@ -20,6 +20,7 @@ import com.google.api.client.http.javanet.NetHttpTransport; import dagger.Binds; import dagger.Module; import dagger.Provides; +import google.registry.config.RegistryConfig; import javax.inject.Named; import javax.inject.Provider; @@ -43,9 +44,8 @@ class DefaultRequestFactoryModule { @Provides @Named("default") public HttpRequestFactory provideHttpRequestFactory( - AppEngineConnectionFlags connectionFlags, Provider credentialProvider) { - if (connectionFlags.getServer().getHost().equals("localhost")) { + if (RegistryConfig.areServersLocal()) { return new NetHttpTransport() .createRequestFactory( request -> request diff --git a/java/google/registry/tools/DeleteAllocationTokensCommand.java b/java/google/registry/tools/DeleteAllocationTokensCommand.java new file mode 100644 index 000000000..aaf02f7f1 --- /dev/null +++ b/java/google/registry/tools/DeleteAllocationTokensCommand.java @@ -0,0 +1,107 @@ +// 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.tools; + +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.common.collect.Iterables.partition; +import static com.google.common.collect.Streams.stream; +import static google.registry.model.ofy.ObjectifyService.ofy; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableSet; +import com.googlecode.objectify.Key; +import com.googlecode.objectify.cmd.Query; +import google.registry.model.domain.token.AllocationToken; +import java.util.List; + +/** + * Command to delete unused {@link AllocationToken}s. + * + *

Allocation tokens that have been redeemed cannot be deleted. To delete a single allocation + * token, specify the entire token as the prefix. + */ +@Parameters( + separators = " =", + commandDescription = "Deletes the unused AllocationTokens with a given prefix.") +final class DeleteAllocationTokensCommand extends ConfirmingCommand + implements CommandWithRemoteApi { + + @Parameter( + names = {"-p", "--prefix"}, + description = "Allocation token prefix; if blank, deletes all unused tokens", + required = true) + private String prefix; + + @Parameter( + names = {"--with_domains"}, + description = "Allow deletion of allocation tokens with specified domains; defaults to false") + boolean withDomains; + + @Parameter( + names = {"--dry_run"}, + description = "Do not actually delete the tokens; defaults to false") + boolean dryRun; + + private static final int BATCH_SIZE = 20; + private static final Joiner JOINER = Joiner.on(", "); + + private ImmutableSet> tokensToDelete; + + @Override + public void init() { + Query query = + ofy().load().type(AllocationToken.class).filter("redemptionHistoryEntry", null); + tokensToDelete = + query.keys().list().stream() + .filter(key -> key.getName().startsWith(prefix)) + .collect(toImmutableSet()); + } + + @Override + protected String prompt() { + return String.format( + "Found %d unused tokens starting with '%s' to delete.", tokensToDelete.size(), prefix); + } + + @Override + protected String execute() { + long numDeleted = + stream(partition(tokensToDelete, BATCH_SIZE)) + .mapToLong(batch -> ofy().transact(() -> deleteBatch(batch))) + .sum(); + return String.format("Deleted %d tokens in total.", numDeleted); + } + + /** Deletes a (filtered) batch of AllocationTokens and returns how many were deleted. */ + private long deleteBatch(List> batch) { + // Load the tokens in the same transaction as they are deleted to verify they weren't redeemed + // since the query ran. This also filters out per-domain tokens if they're not to be deleted. + ImmutableSet tokensToDelete = + ofy().load().keys(batch).values().stream() + .filter(t -> withDomains || !t.getDomainName().isPresent()) + .filter(t -> !t.isRedeemed()) + .collect(toImmutableSet()); + if (!dryRun) { + ofy().delete().entities(tokensToDelete); + } + System.out.printf( + "%s tokens: %s\n", + dryRun ? "Would delete" : "Deleted", + JOINER.join(batch.stream().map(Key::getName).collect(toImmutableSet()))); + return tokensToDelete.size(); + } +} diff --git a/java/google/registry/tools/EppToolCommand.java b/java/google/registry/tools/EppToolCommand.java index 25d93f1cd..895124cbe 100644 --- a/java/google/registry/tools/EppToolCommand.java +++ b/java/google/registry/tools/EppToolCommand.java @@ -59,7 +59,7 @@ abstract class EppToolCommand extends ConfirmingCommand private List commands = new ArrayList<>(); - private Connection connection; + private AppEngineConnection connection; static class XmlEppParameters { final String clientId; @@ -95,7 +95,7 @@ abstract class EppToolCommand extends ConfirmingCommand } @Override - public void setConnection(Connection connection) { + public void setConnection(AppEngineConnection connection) { this.connection = connection; } @@ -145,11 +145,13 @@ abstract class EppToolCommand extends ConfirmingCommand params.put("xml", URLEncoder.encode(command.xml, UTF_8.toString())); String requestBody = Joiner.on('&').withKeyValueSeparator("=").join(filterValues(params, Objects::nonNull)); - responses.add(nullToEmpty(connection.send( - "/_dr/epptool", - ImmutableMap.of(), - MediaType.FORM_DATA, - requestBody.getBytes(UTF_8)))); + responses.add( + nullToEmpty( + connection.sendPostRequest( + "/_dr/epptool", + ImmutableMap.of(), + MediaType.FORM_DATA, + requestBody.getBytes(UTF_8)))); } return responses.build(); } diff --git a/java/google/registry/tools/ExecuteEppCommand.java b/java/google/registry/tools/ExecuteEppCommand.java index 52cf04fff..1117f8d30 100644 --- a/java/google/registry/tools/ExecuteEppCommand.java +++ b/java/google/registry/tools/ExecuteEppCommand.java @@ -20,10 +20,8 @@ import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.google.common.io.CharStreams; import com.google.common.io.Files; -import google.registry.util.NonFinalForTesting; import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; @@ -40,14 +38,10 @@ final class ExecuteEppCommand extends MutatingEppToolCommand { required = true) String clientId; - @NonFinalForTesting - private static InputStream stdin = System.in; - @Override protected void initMutatingEppToolCommand() throws IOException { if (mainParameters.isEmpty()) { - addXmlCommand( - clientId, CharStreams.toString(new InputStreamReader(stdin, UTF_8))); + addXmlCommand(clientId, CharStreams.toString(new InputStreamReader(System.in, UTF_8))); } else { for (String command : mainParameters) { addXmlCommand(clientId, Files.asCharSource(new File(command), UTF_8).read()); diff --git a/java/google/registry/tools/GenerateAllocationTokensCommand.java b/java/google/registry/tools/GenerateAllocationTokensCommand.java index 2daf39be6..7274f57fa 100644 --- a/java/google/registry/tools/GenerateAllocationTokensCommand.java +++ b/java/google/registry/tools/GenerateAllocationTokensCommand.java @@ -31,6 +31,7 @@ import com.google.common.base.Splitter; import com.google.common.collect.ImmutableSet; import com.google.common.io.Files; import com.googlecode.objectify.Key; +import google.registry.config.RegistryConfig.Config; import google.registry.model.domain.token.AllocationToken; import google.registry.util.NonFinalForTesting; import google.registry.util.Retrier; @@ -40,16 +41,15 @@ import java.io.IOException; import java.util.Collection; import java.util.Deque; import javax.inject.Inject; -import javax.inject.Named; /** Command to generate and persist {@link AllocationToken}s. */ -@NonFinalForTesting @Parameters( separators = " =", commandDescription = "Generates and persists the given number of AllocationTokens, printing each token to stdout." ) -public class GenerateAllocationTokensCommand implements CommandWithRemoteApi { +@NonFinalForTesting +class GenerateAllocationTokensCommand implements CommandWithRemoteApi { @Parameter( names = {"-p", "--prefix"}, @@ -80,7 +80,10 @@ public class GenerateAllocationTokensCommand implements CommandWithRemoteApi { description = "Do not actually persist the tokens; defaults to false") boolean dryRun; - @Inject @Named("base58StringGenerator") StringGenerator stringGenerator; + @Inject + @Config("base58StringGenerator") + StringGenerator stringGenerator; + @Inject Retrier retrier; private static final int BATCH_SIZE = 20; diff --git a/java/google/registry/tools/GenerateLordnCommand.java b/java/google/registry/tools/GenerateLordnCommand.java index 153530aee..81815f117 100644 --- a/java/google/registry/tools/GenerateLordnCommand.java +++ b/java/google/registry/tools/GenerateLordnCommand.java @@ -22,7 +22,7 @@ import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.google.common.collect.ImmutableList; import google.registry.model.domain.DomainResource; -import google.registry.tmch.LordnTask; +import google.registry.tmch.LordnTaskUtils; import google.registry.tools.params.PathParameter; import java.io.IOException; import java.nio.file.Files; @@ -61,26 +61,28 @@ final class GenerateLordnCommand implements CommandWithRemoteApi { for (DomainResource domain : ofy().load().type(DomainResource.class).filter("tld", tld)) { String status = " "; if (domain.getLaunchNotice() == null && domain.getSmdId() != null) { - sunriseCsv.add(LordnTask.getCsvLineForSunriseDomain(domain, domain.getCreationTime())); + sunriseCsv.add(LordnTaskUtils.getCsvLineForSunriseDomain(domain, domain.getCreationTime())); status = "S"; } else if (domain.getLaunchNotice() != null || domain.getSmdId() != null) { - claimsCsv.add(LordnTask.getCsvLineForClaimsDomain(domain, domain.getCreationTime())); + claimsCsv.add(LordnTaskUtils.getCsvLineForClaimsDomain(domain, domain.getCreationTime())); status = "C"; } System.out.printf("%s[%s] ", domain.getFullyQualifiedDomainName(), status); } ImmutableList claimsRows = claimsCsv.build(); - ImmutableList claimsAll = new ImmutableList.Builder() - .add(String.format("1,%s,%d", now, claimsRows.size())) - .add(LordnTask.COLUMNS_CLAIMS) - .addAll(claimsRows) - .build(); + ImmutableList claimsAll = + new ImmutableList.Builder() + .add(String.format("1,%s,%d", now, claimsRows.size())) + .add(LordnTaskUtils.COLUMNS_CLAIMS) + .addAll(claimsRows) + .build(); ImmutableList sunriseRows = sunriseCsv.build(); - ImmutableList sunriseAll = new ImmutableList.Builder() - .add(String.format("1,%s,%d", now.plusMillis(1), sunriseRows.size())) - .add(LordnTask.COLUMNS_SUNRISE) - .addAll(sunriseRows) - .build(); + ImmutableList sunriseAll = + new ImmutableList.Builder() + .add(String.format("1,%s,%d", now.plusMillis(1), sunriseRows.size())) + .add(LordnTaskUtils.COLUMNS_SUNRISE) + .addAll(sunriseRows) + .build(); Files.write(claimsOutputPath, claimsAll, UTF_8); Files.write(sunriseOutputPath, sunriseAll, UTF_8); } diff --git a/java/google/registry/tools/GenerateZoneFilesCommand.java b/java/google/registry/tools/GenerateZoneFilesCommand.java index 1992e405d..19351bc8f 100644 --- a/java/google/registry/tools/GenerateZoneFilesCommand.java +++ b/java/google/registry/tools/GenerateZoneFilesCommand.java @@ -45,10 +45,10 @@ final class GenerateZoneFilesCommand implements CommandWithConnection, CommandWi validateWith = DateParameter.class) private DateTime exportDate = DateTime.now(UTC).minus(standardMinutes(2)).withTimeAtStartOfDay(); - private Connection connection; + private AppEngineConnection connection; @Override - public void setConnection(Connection connection) { + public void setConnection(AppEngineConnection connection) { this.connection = connection; } @@ -59,10 +59,7 @@ final class GenerateZoneFilesCommand implements CommandWithConnection, CommandWi "tlds", mainParameters, "exportTime", exportDate.toString()); Map response = connection.sendJson(GenerateZoneFilesAction.PATH, params); - System.out.printf( - "Job started at %s%s\n", - connection.getServerUrl(), - response.get("jobPath")); + System.out.printf("Job started at %s %s\n", connection.getServer(), response.get("jobPath")); System.out.println("Output files:"); @SuppressWarnings("unchecked") List filenames = (List) response.get("filenames"); diff --git a/java/google/registry/tools/ListDomainsCommand.java b/java/google/registry/tools/ListDomainsCommand.java index b5be4c709..7928c7579 100644 --- a/java/google/registry/tools/ListDomainsCommand.java +++ b/java/google/registry/tools/ListDomainsCommand.java @@ -15,11 +15,14 @@ package google.registry.tools; import static com.google.common.base.Preconditions.checkArgument; +import static google.registry.model.registry.Registries.getTldsOfType; +import static google.registry.util.CollectionUtils.isNullOrEmpty; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableMap; +import google.registry.model.registry.Registry.TldType; import google.registry.tools.server.ListDomainsAction; import java.util.List; @@ -29,8 +32,7 @@ final class ListDomainsCommand extends ListObjectsCommand { @Parameter( names = {"-t", "--tld", "--tlds"}, - description = "Comma-delimited list of top-level domain(s) to list second-level domains of.", - required = true) + description = "Comma-delimited list of TLDs to list domains on; defaults to all REAL TLDs.") private List tlds; @Parameter( @@ -47,6 +49,10 @@ final class ListDomainsCommand extends ListObjectsCommand { /** Returns a map of parameters to be sent to the server (in addition to the usual ones). */ @Override ImmutableMap getParameterMap() { + // Default to all REAL TLDs if not specified. + if (isNullOrEmpty(tlds)) { + tlds = getTldsOfType(TldType.REAL).asList(); + } String tldsParam = Joiner.on(',').join(tlds); checkArgument(tldsParam.length() < 1024, "Total length of TLDs is too long for URL parameter"); return ImmutableMap.of("tlds", tldsParam, "limit", maxDomains); diff --git a/java/google/registry/tools/ListObjectsCommand.java b/java/google/registry/tools/ListObjectsCommand.java index 402af428a..8b670a1c0 100644 --- a/java/google/registry/tools/ListObjectsCommand.java +++ b/java/google/registry/tools/ListObjectsCommand.java @@ -54,10 +54,10 @@ abstract class ListObjectsCommand implements CommandWithConnection, CommandWithR description = "Whether to print full field names in header row (as opposed to aliases)") private boolean fullFieldNames = false; - private Connection connection; + private AppEngineConnection connection; @Override - public void setConnection(Connection connection) { + public void setConnection(AppEngineConnection connection) { this.connection = connection; } @@ -83,11 +83,9 @@ abstract class ListObjectsCommand implements CommandWithConnection, CommandWithR } params.putAll(getParameterMap()); // Call the server and get the response data. - String response = connection.send( - getCommandPath(), - params.build(), - MediaType.PLAIN_TEXT_UTF_8, - new byte[0]); + String response = + connection.sendPostRequest( + getCommandPath(), params.build(), MediaType.PLAIN_TEXT_UTF_8, new byte[0]); // Parse the returned JSON and make sure it's a map. Object obj = JSONValue.parse(response.substring(JSON_SAFETY_PREFIX.length())); if (!(obj instanceof Map)) { diff --git a/java/google/registry/tools/LoadTestCommand.java b/java/google/registry/tools/LoadTestCommand.java index 4c80540d0..f61b26739 100644 --- a/java/google/registry/tools/LoadTestCommand.java +++ b/java/google/registry/tools/LoadTestCommand.java @@ -77,10 +77,10 @@ class LoadTestCommand extends ConfirmingCommand description = "Time to run the load test in seconds.") int runSeconds = DEFAULT_RUN_SECONDS; - private Connection connection; + private AppEngineConnection connection; @Override - public void setConnection(Connection connection) { + public void setConnection(AppEngineConnection connection) { this.connection = connection; } @@ -127,10 +127,7 @@ class LoadTestCommand extends ConfirmingCommand .put("runSeconds", runSeconds) .build(); - return connection.send( - LoadTestAction.PATH, - params, - MediaType.PLAIN_TEXT_UTF_8, - new byte[0]); + return connection.sendPostRequest( + LoadTestAction.PATH, params, MediaType.PLAIN_TEXT_UTF_8, new byte[0]); } } diff --git a/java/google/registry/tools/MetricToolModule.java b/java/google/registry/tools/MetricToolModule.java deleted file mode 100644 index 6ed06998b..000000000 --- a/java/google/registry/tools/MetricToolModule.java +++ /dev/null @@ -1,53 +0,0 @@ -// 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.tools; - -import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; -import com.google.api.services.monitoring.v3.Monitoring; -import com.google.api.services.monitoring.v3.model.MonitoredResource; -import com.google.monitoring.metrics.MetricWriter; -import com.google.monitoring.metrics.stackdriver.StackdriverWriter; -import dagger.Module; -import dagger.Provides; -import google.registry.config.CredentialModule.DefaultCredential; -import google.registry.config.RegistryConfig.Config; - -/** Dagger module for metrics on the client tool. */ -@Module -public final class MetricToolModule { - - @Provides - static Monitoring provideMonitoring( - @DefaultCredential GoogleCredential credential, @Config("projectId") String projectId) { - return new Monitoring.Builder( - credential.getTransport(), credential.getJsonFactory(), credential) - .setApplicationName(projectId) - .build(); - } - - @Provides - static MetricWriter provideMetricWriter( - Monitoring monitoringClient, - @Config("projectId") String projectId, - @Config("stackdriverMaxQps") int maxQps, - @Config("stackdriverMaxPointsPerRequest") int maxPointsPerRequest) { - return new StackdriverWriter( - monitoringClient, - projectId, - new MonitoredResource().setType("global"), - maxQps, - maxPointsPerRequest); - } -} diff --git a/java/google/registry/tools/RegistrarContactCommand.java b/java/google/registry/tools/RegistrarContactCommand.java index 89b5fc826..411d1bf10 100644 --- a/java/google/registry/tools/RegistrarContactCommand.java +++ b/java/google/registry/tools/RegistrarContactCommand.java @@ -57,19 +57,19 @@ final class RegistrarContactCommand extends MutatingCommand { @Parameter( description = "Client identifier of the registrar account.", required = true) - private List mainParameters; + List mainParameters; @Parameter( names = "--mode", description = "Type of operation you want to perform (LIST, CREATE, UPDATE, or DELETE).", required = true) - private Mode mode; + Mode mode; @Nullable @Parameter( names = "--name", description = "Contact name.") - private String name; + String name; @Nullable @Parameter( @@ -82,7 +82,7 @@ final class RegistrarContactCommand extends MutatingCommand { @Parameter( names = "--email", description = "Contact email address.") - private String email; + String email; @Nullable @Parameter( @@ -105,7 +105,7 @@ final class RegistrarContactCommand extends MutatingCommand { names = "--allow_console_access", description = "Enable or disable access to the registrar console for this contact.", arity = 1) - private Boolean allowConsoleAccess; + Boolean allowConsoleAccess; @Nullable @Parameter( @@ -138,7 +138,7 @@ final class RegistrarContactCommand extends MutatingCommand { validateWith = PathParameter.OutputFile.class) private Path output = Paths.get("/dev/stdout"); - private enum Mode { LIST, CREATE, UPDATE, DELETE } + enum Mode { LIST, CREATE, UPDATE, DELETE } private static final ImmutableSet MODES_REQUIRING_CONTACT_SYNC = ImmutableSet.of(Mode.CREATE, Mode.UPDATE, Mode.DELETE); diff --git a/java/google/registry/tools/RegistryCli.java b/java/google/registry/tools/RegistryCli.java index 2daa6ab02..2de69bd38 100644 --- a/java/google/registry/tools/RegistryCli.java +++ b/java/google/registry/tools/RegistryCli.java @@ -26,19 +26,11 @@ import com.beust.jcommander.Parameter; import com.beust.jcommander.ParameterException; import com.beust.jcommander.Parameters; import com.beust.jcommander.ParametersDelegate; -import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; -import com.google.monitoring.metrics.IncrementableMetric; -import com.google.monitoring.metrics.LabelDescriptor; -import com.google.monitoring.metrics.Metric; -import com.google.monitoring.metrics.MetricPoint; -import com.google.monitoring.metrics.MetricRegistryImpl; -import com.google.monitoring.metrics.MetricWriter; +import google.registry.config.RegistryConfig; import google.registry.model.ofy.ObjectifyService; import google.registry.tools.params.ParameterFactory; -import java.io.IOException; import java.security.Security; import java.util.Map; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -62,14 +54,6 @@ final class RegistryCli implements AutoCloseable, CommandRunner { description = "Returns all command names.") private boolean showAllCommands; - @VisibleForTesting - boolean uploadMetrics = true; - - // Do not make this final - compile-time constant inlining may interfere with JCommander. - @ParametersDelegate - private AppEngineConnectionFlags appEngineConnectionFlags = - new AppEngineConnectionFlags(); - // Do not make this final - compile-time constant inlining may interfere with JCommander. @ParametersDelegate @@ -85,24 +69,6 @@ final class RegistryCli implements AutoCloseable, CommandRunner { // "shell". private boolean isFirstUse = true; - private static final ImmutableSet LABEL_DESCRIPTORS_FOR_COMMANDS = - ImmutableSet.of( - LabelDescriptor.create("program", "The program used - e.g. nomulus or gtech_tool"), - LabelDescriptor.create("environment", "The environment used - e.g. sandbox"), - LabelDescriptor.create("command", "The command used"), - LabelDescriptor.create("success", "Whether the command succeeded"), - LabelDescriptor.create("shell", "Whether the command was called from the nomulus shell")); - - private static final IncrementableMetric commandsCalledCount = - MetricRegistryImpl.getDefault() - .newIncrementableMetric( - "/tools/commands_called", - "Count of tool commands called", - "count", - LABEL_DESCRIPTORS_FOR_COMMANDS); - - private MetricWriter metricWriter = null; - Map> commands; String programName; @@ -114,7 +80,6 @@ final class RegistryCli implements AutoCloseable, CommandRunner { Security.addProvider(new BouncyCastleProvider()); component = DaggerRegistryToolComponent.builder() - .flagsModule(new AppEngineConnectionFlags.FlagsModule(appEngineConnectionFlags)) .build(); } @@ -124,13 +89,9 @@ final class RegistryCli implements AutoCloseable, CommandRunner { // http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeArguments.html#FAQ104 @Override public void run(String[] args) throws Exception { - boolean inShell = !isFirstUse; - isFirstUse = false; // Create the JCommander instance. - // If we're in the shell, we don't want to update the RegistryCli's parameters (so we give a - // dummy object to update) - JCommander jcommander = new JCommander(inShell ? new Object() : this); + JCommander jcommander = new JCommander(this); jcommander.addConverterFactory(new ParameterFactory()); jcommander.setProgramName(programName); @@ -149,8 +110,8 @@ final class RegistryCli implements AutoCloseable, CommandRunner { // Create the "help" and "shell" commands (these are special in that they don't have a default // constructor). jcommander.addCommand("help", new HelpCommand(jcommander)); - if (!inShell) { - // If we aren't inside a shell, then we want to add the shell command. + if (isFirstUse) { + isFirstUse = false; ShellCommand shellCommand = new ShellCommand(this); // We have to build the completions based on the jcommander *before* we add the "shell" // command - to avoid completion for the "shell" command itself. @@ -192,31 +153,17 @@ final class RegistryCli implements AutoCloseable, CommandRunner { jcommander.getCommands().get(jcommander.getParsedCommand()).getObjects()); loggingParams.configureLogging(); // Must be called after parameters are parsed. - boolean success = false; try { runCommand(command); - success = true; } catch (AuthModule.LoginRequiredException ex) { System.err.println("==================================================================="); System.err.println("You must login using 'nomulus login' prior to running this command."); System.err.println("==================================================================="); - } finally { - commandsCalledCount.increment( - programName, - environment.toString(), - command.getClass().getSimpleName(), - String.valueOf(success), - String.valueOf(inShell)); - exportMetrics(); } } @Override public void close() { - exportMetrics(); - if (metricWriter != null) { - metricWriter = null; - } if (installer != null) { installer.uninstall(); installer = null; @@ -233,14 +180,6 @@ final class RegistryCli implements AutoCloseable, CommandRunner { private void runCommand(Command command) throws Exception { injectReflectively(RegistryToolComponent.class, component, command); - if (metricWriter == null && uploadMetrics) { - try { - metricWriter = component.metricWriter(); - } catch (Exception e) { - System.err.format("Failed to get metricWriter. Got error:\n%s\n\n", e); - uploadMetrics = false; - } - } if (command instanceof CommandWithConnection) { ((CommandWithConnection) command).setConnection(getConnection()); @@ -253,7 +192,7 @@ final class RegistryCli implements AutoCloseable, CommandRunner { RemoteApiOptions options = new RemoteApiOptions(); options.server( getConnection().getServer().getHost(), getConnection().getServer().getPort()); - if (getConnection().isLocalhost()) { + if (RegistryConfig.areServersLocal()) { // Use dev credentials for localhost. options.useDevelopmentServerCredential(); } else { @@ -272,25 +211,6 @@ final class RegistryCli implements AutoCloseable, CommandRunner { command.run(); } - private void exportMetrics() { - if (metricWriter == null) { - return; - } - try { - for (Metric metric : MetricRegistryImpl.getDefault().getRegisteredMetrics()) { - for (MetricPoint point : metric.getTimestampedValues()) { - metricWriter.write(point); - } - } - metricWriter.flush(); - } catch (IOException e) { - System.err.format("Failed to export metrics. Got error:\n%s\n\n", e); - System.err.println("Maybe you need to login? Try calling:"); - System.err.println(" gcloud auth application-default login"); - } - } - - @VisibleForTesting void setEnvironment(RegistryToolEnvironment environment) { this.environment = environment; } diff --git a/java/google/registry/tools/RegistryTool.java b/java/google/registry/tools/RegistryTool.java index a16b59433..ac47090d9 100644 --- a/java/google/registry/tools/RegistryTool.java +++ b/java/google/registry/tools/RegistryTool.java @@ -48,6 +48,7 @@ public final class RegistryTool { .put("create_sandbox_tld", CreateSandboxTldCommand.class) .put("create_tld", CreateTldCommand.class) .put("curl", CurlCommand.class) + .put("delete_allocation_tokens", DeleteAllocationTokensCommand.class) .put("delete_domain", DeleteDomainCommand.class) .put("delete_host", DeleteHostCommand.class) .put("delete_premium_list", DeletePremiumListCommand.class) diff --git a/java/google/registry/tools/RegistryToolComponent.java b/java/google/registry/tools/RegistryToolComponent.java index f0ea20337..2c8363671 100644 --- a/java/google/registry/tools/RegistryToolComponent.java +++ b/java/google/registry/tools/RegistryToolComponent.java @@ -14,7 +14,6 @@ package google.registry.tools; -import com.google.monitoring.metrics.MetricWriter; import dagger.Component; import google.registry.bigquery.BigqueryModule; import google.registry.config.CredentialModule; @@ -22,17 +21,15 @@ import google.registry.config.RegistryConfig.ConfigModule; import google.registry.dns.writer.VoidDnsWriterModule; import google.registry.dns.writer.clouddns.CloudDnsWriterModule; import google.registry.dns.writer.dnsupdate.DnsUpdateWriterModule; +import google.registry.keyring.KeyringModule; +import google.registry.keyring.api.DummyKeyringModule; import google.registry.keyring.api.KeyModule; import google.registry.keyring.kms.KmsModule; import google.registry.rde.RdeModule; -import google.registry.request.Modules.AppIdentityCredentialModule; import google.registry.request.Modules.DatastoreServiceModule; -import google.registry.request.Modules.GoogleCredentialModule; import google.registry.request.Modules.Jackson2Module; -import google.registry.request.Modules.NetHttpTransportModule; import google.registry.request.Modules.URLFetchServiceModule; import google.registry.request.Modules.UrlFetchTransportModule; -import google.registry.request.Modules.UseAppIdentityCredentialForGoogleApisModule; import google.registry.request.Modules.UserServiceModule; import google.registry.util.AppEngineServiceUtilsImpl.AppEngineServiceUtilsModule; import google.registry.util.SystemClock.SystemClockModule; @@ -49,37 +46,30 @@ import javax.inject.Singleton; @Singleton @Component( modules = { - AppEngineConnectionFlags.FlagsModule.class, AppEngineServiceUtilsModule.class, // TODO(b/36866706): Find a way to replace this with a command-line friendly version - AppIdentityCredentialModule.class, AuthModule.class, BigqueryModule.class, ConfigModule.class, CredentialModule.class, DatastoreServiceModule.class, - google.registry.keyring.api.DummyKeyringModule.class, + DummyKeyringModule.class, CloudDnsWriterModule.class, DefaultRequestFactoryModule.class, DefaultRequestFactoryModule.RequestFactoryModule.class, DnsUpdateWriterModule.class, - GoogleCredentialModule.class, Jackson2Module.class, KeyModule.class, + KeyringModule.class, KmsModule.class, - NetHttpTransportModule.class, RdeModule.class, - RegistryToolModule.class, SystemClockModule.class, SystemSleeperModule.class, URLFetchServiceModule.class, UrlFetchTransportModule.class, - // TODO(b/36866706): Find a way to replace this with a command-line friendly version - UseAppIdentityCredentialForGoogleApisModule.class, UserServiceModule.class, VoidDnsWriterModule.class, WhoisModule.class, - MetricToolModule.class, }) interface RegistryToolComponent { void inject(CheckDomainClaimsCommand command); @@ -118,6 +108,4 @@ interface RegistryToolComponent { void inject(WhoisQueryCommand command); AppEngineConnection appEngineConnection(); - - MetricWriter metricWriter(); } diff --git a/java/google/registry/tools/RegistryToolModule.java b/java/google/registry/tools/RegistryToolModule.java deleted file mode 100644 index eb7cefac8..000000000 --- a/java/google/registry/tools/RegistryToolModule.java +++ /dev/null @@ -1,67 +0,0 @@ -// 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; - -import dagger.Binds; -import dagger.Module; -import dagger.Provides; -import google.registry.util.RandomStringGenerator; -import google.registry.util.StringGenerator; -import google.registry.util.StringGenerator.Alphabets; -import java.security.NoSuchAlgorithmException; -import java.security.ProviderException; -import java.security.SecureRandom; -import javax.inject.Named; - -/** Dagger module for Registry Tool. */ -@Module -abstract class RegistryToolModule { - - @Provides - static RegistryToolEnvironment provideRegistryToolEnvironment() { - return RegistryToolEnvironment.get(); - } - - @Binds - abstract StringGenerator provideStringGenerator(RandomStringGenerator stringGenerator); - - @Provides - static SecureRandom provideSecureRandom() { - try { - return SecureRandom.getInstance("NativePRNG"); - } catch (NoSuchAlgorithmException e) { - throw new ProviderException(e); - } - } - - @Provides - @Named("alphabetBase64") - static String provideAlphabetBase64() { - return Alphabets.BASE_64; - } - - @Provides - @Named("alphabetBase58") - static String provideAlphabetBase58() { - return Alphabets.BASE_58; - } - - @Provides - @Named("base58StringGenerator") - static StringGenerator provideBase58StringGenerator( - @Named("alphabetBase58") String alphabet, SecureRandom random) { - return new RandomStringGenerator(alphabet, random); - } -} diff --git a/java/google/registry/tools/SetupOteCommand.java b/java/google/registry/tools/SetupOteCommand.java index bc6b34d9e..404ef1e88 100644 --- a/java/google/registry/tools/SetupOteCommand.java +++ b/java/google/registry/tools/SetupOteCommand.java @@ -15,15 +15,21 @@ package google.registry.tools; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.tools.CommandUtilities.promptForYes; import static google.registry.util.X509Utils.loadCertificate; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSortedMap; import com.google.re2j.Pattern; +import google.registry.config.RegistryConfig.Config; import google.registry.config.RegistryEnvironment; +import google.registry.model.common.GaeUserIdConverter; import google.registry.model.registrar.Registrar; import google.registry.model.registry.Registry.TldState; import google.registry.tools.params.PathParameter; @@ -53,6 +59,10 @@ final class SetupOteCommand extends ConfirmingCommand implements CommandWithRemo private static final Duration SHORT_REDEMPTION_GRACE_PERIOD = Duration.standardMinutes(10); private static final Duration SHORT_PENDING_DELETE_LENGTH = Duration.standardMinutes(5); + // Whether to prompt the user on command failures. Set to false for testing of these failures. + @VisibleForTesting + static boolean interactive = true; + private static final ImmutableSortedMap EAP_FEE_SCHEDULE = ImmutableSortedMap.of( new DateTime(0), @@ -87,6 +97,14 @@ final class SetupOteCommand extends ConfirmingCommand implements CommandWithRemo ) private List ipWhitelist = new ArrayList<>(); + @Parameter( + names = {"--email"}, + description = + "the registrar's account to use for console access. " + + "Must be on the registry's G-Suite domain.", + required = true) + private String email; + @Parameter( names = {"-c", "--certfile"}, description = "full path to cert file in PEM format (best if on local storage)", @@ -122,7 +140,9 @@ final class SetupOteCommand extends ConfirmingCommand implements CommandWithRemo ) private boolean eapOnly = false; - @Inject StringGenerator passwordGenerator; + @Inject + @Config("base64StringGenerator") + StringGenerator passwordGenerator; /** * Long registrar names are truncated and then have an incrementing digit appended at the end so @@ -130,6 +150,21 @@ final class SetupOteCommand extends ConfirmingCommand implements CommandWithRemo */ private int roidSuffixCounter = 0; + /** Runs a command, clearing the cache before and prompting the user on failures. */ + private void runCommand(Command command) { + ofy().clearSessionCache(); + try { + command.run(); + } catch (Exception e) { + System.err.format("Command failed with error %s\n", e); + if (interactive && promptForYes("Continue to next command?")) { + return; + } + Throwables.throwIfUnchecked(e); + throw new RuntimeException(e); + } + } + /** Constructs and runs a CreateTldCommand. */ private void createTld( String tldName, @@ -137,8 +172,7 @@ final class SetupOteCommand extends ConfirmingCommand implements CommandWithRemo Duration addGracePeriod, Duration redemptionGracePeriod, Duration pendingDeleteLength, - boolean isEarlyAccess) - throws Exception { + boolean isEarlyAccess) { CreateTldCommand command = new CreateTldCommand(); command.addGracePeriod = addGracePeriod; command.dnsWriters = dnsWriters; @@ -158,11 +192,11 @@ final class SetupOteCommand extends ConfirmingCommand implements CommandWithRemo if (isEarlyAccess) { command.eapFeeSchedule = EAP_FEE_SCHEDULE; } - command.run(); + runCommand(command); } /** Constructs and runs a CreateRegistrarCommand */ - private void createRegistrar(String registrarName, String password, String tld) throws Exception { + private void createRegistrar(String registrarName, String password, String tld) { CreateRegistrarCommand command = new CreateRegistrarCommand(); command.mainParameters = ImmutableList.of(registrarName); command.createGoogleGroups = false; // Don't create Google Groups for OT&E registrars. @@ -183,7 +217,19 @@ final class SetupOteCommand extends ConfirmingCommand implements CommandWithRemo command.phone = Optional.of("+1.2125550100"); command.icannReferralEmail = "nightmare@registrar.test"; command.force = force; - command.run(); + runCommand(command); + } + + /** Constructs and runs a RegistrarContactCommand */ + private void createRegistrarContact(String registrarName) { + RegistrarContactCommand command = new RegistrarContactCommand(); + command.mainParameters = ImmutableList.of(registrarName); + command.mode = RegistrarContactCommand.Mode.CREATE; + command.name = email; + command.email = email; + command.allowConsoleAccess = true; + command.force = force; + runCommand(command); } /** Run any pre-execute command checks */ @@ -193,6 +239,14 @@ final class SetupOteCommand extends ConfirmingCommand implements CommandWithRemo REGISTRAR_PATTERN.matcher(registrar).matches(), "Registrar name is invalid (see usage text for requirements)."); + // Make sure the email is "correct" - as in it's a valid email we can convert to gaeId + // There's no need to look at the result - it'll be converted again inside + // RegistrarContactCommand. + checkNotNull( + GaeUserIdConverter.convertEmailAddressToGaeUserId(email), + "Email address %s is not associated with any GAE ID", + email); + boolean warned = false; if (RegistryEnvironment.get() != RegistryEnvironment.SANDBOX && RegistryEnvironment.get() != RegistryEnvironment.UNITTEST) { @@ -227,7 +281,9 @@ final class SetupOteCommand extends ConfirmingCommand implements CommandWithRemo return "Creating TLD:\n" + " " + registrar + "-eap\n" + "Creating registrar:\n" - + " " + registrar + "-5 (access to TLD " + registrar + "-eap)"; + + " " + registrar + "-5 (access to TLD " + registrar + "-eap)\n" + + "Giving contact access to this registrar:\n" + + " " + email; } else { return "Creating TLDs:\n" + " " + registrar + "-sunrise\n" @@ -239,7 +295,9 @@ final class SetupOteCommand extends ConfirmingCommand implements CommandWithRemo + " " + registrar + "-2 (access to TLD " + registrar + "-landrush)\n" + " " + registrar + "-3 (access to TLD " + registrar + "-ga)\n" + " " + registrar + "-4 (access to TLD " + registrar + "-ga)\n" - + " " + registrar + "-5 (access to TLD " + registrar + "-eap)"; + + " " + registrar + "-5 (access to TLD " + registrar + "-eap)\n" + + "Giving contact access to these registrars:\n" + + " " + email; } } @@ -297,6 +355,7 @@ final class SetupOteCommand extends ConfirmingCommand implements CommandWithRemo for (List r : registrars) { createRegistrar(r.get(0), r.get(1), r.get(2)); + createRegistrarContact(r.get(0)); } StringBuilder output = new StringBuilder(); diff --git a/java/google/registry/tools/ShellCommand.java b/java/google/registry/tools/ShellCommand.java index dcdf9204a..eb6abe680 100644 --- a/java/google/registry/tools/ShellCommand.java +++ b/java/google/registry/tools/ShellCommand.java @@ -145,6 +145,80 @@ public class ShellCommand implements Command { return this; } + private static class OutputEncapsulator { + private PrintStream orgStdout; + private PrintStream orgStderr; + + private EncapsulatingOutputStream encapsulatedOutputStream = null; + private EncapsulatingOutputStream encapsulatedErrorStream = null; + + private Exception error; + + private OutputEncapsulator() { + orgStdout = System.out; + orgStderr = System.err; + encapsulatedOutputStream = new EncapsulatingOutputStream(System.out, "out: "); + encapsulatedErrorStream = new EncapsulatingOutputStream(System.out, "err: "); + System.setOut(new PrintStream(encapsulatedOutputStream)); + System.setErr(new PrintStream(encapsulatedErrorStream)); + } + + void setError(Exception e) { + error = e; + } + + private void restoreOriginalStreams() { + try { + encapsulatedOutputStream.dumpLastLine(); + encapsulatedErrorStream.dumpLastLine(); + System.setOut(orgStdout); + System.setErr(orgStderr); + if (error != null) { + emitFailure(error); + } else { + emitSuccess(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Emit a success command separator. + * + *

Dumps the last line of output prior to doing this. + */ + private void emitSuccess() { + System.out.println(SUCCESS); + System.out.flush(); + } + + /** + * Emit a failure message obtained from the throwable. + * + *

Dumps the last line of output prior to doing this. + */ + private void emitFailure(Throwable e) { + System.out.println( + FAILURE + + e.getClass().getName() + + " " + + e.getMessage().replace("\\", "\\\\").replace("\n", "\\n")); + } + + /** Run "func" with output encapsulation. */ + static void run(CommandRunner runner, String[] args) { + OutputEncapsulator encapsulator = new OutputEncapsulator(); + try { + runner.run(args); + } catch (Exception e) { + encapsulator.setError(e); + } finally { + encapsulator.restoreOriginalStreams(); + } + } + } + /** Run the shell until the user presses "Ctrl-D". */ @Override public void run() { @@ -154,23 +228,6 @@ public class ShellCommand implements Command { String line; DateTime lastTime = clock.nowUtc(); while ((line = getLine()) != null) { - PrintStream orgStdout = null; - PrintStream orgStderr = null; - EncapsulatingOutputStream encapsulatedOutputStream = null; - EncapsulatingOutputStream encapsulatedErrorStream = null; - - - // Wrap standard output and error if requested. We have to do so here in run because the flags - // haven't been processed in the constructor. - if (encapsulateOutput) { - orgStdout = System.out; - orgStderr = System.err; - encapsulatedOutputStream = new EncapsulatingOutputStream(System.out, "out: "); - encapsulatedErrorStream = new EncapsulatingOutputStream(System.out, "err: "); - System.setOut(new PrintStream(encapsulatedOutputStream)); - System.setErr(new PrintStream(encapsulatedErrorStream)); - } - // Make sure we're not idle for too long. Only relevant when we're "extra careful" if (!dontExitOnIdle && beExtraCareful @@ -184,28 +241,17 @@ public class ShellCommand implements Command { if (lineArgs.length == 0) { continue; } - Exception lastError = null; - try { - System.out.println("Barf!!!"); - runner.run(lineArgs); - } catch (Exception e) { - lastError = e; - System.err.println("Got an exception:\n" + e); - } - try { - if (encapsulatedOutputStream != null) { - encapsulatedOutputStream.dumpLastLine(); - encapsulatedErrorStream.dumpLastLine(); - System.setOut(orgStdout); - System.setErr(orgStderr); - if (lastError == null) { - emitSuccess(); - } else { - emitFailure(lastError); - } + + // Wrap standard output and error if requested. We have to do so here in run because the flags + // haven't been processed in the constructor. + if (encapsulateOutput) { + OutputEncapsulator.run(runner, lineArgs); + } else { + try { + runner.run(lineArgs); + } catch (Exception e) { + System.err.println("Got an exception:\n" + e); } - } catch (IOException e) { - throw new RuntimeException(e); } } if (!encapsulateOutput) { @@ -244,29 +290,6 @@ public class ShellCommand implements Command { return resultBuilder.build().toArray(new String[0]); } - /** - * Emit a success command separator. - * - *

Dumps the last line of output prior to doing this. - */ - private void emitSuccess() { - System.out.println(SUCCESS); - System.out.flush(); - } - - /** - * Emit a failure message obtained from the throwable. - * - *

Dumps the last line of output prior to doing this. - */ - private void emitFailure(Throwable e) { - System.out.println( - FAILURE - + e.getClass().getName() - + " " - + e.getMessage().replace("\\", "\\\\").replace("\n", "\\n")); - } - @VisibleForTesting static class JCommanderCompletor implements Completor { diff --git a/java/google/registry/tools/VerifyOteCommand.java b/java/google/registry/tools/VerifyOteCommand.java index 9a4fb465f..1129c2e7e 100644 --- a/java/google/registry/tools/VerifyOteCommand.java +++ b/java/google/registry/tools/VerifyOteCommand.java @@ -57,10 +57,10 @@ final class VerifyOteCommand implements CommandWithConnection, CommandWithRemote description = "Only show a summary of information") private boolean summarize; - private Connection connection; + private AppEngineConnection connection; @Override - public void setConnection(Connection connection) { + public void setConnection(AppEngineConnection connection) { this.connection = connection; } diff --git a/java/google/registry/tools/logging.properties b/java/google/registry/tools/logging.properties index d66a2fcff..f724c987e 100644 --- a/java/google/registry/tools/logging.properties +++ b/java/google/registry/tools/logging.properties @@ -2,7 +2,4 @@ handlers = java.util.logging.ConsoleHandler .level = INFO com.google.wrappers.base.GoogleInit.level = WARNING com.google.monitoring.metrics.MetricRegistryImpl.level = WARNING -com.google.monitoring.metrics.MetricReporter.level = WARNING -com.google.monitoring.metrics.MetricExporter.level = WARNING -com.google.monitoring.metrics.stackdriver.StackdriverWriter.level = WARNING diff --git a/java/google/registry/ui/js/registrar/console.js b/java/google/registry/ui/js/registrar/console.js index 99d8a4cfa..9c8d639da 100644 --- a/java/google/registry/ui/js/registrar/console.js +++ b/java/google/registry/ui/js/registrar/console.js @@ -14,11 +14,13 @@ goog.provide('registry.registrar.Console'); +goog.require('goog.Uri'); goog.require('goog.dispose'); goog.require('goog.dom'); goog.require('goog.dom.classlist'); goog.require('goog.net.XhrIo'); goog.require('registry.Console'); +goog.require('registry.Resource'); goog.require('registry.registrar.Contact'); goog.require('registry.registrar.ContactSettings'); goog.require('registry.registrar.ContactUs'); @@ -76,7 +78,7 @@ registry.registrar.Console = function(params) { /** * @type {!Object.} + * !registry.Resource)>} */ this.pageMap = {}; this.pageMap['security-settings'] = registry.registrar.SecuritySettings; @@ -136,7 +138,10 @@ registry.registrar.Console.prototype.handleHashChange = function() { componentCtor = this.pageMap['']; } var oldComponent = this.component_; - this.component_ = new componentCtor(this, this.params.xsrfToken); + const resource = new registry.Resource( + new goog.Uri('/registrar-settings'), this.params.clientId, + this.params.xsrfToken); + this.component_ = new componentCtor(this, resource); this.registerDisposable(this.component_); this.component_.basePath = type; this.component_.bindToDom(id); @@ -155,7 +160,7 @@ registry.registrar.Console.prototype.changeNavStyle = function() { slashNdx = slashNdx == -1 ? hashToken.length : slashNdx; var regNavlist = goog.dom.getRequiredElement('reg-navlist'); var path = hashToken.substring(0, slashNdx); - var active = regNavlist.querySelector('a[href="/registrar#' + path + '"]'); + var active = regNavlist.querySelector('a[href="#' + path + '"]'); if (goog.isNull(active)) { registry.util.log('Unknown path or path form in changeNavStyle.'); return; diff --git a/java/google/registry/ui/js/registrar/contact_settings.js b/java/google/registry/ui/js/registrar/contact_settings.js index 480155558..d690bc79a 100644 --- a/java/google/registry/ui/js/registrar/contact_settings.js +++ b/java/google/registry/ui/js/registrar/contact_settings.js @@ -14,7 +14,6 @@ goog.provide('registry.registrar.ContactSettings'); -goog.require('goog.Uri'); goog.require('goog.array'); goog.require('goog.dom'); goog.require('goog.dom.TagName'); @@ -38,18 +37,15 @@ goog.forwardDeclare('registry.registrar.Console'); * updating only that field of the Registrar object and not * implementing the create action from edit_item. * @param {!registry.registrar.Console} console - * @param {string} xsrfToken Security token to pass back to the server. + * @param {!registry.Resource} resource the RESTful resource for the registrar. * @constructor * @extends {registry.ResourceComponent} * @final */ -registry.registrar.ContactSettings = function(console, xsrfToken) { +registry.registrar.ContactSettings = function(console, resource) { registry.registrar.ContactSettings.base( - this, 'constructor', - console, - new registry.Resource(new goog.Uri('/registrar-settings'), xsrfToken), - registry.soy.registrar.contacts.contact, - null); + this, 'constructor', console, resource, + registry.soy.registrar.contacts.contact, null); }; goog.inherits(registry.registrar.ContactSettings, registry.ResourceComponent); diff --git a/java/google/registry/ui/js/registrar/contact_us.js b/java/google/registry/ui/js/registrar/contact_us.js index ae7870d0f..0d19c09ad 100644 --- a/java/google/registry/ui/js/registrar/contact_us.js +++ b/java/google/registry/ui/js/registrar/contact_us.js @@ -14,7 +14,6 @@ goog.provide('registry.registrar.ContactUs'); -goog.require('goog.Uri'); goog.require('goog.dom'); goog.require('registry.Resource'); goog.require('registry.ResourceComponent'); @@ -27,19 +26,15 @@ goog.forwardDeclare('registry.registrar.Console'); /** * Contact Us page. * @param {!registry.registrar.Console} console - * @param {string} xsrfToken Security token to pass back to the server. + * @param {!registry.Resource} resource the RESTful resource for the registrar. * @constructor * @extends {registry.ResourceComponent} * @final */ -registry.registrar.ContactUs = function(console, xsrfToken) { +registry.registrar.ContactUs = function(console, resource) { registry.registrar.ContactUs.base( - this, - 'constructor', - console, - new registry.Resource(new goog.Uri('/registrar-settings'), xsrfToken), - registry.soy.registrar.console.contactUs, - null); + this, 'constructor', console, resource, + registry.soy.registrar.console.contactUs, null); }; goog.inherits(registry.registrar.ContactUs, registry.ResourceComponent); diff --git a/java/google/registry/ui/js/registrar/resources.js b/java/google/registry/ui/js/registrar/resources.js index 003636b90..5f188a38d 100644 --- a/java/google/registry/ui/js/registrar/resources.js +++ b/java/google/registry/ui/js/registrar/resources.js @@ -14,7 +14,6 @@ goog.provide('registry.registrar.Resources'); -goog.require('goog.Uri'); goog.require('goog.dom'); goog.require('registry.Resource'); goog.require('registry.ResourceComponent'); @@ -27,19 +26,15 @@ goog.forwardDeclare('registry.registrar.Console'); /** * Resources and billing page. * @param {!registry.registrar.Console} console - * @param {string} xsrfToken Security token to pass back to the server. + * @param {!registry.Resource} resource the RESTful resource for the registrar. * @constructor * @extends {registry.ResourceComponent} * @final */ -registry.registrar.Resources = function(console, xsrfToken) { +registry.registrar.Resources = function(console, resource) { registry.registrar.Resources.base( - this, - 'constructor', - console, - new registry.Resource(new goog.Uri('/registrar-settings'), xsrfToken), - registry.soy.registrar.console.resources, - null); + this, 'constructor', console, resource, + registry.soy.registrar.console.resources, null); }; goog.inherits(registry.registrar.Resources, registry.ResourceComponent); diff --git a/java/google/registry/ui/js/registrar/security_settings.js b/java/google/registry/ui/js/registrar/security_settings.js index 849c8fb9a..1a3f2ed34 100644 --- a/java/google/registry/ui/js/registrar/security_settings.js +++ b/java/google/registry/ui/js/registrar/security_settings.js @@ -14,7 +14,6 @@ goog.provide('registry.registrar.SecuritySettings'); -goog.require('goog.Uri'); goog.require('goog.array'); goog.require('goog.dom'); goog.require('goog.dom.classlist'); @@ -32,19 +31,15 @@ goog.forwardDeclare('registry.registrar.Console'); /** * Security Settings page. * @param {!registry.registrar.Console} console - * @param {string} xsrfToken Security token to pass back to the server. + * @param {!registry.Resource} resource the RESTful resource for the registrar. * @constructor * @extends {registry.ResourceComponent} * @final */ -registry.registrar.SecuritySettings = function(console, xsrfToken) { +registry.registrar.SecuritySettings = function(console, resource) { registry.registrar.SecuritySettings.base( - this, - 'constructor', - console, - new registry.Resource(new goog.Uri('/registrar-settings'), xsrfToken), - registry.soy.registrar.security.settings, - null); + this, 'constructor', console, resource, + registry.soy.registrar.security.settings, null); }; goog.inherits(registry.registrar.SecuritySettings, registry.ResourceComponent); diff --git a/java/google/registry/ui/js/registrar/whois_settings.js b/java/google/registry/ui/js/registrar/whois_settings.js index 7234c5117..68297dbb7 100644 --- a/java/google/registry/ui/js/registrar/whois_settings.js +++ b/java/google/registry/ui/js/registrar/whois_settings.js @@ -14,7 +14,6 @@ goog.provide('registry.registrar.WhoisSettings'); -goog.require('goog.Uri'); goog.require('goog.dom'); goog.require('registry.Resource'); goog.require('registry.ResourceComponent'); @@ -27,19 +26,15 @@ goog.forwardDeclare('registry.registrar.Console'); /** * WHOIS Settings page. * @param {!registry.registrar.Console} console - * @param {string} xsrfToken Cross-site request forgery protection token. + * @param {!registry.Resource} resource the RESTful resource for the registrar. * @constructor * @extends {registry.ResourceComponent} * @final */ -registry.registrar.WhoisSettings = function(console, xsrfToken) { +registry.registrar.WhoisSettings = function(console, resource) { registry.registrar.WhoisSettings.base( - this, - 'constructor', - console, - new registry.Resource(new goog.Uri('/registrar-settings'), xsrfToken), - registry.soy.registrar.whois.settings, - null); + this, 'constructor', console, resource, + registry.soy.registrar.whois.settings, null); }; goog.inherits(registry.registrar.WhoisSettings, registry.ResourceComponent); diff --git a/java/google/registry/ui/js/resource.js b/java/google/registry/ui/js/resource.js index c58bfe5ea..5209672e2 100644 --- a/java/google/registry/ui/js/resource.js +++ b/java/google/registry/ui/js/resource.js @@ -25,13 +25,16 @@ goog.forwardDeclare('goog.Uri'); * Provide a CRUD view of a server resource. * * @param {!goog.Uri} baseUri Target RESTful resource. + * @param {string} id the ID of the target resource * @param {string} xsrfToken Security token to pass back to the server. * @extends {registry.Session} * @constructor */ -registry.Resource = function(baseUri, xsrfToken) { +registry.Resource = function(baseUri, id, xsrfToken) { registry.Resource.base(this, 'constructor', baseUri, xsrfToken, registry.Session.ContentType.JSON); + /** @const @private {string} the ID of the target resource. */ + this.id_ = id; }; goog.inherits(registry.Resource, registry.Session); @@ -73,5 +76,6 @@ registry.Resource.prototype.send_ = var req = {}; req['op'] = opCode; req['args'] = argsObj; + req['id'] = this.id_; this.sendXhrIo(goog.json.serialize(req), callback); }; diff --git a/java/google/registry/ui/server/registrar/AuthenticatedRegistrarAccessor.java b/java/google/registry/ui/server/registrar/AuthenticatedRegistrarAccessor.java new file mode 100644 index 000000000..f1a531d93 --- /dev/null +++ b/java/google/registry/ui/server/registrar/AuthenticatedRegistrarAccessor.java @@ -0,0 +1,202 @@ +// 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.ui.server.registrar; + +import static google.registry.model.ofy.ObjectifyService.ofy; + +import com.google.appengine.api.users.User; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.flogger.FluentLogger; +import google.registry.config.RegistryConfig.Config; +import google.registry.model.registrar.Registrar; +import google.registry.model.registrar.RegistrarContact; +import google.registry.request.HttpException.ForbiddenException; +import google.registry.request.auth.AuthResult; +import google.registry.request.auth.UserAuthInfo; +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +/** + * Allows access only to {@link Registrar}s the current user has access to. + * + *

A user has OWNER role on a Registrar if there exists a {@link RegistrarContact} with + * that user's gaeId and the registrar as a parent. + * + *

An admin has in addition OWNER role on {@link #registryAdminClientId}. + * + *

An admin also has ADMIN role on ALL registrars. + */ +@Immutable +public class AuthenticatedRegistrarAccessor { + + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + /** The role under which access is granted. */ + public enum Role { + OWNER, + ADMIN + } + + AuthResult authResult; + String registryAdminClientId; + + /** + * Gives all roles a user has for a given clientId. + * + *

The order is significant, with "more specific to this user" coming first. + */ + private final ImmutableSetMultimap roleMap; + + @Inject + public AuthenticatedRegistrarAccessor( + AuthResult authResult, @Config("registryAdminClientId") String registryAdminClientId) { + this.authResult = authResult; + this.registryAdminClientId = registryAdminClientId; + this.roleMap = createRoleMap(authResult, registryAdminClientId); + + logger.atInfo().log( + "%s has the following roles: %s", authResult.userIdForLogging(), roleMap); + } + + /** + * A map that gives all roles a user has for a given clientId. + * + *

Throws a {@link ForbiddenException} if the user is not logged in. + * + *

The result is ordered starting from "most specific to this user". + * + *

If you want to load the {@link Registrar} object from these (or any other) {@code clientId}, + * in order to perform actions on behalf of a user, you must use {@link #getRegistrar} which makes + * sure the user has permissions. + * + *

Note that this is an OPTIONAL step in the authentication - only used if we don't have any + * other clue as to the requested {@code clientId}. It is perfectly OK to get a {@code clientId} + * from any other source, as long as the registrar is then loaded using {@link #getRegistrar}. + */ + public ImmutableSetMultimap getAllClientIdWithRoles() { + return roleMap; + } + + /** + * "Guesses" which client ID the user wants from all those they have access to. + * + *

If no such ClientIds exist, throws a ForbiddenException. + * + *

This should be the ClientId "most likely wanted by the user". + * + *

If you want to load the {@link Registrar} object from this (or any other) {@code clientId}, + * in order to perform actions on behalf of a user, you must use {@link #getRegistrar} which makes + * sure the user has permissions. + * + *

Note that this is an OPTIONAL step in the authentication - only used if we don't have any + * other clue as to the requested {@code clientId}. It is perfectly OK to get a {@code clientId} + * from any other source, as long as the registrar is then loaded using {@link #getRegistrar}. + */ + public String guessClientId() { + verifyLoggedIn(); + return getAllClientIdWithRoles().keySet().stream() + .findFirst() + .orElseThrow( + () -> + new ForbiddenException( + String.format( + "%s isn't associated with any registrar", + authResult.userIdForLogging()))); + } + + /** + * Loads a Registrar IFF the user is authorized. + * + *

Throws a {@link ForbiddenException} if the user is not logged in, or not authorized to + * access the requested registrar. + * + * @param clientId ID of the registrar we request + */ + public Registrar getRegistrar(String clientId) { + verifyLoggedIn(); + + ImmutableSet roles = getAllClientIdWithRoles().get(clientId); + + if (roles.isEmpty()) { + throw new ForbiddenException( + String.format( + "%s doesn't have access to registrar %s", + authResult.userIdForLogging(), clientId)); + } + + Registrar registrar = + Registrar.loadByClientId(clientId) + .orElseThrow( + () -> new ForbiddenException(String.format("Registrar %s not found", clientId))); + + if (!clientId.equals(registrar.getClientId())) { + logger.atSevere().log( + "registrarLoader.apply(clientId) returned a Registrar with a different clientId. " + + "Requested: %s, returned: %s.", + clientId, registrar.getClientId()); + throw new ForbiddenException("Internal error - please check logs"); + } + + logger.atInfo().log( + "%s has %s access to registrar %s.", authResult.userIdForLogging(), roles, clientId); + return registrar; + } + + private static ImmutableSetMultimap createRoleMap( + AuthResult authResult, String registryAdminClientId) { + + if (!authResult.userAuthInfo().isPresent()) { + return ImmutableSetMultimap.of(); + } + + UserAuthInfo userAuthInfo = authResult.userAuthInfo().get(); + + boolean isAdmin = userAuthInfo.isUserAdmin(); + User user = userAuthInfo.user(); + + ImmutableSetMultimap.Builder builder = new ImmutableSetMultimap.Builder<>(); + + ofy() + .load() + .type(RegistrarContact.class) + .filter("gaeUserId", user.getUserId()) + .forEach( + contact -> + builder + .put(contact.getParent().getName(), Role.OWNER)); + if (isAdmin && !Strings.isNullOrEmpty(registryAdminClientId)) { + builder + .put(registryAdminClientId, Role.OWNER); + } + + if (isAdmin) { + // Admins have access to all registrars + ofy() + .load() + .type(Registrar.class) + .forEach(registrar -> builder.put(registrar.getClientId(), Role.ADMIN)); + } + + return builder.build(); + } + + private void verifyLoggedIn() { + if (!authResult.userAuthInfo().isPresent()) { + throw new ForbiddenException("Not logged in"); + } + } +} diff --git a/java/google/registry/ui/server/registrar/BUILD b/java/google/registry/ui/server/registrar/BUILD index 2723a052f..e815fa505 100644 --- a/java/google/registry/ui/server/registrar/BUILD +++ b/java/google/registry/ui/server/registrar/BUILD @@ -26,11 +26,13 @@ java_library( "//java/google/registry/util", "//third_party/objectify:objectify-v4_1", "@com_google_appengine_api_1_0_sdk", + "@com_google_auto_value", "@com_google_code_findbugs_jsr305", "@com_google_dagger", "@com_google_flogger", "@com_google_flogger_system_backend", "@com_google_guava", + "@com_google_monitoring_client_metrics", "@com_google_re2j", "@io_bazel_rules_closure//closure/templates", "@javax_inject", diff --git a/java/google/registry/ui/server/registrar/ConsoleUiAction.java b/java/google/registry/ui/server/registrar/ConsoleUiAction.java index e21a33131..be45d805a 100644 --- a/java/google/registry/ui/server/registrar/ConsoleUiAction.java +++ b/java/google/registry/ui/server/registrar/ConsoleUiAction.java @@ -16,14 +16,17 @@ package google.registry.ui.server.registrar; import static com.google.common.net.HttpHeaders.LOCATION; import static com.google.common.net.HttpHeaders.X_FRAME_OPTIONS; -import static google.registry.util.PreconditionsUtils.checkArgumentPresent; +import static google.registry.ui.server.registrar.RegistrarConsoleModule.PARAM_CLIENT_ID; import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; import static javax.servlet.http.HttpServletResponse.SC_MOVED_TEMPORARILY; import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE; +import com.google.appengine.api.users.User; import com.google.appengine.api.users.UserService; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.flogger.FluentLogger; import com.google.common.io.Resources; import com.google.common.net.MediaType; import com.google.template.soy.data.SoyMapData; @@ -32,13 +35,16 @@ import com.google.template.soy.tofu.SoyTofu; import google.registry.config.RegistryConfig.Config; import google.registry.model.registrar.Registrar; import google.registry.request.Action; +import google.registry.request.HttpException.ForbiddenException; +import google.registry.request.Parameter; import google.registry.request.Response; import google.registry.request.auth.Auth; import google.registry.request.auth.AuthResult; -import google.registry.request.auth.UserAuthInfo; import google.registry.security.XsrfTokenManager; import google.registry.ui.server.SoyTemplateUtils; +import google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.Role; import google.registry.ui.soy.registrar.ConsoleSoyInfo; +import java.util.Optional; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; @@ -49,6 +55,8 @@ import javax.servlet.http.HttpServletRequest; ) public final class ConsoleUiAction implements Runnable { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + public static final String PATH = "/registrar"; private static final Supplier TOFU_SUPPLIER = @@ -64,7 +72,8 @@ public final class ConsoleUiAction implements Runnable { @Inject HttpServletRequest req; @Inject Response response; - @Inject SessionUtils sessionUtils; + @Inject RegistrarConsoleMetrics registrarConsoleMetrics; + @Inject AuthenticatedRegistrarAccessor registrarAccessor; @Inject UserService userService; @Inject XsrfTokenManager xsrfTokenManager; @Inject AuthResult authResult; @@ -76,6 +85,7 @@ public final class ConsoleUiAction implements Runnable { @Inject @Config("supportPhoneNumber") String supportPhoneNumber; @Inject @Config("technicalDocsUrl") String technicalDocsUrl; @Inject @Config("registrarConsoleEnabled") boolean enabled; + @Inject @Parameter(PARAM_CLIENT_ID) Optional paramClientId; @Inject ConsoleUiAction() {} @Override @@ -97,7 +107,7 @@ public final class ConsoleUiAction implements Runnable { response.setHeader(LOCATION, location); return; } - UserAuthInfo userAuthInfo = authResult.userAuthInfo().get(); + User user = authResult.userAuthInfo().get().user(); response.setContentType(MediaType.HTML_UTF_8); response.setHeader(X_FRAME_OPTIONS, "SAMEORIGIN"); // Disallow iframing. response.setHeader("X-Ui-Compatible", "IE=edge"); // Ask IE not to be silly. @@ -119,9 +129,29 @@ public final class ConsoleUiAction implements Runnable { .render()); return; } - data.put("username", userAuthInfo.user().getNickname()); + data.put("username", user.getNickname()); data.put("logoutUrl", userService.createLogoutURL(PATH)); - if (!sessionUtils.checkRegistrarConsoleLogin(req, userAuthInfo)) { + data.put("xsrfToken", xsrfTokenManager.generateToken(user.getEmail())); + ImmutableSetMultimap roleMap = registrarAccessor.getAllClientIdWithRoles(); + data.put("allClientIds", roleMap.keySet()); + // We set the initual value to the value that will show if guessClientId throws. + String clientId = ""; + try { + clientId = paramClientId.orElse(registrarAccessor.guessClientId()); + data.put("clientId", clientId); + + // We want to load the registrar even if we won't use it later (even if we remove the + // requireFeeExtension) - to make sure the user indeed has access to the guessed registrar. + // + // Note that not doing so (and just passing the "clientId" as given) isn't a security issue + // since we double check the access to the registrar on any read / update request. We have to + // - since the access might get revoked between the initial page load and the request! (also + // because the requests come from the browser, and can easily be faked) + Registrar registrar = registrarAccessor.getRegistrar(clientId); + data.put("requireFeeExtension", registrar.getPremiumPriceAckRequired()); + } catch (ForbiddenException e) { + logger.atWarning().withCause(e).log( + "User %s doesn't have access to registrar console.", authResult.userIdForLogging()); response.setStatus(SC_FORBIDDEN); response.setPayload( TOFU_SUPPLIER.get() @@ -129,15 +159,14 @@ public final class ConsoleUiAction implements Runnable { .setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get()) .setData(data) .render()); + registrarConsoleMetrics.registerConsoleRequest( + clientId, paramClientId.isPresent(), roleMap.get(clientId), "FORBIDDEN"); return; + } catch (Exception e) { + registrarConsoleMetrics.registerConsoleRequest( + clientId, paramClientId.isPresent(), roleMap.get(clientId), "UNEXPECTED ERROR"); + throw e; } - String clientId = sessionUtils.getRegistrarClientId(req); - Registrar registrar = - checkArgumentPresent( - Registrar.loadByClientIdCached(clientId), "Registrar %s does not exist", clientId); - data.put("xsrfToken", xsrfTokenManager.generateToken(userAuthInfo.user().getEmail())); - data.put("clientId", clientId); - data.put("requireFeeExtension", registrar.getPremiumPriceAckRequired()); String payload = TOFU_SUPPLIER.get() .newRenderer(ConsoleSoyInfo.MAIN) @@ -145,5 +174,7 @@ public final class ConsoleUiAction implements Runnable { .setData(data) .render(); response.setPayload(payload); + registrarConsoleMetrics.registerConsoleRequest( + clientId, paramClientId.isPresent(), roleMap.get(clientId), "SUCCESS"); } } diff --git a/java/google/registry/ui/server/registrar/RegistrarConsoleMetrics.java b/java/google/registry/ui/server/registrar/RegistrarConsoleMetrics.java new file mode 100644 index 000000000..338a595e4 --- /dev/null +++ b/java/google/registry/ui/server/registrar/RegistrarConsoleMetrics.java @@ -0,0 +1,69 @@ +// 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.ui.server.registrar; + +import com.google.common.collect.ImmutableSet; +import com.google.monitoring.metrics.IncrementableMetric; +import com.google.monitoring.metrics.LabelDescriptor; +import com.google.monitoring.metrics.MetricRegistryImpl; +import google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.Role; +import javax.inject.Inject; + +final class RegistrarConsoleMetrics { + + private static final ImmutableSet CONSOLE_LABEL_DESCRIPTORS = + ImmutableSet.of( + LabelDescriptor.create("clientId", "target registrar client ID"), + LabelDescriptor.create("explicitClientId", "whether the client ID is set explicitly"), + LabelDescriptor.create("role", "Role[s] of the user making the request"), + LabelDescriptor.create("status", "whether the request is successful")); + + static final IncrementableMetric consoleRequestMetric = + MetricRegistryImpl.getDefault() + .newIncrementableMetric( + "/console/registrar/console_requests", + "Count of /registrar requests", + "count", + CONSOLE_LABEL_DESCRIPTORS); + + private static final ImmutableSet SETTINGS_LABEL_DESCRIPTORS = + ImmutableSet.of( + LabelDescriptor.create("clientId", "target registrar client ID"), + LabelDescriptor.create("action", "action performed"), + LabelDescriptor.create("role", "Role[s] of the user making the request"), + LabelDescriptor.create("status", "whether the request is successful")); + + static final IncrementableMetric settingsRequestMetric = + MetricRegistryImpl.getDefault() + .newIncrementableMetric( + "/console/registrar/setting_requests", + "Count of /registrar-settings requests", + "count", + SETTINGS_LABEL_DESCRIPTORS); + + @Inject + public RegistrarConsoleMetrics() {} + + void registerConsoleRequest( + String clientId, boolean explicitClientId, ImmutableSet roles, String status) { + consoleRequestMetric.increment( + clientId, String.valueOf(explicitClientId), String.valueOf(roles), status); + } + + void registerSettingsRequest( + String clientId, String action, ImmutableSet roles, String status) { + settingsRequestMetric.increment(clientId, action, String.valueOf(roles), status); + } +} diff --git a/java/google/registry/ui/server/registrar/RegistrarConsoleModule.java b/java/google/registry/ui/server/registrar/RegistrarConsoleModule.java new file mode 100644 index 000000000..9026c5ce5 --- /dev/null +++ b/java/google/registry/ui/server/registrar/RegistrarConsoleModule.java @@ -0,0 +1,37 @@ +// 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.ui.server.registrar; + + +import static google.registry.request.RequestParameters.extractOptionalParameter; + +import dagger.Module; +import dagger.Provides; +import google.registry.request.Parameter; +import java.util.Optional; +import javax.servlet.http.HttpServletRequest; + +/** Dagger module for the Registrar Console parameters. */ +@Module +public final class RegistrarConsoleModule { + + static final String PARAM_CLIENT_ID = "clientId"; + + @Provides + @Parameter(PARAM_CLIENT_ID) + static Optional provideClientId(HttpServletRequest req) { + return extractOptionalParameter(req, PARAM_CLIENT_ID); + } +} diff --git a/java/google/registry/ui/server/registrar/RegistrarSettingsAction.java b/java/google/registry/ui/server/registrar/RegistrarSettingsAction.java index 83d98cb00..412d03fa1 100644 --- a/java/google/registry/ui/server/registrar/RegistrarSettingsAction.java +++ b/java/google/registry/ui/server/registrar/RegistrarSettingsAction.java @@ -21,6 +21,8 @@ import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.security.JsonResponseHelper.Status.ERROR; import static google.registry.security.JsonResponseHelper.Status.SUCCESS; +import com.google.auto.value.AutoValue; +import com.google.common.base.Ascii; import com.google.common.base.Strings; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; @@ -30,12 +32,13 @@ import com.google.common.collect.Multimap; import com.google.common.collect.Streams; import com.google.common.flogger.FluentLogger; import google.registry.config.RegistryConfig.Config; +import google.registry.config.RegistryEnvironment; import google.registry.model.registrar.Registrar; import google.registry.model.registrar.RegistrarContact; -import google.registry.model.registrar.RegistrarContact.Builder; import google.registry.model.registrar.RegistrarContact.Type; import google.registry.request.Action; import google.registry.request.HttpException.BadRequestException; +import google.registry.request.HttpException.ForbiddenException; import google.registry.request.JsonActionRunner; import google.registry.request.auth.Auth; import google.registry.request.auth.AuthResult; @@ -43,6 +46,7 @@ import google.registry.security.JsonResponseHelper; import google.registry.ui.forms.FormException; import google.registry.ui.forms.FormFieldException; import google.registry.ui.server.RegistrarFormFields; +import google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.Role; import google.registry.util.AppEngineServiceUtils; import google.registry.util.CollectionUtils; import google.registry.util.DiffUtils; @@ -54,7 +58,6 @@ import java.util.Optional; import java.util.Set; import java.util.function.Predicate; import javax.inject.Inject; -import javax.servlet.http.HttpServletRequest; import org.joda.time.DateTime; /** @@ -74,13 +77,16 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA static final String OP_PARAM = "op"; static final String ARGS_PARAM = "args"; + static final String ID_PARAM = "id"; - @Inject HttpServletRequest request; @Inject JsonActionRunner jsonActionRunner; @Inject AppEngineServiceUtils appEngineServiceUtils; - @Inject AuthResult authResult; + @Inject RegistrarConsoleMetrics registrarConsoleMetrics; @Inject SendEmailUtils sendEmailUtils; - @Inject SessionUtils sessionUtils; + @Inject AuthenticatedRegistrarAccessor registrarAccessor; + @Inject AuthResult authResult; + @Inject RegistryEnvironment registryEnvironment; + @Inject @Config("registrarChangesNotificationEmailAddresses") ImmutableList registrarChangesNotificationEmailAddresses; @Inject RegistrarSettingsAction() {} @@ -99,46 +105,73 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA throw new BadRequestException("Malformed JSON"); } - Registrar initialRegistrar = sessionUtils.getRegistrarForAuthResult(request, authResult); + String clientId = (String) input.get(ID_PARAM); + if (Strings.isNullOrEmpty(clientId)) { + throw new BadRequestException(String.format("Missing key for resource ID: %s", ID_PARAM)); + } + // Process the operation. Though originally derived from a CRUD // handler, registrar-settings really only supports read and update. String op = Optional.ofNullable((String) input.get(OP_PARAM)).orElse("read"); @SuppressWarnings("unchecked") Map args = (Map) Optional.ofNullable(input.get(ARGS_PARAM)).orElse(ImmutableMap.of()); - logger.atInfo().log( - "Received request '%s' on registrar '%s' with args %s", - op, initialRegistrar.getClientId(), args); + + logger.atInfo().log("Received request '%s' on registrar '%s' with args %s", op, clientId, args); + String status = "SUCCESS"; try { switch (op) { case "update": - return update(args, initialRegistrar.getClientId()); + return update(args, clientId).toJsonResponse(); case "read": - return JsonResponseHelper.create(SUCCESS, "Success", initialRegistrar.toJsonMap()); + return read(clientId).toJsonResponse(); default: - return JsonResponseHelper.create(ERROR, "Unknown or unsupported operation: " + op); + throw new IllegalArgumentException("Unknown or unsupported operation: " + op); } - } catch (FormFieldException e) { + } catch (Throwable e) { logger.atWarning().withCause(e).log( - "Failed to perform operation '%s' on registrar '%s' for args %s", - op, initialRegistrar.getClientId(), args); - return JsonResponseHelper.createFormFieldError(e.getMessage(), e.getFieldName()); - } catch (FormException e) { - logger.atWarning().withCause(e).log( - "Failed to perform operation '%s' on registrar '%s' for args %s", - op, initialRegistrar.getClientId(), args); - return JsonResponseHelper.create(ERROR, e.getMessage()); + "Failed to perform operation '%s' on registrar '%s' for args %s", op, clientId, args); + status = "ERROR: " + e.getClass().getSimpleName(); + if (e instanceof FormFieldException) { + FormFieldException formFieldException = (FormFieldException) e; + return JsonResponseHelper.createFormFieldError( + formFieldException.getMessage(), formFieldException.getFieldName()); + } + return JsonResponseHelper.create( + ERROR, Optional.ofNullable(e.getMessage()).orElse("Unspecified error")); + } finally { + registrarConsoleMetrics.registerSettingsRequest( + clientId, op, registrarAccessor.getAllClientIdWithRoles().get(clientId), status); } } - Map update(final Map args, String clientId) { + @AutoValue + abstract static class RegistrarResult { + abstract String message(); + + abstract Registrar registrar(); + + Map toJsonResponse() { + return JsonResponseHelper.create(SUCCESS, message(), registrar().toJsonMap()); + } + + static RegistrarResult create(String message, Registrar registrar) { + return new AutoValue_RegistrarSettingsAction_RegistrarResult(message, registrar); + } + } + + private RegistrarResult read(String clientId) { + return RegistrarResult.create("Success", registrarAccessor.getRegistrar(clientId)); + } + + private RegistrarResult update(final Map args, String clientId) { return ofy() .transact( () -> { - // We load the registrar here rather than use the initialRegistrar above - to make + // 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 // guaranteed to not change before we update it. - Registrar registrar = Registrar.loadByClientId(clientId).get(); + Registrar registrar = registrarAccessor.getRegistrar(clientId); // Verify that the registrar hasn't been changed. // To do that - we find the latest update time (or null if the registrar has been // deleted) and compare to the update time from the args. The update time in the args @@ -152,8 +185,8 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA "registrar changed since reading the data! " + " Last updated at %s, but args data last updated at %s", latest, latestFromArgs); - return JsonResponseHelper.create( - ERROR, "registrar has been changed by someone else. Please reload and retry."); + throw new IllegalStateException( + "registrar has been changed by someone else. Please reload and retry."); } // Keep the current contacts so we can later check that no required contact was @@ -162,7 +195,8 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA // Update the registrar from the request. Registrar.Builder builder = registrar.asBuilder(); - changeRegistrarFields(registrar, builder, args); + Set roles = registrarAccessor.getAllClientIdWithRoles().get(clientId); + changeRegistrarFields(registrar, roles, builder, args); // read the contacts from the request. ImmutableSet updatedContacts = readContacts(registrar, args); @@ -185,8 +219,7 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA // Email and return update. sendExternalUpdatesIfNecessary( registrar, contacts, updatedRegistrar, updatedContacts); - return JsonResponseHelper.create( - SUCCESS, "Saved " + clientId, updatedRegistrar.toJsonMap()); + return RegistrarResult.create("Saved " + clientId, updatedRegistrar); }); } @@ -203,11 +236,12 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA return result; } - /** - * Updates a registrar builder with the supplied args from the http request; - */ + /** Updates a registrar builder with the supplied args from the http request; */ public static void changeRegistrarFields( - Registrar existingRegistrarObj, Registrar.Builder builder, Map args) { + Registrar existingRegistrarObj, + Set roles, + Registrar.Builder builder, + Map args) { // BILLING RegistrarFormFields.PREMIUM_PRICE_ACK_REQUIRED @@ -247,6 +281,18 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA .ifPresent( certificate -> builder.setFailoverClientCertificate(certificate, ofy().getTransactionTime())); + + // Update allowed TLDs only when it is modified + Set updatedAllowedTlds = + RegistrarFormFields.ALLOWED_TLDS_FIELD.extractUntyped(args).orElse(ImmutableSet.of()); + if (!updatedAllowedTlds.equals(existingRegistrarObj.getAllowedTlds())) { + // Only admin is allowed to update allowed TLDs + if (roles.contains(Role.ADMIN)) { + builder.setAllowedTlds(updatedAllowedTlds); + } else { + throw new ForbiddenException("Only admin can update allowed TLDs."); + } + } } /** Reads the contacts from the supplied args. */ @@ -254,7 +300,8 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA Registrar registrar, Map args) { ImmutableSet.Builder contacts = new ImmutableSet.Builder<>(); - Optional> builders = RegistrarFormFields.CONTACTS_FIELD.extractUntyped(args); + Optional> builders = + RegistrarFormFields.CONTACTS_FIELD.extractUntyped(args); if (builders.isPresent()) { builders.get().forEach(c -> contacts.add(c.setParent(registrar).build())); } @@ -371,13 +418,20 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA return; } enqueueRegistrarSheetSync(appEngineServiceUtils.getCurrentVersionHostname("backend")); - if (!registrarChangesNotificationEmailAddresses.isEmpty()) { - sendEmailUtils.sendEmail( - registrarChangesNotificationEmailAddresses, - String.format("Registrar %s updated", existingRegistrar.getRegistrarName()), - "The following changes were made to the registrar:\n" - + DiffUtils.prettyPrintDiffedMap(diffs, null)); - } + String environment = Ascii.toLowerCase(String.valueOf(registryEnvironment)); + sendEmailUtils.sendEmail( + registrarChangesNotificationEmailAddresses, + String.format( + "Registrar %s (%s) updated in %s", + existingRegistrar.getRegistrarName(), + existingRegistrar.getClientId(), + environment), + String.format( + "The following changes were made on %s to the registrar %s by %s:\n\n%s", + environment, + existingRegistrar.getClientId(), + authResult.userIdForLogging(), + DiffUtils.prettyPrintDiffedMap(diffs, null))); } /** Thrown when a set of contacts doesn't meet certain constraints. */ diff --git a/java/google/registry/ui/server/registrar/SendEmailUtils.java b/java/google/registry/ui/server/registrar/SendEmailUtils.java index 3de2523c5..90fcc5469 100644 --- a/java/google/registry/ui/server/registrar/SendEmailUtils.java +++ b/java/google/registry/ui/server/registrar/SendEmailUtils.java @@ -21,7 +21,6 @@ import com.google.common.base.Joiner; import com.google.common.collect.Streams; import com.google.common.flogger.FluentLogger; import google.registry.config.RegistryConfig.Config; -import google.registry.util.NonFinalForTesting; import google.registry.util.SendEmailService; import java.util.List; import java.util.Objects; @@ -39,18 +38,18 @@ public class SendEmailUtils { private final String gSuiteOutgoingEmailAddress; private final String gSuiteOutoingEmailDisplayName; + private final SendEmailService emailService; @Inject public SendEmailUtils( @Config("gSuiteOutgoingEmailAddress") String gSuiteOutgoingEmailAddress, - @Config("gSuiteOutoingEmailDisplayName") String gSuiteOutoingEmailDisplayName) { + @Config("gSuiteOutoingEmailDisplayName") String gSuiteOutoingEmailDisplayName, + SendEmailService emailService) { this.gSuiteOutgoingEmailAddress = gSuiteOutgoingEmailAddress; this.gSuiteOutoingEmailDisplayName = gSuiteOutoingEmailDisplayName; + this.emailService = emailService; } - @NonFinalForTesting - private static SendEmailService emailService = new SendEmailService(); - /** * Sends an email from Nomulus to the specified recipient(s). Returns true iff sending was * successful. diff --git a/java/google/registry/ui/server/registrar/SessionUtils.java b/java/google/registry/ui/server/registrar/SessionUtils.java deleted file mode 100644 index 38eeecc5d..000000000 --- a/java/google/registry/ui/server/registrar/SessionUtils.java +++ /dev/null @@ -1,209 +0,0 @@ -// 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.ui.server.registrar; - -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.base.Verify.verify; -import static google.registry.model.ofy.ObjectifyService.ofy; -import static google.registry.util.PreconditionsUtils.checkArgumentPresent; - -import com.google.appengine.api.users.User; -import com.google.common.base.Strings; -import com.google.common.flogger.FluentLogger; -import com.googlecode.objectify.Key; -import google.registry.config.RegistryConfig.Config; -import google.registry.model.registrar.Registrar; -import google.registry.model.registrar.RegistrarContact; -import google.registry.request.HttpException.ForbiddenException; -import google.registry.request.auth.AuthResult; -import google.registry.request.auth.UserAuthInfo; -import java.util.Optional; -import javax.annotation.CheckReturnValue; -import javax.annotation.concurrent.Immutable; -import javax.inject.Inject; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; - -/** HTTP session management helper class. */ -@Immutable -public class SessionUtils { - - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - - private static final String CLIENT_ID_ATTRIBUTE = "clientId"; - - @Inject - @Config("registryAdminClientId") - String registryAdminClientId; - - @Inject - public SessionUtils() {} - - /** - * Checks that the authentication result indicates a user that has access to the registrar - * console, then gets the associated registrar. - * - *

Throws a {@link ForbiddenException} if the user is not logged in, or not authorized to use - * the registrar console. - */ - @CheckReturnValue - Registrar getRegistrarForAuthResult(HttpServletRequest request, AuthResult authResult) { - if (!authResult.userAuthInfo().isPresent()) { - throw new ForbiddenException("Not logged in"); - } - if (!checkRegistrarConsoleLogin(request, authResult.userAuthInfo().get())) { - throw new ForbiddenException("Not authorized to access Registrar Console"); - } - String clientId = getRegistrarClientId(request); - return checkArgumentPresent( - Registrar.loadByClientId(clientId), - "Registrar %s not found", - clientId); - } - - /** - * Checks that the specified user has access to the Registrar Console. - * - *

This routine will first check the HTTP session (creating one if it doesn't exist) for the - * {@code clientId} attribute: - * - *

    - *
  • If it does not exist, then we will attempt to guess the {@link Registrar} with which the - * user is associated. The {@code clientId} of the first matching {@code Registrar} will - * then be stored to the HTTP session. - *
  • If it does exist, then we'll fetch the Registrar from Datastore to make sure access - * wasn't revoked. - *
- * - *

Note: You must ensure the user has logged in before calling this method. - * - * @return {@code false} if user does not have access, in which case the caller should write an - * error response and abort the request. - */ - @CheckReturnValue - public boolean checkRegistrarConsoleLogin(HttpServletRequest req, UserAuthInfo userAuthInfo) { - checkState(userAuthInfo != null, "No logged in user found"); - User user = userAuthInfo.user(); - HttpSession session = req.getSession(); - String clientId = (String) session.getAttribute(CLIENT_ID_ATTRIBUTE); - - // Use the clientId if it exists - if (clientId != null) { - if (!hasAccessToRegistrar(clientId, user.getUserId(), userAuthInfo.isUserAdmin())) { - logger.atInfo().log("Registrar Console access revoked: %s", clientId); - session.invalidate(); - return false; - } - logger.atInfo().log( - "Associating user %s with given registrar %s.", user.getUserId(), clientId); - return true; - } - - // The clientId was null, so let's try and find a registrar this user is associated with - Optional registrar = findRegistrarForUser(user.getUserId()); - if (registrar.isPresent()) { - verify(isInAllowedContacts(registrar.get(), user.getUserId())); - logger.atInfo().log( - "Associating user %s with found registrar %s.", - user.getUserId(), registrar.get().getClientId()); - session.setAttribute(CLIENT_ID_ATTRIBUTE, registrar.get().getClientId()); - return true; - } - - // We couldn't guess the registrar, but maybe the user is an admin and we can use the - // registryAdminClientId - if (userAuthInfo.isUserAdmin()) { - if (Strings.isNullOrEmpty(registryAdminClientId)) { - logger.atInfo().log( - "Cannot associate admin user %s with configured client Id." - + " ClientId is null or empty.", - user.getUserId()); - return false; - } - if (!Registrar.loadByClientIdCached(registryAdminClientId).isPresent()) { - logger.atInfo().log( - "Cannot associate admin user %s with configured client Id %s." - + " Registrar does not exist.", - user.getUserId(), registryAdminClientId); - return false; - } - logger.atInfo().log( - "User %s is an admin with no associated registrar." - + " Automatically associating the user with configured client Id %s.", - user.getUserId(), registryAdminClientId); - session.setAttribute(CLIENT_ID_ATTRIBUTE, registryAdminClientId); - return true; - } - - // We couldn't find any relevant clientId - logger.atInfo().log("User not associated with any Registrar: %s", user.getUserId()); - return false; - } - - /** - * Returns {@link Registrar} clientId associated with HTTP session. - * - * @throws IllegalStateException if you forgot to call {@link #checkRegistrarConsoleLogin}. - */ - @CheckReturnValue - public String getRegistrarClientId(HttpServletRequest req) { - String clientId = (String) req.getSession().getAttribute(CLIENT_ID_ATTRIBUTE); - checkState(clientId != null, "You forgot to call checkRegistrarConsoleLogin()"); - return clientId; - } - - /** Returns first {@link Registrar} that {@code gaeUserId} is authorized to administer. */ - private static Optional findRegistrarForUser(String gaeUserId) { - RegistrarContact contact = - ofy().load().type(RegistrarContact.class).filter("gaeUserId", gaeUserId).first().now(); - if (contact == null) { - return Optional.empty(); - } - String registrarClientId = contact.getParent().getName(); - Optional result = Registrar.loadByClientIdCached(registrarClientId); - if (!result.isPresent()) { - logger.atSevere().log( - "A contact record exists for non-existent registrar: %s.", Key.create(contact)); - } - return result; - } - - /** @see #isInAllowedContacts(Registrar, String) */ - boolean hasAccessToRegistrar(String clientId, String gaeUserId, boolean isAdmin) { - Optional registrar = Registrar.loadByClientIdCached(clientId); - if (!registrar.isPresent()) { - logger.atWarning().log("Registrar '%s' disappeared from Datastore!", clientId); - return false; - } - if (isAdmin && clientId.equals(registryAdminClientId)) { - return true; - } - return isInAllowedContacts(registrar.get(), gaeUserId); - } - - /** - * Returns {@code true} if {@code gaeUserId} is listed in contacts with access to the registrar. - * - *

Each registrar contact can either have getGaeUserId equals null or the user's gaeUserId. - * Null means the contact doesn't have access to the registrar console. None-null means the - * contact has access. - */ - private static boolean isInAllowedContacts(Registrar registrar, final String gaeUserId) { - return registrar - .getContacts() - .stream() - .anyMatch(contact -> gaeUserId.equals(contact.getGaeUserId())); - } -} diff --git a/java/google/registry/ui/soy/registrar/Console.soy b/java/google/registry/ui/soy/registrar/Console.soy index 860aba56a..65ef43d21 100644 --- a/java/google/registry/ui/soy/registrar/Console.soy +++ b/java/google/registry/ui/soy/registrar/Console.soy @@ -23,6 +23,7 @@ {template .main} {@param xsrfToken: string} /** Security token. */ {@param clientId: string} /** Registrar client identifier. */ + {@param allClientIds: list} /** All registrar client identifiers for the user. */ {@param username: string} /** Arbitrary username to display. */ {@param logoutUrl: string} /** Generated URL for logging out of Google. */ {@param productName: string} /** Name to display for this software product. */ @@ -75,24 +76,38 @@ /** Sidebar nav. Ids on each elt for testing only. */ {template .navbar_ visibility="private"} + {@param clientId: string} /** Registrar client identifier. */ + {@param allClientIds: list} +

{/template} @@ -130,25 +145,41 @@ {@param logoutUrl: string} /** Generated URL for logging out of Google. */ {@param logoFilename: string} {@param productName: string} + {@param? clientId: string} {call registry.soy.console.header} {param app: 'registrar' /} - {param subtitle: 'Please Login' /} + {param subtitle: 'Not Authorized' /} {/call}
{$productName}

You need permission

-

- The account you are logged in as is not associated with {$productName}. - Please contact your customer service representative or - switch to an account associated with {$productName}. + {if isNonnull($clientId)} // A clientId was given - but we don't have access to it +

+ The account you are logged in as is not associated with the registrar + {sp}{$clientId}. Please contact your customer service representative or + switch to an account associated with {$clientId}. Alternatively, click + {sp}here to find a registrar associated with your + account. + {else} +

+ The account you are logged in as is not associated with {$productName}. + Please contact your customer service representative or + switch to an account associated with {$productName}. + {/if}

You are signed in as {$username}.

Logout and switch to another account + {if isNonnull($clientId)} // A clientId was given - but we don't have access to it + {sp} + Find registrar associated with your account + {/if}
{/template} diff --git a/java/google/registry/util/RandomStringGenerator.java b/java/google/registry/util/RandomStringGenerator.java index 625448d4e..db75519ea 100644 --- a/java/google/registry/util/RandomStringGenerator.java +++ b/java/google/registry/util/RandomStringGenerator.java @@ -17,16 +17,13 @@ package google.registry.util; import static com.google.common.base.Preconditions.checkArgument; import java.security.SecureRandom; -import javax.inject.Inject; -import javax.inject.Named; /** Random string generator. */ public class RandomStringGenerator extends StringGenerator { private final SecureRandom random; - @Inject - public RandomStringGenerator(@Named("alphabetBase64") String alphabet, SecureRandom random) { + public RandomStringGenerator(String alphabet, SecureRandom random) { super(alphabet); this.random = random; } diff --git a/java/google/registry/util/SendEmailService.java b/java/google/registry/util/SendEmailService.java index 0a73d315f..86c2a46d8 100644 --- a/java/google/registry/util/SendEmailService.java +++ b/java/google/registry/util/SendEmailService.java @@ -15,6 +15,8 @@ package google.registry.util; import java.util.Properties; +import javax.inject.Inject; +import javax.inject.Singleton; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Session; @@ -22,8 +24,12 @@ import javax.mail.Transport; import javax.mail.internet.MimeMessage; /** Wrapper around javax.mail's Transport.send that can be mocked for testing purposes. */ +@Singleton public class SendEmailService { + @Inject + SendEmailService() {}; + /** Returns a new MimeMessage using default App Engine transport settings. */ public Message createMessage() { return new MimeMessage(Session.getDefaultInstance(new Properties(), null)); diff --git a/java/google/registry/util/StringGenerator.java b/java/google/registry/util/StringGenerator.java index 9c2aa25bd..bb3be99e8 100644 --- a/java/google/registry/util/StringGenerator.java +++ b/java/google/registry/util/StringGenerator.java @@ -18,10 +18,11 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Strings.isNullOrEmpty; import com.google.common.collect.ImmutableList; +import java.io.Serializable; import java.util.Collection; /** String generator. */ -public abstract class StringGenerator { +public abstract class StringGenerator implements Serializable { public static final int DEFAULT_PASSWORD_LENGTH = 16; diff --git a/java/google/registry/util/UrlFetchUtils.java b/java/google/registry/util/UrlFetchUtils.java index 29f8a2575..bf30b0174 100644 --- a/java/google/registry/util/UrlFetchUtils.java +++ b/java/google/registry/util/UrlFetchUtils.java @@ -35,9 +35,6 @@ import java.util.Random; /** Helper methods for the App Engine URL fetch service. */ public final class UrlFetchUtils { - @NonFinalForTesting - private static Random random = new Random(); - /** Returns value of first header matching {@code name}. */ public static Optional getHeaderFirst(HTTPResponse rsp, String name) { return getHeaderFirstInternal(rsp.getHeadersUncombined(), name); @@ -63,11 +60,16 @@ public final class UrlFetchUtils { * *

This is equivalent to running the command: {@code curl -F fieldName=@payload.txt URL} * - * @see RFC2388 - Returning Values from Forms + * @see RFC2388 - Returning Values from Forms */ public static void setPayloadMultipart( - HTTPRequest request, String name, String filename, MediaType contentType, String data) { - String boundary = createMultipartBoundary(); + HTTPRequest request, + String name, + String filename, + MediaType contentType, + String data, + Random random) { + String boundary = createMultipartBoundary(random); checkState( !data.contains(boundary), "Multipart data contains autogenerated boundary: %s", boundary); @@ -87,7 +89,7 @@ public final class UrlFetchUtils { request.setPayload(payload); } - private static String createMultipartBoundary() { + private static String createMultipartBoundary(Random random) { // Generate 192 random bits (24 bytes) to produce 192/log_2(64) = 192/6 = 32 base64 digits. byte[] rand = new byte[24]; random.nextBytes(rand); diff --git a/javatests/google/registry/beam/invoicing/InvoicingPipelineTest.java b/javatests/google/registry/beam/invoicing/InvoicingPipelineTest.java index 3e6ead99f..538dae227 100644 --- a/javatests/google/registry/beam/invoicing/InvoicingPipelineTest.java +++ b/javatests/google/registry/beam/invoicing/InvoicingPipelineTest.java @@ -177,9 +177,7 @@ public class InvoicingPipelineTest { "2017-10-01,2022-09-30,234,70.75,JPY,10125,1,PURCHASE,theRegistrar - hello,1," + "CREATE | TLD: hello | TERM: 5-year,70.75,JPY,", "2017-10-01,2018-09-30,456,20.50,USD,10125,1,PURCHASE,bestdomains - test,1," - + "RENEW | TLD: test | TERM: 1-year,20.50,USD,116688", - "2017-10-01,2018-09-30,789,0.00,USD,10125,1,PURCHASE,anotherRegistrar - test,1," - + "CREATE | TLD: test | TERM: 1-year,0.00,USD,"); + + "RENEW | TLD: test | TERM: 1-year,20.50,USD,116688"); } @Test diff --git a/javatests/google/registry/flows/BUILD b/javatests/google/registry/flows/BUILD index f23f40b85..083dfd71b 100644 --- a/javatests/google/registry/flows/BUILD +++ b/javatests/google/registry/flows/BUILD @@ -47,6 +47,7 @@ java_library( "@com_google_flogger_system_backend", "@com_google_guava", "@com_google_guava_testlib", + "@com_google_monitoring_client_contrib", "@com_google_monitoring_client_metrics", "@com_google_re2j", "@com_google_truth", diff --git a/javatests/google/registry/flows/EppCommitLogsTest.java b/javatests/google/registry/flows/EppCommitLogsTest.java index aa07fa2ff..0ed3dddd0 100644 --- a/javatests/google/registry/flows/EppCommitLogsTest.java +++ b/javatests/google/registry/flows/EppCommitLogsTest.java @@ -70,7 +70,7 @@ public class EppCommitLogsTest extends ShardableTestCase { sessionMetadata.setClientId("TheRegistrar"); DaggerEppTestComponent.builder() .fakesAndMocksModule( - FakesAndMocksModule.create(clock, EppMetric.builderForRequest("request-id-1", clock))) + FakesAndMocksModule.create(clock, EppMetric.builderForRequest(clock))) .build() .startRequest() .flowComponentBuilder() diff --git a/javatests/google/registry/flows/EppControllerTest.java b/javatests/google/registry/flows/EppControllerTest.java index 7c062e5df..1e882aca6 100644 --- a/javatests/google/registry/flows/EppControllerTest.java +++ b/javatests/google/registry/flows/EppControllerTest.java @@ -16,7 +16,6 @@ package google.registry.flows; import static com.google.common.io.BaseEncoding.base64; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; import static google.registry.flows.EppXmlTransformer.marshal; import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.LogsSubject.assertAboutLogs; @@ -40,7 +39,6 @@ import google.registry.model.eppoutput.EppOutput; import google.registry.model.eppoutput.EppResponse; import google.registry.model.eppoutput.Result; import google.registry.model.eppoutput.Result.Code; -import google.registry.monitoring.whitebox.BigQueryMetricsEnqueuer; import google.registry.monitoring.whitebox.EppMetric; import google.registry.testing.AppEngineRule; import google.registry.testing.FakeClock; @@ -60,7 +58,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import org.mockito.ArgumentCaptor; import org.mockito.Matchers; import org.mockito.Mock; @@ -74,7 +71,6 @@ public class EppControllerTest extends ShardableTestCase { @Mock SessionMetadata sessionMetadata; @Mock TransportCredentials transportCredentials; @Mock EppMetrics eppMetrics; - @Mock BigQueryMetricsEnqueuer bigQueryMetricsEnqueuer; @Mock FlowComponent.Builder flowComponentBuilder; @Mock FlowComponent flowComponent; @Mock FlowRunner flowRunner; @@ -111,9 +107,8 @@ public class EppControllerTest extends ShardableTestCase { when(result.getCode()).thenReturn(Code.SUCCESS_WITH_NO_MESSAGES); eppController = new EppController(); - eppController.eppMetricBuilder = EppMetric.builderForRequest("request-id-1", clock); + eppController.eppMetricBuilder = EppMetric.builderForRequest(clock); when(flowRunner.run(eppController.eppMetricBuilder)).thenReturn(eppOutput); - eppController.bigQueryMetricsEnqueuer = bigQueryMetricsEnqueuer; eppController.flowComponentBuilder = flowComponentBuilder; eppController.eppMetrics = eppMetrics; eppController.serverTridProvider = new FakeServerTridProvider(); @@ -132,49 +127,6 @@ public class EppControllerTest extends ShardableTestCase { ValidationMode.STRICT); } - @Test - public void testHandleEppCommand_unmarshallableData_exportsMetric() { - eppController.handleEppCommand( - sessionMetadata, - transportCredentials, - EppRequestSource.UNIT_TEST, - false, - false, - new byte[0]); - - ArgumentCaptor metricCaptor = ArgumentCaptor.forClass(EppMetric.class); - verify(bigQueryMetricsEnqueuer).export(metricCaptor.capture()); - EppMetric metric = metricCaptor.getValue(); - assertThat(metric.getRequestId()).isEqualTo("request-id-1"); - assertThat(metric.getStartTimestamp()).isEqualTo(START_TIME); - assertThat(metric.getEndTimestamp()).isEqualTo(clock.nowUtc()); - assertThat(metric.getClientId()).hasValue("some-client"); - assertThat(metric.getPrivilegeLevel()).hasValue("NORMAL"); - assertThat(metric.getStatus()).hasValue(Code.SYNTAX_ERROR); - } - - @Test - public void testHandleEppCommand_regularEppCommand_exportsBigQueryMetric() { - eppController.handleEppCommand( - sessionMetadata, - transportCredentials, - EppRequestSource.UNIT_TEST, - false, - true, - domainCreateXml.getBytes(UTF_8)); - - ArgumentCaptor metricCaptor = ArgumentCaptor.forClass(EppMetric.class); - verify(bigQueryMetricsEnqueuer).export(metricCaptor.capture()); - EppMetric metric = metricCaptor.getValue(); - assertThat(metric.getRequestId()).isEqualTo("request-id-1"); - assertThat(metric.getStartTimestamp()).isEqualTo(START_TIME); - assertThat(metric.getEndTimestamp()).isEqualTo(clock.nowUtc()); - assertThat(metric.getClientId()).hasValue("some-client"); - assertThat(metric.getPrivilegeLevel()).hasValue("SUPERUSER"); - assertThat(metric.getStatus()).hasValue(Code.SUCCESS_WITH_NO_MESSAGES); - assertThat(metric.getEppTarget()).hasValue("example.tld"); - } - @Test public void testHandleEppCommand_regularEppCommand_exportsEppMetrics() { createTld("tld"); @@ -182,12 +134,10 @@ public class EppControllerTest extends ShardableTestCase { // FlowRunner, not EppController, and since FlowRunner is mocked out for these tests they won't // actually get values. EppMetric.Builder metricBuilder = - EppMetric.builderForRequest("request-id-1", clock) + EppMetric.builderForRequest(clock) .setClientId("some-client") - .setEppTarget("example.tld") .setStatus(Code.SUCCESS_WITH_NO_MESSAGES) - .setTld("tld") - .setPrivilegeLevel("SUPERUSER"); + .setTld("tld"); eppController.handleEppCommand( sessionMetadata, transportCredentials, @@ -210,8 +160,6 @@ public class EppControllerTest extends ShardableTestCase { true, true, domainCreateXml.getBytes(UTF_8)); - - verifyZeroInteractions(bigQueryMetricsEnqueuer); verifyZeroInteractions(eppMetrics); } diff --git a/javatests/google/registry/flows/EppLifecycleContactTest.java b/javatests/google/registry/flows/EppLifecycleContactTest.java index aa6f91c73..bdb33f4d3 100644 --- a/javatests/google/registry/flows/EppLifecycleContactTest.java +++ b/javatests/google/registry/flows/EppLifecycleContactTest.java @@ -52,8 +52,6 @@ public class EppLifecycleContactTest extends EppTestCase { .and() .hasCommandName("ContactCreate") .and() - .hasEppTarget("sh8013") - .and() .hasStatus(SUCCESS); assertThatCommand("contact_info.xml") .atTime("2000-06-01T00:01:00Z") @@ -63,8 +61,6 @@ public class EppLifecycleContactTest extends EppTestCase { .and() .hasCommandName("ContactInfo") .and() - .hasEppTarget("sh8013") - .and() .hasStatus(SUCCESS); assertThatCommand("contact_delete_sh8013.xml") .hasResponse("contact_delete_response_sh8013.xml"); @@ -73,8 +69,6 @@ public class EppLifecycleContactTest extends EppTestCase { .and() .hasCommandName("ContactDelete") .and() - .hasEppTarget("sh8013") - .and() .hasStatus(SUCCESS_WITH_ACTION_PENDING); assertThatLogoutSucceeds(); } diff --git a/javatests/google/registry/flows/EppLifecycleDomainTest.java b/javatests/google/registry/flows/EppLifecycleDomainTest.java index b4274518b..edd705e77 100644 --- a/javatests/google/registry/flows/EppLifecycleDomainTest.java +++ b/javatests/google/registry/flows/EppLifecycleDomainTest.java @@ -440,8 +440,6 @@ public class EppLifecycleDomainTest extends EppTestCase { .and() .hasCommandName("HostUpdate") .and() - .hasEppTarget("ns3.fakesite.example") - .and() .hasStatus(SUCCESS); // Delete the fakesite.example domain (which should succeed since it no longer has subords). assertThatCommand("domain_delete.xml", ImmutableMap.of("DOMAIN", "fakesite.example")) @@ -454,8 +452,6 @@ public class EppLifecycleDomainTest extends EppTestCase { .and() .hasCommandName("DomainDelete") .and() - .hasEppTarget("fakesite.example") - .and() .hasStatus(SUCCESS_WITH_ACTION_PENDING); // Check info on the renamed host and verify that it's still around and wasn't deleted. assertThatCommand("host_info_ns9000_example.xml") @@ -466,8 +462,6 @@ public class EppLifecycleDomainTest extends EppTestCase { .and() .hasCommandName("HostInfo") .and() - .hasEppTarget("ns9000.example.external") - .and() .hasStatus(SUCCESS); assertThatLogoutSucceeds(); assertThat(getRecordedEppMetric()) @@ -575,8 +569,6 @@ public class EppLifecycleDomainTest extends EppTestCase { .and() .hasCommandName("DomainCheck") .and() - .hasEppTarget("rich.example") - .and() .hasTld("example") .and() .hasStatus(SUCCESS); diff --git a/javatests/google/registry/flows/EppLifecycleHostTest.java b/javatests/google/registry/flows/EppLifecycleHostTest.java index fb918c292..2328424c3 100644 --- a/javatests/google/registry/flows/EppLifecycleHostTest.java +++ b/javatests/google/registry/flows/EppLifecycleHostTest.java @@ -67,8 +67,6 @@ public class EppLifecycleHostTest extends EppTestCase { .and() .hasCommandName("HostCreate") .and() - .hasEppTarget("ns1.example.tld") - .and() .hasStatus(SUCCESS); assertThatCommand("host_info.xml", ImmutableMap.of("HOSTNAME", "ns1.example.tld")) .atTime("2000-06-02T00:02:00Z") @@ -81,8 +79,6 @@ public class EppLifecycleHostTest extends EppTestCase { .and() .hasCommandName("HostInfo") .and() - .hasEppTarget("ns1.example.tld") - .and() .hasStatus(SUCCESS); assertThatCommand("host_delete.xml", ImmutableMap.of("HOSTNAME", "ns1.example.tld")) .atTime("2000-06-02T00:03:00Z") @@ -92,8 +88,6 @@ public class EppLifecycleHostTest extends EppTestCase { .and() .hasCommandName("HostDelete") .and() - .hasEppTarget("ns1.example.tld") - .and() .hasStatus(SUCCESS_WITH_ACTION_PENDING); assertThatLogoutSucceeds(); } diff --git a/javatests/google/registry/flows/EppTestCase.java b/javatests/google/registry/flows/EppTestCase.java index d76e602cf..86b0001d1 100644 --- a/javatests/google/registry/flows/EppTestCase.java +++ b/javatests/google/registry/flows/EppTestCase.java @@ -32,7 +32,6 @@ import google.registry.testing.FakeHttpSession; import google.registry.testing.FakeResponse; import google.registry.testing.InjectRule; import google.registry.testing.ShardableTestCase; -import google.registry.tmch.TmchCertificateAuthority; import java.util.Map; import javax.annotation.Nullable; import org.joda.time.DateTime; @@ -58,8 +57,6 @@ public class EppTestCase extends ShardableTestCase { public void initTestCase() { // For transactional flows inject.setStaticField(Ofy.class, "clock", clock); - // For SignedMark signature validity - inject.setStaticField(TmchCertificateAuthority.class, "clock", clock); } /** @@ -167,7 +164,7 @@ public class EppTestCase extends ShardableTestCase { EppRequestHandler handler = new EppRequestHandler(); FakeResponse response = new FakeResponse(); handler.response = response; - eppMetricBuilder = EppMetric.builderForRequest("request-id-1", clock); + eppMetricBuilder = EppMetric.builderForRequest(clock); handler.eppController = DaggerEppTestComponent.builder() .fakesAndMocksModule(FakesAndMocksModule.create(clock, eppMetricBuilder)) .build() diff --git a/javatests/google/registry/flows/EppTestComponent.java b/javatests/google/registry/flows/EppTestComponent.java index d2e86259b..72206f538 100644 --- a/javatests/google/registry/flows/EppTestComponent.java +++ b/javatests/google/registry/flows/EppTestComponent.java @@ -32,7 +32,6 @@ import google.registry.flows.async.AsyncFlowEnqueuer; import google.registry.flows.custom.CustomLogicFactory; import google.registry.flows.custom.TestCustomLogicFactory; import google.registry.flows.domain.DomainFlowTmchUtils; -import google.registry.monitoring.whitebox.BigQueryMetricsEnqueuer; import google.registry.monitoring.whitebox.EppMetric; import google.registry.request.RequestScope; import google.registry.request.lock.LockHandler; @@ -64,7 +63,6 @@ interface EppTestComponent { class FakesAndMocksModule { private AsyncFlowEnqueuer asyncFlowEnqueuer; - private BigQueryMetricsEnqueuer metricsEnqueuer; private DnsQueue dnsQueue; private DomainFlowTmchUtils domainFlowTmchUtils; private EppMetric.Builder metricBuilder; @@ -75,14 +73,14 @@ interface EppTestComponent { public static FakesAndMocksModule create() { FakeClock clock = new FakeClock(); - return create(clock, EppMetric.builderForRequest("request-id-1", clock)); + return create(clock, EppMetric.builderForRequest(clock)); } public static FakesAndMocksModule create(FakeClock clock, EppMetric.Builder metricBuilder) { return create( clock, metricBuilder, - new TmchXmlSignature(new TmchCertificateAuthority(TmchCaMode.PILOT))); + new TmchXmlSignature(new TmchCertificateAuthority(TmchCaMode.PILOT, clock))); } public static FakesAndMocksModule create( @@ -106,7 +104,6 @@ interface EppTestComponent { instance.dnsQueue = DnsQueue.create(); instance.metricBuilder = eppMetricBuilder; instance.appEngineServiceUtils = appEngineServiceUtils; - instance.metricsEnqueuer = mock(BigQueryMetricsEnqueuer.class); instance.lockHandler = new FakeLockHandler(true); return instance; } @@ -116,11 +113,6 @@ interface EppTestComponent { return asyncFlowEnqueuer; } - @Provides - BigQueryMetricsEnqueuer provideBigQueryMetricsEnqueuer() { - return metricsEnqueuer; - } - @Provides Clock provideClock() { return clock; diff --git a/javatests/google/registry/flows/EppXmlSanitizerTest.java b/javatests/google/registry/flows/EppXmlSanitizerTest.java index 3cc098608..1bdad99f6 100644 --- a/javatests/google/registry/flows/EppXmlSanitizerTest.java +++ b/javatests/google/registry/flows/EppXmlSanitizerTest.java @@ -17,6 +17,7 @@ package google.registry.flows; import static com.google.common.truth.Truth.assertThat; import static google.registry.flows.EppXmlSanitizer.sanitizeEppXml; import static google.registry.testing.TestDataHelper.loadBytes; +import static java.nio.charset.StandardCharsets.UTF_16LE; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableMap; @@ -30,12 +31,12 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class EppXmlSanitizerTest { - private static final String XML_HEADER = ""; + private static final String UTF8_HEADER = ""; @Test public void testSanitize_noSensitiveData_noop() throws Exception { byte[] inputXmlBytes = loadBytes(getClass(), "host_create.xml").read(); - String expectedXml = XML_HEADER + new String(inputXmlBytes, UTF_8); + String expectedXml = UTF8_HEADER + new String(inputXmlBytes, UTF_8); String sanitizedXml = sanitizeEppXml(inputXmlBytes); assertThat(sanitizedXml).isEqualTo(expectedXml); @@ -50,7 +51,7 @@ public class EppXmlSanitizerTest { ImmutableMap.of("PW", "oldpass", "NEWPW", "newPw")) .getEppXml(); String expectedXml = - XML_HEADER + UTF8_HEADER + new EppLoader( this, "login_update_password.xml", @@ -68,7 +69,7 @@ public class EppXmlSanitizerTest { this, "login_wrong_case.xml", ImmutableMap.of("PW", "oldpass", "NEWPW", "newPw")) .getEppXml(); String expectedXml = - XML_HEADER + UTF8_HEADER + new EppLoader( this, "login_wrong_case.xml", @@ -83,7 +84,7 @@ public class EppXmlSanitizerTest { public void testSanitize_contactAuthInfo_sanitized() throws Exception { byte[] inputXmlBytes = loadBytes(getClass(), "contact_info.xml").read(); String expectedXml = - XML_HEADER + UTF8_HEADER + new EppLoader(this, "contact_info_sanitized.xml", ImmutableMap.of()).getEppXml(); String sanitizedXml = sanitizeEppXml(inputXmlBytes); @@ -94,7 +95,7 @@ public class EppXmlSanitizerTest { public void testSanitize_contactCreateResponseAuthInfo_sanitized() throws Exception { byte[] inputXmlBytes = loadBytes(getClass(), "contact_info_from_create_response.xml").read(); String expectedXml = - XML_HEADER + UTF8_HEADER + new EppLoader( this, "contact_info_from_create_response_sanitized.xml", ImmutableMap.of()) .getEppXml(); @@ -106,7 +107,7 @@ public class EppXmlSanitizerTest { @Test public void testSanitize_emptyElement_transformedToLongForm() { byte[] inputXmlBytes = "".getBytes(UTF_8); - assertThat(sanitizeEppXml(inputXmlBytes)).isEqualTo(XML_HEADER + "\n"); + assertThat(sanitizeEppXml(inputXmlBytes)).isEqualTo(UTF8_HEADER + "\n"); } @Test @@ -119,7 +120,22 @@ public class EppXmlSanitizerTest { @Test public void testSanitize_unicode_hasCorrectCharCount() { byte[] inputXmlBytes = "\u007F\u4E43x".getBytes(UTF_8); - String expectedXml = XML_HEADER + "C**\n"; + String expectedXml = UTF8_HEADER + "C**\n"; assertThat(sanitizeEppXml(inputXmlBytes)).isEqualTo(expectedXml); } + + @Test + public void testSanitize_emptyString_encodedToBase64() { + byte[] inputXmlBytes = "".getBytes(UTF_8); + assertThat(sanitizeEppXml(inputXmlBytes)).isEqualTo(""); + } + + @Test + public void testSanitize_utf16_encodingPreserved() { + // Test data should specify an endian-specific UTF-16 scheme for easy assertion. If 'UTF-16' is + // used, the XMLEventReader in sanitizer may resolve it to an endian-specific one. + String inputXml = "

\u03bc

\n"; + String sanitizedXml = sanitizeEppXml(inputXml.getBytes(UTF_16LE)); + assertThat(sanitizedXml).isEqualTo(inputXml); + } } diff --git a/javatests/google/registry/flows/FlowRunnerTest.java b/javatests/google/registry/flows/FlowRunnerTest.java index 82a3362fa..c828425ba 100644 --- a/javatests/google/registry/flows/FlowRunnerTest.java +++ b/javatests/google/registry/flows/FlowRunnerTest.java @@ -54,8 +54,7 @@ public class FlowRunnerTest extends ShardableTestCase { public final AppEngineRule appEngineRule = new AppEngineRule.Builder().build(); private final FlowRunner flowRunner = new FlowRunner(); - private final EppMetric.Builder eppMetricBuilder = - EppMetric.builderForRequest("request-id-1", new FakeClock()); + private final EppMetric.Builder eppMetricBuilder = EppMetric.builderForRequest(new FakeClock()); private final TestLogHandler handler = new TestLogHandler(); @@ -84,19 +83,6 @@ public class FlowRunnerTest extends ShardableTestCase { flowRunner.flowReporter = Mockito.mock(FlowReporter.class); } - @Test - public void testRun_nonTransactionalCommand_incrementsMetricAttempts() throws Exception { - flowRunner.run(eppMetricBuilder); - assertThat(eppMetricBuilder.build().getAttempts()).isEqualTo(1); - } - - @Test - public void testRun_transactionalCommand_incrementsMetricAttempts() throws Exception { - flowRunner.isTransactional = true; - flowRunner.run(eppMetricBuilder); - assertThat(eppMetricBuilder.build().getAttempts()).isEqualTo(1); - } - @Test public void testRun_nonTransactionalCommand_setsCommandNameOnMetric() throws Exception { flowRunner.isTransactional = true; diff --git a/javatests/google/registry/flows/FlowTestCase.java b/javatests/google/registry/flows/FlowTestCase.java index 7db1baca2..80e25ec28 100644 --- a/javatests/google/registry/flows/FlowTestCase.java +++ b/javatests/google/registry/flows/FlowTestCase.java @@ -110,8 +110,6 @@ public abstract class FlowTestCase extends ShardableTestCase { ofy().saveWithoutBackup().entity(new ClaimsListSingleton()).now(); // For transactional flows inject.setStaticField(Ofy.class, "clock", clock); - // For SignedMark signature validity - inject.setStaticField(TmchCertificateAuthority.class, "clock", clock); } protected void removeServiceExtensionUri(String uri) { @@ -278,7 +276,7 @@ public abstract class FlowTestCase extends ShardableTestCase { private EppOutput runFlowInternal(CommitMode commitMode, UserPrivileges userPrivileges) throws Exception { - eppMetricBuilder = EppMetric.builderForRequest("request-id-1", clock); + eppMetricBuilder = EppMetric.builderForRequest(clock); // Assert that the xml triggers the flow we expect. assertThat(FlowPicker.getFlowClass(eppLoader.getEpp())) .isEqualTo(new TypeInstantiator(getClass()){}.getExactType()); @@ -286,7 +284,7 @@ public abstract class FlowTestCase extends ShardableTestCase { TmchXmlSignature tmchXmlSignature = testTmchXmlSignature != null ? testTmchXmlSignature - : new TmchXmlSignature(new TmchCertificateAuthority(tmchCaMode)); + : new TmchXmlSignature(new TmchCertificateAuthority(tmchCaMode, clock)); return DaggerEppTestComponent.builder() .fakesAndMocksModule(FakesAndMocksModule.create(clock, eppMetricBuilder, tmchXmlSignature)) .build() diff --git a/javatests/google/registry/flows/async/AsyncFlowMetricsTest.java b/javatests/google/registry/flows/async/AsyncFlowMetricsTest.java index 88f39e2b7..728f6de8d 100644 --- a/javatests/google/registry/flows/async/AsyncFlowMetricsTest.java +++ b/javatests/google/registry/flows/async/AsyncFlowMetricsTest.java @@ -14,19 +14,14 @@ package google.registry.flows.async; +import static com.google.monitoring.metrics.contrib.DistributionMetricSubject.assertThat; +import static com.google.monitoring.metrics.contrib.LongMetricSubject.assertThat; import static google.registry.flows.async.AsyncFlowMetrics.OperationResult.SUCCESS; import static google.registry.flows.async.AsyncFlowMetrics.OperationType.CONTACT_AND_HOST_DELETE; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import com.google.monitoring.metrics.EventMetric; -import com.google.monitoring.metrics.IncrementableMetric; +import com.google.common.collect.ImmutableSet; import google.registry.testing.FakeClock; -import google.registry.testing.InjectRule; import google.registry.testing.ShardableTestCase; -import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -35,26 +30,8 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class AsyncFlowMetricsTest extends ShardableTestCase { - @Rule public final InjectRule inject = new InjectRule(); - - private final IncrementableMetric asyncFlowOperationCounts = mock(IncrementableMetric.class); - private final EventMetric asyncFlowOperationProcessingTime = mock(EventMetric.class); - private final EventMetric asyncFlowBatchSize = mock(EventMetric.class); - private AsyncFlowMetrics asyncFlowMetrics; - private FakeClock clock; - - @Before - public void setUp() { - clock = new FakeClock(); - asyncFlowMetrics = new AsyncFlowMetrics(clock); - inject.setStaticField( - AsyncFlowMetrics.class, "asyncFlowOperationCounts", asyncFlowOperationCounts); - inject.setStaticField( - AsyncFlowMetrics.class, - "asyncFlowOperationProcessingTime", - asyncFlowOperationProcessingTime); - inject.setStaticField(AsyncFlowMetrics.class, "asyncFlowBatchSize", asyncFlowBatchSize); - } + private final FakeClock clock = new FakeClock(); + private final AsyncFlowMetrics asyncFlowMetrics = new AsyncFlowMetrics(clock); @Test public void testRecordAsyncFlowResult_calculatesDurationMillisCorrectly() { @@ -62,9 +39,13 @@ public class AsyncFlowMetricsTest extends ShardableTestCase { CONTACT_AND_HOST_DELETE, SUCCESS, clock.nowUtc().minusMinutes(10).minusSeconds(5).minusMillis(566)); - verify(asyncFlowOperationCounts).increment("contactAndHostDelete", "success"); - verify(asyncFlowOperationProcessingTime).record(605566.0, "contactAndHostDelete", "success"); - verifyNoMoreInteractions(asyncFlowOperationCounts); - verifyNoMoreInteractions(asyncFlowOperationProcessingTime); + assertThat(AsyncFlowMetrics.asyncFlowOperationCounts) + .hasValueForLabels(1, "contactAndHostDelete", "success") + .and() + .hasNoOtherValues(); + assertThat(AsyncFlowMetrics.asyncFlowOperationProcessingTime) + .hasDataSetForLabels(ImmutableSet.of(605566.0), "contactAndHostDelete", "success") + .and() + .hasNoOtherValues(); } } diff --git a/javatests/google/registry/flows/domain/DomainCreateFlowTest.java b/javatests/google/registry/flows/domain/DomainCreateFlowTest.java index 33f417273..33ab45c30 100644 --- a/javatests/google/registry/flows/domain/DomainCreateFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainCreateFlowTest.java @@ -52,8 +52,8 @@ import static google.registry.testing.TaskQueueHelper.assertDnsTasksEnqueued; import static google.registry.testing.TaskQueueHelper.assertNoDnsTasksEnqueued; import static google.registry.testing.TaskQueueHelper.assertNoTasksEnqueued; import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued; -import static google.registry.tmch.LordnTask.QUEUE_CLAIMS; -import static google.registry.tmch.LordnTask.QUEUE_SUNRISE; +import static google.registry.tmch.LordnTaskUtils.QUEUE_CLAIMS; +import static google.registry.tmch.LordnTaskUtils.QUEUE_SUNRISE; import static google.registry.util.DateTimeUtils.END_OF_TIME; import static google.registry.util.DateTimeUtils.START_OF_TIME; import static org.joda.money.CurrencyUnit.EUR; @@ -270,7 +270,8 @@ public class DomainCreateFlowTest extends ResourceFlowTestCase().add(createBillingEvent).add(renewBillingEvent); // If EAP is applied, a billing event for EAP should be present. - if (!eapFee.isZero()) { + // EAP fees are bypassed for anchor tenant domains. + if (!isAnchorTenant && !eapFee.isZero()) { BillingEvent.OneTime eapBillingEvent = new BillingEvent.OneTime.Builder() .setReason(Reason.FEE_EARLY_ACCESS) @@ -1012,6 +1013,22 @@ public class DomainCreateFlowTest extends ResourceFlowTestCase labelsToKeys = new HashMap<>(); - for (int i = 0; i <= ClaimsListShard.shardSize * 3; i++) { + for (int i = 0; i <= shardSize * 3; i++) { labelsToKeys.put(Integer.toString(i), Integer.toString(i)); } DateTime now = DateTime.now(UTC); // Save it with sharding, and make sure that reloading it works. ClaimsListShard unsharded = ClaimsListShard.create(now, ImmutableMap.copyOf(labelsToKeys)); - unsharded.save(); + unsharded.save(shardSize); assertThat(ClaimsListShard.get().labelsToKeys).isEqualTo(unsharded.labelsToKeys); List shards1 = ofy().load().type(ClaimsListShard.class).list(); assertThat(shards1).hasSize(4); @@ -97,11 +89,11 @@ public class ClaimsListShardTest { // Create a smaller ClaimsList that will need only 2 shards to save. labelsToKeys = new HashMap<>(); - for (int i = 0; i <= ClaimsListShard.shardSize; i++) { + for (int i = 0; i <= shardSize; i++) { labelsToKeys.put(Integer.toString(i), Integer.toString(i)); } unsharded = ClaimsListShard.create(now.plusDays(1), ImmutableMap.copyOf(labelsToKeys)); - unsharded.save(); + unsharded.save(shardSize); ofy().clearSessionCache(); assertThat(ClaimsListShard.get().labelsToKeys).hasSize(unsharded.labelsToKeys.size()); assertThat(ClaimsListShard.get().labelsToKeys).isEqualTo(unsharded.labelsToKeys); diff --git a/javatests/google/registry/module/backend/testdata/backend_routing.txt b/javatests/google/registry/module/backend/testdata/backend_routing.txt index 4f1e88ee7..db8e9b38c 100644 --- a/javatests/google/registry/module/backend/testdata/backend_routing.txt +++ b/javatests/google/registry/module/backend/testdata/backend_routing.txt @@ -26,7 +26,6 @@ PATH CLASS METHOD /_dr/task/importRdeHosts RdeHostImportAction GET n INTERNAL APP IGNORED /_dr/task/linkRdeHosts RdeHostLinkAction GET n INTERNAL APP IGNORED /_dr/task/loadSnapshot LoadSnapshotAction POST n INTERNAL APP IGNORED -/_dr/task/metrics MetricsExportAction POST n INTERNAL APP IGNORED /_dr/task/nordnUpload NordnUploadAction POST y INTERNAL APP IGNORED /_dr/task/nordnVerify NordnVerifyAction POST y INTERNAL APP IGNORED /_dr/task/pollBigqueryJob BigqueryPollJobAction GET,POST y INTERNAL APP IGNORED diff --git a/javatests/google/registry/monitoring/whitebox/BigQueryMetricsEnqueuerTest.java b/javatests/google/registry/monitoring/whitebox/BigQueryMetricsEnqueuerTest.java deleted file mode 100644 index 45fc65442..000000000 --- a/javatests/google/registry/monitoring/whitebox/BigQueryMetricsEnqueuerTest.java +++ /dev/null @@ -1,111 +0,0 @@ -// 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.monitoring.whitebox; - -import static com.google.appengine.api.taskqueue.QueueFactory.getQueue; -import static google.registry.bigquery.BigqueryUtils.toBigqueryTimestamp; -import static google.registry.monitoring.whitebox.BigQueryMetricsEnqueuer.QUEUE_BIGQUERY_STREAMING_METRICS; -import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued; -import static org.mockito.Mockito.when; - -import com.google.api.services.bigquery.model.TableFieldSchema; -import com.google.auto.value.AutoValue; -import com.google.common.base.Suppliers; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import google.registry.testing.AppEngineRule; -import google.registry.testing.MockitoJUnitRule; -import google.registry.testing.TaskQueueHelper.TaskMatcher; -import google.registry.util.AppEngineServiceUtils; -import org.joda.time.DateTime; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.Mock; - -/** Unit tests for {@link BigQueryMetricsEnqueuer}. */ -@RunWith(JUnit4.class) -public class BigQueryMetricsEnqueuerTest { - - @Rule - public final AppEngineRule appEngine = - AppEngineRule.builder().withDatastore().withLocalModules().withTaskQueue().build(); - - @Rule public final MockitoJUnitRule mocks = MockitoJUnitRule.create(); - - @Mock private AppEngineServiceUtils appEngineServiceUtils; - - private BigQueryMetricsEnqueuer enqueuer; - - @Before - public void setUp() { - enqueuer = new BigQueryMetricsEnqueuer(); - enqueuer.idGenerator = Suppliers.ofInstance("laffo"); - enqueuer.appEngineServiceUtils = appEngineServiceUtils; - enqueuer.queue = getQueue(QUEUE_BIGQUERY_STREAMING_METRICS); - when(appEngineServiceUtils.getCurrentVersionHostname("backend")) - .thenReturn("backend.test.localhost"); - } - - @Test - public void testExport() { - TestMetric metric = - TestMetric.create( - DateTime.parse("1984-12-18TZ"), DateTime.parse("1984-12-18TZ").plusMillis(1)); - - enqueuer.export(metric); - - assertTasksEnqueued("bigquery-streaming-metrics", - new TaskMatcher() - .url("/_dr/task/metrics") - .header("Host", "backend.test.localhost") - .param("tableId", "test") - .param("startTime", "472176000.000000") - .param("endTime", "472176000.001000") - .param("insertId", "laffo")); - } - - /** A stub implementation of {@link BigQueryMetric}. */ - @AutoValue - abstract static class TestMetric implements BigQueryMetric { - - static TestMetric create(DateTime startTimestamp, DateTime endTimestamp) { - return new AutoValue_BigQueryMetricsEnqueuerTest_TestMetric(startTimestamp, endTimestamp); - } - - @Override - public String getTableId() { - return "test"; - } - - @Override - public ImmutableList getSchemaFields() { - return null; - } - - @Override - public ImmutableMap getBigQueryRowEncoding() { - return ImmutableMap.of( - "startTime", toBigqueryTimestamp(getStartTimestamp()), - "endTime", toBigqueryTimestamp(getEndTimestamp())); - } - - abstract DateTime getStartTimestamp(); - - abstract DateTime getEndTimestamp(); - } -} diff --git a/javatests/google/registry/monitoring/whitebox/EppMetricTest.java b/javatests/google/registry/monitoring/whitebox/EppMetricTest.java index 6c21ee1ac..7fd47d227 100644 --- a/javatests/google/registry/monitoring/whitebox/EppMetricTest.java +++ b/javatests/google/registry/monitoring/whitebox/EppMetricTest.java @@ -14,18 +14,13 @@ package google.registry.monitoring.whitebox; -import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth8.assertThat; import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.createTlds; -import com.google.api.services.bigquery.model.TableFieldSchema; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import google.registry.model.eppoutput.Result.Code; import google.registry.testing.AppEngineRule; import google.registry.testing.FakeClock; -import org.joda.time.DateTime; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,7 +35,7 @@ public class EppMetricTest { @Test public void test_invalidTld_isRecordedAsInvalid() { EppMetric metric = - EppMetric.builderForRequest("request-id-1", new FakeClock()) + EppMetric.builderForRequest(new FakeClock()) .setTlds(ImmutableSet.of("notarealtld")) .build(); assertThat(metric.getTld()).hasValue("_invalid"); @@ -50,9 +45,7 @@ public class EppMetricTest { public void test_validTld_isRecorded() { createTld("example"); EppMetric metric = - EppMetric.builderForRequest("request-id-1", new FakeClock()) - .setTlds(ImmutableSet.of("example")) - .build(); + EppMetric.builderForRequest(new FakeClock()).setTlds(ImmutableSet.of("example")).build(); assertThat(metric.getTld()).hasValue("example"); } @@ -60,7 +53,7 @@ public class EppMetricTest { public void test_multipleTlds_areRecordedAsVarious() { createTlds("foo", "bar"); EppMetric metric = - EppMetric.builderForRequest("request-id-1", new FakeClock()) + EppMetric.builderForRequest(new FakeClock()) .setTlds(ImmutableSet.of("foo", "bar", "baz")) .build(); assertThat(metric.getTld()).hasValue("_various"); @@ -69,64 +62,7 @@ public class EppMetricTest { @Test public void test_zeroTlds_areRecordedAsAbsent() { EppMetric metric = - EppMetric.builderForRequest("request-id-1", new FakeClock()) - .setTlds(ImmutableSet.of()) - .build(); + EppMetric.builderForRequest(new FakeClock()).setTlds(ImmutableSet.of()).build(); assertThat(metric.getTld()).isEmpty(); } - - @Test - public void testGetBigQueryRowEncoding_encodesCorrectly() { - EppMetric metric = - EppMetric.builder() - .setRequestId("request-id-1") - .setStartTimestamp(new DateTime(1337)) - .setEndTimestamp(new DateTime(1338)) - .setCommandName("command") - .setClientId("client") - .setTld("example") - .setPrivilegeLevel("level") - .setEppTarget("target") - .setStatus(Code.COMMAND_USE_ERROR) - .incrementAttempts() - .build(); - - assertThat(metric.getBigQueryRowEncoding()) - .containsExactlyEntriesIn( - new ImmutableMap.Builder() - .put("requestId", "request-id-1") - .put("startTime", "1.337000") - .put("endTime", "1.338000") - .put("commandName", "command") - .put("clientId", "client") - .put("tld", "example") - .put("privilegeLevel", "level") - .put("eppTarget", "target") - .put("eppStatus", "2002") - .put("attempts", "1") - .build()); - } - - @Test - public void testGetBigQueryRowEncoding_hasAllSchemaFields() { - EppMetric metric = - EppMetric.builder() - .setRequestId("request-id-1") - .setStartTimestamp(new DateTime(1337)) - .setEndTimestamp(new DateTime(1338)) - .setCommandName("command") - .setClientId("client") - .setTld("example") - .setPrivilegeLevel("level") - .setEppTarget("target") - .setStatus(Code.COMMAND_USE_ERROR) - .incrementAttempts() - .build(); - ImmutableSet.Builder schemaFieldNames = new ImmutableSet.Builder<>(); - for (TableFieldSchema schemaField : metric.getSchemaFields()) { - schemaFieldNames.add(schemaField.getName()); - } - - assertThat(metric.getBigQueryRowEncoding().keySet()).isEqualTo(schemaFieldNames.build()); - } } diff --git a/javatests/google/registry/monitoring/whitebox/MetricsExportActionTest.java b/javatests/google/registry/monitoring/whitebox/MetricsExportActionTest.java deleted file mode 100644 index 5e54adb64..000000000 --- a/javatests/google/registry/monitoring/whitebox/MetricsExportActionTest.java +++ /dev/null @@ -1,108 +0,0 @@ -// 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.monitoring.whitebox; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.google.api.services.bigquery.Bigquery; -import com.google.api.services.bigquery.Bigquery.Tabledata; -import com.google.api.services.bigquery.Bigquery.Tabledata.InsertAll; -import com.google.api.services.bigquery.model.TableDataInsertAllRequest; -import com.google.api.services.bigquery.model.TableDataInsertAllResponse; -import com.google.api.services.bigquery.model.TableDataInsertAllResponse.InsertErrors; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableListMultimap; -import google.registry.bigquery.CheckedBigquery; -import google.registry.testing.AppEngineRule; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.Matchers; - -/** Unit tests for {@link MetricsExportAction}. */ -@RunWith(JUnit4.class) -public class MetricsExportActionTest { - - @Rule - public final AppEngineRule appEngine = - AppEngineRule.builder().withDatastore().withTaskQueue().build(); - - private final CheckedBigquery checkedBigquery = mock(CheckedBigquery.class); - private final Bigquery bigquery = mock(Bigquery.class); - private final Tabledata tabledata = mock(Tabledata.class); - private final InsertAll insertAll = mock(InsertAll.class); - - private TableDataInsertAllResponse response = new TableDataInsertAllResponse(); - private long currentTimeMillis = 1000000000000L; - - private ImmutableListMultimap parameters = - new ImmutableListMultimap.Builder() - .put("startTime", String.valueOf(MILLISECONDS.toSeconds(currentTimeMillis - 100))) - .put("endTime", String.valueOf(MILLISECONDS.toSeconds(currentTimeMillis))) - .put("jobname", "test job") - .put("status", "success") - .put("tld", "test") - .build(); - - MetricsExportAction action; - - @Before - public void setup() throws Exception { - when(checkedBigquery.ensureDataSetAndTableExist(anyString(), anyString(), anyString())) - .thenReturn(bigquery); - - when(bigquery.tabledata()).thenReturn(tabledata); - when(tabledata.insertAll( - anyString(), - anyString(), - anyString(), - Matchers.any(TableDataInsertAllRequest.class))).thenReturn(insertAll); - action = new MetricsExportAction(); - action.checkedBigquery = checkedBigquery; - action.insertId = "insert id"; - action.parameters = parameters; - action.projectId = "project id"; - action.tableId = "eppMetrics"; - } - - @Test - public void testSuccess_nullErrors() throws Exception { - when(insertAll.execute()).thenReturn(response); - response.setInsertErrors(null); - action.run(); - verify(insertAll).execute(); - } - - @Test - public void testSuccess_emptyErrors() throws Exception { - when(insertAll.execute()).thenReturn(response); - response.setInsertErrors(ImmutableList.of()); - action.run(); - verify(insertAll).execute(); - } - - @Test - public void testFailure_errors() throws Exception { - when(insertAll.execute()).thenReturn(response); - response.setInsertErrors(ImmutableList.of(new InsertErrors())); - action.run(); - } -} diff --git a/javatests/google/registry/proxy/handler/EppQuotaHandlerTest.java b/javatests/google/registry/proxy/handler/EppQuotaHandlerTest.java index 1669df3a0..df2211aa9 100644 --- a/javatests/google/registry/proxy/handler/EppQuotaHandlerTest.java +++ b/javatests/google/registry/proxy/handler/EppQuotaHandlerTest.java @@ -109,7 +109,11 @@ public class EppQuotaHandlerTest { .thenReturn(QuotaResponse.create(false, clientCertHash, now)); OverQuotaException e = assertThrows(OverQuotaException.class, () -> channel.writeInbound(message)); + ChannelFuture unusedFuture = channel.close(); assertThat(e).hasMessageThat().contains(clientCertHash); + verify(quotaManager).acquireQuota(QuotaRequest.create(clientCertHash)); + // Make sure that quotaManager.releaseQuota() is not called when the channel closes. + verifyNoMoreInteractions(quotaManager); verify(metrics).registerQuotaRejection("epp", clientCertHash); verifyNoMoreInteractions(metrics); } diff --git a/javatests/google/registry/rdap/RdapActionBaseTest.java b/javatests/google/registry/rdap/RdapActionBaseTest.java index e2143861d..7a93e3cd7 100644 --- a/javatests/google/registry/rdap/RdapActionBaseTest.java +++ b/javatests/google/registry/rdap/RdapActionBaseTest.java @@ -17,62 +17,37 @@ package google.registry.rdap; import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN; import static com.google.common.truth.Truth.assertThat; import static google.registry.request.Action.Method.GET; -import static google.registry.request.Action.Method.HEAD; import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.TestDataHelper.loadFile; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import com.google.appengine.api.users.User; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import google.registry.model.ofy.Ofy; import google.registry.rdap.RdapJsonFormatter.BoilerplateType; import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.SearchType; import google.registry.rdap.RdapMetrics.WildcardType; import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; import google.registry.request.Action; -import google.registry.request.auth.AuthLevel; -import google.registry.request.auth.AuthResult; -import google.registry.request.auth.UserAuthInfo; -import google.registry.testing.AppEngineRule; -import google.registry.testing.FakeClock; -import google.registry.testing.FakeResponse; -import google.registry.testing.InjectRule; -import google.registry.ui.server.registrar.SessionUtils; import java.util.Optional; -import org.joda.time.DateTime; import org.json.simple.JSONValue; 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 RdapActionBase}. */ @RunWith(JUnit4.class) -public class RdapActionBaseTest { +public class RdapActionBaseTest extends RdapActionBaseTestCase { - @Rule - public final AppEngineRule appEngine = AppEngineRule.builder() - .withDatastore() - .build(); - - @Rule - public final InjectRule inject = new InjectRule(); - - private final FakeResponse response = new FakeResponse(); - private final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01TZ")); - private final SessionUtils sessionUtils = mock(SessionUtils.class); - private final User user = new User("rdap.user@example.com", "gmail.com", "12345"); - private final UserAuthInfo userAuthInfo = UserAuthInfo.create(user, false); - private final RdapMetrics rdapMetrics = mock(RdapMetrics.class); + public RdapActionBaseTest() { + super(RdapTestAction.class, RdapTestAction.PATH); + } /** * Dummy RdapActionBase subclass used for testing. */ - static class RdapTestAction extends RdapActionBase { + public static class RdapTestAction extends RdapActionBase { public static final String PATH = "/rdap/test/"; @@ -112,37 +87,10 @@ public class RdapActionBaseTest { } } - private RdapTestAction action; - @Before public void setUp() { createTld("thing"); - inject.setStaticField(Ofy.class, "clock", clock); - action = new RdapTestAction(); - action.sessionUtils = sessionUtils; - action.authResult = AuthResult.create(AuthLevel.USER, userAuthInfo); - action.includeDeletedParam = Optional.empty(); - action.registrarParam = Optional.empty(); - action.formatOutputParam = Optional.empty(); - action.response = response; - action.rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter(); - action.rdapMetrics = rdapMetrics; - } - - private Object generateActualJson(String domainName) { - action.requestPath = RdapTestAction.PATH + domainName; action.fullServletPath = "http://myserver.example.com" + RdapTestAction.PATH; - action.requestMethod = GET; - action.run(); - return JSONValue.parse(response.getPayload()); - } - - private String generateHeadPayload(String domainName) { - action.requestPath = RdapTestAction.PATH + domainName; - action.fullServletPath = "http://myserver.example.com" + RdapTestAction.PATH; - action.requestMethod = HEAD; - action.run(); - return response.getPayload(); } @Test @@ -241,7 +189,6 @@ public class RdapActionBaseTest { @Test public void testUnformatted() { action.requestPath = RdapTestAction.PATH + "no.thing"; - action.fullServletPath = "http://myserver.example.com" + RdapTestAction.PATH; action.requestMethod = GET; action.run(); assertThat(response.getPayload()) @@ -251,7 +198,6 @@ public class RdapActionBaseTest { @Test public void testFormatted() { action.requestPath = RdapTestAction.PATH + "no.thing?formatOutput=true"; - action.fullServletPath = "http://myserver.example.com" + RdapTestAction.PATH; action.requestMethod = GET; action.formatOutputParam = Optional.of(true); action.run(); diff --git a/javatests/google/registry/rdap/RdapActionBaseTestCase.java b/javatests/google/registry/rdap/RdapActionBaseTestCase.java new file mode 100644 index 000000000..9ddb7f35f --- /dev/null +++ b/javatests/google/registry/rdap/RdapActionBaseTestCase.java @@ -0,0 +1,142 @@ +// 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.rdap; + +import static google.registry.rdap.RdapAuthorization.Role.ADMINISTRATOR; +import static google.registry.rdap.RdapAuthorization.Role.PUBLIC; +import static google.registry.rdap.RdapAuthorization.Role.REGISTRAR; +import static google.registry.request.Action.Method.GET; +import static google.registry.request.Action.Method.HEAD; +import static google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.Role.OWNER; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.appengine.api.users.User; +import com.google.common.collect.ImmutableSetMultimap; +import google.registry.model.ofy.Ofy; +import google.registry.request.Action; +import google.registry.request.auth.AuthLevel; +import google.registry.request.auth.AuthResult; +import google.registry.request.auth.UserAuthInfo; +import google.registry.testing.AppEngineRule; +import google.registry.testing.FakeClock; +import google.registry.testing.FakeResponse; +import google.registry.testing.InjectRule; +import google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor; +import google.registry.util.TypeUtils; +import java.util.Optional; +import org.joda.time.DateTime; +import org.json.simple.JSONValue; +import org.junit.Before; +import org.junit.Rule; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Common unit test code for actions inheriting {@link RdapActionBase}. */ +@RunWith(JUnit4.class) +public class RdapActionBaseTestCase { + + @Rule + public final AppEngineRule appEngine = AppEngineRule.builder() + .withDatastore() + .build(); + + @Rule + public final InjectRule inject = new InjectRule(); + + protected static final AuthResult AUTH_RESULT = + AuthResult.create( + AuthLevel.USER, + UserAuthInfo.create(new User("rdap.user@user.com", "gmail.com", "12345"), false)); + + protected static final AuthResult AUTH_RESULT_ADMIN = + AuthResult.create( + AuthLevel.USER, + UserAuthInfo.create(new User("rdap.admin@google.com", "gmail.com", "12345"), true)); + + protected final AuthenticatedRegistrarAccessor registrarAccessor = + mock(AuthenticatedRegistrarAccessor.class); + + protected FakeResponse response = new FakeResponse(); + protected final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01TZ")); + protected final RdapMetrics rdapMetrics = mock(RdapMetrics.class); + + protected RdapAuthorization.Role metricRole = PUBLIC; + protected A action; + + protected final String actionPath; + protected final Class rdapActionClass; + + protected RdapActionBaseTestCase(Class rdapActionClass, String actionPath) { + this.rdapActionClass = rdapActionClass; + this.actionPath = actionPath; + } + + @Before + public void baseSetUp() { + inject.setStaticField(Ofy.class, "clock", clock); + action = TypeUtils.instantiate(rdapActionClass); + action.registrarAccessor = registrarAccessor; + action.clock = clock; + action.authResult = AUTH_RESULT; + action.includeDeletedParam = Optional.empty(); + action.registrarParam = Optional.empty(); + action.formatOutputParam = Optional.empty(); + action.response = response; + action.rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter(); + action.rdapMetrics = rdapMetrics; + action.requestMethod = Action.Method.GET; + action.fullServletPath = "https://example.tld/rdap"; + action.rdapWhoisServer = null; + logout(); + } + + protected void login(String clientId) { + when(registrarAccessor.getAllClientIdWithRoles()) + .thenReturn(ImmutableSetMultimap.of(clientId, OWNER)); + action.authResult = AUTH_RESULT; + metricRole = REGISTRAR; + } + + protected void logout() { + when(registrarAccessor.getAllClientIdWithRoles()).thenReturn(ImmutableSetMultimap.of()); + action.authResult = AUTH_RESULT; + metricRole = PUBLIC; + } + + protected void loginAsAdmin() { + // when admin, we don't actually check what they have access to - so it doesn't matter what we + // return. + // null isn't actually a legal value, we just want to make sure it's never actually used. + when(registrarAccessor.getAllClientIdWithRoles()).thenReturn(null); + action.authResult = AUTH_RESULT_ADMIN; + metricRole = ADMINISTRATOR; + } + + protected Object generateActualJson(String domainName) { + action.requestPath = actionPath + domainName; + action.requestMethod = GET; + action.run(); + return JSONValue.parse(response.getPayload()); + } + + protected String generateHeadPayload(String domainName) { + action.requestPath = actionPath + domainName; + action.fullServletPath = "http://myserver.example.com" + actionPath; + action.requestMethod = HEAD; + action.run(); + return response.getPayload(); + } +} diff --git a/javatests/google/registry/rdap/RdapDomainActionTest.java b/javatests/google/registry/rdap/RdapDomainActionTest.java index af19848e9..65acf8ffe 100644 --- a/javatests/google/registry/rdap/RdapDomainActionTest.java +++ b/javatests/google/registry/rdap/RdapDomainActionTest.java @@ -25,11 +25,8 @@ import static google.registry.testing.FullFieldsTestEntityHelper.makeHistoryEntr import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar; import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarContacts; import static google.registry.testing.TestDataHelper.loadFile; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import com.google.appengine.api.users.User; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -37,7 +34,6 @@ import google.registry.model.contact.ContactResource; import google.registry.model.domain.DomainBase; import google.registry.model.domain.Period; import google.registry.model.host.HostResource; -import google.registry.model.ofy.Ofy; import google.registry.model.registrar.Registrar; import google.registry.model.registry.Registry; import google.registry.model.reporting.HistoryEntry; @@ -46,55 +42,28 @@ import google.registry.rdap.RdapMetrics.SearchType; import google.registry.rdap.RdapMetrics.WildcardType; import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; import google.registry.request.Action; -import google.registry.request.auth.AuthLevel; -import google.registry.request.auth.AuthResult; -import google.registry.request.auth.UserAuthInfo; -import google.registry.testing.AppEngineRule; -import google.registry.testing.FakeClock; -import google.registry.testing.FakeResponse; -import google.registry.testing.InjectRule; -import google.registry.ui.server.registrar.SessionUtils; import java.util.List; import java.util.Map; import java.util.Optional; import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import org.joda.time.DateTime; import org.json.simple.JSONObject; import org.json.simple.JSONValue; import org.junit.Before; import org.junit.Ignore; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Unit tests for {@link RdapDomainAction}. */ @RunWith(JUnit4.class) -public class RdapDomainActionTest { +public class RdapDomainActionTest extends RdapActionBaseTestCase { - @Rule - public final AppEngineRule appEngine = AppEngineRule.builder() - .withDatastore() - .build(); - - @Rule - public final InjectRule inject = new InjectRule(); - - private final HttpServletRequest request = mock(HttpServletRequest.class); - private final FakeResponse response = new FakeResponse(); - private final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01TZ")); - private final SessionUtils sessionUtils = mock(SessionUtils.class); - private final User user = new User("rdap.user@example.com", "gmail.com", "12345"); - private final UserAuthInfo userAuthInfo = UserAuthInfo.create(user, false); - private final UserAuthInfo adminUserAuthInfo = UserAuthInfo.create(user, true); - private final RdapMetrics rdapMetrics = mock(RdapMetrics.class); - - private RdapDomainAction action; + public RdapDomainActionTest() { + super(RdapDomainAction.class, RdapDomainAction.PATH); + } @Before public void setUp() { - inject.setStaticField(Ofy.class, "clock", clock); // lol createTld("lol"); Registrar registrarLol = persistResource(makeRegistrar( @@ -259,38 +228,6 @@ public class RdapDomainActionTest { Period.create(1, Period.Unit.YEARS), "deleted", clock.nowUtc().minusMonths(6))); - - action = new RdapDomainAction(); - action.clock = clock; - action.request = request; - action.requestMethod = Action.Method.GET; - action.fullServletPath = "https://example.com/rdap"; - action.response = response; - action.registrarParam = Optional.empty(); - action.includeDeletedParam = Optional.empty(); - action.formatOutputParam = Optional.empty(); - action.rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter(); - action.rdapWhoisServer = null; - action.sessionUtils = sessionUtils; - action.authResult = AuthResult.create(AuthLevel.USER, userAuthInfo); - action.rdapMetrics = rdapMetrics; - } - - private void login(String clientId) { - when(sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)).thenReturn(true); - when(sessionUtils.getRegistrarClientId(request)).thenReturn(clientId); - } - - private void loginAsAdmin() { - when(sessionUtils.checkRegistrarConsoleLogin(request, adminUserAuthInfo)).thenReturn(true); - when(sessionUtils.getRegistrarClientId(request)).thenReturn("irrelevant"); - action.authResult = AuthResult.create(AuthLevel.USER, adminUserAuthInfo); - } - - private Object generateActualJson(String domainName) { - action.requestPath = RdapDomainAction.PATH + domainName; - action.run(); - return JSONValue.parse(response.getPayload()); } private Object generateExpectedJson( @@ -397,7 +334,7 @@ public class RdapDomainActionTest { builder, false, RdapTestHelper.createNotices( - "https://example.com/rdap/", + "https://example.tld/rdap/", (contactRoids == null) ? RdapTestHelper.ContactNoticeType.DOMAIN : RdapTestHelper.ContactNoticeType.NONE, diff --git a/javatests/google/registry/rdap/RdapDomainSearchActionTest.java b/javatests/google/registry/rdap/RdapDomainSearchActionTest.java index b2bb88b84..94864f2cb 100644 --- a/javatests/google/registry/rdap/RdapDomainSearchActionTest.java +++ b/javatests/google/registry/rdap/RdapDomainSearchActionTest.java @@ -16,8 +16,6 @@ package google.registry.rdap; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; -import static google.registry.rdap.RdapAuthorization.Role.ADMINISTRATOR; -import static google.registry.rdap.RdapAuthorization.Role.REGISTRAR; import static google.registry.request.Action.Method.POST; import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.persistDomainAsDeleted; @@ -31,10 +29,7 @@ import static google.registry.testing.FullFieldsTestEntityHelper.makeHistoryEntr import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar; import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarContacts; import static google.registry.testing.TestDataHelper.loadFile; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import com.google.appengine.api.users.User; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; @@ -46,7 +41,6 @@ import google.registry.model.contact.ContactResource; import google.registry.model.domain.DomainResource; import google.registry.model.domain.Period; import google.registry.model.host.HostResource; -import google.registry.model.ofy.Ofy; import google.registry.model.registrar.Registrar; import google.registry.model.registry.Registry; import google.registry.model.reporting.HistoryEntry; @@ -54,15 +48,7 @@ import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.SearchType; import google.registry.rdap.RdapMetrics.WildcardType; import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; -import google.registry.request.Action; -import google.registry.request.auth.AuthLevel; -import google.registry.request.auth.AuthResult; -import google.registry.request.auth.UserAuthInfo; -import google.registry.testing.AppEngineRule; -import google.registry.testing.FakeClock; import google.registry.testing.FakeResponse; -import google.registry.testing.InjectRule; -import google.registry.ui.server.registrar.SessionUtils; import google.registry.util.Idn; import java.net.IDN; import java.net.URLDecoder; @@ -71,35 +57,22 @@ import java.util.List; import java.util.Map; import java.util.Optional; import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import org.joda.time.DateTime; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.JSONValue; import org.junit.Before; import org.junit.Ignore; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Unit tests for {@link RdapDomainSearchAction}. */ @RunWith(JUnit4.class) -public class RdapDomainSearchActionTest extends RdapSearchActionTestCase { +public class RdapDomainSearchActionTest extends RdapSearchActionTestCase { - @Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build(); - - @Rule public final InjectRule inject = new InjectRule(); - - private final HttpServletRequest request = mock(HttpServletRequest.class); - private final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01T00:00:00Z")); - private final SessionUtils sessionUtils = mock(SessionUtils.class); - private final User user = new User("rdap.user@example.com", "gmail.com", "12345"); - private final UserAuthInfo userAuthInfo = UserAuthInfo.create(user, false); - private final UserAuthInfo adminUserAuthInfo = UserAuthInfo.create(user, true); - private final RdapDomainSearchAction action = new RdapDomainSearchAction(); - - private FakeResponse response = new FakeResponse(); + public RdapDomainSearchActionTest() { + super(RdapDomainSearchAction.class, RdapDomainSearchAction.PATH); + } private Registrar registrar; private DomainResource domainCatLol; @@ -124,6 +97,7 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase { private Object generateActualJson( RequestType requestType, String paramValue, String cursor) { action.requestPath = RdapDomainSearchAction.PATH; + action.requestMethod = POST; String requestTypeParam = null; switch (requestType) { case NAME: @@ -173,7 +147,6 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase { @Before public void setUp() { RdapDomainSearchAction.maxNameserversInFirstStage = 40; - inject.setStaticField(Ofy.class, "clock", clock); // cat.lol and cat2.lol createTld("lol"); @@ -387,37 +360,7 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase { "created", clock.nowUtc())); - action.clock = clock; - action.request = request; - action.requestMethod = Action.Method.GET; - action.fullServletPath = "https://example.com/rdap"; - action.requestUrl = "https://example.com/rdap/domains"; - action.parameterMap = ImmutableListMultimap.of(); action.requestMethod = POST; - action.response = response; - action.registrarParam = Optional.empty(); - action.includeDeletedParam = Optional.empty(); - action.formatOutputParam = Optional.empty(); - action.rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter(); - action.rdapWhoisServer = null; - action.sessionUtils = sessionUtils; - action.authResult = AuthResult.create(AuthLevel.USER, userAuthInfo); - action.rdapMetrics = rdapMetrics; - action.cursorTokenParam = Optional.empty(); - action.rdapResultSetMaxSize = 4; - } - - private void login(String clientId) { - when(sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)).thenReturn(true); - when(sessionUtils.getRegistrarClientId(request)).thenReturn(clientId); - metricRole = REGISTRAR; - } - - private void loginAsAdmin() { - when(sessionUtils.checkRegistrarConsoleLogin(request, adminUserAuthInfo)).thenReturn(true); - when(sessionUtils.getRegistrarClientId(request)).thenReturn("irrelevant"); - action.authResult = AuthResult.create(AuthLevel.USER, adminUserAuthInfo); - metricRole = ADMINISTRATOR; } private Object generateExpectedJsonForTwoDomains() { @@ -558,7 +501,7 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase { builder.put("domainSearchResults", ImmutableList.of(obj)); builder.put("rdapConformance", ImmutableList.of("rdap_level_0")); RdapTestHelper.addDomainBoilerplateNotices( - builder, RdapTestHelper.createNotices("https://example.com/rdap/")); + builder, RdapTestHelper.createNotices("https://example.tld/rdap/")); return new JSONObject(builder.build()); } diff --git a/javatests/google/registry/rdap/RdapEntityActionTest.java b/javatests/google/registry/rdap/RdapEntityActionTest.java index ae5ef267d..44835b21b 100644 --- a/javatests/google/registry/rdap/RdapEntityActionTest.java +++ b/javatests/google/registry/rdap/RdapEntityActionTest.java @@ -25,65 +25,35 @@ import static google.registry.testing.FullFieldsTestEntityHelper.makeHostResourc import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar; import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarContacts; import static google.registry.testing.TestDataHelper.loadFile; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import com.google.appengine.api.users.User; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import google.registry.model.contact.ContactResource; import google.registry.model.host.HostResource; -import google.registry.model.ofy.Ofy; import google.registry.model.registrar.Registrar; import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.SearchType; import google.registry.rdap.RdapMetrics.WildcardType; import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; import google.registry.request.Action; -import google.registry.request.auth.AuthLevel; -import google.registry.request.auth.AuthResult; -import google.registry.request.auth.UserAuthInfo; -import google.registry.testing.AppEngineRule; -import google.registry.testing.FakeClock; -import google.registry.testing.FakeResponse; -import google.registry.testing.InjectRule; -import google.registry.ui.server.registrar.SessionUtils; import java.util.Map; import java.util.Optional; import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import org.joda.time.DateTime; import org.json.simple.JSONValue; 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 RdapEntityAction}. */ @RunWith(JUnit4.class) -public class RdapEntityActionTest { +public class RdapEntityActionTest extends RdapActionBaseTestCase { - @Rule - public final AppEngineRule appEngine = AppEngineRule.builder() - .withDatastore() - .build(); - - @Rule - public final InjectRule inject = new InjectRule(); - - private final HttpServletRequest request = mock(HttpServletRequest.class); - private final FakeResponse response = new FakeResponse(); - private final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01TZ")); - private final SessionUtils sessionUtils = mock(SessionUtils.class); - private final User user = new User("rdap.user@example.com", "gmail.com", "12345"); - private final UserAuthInfo userAuthInfo = UserAuthInfo.create(user, false); - private final UserAuthInfo adminUserAuthInfo = UserAuthInfo.create(user, true); - private final RdapMetrics rdapMetrics = mock(RdapMetrics.class); - - private RdapEntityAction action; + public RdapEntityActionTest() { + super(RdapEntityAction.class, RdapEntityAction.PATH); + } private Registrar registrarLol; private ContactResource registrant; @@ -94,7 +64,6 @@ public class RdapEntityActionTest { @Before public void setUp() { - inject.setStaticField(Ofy.class, "clock", clock); // lol createTld("lol"); registrarLol = persistResource(makeRegistrar( @@ -161,37 +130,6 @@ public class RdapEntityActionTest { clock.nowUtc().minusYears(1), registrarLol, clock.nowUtc().minusMonths(6)); - action = new RdapEntityAction(); - action.clock = clock; - action.request = request; - action.requestMethod = Action.Method.GET; - action.fullServletPath = "https://example.com/rdap"; - action.response = response; - action.registrarParam = Optional.empty(); - action.includeDeletedParam = Optional.empty(); - action.formatOutputParam = Optional.empty(); - action.rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter(); - action.rdapWhoisServer = null; - action.sessionUtils = sessionUtils; - action.authResult = AuthResult.create(AuthLevel.USER, userAuthInfo); - action.rdapMetrics = rdapMetrics; - } - - private void login(String registrar) { - when(sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)).thenReturn(true); - when(sessionUtils.getRegistrarClientId(request)).thenReturn(registrar); - } - - private void loginAsAdmin() { - action.authResult = AuthResult.create(AuthLevel.USER, adminUserAuthInfo); - when(sessionUtils.checkRegistrarConsoleLogin(request, adminUserAuthInfo)).thenReturn(true); - when(sessionUtils.getRegistrarClientId(request)).thenReturn("irrelevant"); - } - - private Object generateActualJson(String name) { - action.requestPath = RdapEntityAction.PATH + name; - action.run(); - return JSONValue.parse(response.getPayload()); } private Object generateExpectedJson(String handle, String expectedOutputFile) { @@ -259,7 +197,7 @@ public class RdapEntityActionTest { RdapTestHelper.addNonDomainBoilerplateNotices( builder, RdapTestHelper.createNotices( - "https://example.com/rdap/", + "https://example.tld/rdap/", addNoPersonalDataRemark ? RdapTestHelper.ContactNoticeType.CONTACT : RdapTestHelper.ContactNoticeType.NONE, diff --git a/javatests/google/registry/rdap/RdapEntitySearchActionTest.java b/javatests/google/registry/rdap/RdapEntitySearchActionTest.java index b5c4a576e..da1f0889d 100644 --- a/javatests/google/registry/rdap/RdapEntitySearchActionTest.java +++ b/javatests/google/registry/rdap/RdapEntitySearchActionTest.java @@ -16,8 +16,6 @@ package google.registry.rdap; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; -import static google.registry.rdap.RdapAuthorization.Role.ADMINISTRATOR; -import static google.registry.rdap.RdapAuthorization.Role.REGISTRAR; import static google.registry.request.Action.Method.GET; import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.persistResource; @@ -30,68 +28,44 @@ import static google.registry.testing.FullFieldsTestEntityHelper.makeHistoryEntr import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar; import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarContacts; import static google.registry.testing.TestDataHelper.loadFile; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import com.google.appengine.api.users.User; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import google.registry.model.ImmutableObject; import google.registry.model.contact.ContactResource; -import google.registry.model.ofy.Ofy; import google.registry.model.registrar.Registrar; import google.registry.model.reporting.HistoryEntry; import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.SearchType; import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; -import google.registry.request.Action; -import google.registry.request.auth.AuthLevel; -import google.registry.request.auth.AuthResult; -import google.registry.request.auth.UserAuthInfo; -import google.registry.testing.AppEngineRule; -import google.registry.testing.FakeClock; import google.registry.testing.FakeResponse; -import google.registry.testing.InjectRule; -import google.registry.ui.server.registrar.SessionUtils; import java.net.URLDecoder; import java.util.List; import java.util.Map; import java.util.Optional; import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import org.joda.time.DateTime; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.JSONValue; 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 RdapEntitySearchAction}. */ @RunWith(JUnit4.class) -public class RdapEntitySearchActionTest extends RdapSearchActionTestCase { +public class RdapEntitySearchActionTest extends RdapSearchActionTestCase { - @Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build(); - @Rule public final InjectRule inject = new InjectRule(); + public RdapEntitySearchActionTest() { + super(RdapEntitySearchAction.class, RdapEntitySearchAction.PATH); + } private enum QueryType { FULL_NAME, HANDLE } - private final HttpServletRequest request = mock(HttpServletRequest.class); - private final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01T00:00:00Z")); - private final SessionUtils sessionUtils = mock(SessionUtils.class); - private final User user = new User("rdap.user@example.com", "gmail.com", "12345"); - private final UserAuthInfo userAuthInfo = UserAuthInfo.create(user, false); - private final UserAuthInfo adminUserAuthInfo = UserAuthInfo.create(user, true); - private final RdapEntitySearchAction action = new RdapEntitySearchAction(); - - private FakeResponse response = new FakeResponse(); - private Registrar registrarDeleted; private Registrar registrarInactive; private Registrar registrarTest; @@ -134,8 +108,6 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase { @Before public void setUp() { - inject.setStaticField(Ofy.class, "clock", clock); - createTld("tld"); // deleted @@ -181,40 +153,9 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase { registrarDeleted, clock.nowUtc().minusMonths(6)); - action.clock = clock; - action.request = request; - action.requestMethod = Action.Method.GET; - action.fullServletPath = "https://example.com/rdap"; - action.requestUrl = "https://example.com/rdap/entities"; - action.requestPath = RdapEntitySearchAction.PATH; - action.parameterMap = ImmutableListMultimap.of(); - action.response = response; - action.rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter(); - action.rdapResultSetMaxSize = 4; - action.rdapWhoisServer = null; action.fnParam = Optional.empty(); action.handleParam = Optional.empty(); action.subtypeParam = Optional.empty(); - action.registrarParam = Optional.empty(); - action.includeDeletedParam = Optional.empty(); - action.formatOutputParam = Optional.empty(); - action.sessionUtils = sessionUtils; - action.authResult = AuthResult.create(AuthLevel.USER, userAuthInfo); - action.rdapMetrics = rdapMetrics; - action.cursorTokenParam = Optional.empty(); - } - - private void login(String registrar) { - when(sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)).thenReturn(true); - when(sessionUtils.getRegistrarClientId(request)).thenReturn(registrar); - metricRole = REGISTRAR; - } - - private void loginAsAdmin() { - action.authResult = AuthResult.create(AuthLevel.USER, adminUserAuthInfo); - when(sessionUtils.checkRegistrarConsoleLogin(request, adminUserAuthInfo)).thenReturn(true); - when(sessionUtils.getRegistrarClientId(request)).thenReturn("noregistrar"); - metricRole = ADMINISTRATOR; } private Object generateExpectedJson(String expectedOutputFile) { @@ -266,7 +207,7 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase { builder.put("entitySearchResults", ImmutableList.of(obj)); builder.put("rdapConformance", ImmutableList.of("rdap_level_0")); RdapTestHelper.addNonDomainBoilerplateNotices( - builder, RdapTestHelper.createNotices("https://example.com/rdap/")); + builder, RdapTestHelper.createNotices("https://example.tld/rdap/")); return new JSONObject(builder.build()); } diff --git a/javatests/google/registry/rdap/RdapHelpActionTest.java b/javatests/google/registry/rdap/RdapHelpActionTest.java index e808daf04..7117d26e9 100644 --- a/javatests/google/registry/rdap/RdapHelpActionTest.java +++ b/javatests/google/registry/rdap/RdapHelpActionTest.java @@ -16,72 +16,22 @@ package google.registry.rdap; import static com.google.common.truth.Truth.assertThat; import static google.registry.testing.TestDataHelper.loadFile; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import com.google.appengine.api.users.User; import com.google.common.collect.ImmutableMap; -import google.registry.model.ofy.Ofy; import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.SearchType; import google.registry.rdap.RdapMetrics.WildcardType; import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; import google.registry.request.Action; -import google.registry.request.auth.AuthLevel; -import google.registry.request.auth.AuthResult; -import google.registry.request.auth.UserAuthInfo; -import google.registry.testing.FakeClock; -import google.registry.testing.FakeResponse; -import google.registry.testing.InjectRule; -import google.registry.ui.server.registrar.SessionUtils; -import java.util.Optional; -import org.joda.time.DateTime; import org.json.simple.JSONValue; -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 RdapHelpAction}. */ -@RunWith(JUnit4.class) -public class RdapHelpActionTest { +public class RdapHelpActionTest extends RdapActionBaseTestCase { - @Rule - public final InjectRule inject = new InjectRule(); - - private final FakeResponse response = new FakeResponse(); - private final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01TZ")); - private final SessionUtils sessionUtils = mock(SessionUtils.class); - private final User user = new User("rdap.user@example.com", "gmail.com", "12345"); - private final UserAuthInfo userAuthInfo = UserAuthInfo.create(user, false); - private final RdapMetrics rdapMetrics = mock(RdapMetrics.class); - - private RdapHelpAction action; - - @Before - public void setUp() { - inject.setStaticField(Ofy.class, "clock", clock); - - action = new RdapHelpAction(); - action.clock = clock; - action.fullServletPath = "https://example.tld/rdap"; - action.requestMethod = Action.Method.GET; - action.sessionUtils = sessionUtils; - action.authResult = AuthResult.create(AuthLevel.USER, userAuthInfo); - action.includeDeletedParam = Optional.empty(); - action.registrarParam = Optional.empty(); - action.formatOutputParam = Optional.empty(); - action.response = response; - action.rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter(); - action.rdapWhoisServer = null; - action.rdapMetrics = rdapMetrics; - } - - private Object generateActualJson(String helpPath) { - action.requestPath = RdapHelpAction.PATH + helpPath; - action.run(); - return JSONValue.parse(response.getPayload()); + public RdapHelpActionTest() { + super(RdapHelpAction.class, RdapHelpAction.PATH); } private Object generateExpectedJson(String name, String expectedOutputFile) { diff --git a/javatests/google/registry/rdap/RdapNameserverActionTest.java b/javatests/google/registry/rdap/RdapNameserverActionTest.java index 7a3196a0b..5f90fc1e0 100644 --- a/javatests/google/registry/rdap/RdapNameserverActionTest.java +++ b/javatests/google/registry/rdap/RdapNameserverActionTest.java @@ -20,65 +20,36 @@ import static google.registry.testing.DatastoreHelper.persistResource; import static google.registry.testing.FullFieldsTestEntityHelper.makeAndPersistHostResource; import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar; import static google.registry.testing.TestDataHelper.loadFile; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import com.google.appengine.api.users.User; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import google.registry.model.ofy.Ofy; import google.registry.model.registrar.Registrar; import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.SearchType; import google.registry.rdap.RdapMetrics.WildcardType; import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; import google.registry.request.Action; -import google.registry.request.auth.AuthLevel; -import google.registry.request.auth.AuthResult; -import google.registry.request.auth.UserAuthInfo; -import google.registry.testing.AppEngineRule; -import google.registry.testing.FakeClock; -import google.registry.testing.FakeResponse; -import google.registry.testing.InjectRule; -import google.registry.ui.server.registrar.SessionUtils; import java.util.Map; import java.util.Optional; import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import org.joda.time.DateTime; import org.json.simple.JSONValue; 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 RdapNameserverAction}. */ @RunWith(JUnit4.class) -public class RdapNameserverActionTest { +public class RdapNameserverActionTest extends RdapActionBaseTestCase { - @Rule - public final AppEngineRule appEngine = AppEngineRule.builder() - .withDatastore() - .build(); - - @Rule - public final InjectRule inject = new InjectRule(); - - private final HttpServletRequest request = mock(HttpServletRequest.class); - private final FakeResponse response = new FakeResponse(); - private final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01TZ")); - private final SessionUtils sessionUtils = mock(SessionUtils.class); - private final User user = new User("rdap.user@example.com", "gmail.com", "12345"); - private final UserAuthInfo userAuthInfo = UserAuthInfo.create(user, false); - private final UserAuthInfo adminUserAuthInfo = UserAuthInfo.create(user, true); - private final RdapMetrics rdapMetrics = mock(RdapMetrics.class); + public RdapNameserverActionTest() { + super(RdapNameserverAction.class, RdapNameserverAction.PATH); + } @Before public void setUp() { - inject.setStaticField(Ofy.class, "clock", clock); // normal createTld("lol"); makeAndPersistHostResource( @@ -104,45 +75,6 @@ public class RdapNameserverActionTest { makeAndPersistHostResource("ns1.domain.external", "9.10.11.12", clock.nowUtc().minusYears(1)); } - private RdapNameserverAction newRdapNameserverAction( - String input, Optional desiredRegistrar, Optional includeDeleted) { - return newRdapNameserverAction( - input, desiredRegistrar, includeDeleted, AuthResult.create(AuthLevel.USER, userAuthInfo)); - } - - private RdapNameserverAction newRdapNameserverAction( - String input, - Optional desiredRegistrar, - Optional includeDeleted, - AuthResult authResult) { - RdapNameserverAction action = new RdapNameserverAction(); - action.clock = clock; - action.request = request; - action.requestMethod = Action.Method.GET; - action.fullServletPath = "https://example.tld/rdap"; - action.response = response; - action.requestPath = RdapNameserverAction.PATH.concat(input); - action.registrarParam = desiredRegistrar; - action.includeDeletedParam = includeDeleted; - action.formatOutputParam = Optional.empty(); - action.rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter(); - action.rdapWhoisServer = null; - action.authResult = authResult; - action.sessionUtils = sessionUtils; - action.rdapMetrics = rdapMetrics; - return action; - } - - private Object generateActualJson(String name) { - return generateActualJson(name, Optional.empty(), Optional.empty()); - } - - private Object generateActualJson( - String name, Optional desiredRegistrar, Optional includeDeleted) { - newRdapNameserverAction(name, desiredRegistrar, includeDeleted).run(); - return JSONValue.parse(response.getPayload()); - } - private Object generateExpectedJson( String name, @Nullable ImmutableMap otherSubstitutions, @@ -323,9 +255,8 @@ public class RdapNameserverActionTest { @Test public void testNameserver_found_sameRegistrarRequested() { - assertThat( - generateActualJson( - "ns1.cat.lol", Optional.of("TheRegistrar"), Optional.empty())) + action.registrarParam = Optional.of("TheRegistrar"); + assertThat(generateActualJson("ns1.cat.lol")) .isEqualTo( generateExpectedJsonWithTopLevelEntries( "ns1.cat.lol", @@ -340,7 +271,9 @@ public class RdapNameserverActionTest { @Test public void testNameserver_notFound_differentRegistrarRequested() { - generateActualJson("ns1.cat.lol", Optional.of("otherregistrar"), Optional.of(false)); + action.registrarParam = Optional.of("otherregistrar"); + action.includeDeletedParam = Optional.of(false); + generateActualJson("ns1.cat.lol"); assertThat(response.getStatus()).isEqualTo(404); } @@ -352,31 +285,32 @@ public class RdapNameserverActionTest { @Test public void testDeletedNameserver_notFound_includeDeletedSetFalse() { - generateActualJson("nsdeleted.cat.lol", Optional.empty(), Optional.of(false)); + action.includeDeletedParam = Optional.of(false); + generateActualJson("nsdeleted.cat.lol"); assertThat(response.getStatus()).isEqualTo(404); } @Test public void testDeletedNameserver_notFound_notLoggedIn() { - when(sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)).thenReturn(false); - generateActualJson("nsdeleted.cat.lol", Optional.empty(), Optional.of(true)); + logout(); + action.includeDeletedParam = Optional.of(true); + generateActualJson("nsdeleted.cat.lol"); assertThat(response.getStatus()).isEqualTo(404); } @Test public void testDeletedNameserver_notFound_loggedInAsDifferentRegistrar() { - when(sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)).thenReturn(true); - when(sessionUtils.getRegistrarClientId(request)).thenReturn("otherregistrar"); - generateActualJson("nsdeleted.cat.lol", Optional.empty(), Optional.of(true)); + login("otherregistrar"); + action.includeDeletedParam = Optional.of(true); + generateActualJson("nsdeleted.cat.lol"); assertThat(response.getStatus()).isEqualTo(404); } @Test public void testDeletedNameserver_found_loggedInAsCorrectRegistrar() { - when(sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)).thenReturn(true); - when(sessionUtils.getRegistrarClientId(request)).thenReturn("TheRegistrar"); - assertThat( - generateActualJson("nsdeleted.cat.lol", Optional.empty(), Optional.of(true))) + login("TheRegistrar"); + action.includeDeletedParam = Optional.of(true); + assertThat(generateActualJson("nsdeleted.cat.lol")) .isEqualTo( generateExpectedJsonWithTopLevelEntries( "nsdeleted.cat.lol", @@ -391,15 +325,9 @@ public class RdapNameserverActionTest { @Test public void testDeletedNameserver_found_loggedInAsAdmin() { - when(sessionUtils.checkRegistrarConsoleLogin(request, adminUserAuthInfo)).thenReturn(true); - when(sessionUtils.getRegistrarClientId(request)).thenReturn("irrelevant"); - newRdapNameserverAction( - "nsdeleted.cat.lol", - Optional.empty(), - Optional.of(true), - AuthResult.create(AuthLevel.USER, adminUserAuthInfo)) - .run(); - assertThat(JSONValue.parse(response.getPayload())) + loginAsAdmin(); + action.includeDeletedParam = Optional.of(true); + assertThat(generateActualJson("nsdeleted.cat.lol")) .isEqualTo( generateExpectedJsonWithTopLevelEntries( "nsdeleted.cat.lol", @@ -414,10 +342,10 @@ public class RdapNameserverActionTest { @Test public void testDeletedNameserver_found_sameRegistrarRequested() { - when(sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)).thenReturn(true); - when(sessionUtils.getRegistrarClientId(request)).thenReturn("TheRegistrar"); - assertThat( - generateActualJson("nsdeleted.cat.lol", Optional.of("TheRegistrar"), Optional.of(true))) + login("TheRegistrar"); + action.registrarParam = Optional.of("TheRegistrar"); + action.includeDeletedParam = Optional.of(true); + assertThat(generateActualJson("nsdeleted.cat.lol")) .isEqualTo( generateExpectedJsonWithTopLevelEntries( "nsdeleted.cat.lol", @@ -432,9 +360,10 @@ public class RdapNameserverActionTest { @Test public void testDeletedNameserver_notFound_differentRegistrarRequested() { - when(sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)).thenReturn(true); - when(sessionUtils.getRegistrarClientId(request)).thenReturn("TheRegistrar"); - generateActualJson("ns1.cat.lol", Optional.of("otherregistrar"), Optional.of(false)); + login("TheRegistrar"); + action.registrarParam = Optional.of("otherregistrar"); + action.includeDeletedParam = Optional.of(false); + generateActualJson("ns1.cat.lol"); assertThat(response.getStatus()).isEqualTo(404); } diff --git a/javatests/google/registry/rdap/RdapNameserverSearchActionTest.java b/javatests/google/registry/rdap/RdapNameserverSearchActionTest.java index 2b477e6bd..2ca9be4a7 100644 --- a/javatests/google/registry/rdap/RdapNameserverSearchActionTest.java +++ b/javatests/google/registry/rdap/RdapNameserverSearchActionTest.java @@ -15,8 +15,6 @@ package google.registry.rdap; import static com.google.common.truth.Truth.assertThat; -import static google.registry.rdap.RdapAuthorization.Role.ADMINISTRATOR; -import static google.registry.rdap.RdapAuthorization.Role.REGISTRAR; import static google.registry.request.Action.Method.GET; import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.persistResource; @@ -29,10 +27,7 @@ import static google.registry.testing.FullFieldsTestEntityHelper.makeHostResourc import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar; import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarContacts; import static google.registry.testing.TestDataHelper.loadFile; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import com.google.appengine.api.users.User; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; @@ -40,51 +35,31 @@ import com.google.common.collect.ImmutableSet; import com.googlecode.objectify.Key; import google.registry.model.domain.DomainResource; import google.registry.model.host.HostResource; -import google.registry.model.ofy.Ofy; import google.registry.model.registrar.Registrar; import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.SearchType; import google.registry.rdap.RdapMetrics.WildcardType; import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; -import google.registry.request.Action; -import google.registry.request.auth.AuthLevel; -import google.registry.request.auth.AuthResult; -import google.registry.request.auth.UserAuthInfo; -import google.registry.testing.AppEngineRule; -import google.registry.testing.FakeClock; import google.registry.testing.FakeResponse; -import google.registry.testing.InjectRule; -import google.registry.ui.server.registrar.SessionUtils; import java.net.URLDecoder; import java.util.Optional; import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import org.joda.time.DateTime; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.JSONValue; 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 RdapNameserverSearchAction}. */ @RunWith(JUnit4.class) -public class RdapNameserverSearchActionTest extends RdapSearchActionTestCase { +public class RdapNameserverSearchActionTest + extends RdapSearchActionTestCase { - @Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build(); - - @Rule public final InjectRule inject = new InjectRule(); - - private final HttpServletRequest request = mock(HttpServletRequest.class); - private FakeResponse response = new FakeResponse(); - private final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01T00:00:00Z")); - private final SessionUtils sessionUtils = mock(SessionUtils.class); - private final User user = new User("rdap.user@example.com", "gmail.com", "12345"); - private final UserAuthInfo userAuthInfo = UserAuthInfo.create(user, false); - private final UserAuthInfo adminUserAuthInfo = UserAuthInfo.create(user, true); - private final RdapNameserverSearchAction action = new RdapNameserverSearchAction(); + public RdapNameserverSearchActionTest() { + super(RdapNameserverSearchAction.class, RdapNameserverSearchAction.PATH); + } private DomainResource domainCatLol; private HostResource hostNs1CatLol; @@ -177,40 +152,8 @@ public class RdapNameserverSearchActionTest extends RdapSearchActionTestCase { persistResource( hostNs2CatLol.asBuilder().setSuperordinateDomain(Key.create(domainCatLol)).build()); - inject.setStaticField(Ofy.class, "clock", clock); - action.clock = clock; - action.fullServletPath = "https://example.tld/rdap"; - action.requestUrl = "https://example.tld/rdap/nameservers"; - action.requestPath = RdapNameserverSearchAction.PATH; - action.parameterMap = ImmutableListMultimap.of(); - action.request = request; - action.requestMethod = Action.Method.GET; - action.response = response; - action.rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter(); - action.rdapResultSetMaxSize = 4; - action.rdapWhoisServer = null; action.ipParam = Optional.empty(); action.nameParam = Optional.empty(); - action.registrarParam = Optional.empty(); - action.includeDeletedParam = Optional.empty(); - action.formatOutputParam = Optional.empty(); - action.authResult = AuthResult.create(AuthLevel.USER, userAuthInfo); - action.sessionUtils = sessionUtils; - action.rdapMetrics = rdapMetrics; - action.cursorTokenParam = Optional.empty(); - } - - private void login(String clientId) { - when(sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)).thenReturn(true); - when(sessionUtils.getRegistrarClientId(request)).thenReturn(clientId); - metricRole = REGISTRAR; - } - - private void loginAsAdmin() { - when(sessionUtils.checkRegistrarConsoleLogin(request, adminUserAuthInfo)).thenReturn(true); - when(sessionUtils.getRegistrarClientId(request)).thenReturn("irrelevant"); - action.authResult = AuthResult.create(AuthLevel.USER, adminUserAuthInfo); - metricRole = ADMINISTRATOR; } private Object generateExpectedJson(String expectedOutputFile) { diff --git a/javatests/google/registry/rdap/RdapSearchActionTestCase.java b/javatests/google/registry/rdap/RdapSearchActionTestCase.java index 098abf639..eaa19eca8 100644 --- a/javatests/google/registry/rdap/RdapSearchActionTestCase.java +++ b/javatests/google/registry/rdap/RdapSearchActionTestCase.java @@ -14,26 +14,39 @@ package google.registry.rdap; -import static google.registry.rdap.RdapAuthorization.Role.PUBLIC; import static javax.servlet.http.HttpServletResponse.SC_OK; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import com.google.common.collect.ImmutableListMultimap; import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.SearchType; import google.registry.rdap.RdapMetrics.WildcardType; import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; import google.registry.request.Action; import java.util.Optional; +import org.junit.Before; -public class RdapSearchActionTestCase { +/** Common unit test code for actions inheriting {@link RdapSearchActionBase}. */ +public class RdapSearchActionTestCase + extends RdapActionBaseTestCase { + + protected RdapSearchActionTestCase(Class rdapActionClass, String path) { + super(rdapActionClass, path); + } - RdapAuthorization.Role metricRole = PUBLIC; SearchType metricSearchType = SearchType.NONE; WildcardType metricWildcardType = WildcardType.INVALID; int metricPrefixLength = 0; int metricStatusCode = SC_OK; - final RdapMetrics rdapMetrics = mock(RdapMetrics.class); + + @Before + public void initRdapSearchActionTestCase() { + action.parameterMap = ImmutableListMultimap.of(); + action.cursorTokenParam = Optional.empty(); + action.rdapResultSetMaxSize = 4; + action.requestUrl = "https://example.tld" + actionPath; + action.requestPath = actionPath; + } void rememberWildcardType(String queryString) { try { diff --git a/javatests/google/registry/rdap/testdata/rdap_associated_contact.json b/javatests/google/registry/rdap/testdata/rdap_associated_contact.json index fcb9805d3..272663dd0 100644 --- a/javatests/google/registry/rdap/testdata/rdap_associated_contact.json +++ b/javatests/google/registry/rdap/testdata/rdap_associated_contact.json @@ -5,9 +5,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/%NAME%", + "value" : "https://example.tld/rdap/entity/%NAME%", "rel" : "self", - "href": "https://example.com/rdap/entity/%NAME%", + "href": "https://example.tld/rdap/entity/%NAME%", "type" : "application/rdap+json" } ], diff --git a/javatests/google/registry/rdap/testdata/rdap_associated_contact_no_personal_data.json b/javatests/google/registry/rdap/testdata/rdap_associated_contact_no_personal_data.json index 09efb1f90..797433221 100644 --- a/javatests/google/registry/rdap/testdata/rdap_associated_contact_no_personal_data.json +++ b/javatests/google/registry/rdap/testdata/rdap_associated_contact_no_personal_data.json @@ -5,9 +5,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/%NAME%", + "value" : "https://example.tld/rdap/entity/%NAME%", "rel" : "self", - "href": "https://example.com/rdap/entity/%NAME%", + "href": "https://example.tld/rdap/entity/%NAME%", "type" : "application/rdap+json" } ], diff --git a/javatests/google/registry/rdap/testdata/rdap_contact.json b/javatests/google/registry/rdap/testdata/rdap_contact.json index ae00985a0..4126d80bf 100644 --- a/javatests/google/registry/rdap/testdata/rdap_contact.json +++ b/javatests/google/registry/rdap/testdata/rdap_contact.json @@ -5,9 +5,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/%NAME%", + "value" : "https://example.tld/rdap/entity/%NAME%", "rel" : "self", - "href": "https://example.com/rdap/entity/%NAME%", + "href": "https://example.tld/rdap/entity/%NAME%", "type" : "application/rdap+json" } ], diff --git a/javatests/google/registry/rdap/testdata/rdap_contact_deleted.json b/javatests/google/registry/rdap/testdata/rdap_contact_deleted.json index 2e4072474..3dba0de55 100644 --- a/javatests/google/registry/rdap/testdata/rdap_contact_deleted.json +++ b/javatests/google/registry/rdap/testdata/rdap_contact_deleted.json @@ -5,9 +5,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/%NAME%", + "value" : "https://example.tld/rdap/entity/%NAME%", "rel" : "self", - "href": "https://example.com/rdap/entity/%NAME%", + "href": "https://example.tld/rdap/entity/%NAME%", "type" : "application/rdap+json" } ], diff --git a/javatests/google/registry/rdap/testdata/rdap_contact_no_personal_data_with_remark.json b/javatests/google/registry/rdap/testdata/rdap_contact_no_personal_data_with_remark.json index 99be4fe79..fc5d60088 100644 --- a/javatests/google/registry/rdap/testdata/rdap_contact_no_personal_data_with_remark.json +++ b/javatests/google/registry/rdap/testdata/rdap_contact_no_personal_data_with_remark.json @@ -5,9 +5,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/%NAME%", + "value" : "https://example.tld/rdap/entity/%NAME%", "rel" : "self", - "href": "https://example.com/rdap/entity/%NAME%", + "href": "https://example.tld/rdap/entity/%NAME%", "type" : "application/rdap+json" } ], diff --git a/javatests/google/registry/rdap/testdata/rdap_domain.json b/javatests/google/registry/rdap/testdata/rdap_domain.json index b02e3f86e..f2aa4b6cc 100644 --- a/javatests/google/registry/rdap/testdata/rdap_domain.json +++ b/javatests/google/registry/rdap/testdata/rdap_domain.json @@ -8,10 +8,10 @@ "handle": "%HANDLE%", "links": [ { - "href": "https://example.com/rdap/domain/%NAME%", + "href": "https://example.tld/rdap/domain/%NAME%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%NAME%" + "value": "https://example.tld/rdap/domain/%NAME%" } ], "events": [ @@ -42,10 +42,10 @@ "handle": "%NAMESERVER1ROID%", "links": [ { - "href": "https://example.com/rdap/nameserver/%NAMESERVER1NAME%", + "href": "https://example.tld/rdap/nameserver/%NAMESERVER1NAME%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/nameserver/%NAMESERVER1NAME%" + "value": "https://example.tld/rdap/nameserver/%NAMESERVER1NAME%" } ], "ldhName": "%NAMESERVER1NAME%", @@ -74,10 +74,10 @@ "roles": [ "registrar" ], "links": [ { - "href": "https://example.com/rdap/entity/1", + "href": "https://example.tld/rdap/entity/1", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/1" + "value": "https://example.tld/rdap/entity/1" } ], "vcardArray" : [ @@ -129,10 +129,10 @@ "handle": "%NAMESERVER2ROID%", "links": [ { - "href": "https://example.com/rdap/nameserver/%NAMESERVER2NAME%", + "href": "https://example.tld/rdap/nameserver/%NAMESERVER2NAME%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/nameserver/%NAMESERVER2NAME%" + "value": "https://example.tld/rdap/nameserver/%NAMESERVER2NAME%" } ], "ldhName": "%NAMESERVER2NAME%", @@ -161,10 +161,10 @@ "roles": [ "registrar" ], "links": [ { - "href": "https://example.com/rdap/entity/1", + "href": "https://example.tld/rdap/entity/1", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/1" + "value": "https://example.tld/rdap/entity/1" } ], "vcardArray" : [ @@ -222,10 +222,10 @@ ], "links": [ { - "href": "https://example.com/rdap/entity/%CONTACT1ROID%", + "href": "https://example.tld/rdap/entity/%CONTACT1ROID%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/%CONTACT1ROID%" + "value": "https://example.tld/rdap/entity/%CONTACT1ROID%" } ], "events": [ @@ -315,10 +315,10 @@ ], "links": [ { - "href": "https://example.com/rdap/entity/%CONTACT2ROID%", + "href": "https://example.tld/rdap/entity/%CONTACT2ROID%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/%CONTACT2ROID%" + "value": "https://example.tld/rdap/entity/%CONTACT2ROID%" } ], "events": [ @@ -408,10 +408,10 @@ ], "links": [ { - "href": "https://example.com/rdap/entity/%CONTACT3ROID%", + "href": "https://example.tld/rdap/entity/%CONTACT3ROID%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/%CONTACT3ROID%" + "value": "https://example.tld/rdap/entity/%CONTACT3ROID%" } ], "events": [ @@ -497,10 +497,10 @@ "roles": [ "registrar" ], "links": [ { - "href": "https://example.com/rdap/entity/1", + "href": "https://example.tld/rdap/entity/1", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/1" + "value": "https://example.tld/rdap/entity/1" } ], "vcardArray" : [ diff --git a/javatests/google/registry/rdap/testdata/rdap_domain_cat2.json b/javatests/google/registry/rdap/testdata/rdap_domain_cat2.json index f8c03c622..5999d4bc0 100644 --- a/javatests/google/registry/rdap/testdata/rdap_domain_cat2.json +++ b/javatests/google/registry/rdap/testdata/rdap_domain_cat2.json @@ -8,10 +8,10 @@ "handle": "%HANDLE%", "links": [ { - "href": "https://example.com/rdap/domain/%NAME%", + "href": "https://example.tld/rdap/domain/%NAME%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%NAME%" + "value": "https://example.tld/rdap/domain/%NAME%" } ], "events": [ @@ -42,10 +42,10 @@ "handle": "%NAMESERVER1ROID%", "links": [ { - "href": "https://example.com/rdap/nameserver/%NAMESERVER1NAME%", + "href": "https://example.tld/rdap/nameserver/%NAMESERVER1NAME%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/nameserver/%NAMESERVER1NAME%" + "value": "https://example.tld/rdap/nameserver/%NAMESERVER1NAME%" } ], "ldhName": "%NAMESERVER1NAME%", @@ -74,10 +74,10 @@ "roles": [ "registrar" ], "links": [ { - "href": "https://example.com/rdap/entity/1", + "href": "https://example.tld/rdap/entity/1", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/1" + "value": "https://example.tld/rdap/entity/1" } ], "vcardArray" : [ @@ -129,10 +129,10 @@ "handle": "%NAMESERVER2ROID%", "links": [ { - "href": "https://example.com/rdap/nameserver/%NAMESERVER2NAME%", + "href": "https://example.tld/rdap/nameserver/%NAMESERVER2NAME%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/nameserver/%NAMESERVER2NAME%" + "value": "https://example.tld/rdap/nameserver/%NAMESERVER2NAME%" } ], "ldhName": "%NAMESERVER2NAME%", @@ -161,10 +161,10 @@ "roles": [ "registrar" ], "links": [ { - "href": "https://example.com/rdap/entity/1", + "href": "https://example.tld/rdap/entity/1", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/1" + "value": "https://example.tld/rdap/entity/1" } ], "vcardArray" : [ @@ -222,10 +222,10 @@ ], "links": [ { - "href": "https://example.com/rdap/entity/%CONTACT1ROID%", + "href": "https://example.tld/rdap/entity/%CONTACT1ROID%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/%CONTACT1ROID%" + "value": "https://example.tld/rdap/entity/%CONTACT1ROID%" } ], "events": [ @@ -315,10 +315,10 @@ ], "links": [ { - "href": "https://example.com/rdap/entity/%CONTACT2ROID%", + "href": "https://example.tld/rdap/entity/%CONTACT2ROID%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/%CONTACT2ROID%" + "value": "https://example.tld/rdap/entity/%CONTACT2ROID%" } ], "events": [ @@ -408,10 +408,10 @@ ], "links": [ { - "href": "https://example.com/rdap/entity/%CONTACT3ROID%", + "href": "https://example.tld/rdap/entity/%CONTACT3ROID%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/%CONTACT3ROID%" + "value": "https://example.tld/rdap/entity/%CONTACT3ROID%" } ], "events": [ @@ -497,10 +497,10 @@ "roles": [ "registrar" ], "links": [ { - "href": "https://example.com/rdap/entity/1", + "href": "https://example.tld/rdap/entity/1", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/1" + "value": "https://example.tld/rdap/entity/1" } ], "vcardArray" : [ diff --git a/javatests/google/registry/rdap/testdata/rdap_domain_deleted.json b/javatests/google/registry/rdap/testdata/rdap_domain_deleted.json index d5827f9fb..0575d830c 100644 --- a/javatests/google/registry/rdap/testdata/rdap_domain_deleted.json +++ b/javatests/google/registry/rdap/testdata/rdap_domain_deleted.json @@ -9,10 +9,10 @@ "handle": "%HANDLE%", "links": [ { - "href": "https://example.com/rdap/domain/%NAME%", + "href": "https://example.tld/rdap/domain/%NAME%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%NAME%" + "value": "https://example.tld/rdap/domain/%NAME%" } ], "events": [ @@ -48,10 +48,10 @@ "handle": "%NAMESERVER1ROID%", "links": [ { - "href": "https://example.com/rdap/nameserver/%NAMESERVER1NAME%", + "href": "https://example.tld/rdap/nameserver/%NAMESERVER1NAME%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/nameserver/%NAMESERVER1NAME%" + "value": "https://example.tld/rdap/nameserver/%NAMESERVER1NAME%" } ], "ldhName": "%NAMESERVER1NAME%", @@ -80,10 +80,10 @@ "roles": [ "registrar" ], "links": [ { - "href": "https://example.com/rdap/entity/1", + "href": "https://example.tld/rdap/entity/1", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/1" + "value": "https://example.tld/rdap/entity/1" } ], "vcardArray" : [ @@ -134,10 +134,10 @@ "handle": "%NAMESERVER2ROID%", "links": [ { - "href": "https://example.com/rdap/nameserver/%NAMESERVER2NAME%", + "href": "https://example.tld/rdap/nameserver/%NAMESERVER2NAME%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/nameserver/%NAMESERVER2NAME%" + "value": "https://example.tld/rdap/nameserver/%NAMESERVER2NAME%" } ], "ldhName": "%NAMESERVER2NAME%", @@ -166,10 +166,10 @@ "roles": [ "registrar" ], "links": [ { - "href": "https://example.com/rdap/entity/1", + "href": "https://example.tld/rdap/entity/1", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/1" + "value": "https://example.tld/rdap/entity/1" } ], "vcardArray" : [ @@ -226,10 +226,10 @@ ], "links": [ { - "href": "https://example.com/rdap/entity/%CONTACT1ROID%", + "href": "https://example.tld/rdap/entity/%CONTACT1ROID%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/%CONTACT1ROID%" + "value": "https://example.tld/rdap/entity/%CONTACT1ROID%" } ], "events": [ @@ -318,10 +318,10 @@ ], "links": [ { - "href": "https://example.com/rdap/entity/%CONTACT2ROID%", + "href": "https://example.tld/rdap/entity/%CONTACT2ROID%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/%CONTACT2ROID%" + "value": "https://example.tld/rdap/entity/%CONTACT2ROID%" } ], "events": [ @@ -410,10 +410,10 @@ ], "links": [ { - "href": "https://example.com/rdap/entity/%CONTACT3ROID%", + "href": "https://example.tld/rdap/entity/%CONTACT3ROID%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/%CONTACT3ROID%" + "value": "https://example.tld/rdap/entity/%CONTACT3ROID%" } ], "events": [ @@ -499,10 +499,10 @@ "roles": [ "registrar" ], "links": [ { - "href": "https://example.com/rdap/entity/1", + "href": "https://example.tld/rdap/entity/1", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/1" + "value": "https://example.tld/rdap/entity/1" } ], "vcardArray" : [ diff --git a/javatests/google/registry/rdap/testdata/rdap_domain_no_contacts.json b/javatests/google/registry/rdap/testdata/rdap_domain_no_contacts.json index e8b0e4945..cfb1346c0 100644 --- a/javatests/google/registry/rdap/testdata/rdap_domain_no_contacts.json +++ b/javatests/google/registry/rdap/testdata/rdap_domain_no_contacts.json @@ -10,10 +10,10 @@ ], "links": [ { - "href": "https://example.com/rdap/domain/%NAME%", + "href": "https://example.tld/rdap/domain/%NAME%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%NAME%" + "value": "https://example.tld/rdap/domain/%NAME%" } ], "events": [ @@ -44,10 +44,10 @@ "handle": "%NAMESERVER1ROID%", "links": [ { - "href": "https://example.com/rdap/nameserver/%NAMESERVER1NAME%", + "href": "https://example.tld/rdap/nameserver/%NAMESERVER1NAME%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/nameserver/%NAMESERVER1NAME%" + "value": "https://example.tld/rdap/nameserver/%NAMESERVER1NAME%" } ], "ldhName": "%NAMESERVER1NAME%", @@ -76,10 +76,10 @@ "roles": [ "registrar" ], "links": [ { - "href": "https://example.com/rdap/entity/1", + "href": "https://example.tld/rdap/entity/1", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/1" + "value": "https://example.tld/rdap/entity/1" } ], "vcardArray" : [ @@ -131,10 +131,10 @@ "handle": "%NAMESERVER2ROID%", "links": [ { - "href": "https://example.com/rdap/nameserver/%NAMESERVER2NAME%", + "href": "https://example.tld/rdap/nameserver/%NAMESERVER2NAME%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/nameserver/%NAMESERVER2NAME%" + "value": "https://example.tld/rdap/nameserver/%NAMESERVER2NAME%" } ], "ldhName": "%NAMESERVER2NAME%", @@ -163,10 +163,10 @@ "roles": [ "registrar" ], "links": [ { - "href": "https://example.com/rdap/entity/1", + "href": "https://example.tld/rdap/entity/1", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/1" + "value": "https://example.tld/rdap/entity/1" } ], "vcardArray" : [ @@ -220,9 +220,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/1", + "value" : "https://example.tld/rdap/entity/1", "rel" : "self", - "href" : "https://example.com/rdap/entity/1", + "href" : "https://example.tld/rdap/entity/1", "type" : "application/rdap+json" } ], diff --git a/javatests/google/registry/rdap/testdata/rdap_domain_no_contacts_with_remark.json b/javatests/google/registry/rdap/testdata/rdap_domain_no_contacts_with_remark.json index 7ae7a4c71..e9cffacab 100644 --- a/javatests/google/registry/rdap/testdata/rdap_domain_no_contacts_with_remark.json +++ b/javatests/google/registry/rdap/testdata/rdap_domain_no_contacts_with_remark.json @@ -10,10 +10,10 @@ ], "links": [ { - "href": "https://example.com/rdap/domain/%NAME%", + "href": "https://example.tld/rdap/domain/%NAME%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%NAME%" + "value": "https://example.tld/rdap/domain/%NAME%" } ], "events": [ @@ -44,10 +44,10 @@ "handle": "%NAMESERVER1ROID%", "links": [ { - "href": "https://example.com/rdap/nameserver/%NAMESERVER1NAME%", + "href": "https://example.tld/rdap/nameserver/%NAMESERVER1NAME%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/nameserver/%NAMESERVER1NAME%" + "value": "https://example.tld/rdap/nameserver/%NAMESERVER1NAME%" } ], "ldhName": "%NAMESERVER1NAME%", @@ -76,10 +76,10 @@ "roles": [ "registrar" ], "links": [ { - "href": "https://example.com/rdap/entity/1", + "href": "https://example.tld/rdap/entity/1", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/1" + "value": "https://example.tld/rdap/entity/1" } ], "vcardArray" : [ @@ -131,10 +131,10 @@ "handle": "%NAMESERVER2ROID%", "links": [ { - "href": "https://example.com/rdap/nameserver/%NAMESERVER2NAME%", + "href": "https://example.tld/rdap/nameserver/%NAMESERVER2NAME%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/nameserver/%NAMESERVER2NAME%" + "value": "https://example.tld/rdap/nameserver/%NAMESERVER2NAME%" } ], "ldhName": "%NAMESERVER2NAME%", @@ -163,10 +163,10 @@ "roles": [ "registrar" ], "links": [ { - "href": "https://example.com/rdap/entity/1", + "href": "https://example.tld/rdap/entity/1", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/1" + "value": "https://example.tld/rdap/entity/1" } ], "vcardArray" : [ @@ -220,9 +220,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/1", + "value" : "https://example.tld/rdap/entity/1", "rel" : "self", - "href" : "https://example.com/rdap/entity/1", + "href" : "https://example.tld/rdap/entity/1", "type" : "application/rdap+json" } ], diff --git a/javatests/google/registry/rdap/testdata/rdap_domain_unicode.json b/javatests/google/registry/rdap/testdata/rdap_domain_unicode.json index 2d50cfaaa..1f228a31f 100644 --- a/javatests/google/registry/rdap/testdata/rdap_domain_unicode.json +++ b/javatests/google/registry/rdap/testdata/rdap_domain_unicode.json @@ -9,10 +9,10 @@ "handle": "%HANDLE%", "links": [ { - "href": "https://example.com/rdap/domain/%PUNYCODENAME%", + "href": "https://example.tld/rdap/domain/%PUNYCODENAME%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%PUNYCODENAME%" + "value": "https://example.tld/rdap/domain/%PUNYCODENAME%" } ], "events": [ @@ -44,11 +44,11 @@ "links": [ { "href": - "https://example.com/rdap/nameserver/%NAMESERVER1PUNYCODENAME%", + "https://example.tld/rdap/nameserver/%NAMESERVER1PUNYCODENAME%", "type": "application/rdap+json", "rel": "self", "value": - "https://example.com/rdap/nameserver/%NAMESERVER1PUNYCODENAME%" + "https://example.tld/rdap/nameserver/%NAMESERVER1PUNYCODENAME%" } ], "ldhName": "%NAMESERVER1PUNYCODENAME%", @@ -77,10 +77,10 @@ "roles": [ "registrar" ], "links": [ { - "href": "https://example.com/rdap/entity/1", + "href": "https://example.tld/rdap/entity/1", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/1" + "value": "https://example.tld/rdap/entity/1" } ], "vcardArray" : [ @@ -133,11 +133,11 @@ "links": [ { "href": - "https://example.com/rdap/nameserver/%NAMESERVER2PUNYCODENAME%", + "https://example.tld/rdap/nameserver/%NAMESERVER2PUNYCODENAME%", "type": "application/rdap+json", "rel": "self", "value": - "https://example.com/rdap/nameserver/%NAMESERVER2PUNYCODENAME%" + "https://example.tld/rdap/nameserver/%NAMESERVER2PUNYCODENAME%" } ], "ldhName": "%NAMESERVER2PUNYCODENAME%", @@ -166,10 +166,10 @@ "roles": [ "registrar" ], "links": [ { - "href": "https://example.com/rdap/entity/1", + "href": "https://example.tld/rdap/entity/1", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/1" + "value": "https://example.tld/rdap/entity/1" } ], "vcardArray" : [ @@ -227,10 +227,10 @@ ], "links": [ { - "href": "https://example.com/rdap/entity/%CONTACT1ROID%", + "href": "https://example.tld/rdap/entity/%CONTACT1ROID%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/%CONTACT1ROID%" + "value": "https://example.tld/rdap/entity/%CONTACT1ROID%" } ], "events": [ @@ -320,10 +320,10 @@ ], "links": [ { - "href": "https://example.com/rdap/entity/%CONTACT2ROID%", + "href": "https://example.tld/rdap/entity/%CONTACT2ROID%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/%CONTACT2ROID%" + "value": "https://example.tld/rdap/entity/%CONTACT2ROID%" } ], "events": [ @@ -413,10 +413,10 @@ ], "links": [ { - "href": "https://example.com/rdap/entity/%CONTACT3ROID%", + "href": "https://example.tld/rdap/entity/%CONTACT3ROID%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/%CONTACT3ROID%" + "value": "https://example.tld/rdap/entity/%CONTACT3ROID%" } ], "events": [ @@ -502,10 +502,10 @@ "roles": [ "registrar" ], "links": [ { - "href": "https://example.com/rdap/entity/1", + "href": "https://example.tld/rdap/entity/1", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/1" + "value": "https://example.tld/rdap/entity/1" } ], "vcardArray" : [ diff --git a/javatests/google/registry/rdap/testdata/rdap_domain_unicode_no_contacts_with_remark.json b/javatests/google/registry/rdap/testdata/rdap_domain_unicode_no_contacts_with_remark.json index 22ad10440..188eb77a1 100644 --- a/javatests/google/registry/rdap/testdata/rdap_domain_unicode_no_contacts_with_remark.json +++ b/javatests/google/registry/rdap/testdata/rdap_domain_unicode_no_contacts_with_remark.json @@ -11,10 +11,10 @@ ], "links": [ { - "href": "https://example.com/rdap/domain/%PUNYCODENAME%", + "href": "https://example.tld/rdap/domain/%PUNYCODENAME%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%PUNYCODENAME%" + "value": "https://example.tld/rdap/domain/%PUNYCODENAME%" } ], "events": [ @@ -45,10 +45,10 @@ "handle": "%NAMESERVER1ROID%", "links": [ { - "href": "https://example.com/rdap/nameserver/%NAMESERVER1PUNYCODENAME%", + "href": "https://example.tld/rdap/nameserver/%NAMESERVER1PUNYCODENAME%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/nameserver/%NAMESERVER1PUNYCODENAME%" + "value": "https://example.tld/rdap/nameserver/%NAMESERVER1PUNYCODENAME%" } ], "ldhName": "%NAMESERVER1PUNYCODENAME%", @@ -78,10 +78,10 @@ "roles": [ "registrar" ], "links": [ { - "href": "https://example.com/rdap/entity/1", + "href": "https://example.tld/rdap/entity/1", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/1" + "value": "https://example.tld/rdap/entity/1" } ], "vcardArray" : [ @@ -133,10 +133,10 @@ "handle": "%NAMESERVER2ROID%", "links": [ { - "href": "https://example.com/rdap/nameserver/%NAMESERVER2PUNYCODENAME%", + "href": "https://example.tld/rdap/nameserver/%NAMESERVER2PUNYCODENAME%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/nameserver/%NAMESERVER2PUNYCODENAME%" + "value": "https://example.tld/rdap/nameserver/%NAMESERVER2PUNYCODENAME%" } ], "ldhName": "%NAMESERVER2PUNYCODENAME%", @@ -166,10 +166,10 @@ "roles": [ "registrar" ], "links": [ { - "href": "https://example.com/rdap/entity/1", + "href": "https://example.tld/rdap/entity/1", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/entity/1" + "value": "https://example.tld/rdap/entity/1" } ], "vcardArray" : [ @@ -223,9 +223,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/1", + "value" : "https://example.tld/rdap/entity/1", "rel" : "self", - "href" : "https://example.com/rdap/entity/1", + "href" : "https://example.tld/rdap/entity/1", "type" : "application/rdap+json" } ], diff --git a/javatests/google/registry/rdap/testdata/rdap_domains_four_truncated.json b/javatests/google/registry/rdap/testdata/rdap_domains_four_truncated.json index c59d21c60..ae33f9ec8 100644 --- a/javatests/google/registry/rdap/testdata/rdap_domains_four_truncated.json +++ b/javatests/google/registry/rdap/testdata/rdap_domains_four_truncated.json @@ -10,10 +10,10 @@ "handle": "%DOMAINHANDLE1%", "links": [ { - "href": "https://example.com/rdap/domain/%DOMAINNAME1%", + "href": "https://example.tld/rdap/domain/%DOMAINNAME1%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%DOMAINNAME1%" + "value": "https://example.tld/rdap/domain/%DOMAINNAME1%" } ], "ldhName": "%DOMAINNAME1%", @@ -38,10 +38,10 @@ "handle": "%DOMAINHANDLE2%", "links": [ { - "href": "https://example.com/rdap/domain/%DOMAINNAME2%", + "href": "https://example.tld/rdap/domain/%DOMAINNAME2%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%DOMAINNAME2%" + "value": "https://example.tld/rdap/domain/%DOMAINNAME2%" } ], "ldhName": "%DOMAINNAME2%", @@ -66,10 +66,10 @@ "handle": "%DOMAINHANDLE3%", "links": [ { - "href": "https://example.com/rdap/domain/%DOMAINNAME3%", + "href": "https://example.tld/rdap/domain/%DOMAINNAME3%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%DOMAINNAME3%" + "value": "https://example.tld/rdap/domain/%DOMAINNAME3%" } ], "ldhName": "%DOMAINNAME3%", @@ -94,10 +94,10 @@ "handle": "%DOMAINHANDLE4%", "links": [ { - "href": "https://example.com/rdap/domain/%DOMAINNAME4%", + "href": "https://example.tld/rdap/domain/%DOMAINNAME4%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%DOMAINNAME4%" + "value": "https://example.tld/rdap/domain/%DOMAINNAME4%" } ], "ldhName": "%DOMAINNAME4%", @@ -134,7 +134,7 @@ { "type" : "application/rdap+json", "rel" : "next", - "href" : "https://example.com/rdap/domains?%NEXT_QUERY%" + "href" : "https://example.tld/rdap/domains?%NEXT_QUERY%" } ] }, @@ -156,7 +156,7 @@ "links" : [ { - "value" : "https://example.com/rdap/help/tos", + "value" : "https://example.tld/rdap/help/tos", "rel" : "alternate", "href" : "https://www.registry.tld/about/rdap/tos.html", "type" : "text/html" diff --git a/javatests/google/registry/rdap/testdata/rdap_domains_four_with_one_unicode.json b/javatests/google/registry/rdap/testdata/rdap_domains_four_with_one_unicode.json index 78da1f8d0..7284756c8 100644 --- a/javatests/google/registry/rdap/testdata/rdap_domains_four_with_one_unicode.json +++ b/javatests/google/registry/rdap/testdata/rdap_domains_four_with_one_unicode.json @@ -10,10 +10,10 @@ "handle": "%DOMAINHANDLE1%", "links": [ { - "href": "https://example.com/rdap/domain/%DOMAINNAME1%", + "href": "https://example.tld/rdap/domain/%DOMAINNAME1%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%DOMAINNAME1%" + "value": "https://example.tld/rdap/domain/%DOMAINNAME1%" } ], "ldhName": "%DOMAINNAME1%", @@ -38,10 +38,10 @@ "handle": "%DOMAINHANDLE2%", "links": [ { - "href": "https://example.com/rdap/domain/%DOMAINNAME2%", + "href": "https://example.tld/rdap/domain/%DOMAINNAME2%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%DOMAINNAME2%" + "value": "https://example.tld/rdap/domain/%DOMAINNAME2%" } ], "ldhName": "%DOMAINNAME2%", @@ -66,10 +66,10 @@ "handle": "%DOMAINHANDLE3%", "links": [ { - "href": "https://example.com/rdap/domain/%DOMAINNAME3%", + "href": "https://example.tld/rdap/domain/%DOMAINNAME3%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%DOMAINNAME3%" + "value": "https://example.tld/rdap/domain/%DOMAINNAME3%" } ], "ldhName": "%DOMAINNAME3%", @@ -94,10 +94,10 @@ "handle": "%DOMAINHANDLE4%", "links": [ { - "href": "https://example.com/rdap/domain/%DOMAINPUNYCODENAME4%", + "href": "https://example.tld/rdap/domain/%DOMAINPUNYCODENAME4%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%DOMAINPUNYCODENAME4%" + "value": "https://example.tld/rdap/domain/%DOMAINPUNYCODENAME4%" } ], "ldhName": "%DOMAINPUNYCODENAME4%", @@ -137,7 +137,7 @@ "links" : [ { - "value" : "https://example.com/rdap/help/tos", + "value" : "https://example.tld/rdap/help/tos", "rel" : "alternate", "href" : "https://www.registry.tld/about/rdap/tos.html", "type" : "text/html" diff --git a/javatests/google/registry/rdap/testdata/rdap_domains_four_with_one_unicode_truncated.json b/javatests/google/registry/rdap/testdata/rdap_domains_four_with_one_unicode_truncated.json index d1faa63d2..65456c053 100644 --- a/javatests/google/registry/rdap/testdata/rdap_domains_four_with_one_unicode_truncated.json +++ b/javatests/google/registry/rdap/testdata/rdap_domains_four_with_one_unicode_truncated.json @@ -10,10 +10,10 @@ "handle": "%DOMAINHANDLE1%", "links": [ { - "href": "https://example.com/rdap/domain/%DOMAINPUNYCODENAME1%", + "href": "https://example.tld/rdap/domain/%DOMAINPUNYCODENAME1%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%DOMAINPUNYCODENAME1%" + "value": "https://example.tld/rdap/domain/%DOMAINPUNYCODENAME1%" } ], "ldhName": "%DOMAINPUNYCODENAME1%", @@ -38,10 +38,10 @@ "handle": "%DOMAINHANDLE2%", "links": [ { - "href": "https://example.com/rdap/domain/%DOMAINPUNYCODENAME2%", + "href": "https://example.tld/rdap/domain/%DOMAINPUNYCODENAME2%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%DOMAINPUNYCODENAME2%" + "value": "https://example.tld/rdap/domain/%DOMAINPUNYCODENAME2%" } ], "ldhName": "%DOMAINPUNYCODENAME2%", @@ -66,10 +66,10 @@ "handle": "%DOMAINHANDLE3%", "links": [ { - "href": "https://example.com/rdap/domain/%DOMAINPUNYCODENAME3%", + "href": "https://example.tld/rdap/domain/%DOMAINPUNYCODENAME3%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%DOMAINPUNYCODENAME3%" + "value": "https://example.tld/rdap/domain/%DOMAINPUNYCODENAME3%" } ], "ldhName": "%DOMAINPUNYCODENAME3%", @@ -94,10 +94,10 @@ "handle": "%DOMAINHANDLE4%", "links": [ { - "href": "https://example.com/rdap/domain/%DOMAINPUNYCODENAME4%", + "href": "https://example.tld/rdap/domain/%DOMAINPUNYCODENAME4%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%DOMAINPUNYCODENAME4%" + "value": "https://example.tld/rdap/domain/%DOMAINPUNYCODENAME4%" } ], "ldhName": "%DOMAINPUNYCODENAME4%", @@ -135,7 +135,7 @@ { "type" : "application/rdap+json", "rel" : "next", - "href" : "https://example.com/rdap/domains?%NEXT_QUERY%" + "href" : "https://example.tld/rdap/domains?%NEXT_QUERY%" } ] }, @@ -157,7 +157,7 @@ "links" : [ { - "value" : "https://example.com/rdap/help/tos", + "value" : "https://example.tld/rdap/help/tos", "rel" : "alternate", "href" : "https://www.registry.tld/about/rdap/tos.html", "type" : "text/html" diff --git a/javatests/google/registry/rdap/testdata/rdap_domains_two.json b/javatests/google/registry/rdap/testdata/rdap_domains_two.json index f3f563358..17e4a8888 100644 --- a/javatests/google/registry/rdap/testdata/rdap_domains_two.json +++ b/javatests/google/registry/rdap/testdata/rdap_domains_two.json @@ -10,10 +10,10 @@ "handle": "%DOMAINHANDLE1%", "links": [ { - "href": "https://example.com/rdap/domain/%DOMAINNAME1%", + "href": "https://example.tld/rdap/domain/%DOMAINNAME1%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%DOMAINNAME1%" + "value": "https://example.tld/rdap/domain/%DOMAINNAME1%" } ], "ldhName": "%DOMAINNAME1%", @@ -38,10 +38,10 @@ "handle": "%DOMAINHANDLE2%", "links": [ { - "href": "https://example.com/rdap/domain/%DOMAINNAME2%", + "href": "https://example.tld/rdap/domain/%DOMAINNAME2%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%DOMAINNAME2%" + "value": "https://example.tld/rdap/domain/%DOMAINNAME2%" } ], "ldhName": "%DOMAINNAME2%", @@ -80,7 +80,7 @@ "links" : [ { - "value" : "https://example.com/rdap/help/tos", + "value" : "https://example.tld/rdap/help/tos", "rel" : "alternate", "href" : "https://www.registry.tld/about/rdap/tos.html", "type" : "text/html" diff --git a/javatests/google/registry/rdap/testdata/rdap_incomplete_domain_result_set.json b/javatests/google/registry/rdap/testdata/rdap_incomplete_domain_result_set.json index 63284469e..6d54897ea 100644 --- a/javatests/google/registry/rdap/testdata/rdap_incomplete_domain_result_set.json +++ b/javatests/google/registry/rdap/testdata/rdap_incomplete_domain_result_set.json @@ -7,8 +7,8 @@ [ { "rel":"self", - "href":"https://example.com/rdap/domain/%DOMAINNAME1%", - "value":"https://example.com/rdap/domain/%DOMAINNAME1%", + "href":"https://example.tld/rdap/domain/%DOMAINNAME1%", + "value":"https://example.tld/rdap/domain/%DOMAINNAME1%", "type":"application/rdap+json" } ], @@ -39,8 +39,8 @@ [ { "rel":"self", - "href":"https://example.com/rdap/domain/%DOMAINNAME2%", - "value":"https://example.com/rdap/domain/%DOMAINNAME2%", + "href":"https://example.tld/rdap/domain/%DOMAINNAME2%", + "value":"https://example.tld/rdap/domain/%DOMAINNAME2%", "type":"application/rdap+json" } ], @@ -71,8 +71,8 @@ [ { "rel":"self", - "href":"https://example.com/rdap/domain/%DOMAINNAME3%", - "value":"https://example.com/rdap/domain/%DOMAINNAME3%", + "href":"https://example.tld/rdap/domain/%DOMAINNAME3%", + "value":"https://example.tld/rdap/domain/%DOMAINNAME3%", "type":"application/rdap+json" } ], @@ -127,7 +127,7 @@ { "rel":"alternate", "href":"https://www.registry.tld/about/rdap/tos.html", - "value":"https://example.com/rdap/help/tos", + "value":"https://example.tld/rdap/help/tos", "type":"text/html" } ], diff --git a/javatests/google/registry/rdap/testdata/rdap_incomplete_domains.json b/javatests/google/registry/rdap/testdata/rdap_incomplete_domains.json index 95d4f55e6..4bbe9af83 100644 --- a/javatests/google/registry/rdap/testdata/rdap_incomplete_domains.json +++ b/javatests/google/registry/rdap/testdata/rdap_incomplete_domains.json @@ -20,10 +20,10 @@ "handle":"%DOMAINHANDLE1%", "links":[ { - "value":"https://example.com/rdap/domain/%DOMAINNAME1%", + "value":"https://example.tld/rdap/domain/%DOMAINNAME1%", "type":"application/rdap+json", "rel":"self", - "href":"https://example.com/rdap/domain/%DOMAINNAME1%" + "href":"https://example.tld/rdap/domain/%DOMAINNAME1%" } ], "objectClassName":"domain" @@ -48,10 +48,10 @@ "handle":"%DOMAINHANDLE2%", "links":[ { - "value":"https://example.com/rdap/domain/%DOMAINNAME2%", + "value":"https://example.tld/rdap/domain/%DOMAINNAME2%", "type":"application/rdap+json", "rel":"self", - "href":"https://example.com/rdap/domain/%DOMAINNAME2%" + "href":"https://example.tld/rdap/domain/%DOMAINNAME2%" } ], "objectClassName":"domain" @@ -81,7 +81,7 @@ ], "links":[ { - "value":"https://example.com/rdap/help/tos", + "value":"https://example.tld/rdap/help/tos", "type":"text/html", "rel":"alternate", "href":"https://www.registry.tld/about/rdap/tos.html" diff --git a/javatests/google/registry/rdap/testdata/rdap_multiple_contacts.json b/javatests/google/registry/rdap/testdata/rdap_multiple_contacts.json index fe20cdccc..d8272cfdb 100644 --- a/javatests/google/registry/rdap/testdata/rdap_multiple_contacts.json +++ b/javatests/google/registry/rdap/testdata/rdap_multiple_contacts.json @@ -8,9 +8,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/2-ROID", + "value" : "https://example.tld/rdap/entity/2-ROID", "rel" : "self", - "href": "https://example.com/rdap/entity/2-ROID", + "href": "https://example.tld/rdap/entity/2-ROID", "type" : "application/rdap+json" } ], @@ -55,9 +55,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/2-Registrar", + "value" : "https://example.tld/rdap/entity/2-Registrar", "rel" : "self", - "href": "https://example.com/rdap/entity/2-Registrar", + "href": "https://example.tld/rdap/entity/2-Registrar", "type" : "application/rdap+json" } ], @@ -122,7 +122,7 @@ "links" : [ { - "value" : "https://example.com/rdap/help/tos", + "value" : "https://example.tld/rdap/help/tos", "rel" : "alternate", "href" : "https://www.registry.tld/about/rdap/tos.html", "type" : "text/html" diff --git a/javatests/google/registry/rdap/testdata/rdap_multiple_contacts2.json b/javatests/google/registry/rdap/testdata/rdap_multiple_contacts2.json index 674a7c652..7ee99fdff 100644 --- a/javatests/google/registry/rdap/testdata/rdap_multiple_contacts2.json +++ b/javatests/google/registry/rdap/testdata/rdap_multiple_contacts2.json @@ -8,9 +8,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/4-ROID", + "value" : "https://example.tld/rdap/entity/4-ROID", "rel" : "self", - "href": "https://example.com/rdap/entity/4-ROID", + "href": "https://example.tld/rdap/entity/4-ROID", "type" : "application/rdap+json" } ], @@ -54,9 +54,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/2-ROID", + "value" : "https://example.tld/rdap/entity/2-ROID", "rel" : "self", - "href": "https://example.com/rdap/entity/2-ROID", + "href": "https://example.tld/rdap/entity/2-ROID", "type" : "application/rdap+json" } ], @@ -115,7 +115,7 @@ "links" : [ { - "value" : "https://example.com/rdap/help/tos", + "value" : "https://example.tld/rdap/help/tos", "rel" : "alternate", "href" : "https://www.registry.tld/about/rdap/tos.html", "type" : "text/html" diff --git a/javatests/google/registry/rdap/testdata/rdap_nontruncated_contacts.json b/javatests/google/registry/rdap/testdata/rdap_nontruncated_contacts.json index 00c643b1a..3b7b1dd2b 100644 --- a/javatests/google/registry/rdap/testdata/rdap_nontruncated_contacts.json +++ b/javatests/google/registry/rdap/testdata/rdap_nontruncated_contacts.json @@ -8,9 +8,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/0001-ROID", + "value" : "https://example.tld/rdap/entity/0001-ROID", "rel" : "self", - "href": "https://example.com/rdap/entity/0001-ROID", + "href": "https://example.tld/rdap/entity/0001-ROID", "type" : "application/rdap+json" } ], @@ -54,9 +54,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/0002-ROID", + "value" : "https://example.tld/rdap/entity/0002-ROID", "rel" : "self", - "href": "https://example.com/rdap/entity/0002-ROID", + "href": "https://example.tld/rdap/entity/0002-ROID", "type" : "application/rdap+json" } ], @@ -100,9 +100,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/0003-ROID", + "value" : "https://example.tld/rdap/entity/0003-ROID", "rel" : "self", - "href": "https://example.com/rdap/entity/0003-ROID", + "href": "https://example.tld/rdap/entity/0003-ROID", "type" : "application/rdap+json" } ], @@ -146,9 +146,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/0004-ROID", + "value" : "https://example.tld/rdap/entity/0004-ROID", "rel" : "self", - "href": "https://example.com/rdap/entity/0004-ROID", + "href": "https://example.tld/rdap/entity/0004-ROID", "type" : "application/rdap+json" } ], @@ -207,7 +207,7 @@ "links" : [ { - "value" : "https://example.com/rdap/help/tos", + "value" : "https://example.tld/rdap/help/tos", "rel" : "alternate", "href" : "https://www.registry.tld/about/rdap/tos.html", "type" : "text/html" diff --git a/javatests/google/registry/rdap/testdata/rdap_nontruncated_domains.json b/javatests/google/registry/rdap/testdata/rdap_nontruncated_domains.json index 71cfa3e82..beba670dc 100644 --- a/javatests/google/registry/rdap/testdata/rdap_nontruncated_domains.json +++ b/javatests/google/registry/rdap/testdata/rdap_nontruncated_domains.json @@ -10,10 +10,10 @@ "handle": "%DOMAINHANDLE1%", "links": [ { - "href": "https://example.com/rdap/domain/%DOMAINNAME1%", + "href": "https://example.tld/rdap/domain/%DOMAINNAME1%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%DOMAINNAME1%" + "value": "https://example.tld/rdap/domain/%DOMAINNAME1%" } ], "ldhName": "%DOMAINNAME1%", @@ -38,10 +38,10 @@ "handle": "%DOMAINHANDLE2%", "links": [ { - "href": "https://example.com/rdap/domain/%DOMAINNAME2%", + "href": "https://example.tld/rdap/domain/%DOMAINNAME2%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%DOMAINNAME2%" + "value": "https://example.tld/rdap/domain/%DOMAINNAME2%" } ], "ldhName": "%DOMAINNAME2%", @@ -66,10 +66,10 @@ "handle": "%DOMAINHANDLE3%", "links": [ { - "href": "https://example.com/rdap/domain/%DOMAINNAME3%", + "href": "https://example.tld/rdap/domain/%DOMAINNAME3%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%DOMAINNAME3%" + "value": "https://example.tld/rdap/domain/%DOMAINNAME3%" } ], "ldhName": "%DOMAINNAME3%", @@ -94,10 +94,10 @@ "handle": "%DOMAINHANDLE4%", "links": [ { - "href": "https://example.com/rdap/domain/%DOMAINNAME4%", + "href": "https://example.tld/rdap/domain/%DOMAINNAME4%", "type": "application/rdap+json", "rel": "self", - "value": "https://example.com/rdap/domain/%DOMAINNAME4%" + "value": "https://example.tld/rdap/domain/%DOMAINNAME4%" } ], "ldhName": "%DOMAINNAME4%", @@ -136,7 +136,7 @@ "links" : [ { - "value" : "https://example.com/rdap/help/tos", + "value" : "https://example.tld/rdap/help/tos", "rel" : "alternate", "href" : "https://www.registry.tld/about/rdap/tos.html", "type" : "text/html" diff --git a/javatests/google/registry/rdap/testdata/rdap_nontruncated_registrars.json b/javatests/google/registry/rdap/testdata/rdap_nontruncated_registrars.json index 70d52494d..278e235f3 100644 --- a/javatests/google/registry/rdap/testdata/rdap_nontruncated_registrars.json +++ b/javatests/google/registry/rdap/testdata/rdap_nontruncated_registrars.json @@ -8,9 +8,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/301", + "value" : "https://example.tld/rdap/entity/301", "rel" : "self", - "href": "https://example.com/rdap/entity/301", + "href": "https://example.tld/rdap/entity/301", "type" : "application/rdap+json" } ], @@ -61,9 +61,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/302", + "value" : "https://example.tld/rdap/entity/302", "rel" : "self", - "href": "https://example.com/rdap/entity/302", + "href": "https://example.tld/rdap/entity/302", "type" : "application/rdap+json" } ], @@ -114,9 +114,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/303", + "value" : "https://example.tld/rdap/entity/303", "rel" : "self", - "href": "https://example.com/rdap/entity/303", + "href": "https://example.tld/rdap/entity/303", "type" : "application/rdap+json" } ], @@ -167,9 +167,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/304", + "value" : "https://example.tld/rdap/entity/304", "rel" : "self", - "href": "https://example.com/rdap/entity/304", + "href": "https://example.tld/rdap/entity/304", "type" : "application/rdap+json" } ], @@ -235,7 +235,7 @@ "links" : [ { - "value" : "https://example.com/rdap/help/tos", + "value" : "https://example.tld/rdap/help/tos", "rel" : "alternate", "href" : "https://www.registry.tld/about/rdap/tos.html", "type" : "text/html" diff --git a/javatests/google/registry/rdap/testdata/rdap_registrar.json b/javatests/google/registry/rdap/testdata/rdap_registrar.json index cc7ce08da..ea06b7eb2 100644 --- a/javatests/google/registry/rdap/testdata/rdap_registrar.json +++ b/javatests/google/registry/rdap/testdata/rdap_registrar.json @@ -6,9 +6,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/%NAME%", + "value" : "https://example.tld/rdap/entity/%NAME%", "rel" : "self", - "href": "https://example.com/rdap/entity/%NAME%", + "href": "https://example.tld/rdap/entity/%NAME%", "type" : "application/rdap+json" } ], diff --git a/javatests/google/registry/rdap/testdata/rdap_truncated_contacts.json b/javatests/google/registry/rdap/testdata/rdap_truncated_contacts.json index 5e107ff04..0da5c435f 100644 --- a/javatests/google/registry/rdap/testdata/rdap_truncated_contacts.json +++ b/javatests/google/registry/rdap/testdata/rdap_truncated_contacts.json @@ -8,9 +8,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/0001-ROID", + "value" : "https://example.tld/rdap/entity/0001-ROID", "rel" : "self", - "href": "https://example.com/rdap/entity/0001-ROID", + "href": "https://example.tld/rdap/entity/0001-ROID", "type" : "application/rdap+json" } ], @@ -54,9 +54,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/0002-ROID", + "value" : "https://example.tld/rdap/entity/0002-ROID", "rel" : "self", - "href": "https://example.com/rdap/entity/0002-ROID", + "href": "https://example.tld/rdap/entity/0002-ROID", "type" : "application/rdap+json" } ], @@ -100,9 +100,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/0003-ROID", + "value" : "https://example.tld/rdap/entity/0003-ROID", "rel" : "self", - "href": "https://example.com/rdap/entity/0003-ROID", + "href": "https://example.tld/rdap/entity/0003-ROID", "type" : "application/rdap+json" } ], @@ -146,9 +146,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/0004-ROID", + "value" : "https://example.tld/rdap/entity/0004-ROID", "rel" : "self", - "href": "https://example.com/rdap/entity/0004-ROID", + "href": "https://example.tld/rdap/entity/0004-ROID", "type" : "application/rdap+json" } ], @@ -203,7 +203,7 @@ [ { "type" : "application/rdap+json", - "href" : "https://example.com/rdap/entities?%NAME%", + "href" : "https://example.tld/rdap/entities?%NAME%", "rel" : "next" } ], @@ -227,7 +227,7 @@ "links" : [ { - "value" : "https://example.com/rdap/help/tos", + "value" : "https://example.tld/rdap/help/tos", "rel" : "alternate", "href" : "https://www.registry.tld/about/rdap/tos.html", "type" : "text/html" diff --git a/javatests/google/registry/rdap/testdata/rdap_truncated_mixed_entities.json b/javatests/google/registry/rdap/testdata/rdap_truncated_mixed_entities.json index a00ccff41..e8cc66966 100644 --- a/javatests/google/registry/rdap/testdata/rdap_truncated_mixed_entities.json +++ b/javatests/google/registry/rdap/testdata/rdap_truncated_mixed_entities.json @@ -8,9 +8,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/0001-ROID", + "value" : "https://example.tld/rdap/entity/0001-ROID", "rel" : "self", - "href": "https://example.com/rdap/entity/0001-ROID", + "href": "https://example.tld/rdap/entity/0001-ROID", "type" : "application/rdap+json" } ], @@ -54,9 +54,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/0002-ROID", + "value" : "https://example.tld/rdap/entity/0002-ROID", "rel" : "self", - "href": "https://example.com/rdap/entity/0002-ROID", + "href": "https://example.tld/rdap/entity/0002-ROID", "type" : "application/rdap+json" } ], @@ -100,9 +100,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/0003-ROID", + "value" : "https://example.tld/rdap/entity/0003-ROID", "rel" : "self", - "href": "https://example.com/rdap/entity/0003-ROID", + "href": "https://example.tld/rdap/entity/0003-ROID", "type" : "application/rdap+json" } ], @@ -146,9 +146,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/301", + "value" : "https://example.tld/rdap/entity/301", "rel" : "self", - "href": "https://example.com/rdap/entity/301", + "href": "https://example.tld/rdap/entity/301", "type" : "application/rdap+json" } ], @@ -210,7 +210,7 @@ [ { "type" : "application/rdap+json", - "href" : "https://example.com/rdap/entities?%NAME%", + "href" : "https://example.tld/rdap/entities?%NAME%", "rel" : "next" } ], @@ -234,7 +234,7 @@ "links" : [ { - "value" : "https://example.com/rdap/help/tos", + "value" : "https://example.tld/rdap/help/tos", "rel" : "alternate", "href" : "https://www.registry.tld/about/rdap/tos.html", "type" : "text/html" diff --git a/javatests/google/registry/rdap/testdata/rdap_truncated_registrars.json b/javatests/google/registry/rdap/testdata/rdap_truncated_registrars.json index dcc3e9a2f..9b10af5c5 100644 --- a/javatests/google/registry/rdap/testdata/rdap_truncated_registrars.json +++ b/javatests/google/registry/rdap/testdata/rdap_truncated_registrars.json @@ -8,9 +8,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/301", + "value" : "https://example.tld/rdap/entity/301", "rel" : "self", - "href": "https://example.com/rdap/entity/301", + "href": "https://example.tld/rdap/entity/301", "type" : "application/rdap+json" } ], @@ -61,9 +61,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/302", + "value" : "https://example.tld/rdap/entity/302", "rel" : "self", - "href": "https://example.com/rdap/entity/302", + "href": "https://example.tld/rdap/entity/302", "type" : "application/rdap+json" } ], @@ -114,9 +114,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/303", + "value" : "https://example.tld/rdap/entity/303", "rel" : "self", - "href": "https://example.com/rdap/entity/303", + "href": "https://example.tld/rdap/entity/303", "type" : "application/rdap+json" } ], @@ -167,9 +167,9 @@ "links" : [ { - "value" : "https://example.com/rdap/entity/304", + "value" : "https://example.tld/rdap/entity/304", "rel" : "self", - "href": "https://example.com/rdap/entity/304", + "href": "https://example.tld/rdap/entity/304", "type" : "application/rdap+json" } ], @@ -231,7 +231,7 @@ [ { "type" : "application/rdap+json", - "href" : "https://example.com/rdap/entities?%NAME%", + "href" : "https://example.tld/rdap/entities?%NAME%", "rel" : "next" } ], @@ -255,7 +255,7 @@ "links" : [ { - "value" : "https://example.com/rdap/help/tos", + "value" : "https://example.tld/rdap/help/tos", "rel" : "alternate", "href" : "https://www.registry.tld/about/rdap/tos.html", "type" : "text/html" diff --git a/javatests/google/registry/rde/imports/RdeDomainImportActionTest.java b/javatests/google/registry/rde/imports/RdeDomainImportActionTest.java index b4ebca5cb..acdfa80e8 100644 --- a/javatests/google/registry/rde/imports/RdeDomainImportActionTest.java +++ b/javatests/google/registry/rde/imports/RdeDomainImportActionTest.java @@ -50,9 +50,12 @@ import google.registry.model.transfer.TransferStatus; import google.registry.request.Response; import google.registry.testing.FakeResponse; import google.registry.testing.mapreduce.MapreduceTestCase; +import google.registry.util.RandomStringGenerator; +import google.registry.util.StringGenerator; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.security.SecureRandom; import java.util.List; import java.util.Optional; import javax.annotation.Nullable; @@ -91,12 +94,14 @@ public class RdeDomainImportActionTest extends MapreduceTestCase { @@ -426,7 +424,7 @@ public class XjcToDomainResourceConverterTest { createAutoRenewPollMessageForDomainImport(xjcDomain, historyEntry); ofy().save().entities(historyEntry, autorenewBillingEvent, autorenewPollMessage); return XjcToDomainResourceConverter.convertDomain( - xjcDomain, autorenewBillingEvent, autorenewPollMessage); + xjcDomain, autorenewBillingEvent, autorenewPollMessage, stringGenerator); }); } diff --git a/javatests/google/registry/reporting/spec11/BUILD b/javatests/google/registry/reporting/spec11/BUILD index e7c9b00ba..fb60fa9eb 100644 --- a/javatests/google/registry/reporting/spec11/BUILD +++ b/javatests/google/registry/reporting/spec11/BUILD @@ -19,6 +19,7 @@ java_library( "@com_google_apis_google_api_services_dataflow", "@com_google_appengine_api_1_0_sdk", "@com_google_appengine_tools_appengine_gcs_client", + "@com_google_code_findbugs_jsr305", "@com_google_dagger", "@com_google_guava", "@com_google_truth", diff --git a/javatests/google/registry/reporting/spec11/Spec11EmailUtilsTest.java b/javatests/google/registry/reporting/spec11/Spec11EmailUtilsTest.java index ce43ad2ae..8bf3c1386 100644 --- a/javatests/google/registry/reporting/spec11/Spec11EmailUtilsTest.java +++ b/javatests/google/registry/reporting/spec11/Spec11EmailUtilsTest.java @@ -36,7 +36,9 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Properties; +import javax.annotation.Nullable; import javax.mail.Message; +import javax.mail.Message.RecipientType; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.internet.InternetAddress; @@ -83,6 +85,8 @@ public class Spec11EmailUtilsTest { new YearMonth(2018, 6), "my-sender@test.com", "my-receiver@test.com", + "my-reply-to@test.com", + "{LIST_OF_THREATS}\n{REPLY_TO_EMAIL}", "test-bucket", "icann/spec11/2018-06/SPEC11_MONTHLY_REPORT", gcsUtils, @@ -99,27 +103,21 @@ public class Spec11EmailUtilsTest { capturedMessages.get(0), "my-sender@test.com", "a@fake.com", + "my-reply-to@test.com", "Google Registry Monthly Threat Detector [2018-06]", - "Hello registrar partner,\n" - + "We have detected problems with the following domains:\n" - + "a.com - MALWARE\n" - + "At the moment, no action is required. This is purely informatory." - + "Regards,\nGoogle Registry\n"); + "a.com - MALWARE\n\nmy-reply-to@test.com"); validateMessage( capturedMessages.get(1), "my-sender@test.com", "b@fake.com", + "my-reply-to@test.com", "Google Registry Monthly Threat Detector [2018-06]", - "Hello registrar partner,\n" - + "We have detected problems with the following domains:\n" - + "b.com - MALWARE\n" - + "c.com - MALWARE\n" - + "At the moment, no action is required. This is purely informatory." - + "Regards,\nGoogle Registry\n"); + "b.com - MALWARE\nc.com - MALWARE\n\nmy-reply-to@test.com"); validateMessage( capturedMessages.get(2), "my-sender@test.com", "my-receiver@test.com", + null, "Spec11 Pipeline Success 2018-06", "Spec11 reporting completed successfully."); } @@ -163,6 +161,7 @@ public class Spec11EmailUtilsTest { gotMessage.getValue(), "my-sender@test.com", "my-receiver@test.com", + null, "Spec11 Emailing Failure 2018-06", "Emailing spec11 reports failed due to expected"); } @@ -175,17 +174,31 @@ public class Spec11EmailUtilsTest { gotMessage.getValue(), "my-sender@test.com", "my-receiver@test.com", + null, "Spec11 Pipeline Alert: 2018-06", "Alert!"); } private void validateMessage( - Message message, String from, String recipient, String subject, String body) + Message message, + String from, + String recipient, + @Nullable String replyTo, + String subject, + String body) throws MessagingException, IOException { assertThat(message.getFrom()).asList().containsExactly(new InternetAddress(from)); - assertThat(message.getAllRecipients()) + assertThat(message.getRecipients(RecipientType.TO)) .asList() .containsExactly(new InternetAddress(recipient)); + if (replyTo == null) { + assertThat(message.getRecipients(RecipientType.BCC)).isNull(); + } else { + assertThat(message.getRecipients(RecipientType.BCC)) + .asList() + .containsExactly(new InternetAddress(replyTo)); + } + assertThat(message.getRecipients(RecipientType.CC)).isNull(); assertThat(message.getSubject()).isEqualTo(subject); assertThat(message.getContentType()).isEqualTo("text/plain"); assertThat(message.getContent().toString()).isEqualTo(body); diff --git a/javatests/google/registry/testing/DatastoreHelper.java b/javatests/google/registry/testing/DatastoreHelper.java index 70e20427c..56b3b52fc 100644 --- a/javatests/google/registry/testing/DatastoreHelper.java +++ b/javatests/google/registry/testing/DatastoreHelper.java @@ -97,7 +97,7 @@ import google.registry.model.smd.EncodedSignedMark; import google.registry.model.transfer.TransferData; import google.registry.model.transfer.TransferData.Builder; import google.registry.model.transfer.TransferStatus; -import google.registry.tmch.LordnTask; +import google.registry.tmch.LordnTaskUtils; import java.util.Arrays; import java.util.List; import java.util.Set; @@ -363,9 +363,10 @@ public class DatastoreHelper { /** Persists a domain and enqueues a LORDN task of the appropriate type for it. */ public static DomainResource persistDomainAndEnqueueLordn(final DomainResource domain) { final DomainResource persistedDomain = persistResource(domain); - // Calls {@link LordnTask#enqueueDomainResourceTask} wrapped in an ofy transaction so that the + // Calls {@link LordnTaskUtils#enqueueDomainResourceTask} wrapped in an ofy transaction so that + // the // transaction time is set correctly. - ofy().transactNew(() -> LordnTask.enqueueDomainResourceTask(persistedDomain)); + ofy().transactNew(() -> LordnTaskUtils.enqueueDomainResourceTask(persistedDomain)); return persistedDomain; } @@ -453,7 +454,7 @@ public class DatastoreHelper { registrar.asBuilder().setAllowedTlds(union(registrar.getAllowedTlds(), tld)).build()); } - private static void disallowRegistrarAccess(String clientId, String tld) { + public static void disallowRegistrarAccess(String clientId, String tld) { Registrar registrar = loadRegistrar(clientId); persistResource( registrar.asBuilder().setAllowedTlds(difference(registrar.getAllowedTlds(), tld)).build()); diff --git a/javatests/google/registry/testing/EppMetricSubject.java b/javatests/google/registry/testing/EppMetricSubject.java index b80ba0e2e..c8187d2f8 100644 --- a/javatests/google/registry/testing/EppMetricSubject.java +++ b/javatests/google/registry/testing/EppMetricSubject.java @@ -44,10 +44,6 @@ public class EppMetricSubject extends Subject { return hasValue(commandName, actual().getCommandName(), "has commandName"); } - public And hasEppTarget(String eppTarget) { - return hasValue(eppTarget, actual().getEppTarget(), "has eppTarget"); - } - public And hasStatus(Code status) { return hasValue(status, actual().getStatus(), "has status"); } diff --git a/javatests/google/registry/testing/GpgSystemCommandRule.java b/javatests/google/registry/testing/GpgSystemCommandRule.java index b073a46b5..522295f32 100644 --- a/javatests/google/registry/testing/GpgSystemCommandRule.java +++ b/javatests/google/registry/testing/GpgSystemCommandRule.java @@ -17,6 +17,7 @@ package google.registry.testing; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.truth.Truth.assertWithMessage; import static java.nio.charset.StandardCharsets.UTF_8; @@ -84,9 +85,14 @@ public final class GpgSystemCommandRule extends ExternalResource { @Override protected void before() throws IOException, InterruptedException { checkState(Objects.equals(cwd, DEV_NULL)); - File tmpRoot = null; - tmpRoot = new File(System.getenv("TMPDIR")); - cwd = File.createTempFile(TEMP_FILE_PREFIX, "", tmpRoot); + String tmpRootDirString = System.getenv("TMPDIR"); + // Create the working directory for the forked process on Temp file system. Create under the + // path specified by 'TMPDIR' envrionment variable if defined, otherwise create under the + // runtime's default (typically /tmp). + cwd = + isNullOrEmpty(tmpRootDirString) + ? File.createTempFile(TEMP_FILE_PREFIX, "") + : File.createTempFile(TEMP_FILE_PREFIX, "", new File(tmpRootDirString)); cwd.delete(); cwd.mkdir(); conf = new File(cwd, ".gnupg"); @@ -118,6 +124,7 @@ public final class GpgSystemCommandRule extends ExternalResource { @Override protected void after() { + // TODO(weiminyu): we should delete the cwd tree. cwd = DEV_NULL; conf = DEV_NULL; } diff --git a/javatests/google/registry/testing/TestDataHelper.java b/javatests/google/registry/testing/TestDataHelper.java index a7cf34415..49dcd83c7 100644 --- a/javatests/google/registry/testing/TestDataHelper.java +++ b/javatests/google/registry/testing/TestDataHelper.java @@ -23,7 +23,9 @@ import com.google.common.collect.ImmutableMap; import com.google.common.io.ByteSource; import com.google.common.io.MoreFiles; import com.google.common.io.Resources; +import java.io.IOException; import java.net.URI; +import java.nio.file.FileSystemAlreadyExistsException; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.Paths; @@ -93,7 +95,21 @@ public final class TestDataHelper { /** Returns a recursive iterable of all files in the given directory. */ public static Iterable listFiles(Class context, String directory) throws Exception { URI dir = Resources.getResource(context, directory).toURI(); - FileSystems.newFileSystem(dir, ImmutableMap.of("create", "true")); + ensureFileSystemPresentForUri(dir); return MoreFiles.fileTraverser().breadthFirst(Paths.get(dir)); } + + private static void ensureFileSystemPresentForUri(URI uri) throws IOException { + if (uri.getScheme().equals(FileSystems.getDefault().provider().getScheme())) { + // URI maps to default file system (file://...), which must be present. Besides, calling + // FileSystem.newFileSystem on this URI may trigger FileSystemAlreadyExistsException. + return; + } + try { + // URI maps to a special file system, e.g., jar:... + FileSystems.newFileSystem(uri, ImmutableMap.of("create", "true")); + } catch (FileSystemAlreadyExistsException e) { + // ignore. + } + } } diff --git a/javatests/google/registry/testing/sftp/BUILD b/javatests/google/registry/testing/sftp/BUILD index 906daecf7..edc415924 100644 --- a/javatests/google/registry/testing/sftp/BUILD +++ b/javatests/google/registry/testing/sftp/BUILD @@ -17,6 +17,8 @@ java_library( "@junit", "@org_apache_ftpserver_core", "@org_apache_sshd_core", + "@org_apache_sshd_scp", + "@org_apache_sshd_sftp", "@org_bouncycastle_bcpkix_jdk15on", ], ) diff --git a/javatests/google/registry/testing/sftp/TestSftpServer.java b/javatests/google/registry/testing/sftp/TestSftpServer.java index 042d0967e..e7240f91b 100644 --- a/javatests/google/registry/testing/sftp/TestSftpServer.java +++ b/javatests/google/registry/testing/sftp/TestSftpServer.java @@ -31,11 +31,11 @@ import org.apache.sshd.common.NamedFactory; import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory; import org.apache.sshd.common.keyprovider.KeyPairProvider; import org.apache.sshd.common.random.SingletonRandomFactory; -import org.apache.sshd.server.Command; import org.apache.sshd.server.ServerBuilder; import org.apache.sshd.server.SshServer; import org.apache.sshd.server.auth.password.PasswordAuthenticator; import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator; +import org.apache.sshd.server.command.Command; import org.apache.sshd.server.scp.ScpCommandFactory; import org.apache.sshd.server.session.ServerSession; import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory; diff --git a/javatests/google/registry/tldconfig/idn/IdnLabelValidatorTest.java b/javatests/google/registry/tldconfig/idn/IdnLabelValidatorTest.java index 99a2cf634..2ec2610cc 100644 --- a/javatests/google/registry/tldconfig/idn/IdnLabelValidatorTest.java +++ b/javatests/google/registry/tldconfig/idn/IdnLabelValidatorTest.java @@ -15,12 +15,9 @@ package google.registry.tldconfig.idn; import static com.google.common.truth.Truth8.assertThat; -import static google.registry.tldconfig.idn.IdnLabelValidator.findValidIdnTableForTld; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import google.registry.testing.InjectRule; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -29,64 +26,63 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class IdnLabelValidatorTest { - @Rule - public final InjectRule inject = new InjectRule(); + private IdnLabelValidator idnLabelValidator = IdnLabelValidator.createDefaultIdnLabelValidator(); private void doJapaneseLanguageTests(String tld) { - assertThat(findValidIdnTableForTld("foo", tld)).isPresent(); - assertThat(findValidIdnTableForTld("12379foar", tld)).isPresent(); - assertThat(findValidIdnTableForTld("みんな", tld)).isPresent(); - assertThat(findValidIdnTableForTld("アシヨ", tld)).isPresent(); - assertThat(findValidIdnTableForTld("わみけ", tld)).isPresent(); - assertThat(findValidIdnTableForTld("みんなアシヨわみけabc", tld)).isPresent(); - assertThat(findValidIdnTableForTld("-みんなアシヨわみけ-", tld)).isPresent(); - assertThat(findValidIdnTableForTld("あいう〆", tld)).isPresent(); - assertThat(findValidIdnTableForTld("〆わをん", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("foo", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("12379foar", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("みんな", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("アシヨ", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("わみけ", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("みんなアシヨわみけabc", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("-みんなアシヨわみけ-", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("あいう〆", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("〆わをん", tld)).isPresent(); // This should fail since it mixes Japanese characters with extended Latin characters. These are // allowed individually, but not together, since they are in separate IDN tables. - assertThat(findValidIdnTableForTld("みんなアシヨわみけæ", tld)).isEmpty(); + assertThat(idnLabelValidator.findValidIdnTableForTld("みんなアシヨわみけæ", tld)).isEmpty(); // This fails because it has Cyrillic characters, which just aren't allowed in either IDN table. - assertThat(findValidIdnTableForTld("aЖЗ", tld)).isEmpty(); + assertThat(idnLabelValidator.findValidIdnTableForTld("aЖЗ", tld)).isEmpty(); - assertThat(findValidIdnTableForTld("abcdefghæ", tld)).isPresent(); - assertThat(findValidIdnTableForTld("happy", tld)).isPresent(); - assertThat(findValidIdnTableForTld("ite-love-you", tld)).isPresent(); - assertThat(findValidIdnTableForTld("凹凸商事", tld)).isPresent(); - assertThat(findValidIdnTableForTld("いすゞ製鉄", tld)).isPresent(); - assertThat(findValidIdnTableForTld("日々の生活", tld)).isPresent(); - assertThat(findValidIdnTableForTld("1000万円", tld)).isPresent(); - assertThat(findValidIdnTableForTld("たか--い", tld)).isPresent(); - assertThat(findValidIdnTableForTld("ザ・セール", tld)).isPresent(); - assertThat(findValidIdnTableForTld("example・例", tld)).isPresent(); - assertThat(findValidIdnTableForTld("カレー・ライス", tld)).isPresent(); - assertThat(findValidIdnTableForTld("〆切・明日", tld)).isPresent(); - assertThat(findValidIdnTableForTld("そのスピードで", tld)).isPresent(); - assertThat(findValidIdnTableForTld("visaクレジットカード", tld)).isPresent(); - assertThat(findValidIdnTableForTld("cdケース", tld)).isPresent(); - assertThat(findValidIdnTableForTld("らーめん", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("abcdefghæ", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("happy", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("ite-love-you", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("凹凸商事", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("いすゞ製鉄", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("日々の生活", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("1000万円", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("たか--い", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("ザ・セール", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("example・例", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("カレー・ライス", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("〆切・明日", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("そのスピードで", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("visaクレジットカード", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("cdケース", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("らーめん", tld)).isPresent(); // These fail because they have a KATAKANA MIDDLE DOT or IDEOGRAPHIC_CLOSING_MARK without any // Japanese non-exception characters. - assertThat(findValidIdnTableForTld("eco・driving", tld)).isEmpty(); - assertThat(findValidIdnTableForTld("・ー・", tld)).isEmpty(); - assertThat(findValidIdnTableForTld("〆〆example・・", tld)).isEmpty(); - assertThat(findValidIdnTableForTld("abc〆", tld)).isEmpty(); - assertThat(findValidIdnTableForTld("〆xyz", tld)).isEmpty(); - assertThat(findValidIdnTableForTld("〆bar・", tld)).isEmpty(); + assertThat(idnLabelValidator.findValidIdnTableForTld("eco・driving", tld)).isEmpty(); + assertThat(idnLabelValidator.findValidIdnTableForTld("・ー・", tld)).isEmpty(); + assertThat(idnLabelValidator.findValidIdnTableForTld("〆〆example・・", tld)).isEmpty(); + assertThat(idnLabelValidator.findValidIdnTableForTld("abc〆", tld)).isEmpty(); + assertThat(idnLabelValidator.findValidIdnTableForTld("〆xyz", tld)).isEmpty(); + assertThat(idnLabelValidator.findValidIdnTableForTld("〆bar・", tld)).isEmpty(); // This is a Japanese label with exactly 15 characters. - assertThat(findValidIdnTableForTld("カレー・ライスaaaaaaaa", tld)).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("カレー・ライスaaaaaaaa", tld)).isPresent(); // Should fail since it has Japanese characters but is more than 15 characters long. - assertThat(findValidIdnTableForTld("カレー・ライスaaaaaaaaa", tld)).isEmpty(); + assertThat(idnLabelValidator.findValidIdnTableForTld("カレー・ライスaaaaaaaaa", tld)).isEmpty(); // Should fail since it has a prolonged sound mark that is not preceded by Hiragana or Katakana // characters. - assertThat(findValidIdnTableForTld("aー", tld)).isEmpty(); - assertThat(findValidIdnTableForTld("-ー", tld)).isEmpty(); - assertThat(findValidIdnTableForTld("0ー", tld)).isEmpty(); + assertThat(idnLabelValidator.findValidIdnTableForTld("aー", tld)).isEmpty(); + assertThat(idnLabelValidator.findValidIdnTableForTld("-ー", tld)).isEmpty(); + assertThat(idnLabelValidator.findValidIdnTableForTld("0ー", tld)).isEmpty(); } @Test @@ -107,11 +103,10 @@ public class IdnLabelValidatorTest { @Test public void testOverridenTables() { // Set .tld to have only the extended latin table and not japanese. - inject.setStaticField( - IdnLabelValidator.class, - "idnTableListsPerTld", - ImmutableMap.of("tld", ImmutableList.of(IdnTableEnum.EXTENDED_LATIN))); - assertThat(findValidIdnTableForTld("foo", "tld")).isPresent(); - assertThat(findValidIdnTableForTld("みんな", "tld")).isEmpty(); + idnLabelValidator = + new IdnLabelValidator( + ImmutableMap.of("tld", ImmutableList.of(IdnTableEnum.EXTENDED_LATIN))); + assertThat(idnLabelValidator.findValidIdnTableForTld("foo", "tld")).isPresent(); + assertThat(idnLabelValidator.findValidIdnTableForTld("みんな", "tld")).isEmpty(); } } diff --git a/javatests/google/registry/tmch/LordnTaskTest.java b/javatests/google/registry/tmch/LordnTaskUtilsTest.java similarity index 63% rename from javatests/google/registry/tmch/LordnTaskTest.java rename to javatests/google/registry/tmch/LordnTaskUtilsTest.java index d2229d95d..9ce2bf00a 100644 --- a/javatests/google/registry/tmch/LordnTaskTest.java +++ b/javatests/google/registry/tmch/LordnTaskUtilsTest.java @@ -22,18 +22,7 @@ import static google.registry.testing.DatastoreHelper.persistActiveContact; import static google.registry.testing.DatastoreHelper.persistDomainAndEnqueueLordn; import static google.registry.testing.JUnitBackports.assertThrows; import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import com.google.appengine.api.taskqueue.LeaseOptions; -import com.google.appengine.api.taskqueue.Queue; -import com.google.appengine.api.taskqueue.TaskHandle; -import com.google.appengine.api.taskqueue.TaskOptions; -import com.google.appengine.api.taskqueue.TaskOptions.Method; -import com.google.appengine.api.taskqueue.TransientFailureException; -import com.google.apphosting.api.DeadlineExceededException; -import com.google.common.collect.ImmutableList; import com.googlecode.objectify.Key; import google.registry.model.domain.DomainResource; import google.registry.model.domain.launch.LaunchNotice; @@ -44,7 +33,6 @@ import google.registry.testing.FakeClock; import google.registry.testing.InjectRule; import google.registry.testing.TaskQueueHelper.TaskMatcher; import google.registry.util.Clock; -import java.util.List; import org.joda.time.DateTime; import org.junit.Before; import org.junit.Rule; @@ -52,9 +40,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** Unit tests for {@link LordnTask}. */ +/** Unit tests for {@link LordnTaskUtils}. */ @RunWith(JUnit4.class) -public class LordnTaskTest { +public class LordnTaskUtilsTest { private static final Clock clock = new FakeClock(DateTime.parse("2010-05-01T10:11:12Z")); @@ -70,32 +58,9 @@ public class LordnTaskTest { public void before() { createTld("example"); inject.setStaticField(Ofy.class, "clock", clock); - inject.setStaticField(LordnTask.class, "backOffMillis", 1L); } - @Test - public void test_convertTasksToCsv() { - List tasks = ImmutableList.of( - makeTaskHandle("task1", "example", "csvLine1", "lordn-sunrise"), - makeTaskHandle("task2", "example", "csvLine2", "lordn-sunrise"), - makeTaskHandle("task3", "example", "ending", "lordn-sunrise")); - assertThat(LordnTask.convertTasksToCsv(tasks, clock.nowUtc(), "col1,col2")) - .isEqualTo("1,2010-05-01T10:11:12.000Z,3\ncol1,col2\ncsvLine1\ncsvLine2\nending\n"); - } - @Test - public void test_convertTasksToCsv_doesntFailOnEmptyTasks() { - assertThat( - LordnTask.convertTasksToCsv(ImmutableList.of(), clock.nowUtc(), "col1,col2")) - .isEqualTo("1,2010-05-01T10:11:12.000Z,0\ncol1,col2\n"); - } - - @Test - public void test_convertTasksToCsv_throwsNpeOnNullTasks() { - assertThrows( - NullPointerException.class, - () -> LordnTask.convertTasksToCsv(null, clock.nowUtc(), "header")); - } private DomainResource.Builder newDomainBuilder(DateTime applicationTime) { return new DomainResource.Builder() @@ -173,38 +138,6 @@ public class LordnTaskTest { public void test_enqueueDomainResourceTask_throwsNpeOnNullDomain() { assertThrows( NullPointerException.class, - () -> ofy().transactNew(() -> LordnTask.enqueueDomainResourceTask(null))); - } - - @SuppressWarnings("unchecked") - @Test - public void test_loadAllTasks_retryLogic_thirdTrysTheCharm() { - Queue queue = mock(Queue.class); - TaskHandle task = new TaskHandle(TaskOptions.Builder.withTaskName("blah"), "blah"); - when(queue.leaseTasks(any(LeaseOptions.class))) - .thenThrow(TransientFailureException.class) - .thenThrow(DeadlineExceededException.class) - .thenReturn(ImmutableList.of(task), ImmutableList.of()); - assertThat(LordnTask.loadAllTasks(queue, "tld")).containsExactly(task); - } - - @SuppressWarnings("unchecked") - @Test - public void test_loadAllTasks_retryLogic_allFailures() { - Queue queue = mock(Queue.class); - when(queue.leaseTasks(any(LeaseOptions.class))).thenThrow(TransientFailureException.class); - RuntimeException thrown = - assertThrows(RuntimeException.class, () -> LordnTask.loadAllTasks(queue, "tld")); - assertThat(thrown).hasMessageThat().contains("Error leasing tasks"); - } - - private static TaskHandle makeTaskHandle( - String taskName, - String tag, - String payload, - String queue) { - return new TaskHandle( - TaskOptions.Builder.withPayload(payload).method(Method.PULL).tag(tag).taskName(taskName), - queue); + () -> ofy().transactNew(() -> LordnTaskUtils.enqueueDomainResourceTask(null))); } } diff --git a/javatests/google/registry/tmch/NordnUploadActionTest.java b/javatests/google/registry/tmch/NordnUploadActionTest.java index 1295d7bf7..2ec070283 100644 --- a/javatests/google/registry/tmch/NordnUploadActionTest.java +++ b/javatests/google/registry/tmch/NordnUploadActionTest.java @@ -32,13 +32,21 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED; import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.google.appengine.api.taskqueue.LeaseOptions; +import com.google.appengine.api.taskqueue.Queue; +import com.google.appengine.api.taskqueue.TaskHandle; +import com.google.appengine.api.taskqueue.TaskOptions; +import com.google.appengine.api.taskqueue.TaskOptions.Method; +import com.google.appengine.api.taskqueue.TransientFailureException; import com.google.appengine.api.urlfetch.HTTPHeader; import com.google.appengine.api.urlfetch.HTTPRequest; import com.google.appengine.api.urlfetch.HTTPResponse; import com.google.appengine.api.urlfetch.URLFetchService; +import com.google.apphosting.api.DeadlineExceededException; import com.google.common.base.VerifyException; import com.google.common.collect.ImmutableList; import google.registry.model.domain.DomainResource; @@ -55,7 +63,9 @@ import google.registry.util.Retrier; import google.registry.util.TaskQueueUtils; import google.registry.util.UrlFetchException; import java.net.URL; +import java.util.List; import java.util.Optional; +import java.util.Random; import org.joda.time.DateTime; import org.junit.Before; import org.junit.Rule; @@ -70,15 +80,15 @@ import org.mockito.Mock; @RunWith(JUnit4.class) public class NordnUploadActionTest { - private static final String CLAIMS_CSV = "1,2000-01-01T00:00:00.000Z,1\n" + private static final String CLAIMS_CSV = "1,2010-05-01T10:11:12.000Z,1\n" + "roid,domain-name,notice-id,registrar-id,registration-datetime,ack-datetime," + "application-datetime\n" - + "2-TLD,claims-landrush1.tld,landrush1tcn,99999,2000-01-01T00:00:00.000Z," + + "2-TLD,claims-landrush1.tld,landrush1tcn,99999,2010-05-01T10:11:12.000Z," + "1969-12-31T23:00:00.000Z,1969-12-31T00:00:00.000Z\n"; - private static final String SUNRISE_CSV = "1,2000-01-01T00:00:00.000Z,1\n" + private static final String SUNRISE_CSV = "1,2010-05-01T10:11:12.000Z,1\n" + "roid,domain-name,SMD-id,registrar-id,registration-datetime,application-datetime\n" - + "2-TLD,sunrise1.tld,my-smdid,99999,2000-01-01T00:00:00.000Z,1969-12-31T00:00:00.000Z\n"; + + "2-TLD,sunrise1.tld,my-smdid,99999,2010-05-01T10:11:12.000Z,1969-12-31T00:00:00.000Z\n"; private static final String LOCATION_URL = "http://trololol"; @@ -93,7 +103,7 @@ public class NordnUploadActionTest { @Mock private HTTPResponse httpResponse; @Captor private ArgumentCaptor httpRequestCaptor; - private final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01TZ")); + private final FakeClock clock = new FakeClock(DateTime.parse("2010-05-01T10:11:12Z")); private final LordnRequestInitializer lordnRequestInitializer = new LordnRequestInitializer(); private final NordnUploadAction action = new NordnUploadAction(); @@ -115,6 +125,55 @@ public class NordnUploadActionTest { action.taskQueueUtils = new TaskQueueUtils(new Retrier(new FakeSleeper(clock), 3)); action.tld = "tld"; action.tmchMarksdbUrl = "http://127.0.0.1"; + action.random = new Random(); + action.retrier = new Retrier(new FakeSleeper(clock), 3); + } + + @Test + public void test_convertTasksToCsv() { + List tasks = + ImmutableList.of( + makeTaskHandle("task1", "example", "csvLine1", "lordn-sunrise"), + makeTaskHandle("task2", "example", "csvLine2", "lordn-sunrise"), + makeTaskHandle("task3", "example", "ending", "lordn-sunrise")); + assertThat(NordnUploadAction.convertTasksToCsv(tasks, clock.nowUtc(), "col1,col2")) + .isEqualTo("1,2010-05-01T10:11:12.000Z,3\ncol1,col2\ncsvLine1\ncsvLine2\nending\n"); + } + + @Test + public void test_convertTasksToCsv_doesntFailOnEmptyTasks() { + assertThat(NordnUploadAction.convertTasksToCsv(ImmutableList.of(), clock.nowUtc(), "col1,col2")) + .isEqualTo("1,2010-05-01T10:11:12.000Z,0\ncol1,col2\n"); + } + + @Test + public void test_convertTasksToCsv_throwsNpeOnNullTasks() { + assertThrows( + NullPointerException.class, + () -> NordnUploadAction.convertTasksToCsv(null, clock.nowUtc(), "header")); + } + + @SuppressWarnings("unchecked") + @Test + public void test_loadAllTasks_retryLogic_thirdTrysTheCharm() { + Queue queue = mock(Queue.class); + TaskHandle task = new TaskHandle(TaskOptions.Builder.withTaskName("blah"), "blah"); + when(queue.leaseTasks(any(LeaseOptions.class))) + .thenThrow(TransientFailureException.class) + .thenThrow(DeadlineExceededException.class) + .thenReturn(ImmutableList.of(task), ImmutableList.of()); + assertThat(action.loadAllTasks(queue, "tld")).containsExactly(task); + } + + @SuppressWarnings("unchecked") + @Test + public void test_loadAllTasks_retryLogic_allFailures() { + Queue queue = mock(Queue.class); + when(queue.leaseTasks(any(LeaseOptions.class))) + .thenThrow(new TransientFailureException("some transient error")); + RuntimeException thrown = + assertThrows(TransientFailureException.class, () -> action.loadAllTasks(queue, "tld")); + assertThat(thrown).hasMessageThat().isEqualTo("some transient error"); } @Test @@ -228,4 +287,11 @@ public class NordnUploadActionTest { .setApplicationTime(domain.getCreationTime().minusDays(1)) .build()); } + + private static TaskHandle makeTaskHandle( + String taskName, String tag, String payload, String queue) { + return new TaskHandle( + TaskOptions.Builder.withPayload(payload).method(Method.PULL).tag(tag).taskName(taskName), + queue); + } } diff --git a/javatests/google/registry/tmch/TmchActionTestCase.java b/javatests/google/registry/tmch/TmchActionTestCase.java index 4d75a29e1..cefa7902b 100644 --- a/javatests/google/registry/tmch/TmchActionTestCase.java +++ b/javatests/google/registry/tmch/TmchActionTestCase.java @@ -24,7 +24,6 @@ import com.google.appengine.api.urlfetch.URLFetchService; import google.registry.testing.AppEngineRule; import google.registry.testing.BouncyCastleProviderRule; import google.registry.testing.FakeClock; -import google.registry.testing.InjectRule; import google.registry.testing.MockitoJUnitRule; import org.junit.Before; import org.junit.Rule; @@ -44,7 +43,6 @@ public class TmchActionTestCase { @Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build(); @Rule public final MockitoJUnitRule mocks = MockitoJUnitRule.create(); @Rule public final BouncyCastleProviderRule bouncy = new BouncyCastleProviderRule(); - @Rule public final InjectRule inject = new InjectRule(); @Mock URLFetchService fetchService; @Mock HTTPResponse httpResponse; @@ -55,7 +53,6 @@ public class TmchActionTestCase { @Before public void commonBefore() throws Exception { - inject.setStaticField(TmchCertificateAuthority.class, "clock", clock); marksdb.fetchService = fetchService; marksdb.tmchMarksdbUrl = MARKSDB_URL; marksdb.marksdbPublicKey = TmchData.loadPublicKey(TmchTestData.loadBytes("pubkey")); diff --git a/javatests/google/registry/tmch/TmchCertificateAuthorityTest.java b/javatests/google/registry/tmch/TmchCertificateAuthorityTest.java index 128243a53..1d84565ba 100644 --- a/javatests/google/registry/tmch/TmchCertificateAuthorityTest.java +++ b/javatests/google/registry/tmch/TmchCertificateAuthorityTest.java @@ -25,13 +25,11 @@ import static google.registry.util.X509Utils.loadCertificate; import google.registry.model.tmch.TmchCrl; import google.registry.testing.AppEngineRule; import google.registry.testing.FakeClock; -import google.registry.testing.InjectRule; import java.security.SignatureException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.CertificateRevokedException; import org.joda.time.DateTime; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -48,38 +46,35 @@ public class TmchCertificateAuthorityTest { public final AppEngineRule appEngine = AppEngineRule.builder() .withDatastore() .build(); - @Rule - public final InjectRule inject = new InjectRule(); private FakeClock clock = new FakeClock(DateTime.parse("2014-01-01T00:00:00Z")); - @Before - public void before() { - inject.setStaticField(TmchCertificateAuthority.class, "clock", clock); - } - @Test public void testFailure_prodRootExpired() { - TmchCertificateAuthority tmchCertificateAuthority = new TmchCertificateAuthority(PRODUCTION); + TmchCertificateAuthority tmchCertificateAuthority = + new TmchCertificateAuthority(PRODUCTION, clock); clock.setTo(DateTime.parse("2024-01-01T00:00:00Z")); CertificateExpiredException e = - assertThrows(CertificateExpiredException.class, tmchCertificateAuthority::getRoot); + assertThrows( + CertificateExpiredException.class, tmchCertificateAuthority::getAndValidateRoot); assertThat(e).hasMessageThat().containsMatch("NotAfter: Sun Jul 23 23:59:59 UTC 2023"); } @Test public void testFailure_prodRootNotYetValid() { - TmchCertificateAuthority tmchCertificateAuthority = new TmchCertificateAuthority(PRODUCTION); + TmchCertificateAuthority tmchCertificateAuthority = + new TmchCertificateAuthority(PRODUCTION, clock); clock.setTo(DateTime.parse("2000-01-01T00:00:00Z")); CertificateNotYetValidException e = - assertThrows(CertificateNotYetValidException.class, tmchCertificateAuthority::getRoot); + assertThrows( + CertificateNotYetValidException.class, tmchCertificateAuthority::getAndValidateRoot); assertThat(e).hasMessageThat().containsMatch("NotBefore: Wed Jul 24 00:00:00 UTC 2013"); } @Test public void testFailure_crlDoesntMatchCerts() { // Use the prod cl, which won't match our test certificate. - TmchCertificateAuthority tmchCertificateAuthority = new TmchCertificateAuthority(PILOT); + TmchCertificateAuthority tmchCertificateAuthority = new TmchCertificateAuthority(PILOT, clock); TmchCrl.set( readResourceUtf8(TmchCertificateAuthority.class, "icann-tmch.crl"), "http://cert.crl"); SignatureException e = @@ -91,13 +86,14 @@ public class TmchCertificateAuthorityTest { @Test public void testSuccess_verify() throws Exception { - TmchCertificateAuthority tmchCertificateAuthority = new TmchCertificateAuthority(PILOT); + TmchCertificateAuthority tmchCertificateAuthority = new TmchCertificateAuthority(PILOT, clock); tmchCertificateAuthority.verify(loadCertificate(GOOD_TEST_CERTIFICATE)); } @Test public void testFailure_verifySignatureDoesntMatch() { - TmchCertificateAuthority tmchCertificateAuthority = new TmchCertificateAuthority(PRODUCTION); + TmchCertificateAuthority tmchCertificateAuthority = + new TmchCertificateAuthority(PRODUCTION, clock); SignatureException e = assertThrows( SignatureException.class, @@ -107,7 +103,7 @@ public class TmchCertificateAuthorityTest { @Test public void testFailure_verifyRevoked() { - TmchCertificateAuthority tmchCertificateAuthority = new TmchCertificateAuthority(PILOT); + TmchCertificateAuthority tmchCertificateAuthority = new TmchCertificateAuthority(PILOT, clock); CertificateRevokedException thrown = assertThrows( CertificateRevokedException.class, diff --git a/javatests/google/registry/tmch/TmchCrlActionTest.java b/javatests/google/registry/tmch/TmchCrlActionTest.java index 5d11b455b..6f3b447c3 100644 --- a/javatests/google/registry/tmch/TmchCrlActionTest.java +++ b/javatests/google/registry/tmch/TmchCrlActionTest.java @@ -36,7 +36,7 @@ public class TmchCrlActionTest extends TmchActionTestCase { private TmchCrlAction newTmchCrlAction(TmchCaMode tmchCaMode) throws MalformedURLException { TmchCrlAction action = new TmchCrlAction(); action.marksdb = marksdb; - action.tmchCertificateAuthority = new TmchCertificateAuthority(tmchCaMode); + action.tmchCertificateAuthority = new TmchCertificateAuthority(tmchCaMode, clock); action.tmchCrlUrl = new URL("http://sloth.lol/tmch.crl"); return action; } diff --git a/javatests/google/registry/tmch/TmchTestDataExpirationTest.java b/javatests/google/registry/tmch/TmchTestDataExpirationTest.java index 57e9d9247..202ce8de1 100644 --- a/javatests/google/registry/tmch/TmchTestDataExpirationTest.java +++ b/javatests/google/registry/tmch/TmchTestDataExpirationTest.java @@ -24,6 +24,7 @@ import google.registry.flows.domain.DomainFlowTmchUtils; import google.registry.model.smd.EncodedSignedMark; import google.registry.testing.AppEngineRule; import google.registry.util.ResourceUtils; +import google.registry.util.SystemClock; import java.nio.file.Path; import org.joda.time.DateTime; import org.junit.Rule; @@ -43,7 +44,8 @@ public class TmchTestDataExpirationTest { public void testActiveSignedMarkFiles_areValidAndNotExpired() throws Exception { DomainFlowTmchUtils tmchUtils = new DomainFlowTmchUtils( - new TmchXmlSignature(new TmchCertificateAuthority(TmchCaMode.PILOT))); + new TmchXmlSignature( + new TmchCertificateAuthority(TmchCaMode.PILOT, new SystemClock()))); for (Path path : listFiles(TmchTestDataExpirationTest.class, "testdata/active/")) { if (path.toString().endsWith(".smd")) { diff --git a/javatests/google/registry/tmch/TmchTestSuite.java b/javatests/google/registry/tmch/TmchTestSuite.java index 9917ca32d..8d9facd3b 100644 --- a/javatests/google/registry/tmch/TmchTestSuite.java +++ b/javatests/google/registry/tmch/TmchTestSuite.java @@ -21,7 +21,7 @@ import org.junit.runners.Suite.SuiteClasses; /** Convenience class to run all TMCH tests inside IDE with one keystroke. */ @RunWith(Suite.class) @SuiteClasses({ - LordnTaskTest.class, + LordnTaskUtilsTest.class, NordnUploadAction.class, NordnVerifyAction.class, SmdrlCsvParserTest.class, diff --git a/javatests/google/registry/tmch/TmchXmlSignatureTest.java b/javatests/google/registry/tmch/TmchXmlSignatureTest.java index 30460b034..9bf94c5c1 100644 --- a/javatests/google/registry/tmch/TmchXmlSignatureTest.java +++ b/javatests/google/registry/tmch/TmchXmlSignatureTest.java @@ -72,17 +72,17 @@ public class TmchXmlSignatureTest { private final FakeClock clock = new FakeClock(DateTime.parse("2018-05-15T23:15:37.4Z")); private byte[] smdData; - private TmchXmlSignature tmchXmlSignature; + private TmchXmlSignature tmchXmlSignature = + new TmchXmlSignature(new TmchCertificateAuthority(TmchCaMode.PILOT, clock)); @Before public void before() { - inject.setStaticField(TmchCertificateAuthority.class, "clock", clock); - tmchXmlSignature = new TmchXmlSignature(new TmchCertificateAuthority(TmchCaMode.PILOT)); } @Test public void testWrongCertificateAuthority() { - tmchXmlSignature = new TmchXmlSignature(new TmchCertificateAuthority(TmchCaMode.PRODUCTION)); + tmchXmlSignature = + new TmchXmlSignature(new TmchCertificateAuthority(TmchCaMode.PRODUCTION, clock)); smdData = loadSmd("active/Court-Agent-Arab-Active.smd"); CertificateSignatureException e = assertThrows(CertificateSignatureException.class, () -> tmchXmlSignature.verify(smdData)); diff --git a/javatests/google/registry/tools/CreateOrUpdatePremiumListCommandTestCase.java b/javatests/google/registry/tools/CreateOrUpdatePremiumListCommandTestCase.java index a4b8ba84e..cb8f1a7ad 100644 --- a/javatests/google/registry/tools/CreateOrUpdatePremiumListCommandTestCase.java +++ b/javatests/google/registry/tools/CreateOrUpdatePremiumListCommandTestCase.java @@ -23,7 +23,6 @@ import com.google.common.collect.ImmutableMap; import com.google.common.io.Files; import com.google.common.net.MediaType; import google.registry.testing.UriParameters; -import google.registry.tools.CommandWithConnection.Connection; import java.io.File; import java.nio.charset.StandardCharsets; import org.mockito.ArgumentCaptor; @@ -46,13 +45,14 @@ public abstract class CreateOrUpdatePremiumListCommandTestCase< } void verifySentParams( - Connection connection, String path, ImmutableMap parameterMap) - throws Exception { - verify(connection).send( - eq(path), - urlParamCaptor.capture(), - eq(MediaType.FORM_DATA), - requestBodyCaptor.capture()); + AppEngineConnection connection, String path, ImmutableMap parameterMap) + throws Exception { + verify(connection) + .sendPostRequest( + eq(path), + urlParamCaptor.capture(), + eq(MediaType.FORM_DATA), + requestBodyCaptor.capture()); assertThat(new ImmutableMap.Builder() .putAll(urlParamCaptor.getValue()) .putAll(UriParameters.parse(new String(requestBodyCaptor.getValue(), UTF_8)).entries()) diff --git a/javatests/google/registry/tools/CreatePremiumListCommandTest.java b/javatests/google/registry/tools/CreatePremiumListCommandTest.java index 9c1ecada6..3a3b136b0 100644 --- a/javatests/google/registry/tools/CreatePremiumListCommandTest.java +++ b/javatests/google/registry/tools/CreatePremiumListCommandTest.java @@ -28,7 +28,6 @@ import com.beust.jcommander.ParameterException; import com.google.common.base.VerifyException; import com.google.common.collect.ImmutableMap; import com.google.common.net.MediaType; -import google.registry.tools.CommandWithConnection.Connection; import google.registry.tools.server.CreatePremiumListAction; import org.junit.Before; import org.junit.Test; @@ -38,8 +37,7 @@ import org.mockito.Mock; public class CreatePremiumListCommandTest extends CreateOrUpdatePremiumListCommandTestCase { - @Mock - Connection connection; + @Mock AppEngineConnection connection; String premiumTermsPath; String premiumTermsCsv; @@ -53,12 +51,12 @@ public class CreatePremiumListCommandTest "example_premium_terms.csv", loadFile(CreatePremiumListCommandTest.class, "example_premium_terms.csv")); servletPath = "/_dr/admin/createPremiumList"; - when(connection.send( - eq(CreatePremiumListAction.PATH), - anyMapOf(String.class, String.class), - any(MediaType.class), - any(byte[].class))) - .thenReturn(JSON_SAFETY_PREFIX + "{\"status\":\"success\",\"lines\":[]}"); + when(connection.sendPostRequest( + eq(CreatePremiumListAction.PATH), + anyMapOf(String.class, String.class), + any(MediaType.class), + any(byte[].class))) + .thenReturn(JSON_SAFETY_PREFIX + "{\"status\":\"success\",\"lines\":[]}"); } @Test @@ -86,13 +84,12 @@ public class CreatePremiumListCommandTest public void testRun_errorResponse() throws Exception { reset(connection); command.setConnection(connection); - when(connection.send( - eq(CreatePremiumListAction.PATH), - anyMapOf(String.class, String.class), - any(MediaType.class), - any(byte[].class))) - .thenReturn( - JSON_SAFETY_PREFIX + "{\"status\":\"error\",\"error\":\"foo already exists\"}"); + when(connection.sendPostRequest( + eq(CreatePremiumListAction.PATH), + anyMapOf(String.class, String.class), + any(MediaType.class), + any(byte[].class))) + .thenReturn(JSON_SAFETY_PREFIX + "{\"status\":\"error\",\"error\":\"foo already exists\"}"); VerifyException thrown = assertThrows( VerifyException.class, () -> runCommandForced("-i=" + premiumTermsPath, "-n=foo")); diff --git a/javatests/google/registry/tools/CreateRegistrarCommandTest.java b/javatests/google/registry/tools/CreateRegistrarCommandTest.java index f6f278bbc..36fc23878 100644 --- a/javatests/google/registry/tools/CreateRegistrarCommandTest.java +++ b/javatests/google/registry/tools/CreateRegistrarCommandTest.java @@ -34,7 +34,6 @@ import com.google.common.collect.Range; import com.google.common.net.MediaType; import google.registry.model.registrar.Registrar; import google.registry.testing.CertificateSamples; -import google.registry.tools.CommandWithConnection.Connection; import java.io.IOException; import java.util.Optional; import org.joda.money.CurrencyUnit; @@ -47,8 +46,7 @@ import org.mockito.Mockito; /** Unit tests for {@link CreateRegistrarCommand}. */ public class CreateRegistrarCommandTest extends CommandTestCase { - @Mock - private Connection connection; + @Mock private AppEngineConnection connection; @Before public void init() { @@ -93,11 +91,12 @@ public class CreateRegistrarCommandTest extends CommandTestCase { - @Mock - private Connection connection; + @Mock private AppEngineConnection connection; @Before public void init() { @@ -41,16 +39,18 @@ public class CreateRegistrarGroupsCommandTest extends @Test public void test_createGroupsForTwoRegistrars() throws Exception { runCommandForced("NewRegistrar", "TheRegistrar"); - verify(connection).send( - eq("/_dr/admin/createGroups"), - eq(ImmutableMap.of("clientId", "NewRegistrar")), - eq(MediaType.PLAIN_TEXT_UTF_8), - eq(new byte[0])); - verify(connection).send( - eq("/_dr/admin/createGroups"), - eq(ImmutableMap.of("clientId", "TheRegistrar")), - eq(MediaType.PLAIN_TEXT_UTF_8), - eq(new byte[0])); + verify(connection) + .sendPostRequest( + eq("/_dr/admin/createGroups"), + eq(ImmutableMap.of("clientId", "NewRegistrar")), + eq(MediaType.PLAIN_TEXT_UTF_8), + eq(new byte[0])); + verify(connection) + .sendPostRequest( + eq("/_dr/admin/createGroups"), + eq(ImmutableMap.of("clientId", "TheRegistrar")), + eq(MediaType.PLAIN_TEXT_UTF_8), + eq(new byte[0])); assertInStdout("Success!"); } diff --git a/javatests/google/registry/tools/CurlCommandTest.java b/javatests/google/registry/tools/CurlCommandTest.java index 35baa0057..84144524d 100644 --- a/javatests/google/registry/tools/CurlCommandTest.java +++ b/javatests/google/registry/tools/CurlCommandTest.java @@ -16,13 +16,19 @@ package google.registry.tools; import static com.google.common.truth.Truth.assertThat; import static google.registry.testing.JUnitBackports.assertThrows; +import static google.registry.tools.AppEngineConnection.Service.BACKEND; +import static google.registry.tools.AppEngineConnection.Service.DEFAULT; +import static google.registry.tools.AppEngineConnection.Service.PUBAPI; +import static google.registry.tools.AppEngineConnection.Service.TOOLS; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableMap; import com.google.common.net.MediaType; -import google.registry.tools.CommandWithConnection.Connection; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -31,34 +37,42 @@ import org.mockito.Mock; /** Unit tests for {@link RefreshDnsForAllDomainsCommand}. */ public class CurlCommandTest extends CommandTestCase { - @Mock private Connection connection; + @Mock private AppEngineConnection connection; + @Mock private AppEngineConnection connectionForService; @Before public void init() { command.setConnection(connection); + when(connection.withService(any())).thenReturn(connectionForService); } @Captor ArgumentCaptor> urlParamCaptor; @Test public void testGetInvocation() throws Exception { - runCommand("--path=/foo/bar?a=1&b=2"); - verify(connection) + runCommand("--path=/foo/bar?a=1&b=2", "--service=TOOLS"); + verify(connection).withService(TOOLS); + verifyNoMoreInteractions(connection); + verify(connectionForService) .sendGetRequest(eq("/foo/bar?a=1&b=2"), eq(ImmutableMap.of())); } @Test public void testExplicitGetInvocation() throws Exception { - runCommand("--path=/foo/bar?a=1&b=2", "--request=GET"); - verify(connection) + runCommand("--path=/foo/bar?a=1&b=2", "--request=GET", "--service=BACKEND"); + verify(connection).withService(BACKEND); + verifyNoMoreInteractions(connection); + verify(connectionForService) .sendGetRequest(eq("/foo/bar?a=1&b=2"), eq(ImmutableMap.of())); } @Test public void testPostInvocation() throws Exception { - runCommand("--path=/foo/bar?a=1&b=2", "--data=some data"); - verify(connection) - .send( + runCommand("--path=/foo/bar?a=1&b=2", "--data=some data", "--service=DEFAULT"); + verify(connection).withService(DEFAULT); + verifyNoMoreInteractions(connection); + verify(connectionForService) + .sendPostRequest( eq("/foo/bar?a=1&b=2"), eq(ImmutableMap.of()), eq(MediaType.PLAIN_TEXT_UTF_8), @@ -67,9 +81,12 @@ public class CurlCommandTest extends CommandTestCase { @Test public void testMultiDataPost() throws Exception { - runCommand("--path=/foo/bar?a=1&b=2", "--data=first=100", "-d", "second=200"); - verify(connection) - .send( + runCommand( + "--path=/foo/bar?a=1&b=2", "--data=first=100", "-d", "second=200", "--service=PUBAPI"); + verify(connection).withService(PUBAPI); + verifyNoMoreInteractions(connection); + verify(connectionForService) + .sendPostRequest( eq("/foo/bar?a=1&b=2"), eq(ImmutableMap.of()), eq(MediaType.PLAIN_TEXT_UTF_8), @@ -78,9 +95,11 @@ public class CurlCommandTest extends CommandTestCase { @Test public void testExplicitPostInvocation() throws Exception { - runCommand("--path=/foo/bar?a=1&b=2", "--request=POST"); - verify(connection) - .send( + runCommand("--path=/foo/bar?a=1&b=2", "--request=POST", "--service=TOOLS"); + verify(connection).withService(TOOLS); + verifyNoMoreInteractions(connection); + verify(connectionForService) + .sendPostRequest( eq("/foo/bar?a=1&b=2"), eq(ImmutableMap.of()), eq(MediaType.PLAIN_TEXT_UTF_8), @@ -94,7 +113,10 @@ public class CurlCommandTest extends CommandTestCase { IllegalArgumentException.class, () -> runCommand( - "--path=/foo/bar?a=1&b=2", "--request=GET", "--data=inappropriate data")); + "--path=/foo/bar?a=1&b=2", + "--request=GET", + "--data=inappropriate data", + "--service=TOOLS")); assertThat(thrown).hasMessageThat().contains("You may not specify a body for a get method."); } } diff --git a/javatests/google/registry/tools/DefaultRequestFactoryModuleTest.java b/javatests/google/registry/tools/DefaultRequestFactoryModuleTest.java index 3a8d21ef0..e6ee2eff5 100644 --- a/javatests/google/registry/tools/DefaultRequestFactoryModuleTest.java +++ b/javatests/google/registry/tools/DefaultRequestFactoryModuleTest.java @@ -20,7 +20,7 @@ import com.google.api.client.auth.oauth2.Credential; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpRequestInitializer; -import com.google.common.net.HostAndPort; +import google.registry.config.RegistryConfig; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -50,10 +50,8 @@ public class DefaultRequestFactoryModuleTest { @Test public void test_provideHttpRequestFactory_localhost() { // Make sure that localhost creates a request factory with an initializer. - HttpRequestFactory factory = - module.provideHttpRequestFactory( - new AppEngineConnectionFlags(HostAndPort.fromParts("localhost", 1000)), - () -> FAKE_CREDENTIAL); + RegistryConfig.CONFIG_SETTINGS.get().appEngine.isLocal = true; + HttpRequestFactory factory = module.provideHttpRequestFactory(() -> FAKE_CREDENTIAL); HttpRequestInitializer initializer = factory.getInitializer(); assertThat(initializer).isNotNull(); assertThat(initializer).isNotSameAs(FAKE_CREDENTIAL); @@ -62,11 +60,8 @@ public class DefaultRequestFactoryModuleTest { @Test public void test_provideHttpRequestFactory_remote() { // Make sure that example.com creates a request factory with the UNITTEST client id but no - // initializer. - HttpRequestFactory factory = - module.provideHttpRequestFactory( - new AppEngineConnectionFlags(HostAndPort.fromParts("example.com", 1000)), - () -> FAKE_CREDENTIAL); + RegistryConfig.CONFIG_SETTINGS.get().appEngine.isLocal = false; + HttpRequestFactory factory = module.provideHttpRequestFactory(() -> FAKE_CREDENTIAL); assertThat(factory.getInitializer()).isSameAs(FAKE_CREDENTIAL); } } diff --git a/javatests/google/registry/tools/DeleteAllocationTokensCommandTest.java b/javatests/google/registry/tools/DeleteAllocationTokensCommandTest.java new file mode 100644 index 000000000..0ce485f23 --- /dev/null +++ b/javatests/google/registry/tools/DeleteAllocationTokensCommandTest.java @@ -0,0 +1,140 @@ +// 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.tools; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.testing.DatastoreHelper.persistResource; +import static google.registry.testing.JUnitBackports.assertThrows; + +import com.beust.jcommander.ParameterException; +import com.googlecode.objectify.Key; +import google.registry.model.domain.token.AllocationToken; +import google.registry.model.reporting.HistoryEntry; +import java.util.Collection; +import javax.annotation.Nullable; +import org.junit.Before; +import org.junit.Test; + +/** Unit tests for {@link DeleteAllocationTokensCommand}. */ +public class DeleteAllocationTokensCommandTest + extends CommandTestCase { + + private AllocationToken preRed1; + private AllocationToken preRed2; + private AllocationToken preNot1; + private AllocationToken preNot2; + private AllocationToken othrRed; + private AllocationToken othrNot; + + @Before + public void init() { + preRed1 = persistToken("prefix12345AA", null, true); + preRed2 = persistToken("prefixgh8907a", null, true); + preNot1 = persistToken("prefix2978204", null, false); + preNot2 = persistToken("prefix8ZZZhs8", null, false); + othrRed = persistToken("h97987sasdfhh", null, true); + othrNot = persistToken("asdgfho7HASDS", null, false); + } + + @Test + public void test_deleteAllUnredeemedTokens_whenEmptyPrefixSpecified() throws Exception { + runCommandForced("--prefix", ""); + assertThat(reloadTokens(preNot1, preNot2, othrNot)).isEmpty(); + assertThat(reloadTokens(preRed1, preRed2, othrRed)).containsExactly(preRed1, preRed2, othrRed); + } + + @Test + public void test_deleteOnlyUnredeemedTokensWithPrefix() throws Exception { + runCommandForced("--prefix", "prefix"); + assertThat(reloadTokens(preNot1, preNot2)).isEmpty(); + assertThat(reloadTokens(preRed1, preRed2, othrRed, othrNot)) + .containsExactly(preRed1, preRed2, othrRed, othrNot); + } + + @Test + public void test_deleteSingleAllocationToken() throws Exception { + runCommandForced("--prefix", "asdgfho7HASDS"); + assertThat(reloadTokens(othrNot)).isEmpty(); + assertThat(reloadTokens(preRed1, preRed2, preNot1, preNot2, othrRed)) + .containsExactly(preRed1, preRed2, preNot1, preNot2, othrRed); + } + + @Test + public void test_deleteTokensWithNonExistentPrefix_doesNothing() throws Exception { + runCommandForced("--prefix", "nonexistent"); + assertThat(reloadTokens(preRed1, preRed2, preNot1, preNot2, othrRed, othrNot)) + .containsExactly(preRed1, preRed2, preNot1, preNot2, othrRed, othrNot); + } + + @Test + public void test_dryRun_deletesNothing() throws Exception { + runCommandForced("--prefix", "", "--dry_run"); + assertThat(reloadTokens(preRed1, preRed2, preNot1, preNot2, othrRed, othrNot)) + .containsExactly(preRed1, preRed2, preNot1, preNot2, othrRed, othrNot); + } + + @Test + public void test_defaultOptions_doesntDeletePerDomainTokens() throws Exception { + AllocationToken preDom1 = persistToken("prefixasdfg897as", "foo.bar", false); + AllocationToken preDom2 = persistToken("prefix98HAZXadbn", "foo.bar", true); + runCommandForced("--prefix", "prefix"); + assertThat(reloadTokens(preNot1, preNot2)).isEmpty(); + assertThat(reloadTokens(preRed1, preRed2, preDom1, preDom2, othrRed, othrNot)) + .containsExactly(preRed1, preRed2, preDom1, preDom2, othrRed, othrNot); + } + + @Test + public void test_withDomains_doesDeletePerDomainTokens() throws Exception { + AllocationToken preDom1 = persistToken("prefixasdfg897as", "foo.bar", false); + AllocationToken preDom2 = persistToken("prefix98HAZXadbn", "foo.bar", true); + runCommandForced("--prefix", "prefix", "--with_domains"); + assertThat(reloadTokens(preNot1, preNot2, preDom1)).isEmpty(); + assertThat(reloadTokens(preRed1, preRed2, preDom2, othrRed, othrNot)) + .containsExactly(preRed1, preRed2, preDom2, othrRed, othrNot); + } + + @Test + public void test_batching() throws Exception { + for (int i = 0; i < 50; i++) { + persistToken(String.format("batch%2d", i), null, i % 2 == 0); + } + assertThat(ofy().load().type(AllocationToken.class).count()).isEqualTo(56); + runCommandForced("--prefix", "batch"); + assertThat(ofy().load().type(AllocationToken.class).count()).isEqualTo(56 - 25); + } + + @Test + public void test_prefixIsRequired() { + ParameterException thrown = assertThrows(ParameterException.class, () -> runCommandForced()); + assertThat(thrown) + .hasMessageThat() + .isEqualTo("The following option is required: -p, --prefix "); + } + + private static AllocationToken persistToken( + String token, @Nullable String domainName, boolean redeemed) { + AllocationToken.Builder builder = + new AllocationToken.Builder().setToken(token).setDomainName(domainName); + if (redeemed) { + builder.setRedemptionHistoryEntry(Key.create(HistoryEntry.class, 1051L)); + } + return persistResource(builder.build()); + } + + private static Collection reloadTokens(AllocationToken ... tokens) { + return ofy().load().entities(tokens).values(); + } +} diff --git a/javatests/google/registry/tools/EppToolVerifier.java b/javatests/google/registry/tools/EppToolVerifier.java index 7e912c148..03cfc5d08 100644 --- a/javatests/google/registry/tools/EppToolVerifier.java +++ b/javatests/google/registry/tools/EppToolVerifier.java @@ -28,7 +28,6 @@ import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.net.MediaType; -import google.registry.tools.CommandWithConnection.Connection; import google.registry.tools.server.ToolsTestData; import java.net.URLDecoder; import java.util.Map; @@ -49,7 +48,7 @@ import org.mockito.ArgumentCaptor; */ public class EppToolVerifier { - private final Connection connection = mock(Connection.class); + private final AppEngineConnection connection = mock(AppEngineConnection.class); private String clientId; private boolean superuser; @@ -167,11 +166,9 @@ public class EppToolVerifier { return; } ArgumentCaptor params = ArgumentCaptor.forClass(byte[].class); - verify(connection, atLeast(0)).send( - eq("/_dr/epptool"), - eq(ImmutableMap.of()), - eq(MediaType.FORM_DATA), - params.capture()); + verify(connection, atLeast(0)) + .sendPostRequest( + eq("/_dr/epptool"), eq(ImmutableMap.of()), eq(MediaType.FORM_DATA), params.capture()); capturedParams = ImmutableList.copyOf(params.getAllValues()); paramIndex = 0; } @@ -198,7 +195,7 @@ public class EppToolVerifier { } /** Returns the (mock) Connection that is being monitored by this verifier. */ - private Connection getConnection() { + private AppEngineConnection getConnection() { return connection; } } diff --git a/javatests/google/registry/tools/ExecuteEppCommandTest.java b/javatests/google/registry/tools/ExecuteEppCommandTest.java index 1958d2aca..759c3a0c2 100644 --- a/javatests/google/registry/tools/ExecuteEppCommandTest.java +++ b/javatests/google/registry/tools/ExecuteEppCommandTest.java @@ -18,19 +18,14 @@ import static google.registry.testing.JUnitBackports.assertThrows; import static java.nio.charset.StandardCharsets.UTF_8; import com.beust.jcommander.ParameterException; -import google.registry.testing.InjectRule; import google.registry.tools.server.ToolsTestData; import java.io.ByteArrayInputStream; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; /** Unit tests for {@link ExecuteEppCommand}. */ public class ExecuteEppCommandTest extends EppToolCommandTestCase { - @Rule - public InjectRule inject = new InjectRule(); - private String xmlInput; private String eppFile; @@ -60,8 +55,7 @@ public class ExecuteEppCommandTest extends EppToolCommandTestCase extends CommandTestCase { - @Mock Connection connection; + @Mock AppEngineConnection connection; /** Where to find the servlet task; set by the subclass. */ abstract String getTaskPath(); @@ -62,7 +61,7 @@ public abstract class ListObjectsCommandTestCase .collect(toImmutableList()); } command.setConnection(connection); - when(connection.send( + when(connection.sendPostRequest( eq(getTaskPath()), anyMapOf(String.class, Object.class), eq(MediaType.PLAIN_TEXT_UTF_8), @@ -82,7 +81,7 @@ public abstract class ListObjectsCommandTestCase fullFieldNames.ifPresent(aBoolean -> params.put(FULL_FIELD_NAMES_PARAM, aBoolean)); params.putAll(getOtherParameters()); verify(connection) - .send( + .sendPostRequest( eq(getTaskPath()), eq(params.build()), eq(MediaType.PLAIN_TEXT_UTF_8), eq(new byte[0])); } diff --git a/javatests/google/registry/tools/LoadTestCommandTest.java b/javatests/google/registry/tools/LoadTestCommandTest.java index a4a496563..914939d90 100644 --- a/javatests/google/registry/tools/LoadTestCommandTest.java +++ b/javatests/google/registry/tools/LoadTestCommandTest.java @@ -24,7 +24,6 @@ import static org.mockito.Mockito.verifyZeroInteractions; import com.google.common.collect.ImmutableMap; import com.google.common.net.MediaType; import google.registry.model.registrar.Registrar; -import google.registry.tools.CommandWithConnection.Connection; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -32,7 +31,7 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class LoadTestCommandTest extends CommandTestCase { - Connection connection = mock(Connection.class); + AppEngineConnection connection = mock(AppEngineConnection.class); @Before public void setUp() { @@ -55,11 +54,9 @@ public class LoadTestCommandTest extends CommandTestCase { .put("contactInfos", 1) .put("runSeconds", 4600) .build(); - verify(connection).send( - eq("/_dr/loadtest"), - eq(parms), - eq(MediaType.PLAIN_TEXT_UTF_8), - eq(new byte[0])); + verify(connection) + .sendPostRequest( + eq("/_dr/loadtest"), eq(parms), eq(MediaType.PLAIN_TEXT_UTF_8), eq(new byte[0])); } @Test @@ -86,11 +83,9 @@ public class LoadTestCommandTest extends CommandTestCase { .put("contactInfos", 15) .put("runSeconds", 16) .build(); - verify(connection).send( - eq("/_dr/loadtest"), - eq(parms), - eq(MediaType.PLAIN_TEXT_UTF_8), - eq(new byte[0])); + verify(connection) + .sendPostRequest( + eq("/_dr/loadtest"), eq(parms), eq(MediaType.PLAIN_TEXT_UTF_8), eq(new byte[0])); } @Test diff --git a/javatests/google/registry/tools/SetupOteCommandTest.java b/javatests/google/registry/tools/SetupOteCommandTest.java index 8ecdf7eb8..dc403172b 100644 --- a/javatests/google/registry/tools/SetupOteCommandTest.java +++ b/javatests/google/registry/tools/SetupOteCommandTest.java @@ -30,6 +30,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import google.registry.model.registrar.Registrar; +import google.registry.model.registrar.RegistrarContact; import google.registry.model.registry.Registry; import google.registry.model.registry.Registry.TldState; import google.registry.testing.DeterministicStringGenerator; @@ -57,6 +58,7 @@ public class SetupOteCommandTest extends CommandTestCase { @Before public void init() { + SetupOteCommand.interactive = false; command.validDnsWriterNames = ImmutableSet.of("FooDnsWriter", "BarDnsWriter", "VoidDnsWriter"); command.passwordGenerator = passwordGenerator; persistPremiumList("default_sandbox_list", "sandbox,USD 1000"); @@ -144,11 +146,22 @@ public class SetupOteCommandTest extends CommandTestCase { verifyRegistrarCreation(registrarName, allowedTld, password, ipWhitelist, false); } + private void verifyRegistrarContactCreation(String registrarName, String email) { + ImmutableSet registrarContacts = + loadRegistrar(registrarName).getContacts(); + assertThat(registrarContacts).hasSize(1); + RegistrarContact registrarContact = registrarContacts.stream().findAny().get(); + assertThat(registrarContact.getEmailAddress()).isEqualTo(email); + assertThat(registrarContact.getName()).isEqualTo(email); + assertThat(registrarContact.getGaeUserId()).isNotNull(); + } + @Test public void testSuccess() throws Exception { runCommandForced( "--ip_whitelist=1.1.1.1", "--registrar=blobio", + "--email=contact@email.com", "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename()); @@ -189,6 +202,12 @@ public class SetupOteCommandTest extends CommandTestCase { verifyRegistrarCreation("blobio-3", "blobio-ga", passwords.get(2), ipAddress); verifyRegistrarCreation("blobio-4", "blobio-ga", passwords.get(3), ipAddress); verifyRegistrarCreation("blobio-5", "blobio-eap", passwords.get(4), ipAddress); + + verifyRegistrarContactCreation("blobio-1", "contact@email.com"); + verifyRegistrarContactCreation("blobio-2", "contact@email.com"); + verifyRegistrarContactCreation("blobio-3", "contact@email.com"); + verifyRegistrarContactCreation("blobio-4", "contact@email.com"); + verifyRegistrarContactCreation("blobio-5", "contact@email.com"); } @Test @@ -196,6 +215,7 @@ public class SetupOteCommandTest extends CommandTestCase { runCommandForced( "--ip_whitelist=1.1.1.1", "--registrar=abc", + "--email=abc@email.com", "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename()); @@ -236,6 +256,12 @@ public class SetupOteCommandTest extends CommandTestCase { verifyRegistrarCreation("abc-3", "abc-ga", passwords.get(2), ipAddress); verifyRegistrarCreation("abc-4", "abc-ga", passwords.get(3), ipAddress); verifyRegistrarCreation("abc-5", "abc-eap", passwords.get(4), ipAddress); + + verifyRegistrarContactCreation("abc-1", "abc@email.com"); + verifyRegistrarContactCreation("abc-2", "abc@email.com"); + verifyRegistrarContactCreation("abc-3", "abc@email.com"); + verifyRegistrarContactCreation("abc-4", "abc@email.com"); + verifyRegistrarContactCreation("abc-5", "abc@email.com"); } @Test @@ -244,6 +270,7 @@ public class SetupOteCommandTest extends CommandTestCase { "--eap_only", "--ip_whitelist=1.1.1.1", "--registrar=blobio", + "--email=contact@email.com", "--dns_writers=VoidDnsWriter", "--certhash=" + SAMPLE_CERT_HASH); @@ -262,6 +289,8 @@ public class SetupOteCommandTest extends CommandTestCase { ImmutableList.of(CidrAddressBlock.create("1.1.1.1")); verifyRegistrarCreation("blobio-5", "blobio-eap", passwords.get(0), ipAddress, true); + + verifyRegistrarContactCreation("blobio-5", "contact@email.com"); } @Test @@ -270,6 +299,7 @@ public class SetupOteCommandTest extends CommandTestCase { "--eap_only", "--ip_whitelist=1.1.1.1", "--registrar=blobio", + "--email=contact@email.com", "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename()); @@ -288,6 +318,8 @@ public class SetupOteCommandTest extends CommandTestCase { CidrAddressBlock.create("1.1.1.1")); verifyRegistrarCreation("blobio-5", "blobio-eap", passwords.get(0), ipAddress); + + verifyRegistrarContactCreation("blobio-5", "contact@email.com"); } @Test @@ -295,6 +327,7 @@ public class SetupOteCommandTest extends CommandTestCase { runCommandForced( "--ip_whitelist=1.1.1.1,2.2.2.2", "--registrar=blobio", + "--email=contact@email.com", "--dns_writers=FooDnsWriter", "--certfile=" + getCertFilename()); @@ -336,6 +369,12 @@ public class SetupOteCommandTest extends CommandTestCase { verifyRegistrarCreation("blobio-3", "blobio-ga", passwords.get(2), ipAddresses); verifyRegistrarCreation("blobio-4", "blobio-ga", passwords.get(3), ipAddresses); verifyRegistrarCreation("blobio-5", "blobio-eap", passwords.get(4), ipAddresses); + + verifyRegistrarContactCreation("blobio-1", "contact@email.com"); + verifyRegistrarContactCreation("blobio-2", "contact@email.com"); + verifyRegistrarContactCreation("blobio-3", "contact@email.com"); + verifyRegistrarContactCreation("blobio-4", "contact@email.com"); + verifyRegistrarContactCreation("blobio-5", "contact@email.com"); } @Test @@ -343,6 +382,7 @@ public class SetupOteCommandTest extends CommandTestCase { runCommandForced( "--ip_whitelist=1.1.1.1", "--registrar=blobio", + "--email=contact@email.com", "--certfile=" + getCertFilename(), "--dns_writers=BarDnsWriter", "--premium_list=alternate_list"); @@ -384,6 +424,12 @@ public class SetupOteCommandTest extends CommandTestCase { verifyRegistrarCreation("blobio-3", "blobio-ga", passwords.get(2), ipAddress); verifyRegistrarCreation("blobio-4", "blobio-ga", passwords.get(3), ipAddress); verifyRegistrarCreation("blobio-5", "blobio-eap", passwords.get(4), ipAddress); + + verifyRegistrarContactCreation("blobio-1", "contact@email.com"); + verifyRegistrarContactCreation("blobio-2", "contact@email.com"); + verifyRegistrarContactCreation("blobio-3", "contact@email.com"); + verifyRegistrarContactCreation("blobio-4", "contact@email.com"); + verifyRegistrarContactCreation("blobio-5", "contact@email.com"); } @Test @@ -394,6 +440,7 @@ public class SetupOteCommandTest extends CommandTestCase { () -> runCommandForced( "--registrar=blobio", + "--email=contact@email.com", "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename())); assertThat(thrown).hasMessageThat().contains("option is required: -w, --ip_whitelist"); @@ -407,6 +454,7 @@ public class SetupOteCommandTest extends CommandTestCase { () -> runCommandForced( "--ip_whitelist=1.1.1.1", + "--email=contact@email.com", "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename())); assertThat(thrown).hasMessageThat().contains("option is required: -r, --registrar"); @@ -419,7 +467,10 @@ public class SetupOteCommandTest extends CommandTestCase { IllegalArgumentException.class, () -> runCommandForced( - "--ip_whitelist=1.1.1.1", "--dns_writers=VoidDnsWriter", "--registrar=blobio")); + "--ip_whitelist=1.1.1.1", + "--email=contact@email.com", + "--dns_writers=VoidDnsWriter", + "--registrar=blobio")); assertThat(thrown) .hasMessageThat() .contains( @@ -434,6 +485,7 @@ public class SetupOteCommandTest extends CommandTestCase { () -> runCommandForced( "--ip_whitelist=1.1.1.1", + "--email=contact@email.com", "--dns_writers=VoidDnsWriter", "--registrar=blobio", "--certfile=" + getCertFilename(), @@ -452,11 +504,26 @@ public class SetupOteCommandTest extends CommandTestCase { () -> runCommandForced( "--ip_whitelist=1.1.1.1", + "--email=contact@email.com", "--certfile=" + getCertFilename(), "--registrar=blobio")); assertThat(thrown).hasMessageThat().contains("option is required: --dns_writers"); } + @Test + public void testFailure_missingEmail() { + ParameterException thrown = + assertThrows( + ParameterException.class, + () -> + runCommandForced( + "--ip_whitelist=1.1.1.1", + "--dns_writers=VoidDnsWriter", + "--certfile=" + getCertFilename(), + "--registrar=blobio")); + assertThat(thrown).hasMessageThat().contains("option is required: --email"); + } + @Test public void testFailure_invalidCert() { CertificateParsingException thrown = @@ -466,6 +533,7 @@ public class SetupOteCommandTest extends CommandTestCase { runCommandForced( "--ip_whitelist=1.1.1.1", "--registrar=blobio", + "--email=contact@email.com", "--dns_writers=VoidDnsWriter", "--certfile=/dev/null")); assertThat(thrown).hasMessageThat().contains("No X509Certificate found"); @@ -480,6 +548,7 @@ public class SetupOteCommandTest extends CommandTestCase { runCommandForced( "--ip_whitelist=1.1.1.1", "--registrar=3blobio", + "--email=contact@email.com", "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename())); assertThat(thrown).hasMessageThat().contains("Registrar name is invalid"); @@ -494,6 +563,7 @@ public class SetupOteCommandTest extends CommandTestCase { runCommandForced( "--ip_whitelist=1.1.1.1", "--registrar=blobio", + "--email=contact@email.com", "--dns_writers=InvalidDnsWriter", "--certfile=" + getCertFilename())); assertThat(thrown) @@ -510,6 +580,7 @@ public class SetupOteCommandTest extends CommandTestCase { runCommandForced( "--ip_whitelist=1.1.1.1", "--registrar=bl", + "--email=contact@email.com", "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename())); assertThat(thrown).hasMessageThat().contains("Registrar name is invalid"); @@ -524,6 +595,7 @@ public class SetupOteCommandTest extends CommandTestCase { runCommandForced( "--ip_whitelist=1.1.1.1", "--registrar=blobiotoooolong", + "--email=contact@email.com", "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename())); assertThat(thrown).hasMessageThat().contains("Registrar name is invalid"); @@ -538,6 +610,7 @@ public class SetupOteCommandTest extends CommandTestCase { runCommandForced( "--ip_whitelist=1.1.1.1", "--registrar=blo#bio", + "--email=contact@email.com", "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename())); assertThat(thrown).hasMessageThat().contains("Registrar name is invalid"); @@ -552,6 +625,7 @@ public class SetupOteCommandTest extends CommandTestCase { runCommandForced( "--ip_whitelist=1.1.1.1", "--registrar=blobio", + "--email=contact@email.com", "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename(), "--premium_list=foo")); @@ -568,6 +642,7 @@ public class SetupOteCommandTest extends CommandTestCase { runCommandForced( "--ip_whitelist=1.1.1.1", "--registrar=blobio", + "--email=contact@email.com", "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename())); assertThat(thrown).hasMessageThat().contains("TLD 'blobio-sunrise' already exists"); @@ -587,6 +662,7 @@ public class SetupOteCommandTest extends CommandTestCase { runCommandForced( "--ip_whitelist=1.1.1.1", "--registrar=blobio", + "--email=contact@email.com", "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename())); assertThat(thrown).hasMessageThat().contains("Registrar blobio-1 already exists"); diff --git a/javatests/google/registry/tools/ShellCommandTest.java b/javatests/google/registry/tools/ShellCommandTest.java index bc6a1447d..e32a46f08 100644 --- a/javatests/google/registry/tools/ShellCommandTest.java +++ b/javatests/google/registry/tools/ShellCommandTest.java @@ -53,6 +53,9 @@ public class ShellCommandTest { PrintStream orgStdout; PrintStream orgStderr; + ByteArrayOutputStream stdout; + ByteArrayOutputStream stderr; + public ShellCommandTest() {} @Before @@ -152,7 +155,6 @@ public class ShellCommandTest { public void testMultipleCommandInvocations() throws Exception { try (RegistryCli cli = new RegistryCli("unittest", ImmutableMap.of("test_command", TestCommand.class))) { - cli.uploadMetrics = false; RegistryToolEnvironment.UNITTEST.setup(); cli.setEnvironment(RegistryToolEnvironment.UNITTEST); cli.run(new String[] {"test_command", "-x", "xval", "arg1", "arg2"}); @@ -170,7 +172,7 @@ public class ShellCommandTest { public void testNonExistentCommand() { try (RegistryCli cli = new RegistryCli("unittest", ImmutableMap.of("test_command", TestCommand.class))) { - cli.uploadMetrics = false; + cli.setEnvironment(RegistryToolEnvironment.UNITTEST); assertThrows(MissingCommandException.class, () -> cli.run(new String[] {"bad_command"})); } @@ -270,14 +272,7 @@ public class ShellCommandTest { @Test public void testEncapsulatedOutput_command() throws Exception { RegistryToolEnvironment.ALPHA.setup(); - - // capture output (have to do this before the shell command is created) - ByteArrayOutputStream stdout = new ByteArrayOutputStream(); - ByteArrayOutputStream stderr = new ByteArrayOutputStream(); - System.setOut(new PrintStream(stdout)); - System.setErr(new PrintStream(stderr)); - System.setIn(new ByteArrayInputStream("command1\n".getBytes(UTF_8))); - + captureOutput(); ShellCommand shellCommand = new ShellCommand( args -> { @@ -297,6 +292,33 @@ public class ShellCommandTest { + "SUCCESS\n"); } + @Test + public void testEncapsulatedOutput_noCommand() throws Exception { + captureOutput(); + ShellCommand shellCommand = + createShellCommand( + args -> { + System.out.println("first line"); + }, + Duration.ZERO, + "", + "do something"); + shellCommand.encapsulateOutput = true; + shellCommand.run(); + assertThat(stderr.toString()).isEmpty(); + assertThat(stdout.toString()) + .isEqualTo("out: first line\nSUCCESS\n"); + } + + void captureOutput() { + // capture output (have to do this before the shell command is created) + stdout = new ByteArrayOutputStream(); + stderr = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdout)); + System.setErr(new PrintStream(stderr)); + System.setIn(new ByteArrayInputStream("command1\n".getBytes(UTF_8))); + } + @Parameters(commandDescription = "Test command") static class TestCommand implements Command { enum OrgType { diff --git a/javatests/google/registry/tools/UpdatePremiumListCommandTest.java b/javatests/google/registry/tools/UpdatePremiumListCommandTest.java index d8858547a..2a8b0ba8d 100644 --- a/javatests/google/registry/tools/UpdatePremiumListCommandTest.java +++ b/javatests/google/registry/tools/UpdatePremiumListCommandTest.java @@ -23,7 +23,6 @@ import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableMap; import com.google.common.net.MediaType; -import google.registry.tools.CommandWithConnection.Connection; import google.registry.tools.server.UpdatePremiumListAction; import org.junit.Before; import org.junit.Test; @@ -33,8 +32,7 @@ import org.mockito.Mock; public class UpdatePremiumListCommandTest extends CreateOrUpdatePremiumListCommandTestCase { - @Mock - Connection connection; + @Mock AppEngineConnection connection; String premiumTermsPath; String premiumTermsCsv; @@ -48,12 +46,12 @@ public class UpdatePremiumListCommandTest writeToNamedTmpFile( "example_premium_terms.csv", loadFile(UpdatePremiumListCommandTest.class, "example_premium_terms.csv")); - when(connection.send( - eq(UpdatePremiumListAction.PATH), - anyMapOf(String.class, String.class), - any(MediaType.class), - any(byte[].class))) - .thenReturn(JSON_SAFETY_PREFIX + "{\"status\":\"success\",\"lines\":[]}"); + when(connection.sendPostRequest( + eq(UpdatePremiumListAction.PATH), + anyMapOf(String.class, String.class), + any(MediaType.class), + any(byte[].class))) + .thenReturn(JSON_SAFETY_PREFIX + "{\"status\":\"success\",\"lines\":[]}"); } @Test diff --git a/javatests/google/registry/tools/UpdateSmdCommandTest.java b/javatests/google/registry/tools/UpdateSmdCommandTest.java index c1334b2ee..d112bf311 100644 --- a/javatests/google/registry/tools/UpdateSmdCommandTest.java +++ b/javatests/google/registry/tools/UpdateSmdCommandTest.java @@ -75,8 +75,6 @@ public class UpdateSmdCommandTest extends CommandTestCase { @Before public void init() { - // For SignedMark signature validity - inject.setStaticField(TmchCertificateAuthority.class, "clock", clock); inject.setStaticField(Ofy.class, "clock", clock); createTld("xn--q9jyb4c"); clock.advanceOneMilli(); @@ -87,7 +85,7 @@ public class UpdateSmdCommandTest extends CommandTestCase { .build()); clock.advanceOneMilli(); command.tmchUtils = - new DomainFlowTmchUtils(new TmchXmlSignature(new TmchCertificateAuthority(PILOT))); + new DomainFlowTmchUtils(new TmchXmlSignature(new TmchCertificateAuthority(PILOT, clock))); } private DomainApplication reloadDomainApplication() { diff --git a/javatests/google/registry/tools/VerifyOteCommandTest.java b/javatests/google/registry/tools/VerifyOteCommandTest.java index 5c6c2ccc9..39ed997f1 100644 --- a/javatests/google/registry/tools/VerifyOteCommandTest.java +++ b/javatests/google/registry/tools/VerifyOteCommandTest.java @@ -27,7 +27,6 @@ import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import google.registry.model.registrar.Registrar; -import google.registry.tools.CommandWithConnection.Connection; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -35,7 +34,7 @@ import org.mockito.Mock; /** Unit tests for {@link VerifyOteCommand}. */ public class VerifyOteCommandTest extends CommandTestCase { - @Mock private Connection connection; + @Mock private AppEngineConnection connection; @Before public void init() throws Exception { diff --git a/javatests/google/registry/ui/js/registrar/BUILD b/javatests/google/registry/ui/js/registrar/BUILD index 2107c6404..cb282707e 100644 --- a/javatests/google/registry/ui/js/registrar/BUILD +++ b/javatests/google/registry/ui/js/registrar/BUILD @@ -13,6 +13,7 @@ closure_js_library( deps = [ "//java/google/registry/ui/js", "//java/google/registry/ui/js/registrar", + "//java/google/registry/ui/soy/registrar", "@io_bazel_rules_closure//closure/library", "@io_bazel_rules_closure//closure/library:testing", ], @@ -28,7 +29,6 @@ closure_js_test( ":console_test_util", "//java/google/registry/ui/js", "//java/google/registry/ui/js/registrar", - "//java/google/registry/ui/soy/registrar", "//javatests/google/registry/ui/js:testing", "@io_bazel_rules_closure//closure/library", "@io_bazel_rules_closure//closure/library:testing", diff --git a/javatests/google/registry/ui/js/registrar/console_test.js b/javatests/google/registry/ui/js/registrar/console_test.js index fd3a346e4..afb2347bf 100644 --- a/javatests/google/registry/ui/js/registrar/console_test.js +++ b/javatests/google/registry/ui/js/registrar/console_test.js @@ -17,7 +17,6 @@ goog.setTestOnly(); goog.require('goog.dom'); goog.require('goog.dom.classlist'); goog.require('goog.json'); -goog.require('goog.soy'); goog.require('goog.testing.MockControl'); goog.require('goog.testing.PropertyReplacer'); goog.require('goog.testing.asserts'); @@ -25,15 +24,14 @@ goog.require('goog.testing.jsunit'); goog.require('goog.testing.mockmatchers'); goog.require('goog.testing.net.XhrIo'); goog.require('registry.registrar.ConsoleTestUtil'); -goog.require('registry.soy.registrar.console'); goog.require('registry.testing'); goog.require('registry.util'); -var $ = goog.dom.getRequiredElement; -var stubs = new goog.testing.PropertyReplacer(); +const $ = goog.dom.getRequiredElement; +const stubs = new goog.testing.PropertyReplacer(); -var test = { +const test = { testXsrfToken: 'testToken', testClientId: 'daddy', mockControl: new goog.testing.MockControl() @@ -44,24 +42,13 @@ function setUp() { registry.testing.addToDocument('
'); registry.testing.addToDocument('
'); stubs.setPath('goog.net.XhrIo', goog.testing.net.XhrIo); - var testElt = goog.dom.getElement('test'); - goog.soy.renderElement(testElt, registry.soy.registrar.console.main, { + registry.registrar.ConsoleTestUtil.renderConsoleMain($('test'), { xsrfToken: test.testXsrfToken, - username: 'blah', - logoutUrl: 'omg', - isAdmin: true, clientId: test.testClientId, - logoFilename: 'logo.png', - productName: 'Nomulus', - integrationEmail: 'integration@example.com', - supportEmail: 'support@example.com', - announcementsEmail: 'announcement@example.com', - supportPhoneNumber: '+1 (888) 555 0123', - technicalDocsUrl: 'http://example.com/techdocs', }); registry.registrar.ConsoleTestUtil.setup(test); - var regNavlist = $('reg-navlist'); - var active = regNavlist.querySelector('a[href="/registrar#contact-us"]'); + const regNavlist = $('reg-navlist'); + const active = regNavlist.querySelector('a[href="#contact-us"]'); assertTrue(active != null); } @@ -78,7 +65,7 @@ function testButter() { productName: 'Foo Registry' }); registry.util.butter('butter msg'); - var butter = goog.dom.getElementByClass(goog.getCssName('kd-butterbar')); + const butter = goog.dom.getElementByClass(goog.getCssName('kd-butterbar')); assertNotNull(butter.innerHTML.match(/.*butter msg.*/)); assertTrue(goog.dom.classlist.contains(butter, goog.getCssName('shown'))); } @@ -131,7 +118,7 @@ function testNavToResources() { premiumPriceAckRequired: false, readonly: true, }); - var xhr = goog.testing.net.XhrIo.getSendInstances().pop(); + const xhr = goog.testing.net.XhrIo.getSendInstances().pop(); assertTrue(xhr.isActive()); assertEquals('/registrar-settings', xhr.getLastUri()); assertEquals(test.testXsrfToken, @@ -157,12 +144,12 @@ function testNavToContactUs() { announcementsEmail: 'announcement@example.com', supportPhoneNumber: '+1 (888) 555 0123' }); - var xhr = goog.testing.net.XhrIo.getSendInstances().pop(); + const xhr = goog.testing.net.XhrIo.getSendInstances().pop(); assertTrue(xhr.isActive()); assertEquals('/registrar-settings', xhr.getLastUri()); assertEquals(test.testXsrfToken, xhr.getLastRequestHeaders()['X-CSRF-Token']); - var passcode = '5-5-5-5-5'; + const passcode = '5-5-5-5-5'; xhr.simulateResponse(200, goog.json.serialize({ status: 'SUCCESS', message: 'OK', diff --git a/javatests/google/registry/ui/js/registrar/console_test_util.js b/javatests/google/registry/ui/js/registrar/console_test_util.js index dc9f31685..2df8da282 100644 --- a/javatests/google/registry/ui/js/registrar/console_test_util.js +++ b/javatests/google/registry/ui/js/registrar/console_test_util.js @@ -18,9 +18,11 @@ goog.setTestOnly('registry.registrar.ConsoleTestUtil'); goog.require('goog.History'); goog.require('goog.asserts'); goog.require('goog.dom.xml'); +goog.require('goog.soy'); goog.require('goog.testing.mockmatchers'); goog.require('registry.registrar.Console'); goog.require('registry.registrar.EppSession'); +goog.require('registry.soy.registrar.console'); goog.require('registry.xml'); @@ -28,7 +30,7 @@ goog.require('registry.xml'); * Utility method that attaches mocks to a `TestCase`. This was * originally in the ctor for ConsoleTest and should simply be * inherited but jstd_test breaks inheritance in test cases. - * @param {Object} test the test case to configure. + * @param {!Object} test the test case to configure. */ registry.registrar.ConsoleTestUtil.setup = function(test) { test.historyMock = test.mockControl.createLooseMock(goog.History, true); @@ -42,13 +44,42 @@ registry.registrar.ConsoleTestUtil.setup = function(test) { .$returns(test.sessionMock); }; +/** + * Utility method that renders the registry.soy.registrar.console.main element. + * + * This element has a lot of parameters. We use defaults everywhere, but you can + * override them with 'opt_args'. + * + * @param {!Element} element the element whose content we are rendering into. + * @param {?Object=} opt_args override for the default values of the soy params. + */ +registry.registrar.ConsoleTestUtil.renderConsoleMain = function( + element, opt_args) { + const args = opt_args || {}; + goog.soy.renderElement(element, registry.soy.registrar.console.main, { + xsrfToken: args.xsrfToken || 'ignore', + username: args.username || 'jart', + logoutUrl: args.logoutUrl || 'https://logout.url.com', + isAdmin: goog.isDefAndNotNull(args.isAdmin) ? args.isAdmin : true, + clientId: args.clientId || 'ignore', + allClientIds: args.allClientIds || ['clientId1', 'clientId2'], + logoFilename: args.logoFilename || 'logo.png', + productName: args.productName || 'Nomulus', + integrationEmail: args.integrationEmail || 'integration@example.com', + supportEmail: args.supportEmail || 'support@example.com', + announcementsEmail: args.announcementsEmail || 'announcement@example.com', + supportPhoneNumber: args.supportPhoneNumber || '+1 (888) 555 0123', + technicalDocsUrl: args.technicalDocsUrl || 'http://example.com/techdocs', + }); +}; + /** * Simulates visiting a page on the console. Sets path, then mocks * session and calls `handleHashChange_`. - * @param {Object} test the test case to configure. - * @param {Object=} opt_args may include path, isEppLoggedIn. - * @param {Function=} opt_moar extra setup after called just before + * @param {!Object} test the test case to configure. + * @param {?Object=} opt_args may include path, isEppLoggedIn. + * @param {?Function=} opt_moar extra setup after called just before * `$replayAll`. See memegen/3437690. */ registry.registrar.ConsoleTestUtil.visit = function( @@ -72,7 +103,7 @@ registry.registrar.ConsoleTestUtil.visit = function( goog.testing.mockmatchers.isFunction) .$does(function(args, cb) { // XXX: Args should be checked. - var xml = goog.dom.xml.loadXml(opt_args.rspXml); + const xml = goog.dom.xml.loadXml(opt_args.rspXml); goog.asserts.assert(xml != null); cb(registry.xml.convertToJson(xml)); }).$anyTimes(); diff --git a/javatests/google/registry/ui/js/registrar/contact_settings_test.js b/javatests/google/registry/ui/js/registrar/contact_settings_test.js index 66c78b3f4..bf74d59f0 100644 --- a/javatests/google/registry/ui/js/registrar/contact_settings_test.js +++ b/javatests/google/registry/ui/js/registrar/contact_settings_test.js @@ -17,23 +17,21 @@ goog.setTestOnly(); goog.require('goog.array'); goog.require('goog.dispose'); goog.require('goog.dom'); -goog.require('goog.soy'); goog.require('goog.testing.MockControl'); goog.require('goog.testing.PropertyReplacer'); goog.require('goog.testing.asserts'); goog.require('goog.testing.jsunit'); goog.require('goog.testing.net.XhrIo'); goog.require('registry.registrar.ConsoleTestUtil'); -goog.require('registry.soy.registrar.console'); goog.require('registry.testing'); goog.require('registry.util'); -var $ = goog.dom.getRequiredElement; -var stubs = new goog.testing.PropertyReplacer(); -var testContact = null; +const $ = goog.dom.getRequiredElement; +const stubs = new goog.testing.PropertyReplacer(); +let testContact = null; -var test = { +const test = { testXsrfToken: '༼༎෴ ༎༽', testClientId: 'testClientId', mockControl: new goog.testing.MockControl() @@ -44,19 +42,9 @@ function setUp() { registry.testing.addToDocument('
'); registry.testing.addToDocument('
'); testContact = createTestContact(); - goog.soy.renderElement($('test'), registry.soy.registrar.console.main, { + registry.registrar.ConsoleTestUtil.renderConsoleMain($('test'), { xsrfToken: test.testXsrfToken, - username: 'blah', - logoutUrl: 'omg', - isAdmin: true, clientId: test.testClientId, - logoFilename: 'logo.png', - productName: 'Nomulus', - integrationEmail: 'integration@google.com', - supportEmail: 'support@google.com', - announcementsEmail: 'announcements@google.com', - supportPhoneNumber: '123 456 7890', - technicalDocsUrl: 'http://example.com/techdocs' }); stubs.setPath('goog.net.XhrIo', goog.testing.net.XhrIo); registry.registrar.ConsoleTestUtil.setup(test); @@ -82,7 +70,7 @@ function testCollectionView() { registry.testing.assertReqMockRsp( test.testXsrfToken, '/registrar-settings', - {op: 'read', args: {}}, + {op: 'read', id: 'testClientId', args: {}}, { status: 'SUCCESS', message: 'OK', @@ -106,7 +94,7 @@ function testItemView() { registry.testing.assertReqMockRsp( test.testXsrfToken, '/registrar-settings', - {op: 'read', args: {}}, + {op: 'read', id: 'testClientId', args: {}}, { status: 'SUCCESS', message: 'OK', @@ -149,6 +137,7 @@ function testItemEdit() { '/registrar-settings', { op: 'update', + id: 'testClientId', args: { contacts: [testContact], readonly: false @@ -181,6 +170,7 @@ function testChangeContactTypes() { '/registrar-settings', { op: 'update', + id: 'testClientId', args: { contacts: [testContact], readonly: false @@ -204,9 +194,9 @@ function testOneOfManyUpdate() { registry.registrar.ConsoleTestUtil.visit(test, { path: 'contact-settings/test@example.com', xsrfToken: test.testXsrfToken, - testClientId: test.testClientId + clientId: test.testClientId }); - var testContacts = [ + const testContacts = [ createTestContact('new1@asdf.com'), testContact, createTestContact('new2@asdf.com') @@ -214,7 +204,7 @@ function testOneOfManyUpdate() { registry.testing.assertReqMockRsp( test.testXsrfToken, '/registrar-settings', - {op: 'read', args: {}}, + {op: 'read', id: 'testClientId', args: {}}, { status: 'SUCCESS', message: 'OK', @@ -235,7 +225,14 @@ function testOneOfManyUpdate() { registry.testing.assertReqMockRsp( test.testXsrfToken, '/registrar-settings', - {op: 'update', args: {contacts: testContacts, readonly: false}}, + { + op: 'update', + id: 'testClientId', + args: { + contacts: testContacts, + readonly: false, + }, + }, { status: 'SUCCESS', message: 'OK', @@ -251,13 +248,15 @@ function testDomainWhoisAbuseContactOverride() { registry.registrar.ConsoleTestUtil.visit(test, { path: 'contact-settings/test@example.com', xsrfToken: test.testXsrfToken, - testClientId: test.testClientId + clientId: test.testClientId }); - var oldDomainWhoisAbuseContact = createTestContact('old@asdf.com'); + const oldDomainWhoisAbuseContact = createTestContact('old@asdf.com'); oldDomainWhoisAbuseContact.visibleInDomainWhoisAsAbuse = true; - var testContacts = [oldDomainWhoisAbuseContact, testContact]; + const testContacts = [oldDomainWhoisAbuseContact, testContact]; registry.testing.assertReqMockRsp( - test.testXsrfToken, '/registrar-settings', {op: 'read', args: {}}, + test.testXsrfToken, + '/registrar-settings', + {op: 'read', id: 'testClientId', args: {}}, {status: 'SUCCESS', message: 'OK', results: [{contacts: testContacts}]}); // Edit testContact. registry.testing.click($('reg-app-btn-edit')); @@ -271,8 +270,13 @@ function testDomainWhoisAbuseContactOverride() { testContact.visibleInDomainWhoisAsAbuse = true; oldDomainWhoisAbuseContact.visibleInDomainWhoisAsAbuse = false; registry.testing.assertReqMockRsp( - test.testXsrfToken, '/registrar-settings', - {op: 'update', args: {contacts: testContacts, readonly: false}}, + test.testXsrfToken, + '/registrar-settings', + { + op: 'update', + id: 'testClientId', + args: {contacts: testContacts, readonly: false}, + }, {status: 'SUCCESS', message: 'OK', results: [{contacts: testContacts}]}); } @@ -281,9 +285,9 @@ function testDelete() { registry.registrar.ConsoleTestUtil.visit(test, { path: 'contact-settings/test@example.com', xsrfToken: test.testXsrfToken, - testClientId: test.testClientId + clientId: test.testClientId }); - var testContacts = [ + const testContacts = [ createTestContact('new1@asdf.com'), testContact, createTestContact('new2@asdf.com') @@ -291,7 +295,7 @@ function testDelete() { registry.testing.assertReqMockRsp( test.testXsrfToken, '/registrar-settings', - {op: 'read', args: {}}, + {op: 'read', id: 'testClientId', args: {}}, { status: 'SUCCESS', message: 'OK', @@ -309,7 +313,11 @@ function testDelete() { registry.testing.assertReqMockRsp( test.testXsrfToken, '/registrar-settings', - {op: 'update', args: {contacts: testContacts, readonly: false}}, + { + op: 'update', + id: 'testClientId', + args: {contacts: testContacts, readonly: false}, + }, { status: 'SUCCESS', message: 'OK', @@ -323,10 +331,10 @@ function testDelete() { /** * @param {string=} opt_email - * @return {Object} + * @return {!Object} */ function createTestContact(opt_email) { - var nameMail = opt_email || 'test@example.com'; + const nameMail = opt_email || 'test@example.com'; return { name: nameMail, emailAddress: nameMail, @@ -343,14 +351,14 @@ function createTestContact(opt_email) { /** * Convert parsed formContact to simulated wire form. * @param {!Element} contact - * @return {Object} + * @return {!Object} */ function simulateJsonForContact(contact) { contact.visibleInWhoisAsAdmin = contact.visibleInWhoisAsAdmin == 'true'; contact.visibleInWhoisAsTech = contact.visibleInWhoisAsTech == 'true'; contact.visibleInDomainWhoisAsAbuse = contact.visibleInDomainWhoisAsAbuse == 'true'; contact.types = ''; - for (var tNdx in contact.type) { + for (const tNdx in contact.type) { if (contact.type[tNdx]) { if (contact.types.length > 0) { contact.types += ','; diff --git a/javatests/google/registry/ui/js/registrar/contact_test.js b/javatests/google/registry/ui/js/registrar/contact_test.js index 62781b12c..a639458d0 100644 --- a/javatests/google/registry/ui/js/registrar/contact_test.js +++ b/javatests/google/registry/ui/js/registrar/contact_test.js @@ -16,18 +16,16 @@ goog.setTestOnly(); goog.require('goog.dispose'); goog.require('goog.dom'); -goog.require('goog.soy'); goog.require('goog.testing.MockControl'); goog.require('goog.testing.asserts'); goog.require('goog.testing.jsunit'); goog.require('registry.registrar.ConsoleTestUtil'); -goog.require('registry.soy.registrar.console'); goog.require('registry.testing'); -var $ = goog.dom.getRequiredElement; +const $ = goog.dom.getRequiredElement; -var test = { +const test = { mockControl: new goog.testing.MockControl() }; @@ -35,20 +33,7 @@ var test = { function setUp() { registry.testing.addToDocument('
'); registry.testing.addToDocument('
'); - goog.soy.renderElement($('test'), registry.soy.registrar.console.main, { - xsrfToken: 'test', - username: 'blah', - logoutUrl: 'omg', - isAdmin: true, - clientId: 'daddy', - logoFilename: 'logo.png', - productName: 'Nomulus', - integrationEmail: 'integration@example.com', - supportEmail: 'support@example.com', - announcementsEmail: 'announcement@example.com', - supportPhoneNumber: '+1 (888) 555 0123', - technicalDocsUrl: 'http://example.com/techdocs' - }); + registry.registrar.ConsoleTestUtil.renderConsoleMain($('test'), {}); registry.registrar.ConsoleTestUtil.setup(test); } @@ -126,7 +111,7 @@ function testEdit() { /** Contact hash path should nav to contact page. */ function testAddPostalInfo() { testEdit(); - var addPiBtn = $('domain-contact-postalInfo-add-button'); + const addPiBtn = $('domain-contact-postalInfo-add-button'); assertNull(addPiBtn.getAttribute('disabled')); registry.testing.click(addPiBtn); assertTrue(addPiBtn.hasAttribute('disabled')); diff --git a/javatests/google/registry/ui/js/registrar/domain_test.js b/javatests/google/registry/ui/js/registrar/domain_test.js index 44e10d6a8..92630b8a8 100644 --- a/javatests/google/registry/ui/js/registrar/domain_test.js +++ b/javatests/google/registry/ui/js/registrar/domain_test.js @@ -17,7 +17,6 @@ goog.setTestOnly(); goog.require('goog.History'); goog.require('goog.dispose'); goog.require('goog.dom'); -goog.require('goog.soy'); goog.require('goog.testing.MockControl'); goog.require('goog.testing.PropertyReplacer'); goog.require('goog.testing.asserts'); @@ -25,36 +24,23 @@ goog.require('goog.testing.jsunit'); goog.require('goog.testing.mockmatchers'); goog.require('goog.testing.net.XhrIo'); goog.require('registry.registrar.Console'); -goog.require('registry.soy.registrar.console'); +goog.require('registry.registrar.ConsoleTestUtil'); goog.require('registry.testing'); -var $ = goog.dom.getRequiredElement; -var _ = goog.testing.mockmatchers.ignoreArgument; -var stubs = new goog.testing.PropertyReplacer(); -var mocks = new goog.testing.MockControl(); +const $ = goog.dom.getRequiredElement; +const _ = goog.testing.mockmatchers.ignoreArgument; +const stubs = new goog.testing.PropertyReplacer(); +const mocks = new goog.testing.MockControl(); -var historyMock; -var registrarConsole; +let historyMock; +let registrarConsole; function setUp() { registry.testing.addToDocument('
'); registry.testing.addToDocument('
'); - goog.soy.renderElement($('test'), registry.soy.registrar.console.main, { - xsrfToken: 'ignore', - username: 'jart', - logoutUrl: 'https://justinetunney.com', - isAdmin: true, - clientId: 'ignore', - logoFilename: 'logo.png', - productName: 'Nomulus', - integrationEmail: 'integration@example.com', - supportEmail: 'support@example.com', - announcementsEmail: 'announcement@example.com', - supportPhoneNumber: '+1 (888) 555 0123', - technicalDocsUrl: 'http://example.com/techdocs' - }); + registry.registrar.ConsoleTestUtil.renderConsoleMain($('test'), {}); stubs.setPath('goog.net.XhrIo', goog.testing.net.XhrIo); historyMock = mocks.createStrictMock(goog.History); @@ -81,7 +67,7 @@ function tearDown() { /** Handles EPP login. */ function handleLogin() { - var request = registry.testing.loadXml( + const request = registry.testing.loadXml( '' + '' + ' ' + @@ -101,7 +87,7 @@ function handleLogin() { ' asdf-1235' + ' ' + ''); - var response = registry.testing.loadXml( + const response = registry.testing.loadXml( '' + '' + ' ' + @@ -114,7 +100,7 @@ function handleLogin() { ' ' + ' ' + ''); - var xhr = goog.testing.net.XhrIo.getSendInstances().pop(); + const xhr = goog.testing.net.XhrIo.getSendInstances().pop(); assertTrue(xhr.isActive()); assertEquals('/registrar-xhr', xhr.getLastUri()); assertEquals('☢', xhr.getLastRequestHeaders()['X-CSRF-Token']); @@ -132,7 +118,7 @@ function testView() { registrarConsole.handleHashChange(); handleLogin(); - var request = registry.testing.loadXml( + const request = registry.testing.loadXml( '' + '' + ' ' + @@ -144,7 +130,7 @@ function testView() { ' abc-1234' + ' ' + ''); - var response = registry.testing.loadXml( + const response = registry.testing.loadXml( '' + '' + ' ' + @@ -180,7 +166,7 @@ function testView() { ' ' + ' ' + ''); - var xhr = goog.testing.net.XhrIo.getSendInstances().pop(); + const xhr = goog.testing.net.XhrIo.getSendInstances().pop(); assertTrue('XHR is inactive.', xhr.isActive()); assertEquals('/registrar-xhr', xhr.getLastUri()); assertEquals('☢', xhr.getLastRequestHeaders()['X-CSRF-Token']); @@ -218,7 +204,7 @@ function testEdit() { registry.testing.click($('reg-app-btn-save')); - var request = registry.testing.loadXml( + let request = registry.testing.loadXml( '' + '' + ' ' + @@ -236,7 +222,7 @@ function testEdit() { ' abc-1234' + ' ' + ''); - var response = registry.testing.loadXml( + let response = registry.testing.loadXml( '' + '' + ' ' + @@ -249,7 +235,7 @@ function testEdit() { ' ' + ' ' + ''); - var xhr = goog.testing.net.XhrIo.getSendInstances().pop(); + let xhr = goog.testing.net.XhrIo.getSendInstances().pop(); assertTrue('XHR is inactive.', xhr.isActive()); assertEquals('/registrar-xhr', xhr.getLastUri()); assertEquals('☢', xhr.getLastRequestHeaders()['X-CSRF-Token']); @@ -371,7 +357,7 @@ function testCreate() { registry.testing.click($('reg-app-btn-save')); - var request = registry.testing.loadXml( + let request = registry.testing.loadXml( '' + '' + ' ' + @@ -391,7 +377,7 @@ function testCreate() { ' abc-1234' + ' ' + ''); - var response = registry.testing.loadXml( + let response = registry.testing.loadXml( '' + '' + ' ' + @@ -411,7 +397,7 @@ function testCreate() { ' ' + ' ' + ''); - var xhr = goog.testing.net.XhrIo.getSendInstances().pop(); + let xhr = goog.testing.net.XhrIo.getSendInstances().pop(); assertTrue('XHR is inactive.', xhr.isActive()); assertEquals('/registrar-xhr', xhr.getLastUri()); assertEquals('☢', xhr.getLastRequestHeaders()['X-CSRF-Token']); diff --git a/javatests/google/registry/ui/js/registrar/host_test.js b/javatests/google/registry/ui/js/registrar/host_test.js index 0ed64350e..71fea32c0 100644 --- a/javatests/google/registry/ui/js/registrar/host_test.js +++ b/javatests/google/registry/ui/js/registrar/host_test.js @@ -17,7 +17,6 @@ goog.setTestOnly(); goog.require('goog.History'); goog.require('goog.dispose'); goog.require('goog.dom'); -goog.require('goog.soy'); goog.require('goog.testing.MockControl'); goog.require('goog.testing.PropertyReplacer'); goog.require('goog.testing.asserts'); @@ -25,36 +24,23 @@ goog.require('goog.testing.jsunit'); goog.require('goog.testing.mockmatchers'); goog.require('goog.testing.net.XhrIo'); goog.require('registry.registrar.Console'); -goog.require('registry.soy.registrar.console'); +goog.require('registry.registrar.ConsoleTestUtil'); goog.require('registry.testing'); -var $ = goog.dom.getRequiredElement; -var _ = goog.testing.mockmatchers.ignoreArgument; -var stubs = new goog.testing.PropertyReplacer(); -var mocks = new goog.testing.MockControl(); +const $ = goog.dom.getRequiredElement; +const _ = goog.testing.mockmatchers.ignoreArgument; +const stubs = new goog.testing.PropertyReplacer(); +const mocks = new goog.testing.MockControl(); -var historyMock; -var registrarConsole; +let historyMock; +let registrarConsole; function setUp() { registry.testing.addToDocument('
'); registry.testing.addToDocument('
'); - goog.soy.renderElement($('test'), registry.soy.registrar.console.main, { - xsrfToken: 'ignore', - username: 'jart', - logoutUrl: 'https://example.com', - isAdmin: true, - clientId: 'ignore', - logoFilename: 'logo.png', - productName: 'Nomulus', - integrationEmail: 'integration@example.com', - supportEmail: 'support@example.com', - announcementsEmail: 'announcement@example.com', - supportPhoneNumber: '+1 (888) 555 0123', - technicalDocsUrl: 'http://example.com/techdocs' - }); + registry.registrar.ConsoleTestUtil.renderConsoleMain($('test'), {}); stubs.setPath('goog.net.XhrIo', goog.testing.net.XhrIo); historyMock = mocks.createStrictMock(goog.History); @@ -81,7 +67,7 @@ function tearDown() { /** Handles EPP login. */ function handleLogin() { - var request = registry.testing.loadXml( + const request = registry.testing.loadXml( '' + '' + ' ' + @@ -101,7 +87,7 @@ function handleLogin() { ' asdf-1235' + ' ' + ''); - var response = registry.testing.loadXml( + const response = registry.testing.loadXml( '' + '' + ' ' + @@ -114,7 +100,7 @@ function handleLogin() { ' ' + ' ' + ''); - var xhr = goog.testing.net.XhrIo.getSendInstances().pop(); + const xhr = goog.testing.net.XhrIo.getSendInstances().pop(); assertTrue(xhr.isActive()); assertEquals('/registrar-xhr', xhr.getLastUri()); assertEquals('☢', xhr.getLastRequestHeaders()['X-CSRF-Token']); @@ -132,7 +118,7 @@ function testView() { registrarConsole.handleHashChange(); handleLogin(); - var request = registry.testing.loadXml( + const request = registry.testing.loadXml( '' + '' + ' ' + @@ -144,7 +130,7 @@ function testView() { ' abc-1234' + ' ' + ''); - var response = registry.testing.loadXml( + const response = registry.testing.loadXml( '' + '' + @@ -170,7 +156,7 @@ function testView() { ' ' + ' ' + ''); - var xhr = goog.testing.net.XhrIo.getSendInstances().pop(); + const xhr = goog.testing.net.XhrIo.getSendInstances().pop(); assertTrue('XHR is inactive.', xhr.isActive()); assertEquals('/registrar-xhr', xhr.getLastUri()); assertEquals('application/epp+xml', @@ -207,7 +193,7 @@ function testEditFirstAddr_ignoreSecond_addThird() { registry.testing.click($('reg-app-btn-save')); - var request = registry.testing.loadXml( + let request = registry.testing.loadXml( '' + '' + ' ' + @@ -226,7 +212,7 @@ function testEditFirstAddr_ignoreSecond_addThird() { ' abc-1234' + ' ' + ''); - var response = registry.testing.loadXml( + let response = registry.testing.loadXml( '' + '' + ' ' + @@ -239,7 +225,7 @@ function testEditFirstAddr_ignoreSecond_addThird() { ' ' + ' ' + ''); - var xhr = goog.testing.net.XhrIo.getSendInstances().pop(); + let xhr = goog.testing.net.XhrIo.getSendInstances().pop(); assertTrue('XHR is inactive.', xhr.isActive()); assertEquals('/registrar-xhr', xhr.getLastUri()); assertEquals('☢', xhr.getLastRequestHeaders()['X-CSRF-Token']); @@ -327,7 +313,7 @@ function testCreate() { registry.testing.click($('reg-app-btn-save')); - var request = registry.testing.loadXml( + let request = registry.testing.loadXml( '' + '' + ' ' + @@ -342,7 +328,7 @@ function testCreate() { ' abc-1234' + ' ' + ''); - var response = registry.testing.loadXml( + let response = registry.testing.loadXml( '' + '' + ' ' + @@ -361,7 +347,7 @@ function testCreate() { ' ' + ' ' + ''); - var xhr = goog.testing.net.XhrIo.getSendInstances().pop(); + let xhr = goog.testing.net.XhrIo.getSendInstances().pop(); assertTrue('XHR is inactive.', xhr.isActive()); assertEquals('/registrar-xhr', xhr.getLastUri()); assertEquals('☢', xhr.getLastRequestHeaders()['X-CSRF-Token']); diff --git a/javatests/google/registry/ui/js/registrar/security_settings_test.js b/javatests/google/registry/ui/js/registrar/security_settings_test.js index 0babd4512..4e7aaafc1 100644 --- a/javatests/google/registry/ui/js/registrar/security_settings_test.js +++ b/javatests/google/registry/ui/js/registrar/security_settings_test.js @@ -16,22 +16,20 @@ goog.setTestOnly(); goog.require('goog.dispose'); goog.require('goog.dom'); -goog.require('goog.soy'); goog.require('goog.testing.MockControl'); goog.require('goog.testing.PropertyReplacer'); goog.require('goog.testing.asserts'); goog.require('goog.testing.jsunit'); goog.require('goog.testing.net.XhrIo'); goog.require('registry.registrar.ConsoleTestUtil'); -goog.require('registry.soy.registrar.console'); goog.require('registry.testing'); goog.require('registry.util'); -var $ = goog.dom.getRequiredElement; -var stubs = new goog.testing.PropertyReplacer(); +const $ = goog.dom.getRequiredElement; +const stubs = new goog.testing.PropertyReplacer(); -var expectedRegistrar = { +const expectedRegistrar = { ipAddressWhitelist: [], phonePasscode: '12345', clientCertificate: null, @@ -39,7 +37,7 @@ var expectedRegistrar = { failoverClientCertificate: null }; -var test = { +const test = { testXsrfToken: '༼༎෴ ༎༽', testClientId: 'testClientId', mockControl: new goog.testing.MockControl() @@ -49,19 +47,9 @@ var test = { function setUp() { registry.testing.addToDocument('
'); registry.testing.addToDocument('
'); - goog.soy.renderElement($('test'), registry.soy.registrar.console.main, { - username: 'jart', - logoutUrl: 'https://example.com', - isAdmin: true, + registry.registrar.ConsoleTestUtil.renderConsoleMain($('test'), { xsrfToken: test.testXsrfToken, clientId: test.testClientId, - logoFilename: 'logo.png', - productName: 'Nomulus', - integrationEmail: 'integration@example.com', - supportEmail: 'support@example.com', - announcementsEmail: 'announcement@example.com', - supportPhoneNumber: '+1 (888) 555 0123', - technicalDocsUrl: 'http://example.com/techdocs' }); stubs.setPath('goog.net.XhrIo', goog.testing.net.XhrIo); registry.registrar.ConsoleTestUtil.setup(test); @@ -80,12 +68,12 @@ function testView() { registry.registrar.ConsoleTestUtil.visit(test, { path: 'security-settings', xsrfToken: test.testXsrfToken, - testClientId: test.testClientId + clientId: test.testClientId }); registry.testing.assertReqMockRsp( test.testXsrfToken, '/registrar-settings', - {op: 'read', args: {}}, + {op: 'read', id: 'testClientId', args: {}}, { status: 'SUCCESS', message: 'OK', @@ -101,14 +89,14 @@ function testEdit() { registry.testing.click($('reg-app-btn-edit')); - var form = document.forms.namedItem('item'); + const form = document.forms.namedItem('item'); form.elements['newIp'].value = '1.1.1.1'; registry.testing.click($('btn-add-ip')); form.elements['newIp'].value = '2.2.2.2'; registry.testing.click($('btn-add-ip')); - var exampleCert = $('exampleCert').value; - var exampleCertHash = '6NKKNBnd2fKFooBINmn3V7L3JOTHh02+2lAqYHdlTgk'; + const exampleCert = $('exampleCert').value; + const exampleCertHash = '6NKKNBnd2fKFooBINmn3V7L3JOTHh02+2lAqYHdlTgk'; form.elements['clientCertificate'].value = exampleCert; form.elements['failoverClientCertificate'].value = 'bourgeois blues'; registry.testing.click($('reg-app-btn-save')); @@ -116,7 +104,7 @@ function testEdit() { registry.testing.assertReqMockRsp( test.testXsrfToken, '/registrar-settings', - {op: 'update', args: { + {op: 'update', id: 'testClientId', args: { clientCertificate: exampleCert, clientCertificateHash: null, failoverClientCertificate: 'bourgeois blues', @@ -137,7 +125,7 @@ function testEdit() { registry.testing.assertReqMockRsp( test.testXsrfToken, '/registrar-settings', - {op: 'read', args: {}}, + {op: 'read', id: 'testClientId', args: {}}, {status: 'SUCCESS', message: 'OK', results: [expectedRegistrar]}); diff --git a/javatests/google/registry/ui/js/registrar/whois_settings_test.js b/javatests/google/registry/ui/js/registrar/whois_settings_test.js index cc708d0be..1891ccf13 100644 --- a/javatests/google/registry/ui/js/registrar/whois_settings_test.js +++ b/javatests/google/registry/ui/js/registrar/whois_settings_test.js @@ -17,23 +17,21 @@ goog.setTestOnly(); goog.require('goog.dispose'); goog.require('goog.dom'); goog.require('goog.dom.classlist'); -goog.require('goog.soy'); goog.require('goog.testing.MockControl'); goog.require('goog.testing.PropertyReplacer'); goog.require('goog.testing.asserts'); goog.require('goog.testing.jsunit'); goog.require('goog.testing.net.XhrIo'); goog.require('registry.registrar.ConsoleTestUtil'); -goog.require('registry.soy.registrar.console'); goog.require('registry.testing'); goog.require('registry.util'); -var $ = goog.dom.getRequiredElement; -var $$ = goog.dom.getRequiredElementByClass; -var stubs = new goog.testing.PropertyReplacer(); +const $ = goog.dom.getRequiredElement; +const $$ = goog.dom.getRequiredElementByClass; +const stubs = new goog.testing.PropertyReplacer(); -var test = { +const test = { testXsrfToken: '༼༎෴ ༎༽', testClientId: 'testClientId', mockControl: new goog.testing.MockControl() @@ -43,19 +41,9 @@ var test = { function setUp() { registry.testing.addToDocument('
'); registry.testing.addToDocument('
'); - goog.soy.renderElement($('test'), registry.soy.registrar.console.main, { + registry.registrar.ConsoleTestUtil.renderConsoleMain($('test'), { xsrfToken: test.testXsrfToken, - username: 'blah', - logoutUrl: 'omg', - isAdmin: true, clientId: test.testClientId, - logoFilename: 'logo.png', - productName: 'Nomulus', - integrationEmail: 'integration@example.com', - supportEmail: 'support@example.com', - announcementsEmail: 'announcement@example.com', - supportPhoneNumber: '+1 (888) 555 0123', - technicalDocsUrl: 'http://example.com/techdocs' }); stubs.setPath('goog.net.XhrIo', goog.testing.net.XhrIo); registry.registrar.ConsoleTestUtil.setup(test); @@ -72,7 +60,7 @@ function tearDown() { /** * Creates test registrar. - * @return {Object} + * @return {!Object} */ function createTestRegistrar() { return { @@ -98,19 +86,19 @@ function testView() { registry.registrar.ConsoleTestUtil.visit(test, { path: 'whois-settings', xsrfToken: test.testXsrfToken, - testClientId: test.testClientId + clientId: test.testClientId }); - var testRegistrar = createTestRegistrar(); + const testRegistrar = createTestRegistrar(); registry.testing.assertReqMockRsp( test.testXsrfToken, '/registrar-settings', - {op: 'read', args: {}}, + {op: 'read', id: 'testClientId', args: {}}, { status: 'SUCCESS', message: 'OK', results: [testRegistrar] }); - var parsed = registry.util.parseForm('item'); + const parsed = registry.util.parseForm('item'); parsed.ianaIdentifier = parseInt(parsed.ianaIdentifier); registry.testing.assertObjectEqualsPretty(testRegistrar, parsed); } @@ -123,13 +111,13 @@ function testEdit() { $('localizedAddress.street[0]').value = 'look at me i am'; $('localizedAddress.street[1]').value = 'the mistress of the night'; $('localizedAddress.street[2]').value = ''; - var parsed = registry.util.parseForm('item'); + const parsed = registry.util.parseForm('item'); parsed.readonly = false; registry.testing.click($('reg-app-btn-save')); registry.testing.assertReqMockRsp( test.testXsrfToken, '/registrar-settings', - {op: 'update', args: parsed}, + {op: 'update', id: 'testClientId', args: parsed}, { status: 'SUCCESS', message: 'OK', @@ -142,20 +130,20 @@ function testEditFieldError_insertsError() { testView(); registry.testing.click($('reg-app-btn-edit')); $('phoneNumber').value = 'foo'; - var parsed = registry.util.parseForm('item'); + const parsed = registry.util.parseForm('item'); parsed.readonly = false; registry.testing.click($('reg-app-btn-save')); - var errMsg = 'Carpe brunchus. --Pablo'; + const errMsg = 'Carpe brunchus. --Pablo'; registry.testing.assertReqMockRsp( test.testXsrfToken, '/registrar-settings', - {op: 'update', args: parsed}, + {op: 'update', id: 'testClientId', args: parsed}, { status: 'ERROR', field: 'phoneNumber', message: errMsg }); - var msgBox = goog.dom.getNextElementSibling($('phoneNumber')); + const msgBox = goog.dom.getNextElementSibling($('phoneNumber')); assertTrue(goog.dom.classlist.contains(msgBox, 'kd-errormessage')); assertTrue(goog.dom.classlist.contains($('phoneNumber'), 'kd-formerror')); assertEquals(errMsg, goog.dom.getTextContent(msgBox)); @@ -165,15 +153,15 @@ function testEditFieldError_insertsError() { function testEditNonFieldError_showsButterBar() { testView(); registry.testing.click($('reg-app-btn-edit')); - var parsed = registry.util.parseForm('item'); + const parsed = registry.util.parseForm('item'); parsed.readonly = false; registry.testing.click($('reg-app-btn-save')); - var errMsg = 'One must still have chaos in oneself to be able to give ' + + const errMsg = 'One must still have chaos in oneself to be able to give ' + 'birth to a dancing star. --Nietzsche'; registry.testing.assertReqMockRsp( test.testXsrfToken, '/registrar-settings', - {op: 'update', args: parsed}, + {op: 'update', id: 'testClientId', args: parsed}, { status: 'ERROR', message: errMsg diff --git a/javatests/google/registry/ui/js/testing.js b/javatests/google/registry/ui/js/testing.js index 84984c3f4..8d89987dd 100644 --- a/javatests/google/registry/ui/js/testing.js +++ b/javatests/google/registry/ui/js/testing.js @@ -106,11 +106,10 @@ registry.testing.assertObjectEqualsPretty = function(a, b) { try { assertObjectEquals(a, b); } catch (e) { - throw Error(e.message + '\n' + - 'expected: ' + - registry.testing.pretty_.format(a) + '\n' + - 'got: ' + - registry.testing.pretty_.format(b)); + e.message = e.message + '\n' + + 'expected: ' + registry.testing.pretty_.format(a) + '\n' + + 'got: ' + registry.testing.pretty_.format(b); + throw e; } }; diff --git a/javatests/google/registry/ui/server/registrar/AuthenticatedRegistrarAccessorTest.java b/javatests/google/registry/ui/server/registrar/AuthenticatedRegistrarAccessorTest.java new file mode 100644 index 000000000..dda2fde6c --- /dev/null +++ b/javatests/google/registry/ui/server/registrar/AuthenticatedRegistrarAccessorTest.java @@ -0,0 +1,302 @@ +// 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.ui.server.registrar; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.testing.AppEngineRule.THE_REGISTRAR_GAE_USER_ID; +import static google.registry.testing.DatastoreHelper.loadRegistrar; +import static google.registry.testing.DatastoreHelper.persistResource; +import static google.registry.testing.JUnitBackports.assertThrows; +import static google.registry.testing.LogsSubject.assertAboutLogs; +import static google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.Role.ADMIN; +import static google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.Role.OWNER; +import static org.mockito.Mockito.mock; + +import com.google.appengine.api.users.User; +import com.google.common.flogger.LoggerConfig; +import com.google.common.testing.NullPointerTester; +import com.google.common.testing.TestLogHandler; +import google.registry.request.HttpException.ForbiddenException; +import google.registry.request.auth.AuthLevel; +import google.registry.request.auth.AuthResult; +import google.registry.request.auth.UserAuthInfo; +import google.registry.testing.AppEngineRule; +import google.registry.testing.InjectRule; +import java.util.logging.Level; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.After; +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 AuthenticatedRegistrarAccessor}. */ +@RunWith(JUnit4.class) +public class AuthenticatedRegistrarAccessorTest { + + @Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build(); + @Rule public final InjectRule inject = new InjectRule(); + + private final HttpServletRequest req = mock(HttpServletRequest.class); + private final HttpServletResponse rsp = mock(HttpServletResponse.class); + private final TestLogHandler testLogHandler = new TestLogHandler(); + + private static final AuthResult AUTHORIZED_USER = createAuthResult(true, false); + private static final AuthResult UNAUTHORIZED_USER = createAuthResult(false, false); + private static final AuthResult AUTHORIZED_ADMIN = createAuthResult(true, true); + private static final AuthResult UNAUTHORIZED_ADMIN = createAuthResult(false, true); + private static final AuthResult NO_USER = AuthResult.create(AuthLevel.NONE); + private static final String DEFAULT_CLIENT_ID = "TheRegistrar"; + private static final String ADMIN_CLIENT_ID = "NewRegistrar"; + + private static AuthResult createAuthResult(boolean isAuthorized, boolean isAdmin) { + return AuthResult.create( + AuthLevel.USER, + UserAuthInfo.create( + new User( + String.format( + "%s_%s@gmail.com", isAuthorized ? "good" : "evil", isAdmin ? "admin" : "user"), + "gmail.com", + isAuthorized ? THE_REGISTRAR_GAE_USER_ID : "badGaeUserId"), + isAdmin)); + } + + @Before + public void before() { + LoggerConfig.getConfig(AuthenticatedRegistrarAccessor.class).addHandler(testLogHandler); + persistResource(loadRegistrar(ADMIN_CLIENT_ID)); + } + + @After + public void after() { + LoggerConfig.getConfig(AuthenticatedRegistrarAccessor.class).removeHandler(testLogHandler); + } + + private String formatMessage(String message, AuthResult authResult, String clientId) { + return message + .replace("{user}", authResult.userIdForLogging()) + .replace("{clientId}", String.valueOf(clientId)); + } + + /** Users only have access to the registrars they are a contact for. */ + @Test + public void getAllClientIdWithAccess_authorizedUser() { + AuthenticatedRegistrarAccessor registrarAccessor = + new AuthenticatedRegistrarAccessor(AUTHORIZED_USER, ADMIN_CLIENT_ID); + + assertThat(registrarAccessor.getAllClientIdWithRoles()) + .containsExactly(DEFAULT_CLIENT_ID, OWNER); + } + + /** Logged out users don't have access to anything. */ + @Test + public void getAllClientIdWithAccess_loggedOutUser() { + AuthenticatedRegistrarAccessor registrarAccessor = + new AuthenticatedRegistrarAccessor(NO_USER, ADMIN_CLIENT_ID); + + assertThat(registrarAccessor.getAllClientIdWithRoles()).isEmpty(); + } + + /** Unauthorized users don't have access to anything. */ + @Test + public void getAllClientIdWithAccess_unauthorizedUser() { + AuthenticatedRegistrarAccessor registrarAccessor = + new AuthenticatedRegistrarAccessor(UNAUTHORIZED_USER, ADMIN_CLIENT_ID); + + assertThat(registrarAccessor.getAllClientIdWithRoles()).isEmpty(); + } + + /** Admins have read/write access to the authorized registrars, AND the admin registrar. */ + @Test + public void getAllClientIdWithAccess_authorizedAdmin() { + AuthenticatedRegistrarAccessor registrarAccessor = + new AuthenticatedRegistrarAccessor(AUTHORIZED_ADMIN, ADMIN_CLIENT_ID); + + assertThat(registrarAccessor.getAllClientIdWithRoles()) + .containsExactly( + DEFAULT_CLIENT_ID, OWNER, + DEFAULT_CLIENT_ID, ADMIN, + ADMIN_CLIENT_ID, OWNER, + ADMIN_CLIENT_ID, ADMIN) + .inOrder(); + } + + /** + * Unauthorized admins only have full access to the admin registrar, and read-only to the rest. + */ + @Test + public void getAllClientIdWithAccess_unauthorizedAdmin() { + AuthenticatedRegistrarAccessor registrarAccessor = + new AuthenticatedRegistrarAccessor(UNAUTHORIZED_ADMIN, ADMIN_CLIENT_ID); + + assertThat(registrarAccessor.getAllClientIdWithRoles()) + .containsExactly( + ADMIN_CLIENT_ID, OWNER, + ADMIN_CLIENT_ID, ADMIN, + DEFAULT_CLIENT_ID, ADMIN) + .inOrder(); + } + + /** Fail loading registrar if user doesn't have access to it. */ + @Test + public void testGetRegistrarForUser_noAccess_isNotAdmin() { + expectGetRegistrarFailure( + DEFAULT_CLIENT_ID, + UNAUTHORIZED_USER, + "{user} doesn't have access to registrar {clientId}"); + } + + /** Fail loading registrar if there's no user associated with the request. */ + @Test + public void testGetRegistrarForUser_noUser() { + expectGetRegistrarFailure(DEFAULT_CLIENT_ID, NO_USER, "Not logged in"); + } + + /** Succeed loading registrar if user has access to it. */ + @Test + public void testGetRegistrarForUser_hasAccess_isNotAdmin() { + expectGetRegistrarSuccess( + AUTHORIZED_USER, "{user} has [OWNER] access to registrar {clientId}"); + } + + /** Succeed loading registrar if admin with access. */ + @Test + public void testGetRegistrarForUser_hasAccess_isAdmin() { + expectGetRegistrarSuccess( + AUTHORIZED_ADMIN, "{user} has [OWNER, ADMIN] access to registrar {clientId}"); + } + + /** Succeed loading registrar for admin even if they aren't on the approved contacts list. */ + @Test + public void testGetRegistrarForUser_noAccess_isAdmin() { + expectGetRegistrarSuccess( + UNAUTHORIZED_ADMIN, "{user} has [ADMIN] access to registrar {clientId}."); + } + + /** Fail loading registrar even if admin, if registrar doesn't exist. */ + @Test + public void testGetRegistrarForUser_doesntExist_isAdmin() { + expectGetRegistrarFailure( + "BadClientId", + AUTHORIZED_ADMIN, + "{user} doesn't have access to registrar {clientId}"); + } + + private void expectGetRegistrarSuccess( + AuthResult authResult, String message) { + AuthenticatedRegistrarAccessor registrarAccessor = + new AuthenticatedRegistrarAccessor(authResult, ADMIN_CLIENT_ID); + + assertThat(registrarAccessor.getRegistrar(DEFAULT_CLIENT_ID)).isNotNull(); + assertAboutLogs() + .that(testLogHandler) + .hasLogAtLevelWithMessage( + Level.INFO, formatMessage(message, authResult, DEFAULT_CLIENT_ID)); + } + + private void expectGetRegistrarFailure( + String clientId, AuthResult authResult, String message) { + AuthenticatedRegistrarAccessor registrarAccessor = + new AuthenticatedRegistrarAccessor(authResult, ADMIN_CLIENT_ID); + + ForbiddenException exception = + assertThrows( + ForbiddenException.class, () -> registrarAccessor.getRegistrar(clientId)); + + assertThat(exception).hasMessageThat().contains(formatMessage(message, authResult, clientId)); + } + + /** If a user has access to a registrar, we should guess that registrar. */ + @Test + public void testGuessClientIdForUser_hasAccess_isNotAdmin() { + AuthenticatedRegistrarAccessor registrarAccessor = + new AuthenticatedRegistrarAccessor(AUTHORIZED_USER, ADMIN_CLIENT_ID); + + assertThat(registrarAccessor.guessClientId()).isEqualTo(DEFAULT_CLIENT_ID); + } + + /** If a user doesn't have access to any registrars, guess returns nothing. */ + @Test + public void testGuessClientIdForUser_noAccess_isNotAdmin() { + expectGuessRegistrarFailure(UNAUTHORIZED_USER, "{user} isn't associated with any registrar"); + } + + /** + * If an admin has access to a registrar, we should guess that registrar (rather than the + * ADMIN_CLIENT_ID). + */ + @Test + public void testGuessClientIdForUser_hasAccess_isAdmin() { + AuthenticatedRegistrarAccessor registrarAccessor = + new AuthenticatedRegistrarAccessor(AUTHORIZED_ADMIN, ADMIN_CLIENT_ID); + + assertThat(registrarAccessor.guessClientId()).isEqualTo(DEFAULT_CLIENT_ID); + } + + /** If an admin doesn't have access to a registrar, we should guess the ADMIN_CLIENT_ID. */ + @Test + public void testGuessClientIdForUser_noAccess_isAdmin() { + AuthenticatedRegistrarAccessor registrarAccessor = + new AuthenticatedRegistrarAccessor(UNAUTHORIZED_ADMIN, ADMIN_CLIENT_ID); + + assertThat(registrarAccessor.guessClientId()).isEqualTo(ADMIN_CLIENT_ID); + } + + /** + * If an admin is not associated with a registrar and there is no configured adminClientId, but + * since it's an admin - we have read-only access to everything - return one of the existing + * registrars. + */ + @Test + public void testGuessClientIdForUser_noAccess_isAdmin_adminClientIdEmpty() { + AuthenticatedRegistrarAccessor registrarAccessor = + new AuthenticatedRegistrarAccessor(UNAUTHORIZED_ADMIN, ""); + + assertThat(registrarAccessor.guessClientId()).isAnyOf(ADMIN_CLIENT_ID, DEFAULT_CLIENT_ID); + } + + /** + * If an admin is not associated with a registrar and the configured adminClientId points to a + * non-existent registrar, we still guess it (we will later fail loading the registrar). + */ + @Test + public void testGuessClientIdForUser_noAccess_isAdmin_adminClientIdInvalid() { + AuthenticatedRegistrarAccessor registrarAccessor = + new AuthenticatedRegistrarAccessor(UNAUTHORIZED_ADMIN, "NonexistentRegistrar"); + + assertThat(registrarAccessor.guessClientId()).isEqualTo("NonexistentRegistrar"); + } + + private void expectGuessRegistrarFailure(AuthResult authResult, String message) { + AuthenticatedRegistrarAccessor registrarAccessor = + new AuthenticatedRegistrarAccessor(authResult, ADMIN_CLIENT_ID); + + ForbiddenException exception = + assertThrows(ForbiddenException.class, () -> registrarAccessor.guessClientId()); + assertThat(exception) + .hasMessageThat() + .contains(formatMessage(message, authResult, null)); + } + + @Test + public void testNullness() { + new NullPointerTester() + .setDefault(HttpServletRequest.class, req) + .setDefault(HttpServletResponse.class, rsp) + .testAllPublicStaticMethods(AuthenticatedRegistrarAccessor.class); + } +} diff --git a/javatests/google/registry/ui/server/registrar/BUILD b/javatests/google/registry/ui/server/registrar/BUILD index d3137c720..e7a04c602 100644 --- a/javatests/google/registry/ui/server/registrar/BUILD +++ b/javatests/google/registry/ui/server/registrar/BUILD @@ -28,6 +28,7 @@ java_library( "@com_google_flogger_system_backend", "@com_google_guava", "@com_google_guava_testlib", + "@com_google_monitoring_client_contrib", "@com_google_truth", "@com_google_truth_extensions_truth_java8_extension", "@com_googlecode_json_simple", diff --git a/javatests/google/registry/ui/server/registrar/ConsoleUiActionTest.java b/javatests/google/registry/ui/server/registrar/ConsoleUiActionTest.java index 9c7e39986..5cfb05ac9 100644 --- a/javatests/google/registry/ui/server/registrar/ConsoleUiActionTest.java +++ b/javatests/google/registry/ui/server/registrar/ConsoleUiActionTest.java @@ -16,14 +16,19 @@ package google.registry.ui.server.registrar; import static com.google.common.net.HttpHeaders.LOCATION; import static com.google.common.truth.Truth.assertThat; +import static com.google.monitoring.metrics.contrib.LongMetricSubject.assertThat; +import static google.registry.testing.DatastoreHelper.loadRegistrar; +import static google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.Role.ADMIN; +import static google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.Role.OWNER; import static javax.servlet.http.HttpServletResponse.SC_MOVED_TEMPORARILY; -import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import com.google.appengine.api.users.User; import com.google.appengine.api.users.UserServiceFactory; +import com.google.common.collect.ImmutableSetMultimap; import com.google.common.net.MediaType; +import google.registry.request.HttpException.ForbiddenException; import google.registry.request.auth.AuthLevel; import google.registry.request.auth.AuthResult; import google.registry.request.auth.UserAuthInfo; @@ -32,7 +37,9 @@ import google.registry.testing.AppEngineRule; import google.registry.testing.FakeClock; import google.registry.testing.FakeResponse; import google.registry.testing.UserInfo; +import java.util.Optional; import javax.servlet.http.HttpServletRequest; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -49,7 +56,8 @@ public class ConsoleUiActionTest { .withUserService(UserInfo.create("marla.singer@example.com", "12345")) .build(); - private final SessionUtils sessionUtils = mock(SessionUtils.class); + private final AuthenticatedRegistrarAccessor registrarAccessor = + mock(AuthenticatedRegistrarAccessor.class); private final HttpServletRequest request = mock(HttpServletRequest.class); private final FakeResponse response = new FakeResponse(); private final ConsoleUiAction action = new ConsoleUiAction(); @@ -67,31 +75,58 @@ public class ConsoleUiActionTest { action.technicalDocsUrl = "http://example.com/technical-docs"; action.req = request; action.response = response; - action.sessionUtils = sessionUtils; + action.registrarConsoleMetrics = new RegistrarConsoleMetrics(); + action.registrarAccessor = registrarAccessor; action.userService = UserServiceFactory.getUserService(); action.xsrfTokenManager = new XsrfTokenManager(new FakeClock(), action.userService); - UserAuthInfo userAuthInfo = UserAuthInfo.create(user, false); - action.authResult = AuthResult.create(AuthLevel.USER, userAuthInfo); - when(sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)).thenReturn(true); - when(sessionUtils.getRegistrarClientId(request)).thenReturn("TheRegistrar"); + action.paramClientId = Optional.empty(); + AuthResult authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user, false)); + action.authResult = authResult; + when(registrarAccessor.getRegistrar("TheRegistrar")) + .thenReturn(loadRegistrar("TheRegistrar")); + when(registrarAccessor.getAllClientIdWithRoles()) + .thenReturn( + ImmutableSetMultimap.of( + "TheRegistrar", OWNER, + "OtherRegistrar", OWNER, + "OtherRegistrar", ADMIN, + "AdminRegistrar", ADMIN)); + when(registrarAccessor.guessClientId()).thenCallRealMethod(); + // Used for error message in guessClientId + registrarAccessor.authResult = authResult; + RegistrarConsoleMetrics.consoleRequestMetric.reset(); + } + + @After + public void tearDown() throws Exception { + assertThat(RegistrarConsoleMetrics.consoleRequestMetric).hasNoOtherValues(); + } + + public void assertMetric(String clientId, String explicitClientId, String roles, String status) { + assertThat(RegistrarConsoleMetrics.consoleRequestMetric) + .hasValueForLabels(1, clientId, explicitClientId, roles, status); + RegistrarConsoleMetrics.consoleRequestMetric.reset(clientId, explicitClientId, roles, status); } @Test public void testWebPage_disallowsIframe() { action.run(); assertThat(response.getHeaders()).containsEntry("X-Frame-Options", "SAMEORIGIN"); + assertMetric("TheRegistrar", "false", "[OWNER]", "SUCCESS"); } @Test public void testWebPage_setsHtmlUtf8ContentType() { action.run(); assertThat(response.getContentType()).isEqualTo(MediaType.HTML_UTF_8); + assertMetric("TheRegistrar", "false", "[OWNER]", "SUCCESS"); } @Test public void testWebPage_containsUserNickname() { action.run(); assertThat(response.getPayload()).contains("marla.singer"); + assertMetric("TheRegistrar", "false", "[OWNER]", "SUCCESS"); } @Test @@ -99,6 +134,7 @@ public class ConsoleUiActionTest { action.run(); assertThat(response.getPayload()).contains("Registrar Console"); assertThat(response.getPayload()).contains("reg-content-and-footer"); + assertMetric("TheRegistrar", "false", "[OWNER]", "SUCCESS"); } @Test @@ -110,11 +146,11 @@ public class ConsoleUiActionTest { @Test public void testUserDoesntHaveAccessToAnyRegistrar_showsWhoAreYouPage() { - when(sessionUtils.checkRegistrarConsoleLogin( - any(HttpServletRequest.class), any(UserAuthInfo.class))) - .thenReturn(false); + when(registrarAccessor.getAllClientIdWithRoles()).thenReturn(ImmutableSetMultimap.of()); action.run(); assertThat(response.getPayload()).contains("

You need permission

"); + assertThat(response.getPayload()).contains("not associated with Nomulus."); + assertMetric("", "false", "[]", "FORBIDDEN"); } @Test @@ -134,4 +170,36 @@ public class ConsoleUiActionTest { assertThat(response.getStatus()).isEqualTo(SC_MOVED_TEMPORARILY); assertThat(response.getHeaders().get(LOCATION)).isEqualTo("/"); } + + @Test + public void testSettingClientId_notAllowed_showsNeedPermissionPage() { + // Behaves the same way if fakeRegistrar exists, but we don't have access to it + action.paramClientId = Optional.of("fakeRegistrar"); + when(registrarAccessor.getRegistrar("fakeRegistrar")) + .thenThrow(new ForbiddenException("forbidden")); + action.run(); + assertThat(response.getPayload()).contains("

You need permission

"); + assertThat(response.getPayload()).contains("not associated with the registrar fakeRegistrar."); + assertMetric("fakeRegistrar", "true", "[]", "FORBIDDEN"); + } + + @Test + public void testSettingClientId_allowed_showsRegistrarConsole() { + action.paramClientId = Optional.of("OtherRegistrar"); + when(registrarAccessor.getRegistrar("OtherRegistrar")) + .thenReturn(loadRegistrar("TheRegistrar")); + action.run(); + assertThat(response.getPayload()).contains("Registrar Console"); + assertThat(response.getPayload()).contains("reg-content-and-footer"); + assertMetric("OtherRegistrar", "true", "[OWNER, ADMIN]", "SUCCESS"); + } + + @Test + public void testUserHasAccessAsTheRegistrar_showsClientIdChooser() { + action.run(); + assertThat(response.getPayload()).contains("