diff --git a/appengine_war.gradle b/appengine_war.gradle index f2239220e..45d61cc29 100644 --- a/appengine_war.gradle +++ b/appengine_war.gradle @@ -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' diff --git a/core/build.gradle b/core/build.gradle index 9bfeabbbf..b91332767 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -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 } diff --git a/core/gradle.lockfile b/core/gradle.lockfile index 85412b903..55960e891 100644 --- a/core/gradle.lockfile +++ b/core/gradle.lockfile @@ -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 diff --git a/core/src/main/java/google/registry/config/RegistryConfig.java b/core/src/main/java/google/registry/config/RegistryConfig.java index 6b12f80e4..884e3213b 100644 --- a/core/src/main/java/google/registry/config/RegistryConfig.java +++ b/core/src/main/java/google/registry/config/RegistryConfig.java @@ -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 provideAnalyticsConfig(RegistryConfigSettings config) { - // Can't be an ImmutableMap because it may contain null values. - HashMap 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. * diff --git a/core/src/main/java/google/registry/config/RegistryConfigSettings.java b/core/src/main/java/google/registry/config/RegistryConfigSettings.java index 781eba482..47c0e4bde 100644 --- a/core/src/main/java/google/registry/config/RegistryConfigSettings.java +++ b/core/src/main/java/google/registry/config/RegistryConfigSettings.java @@ -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. */ diff --git a/core/src/main/java/google/registry/config/files/default-config.yaml b/core/src/main/java/google/registry/config/files/default-config.yaml index 2ca2405ff..a5abfe222 100644 --- a/core/src/main/java/google/registry/config/files/default-config.yaml +++ b/core/src/main/java/google/registry/config/files/default-config.yaml @@ -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. diff --git a/core/src/main/java/google/registry/env/alpha/default/WEB-INF/appengine-web.xml b/core/src/main/java/google/registry/env/alpha/default/WEB-INF/appengine-web.xml index 9e48b2ade..aef316a24 100644 --- a/core/src/main/java/google/registry/env/alpha/default/WEB-INF/appengine-web.xml +++ b/core/src/main/java/google/registry/env/alpha/default/WEB-INF/appengine-web.xml @@ -24,9 +24,7 @@ - - - - + + diff --git a/core/src/main/java/google/registry/env/common/default/WEB-INF/web.xml b/core/src/main/java/google/registry/env/common/default/WEB-INF/web.xml index 4eb1b3832..747b42402 100644 --- a/core/src/main/java/google/registry/env/common/default/WEB-INF/web.xml +++ b/core/src/main/java/google/registry/env/common/default/WEB-INF/web.xml @@ -18,58 +18,6 @@ /_dr/epp - - - frontend-servlet - /registrar-xhr - - - - - frontend-servlet - /registrar - - - - - frontend-servlet - /registrar-create - - - - - frontend-servlet - /registrar-ote-setup - - - - - frontend-servlet - /registrar-ote-status - - - - - frontend-servlet - /registrar-settings - - - - - frontend-servlet - /registry-lock-get - - - - frontend-servlet - /registry-lock-post - - - - frontend-servlet - /registry-lock-verify - - frontend-servlet @@ -84,47 +32,18 @@ Admin-only internal section. Requests for paths covered by the URL patterns below will be checked for a logged-in user account that's allowed to access the AppEngine admin console (NOTE: this includes Editor/Viewer permissions in addition to Owner and the new IAM - App Engine Admin role. See https://cloud.google.com/appengine/docs/java/access-control + App Engine Admin role. See https://cloud.google.com/appengine/docs/java/access-control specifically the "Access handlers that have a login:admin restriction" line.) 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. - /_ah/* - - - /assets/sources/* - - - /assets/js/registrar_bin.js.map - /assets/js/registrar_dbg.js - /assets/css/registrar_dbg.css - admin - - - - CONFIDENTIAL - - - - - - Registrar console - - Registrar console requires user login. This is in addition to the - code-level "requireLogin" configuration on individual @Actions. - - /registrar* - - - * - CONFIDENTIAL diff --git a/core/src/main/java/google/registry/env/crash/default/WEB-INF/appengine-web.xml b/core/src/main/java/google/registry/env/crash/default/WEB-INF/appengine-web.xml index 4be188269..e6dc4b529 100644 --- a/core/src/main/java/google/registry/env/crash/default/WEB-INF/appengine-web.xml +++ b/core/src/main/java/google/registry/env/crash/default/WEB-INF/appengine-web.xml @@ -24,9 +24,6 @@ - - - - + diff --git a/core/src/main/java/google/registry/env/local/default/WEB-INF/appengine-web.xml b/core/src/main/java/google/registry/env/local/default/WEB-INF/appengine-web.xml index 0dccbdd7d..5799e282d 100644 --- a/core/src/main/java/google/registry/env/local/default/WEB-INF/appengine-web.xml +++ b/core/src/main/java/google/registry/env/local/default/WEB-INF/appengine-web.xml @@ -28,16 +28,7 @@ - - - - - - - - - - + diff --git a/core/src/main/java/google/registry/env/production/default/WEB-INF/appengine-web.xml b/core/src/main/java/google/registry/env/production/default/WEB-INF/appengine-web.xml index a2a649c74..7816aa150 100644 --- a/core/src/main/java/google/registry/env/production/default/WEB-INF/appengine-web.xml +++ b/core/src/main/java/google/registry/env/production/default/WEB-INF/appengine-web.xml @@ -23,10 +23,7 @@ - - - - + diff --git a/core/src/main/java/google/registry/env/qa/default/WEB-INF/appengine-web.xml b/core/src/main/java/google/registry/env/qa/default/WEB-INF/appengine-web.xml index 5525b99ab..854c9a21d 100644 --- a/core/src/main/java/google/registry/env/qa/default/WEB-INF/appengine-web.xml +++ b/core/src/main/java/google/registry/env/qa/default/WEB-INF/appengine-web.xml @@ -27,10 +27,7 @@ - - - - + diff --git a/core/src/main/java/google/registry/env/sandbox/default/WEB-INF/appengine-web.xml b/core/src/main/java/google/registry/env/sandbox/default/WEB-INF/appengine-web.xml index c5b18dac4..0e1d65048 100644 --- a/core/src/main/java/google/registry/env/sandbox/default/WEB-INF/appengine-web.xml +++ b/core/src/main/java/google/registry/env/sandbox/default/WEB-INF/appengine-web.xml @@ -23,10 +23,7 @@ - - - - + diff --git a/core/src/main/java/google/registry/model/common/FeatureFlag.java b/core/src/main/java/google/registry/model/common/FeatureFlag.java index 3386bf1d8..bd682348b 100644 --- a/core/src/main/java/google/registry/model/common/FeatureFlag.java +++ b/core/src/main/java/google/registry/model/common/FeatureFlag.java @@ -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. */ diff --git a/core/src/main/java/google/registry/module/frontend/FrontendRequestComponent.java b/core/src/main/java/google/registry/module/frontend/FrontendRequestComponent.java index fc1b9e964..aece4d002 100644 --- a/core/src/main/java/google/registry/module/frontend/FrontendRequestComponent.java +++ b/core/src/main/java/google/registry/module/frontend/FrontendRequestComponent.java @@ -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 diff --git a/core/src/main/java/google/registry/ui/assets/images/ajax-loader.gif b/core/src/main/java/google/registry/ui/assets/images/ajax-loader.gif deleted file mode 100644 index d0bce1542..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/ajax-loader.gif and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/android_sad.png b/core/src/main/java/google/registry/ui/assets/images/android_sad.png deleted file mode 100644 index 166d49fa7..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/android_sad.png and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/disclosure_arrow_dk_grey.png b/core/src/main/java/google/registry/ui/assets/images/disclosure_arrow_dk_grey.png deleted file mode 100644 index 8d3d124fb..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/disclosure_arrow_dk_grey.png and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/disclosure_arrow_dk_grey_down.png b/core/src/main/java/google/registry/ui/assets/images/disclosure_arrow_dk_grey_down.png deleted file mode 100644 index 88f9bde93..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/disclosure_arrow_dk_grey_down.png and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/disclosure_arrow_dk_grey_up_down.png b/core/src/main/java/google/registry/ui/assets/images/disclosure_arrow_dk_grey_up_down.png deleted file mode 100644 index d81da2539..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/disclosure_arrow_dk_grey_up_down.png and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/explore_24.png b/core/src/main/java/google/registry/ui/assets/images/explore_24.png deleted file mode 100644 index a2c32fa19..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/explore_24.png and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/folder.png b/core/src/main/java/google/registry/ui/assets/images/folder.png deleted file mode 100644 index 770c6d90b..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/folder.png and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/google_registry.png b/core/src/main/java/google/registry/ui/assets/images/google_registry.png deleted file mode 100644 index 45682d07c..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/google_registry.png and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/homeContact.png b/core/src/main/java/google/registry/ui/assets/images/homeContact.png deleted file mode 100644 index 6fba005ec..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/homeContact.png and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/homeResources.png b/core/src/main/java/google/registry/ui/assets/images/homeResources.png deleted file mode 100644 index 019a5ee68..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/homeResources.png and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/homeSettings.png b/core/src/main/java/google/registry/ui/assets/images/homeSettings.png deleted file mode 100644 index f17d17edd..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/homeSettings.png and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/ic_contacts_blue_12.png b/core/src/main/java/google/registry/ui/assets/images/ic_contacts_blue_12.png deleted file mode 100644 index 8d718a9d1..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/ic_contacts_blue_12.png and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/ic_contacts_blue_8.png b/core/src/main/java/google/registry/ui/assets/images/ic_contacts_blue_8.png deleted file mode 100644 index 67723facd..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/ic_contacts_blue_8.png and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/icons/svg/Gcomm/search.svg b/core/src/main/java/google/registry/ui/assets/images/icons/svg/Gcomm/search.svg deleted file mode 100644 index 0358af3fe..000000000 --- a/core/src/main/java/google/registry/ui/assets/images/icons/svg/Gcomm/search.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - -]> - - - - - - diff --git a/core/src/main/java/google/registry/ui/assets/images/icons/svg/search.svg b/core/src/main/java/google/registry/ui/assets/images/icons/svg/search.svg deleted file mode 100644 index e948041f7..000000000 --- a/core/src/main/java/google/registry/ui/assets/images/icons/svg/search.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - -]> - - - - - - diff --git a/core/src/main/java/google/registry/ui/assets/images/icons/svg/settings.svg b/core/src/main/java/google/registry/ui/assets/images/icons/svg/settings.svg deleted file mode 100644 index c8b484f61..000000000 --- a/core/src/main/java/google/registry/ui/assets/images/icons/svg/settings.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - -]> - - - - - - diff --git a/core/src/main/java/google/registry/ui/assets/images/loader1x.gif b/core/src/main/java/google/registry/ui/assets/images/loader1x.gif deleted file mode 100644 index 88cd65c49..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/loader1x.gif and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/loader4x.gif b/core/src/main/java/google/registry/ui/assets/images/loader4x.gif deleted file mode 100644 index 1e8e40af0..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/loader4x.gif and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/logo.png b/core/src/main/java/google/registry/ui/assets/images/logo.png deleted file mode 100644 index 0531700ca..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/logo.png and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/logo_sm.gif b/core/src/main/java/google/registry/ui/assets/images/logo_sm.gif deleted file mode 100644 index 3b252c49e..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/logo_sm.gif and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/offline_lightning.png b/core/src/main/java/google/registry/ui/assets/images/offline_lightning.png deleted file mode 100644 index b63dc4e6d..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/offline_lightning.png and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/ribbon_certified-128.png b/core/src/main/java/google/registry/ui/assets/images/ribbon_certified-128.png deleted file mode 100644 index 1516c5824..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/ribbon_certified-128.png and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/ribbon_certified-64.png b/core/src/main/java/google/registry/ui/assets/images/ribbon_certified-64.png deleted file mode 100644 index 6d9b88dec..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/ribbon_certified-64.png and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/visibleOff_16.png b/core/src/main/java/google/registry/ui/assets/images/visibleOff_16.png deleted file mode 100644 index f83bab423..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/visibleOff_16.png and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/assets/images/visibleOn_16.png b/core/src/main/java/google/registry/ui/assets/images/visibleOn_16.png deleted file mode 100644 index 01085f983..000000000 Binary files a/core/src/main/java/google/registry/ui/assets/images/visibleOn_16.png and /dev/null differ diff --git a/core/src/main/java/google/registry/ui/epptmpl/contact_info.xml b/core/src/main/java/google/registry/ui/epptmpl/contact_info.xml deleted file mode 100644 index 6c9de20ad..000000000 --- a/core/src/main/java/google/registry/ui/epptmpl/contact_info.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - sh8013 - - 2fooBAR - - - - ABC-12345 - - diff --git a/core/src/main/java/google/registry/ui/epptmpl/contact_transfer_request.xml b/core/src/main/java/google/registry/ui/epptmpl/contact_transfer_request.xml deleted file mode 100644 index dddbf6786..000000000 --- a/core/src/main/java/google/registry/ui/epptmpl/contact_transfer_request.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - sh8013 - - 2fooBAR - - - - ABC-12345 - - diff --git a/core/src/main/java/google/registry/ui/epptmpl/domain_create_no_hosts_or_dsdata.xml b/core/src/main/java/google/registry/ui/epptmpl/domain_create_no_hosts_or_dsdata.xml deleted file mode 100644 index 41c6a586e..000000000 --- a/core/src/main/java/google/registry/ui/epptmpl/domain_create_no_hosts_or_dsdata.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - example.tld - 2 - jd1234 - sh8013 - sh8013 - - 2fooBAR - - - - ABC-12345 - - diff --git a/core/src/main/java/google/registry/ui/epptmpl/domain_delete.xml b/core/src/main/java/google/registry/ui/epptmpl/domain_delete.xml deleted file mode 100644 index a0f5cc345..000000000 --- a/core/src/main/java/google/registry/ui/epptmpl/domain_delete.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - example.tld - - - ABC-12345 - - diff --git a/core/src/main/java/google/registry/ui/epptmpl/domain_update_restore_request.xml b/core/src/main/java/google/registry/ui/epptmpl/domain_update_restore_request.xml deleted file mode 100644 index 124bc0b89..000000000 --- a/core/src/main/java/google/registry/ui/epptmpl/domain_update_restore_request.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - example.tld - - - - - - - - - ABC-12345 - - diff --git a/core/src/main/java/google/registry/ui/epptmpl/login_valid.xml b/core/src/main/java/google/registry/ui/epptmpl/login_valid.xml deleted file mode 100644 index 210c07aa3..000000000 --- a/core/src/main/java/google/registry/ui/epptmpl/login_valid.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - NewRegistrar - foo-BAR2 - - 1.0 - en - - - urn:ietf:params:xml:ns:host-1.0 - urn:ietf:params:xml:ns:domain-1.0 - urn:ietf:params:xml:ns:contact-1.0 - - - ABC-12345 - - diff --git a/core/src/main/java/google/registry/ui/epptmpl/logout.xml b/core/src/main/java/google/registry/ui/epptmpl/logout.xml deleted file mode 100644 index 1a0687d52..000000000 --- a/core/src/main/java/google/registry/ui/epptmpl/logout.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - ABC-12345 - - diff --git a/core/src/main/java/google/registry/ui/epptmpl/poll.xml b/core/src/main/java/google/registry/ui/epptmpl/poll.xml deleted file mode 100644 index f6f78893f..000000000 --- a/core/src/main/java/google/registry/ui/epptmpl/poll.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - ABC-12345 - - diff --git a/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java b/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java index 5a02ae2dd..8625bef98 100644 --- a/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java +++ b/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java @@ -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); diff --git a/core/src/main/java/google/registry/ui/server/console/ConsoleRegistryLockAction.java b/core/src/main/java/google/registry/ui/server/console/ConsoleRegistryLockAction.java index 760deab20..b5d3878d5 100644 --- a/core/src/main/java/google/registry/ui/server/console/ConsoleRegistryLockAction.java +++ b/core/src/main/java/google/registry/ui/server/console/ConsoleRegistryLockAction.java @@ -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; diff --git a/core/src/main/java/google/registry/ui/server/console/settings/ContactAction.java b/core/src/main/java/google/registry/ui/server/console/settings/ContactAction.java index cf2e9e96b..1a183aaa4 100644 --- a/core/src/main/java/google/registry/ui/server/console/settings/ContactAction.java +++ b/core/src/main/java/google/registry/ui/server/console/settings/ContactAction.java @@ -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 oldContacts = registrar.getContacts(); - // TODO: @ptkach - refactor out contacts update functionality after RegistrarSettingsAction is - // deprecated ImmutableSet updatedContacts = - RegistrarSettingsAction.readContacts( - registrar, - oldContacts, - Collections.singletonMap( - "contacts", - contacts.get().stream().map(RegistrarPoc::toJsonMap).collect(toImmutableList()))); + RegistrarFormFields.getRegistrarContactBuilders( + oldContacts, + Collections.singletonMap( + "contacts", + 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 existingContacts, ImmutableSet updatedContacts) { + // Check that no two contacts use the same email address. + Set 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 oldContactsByType = HashMultimap.create(); + for (RegistrarPoc contact : existingContacts) { + for (Type t : contact.getTypes()) { + oldContactsByType.put(t, contact); + } + } + Multimap 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 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 existingContacts, ImmutableSet 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 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). + * + *

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 getDomainWhoisVisibleAbuseContact( + Set 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 oldContactsByType, + Multimap 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())); + } + } } diff --git a/core/src/main/java/google/registry/ui/server/registrar/ConsoleOteSetupAction.java b/core/src/main/java/google/registry/ui/server/registrar/ConsoleOteSetupAction.java deleted file mode 100644 index 69c3f2075..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/ConsoleOteSetupAction.java +++ /dev/null @@ -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. - * - *

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. - * - *

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 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 clientId; - - @Inject - @Parameter("email") - Optional email; - - @Inject - @Parameter("password") - Optional optionalPassword; - - @Inject CloudTasksUtils cloudTasksUtils; - - @Inject IamClient iamClient; - - @Inject - @Config("gSuiteConsoleUserGroupEmailAddress") - Optional maybeGroupEmailAddress; - - @Inject - ConsoleOteSetupAction() {} - - @Override - public void runAfterLogin(Map 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 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 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 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 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()); - } -} diff --git a/core/src/main/java/google/registry/ui/server/registrar/ConsoleRegistrarCreatorAction.java b/core/src/main/java/google/registry/ui/server/registrar/ConsoleRegistrarCreatorAction.java deleted file mode 100644 index f75cb6802..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/ConsoleRegistrarCreatorAction.java +++ /dev/null @@ -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. - * - *

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 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 clientId; - - @Inject - @Parameter("consoleName") - Optional name; - - @Inject CloudTasksUtils cloudTasksUtils; - - @Inject IamClient iamClient; - - @Inject - @Config("gSuiteConsoleUserGroupEmailAddress") - Optional maybeGroupEmailAddress; - - @Inject @Parameter("billingAccount") Optional billingAccount; - @Inject @Parameter("ianaId") Optional ianaId; - @Inject @Parameter("referralEmail") Optional referralEmail; - @Inject @Parameter("driveId") Optional driveId; - @Inject @Parameter("consoleUserEmail") Optional consoleUserEmail; - - // Address fields, some of which are required and others are optional. - @Inject @Parameter("street1") Optional street1; - @Inject @Parameter("street2") Optional optionalStreet2; - @Inject @Parameter("street3") Optional optionalStreet3; - @Inject @Parameter("city") Optional city; - @Inject @Parameter("state") Optional optionalState; - @Inject @Parameter("zip") Optional optionalZip; - @Inject @Parameter("countryCode") Optional countryCode; - - @Inject @Parameter("password") Optional optionalPassword; - @Inject @Parameter("passcode") Optional optionalPasscode; - - @Inject ConsoleRegistrarCreatorAction() {} - - @Override - public void runAfterLogin(Map 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 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 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 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); - } -} diff --git a/core/src/main/java/google/registry/ui/server/registrar/ConsoleUiAction.java b/core/src/main/java/google/registry/ui/server/registrar/ConsoleUiAction.java deleted file mode 100644 index 0a0915ea0..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/ConsoleUiAction.java +++ /dev/null @@ -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 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 paramClientId; - - @Inject - ConsoleUiAction() {} - - @Override - public void runAfterLogin(Map 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 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 = ""; - 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; - } -} diff --git a/core/src/main/java/google/registry/ui/server/registrar/HtmlAction.java b/core/src/main/java/google/registry/ui/server/registrar/HtmlAction.java deleted file mode 100644 index c5867f9a8..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/HtmlAction.java +++ /dev/null @@ -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. - * - *

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 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 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 data); - - public abstract String getPath(); -} diff --git a/core/src/main/java/google/registry/ui/server/registrar/JsonGetAction.java b/core/src/main/java/google/registry/ui/server/registrar/JsonGetAction.java deleted file mode 100644 index fb38b7c9c..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/JsonGetAction.java +++ /dev/null @@ -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 {} diff --git a/core/src/main/java/google/registry/ui/server/registrar/OteStatusAction.java b/core/src/main/java/google/registry/ui/server/registrar/OteStatusAction.java deleted file mode 100644 index 8eaec51b6..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/OteStatusAction.java +++ /dev/null @@ -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 handleJsonRequest(Map 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 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 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); - } -} diff --git a/core/src/main/java/google/registry/ui/server/registrar/RegistrarConsoleMetrics.java b/core/src/main/java/google/registry/ui/server/registrar/RegistrarConsoleMetrics.java deleted file mode 100644 index e1993bb1c..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/RegistrarConsoleMetrics.java +++ /dev/null @@ -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 CONSOLE_LABEL_DESCRIPTORS = - ImmutableSet.of( - LabelDescriptor.create("clientId", "target registrar client ID"), - LabelDescriptor.create("explicitClientId", "whether the client ID is set explicitly"), - LabelDescriptor.create("role", "Role[s] of the user making the request"), - LabelDescriptor.create("status", "whether the request is successful")); - - static final IncrementableMetric consoleRequestMetric = - MetricRegistryImpl.getDefault() - .newIncrementableMetric( - "/console/registrar/console_requests", - "Count of /registrar requests", - "count", - CONSOLE_LABEL_DESCRIPTORS); - - private static final ImmutableSet SETTINGS_LABEL_DESCRIPTORS = - ImmutableSet.of( - LabelDescriptor.create("clientId", "target registrar client ID"), - LabelDescriptor.create("action", "action performed"), - LabelDescriptor.create("role", "Role[s] of the user making the request"), - LabelDescriptor.create("status", "whether the request is successful")); - - static final IncrementableMetric settingsRequestMetric = - MetricRegistryImpl.getDefault() - .newIncrementableMetric( - "/console/registrar/setting_requests", - "Count of /registrar-settings requests", - "count", - SETTINGS_LABEL_DESCRIPTORS); - - @Inject - public RegistrarConsoleMetrics() {} - - void registerConsoleRequest( - String clientId, boolean explicitClientId, ImmutableSet roles, String status) { - consoleRequestMetric.increment( - clientId, String.valueOf(explicitClientId), String.valueOf(roles), status); - } - - void registerSettingsRequest( - String clientId, String action, ImmutableSet roles, String status) { - settingsRequestMetric.increment(clientId, action, String.valueOf(roles), status); - } -} diff --git a/core/src/main/java/google/registry/ui/server/registrar/RegistrarSettingsAction.java b/core/src/main/java/google/registry/ui/server/registrar/RegistrarSettingsAction.java deleted file mode 100644 index 7558349c1..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/RegistrarSettingsAction.java +++ /dev/null @@ -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 handleJsonRequest(Map 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 args = - (Map) - Optional.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 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 contacts, - ImmutableSet updatedContacts) { - - static EmailInfo create( - Registrar registrar, - Registrar updatedRegistrar, - ImmutableSet contacts, - ImmutableSet 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 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 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 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 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 expandRegistrarWithContacts( - Iterable contacts, Registrar registrar) { - ImmutableSet> 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 result = new LinkedHashMap<>(registrar.toDiffableFieldMap()); - result.put("contacts", expandedContacts); - return result; - } - - /** - * Updates registrar with the OWNER-controlled args from the http request. - * - *

If any changes were made and the user isn't an OWNER - throws a {@link ForbiddenException}. - */ - private Registrar checkAndUpdateOwnerControlledFields( - Registrar initialRegistrar, Map 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 certificateString = - RegistrarFormFields.CLIENT_CERTIFICATE_FIELD.extractUntyped(args); - if (certificateString.isPresent()) { - if (validateCertificate(initialRegistrar.getClientCertificate(), certificateString.get())) { - builder.setClientCertificate(certificateString.get(), tm().getTransactionTime()); - } - } - - Optional 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 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. - * - *

If any changes were made and the user isn't an ADMIN - throws a {@link ForbiddenException}. - */ - private Registrar checkAndUpdateAdminControlledFields( - Registrar initialRegistrar, Map args) { - Registrar.Builder builder = initialRegistrar.asBuilder(); - - Set 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. - * - *

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 readContacts( - Registrar registrar, ImmutableSet existingContacts, Map 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 existingContacts, ImmutableSet updatedContacts) { - // Check that no two contacts use the same email address. - Set 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 oldContactsByType = HashMultimap.create(); - for (RegistrarPoc contact : existingContacts) { - for (Type t : contact.getTypes()) { - oldContactsByType.put(t, contact); - } - } - Multimap 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 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 existingContacts, ImmutableSet 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 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 oldContactsByType, - Multimap 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). - * - *

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 getDomainWhoisVisibleAbuseContact( - Set 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 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 changedKeys = (Set) 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())); - } - } -} diff --git a/core/src/main/java/google/registry/ui/server/registrar/RegistryLockGetAction.java b/core/src/main/java/google/registry/ui/server/registrar/RegistryLockGetAction.java deleted file mode 100644 index 90d760d9d..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/RegistryLockGetAction.java +++ /dev/null @@ -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. - * - *

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 paramClientId; - - @Inject - RegistryLockGetAction( - @RequestMethod Method method, - Response response, - AuthenticatedRegistrarAccessor registrarAccessor, - AuthResult authResult, - @Parameter("consoleClientId") Optional 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 resultMap = getLockedDomainsMap(paramClientId.get()); - ImmutableMap 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 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> 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 lockToMap(RegistryLock lock, boolean isAdmin) { - DateTime now = tm().getTransactionTime(); - return new ImmutableMap.Builder() - .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(); - } -} diff --git a/core/src/main/java/google/registry/ui/server/registrar/RegistryLockPostAction.java b/core/src/main/java/google/registry/ui/server/registrar/RegistryLockPostAction.java deleted file mode 100644 index 8dd6d448b..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/RegistryLockPostAction.java +++ /dev/null @@ -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. - * - *

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 handleJsonRequest(Map 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 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; - } -} diff --git a/core/src/main/java/google/registry/ui/server/registrar/RegistryLockVerifyAction.java b/core/src/main/java/google/registry/ui/server/registrar/RegistryLockVerifyAction.java deleted file mode 100644 index d8751442c..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/RegistryLockVerifyAction.java +++ /dev/null @@ -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 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 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; - } -} diff --git a/core/src/main/java/google/registry/ui/server/registrar/package-info.java b/core/src/main/java/google/registry/ui/server/registrar/package-info.java deleted file mode 100644 index 508f80830..000000000 --- a/core/src/main/java/google/registry/ui/server/registrar/package-info.java +++ /dev/null @@ -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; diff --git a/core/src/test/java/google/registry/module/RequestComponentTest.java b/core/src/test/java/google/registry/module/RequestComponentTest.java index 6f2db2477..e1a2fbd54 100644 --- a/core/src/test/java/google/registry/module/RequestComponentTest.java +++ b/core/src/test/java/google/registry/module/RequestComponentTest.java @@ -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 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 getRoutes(Class context, String filename) { diff --git a/core/src/test/java/google/registry/server/RegistryTestServer.java b/core/src/test/java/google/registry/server/RegistryTestServer.java index df4adb3e6..c59db4d4a 100644 --- a/core/src/test/java/google/registry/server/RegistryTestServer.java +++ b/core/src/test/java/google/registry/server/RegistryTestServer.java @@ -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; diff --git a/core/src/test/java/google/registry/ui/server/ActionMembershipTest.java b/core/src/test/java/google/registry/ui/server/ActionMembershipTest.java index a4f6af0e2..2c85d8dff 100644 --- a/core/src/test/java/google/registry/ui/server/ActionMembershipTest.java +++ b/core/src/test/java/google/registry/ui/server/ActionMembershipTest.java @@ -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 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()); } }); diff --git a/core/src/test/java/google/registry/ui/server/console/settings/ContactActionTest.java b/core/src/test/java/google/registry/ui/server/console/settings/ContactActionTest.java index 040188d4a..e58234c98 100644 --- a/core/src/test/java/google/registry/ui/server/console/settings/ContactActionTest.java +++ b/core/src/test/java/google/registry/ui/server/console/settings/ContactActionTest.java @@ -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 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> 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))); } } } diff --git a/core/src/test/java/google/registry/ui/server/registrar/ConsoleOteSetupActionTest.java b/core/src/test/java/google/registry/ui/server/registrar/ConsoleOteSetupActionTest.java deleted file mode 100644 index e04973299..000000000 --- a/core/src/test/java/google/registry/ui/server/registrar/ConsoleOteSetupActionTest.java +++ /dev/null @@ -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("

Setup OT&E

"); - 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("

You need permission

"); - 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("

OT&E successfully created for registrar myclientid!

"); - ArgumentCaptor 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("

OT&E successfully created for registrar myclientid!

"); - 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("

You need permission

"); - assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); - } -} diff --git a/core/src/test/java/google/registry/ui/server/registrar/ConsoleRegistrarCreatorActionTest.java b/core/src/test/java/google/registry/ui/server/registrar/ConsoleRegistrarCreatorActionTest.java deleted file mode 100644 index 44100d58a..000000000 --- a/core/src/test/java/google/registry/ui/server/registrar/ConsoleRegistrarCreatorActionTest.java +++ /dev/null @@ -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("

Create Registrar

"); - assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); - } - - @Test - void testGet_authorized_onProduction() { - RegistryEnvironment.PRODUCTION.setup(systemPropertyExtension); - action.run(); - assertThat(response.getPayload()).contains("

Create Registrar

"); - assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); - } - - @Test - void testGet_unauthorized() { - action.registrarAccessor = - AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of()); - action.run(); - assertThat(response.getPayload()).contains("

You need permission

"); - 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("

Successfully created Registrar myclientid

"); - - ArgumentCaptor 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("

Successfully created Registrar myclientid

"); - 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("

Successfully created Registrar myclientid

"); - - 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("

Successfully created Registrar myclientid

"); - - 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("

You need permission

"); - assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); - } -} diff --git a/core/src/test/java/google/registry/ui/server/registrar/ConsoleUiActionTest.java b/core/src/test/java/google/registry/ui/server/registrar/ConsoleUiActionTest.java deleted file mode 100644 index 51d5946b6..000000000 --- a/core/src/test/java/google/registry/ui/server/registrar/ConsoleUiActionTest.java +++ /dev/null @@ -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("

Console is disabled

"); - assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); - } - - @Test - void testUserDoesntHaveAccessToAnyRegistrar_showsWhoAreYouPage() { - action.registrarAccessor = - AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of()); - action.run(); - assertThat(response.getPayload()).contains("

You need permission

"); - assertThat(response.getPayload()).contains("not associated with Nomulus."); - assertThat(response.getPayload()).contains("gtag('config', 'sampleId')"); - assertMetric("", "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("

You need permission

"); - 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("