1
0
mirror of https://github.com/google/nomulus synced 2025-12-23 14:25:44 +00:00

Delete legacy console (#2579)

This commit is contained in:
Lai Jiang
2024-10-17 16:48:10 -04:00
committed by GitHub
parent 91e241374d
commit 1171c5cfcb
152 changed files with 548 additions and 7958 deletions

View File

@@ -47,20 +47,9 @@ war {
if (project.path == ":services:default") {
war {
from("${coreResourcesDir}/google/registry/ui") {
include "registrar_bin.js"
if (environment != "production") {
include "registrar_bin.js.map"
}
into("assets/js")
}
from("${coreResourcesDir}/google/registry/ui/css") {
include "registrar*"
into("assets/css")
}
from("${coreResourcesDir}/google/registry/ui/assets/images") {
include "**/*"
into("assets/images")
from("${coreResourcesDir}/google/registry/ui/html") {
include "*.html"
into("registrar")
}
}
}
@@ -104,7 +93,6 @@ appengineDeployAll.finalizedBy ':deployCloudSchedulerAndQueue'
rootProject.deploy.dependsOn appengineDeployAll
rootProject.stage.dependsOn appengineStage
tasks['war'].dependsOn ':core:compileProdJS'
tasks['war'].dependsOn ':core:processResources'
tasks['war'].dependsOn ':core:jar'

View File

@@ -98,10 +98,8 @@ processTestResources {
}
configurations {
css
jaxb
soy
closureCompiler
devtool
nonprodImplementation.extendsFrom implementation
@@ -120,8 +118,6 @@ configurations {
all {
// servlet-api:3.1 pulled in but not used by soy compiler
exclude group: 'javax.servlet', module: 'javax.servlet-api'
// Jetty's servlet-api:2.5 implementation, pulled in by other Jetty jars
exclude group: 'org.mortbay.jetty', module: 'servlet-api'
}
}
@@ -297,22 +293,15 @@ dependencies {
jaxb deps['org.glassfish.jaxb:jaxb-xjc']
// Dependency needed for soy to java compilation.
soy deps['com.google.inject:guice']
soy deps['com.google.protobuf:protobuf-java']
soy deps['com.google.template:soy']
// Dependencies needed for compiling stylesheets to javascript
css deps['com.google.closure-stylesheets:closure-stylesheets']
css deps['args4j:args4j']
// Tool dependencies. used for doc generation.
implementation files("${System.properties['java.home']}/../lib/tools.jar")
// Flyway classes needed to generate the golden file.
implementation deps['org.flywaydb:flyway-core']
implementation deps['org.flywaydb:flyway-database-postgresql']
closureCompiler deps['com.google.javascript:closure-compiler']
}
task jaxbToJava {
@@ -383,9 +372,8 @@ task soyToJava {
// Relative paths of soy directories.
def spec11SoyDir = "google/registry/reporting/spec11/soy"
def toolsSoyDir = "google/registry/tools/soy"
def registrarSoyDir = "google/registry/ui/soy/registrar"
def soyRelativeDirs = [spec11SoyDir, toolsSoyDir, registrarSoyDir]
def soyRelativeDirs = [spec11SoyDir, toolsSoyDir]
soyRelativeDirs.each {
inputs.dir "${resourcesSourceDir}/${it}"
outputs.dir "${generatedDir}/${it}"
@@ -399,8 +387,7 @@ task soyToJava {
"--outputDirectory", "${outputDirectory}",
"--javaClassNameSource", "filename",
"--allowExternalCalls", "true",
"--srcs", "${soyFiles.join(',')}",
"--compileTimeGlobalsFile", "${resourcesSourceDir}/google/registry/ui/globals.txt"
"--srcs", "${soyFiles.join(',')}"
}
// Replace the "@link" tags after the "@deprecated" tags in the generated
@@ -427,12 +414,6 @@ task soyToJava {
dir: "${resourcesSourceDir}/${toolsSoyDir}",
include: ['**/*.soy']))
soyToJava('google.registry.ui.soy.registrar',
"${generatedDir}/${registrarSoyDir}",
fileTree(
dir: "${resourcesSourceDir}/${registrarSoyDir}",
include: ['**/*.soy']))
soyToJava('google.registry.reporting.spec11.soy',
"${generatedDir}/${spec11SoyDir}",
fileTree(
@@ -441,134 +422,8 @@ task soyToJava {
}
}
task soyToJS(type: JavaExec) {
def rootSoyDirectory = "${resourcesSourceDir}/google/registry/ui/soy/registrar"
def outputSoyDirectory = "${generatedDir}/google/registry/ui/soy/registrar"
inputs.dir rootSoyDirectory
outputs.dir outputSoyDirectory
def inputSoyFiles = files {
file("${rootSoyDirectory}").listFiles()
}.filter {
it.name.endsWith(".soy")
}
classpath configurations.soy
main = "com.google.template.soy.SoyToJsSrcCompiler"
args "--outputPathFormat", "${outputSoyDirectory}/{INPUT_FILE_NAME}.js",
"--allowExternalCalls", "false",
"--srcs", "${inputSoyFiles.join(',')}",
"--compileTimeGlobalsFile", "${resourcesSourceDir}/google/registry/ui/globals.txt"
}
task stylesheetsToJavascript {
def cssSourceDir = "${jsDir}/google/registry/ui/css"
def outputDir = "${resourcesDir}/google/registry/ui/css"
inputs.dir cssSourceDir
outputs.dir outputDir
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 {
file("${outputDir}").mkdirs()
def ignoredFiles = ["demo_css.css", "registrar_imports_raw.css"]
def sourceFiles = []
// include all CSS files that we find except for the ones explicitly ignored
fileTree(cssSourceDir).each {
if (it.name.endsWith(".css") && !ignoredFiles.contains(it.name)) {
sourceFiles << (cssSourceDir + "/" + it.name)
}
}
// The css files have to be passed to the compiler in alphabetic order to
// avoid some flaky style issues
sourceFiles.sort()
cssCompile("${outputDir}/registrar_bin", false, sourceFiles)
cssCompile("${outputDir}/registrar_dbg", true, sourceFiles)
}
}
task compileProdJS(type: JavaExec) {
def outputDir = "${resourcesDir}/google/registry/ui"
def nodeModulesDir = "${rootDir}/node_modules"
def cssSourceDir = "${resourcesDir}/google/registry/ui/css"
def jsSourceDir = "${jsDir}/google/registry/ui/js"
def externsDir = "${jsDir}/google/registry/ui/externs"
def soySourceDir = "${generatedDir}/google/registry/ui/soy"
[nodeModulesDir, cssSourceDir, jsSourceDir, externsDir, soySourceDir].each {
inputs.dir "${it}"
}
outputs.dir outputDir
classpath configurations.closureCompiler
main = 'com.google.javascript.jscomp.CommandLineRunner'
def closureArgs = []
closureArgs << "--js_output_file=${outputDir}/registrar_bin.js"
// sourcemap-related configuration
closureArgs << "--create_source_map=${outputDir}/registrar_bin.js.map"
closureArgs << "--source_map_include_content=true"
closureArgs << "--source_map_location_mapping=${rootDir}/|"
closureArgs << "--output_wrapper=\"%output% //# sourceMappingURL=registrar_bin.js.map\""
// compilation options
closureArgs << "--compilation_level=ADVANCED"
closureArgs << "--entry_point=goog:registry.registrar.main"
closureArgs << "--generate_exports"
// manually include all the required js files
closureArgs << "--js=${nodeModulesDir}/google-closure-library/**/*.js"
closureArgs << "--js=${jsDir}/*.js"
closureArgs << "--js=${cssSourceDir}/registrar_bin.css.js"
closureArgs << "--js=${jsSourceDir}/**.js"
closureArgs << "--js=${externsDir}/json.js"
closureArgs << "--js=${soySourceDir}/**.js"
args closureArgs
}
compileProdJS.dependsOn soyToJava
compileJava.dependsOn jaxbToJava
compileJava.dependsOn soyToJava
// The Closure JS compiler does not support Windows. It is fine to disable it if
// all we want to do is to complile the Java code on Windows.
if (!System.properties['os.name'].toLowerCase().contains('windows')) {
compileJava.dependsOn compileProdJS
assemble.dependsOn compileProdJS
}
// stylesheetsToJavascript must happen after processResources, which wipes the
// resources folder before copying data into it.
stylesheetsToJavascript.dependsOn processResources
classes.dependsOn stylesheetsToJavascript
compileProdJS.dependsOn stylesheetsToJavascript
compileProdJS.dependsOn rootProject.npmInstall
compileProdJS.dependsOn processResources
compileProdJS.dependsOn processTestResources
compileProdJS.dependsOn soyToJS
// Make testing artifacts available to be depended up on by other projects.
// TODO: factor out google.registry.testing to be a separate project.
@@ -829,16 +684,7 @@ artifacts {
devtool devtool
}
task copyJsFilesForTestServer(dependsOn: assemble, type: Copy) {
// Unfortunately the test server relies on having some compiled JS/CSS
// in place, so copy it over here
from "${resourcesDir}/google/registry/ui/"
include '**/*.js'
include '**/*.css'
into "${project.projectDir}/src/main/resources/google/registry/ui/"
}
task runTestServer(dependsOn: copyJsFilesForTestServer, type: JavaExec) {
task runTestServer(type: JavaExec) {
main = 'google.registry.server.RegistryTestServerMain'
classpath = sourceSets.test.runtimeClasspath
}

View File

@@ -3,9 +3,7 @@
# This file is expected to be part of source control.
aopalliance:aopalliance:1.0=annotationProcessor,compileClasspath,deploy_jar,errorprone,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
args4j:args4j:2.0.23=soy
args4j:args4j:2.0.26=css
args4j:args4j:2.33=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
cglib:cglib-nodep:2.2=css
com.charleskorn.kaml:kaml:0.20.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-annotations:2.18.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-core:2.18.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -38,7 +36,7 @@ com.google.api-client:google-api-client-servlet:2.2.0=testRuntimeClasspath
com.google.api-client:google-api-client-servlet:2.7.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath
com.google.api-client:google-api-client:2.7.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:gapic-google-cloud-storage-v2:2.32.1-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.api.grpc:gapic-google-cloud-storage-v2:2.43.1-beta=testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:gapic-google-cloud-storage-v2:2.43.2-beta=testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:3.5.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.177.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.177.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -49,7 +47,7 @@ com.google.api.grpc:grpc-google-cloud-spanner-admin-database-v1:6.66.0=compileCl
com.google.api.grpc:grpc-google-cloud-spanner-admin-instance-v1:6.66.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-spanner-v1:6.66.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-storage-v2:2.32.1-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.api.grpc:grpc-google-cloud-storage-v2:2.43.1-beta=testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-storage-v2:2.43.2-beta=testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-common-protos:2.39.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1:3.5.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta1:0.177.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -70,19 +68,25 @@ com.google.api.grpc:proto-google-cloud-spanner-admin-instance-v1:6.66.0=compileC
com.google.api.grpc:proto-google-cloud-spanner-executor-v1:6.66.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-spanner-v1:6.66.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-storage-v2:2.32.1-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.api.grpc:proto-google-cloud-storage-v2:2.43.1-beta=testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-storage-v2:2.43.2-beta=testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2:2.29.0=testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2beta2:0.119.0=testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2beta2:0.141.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2beta3:0.119.0=testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2beta3:0.141.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath
com.google.api.grpc:proto-google-common-protos:2.45.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-iam-v1:1.40.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:api-common:2.37.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:gax-grpc:2.54.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:gax-httpjson:2.54.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:gax:2.54.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-common-protos:2.45.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.api.grpc:proto-google-common-protos:2.46.0=testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-iam-v1:1.40.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.api.grpc:proto-google-iam-v1:1.41.0=testCompileClasspath,testRuntimeClasspath
com.google.api:api-common:2.37.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.api:api-common:2.38.0=testCompileClasspath,testRuntimeClasspath
com.google.api:gax-grpc:2.54.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.api:gax-grpc:2.55.0=testCompileClasspath,testRuntimeClasspath
com.google.api:gax-httpjson:2.54.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.api:gax-httpjson:2.55.0=testCompileClasspath,testRuntimeClasspath
com.google.api:gax:2.54.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.api:gax:2.55.0=testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-admin-directory:directory_v1-rev20240924-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-bigquery:v2-rev20240919-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-cloudresourcemanager:v1-rev20240310-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -99,10 +103,10 @@ com.google.apis:google-api-services-pubsub:v1-rev20220904-2.0.0=compileClasspath
com.google.apis:google-api-services-sheets:v4-rev20240917-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-sqladmin:v1beta4-rev20240925-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-storage:v1-rev20240319-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.apis:google-api-services-storage:v1-rev20240819-2.0.0=testRuntimeClasspath
com.google.apis:google-api-services-storage:v1-rev20240924-2.0.0=testCompileClasspath
com.google.auth:google-auth-library-credentials:1.27.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auth:google-auth-library-oauth2-http:1.27.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-storage:v1-rev20240924-2.0.0=testCompileClasspath,testRuntimeClasspath
com.google.auth:google-auth-library-credentials:1.28.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auth:google-auth-library-oauth2-http:1.27.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.auth:google-auth-library-oauth2-http:1.28.0=testCompileClasspath,testRuntimeClasspath
com.google.auto.service:auto-service-annotations:1.0.1=errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.auto.service:auto-service-annotations:1.1.1=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auto.service:auto-service:1.1.1=annotationProcessor
@@ -111,7 +115,6 @@ com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,errorprone,
com.google.auto.value:auto-value:1.10.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auto.value:auto-value:1.11.0=annotationProcessor,testAnnotationProcessor
com.google.auto:auto-common:1.2.1=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.closure-stylesheets:closure-stylesheets:1.5.0=css
com.google.cloud.bigdataoss:gcsio:2.2.16=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.bigdataoss:util:2.2.16=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.bigtable:bigtable-client-core-config:1.28.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -125,14 +128,14 @@ com.google.cloud.sql:postgres-socket-factory:1.21.0=deploy_jar,runtimeClasspath,
com.google.cloud:google-cloud-bigquerystorage:3.5.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-bigtable:2.39.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core-grpc:2.38.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.cloud:google-cloud-core-grpc:2.44.1=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core-grpc:2.45.0=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core-http:2.31.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.cloud:google-cloud-core-http:2.44.1=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core-http:2.45.0=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core:2.38.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.cloud:google-cloud-core:2.44.1=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core:2.45.0=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-firestore:3.21.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-monitoring:3.44.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-nio:0.127.24=testCompileClasspath
com.google.cloud:google-cloud-nio:0.127.25=testCompileClasspath
com.google.cloud:google-cloud-nio:0.127.6=testRuntimeClasspath
com.google.cloud:google-cloud-pubsub:1.129.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-pubsublite:1.13.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -140,25 +143,25 @@ com.google.cloud:google-cloud-secretmanager:2.29.0=testRuntimeClasspath
com.google.cloud:google-cloud-secretmanager:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath
com.google.cloud:google-cloud-spanner:6.66.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-storage:2.32.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.cloud:google-cloud-storage:2.43.1=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-storage:2.43.2=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-tasks:2.29.0=testRuntimeClasspath
com.google.cloud:google-cloud-tasks:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath
com.google.cloud:grpc-gcp:1.5.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:libraries-bom:26.26.0=testRuntimeClasspath
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.21.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.code.findbugs:jsr305:3.0.1=css
com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,checkstyle,compileClasspath,deploy_jar,errorprone,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.code.gson:gson:2.11.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.code.gson:gson:2.7=css,soy
com.google.code.gson:gson:2.7=soy
com.google.common.html.types:types:1.0.6=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
com.google.dagger:dagger-compiler:2.51.1=annotationProcessor,testAnnotationProcessor
com.google.dagger:dagger-spi:2.51.1=annotationProcessor,testAnnotationProcessor
com.google.dagger:dagger:2.51.1=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.devtools.ksp:symbol-processing-api:1.9.20-1.0.14=annotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_annotation:2.23.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.1.3=soy
com.google.errorprone:error_prone_annotations:2.23.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.32.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.errorprone:error_prone_annotations:2.7.1=checkstyle,soy
com.google.errorprone:error_prone_annotations:2.33.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.errorprone:error_prone_annotations:2.7.1=checkstyle
com.google.errorprone:error_prone_check_api:2.23.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_core:2.23.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_type_annotations:2.23.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
@@ -170,17 +173,17 @@ com.google.flogger:flogger-system-backend:0.8=compileClasspath,deploy_jar,nonpro
com.google.flogger:flogger:0.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.flogger:google-extensions:0.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.googlejavaformat:google-java-format:1.5=annotationProcessor,testAnnotationProcessor
com.google.guava:failureaccess:1.0.1=checkstyle,errorprone,nonprodAnnotationProcessor,soy
com.google.guava:failureaccess:1.0.1=checkstyle,errorprone,nonprodAnnotationProcessor
com.google.guava:failureaccess:1.0.2=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.guava:guava-parent:32.1.1-jre=errorprone,nonprodAnnotationProcessor
com.google.guava:guava-testlib:33.3.1-jre=testCompileClasspath,testRuntimeClasspath
com.google.guava:guava:20.0=css
com.google.guava:guava:31.0.1-jre=checkstyle,soy
com.google.guava:guava:25.1-jre=soy
com.google.guava:guava:31.0.1-jre=checkstyle
com.google.guava:guava:32.1.1-jre=errorprone,nonprodAnnotationProcessor
com.google.guava:guava:33.0.0-jre=annotationProcessor,testAnnotationProcessor
com.google.guava:guava:33.3.1-android=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.guava:guava:33.3.1-jre=testCompileClasspath,testRuntimeClasspath
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.gwt:gwt-user:2.10.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-apache-v2:1.45.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-appengine:1.43.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
@@ -191,14 +194,11 @@ com.google.http-client:google-http-client-jackson2:1.45.0=testCompileClasspath,t
com.google.http-client:google-http-client-protobuf:1.44.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client:1.45.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.inject.extensions:guice-multibindings:4.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
com.google.inject:guice:4.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.inject:guice:4.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
com.google.inject:guice:5.1.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.inject:guice:7.0.0=soy
com.google.j2objc:j2objc-annotations:1.3=checkstyle,soy
com.google.j2objc:j2objc-annotations:1.1=soy
com.google.j2objc:j2objc-annotations:1.3=checkstyle
com.google.j2objc:j2objc-annotations:3.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.javascript:closure-compiler-externs:v20160713=css
com.google.javascript:closure-compiler-unshaded:v20160713=css
com.google.javascript:closure-compiler:v20210505=closureCompiler
com.google.jsinterop:jsinterop-annotations:1.0.1=soy
com.google.jsinterop:jsinterop-annotations:2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.monitoring-client:contrib:1.0.7=testCompileClasspath,testRuntimeClasspath
@@ -211,7 +211,6 @@ com.google.oauth-client:google-oauth-client-servlet:1.34.1=testRuntimeClasspath
com.google.oauth-client:google-oauth-client-servlet:1.36.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath
com.google.oauth-client:google-oauth-client:1.36.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.protobuf:protobuf-java-util:3.25.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.protobuf:protobuf-java:2.5.0=css
com.google.protobuf:protobuf-java:3.19.6=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.protobuf:protobuf-java:3.25.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
com.google.re2j:re2j:1.7=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -281,11 +280,12 @@ io.grpc:grpc-grpclb:1.68.0=compileClasspath,deploy_jar,nonprodCompileClasspath,n
io.grpc:grpc-inprocess:1.68.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-netty-shaded:1.68.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-netty:1.62.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-opentelemetry:1.68.0=testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-protobuf-lite:1.68.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-opentelemetry:1.67.1=testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-protobuf-lite:1.67.1=testCompileClasspath
io.grpc:grpc-protobuf-lite:1.68.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-protobuf:1.68.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-rls:1.62.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath
io.grpc:grpc-rls:1.68.0=testRuntimeClasspath
io.grpc:grpc-rls:1.67.1=testRuntimeClasspath
io.grpc:grpc-services:1.62.2=compileClasspath,nonprodCompileClasspath,testCompileClasspath
io.grpc:grpc-services:1.68.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-stub:1.68.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -346,17 +346,15 @@ io.smallrye:jandex:3.1.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,tes
jakarta-regexp:jakarta-regexp:1.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.activation:jakarta.activation-api:2.1.3=compileClasspath,deploy_jar,jaxb,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.inject:jakarta.inject-api:1.0.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.inject:jakarta.inject-api:2.0.1=soy
jakarta.mail:jakarta.mail-api:2.1.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.persistence:jakarta.persistence-api:3.2.0=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
jakarta.servlet:jakarta.servlet-api:6.0.0=testCompileClasspath,testRuntimeClasspath
jakarta.servlet:jakarta.servlet-api:6.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
jakarta.transaction:jakarta.transaction-api:2.0.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.xml.bind:jakarta.xml.bind-api:4.0.2=compileClasspath,deploy_jar,jaxb,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
javacc:javacc:4.1=css
javax.annotation:javax.annotation-api:1.3.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
javax.annotation:jsr250-api:1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
javax.inject:javax.inject:1=annotationProcessor,compileClasspath,deploy_jar,errorprone,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
javax.inject:javax.inject:1=annotationProcessor,compileClasspath,deploy_jar,errorprone,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
javax.jdo:jdo2-api:2.3-20090302111651=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
javax.servlet:servlet-api:2.5=testRuntimeClasspath
javax.validation:validation-api:1.0.0.GA=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -367,7 +365,6 @@ net.bytebuddy:byte-buddy-agent:1.15.3=testCompileClasspath,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.12=compileClasspath,nonprodCompileClasspath
net.bytebuddy:byte-buddy:1.14.15=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath
net.bytebuddy:byte-buddy:1.15.3=testCompileClasspath,testRuntimeClasspath
net.java.dev.javacc:javacc:4.1=css
net.java.dev.jna:jna:5.13.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
net.ltgt.gradle.incap:incap:0.2=annotationProcessor,testAnnotationProcessor
net.sf.saxon:Saxon-HE:10.6=checkstyle
@@ -415,7 +412,7 @@ org.apache.sshd:sshd-common:2.14.0=testCompileClasspath,testRuntimeClasspath
org.apache.sshd:sshd-core:2.14.0=testCompileClasspath,testRuntimeClasspath
org.apache.sshd:sshd-scp:2.14.0=testCompileClasspath,testRuntimeClasspath
org.apache.sshd:sshd-sftp:2.14.0=testCompileClasspath,testRuntimeClasspath
org.apache.tomcat:tomcat-annotations-api:11.0.0-M26=testCompileClasspath,testRuntimeClasspath
org.apache.tomcat:tomcat-annotations-api:11.0.0=testCompileClasspath,testRuntimeClasspath
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath
org.bouncycastle:bcpg-jdk18on:1.78.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.bouncycastle:bcpkix-jdk18on:1.78.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -424,13 +421,14 @@ org.bouncycastle:bcutil-jdk18on:1.78.1=compileClasspath,deploy_jar,nonprodCompil
org.checkerframework:checker-compat-qual:2.5.3=compileClasspath,nonprodCompileClasspath,testCompileClasspath
org.checkerframework:checker-compat-qual:2.5.5=annotationProcessor,testAnnotationProcessor
org.checkerframework:checker-compat-qual:2.5.6=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.checkerframework:checker-qual:3.12.0=checkstyle,soy
org.checkerframework:checker-qual:2.0.0=soy
org.checkerframework:checker-qual:3.12.0=checkstyle
org.checkerframework:checker-qual:3.33.0=errorprone,nonprodAnnotationProcessor
org.checkerframework:checker-qual:3.41.0=annotationProcessor,testAnnotationProcessor
org.checkerframework:checker-qual:3.47.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.codehaus.mojo:animal-sniffer-annotations:1.14=soy
org.codehaus.mojo:animal-sniffer-annotations:1.24=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.conscrypt:conscrypt-openjdk-uber:2.5.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.easymock:easymock:3.0=css
org.eclipse.angus:angus-activation:2.0.2=deploy_jar,jaxb,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.eclipse.angus:jakarta.mail:2.0.3=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.eclipse.collections:eclipse-collections-api:11.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -457,7 +455,6 @@ org.glassfish.jaxb:txw2:4.0.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspat
org.glassfish.jaxb:txw2:4.0.5=jaxb
org.glassfish.jaxb:xsom:4.0.5=jaxb
org.gwtproject:gwt-user:2.10.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.hamcrest:hamcrest-core:1.1=css
org.hamcrest:hamcrest-core:1.3=nonprodCompileClasspath,nonprodRuntimeClasspath
org.hamcrest:hamcrest-core:3.0=testCompileClasspath,testRuntimeClasspath
org.hamcrest:hamcrest-library:3.0=testCompileClasspath,testRuntimeClasspath
@@ -510,10 +507,8 @@ org.junit.platform:junit-platform-runner:1.11.2=testCompileClasspath,testRuntime
org.junit.platform:junit-platform-suite-api:1.11.2=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-suite-commons:1.11.2=testRuntimeClasspath
org.junit:junit-bom:5.11.2=testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-core:1.10.19=css
org.mockito:mockito-core:5.14.1=testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-junit-jupiter:5.14.1=testCompileClasspath,testRuntimeClasspath
org.objenesis:objenesis:2.1=css
org.objenesis:objenesis:3.3=testRuntimeClasspath
org.ogce:xpp3:1.1.6=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath

View File

@@ -46,8 +46,6 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.net.URI;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
@@ -138,22 +136,10 @@ public final class RegistryConfig {
return config.gcpProject.locationId;
}
/**
* The filename of the logo to be displayed in the header of the registrar console.
*
* @see google.registry.ui.server.registrar.ConsoleUiAction
*/
@Provides
@Config("logoFilename")
public static String provideLogoFilename(RegistryConfigSettings config) {
return config.registrarConsole.logoFilename;
}
/**
* The product name of this specific registry. Used throughout the registrar console.
*
* @see google.registry.ui.server.registrar.ConsoleUiAction
* @see google.registry.ui.server.console.ConsoleUserDataAction
*/
@Provides
@Config("productName")
@@ -180,23 +166,11 @@ public final class RegistryConfig {
return config.misc.isEmailSendingEnabled;
}
/**
* The e-mail address for questions about integrating with the registry. Used in the
* "contact-us" section of the registrar console.
*
* @see google.registry.ui.server.registrar.ConsoleUiAction
*/
@Provides
@Config("integrationEmail")
public static String provideIntegrationEmail(RegistryConfigSettings config) {
return config.registrarConsole.integrationEmailAddress;
}
/**
* The e-mail address for general support. Used in the "contact-us" section of the registrar
* console.
*
* @see google.registry.ui.server.registrar.ConsoleUiAction
* @see google.registry.ui.server.console.ConsoleUserDataAction
*/
@Provides
@Config("supportEmail")
@@ -204,18 +178,6 @@ public final class RegistryConfig {
return config.registrarConsole.supportEmailAddress;
}
/**
* The "From" e-mail address for announcements. Used in the "contact-us" section of the
* registrar console.
*
* @see google.registry.ui.server.registrar.ConsoleUiAction
*/
@Provides
@Config("announcementsEmail")
public static String provideAnnouncementsEmail(RegistryConfigSettings config) {
return config.registrarConsole.announcementsEmailAddress;
}
/**
* The DUM file name, used as a file name base for DUM csv file
*
@@ -230,7 +192,7 @@ public final class RegistryConfig {
/**
* The contact phone number. Used in the "contact-us" section of the registrar console.
*
* @see google.registry.ui.server.registrar.ConsoleUiAction
* @see google.registry.ui.server.console.ConsoleUserDataAction
*/
@Provides
@Config("supportPhoneNumber")
@@ -242,7 +204,7 @@ public final class RegistryConfig {
* The URL for technical support docs. Used in the "contact-us" section of the registrar
* console.
*
* @see google.registry.ui.server.registrar.ConsoleUiAction
* @see google.registry.ui.server.console.ConsoleUserDataAction
*/
@Provides
@Config("technicalDocsUrl")
@@ -250,22 +212,6 @@ public final class RegistryConfig {
return config.registrarConsole.technicalDocsUrl;
}
/**
* Configuration for analytics services installed in the web console.
*
* @see google.registry.ui.server.registrar.ConsoleUiAction
* @see google.registry.ui.soy.registrar.AnalyticsSoyInfo
*/
@Provides
@Config("analyticsConfig")
public static Map<String, Object> provideAnalyticsConfig(RegistryConfigSettings config) {
// Can't be an ImmutableMap because it may contain null values.
HashMap<String, Object> analyticsConfig = new HashMap<>();
analyticsConfig.put(
"googleAnalyticsId", config.registrarConsole.analyticsConfig.googleAnalyticsId);
return Collections.unmodifiableMap(analyticsConfig);
}
/**
* Returns the Google Cloud Storage bucket for storing zone files.
*
@@ -520,7 +466,7 @@ public final class RegistryConfig {
* Returns the email address(es) that notifications of registrar and/or registrar contact
* updates should be sent to, or the empty list if updates should not be sent.
*
* @see google.registry.ui.server.registrar.RegistrarSettingsAction
* @see google.registry.ui.server.SendEmailUtils
*/
@Provides
@Config("registrarChangesNotificationEmailAddresses")
@@ -914,17 +860,6 @@ public final class RegistryConfig {
return URI.create(config.rde.uploadUrl);
}
/**
* Whether or not the registrar console is enabled.
*
* @see google.registry.ui.server.registrar.ConsoleUiAction
*/
@Provides
@Config("registrarConsoleEnabled")
public static boolean provideRegistrarConsoleEnabled() {
return true;
}
/**
* Maximum amount of time for syncing a spreadsheet, before killing.
*

View File

@@ -189,18 +189,9 @@ public class RegistryConfigSettings {
/** Configuration for the web-based registrar console. */
public static class RegistrarConsole {
public String dumFileName;
public String logoFilename;
public String supportPhoneNumber;
public String supportEmailAddress;
public String announcementsEmailAddress;
public String integrationEmailAddress;
public String technicalDocsUrl;
public AnalyticsConfig analyticsConfig;
}
/** Configuration for analytics services installed in the registrar console */
public static class AnalyticsConfig {
public String googleAnalyticsId;
}
/** Configuration for monitoring. */

View File

@@ -422,30 +422,15 @@ registrarConsole:
# DUM download file name, excluding the extension
dumFileName: dum_file_name
# Filename of the logo to use in the header of the console. This filename is
# relative to ui/assets/images/
logoFilename: logo.png
# Contact phone number for support with the registry.
supportPhoneNumber: +1 (888) 555 0123
# Contact email address for support with the registry.
supportEmailAddress: support@example.com
# From: email address used to send announcements from the registry.
announcementsEmailAddress: announcements@example.com
# Contact email address for questions about integrating with the registry.
integrationEmailAddress: integration@example.com
# URL linking to directory of technical support docs on the registry.
technicalDocsUrl: http://example.com/your_support_docs/
# Configuration for all analytics services installed in the web console
analyticsConfig:
# Google Analytics account where data on console use is sent, optional
googleAnalyticsId: null
monitoring:
# Max queries per second for the Google Cloud Monitoring V3 (aka Stackdriver)
# API. The limit can be adjusted by contacting Cloud Support.

View File

@@ -24,9 +24,7 @@
<static-files>
<include path="/*.html" expiration="1m"/>
<include path="/assets/js/**" expiration="1m"/>
<include path="/assets/css/**" expiration="1m"/>
<include path="/assets/images/**" expiration="1m"/>
<include path="/assets/sources/**" expiration="1m"/>
<include path="/registrar/*.html" expiration="1m"/>
</static-files>
</appengine-web-app>

View File

@@ -18,58 +18,6 @@
<url-pattern>/_dr/epp</url-pattern>
</servlet-mapping>
<!-- Registrar Console endpoint, which accepts EPP XHRs from GAE GAIA-authenticated sessions. -->
<servlet-mapping>
<servlet-name>frontend-servlet</servlet-name>
<url-pattern>/registrar-xhr</url-pattern>
</servlet-mapping>
<!-- Registrar Console. -->
<servlet-mapping>
<servlet-name>frontend-servlet</servlet-name>
<url-pattern>/registrar</url-pattern>
</servlet-mapping>
<!-- Registrar creation console. -->
<servlet-mapping>
<servlet-name>frontend-servlet</servlet-name>
<url-pattern>/registrar-create</url-pattern>
</servlet-mapping>
<!-- OT&E creation console. -->
<servlet-mapping>
<servlet-name>frontend-servlet</servlet-name>
<url-pattern>/registrar-ote-setup</url-pattern>
</servlet-mapping>
<!-- OT&E status console. -->
<servlet-mapping>
<servlet-name>frontend-servlet</servlet-name>
<url-pattern>/registrar-ote-status</url-pattern>
</servlet-mapping>
<!-- Registrar Self-serve Settings. -->
<servlet-mapping>
<servlet-name>frontend-servlet</servlet-name>
<url-pattern>/registrar-settings</url-pattern>
</servlet-mapping>
<!-- Registry lock get/post/verify. -->
<servlet-mapping>
<servlet-name>frontend-servlet</servlet-name>
<url-pattern>/registry-lock-get</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>frontend-servlet</servlet-name>
<url-pattern>/registry-lock-post</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>frontend-servlet</servlet-name>
<url-pattern>/registry-lock-verify</url-pattern>
</servlet-mapping>
<!-- Registrar console endpoints -->
<servlet-mapping>
<servlet-name>frontend-servlet</servlet-name>
@@ -90,41 +38,12 @@
TODO(b/28219927): lift some of these restrictions so that we can allow OAuth authentication
for endpoints that need to be accessed by open-source automated processes.
</description>
<!-- Internal AppEngine endpoints. The '_ah' is short for app hosting. -->
<url-pattern>/_ah/*</url-pattern>
<!-- Verbatim JavaScript sources (only visible to admins for debugging). -->
<url-pattern>/assets/sources/*</url-pattern>
<!-- TODO(b/26776367): Move these files to /assets/sources. -->
<url-pattern>/assets/js/registrar_bin.js.map</url-pattern>
<url-pattern>/assets/js/registrar_dbg.js</url-pattern>
<url-pattern>/assets/css/registrar_dbg.css</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
<!-- Repeated here since catch-all rule below is not inherited. -->
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Registrar console</web-resource-name>
<description>
Registrar console requires user login. This is in addition to the
code-level "requireLogin" configuration on individual @Actions.
</description>
<url-pattern>/registrar*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
<!-- Repeated here since catch-all rule below is not inherited. -->
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>

View File

@@ -24,9 +24,6 @@
<static-files>
<include path="/*.html" expiration="1m"/>
<include path="/assets/js/**" expiration="1m"/>
<include path="/assets/css/**" expiration="1m"/>
<include path="/assets/images/**" expiration="1m"/>
<include path="/assets/sources/**" expiration="1m"/>
<include path="/registrar/*.html" expiration="1m"/>
</static-files>
</appengine-web-app>

View File

@@ -28,16 +28,7 @@
<include path="/*.html">
<http-header name="Cache-Control" value="max-age=0,must-revalidate" />
</include>
<include path="/assets/js/**">
<http-header name="Cache-Control" value="max-age=0,must-revalidate" />
</include>
<include path="/assets/css/**">
<http-header name="Cache-Control" value="max-age=0,must-revalidate" />
</include>
<include path="/assets/images/**">
<http-header name="Cache-Control" value="max-age=0,must-revalidate" />
</include>
<include path="/assets/sources/**">
<include path="/registrar/*.html">
<http-header name="Cache-Control" value="max-age=0,must-revalidate" />
</include>
</static-files>

View File

@@ -23,10 +23,7 @@
<static-files>
<include path="/*.html" expiration="1d"/>
<include path="/assets/js/**" expiration="1d"/>
<include path="/assets/css/**" expiration="1d"/>
<include path="/assets/images/**" expiration="1d"/>
<include path="/assets/sources/**" expiration="1d"/>
<include path="/registrar/*.html" expiration="1d"/>
</static-files>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->

View File

@@ -27,10 +27,7 @@
<static-files>
<include path="/*.html" expiration="1h"/>
<include path="/assets/js/**" expiration="1h"/>
<include path="/assets/css/**" expiration="1h"/>
<include path="/assets/images/**" expiration="1h"/>
<include path="/assets/sources/**" expiration="1h"/>
<include path="/registrar/*.html" expiration="1h"/>
</static-files>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->

View File

@@ -23,10 +23,7 @@
<static-files>
<include path="/*.html" expiration="1d"/>
<include path="/assets/js/**" expiration="1d"/>
<include path="/assets/css/**" expiration="1d"/>
<include path="/assets/images/**" expiration="1d"/>
<include path="/assets/sources/**" expiration="1d"/>
<include path="/registrar/*.html" expiration="1d"/>
</static-files>
<!-- Prevent uncaught servlet errors from leaking a stack trace. -->

View File

@@ -66,7 +66,6 @@ public class FeatureFlag extends ImmutableObject implements Buildable {
TEST_FEATURE,
MINIMUM_DATASET_CONTACTS_OPTIONAL,
MINIMUM_DATASET_CONTACTS_PROHIBITED,
NEW_CONSOLE
}
/** The name of the flag/feature. */

View File

@@ -40,14 +40,6 @@ import google.registry.ui.server.console.RegistrarsAction;
import google.registry.ui.server.console.settings.ContactAction;
import google.registry.ui.server.console.settings.SecurityAction;
import google.registry.ui.server.console.settings.WhoisRegistrarFieldsAction;
import google.registry.ui.server.registrar.ConsoleOteSetupAction;
import google.registry.ui.server.registrar.ConsoleRegistrarCreatorAction;
import google.registry.ui.server.registrar.ConsoleUiAction;
import google.registry.ui.server.registrar.OteStatusAction;
import google.registry.ui.server.registrar.RegistrarSettingsAction;
import google.registry.ui.server.registrar.RegistryLockGetAction;
import google.registry.ui.server.registrar.RegistryLockPostAction;
import google.registry.ui.server.registrar.RegistryLockVerifyAction;
/** Dagger component with per-request lifetime for "default" App Engine module. */
@RequestScope
@@ -69,15 +61,10 @@ public interface FrontendRequestComponent {
ConsoleOteAction consoleOteAction();
ConsoleOteSetupAction consoleOteSetupAction();
ConsoleRegistrarCreatorAction consoleRegistrarCreatorAction();
ConsoleRegistryLockAction consoleRegistryLockAction();
ConsoleRegistryLockVerifyAction consoleRegistryLockVerifyAction();
ConsoleUiAction consoleUiAction();
ConsoleUpdateRegistrarAction consoleUpdateRegistrarAction();
ConsoleUserDataAction consoleUserDataAction();
@@ -89,18 +76,13 @@ public interface FrontendRequestComponent {
ContactAction contactAction();
EppTlsAction eppTlsAction();
FlowComponent.Builder flowComponentBuilder();
OteStatusAction oteStatusAction();
RegistrarsAction registrarsAction();
RegistrarSettingsAction registrarSettingsAction();
RegistryLockGetAction registryLockGetAction();
RegistryLockPostAction registryLockPostAction();
RegistryLockVerifyAction registryLockVerifyAction();
SecurityAction securityAction();
WhoisRegistrarFieldsAction whoisRegistrarFieldsAction();
@Subcomponent.Builder

Binary file not shown.

Before

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1016 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 535 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 B

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="21px" height="21px" viewBox="0 0 21 21" overflow="visible" enable-background="new 0 0 21 21"
xml:space="preserve">
<defs>
</defs>
<path fill="#FFFFFF" d="M17.646,16.501L14,12.855c0.566-0.811,0.903-1.792,0.903-2.855c0-2.762-2.238-5-5-5c-2.761,0-5,2.238-5,5
s2.239,5,5,5c1.064,0,2.045-0.337,2.856-0.903l3.646,3.646c0.343,0.343,0.898,0.344,1.241,0.001
C17.99,17.399,17.99,16.845,17.646,16.501z M9.903,13.2c-1.767,0-3.199-1.433-3.199-3.2s1.433-3.2,3.199-3.2
c1.77,0,3.199,1.433,3.199,3.2S11.673,13.2,9.903,13.2z"/>
<rect opacity="0" fill="#4387FD" width="21" height="21"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="21px" height="21px" viewBox="0 0 21 21" overflow="visible" enable-background="new 0 0 21 21"
xml:space="preserve">
<defs>
</defs>
<path fill="#FFFFFF" d="M17.646,16.501L14,12.855c0.566-0.811,0.903-1.792,0.903-2.855c0-2.762-2.238-5-5-5c-2.761,0-5,2.238-5,5
s2.239,5,5,5c1.064,0,2.045-0.337,2.856-0.903l3.646,3.646c0.343,0.343,0.898,0.344,1.241,0.001
C17.99,17.399,17.99,16.845,17.646,16.501z M9.903,13.2c-1.767,0-3.199-1.433-3.199-3.2s1.433-3.2,3.199-3.2
c1.77,0,3.199,1.433,3.199,3.2S11.673,13.2,9.903,13.2z"/>
<rect opacity="0" fill="#4387FD" width="21" height="21"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="21px" height="21px" viewBox="0 0 21 21" overflow="visible" enable-background="new 0 0 21 21"
xml:space="preserve">
<defs>
</defs>
<path d="M16.895,11.64C16.96,11.269,17,10.89,17,10.5s-0.04-0.77-0.106-1.14l1.535-1.129l0.183-0.683l-1.5-2.598l-0.684-0.184
l-1.738,0.762c-0.581-0.49-1.25-0.875-1.979-1.138L12.5,2.5L12,2H9L8.5,2.499L8.29,4.395C7.562,4.658,6.896,5.043,6.314,5.531
L4.571,4.768L3.889,4.951l-1.5,2.598l0.183,0.684l1.535,1.129C4.04,9.731,4,10.111,4,10.5s0.04,0.769,0.106,1.139l-1.535,1.129
l-0.183,0.684l1.5,2.598l0.682,0.184l1.744-0.764c0.58,0.488,1.248,0.873,1.975,1.137l0.21,1.896L9,19h3l0.5-0.5l0.21-1.892
c0.729-0.263,1.398-0.648,1.979-1.138l1.738,0.762l0.684-0.184l1.499-2.598l-0.181-0.683L16.895,11.64z M14.15,10.5
c0,2.016-1.635,3.65-3.65,3.65c-2.016,0-3.65-1.635-3.65-3.65s1.635-3.65,3.65-3.65C12.515,6.85,14.15,8.484,14.15,10.5z"/>
<rect opacity="0" fill="#4387FD" width="21" height="21"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 480 B

View File

@@ -1,14 +0,0 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<info>
<contact:info
xmlns:contact="urn:ietf:params:xml:ns:contact-1.0">
<contact:id>sh8013</contact:id>
<contact:authInfo>
<contact:pw>2fooBAR</contact:pw>
</contact:authInfo>
</contact:info>
</info>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View File

@@ -1,14 +0,0 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<transfer op="request">
<contact:transfer
xmlns:contact="urn:ietf:params:xml:ns:contact-1.0">
<contact:id>sh8013</contact:id>
<contact:authInfo>
<contact:pw>2fooBAR</contact:pw>
</contact:authInfo>
</contact:transfer>
</transfer>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View File

@@ -1,18 +0,0 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<create>
<domain:create
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
<domain:period unit="y">2</domain:period>
<domain:registrant>jd1234</domain:registrant>
<domain:contact type="admin">sh8013</domain:contact>
<domain:contact type="tech">sh8013</domain:contact>
<domain:authInfo>
<domain:pw>2fooBAR</domain:pw>
</domain:authInfo>
</domain:create>
</create>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View File

@@ -1,11 +0,0 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<delete>
<domain:delete
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
</domain:delete>
</delete>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View File

@@ -1,17 +0,0 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
<domain:chg/>
</domain:update>
</update>
<extension>
<rgp:update xmlns:rgp="urn:ietf:params:xml:ns:rgp-1.0">
<rgp:restore op="request"/>
</rgp:update>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View File

@@ -1,18 +0,0 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<login>
<clID>NewRegistrar</clID>
<pw>foo-BAR2</pw>
<options>
<version>1.0</version>
<lang>en</lang>
</options>
<svcs>
<objURI>urn:ietf:params:xml:ns:host-1.0</objURI>
<objURI>urn:ietf:params:xml:ns:domain-1.0</objURI>
<objURI>urn:ietf:params:xml:ns:contact-1.0</objURI>
</svcs>
</login>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View File

@@ -1,6 +0,0 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<logout/>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View File

@@ -1,6 +0,0 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<poll op="req"/>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View File

@@ -16,9 +16,6 @@ package google.registry.ui.server.console;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.model.common.FeatureFlag.FeatureName.NEW_CONSOLE;
import static google.registry.model.common.FeatureFlag.isActiveNow;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.POST;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
@@ -36,19 +33,15 @@ import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig;
import google.registry.export.sheet.SyncRegistrarsSheetAction;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.registrar.RegistrarPocBase;
import google.registry.request.HttpException;
import google.registry.security.XsrfTokenManager;
import google.registry.ui.server.registrar.ConsoleUiAction;
import google.registry.util.DiffUtils;
import google.registry.util.RegistryEnvironment;
import jakarta.servlet.http.Cookie;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -83,25 +76,6 @@ public abstract class ConsoleApiAction implements Runnable {
}
User user = consoleApiParams.authResult().user().get();
// This allows us to enable console to a selected cohort of users with release
// We can ignore it in tests
UserRoles userRoles = user.getUserRoles();
boolean hasGlobalOrTestingRole =
!GlobalRole.NONE.equals(userRoles.getGlobalRole())
|| userRoles.hasPermission(
registryAdminClientId, ConsolePermission.VIEW_REGISTRAR_DETAILS);
if (!hasGlobalOrTestingRole
&& RegistryEnvironment.get() != RegistryEnvironment.UNITTEST
&& tm().transact(() -> !isActiveNow(NEW_CONSOLE))) {
try {
consoleApiParams.response().sendRedirect(ConsoleUiAction.PATH);
return;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
try {
if (consoleApiParams.request().getMethod().equals(GET.toString())) {
getHandler(user);

View File

@@ -19,7 +19,6 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.POST;
import static google.registry.ui.server.registrar.RegistryLockPostAction.VERIFICATION_EMAIL_TEMPLATE;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
@@ -64,6 +63,12 @@ import org.joda.time.Duration;
public class ConsoleRegistryLockAction extends ConsoleApiAction {
static final String PATH = "/console-api/registry-lock";
static final String VERIFICATION_EMAIL_TEMPLATE =
"""
Please click the link below to perform the lock / unlock action on domain %s. Note: this\
code will expire in one hour.
%s""";
private final DomainLockUtils domainLockUtils;
private final GmailClient gmailClient;

View File

@@ -16,19 +16,24 @@ package google.registry.ui.server.console.settings;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Sets.difference;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.POST;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.flogger.FluentLogger;
import com.google.gson.Gson;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.registrar.RegistrarPocBase.Type;
import google.registry.persistence.transaction.QueryComposer.Comparator;
import google.registry.request.Action;
import google.registry.request.Action.GaeService;
@@ -36,11 +41,14 @@ import google.registry.request.Action.GkeService;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.ui.forms.FormException;
import google.registry.ui.server.RegistrarFormFields;
import google.registry.ui.server.console.ConsoleApiAction;
import google.registry.ui.server.console.ConsoleApiParams;
import google.registry.ui.server.registrar.RegistrarSettingsAction;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import javax.inject.Inject;
@Action(
@@ -97,18 +105,20 @@ public class ContactAction extends ConsoleApiAction {
String.format("Unknown registrar %s", registrarId)));
ImmutableSet<RegistrarPoc> oldContacts = registrar.getContacts();
// TODO: @ptkach - refactor out contacts update functionality after RegistrarSettingsAction is
// deprecated
ImmutableSet<RegistrarPoc> updatedContacts =
RegistrarSettingsAction.readContacts(
registrar,
RegistrarFormFields.getRegistrarContactBuilders(
oldContacts,
Collections.singletonMap(
"contacts",
contacts.get().stream().map(RegistrarPoc::toJsonMap).collect(toImmutableList())));
contacts.get().stream()
.map(RegistrarPoc::toJsonMap)
.collect(toImmutableList())))
.stream()
.map(builder -> builder.setRegistrar(registrar).build())
.collect(toImmutableSet());
try {
RegistrarSettingsAction.checkContactRequirements(oldContacts, updatedContacts);
checkContactRequirements(oldContacts, updatedContacts);
} catch (FormException e) {
logger.atWarning().withCause(e).log(
"Error processing contacts post request for registrar: %s", registrarId);
@@ -127,4 +137,165 @@ public class ContactAction extends ConsoleApiAction {
consoleApiParams.response().setStatus(SC_OK);
}
/**
* Enforces business logic checks on registrar contacts.
*
* @throws FormException if the checks fail.
*/
private static void checkContactRequirements(
ImmutableSet<RegistrarPoc> existingContacts, ImmutableSet<RegistrarPoc> updatedContacts) {
// Check that no two contacts use the same email address.
Set<String> emails = new HashSet<>();
for (RegistrarPoc contact : updatedContacts) {
if (!emails.add(contact.getEmailAddress())) {
throw new ContactRequirementException(
String.format(
"One email address (%s) cannot be used for multiple contacts",
contact.getEmailAddress()));
}
}
// Check that required contacts don't go away, once they are set.
Multimap<Type, RegistrarPoc> oldContactsByType = HashMultimap.create();
for (RegistrarPoc contact : existingContacts) {
for (Type t : contact.getTypes()) {
oldContactsByType.put(t, contact);
}
}
Multimap<Type, RegistrarPoc> newContactsByType = HashMultimap.create();
for (RegistrarPoc contact : updatedContacts) {
for (Type t : contact.getTypes()) {
newContactsByType.put(t, contact);
}
}
for (Type t : difference(oldContactsByType.keySet(), newContactsByType.keySet())) {
if (t.isRequired()) {
throw new ContactRequirementException(t);
}
}
ensurePhoneNumberNotRemovedForContactTypes(oldContactsByType, newContactsByType, Type.TECH);
Optional<RegistrarPoc> domainWhoisAbuseContact =
getDomainWhoisVisibleAbuseContact(updatedContacts);
// If the new set has a domain WHOIS abuse contact, it must have a phone number.
if (domainWhoisAbuseContact.isPresent()
&& domainWhoisAbuseContact.get().getPhoneNumber() == null) {
throw new ContactRequirementException(
"The abuse contact visible in domain WHOIS query must have a phone number");
}
// If there was a domain WHOIS abuse contact in the old set, the new set must have one.
if (getDomainWhoisVisibleAbuseContact(existingContacts).isPresent()
&& domainWhoisAbuseContact.isEmpty()) {
throw new ContactRequirementException(
"An abuse contact visible in domain WHOIS query must be designated");
}
checkContactRegistryLockRequirements(existingContacts, updatedContacts);
}
private static void checkContactRegistryLockRequirements(
ImmutableSet<RegistrarPoc> existingContacts, ImmutableSet<RegistrarPoc> updatedContacts) {
// Any contact(s) with new passwords must be allowed to set them
for (RegistrarPoc updatedContact : updatedContacts) {
if (updatedContact.isRegistryLockAllowed()
|| updatedContact.isAllowedToSetRegistryLockPassword()) {
RegistrarPoc existingContact =
existingContacts.stream()
.filter(
contact -> contact.getEmailAddress().equals(updatedContact.getEmailAddress()))
.findFirst()
.orElseThrow(
() ->
new FormException(
"Cannot set registry lock password directly on new contact"));
// Can't modify registry lock email address
if (!Objects.equals(
updatedContact.getRegistryLockEmailAddress(),
existingContact.getRegistryLockEmailAddress())) {
throw new FormException("Cannot modify registryLockEmailAddress through the UI");
}
if (updatedContact.isRegistryLockAllowed()) {
// the password must have been set before or the user was allowed to set it now
if (!existingContact.isAllowedToSetRegistryLockPassword()
&& !existingContact.isRegistryLockAllowed()) {
throw new FormException("Registrar contact not allowed to set registry lock password");
}
}
if (updatedContact.isAllowedToSetRegistryLockPassword()) {
if (!existingContact.isAllowedToSetRegistryLockPassword()) {
throw new FormException(
"Cannot modify isAllowedToSetRegistryLockPassword through the UI");
}
}
}
}
// Any previously-existing contacts with registry lock enabled cannot be deleted
existingContacts.stream()
.filter(RegistrarPoc::isRegistryLockAllowed)
.forEach(
contact -> {
Optional<RegistrarPoc> updatedContactOptional =
updatedContacts.stream()
.filter(
updatedContact ->
updatedContact.getEmailAddress().equals(contact.getEmailAddress()))
.findFirst();
if (updatedContactOptional.isEmpty()) {
throw new FormException(
String.format(
"Cannot delete the contact %s that has registry lock enabled",
contact.getEmailAddress()));
}
if (!updatedContactOptional.get().isRegistryLockAllowed()) {
throw new FormException(
String.format(
"Cannot remove the ability to use registry lock on the contact %s",
contact.getEmailAddress()));
}
});
}
/**
* Retrieves the registrar contact whose phone number and email address is visible in domain WHOIS
* query as abuse contact (if any).
*
* <p>Frontend processing ensures that only one contact can be set as abuse contact in domain
* WHOIS record.
*
* <p>Therefore, it is possible to return inside the loop once one such contact is found.
*/
private static Optional<RegistrarPoc> getDomainWhoisVisibleAbuseContact(
Set<RegistrarPoc> contacts) {
return contacts.stream().filter(RegistrarPoc::getVisibleInDomainWhoisAsAbuse).findFirst();
}
/**
* Ensure that for each given registrar type, a phone number is present after update, if there was
* one before.
*/
private static void ensurePhoneNumberNotRemovedForContactTypes(
Multimap<Type, RegistrarPoc> oldContactsByType,
Multimap<Type, RegistrarPoc> newContactsByType,
Type... types) {
for (Type type : types) {
if (oldContactsByType.get(type).stream().anyMatch(contact -> contact.getPhoneNumber() != null)
&& newContactsByType.get(type).stream()
.noneMatch(contact -> contact.getPhoneNumber() != null)) {
throw new ContactRequirementException(
String.format(
"Please provide a phone number for at least one %s contact",
type.getDisplayName()));
}
}
}
/** Thrown when a set of contacts doesn't meet certain constraints. */
private static class ContactRequirementException extends FormException {
ContactRequirementException(String msg) {
super(msg);
}
ContactRequirementException(Type type) {
super(String.format("Must have at least one %s contact", type.getDisplayName()));
}
}
}

View File

@@ -1,210 +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.ui.server.registrar;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.ui.server.SoyTemplateUtils.CSS_RENAMING_MAP_SUPPLIER;
import static google.registry.util.RegistryEnvironment.PRODUCTION;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import com.google.common.base.Ascii;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import com.google.template.soy.tofu.SoyTofu;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.OteAccountBuilder;
import google.registry.request.Action;
import google.registry.request.Action.GaeService;
import google.registry.request.Action.Method;
import google.registry.request.HttpException.BadRequestException;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.tools.IamClient;
import google.registry.ui.server.SendEmailUtils;
import google.registry.ui.server.SoyTemplateUtils;
import google.registry.ui.soy.registrar.OteSetupConsoleSoyInfo;
import google.registry.util.RegistryEnvironment;
import google.registry.util.StringGenerator;
import java.util.Map;
import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Named;
/**
* Action that serves OT&amp;E setup web page.
*
* <p>This Action does 2 things: - for GET, just returns the form that asks for the clientId and
* email. - for POST, receives the clientId and email and generates the OTE entities.
*
* <p>TODO(b/120201577): once we can have 2 different Actions with the same path (different
* Methods), separate this class to 2 Actions.
*/
@Action(
service = GaeService.DEFAULT,
path = ConsoleOteSetupAction.PATH,
method = {Method.POST, Method.GET},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public final class ConsoleOteSetupAction extends HtmlAction {
public static final String PATH = "/registrar-ote-setup";
private static final int PASSWORD_LENGTH = 16;
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final Supplier<SoyTofu> TOFU_SUPPLIER =
SoyTemplateUtils.createTofuSupplier(
google.registry.ui.soy.registrar.AnalyticsSoyInfo.getInstance(),
google.registry.ui.soy.registrar.ConsoleSoyInfo.getInstance(),
google.registry.ui.soy.registrar.ConsoleUtilsSoyInfo.getInstance(),
google.registry.ui.soy.registrar.FormsSoyInfo.getInstance(),
google.registry.ui.soy.registrar.OteSetupConsoleSoyInfo.getInstance());
@Inject AuthenticatedRegistrarAccessor registrarAccessor;
@Inject SendEmailUtils sendEmailUtils;
@Inject
@Named("base58StringGenerator")
StringGenerator passwordGenerator;
@Inject
@Parameter("consoleClientId")
Optional<String> clientId;
@Inject
@Parameter("email")
Optional<String> email;
@Inject
@Parameter("password")
Optional<String> optionalPassword;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject IamClient iamClient;
@Inject
@Config("gSuiteConsoleUserGroupEmailAddress")
Optional<String> maybeGroupEmailAddress;
@Inject
ConsoleOteSetupAction() {}
@Override
public void runAfterLogin(Map<String, Object> data) {
checkState(
!RegistryEnvironment.get().equals(PRODUCTION), "Can't create OT&E in prod");
if (!registrarAccessor.isAdmin()) {
response.setStatus(SC_FORBIDDEN);
response.setPayload(
TOFU_SUPPLIER
.get()
.newRenderer(OteSetupConsoleSoyInfo.WHOAREYOU)
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
.setData(data)
.render());
return;
}
switch (method) {
case POST -> runPost(data);
case GET -> runGet(data);
default ->
throw new BadRequestException(
String.format("Action cannot be called with method %s", method));
}
}
@Override
public String getPath() {
return PATH;
}
private void runPost(Map<String, Object> data) {
try {
checkState(clientId.isPresent() && email.isPresent(), "Must supply clientId and email");
data.put("baseClientId", clientId.get());
data.put("contactEmail", email.get());
String password = optionalPassword.orElse(passwordGenerator.createString(PASSWORD_LENGTH));
OteAccountBuilder oteAccountBuilder =
OteAccountBuilder.forRegistrarId(clientId.get())
.addUser(email.get())
.setPassword(password);
ImmutableMap<String, String> clientIdToTld = oteAccountBuilder.buildAndPersist();
oteAccountBuilder.grantIapPermission(maybeGroupEmailAddress, cloudTasksUtils, iamClient);
sendExternalUpdates(clientIdToTld);
data.put("clientIdToTld", clientIdToTld);
data.put("password", password);
response.setPayload(
TOFU_SUPPLIER
.get()
.newRenderer(OteSetupConsoleSoyInfo.RESULT_SUCCESS)
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
.setData(data)
.render());
} catch (Throwable e) {
logger.atWarning().withCause(e).log(
"Failed to setup OT&E. clientId: %s, email: %s", clientId.get(), email.get());
data.put("errorMessage", e.getMessage());
response.setPayload(
TOFU_SUPPLIER
.get()
.newRenderer(OteSetupConsoleSoyInfo.FORM_PAGE)
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
.setData(data)
.render());
}
}
private void runGet(Map<String, Object> data) {
// set the values to pre-fill, if given
data.put("baseClientId", clientId.orElse(null));
data.put("contactEmail", email.orElse(null));
response.setPayload(
TOFU_SUPPLIER
.get()
.newRenderer(OteSetupConsoleSoyInfo.FORM_PAGE)
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
.setData(data)
.render());
}
private void sendExternalUpdates(ImmutableMap<String, String> clientIdToTld) {
if (!sendEmailUtils.hasRecipients()) {
return;
}
String environment = Ascii.toLowerCase(String.valueOf(RegistryEnvironment.get()));
StringBuilder builder = new StringBuilder();
builder.append(
String.format(
"The following entities were created in %s by %s:\n",
environment, registrarAccessor.userIdForLogging()));
clientIdToTld.forEach(
(clientId, tld) ->
builder.append(
String.format(" Registrar %s with access to TLD %s\n", clientId, tld)));
builder.append(String.format("Gave user %s web access to these Registrars\n", email.get()));
sendEmailUtils.sendEmail(
String.format("OT&E for registrar %s created in %s", clientId.get(), environment),
builder.toString());
}
}

View File

@@ -1,331 +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.ui.server.registrar;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.ui.server.SoyTemplateUtils.CSS_RENAMING_MAP_SUPPLIER;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import com.google.common.base.Ascii;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import com.google.template.soy.tofu.SoyTofu;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registrar.RegistrarBase.State;
import google.registry.request.Action;
import google.registry.request.Action.GaeService;
import google.registry.request.Action.Method;
import google.registry.request.HttpException.BadRequestException;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.tools.IamClient;
import google.registry.ui.server.SendEmailUtils;
import google.registry.ui.server.SoyTemplateUtils;
import google.registry.ui.soy.registrar.AnalyticsSoyInfo;
import google.registry.ui.soy.registrar.ConsoleSoyInfo;
import google.registry.ui.soy.registrar.ConsoleUtilsSoyInfo;
import google.registry.ui.soy.registrar.FormsSoyInfo;
import google.registry.ui.soy.registrar.RegistrarCreateConsoleSoyInfo;
import google.registry.util.RegistryEnvironment;
import google.registry.util.StringGenerator;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
import javax.inject.Inject;
import javax.inject.Named;
import org.joda.money.CurrencyUnit;
/**
* Action that serves Registrar creation page.
*
* <p>This Action does 2 things: - for GET, just returns the form that asks for the required
* information. - for POST, receives the information and creates the Registrar.
*/
@Action(
service = GaeService.DEFAULT,
path = ConsoleRegistrarCreatorAction.PATH,
method = {Method.POST, Method.GET},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public final class ConsoleRegistrarCreatorAction extends HtmlAction {
private static final int PASSWORD_LENGTH = 16;
private static final int PASSCODE_LENGTH = 5;
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
public static final String PATH = "/registrar-create";
private static final Supplier<SoyTofu> TOFU_SUPPLIER =
SoyTemplateUtils.createTofuSupplier(
AnalyticsSoyInfo.getInstance(),
ConsoleSoyInfo.getInstance(),
ConsoleUtilsSoyInfo.getInstance(),
FormsSoyInfo.getInstance(),
RegistrarCreateConsoleSoyInfo.getInstance());
@Inject AuthenticatedRegistrarAccessor registrarAccessor;
@Inject SendEmailUtils sendEmailUtils;
@Inject @Named("base58StringGenerator") StringGenerator passwordGenerator;
@Inject @Named("digitOnlyStringGenerator") StringGenerator passcodeGenerator;
@Inject
@Parameter("consoleClientId")
Optional<String> clientId;
@Inject
@Parameter("consoleName")
Optional<String> name;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject IamClient iamClient;
@Inject
@Config("gSuiteConsoleUserGroupEmailAddress")
Optional<String> maybeGroupEmailAddress;
@Inject @Parameter("billingAccount") Optional<String> billingAccount;
@Inject @Parameter("ianaId") Optional<Integer> ianaId;
@Inject @Parameter("referralEmail") Optional<String> referralEmail;
@Inject @Parameter("driveId") Optional<String> driveId;
@Inject @Parameter("consoleUserEmail") Optional<String> consoleUserEmail;
// Address fields, some of which are required and others are optional.
@Inject @Parameter("street1") Optional<String> street1;
@Inject @Parameter("street2") Optional<String> optionalStreet2;
@Inject @Parameter("street3") Optional<String> optionalStreet3;
@Inject @Parameter("city") Optional<String> city;
@Inject @Parameter("state") Optional<String> optionalState;
@Inject @Parameter("zip") Optional<String> optionalZip;
@Inject @Parameter("countryCode") Optional<String> countryCode;
@Inject @Parameter("password") Optional<String> optionalPassword;
@Inject @Parameter("passcode") Optional<String> optionalPasscode;
@Inject ConsoleRegistrarCreatorAction() {}
@Override
public void runAfterLogin(Map<String, Object> data) {
if (!registrarAccessor.isAdmin()) {
response.setStatus(SC_FORBIDDEN);
response.setPayload(
TOFU_SUPPLIER
.get()
.newRenderer(RegistrarCreateConsoleSoyInfo.WHOAREYOU)
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
.setData(data)
.render());
return;
}
switch (method) {
case POST -> runPost(data);
case GET -> runGet(data);
default ->
throw new BadRequestException(
String.format("Action cannot be called with method %s", method));
}
}
@Override
public String getPath() {
return PATH;
}
private static void checkPresent(Optional<?> value, String name) {
checkState(value.isPresent(), "Missing value for %s", name);
}
private static final Splitter LINE_SPLITTER =
Splitter.onPattern("\r?\n").trimResults().omitEmptyStrings();
private static final Splitter ENTRY_SPLITTER =
Splitter.on('=').trimResults().omitEmptyStrings().limit(2);
private static ImmutableMap<CurrencyUnit, String> parseBillingAccount(String billingAccount) {
try {
return LINE_SPLITTER.splitToList(billingAccount).stream()
.map(ENTRY_SPLITTER::splitToList)
.peek(
list ->
checkState(
list.size() == 2,
"Can't parse line %s. The format should be [currency]=[account ID]",
list))
.collect(
toImmutableMap(
list -> CurrencyUnit.of(Ascii.toUpperCase(list.getFirst())),
list -> list.get(1)));
} catch (Throwable e) {
throw new RuntimeException("Error parsing billing accounts - " + e.getMessage(), e);
}
}
private void runPost(Map<String, Object> data) {
try {
checkPresent(clientId, "clientId");
checkPresent(name, "name");
checkPresent(billingAccount, "billingAccount");
checkPresent(ianaId, "ianaId");
checkPresent(referralEmail, "referralEmail");
checkPresent(driveId, "driveId");
checkPresent(consoleUserEmail, "consoleUserEmail");
checkPresent(street1, "street");
checkPresent(city, "city");
checkPresent(countryCode, "countryCode");
data.put("clientId", clientId.get());
data.put("name", name.get());
data.put("ianaId", ianaId.get());
data.put("referralEmail", referralEmail.get());
data.put("billingAccount", billingAccount.get());
data.put("driveId", driveId.get());
data.put("consoleUserEmail", consoleUserEmail.get());
data.put("street1", street1.get());
optionalStreet2.ifPresent(street2 -> data.put("street2", street2));
optionalStreet3.ifPresent(street3 -> data.put("street3", street3));
data.put("city", city.get());
optionalState.ifPresent(state -> data.put("state", state));
optionalZip.ifPresent(zip -> data.put("zip", zip));
data.put("countryCode", countryCode.get());
String password = optionalPassword.orElse(passwordGenerator.createString(PASSWORD_LENGTH));
String phonePasscode =
optionalPasscode.orElse(passcodeGenerator.createString(PASSCODE_LENGTH));
Registrar registrar =
new Registrar.Builder()
.setRegistrarId(clientId.get())
.setRegistrarName(name.get())
.setBillingAccountMap(parseBillingAccount(billingAccount.get()))
.setIanaIdentifier(Long.valueOf(ianaId.get()))
.setIcannReferralEmail(referralEmail.get())
.setEmailAddress(referralEmail.get())
.setDriveFolderId(driveId.get())
.setType(Registrar.Type.REAL)
.setPassword(password)
.setPhonePasscode(phonePasscode)
.setState(State.PENDING)
.setLocalizedAddress(
new RegistrarAddress.Builder()
.setStreet(
Stream.of(street1, optionalStreet2, optionalStreet3)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(toImmutableList()))
.setCity(city.get())
.setState(optionalState.orElse(null))
.setCountryCode(countryCode.get())
.setZip(optionalZip.orElse(null))
.build())
.build();
User user =
new User.Builder()
.setEmailAddress(consoleUserEmail.get())
.setUserRoles(
new UserRoles.Builder()
.setRegistrarRoles(
ImmutableMap.of(
registrar.getRegistrarId(), RegistrarRole.ACCOUNT_MANAGER))
.build())
.build();
tm().transact(
() -> {
checkState(
Registrar.loadByRegistrarId(registrar.getRegistrarId()).isEmpty(),
"Registrar with client ID %s already exists",
registrar.getRegistrarId());
tm().put(registrar);
tm().put(user);
});
User.grantIapPermission(
user.getEmailAddress(), maybeGroupEmailAddress, cloudTasksUtils, null, iamClient);
data.put("password", password);
data.put("passcode", phonePasscode);
sendExternalUpdates();
response.setPayload(
TOFU_SUPPLIER
.get()
.newRenderer(RegistrarCreateConsoleSoyInfo.RESULT_SUCCESS)
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
.setData(data)
.render());
} catch (Throwable e) {
logger.atWarning().withCause(e).log(
"Failed to create registrar. clientId: %s, data: %s", clientId.get(), data);
data.put("errorMessage", e.getMessage());
response.setPayload(
TOFU_SUPPLIER
.get()
.newRenderer(RegistrarCreateConsoleSoyInfo.FORM_PAGE)
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
.setData(data)
.render());
}
}
private void runGet(Map<String, Object> data) {
// set the values to pre-fill, if given
data.put("clientId", clientId.orElse(null));
data.put("name", name.orElse(null));
data.put("ianaId", ianaId.orElse(null));
data.put("referralEmail", referralEmail.orElse(null));
data.put("driveId", driveId.orElse(null));
data.put("consoleUserEmail", consoleUserEmail.orElse(null));
response.setPayload(
TOFU_SUPPLIER
.get()
.newRenderer(RegistrarCreateConsoleSoyInfo.FORM_PAGE)
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
.setData(data)
.render());
}
private static String toEmailLine(Optional<?> value, String name) {
return String.format(" %s: %s\n", name, value.orElse(null));
}
private void sendExternalUpdates() {
if (!sendEmailUtils.hasRecipients()) {
return;
}
String environment = Ascii.toLowerCase(String.valueOf(RegistryEnvironment.get()));
String body =
String.format(
"The following registrar was created in %s by %s:\n",
environment, registrarAccessor.userIdForLogging())
+ toEmailLine(clientId, "clientId")
+ toEmailLine(name, "name")
+ toEmailLine(billingAccount, "billingAccount")
+ toEmailLine(ianaId, "ianaId")
+ toEmailLine(referralEmail, "referralEmail")
+ toEmailLine(driveId, "driveId")
+ String.format("Gave user %s web access to the registrar\n", consoleUserEmail.get());
sendEmailUtils.sendEmail(
String.format("Registrar %s created in %s", clientId.get(), environment), body);
}
}

View File

@@ -1,198 +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.Strings.isNullOrEmpty;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.ADMIN;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.OWNER;
import static google.registry.ui.server.SoyTemplateUtils.CSS_RENAMING_MAP_SUPPLIER;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.flogger.FluentLogger;
import com.google.template.soy.data.SoyMapData;
import com.google.template.soy.tofu.SoyTofu;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.common.FeatureFlag;
import google.registry.request.Action;
import google.registry.request.Action.GaeService;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role;
import google.registry.ui.server.SoyTemplateUtils;
import google.registry.ui.soy.registrar.ConsoleSoyInfo;
import google.registry.util.RegistryEnvironment;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import javax.inject.Inject;
/** Action that serves Registrar Console single HTML page (SPA). */
@Action(
service = GaeService.DEFAULT,
path = ConsoleUiAction.PATH,
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public final class ConsoleUiAction extends HtmlAction {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
public static final String PATH = "/registrar";
private static final Supplier<SoyTofu> TOFU_SUPPLIER =
SoyTemplateUtils.createTofuSupplier(
google.registry.ui.soy.registrar.AnalyticsSoyInfo.getInstance(),
google.registry.ui.soy.registrar.ConsoleSoyInfo.getInstance(),
google.registry.ui.soy.registrar.ConsoleUtilsSoyInfo.getInstance());
@Inject RegistrarConsoleMetrics registrarConsoleMetrics;
@Inject AuthenticatedRegistrarAccessor registrarAccessor;
@Inject
@Config("integrationEmail")
String integrationEmail;
@Inject
@Config("supportEmail")
String supportEmail;
@Inject
@Config("announcementsEmail")
String announcementsEmail;
@Inject
@Config("supportPhoneNumber")
String supportPhoneNumber;
@Inject
@Config("technicalDocsUrl")
String technicalDocsUrl;
@Inject
@Config("registrarConsoleEnabled")
boolean enabled;
@Inject
@Parameter("consoleClientId")
Optional<String> paramClientId;
@Inject
ConsoleUiAction() {}
@Override
public void runAfterLogin(Map<String, Object> data) {
// This console is deprecated.
// Unless an explict "noredirect" URL parameter is included, it will redirect to the new
// console.
if (isNullOrEmpty(req.getParameter("noredirect"))) {
try {
response.sendRedirect("/console");
return;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
SoyMapData soyMapData = new SoyMapData();
data.forEach((key, value) -> soyMapData.put(key, value));
soyMapData.put("integrationEmail", integrationEmail);
soyMapData.put("supportEmail", supportEmail);
soyMapData.put("announcementsEmail", announcementsEmail);
soyMapData.put("supportPhoneNumber", supportPhoneNumber);
soyMapData.put("technicalDocsUrl", technicalDocsUrl);
if (!enabled) {
response.setStatus(SC_SERVICE_UNAVAILABLE);
response.setPayload(
TOFU_SUPPLIER
.get()
.newRenderer(ConsoleSoyInfo.DISABLED)
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
.setData(soyMapData)
.render());
return;
}
ImmutableSetMultimap<String, Role> roleMap = registrarAccessor.getAllRegistrarIdsWithRoles();
soyMapData.put("allClientIds", roleMap.keySet());
soyMapData.put("environment", RegistryEnvironment.get().toString());
boolean newConsole =
tm().transact(
() ->
FeatureFlag.getUncached(FeatureFlag.FeatureName.NEW_CONSOLE)
.map(
flag ->
flag.getStatus(tm().getTransactionTime())
.equals(FeatureFlag.FeatureStatus.ACTIVE))
.orElse(false));
soyMapData.put("includeDeprecationWarning", newConsole);
// We set the initial value to the value that will show if guessClientId throws.
String clientId = "<null>";
try {
clientId = paramClientId.orElse(registrarAccessor.guessRegistrarId());
soyMapData.put("clientId", clientId);
soyMapData.put("isOwner", roleMap.containsEntry(clientId, OWNER));
soyMapData.put("isAdmin", roleMap.containsEntry(clientId, ADMIN));
// 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)
registrarAccessor.getRegistrar(clientId);
} catch (RegistrarAccessDeniedException 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()
.newRenderer(ConsoleSoyInfo.WHOAREYOU)
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
.setData(soyMapData)
.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 payload =
TOFU_SUPPLIER
.get()
.newRenderer(ConsoleSoyInfo.MAIN)
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
.setData(soyMapData)
.render();
response.setPayload(payload);
registrarConsoleMetrics.registerConsoleRequest(
clientId, paramClientId.isPresent(), roleMap.get(clientId), "SUCCESS");
}
@Override
public String getPath() {
return PATH;
}
}

View File

@@ -1,92 +0,0 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.server.registrar;
import static com.google.common.net.HttpHeaders.X_FRAME_OPTIONS;
import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.User;
import google.registry.request.Action;
import google.registry.request.RequestMethod;
import google.registry.request.Response;
import google.registry.request.auth.AuthResult;
import google.registry.security.XsrfTokenManager;
import jakarta.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
/**
* Handles some of the nitty-gritty of responding to requests that should return HTML, including
* login, redirects, analytics, and some headers.
*
* <p>If the user is not logged in, this will redirect to the login URL.
*/
public abstract class HtmlAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Inject HttpServletRequest req;
@Inject Response response;
@Inject XsrfTokenManager xsrfTokenManager;
@Inject AuthResult authResult;
@Inject @RequestMethod Action.Method method;
@Inject
@Config("logoFilename")
String logoFilename;
@Inject
@Config("productName")
String productName;
@Inject
@Config("analyticsConfig")
Map<String, Object> analyticsConfig;
@Override
public void run() {
response.setHeader(X_FRAME_OPTIONS, "SAMEORIGIN"); // Disallow iframing.
response.setHeader("X-Ui-Compatible", "IE=edge"); // Ask IE not to be silly.
if (authResult.user().isEmpty()) {
response.setStatus(SC_UNAUTHORIZED);
return;
}
response.setContentType(MediaType.HTML_UTF_8);
User user = authResult.user().get();
// Using HashMap to allow null values
HashMap<String, Object> data = new HashMap<>();
data.put("logoFilename", logoFilename);
data.put("productName", productName);
data.put("username", user.getEmailAddress());
data.put("logoutUrl", "/registrar?gcp-iap-mode=CLEAR_LOGIN_COOKIE");
data.put("analyticsConfig", analyticsConfig);
data.put("xsrfToken", xsrfTokenManager.generateToken(user.getEmailAddress()));
logger.atInfo().log(
"User %s is accessing %s with method %s.",
authResult.userIdForLogging(), getClass().getName(), method);
runAfterLogin(data);
}
public abstract void runAfterLogin(Map<String, Object> data);
public abstract String getPath();
}

View File

@@ -1,21 +0,0 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.server.registrar;
/**
* Marker interface for {@link google.registry.request.Action}s that serve GET requests and return
* JSON, rather than HTML.
*/
public interface JsonGetAction extends Runnable {}

View File

@@ -1,126 +0,0 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.server.registrar;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.security.JsonResponseHelper.Status.ERROR;
import static google.registry.security.JsonResponseHelper.Status.SUCCESS;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import google.registry.model.OteAccountBuilder;
import google.registry.model.OteStats;
import google.registry.model.OteStats.StatType;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarBase.Type;
import google.registry.request.Action;
import google.registry.request.Action.GaeService;
import google.registry.request.JsonActionRunner;
import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException;
import google.registry.security.JsonResponseHelper;
import java.util.Map;
import java.util.Optional;
import javax.inject.Inject;
/**
* Admin servlet that allows creating or updating a registrar. Deletes are not allowed so as to
* preserve history.
*/
@Action(
service = GaeService.DEFAULT,
path = OteStatusAction.PATH,
method = Action.Method.POST,
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public final class OteStatusAction implements Runnable, JsonActionRunner.JsonAction {
public static final String PATH = "/registrar-ote-status";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final String CLIENT_ID_PARAM = "clientId";
private static final String COMPLETED_PARAM = "completed";
private static final String DETAILS_PARAM = "details";
private static final String STAT_TYPE_DESCRIPTION_PARAM = "description";
private static final String STAT_TYPE_REQUIREMENT_PARAM = "requirement";
private static final String STAT_TYPE_TIMES_PERFORMED_PARAM = "timesPerformed";
@Inject AuthenticatedRegistrarAccessor registrarAccessor;
@Inject JsonActionRunner jsonActionRunner;
@Inject
OteStatusAction() {}
@Override
public void run() {
jsonActionRunner.run(this);
}
@Override
public Map<String, ?> handleJsonRequest(Map<String, ?> input) {
try {
checkArgument(input != null, "Malformed JSON");
String oteClientId = (String) input.get(CLIENT_ID_PARAM);
checkArgument(
!Strings.isNullOrEmpty(oteClientId), "Missing key for OT&E client: %s", CLIENT_ID_PARAM);
String baseClientId = OteAccountBuilder.getBaseRegistrarId(oteClientId);
Registrar oteRegistrar = registrarAccessor.getRegistrar(oteClientId);
verifyOteAccess(baseClientId);
checkArgument(
Type.OTE.equals(oteRegistrar.getType()),
"Registrar with ID %s is not an OT&E registrar",
oteClientId);
OteStats oteStats = OteStats.getFromRegistrar(baseClientId);
return JsonResponseHelper.create(
SUCCESS, "OT&E check completed successfully", convertOteStats(baseClientId, oteStats));
} catch (Throwable e) {
logger.atWarning().withCause(e).log(
"Failed to verify OT&E status for registrar with input: %s", input);
return JsonResponseHelper.create(
ERROR, Optional.ofNullable(e.getMessage()).orElse("Unspecified error"));
}
}
private void verifyOteAccess(String baseClientId) throws RegistrarAccessDeniedException {
for (String oteClientId : OteAccountBuilder.createRegistrarIdToTldMap(baseClientId).keySet()) {
registrarAccessor.verifyAccess(oteClientId);
}
}
private Map<String, Object> convertOteStats(String baseClientId, OteStats oteStats) {
return ImmutableMap.of(
CLIENT_ID_PARAM, baseClientId,
COMPLETED_PARAM, oteStats.getFailures().isEmpty(),
DETAILS_PARAM,
StatType.REQUIRED_STAT_TYPES.stream()
.map(statType -> convertSingleRequirement(statType, oteStats.getCount(statType)))
.collect(toImmutableList()));
}
private Map<String, Object> convertSingleRequirement(StatType statType, int count) {
int requirement = statType.getRequirement();
return ImmutableMap.of(
STAT_TYPE_DESCRIPTION_PARAM, statType.getDescription(),
STAT_TYPE_REQUIREMENT_PARAM, requirement,
STAT_TYPE_TIMES_PERFORMED_PARAM, count,
COMPLETED_PARAM, count >= requirement);
}
}

View File

@@ -1,69 +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.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.request.auth.AuthenticatedRegistrarAccessor.Role;
import javax.inject.Inject;
final class RegistrarConsoleMetrics {
private static final ImmutableSet<LabelDescriptor> 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<LabelDescriptor> 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<Role> roles, String status) {
consoleRequestMetric.increment(
clientId, String.valueOf(explicitClientId), String.valueOf(roles), status);
}
void registerSettingsRequest(
String clientId, String action, ImmutableSet<Role> roles, String status) {
settingsRequestMetric.increment(clientId, action, String.valueOf(roles), status);
}
}

View File

@@ -1,641 +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.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Sets.difference;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.security.JsonResponseHelper.Status.ERROR;
import static google.registry.security.JsonResponseHelper.Status.SUCCESS;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import static google.registry.util.RegistryEnvironment.PRODUCTION;
import com.google.common.base.Ascii;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import google.registry.batch.CloudTasksUtils;
import google.registry.export.sheet.SyncRegistrarsSheetAction;
import google.registry.flows.certs.CertificateChecker;
import google.registry.flows.certs.CertificateChecker.InsecureCertificateException;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.registrar.RegistrarPocBase.Type;
import google.registry.request.Action;
import google.registry.request.Action.GaeService;
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;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role;
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.SendEmailUtils;
import google.registry.util.CollectionUtils;
import google.registry.util.DiffUtils;
import google.registry.util.RegistryEnvironment;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import javax.inject.Inject;
import org.joda.time.DateTime;
/**
* Admin servlet that allows creating or updating a registrar. Deletes are not allowed so as to
* preserve history.
*/
@Action(
service = GaeService.DEFAULT,
path = RegistrarSettingsAction.PATH,
method = Action.Method.POST,
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonAction {
public static final String PATH = "/registrar-settings";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
static final String OP_PARAM = "op";
static final String ARGS_PARAM = "args";
static final String ID_PARAM = "id";
@Inject JsonActionRunner jsonActionRunner;
@Inject RegistrarConsoleMetrics registrarConsoleMetrics;
@Inject SendEmailUtils sendEmailUtils;
@Inject AuthenticatedRegistrarAccessor registrarAccessor;
@Inject AuthResult authResult;
@Inject CertificateChecker certificateChecker;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject RegistrarSettingsAction() {}
private static boolean hasPhone(RegistrarPoc contact) {
return contact.getPhoneNumber() != null;
}
@Override
public void run() {
jsonActionRunner.run(this);
}
@Override
public Map<String, Object> handleJsonRequest(Map<String, ?> input) {
if (input == null) {
throw new BadRequestException("Malformed JSON");
}
String registrarId = (String) input.get(ID_PARAM);
if (Strings.isNullOrEmpty(registrarId)) {
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<String, ?> args =
(Map<String, Object>)
Optional.<Object>ofNullable(input.get(ARGS_PARAM)).orElse(ImmutableMap.of());
logger.atInfo().log(
"Received request '%s' on registrar '%s' with args %s", op, registrarId, args);
String status = "SUCCESS";
try {
return switch (op) {
case "update" -> update(args, registrarId).toJsonResponse();
case "read" -> read(registrarId).toJsonResponse();
default -> throw new IllegalArgumentException("Unknown or unsupported operation: " + op);
};
} catch (Throwable e) {
logger.atWarning().withCause(e).log(
"Failed to perform operation '%s' on registrar '%s' for args %s", op, registrarId, args);
status = "ERROR: " + e.getClass().getSimpleName();
if (e instanceof FormFieldException formFieldException) {
return JsonResponseHelper.createFormFieldError(
formFieldException.getMessage(), formFieldException.getFieldName());
}
return JsonResponseHelper.create(
ERROR, Optional.ofNullable(e.getMessage()).orElse("Unspecified error"));
} finally {
registrarConsoleMetrics.registerSettingsRequest(
registrarId, op, registrarAccessor.getRolesForRegistrar(registrarId), status);
}
}
record RegistrarResult(String message, Registrar registrar) {
Map<String, Object> toJsonResponse() {
return JsonResponseHelper.create(SUCCESS, message(), registrar().toJsonMap());
}
static RegistrarResult create(String message, Registrar registrar) {
return new RegistrarResult(message, registrar);
}
}
record EmailInfo(
Registrar registrar,
Registrar updatedRegistrar,
ImmutableSet<RegistrarPoc> contacts,
ImmutableSet<RegistrarPoc> updatedContacts) {
static EmailInfo create(
Registrar registrar,
Registrar updatedRegistrar,
ImmutableSet<RegistrarPoc> contacts,
ImmutableSet<RegistrarPoc> updatedContacts) {
return new EmailInfo(registrar, updatedRegistrar, contacts, updatedContacts);
}
}
private RegistrarResult read(String registrarId) {
return RegistrarResult.create("Success", loadRegistrarUnchecked(registrarId));
}
private Registrar loadRegistrarUnchecked(String registrarId) {
try {
return registrarAccessor.getRegistrar(registrarId);
} catch (RegistrarAccessDeniedException e) {
throw new ForbiddenException(e.getMessage(), e);
}
}
private RegistrarResult update(final Map<String, ?> args, String registrarId) {
// Email the updates
sendExternalUpdatesIfNecessary(tm().transact(() -> saveUpdates(args, registrarId)));
// Reload the result outside the transaction to get the most recent version
return RegistrarResult.create("Saved " + registrarId, loadRegistrarUnchecked(registrarId));
}
/** Saves the updates and returns info needed for the update email */
private EmailInfo saveUpdates(final Map<String, ?> args, String registrarId) {
// We load the registrar here rather than outside 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 = loadRegistrarUnchecked(registrarId);
// Detach the registrar to avoid Hibernate object-updates, since we wish to email
// out the diffs between the existing and updated registrar objects
tm().getEntityManager().detach(registrar);
// 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
// comes from the read that gave the UI the data - if it's out of date, then the UI
// had out of date data.
DateTime latest = registrar.getLastUpdateTime();
DateTime latestFromArgs = RegistrarFormFields.LAST_UPDATE_TIME.extractUntyped(args).get();
if (!latestFromArgs.equals(latest)) {
logger.atWarning().log(
"Registrar changed since reading the data!"
+ " Last updated at %s, but args data last updated at %s.",
latest, latestFromArgs);
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
// removed, email the changes to the contacts
ImmutableSet<RegistrarPoc> contacts = registrar.getContacts();
Registrar updatedRegistrar = registrar;
// Do OWNER only updates to the registrar from the request.
updatedRegistrar = checkAndUpdateOwnerControlledFields(updatedRegistrar, args);
// Do ADMIN only updates to the registrar from the request.
updatedRegistrar = checkAndUpdateAdminControlledFields(updatedRegistrar, args);
// read the contacts from the request.
ImmutableSet<RegistrarPoc> updatedContacts = readContacts(registrar, contacts, args);
// Save the updated contacts
if (!updatedContacts.equals(contacts)) {
if (!registrarAccessor.hasRoleOnRegistrar(Role.OWNER, registrar.getRegistrarId())) {
throw new ForbiddenException("Only OWNERs can update the contacts");
}
checkContactRequirements(contacts, updatedContacts);
RegistrarPoc.updateContacts(updatedRegistrar, updatedContacts);
updatedRegistrar = updatedRegistrar.asBuilder().setContactsRequireSyncing(true).build();
}
// Save the updated registrar
if (!updatedRegistrar.equals(registrar)) {
tm().put(updatedRegistrar);
}
return EmailInfo.create(registrar, updatedRegistrar, contacts, updatedContacts);
}
private Map<String, Object> expandRegistrarWithContacts(
Iterable<RegistrarPoc> contacts, Registrar registrar) {
ImmutableSet<Map<String, Object>> expandedContacts =
Streams.stream(contacts)
.map(RegistrarPoc::toDiffableFieldMap)
// Note: per the javadoc, toDiffableFieldMap includes sensitive data, but we don't want
// to display it here
.peek(
map -> {
map.remove("registryLockPasswordHash");
map.remove("registryLockPasswordSalt");
})
.collect(toImmutableSet());
// Use LinkedHashMap here to preserve ordering; null values mean we can't use ImmutableMap.
LinkedHashMap<String, Object> result = new LinkedHashMap<>(registrar.toDiffableFieldMap());
result.put("contacts", expandedContacts);
return result;
}
/**
* Updates registrar with the OWNER-controlled args from the http request.
*
* <p>If any changes were made and the user isn't an OWNER - throws a {@link ForbiddenException}.
*/
private Registrar checkAndUpdateOwnerControlledFields(
Registrar initialRegistrar, Map<String, ?> args) {
Registrar.Builder builder = initialRegistrar.asBuilder();
// WHOIS
//
// Because of how whoisServer handles "default value", it's possible that setting the existing
// value will still change the Registrar. So we first check whether the value has changed.
//
// The problem is - if the Registrar has a "null" whoisServer value, the console gets the
// "default value" instead of the actual (null) value.
// This was done so we display the "default" value, but it also means that it always looks like
// the user updated the whoisServer value from "null" to the default value.
//
// TODO(b/119913848):once a null whoisServer value is sent to the console as "null", there's no
// need to check for equality before setting the value in the builder.
String updatedWhoisServer =
RegistrarFormFields.WHOIS_SERVER_FIELD.extractUntyped(args).orElse(null);
if (!Objects.equals(initialRegistrar.getWhoisServer(), updatedWhoisServer)) {
builder.setWhoisServer(updatedWhoisServer);
}
builder.setUrl(RegistrarFormFields.URL_FIELD.extractUntyped(args).orElse(null));
// If the email is already null / empty - we can keep it so. But if it's set - it's required to
// remain set.
(Strings.isNullOrEmpty(initialRegistrar.getEmailAddress())
? RegistrarFormFields.EMAIL_ADDRESS_FIELD_OPTIONAL
: RegistrarFormFields.EMAIL_ADDRESS_FIELD_REQUIRED)
.extractUntyped(args)
.ifPresent(builder::setEmailAddress);
builder.setPhoneNumber(
RegistrarFormFields.PHONE_NUMBER_FIELD.extractUntyped(args).orElse(null));
builder.setFaxNumber(RegistrarFormFields.FAX_NUMBER_FIELD.extractUntyped(args).orElse(null));
builder.setLocalizedAddress(
RegistrarFormFields.L10N_ADDRESS_FIELD.extractUntyped(args).orElse(null));
// Security
builder.setIpAddressAllowList(
RegistrarFormFields.IP_ADDRESS_ALLOW_LIST_FIELD
.extractUntyped(args)
.orElse(ImmutableList.of()));
Optional<String> certificateString =
RegistrarFormFields.CLIENT_CERTIFICATE_FIELD.extractUntyped(args);
if (certificateString.isPresent()) {
if (validateCertificate(initialRegistrar.getClientCertificate(), certificateString.get())) {
builder.setClientCertificate(certificateString.get(), tm().getTransactionTime());
}
}
Optional<String> failoverCertificateString =
RegistrarFormFields.FAILOVER_CLIENT_CERTIFICATE_FIELD.extractUntyped(args);
if (failoverCertificateString.isPresent()) {
if (validateCertificate(
initialRegistrar.getFailoverClientCertificate(), failoverCertificateString.get())) {
builder.setFailoverClientCertificate(
failoverCertificateString.get(), tm().getTransactionTime());
}
}
return checkNotChangedUnlessAllowed(builder, initialRegistrar, Role.OWNER);
}
/**
* Returns true if the registrar should accept the new certificate. Returns false if the
* certificate is already the one stored for the registrar.
*/
private boolean validateCertificate(
Optional<String> existingCertificate, String certificateString) {
if (existingCertificate.isEmpty() || !existingCertificate.get().equals(certificateString)) {
try {
certificateChecker.validateCertificate(certificateString);
} catch (InsecureCertificateException e) {
throw new IllegalArgumentException(e.getMessage());
}
return true;
}
return false;
}
/**
* Updates a registrar with the ADMIN-controlled args from the http request.
*
* <p>If any changes were made and the user isn't an ADMIN - throws a {@link ForbiddenException}.
*/
private Registrar checkAndUpdateAdminControlledFields(
Registrar initialRegistrar, Map<String, ?> args) {
Registrar.Builder builder = initialRegistrar.asBuilder();
Set<String> updatedAllowedTlds =
RegistrarFormFields.ALLOWED_TLDS_FIELD.extractUntyped(args).orElse(ImmutableSet.of());
// Temporarily block anyone from removing an allowed TLD.
// This is so we can start having Support users use the console in production before we finish
// implementing configurable access control.
// TODO(b/119549884): remove this code once configurable access control is implemented.
if (!Sets.difference(initialRegistrar.getAllowedTlds(), updatedAllowedTlds).isEmpty()) {
throw new ForbiddenException("Can't remove allowed TLDs using the console.");
}
if (!Sets.difference(updatedAllowedTlds, initialRegistrar.getAllowedTlds()).isEmpty()) {
// If a REAL registrar isn't in compliance with regard to having an abuse contact set,
// prevent addition of allowed TLDs until that's fixed.
if (Registrar.Type.REAL.equals(initialRegistrar.getType())
&& PRODUCTION.equals(RegistryEnvironment.get())) {
checkArgumentPresent(
initialRegistrar.getWhoisAbuseContact(),
"Cannot add allowed TLDs if there is no WHOIS abuse contact set.");
}
}
builder.setAllowedTlds(updatedAllowedTlds);
return checkNotChangedUnlessAllowed(builder, initialRegistrar, Role.ADMIN);
}
/**
* Makes sure {@code builder.build}is different from {@code originalRegistrar} only if we have the
* correct role.
*
* <p>On success, returns {@code builder.build()}.
*/
private Registrar checkNotChangedUnlessAllowed(
Registrar.Builder builder, Registrar originalRegistrar, Role allowedRole) {
Registrar updatedRegistrar = builder.build();
if (updatedRegistrar.equals(originalRegistrar)) {
return updatedRegistrar;
}
checkArgument(
updatedRegistrar.getRegistrarId().equals(originalRegistrar.getRegistrarId()),
"Can't change clientId (%s -> %s)",
originalRegistrar.getRegistrarId(),
updatedRegistrar.getRegistrarId());
if (registrarAccessor.hasRoleOnRegistrar(allowedRole, originalRegistrar.getRegistrarId())) {
return updatedRegistrar;
}
Map<?, ?> diffs =
DiffUtils.deepDiff(
originalRegistrar.toDiffableFieldMap(), updatedRegistrar.toDiffableFieldMap(), true);
// It's expected that the update timestamp will be changed, as it gets reset whenever we change
// nested collections. If it's the only change, just return the original registrar.
if (diffs.keySet().equals(ImmutableSet.of("lastUpdateTime"))) {
return originalRegistrar;
}
throw new ForbiddenException(
String.format("Unauthorized: only %s can change fields %s", allowedRole, diffs.keySet()));
}
/** Reads the contacts from the supplied args. */
public static ImmutableSet<RegistrarPoc> readContacts(
Registrar registrar, ImmutableSet<RegistrarPoc> existingContacts, Map<String, ?> args) {
return RegistrarFormFields.getRegistrarContactBuilders(existingContacts, args).stream()
.map(builder -> builder.setRegistrar(registrar).build())
.collect(toImmutableSet());
}
/**
* Enforces business logic checks on registrar contacts.
*
* @throws FormException if the checks fail.
*/
public static void checkContactRequirements(
ImmutableSet<RegistrarPoc> existingContacts, ImmutableSet<RegistrarPoc> updatedContacts) {
// Check that no two contacts use the same email address.
Set<String> emails = new HashSet<>();
for (RegistrarPoc contact : updatedContacts) {
if (!emails.add(contact.getEmailAddress())) {
throw new ContactRequirementException(
String.format(
"One email address (%s) cannot be used for multiple contacts",
contact.getEmailAddress()));
}
}
// Check that required contacts don't go away, once they are set.
Multimap<Type, RegistrarPoc> oldContactsByType = HashMultimap.create();
for (RegistrarPoc contact : existingContacts) {
for (Type t : contact.getTypes()) {
oldContactsByType.put(t, contact);
}
}
Multimap<Type, RegistrarPoc> newContactsByType = HashMultimap.create();
for (RegistrarPoc contact : updatedContacts) {
for (Type t : contact.getTypes()) {
newContactsByType.put(t, contact);
}
}
for (Type t : difference(oldContactsByType.keySet(), newContactsByType.keySet())) {
if (t.isRequired()) {
throw new ContactRequirementException(t);
}
}
ensurePhoneNumberNotRemovedForContactTypes(oldContactsByType, newContactsByType, Type.TECH);
Optional<RegistrarPoc> domainWhoisAbuseContact =
getDomainWhoisVisibleAbuseContact(updatedContacts);
// If the new set has a domain WHOIS abuse contact, it must have a phone number.
if (domainWhoisAbuseContact.isPresent()
&& domainWhoisAbuseContact.get().getPhoneNumber() == null) {
throw new ContactRequirementException(
"The abuse contact visible in domain WHOIS query must have a phone number");
}
// If there was a domain WHOIS abuse contact in the old set, the new set must have one.
if (getDomainWhoisVisibleAbuseContact(existingContacts).isPresent()
&& domainWhoisAbuseContact.isEmpty()) {
throw new ContactRequirementException(
"An abuse contact visible in domain WHOIS query must be designated");
}
checkContactRegistryLockRequirements(existingContacts, updatedContacts);
}
private static void checkContactRegistryLockRequirements(
ImmutableSet<RegistrarPoc> existingContacts, ImmutableSet<RegistrarPoc> updatedContacts) {
// Any contact(s) with new passwords must be allowed to set them
for (RegistrarPoc updatedContact : updatedContacts) {
if (updatedContact.isRegistryLockAllowed()
|| updatedContact.isAllowedToSetRegistryLockPassword()) {
RegistrarPoc existingContact =
existingContacts.stream()
.filter(
contact -> contact.getEmailAddress().equals(updatedContact.getEmailAddress()))
.findFirst()
.orElseThrow(
() ->
new FormException(
"Cannot set registry lock password directly on new contact"));
// Can't modify registry lock email address
if (!Objects.equals(
updatedContact.getRegistryLockEmailAddress(),
existingContact.getRegistryLockEmailAddress())) {
throw new FormException("Cannot modify registryLockEmailAddress through the UI");
}
if (updatedContact.isRegistryLockAllowed()) {
// the password must have been set before or the user was allowed to set it now
if (!existingContact.isAllowedToSetRegistryLockPassword()
&& !existingContact.isRegistryLockAllowed()) {
throw new FormException("Registrar contact not allowed to set registry lock password");
}
}
if (updatedContact.isAllowedToSetRegistryLockPassword()) {
if (!existingContact.isAllowedToSetRegistryLockPassword()) {
throw new FormException(
"Cannot modify isAllowedToSetRegistryLockPassword through the UI");
}
}
}
}
// Any previously-existing contacts with registry lock enabled cannot be deleted
existingContacts.stream()
.filter(RegistrarPoc::isRegistryLockAllowed)
.forEach(
contact -> {
Optional<RegistrarPoc> updatedContactOptional =
updatedContacts.stream()
.filter(
updatedContact ->
updatedContact.getEmailAddress().equals(contact.getEmailAddress()))
.findFirst();
if (updatedContactOptional.isEmpty()) {
throw new FormException(
String.format(
"Cannot delete the contact %s that has registry lock enabled",
contact.getEmailAddress()));
}
if (!updatedContactOptional.get().isRegistryLockAllowed()) {
throw new FormException(
String.format(
"Cannot remove the ability to use registry lock on the contact %s",
contact.getEmailAddress()));
}
});
}
/**
* Ensure that for each given registrar type, a phone number is present after update, if there was
* one before.
*/
private static void ensurePhoneNumberNotRemovedForContactTypes(
Multimap<Type, RegistrarPoc> oldContactsByType,
Multimap<Type, RegistrarPoc> newContactsByType,
Type... types) {
for (Type type : types) {
if (oldContactsByType.get(type).stream().anyMatch(RegistrarSettingsAction::hasPhone)
&& newContactsByType.get(type).stream().noneMatch(RegistrarSettingsAction::hasPhone)) {
throw new ContactRequirementException(
String.format(
"Please provide a phone number for at least one %s contact",
type.getDisplayName()));
}
}
}
/**
* Retrieves the registrar contact whose phone number and email address is visible in domain WHOIS
* query as abuse contact (if any).
*
* <p>Frontend processing ensures that only one contact can be set as abuse contact in domain
* WHOIS record. Therefore, it is possible to return inside the loop once one such contact is
* found.
*/
private static Optional<RegistrarPoc> getDomainWhoisVisibleAbuseContact(
Set<RegistrarPoc> contacts) {
return contacts.stream().filter(RegistrarPoc::getVisibleInDomainWhoisAsAbuse).findFirst();
}
/**
* Determines if any changes were made to the registrar besides the lastUpdateTime, and if so,
* sends an email with a diff of the changes to the configured notification email address and all
* contact addresses and enqueues a task to re-sync the registrar sheet.
*/
private void sendExternalUpdatesIfNecessary(EmailInfo emailInfo) {
ImmutableSet<RegistrarPoc> existingContacts = emailInfo.contacts();
if (!sendEmailUtils.hasRecipients() && existingContacts.isEmpty()) {
return;
}
Registrar existingRegistrar = emailInfo.registrar();
Map<?, ?> diffs =
DiffUtils.deepDiff(
expandRegistrarWithContacts(existingContacts, existingRegistrar),
expandRegistrarWithContacts(emailInfo.updatedContacts(), emailInfo.updatedRegistrar()),
true);
@SuppressWarnings("unchecked")
Set<String> changedKeys = (Set<String>) diffs.keySet();
if (CollectionUtils.difference(changedKeys, "lastUpdateTime").isEmpty()) {
return;
}
if (!RegistryEnvironment.isInTestServer()) {
// Enqueues a sync registrar sheet task if enqueuing is not triggered by console tests and
// there's an update besides the lastUpdateTime
cloudTasksUtils.enqueue(
SyncRegistrarsSheetAction.QUEUE,
cloudTasksUtils.createTask(
SyncRegistrarsSheetAction.class, Action.Method.POST, ImmutableMultimap.of()));
}
String environment = Ascii.toLowerCase(String.valueOf(RegistryEnvironment.get()));
sendEmailUtils.sendEmail(
String.format(
"Registrar %s (%s) updated in registry %s environment",
existingRegistrar.getRegistrarName(), existingRegistrar.getRegistrarId(), environment),
String.format(
"""
The following changes were made in registry %s environment to the registrar %s by\
%s:
%s""",
environment,
existingRegistrar.getRegistrarId(),
authResult.userIdForLogging(),
DiffUtils.prettyPrintDiffedMap(diffs, null)),
existingContacts.stream()
.filter(c -> c.getTypes().contains(Type.ADMIN))
.map(RegistrarPoc::getEmailAddress)
.collect(toImmutableList()));
}
/** Thrown when a set of contacts doesn't meet certain constraints. */
private static class ContactRequirementException extends FormException {
ContactRequirementException(String msg) {
super(msg);
}
ContactRequirementException(Type type) {
super(String.format("Must have at least one %s contact", type.getDisplayName()));
}
}
}

View File

@@ -1,182 +0,0 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.server.registrar;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.security.JsonResponseHelper.Status.SUCCESS;
import static google.registry.ui.server.console.ConsoleModule.PARAM_CLIENT_ID;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import com.google.gson.Gson;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.User;
import google.registry.model.domain.RegistryLock;
import google.registry.model.registrar.Registrar;
import google.registry.model.tld.RegistryLockDao;
import google.registry.request.Action;
import google.registry.request.Action.GaeService;
import google.registry.request.Action.Method;
import google.registry.request.Parameter;
import google.registry.request.RequestMethod;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException;
import google.registry.security.JsonResponseHelper;
import java.util.Optional;
import javax.inject.Inject;
import org.joda.time.DateTime;
/**
* Action that allows for getting locks for a particular registrar.
*
* <p>Note: at the moment we have no mechanism for JSON GET/POSTs in the same class or at the same
* URL, which is why this is distinct from the {@link RegistryLockPostAction}.
*/
@Action(
service = GaeService.DEFAULT,
path = RegistryLockGetAction.PATH,
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public final class RegistryLockGetAction implements JsonGetAction {
public static final String PATH = "/registry-lock-get";
private static final String LOCK_ENABLED_FOR_CONTACT_PARAM = "lockEnabledForContact";
private static final String EMAIL_PARAM = "email";
private static final String LOCKS_PARAM = "locks";
private static final String DOMAIN_NAME_PARAM = "domainName";
private static final String LOCKED_TIME_PARAM = "lockedTime";
private static final String LOCKED_BY_PARAM = "lockedBy";
private static final String IS_LOCK_PENDING_PARAM = "isLockPending";
private static final String IS_UNLOCK_PENDING_PARAM = "isUnlockPending";
private static final String USER_CAN_UNLOCK_PARAM = "userCanUnlock";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final Gson GSON = new Gson();
@VisibleForTesting Method method;
private final Response response;
@VisibleForTesting AuthenticatedRegistrarAccessor registrarAccessor;
@VisibleForTesting AuthResult authResult;
@VisibleForTesting Optional<String> paramClientId;
@Inject
RegistryLockGetAction(
@RequestMethod Method method,
Response response,
AuthenticatedRegistrarAccessor registrarAccessor,
AuthResult authResult,
@Parameter("consoleClientId") Optional<String> paramClientId) {
this.method = method;
this.response = response;
this.registrarAccessor = registrarAccessor;
this.authResult = authResult;
this.paramClientId = paramClientId;
}
@Override
public void run() {
checkArgument(Method.GET.equals(method), "Only GET requests allowed");
checkArgument(authResult.user().isPresent(), "User must be present");
checkArgument(paramClientId.isPresent(), "clientId must be present");
response.setContentType(MediaType.JSON_UTF_8);
try {
ImmutableMap<String, ?> resultMap = getLockedDomainsMap(paramClientId.get());
ImmutableMap<String, ?> payload =
JsonResponseHelper.create(SUCCESS, "Successful locks retrieval", resultMap);
response.setPayload(GSON.toJson(payload));
} catch (RegistrarAccessDeniedException e) {
logger.atWarning().withCause(e).log(
"User %s doesn't have access to this registrar.", authResult.userIdForLogging());
response.setStatus(SC_FORBIDDEN);
} catch (Exception e) {
logger.atWarning().withCause(e).log(
"Unexpected error when retrieving locks for a registrar.");
response.setStatus(SC_INTERNAL_SERVER_ERROR);
}
}
static void verifyLockAccess(
AuthenticatedRegistrarAccessor registrarAccessor, String clientId, boolean isAdmin)
throws RegistrarAccessDeniedException {
Registrar registrar = registrarAccessor.getRegistrar(clientId);
checkArgument(
isAdmin || registrar.isRegistryLockAllowed(),
"Registry lock not allowed for registrar %s",
clientId);
}
private ImmutableMap<String, ?> getLockedDomainsMap(String registrarId)
throws RegistrarAccessDeniedException {
// Note: admins always have access to the locks page
checkArgument(authResult.user().isPresent(), "User must be present");
boolean isAdmin = registrarAccessor.isAdmin();
verifyLockAccess(registrarAccessor, registrarId, isAdmin);
User user = authResult.user().get();
// Split logic depending on whether we are using the old auth system or the new one
boolean isRegistryLockAllowed;
isRegistryLockAllowed =
user.getUserRoles().hasPermission(registrarId, ConsolePermission.REGISTRY_LOCK);
String relevantEmail = user.getRegistryLockEmailAddress().orElse(user.getEmailAddress());
// Use the contact's registry lock email if it's present, else use the login email (for admins)
return ImmutableMap.of(
LOCK_ENABLED_FOR_CONTACT_PARAM,
isRegistryLockAllowed,
EMAIL_PARAM,
relevantEmail,
PARAM_CLIENT_ID,
registrarId,
LOCKS_PARAM,
getLockedDomains(registrarId, isAdmin));
}
private static ImmutableList<ImmutableMap<String, ?>> getLockedDomains(
String registrarId, boolean isAdmin) {
return tm().transact(
() ->
RegistryLockDao.getLocksByRegistrarId(registrarId).stream()
.filter(lock -> !lock.isLockRequestExpired(tm().getTransactionTime()))
.map(lock -> lockToMap(lock, isAdmin))
.collect(toImmutableList()));
}
private static ImmutableMap<String, ?> lockToMap(RegistryLock lock, boolean isAdmin) {
DateTime now = tm().getTransactionTime();
return new ImmutableMap.Builder<String, Object>()
.put(DOMAIN_NAME_PARAM, lock.getDomainName())
.put(LOCKED_TIME_PARAM, lock.getLockCompletionTime().map(DateTime::toString).orElse(""))
.put(LOCKED_BY_PARAM, lock.isSuperuser() ? "admin" : lock.getRegistrarPocId())
.put(IS_LOCK_PENDING_PARAM, lock.getLockCompletionTime().isEmpty())
.put(
IS_UNLOCK_PENDING_PARAM,
lock.getUnlockRequestTime().isPresent()
&& lock.getUnlockCompletionTime().isEmpty()
&& !lock.isUnlockRequestExpired(now))
.put(USER_CAN_UNLOCK_PARAM, isAdmin || !lock.isSuperuser())
.build();
}
}

View File

@@ -1,197 +0,0 @@
// Copyright 2020 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.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.security.JsonResponseHelper.Status.ERROR;
import static google.registry.security.JsonResponseHelper.Status.SUCCESS;
import static google.registry.ui.server.registrar.RegistryLockGetAction.verifyLockAccess;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import com.google.gson.Gson;
import google.registry.flows.domain.DomainFlowUtils;
import google.registry.groups.GmailClient;
import google.registry.model.console.User;
import google.registry.model.domain.RegistryLock;
import google.registry.request.Action;
import google.registry.request.Action.GaeService;
import google.registry.request.Action.Method;
import google.registry.request.HttpException.ForbiddenException;
import google.registry.request.JsonActionRunner;
import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException;
import google.registry.security.JsonResponseHelper;
import google.registry.tools.DomainLockUtils;
import google.registry.util.EmailMessage;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import jakarta.servlet.http.HttpServletRequest;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.Optional;
import javax.inject.Inject;
import org.apache.http.client.utils.URIBuilder;
import org.joda.time.Duration;
/**
* UI action that allows for creating registry locks. Locks / unlocks must be verified separately
* before they are written permanently.
*
* <p>Note: at the moment we have no mechanism for JSON GET/POSTs in the same class or at the same
* URL, which is why this is distinct from the {@link RegistryLockGetAction}.
*/
@Action(
service = GaeService.DEFAULT,
path = RegistryLockPostAction.PATH,
method = Method.POST,
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public class RegistryLockPostAction implements Runnable, JsonActionRunner.JsonAction {
public static final String PATH = "/registry-lock-post";
public static final String VERIFICATION_EMAIL_TEMPLATE =
"""
Please click the link below to perform the lock / unlock action on domain %s. Note: this\
code will expire in one hour.
%s""";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final Gson GSON = new Gson();
private final HttpServletRequest req;
private final JsonActionRunner jsonActionRunner;
private final AuthResult authResult;
private final AuthenticatedRegistrarAccessor registrarAccessor;
private final GmailClient gmailClient;
private final DomainLockUtils domainLockUtils;
@Inject
RegistryLockPostAction(
HttpServletRequest req,
JsonActionRunner jsonActionRunner,
AuthResult authResult,
AuthenticatedRegistrarAccessor registrarAccessor,
GmailClient gmailClient,
DomainLockUtils domainLockUtils) {
this.req = req;
this.jsonActionRunner = jsonActionRunner;
this.authResult = authResult;
this.registrarAccessor = registrarAccessor;
this.gmailClient = gmailClient;
this.domainLockUtils = domainLockUtils;
}
@Override
public void run() {
jsonActionRunner.run(this);
}
@Override
public Map<String, ?> handleJsonRequest(Map<String, ?> input) {
try {
checkArgumentNotNull(input, "Null JSON");
RegistryLockPostInput postInput =
GSON.fromJson(GSON.toJsonTree(input), RegistryLockPostInput.class);
String registrarId = postInput.registrarId;
checkArgument(!Strings.isNullOrEmpty(registrarId), "Missing key for registrarId");
checkArgument(!Strings.isNullOrEmpty(postInput.domainName), "Missing key for domainName");
DomainFlowUtils.validateDomainName(postInput.domainName);
checkNotNull(postInput.isLock, "Missing key for isLock");
User user =
authResult.user().orElseThrow(() -> new ForbiddenException("User is not logged in"));
// TODO: Move this line to the transaction below during nested transaction refactoring.
String userEmail = verifyPasswordAndGetEmail(user, postInput);
tm().transact(
() -> {
RegistryLock registryLock =
postInput.isLock
? domainLockUtils.saveNewRegistryLockRequest(
postInput.domainName,
registrarId,
userEmail,
registrarAccessor.isAdmin())
: domainLockUtils.saveNewRegistryUnlockRequest(
postInput.domainName,
registrarId,
registrarAccessor.isAdmin(),
Optional.ofNullable(postInput.relockDurationMillis).map(Duration::new));
sendVerificationEmail(registryLock, userEmail, postInput.isLock);
});
String action = postInput.isLock ? "lock" : "unlock";
return JsonResponseHelper.create(SUCCESS, String.format("Successful %s", action));
} catch (Throwable e) {
logger.atWarning().withCause(e).log("Failed to lock/unlock domain.");
return JsonResponseHelper.create(
ERROR,
Optional.ofNullable(Throwables.getRootCause(e).getMessage()).orElse("Unspecified error"));
}
}
private void sendVerificationEmail(RegistryLock lock, String userEmail, boolean isLock) {
try {
String url =
new URIBuilder()
.setScheme("https")
.setHost(req.getServerName())
.setPath("registry-lock-verify")
.setParameter("lockVerificationCode", lock.getVerificationCode())
.build()
.toString();
String body = String.format(VERIFICATION_EMAIL_TEMPLATE, lock.getDomainName(), url);
ImmutableList<InternetAddress> recipients =
ImmutableList.of(new InternetAddress(userEmail, true));
String action = isLock ? "lock" : "unlock";
gmailClient.sendEmail(
EmailMessage.newBuilder()
.setBody(body)
.setSubject(String.format("Registry %s verification", action))
.setRecipients(recipients)
.build());
} catch (AddressException | URISyntaxException e) {
throw new RuntimeException(e); // caught above -- this is so we can run in a transaction
}
}
private String verifyPasswordAndGetEmail(User user, RegistryLockPostInput postInput)
throws RegistrarAccessDeniedException {
if (registrarAccessor.isAdmin()) {
return user.getEmailAddress();
}
// Verify that the registrar has locking enabled
verifyLockAccess(registrarAccessor, postInput.registrarId, false);
checkArgument(
user.verifyRegistryLockPassword(postInput.password),
"Incorrect registry lock password for user");
return user.getRegistryLockEmailAddress()
.orElseThrow(() -> new IllegalArgumentException("User has no registry lock email address"));
}
/** Value class that represents the expected input body from the UI request. */
private static class RegistryLockPostInput {
private String registrarId;
private String domainName;
private Boolean isLock;
private String password;
private Long relockDurationMillis;
}
}

View File

@@ -1,91 +0,0 @@
// Copyright 2020 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.ui.server.SoyTemplateUtils.CSS_RENAMING_MAP_SUPPLIER;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.flogger.FluentLogger;
import com.google.template.soy.tofu.SoyTofu;
import google.registry.model.domain.RegistryLock;
import google.registry.request.Action;
import google.registry.request.Action.GaeService;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.tools.DomainLockUtils;
import google.registry.ui.server.SoyTemplateUtils;
import google.registry.ui.soy.registrar.RegistryLockVerificationSoyInfo;
import java.util.Map;
import javax.inject.Inject;
/** Action that allows for verification of registry lock / unlock requests */
@Action(
service = GaeService.DEFAULT,
path = RegistryLockVerifyAction.PATH,
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public final class RegistryLockVerifyAction extends HtmlAction {
public static final String PATH = "/registry-lock-verify";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final Supplier<SoyTofu> TOFU_SUPPLIER =
SoyTemplateUtils.createTofuSupplier(
google.registry.ui.soy.registrar.AnalyticsSoyInfo.getInstance(),
google.registry.ui.soy.registrar.ConsoleSoyInfo.getInstance(),
google.registry.ui.soy.registrar.ConsoleUtilsSoyInfo.getInstance(),
google.registry.ui.soy.registrar.RegistryLockVerificationSoyInfo.getInstance());
private final DomainLockUtils domainLockUtils;
private final String lockVerificationCode;
@Inject
public RegistryLockVerifyAction(
DomainLockUtils domainLockUtils,
@Parameter("lockVerificationCode") String lockVerificationCode) {
this.domainLockUtils = domainLockUtils;
this.lockVerificationCode = lockVerificationCode;
}
@Override
public void runAfterLogin(Map<String, Object> data) {
try {
boolean isAdmin = authResult.user().get().getUserRoles().isAdmin();
RegistryLock resultLock =
domainLockUtils.verifyVerificationCode(lockVerificationCode, isAdmin);
data.put("isLock", resultLock.getUnlockCompletionTime().isEmpty());
data.put("success", true);
data.put("domainName", resultLock.getDomainName());
} catch (Throwable t) {
logger.atWarning().withCause(t).log(
"Error when verifying verification code '%s'.", lockVerificationCode);
data.put("success", false);
data.put("errorMessage", Throwables.getRootCause(t).getMessage());
}
response.setPayload(
TOFU_SUPPLIER
.get()
.newRenderer(RegistryLockVerificationSoyInfo.VERIFICATION_PAGE)
.setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get())
.setData(data)
.render());
}
@Override
public String getPath() {
return PATH;
}
}

View File

@@ -1,16 +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.
@javax.annotation.ParametersAreNonnullByDefault
package google.registry.ui.server.registrar;

View File

@@ -19,8 +19,6 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import google.registry.module.backend.BackendRequestComponent;
import google.registry.module.bsa.BsaRequestComponent;
import google.registry.module.frontend.FrontendRequestComponent;
@@ -43,18 +41,6 @@ public class RequestComponentTest {
PubApiRequestComponent.class, "pubapi",
BsaRequestComponent.class, "bsa");
// Paths that do not route to Jetty (all for the legacy console).
private static final ImmutableSet<String> ignoredPaths =
ImmutableSet.of(
"/registrar",
"/registrar-create",
"/registrar-ote-setup",
"/registrar-ote-status",
"/registrar-settings",
"/registry-lock-get",
"/registry-lock-post",
"/registry-lock-verify");
@Test
void testRoutingMap() {
GoldenFileTestHelper.assertThatRoutesFromComponent(RequestComponent.class)
@@ -69,12 +55,7 @@ public class RequestComponentTest {
for (var component : GaeComponents.entrySet()) {
gaeRoutes.addAll(getRoutes(component.getKey(), component.getValue() + "_routing.txt"));
}
assertThat(Sets.difference(jettyRoutes, gaeRoutes)).isEmpty();
assertThat(
Sets.difference(gaeRoutes, jettyRoutes).stream()
.map(Route::path)
.collect(Collectors.toSet()))
.containsExactlyElementsIn(ignoredPaths);
assertThat(jettyRoutes).isEqualTo(gaeRoutes);
}
private Set<Route> getRoutes(Class<?> context, String filename) {

View File

@@ -40,10 +40,6 @@ public final class RegistryTestServer {
.put(
"/error.html",
PROJECT_ROOT.resolve("core/src/main/java/google/registry/ui/html/error.html"))
.put("/assets/js/*", RESOURCES_DIR.resolve("google/registry/ui"))
.put("/assets/css/*", RESOURCES_DIR.resolve("google/registry/ui/css"))
.put("/assets/sources/*", PROJECT_ROOT)
.put("/assets/*", PROJECT_ROOT.resolve("core/src/main/java/google/registry/ui/assets"))
.put("/console/*", PROJECT_ROOT.resolve("console-webapp/staged/dist"))
.build();
@@ -73,17 +69,7 @@ public final class RegistryTestServer {
// Notification of Registered Domain Names (NORDN)
route("/_dr/task/nordnUpload", BackendServlet.class),
route("/_dr/task/nordnVerify", BackendServlet.class),
// Registrar Console
route("/registrar", FrontendServlet.class),
route("/registrar-create", FrontendServlet.class),
route("/registrar-ote-setup", FrontendServlet.class),
route("/registrar-ote-status", FrontendServlet.class),
route("/registrar-settings", FrontendServlet.class),
route("/registry-lock-get", FrontendServlet.class),
route("/registry-lock-post", FrontendServlet.class),
route("/registry-lock-verify", FrontendServlet.class));
route("/_dr/task/nordnVerify", BackendServlet.class));
private final TestServer server;

View File

@@ -20,8 +20,6 @@ import com.google.common.collect.ImmutableSet;
import google.registry.request.Action;
import google.registry.request.JsonActionRunner;
import google.registry.ui.server.console.ConsoleApiAction;
import google.registry.ui.server.registrar.HtmlAction;
import google.registry.ui.server.registrar.JsonGetAction;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ScanResult;
import org.junit.jupiter.api.Test;
@@ -32,10 +30,8 @@ final class ActionMembershipTest {
@Test
void testAllActionsEitherHtmlOrJson() {
// All UI actions should serve valid HTML or JSON. There are three valid options:
// 1. Extending HtmlAction to signal that we are serving an HTML page
// 2. Extending JsonAction to show that we are serving JSON POST requests
// 3. Extending JsonGetAction to serve JSON GET requests
// 4. Extending ConsoleApiAction to serve JSON requests
// 1. Extending JsonAction to show that we are serving JSON POST requests
// 2. Extending ConsoleApiAction to serve JSON requests
ImmutableSet.Builder<String> failingClasses = new ImmutableSet.Builder<>();
try (ScanResult scanResult =
new ClassGraph().enableAnnotationInfo().whitelistPackages("google.registry.ui").scan()) {
@@ -44,9 +40,8 @@ final class ActionMembershipTest {
.forEach(
classInfo -> {
if (!classInfo.extendsSuperclass(ConsoleApiAction.class.getName())
&& !classInfo.extendsSuperclass(HtmlAction.class.getName())
&& !classInfo.implementsInterface(JsonActionRunner.JsonAction.class.getName())
&& !classInfo.implementsInterface(JsonGetAction.class.getName())) {
&& !classInfo.implementsInterface(
JsonActionRunner.JsonAction.class.getName())) {
failingClasses.add(classInfo.getName());
}
});

View File

@@ -16,15 +16,17 @@ package google.registry.ui.server.console.settings;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.registrar.RegistrarPocBase.Type.ABUSE;
import static google.registry.model.registrar.RegistrarPocBase.Type.ADMIN;
import static google.registry.model.registrar.RegistrarPocBase.Type.TECH;
import static google.registry.testing.DatabaseHelper.createAdminUser;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.loadAllOf;
import static google.registry.testing.SqlHelper.saveRegistrar;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -46,13 +48,10 @@ import google.registry.request.auth.AuthResult;
import google.registry.testing.ConsoleApiParamsUtils;
import google.registry.testing.FakeResponse;
import google.registry.ui.server.console.ConsoleApiParams;
import google.registry.ui.server.console.ConsoleModule;
import google.registry.util.EmailMessage;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
@@ -69,17 +68,11 @@ class ContactActionTest {
+ "\"types\":[\"ADMIN\"],\"visibleInWhoisAsAdmin\":true,"
+ "\"visibleInWhoisAsTech\":false,\"visibleInDomainWhoisAsAbuse\":false}";
private static String jsonRegistrar2 =
"{\"name\":\"Test Registrar 2\","
+ "\"emailAddress\":\"test.registrar2@example.com\","
+ "\"registrarId\":\"registrarId\","
+ "\"phoneNumber\":\"+1.1234567890\",\"faxNumber\":\"+1.1234567891\","
+ "\"types\":[\"ADMIN\"],\"visibleInWhoisAsAdmin\":true,"
+ "\"visibleInWhoisAsTech\":false,\"visibleInDomainWhoisAsAbuse\":false}";
private Registrar testRegistrar;
private ConsoleApiParams consoleApiParams;
private RegistrarPoc testRegistrarPoc;
private RegistrarPoc testRegistrarPoc1;
private RegistrarPoc testRegistrarPoc2;
private static final Gson GSON = RequestModule.provideGson();
@RegisterExtension
@@ -89,7 +82,7 @@ class ContactActionTest {
@BeforeEach
void beforeEach() {
testRegistrar = saveRegistrar("registrarId");
testRegistrarPoc =
testRegistrarPoc1 =
new RegistrarPoc.Builder()
.setRegistrar(testRegistrar)
.setName("Test Registrar 1")
@@ -101,17 +94,24 @@ class ContactActionTest {
.setVisibleInWhoisAsTech(false)
.setVisibleInDomainWhoisAsAbuse(false)
.build();
testRegistrarPoc2 =
testRegistrarPoc1
.asBuilder()
.setName("Test Registrar 2")
.setEmailAddress("test.registrar2@example.com")
.setPhoneNumber("+1.1234567890")
.setFaxNumber("+1.1234567891")
.build();
}
@Test
void testSuccess_getContactInfo() throws IOException {
insertInDb(testRegistrarPoc);
insertInDb(testRegistrarPoc1);
ContactAction action =
createAction(
Action.Method.GET,
AuthResult.createUser(createAdminUser("email@email.com")),
testRegistrar.getRegistrarId(),
null);
testRegistrar.getRegistrarId());
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK);
assertThat(((FakeResponse) consoleApiParams.response()).getPayload())
@@ -120,13 +120,13 @@ class ContactActionTest {
@Test
void testSuccess_noOp() throws IOException {
insertInDb(testRegistrarPoc);
insertInDb(testRegistrarPoc1);
ContactAction action =
createAction(
Action.Method.POST,
AuthResult.createUser(createAdminUser("email@email.com")),
testRegistrar.getRegistrarId(),
"[" + jsonRegistrar1 + "]");
testRegistrarPoc1);
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK);
verify(consoleApiParams.sendEmailUtils().gmailClient, never()).sendEmail(any());
@@ -134,14 +134,13 @@ class ContactActionTest {
@Test
void testSuccess_onlyContactsWithNonEmptyType() throws IOException {
testRegistrarPoc = testRegistrarPoc.asBuilder().setTypes(ImmutableSet.of()).build();
insertInDb(testRegistrarPoc);
testRegistrarPoc1 = testRegistrarPoc1.asBuilder().setTypes(ImmutableSet.of()).build();
insertInDb(testRegistrarPoc1);
ContactAction action =
createAction(
Action.Method.GET,
AuthResult.createUser(createAdminUser("email@email.com")),
testRegistrar.getRegistrarId(),
null);
testRegistrar.getRegistrarId());
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK);
assertThat(((FakeResponse) consoleApiParams.response()).getPayload()).isEqualTo("[]");
@@ -149,13 +148,14 @@ class ContactActionTest {
@Test
void testSuccess_postCreateContactInfo() throws IOException {
insertInDb(testRegistrarPoc);
insertInDb(testRegistrarPoc1);
ContactAction action =
createAction(
Action.Method.POST,
AuthResult.createUser(createAdminUser("email@email.com")),
testRegistrar.getRegistrarId(),
"[" + jsonRegistrar1 + "," + jsonRegistrar2 + "]");
testRegistrarPoc1,
testRegistrarPoc2);
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK);
assertThat(
@@ -168,14 +168,14 @@ class ContactActionTest {
@Test
void testSuccess_postUpdateContactInfo() throws IOException {
testRegistrarPoc = testRegistrarPoc.asBuilder().setEmailAddress("incorrect@email.com").build();
insertInDb(testRegistrarPoc);
insertInDb(testRegistrarPoc1.asBuilder().setEmailAddress("incorrect@email.com").build());
ContactAction action =
createAction(
Action.Method.POST,
AuthResult.createUser(createAdminUser("email@email.com")),
testRegistrar.getRegistrarId(),
"[" + jsonRegistrar1 + "," + jsonRegistrar2 + "]");
testRegistrarPoc1,
testRegistrarPoc2);
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK);
HashMap<String, String> testResult = new HashMap<>();
@@ -191,15 +191,205 @@ class ContactActionTest {
}
@Test
void testSuccess_sendsEmail() throws IOException, AddressException {
testRegistrarPoc = testRegistrarPoc.asBuilder().setEmailAddress("incorrect@email.com").build();
insertInDb(testRegistrarPoc);
void testFailure_postUpdateContactInfo_duplicateEmails() throws IOException {
ContactAction action =
createAction(
Action.Method.POST,
AuthResult.createUser(createAdminUser("email@email.com")),
testRegistrar.getRegistrarId(),
"[" + jsonRegistrar1 + "]");
testRegistrarPoc1,
testRegistrarPoc2.asBuilder().setEmailAddress("test.registrar1@example.com").build());
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST);
assertThat(((FakeResponse) consoleApiParams.response()).getPayload())
.isEqualTo(
"One email address (test.registrar1@example.com) cannot be used for multiple contacts");
assertThat(
loadAllOf(RegistrarPoc.class).stream()
.filter(r -> r.registrarId.equals(testRegistrar.getRegistrarId()))
.collect(toImmutableList()))
.isEmpty();
}
@Test
void testFailure_postUpdateContactInfo_requiredContactRemoved() throws IOException {
insertInDb(testRegistrarPoc1);
ContactAction action =
createAction(
Action.Method.POST,
AuthResult.createUser(createAdminUser("email@email.com")),
testRegistrar.getRegistrarId(),
testRegistrarPoc1.asBuilder().setTypes(ImmutableSet.of(ABUSE)).build());
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST);
assertThat(((FakeResponse) consoleApiParams.response()).getPayload())
.isEqualTo("Must have at least one primary contact");
assertThat(
loadAllOf(RegistrarPoc.class).stream()
.filter(r -> r.registrarId.equals(testRegistrar.getRegistrarId()))
.collect(toImmutableList()))
.containsExactly(testRegistrarPoc1);
}
@Test
void testFailure_postUpdateContactInfo_phoneNumberRemoved() throws IOException {
testRegistrarPoc1 =
testRegistrarPoc1.asBuilder().setTypes(ImmutableSet.of(ADMIN, TECH)).build();
insertInDb(testRegistrarPoc1);
ContactAction action =
createAction(
Action.Method.POST,
AuthResult.createUser(createAdminUser("email@email.com")),
testRegistrar.getRegistrarId(),
testRegistrarPoc1
.asBuilder()
.setPhoneNumber(null)
.setTypes(ImmutableSet.of(ADMIN, TECH))
.build());
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST);
assertThat(((FakeResponse) consoleApiParams.response()).getPayload())
.isEqualTo("Please provide a phone number for at least one technical contact");
assertThat(
loadAllOf(RegistrarPoc.class).stream()
.filter(r -> r.registrarId.equals(testRegistrar.getRegistrarId()))
.collect(toImmutableList()))
.containsExactly(testRegistrarPoc1);
}
@Test
void testFailure_postUpdateContactInfo_whoisContactMissingPhoneNumber() throws IOException {
ContactAction action =
createAction(
Action.Method.POST,
AuthResult.createUser(createAdminUser("email@email.com")),
testRegistrar.getRegistrarId(),
testRegistrarPoc1
.asBuilder()
.setPhoneNumber(null)
.setVisibleInDomainWhoisAsAbuse(true)
.build());
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST);
assertThat(((FakeResponse) consoleApiParams.response()).getPayload())
.isEqualTo("The abuse contact visible in domain WHOIS query must have a phone number");
assertThat(
loadAllOf(RegistrarPoc.class).stream()
.filter(r -> r.registrarId.equals(testRegistrar.getRegistrarId()))
.collect(toImmutableList()))
.isEmpty();
}
@Test
void testFailure_postUpdateContactInfo_whoisContactPhoneNumberRemoved() throws IOException {
testRegistrarPoc1 = testRegistrarPoc1.asBuilder().setVisibleInDomainWhoisAsAbuse(true).build();
insertInDb(testRegistrarPoc1);
ContactAction action =
createAction(
Action.Method.POST,
AuthResult.createUser(createAdminUser("email@email.com")),
testRegistrar.getRegistrarId(),
testRegistrarPoc1.asBuilder().setVisibleInDomainWhoisAsAbuse(false).build());
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST);
assertThat(((FakeResponse) consoleApiParams.response()).getPayload())
.isEqualTo("An abuse contact visible in domain WHOIS query must be designated");
assertThat(
loadAllOf(RegistrarPoc.class).stream()
.filter(r -> r.registrarId.equals(testRegistrar.getRegistrarId()))
.collect(toImmutableList()))
.containsExactly(testRegistrarPoc1);
}
@Test
void testFailure_postUpdateContactInfo_newContactCannotSetRegistryLockPassword()
throws IOException {
ContactAction action =
createAction(
Action.Method.POST,
AuthResult.createUser(createAdminUser("email@email.com")),
testRegistrar.getRegistrarId(),
testRegistrarPoc1
.asBuilder()
.setAllowedToSetRegistryLockPassword(true)
.setRegistryLockEmailAddress("lock@example.com")
.build());
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST);
assertThat(((FakeResponse) consoleApiParams.response()).getPayload())
.isEqualTo("Cannot set registry lock password directly on new contact");
assertThat(
loadAllOf(RegistrarPoc.class).stream()
.filter(r -> r.registrarId.equals(testRegistrar.getRegistrarId()))
.collect(toImmutableList()))
.isEmpty();
}
@Test
void testFailure_postUpdateContactInfo_cannotModifyRegistryLockEmail() throws IOException {
testRegistrarPoc1 =
testRegistrarPoc1
.asBuilder()
.setRegistryLockEmailAddress("lock@example.com")
.setAllowedToSetRegistryLockPassword(true)
.build();
insertInDb(testRegistrarPoc1);
ContactAction action =
createAction(
Action.Method.POST,
AuthResult.createUser(createAdminUser("email@email.com")),
testRegistrar.getRegistrarId(),
testRegistrarPoc1
.asBuilder()
.setRegistryLockEmailAddress("unlock@example.com")
.build());
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST);
assertThat(((FakeResponse) consoleApiParams.response()).getPayload())
.isEqualTo("Cannot modify registryLockEmailAddress through the UI");
assertThat(
loadAllOf(RegistrarPoc.class).stream()
.filter(r -> r.registrarId.equals(testRegistrar.getRegistrarId()))
.collect(toImmutableList()))
.containsExactly(testRegistrarPoc1);
}
@Test
void testFailure_postUpdateContactInfo_cannotSetIsAllowedToSetRegistryLockPassword()
throws IOException {
testRegistrarPoc1 =
testRegistrarPoc1
.asBuilder()
.setRegistryLockEmailAddress("lock@example.com")
.setAllowedToSetRegistryLockPassword(false)
.build();
insertInDb(testRegistrarPoc1);
ContactAction action =
createAction(
Action.Method.POST,
AuthResult.createUser(createAdminUser("email@email.com")),
testRegistrar.getRegistrarId(),
testRegistrarPoc1.asBuilder().setAllowedToSetRegistryLockPassword(true).build());
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST);
assertThat(((FakeResponse) consoleApiParams.response()).getPayload())
.isEqualTo("Cannot modify isAllowedToSetRegistryLockPassword through the UI");
assertThat(
loadAllOf(RegistrarPoc.class).stream()
.filter(r -> r.registrarId.equals(testRegistrar.getRegistrarId()))
.collect(toImmutableList()))
.containsExactly(testRegistrarPoc1);
}
@Test
void testSuccess_sendsEmail() throws IOException, AddressException {
insertInDb(testRegistrarPoc1.asBuilder().setEmailAddress("incorrect@email.com").build());
ContactAction action =
createAction(
Action.Method.POST,
AuthResult.createUser(createAdminUser("email@email.com")),
testRegistrar.getRegistrarId(),
testRegistrarPoc1);
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK);
verify(consoleApiParams.sendEmailUtils().gmailClient, times(1))
@@ -245,13 +435,13 @@ class ContactActionTest {
@Test
void testSuccess_postDeleteContactInfo() throws IOException {
insertInDb(testRegistrarPoc);
insertInDb(testRegistrarPoc1);
ContactAction action =
createAction(
Action.Method.POST,
AuthResult.createUser(createAdminUser("email@email.com")),
testRegistrar.getRegistrarId(),
"[" + jsonRegistrar2 + "]");
testRegistrarPoc2);
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK);
assertThat(
@@ -264,7 +454,7 @@ class ContactActionTest {
@Test
void testFailure_postDeleteContactInfo_missingPermission() throws IOException {
insertInDb(testRegistrarPoc);
insertInDb(testRegistrarPoc1);
ContactAction action =
createAction(
Action.Method.POST,
@@ -279,26 +469,21 @@ class ContactActionTest {
.build())
.build()),
testRegistrar.getRegistrarId(),
"[" + jsonRegistrar2 + "]");
testRegistrarPoc2);
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_FORBIDDEN);
}
private ContactAction createAction(
Action.Method method, AuthResult authResult, String registrarId, String contacts)
Action.Method method, AuthResult authResult, String registrarId, RegistrarPoc... contacts)
throws IOException {
consoleApiParams = ConsoleApiParamsUtils.createFake(authResult);
when(consoleApiParams.request().getMethod()).thenReturn(method.toString());
if (method.equals(Action.Method.GET)) {
return new ContactAction(consoleApiParams, GSON, registrarId, Optional.empty());
} else {
doReturn(new BufferedReader(new StringReader(contacts)))
.when(consoleApiParams.request())
.getReader();
Optional<ImmutableSet<RegistrarPoc>> maybeContacts =
ConsoleModule.provideContacts(
GSON, RequestModule.provideJsonBody(consoleApiParams.request(), GSON));
return new ContactAction(consoleApiParams, GSON, registrarId, maybeContacts);
return new ContactAction(
consoleApiParams, GSON, registrarId, Optional.of(ImmutableSet.copyOf(contacts)));
}
}
}

View File

@@ -1,212 +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.ui.server.registrar;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.OteAccountBuilderTest.verifyIapPermission;
import static google.registry.model.OteAccountBuilderTest.verifyUser;
import static google.registry.model.registrar.Registrar.loadByRegistrarId;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import static org.joda.money.CurrencyUnit.USD;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSetMultimap;
import google.registry.groups.GmailClient;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.model.tld.Tld;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.Action.Method;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.security.XsrfTokenManager;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.testing.SystemPropertyExtension;
import google.registry.tools.IamClient;
import google.registry.ui.server.SendEmailUtils;
import google.registry.util.EmailMessage;
import google.registry.util.RegistryEnvironment;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
/** Unit tests for {@link ConsoleOteSetupAction}. */
@ExtendWith(MockitoExtension.class)
public final class ConsoleOteSetupActionTest {
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
@RegisterExtension
@Order(value = Integer.MAX_VALUE)
final SystemPropertyExtension systemPropertyExtension = new SystemPropertyExtension();
private final IamClient iamClient = mock(IamClient.class);
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
private final FakeResponse response = new FakeResponse();
private final ConsoleOteSetupAction action = new ConsoleOteSetupAction();
private final User user =
new User.Builder()
.setEmailAddress("marla.singer@example.com")
.setUserRoles(new UserRoles())
.build();
@Mock HttpServletRequest request;
@Mock GmailClient gmailClient;
@BeforeEach
void beforeEach() throws Exception {
persistPremiumList("default_sandbox_list", USD, "sandbox,USD 1000");
action.req = request;
action.method = Method.GET;
action.response = response;
action.registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(
ImmutableSetMultimap.of("unused", AuthenticatedRegistrarAccessor.Role.ADMIN));
action.xsrfTokenManager = new XsrfTokenManager(new FakeClock());
action.authResult = AuthResult.createUser(user);
action.sendEmailUtils =
new SendEmailUtils(
ImmutableList.of("notification@test.example", "notification2@test.example"),
gmailClient);
action.logoFilename = "logo.png";
action.productName = "Nomulus";
action.clientId = Optional.empty();
action.email = Optional.empty();
action.analyticsConfig = ImmutableMap.of("googleAnalyticsId", "sampleId");
action.optionalPassword = Optional.empty();
action.passwordGenerator = new DeterministicStringGenerator("abcdefghijklmnopqrstuvwxyz");
action.maybeGroupEmailAddress = Optional.of("group@example.com");
action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
action.iamClient = iamClient;
}
@Test
void testNoUser_redirect() {
action.authResult = AuthResult.NOT_AUTHENTICATED;
action.run();
assertThat(response.getStatus()).isEqualTo(SC_UNAUTHORIZED);
}
@Test
void testGet_authorized() {
action.run();
assertThat(response.getPayload()).contains("<h1>Setup OT&E</h1>");
assertThat(response.getPayload()).contains("gtag('config', 'sampleId')");
}
@Test
void testGet_authorized_onProduction() {
RegistryEnvironment.PRODUCTION.setup(systemPropertyExtension);
assertThrows(IllegalStateException.class, action::run);
}
@Test
void testGet_unauthorized() {
action.registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of());
action.run();
assertThat(response.getPayload()).contains("<h1>You need permission</h1>");
assertThat(response.getPayload()).contains("gtag('config', 'sampleId')");
}
@Test
void testPost_authorized() {
action.clientId = Optional.of("myclientid");
action.email = Optional.of("contact@registry.example");
action.method = Method.POST;
action.run();
// We just check some samples to make sure OteAccountBuilder was called successfully. We aren't
// checking that all the entities are there or that they have the correct values.
assertThat(loadByRegistrarId("myclientid-3")).isPresent();
assertThat(Tld.get("myclientid-ga")).isNotNull();
verifyUser("myclientid-5", "contact@registry.example");
assertThat(response.getPayload())
.contains("<h1>OT&E successfully created for registrar myclientid!</h1>");
ArgumentCaptor<EmailMessage> contentCaptor = ArgumentCaptor.forClass(EmailMessage.class);
verify(gmailClient).sendEmail(contentCaptor.capture());
EmailMessage emailMessage = contentCaptor.getValue();
assertThat(emailMessage.subject())
.isEqualTo("OT&E for registrar myclientid created in unittest");
assertThat(emailMessage.body())
.isEqualTo(
"""
The following entities were created in unittest by TestUserId:
Registrar myclientid-1 with access to TLD myclientid-sunrise
Registrar myclientid-3 with access to TLD myclientid-ga
Registrar myclientid-4 with access to TLD myclientid-ga
Registrar myclientid-5 with access to TLD myclientid-eap
Gave user contact@registry.example web access to these Registrars
""");
assertThat(response.getPayload()).contains("gtag('config', 'sampleId')");
verifyIapPermission(
"contact@registry.example", action.maybeGroupEmailAddress, cloudTasksHelper, iamClient);
}
@Test
void testPost_authorized_noConsoleGroup() {
action.maybeGroupEmailAddress = Optional.empty();
testPost_authorized();
}
@Test
void testPost_authorized_setPassword() {
action.clientId = Optional.of("myclientid");
action.email = Optional.of("contact@registry.example");
action.optionalPassword = Optional.of("SomePassword");
action.method = Method.POST;
action.run();
// We just check some samples to make sure OteAccountBuilder was called successfully. We aren't
// checking that all the entities are there or that they have the correct values.
assertThat(loadByRegistrarId("myclientid-4").get().verifyPassword("SomePassword")).isTrue();
assertThat(response.getPayload())
.contains("<h1>OT&E successfully created for registrar myclientid!</h1>");
assertThat(response.getPayload())
.contains("SomePassword");
}
@Test
void testPost_unauthorized() {
action.registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of());
action.clientId = Optional.of("myclientid");
action.email = Optional.of("contact@registry.example");
action.method = Method.POST;
action.run();
assertThat(response.getPayload()).contains("<h1>You need permission</h1>");
assertThat(response.getPayload()).contains("gtag('config', 'sampleId')");
}
}

View File

@@ -1,465 +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.ui.server.registrar;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.OteAccountBuilderTest.verifyIapPermission;
import static google.registry.model.OteAccountBuilderTest.verifyUser;
import static google.registry.model.registrar.Registrar.loadByRegistrarId;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import static org.joda.money.CurrencyUnit.USD;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSetMultimap;
import google.registry.groups.GmailClient;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.Action.Method;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.security.XsrfTokenManager;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.testing.SystemPropertyExtension;
import google.registry.tools.IamClient;
import google.registry.ui.server.SendEmailUtils;
import google.registry.util.EmailMessage;
import google.registry.util.RegistryEnvironment;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Optional;
import org.joda.money.CurrencyUnit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
final class ConsoleRegistrarCreatorActionTest {
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
@RegisterExtension
@Order(Integer.MAX_VALUE)
final SystemPropertyExtension systemPropertyExtension = new SystemPropertyExtension();
private final FakeResponse response = new FakeResponse();
private final ConsoleRegistrarCreatorAction action = new ConsoleRegistrarCreatorAction();
private final User user =
new User.Builder()
.setEmailAddress("marla.singer@example.com")
.setUserRoles(new UserRoles())
.build();
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
private final IamClient iamClient = mock(IamClient.class);
@Mock HttpServletRequest request;
@Mock GmailClient gmailClient;
@BeforeEach
void beforeEach() throws Exception {
persistPremiumList("default_sandbox_list", USD, "sandbox,USD 1000");
action.req = request;
action.method = Method.GET;
action.response = response;
action.registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(
ImmutableSetMultimap.of("unused", AuthenticatedRegistrarAccessor.Role.ADMIN));
action.xsrfTokenManager = new XsrfTokenManager(new FakeClock());
action.authResult = AuthResult.createUser(user);
action.sendEmailUtils =
new SendEmailUtils(
ImmutableList.of("notification@test.example", "notification2@test.example"),
gmailClient);
action.logoFilename = "logo.png";
action.productName = "Nomulus";
action.clientId = Optional.empty();
action.name = Optional.empty();
action.billingAccount = Optional.empty();
action.ianaId = Optional.empty();
action.referralEmail = Optional.empty();
action.driveId = Optional.empty();
action.consoleUserEmail = Optional.empty();
action.street1 = Optional.empty();
action.optionalStreet2 = Optional.empty();
action.optionalStreet3 = Optional.empty();
action.city = Optional.empty();
action.optionalState = Optional.empty();
action.optionalZip = Optional.empty();
action.countryCode = Optional.empty();
action.optionalPassword = Optional.empty();
action.optionalPasscode = Optional.empty();
action.passwordGenerator = new DeterministicStringGenerator("abcdefghijklmnopqrstuvwxyz");
action.passcodeGenerator = new DeterministicStringGenerator("314159265");
action.analyticsConfig = ImmutableMap.of("googleAnalyticsId", "sampleId");
action.maybeGroupEmailAddress = Optional.of("group@example.com");
action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
action.iamClient = iamClient;
}
@Test
void testNoUser_unauthroized() {
action.authResult = AuthResult.NOT_AUTHENTICATED;
action.run();
assertThat(response.getStatus()).isEqualTo(SC_UNAUTHORIZED);
}
@Test
void testGet_authorized() {
action.run();
assertThat(response.getPayload()).contains("<h1>Create Registrar</h1>");
assertThat(response.getPayload()).contains("gtag('config', 'sampleId')");
}
@Test
void testGet_authorized_onProduction() {
RegistryEnvironment.PRODUCTION.setup(systemPropertyExtension);
action.run();
assertThat(response.getPayload()).contains("<h1>Create Registrar</h1>");
assertThat(response.getPayload()).contains("gtag('config', 'sampleId')");
}
@Test
void testGet_unauthorized() {
action.registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of());
action.run();
assertThat(response.getPayload()).contains("<h1>You need permission</h1>");
assertThat(response.getPayload()).contains("gtag('config', 'sampleId')");
}
void runTestPost_authorized_minimalAddress() {
action.clientId = Optional.of("myclientid");
action.name = Optional.of("registrar name");
action.billingAccount = Optional.of("USD=billing-account");
action.ianaId = Optional.of(12321);
action.referralEmail = Optional.of("icann@example.com");
action.driveId = Optional.of("drive-id");
action.consoleUserEmail = Optional.of("myclientid@registry.example");
action.street1 = Optional.of("my street");
action.city = Optional.of("my city");
action.countryCode = Optional.of("CC");
action.method = Method.POST;
action.run();
assertThat(response.getPayload())
.contains("<h1>Successfully created Registrar myclientid</h1>");
ArgumentCaptor<EmailMessage> contentCaptor = ArgumentCaptor.forClass(EmailMessage.class);
verify(gmailClient).sendEmail(contentCaptor.capture());
EmailMessage emailMessage = contentCaptor.getValue();
assertThat(emailMessage.subject()).isEqualTo("Registrar myclientid created in unittest");
assertThat(emailMessage.body())
.isEqualTo(
"""
The following registrar was created in unittest by TestUserId:
clientId: myclientid
name: registrar name
billingAccount: USD=billing-account
ianaId: 12321
referralEmail: icann@example.com
driveId: drive-id
Gave user myclientid@registry.example web access to the registrar
""");
Registrar registrar = loadByRegistrarId("myclientid").orElse(null);
assertThat(registrar).isNotNull();
assertThat(registrar.getRegistrarId()).isEqualTo("myclientid");
assertThat(registrar.getBillingAccountMap()).containsExactly(USD, "billing-account");
assertThat(registrar.getDriveFolderId()).isEqualTo("drive-id");
assertThat(registrar.getIanaIdentifier()).isEqualTo(12321L);
assertThat(registrar.getIcannReferralEmail()).isEqualTo("icann@example.com");
assertThat(registrar.getEmailAddress()).isEqualTo("icann@example.com");
assertThat(registrar.verifyPassword("abcdefghijklmnop")).isTrue();
assertThat(registrar.getPhonePasscode()).isEqualTo("31415");
assertThat(registrar.getState()).isEqualTo(Registrar.State.PENDING);
assertThat(registrar.getType()).isEqualTo(Registrar.Type.REAL);
assertThat(registrar.getLocalizedAddress()).isEqualTo(new RegistrarAddress.Builder()
.setStreet(ImmutableList.of("my street"))
.setCity("my city")
.setCountryCode("CC")
.build());
verifyUser("myclientid", "myclientid@registry.example");
}
@Test
void testPost_authorized_minimalAddress_consoleUserGroup() {
runTestPost_authorized_minimalAddress();
verifyIapPermission(
"myclientid@registry.example", action.maybeGroupEmailAddress, cloudTasksHelper, iamClient);
}
@Test
void testPost_authorized_minimalAddress_NoConsoleUserGroup() {
action.maybeGroupEmailAddress = Optional.empty();
runTestPost_authorized_minimalAddress();
verifyIapPermission(
"myclientid@registry.example", action.maybeGroupEmailAddress, cloudTasksHelper, iamClient);
}
@Test
void testPost_authorized_allAddress() {
action.clientId = Optional.of("myclientid");
action.name = Optional.of("registrar name");
action.billingAccount = Optional.of("USD=billing-account");
action.ianaId = Optional.of(12321);
action.referralEmail = Optional.of("icann@example.com");
action.driveId = Optional.of("drive-id");
action.consoleUserEmail = Optional.of("myclientid@registry.example");
action.street1 = Optional.of("my street");
action.optionalStreet2 = Optional.of("more street");
action.optionalStreet3 = Optional.of("final street");
action.city = Optional.of("my city");
action.optionalState = Optional.of("province");
action.optionalZip = Optional.of("12345-678");
action.countryCode = Optional.of("CC");
action.method = Method.POST;
action.run();
assertThat(response.getPayload())
.contains("<h1>Successfully created Registrar myclientid</h1>");
assertThat(response.getPayload()).contains("gtag('config', 'sampleId')");
Registrar registrar = loadByRegistrarId("myclientid").orElse(null);
assertThat(registrar).isNotNull();
assertThat(registrar.getLocalizedAddress()).isEqualTo(new RegistrarAddress.Builder()
.setStreet(ImmutableList.of("my street", "more street", "final street"))
.setCity("my city")
.setState("province")
.setZip("12345-678")
.setCountryCode("CC")
.build());
}
@Test
void testPost_authorized_multipleBillingLines() {
action.clientId = Optional.of("myclientid");
action.name = Optional.of("registrar name");
action.ianaId = Optional.of(12321);
action.referralEmail = Optional.of("icann@example.com");
action.driveId = Optional.of("drive-id");
action.consoleUserEmail = Optional.of("myclientid@registry.example");
action.street1 = Optional.of("my street");
action.city = Optional.of("my city");
action.countryCode = Optional.of("CC");
action.method = Method.POST;
action.billingAccount =
Optional.of(
"""
JPY=billing-account-yen
Usd = billing-account-usd \r
\s
eur=billing-account-eur
""");
action.run();
assertThat(response.getPayload())
.contains("<h1>Successfully created Registrar myclientid</h1>");
Registrar registrar = loadByRegistrarId("myclientid").orElse(null);
assertThat(registrar).isNotNull();
assertThat(registrar.getBillingAccountMap())
.containsExactly(
CurrencyUnit.JPY,
"billing-account-yen",
USD,
"billing-account-usd",
CurrencyUnit.EUR,
"billing-account-eur");
}
@Test
void testPost_authorized_repeatingCurrency_fails() {
action.clientId = Optional.of("myclientid");
action.name = Optional.of("registrar name");
action.ianaId = Optional.of(12321);
action.referralEmail = Optional.of("icann@example.com");
action.driveId = Optional.of("drive-id");
action.consoleUserEmail = Optional.of("myclientid@registry.example");
action.street1 = Optional.of("my street");
action.city = Optional.of("my city");
action.countryCode = Optional.of("CC");
action.method = Method.POST;
action.billingAccount =
Optional.of(
"""
JPY=billing-account-1
jpy=billing-account-2
""");
action.run();
assertThat(response.getPayload())
.contains(
"Failed: Error parsing billing accounts - Multiple entries with same key:"
+ " JPY=billing-account-2 and JPY=billing-account-1");
}
@Test
void testPost_authorized_badCurrency_fails() {
action.clientId = Optional.of("myclientid");
action.name = Optional.of("registrar name");
action.ianaId = Optional.of(12321);
action.referralEmail = Optional.of("icann@example.com");
action.driveId = Optional.of("drive-id");
action.consoleUserEmail = Optional.of("myclientid@registry.example");
action.street1 = Optional.of("my street");
action.city = Optional.of("my city");
action.countryCode = Optional.of("CC");
action.method = Method.POST;
action.billingAccount =
Optional.of(
"""
JPY=billing-account-1
xyz=billing-account-2
usd=billing-account-3
""");
action.run();
assertThat(response.getPayload())
.contains("Failed: Error parsing billing accounts - Unknown currency &#39;XYZ&#39;");
}
@Test
void testPost_authorized_badBillingLine_fails() {
action.clientId = Optional.of("myclientid");
action.name = Optional.of("registrar name");
action.ianaId = Optional.of(12321);
action.referralEmail = Optional.of("icann@example.com");
action.driveId = Optional.of("drive-id");
action.consoleUserEmail = Optional.of("myclientid@registry.example");
action.street1 = Optional.of("my street");
action.city = Optional.of("my city");
action.countryCode = Optional.of("CC");
action.method = Method.POST;
action.billingAccount =
Optional.of(
"""
JPY=billing-account-1
some bad line
usd=billing-account-2
""");
action.run();
assertThat(response.getPayload())
.contains(
"Failed: Error parsing billing accounts - Can&#39;t parse line [some bad line]."
+ " The format should be [currency]=[account ID]");
}
@Test
void testPost_authorized_setPassword() {
action.clientId = Optional.of("myclientid");
action.name = Optional.of("registrar name");
action.billingAccount = Optional.of("USD=billing-account");
action.ianaId = Optional.of(12321);
action.referralEmail = Optional.of("icann@example.com");
action.driveId = Optional.of("drive-id");
action.consoleUserEmail = Optional.of("myclientid@registry.example");
action.street1 = Optional.of("my street");
action.city = Optional.of("my city");
action.countryCode = Optional.of("CC");
action.optionalPassword = Optional.of("SomePassword");
action.optionalPasscode = Optional.of("10203");
action.method = Method.POST;
action.run();
assertThat(response.getPayload())
.contains("<h1>Successfully created Registrar myclientid</h1>");
Registrar registrar = loadByRegistrarId("myclientid").orElse(null);
assertThat(registrar).isNotNull();
assertThat(registrar.verifyPassword("SomePassword")).isTrue();
assertThat(registrar.getPhonePasscode()).isEqualTo("10203");
}
@Test
void testPost_badEmailFails() {
action.clientId = Optional.of("myclientid");
action.name = Optional.of("registrar name");
action.billingAccount = Optional.of("USD=billing-account");
action.ianaId = Optional.of(12321);
action.referralEmail = Optional.of("lolcat");
action.driveId = Optional.of("drive-id");
action.consoleUserEmail = Optional.of("myclientid@registry.example");
action.street1 = Optional.of("my street");
action.city = Optional.of("my city");
action.countryCode = Optional.of("CC");
action.method = Method.POST;
action.run();
assertThat(response.getPayload())
.contains("Failed: Provided email lolcat is not a valid email address");
}
@Test
void testPost_unauthorized() {
action.registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of());
action.clientId = Optional.of("myclientid");
action.consoleUserEmail = Optional.of("myclientid@registry.example");
action.method = Method.POST;
action.run();
assertThat(response.getPayload()).contains("<h1>You need permission</h1>");
assertThat(response.getPayload()).contains("gtag('config', 'sampleId')");
}
}

View File

@@ -1,203 +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.truth.Truth.assertThat;
import static com.google.monitoring.metrics.contrib.LongMetricSubject.assertThat;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.ADMIN;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.OWNER;
import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.net.MediaType;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.Action.Method;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.security.XsrfTokenManager;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Optional;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link ConsoleUiAction}. */
class ConsoleUiActionTest {
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
private final HttpServletRequest request = mock(HttpServletRequest.class);
private final FakeResponse response = new FakeResponse();
private final ConsoleUiAction action = new ConsoleUiAction();
private final User user =
new User.Builder()
.setEmailAddress("marla.singer@example.com")
.setUserRoles(new UserRoles())
.build();
@BeforeEach
void beforeEach() {
action.enabled = true;
action.logoFilename = "logo.png";
action.productName = "Nomulus";
action.integrationEmail = "integration@example.com";
action.supportEmail = "support@example.com";
action.announcementsEmail = "announcements@example.com";
action.supportPhoneNumber = "1 (888) 555 0123";
action.technicalDocsUrl = "http://example.com/technical-docs";
action.req = request;
action.response = response;
action.registrarConsoleMetrics = new RegistrarConsoleMetrics();
action.xsrfTokenManager = new XsrfTokenManager(new FakeClock());
action.method = Method.GET;
action.paramClientId = Optional.empty();
action.authResult = AuthResult.createUser(user);
action.analyticsConfig = ImmutableMap.of("googleAnalyticsId", "sampleId");
action.registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(
ImmutableSetMultimap.of(
"TheRegistrar", OWNER,
"NewRegistrar", OWNER,
"NewRegistrar", ADMIN,
"AdminRegistrar", ADMIN));
RegistrarConsoleMetrics.consoleRequestMetric.reset();
when(request.getParameter("noredirect")).thenReturn("true");
}
@AfterEach
void afterEach() {
assertThat(RegistrarConsoleMetrics.consoleRequestMetric).hasNoOtherValues();
}
void assertMetric(String registrarId, String explicitClientId, String roles, String status) {
assertThat(RegistrarConsoleMetrics.consoleRequestMetric)
.hasValueForLabels(1, registrarId, explicitClientId, roles, status);
RegistrarConsoleMetrics.consoleRequestMetric.reset(
registrarId, explicitClientId, roles, status);
}
@Test
void testWebPage_redirect() {
when(request.getParameter("noredirect")).thenReturn(null);
action.run();
assertThat(response.getStatus()).isEqualTo(302);
assertThat(response.getPayload()).isEqualTo("Redirected to /console");
}
@Test
void testWebPage_disallowsIframe() {
action.run();
assertThat(response.getHeaders()).containsEntry("X-Frame-Options", "SAMEORIGIN");
assertMetric("TheRegistrar", "false", "[OWNER]", "SUCCESS");
}
@Test
void testWebPage_setsHtmlUtf8ContentType() {
action.run();
assertThat(response.getContentType()).isEqualTo(MediaType.HTML_UTF_8);
assertMetric("TheRegistrar", "false", "[OWNER]", "SUCCESS");
}
@Test
void testWebPage_containsUserNickname() {
action.run();
assertThat(response.getPayload()).contains("marla.singer");
assertMetric("TheRegistrar", "false", "[OWNER]", "SUCCESS");
}
@Test
void testWebPage_containsGoogleAnalyticsId() {
action.run();
assertThat(response.getPayload()).contains("gtag('config', 'sampleId')");
assertMetric("TheRegistrar", "false", "[OWNER]", "SUCCESS");
}
@Test
void testUserHasAccessAsTheRegistrar_showsRegistrarConsole() {
action.run();
assertThat(response.getPayload()).contains("Registrar Console");
assertThat(response.getPayload()).contains("reg-content-and-footer");
assertMetric("TheRegistrar", "false", "[OWNER]", "SUCCESS");
}
@Test
void testConsoleDisabled_showsDisabledPage() {
action.enabled = false;
action.run();
assertThat(response.getPayload()).contains("<h1>Console is disabled</h1>");
assertThat(response.getPayload()).contains("gtag('config', 'sampleId')");
}
@Test
void testUserDoesntHaveAccessToAnyRegistrar_showsWhoAreYouPage() {
action.registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of());
action.run();
assertThat(response.getPayload()).contains("<h1>You need permission</h1>");
assertThat(response.getPayload()).contains("not associated with Nomulus.");
assertThat(response.getPayload()).contains("gtag('config', 'sampleId')");
assertMetric("<null>", "false", "[]", "FORBIDDEN");
}
@Test
void testNoUser_not_logged_in() {
action.authResult = AuthResult.NOT_AUTHENTICATED;
action.run();
assertThat(response.getStatus()).isEqualTo(SC_UNAUTHORIZED);
}
@Test
void testSettingClientId_notAllowed_showsNeedPermissionPage() {
// Behaves the same way if fakeRegistrar exists, but we don't have access to it
action.paramClientId = Optional.of("fakeRegistrar");
action.run();
assertThat(response.getPayload()).contains("<h1>You need permission</h1>");
assertThat(response.getPayload()).contains("not associated with the registrar fakeRegistrar.");
assertThat(response.getPayload()).contains("gtag('config', 'sampleId')");
assertMetric("fakeRegistrar", "true", "[]", "FORBIDDEN");
}
@Test
void testSettingClientId_allowed_showsRegistrarConsole() {
action.paramClientId = Optional.of("NewRegistrar");
action.run();
assertThat(response.getPayload()).contains("Registrar Console");
assertThat(response.getPayload()).contains("reg-content-and-footer");
assertThat(response.getPayload()).contains("gtag('config', 'sampleId')");
assertMetric("NewRegistrar", "true", "[OWNER, ADMIN]", "SUCCESS");
}
@Test
void testUserHasAccessAsTheRegistrar_showsClientIdChooser() {
action.run();
assertThat(response.getPayload()).contains("<option value=\"TheRegistrar\" selected>");
assertThat(response.getPayload()).contains("<option value=\"NewRegistrar\">");
assertThat(response.getPayload()).contains("<option value=\"AdminRegistrar\">");
assertThat(response.getPayload()).contains("gtag('config', 'sampleId')");
assertMetric("TheRegistrar", "false", "[OWNER]", "SUCCESS");
}
}

View File

@@ -1,409 +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.truth.Truth.assertThat;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.persistSimpleResource;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.registrar.RegistrarPocBase.Type;
import google.registry.persistence.transaction.JpaTransactionManagerExtension;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
/**
* Unit tests for contact_settings.js use of {@link RegistrarSettingsAction}.
*
* <p>The default read and session validation tests are handled by the superclass.
*/
class ContactSettingsTest extends RegistrarSettingsActionTestCase {
@Test
void testPost_readContacts_success() {
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
"op", "read",
"id", CLIENT_ID,
"args", ImmutableMap.of()));
@SuppressWarnings("unchecked")
List<Map<String, ?>> results = (List<Map<String, ?>>) response.get("results");
assertThat(results.get(0).get("contacts"))
.isEqualTo(loadRegistrar(CLIENT_ID).toJsonMap().get("contacts"));
assertMetric(CLIENT_ID, "read", "[OWNER]", "SUCCESS");
}
@Test
void testPost_loadSaveRegistrar_success() {
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", loadRegistrar(CLIENT_ID).toJsonMap()));
assertThat(response).containsEntry("status", "SUCCESS");
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
}
@Test
void testPost_updateContacts_success() throws Exception {
// Remove all the contacts but one by updating with a list of just it
Map<String, Object> adminContact =
loadRegistrar(CLIENT_ID).getContacts().stream()
.filter(rc -> rc.getEmailAddress().equals("Marla.Singer@crr.com"))
.findFirst()
.get()
.toJsonMap();
// Keep an admin to avoid superfluous issues
adminContact.put("types", "ADMIN,TECH");
Registrar registrar = loadRegistrar(CLIENT_ID);
Map<String, Object> regMap = registrar.toJsonMap();
regMap.put("contacts", ImmutableList.of(adminContact));
Map<String, Object> response =
action.handleJsonRequest(ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", regMap));
assertThat(response).containsEntry("status", "SUCCESS");
RegistrarPoc foundContact = Iterables.getOnlyElement(loadRegistrar(CLIENT_ID).getContacts());
assertThat(foundContact.getName()).isEqualTo(adminContact.get("name"));
assertThat(foundContact.getEmailAddress()).isEqualTo(adminContact.get("emailAddress"));
assertThat(foundContact.getPhoneNumber()).isEqualTo(adminContact.get("phoneNumber"));
assertThat(foundContact.getTypes()).containsExactly(Type.ADMIN, Type.TECH);
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
verifyNotificationEmailsSent();
}
@Test
void testPost_updateContacts_requiredTypes_error() {
Map<String, Object> reqJson = loadRegistrar(CLIENT_ID).toJsonMap();
reqJson.put("contacts", ImmutableList.of(techContact.toJsonMap()));
Map<String, Object> response =
action.handleJsonRequest(
ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", reqJson));
assertThat(response).containsEntry("status", "ERROR");
assertThat(response).containsEntry("message", "Must have at least one primary contact");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: ContactRequirementException");
}
@Test
void testPost_updateContacts_requireTechPhone_error() {
Map<String, Object> reqJson = loadRegistrar(CLIENT_ID).toJsonMap();
reqJson.put(
"contacts",
ImmutableList.of(
JpaTransactionManagerExtension.makeRegistrarContact2().toJsonMap(),
techContact.asBuilder().setPhoneNumber(null).build().toJsonMap()));
Map<String, Object> response =
action.handleJsonRequest(
ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", reqJson));
assertThat(response).containsEntry("status", "ERROR");
assertThat(response)
.containsEntry(
"message", "Please provide a phone number for at least one technical contact");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: ContactRequirementException");
}
@Test
void testPost_updateContacts_cannotRemoveWhoisAbuseContact_error() {
// First make the contact's info visible in whois as abuse contact info.
Registrar registrar = loadRegistrar(CLIENT_ID);
RegistrarPoc rc =
JpaTransactionManagerExtension.makeRegistrarContact2()
.asBuilder()
.setVisibleInDomainWhoisAsAbuse(true)
.build();
// Lest we anger the timestamp inversion bug.
// (we also update the registrar so we get the timestamp right)
registrar = persistResource(registrar);
persistSimpleResource(rc);
// Now try to remove the contact.
rc = rc.asBuilder().setVisibleInDomainWhoisAsAbuse(false).build();
Map<String, Object> reqJson = registrar.toJsonMap();
reqJson.put("contacts", ImmutableList.of(rc.toJsonMap(), techContact.toJsonMap()));
Map<String, Object> response =
action.handleJsonRequest(ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", reqJson));
assertThat(response).containsEntry("status", "ERROR");
assertThat(response)
.containsEntry(
"message", "An abuse contact visible in domain WHOIS query must be designated");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: ContactRequirementException");
}
@Test
void testPost_updateContacts_whoisAbuseContactMustHavePhoneNumber_error() {
// First make the contact's info visible in whois as abuse contact info.
Registrar registrar = loadRegistrar(CLIENT_ID);
RegistrarPoc rc =
JpaTransactionManagerExtension.makeRegistrarContact2()
.asBuilder()
.setVisibleInDomainWhoisAsAbuse(true)
.build();
// Lest we anger the timestamp inversion bug.
// (we also update the registrar so we get the timestamp right)
registrar = persistResource(registrar);
persistSimpleResource(rc);
// Now try to set the phone number to null.
rc = rc.asBuilder().setPhoneNumber(null).build();
Map<String, Object> reqJson = registrar.toJsonMap();
reqJson.put("contacts", ImmutableList.of(rc.toJsonMap(), techContact.toJsonMap()));
Map<String, Object> response =
action.handleJsonRequest(ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", reqJson));
assertThat(response).containsEntry("status", "ERROR");
assertThat(response)
.containsEntry(
"message", "The abuse contact visible in domain WHOIS query must have a phone number");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: ContactRequirementException");
}
@Test
void testSuccess_setRegistryLockPassword() {
addPasswordToContactTwo();
String emailAddress = JpaTransactionManagerExtension.makeRegistrarContact2().getEmailAddress();
RegistrarPoc newContactWithPassword =
loadRegistrar(CLIENT_ID).getContacts().stream()
.filter(rc -> rc.getEmailAddress().equals(emailAddress))
.findFirst()
.get();
assertThat(newContactWithPassword.verifyRegistryLockPassword("hellothere")).isTrue();
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
}
@Test
void testSuccess_setRegistryLockPassword_notOverriddenLater() {
addPasswordToContactTwo();
String emailAddress = JpaTransactionManagerExtension.makeRegistrarContact2().getEmailAddress();
RegistrarPoc newContactWithPassword =
loadRegistrar(CLIENT_ID).getContacts().stream()
.filter(rc -> rc.getEmailAddress().equals(emailAddress))
.findFirst()
.get();
assertThat(newContactWithPassword.verifyRegistryLockPassword("hellothere")).isTrue();
Map<String, Object> newContactMap = newContactWithPassword.toJsonMap();
newContactMap.put("name", "Some Other Name");
Map<String, Object> reqJson = loadRegistrar(CLIENT_ID).toJsonMap();
reqJson.put(
"contacts",
ImmutableList.of(
JpaTransactionManagerExtension.makeRegistrarContact1().toJsonMap(),
newContactMap,
JpaTransactionManagerExtension.makeRegistrarContact3().toJsonMap()));
clock.advanceOneMilli();
Map<String, Object> response =
action.handleJsonRequest(ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", reqJson));
assertThat(response).containsAtLeastEntriesIn(ImmutableMap.of("status", "SUCCESS"));
newContactWithPassword =
loadRegistrar(CLIENT_ID).getContacts().stream()
.filter(rc -> rc.getEmailAddress().equals(emailAddress))
.findFirst()
.get();
assertThat(newContactWithPassword.verifyRegistryLockPassword("hellothere")).isTrue();
}
private void addPasswordToContactTwo() {
RegistrarPoc contact =
persistResource(
JpaTransactionManagerExtension.makeRegistrarContact2()
.asBuilder()
.setRegistryLockEmailAddress("johndoe@theregistrar.com")
.setAllowedToSetRegistryLockPassword(true)
.build());
Map<String, Object> contactMap = contact.toJsonMap();
contactMap.put("registryLockPassword", "hellothere");
Map<String, Object> reqJson = loadRegistrar(CLIENT_ID).toJsonMap();
reqJson.put(
"contacts",
ImmutableList.of(
JpaTransactionManagerExtension.makeRegistrarContact1().toJsonMap(),
contactMap,
JpaTransactionManagerExtension.makeRegistrarContact3().toJsonMap()));
Map<String, Object> response =
action.handleJsonRequest(ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", reqJson));
assertThat(response).containsAtLeastEntriesIn(ImmutableMap.of("status", "SUCCESS"));
}
@Test
void testPost_failure_setRegistryLockPassword_newContact() {
Map<String, Object> reqJson = loadRegistrar(CLIENT_ID).toJsonMap();
reqJson.put(
"contacts",
ImmutableList.of(
JpaTransactionManagerExtension.makeRegistrarContact2()
.asBuilder()
.setEmailAddress("someotheremail@example.com")
.setRegistryLockEmailAddress("someotherexample@example.com")
.setAllowedToSetRegistryLockPassword(true)
.build()
.toJsonMap(),
techContact.toJsonMap()));
Map<String, Object> response =
action.handleJsonRequest(ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", reqJson));
assertThat(response)
.containsExactly(
"status",
"ERROR",
"results",
ImmutableList.of(),
"message",
"Cannot set registry lock password directly on new contact");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormException");
}
@Test
void testPost_failure_setRegistryLockPassword_notAllowed() {
// "allowedToSetRegistryLockPassword" must be set through the back end first
// before we can set a password through the UI
Map<String, Object> contactMap =
JpaTransactionManagerExtension.makeRegistrarContact2().toJsonMap();
contactMap.put("allowedToSetRegistryLockPassword", true);
contactMap.put("registryLockPassword", "hellothere");
Map<String, Object> reqJson = loadRegistrar(CLIENT_ID).toJsonMap();
reqJson.put(
"contacts",
ImmutableList.of(
JpaTransactionManagerExtension.makeRegistrarContact1().toJsonMap(),
contactMap,
JpaTransactionManagerExtension.makeRegistrarContact3().toJsonMap()));
Map<String, Object> response =
action.handleJsonRequest(ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", reqJson));
assertThat(response)
.containsExactly(
"status",
"ERROR",
"results",
ImmutableList.of(),
"message",
"Registrar contact not allowed to set registry lock password");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormException");
}
@Test
void testPost_failure_setRegistryLockAllowed() {
// One cannot set the "isAllowedToSetRegistryLockPassword" field through the UI
Map<String, Object> reqJson = loadRegistrar(CLIENT_ID).toJsonMap();
reqJson.put(
"contacts",
ImmutableList.of(
JpaTransactionManagerExtension.makeRegistrarContact2().toJsonMap(),
techContact.asBuilder().setAllowedToSetRegistryLockPassword(true).build().toJsonMap()));
Map<String, Object> response =
action.handleJsonRequest(ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", reqJson));
assertThat(response)
.containsExactly(
"status",
"ERROR",
"results",
ImmutableList.of(),
"message",
"Cannot modify isAllowedToSetRegistryLockPassword through the UI");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormException");
}
@Test
void testPost_failure_setRegistryLockEmail() {
addPasswordToContactTwo();
Map<String, Object> reqJson = loadRegistrar(CLIENT_ID).toJsonMap();
String emailAddress = JpaTransactionManagerExtension.makeRegistrarContact2().getEmailAddress();
RegistrarPoc newContactWithPassword =
loadRegistrar(CLIENT_ID).getContacts().stream()
.filter(rc -> rc.getEmailAddress().equals(emailAddress))
.findFirst()
.get();
Map<String, Object> contactJson = newContactWithPassword.toJsonMap();
contactJson.put("registryLockEmailAddress", "bogus.email@bogus.tld");
reqJson.put(
"contacts",
ImmutableList.of(
JpaTransactionManagerExtension.makeRegistrarContact1().toJsonMap(),
contactJson,
JpaTransactionManagerExtension.makeRegistrarContact3().toJsonMap()));
Map<String, Object> response =
action.handleJsonRequest(ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", reqJson));
assertThat(response)
.containsExactly(
"status",
"ERROR",
"results",
ImmutableList.of(),
"message",
"Cannot modify registryLockEmailAddress through the UI");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormException");
}
@Test
void testPost_failure_removingRegistryLockContact() {
ImmutableMap<String, String> contact =
ImmutableMap.of(
"name", "contact1",
"emailAddress", "contact1@email.com",
"phoneNumber", "+1.2125650001",
// Have to keep ADMIN or else expect FormException for at-least-one.
"types", "ADMIN,TECH");
Map<String, Object> regMap = loadRegistrar(CLIENT_ID).toJsonMap();
regMap.put("contacts", ImmutableList.of(contact));
Map<String, Object> response =
action.handleJsonRequest(ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", regMap));
assertThat(response)
.containsExactly(
"status",
"ERROR",
"results",
ImmutableList.of(),
"message",
"Cannot delete the contact Marla.Singer@crr.com that has registry lock enabled");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormException");
}
@Test
void testPost_failure_setRegistryLock_passwordTooShort() {
techContact =
persistResource(techContact.asBuilder().setAllowedToSetRegistryLockPassword(true).build());
Map<String, Object> contactMap = techContact.toJsonMap();
contactMap.put("registryLockPassword", "hi");
Map<String, Object> reqJson = loadRegistrar(CLIENT_ID).toJsonMap();
reqJson.put(
"contacts",
ImmutableList.of(
JpaTransactionManagerExtension.makeRegistrarContact2().toJsonMap(), contactMap));
Map<String, Object> response =
action.handleJsonRequest(ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", reqJson));
assertThat(response)
.containsExactly(
"status",
"ERROR",
"results",
ImmutableList.of(),
"message",
"Registry lock password must be at least 8 characters long");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormException");
}
}

View File

@@ -1,159 +0,0 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.server.registrar;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Iterables;
import google.registry.model.OteAccountBuilder;
import google.registry.model.OteStats.StatType;
import google.registry.model.OteStatsTestHelper;
import google.registry.model.registrar.RegistrarBase.Type;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link OteStatusAction} */
public final class OteStatusActionTest {
private static final String CLIENT_ID = "blobio-1";
private static final String BASE_CLIENT_ID = "blobio";
private final OteStatusAction action = new OteStatusAction();
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
@BeforeEach
void beforeEach() {
ImmutableSetMultimap<String, Role> authValues =
OteAccountBuilder.createRegistrarIdToTldMap(BASE_CLIENT_ID).keySet().stream()
.collect(toImmutableSetMultimap(Function.identity(), ignored -> Role.OWNER));
action.registrarAccessor = AuthenticatedRegistrarAccessor.createForTesting(authValues);
}
@Test
@SuppressWarnings("unchecked")
void testSuccess_finishedOte() throws Exception {
OteStatsTestHelper.setupCompleteOte(BASE_CLIENT_ID);
Map<String, ?> actionResult = action.handleJsonRequest(ImmutableMap.of("clientId", CLIENT_ID));
assertThat(actionResult).containsEntry("status", "SUCCESS");
assertThat(actionResult).containsEntry("message", "OT&E check completed successfully");
Map<String, ?> results =
Iterables.getOnlyElement((List<Map<String, ?>>) actionResult.get("results"));
assertThat(results).containsEntry("clientId", BASE_CLIENT_ID);
assertThat(results).containsEntry("completed", true);
assertThat(getFailingResultDetails(results)).isEmpty();
}
@Test
@SuppressWarnings("unchecked")
void testSuccess_incomplete() throws Exception {
OteStatsTestHelper.setupIncompleteOte(BASE_CLIENT_ID);
Map<String, ?> actionResult = action.handleJsonRequest(ImmutableMap.of("clientId", CLIENT_ID));
assertThat(actionResult).containsEntry("status", "SUCCESS");
assertThat(actionResult).containsEntry("message", "OT&E check completed successfully");
Map<String, ?> results =
Iterables.getOnlyElement((List<Map<String, ?>>) actionResult.get("results"));
assertThat(results).containsEntry("clientId", BASE_CLIENT_ID);
assertThat(results).containsEntry("completed", false);
assertThat(getFailingResultDetails(results))
.containsExactly(
ImmutableMap.of(
"description", StatType.HOST_DELETES.getDescription(),
"requirement", StatType.HOST_DELETES.getRequirement(),
"timesPerformed", 0,
"completed", false),
ImmutableMap.of(
"description", StatType.DOMAIN_RESTORES.getDescription(),
"requirement", StatType.DOMAIN_RESTORES.getRequirement(),
"timesPerformed", 0,
"completed", false),
ImmutableMap.of(
"description", StatType.DOMAIN_CREATES_IDN.getDescription(),
"requirement", StatType.DOMAIN_CREATES_IDN.getRequirement(),
"timesPerformed", 0,
"completed", false));
}
@Test
void testFailure_malformedInput() {
assertThat(action.handleJsonRequest(null))
.containsExactlyEntriesIn(errorResultWithMessage("Malformed JSON"));
assertThat(action.handleJsonRequest(ImmutableMap.of()))
.containsExactlyEntriesIn(errorResultWithMessage("Missing key for OT&E client: clientId"));
}
@Test
void testFailure_registrarDoesntExist() {
assertThat(action.handleJsonRequest(ImmutableMap.of("clientId", "nonexistent-3")))
.containsExactlyEntriesIn(
errorResultWithMessage("Registrar nonexistent-3 does not exist"));
}
@Test
void testFailure_notAuthorized() {
persistNewRegistrar(CLIENT_ID, "blobio-1", Type.REAL, 1L);
action.registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of());
assertThat(action.handleJsonRequest(ImmutableMap.of("clientId", CLIENT_ID)))
.containsExactlyEntriesIn(
errorResultWithMessage("TestUserId doesn't have access to registrar blobio-1"));
}
@Test
void testFailure_malformedRegistrarName() {
assertThat(action.handleJsonRequest(ImmutableMap.of("clientId", "badclient-id")))
.containsExactlyEntriesIn(
errorResultWithMessage(
"ID badclient-id is not one of the OT&E registrar IDs for base badclient"));
}
@Test
void testFailure_nonOteRegistrar() {
persistNewRegistrar(CLIENT_ID, "SomeRegistrar", Type.REAL, 1L);
assertThat(action.handleJsonRequest(ImmutableMap.of("clientId", CLIENT_ID)))
.containsExactlyEntriesIn(
errorResultWithMessage("Registrar with ID blobio-1 is not an OT&E registrar"));
}
@SuppressWarnings("unchecked")
private List<Map<?, ?>> getFailingResultDetails(Map<String, ?> results) {
return ((List<Map<?, ?>>) results.get("details"))
.stream()
.filter(result -> !Boolean.TRUE.equals(result.get("completed")))
.collect(toImmutableList());
}
private ImmutableMap<String, ?> errorResultWithMessage(String message) {
return ImmutableMap.of("status", "ERROR", "message", message, "results", ImmutableList.of());
}
}

View File

@@ -1,669 +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.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.TestDataHelper.loadFile;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import com.google.cloud.tasks.v2.HttpMethod;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import google.registry.export.sheet.SyncRegistrarsSheetAction;
import google.registry.model.registrar.Registrar;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role;
import google.registry.testing.CertificateSamples;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.SystemPropertyExtension;
import google.registry.util.CidrAddressBlock;
import google.registry.util.EmailMessage;
import google.registry.util.RegistryEnvironment;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.joda.time.DateTime;
import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.ArgumentCaptor;
/** Tests for {@link RegistrarSettingsAction}. */
class RegistrarSettingsActionTest extends RegistrarSettingsActionTestCase {
@RegisterExtension
final SystemPropertyExtension systemPropertyExtension = new SystemPropertyExtension();
@Test
void testSuccess_updateRegistrarInfo_andSendsNotificationEmail() throws Exception {
String expectedEmailBody = loadFile(getClass(), "update_registrar_email.txt");
// This update changes some values on the admin contact and makes it a tech contact as well,
// while deleting the existing tech contact (by omission).
action.handleJsonRequest(readJsonFromFile("update_registrar.json", getLastUpdateTime()));
verify(rsp, never()).setStatus(anyInt());
verifyNotificationEmailsSent();
ArgumentCaptor<EmailMessage> contentCaptor = ArgumentCaptor.forClass(EmailMessage.class);
verify(gmailClient).sendEmail(contentCaptor.capture());
assertThat(contentCaptor.getValue().body()).isEqualTo(expectedEmailBody);
cloudTasksHelper.assertTasksEnqueued(
"sheet",
new TaskMatcher()
.path(SyncRegistrarsSheetAction.PATH)
.service("Backend")
.method(HttpMethod.POST));
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
}
@Test
void testFailure_updateRegistrarInfo_duplicateContacts() {
Map<String, Object> response = action.handleJsonRequest(
readJsonFromFile("update_registrar_duplicate_contacts.json", getLastUpdateTime()));
assertThat(response).containsExactly(
"status", "ERROR",
"results", ImmutableList.of(),
"message",
"One email address (etphonehome@example.com) cannot be used for multiple contacts");
cloudTasksHelper.assertNoTasksEnqueued("sheet");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: ContactRequirementException");
}
/**
* Make sure that if someone spoofs a different registrar (they don't have access to), we fail.
* Also relevant if the person's privilege were revoked after the page load.
*/
@Test
void testFailure_readRegistrarInfo_notAuthorized() {
setUserWithoutAccess();
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of("id", CLIENT_ID));
assertThat(response)
.containsExactly(
"status", "ERROR",
"results", ImmutableList.of(),
"message", "TestUserId doesn't have access to registrar TheRegistrar");
cloudTasksHelper.assertNoTasksEnqueued("sheet");
assertMetric(CLIENT_ID, "read", "[]", "ERROR: ForbiddenException");
}
/** This is the default read test for the registrar settings actions. */
@Test
void testSuccess_readRegistrarInfo_authorizedReadWrite() {
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of("id", CLIENT_ID));
assertThat(response)
.containsExactly(
"status", "SUCCESS",
"message", "Success",
"results", ImmutableList.of(loadRegistrar(CLIENT_ID).toJsonMap()));
assertMetric(CLIENT_ID, "read", "[OWNER]", "SUCCESS");
}
@Test
void testUpdate_emptyJsonObject_errorLastUpdateTimeFieldRequired() {
Map<String, Object> args = Maps.newHashMap(loadRegistrar(CLIENT_ID).toJsonMap());
args.remove("lastUpdateTime");
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", args));
assertThat(response).containsExactly(
"status", "ERROR",
"field", "lastUpdateTime",
"results", ImmutableList.of(),
"message", "This field is required.");
cloudTasksHelper.assertNoTasksEnqueued("sheet");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormFieldException");
}
@Test
void testUpdate_noEmail_errorEmailFieldRequired() {
Map<String, Object> args = Maps.newHashMap(loadRegistrar(CLIENT_ID).toJsonMap());
args.remove("emailAddress");
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", args));
assertThat(response).containsExactly(
"status", "ERROR",
"field", "emailAddress",
"results", ImmutableList.of(),
"message", "This field is required.");
cloudTasksHelper.assertNoTasksEnqueued("sheet");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormFieldException");
}
@Test
void testFailure_updateRegistrarInfo_notAuthorized() {
setUserWithoutAccess();
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", ImmutableMap.of("lastUpdateTime", getLastUpdateTime())));
assertThat(response)
.containsExactly(
"status", "ERROR",
"results", ImmutableList.of(),
"message", "TestUserId doesn't have access to registrar TheRegistrar");
cloudTasksHelper.assertNoTasksEnqueued("sheet");
assertMetric(CLIENT_ID, "update", "[]", "ERROR: ForbiddenException");
}
@Test
void testUpdate_badEmail_errorEmailField() {
Map<String, Object> args = Maps.newHashMap(loadRegistrar(CLIENT_ID).toJsonMap());
args.put("emailAddress", "lolcat");
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", args));
assertThat(response).containsExactly(
"status", "ERROR",
"field", "emailAddress",
"results", ImmutableList.of(),
"message", "Please enter a valid email address.");
cloudTasksHelper.assertNoTasksEnqueued("sheet");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormFieldException");
}
@Test
void testPost_nonParsableTime_getsAngry() {
Map<String, Object> args = Maps.newHashMap(loadRegistrar(CLIENT_ID).toJsonMap());
args.put("lastUpdateTime", "cookies");
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", args));
assertThat(response).containsExactly(
"status", "ERROR",
"field", "lastUpdateTime",
"results", ImmutableList.of(),
"message", "Not a valid ISO date-time string.");
cloudTasksHelper.assertNoTasksEnqueued("sheet");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormFieldException");
}
@Test
void testPost_nonAsciiCharacters_getsAngry() {
Map<String, Object> args = Maps.newHashMap(loadRegistrar(CLIENT_ID).toJsonMap());
args.put("emailAddress", "ヘ(◕。◕ヘ)@example.com");
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", args));
assertThat(response).containsExactly(
"status", "ERROR",
"field", "emailAddress",
"results", ImmutableList.of(),
"message", "Please only use ASCII-US characters.");
cloudTasksHelper.assertNoTasksEnqueued("sheet");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormFieldException");
}
/**
* Makes sure a field update succeeds IF AND ONLY IF we have the "correct" role.
*
* <p>Each of the Registrar fields can be changed only by a single {@link Role}. We make sure that
* trying to update the field works if the user has the "correct" role, but fails if it doesn't.
*/
private <T> void doTestUpdate(
Role correctRole,
Function<Registrar, T> getter,
T newValue,
BiFunction<Registrar.Builder, T, Registrar.Builder> setter) {
doTestUpdateWithCorrectRole_succeeds(correctRole, getter, newValue, setter);
doTestUpdateWithoutCorrectRole_fails(correctRole, getter, newValue, setter);
}
private <T> void doTestUpdateWithCorrectRole_succeeds(
Role role,
Function<Registrar, T> getter,
T newValue,
BiFunction<Registrar.Builder, T, Registrar.Builder> setter) {
// Set the user to only have the current role for this registrar
action.registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of(CLIENT_ID, role));
// Load the registrar as it is currently in the database, and make sure the requested update
// will actually change it
Registrar registrar = loadRegistrar(CLIENT_ID);
assertThat(getter.apply(registrar)).isNotEqualTo(newValue);
// Call the action to perform the requested update, then load the "updated" registrar and
// return the "database" registrar to its original state (for the next iteration)
Map<String, Object> response =
action.handleJsonRequest(
ImmutableMap.of(
"op",
"update",
"id",
CLIENT_ID,
"args",
setter
.apply(registrar.asBuilder(), newValue)
.setLastUpdateTime(registrar.getLastUpdateTime())
.build()
.toJsonMap()));
Registrar updatedRegistrar = loadRegistrar(CLIENT_ID);
persistResource(registrar);
// This role is authorized to perform this change, make sure the change succeeded
// We got a success result:
assertThat(response).containsEntry("status", "SUCCESS");
assertThat(response).containsEntry("results", ImmutableList.of(updatedRegistrar.toJsonMap()));
// The updatedRegistrar had its value changed:
// (We check it separately from the next assert to get better error message on failure)
assertThat(getter.apply(updatedRegistrar)).isEqualTo(newValue);
// ONLY that value changed:
// (note: the setter won't properly update last update time, so ignore it)
assertAboutImmutableObjects()
.that(updatedRegistrar)
.isEqualExceptFields(
setter.apply(registrar.asBuilder(), newValue).build(), "updateTimestamp");
// We increased the correct metric
assertMetric(CLIENT_ID, "update", String.format("[%s]", role), "SUCCESS");
}
private <T> void doTestUpdateWithoutCorrectRole_fails(
Role correctRole,
Function<Registrar, T> getter,
T newValue,
BiFunction<Registrar.Builder, T, Registrar.Builder> setter) {
// Set the user to only have the current role for this registrar
ImmutableSet<Role> allExceptCorrectRoles =
Sets.difference(ImmutableSet.copyOf(Role.values()), ImmutableSet.of(correctRole))
.immutableCopy();
ImmutableSetMultimap<String, Role> accessMap =
new ImmutableSetMultimap.Builder<String, Role>()
.putAll(CLIENT_ID, allExceptCorrectRoles)
.build();
action.registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(accessMap);
// Load the registrar as it is currently in the database, and make sure the requested update
// will actually change it
Registrar registrar = loadRegistrar(CLIENT_ID);
assertThat(getter.apply(registrar)).isNotEqualTo(newValue);
// Call the action to perform the requested update, then load the "updated" registrar and
// returned the "datastore" registrar to its original state (for the next iteration)
Map<String, Object> response =
action.handleJsonRequest(
ImmutableMap.of(
"op",
"update",
"id",
CLIENT_ID,
"args",
setter
.apply(registrar.asBuilder(), newValue)
.setLastUpdateTime(registrar.getLastUpdateTime())
.build()
.toJsonMap()));
Registrar updatedRegistrar = loadRegistrar(CLIENT_ID);
persistResource(registrar);
// This role is NOT authorized to perform this change, make sure the change failed
// We got an error response with the correct message
assertThat(response).containsEntry("status", "ERROR");
assertThat(response).containsEntry("results", ImmutableList.of());
assertThat(response.get("message").toString())
.containsMatch("Unauthorized: only .* can change fields .*");
// Make sure the value hasn't changed
// (We check it separately from the next assert to get better error message on failure)
assertThat(getter.apply(updatedRegistrar)).isEqualTo(getter.apply(registrar));
// Make sure no other values have changed either
assertThat(updatedRegistrar).isEqualTo(registrar);
// We increased the correct metric
assertMetric(
CLIENT_ID, "update", allExceptCorrectRoles.toString(), "ERROR: ForbiddenException");
}
@Test
void testUpdate_whoisServer() {
doTestUpdate(
Role.OWNER,
Registrar::getWhoisServer,
"new-whois.example",
Registrar.Builder::setWhoisServer);
}
@Test
void testUpdate_phoneNumber() {
doTestUpdate(
Role.OWNER, Registrar::getPhoneNumber, "+1.2345678900", Registrar.Builder::setPhoneNumber);
}
@Test
void testUpdate_faxNumber() {
doTestUpdate(
Role.OWNER, Registrar::getFaxNumber, "+1.2345678900", Registrar.Builder::setFaxNumber);
}
@Test
void testUpdate_url() {
doTestUpdate(Role.OWNER, Registrar::getUrl, "new-url.example", Registrar.Builder::setUrl);
}
@Test
void testUpdate_ipAddressAllowList() {
doTestUpdate(
Role.OWNER,
Registrar::getIpAddressAllowList,
ImmutableList.of(CidrAddressBlock.create("1.1.1.0/24")),
Registrar.Builder::setIpAddressAllowList);
}
@Test
void testUpdate_clientCertificate() {
clock.setTo(DateTime.parse("2020-11-02T00:00:00Z"));
doTestUpdate(
Role.OWNER,
r -> r.getClientCertificate().orElse(null),
CertificateSamples.SAMPLE_CERT3,
(builder, s) -> builder.setClientCertificate(s, clock.nowUtc()));
}
@Test
void testUpdate_clientCertificateWithViolationsFails() {
clock.setTo(DateTime.parse("2020-11-02T00:00:00Z"));
Map<String, Object> args = Maps.newHashMap(loadRegistrar(CLIENT_ID).toJsonMap());
args.put("clientCertificate", CertificateSamples.SAMPLE_CERT);
Map<String, Object> response =
action.handleJsonRequest(
ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", args));
assertThat(response)
.containsExactly(
"status",
"ERROR",
"results",
ImmutableList.of(),
"message",
"Certificate validity period is too long; it must be less than or equal to 398"
+ " days.");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: IllegalArgumentException");
cloudTasksHelper.assertNoTasksEnqueued("sheet");
}
@Test
void testUpdate_clientCertificateWithMultipleViolationsFails() {
clock.setTo(DateTime.parse("2055-11-01T00:00:00Z"));
Map<String, Object> args = Maps.newHashMap(loadRegistrar(CLIENT_ID).toJsonMap());
args.put("clientCertificate", CertificateSamples.SAMPLE_CERT);
Map<String, Object> response =
action.handleJsonRequest(
ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", args));
assertThat(response)
.containsExactly(
"status",
"ERROR",
"results",
ImmutableList.of(),
"message",
"Certificate is expired.\nCertificate validity period is too long; it must be less"
+ " than or equal to 398 days.");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: IllegalArgumentException");
cloudTasksHelper.assertNoTasksEnqueued("sheet");
}
@Test
void testUpdate_failoverClientCertificate() {
clock.setTo(DateTime.parse("2020-11-02T00:00:00Z"));
doTestUpdate(
Role.OWNER,
r -> r.getFailoverClientCertificate().orElse(null),
CertificateSamples.SAMPLE_CERT3,
(builder, s) -> builder.setFailoverClientCertificate(s, clock.nowUtc()));
}
@Test
void testUpdate_failoverClientCertificateWithViolationsFails() {
clock.setTo(DateTime.parse("2020-11-02T00:00:00Z"));
Map<String, Object> args = Maps.newHashMap(loadRegistrar(CLIENT_ID).toJsonMap());
args.put("failoverClientCertificate", CertificateSamples.SAMPLE_CERT);
Map<String, Object> response =
action.handleJsonRequest(
ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", args));
assertThat(response)
.containsExactly(
"status",
"ERROR",
"results",
ImmutableList.of(),
"message",
"Certificate validity period is too long; it must be less than or equal to 398"
+ " days.");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: IllegalArgumentException");
cloudTasksHelper.assertNoTasksEnqueued("sheet");
}
@Test
void testUpdate_failoverClientCertificateWithMultipleViolationsFails() {
clock.setTo(DateTime.parse("2055-11-01T00:00:00Z"));
Map<String, Object> args = Maps.newHashMap(loadRegistrar(CLIENT_ID).toJsonMap());
args.put("failoverClientCertificate", CertificateSamples.SAMPLE_CERT);
Map<String, Object> response =
action.handleJsonRequest(
ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", args));
assertThat(response)
.containsExactly(
"status",
"ERROR",
"results",
ImmutableList.of(),
"message",
"Certificate is expired.\nCertificate validity period is too long; it must be less"
+ " than or equal to 398 days.");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: IllegalArgumentException");
cloudTasksHelper.assertNoTasksEnqueued("sheet");
}
@Test
void testUpdate_allowedTlds() {
doTestUpdate(
Role.ADMIN,
Registrar::getAllowedTlds,
ImmutableSet.of("newtld", "currenttld"),
(builder, s) -> builder.setAllowedTlds(s));
}
@Test
void testUpdate_allowedTlds_failedWhenNoWhoisAbuseContactExists() {
setUserAdmin();
RegistryEnvironment.PRODUCTION.setup(systemPropertyExtension);
Map<String, Object> args = Maps.newHashMap(loadRegistrar(CLIENT_ID).toJsonMap());
args.put("allowedTlds", ImmutableList.of("newtld", "currenttld"));
Map<String, Object> response =
action.handleJsonRequest(
ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", args));
assertThat(response)
.containsExactly(
"status", "ERROR",
"results", ImmutableList.of(),
"message", "Cannot add allowed TLDs if there is no WHOIS abuse contact set.");
assertMetric(CLIENT_ID, "update", "[ADMIN]", "ERROR: IllegalArgumentException");
cloudTasksHelper.assertNoTasksEnqueued("sheet");
}
@Test
void testUpdate_allowedTlds_failedWhenTldNotExist() {
setUserAdmin();
Map<String, Object> args = Maps.newHashMap(loadRegistrar(CLIENT_ID).toJsonMap());
args.put("allowedTlds", ImmutableList.of("invalidtld", "currenttld"));
Map<String, Object> response =
action.handleJsonRequest(
ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", args));
assertThat(response)
.containsExactly(
"status", "ERROR",
"results", ImmutableList.of(),
"message", "TLDs do not exist: invalidtld");
assertMetric(CLIENT_ID, "update", "[ADMIN]", "ERROR: IllegalArgumentException");
cloudTasksHelper.assertNoTasksEnqueued("sheet");
}
@Test
void testUpdate_allowedTlds_failedWhenRemovingTld() {
setUserAdmin();
Map<String, Object> args = Maps.newHashMap(loadRegistrar(CLIENT_ID).toJsonMap());
args.put("allowedTlds", ImmutableList.of("newtld"));
Map<String, Object> response =
action.handleJsonRequest(
ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", args));
assertThat(response)
.containsExactly(
"status", "ERROR",
"results", ImmutableList.of(),
"message", "Can't remove allowed TLDs using the console.");
assertMetric(CLIENT_ID, "update", "[ADMIN]", "ERROR: ForbiddenException");
cloudTasksHelper.assertNoTasksEnqueued("sheet");
}
@Test
void testUpdate_allowedTlds_noChange_successWhenUserIsNotAdmin() {
Map<String, Object> args = Maps.newHashMap(loadRegistrar(CLIENT_ID).toJsonMap());
args.put("allowedTlds", ImmutableList.of("currenttld"));
Map<String, Object> response =
action.handleJsonRequest(
ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", args));
assertThat(response)
.containsExactly(
"status", "SUCCESS",
"message", "Saved TheRegistrar",
"results", ImmutableList.of(loadRegistrar(CLIENT_ID).toJsonMap()));
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
}
@Test
void testUpdate_localizedAddress_city() {
doTestUpdate(
Role.OWNER,
Registrar::getLocalizedAddress,
loadRegistrar(CLIENT_ID).getLocalizedAddress().asBuilder().setCity("newCity").build(),
Registrar.Builder::setLocalizedAddress);
}
@Test
void testUpdate_localizedAddress_countryCode() {
doTestUpdate(
Role.OWNER,
Registrar::getLocalizedAddress,
loadRegistrar(CLIENT_ID).getLocalizedAddress().asBuilder().setCountryCode("GB").build(),
Registrar.Builder::setLocalizedAddress);
}
@Test
void testUpdate_localizedAddress_state() {
doTestUpdate(
Role.OWNER,
Registrar::getLocalizedAddress,
loadRegistrar(CLIENT_ID).getLocalizedAddress().asBuilder().setState("NJ").build(),
Registrar.Builder::setLocalizedAddress);
}
@Test
void testUpdate_localizedAddress_street() {
doTestUpdate(
Role.OWNER,
Registrar::getLocalizedAddress,
loadRegistrar(CLIENT_ID)
.getLocalizedAddress()
.asBuilder()
.setStreet(ImmutableList.of("new street"))
.build(),
Registrar.Builder::setLocalizedAddress);
}
@Test
void testUpdate_localizedAddress_zip() {
doTestUpdate(
Role.OWNER,
Registrar::getLocalizedAddress,
loadRegistrar(CLIENT_ID).getLocalizedAddress().asBuilder().setZip("new zip").build(),
Registrar.Builder::setLocalizedAddress);
}
private static String getLastUpdateTime() {
return loadRegistrar(CLIENT_ID).getLastUpdateTime().toString();
}
static Map<String, Object> readJsonFromFile(String filename, String lastUpdateTime) {
String contents =
loadFile(
RegistrarSettingsActionTestCase.class,
filename,
ImmutableMap.of("LAST_UPDATE_TIME", lastUpdateTime));
try {
@SuppressWarnings("unchecked")
Map<String, Object> json = (Map<String, Object>) JSONValue.parseWithException(contents);
return json;
} catch (ParseException ex) {
throw new RuntimeException(ex);
}
}
}

View File

@@ -1,180 +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.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
import static com.google.monitoring.metrics.contrib.LongMetricSubject.assertThat;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.ADMIN;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.OWNER;
import static google.registry.security.JsonHttpTestUtils.createJsonPayload;
import static google.registry.testing.DatabaseHelper.createTlds;
import static google.registry.testing.DatabaseHelper.disallowRegistrarAccess;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.flows.certs.CertificateChecker;
import google.registry.groups.GmailClient;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.registrar.RegistrarPocBase;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.JsonActionRunner;
import google.registry.request.JsonResponse;
import google.registry.request.ResponseImpl;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.FakeClock;
import google.registry.ui.server.SendEmailUtils;
import google.registry.util.EmailMessage;
import jakarta.mail.internet.InternetAddress;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.joda.time.DateTime;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
/** Base class for tests using {@link RegistrarSettingsAction}. */
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public abstract class RegistrarSettingsActionTestCase {
static final String CLIENT_ID = "TheRegistrar";
final FakeClock clock = new FakeClock(DateTime.parse("2014-01-01T00:00:00Z"));
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
@Mock HttpServletRequest req;
@Mock HttpServletResponse rsp;
@Mock GmailClient gmailClient;
final RegistrarSettingsAction action = new RegistrarSettingsAction();
private final StringWriter writer = new StringWriter();
RegistrarPoc techContact;
CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
@BeforeEach
public void beforeEachRegistrarSettingsActionTestCase() throws Exception {
// Registrar "TheRegistrar" has access to TLD "currenttld" but not to "newtld".
createTlds("currenttld", "newtld");
disallowRegistrarAccess(CLIENT_ID, "newtld");
// Add a technical contact to the registrar (in addition to the default admin contact created by
// JpaTransactionManagerExtension).
techContact =
getOnlyElement(loadRegistrar(CLIENT_ID).getContactsOfType(RegistrarPocBase.Type.TECH));
action.registrarAccessor = null;
action.jsonActionRunner =
new JsonActionRunner(ImmutableMap.of(), new JsonResponse(new ResponseImpl(rsp)));
action.sendEmailUtils =
new SendEmailUtils(
ImmutableList.of("notification@test.example", "notification2@test.example"),
gmailClient);
action.registrarConsoleMetrics = new RegistrarConsoleMetrics();
action.authResult =
AuthResult.createUser(
new User.Builder()
.setEmailAddress("user@email.com")
.setUserRoles(new UserRoles())
.build());
action.certificateChecker =
new CertificateChecker(
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
30,
15,
2048,
ImmutableSet.of("secp256r1", "secp384r1"),
clock);
action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
when(req.getMethod()).thenReturn("POST");
when(rsp.getWriter()).thenReturn(new PrintWriter(writer));
when(req.getContentType()).thenReturn("application/json");
when(req.getReader()).thenReturn(createJsonPayload(ImmutableMap.of("op", "read")));
// We set the default to a user with access, as that's the most common test case. When we want
// to specifically check a user without access, we can switch user for that specific test.
setUserWithAccess();
RegistrarConsoleMetrics.settingsRequestMetric.reset();
}
@AfterEach
public void afterEach() {
assertThat(RegistrarConsoleMetrics.settingsRequestMetric).hasNoOtherValues();
}
void assertMetric(String registrarId, String op, String roles, String status) {
assertThat(RegistrarConsoleMetrics.settingsRequestMetric)
.hasValueForLabels(1, registrarId, op, roles, status);
RegistrarConsoleMetrics.settingsRequestMetric.reset(registrarId, op, roles, status);
}
/** Sets registrarAccessor.getRegistrar to succeed for CLIENT_ID only. */
private void setUserWithAccess() {
action.registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(
ImmutableSetMultimap.of(CLIENT_ID, OWNER));
}
/** Sets registrarAccessor.getRegistrar to always fail. */
void setUserWithoutAccess() {
action.registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of());
}
/**
* Sets registrarAccessor.getAllClientIdWithRoles to return a map with admin role for CLIENT_ID
*/
void setUserAdmin() {
action.registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(
ImmutableSetMultimap.of(CLIENT_ID, ADMIN));
}
/** Verifies that the original contact of TheRegistrar is among those notified of a change. */
void verifyNotificationEmailsSent() throws Exception {
ArgumentCaptor<EmailMessage> captor = ArgumentCaptor.forClass(EmailMessage.class);
verify(gmailClient).sendEmail(captor.capture());
assertThat(captor.getValue().recipients())
.containsExactly(
new InternetAddress("notification@test.example"),
new InternetAddress("notification2@test.example"),
new InternetAddress("johndoe@theregistrar.com"));
}
}

View File

@@ -1,405 +0,0 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.server.registrar;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.ADMIN;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.OWNER;
import static google.registry.testing.SqlHelper.saveRegistryLock;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.gson.Gson;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.model.domain.RegistryLock;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.Action.Method;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import java.util.Map;
import java.util.Optional;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link RegistryLockGetAction}. */
final class RegistryLockGetActionTest {
private static final Gson GSON = new Gson();
private final FakeClock fakeClock = new FakeClock();
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().withClock(fakeClock).buildIntegrationTestExtension();
private final FakeResponse response = new FakeResponse();
private User user;
private AuthResult authResult;
private AuthenticatedRegistrarAccessor accessor;
private RegistryLockGetAction action;
@BeforeEach
void beforeEach() {
user =
new User.Builder()
.setEmailAddress("Marla.Singer@crr.com")
.setUserRoles(
new UserRoles.Builder()
.setRegistrarRoles(
ImmutableMap.of(
"TheRegistrar", RegistrarRole.ACCOUNT_MANAGER_WITH_REGISTRY_LOCK))
.build())
.build();
action = createAction(user);
}
private RegistryLockGetAction createAction(User user) {
fakeClock.setTo(DateTime.parse("2000-06-08T22:00:00.0Z"));
authResult = AuthResult.createUser(user);
accessor =
AuthenticatedRegistrarAccessor.createForTesting(
ImmutableSetMultimap.of(
"TheRegistrar", OWNER,
"NewRegistrar", OWNER));
return new RegistryLockGetAction(
Method.GET, response, accessor, authResult, Optional.of("TheRegistrar"));
}
@Test
void testSuccess_newConsoleUser() {
RegistryLock regularLock =
new RegistryLock.Builder()
.setRepoId("repoId")
.setDomainName("example.test")
.setRegistrarId("TheRegistrar")
.setVerificationCode("123456789ABCDEFGHJKLMNPQRSTUVWXY")
.setRegistrarPocId("johndoe@theregistrar.com")
.setLockCompletionTime(fakeClock.nowUtc())
.build();
saveRegistryLock(regularLock);
google.registry.model.console.User consoleUser =
new google.registry.model.console.User.Builder()
.setEmailAddress("johndoe@theregistrar.com")
.setUserRoles(
new UserRoles.Builder()
.setRegistrarRoles(
ImmutableMap.of(
"TheRegistrar", RegistrarRole.ACCOUNT_MANAGER_WITH_REGISTRY_LOCK))
.build())
.build();
action.authResult = AuthResult.createUser(consoleUser);
action.run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
assertThat(GSON.fromJson(response.getPayload(), Map.class))
.containsExactly(
"status",
"SUCCESS",
"message",
"Successful locks retrieval",
"results",
ImmutableList.of(
ImmutableMap.of(
"lockEnabledForContact",
true,
"email",
"johndoe@theregistrar.com",
"clientId",
"TheRegistrar",
"locks",
ImmutableList.of(
new ImmutableMap.Builder<>()
.put("domainName", "example.test")
.put("lockedTime", "2000-06-08T22:00:00.000Z")
.put("lockedBy", "johndoe@theregistrar.com")
.put("isLockPending", false)
.put("isUnlockPending", false)
.put("userCanUnlock", true)
.build()))));
}
@Test
void testSuccess_retrievesLocks() {
RegistryLock expiredLock =
new RegistryLock.Builder()
.setRepoId("repoId")
.setDomainName("expired.test")
.setRegistrarId("TheRegistrar")
.setVerificationCode("123456789ABCDEFGHJKLMNPQRSTUVWXY")
.setRegistrarPocId("johndoe@theregistrar.com")
.build();
saveRegistryLock(expiredLock);
RegistryLock expiredUnlock =
new RegistryLock.Builder()
.setRepoId("repoId")
.setDomainName("expiredunlock.test")
.setRegistrarId("TheRegistrar")
.setVerificationCode("123456789ABCDEFGHJKLMNPQRSTUVWXY")
.setRegistrarPocId("johndoe@theregistrar.com")
.setLockCompletionTime(fakeClock.nowUtc())
.setUnlockRequestTime(fakeClock.nowUtc())
.build();
saveRegistryLock(expiredUnlock);
fakeClock.advanceBy(Duration.standardDays(1));
RegistryLock regularLock =
new RegistryLock.Builder()
.setRepoId("repoId")
.setDomainName("example.test")
.setRegistrarId("TheRegistrar")
.setVerificationCode("123456789ABCDEFGHJKLMNPQRSTUVWXY")
.setRegistrarPocId("johndoe@theregistrar.com")
.setLockCompletionTime(fakeClock.nowUtc())
.build();
fakeClock.advanceOneMilli();
RegistryLock adminLock =
new RegistryLock.Builder()
.setRepoId("repoId")
.setDomainName("adminexample.test")
.setRegistrarId("TheRegistrar")
.setVerificationCode("122222222ABCDEFGHJKLMNPQRSTUVWXY")
.isSuperuser(true)
.setLockCompletionTime(fakeClock.nowUtc())
.build();
RegistryLock incompleteLock =
new RegistryLock.Builder()
.setRepoId("repoId")
.setDomainName("pending.test")
.setRegistrarId("TheRegistrar")
.setVerificationCode("111111111ABCDEFGHJKLMNPQRSTUVWXY")
.setRegistrarPocId("johndoe@theregistrar.com")
.build();
RegistryLock incompleteUnlock =
new RegistryLock.Builder()
.setRepoId("repoId")
.setDomainName("incompleteunlock.test")
.setRegistrarId("TheRegistrar")
.setVerificationCode("123456789ABCDEFGHJKLMNPQRSTUVWXY")
.setRegistrarPocId("johndoe@theregistrar.com")
.setLockCompletionTime(fakeClock.nowUtc())
.setUnlockRequestTime(fakeClock.nowUtc())
.build();
RegistryLock unlockedLock =
new RegistryLock.Builder()
.setRepoId("repoId")
.setDomainName("unlocked.test")
.setRegistrarId("TheRegistrar")
.setRegistrarPocId("johndoe@theregistrar.com")
.setVerificationCode("123456789ABCDEFGHJKLMNPQRSTUUUUU")
.setLockCompletionTime(fakeClock.nowUtc())
.setUnlockRequestTime(fakeClock.nowUtc())
.setUnlockCompletionTime(fakeClock.nowUtc())
.build();
saveRegistryLock(regularLock);
saveRegistryLock(adminLock);
saveRegistryLock(incompleteLock);
saveRegistryLock(incompleteUnlock);
saveRegistryLock(unlockedLock);
action.run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
assertThat(GSON.fromJson(response.getPayload(), Map.class))
.containsExactly(
"status", "SUCCESS",
"message", "Successful locks retrieval",
"results",
ImmutableList.of(
ImmutableMap.of(
"lockEnabledForContact",
true,
"email",
"Marla.Singer@crr.com",
"clientId",
"TheRegistrar",
"locks",
ImmutableList.of(
new ImmutableMap.Builder<>()
.put("domainName", "adminexample.test")
.put("lockedTime", "2000-06-09T22:00:00.001Z")
.put("lockedBy", "admin")
.put("userCanUnlock", false)
.put("isLockPending", false)
.put("isUnlockPending", false)
.build(),
new ImmutableMap.Builder<>()
.put("domainName", "example.test")
.put("lockedTime", "2000-06-09T22:00:00.000Z")
.put("lockedBy", "johndoe@theregistrar.com")
.put("userCanUnlock", true)
.put("isLockPending", false)
.put("isUnlockPending", false)
.build(),
new ImmutableMap.Builder<>()
.put("domainName", "expiredunlock.test")
.put("lockedTime", "2000-06-08T22:00:00.000Z")
.put("lockedBy", "johndoe@theregistrar.com")
.put("userCanUnlock", true)
.put("isLockPending", false)
.put("isUnlockPending", false)
.build(),
new ImmutableMap.Builder<>()
.put("domainName", "incompleteunlock.test")
.put("lockedTime", "2000-06-09T22:00:00.001Z")
.put("lockedBy", "johndoe@theregistrar.com")
.put("userCanUnlock", true)
.put("isLockPending", false)
.put("isUnlockPending", true)
.build(),
new ImmutableMap.Builder<>()
.put("domainName", "pending.test")
.put("lockedTime", "")
.put("lockedBy", "johndoe@theregistrar.com")
.put("userCanUnlock", true)
.put("isLockPending", true)
.put("isUnlockPending", false)
.build()))));
}
@Test
void testFailure_invalidMethod() {
action.method = Method.POST;
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, action::run);
assertThat(thrown).hasMessageThat().isEqualTo("Only GET requests allowed");
}
@Test
void testFailure_noUser() {
action.authResult = AuthResult.NOT_AUTHENTICATED;
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, action::run);
assertThat(thrown).hasMessageThat().isEqualTo("User must be present");
}
@Test
void testFailure_noClientId() {
action.paramClientId = Optional.empty();
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, action::run);
assertThat(thrown).hasMessageThat().isEqualTo("clientId must be present");
}
@Test
void testFailure_noRegistrarAccess() {
accessor = AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of());
action =
new RegistryLockGetAction(
Method.GET, response, accessor, authResult, Optional.of("TheRegistrar"));
action.run();
assertThat(response.getStatus()).isEqualTo(SC_FORBIDDEN);
}
@Test
void testSuccess_readOnlyAccessForOtherUsers() {
// If lock is not enabled for a user, this should be read-only
action =
createAction(
user.asBuilder()
.setUserRoles(
user.getUserRoles()
.asBuilder()
.setRegistrarRoles(
ImmutableMap.of("TheRegistrar", RegistrarRole.ACCOUNT_MANAGER))
.build())
.build());
action.run();
assertThat(GSON.fromJson(response.getPayload(), Map.class).get("results"))
.isEqualTo(
ImmutableList.of(
ImmutableMap.of(
"lockEnabledForContact",
false,
"email",
"Marla.Singer@crr.com",
"clientId",
"TheRegistrar",
"locks",
ImmutableList.of())));
}
@Test
void testSuccess_lockAllowedForAdmin() {
// Locks are allowed for admins even when they're not enabled for the registrar
authResult =
AuthResult.createUser(
user.asBuilder()
.setUserRoles(user.getUserRoles().asBuilder().setIsAdmin(true).build())
.build());
accessor =
AuthenticatedRegistrarAccessor.createForTesting(
ImmutableSetMultimap.of(
"TheRegistrar", ADMIN,
"NewRegistrar", OWNER));
action =
new RegistryLockGetAction(
Method.GET, response, accessor, authResult, Optional.of("TheRegistrar"));
action.run();
assertThat(GSON.fromJson(response.getPayload(), Map.class).get("results"))
.isEqualTo(
ImmutableList.of(
ImmutableMap.of(
"lockEnabledForContact",
true,
"email",
"Marla.Singer@crr.com",
"clientId",
"TheRegistrar",
"locks",
ImmutableList.of())));
}
@Test
void testFailure_lockNotAllowedForRegistrar() {
// The UI shouldn't be making requests where lock isn't enabled for this registrar
action =
new RegistryLockGetAction(
Method.GET, response, accessor, authResult, Optional.of("NewRegistrar"));
action.run();
assertThat(response.getStatus()).isEqualTo(SC_INTERNAL_SERVER_ERROR);
}
@Test
void testFailure_accessDenied() {
accessor = AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of());
action =
new RegistryLockGetAction(
Method.GET, response, accessor, authResult, Optional.of("TheRegistrar"));
action.run();
assertThat(response.getStatus()).isEqualTo(SC_FORBIDDEN);
}
@Test
void testFailure_badRegistrar() {
action =
new RegistryLockGetAction(
Method.GET, response, accessor, authResult, Optional.of("SomeBadRegistrar"));
action.run();
assertThat(response.getStatus()).isEqualTo(SC_FORBIDDEN);
}
}

View File

@@ -1,547 +0,0 @@
// Copyright 2020 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.collect.ImmutableSetMultimap.toImmutableSetMultimap;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.SqlHelper.getMostRecentRegistryLockByRepoId;
import static google.registry.testing.SqlHelper.getRegistryLockByVerificationCode;
import static google.registry.testing.SqlHelper.saveRegistryLock;
import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STATUSES;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import google.registry.groups.GmailClient;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.model.domain.Domain;
import google.registry.model.domain.RegistryLock;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.JsonActionRunner;
import google.registry.request.JsonResponse;
import google.registry.request.ResponseImpl;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.FakeClock;
import google.registry.tools.DomainLockUtils;
import google.registry.util.EmailMessage;
import google.registry.util.StringGenerator.Alphabets;
import jakarta.mail.internet.InternetAddress;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
/** Unit tests for {@link RegistryLockPostAction}. */
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
final class RegistryLockPostActionTest {
private static final String EXPECTED_EMAIL_MESSAGE =
"""
Please click the link below to perform the lock / unlock action on domain example.tld. Note: \
this code will expire in one hour.
https://registrarconsole.tld/registry-lock-verify?lockVerificationCode=\
123456789ABCDEFGHJKLMNPQRSTUVWXY""";
private final FakeClock clock = new FakeClock();
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
private User userWithoutPermission;
private User userWithLockPermission;
private Domain domain;
private RegistryLockPostAction action;
@Mock GmailClient gmailClient;
@Mock HttpServletRequest mockRequest;
@Mock HttpServletResponse mockResponse;
@BeforeEach
void beforeEach() throws Exception {
userWithLockPermission =
new User.Builder()
.setEmailAddress("Marla.Singer@crr.com")
.setUserRoles(
new UserRoles.Builder()
.setRegistrarRoles(
ImmutableMap.of(
"TheRegistrar", RegistrarRole.ACCOUNT_MANAGER_WITH_REGISTRY_LOCK))
.build())
.setRegistryLockPassword("hi")
.setRegistryLockEmailAddress("Marla.Singer.RegistryLock@crr.com")
.build();
userWithoutPermission =
new User.Builder()
.setEmailAddress("johndoe@theregistrar.com")
.setUserRoles(new UserRoles())
.build();
createTld("tld");
domain = persistResource(DatabaseHelper.newDomain("example.tld"));
when(mockRequest.getServerName()).thenReturn("registrarconsole.tld");
action = createAction(AuthResult.createUser(userWithLockPermission));
}
@Test
void testSuccess_lock() throws Exception {
Map<String, ?> response = action.handleJsonRequest(lockRequest());
assertSuccess(response, "lock", "Marla.Singer.RegistryLock@crr.com");
}
@Test
void testSuccess_unlock() throws Exception {
saveRegistryLock(createLock().asBuilder().setLockCompletionTime(clock.nowUtc()).build());
persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
Map<String, ?> response = action.handleJsonRequest(unlockRequest());
assertSuccess(response, "unlock", "Marla.Singer.RegistryLock@crr.com");
}
@Test
void testSuccess_unlock_relockDurationSet() throws Exception {
saveRegistryLock(createLock().asBuilder().setLockCompletionTime(clock.nowUtc()).build());
persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
ImmutableMap<String, Object> request =
new ImmutableMap.Builder<String, Object>()
.putAll(unlockRequest())
.put("relockDurationMillis", Duration.standardDays(1).getMillis())
.build();
Map<String, ?> response = action.handleJsonRequest(request);
assertSuccess(response, "unlock", "Marla.Singer.RegistryLock@crr.com");
RegistryLock savedUnlockRequest = getMostRecentRegistryLockByRepoId(domain.getRepoId()).get();
assertThat(savedUnlockRequest.getRelockDuration())
.isEqualTo(Optional.of(Duration.standardDays(1)));
}
@Test
void testSuccess_unlock_adminUnlockingAdmin() throws Exception {
saveRegistryLock(
createLock().asBuilder().isSuperuser(true).setLockCompletionTime(clock.nowUtc()).build());
persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
action =
createAction(
AuthResult.createUser(
userWithoutPermission
.asBuilder()
.setUserRoles(
userWithoutPermission.getUserRoles().asBuilder().setIsAdmin(true).build())
.build()));
Map<String, ?> response = action.handleJsonRequest(unlockRequest());
// we should still email the admin user's email address
assertSuccess(response, "unlock", "johndoe@theregistrar.com");
}
@Test
void testFailure_unlock_noLock() {
persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
Map<String, ?> response = action.handleJsonRequest(unlockRequest());
assertFailureWithMessage(response, "No lock object for domain example.tld");
}
@Test
void testFailure_unlock_alreadyUnlocked() {
persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
saveRegistryLock(
createLock()
.asBuilder()
.setLockCompletionTime(clock.nowUtc())
.setUnlockRequestTime(clock.nowUtc())
.build());
Map<String, ?> response = action.handleJsonRequest(unlockRequest());
assertFailureWithMessage(response, "A pending unlock action already exists for example.tld");
}
@Test
void testFailure_unlock_nonAdminUnlockingAdmin() {
saveRegistryLock(
createLock().asBuilder().isSuperuser(true).setLockCompletionTime(clock.nowUtc()).build());
persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
Map<String, ?> response = action.handleJsonRequest(unlockRequest());
assertFailureWithMessage(
response, "Non-admin user cannot unlock admin-locked domain example.tld");
}
@Test
void testSuccess_adminUser() throws Exception {
// Admin user should be able to lock/unlock regardless -- and we use the admin user's email
action =
createAction(
AuthResult.createUser(
userWithoutPermission
.asBuilder()
.setUserRoles(
userWithoutPermission.getUserRoles().asBuilder().setIsAdmin(true).build())
.build()));
Map<String, ?> response = action.handleJsonRequest(lockRequest());
assertSuccess(response, "lock", "johndoe@theregistrar.com");
}
@Test
void testSuccess_adminUser_doesNotRequirePassword() throws Exception {
action =
createAction(
AuthResult.createUser(
userWithoutPermission
.asBuilder()
.setUserRoles(
userWithoutPermission.getUserRoles().asBuilder().setIsAdmin(true).build())
.build()));
Map<String, ?> response =
action.handleJsonRequest(
ImmutableMap.of(
"registrarId", "TheRegistrar",
"domainName", "example.tld",
"isLock", true));
assertSuccess(response, "lock", "johndoe@theregistrar.com");
}
@Test
void testSuccess_consoleUser() throws Exception {
google.registry.model.console.User consoleUser =
new google.registry.model.console.User.Builder()
.setEmailAddress("johndoe@theregistrar.com")
.setRegistryLockEmailAddress("johndoe.registrylock@theregistrar.com")
.setUserRoles(
new UserRoles.Builder()
.setRegistrarRoles(
ImmutableMap.of(
"TheRegistrar", RegistrarRole.ACCOUNT_MANAGER_WITH_REGISTRY_LOCK))
.build())
.setRegistryLockPassword("hi")
.build();
AuthResult consoleAuthResult = AuthResult.createUser(consoleUser);
action = createAction(consoleAuthResult);
Map<String, ?> response = action.handleJsonRequest(lockRequest());
assertSuccess(response, "lock", "johndoe.registrylock@theregistrar.com");
}
@Test
void testSuccess_consoleUser_admin() throws Exception {
google.registry.model.console.User consoleUser =
new google.registry.model.console.User.Builder()
.setEmailAddress("johndoe@theregistrar.com")
.setUserRoles(new UserRoles.Builder().setIsAdmin(true).build())
.build();
AuthResult consoleAuthResult = AuthResult.createUser(consoleUser);
action = createAction(consoleAuthResult);
Map<String, Object> requestMapWithoutPassword =
ImmutableMap.of(
"isLock", true,
"registrarId", "TheRegistrar",
"domainName", "example.tld");
Map<String, ?> response = action.handleJsonRequest(requestMapWithoutPassword);
assertSuccess(response, "lock", "johndoe@theregistrar.com");
}
@Test
void testFailure_noInput() {
Map<String, ?> response = action.handleJsonRequest(null);
assertFailureWithMessage(response, "Null JSON");
}
@Test
void testFailure_noRegistrarId() {
Map<String, ?> response = action.handleJsonRequest(ImmutableMap.of());
assertFailureWithMessage(response, "Missing key for registrarId");
}
@Test
void testFailure_emptyRegistrarId() {
Map<String, ?> response = action.handleJsonRequest(ImmutableMap.of("registrarId", ""));
assertFailureWithMessage(response, "Missing key for registrarId");
}
@Test
void testFailure_unauthorizedRegistrarId() {
AuthResult authResult = AuthResult.createUser(userWithLockPermission);
action = createAction(authResult, ImmutableSet.of("TheRegistrar"));
Map<String, ?> response =
action.handleJsonRequest(
ImmutableMap.of(
"isLock", true,
"registrarId", "NewRegistrar",
"domainName", "example.tld",
"password", "hi"));
assertFailureWithMessage(response, "TestUserId doesn't have access to registrar NewRegistrar");
}
@Test
void testFailure_incorrectRegistrarIdForDomain() {
persistResource(
domain.asBuilder().setPersistedCurrentSponsorRegistrarId("NewRegistrar").build());
Map<String, ?> response = action.handleJsonRequest(lockRequest());
assertFailureWithMessage(response, "Domain example.tld is not owned by registrar TheRegistrar");
}
@Test
void testFailure_noDomainName() {
Map<String, ?> response =
action.handleJsonRequest(
ImmutableMap.of("registrarId", "TheRegistrar", "password", "hi", "isLock", true));
assertFailureWithMessage(response, "Missing key for domainName");
}
@Test
void testFailure_nonPunycodeDomainName() {
Map<String, ?> response =
action.handleJsonRequest(
ImmutableMap.of(
"isLock", true,
"registrarId", "TheRegistrar",
"domainName", "example.みんな",
"password", "hi"));
assertFailureWithMessage(response, "Domain names can only contain a-z, 0-9, '.' and '-'");
}
@Test
void testFailure_noLockParam() {
Map<String, ?> response =
action.handleJsonRequest(
ImmutableMap.of(
"registrarId", "TheRegistrar",
"domainName", "example.tld",
"password", "hi"));
assertFailureWithMessage(response, "Missing key for isLock");
}
@Test
void testFailure_notAllowedOnRegistrar() {
persistResource(
loadRegistrar("TheRegistrar").asBuilder().setRegistryLockAllowed(false).build());
Map<String, ?> response = action.handleJsonRequest(lockRequest());
assertFailureWithMessage(response, "Registry lock not allowed for registrar TheRegistrar");
}
@Test
void testFailure_noPassword() {
Map<String, ?> response =
action.handleJsonRequest(
ImmutableMap.of(
"registrarId", "TheRegistrar",
"domainName", "example.tld",
"isLock", true));
assertFailureWithMessage(response, "Incorrect registry lock password for user");
}
@Test
void testFailure_notEnabledForRegistrarPoc() {
action = createAction(AuthResult.createUser(userWithoutPermission));
Map<String, ?> response =
action.handleJsonRequest(
ImmutableMap.of(
"registrarId", "TheRegistrar",
"domainName", "example.tld",
"isLock", true,
"password", "hi"));
assertFailureWithMessage(response, "Incorrect registry lock password for user");
}
@Test
void testFailure_badPassword() {
Map<String, ?> response =
action.handleJsonRequest(
ImmutableMap.of(
"registrarId", "TheRegistrar",
"domainName", "example.tld",
"isLock", true,
"password", "badPassword"));
assertFailureWithMessage(response, "Incorrect registry lock password for user");
}
@Test
void testFailure_invalidDomain() {
Map<String, ?> response =
action.handleJsonRequest(
ImmutableMap.of(
"registrarId", "TheRegistrar",
"domainName", "bad.tld",
"isLock", true,
"password", "hi"));
assertFailureWithMessage(response, "Domain doesn't exist");
}
@Test
void testSuccess_previousLockUnlocked() throws Exception {
saveRegistryLock(
createLock()
.asBuilder()
.setLockCompletionTime(clock.nowUtc().minusMinutes(1))
.setUnlockRequestTime(clock.nowUtc().minusMinutes(1))
.setUnlockCompletionTime(clock.nowUtc().minusMinutes(1))
.build());
Map<String, ?> response = action.handleJsonRequest(lockRequest());
assertSuccess(response, "lock", "Marla.Singer.RegistryLock@crr.com");
}
@Test
void testSuccess_previousLockExpired() throws Exception {
RegistryLock previousLock = saveRegistryLock(createLock());
String verificationCode = previousLock.getVerificationCode();
previousLock = getRegistryLockByVerificationCode(verificationCode).get();
clock.setTo(previousLock.getLockRequestTime().plusHours(2));
Map<String, ?> response = action.handleJsonRequest(lockRequest());
assertSuccess(response, "lock", "Marla.Singer.RegistryLock@crr.com");
}
@Test
void testFailure_alreadyPendingLock() {
saveRegistryLock(createLock());
Map<String, ?> response = action.handleJsonRequest(lockRequest());
assertFailureWithMessage(
response, "A pending or completed lock action already exists for example.tld");
}
@Test
void testFailure_alreadyLocked() {
persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
Map<String, ?> response = action.handleJsonRequest(lockRequest());
assertFailureWithMessage(response, "Domain example.tld is already locked");
}
@Test
void testFailure_alreadyUnlocked() {
Map<String, ?> response = action.handleJsonRequest(unlockRequest());
assertFailureWithMessage(response, "Domain example.tld is already unlocked");
}
@Test
void testFailure_consoleUser_wrongPassword_noAdmin() {
google.registry.model.console.User consoleUser =
new google.registry.model.console.User.Builder()
.setEmailAddress("johndoe@theregistrar.com")
.setUserRoles(
new UserRoles.Builder()
.setRegistrarRoles(
ImmutableMap.of(
"TheRegistrar", RegistrarRole.ACCOUNT_MANAGER_WITH_REGISTRY_LOCK))
.build())
.setRegistryLockPassword("hi")
.build();
AuthResult consoleAuthResult = AuthResult.createUser(consoleUser);
action = createAction(consoleAuthResult);
Map<String, ?> response =
action.handleJsonRequest(
ImmutableMap.of(
"registrarId", "TheRegistrar",
"domainName", "example.tld",
"isLock", true,
"password", "badPassword"));
assertFailureWithMessage(response, "Incorrect registry lock password for user");
}
private ImmutableMap<String, Object> lockRequest() {
return fullRequest(true);
}
private ImmutableMap<String, Object> unlockRequest() {
return fullRequest(false);
}
private ImmutableMap<String, Object> fullRequest(boolean lock) {
return ImmutableMap.of(
"isLock", lock,
"registrarId", "TheRegistrar",
"domainName", "example.tld",
"password", "hi");
}
private RegistryLock createLock() {
Domain domain = loadByForeignKey(Domain.class, "example.tld", clock.nowUtc()).get();
return new RegistryLock.Builder()
.setDomainName("example.tld")
.isSuperuser(false)
.setVerificationCode(UUID.randomUUID().toString())
.setRegistrarId("TheRegistrar")
.setRepoId(domain.getRepoId())
.setRegistrarPocId("Marla.Singer@crr.com")
.build();
}
private void assertSuccess(Map<String, ?> response, String lockAction, String recipient)
throws Exception {
assertThat(response)
.containsExactly(
"status", "SUCCESS",
"results", ImmutableList.of(),
"message", String.format("Successful %s", lockAction));
verifyEmail(recipient);
}
private void assertFailureWithMessage(Map<String, ?> response, String message) {
assertThat(response)
.containsExactly("status", "ERROR", "results", ImmutableList.of(), "message", message);
verifyNoMoreInteractions(gmailClient);
}
private void verifyEmail(String recipient) throws Exception {
ArgumentCaptor<EmailMessage> emailCaptor = ArgumentCaptor.forClass(EmailMessage.class);
verify(gmailClient).sendEmail(emailCaptor.capture());
EmailMessage sentMessage = emailCaptor.getValue();
assertThat(sentMessage.subject()).matches("Registry (un)?lock verification");
assertThat(sentMessage.body()).isEqualTo(EXPECTED_EMAIL_MESSAGE);
assertThat(sentMessage.recipients()).containsExactly(new InternetAddress(recipient));
}
private RegistryLockPostAction createAction(AuthResult authResult) {
return createAction(authResult, ImmutableSet.of("TheRegistrar", "NewRegistrar"));
}
private RegistryLockPostAction createAction(
AuthResult authResult, ImmutableSet<String> accessibleRegistrars) {
Role role = authResult.user().get().getUserRoles().isAdmin() ? Role.ADMIN : Role.OWNER;
AuthenticatedRegistrarAccessor registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(
accessibleRegistrars.stream().collect(toImmutableSetMultimap(r -> r, r -> role)));
JsonActionRunner jsonActionRunner =
new JsonActionRunner(ImmutableMap.of(), new JsonResponse(new ResponseImpl(mockResponse)));
DomainLockUtils domainLockUtils =
new DomainLockUtils(
new DeterministicStringGenerator(Alphabets.BASE_58),
"adminreg",
new CloudTasksHelper(clock).getTestCloudTasksUtils());
return new RegistryLockPostAction(
mockRequest, jsonActionRunner, authResult, registrarAccessor, gmailClient, domainLockUtils);
}
}

View File

@@ -1,336 +0,0 @@
// Copyright 2020 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.DatabaseHelper.createTlds;
import static google.registry.testing.DatabaseHelper.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.persistActiveHost;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.SqlHelper.getRegistryLockByVerificationCode;
import static google.registry.testing.SqlHelper.saveRegistryLock;
import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STATUSES;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableMap;
import google.registry.model.billing.BillingBase.Reason;
import google.registry.model.billing.BillingEvent;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.RegistryLock;
import google.registry.model.host.Host;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.tld.Tld;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.auth.AuthResult;
import google.registry.security.XsrfTokenManager;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.tools.DomainLockUtils;
import google.registry.util.StringGenerator;
import google.registry.util.StringGenerator.Alphabets;
import jakarta.servlet.http.HttpServletRequest;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link RegistryLockVerifyAction}. */
final class RegistryLockVerifyActionTest {
private final FakeClock fakeClock = new FakeClock();
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().withClock(fakeClock).buildIntegrationTestExtension();
private final HttpServletRequest request = mock(HttpServletRequest.class);
private final User user =
new User.Builder()
.setEmailAddress("marla.singer@example.com")
.setUserRoles(new UserRoles())
.build();
private final String lockId = "123456789ABCDEFGHJKLMNPQRSTUVWXY";
private final StringGenerator stringGenerator =
new DeterministicStringGenerator(Alphabets.BASE_58);
private FakeResponse response;
private Domain domain;
private AuthResult authResult;
private RegistryLockVerifyAction action;
private CloudTasksHelper cloudTasksHelper = new CloudTasksHelper(fakeClock);
@BeforeEach
void beforeEach() {
createTlds("tld", "net");
Host host = persistActiveHost("ns1.example.net");
domain = persistResource(DatabaseHelper.newDomain("example.tld", host));
when(request.getRequestURI()).thenReturn("https://registry.example/registry-lock-verification");
action = createAction(lockId);
}
@Test
void testSuccess_lockDomain() {
saveRegistryLock(createLock());
action.run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
assertThat(reloadDomain().getStatusValues()).containsExactlyElementsIn(REGISTRY_LOCK_STATUSES);
assertThat(response.getPayload()).contains("Success: lock has been applied to example.tld");
DomainHistory historyEntry =
getOnlyHistoryEntryOfType(domain, HistoryEntry.Type.DOMAIN_UPDATE, DomainHistory.class);
assertThat(historyEntry.getRequestedByRegistrar()).isTrue();
assertThat(historyEntry.getBySuperuser()).isFalse();
assertThat(historyEntry.getReason())
.isEqualTo("Lock of a domain through a RegistryLock operation");
assertBillingEvent(historyEntry);
}
@Test
void testSuccess_unlockDomain() {
action = createAction(lockId);
domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
saveRegistryLock(
createLock()
.asBuilder()
.setLockCompletionTime(fakeClock.nowUtc())
.setUnlockRequestTime(fakeClock.nowUtc())
.build());
action.run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
assertThat(response.getPayload()).contains("Success: unlock has been applied to example.tld");
assertThat(reloadDomain().getStatusValues()).containsNoneIn(REGISTRY_LOCK_STATUSES);
DomainHistory historyEntry =
getOnlyHistoryEntryOfType(domain, HistoryEntry.Type.DOMAIN_UPDATE, DomainHistory.class);
assertThat(historyEntry.getRequestedByRegistrar()).isTrue();
assertThat(historyEntry.getBySuperuser()).isFalse();
assertThat(historyEntry.getReason())
.isEqualTo("Unlock of a domain through a RegistryLock operation");
assertBillingEvent(historyEntry);
}
@Test
void testSuccess_adminLock_createsOnlyHistoryEntry() {
action.authResult =
AuthResult.createUser(
user.asBuilder()
.setUserRoles(user.getUserRoles().asBuilder().setIsAdmin(true).build())
.build());
saveRegistryLock(createLock().asBuilder().isSuperuser(true).build());
action.run();
HistoryEntry historyEntry = getOnlyHistoryEntryOfType(domain, HistoryEntry.Type.DOMAIN_UPDATE);
assertThat(historyEntry.getRequestedByRegistrar()).isFalse();
assertThat(historyEntry.getBySuperuser()).isTrue();
DatabaseHelper.assertNoBillingEvents();
}
@Test
void testFailure_badVerificationCode() {
saveRegistryLock(
createLock().asBuilder().setVerificationCode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").build());
action.run();
assertThat(response.getPayload()).contains("Failed: Invalid verification code");
assertNoDomainChanges();
}
@Test
void testFailure_alreadyVerified() {
saveRegistryLock(createLock().asBuilder().setLockCompletionTime(fakeClock.nowUtc()).build());
action.run();
assertThat(response.getPayload())
.contains("Lock/unlock with code 123456789ABCDEFGHJKLMNPQRSTUVWXY is already completed");
assertNoDomainChanges();
}
@Test
void testFailure_expired() {
saveRegistryLock(createLock());
fakeClock.advanceBy(Duration.standardHours(2));
action.run();
assertThat(response.getPayload())
.contains("Failed: The pending lock has expired; please try again");
assertNoDomainChanges();
}
@Test
void testFailure_nonAdmin_verifyingAdminLock() {
saveRegistryLock(createLock().asBuilder().isSuperuser(true).build());
action.run();
assertThat(response.getPayload()).contains("Failed: Non-admin user cannot complete admin lock");
assertNoDomainChanges();
}
@Test
void testFailure_alreadyUnlocked() {
action = createAction(lockId);
saveRegistryLock(
createLock()
.asBuilder()
.setLockCompletionTime(fakeClock.nowUtc())
.setUnlockRequestTime(fakeClock.nowUtc())
.setUnlockCompletionTime(fakeClock.nowUtc())
.build());
action.run();
assertThat(response.getPayload())
.contains("Lock/unlock with code 123456789ABCDEFGHJKLMNPQRSTUVWXY is already completed");
assertNoDomainChanges();
}
@Test
void testFailure_alreadyLocked() {
saveRegistryLock(createLock());
domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
action.run();
assertThat(response.getPayload()).contains("Failed: Domain example.tld is already locked");
assertNoDomainChanges();
}
@Test
void testFailure_notLoggedIn() {
action.authResult = AuthResult.NOT_AUTHENTICATED;
action.run();
assertThat(response.getStatus()).isEqualTo(SC_UNAUTHORIZED);
assertNoDomainChanges();
}
@Test
void testFailure_doesNotChangeLockObject() {
// A failure when performing actions means that no actions should be taken in the Cloud SQL
// RegistryLock object
RegistryLock lock = createLock();
saveRegistryLock(lock);
// reload the lock to pick up creation time
lock = getRegistryLockByVerificationCode(lock.getVerificationCode()).get();
fakeClock.advanceOneMilli();
domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
action.run();
// we would have failed during the lock acquisition segment of the action
assertThat(response.getPayload()).contains("Failed: Domain example.tld is already locked");
// verify that the changes to the SQL object were rolled back
RegistryLock afterAction = getRegistryLockByVerificationCode(lock.getVerificationCode()).get();
assertThat(afterAction).isEqualTo(lock);
}
@Test
void testFailure_lock_unlock_lockAgain() {
RegistryLock lock = saveRegistryLock(createLock());
action.run();
assertThat(response.getPayload()).contains("Success: lock has been applied to example.tld");
String unlockVerificationCode = "some-unlock-code";
saveRegistryLock(
loadByEntity(lock)
.asBuilder()
.setVerificationCode(unlockVerificationCode)
.setUnlockRequestTime(fakeClock.nowUtc())
.build());
action = createAction(unlockVerificationCode);
action.run();
assertThat(response.getPayload()).contains("Success: unlock has been applied to example.tld");
action = createAction(lockId);
action.run();
assertThat(response.getPayload()).contains("Failed: Invalid verification code");
}
@Test
void testFailure_lock_lockAgain() {
saveRegistryLock(createLock());
action.run();
assertThat(response.getPayload()).contains("Success: lock has been applied to example.tld");
action = createAction(lockId);
action.run();
assertThat(response.getPayload())
.contains("Lock/unlock with code 123456789ABCDEFGHJKLMNPQRSTUVWXY is already completed");
}
@Test
void testFailure_unlock_unlockAgain() {
action = createAction(lockId);
domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
saveRegistryLock(
createLock()
.asBuilder()
.setLockCompletionTime(fakeClock.nowUtc())
.setUnlockRequestTime(fakeClock.nowUtc())
.build());
action.run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
assertThat(response.getPayload()).contains("Success: unlock has been applied to example.tld");
action = createAction(lockId);
action.run();
assertThat(response.getPayload())
.contains("Lock/unlock with code 123456789ABCDEFGHJKLMNPQRSTUVWXY is already completed");
}
private RegistryLock createLock() {
return new RegistryLock.Builder()
.setDomainName("example.tld")
.setRegistrarId("TheRegistrar")
.setRepoId("repoId")
.setRegistrarPocId("marla.singer@example.com")
.isSuperuser(false)
.setVerificationCode(lockId)
.build();
}
private Domain reloadDomain() {
return loadByEntity(domain);
}
private void assertNoDomainChanges() {
assertThat(reloadDomain()).isEqualTo(domain);
}
private void assertBillingEvent(DomainHistory historyEntry) {
DatabaseHelper.assertBillingEvents(
new BillingEvent.Builder()
.setReason(Reason.SERVER_STATUS)
.setTargetId(domain.getForeignKey())
.setRegistrarId(domain.getCurrentSponsorRegistrarId())
.setCost(Tld.get(domain.getTld()).getRegistryLockOrUnlockBillingCost())
.setEventTime(fakeClock.nowUtc())
.setBillingTime(fakeClock.nowUtc())
.setDomainHistory(historyEntry)
.build());
}
private RegistryLockVerifyAction createAction(String lockVerificationCode) {
response = new FakeResponse();
RegistryLockVerifyAction action =
new RegistryLockVerifyAction(
new DomainLockUtils(
stringGenerator, "adminreg", cloudTasksHelper.getTestCloudTasksUtils()),
lockVerificationCode);
authResult = AuthResult.createUser(user);
action.req = request;
action.response = response;
action.authResult = authResult;
action.logoFilename = "logo.png";
action.productName = "Nomulus";
action.analyticsConfig = ImmutableMap.of("googleAnalyticsId", "sampleId");
action.xsrfTokenManager = new XsrfTokenManager(new FakeClock());
return action;
}
}

View File

@@ -1,200 +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.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.testing.CertificateSamples.SAMPLE_CERT;
import static google.registry.testing.CertificateSamples.SAMPLE_CERT2;
import static google.registry.testing.CertificateSamples.SAMPLE_CERT2_HASH;
import static google.registry.testing.CertificateSamples.SAMPLE_CERT3;
import static google.registry.testing.CertificateSamples.SAMPLE_CERT3_HASH;
import static google.registry.testing.CertificateSamples.SAMPLE_CERT_HASH;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import google.registry.model.registrar.Registrar;
import java.util.Map;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
/**
* Unit tests for security_settings.js use of {@link RegistrarSettingsAction}.
*
* <p>The default read and session validation tests are handled by the superclass.
*/
class SecuritySettingsTest extends RegistrarSettingsActionTestCase {
@Test
void testPost_updateCert_success() throws Exception {
clock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
Registrar modified =
loadRegistrar(CLIENT_ID)
.asBuilder()
.setClientCertificate(SAMPLE_CERT3, clock.nowUtc())
.build();
Map<String, Object> modifiedJsonMap = modified.toJsonMap();
Map<String, Object> response =
action.handleJsonRequest(
ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", modifiedJsonMap));
modifiedJsonMap.put("lastUpdateTime", clock.nowUtc().toString());
assertThat(response).containsEntry("status", "SUCCESS");
assertThat(response).containsEntry("results", ImmutableList.of(modifiedJsonMap));
assertAboutImmutableObjects()
.that(loadRegistrar(CLIENT_ID))
.isEqualExceptFields(modified, "updateTimestamp");
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
verifyNotificationEmailsSent();
}
@Test
void testPost_updateCert_failure() {
Map<String, Object> reqJson = loadRegistrar(CLIENT_ID).toJsonMap();
reqJson.put("clientCertificate", "BLAH");
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", reqJson));
assertThat(response).containsEntry("status", "ERROR");
assertThat(response).containsEntry("message", "Invalid X.509 PEM certificate");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormFieldException");
}
@Test
void testPost_updateCertWithViolations_failure() {
clock.setTo(DateTime.parse("2055-11-01T00:00:00Z"));
Map<String, Object> reqJson = loadRegistrar(CLIENT_ID).toJsonMap();
reqJson.put("clientCertificate", SAMPLE_CERT);
Map<String, Object> response =
action.handleJsonRequest(
ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", reqJson));
assertThat(response).containsEntry("status", "ERROR");
assertThat(response)
.containsEntry(
"message",
"Certificate is expired.\nCertificate validity period is too long; it must be less"
+ " than or equal to 398 days.");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: IllegalArgumentException");
}
@Test
void testPost_updateFailoverCertWithViolations_failure() {
clock.setTo(DateTime.parse("2055-11-01T00:00:00Z"));
Map<String, Object> reqJson = loadRegistrar(CLIENT_ID).toJsonMap();
reqJson.put("failoverClientCertificate", SAMPLE_CERT2);
Map<String, Object> response =
action.handleJsonRequest(
ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", reqJson));
assertThat(response).containsEntry("status", "ERROR");
assertThat(response)
.containsEntry(
"message",
"Certificate is expired.\nCertificate validity period is too long; it must be less"
+ " than or equal to 398 days.");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: IllegalArgumentException");
}
@Test
void testChangeCertificates() throws Exception {
clock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
Map<String, Object> jsonMap = loadRegistrar(CLIENT_ID).toJsonMap();
jsonMap.put("clientCertificate", SAMPLE_CERT3);
jsonMap.put("failoverClientCertificate", null);
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
"op", "update", "id", CLIENT_ID, "args", jsonMap));
assertThat(response).containsEntry("status", "SUCCESS");
Registrar registrar = loadRegistrar(CLIENT_ID);
assertThat(registrar.getClientCertificate()).hasValue(SAMPLE_CERT3);
assertThat(registrar.getClientCertificateHash()).hasValue(SAMPLE_CERT3_HASH);
assertThat(registrar.getFailoverClientCertificate()).isEmpty();
assertThat(registrar.getFailoverClientCertificateHash()).isEmpty();
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
verifyNotificationEmailsSent();
}
@Test
void testChangeFailoverCertificate() throws Exception {
clock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
Map<String, Object> jsonMap = loadRegistrar(CLIENT_ID).toJsonMap();
jsonMap.put("failoverClientCertificate", SAMPLE_CERT3);
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
"op", "update", "id", CLIENT_ID, "args", jsonMap));
assertThat(response).containsEntry("status", "SUCCESS");
Registrar registrar = loadRegistrar(CLIENT_ID);
assertThat(registrar.getFailoverClientCertificate()).hasValue(SAMPLE_CERT3);
assertThat(registrar.getFailoverClientCertificateHash()).hasValue(SAMPLE_CERT3_HASH);
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
verifyNotificationEmailsSent();
}
@Test
void testEmptyOrNullCertificate_doesNotClearOutCurrentOne() {
Registrar initialRegistrar =
persistResource(
loadRegistrar(CLIENT_ID)
.asBuilder()
.setClientCertificate(SAMPLE_CERT, START_OF_TIME)
.setFailoverClientCertificate(SAMPLE_CERT2, START_OF_TIME)
.build());
Map<String, Object> jsonMap = initialRegistrar.toJsonMap();
jsonMap.put("clientCertificate", null);
jsonMap.put("failoverClientCertificate", "");
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
"op", "update", "id", CLIENT_ID, "args", jsonMap));
assertThat(response).containsEntry("status", "SUCCESS");
Registrar registrar = loadRegistrar(CLIENT_ID);
assertThat(registrar.getClientCertificate()).hasValue(SAMPLE_CERT);
assertThat(registrar.getClientCertificateHash()).hasValue(SAMPLE_CERT_HASH);
assertThat(registrar.getFailoverClientCertificate()).hasValue(SAMPLE_CERT2);
assertThat(registrar.getFailoverClientCertificateHash()).hasValue(SAMPLE_CERT2_HASH);
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
}
@Test
void testToJsonMap_containsCertificate() {
Map<String, Object> jsonMap =
loadRegistrar(CLIENT_ID)
.asBuilder()
.setClientCertificate(SAMPLE_CERT2, START_OF_TIME)
.build()
.toJsonMap();
assertThat(jsonMap).containsEntry("clientCertificate", SAMPLE_CERT2);
assertThat(jsonMap).containsEntry("clientCertificateHash", SAMPLE_CERT2_HASH);
}
@Test
void testToJsonMap_containsFailoverCertificate() {
Map<String, Object> jsonMap =
loadRegistrar(CLIENT_ID)
.asBuilder()
.setFailoverClientCertificate(SAMPLE_CERT2, START_OF_TIME)
.build()
.toJsonMap();
assertThat(jsonMap).containsEntry("failoverClientCertificate", SAMPLE_CERT2);
assertThat(jsonMap).containsEntry("failoverClientCertificateHash", SAMPLE_CERT2_HASH);
}
}

View File

@@ -1,131 +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.truth.Truth.assertThat;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
import java.util.Map;
import org.junit.jupiter.api.Test;
/**
* Unit tests for security_settings.js use of {@link RegistrarSettingsAction}.
*
* <p>The default read and session validation tests are handled by the superclass.
*/
class WhoisSettingsTest extends RegistrarSettingsActionTestCase {
@Test
void testPost_update_success() throws Exception {
Registrar modified =
loadRegistrar(CLIENT_ID)
.asBuilder()
.setEmailAddress("hello.kitty@example.com")
.setPhoneNumber("+1.2125650000")
.setFaxNumber("+1.2125650001")
.setUrl("http://acme.com/")
.setWhoisServer("ns1.foo.bar")
.setLocalizedAddress(
new RegistrarAddress.Builder()
.setStreet(ImmutableList.of("76 Ninth Avenue", "Eleventh Floor"))
.setCity("New York")
.setState("NY")
.setZip("10009")
.setCountryCode("US")
.build())
.build();
Map<String, Object> response =
action.handleJsonRequest(
ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", modified.toJsonMap()));
assertThat(response.get("status")).isEqualTo("SUCCESS");
assertThat(response.get("results")).isEqualTo(ImmutableList.of(modified.toJsonMap()));
assertThat(loadRegistrar(CLIENT_ID)).isEqualTo(modified);
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
verifyNotificationEmailsSent();
}
@Test
void testPost_badUsStateCode_returnsFormFieldError() {
Registrar modified =
loadRegistrar(CLIENT_ID)
.asBuilder()
.setEmailAddress("hello.kitty@example.com")
.setPhoneNumber("+1.2125650000")
.setFaxNumber("+1.2125650001")
.setLocalizedAddress(
new RegistrarAddress.Builder()
.setStreet(ImmutableList.of("76 Ninth Avenue", "Eleventh Floor"))
.setCity("New York")
.setState("ZZ")
.setZip("10009")
.setCountryCode("US")
.build())
.build();
Map<String, Object> response =
action.handleJsonRequest(
ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", modified.toJsonMap()));
assertThat(response.get("status")).isEqualTo("ERROR");
assertThat(response.get("field")).isEqualTo("localizedAddress.state");
assertThat(response.get("message")).isEqualTo("Unknown US state code.");
assertThat(loadRegistrar(CLIENT_ID)).isNotEqualTo(modified);
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormFieldException");
}
@Test
void testPost_badAddress_returnsFormFieldError() {
Registrar modified =
loadRegistrar(CLIENT_ID)
.asBuilder()
.setEmailAddress("hello.kitty@example.com")
.setPhoneNumber("+1.2125650000")
.setFaxNumber("+1.2125650001")
.setLocalizedAddress(
new RegistrarAddress.Builder()
.setStreet(ImmutableList.of("76 Ninth Avenue", "lol".repeat(200)))
.setCity("New York")
.setState("NY")
.setZip("10009")
.setCountryCode("US")
.build())
.build();
Map<String, Object> response =
action.handleJsonRequest(
ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", modified.toJsonMap()));
assertThat(response.get("status")).isEqualTo("ERROR");
assertThat(response.get("field")).isEqualTo("localizedAddress.street[1]");
assertThat((String) response.get("message"))
.contains("Number of characters (600) not in range");
assertThat(loadRegistrar(CLIENT_ID)).isNotEqualTo(modified);
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormFieldException");
}
@Test
void testPost_badWhoisServer_returnsFormFieldError() {
Registrar modified =
loadRegistrar(CLIENT_ID).asBuilder().setWhoisServer("tears@dry.tragical.lol").build();
Map<String, Object> response =
action.handleJsonRequest(
ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", modified.toJsonMap()));
assertThat(response.get("status")).isEqualTo("ERROR");
assertThat(response.get("field")).isEqualTo("whoisServer");
assertThat(response.get("message")).isEqualTo("Not a valid hostname.");
assertThat(loadRegistrar(CLIENT_ID)).isNotEqualTo(modified);
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormFieldException");
}
}

View File

@@ -1,70 +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.webdriver;
import static google.registry.model.console.RegistrarRole.ACCOUNT_MANAGER;
import static google.registry.server.Fixture.BASIC;
import static google.registry.server.Route.route;
import com.google.common.collect.ImmutableMap;
import google.registry.module.frontend.FrontendServlet;
import google.registry.server.RegistryTestServer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junitpioneer.jupiter.RetryingTest;
import org.openqa.selenium.By;
/** Registrar Console Screenshot Differ tests. */
@Disabled
public class OteSetupConsoleScreenshotTest extends WebDriverTestCase {
@RegisterExtension
final TestServerExtension server =
new TestServerExtension.Builder()
.setRunfiles(RegistryTestServer.RUNFILES)
.setRoutes(route("/registrar-ote-setup", FrontendServlet.class))
.setFixtures(BASIC)
.setEmail("Marla.Singer@google.com")
.setRegistryLockEmail("Marla.Singer.RegistryLock@google.com")
.build();
@BeforeEach
void beforeEach() {
server.setRegistrarRoles(ImmutableMap.of("TheRegistrar", ACCOUNT_MANAGER));
}
@RetryingTest(3)
void get_owner_fails() throws Throwable {
driver.get(server.getUrl("/registrar-ote-setup"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("unauthorized");
}
@RetryingTest(3)
void get_admin_succeeds() throws Throwable {
server.setIsAdmin(true);
driver.get(server.getUrl("/registrar-ote-setup"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("formEmpty");
driver.findElement(By.id("clientId")).sendKeys("acmereg");
driver.findElement(By.id("email")).sendKeys("acmereg@registry.example");
driver.findElement(By.id("password")).sendKeys("StRoNgPaSsWoRd");
driver.diffPage("formFilled");
driver.findElement(By.id("submit-button")).click();
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("oteResult");
}
}

View File

@@ -22,7 +22,7 @@ for more information on using webdriver.
# Generate the golden image for a certain test
$ ./gradlew :core:generateGoldenImages \
--tests "google.registry.webdriver.OteSetupConsoleScreenshotTest.get_owner_fails"
--tests "google.registry.webdriver.[MyTestClass.my_test]"
```
3. Screenshot differs by X pixels
* If you made any change to the existing test and expected to affect the web page,

View File

@@ -1,545 +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.webdriver;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.JpaTransactionManagerExtension.makeRegistrar2;
import static google.registry.persistence.transaction.JpaTransactionManagerExtension.makeRegistrarContact2;
import static google.registry.persistence.transaction.JpaTransactionManagerExtension.makeRegistrarContact3;
import static google.registry.server.Fixture.BASIC;
import static google.registry.server.Route.route;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.SqlHelper.saveRegistryLock;
import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STATUSES;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.model.common.FeatureFlag;
import google.registry.model.console.RegistrarRole;
import google.registry.model.domain.Domain;
import google.registry.model.domain.RegistryLock;
import google.registry.model.registrar.RegistrarBase.State;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.module.frontend.FrontendServlet;
import google.registry.server.RegistryTestServer;
import google.registry.testing.CertificateSamples;
import google.registry.testing.DatabaseHelper;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junitpioneer.jupiter.RetryingTest;
import org.openqa.selenium.By;
import org.openqa.selenium.Dimension;
/** Registrar Console Screenshot Differ tests. */
@Disabled
class RegistrarConsoleScreenshotTest extends WebDriverTestCase {
@RegisterExtension
final TestServerExtension server =
new TestServerExtension.Builder()
.setRunfiles(RegistryTestServer.RUNFILES)
.setRoutes(
route("/registrar", FrontendServlet.class),
route("/registrar-ote-status", FrontendServlet.class),
route("/registrar-settings", FrontendServlet.class),
route("/registry-lock-get", FrontendServlet.class),
route("/registry-lock-verify", FrontendServlet.class))
.setFixtures(BASIC)
.setEmail("Marla.Singer@crr.com") // from makeRegistrarContact3
.setRegistryLockEmail("Marla.Singer.RegistryLock@crr.com")
.build();
@BeforeEach
void beforeEach() {
server.setRegistrarRoles(ImmutableMap.of("TheRegistrar", RegistrarRole.ACCOUNT_MANAGER));
}
@RetryingTest(3)
void index_owner() throws Throwable {
driver.get(server.getUrl("/registrar"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("page");
}
/** Admins have an extra "admin" tab. */
@RetryingTest(3)
void index_adminAndOwner() throws Throwable {
server.setIsAdmin(true);
driver.get(server.getUrl("/registrar"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("page");
}
/** Admins who aren't owners still have all the tabs. */
@RetryingTest(3)
void index_admin() throws Throwable {
server.setIsAdmin(true);
// To make sure we're only ADMIN (and not also "OWNER"), we switch to the NewRegistrar we
// aren't in the contacts of
driver.get(server.getUrl("/registrar?clientId=NewRegistrar"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("page");
}
@RetryingTest(3)
void contactUs() throws Throwable {
driver.get(server.getUrl("/registrar#contact-us"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("page");
}
@RetryingTest(3)
void settingsContact() throws Throwable {
driver.get(server.getUrl("/registrar#contact-settings"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("page");
}
@RetryingTest(3)
void settingsContactItem() throws Throwable {
driver.get(server.getUrl("/registrar#contact-settings/johndoe@theregistrar.com"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("page");
}
/** Admins shouldn't have the "edit" button */
@RetryingTest(3)
void settingsContactItem_asAdmin() throws Throwable {
server.setIsAdmin(true);
driver.get(
server.getUrl(
"/registrar?clientId=NewRegistrar#contact-settings/janedoe@theregistrar.com"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("page");
}
@RetryingTest(3)
void settingsContactEdit() throws Throwable {
driver.manage().window().setSize(new Dimension(1050, 2000));
driver.get(server.getUrl("/registrar#contact-settings/johndoe@theregistrar.com"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.waitForDisplayedElement(By.id("reg-app-btn-edit")).click();
driver.diffPage("page");
}
@RetryingTest(3)
void settingsContactEdit_setRegistryLockPassword() throws Throwable {
persistResource(makeRegistrar2().asBuilder().setRegistryLockAllowed(true).build());
persistResource(
makeRegistrarContact2()
.asBuilder()
.setRegistryLockEmailAddress("johndoe.registrylock@example.com")
.setAllowedToSetRegistryLockPassword(true)
.build());
driver.manage().window().setSize(new Dimension(1050, 2000));
driver.get(server.getUrl("/registrar#contact-settings/johndoe@theregistrar.com"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.waitForDisplayedElement(By.id("reg-app-btn-edit")).click();
// The password should show as dots when the user types it in
driver.findElement(By.id("contacts[1].registryLockPassword")).sendKeys("password");
driver.diffPage("page_with_password");
// Show the password if the user clicks the button
driver.findElement(By.id("showOrHideRegistryLockPassword")).click();
Thread.sleep(5);
driver.diffPage("page_with_shown_password");
// Hide it again
driver.findElement(By.id("showOrHideRegistryLockPassword")).click();
Thread.sleep(5);
driver.diffPage("page_with_password_after_hide");
// Now click the Save button and wait for another Edit button to show up
driver.waitForRefreshedElementAfterAction(
() -> driver.waitForDisplayedElement(By.id("reg-app-btn-save")).click(),
By.id("reg-app-btn-edit"));
driver.diffPage("contact_view");
RegistrarPoc contact =
loadRegistrar("TheRegistrar").getContacts().stream()
.filter(c -> "johndoe@theregistrar.com".equals(c.getEmailAddress()))
.findFirst()
.get();
assertThat(contact.verifyRegistryLockPassword("password")).isTrue();
assertThat(contact.getRegistryLockEmailAddress())
.isEqualTo(Optional.of("johndoe.registrylock@example.com"));
}
@RetryingTest(3)
void settingsContactEdit_setRegistryLockPassword_alreadySet() throws Throwable {
persistResource(
makeRegistrarContact2()
.asBuilder()
.setAllowedToSetRegistryLockPassword(true)
.setRegistryLockPassword("hi")
.build());
driver.manage().window().setSize(new Dimension(1050, 2000));
driver.get(server.getUrl("/registrar#contact-settings/johndoe@theregistrar.com"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.waitForDisplayedElement(By.id("reg-app-btn-edit")).click();
driver.diffPage("page");
}
@RetryingTest(3)
void settingsContactEdit_setRegistryLockPassword_notAllowedForContact() throws Throwable {
persistResource(makeRegistrar2().asBuilder().setRegistryLockAllowed(true).build());
driver.manage().window().setSize(new Dimension(1050, 2000));
driver.get(server.getUrl("/registrar#contact-settings/johndoe@theregistrar.com"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.waitForDisplayedElement(By.id("reg-app-btn-edit")).click();
driver.diffPage("page");
}
@RetryingTest(3)
void settingsContactAdd() throws Throwable {
driver.manage().window().setSize(new Dimension(1050, 2000));
driver.get(server.getUrl("/registrar#contact-settings"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.waitForDisplayedElement(By.id("reg-app-btn-add")).click();
// Attempt to fix flaky tests. The going theory is that the click button CSS animation needs to
// finish before the screenshot is captured.
Thread.sleep(250);
driver.diffPage("page");
}
@RetryingTest(3)
void settingsAdmin_whenAdmin() throws Throwable {
server.setIsAdmin(true);
driver.manage().window().setSize(new Dimension(1050, 2000));
// To make sure we're only ADMIN (and not also "OWNER"), we switch to the NewRegistrar we
// aren't in the contacts of
driver.get(server.getUrl("/registrar?clientId=NewRegistrar#admin-settings"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("view");
driver.waitForDisplayedElement(By.id("reg-app-btn-edit")).click();
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("edit");
}
/**
* Makes sure the user can't "manually" enter the admin-settings.
*
* <p>Users don't have the "admin setting" tab (see the {@link #index_owner()} test). However, we
* also want to make sure that if a user enters the URL fragment manually they don't get the admin
* page.
*
* <p>Note that failure here is a UI issue only and not a security issue, since any "admin" change
* a user tries to do is validated on the backend and fails for non-admins.
*/
@RetryingTest(3)
void settingsAdmin_whenNotAdmin_showsHome() throws Throwable {
driver.manage().window().setSize(new Dimension(1050, 2000));
driver.get(server.getUrl("/registrar#admin-settings"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("view");
}
@RetryingTest(3)
void getOteStatus_noButtonWhenReal() throws Exception {
server.setIsAdmin(true);
driver.get(server.getUrl("/registrar#admin-settings"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("result");
}
@RetryingTest(3)
void getOteStatus_notCompleted() throws Exception {
server.setIsAdmin(true);
driver.get(server.getUrl("/registrar?clientId=oteunfinished-1#admin-settings"));
driver.findElement(By.id("btn-ote-status")).click();
driver.findElement(By.id("ote-results-table")).click();
// the 'hover' styling takes a bit to go away--sleep, so we don't flake
Thread.sleep(250);
driver.diffPage("result");
}
@RetryingTest(3)
void getOteStatus_completed() throws Exception {
server.setIsAdmin(true);
driver.get(server.getUrl("/registrar?clientId=otefinished-1#admin-settings"));
driver.waitForDisplayedElement(By.id("btn-ote-status"));
driver.diffPage("before_click");
driver.findElement(By.id("btn-ote-status")).click();
driver.findElement(By.id("ote-results-table")).click();
// the 'hover' styling takes a bit to go away--sleep, so we don't flake
Thread.sleep(250);
driver.diffPage("result");
}
@RetryingTest(3)
void settingsSecurity() throws Throwable {
driver.manage().window().setSize(new Dimension(1050, 2000));
driver.get(server.getUrl("/registrar#security-settings"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("view");
driver.waitForDisplayedElement(By.id("reg-app-btn-edit")).click();
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("edit");
}
/** Admins shouldn't have the "edit" button */
@RetryingTest(3)
void settingsSecurity_asAdmin() throws Throwable {
server.setIsAdmin(true);
driver.manage().window().setSize(new Dimension(1050, 2000));
driver.get(server.getUrl("/registrar?clientId=NewRegistrar#security-settings"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("view");
}
@RetryingTest(3)
void settingsSecurityWithCerts() throws Throwable {
persistResource(
loadRegistrar("TheRegistrar")
.asBuilder()
.setClientCertificate(CertificateSamples.SAMPLE_CERT, START_OF_TIME)
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT2, START_OF_TIME)
.build());
driver.manage().window().setSize(new Dimension(1050, 2000));
driver.get(server.getUrl("/registrar#security-settings"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("view");
driver.waitForDisplayedElement(By.id("reg-app-btn-edit")).click();
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("edit");
}
@RetryingTest(3)
void index_registrarDisabled() throws Throwable {
persistResource(loadRegistrar("TheRegistrar").asBuilder().setState(State.DISABLED).build());
driver.get(server.getUrl("/registrar"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("view");
}
@RetryingTest(3)
void settingsWhois() throws Throwable {
driver.get(server.getUrl("/registrar#whois-settings"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("page");
}
@RetryingTest(3)
void settingsWhoisEdit() throws Throwable {
driver.manage().window().setSize(new Dimension(1050, 2000));
driver.get(server.getUrl("/registrar#whois-settings"));
driver.waitForDisplayedElement(By.id("reg-app-btn-edit")).click();
driver.waitForDisplayedElement(By.id("reg-app-btn-save"));
driver.diffPage("page");
}
@RetryingTest(3)
void settingsWhoisEditError() throws Throwable {
driver.manage().window().setSize(new Dimension(1050, 2000));
driver.get(server.getUrl("/registrar#whois-settings"));
driver.waitForDisplayedElement(By.id("reg-app-btn-edit")).click();
driver.setFormFieldsById(ImmutableMap.of("faxNumber", "cat"));
driver.waitForDisplayedElement(By.id("reg-app-btn-save")).click();
// After the click, a div element without id would show up with an error message.
driver.waitForElementWithCondition(
By.tagName("div"), e -> e.getText().startsWith("Must be a valid +E.164 phone number,"));
driver.diffPage("page");
}
@RetryingTest(3)
void indexPage_smallScrolledDown() throws Throwable {
driver.manage().window().setSize(new Dimension(600, 300));
driver.get(server.getUrl("/registrar"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.executeScript("document.getElementById('reg-content-and-footer').scrollTop = 200");
Thread.sleep(500);
driver.diffPage("page");
}
@RetryingTest(3)
void registryLockVerify_success() throws Throwable {
String lockVerificationCode = "f1be78a2-2d61-458c-80f0-9dd8f2f8625f";
createTld("tld");
persistResource(DatabaseHelper.newDomain("example-lock.tld"));
saveRegistryLock(
new RegistryLock.Builder()
.setRegistrarPocId("johndoe@theregistrar.com")
.setRepoId("repoId")
.setRegistrarId("TheRegistrar")
.setVerificationCode("f1be78a2-2d61-458c-80f0-9dd8f2f8625f")
.isSuperuser(false)
.setDomainName("example-lock.tld")
.build());
driver.get(
server.getUrl(
"/registry-lock-verify?isLock=true&lockVerificationCode=" + lockVerificationCode));
driver.waitForDisplayedElement(By.id("reg-content"));
driver.diffPage("page");
}
@RetryingTest(3)
void registryLockVerify_unknownLock() throws Throwable {
driver.get(server.getUrl("/registry-lock-verify?isLock=true&lockVerificationCode=asdfasdf"));
driver.waitForDisplayedElement(By.id("reg-content"));
driver.diffPage("page");
}
@RetryingTest(3)
void registryLock_empty() throws Throwable {
server.setRegistrarRoles(
ImmutableMap.of("TheRegistrar", RegistrarRole.ACCOUNT_MANAGER_WITH_REGISTRY_LOCK));
driver.get(server.getUrl("/registrar?clientId=TheRegistrar#registry-lock"));
driver.waitForDisplayedElement(By.tagName("h2"));
driver.diffPage("page");
}
@RetryingTest(3)
void registryLock_notAllowed() throws Throwable {
persistResource(makeRegistrar2().asBuilder().setRegistryLockAllowed(false).build());
driver.get(server.getUrl("/registrar?clientId=TheRegistrar#registry-lock"));
driver.waitForDisplayedElement(By.tagName("h2"));
driver.diffPage("page");
}
@RetryingTest(3)
void registryLock_nonEmpty() throws Throwable {
createDomainAndSaveLock();
server.setRegistrarRoles(
ImmutableMap.of("TheRegistrar", RegistrarRole.ACCOUNT_MANAGER_WITH_REGISTRY_LOCK));
driver.get(server.getUrl("/registrar#registry-lock"));
driver.waitForDisplayedElement(By.tagName("h2"));
driver.diffPage("page");
}
@RetryingTest(3)
void registryLock_nonEmpty_admin() throws Throwable {
createTld("tld");
// expired unlock request
Domain expiredUnlockRequestDomain = persistActiveDomain("expiredunlock.tld");
saveRegistryLock(
createRegistryLock(expiredUnlockRequestDomain)
.asBuilder()
.setLockCompletionTime(START_OF_TIME.minusDays(1))
.setUnlockRequestTime(START_OF_TIME.minusDays(1))
.build());
server.setRegistrarRoles(
ImmutableMap.of("TheRegistrar", RegistrarRole.ACCOUNT_MANAGER_WITH_REGISTRY_LOCK));
Domain domain = persistActiveDomain("example.tld");
saveRegistryLock(createRegistryLock(domain).asBuilder().isSuperuser(true).build());
Domain otherDomain = persistActiveDomain("otherexample.tld");
saveRegistryLock(createRegistryLock(otherDomain));
// include one pending-lock domain
Domain pendingDomain = persistActiveDomain("pending.tld");
saveRegistryLock(
new RegistryLock.Builder()
.setVerificationCode(UUID.randomUUID().toString())
.isSuperuser(false)
.setRegistrarId("TheRegistrar")
.setRegistrarPocId("Marla.Singer@crr.com")
.setDomainName("pending.tld")
.setRepoId(pendingDomain.getRepoId())
.build());
// and one pending-unlock domain
Domain pendingUnlockDomain =
persistResource(
DatabaseHelper.newDomain("pendingunlock.tld")
.asBuilder()
.setStatusValues(REGISTRY_LOCK_STATUSES)
.build());
saveRegistryLock(
new RegistryLock.Builder()
.setVerificationCode(UUID.randomUUID().toString())
.isSuperuser(false)
.setRegistrarId("TheRegistrar")
.setRegistrarPocId("Marla.Singer@crr.com")
.setDomainName(pendingUnlockDomain.getDomainName())
.setRepoId(pendingUnlockDomain.getRepoId())
.setLockCompletionTime(START_OF_TIME)
.setUnlockRequestTime(START_OF_TIME)
.build());
driver.get(server.getUrl("/registrar#registry-lock"));
driver.waitForDisplayedElement(By.tagName("h2"));
driver.diffPage("page");
}
@RetryingTest(3)
void registryLock_unlockModal() throws Throwable {
createDomainAndSaveLock();
server.setRegistrarRoles(
ImmutableMap.of("TheRegistrar", RegistrarRole.ACCOUNT_MANAGER_WITH_REGISTRY_LOCK));
driver.get(server.getUrl("/registrar#registry-lock"));
driver.waitForDisplayedElement(By.tagName("h2"));
driver.findElement(By.id("button-unlock-example.tld")).click();
driver.waitForDisplayedElement(By.className("modal-content"));
driver.findElement(By.id("domain-lock-password")).sendKeys("password");
driver.diffPage("page");
}
@RetryingTest(3)
void registryLock_lockModal() throws Throwable {
server.setIsAdmin(true);
createTld("tld");
persistActiveDomain("example.tld");
server.setRegistrarRoles(
ImmutableMap.of("TheRegistrar", RegistrarRole.ACCOUNT_MANAGER_WITH_REGISTRY_LOCK));
driver.get(server.getUrl("/registrar#registry-lock"));
driver.waitForDisplayedElement(By.tagName("h2"));
driver.findElement(By.id("button-lock-domain")).click();
driver.waitForDisplayedElement(By.className("modal-content"));
driver.findElement(By.id("domain-lock-input-value")).sendKeys("somedomain.tld");
driver.diffPage("page");
}
@RetryingTest(3)
void registryLock_notAllowedForUser() throws Throwable {
persistResource(
makeRegistrarContact3().asBuilder().setAllowedToSetRegistryLockPassword(true).build());
driver.get(server.getUrl("/registrar?clientId=TheRegistrar#registry-lock"));
driver.waitForDisplayedElement(By.tagName("h2"));
driver.diffPage("page");
}
@RetryingTest(3)
void deprecationWarning_active() throws Throwable {
persistResource(
new FeatureFlag.Builder()
.setFeatureName(FeatureFlag.FeatureName.NEW_CONSOLE)
.setStatusMap(ImmutableSortedMap.of(START_OF_TIME, FeatureFlag.FeatureStatus.ACTIVE))
.build());
driver.get(server.getUrl("/registrar"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("page");
}
private static void createDomainAndSaveLock() {
createTld("tld");
Domain domain = persistActiveDomain("example.tld");
saveRegistryLock(createRegistryLock(domain));
}
private static RegistryLock createRegistryLock(Domain domain) {
return new RegistryLock.Builder()
.setVerificationCode(UUID.randomUUID().toString())
.isSuperuser(false)
.setRegistrarId("TheRegistrar")
.setRegistrarPocId("Marla.Singer@crr.com")
.setLockCompletionTime(START_OF_TIME)
.setDomainName(domain.getDomainName())
.setRepoId(domain.getRepoId())
.build();
}
}

View File

@@ -1,205 +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.webdriver;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.server.Fixture.BASIC;
import static google.registry.server.Route.route;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import google.registry.model.console.RegistrarRole;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.module.frontend.FrontendServlet;
import google.registry.server.RegistryTestServer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junitpioneer.jupiter.RetryingTest;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
/** WebDriver tests for Registrar Console UI. */
@Disabled
public class RegistrarConsoleWebTest extends WebDriverTestCase {
@RegisterExtension
public final TestServerExtension server =
new TestServerExtension.Builder()
.setRunfiles(RegistryTestServer.RUNFILES)
.setRoutes(
route("/registrar", FrontendServlet.class),
route("/registrar-settings", FrontendServlet.class))
.setFixtures(BASIC)
.setEmail("Marla.Singer@crr.com")
.setRegistryLockEmail("Marla.Singer.RegistryLock@crr.com")
.build();
@BeforeEach
void beforeEach() {
server.setRegistrarRoles(ImmutableMap.of("TheRegistrar", RegistrarRole.ACCOUNT_MANAGER));
}
/** Checks the identified element has the given text content. */
void assertEltText(String eltId, String eltValue) {
assertThat(driver.findElement(By.id(eltId)).getText()).isEqualTo(eltValue);
}
/** Checks that an element is visible. */
void assertEltVisible(String eltId) throws Throwable {
assertThat(driver.waitForDisplayedElement(By.id(eltId)).isDisplayed()).isTrue();
}
/** Checks that an element is invisible. */
void assertEltInvisible(String eltId) throws Throwable {
assertThat(driver.waitForElement(By.id(eltId)).isDisplayed()).isFalse();
}
/** Checks that searching with the given By produces at least one element with the given text. */
void assertEltTextPresent(By by, String toFind) {
assertThat(driver.findElements(by).stream().map(WebElement::getText).anyMatch(toFind::equals))
.isTrue();
}
@RetryingTest(3)
void testEditButtonsVisibility_owner() throws Throwable {
driver.get(server.getUrl("/registrar#whois-settings"));
assertEltVisible("reg-app-btns-edit");
assertEltInvisible("reg-app-btn-add");
driver.get(server.getUrl("/registrar#security-settings"));
assertEltVisible("reg-app-btns-edit");
assertEltInvisible("reg-app-btn-add");
driver.get(server.getUrl("/registrar#contact-settings"));
assertEltInvisible("reg-app-btns-edit");
assertEltVisible("reg-app-btn-add");
driver.get(server.getUrl("/registrar#resources"));
assertEltVisible("reg-app-btns-edit");
assertEltInvisible("reg-app-btn-add");
}
@RetryingTest(3)
void testEditButtonsVisibility_adminAndOwner() throws Throwable {
server.setIsAdmin(true);
driver.get(server.getUrl("/registrar#whois-settings"));
assertEltVisible("reg-app-btns-edit");
assertEltInvisible("reg-app-btn-add");
driver.get(server.getUrl("/registrar#security-settings"));
assertEltVisible("reg-app-btns-edit");
assertEltInvisible("reg-app-btn-add");
driver.get(server.getUrl("/registrar#contact-settings"));
assertEltInvisible("reg-app-btns-edit");
assertEltVisible("reg-app-btn-add");
driver.get(server.getUrl("/registrar#admin-settings"));
assertEltVisible("reg-app-btns-edit");
assertEltInvisible("reg-app-btn-add");
driver.get(server.getUrl("/registrar#resources"));
assertEltVisible("reg-app-btns-edit");
assertEltInvisible("reg-app-btn-add");
}
@RetryingTest(3)
void testEditButtonsVisibility_adminOnly() throws Throwable {
server.setIsAdmin(true);
// To make sure we're only ADMIN (and not also "OWNER"), we switch to the NewRegistrar for
// which we don't have a role.
driver.get(server.getUrl("/registrar?clientId=NewRegistrar#whois-settings"));
assertEltInvisible("reg-app-btns-edit");
assertEltInvisible("reg-app-btn-add");
driver.get(server.getUrl("/registrar?clientId=NewRegistrar#security-settings"));
assertEltInvisible("reg-app-btns-edit");
assertEltInvisible("reg-app-btn-add");
driver.get(server.getUrl("/registrar?clientId=NewRegistrar#contact-settings"));
assertEltInvisible("reg-app-btns-edit");
assertEltInvisible("reg-app-btn-add");
driver.get(server.getUrl("/registrar?clientId=NewRegistrar#admin-settings"));
assertEltVisible("reg-app-btns-edit");
assertEltInvisible("reg-app-btn-add");
driver.get(server.getUrl("/registrar?clientId=NewRegistrar#resources"));
assertEltInvisible("reg-app-btns-edit");
assertEltInvisible("reg-app-btn-add");
}
@RetryingTest(3)
void testWhoisSettingsEdit() throws Throwable {
driver.get(server.getUrl("/registrar#whois-settings"));
driver.waitForDisplayedElement(By.id("reg-app-btn-edit")).click();
driver.setFormFieldsById(
new ImmutableMap.Builder<String, String>()
.put("emailAddress", "test1@example.com")
.put("registrarId", "ignored")
.put("whoisServer", "foo.bar.baz")
.put("url", "blah.blar")
.put("phoneNumber", "+1.2125650000")
.put("faxNumber", "+1.2125650001")
.put("localizedAddress.street[0]", "Bőulevard őf")
.put("localizedAddress.street[1]", "Brőken Dreams")
.put("localizedAddress.street[2]", "")
.put("localizedAddress.city", "New York")
.put("localizedAddress.state", "NY")
.put("localizedAddress.zip", "10011")
.put("localizedAddress.countryCode", "US")
.build());
driver.findElement(By.id("reg-app-btn-save")).click();
Thread.sleep(1000);
Registrar registrar = loadRegistrar("TheRegistrar");
assertThat(registrar.getEmailAddress()).isEqualTo("test1@example.com");
assertThat(registrar.getRegistrarId()).isEqualTo("TheRegistrar");
assertThat(registrar.getWhoisServer()).isEqualTo("foo.bar.baz");
assertThat(registrar.getUrl()).isEqualTo("blah.blar");
assertThat(registrar.getPhoneNumber()).isEqualTo("+1.2125650000");
assertThat(registrar.getFaxNumber()).isEqualTo("+1.2125650001");
RegistrarAddress address = registrar.getLocalizedAddress();
assertThat(address.getStreet()).containsExactly("Bőulevard őf", "Brőken Dreams");
assertThat(address.getCity()).isEqualTo("New York");
assertThat(address.getState()).isEqualTo("NY");
assertThat(address.getZip()).isEqualTo("10011");
assertThat(address.getCountryCode()).isEqualTo("US");
}
@RetryingTest(3)
void testContactSettingsView() throws Throwable {
driver.get(server.getUrl("/registrar#contact-settings"));
driver.waitForDisplayedElement(By.id("reg-app-btn-add"));
ImmutableList<RegistrarPoc> contacts = loadRegistrar("TheRegistrar").getContacts().asList();
for (RegistrarPoc contact : contacts) {
assertEltTextPresent(By.id("contacts[0].name"), contact.getName());
assertEltTextPresent(By.id("contacts[0].emailAddress"), contact.getEmailAddress());
assertEltTextPresent(By.id("contacts[0].phoneNumber"), contact.getPhoneNumber());
}
}
@RetryingTest(3)
void testSecuritySettingsView() throws Throwable {
driver.get(server.getUrl("/registrar#security-settings"));
driver.waitForDisplayedElement(By.id("reg-app-btn-edit"));
Registrar registrar = loadRegistrar("TheRegistrar");
assertThat(driver.findElement(By.id("phonePasscode")).getAttribute("value"))
.isEqualTo(registrar.getPhonePasscode());
}
}

View File

@@ -1,81 +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.webdriver;
import static google.registry.model.console.RegistrarRole.ACCOUNT_MANAGER;
import static google.registry.server.Fixture.BASIC;
import static google.registry.server.Route.route;
import com.google.common.collect.ImmutableMap;
import google.registry.module.frontend.FrontendServlet;
import google.registry.server.RegistryTestServer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junitpioneer.jupiter.RetryingTest;
import org.openqa.selenium.By;
/** Registrar Console Screenshot Differ tests. */
class RegistrarCreateConsoleScreenshotTest extends WebDriverTestCase {
@RegisterExtension
final TestServerExtension server =
new TestServerExtension.Builder()
.setRunfiles(RegistryTestServer.RUNFILES)
.setRoutes(route("/registrar-create", FrontendServlet.class))
.setFixtures(BASIC)
.setEmail("Marla.Singer@google.com")
.setRegistryLockEmail("Marla.Singer.RegistryLock@google.com")
.build();
@BeforeEach
void beforeEach() {
server.setRegistrarRoles(ImmutableMap.of("TheRegistrar", ACCOUNT_MANAGER));
}
@RetryingTest(3)
void get_owner_fails() throws Throwable {
driver.get(server.getUrl("/registrar-create"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("unauthorized");
}
@RetryingTest(3)
void get_admin_succeeds() throws Throwable {
server.setIsAdmin(true);
driver.get(server.getUrl("/registrar-create"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("formEmpty");
driver.findElement(By.id("clientId")).sendKeys("my-name");
driver.findElement(By.id("name")).sendKeys("registrar name");
driver
.findElement(By.id("billingAccount"))
.sendKeys(""
+ "USD=12345678-abcd-1234-5678-cba987654321\n"
+ "JPY=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee");
driver.findElement(By.id("driveId")).sendKeys("drive-id");
driver.findElement(By.id("ianaId")).sendKeys("15263");
driver.findElement(By.id("referralEmail")).sendKeys("email@icann.example");
driver.findElement(By.id("consoleUserEmail")).sendKeys("my-name@registry.example");
driver.findElement(By.id("street1")).sendKeys("123 Street st.");
driver.findElement(By.id("city")).sendKeys("Citysville");
driver.findElement(By.id("countryCode")).sendKeys("fr");
driver.findElement(By.id("password")).sendKeys("StRoNgPaSsWoRd");
driver.findElement(By.id("passcode")).sendKeys("01234");
driver.diffPage("formFilled");
driver.findElement(By.id("submit-button")).click();
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("createResult");
}
}

View File

@@ -1,13 +1,5 @@
SERVICE PATH CLASS METHODS OK MIN USER_POLICY
FRONTEND /_dr/epp EppTlsAction POST n APP ADMIN
FRONTEND /registrar ConsoleUiAction GET n USER PUBLIC
FRONTEND /registrar-create ConsoleRegistrarCreatorAction POST,GET n USER PUBLIC
FRONTEND /registrar-ote-setup ConsoleOteSetupAction POST,GET n USER PUBLIC
FRONTEND /registrar-ote-status OteStatusAction POST n USER PUBLIC
FRONTEND /registrar-settings RegistrarSettingsAction POST n USER PUBLIC
FRONTEND /registry-lock-get RegistryLockGetAction GET n USER PUBLIC
FRONTEND /registry-lock-post RegistryLockPostAction POST n USER PUBLIC
FRONTEND /registry-lock-verify RegistryLockVerifyAction GET n USER PUBLIC
CONSOLE /console-api/domain ConsoleDomainGetAction GET n USER PUBLIC
CONSOLE /console-api/domain-list ConsoleDomainListAction GET n USER PUBLIC
CONSOLE /console-api/dum-download ConsoleDumDownloadAction GET n USER PUBLIC

View File

@@ -1,71 +0,0 @@
{
"op": "update",
"id": "TheRegistrar",
"args": {
"registrarId": "theregistrar",
"driveFolderId": null,
"registrarName": "The Registrar",
"lastUpdateTime": "%LAST_UPDATE_TIME%",
"state": "ACTIVE",
"type": "REAL",
"contacts": [
{
"visibleInWhoisAsAdmin": true,
"faxNumber": null,
"phoneNumber": "+1.2345678901",
"name": "Extra Terrestrial",
"visibleInWhoisAsTech": false,
"emailAddress": "etphonehome@example.com",
"loginEmailAddress": null,
"types": "ADMIN,BILLING,TECH,WHOIS"
},
{
"visibleInWhoisAsAdmin": false,
"faxNumber": null,
"phoneNumber": "+1.2128675309",
"name": "Marla Singer",
"visibleInWhoisAsTech": false,
"emailAddress": "Marla.Singer@crr.com",
"registryLockEmailAddress": "Marla.Singer.RegistryLock@crr.com",
"loginEmailAddress": "Marla.Singer@crr.com",
"types": "TECH"
}
],
"allowedTlds": [
"currenttld"
],
"clientCertificateHash": null,
"faxNumber": "",
"ianaIdentifier": "1",
"phoneNumber": "+1.2223335555",
"internationalizedAddress": null,
"whoisServer": "foo.bar.baz",
"creationTime": "2014-04-15T21:57:54.765Z",
"clientCertificate": null,
"emailAddress": "thase@the.registrar",
"ipAddressAllowList": [
"1.1.1.1\/32",
"2.2.2.2\/32",
"4.4.4.4\/32"
],
"localizedAddress": {
"street": [
"123 Street Rd",
"Ste 156",
""
],
"city": "New York",
"state": "NY",
"zip": "10011",
"countryCode": "US"
},
"billingIdentifier": null,
"url": null,
"icannReferralEmail": "asdf@asdf.com",
"phonePasscode": null,
"url": "http://my.new.url",
"blockPremiumNames": false,
"lastCertificateUpdateTime": null,
"e": false
}
}

View File

@@ -1,70 +0,0 @@
{
"op": "update",
"id": "TheRegistrar",
"args": {
"registrarId": "theregistrar",
"driveFolderId": null,
"registrarName": "The Registrar",
"lastUpdateTime": "%LAST_UPDATE_TIME%",
"state": "ACTIVE",
"type": "REAL",
"contacts": [
{
"visibleInWhoisAsAdmin": true,
"faxNumber": null,
"phoneNumber": null,
"name": "Extra Terrestrial",
"visibleInWhoisAsTech": false,
"emailAddress": "etphonehome@example.com",
"loginEmailAddress": null,
"types": "ADMIN,BILLING,TECH,WHOIS"
},
{
"visibleInWhoisAsAdmin": true,
"faxNumber": null,
"phoneNumber": null,
"name": "E.T.",
"visibleInWhoisAsTech": false,
"emailAddress": "etphonehome@example.com",
"loginEmailAddress": null,
"types": "MARKETING"
}
],
"allowedTlds": [
"currenttld"
],
"clientCertificateHash": null,
"faxNumber": "",
"ianaIdentifier": "1",
"phoneNumber": "+1.2223335555",
"internationalizedAddress": null,
"whoisServer": "foo.bar.baz",
"creationTime": "2014-04-15T21:57:54.765Z",
"clientCertificate": null,
"emailAddress": "thase@the.registrar",
"ipAddressAllowList": [
"1.1.1.1\/32",
"2.2.2.2\/32",
"4.4.4.4\/32"
],
"localizedAddress": {
"street": [
"123 Street Rd",
"Ste 156",
""
],
"city": "New York",
"state": "NY",
"zip": "10011",
"countryCode": "US"
},
"billingIdentifier": null,
"url": null,
"icannReferralEmail": "asdf@asdf.com",
"phonePasscode": null,
"referralUrl": "",
"blockPremiumNames": false,
"lastCertificateUpdateTime": null,
"e": false
}
}

View File

@@ -1,19 +0,0 @@
The following changes were made in registry unittest environment to the registrar TheRegistrar by user user@email.com:
whoisServer: null -> foo.bar.baz
ipAddressAllowList: null -> [1.1.1.1/32, 2.2.2.2/32, 4.4.4.4/32]
localizedAddress.street.0: 123 Example Bőulevard -> 123 Street Rd
localizedAddress.street.1: null -> Ste 156
localizedAddress.city: Williamsburg -> New York
localizedAddress.zip: 11211 -> 10011
phoneNumber: +1.2223334444 -> +1.2223335555
emailAddress: the.registrar@example.com -> thase@the.registrar
url: http://my.fake.url -> http://my.new.url
contacts:
ADDED:
{name=Extra Terrestrial, emailAddress=etphonehome@example.com, registrarId=TheRegistrar, registryLockEmailAddress=null, phoneNumber=+1.2345678901, faxNumber=null, types=[ADMIN, BILLING, TECH, WHOIS], visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false}
REMOVED:
{name=John Doe, emailAddress=johndoe@theregistrar.com, registrarId=TheRegistrar, registryLockEmailAddress=null, phoneNumber=+1.1234567890, faxNumber=null, types=[ADMIN], visibleInWhoisAsAdmin=false, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false}
FINAL CONTENTS:
{name=Extra Terrestrial, emailAddress=etphonehome@example.com, registrarId=TheRegistrar, registryLockEmailAddress=null, phoneNumber=+1.2345678901, faxNumber=null, types=[ADMIN, BILLING, TECH, WHOIS], visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false},
{name=Marla Singer, emailAddress=Marla.Singer@crr.com, registrarId=TheRegistrar, registryLockEmailAddress=Marla.Singer.RegistryLock@crr.com, phoneNumber=+1.2128675309, faxNumber=null, types=[TECH], visibleInWhoisAsAdmin=false, visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false, allowedToSetRegistryLockPassword=false}

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