Compare commits

..
Author SHA1 Message Date
Pavlo TkachandGitHub 0c123e1676 Unify email notifications for console updates (#2459) 2024-05-31 19:20:56 +00:00
Lai JiangandGitHub 81b239c6b3 Add a presubmit test to catch accidental usage of javax.servlet (#2461) 2024-05-31 13:34:50 +00:00
Weimin YuandGitHub ea8c34bf8b Fix Flyway Gradle tasks (#2460) 2024-05-30 19:23:29 +00:00
Weimin YuandGitHub b3e67e58b5 Change billing for multi-year domain creation (#2446)
* Change billing for multi-year domain creation

From the second year on, charge the renewal price.

See b/322833077
2024-05-29 13:19:54 -04:00
gbrodmanandGitHub 589041b3ed Fully reset domain-list page on registrar change (#2456)
When the registrar changes we should reset the page and the total
results to 0 (since we haven't loaded them yet)

https://b.corp.google.com/issues/343193698
2024-05-29 12:54:01 -04:00
Lai JiangandGitHub 455364ff29 Remove GAE Users service API usage (#2414)
This is the last remaining GAE API that we depend on. By removing it, we are able to remove all common GAE dependencies as well.

To merge this PR, we need to create console User objects that have the same email address as the RegistrarPoc objects' login_email_address and copy over the existing registry lock hashes and salts.

We are also able to simply the code base by removing some redundant logic like AuthMethod (API is now the only supported one) and UserAuthInfo (console user is now the only supported one)

There are several behavioral changes that are worth noting:

The XsrfTokenManager now uses the console user's email address to mint and verify the token. Previously, only email addresses returned by the GAE Users service are used, whereas a blank email address will be used if the user is logged in as a console user. I believe this was an oversight that is now corrected.
The legacy console will return 401 when no user is logged in, instead of redirecting to the Users service login flow.
The logout URL in the legacy console is changed to use the IAP logout flow. It will clear the cookie and redirect the users to IAP login page (tested on QA).
The screenshot changes are mostly due to the console users lacking a display name and therefore showing the email address instead. Some changes are due to using the console user's email address as the registry lock email address, which is being fixed in Add DB column for separate rlock email address #2413 and its follow-up RPs.
2024-05-29 12:37:44 -04:00
Lai JiangandGitHub d90bc1a3e4 Update db README (#2449) 2024-05-29 11:54:31 -04:00
Lai JiangandGitHub 0e3875c1ff Removing leading newline from GKE log messages (#2454)
GKE now displays log messages correctly. There is no need for an extra
leading newline, which now results in a useless blank line for each log
entry in Log Explorer.
2024-05-29 11:54:19 -04:00
Lai JiangandGitHub 3b565b96b7 Add the ability to add/remove console users from a Google Group (#2450)
# Conflicts:
#	config/presubmits.py
2024-05-28 17:00:37 +00:00
Pavlo TkachandGitHub ec6c77927f Add console backend for editing registrar (#2452) 2024-05-28 00:53:32 +00:00
Lai JiangandGitHub e88ff77ecb Harmonize http status code usage (#2451)
Given that we run servlets, it makes sense to always use the status
code contants from the servlet class.
2024-05-24 18:46:37 +00:00
252 changed files with 2542 additions and 2580 deletions
+5 -5
View File
@@ -54,11 +54,11 @@ org.jacoco:org.jacoco.ant:0.8.11=jacocoAnt
org.jacoco:org.jacoco.core:0.8.11=jacocoAnt
org.jacoco:org.jacoco.report:0.8.11=jacocoAnt
org.javassist:javassist:3.28.0-GA=checkstyle
org.junit.jupiter:junit-jupiter-api:5.11.0-M1=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:5.11.0-M1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-commons:1.11.0-M1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-engine:1.11.0-M1=testCompileClasspath,testRuntimeClasspath
org.junit:junit-bom:5.11.0-M1=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:5.11.0-M2=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:5.11.0-M2=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-commons:1.11.0-M2=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-engine:1.11.0-M2=testCompileClasspath,testRuntimeClasspath
org.junit:junit-bom:5.11.0-M2=testCompileClasspath,testRuntimeClasspath
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm-commons:9.6=jacocoAnt
org.ow2.asm:asm-tree:9.6=jacocoAnt
+20 -15
View File
@@ -173,23 +173,11 @@ PRESUBMITS = {
):
"JavaScript files should not include console logging.",
PresubmitCheck(
r".*org\.testcontainers\.shaded.*",
r".*\nimport (static )?.*\.shaded\..*",
"java",
{"/node_modules/"},
):
"Do not use shaded dependencies from testcontainers.",
PresubmitCheck(
r".*autovalue\.shaded.*",
"java",
{"/node_modules/"},
):
"Do not use shaded dependencies from autovalue.",
PresubmitCheck(
r".*avro\.shaded.*",
"java",
{"/node_modules/"},
):
"Do not use shaded dependencies from avro.",
"Do not use shaded dependencies",
PresubmitCheck(
r".*com\.google\.common\.truth\.Truth8.*",
"java",
@@ -202,7 +190,24 @@ PRESUBMITS = {
{"/node_modules/", "JpaTransactionManagerImpl.java"},
):
"Do not use java.util.Date. Use classes in java.time package instead.",
PresubmitCheck(
r".*com\.google\.api\.client\.http\.HttpStatusCodes.*",
"java",
{"/node_modules/"},
):
"Use status code from jakarta.servlet.http.HttpServletResponse.",
PresubmitCheck(
r".*mock\(Response\.class\).*",
"java",
{"/node_modules/"},
):
"Do not mock Response, use FakeResponse.",
PresubmitCheck(
r".*javax\.servlet\..*",
"java",
{"/node_modules/"},
):
"Do not use javax.servlet.* Use jakarta.servlet.* instead.",
}
# Note that this regex only works for one kind of Flyway file. If we want to
@@ -59,6 +59,8 @@ export class DomainListComponent {
) {
effect(() => {
if (this.registrarService.registrarId()) {
this.pageNumber = 0;
this.totalResults = 0;
this.reloadData();
}
});
-6
View File
@@ -153,7 +153,6 @@ dependencies {
implementation deps['com.google.apis:google-api-services-monitoring']
implementation deps['com.google.apis:google-api-services-sheets']
implementation deps['com.google.apis:google-api-services-storage']
testImplementation deps['com.google.appengine:appengine-api-stubs']
implementation deps['com.google.auth:google-auth-library-credentials']
implementation deps['com.google.auth:google-auth-library-oauth2-http']
implementation deps['com.google.cloud.bigdataoss:util']
@@ -250,10 +249,6 @@ dependencies {
implementation deps['us.fatehi:schemacrawler-tools']
implementation deps['xerces:xmlParserAPIs']
implementation deps['org.ogce:xpp3']
// This dependency must come after javax.mail:mail as it would otherwise
// shadow classes in package javax.mail with its own implementation.
implementation deps['com.google.appengine:appengine-api-1.0-sdk']
// Known issue: nebula-lint misses inherited dependency.
implementation project(':common')
testImplementation project(path: ':common', configuration: 'testing')
@@ -271,7 +266,6 @@ dependencies {
testAnnotationProcessor project(':processor')
testImplementation deps['com.google.cloud:google-cloud-nio']
testImplementation deps['com.google.appengine:appengine-testing']
testImplementation deps['com.google.guava:guava-testlib']
testImplementation deps['com.google.monitoring-client:contrib']
testImplementation deps['com.google.protobuf:protobuf-java-util']
+55 -60
View File
@@ -9,21 +9,21 @@ args4j:args4j:2.33=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRu
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.16.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.fasterxml.jackson.core:jackson-annotations:2.17.0=testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-annotations:2.17.1=testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-core:2.16.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.fasterxml.jackson.core:jackson-core:2.17.0=testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-core:2.17.1=testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-databind:2.16.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.fasterxml.jackson.core:jackson-databind:2.17.0=testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-databind:2.17.1=testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.dataformat:jackson-dataformat-toml:2.16.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.fasterxml.jackson.dataformat:jackson-dataformat-toml:2.17.0=testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.dataformat:jackson-dataformat-toml:2.17.1=testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.16.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.17.0=testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.17.1=testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.datatype:jackson-datatype-joda:2.16.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.fasterxml.jackson.datatype:jackson-datatype-joda:2.17.0=testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.datatype:jackson-datatype-joda:2.17.1=testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.16.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.0=testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.1=testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson:jackson-bom:2.16.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.fasterxml.jackson:jackson-bom:2.17.0=testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson:jackson-bom:2.17.1=testCompileClasspath,testRuntimeClasspath
com.fasterxml:classmate:1.5.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.ben-manes.caffeine:caffeine:3.0.5=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.github.ben-manes.caffeine:caffeine:3.1.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -40,13 +40,13 @@ com.github.jnr:jnr-unixsocket:0.38.22=compileClasspath,deploy_jar,nonprodCompile
com.github.jnr:jnr-x86asm:1.0.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.android:annotations:4.1.1.4=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api-client:google-api-client-appengine:2.5.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api-client:google-api-client-appengine:2.6.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api-client:google-api-client-jackson2:2.0.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api-client:google-api-client-java6:2.1.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api-client:google-api-client-servlet:2.5.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api-client:google-api-client-servlet:2.6.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api-client:google-api-client:1.35.2=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.38.0-alpha=testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:gapic-google-cloud-storage-v2:2.39.0-alpha=testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:3.4.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.176.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.176.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -57,8 +57,8 @@ com.google.api.grpc:grpc-google-cloud-spanner-admin-database-v1:6.62.0=compileCl
com.google.api.grpc:grpc-google-cloud-spanner-admin-instance-v1:6.62.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-spanner-v1:6.62.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.38.0-alpha=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:grpc-google-cloud-storage-v2:2.39.0-alpha=testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-common-protos:2.39.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1:3.4.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta1:0.176.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta2:0.176.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -70,23 +70,23 @@ com.google.api.grpc:proto-google-cloud-monitoring-v3:1.64.0=compileClasspath,non
com.google.api.grpc:proto-google-cloud-monitoring-v3:3.39.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-pubsub-v1:1.109.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-pubsublite-v1:1.13.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-secretmanager-v1:2.43.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-secretmanager-v1beta2:2.43.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-secretmanager-v1:2.44.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-secretmanager-v1beta2:2.44.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-spanner-admin-database-v1:6.62.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-spanner-admin-instance-v1:6.62.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-spanner-executor-v1:6.62.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-spanner-v1:6.62.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.38.0-alpha=testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2:2.43.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2beta2:0.133.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2beta3:0.133.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-common-protos:2.39.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-iam-v1:1.34.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:api-common:2.31.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:gax-grpc:2.48.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:gax-httpjson:2.48.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:gax:2.48.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-storage-v2:2.39.0-alpha=testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2:2.44.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2beta2:0.134.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2beta3:0.134.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-common-protos:2.39.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-iam-v1:1.34.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:api-common:2.31.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:gax-grpc:2.48.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:gax-httpjson:2.48.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:gax:2.48.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-admin-directory:directory_v1-rev20240509-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-appengine:v1-rev20240415-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-bigquery:v2-rev20240229-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -94,22 +94,17 @@ com.google.apis:google-api-services-cloudresourcemanager:v1-rev20240310-2.0.0=co
com.google.apis:google-api-services-dataflow:v1b3-rev20240218-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-dns:v2beta1-rev99-1.25.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-drive:v2-rev393-1.25.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-gmail:v1-rev20240422-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-gmail:v1-rev20240520-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-groupssettings:v1-rev20210624-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-healthcare:v1-rev20240130-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-iam:v2-rev20240314-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-iamcredentials:v1-rev20211203-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-monitoring:v3-rev20240427-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-monitoring:v3-rev20240519-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-pubsub:v1-rev20220904-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-sheets:v4-rev20240423-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-sheets:v4-rev20240514-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-sqladmin:v1beta4-rev20240324-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-storage:v1-rev20240311-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.apis:google-api-services-storage:v1-rev20240319-2.0.0=testCompileClasspath,testRuntimeClasspath
com.google.appengine:appengine-api-1.0-sdk:2.0.27=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.appengine:appengine-api-stubs:2.0.27=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.appengine:appengine-remote-api:2.0.27=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.appengine:appengine-testing:2.0.27=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.appengine:appengine-tools-sdk:2.0.27=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auth:google-auth-library-credentials:1.23.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auth:google-auth-library-oauth2-http:1.23.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auto.service:auto-service-annotations:1.0.1=errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
@@ -124,28 +119,28 @@ com.google.cloud.bigdataoss:gcsio:2.2.16=compileClasspath,deploy_jar,nonprodComp
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
com.google.cloud.datastore:datastore-v1-proto-client:2.19.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.sql:jdbc-socket-factory-core:1.18.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.sql:postgres-socket-factory:1.18.0=deploy_jar,runtimeClasspath,testRuntimeClasspath
com.google.cloud.sql:jdbc-socket-factory-core:1.18.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.sql:postgres-socket-factory:1.18.1=deploy_jar,runtimeClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-bigquerystorage:3.4.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-bigtable-stats:2.37.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-bigtable:2.37.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core-grpc:2.36.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.cloud:google-cloud-core-grpc:2.38.0=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core-grpc:2.38.1=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.38.0=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core-http:2.38.1=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core:2.36.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.cloud:google-cloud-core:2.38.0=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core:2.38.1=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-firestore:3.20.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-monitoring:1.82.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.cloud:google-cloud-monitoring:3.39.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-nio:0.127.17=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-nio:0.127.18=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-pubsub:1.127.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-pubsublite:1.13.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-secretmanager:2.43.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-secretmanager:2.44.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-spanner:6.62.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.38.0=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-tasks:2.43.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-storage:2.39.0=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-tasks:2.44.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:grpc-gcp:1.5.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.20.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.code.findbugs:jsr305:3.0.1=css
@@ -159,7 +154,7 @@ com.google.dagger:dagger:2.51.1=annotationProcessor,compileClasspath,deploy_jar,
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.23.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.26.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.errorprone:error_prone_annotations:2.27.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.errorprone:error_prone_annotations:2.7.1=checkstyle,soy
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
@@ -180,17 +175,17 @@ com.google.guava:guava:20.0=css
com.google.guava:guava:31.0.1-jre=checkstyle,soy
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.1.0-jre=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.guava:guava:33.2.0-android=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.guava:guava:33.2.0-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.gwt:gwt-user:2.10.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-apache-v2:1.43.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.http-client:google-http-client-apache-v2:1.44.1=testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-appengine:1.44.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-appengine:1.44.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-gson:1.44.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-jackson2:1.44.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
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.44.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client:1.44.2=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:5.1.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
@@ -253,7 +248,7 @@ com.sun.xml.bind:jaxb-xjc:2.3.9=jaxb
com.sun.xml.fastinfoset:FastInfoset:1.2.15=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.zaxxer:HikariCP:5.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
commons-beanutils:commons-beanutils:1.9.4=checkstyle
commons-codec:commons-codec:1.16.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
commons-codec:commons-codec:1.17.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
commons-collections:commons-collections:3.2.2=checkstyle
commons-io:commons-io:2.16.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
commons-logging:commons-logging:1.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -333,7 +328,6 @@ javax.inject:javax.inject:1=annotationProcessor,compileClasspath,deploy_jar,erro
javax.jdo:jdo2-api:2.3-20090302111651=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
javax.mail:mail:1.5.0-b01=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
javax.persistence:javax.persistence-api:2.2=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
javax.servlet:servlet-api:2.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
javax.validation:validation-api:1.0.0.GA=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
javax.xml.bind:jaxb-api:2.3.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
javax.xml.bind:jaxb-api:2.4.0-b180830.0359=jaxb
@@ -462,17 +456,17 @@ org.json:json:20160212=soy
org.json:json:20240303=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jsoup:jsoup:1.17.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.junit-pioneer:junit-pioneer:2.2.0=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:5.11.0-M1=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:5.11.0-M1=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-migrationsupport:5.11.0-M1=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-params:5.11.0-M1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-commons:1.11.0-M1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-engine:1.11.0-M1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-launcher:1.11.0-M1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-runner:1.11.0-M1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-suite-api:1.11.0-M1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-suite-commons:1.11.0-M1=testRuntimeClasspath
org.junit:junit-bom:5.11.0-M1=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:5.11.0-M2=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:5.11.0-M2=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-migrationsupport:5.11.0-M2=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-params:5.11.0-M2=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-commons:1.11.0-M2=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-engine:1.11.0-M2=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-launcher:1.11.0-M2=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-runner:1.11.0-M2=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-suite-api:1.11.0-M2=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-suite-commons:1.11.0-M2=testRuntimeClasspath
org.junit:junit-bom:5.11.0-M2=testCompileClasspath,testRuntimeClasspath
org.jvnet.staxex:stax-ex:1.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-core:1.10.19=css
org.mockito:mockito-core:5.12.0=testCompileClasspath,testRuntimeClasspath
@@ -489,7 +483,8 @@ org.ow2.asm:asm-commons:9.6=jacocoAnt
org.ow2.asm:asm-tree:7.0=soy
org.ow2.asm:asm-tree:9.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm-tree:9.6=jacocoAnt
org.ow2.asm:asm-util:7.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm-util:7.0=soy
org.ow2.asm:asm-util:9.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm:7.0=soy
org.ow2.asm:asm:9.2=compileClasspath,nonprodCompileClasspath
org.ow2.asm:asm:9.6=deploy_jar,jacocoAnt,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -46,7 +46,7 @@ import javax.net.ssl.HttpsURLConnection;
path = "/_dr/task/executeCannedScript",
method = {POST, GET},
automaticallyPrintOk = true,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class CannedScriptExecutionAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -38,10 +38,7 @@ import org.joda.time.Days;
* An action that checks all {@link BulkPricingPackage} objects for compliance with their max create
* limit.
*/
@Action(
service = Service.BACKEND,
path = CheckBulkComplianceAction.PATH,
auth = Auth.AUTH_API_ADMIN)
@Action(service = Service.BACKEND, path = CheckBulkComplianceAction.PATH, auth = Auth.AUTH_ADMIN)
public class CheckBulkComplianceAction implements Runnable {
public static final String PATH = "/_dr/task/checkBulkCompliance";
@@ -69,7 +69,7 @@ import org.joda.time.Duration;
@Action(
service = Action.Service.BACKEND,
path = DeleteExpiredDomainsAction.PATH,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class DeleteExpiredDomainsAction implements Runnable {
public static final String PATH = "/_dr/task/deleteExpiredDomains";
@@ -56,7 +56,7 @@ import javax.inject.Inject;
service = Action.Service.BACKEND,
path = "/_dr/task/deleteLoadTestData",
method = POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class DeleteLoadTestDataAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -61,7 +61,7 @@ import org.joda.time.Duration;
service = Action.Service.BACKEND,
path = "/_dr/task/deleteProberData",
method = POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class DeleteProberDataAction implements Runnable {
// TODO(b/323026070): Add email alert on failure of this action
@@ -52,7 +52,7 @@ import org.joda.time.DateTime;
@Action(
service = Action.Service.BACKEND,
path = "/_dr/task/expandBillingRecurrences",
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class ExpandBillingRecurrencesAction implements Runnable {
public static final String PARAM_START_TIME = "startTime";
@@ -53,7 +53,7 @@ import org.joda.time.Duration;
path = RelockDomainAction.PATH,
method = POST,
automaticallyPrintOk = true,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class RelockDomainAction implements Runnable {
public static final String PATH = "/_dr/task/relockDomain";
@@ -55,7 +55,7 @@ import javax.inject.Inject;
@Action(
service = Action.Service.BACKEND,
path = ResaveAllEppResourcesPipelineAction.PATH,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class ResaveAllEppResourcesPipelineAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -40,7 +40,7 @@ import org.joda.time.DateTime;
@Action(
service = Action.Service.BACKEND,
path = ResaveEntityAction.PATH,
auth = Auth.AUTH_API_ADMIN,
auth = Auth.AUTH_ADMIN,
method = Method.POST)
public class ResaveEntityAction implements Runnable {
@@ -51,7 +51,7 @@ import org.joda.time.format.DateTimeFormatter;
@Action(
service = Action.Service.BACKEND,
path = SendExpiringCertificateNotificationEmailAction.PATH,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class SendExpiringCertificateNotificationEmailAction implements Runnable {
public static final String PATH = "/_dr/task/sendExpiringCertificateNotificationEmail";
@@ -51,7 +51,7 @@ import org.joda.time.DateTime;
@Action(
service = Service.BACKEND,
path = WipeOutContactHistoryPiiAction.PATH,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class WipeOutContactHistoryPiiAction implements Runnable {
public static final String PATH = "/_dr/task/wipeOutContactHistoryPii";
@@ -53,7 +53,7 @@ import javax.inject.Inject;
service = Action.Service.BSA,
path = BsaDownloadAction.PATH,
method = {GET, POST},
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class BsaDownloadAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -44,7 +44,7 @@ import org.joda.time.Duration;
service = Action.Service.BSA,
path = BsaRefreshAction.PATH,
method = {GET, POST},
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class BsaRefreshAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -32,7 +32,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.POST;
import static google.registry.util.BatchedStreams.toBatches;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
@@ -70,7 +70,7 @@ import org.joda.time.Duration;
service = Action.Service.BSA,
path = BsaValidateAction.PATH,
method = {GET, POST},
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class BsaValidateAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -23,9 +23,9 @@ import static google.registry.model.tld.label.ReservedList.loadReservedLists;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.POST;
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static java.nio.charset.StandardCharsets.US_ASCII;
import com.google.api.client.http.HttpStatusCodes;
import com.google.cloud.storage.BlobId;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
@@ -75,7 +75,7 @@ import org.joda.time.DateTime;
service = Service.BSA,
path = "/_dr/task/uploadBsaUnavailableNames",
method = {GET, POST},
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class UploadBsaUnavailableDomainsAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -190,7 +190,7 @@ public class UploadBsaUnavailableDomainsAction implements Runnable {
return true;
} catch (IOException e) {
logger.atSevere().withCause(e).log("Error while attempting to upload to BSA, aborting.");
response.setStatus(HttpStatusCodes.STATUS_CODE_SERVER_ERROR);
response.setStatus(SC_INTERNAL_SERVER_ERROR);
response.setPayload("Error while attempting to upload to BSA: " + e.getMessage());
return false;
}
@@ -491,6 +491,18 @@ public final class RegistryConfig {
return Optional.ofNullable(Strings.emptyToNull(config.gSuite.supportGroupEmailAddress));
}
/**
* Returns the email address of the group containing emails of console users.
*
* <p>This group should be granted the {@code roles/iap.httpsResourceAccessor} role.
*/
@Provides
@Config("gSuiteConsoleUserGroupEmailAddress")
public static Optional<String> provideGSuiteConsoleUserGroupEmailAddress(
RegistryConfigSettings config) {
return Optional.ofNullable(Strings.emptyToNull(config.gSuite.consoleUserGroupEmailAddress));
}
/**
* 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.
@@ -83,6 +83,7 @@ public class RegistryConfigSettings {
public String outgoingEmailDisplayName;
public String adminAccountEmailAddress;
public String supportGroupEmailAddress;
public String consoleUserGroupEmailAddress;
}
/** Configuration options for registry policy. */
@@ -47,6 +47,11 @@ gSuite:
# given "ADMIN" role on the registrar console.
supportGroupEmailAddress: support@example.com
# Group containing the emails of console users. This group should be granted
# roles/iap.httpsResourceAccessor out-of-band. If this field is empty, each
# console user will be granted to the role individually when they are created.
consoleUserGroupEmailAddress:
registryPolicy:
# Repository identifier (ROID) suffix for contacts and hosts.
contactAndHostRoidSuffix: ROID
@@ -81,7 +81,7 @@ import javax.inject.Inject;
service = Action.Service.BACKEND,
path = "/_dr/cron/fanout",
automaticallyPrintOk = true,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class TldFanoutAction implements Runnable {
/** A set of control params to TldFanoutAction that aren't passed down to the executing action. */
@@ -77,7 +77,7 @@ import org.joda.time.Duration;
path = PublishDnsUpdatesAction.PATH,
method = POST,
automaticallyPrintOk = true,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
public static final String PATH = "/_dr/task/publishDnsUpdates";
@@ -64,7 +64,7 @@ import org.joda.time.Duration;
path = "/_dr/task/readDnsRefreshRequests",
automaticallyPrintOk = true,
method = POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class ReadDnsRefreshRequestsAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -38,7 +38,7 @@ import javax.inject.Inject;
service = Action.Service.BACKEND,
path = "/_dr/dnsRefresh",
automaticallyPrintOk = true,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class RefreshDnsAction implements Runnable {
private final Clock clock;
@@ -33,11 +33,7 @@ import google.registry.request.auth.Auth;
import javax.inject.Inject;
import org.joda.time.DateTime;
@Action(
service = Service.BACKEND,
path = PATH,
method = Action.Method.POST,
auth = Auth.AUTH_API_ADMIN)
@Action(service = Service.BACKEND, path = PATH, method = Action.Method.POST, auth = Auth.AUTH_ADMIN)
public class RefreshDnsOnHostRenameAction implements Runnable {
public static final String QUEUE_HOST_RENAME = "async-host-rename";
@@ -12,6 +12,11 @@
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>tools-servlet</servlet-name>
<url-pattern>/_dr/admin/updateUserGroup</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>tools-servlet</servlet-name>
<url-pattern>/_dr/admin/verifyOte</url-pattern>
@@ -49,7 +49,7 @@ import javax.inject.Inject;
service = Action.Service.BACKEND,
path = "/_dr/task/exportDomainLists",
method = POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class ExportDomainListsAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -48,7 +48,7 @@ import javax.inject.Inject;
service = Action.Service.BACKEND,
path = "/_dr/task/exportPremiumTerms",
method = POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class ExportPremiumTermsAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -37,7 +37,7 @@ import javax.inject.Inject;
service = Action.Service.BACKEND,
path = "/_dr/task/exportReservedTerms",
method = POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class ExportReservedTermsAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -56,7 +56,7 @@ import javax.inject.Inject;
service = Action.Service.BACKEND,
path = "/_dr/task/syncGroupMembers",
method = POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class SyncGroupMembersAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -57,7 +57,7 @@ import org.joda.time.Duration;
service = Action.Service.BACKEND,
path = SyncRegistrarsSheetAction.PATH,
method = POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class SyncRegistrarsSheetAction implements Runnable {
private enum Result {
@@ -29,7 +29,7 @@ import javax.inject.Inject;
service = Action.Service.DEFAULT,
path = "/_dr/epp",
method = Method.POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class EppTlsAction implements Runnable {
@Inject @Payload byte[] inputXmlBytes;
@@ -33,7 +33,7 @@ import javax.inject.Inject;
service = Action.Service.TOOLS,
path = EppToolAction.PATH,
method = Method.POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class EppToolAction implements Runnable {
public static final String PATH = "/_dr/epptool";
@@ -159,7 +159,11 @@ public final class DomainPricingLogic {
case NONPREMIUM -> {
renewCost =
getDomainCostWithDiscount(
false, years, allocationToken, tld.getStandardRenewCost(dateTime));
false,
years,
allocationToken,
tld.getStandardRenewCost(dateTime),
Optional.empty());
isRenewCostPremiumPrice = false;
}
default ->
@@ -252,7 +256,11 @@ public final class DomainPricingLogic {
DomainPrices domainPrices, int years, Optional<AllocationToken> allocationToken)
throws EppException {
return getDomainCostWithDiscount(
domainPrices.isPremium(), years, allocationToken, domainPrices.getCreateCost());
domainPrices.isPremium(),
years,
allocationToken,
domainPrices.getCreateCost(),
Optional.of(domainPrices.getRenewCost()));
}
/** Returns the domain renew cost with allocation-token-related discounts applied. */
@@ -272,24 +280,45 @@ public final class DomainPricingLogic {
}
}
return getDomainCostWithDiscount(
domainPrices.isPremium(), years, allocationToken, domainPrices.getRenewCost());
domainPrices.isPremium(),
years,
allocationToken,
domainPrices.getRenewCost(),
Optional.empty());
}
/**
* Returns the domain creation or renewal cost for the given number of {@code years}.
*
* <p>For domain creation, {@code firstYearCost} is the creation cost while {@code
* subsequentYearCost} is the single-year renewal cost (which is guaranteed to be present).
*
* <p>For domain renewal, {@code firstYearCost} is the single-year renewal cost and {@code
* subsequentYearCost} should be empty.
*/
private Money getDomainCostWithDiscount(
boolean isPremium, int years, Optional<AllocationToken> allocationToken, Money oneYearCost)
boolean isPremium,
int years,
Optional<AllocationToken> allocationToken,
Money firstYearCost,
Optional<Money> subsequentYearCost)
throws AllocationTokenInvalidForPremiumNameException {
checkArgument(years > 0, "Registration years to get cost for must be positive.");
validateTokenForPossiblePremiumName(allocationToken, isPremium);
Money totalDomainFlowCost = oneYearCost.multipliedBy(years);
Money totalDomainFlowCost =
firstYearCost.plus(subsequentYearCost.orElse(firstYearCost).multipliedBy(years - 1));
// Apply the allocation token discount, if applicable.
if (allocationToken.isPresent()
&& allocationToken.get().getTokenBehavior().equals(TokenBehavior.DEFAULT)) {
int discountedYears = Math.min(years, allocationToken.get().getDiscountYears());
Money discount =
oneYearCost.multipliedBy(
discountedYears * allocationToken.get().getDiscountFraction(),
RoundingMode.HALF_EVEN);
totalDomainFlowCost = totalDomainFlowCost.minus(discount);
if (discountedYears > 0) {
var discount =
firstYearCost
.plus(subsequentYearCost.orElse(firstYearCost).multipliedBy(discountedYears - 1))
.multipliedBy(allocationToken.get().getDiscountFraction(), RoundingMode.HALF_EVEN);
totalDomainFlowCost = totalDomainFlowCost.minus(discount);
}
}
return totalDomainFlowCost;
}
@@ -58,7 +58,7 @@ import org.joda.time.DateTime;
path = LoadTestAction.PATH,
method = Action.Method.POST,
automaticallyPrintOk = true,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class LoadTestAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -350,9 +350,8 @@ public class LoadTestAction implements Runnable {
.toBuilder()
.getAppEngineHttpRequest()
.toBuilder()
// instead of adding the X_CSRF_TOKEN to params, this remains as part of
// headers because of the existing setup for authentication in {@link
// google.registry.request.auth.LegacyAuthenticationMechanism}
// TODO: investigate if the following is necessary now that
// LegacyAuthenticationMechanism is gone.
.putHeaders(X_CSRF_TOKEN, xsrfToken)
.build())
.setScheduleTime(
@@ -1,20 +0,0 @@
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.annotations;
/**
* Annotation to indicate a class that should be deleted after the database migration is complete.
*/
public @interface DeleteAfterMigration {}
@@ -47,7 +47,6 @@ import google.registry.rde.JSchModule;
import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.NetHttpTransportModule;
import google.registry.request.Modules.UrlConnectionServiceModule;
import google.registry.request.Modules.UserServiceModule;
import google.registry.request.RequestHandler;
import google.registry.request.auth.AuthModule;
import google.registry.request.auth.RequestAuthenticator;
@@ -88,7 +87,6 @@ import javax.inject.Singleton;
SheetsServiceModule.class,
StackdriverModule.class,
UrlConnectionServiceModule.class,
UserServiceModule.class,
UtilsModule.class,
VoidDnsWriterModule.class,
})
@@ -107,6 +107,7 @@ import google.registry.tools.server.ListReservedListsAction;
import google.registry.tools.server.ListTldsAction;
import google.registry.tools.server.RefreshDnsForAllDomainsAction;
import google.registry.tools.server.ToolsServerModule;
import google.registry.tools.server.UpdateUserGroupAction;
import google.registry.tools.server.VerifyOteAction;
import google.registry.ui.server.console.ConsoleDomainGetAction;
import google.registry.ui.server.console.ConsoleDomainListAction;
@@ -323,6 +324,8 @@ interface RequestComponent {
UpdateRegistrarRdapBaseUrlsAction updateRegistrarRdapBaseUrlsAction();
UpdateUserGroupAction updateUserGroupAction();
UploadBsaUnavailableDomainsAction uploadBsaUnavailableDomains();
VerifyOteAction verifyOteAction();
@@ -43,7 +43,6 @@ import google.registry.rde.JSchModule;
import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.NetHttpTransportModule;
import google.registry.request.Modules.UrlConnectionServiceModule;
import google.registry.request.Modules.UserServiceModule;
import google.registry.request.auth.AuthModule;
import google.registry.util.UtilsModule;
import javax.inject.Singleton;
@@ -78,7 +77,6 @@ import javax.inject.Singleton;
SheetsServiceModule.class,
StackdriverModule.class,
UrlConnectionServiceModule.class,
UserServiceModule.class,
VoidDnsWriterModule.class,
UtilsModule.class
})
@@ -28,7 +28,6 @@ import google.registry.persistence.PersistenceModule;
import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.UrlConnectionServiceModule;
import google.registry.request.Modules.UserServiceModule;
import google.registry.request.auth.AuthModule;
import google.registry.util.UtilsModule;
import javax.inject.Singleton;
@@ -48,7 +47,6 @@ import javax.inject.Singleton;
SecretManagerModule.class,
StackdriverModule.class,
UrlConnectionServiceModule.class,
UserServiceModule.class,
UtilsModule.class
})
interface BsaComponent {
@@ -35,7 +35,6 @@ import google.registry.monitoring.whitebox.StackdriverModule;
import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.NetHttpTransportModule;
import google.registry.request.Modules.UserServiceModule;
import google.registry.request.auth.AuthModule;
import google.registry.ui.ConsoleDebug.ConsoleConfigModule;
import google.registry.util.UtilsModule;
@@ -66,7 +65,6 @@ import javax.inject.Singleton;
SecretManagerModule.class,
ServerTridProviderModule.class,
StackdriverModule.class,
UserServiceModule.class,
UtilsModule.class
})
interface FrontendComponent {
@@ -34,7 +34,6 @@ import google.registry.persistence.PersistenceModule;
import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.NetHttpTransportModule;
import google.registry.request.Modules.UserServiceModule;
import google.registry.request.auth.AuthModule;
import google.registry.util.UtilsModule;
import javax.inject.Singleton;
@@ -61,7 +60,6 @@ import javax.inject.Singleton;
SecretManagerModule.class,
ServerTridProviderModule.class,
StackdriverModule.class,
UserServiceModule.class,
UtilsModule.class
})
interface PubApiComponent {
@@ -35,7 +35,6 @@ import google.registry.monitoring.whitebox.StackdriverModule;
import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.NetHttpTransportModule;
import google.registry.request.Modules.UserServiceModule;
import google.registry.request.auth.AuthModule;
import google.registry.util.UtilsModule;
import javax.inject.Singleton;
@@ -63,7 +62,6 @@ import javax.inject.Singleton;
ServerTridProviderModule.class,
StackdriverModule.class,
ToolsRequestComponentModule.class,
UserServiceModule.class,
UtilsModule.class
})
interface ToolsComponent {
@@ -36,6 +36,7 @@ import google.registry.tools.server.ListReservedListsAction;
import google.registry.tools.server.ListTldsAction;
import google.registry.tools.server.RefreshDnsForAllDomainsAction;
import google.registry.tools.server.ToolsServerModule;
import google.registry.tools.server.UpdateUserGroupAction;
import google.registry.tools.server.VerifyOteAction;
/** Dagger component with per-request lifetime for "tools" App Engine module. */
@@ -50,9 +51,10 @@ import google.registry.tools.server.VerifyOteAction;
WhiteboxModule.class,
})
public interface ToolsRequestComponent {
FlowComponent.Builder flowComponentBuilder();
CreateGroupsAction createGroupsAction();
EppToolAction eppToolAction();
FlowComponent.Builder flowComponentBuilder();
GenerateZoneFilesAction generateZoneFilesAction();
ListDomainsAction listDomainsAction();
ListHostsAction listHostsAction();
@@ -62,6 +64,9 @@ public interface ToolsRequestComponent {
ListTldsAction listTldsAction();
LoadTestAction loadTestAction();
RefreshDnsForAllDomainsAction refreshDnsForAllDomainsAction();
UpdateUserGroupAction updateUserGroupAction();
VerifyOteAction verifyOteAction();
@Subcomponent.Builder
@@ -18,11 +18,11 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import dagger.Module;
import dagger.Provides;
import google.registry.model.console.User;
import google.registry.request.Parameter;
import google.registry.request.RequestParameters;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.UserAuthInfo;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Optional;
@@ -101,11 +101,11 @@ public final class RdapModule {
@Provides
static RdapAuthorization provideRdapAuthorization(
AuthResult authResult, AuthenticatedRegistrarAccessor registrarAccessor) {
if (authResult.userAuthInfo().isEmpty()) {
if (authResult.user().isEmpty()) {
return RdapAuthorization.PUBLIC_AUTHORIZATION;
}
UserAuthInfo userAuthInfo = authResult.userAuthInfo().get();
if (userAuthInfo.isUserAdmin()) {
User user = authResult.user().get();
if (user.getUserRoles().isAdmin()) {
return RdapAuthorization.ADMINISTRATOR_AUTHORIZATION;
}
ImmutableSet<String> clientIds = registrarAccessor.getAllRegistrarIdsWithRoles().keySet();
@@ -14,11 +14,11 @@
package google.registry.rdap;
import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_OK;
import static com.google.common.net.HttpHeaders.ACCEPT_ENCODING;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.UrlConnectionUtils.gUnzipBytes;
import static google.registry.request.UrlConnectionUtils.isGZipped;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableMap;
@@ -55,7 +55,7 @@ import org.apache.commons.csv.CSVRecord;
service = Action.Service.BACKEND,
path = "/_dr/task/updateRegistrarRdapBaseUrls",
automaticallyPrintOk = true,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class UpdateRegistrarRdapBaseUrlsAction implements Runnable {
private static final String RDAP_IDS_URL =
@@ -114,7 +114,7 @@ public final class UpdateRegistrarRdapBaseUrlsAction implements Runnable {
connection.setRequestProperty(ACCEPT_ENCODING, "gzip");
String csvString;
try {
if (connection.getResponseCode() != STATUS_CODE_OK) {
if (connection.getResponseCode() != SC_OK) {
throw new UrlConnectionException("Failed to load RDAP base URLs from ICANN", connection);
}
// With GZIP encoding header in the request (see above) ICANN had still sent response in plain
@@ -66,7 +66,7 @@ import org.joda.time.DateTime;
path = BrdaCopyAction.PATH,
method = POST,
automaticallyPrintOk = true,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class BrdaCopyAction implements Runnable {
public static final String PATH = "/_dr/task/brdaCopy";
@@ -56,7 +56,7 @@ import org.joda.time.Duration;
service = Action.Service.BACKEND,
path = RdeReportAction.PATH,
method = POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class RdeReportAction implements Runnable, EscrowTask {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -14,12 +14,12 @@
package google.registry.rde;
import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_BAD_REQUEST;
import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_OK;
import static google.registry.request.UrlConnectionUtils.getResponseBytes;
import static google.registry.request.UrlConnectionUtils.setBasicAuth;
import static google.registry.request.UrlConnectionUtils.setPayload;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.api.client.http.HttpMethods;
@@ -85,7 +85,7 @@ public class RdeReporter {
try {
responseCode = connection.getResponseCode();
if (responseCode != STATUS_CODE_OK && responseCode != STATUS_CODE_BAD_REQUEST) {
if (responseCode != SC_OK && responseCode != SC_BAD_REQUEST) {
logger.atWarning().log("Connection to RDE report server failed: %d", responseCode);
throw new UrlConnectionException("PUT failed", connection);
}
@@ -96,7 +96,7 @@ public class RdeReporter {
// We know that an HTTP 200 response can only contain a result code of
// 1000 (i. e. success), there is no need to parse it.
if (responseCode != STATUS_CODE_OK) {
if (responseCode != SC_OK) {
XjcIirdeaResult result = parseResult(responseBytes);
logger.atWarning().log(
"Rejected when trying to PUT RDE report to ICANN server: %d %s\n%s",
@@ -207,7 +207,7 @@ import org.joda.time.Duration;
service = Action.Service.BACKEND,
path = RdeStagingAction.PATH,
method = {GET, POST},
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class RdeStagingAction implements Runnable {
public static final String PATH = "/_dr/task/rdeStaging";
@@ -87,7 +87,7 @@ import org.joda.time.Duration;
service = Action.Service.BACKEND,
path = RdeUploadAction.PATH,
method = POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class RdeUploadAction implements Runnable, EscrowTask {
public static final String PATH = "/_dr/task/rdeUpload";
@@ -47,7 +47,7 @@ import javax.inject.Inject;
service = Action.Service.BACKEND,
path = CopyDetailReportsAction.PATH,
method = POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class CopyDetailReportsAction implements Runnable {
public static final String PATH = "/_dr/task/copyDetailReports";
@@ -54,7 +54,7 @@ import org.joda.time.YearMonth;
service = Action.Service.BACKEND,
path = GenerateInvoicesAction.PATH,
method = POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class GenerateInvoicesAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -52,7 +52,7 @@ import org.joda.time.YearMonth;
service = Action.Service.BACKEND,
path = PublishInvoicesAction.PATH,
method = POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class PublishInvoicesAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -14,10 +14,10 @@
package google.registry.reporting.icann;
import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_OK;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.net.MediaType.CSV_UTF_8;
import static google.registry.model.tld.Tlds.assertTldExists;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import com.google.api.client.http.HttpMethods;
import com.google.common.base.Ascii;
@@ -95,7 +95,7 @@ public class IcannHttpReporter {
try {
responseCode = connection.getResponseCode();
content = UrlConnectionUtils.getResponseBytes(connection);
if (responseCode != STATUS_CODE_OK) {
if (responseCode != SC_OK) {
XjcIirdeaResult result = parseResult(content);
logger.atWarning().log(
"PUT rejected, status code %s:\n%s\n%s",
@@ -67,7 +67,7 @@ import org.joda.time.format.DateTimeFormat;
service = Action.Service.BACKEND,
path = IcannReportingStagingAction.PATH,
method = POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class IcannReportingStagingAction implements Runnable {
static final String PATH = "/_dr/task/icannReportingStaging";
@@ -70,7 +70,7 @@ import org.joda.time.Duration;
service = Action.Service.BACKEND,
path = IcannReportingUploadAction.PATH,
method = POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class IcannReportingUploadAction implements Runnable {
static final String PATH = "/_dr/task/icannReportingUpload";
@@ -53,7 +53,7 @@ import org.joda.time.LocalDate;
service = Action.Service.BACKEND,
path = GenerateSpec11ReportAction.PATH,
method = POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class GenerateSpec11ReportAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -59,7 +59,7 @@ import org.json.JSONException;
service = Action.Service.BACKEND,
path = PublishSpec11ReportAction.PATH,
method = POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class PublishSpec11ReportAction implements Runnable {
static final String PATH = "/_dr/task/publishSpec11";
@@ -18,8 +18,6 @@ import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import dagger.Module;
import dagger.Provides;
import java.net.HttpURLConnection;
@@ -47,17 +45,6 @@ public final class Modules {
}
}
/** Dagger module for {@link UserService}. */
@Module
public static final class UserServiceModule {
private static final UserService userService = UserServiceFactory.getUserService();
@Provides
static UserService provideUserService() {
return userService;
}
}
/** Dagger module that causes the Google GSON parser to be used for Google APIs requests. */
@Module
public static final class GsonModule {
@@ -67,10 +54,7 @@ public final class Modules {
}
}
/**
* Dagger module that provides standard {@link NetHttpTransport}. Used in non App Engine
* environment.
*/
/** Dagger module that provides standard {@link NetHttpTransport}. */
@Module
public static final class NetHttpTransportModule {
@@ -27,7 +27,7 @@ import java.util.Map;
* Utility class to help in dumping routing maps.
*
* <p>Each of the App Engine services (frontend, backend, and tools) has a Dagger component used for
* routing requests (e.g. FrontendRequestComponent). This class produces a text file representation
* routing requests (e.g., FrontendRequestComponent). This class produces a text file representation
* of the routing configuration, showing what paths map to what action classes, as well as the
* properties of the action classes' annotations (which cover things like allowable HTTP methods,
* authentication settings, etc.). The text file can be useful for documentation, and is also used
@@ -37,13 +37,12 @@ import java.util.Map;
* the content to be displayed. The columns are:
*
* <ol>
* <li>the URL path which maps to this action (with a "(*)" after it if the prefix flag is set)
* <li>the simple name of the action class
* <li>the allowable HTTP methods
* <li>whether to automatically print "ok" in the response
* <li>the allowable authentication methods
* <li>the minimum authentication level
* <li>the user policy
* <li>the URL path which maps to this action (with a "(*)" after it if the prefix flag is set)
* <li>the simple name of the action class
* <li>the allowable HTTP methods
* <li>whether to automatically print "ok" in the response
* <li>the minimum authentication level
* <li>the user policy
* </ol>
*
* <p>See the Auth class for more information about authentication settings.
@@ -53,11 +52,9 @@ public class RouterDisplayHelper {
private static final String PATH = "path";
private static final String CLASS = "class";
private static final String METHODS = "methods";
private static final String AUTH_METHODS = "authMethods";
private static final String MINIMUM_LEVEL = "minLevel";
private static final String FORMAT =
"%%-%ds %%-%ds %%-%ds %%-2s %%-%ds %%-%ds %%s";
private static final String FORMAT = "%%-%ds %%-%ds %%-%ds %%-2s %%-%ds %%s";
/** Returns a string representation of the routing map in the specified component. */
public static String extractHumanReadableRoutesFromComponent(Class<?> componentClass) {
@@ -82,7 +79,6 @@ public class RouterDisplayHelper {
columnWidths.get(PATH),
columnWidths.get(CLASS),
columnWidths.get(METHODS),
columnWidths.get(AUTH_METHODS),
columnWidths.get(MINIMUM_LEVEL));
}
@@ -93,7 +89,6 @@ public class RouterDisplayHelper {
"CLASS",
"METHODS",
"OK",
"AUTH_METHODS",
"MIN",
"USER_POLICY");
}
@@ -105,7 +100,6 @@ public class RouterDisplayHelper {
route.actionClass().getSimpleName(),
Joiner.on(",").join(route.action().method()),
route.action().automaticallyPrintOk() ? "y" : "n",
Joiner.on(",").join(route.action().auth().authSettings().methods()),
route.action().auth().authSettings().minimumLevel(),
route.action().auth().authSettings().userPolicy());
}
@@ -116,7 +110,6 @@ public class RouterDisplayHelper {
int pathWidth = 4;
int classWidth = 5;
int methodsWidth = 7;
int authMethodsWidth = 12;
int minLevelWidth = 3;
for (Route route : routes) {
int len =
@@ -134,10 +127,6 @@ public class RouterDisplayHelper {
if (len > methodsWidth) {
methodsWidth = len;
}
len = Joiner.on(",").join(route.action().auth().authSettings().methods()).length();
if (len > authMethodsWidth) {
authMethodsWidth = len;
}
len = route.action().auth().authSettings().minimumLevel().toString().length();
if (len > minLevelWidth) {
minLevelWidth = len;
@@ -149,7 +138,6 @@ public class RouterDisplayHelper {
.put(PATH, pathWidth)
.put(CLASS, classWidth)
.put(METHODS, methodsWidth)
.put(AUTH_METHODS, authMethodsWidth)
.put(MINIMUM_LEVEL, minLevelWidth)
.build());
return headerToString(formatString)
@@ -14,12 +14,8 @@
package google.registry.request.auth;
import com.google.common.collect.ImmutableList;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.AuthSettings.AuthMethod;
import google.registry.request.auth.AuthSettings.UserPolicy;
import google.registry.ui.server.registrar.HtmlAction;
import google.registry.ui.server.registrar.JsonGetAction;
/** Enum used to configure authentication settings for Actions. */
public enum Auth {
@@ -27,35 +23,17 @@ public enum Auth {
/**
* Allows anyone to access.
*
* <p>If a user is logged in, will authenticate (and return) them. Otherwise, access is still
* granted, but NOT_AUTHENTICATED is returned.
*
* <p>User-facing legacy console endpoints (those that extend {@link HtmlAction}) use it. They
* need to allow requests from signed-out users so that they can redirect users to the login page.
* After a user is logged in, they check if the user actually has access to the specific console
* using {@link AuthenticatedRegistrarAccessor}.
*
* @see HtmlAction
* <p>This is used for public HTML endpoints like RDAP, the check API, and web WHOIS.
*/
AUTH_PUBLIC_LEGACY(
ImmutableList.of(AuthMethod.API, AuthMethod.LEGACY), AuthLevel.NONE, UserPolicy.PUBLIC),
AUTH_PUBLIC(AuthLevel.NONE, UserPolicy.PUBLIC),
/**
* Allows anyone to access, as long as they are logged in.
*
* <p>This is used by legacy registrar console programmatic endpoints (those that extend {@link
* JsonGetAction}), which are accessed via XHR requests sent from a logged-in user when performing
* actions on the console.
* <p>Note that the action might use {@link AuthenticatedRegistrarAccessor} to impose a more
* fine-grained access control pattern than merely whether the user is logged in/out.
*/
AUTH_PUBLIC_LOGGED_IN(
ImmutableList.of(AuthMethod.API, AuthMethod.LEGACY), AuthLevel.USER, UserPolicy.PUBLIC),
/**
* Allows anyone to access.
*
* <p>This is used for public HTML endpoints like RDAP, the check API, and web WHOIS.
*/
AUTH_PUBLIC(ImmutableList.of(AuthMethod.API), AuthLevel.NONE, UserPolicy.PUBLIC),
AUTH_PUBLIC_LOGGED_IN(AuthLevel.USER, UserPolicy.PUBLIC),
/**
* Allows only the app itself (via service accounts) or admins to access.
@@ -64,12 +42,12 @@ public enum Auth {
* associated service account needs to be allowlisted in the {@code
* auth.allowedServiceAccountEmails} field in the config YAML file.
*/
AUTH_API_ADMIN(ImmutableList.of(AuthMethod.API), AuthLevel.APP, UserPolicy.ADMIN);
AUTH_ADMIN(AuthLevel.APP, UserPolicy.ADMIN);
private final AuthSettings authSettings;
Auth(ImmutableList<AuthMethod> methods, AuthLevel minimumLevel, UserPolicy userPolicy) {
authSettings = AuthSettings.create(methods, minimumLevel, userPolicy);
Auth(AuthLevel minimumLevel, UserPolicy userPolicy) {
authSettings = new AuthSettings(minimumLevel, userPolicy);
}
public AuthSettings authSettings() {
@@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.request.auth.AuthSettings.AuthLevel.APP;
import static google.registry.request.auth.AuthSettings.AuthLevel.USER;
import google.registry.model.console.User;
import google.registry.request.auth.AuthSettings.AuthLevel;
import java.util.Optional;
import javax.annotation.Nullable;
@@ -26,24 +27,23 @@ import javax.annotation.Nullable;
* Results of authentication for a given HTTP request, as emitted by an {@link
* AuthenticationMechanism}.
*
* @param userAuthInfo Information about the authenticated user, if there is one.
* @param appServiceAccount Service account email of the authenticated app, if there is one. This
* will be logged upon successful login.
* @param authLevel the level of authentication obtained
* @param user information about the authenticated user, if there is one
* @param serviceAccountEmail service account email of the authenticated app, if there is one
*/
public record AuthResult(
AuthLevel authLevel, Optional<UserAuthInfo> userAuthInfo, Optional<String> appServiceAccount) {
AuthLevel authLevel, Optional<User> user, Optional<String> serviceAccountEmail) {
public boolean isAuthenticated() {
return authLevel() != AuthLevel.NONE;
}
public String userIdForLogging() {
return userAuthInfo()
.map(
userAuthInfo ->
return user.map(
user ->
String.format(
"%s %s",
userAuthInfo.isUserAdmin() ? "admin" : "user", userAuthInfo.getEmailAddress()))
user.getUserRoles().isAdmin() ? "admin" : "user", user.getEmailAddress()))
.orElse("<logged-out user>");
}
@@ -51,22 +51,21 @@ public record AuthResult(
return create(APP, null, email);
}
public static AuthResult createUser(UserAuthInfo userAuthInfo) {
return create(USER, userAuthInfo, null);
public static AuthResult createUser(User user) {
return create(USER, user, null);
}
private static AuthResult create(
AuthLevel authLevel, @Nullable UserAuthInfo userAuthInfo, @Nullable String email) {
AuthLevel authLevel, @Nullable User user, @Nullable String serviceAccountEmail) {
checkArgument(
userAuthInfo == null || email == null,
"User auth info and service account email cannot be specificed at the same time");
user == null || serviceAccountEmail == null,
"User and service account email cannot be specified at the same time");
checkArgument(authLevel != USER || user != null, "User must be specified for auth level USER");
checkArgument(
authLevel != USER || userAuthInfo != null,
"User auth info must be specified for auth level USER");
checkArgument(
authLevel != APP || email != null,
authLevel != APP || serviceAccountEmail != null,
"Service account email must be specified for auth level APP");
return new AuthResult(authLevel, Optional.ofNullable(userAuthInfo), Optional.ofNullable(email));
return new AuthResult(
authLevel, Optional.ofNullable(user), Optional.ofNullable(serviceAccountEmail));
}
/**
@@ -14,7 +14,6 @@
package google.registry.request.auth;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.Immutable;
import google.registry.model.console.UserRoles;
@@ -25,26 +24,7 @@ import google.registry.model.console.UserRoles;
* values.
*/
@Immutable
public record AuthSettings(
ImmutableList<AuthMethod> methods, AuthLevel minimumLevel, UserPolicy userPolicy) {
static AuthSettings create(
ImmutableList<AuthMethod> methods, AuthLevel minimumLevel, UserPolicy userPolicy) {
return new AuthSettings(methods, minimumLevel, userPolicy);
}
/** Available methods for authentication. */
public enum AuthMethod {
/**
* Authentication methods suitable for API-style access, such as {@link
* OidcTokenAuthenticationMechanism}.
*/
API,
/** Legacy authentication using cookie-based App Engine Users API. Must come last if present. */
LEGACY
}
public record AuthSettings(AuthLevel minimumLevel, UserPolicy userPolicy) {
/**
* Authentication level.
@@ -90,16 +70,7 @@ public record AuthSettings(
/** No user policy is enforced; anyone can access this action. */
PUBLIC,
/**
* If there is a user, it must be an admin, as determined by {@link UserAuthInfo#isUserAdmin()}.
*
* <p>Note that, if the user returned is an App Engine {@link
* com.google.appengine.api.users.User} , anybody with access to the app in the GCP Console,
* including editors and viewers, is an admin.
*
* <p>On the other hand, if the user is a {@link google.registry.model.console.User}, the admin
* role is explicitly defined in that object via the {@link UserRoles#isAdmin()} method.
*/
/** If there is a user, it must be an admin, as determined by {@link UserRoles#isAdmin()}. */
ADMIN
}
}
@@ -18,18 +18,16 @@ import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.base.Preconditions.checkNotNull;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.appengine.api.users.User;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.flogger.FluentLogger;
import dagger.Lazy;
import google.registry.config.RegistryConfig.Config;
import google.registry.groups.GroupsConnection;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarBase.State;
import google.registry.model.registrar.RegistrarPoc;
import java.util.Optional;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@@ -37,24 +35,23 @@ import javax.inject.Inject;
/**
* Allows access only to {@link Registrar}s the current user has access to.
*
* <p>A user has OWNER role on a Registrar if there exists a {@link RegistrarPoc} with that user's
* gaeId and the registrar as a parent.
* <p>A user has OWNER role on a Registrar if there exists a mapping to the registrar in its {@link
* google.registry.model.console.UserRoles} map, regardless of the role.
*
* <p>An "admin" has in addition OWNER role on {@code #registryAdminRegistrarId} and to all
* <p>An "admin" has, in addition, OWNER role on {@code #registryAdminRegistrarId} and to all
* non-{@code REAL} registrars (see {@link Registrar#getType}).
*
* <p>An "admin" also has ADMIN role on ALL registrars.
*
* <p>A user is an "admin" if they are a GAE-admin, or if their email is in the "Support" G Suite
* group.
* <p>A user is an "admin" if it has global admin permission, or if their email is in the "Support"
* G Suite group.
*
* <p>NOTE: to check whether the user is in the "Support" G Suite group, we need a connection to G
* Suite. This in turn requires we have valid JsonCredentials, which not all environments have set
* up. This connection will be created lazily (only if needed).
* Suite. This, in turn, requires us to have valid JsonCredentials, which not all environments have
* set up. This connection will be created lazily (only if needed).
*
* <p>Specifically, we don't instantiate the connection if: (a) gSuiteSupportGroupEmailAddress isn't
* defined, or (b) the user is logged out, or (c) the user is a GAE-admin, or (d) bypassAdminCheck
* is true.
* defined, or (b) the user is logged out, or (c) the user is an admin.
*/
@Immutable
public class AuthenticatedRegistrarAccessor {
@@ -70,8 +67,8 @@ public class AuthenticatedRegistrarAccessor {
private final String userIdForLogging;
/**
* Whether this user is an Admin, meaning either a GAE-admin or a member of the Support G Suite
* group.
* Whether this user is an admin, meaning either they have global admin permission or a member of
* the Support G Suite group.
*/
private final boolean isAdmin;
@@ -84,26 +81,6 @@ public class AuthenticatedRegistrarAccessor {
*/
private final ImmutableSetMultimap<String, Role> roleMap;
/**
* Bypass the "isAdmin" check making all users NOT admins.
*
* <p>Currently our test server doesn't let you change the user after the test server was created.
* This means we'd need multiple test files to test the same actions as both a "regular" user and
* an admin.
*
* <p>To overcome this - we add a flag that lets you dynamically choose whether a user is an admin
* or not by creating a fake "GAE-admin" user and then bypassing the admin check if they want to
* fake a "regular" user.
*
* <p>The reason we don't do it the other way around (have a flag that makes anyone an admin) is
* that such a flag would be a security risk, especially since VisibleForTesting is unenforced
* (and you could set it with reflection anyway).
*
* <p>Instead of having a test flag that elevates permissions (which has security concerns) we add
* this flag that reduces permissions.
*/
@VisibleForTesting public static boolean bypassAdminCheck = false;
@Inject
public AuthenticatedRegistrarAccessor(
AuthResult authResult,
@@ -140,9 +117,7 @@ public class AuthenticatedRegistrarAccessor {
return new AuthenticatedRegistrarAccessor("TestUserId", isAdmin, roleMap);
}
/**
* Returns whether this user is allowed to create new Registrars and TLDs.
*/
/** Returns whether this user is allowed to create new Registrars and TLDs. */
public boolean isAdmin() {
return isAdmin;
}
@@ -282,53 +257,39 @@ public class AuthenticatedRegistrarAccessor {
AuthResult authResult,
Optional<String> gSuiteSupportGroupEmailAddress,
Lazy<GroupsConnection> lazyGroupsConnection) {
if (authResult.userAuthInfo().isEmpty()) {
if (authResult.user().isEmpty()) {
return false;
}
UserAuthInfo userAuthInfo = authResult.userAuthInfo().get();
// both GAE project admin and members of the gSuiteSupportGroupEmailAddress are considered
// admins for the RegistrarConsole.
return !bypassAdminCheck
&& (userAuthInfo.isUserAdmin()
|| checkIsSupport(
lazyGroupsConnection,
userAuthInfo.getEmailAddress(),
gSuiteSupportGroupEmailAddress));
User user = authResult.user().get();
// both user object with admin permission and members of the gSuiteSupportGroupEmailAddress are
// considered admins for the RegistrarConsole.
return user.getUserRoles().isAdmin()
|| checkIsSupport(
lazyGroupsConnection, user.getEmailAddress(), gSuiteSupportGroupEmailAddress);
}
/** Returns a map of registrar IDs to roles for all registrars that the user has access to. */
private static ImmutableSetMultimap<String, Role> createRoleMap(
AuthResult authResult, boolean isAdmin, String registryAdminRegistrarId) {
if (authResult.userAuthInfo().isEmpty()) {
if (authResult.user().isEmpty()) {
return ImmutableSetMultimap.of();
}
ImmutableSetMultimap.Builder<String, Role> builder = new ImmutableSetMultimap.Builder<>();
UserAuthInfo userAuthInfo = authResult.userAuthInfo().get();
if (userAuthInfo.appEngineUser().isPresent()) {
User user = userAuthInfo.appEngineUser().get();
logger.atInfo().log("Checking registrar contacts for user ID %s.", user.getEmail());
// Find all registrars that have a registrar contact with this user's ID.
tm().transact(
() ->
tm().query(
"SELECT r FROM Registrar r INNER JOIN RegistrarPoc rp ON r.registrarId ="
+ " rp.registrarId WHERE lower(rp.loginEmailAddress) = :email AND"
+ " r.state != :state",
Registrar.class)
.setParameter("email", Ascii.toLowerCase(user.getEmail()))
.setParameter("state", State.DISABLED)
.getResultStream()
.forEach(registrar -> builder.put(registrar.getRegistrarId(), Role.OWNER)));
} else {
userAuthInfo
.consoleUser()
.get()
.getUserRoles()
.getRegistrarRoles()
.forEach((k, v) -> builder.put(k, Role.OWNER));
}
authResult
.user()
.get()
.getUserRoles()
.getRegistrarRoles()
.forEach(
(k, v) ->
Registrar.loadByRegistrarId(k)
.ifPresent(
registrar -> {
if (registrar.getState() != State.DISABLED) {
builder.put(k, Role.OWNER);
}
}));
// Admins have ADMIN access to all registrars, and also OWNER access to the registry registrar
// and all non-REAL or non-live registrars.
@@ -1,84 +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.request.auth;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.base.Strings.nullToEmpty;
import static google.registry.request.auth.AuthResult.NOT_AUTHENTICATED;
import static google.registry.security.XsrfTokenManager.P_CSRF_TOKEN;
import static google.registry.security.XsrfTokenManager.X_CSRF_TOKEN;
import com.google.appengine.api.users.UserService;
import com.google.common.collect.ImmutableSet;
import google.registry.security.XsrfTokenManager;
import jakarta.servlet.http.HttpServletRequest;
import javax.inject.Inject;
/**
* Authentication mechanism for legacy cookie-based App Engine authentication.
*
* <p>Just use the values returned by UserService.
*/
public class LegacyAuthenticationMechanism implements AuthenticationMechanism {
private final UserService userService;
private final XsrfTokenManager xsrfTokenManager;
/** HTTP methods which are considered safe, and do not require XSRF protection. */
private static final ImmutableSet<String> SAFE_METHODS = ImmutableSet.of("GET", "HEAD");
@Inject
public LegacyAuthenticationMechanism(UserService userService, XsrfTokenManager xsrfTokenManager) {
this.userService = userService;
this.xsrfTokenManager = xsrfTokenManager;
}
@Override
public AuthResult authenticate(HttpServletRequest request) {
if (!userService.isUserLoggedIn()) {
return NOT_AUTHENTICATED;
}
if (!SAFE_METHODS.contains(request.getMethod()) && !validateXsrf(request)) {
return NOT_AUTHENTICATED;
}
return AuthResult.createUser(
UserAuthInfo.create(userService.getCurrentUser(), userService.isUserAdmin()));
}
private boolean validateXsrf(HttpServletRequest request) {
String headerToken = emptyToNull(request.getHeader(X_CSRF_TOKEN));
if (headerToken != null) {
return xsrfTokenManager.validateToken(headerToken);
}
// If we got here - the header didn't have the token.
// It might be in the POST data - however even checking whether the POST data has this entry
// could break the Action!
//
// Reason: if we do request.getParameter, any Action that injects @Payload or @JsonPayload
// would break since it uses request.getReader - and it's an error to call both getReader and
// getParameter!
//
// However, in this case it's acceptable since if we got here - the POST request didn't even
// have the XSRF header meaning if it doesn't have POST data - it's not from a valid source at
// all (a valid but outdated source would have a bad header value, but getting here means we had
// no value at all)
//
// TODO(b/120201577): Once we know from the @Action whether we can use getParameter or not -
// only check getParameter if that's how this @Action uses getParameters.
return xsrfTokenManager.validateToken(nullToEmpty(request.getParameter(P_CSRF_TOKEN)));
}
}
@@ -14,6 +14,8 @@
package google.registry.request.auth;
import static com.google.common.base.Preconditions.checkState;
import com.google.api.client.json.webtoken.JsonWebSignature;
import com.google.auth.oauth2.TokenVerifier;
import com.google.common.annotations.VisibleForTesting;
@@ -117,7 +119,7 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
}
Optional<User> maybeUser = UserDao.loadUser(email);
if (maybeUser.isPresent()) {
return AuthResult.createUser(UserAuthInfo.create(maybeUser.get()));
return AuthResult.createUser(maybeUser.get());
}
logger.atInfo().log("No end user found for email address %s", email);
if (serviceAccountEmails.stream().anyMatch(e -> e.equals(email))) {
@@ -131,11 +133,17 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
@VisibleForTesting
public static void setAuthResultForTesting(@Nullable AuthResult authResult) {
checkState(
RegistryEnvironment.get() == RegistryEnvironment.UNITTEST,
"Explicitly setting auth result is only supported in tests");
authResultForTesting = authResult;
}
@VisibleForTesting
public static void unsetAuthResultForTesting() {
checkState(
RegistryEnvironment.get() == RegistryEnvironment.UNITTEST,
"Explicitly unsetting auth result is only supported in tests");
authResultForTesting = null;
}
@@ -21,9 +21,7 @@ import static google.registry.request.auth.AuthSettings.AuthLevel.USER;
import static google.registry.request.auth.AuthSettings.UserPolicy.ADMIN;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering;
import com.google.common.flogger.FluentLogger;
import google.registry.request.auth.AuthSettings.AuthMethod;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Optional;
import javax.inject.Inject;
@@ -32,16 +30,12 @@ import javax.inject.Inject;
public class RequestAuthenticator {
private final ImmutableList<AuthenticationMechanism> apiAuthenticationMechanisms;
private final LegacyAuthenticationMechanism legacyAuthenticationMechanism;
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Inject
public RequestAuthenticator(
ImmutableList<AuthenticationMechanism> apiAuthenticationMechanisms,
LegacyAuthenticationMechanism legacyAuthenticationMechanism) {
public RequestAuthenticator(ImmutableList<AuthenticationMechanism> apiAuthenticationMechanisms) {
this.apiAuthenticationMechanisms = apiAuthenticationMechanisms;
this.legacyAuthenticationMechanism = legacyAuthenticationMechanism;
}
/**
@@ -66,8 +60,8 @@ public class RequestAuthenticator {
return Optional.empty();
}
if (auth.userPolicy() == ADMIN
&& authResult.userAuthInfo().isPresent()
&& !authResult.userAuthInfo().get().isUserAdmin()) {
&& authResult.user().isPresent()
&& !authResult.user().get().getUserRoles().isAdmin()) {
logger.atWarning().log(
"Not authorized; user policy is ADMIN, but the user was not an admin.");
return Optional.empty();
@@ -84,28 +78,13 @@ public class RequestAuthenticator {
*/
AuthResult authenticate(AuthSettings auth, HttpServletRequest req) {
checkAuthConfig(auth);
for (AuthMethod authMethod : auth.methods()) {
AuthResult authResult;
switch (authMethod) {
// API-based user authentication mechanisms, such as OIDC.
case API -> {
for (AuthenticationMechanism authMechanism : apiAuthenticationMechanisms) {
authResult = authMechanism.authenticate(req);
if (authResult.isAuthenticated()) {
logger.atInfo().log(
"Authenticated via %s: %s", authMechanism.getClass().getSimpleName(), authResult);
return authResult;
}
}
}
// Legacy authentication via UserService
case LEGACY -> {
authResult = legacyAuthenticationMechanism.authenticate(req);
if (authResult.isAuthenticated()) {
logger.atInfo().log("Authenticated via legacy auth: %s", authResult);
return authResult;
}
}
AuthResult authResult;
for (AuthenticationMechanism authMechanism : apiAuthenticationMechanisms) {
authResult = authMechanism.authenticate(req);
if (authResult.isAuthenticated()) {
logger.atInfo().log(
"Authenticated via %s: %s", authMechanism.getClass().getSimpleName(), authResult);
return authResult;
}
}
logger.atInfo().log("No authentication found.");
@@ -114,10 +93,6 @@ public class RequestAuthenticator {
/** Validates an AuthSettings object, checking for invalid setting combinations. */
static void checkAuthConfig(AuthSettings auth) {
checkArgument(!auth.methods().isEmpty(), "Must specify at least one auth method");
checkArgument(
Ordering.explicit(AuthMethod.API, AuthMethod.LEGACY).isStrictlyOrdered(auth.methods()),
"Auth methods must be unique and strictly in order - API, LEGACY");
checkArgument(
(auth.minimumLevel() != NONE) || (auth.userPolicy() != ADMIN),
"Actions with minimal auth level at NONE should not specify ADMIN user policy");
@@ -1,53 +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.request.auth;
import com.google.appengine.api.users.User;
import java.util.Optional;
/**
* Extra information provided by the authentication mechanism about the user.
*
* @param appEngineUser User object from the AppEngine Users API.
* @param isUserAdmin Whether the user is an admin.
* <p>Note that, in App Engine parlance, an admin is any user who is a project owner, editor, OR
* viewer (as well as the specific role App Engine Admin). So even users with read-only access
* to the App Engine product qualify as an "admin".
*/
public record UserAuthInfo(
Optional<google.registry.model.console.User> consoleUser,
Optional<User> appEngineUser,
boolean isUserAdmin) {
public String getEmailAddress() {
return appEngineUser()
.map(User::getEmail)
.orElseGet(() -> consoleUser().get().getEmailAddress());
}
public String getUsername() {
return appEngineUser()
.map(User::getNickname)
.orElseGet(() -> consoleUser().get().getEmailAddress());
}
public static UserAuthInfo create(User user, boolean isUserAdmin) {
return new UserAuthInfo(Optional.empty(), Optional.of(user), isUserAdmin);
}
public static UserAuthInfo create(google.registry.model.console.User user) {
return new UserAuthInfo(Optional.of(user), Optional.empty(), user.getUserRoles().isAdmin());
}
}
@@ -19,7 +19,6 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.joda.time.DateTimeZone.UTC;
import com.google.appengine.api.users.UserService;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.flogger.FluentLogger;
@@ -49,12 +48,10 @@ public final class XsrfTokenManager {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final Clock clock;
private final UserService userService;
@Inject
public XsrfTokenManager(Clock clock, UserService userService) {
public XsrfTokenManager(Clock clock) {
this.clock = clock;
this.userService = userService;
}
/** Generates an XSRF token for a given user based on email address. */
@@ -81,7 +78,7 @@ public final class XsrfTokenManager {
}
/** Validates an XSRF token against the current logged-in user. */
public boolean validateToken(String token) {
public boolean validateToken(String email, String token) {
checkArgumentNotNull(token);
List<String> tokenParts = Splitter.on(':').splitToList(token);
if (tokenParts.size() != 3) {
@@ -104,12 +101,8 @@ public final class XsrfTokenManager {
logger.atInfo().log("Expired timestamp in XSRF token: %s", token);
return false;
}
String currentUserEmail =
userService.isUserLoggedIn() ? userService.getCurrentUser().getEmail() : "";
// Reconstruct the token to verify validity.
String reconstructedToken =
encodeToken(ServerSecret.get().asBytes(), currentUserEmail, timestampMillis);
String reconstructedToken = encodeToken(ServerSecret.get().asBytes(), email, timestampMillis);
if (!token.equals(reconstructedToken)) {
logger.atWarning().log(
"Reconstructed XSRF mismatch (got != expected): %s != %s", token, reconstructedToken);
@@ -70,7 +70,7 @@ import org.joda.time.Duration;
path = NordnUploadAction.PATH,
method = Action.Method.POST,
automaticallyPrintOk = true,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class NordnUploadAction implements Runnable {
static final String PATH = "/_dr/task/nordnUpload";
@@ -54,7 +54,7 @@ import javax.inject.Inject;
path = NordnVerifyAction.PATH,
method = Action.Method.POST,
automaticallyPrintOk = true,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class NordnVerifyAction implements Runnable {
static final String PATH = "/_dr/task/nordnVerify";
@@ -32,7 +32,7 @@ import javax.inject.Inject;
path = "/_dr/task/tmchCrl",
method = POST,
automaticallyPrintOk = true,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class TmchCrlAction implements Runnable {
@Inject Marksdb marksdb;
@@ -35,7 +35,7 @@ import org.bouncycastle.openpgp.PGPException;
path = "/_dr/task/tmchDnl",
method = POST,
automaticallyPrintOk = true,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class TmchDnlAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -34,7 +34,7 @@ import org.bouncycastle.openpgp.PGPException;
path = "/_dr/task/tmchSmdrl",
method = POST,
automaticallyPrintOk = true,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class TmchSmdrlAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -17,19 +17,32 @@ package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableMap;
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.model.console.UserDao;
import google.registry.tools.server.UpdateUserGroupAction;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.inject.Inject;
/** Command to create a new User. */
@Parameters(separators = " =", commandDescription = "Update a user account")
public class CreateUserCommand extends CreateOrUpdateUserCommand {
public class CreateUserCommand extends CreateOrUpdateUserCommand implements CommandWithConnection {
static final String IAP_SECURED_WEB_APP_USER_ROLE = "roles/iap.httpsResourceAccessor";
static final FluentLogger logger = FluentLogger.forEnclosingClass();
private ServiceConnection connection;
@Inject IamClient iamClient;
@Inject
@Config("gSuiteConsoleUserGroupEmailAddress")
Optional<String> maybeGroupEmailAddress;
@Nullable
@Override
User getExistingUser(String email) {
@@ -40,7 +53,29 @@ public class CreateUserCommand extends CreateOrUpdateUserCommand {
@Override
protected String execute() throws Exception {
String ret = super.execute();
iamClient.addBinding(email, IAP_SECURED_WEB_APP_USER_ROLE);
String groupEmailAddress = maybeGroupEmailAddress.orElse(null);
if (groupEmailAddress != null) {
logger.atInfo().log("Adding %s to group %s", email, groupEmailAddress);
connection.sendPostRequest(
UpdateUserGroupAction.PATH,
ImmutableMap.of(
"userEmailAddress",
email,
"groupEmailAddress",
groupEmailAddress,
"groupUpdateMode",
"ADD"),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
} else {
logger.atInfo().log("Granting IAP role to user %s", email);
iamClient.addBinding(email, IAP_SECURED_WEB_APP_USER_ROLE);
}
return ret;
}
@Override
public void setConnection(ServiceConnection connection) {
this.connection = connection;
}
}
@@ -21,22 +21,39 @@ import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableMap;
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.model.console.UserDao;
import google.registry.tools.server.UpdateUserGroupAction;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.inject.Inject;
/** Deletes a {@link User}. */
@Parameters(separators = " =", commandDescription = "Delete a user account")
public class DeleteUserCommand extends ConfirmingCommand {
public class DeleteUserCommand extends ConfirmingCommand implements CommandWithConnection {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private ServiceConnection connection;
@Inject IamClient iamClient;
@Inject
@Config("gSuiteConsoleUserGroupEmailAddress")
Optional<String> maybeGroupEmailAddress;
@Nullable
@Parameter(names = "--email", description = "Email address of the user", required = true)
String email;
@Override
public void setConnection(ServiceConnection connection) {
this.connection = connection;
}
@Override
protected String prompt() {
checkArgumentNotNull(email, "Email must be provided");
@@ -52,7 +69,24 @@ public class DeleteUserCommand extends ConfirmingCommand {
checkArgumentPresent(optionalUser, "Email no longer corresponds to a valid user");
tm().delete(optionalUser.get());
});
iamClient.removeBinding(email, IAP_SECURED_WEB_APP_USER_ROLE);
String groupEmailAddress = maybeGroupEmailAddress.orElse(null);
if (groupEmailAddress != null) {
logger.atInfo().log("Removing %s from group %s", email, groupEmailAddress);
connection.sendPostRequest(
UpdateUserGroupAction.PATH,
ImmutableMap.of(
"userEmailAddress",
email,
"groupEmailAddress",
groupEmailAddress,
"groupUpdateMode",
"REMOVE"),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
} else {
logger.atInfo().log("Removing IAP role from user %s", email);
iamClient.removeBinding(email, IAP_SECURED_WEB_APP_USER_ROLE);
}
return String.format("Deleted user with email %s", email);
}
}
@@ -39,7 +39,6 @@ import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.rde.RdeModule;
import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.UrlConnectionServiceModule;
import google.registry.request.Modules.UserServiceModule;
import google.registry.tools.AuthModule.LocalCredentialModule;
import google.registry.util.UtilsModule;
import google.registry.whois.NonCachingWhoisModule;
@@ -75,7 +74,6 @@ import javax.inject.Singleton;
SecretManagerKeyringModule.class,
SecretManagerModule.class,
UrlConnectionServiceModule.class,
UserServiceModule.class,
UtilsModule.class,
VoidDnsWriterModule.class,
NonCachingWhoisModule.class
@@ -43,7 +43,7 @@ import javax.inject.Inject;
service = Action.Service.TOOLS,
path = CreateGroupsAction.PATH,
method = POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class CreateGroupsAction implements Runnable {
public static final String PATH = "/_dr/admin/createGroups";
@@ -65,7 +65,7 @@ import org.joda.time.Duration;
service = Action.Service.TOOLS,
path = GenerateZoneFilesAction.PATH,
method = POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class GenerateZoneFilesAction implements Runnable, JsonActionRunner.JsonAction {
private static final FluentLogger log = FluentLogger.forEnclosingClass();
@@ -39,7 +39,7 @@ import javax.inject.Inject;
service = Action.Service.TOOLS,
path = ListDomainsAction.PATH,
method = {GET, POST},
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class ListDomainsAction extends ListObjectsAction<Domain> {
/** An App Engine limitation on how many subqueries can be used in a single query. */
@@ -34,7 +34,7 @@ import org.joda.time.DateTime;
service = Action.Service.TOOLS,
path = ListHostsAction.PATH,
method = {GET, POST},
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class ListHostsAction extends ListObjectsAction<Host> {
public static final String PATH = "/_dr/admin/list/hosts";
@@ -35,7 +35,7 @@ import javax.inject.Inject;
service = Action.Service.TOOLS,
path = ListPremiumListsAction.PATH,
method = {GET, POST},
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class ListPremiumListsAction extends ListObjectsAction<PremiumList> {
public static final String PATH = "/_dr/admin/list/premiumLists";
@@ -30,7 +30,7 @@ import javax.inject.Inject;
service = Action.Service.TOOLS,
path = ListRegistrarsAction.PATH,
method = {GET, POST},
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class ListRegistrarsAction extends ListObjectsAction<Registrar> {
public static final String PATH = "/_dr/admin/list/registrars";
@@ -33,7 +33,7 @@ import javax.inject.Inject;
service = Action.Service.TOOLS,
path = ListReservedListsAction.PATH,
method = {GET, POST},
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class ListReservedListsAction extends ListObjectsAction<ReservedList> {
public static final String PATH = "/_dr/admin/list/reservedLists";
@@ -34,7 +34,7 @@ import org.joda.time.DateTime;
service = Action.Service.TOOLS,
path = ListTldsAction.PATH,
method = {GET, POST},
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public final class ListTldsAction extends ListObjectsAction<Tld> {
public static final String PATH = "/_dr/admin/list/tlds";
@@ -54,7 +54,7 @@ import org.joda.time.Duration;
@Action(
service = Action.Service.TOOLS,
path = "/_dr/task/refreshDnsForAllDomains",
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class RefreshDnsForAllDomainsAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -23,12 +23,11 @@ import static google.registry.request.RequestParameters.extractRequiredParameter
import dagger.Module;
import dagger.Provides;
import google.registry.request.Parameter;
import google.registry.tools.server.UpdateUserGroupAction.Mode;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Optional;
/**
* Dagger module for the tools package.
*/
/** Dagger module for the tools package. */
@Module
public class ToolsServerModule {
@@ -75,4 +74,21 @@ public class ToolsServerModule {
static Optional<Integer> provideRefreshQps(HttpServletRequest req) {
return extractOptionalIntParameter(req, "refreshQps");
}
@Provides
static Mode provideGroupUpdateMode(HttpServletRequest req) {
return Mode.valueOf(extractRequiredParameter(req, "groupUpdateMode"));
}
@Provides
@Parameter("userEmailAddress")
static String provideUserEmailAddress(HttpServletRequest req) {
return extractRequiredParameter(req, "userEmailAddress");
}
@Provides
@Parameter("groupEmailAddress")
static String provideGroupEmailAddress(HttpServletRequest req) {
return extractRequiredParameter(req, "groupEmailAddress");
}
}
@@ -0,0 +1,83 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.tools.server;
import static google.registry.request.Action.Method.POST;
import com.google.common.flogger.FluentLogger;
import google.registry.groups.GroupsConnection;
import google.registry.groups.GroupsConnection.Role;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import javax.inject.Inject;
/** Action that adds or deletes a console user to/from the group that has IAP permissions. */
@Action(
service = Action.Service.TOOLS,
path = UpdateUserGroupAction.PATH,
method = POST,
auth = Auth.AUTH_ADMIN)
public class UpdateUserGroupAction implements Runnable {
public static final String PATH = "/_dr/admin/updateUserGroup";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Inject GroupsConnection groupsConnection;
@Inject Response response;
@Inject
@Parameter("userEmailAddress")
String userEmailAddress;
@Inject
@Parameter("groupEmailAddress")
String groupEmailAddress;
@Inject Mode mode;
@Inject
UpdateUserGroupAction() {}
enum Mode {
ADD,
REMOVE
}
@Override
public void run() {
logger.atInfo().log(
"Updating group %s: %s user %s",
groupEmailAddress, mode == Mode.ADD ? "adding" : "removing", userEmailAddress);
try {
if (mode == Mode.ADD) {
// The group will be created if it does not exist.
groupsConnection.addMemberToGroup(groupEmailAddress, userEmailAddress, Role.MEMBER);
} else {
if (groupsConnection.isMemberOfGroup(userEmailAddress, groupEmailAddress)) {
groupsConnection.removeMemberFromGroup(groupEmailAddress, userEmailAddress);
} else {
logger.atInfo().log(
"Ignoring request to remove non-member %s from group %s",
userEmailAddress, groupEmailAddress);
}
}
} catch (Exception e) {
throw new RuntimeException("Cannot update group", e);
}
}
}
@@ -37,7 +37,7 @@ import javax.inject.Inject;
service = Action.Service.TOOLS,
path = VerifyOteAction.PATH,
method = Action.Method.POST,
auth = Auth.AUTH_API_ADMIN)
auth = Auth.AUTH_ADMIN)
public class VerifyOteAction implements Runnable, JsonAction {
public static final String PATH = "/_dr/admin/verifyOte";
@@ -35,7 +35,7 @@ import javax.mail.internet.InternetAddress;
public class SendEmailUtils {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final GmailClient gmailClient;
public final GmailClient gmailClient;
private final ImmutableList<String> registrarChangesNotificationEmailAddresses;
@Inject
@@ -14,24 +14,44 @@
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.request.Action.Method.GET;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import com.google.api.client.http.HttpStatusCodes;
import com.google.common.base.Ascii;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import google.registry.batch.CloudTasksUtils;
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.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.registrar.RegistrarPocBase;
import google.registry.request.Action.Service;
import google.registry.request.HttpException;
import google.registry.request.auth.AuthResult;
import google.registry.security.XsrfTokenManager;
import google.registry.ui.server.registrar.ConsoleApiParams;
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;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import javax.inject.Inject;
/** Base class for handling Console API requests */
public abstract class ConsoleApiAction implements Runnable {
@@ -40,6 +60,8 @@ public abstract class ConsoleApiAction implements Runnable {
protected ConsoleApiParams consoleApiParams;
@Inject CloudTasksUtils cloudTasksUtils;
public ConsoleApiAction(ConsoleApiParams consoleApiParams) {
this.consoleApiParams = consoleApiParams;
}
@@ -47,13 +69,11 @@ public abstract class ConsoleApiAction implements Runnable {
@Override
public final void run() {
// Shouldn't be even possible because of Auth annotations on the various implementing classes
AuthResult authResult = consoleApiParams.authResult();
if (authResult.userAuthInfo().isEmpty()
|| authResult.userAuthInfo().get().consoleUser().isEmpty()) {
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
if (consoleApiParams.authResult().user().isEmpty()) {
consoleApiParams.response().setStatus(SC_UNAUTHORIZED);
return;
}
User user = consoleApiParams.authResult().userAuthInfo().get().consoleUser().get();
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
@@ -71,21 +91,19 @@ public abstract class ConsoleApiAction implements Runnable {
if (consoleApiParams.request().getMethod().equals(GET.toString())) {
getHandler(user);
} else {
if (verifyXSRF()) {
if (verifyXSRF(user)) {
postHandler(user);
}
}
} catch (ConsolePermissionForbiddenException e) {
logger.atWarning().withCause(e).log("Forbidden");
setFailedResponse("", HttpStatusCodes.STATUS_CODE_FORBIDDEN);
setFailedResponse("", SC_FORBIDDEN);
} catch (HttpException.BadRequestException | IllegalArgumentException e) {
logger.atWarning().withCause(e).log("Error in request");
setFailedResponse(
Throwables.getRootCause(e).getMessage(), HttpStatusCodes.STATUS_CODE_BAD_REQUEST);
setFailedResponse(Throwables.getRootCause(e).getMessage(), SC_BAD_REQUEST);
} catch (Throwable t) {
logger.atWarning().withCause(t).log("Internal server error");
setFailedResponse(
Throwables.getRootCause(t).getMessage(), HttpStatusCodes.STATUS_CODE_SERVER_ERROR);
setFailedResponse(Throwables.getRootCause(t).getMessage(), SC_INTERNAL_SERVER_ERROR);
}
}
@@ -111,23 +129,130 @@ public abstract class ConsoleApiAction implements Runnable {
consoleApiParams.response().setPayload(message);
}
private boolean verifyXSRF() {
private boolean verifyXSRF(User user) {
Optional<Cookie> maybeCookie =
Arrays.stream(consoleApiParams.request().getCookies())
.filter(c -> XsrfTokenManager.X_CSRF_TOKEN.equals(c.getName()))
.findFirst();
if (maybeCookie.isEmpty()
|| !consoleApiParams.xsrfTokenManager().validateToken(maybeCookie.get().getValue())) {
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
|| !consoleApiParams
.xsrfTokenManager()
.validateToken(user.getEmailAddress(), maybeCookie.get().getValue())) {
consoleApiParams.response().setStatus(SC_UNAUTHORIZED);
return false;
}
return true;
}
private Map<String, Object> expandRegistrarWithContacts(
ImmutableSet<RegistrarPoc> contacts, Registrar registrar) {
ImmutableSet<Map<String, Object>> expandedContacts =
contacts.stream()
.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());
Map<String, Object> registrarDiffMap = registrar.toDiffableFieldMap();
Stream.of("passwordHash", "salt") // fields to remove from final diff
.forEach(fieldToBeRemoved -> registrarDiffMap.remove(fieldToBeRemoved));
// Use LinkedHashMap here to preserve ordering; null values mean we can't use ImmutableMap.
LinkedHashMap<String, Object> result = new LinkedHashMap<>(registrarDiffMap);
result.put("contacts", expandedContacts);
return result;
}
protected void sendExternalUpdates(
Map<?, ?> diffs, Registrar registrar, ImmutableSet<RegistrarPoc> contacts) {
if (!consoleApiParams.sendEmailUtils().hasRecipients() && contacts.isEmpty()) {
return;
}
if (!RegistryEnvironment.UNITTEST.equals(RegistryEnvironment.get())
&& cloudTasksUtils != null) {
// 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.createGetTask(
SyncRegistrarsSheetAction.PATH, Service.BACKEND, ImmutableMultimap.of()));
}
String environment = Ascii.toLowerCase(String.valueOf(RegistryEnvironment.get()));
consoleApiParams
.sendEmailUtils()
.sendEmail(
String.format(
"Registrar %s (%s) updated in registry %s environment",
registrar.getRegistrarName(), registrar.getRegistrarId(), environment),
String.format(
"""
The following changes were made in registry %s environment to the registrar %s by\
%s:
%s""",
environment,
registrar.getRegistrarId(),
consoleApiParams.authResult().userIdForLogging(),
DiffUtils.prettyPrintDiffedMap(diffs, null)),
contacts.stream()
.filter(c -> c.getTypes().contains(RegistrarPocBase.Type.ADMIN))
.map(RegistrarPoc::getEmailAddress)
.collect(toImmutableList()));
}
/**
* 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.
*/
protected void sendExternalUpdatesIfNecessary(EmailInfo emailInfo) {
ImmutableSet<RegistrarPoc> existingContacts = emailInfo.contacts();
Registrar existingRegistrar = emailInfo.registrar();
Map<?, ?> diffs =
DiffUtils.deepDiff(
expandRegistrarWithContacts(existingContacts, existingRegistrar),
expandRegistrarWithContacts(emailInfo.updatedContacts(), emailInfo.updatedRegistrar()),
true);
@SuppressWarnings("unchecked")
Set<String> changedKeys = (Set<String>) diffs.keySet();
if (Sets.difference(changedKeys, ImmutableSet.of("lastUpdateTime")).isEmpty()) {
return;
}
sendExternalUpdates(diffs, existingRegistrar, existingContacts);
}
protected record EmailInfo(
Registrar registrar,
Registrar updatedRegistrar,
ImmutableSet<RegistrarPoc> contacts,
ImmutableSet<RegistrarPoc> updatedContacts) {
public static EmailInfo create(
Registrar registrar,
Registrar updatedRegistrar,
ImmutableSet<RegistrarPoc> contacts,
ImmutableSet<RegistrarPoc> updatedContacts) {
return new EmailInfo(registrar, updatedRegistrar, contacts, updatedContacts);
}
}
/** Specialized exception class used for failure when a user doesn't have the right permission. */
private static class ConsolePermissionForbiddenException extends RuntimeException {
private ConsolePermissionForbiddenException(String message) {
super(message);
}
}
}
@@ -15,8 +15,9 @@
package google.registry.ui.server.console;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import com.google.api.client.http.HttpStatusCodes;
import com.google.gson.Gson;
import google.registry.model.EppResourceUtils;
import google.registry.model.console.ConsolePermission;
@@ -59,16 +60,16 @@ public class ConsoleDomainGetAction extends ConsoleApiAction {
EppResourceUtils.loadByForeignKeyCached(
Domain.class, paramDomain, tm().getTransactionTime()));
if (possibleDomain.isEmpty()) {
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_NOT_FOUND);
consoleApiParams.response().setStatus(SC_NOT_FOUND);
return;
}
Domain domain = possibleDomain.get();
if (!user.getUserRoles()
.hasPermission(domain.getCurrentSponsorRegistrarId(), ConsolePermission.DOWNLOAD_DOMAINS)) {
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_NOT_FOUND);
consoleApiParams.response().setStatus(SC_NOT_FOUND);
return;
}
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_OK);
consoleApiParams.response().setStatus(SC_OK);
consoleApiParams.response().setPayload(gson.toJson(domain));
}
}
@@ -17,8 +17,8 @@ package google.registry.ui.server.console;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.console.ConsolePermission.DOWNLOAD_DOMAINS;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import com.google.api.client.http.HttpStatusCodes;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ascii;
import com.google.gson.Gson;
@@ -118,7 +118,7 @@ public class ConsoleDomainListAction extends ConsoleApiAction {
consoleApiParams
.response()
.setPayload(gson.toJson(new DomainListResult(domains, checkpoint, actualTotalResults)));
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_OK);
consoleApiParams.response().setStatus(SC_OK);
}
/** Creates the query to get the total number of matching domains, interpolating as necessary. */
@@ -17,13 +17,16 @@ package google.registry.ui.server.console;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.POST;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import com.google.api.client.http.HttpStatusCodes;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.gson.annotations.Expose;
import google.registry.flows.EppException.AuthenticationErrorException;
import google.registry.flows.PasswordOnlyTransportCredentials;
import google.registry.groups.GmailClient;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
import google.registry.request.Action;
@@ -32,10 +35,9 @@ import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.util.EmailMessage;
import google.registry.util.DiffUtils;
import java.util.Optional;
import javax.inject.Inject;
import javax.mail.internet.InternetAddress;
@Action(
service = Action.Service.DEFAULT,
@@ -43,16 +45,12 @@ import javax.mail.internet.InternetAddress;
method = {POST},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public class ConsoleEppPasswordAction extends ConsoleApiAction {
protected static final String EMAIL_SUBJ = "EPP password update confirmation";
protected static final String EMAIL_BODY =
"Dear %s,\n" + "This is to confirm that your account password has been changed.";
public static final String PATH = "/console-api/eppPassword";
private final PasswordOnlyTransportCredentials credentials =
new PasswordOnlyTransportCredentials();
private final AuthenticatedRegistrarAccessor registrarAccessor;
private final GmailClient gmailClient;
private final Optional<EppPasswordData> eppPasswordChangeRequest;
@@ -60,11 +58,9 @@ public class ConsoleEppPasswordAction extends ConsoleApiAction {
public ConsoleEppPasswordAction(
ConsoleApiParams consoleApiParams,
AuthenticatedRegistrarAccessor registrarAccessor,
GmailClient gmailClient,
@Parameter("eppPasswordChangeRequest") Optional<EppPasswordData> eppPasswordChangeRequest) {
super(consoleApiParams);
this.registrarAccessor = registrarAccessor;
this.gmailClient = gmailClient;
this.eppPasswordChangeRequest = eppPasswordChangeRequest;
}
@@ -92,28 +88,29 @@ public class ConsoleEppPasswordAction extends ConsoleApiAction {
try {
registrar = registrarAccessor.getRegistrar(eppRequestBody.registrarId());
} catch (RegistrarAccessDeniedException e) {
setFailedResponse(e.getMessage(), HttpStatusCodes.STATUS_CODE_NOT_FOUND);
setFailedResponse(e.getMessage(), SC_NOT_FOUND);
return;
}
try {
credentials.validate(registrar, eppRequestBody.oldPassword());
} catch (AuthenticationErrorException e) {
setFailedResponse(e.getMessage(), HttpStatusCodes.STATUS_CODE_FORBIDDEN);
setFailedResponse(e.getMessage(), SC_FORBIDDEN);
return;
}
tm().transact(
() -> {
tm().put(registrar.asBuilder().setPassword(eppRequestBody.newPassword()).build());
this.gmailClient.sendEmail(
EmailMessage.create(
EMAIL_SUBJ,
String.format(EMAIL_BODY, registrar.getRegistrarName()),
new InternetAddress(registrar.getEmailAddress(), true)));
Registrar updatedRegistrar =
registrar.asBuilder().setPassword(eppRequestBody.newPassword()).build();
tm().put(updatedRegistrar);
sendExternalUpdates(
ImmutableMap.of("password", new DiffUtils.DiffPair("********", "••••••••")),
registrar,
ImmutableSet.of());
});
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_OK);
consoleApiParams.response().setStatus(SC_OK);
}
public record EppPasswordData(
@@ -24,8 +24,9 @@ import static google.registry.request.RequestParameters.extractOptionalLongParam
import static google.registry.request.RequestParameters.extractOptionalParameter;
import static google.registry.request.RequestParameters.extractRequiredParameter;
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;
import com.google.api.client.http.HttpStatusCodes;
import com.google.common.collect.ImmutableList;
import com.google.gson.Gson;
import google.registry.flows.EppException;
@@ -90,7 +91,7 @@ public class ConsoleRegistryLockAction extends ConsoleApiAction {
protected void getHandler(User user) {
checkPermission(user, registrarId, ConsolePermission.REGISTRY_LOCK);
consoleApiParams.response().setPayload(gson.toJson(getLockedDomains()));
consoleApiParams.response().setStatus(HttpStatusCodes.STATUS_CODE_OK);
consoleApiParams.response().setStatus(SC_OK);
}
@Override
@@ -124,8 +125,7 @@ public class ConsoleRegistryLockAction extends ConsoleApiAction {
if (!isAdmin) {
checkArgument(maybePassword.isPresent(), "No password provided");
if (!user.verifyRegistryLockPassword(maybePassword.get())) {
setFailedResponse(
"Incorrect registry lock password", HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
setFailedResponse("Incorrect registry lock password", SC_UNAUTHORIZED);
return;
}
}
@@ -147,7 +147,7 @@ public class ConsoleRegistryLockAction extends ConsoleApiAction {
relockDurationMillis.map(Duration::new));
sendVerificationEmail(registryLock, registryLockEmail, isLock);
});
response.setStatus(HttpStatusCodes.STATUS_CODE_OK);
response.setStatus(SC_OK);
}
private void sendVerificationEmail(RegistryLock lock, String userEmail, boolean isLock) {
@@ -0,0 +1,112 @@
// Copyright 2024 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.console;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.POST;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import static org.apache.http.HttpStatus.SC_OK;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
import google.registry.request.Action;
import google.registry.request.HttpException.BadRequestException;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.util.DomainNameUtils;
import google.registry.util.RegistryEnvironment;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.inject.Inject;
@Action(
service = Action.Service.DEFAULT,
path = ConsoleEppPasswordAction.PATH,
method = {POST},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public class ConsoleUpdateRegistrarAction extends ConsoleApiAction {
static final String PATH = "/console-api/registrar";
private final Optional<Registrar> registrar;
@Inject
ConsoleUpdateRegistrarAction(
ConsoleApiParams consoleApiParams,
@Parameter("registrar") Optional<Registrar> registrar) {
super(consoleApiParams);
this.registrar = registrar;
}
@Override
protected void postHandler(User user) {
var errorMsg = "Missing param(s): %s";
Registrar registrarParam =
registrar.orElseThrow(() -> new BadRequestException(String.format(errorMsg, "registrar")));
checkArgument(!Strings.isNullOrEmpty(registrarParam.getRegistrarId()), errorMsg, "registrarId");
checkPermission(
user, registrarParam.getRegistrarId(), ConsolePermission.EDIT_REGISTRAR_DETAILS);
tm().transact(
() -> {
Optional<Registrar> existingRegistrar =
Registrar.loadByRegistrarId(registrarParam.getRegistrarId());
checkArgument(
!existingRegistrar.isEmpty(),
"Registrar with registrarId %s doesn't exists",
registrarParam.getRegistrarId());
// Only allow modifying allowed TLDs if we're in a non-PRODUCTION environment, if the
// registrar is not REAL, or the registrar has a WHOIS abuse contact set.
if (!registrarParam.getAllowedTlds().isEmpty()) {
boolean isRealRegistrar =
Registrar.Type.REAL.equals(existingRegistrar.get().getType());
if (RegistryEnvironment.PRODUCTION.equals(RegistryEnvironment.get())
&& isRealRegistrar) {
checkArgumentPresent(
existingRegistrar.get().getWhoisAbuseContact(),
"Cannot modify allowed TLDs if there is no WHOIS abuse contact set. Please"
+ " use the \"nomulus registrar_contact\" command on this registrar to"
+ " set a WHOIS abuse contact.");
}
}
Registrar updatedRegistrar =
existingRegistrar
.get()
.asBuilder()
.setAllowedTlds(
registrarParam.getAllowedTlds().stream()
.map(DomainNameUtils::canonicalizeHostname)
.collect(Collectors.toSet()))
.setRegistryLockAllowed(registrarParam.isRegistryLockAllowed())
.build();
tm().put(updatedRegistrar);
sendExternalUpdatesIfNecessary(
EmailInfo.create(
existingRegistrar.get(),
updatedRegistrar,
ImmutableSet.of(),
ImmutableSet.of()));
});
consoleApiParams.response().setStatus(SC_OK);
}
}

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