Delete legacy console (#2579)
@@ -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'
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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. -->
|
||||
|
||||
@@ -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. -->
|
||||
|
||||
@@ -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. -->
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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
|
||||
|
||||
|
Before Width: | Height: | Size: 673 B |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 138 B |
|
Before Width: | Height: | Size: 138 B |
|
Before Width: | Height: | Size: 184 B |
|
Before Width: | Height: | Size: 1016 B |
|
Before Width: | Height: | Size: 309 B |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 535 B |
|
Before Width: | Height: | Size: 327 B |
@@ -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 |
@@ -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 |
@@ -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 |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 556 B |
|
Before Width: | Height: | Size: 480 B |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,6 +0,0 @@
|
||||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<command>
|
||||
<logout/>
|
||||
<clTRID>ABC-12345</clTRID>
|
||||
</command>
|
||||
</epp>
|
||||
@@ -1,6 +0,0 @@
|
||||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<command>
|
||||
<poll op="req"/>
|
||||
<clTRID>ABC-12345</clTRID>
|
||||
</command>
|
||||
</epp>
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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&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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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')");
|
||||
}
|
||||
}
|
||||
@@ -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 'XYZ'");
|
||||
}
|
||||
|
||||
@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'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')");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 9.9 KiB |