1
0
mirror of https://github.com/google/nomulus synced 2026-05-21 23:31:51 +00:00

Compare commits

...

60 Commits

Author SHA1 Message Date
Weimin Yu
77ab80f3dc Fix OOM in UploadBsaUnavailableDomains action (#2817)
* Fix OOM in UploadBsaUnavailableDomains action

The action was using string concatenation to generate the upload content.
This causes an OOM when string length exceeds 25MB on our current VM.

This PR witches to streaming upload.

Also added an HTTP upload test.

* Fix OOM in UploadBsaUnavailableDomains action

The action was using string concatenation to generate the upload content.
This causes an OOM when string length exceeds 25MB on our current VM.

This PR witches to streaming upload.

Also added an HTTP upload test.
2025-09-03 18:25:56 +00:00
Pavlo Tkach
5e1cd0120f Adjust proxy resource allocation and update nomulus compute class (#2814) 2025-08-28 18:49:16 +00:00
Weimin Yu
0167dad85f Fix OOM error in BsaValidation (#2813)
Error happened in the case that an unblockable name reported with
'Registered' as reason has been deregistered. We tried to check the
deletion time of the domain to decide if this is a transient error
that is no worth reporting. However, we forgot that we do not have
the domain key in this case.

As best-effort action, and with a case that rarely happens, we decide
not to make the optimization (staleness check) in thise case.
2025-08-27 15:47:13 +00:00
Weimin Yu
1eaf3d4aa8 Fix the schema-verifier in Cloud Build (#2812)
This is the same problem as the one that broke the Java test:

pg_dump upgrade to 17.6 added new meta command lines.
2025-08-25 17:17:49 +00:00
Pavlo Tkach
d9c46170dd Add time-limited logs to Epp Login flow to test increased latency (#2810) 2025-08-22 20:30:53 +00:00
gbrodman
e8a475f48b Remove Contact objects in RDAP output (#2811)
Note: this still includes "contacts" for registrars, which are actually
a different concept that we call RegistrarPoc. That's different from
"Contact" objects, e.g. registrant.
2025-08-22 20:25:20 +00:00
gbrodman
bdaab9daa5 Tag more junit versions to <6.0 (#2809)
We're still picking up some 6.0.0-M2 jars which are causing failures at
least for me when trying to run individual tests in IDEA.
2025-08-22 02:55:59 +00:00
gbrodman
7e07fabf7e Return RDAP 404 for domain w/nonexistent TLD (#2808)
The TLD is technically valid but it doesn't exist for us -- we should
return 404 instead of 400 in these situations according to the RDAP
conformance docs
2025-08-21 15:31:51 +00:00
gbrodman
16859bb36a Use https in RDAP URLs provided (#2807)
Load balancer / internal redirections can result in the final request
URL lacking "https" when finally getting to the servlet. As a result,
even if you use https in the request, the resulting URL can be plain
http.

We need to include the actual (HTTPS) URL in the output, so replace it.
2025-08-20 14:15:54 +00:00
Weimin Yu
7c92928f2c Update gradle dependency locks (#2806)
Also emoved Junit-4.
2025-08-19 16:17:47 +00:00
Pavlo Tkach
de8d205657 Make k8s adjustments (#2803)
This increases hikari fetch size to 40 from 20 in order to decrease the
amount of round trips
This also sets lower CPU as we seem to have overshot CPU consumtion
This also set min replicas to 8 for EPP and max to 16 as we've been
running on 8-10 for the last week
2025-08-19 14:24:11 +00:00
gbrodman
4738b979e4 Add FE for password-reset verification (#2795)
Tested locally and on alpha with dummy values (and throwing an
exception).

I was able to reuse a bit of code from the EPP password reset, but not
all of it.
2025-08-19 03:00:44 +00:00
Ben McIlwain
a61a667992 Add expiry_access_period_enabled boolean column to Tld table (#2804)
This is the first in a series of PRs to implement the expiry access period
(XAP).  The overall fee schedules will be set in YAML config files, so the only
DB change necessary should be this single new boolean column on the Tld entity,
which defaults to false so as to require XAP explicitly being turned on for a
given TLD.

BUG=http://b/437398822
2025-08-18 22:32:46 +00:00
Weimin Yu
1164070576 Update golden schema comparison in schema test (#2805)
Postgresql-17.6 introduces two new lines in pg_dump output as a
security feature: `\restricted {HASH}` and `\unrestricted {HASH}`.

We filter out lines starting with these two prefixes when comparing
schemas.

The db upgrade also adds two empty lines to the pg_dump output. We
know ignore all empty lines when comparing schemas.
2025-08-18 20:41:47 +00:00
gbrodman
d23640a54f Update LoadTestAction for GKE and no-contacts (#2800)
It's necessary to remove the GAE-related code (and use GKE launch
commands instead), and we might as well remove contact-related fields
and actions because of the upcoming move to the minimum data set.
2025-08-13 18:35:46 +00:00
Pavlo Tkach
cc347264f1 Revert "Remove nodeSelector from k8s deployments (#2798)" (#2799)
This reverts commit 5cef2dd8b5.
We faced CPU quota issue with standard machine type, so rolling back to c4
for now to monitor server performance and decide if we want to try to
downgrade again in the future.
2025-08-13 15:05:54 +00:00
Pavlo Tkach
e5d4cbb9fc Increase hikari maximum pool size (#2802) 2025-08-12 21:01:28 +00:00
gbrodman
8c1e0ff4de Chage run-time of DeleteExpiredDomainsAction (#2801)
We probably want this to run before the billing recurrence expansion
pipeline just in case there are any domains that should be deleted
before their billing recurrence gets expanded.
2025-08-12 20:48:04 +00:00
Pavlo Tkach
5cef2dd8b5 Remove nodeSelector from k8s deployments (#2798)
nodeSelector can limit scheduling capabilities of k8s, which leads to delays in assigning new workloads. Since we do not require and particular machine for execution it can be removed.
2025-08-10 16:16:48 +00:00
gbrodman
62b2585220 Fix load-testing URL in backend routing k8s file (#2797) 2025-08-08 20:20:40 +00:00
sharma1210
8692fe35db Provide specific reason for invalid SSL certificate (#2792)
* Fix: Robustly parse certs and provide specific errors

* Add test for expired certificate failure

* fixing indentation

* fixing indentation

* Update SecurityActionTest.java

* Update SecurityActionTest.java for correcting the testcase

* Fix: Provide indentation fix

* Fixing Deduplication in test
2025-08-08 18:41:14 +00:00
Pavlo Tkach
18614ba11e Update resource allocation for all Nomulus GKE deployments (#2796)
ALl deployments received update to averageUtilization cpu. This should allow us to stay ahead of the curve of traffic and create instances before we cpu reached the limit.
Frontend cpu allocation has caused "noise neighbors" problem with pods assigned to nodes where there's not enough bursting capacity, so I increased it.
Adjusted rest of the deployments according to their utilization.
2025-08-08 17:55:08 +00:00
Pavlo Tkach
427f6db820 Update resource allocation for proxy deployment (#2794)
Missing resource requests as well as metrics for when to evict resource
produced situation when under load k8s struggled to assign pods. This
adds default resource requirements based on 2 weeks metrics and
instructions when resource should be evicted.
2025-08-08 15:35:22 +00:00
Weimin Yu
5aa40b2208 Fix error handling in CopyDetailReportsAction (#2793)
* Fix error handling in CopyDetailReportsAction

The action tries to record errors per registrar in an ImmutableMap, without realizing that
there may be duplicate keys due to retries.

Switched to the `buildKeepingLast` method to build the map.

* Addressing comments and rebase
2025-08-06 16:43:29 +00:00
Pavlo Tkach
95c89bc856 Add registrar id header to proxy requests (#2791) 2025-08-05 17:57:04 +00:00
gbrodman
c21b66f0fb Add reset-EPP-password frontend component (#2786) 2025-08-01 19:53:20 +00:00
gbrodman
b070c46231 Allow superuser status to override EPP resource delete prohibited status (#2789) 2025-08-01 19:51:44 +00:00
Weimin Yu
a20eb8d1e9 Fix failures in retries when inserting new objects (#2788)
Given an entity with auto-filled id fields (annotated with
@GeneratedValue, with null as initial value), when inserting it
using Hibernate, the id fields will be filled with non-nulls even
if the transaction fails.

If the same entity instance is used again in a retry, Hibernate mistakes
it as a detached entity and raises an error.

The work around is to make a new copy of the entity in each transaction.
This PR applies this pattern to affected entity types.

We considered applying this pattern to JpaTransactionManagerImpl's insert
method so that individual call sites do not have to change. However, we
decided against it because:

- It is unnecessary for entity types that do not have auto-filled id

- The JpaTransactionManager cannot tell if copying is cheap or
  expensive. It is better exposing this to the user.

- The JpaTransactionManager needs to know how to clone entities. A new
  interface may need to be introduced just for a handful of use cases.
2025-08-01 18:53:50 +00:00
Ben McIlwain
338b8edb97 Make the contacts prohibited feature flag for min reg data set more lenient (#2787)
It will now only throw errors on domain updates if a new contact/registrant has
been specified where none was previously present. This means that domain updates
on unrelated fields (e.g. nameserver changes) will succeed even if there is
existing contact data that the update is not removing.

This is a follow-up to #2781.

BUG=http://b/434958659
2025-07-29 20:54:58 +00:00
gbrodman
9f191e9392 Add Registry Lock password reset on front end (#2785)
This is only enabled for admins, for now at least. It sends an email to
the registry lock email address to reset it.
2025-07-28 20:23:39 +00:00
gbrodman
39c2a79898 Remove superfluous DatabaseHelper db methods (#2784)
Some of these have been around since the Datastore days and are no
longer relevant (dealing with things like Datastore foreign keys). Let's
simplify things.
2025-07-25 17:00:24 +00:00
Pavlo Tkach
e2e9d4cfc7 Add console history api (#2782) 2025-07-18 18:46:21 +00:00
gbrodman
2948dcc1be Add password reset request and verify console actions (#2775)
This works fairly similarly to the registry lock request and
verification mechanism. The request action generates a UUI which is
emailed (in link form) to the user in question. The frontend will send a
request to the verify action with the UUID and hopefully the action
should be finalized.

EPP password requests can be sent by anyone with edit-registrar
permissions and must be approved by an admin POC email.

Registry lock password resets can only be sent by primary contacts, and
are verified/performed by the user in question.
2025-07-17 21:33:29 +00:00
Pavlo Tkach
c5644d5c8b Add stream to the console dum download (#2783) 2025-07-16 18:56:20 +00:00
Ben McIlwain
514d24ed67 Implement the contacts prohibited feature flag for minimum data set (#2781)
This prohibits all contact data on create and update EPP flows for both domain
and contact flows. It also refactors how default values on FeatureFlags work, as
it's safer to specify a single default on the flag itself rather than have to
specify it independently at a number of callsites (and potentially end up having
an inconsistent value). Domain updates on existing domains that still have
contact data will fail unless all contact data is removed, as a forcing function
to require registrars to rectify the situation prior to being able to do any
other kind of domain changes.

Contact-related flows that are still allowed after this point: Updating a domain
to remove all contacts from it, and deleting a contact object.
2025-07-14 15:29:14 +00:00
gbrodman
c6868b771b Update RDAP response profile + tech impl guide versions (#2778)
This corresponds to the Feb 2024 response profile section 1.2 and
implementation guide 1.3 respectively, now that we comply (or are, at
least closer to complying), with the Feb 2024 versions.

This should probably depend on https://github.com/google/nomulus/pull/2771
because that includes a small change included in the Feb 2024 version

This also updates the documentation to reference the proper areas of the
specifications.
2025-07-09 21:02:33 +00:00
gbrodman
f34aec8b56 Add an "about" link to registrars in RDAP (#2771)
From the response profile:
2.4.6. Registrar URL - The entity with the registrar role in the RDAP response
MUST contain a links member [RFC9083]. The links object MUST contain
the elements: value, identical to the the RDAP Base URL for the
Registrar as provided in the IANA “Registrar IDs” registry (i.e.,
https://www.iana.org/assignments/registrar-ids); rel:about, and href
containing the Registrar URL. Note: in cases where the Registry Operator
acts as sponsoring Registrar (e.g., IANA Registrar ID 9999), the href shall
contain a URL from the Registry.
2025-07-08 14:54:07 +00:00
Ben McIlwain
b27b077638 Increment proxy metrics by reciprocal of proxy metrics ratio (#2780)
This is necessary so that the total number of requests/responses adds up
correctly even though some fraction of them are only being recorded. It uses
stochastic rounding so that the totals add up correctly even when the reciprocal
of the ratio isn't an integer.

This is a follow-up to PR #2772.
2025-07-02 15:52:47 +00:00
Ben McIlwain
0e8cd75a58 Add the ability to configure a ratio of proxy metrics to be recorded (#2772)
This ratio defaults to 1.0 (i.e. all metrics will be recorded), but we will set
it much lower in sandbox and production, probably something closer to 0.01. This
will reduce recorded metrics volume and thus StackDriver cost, while still
retaining enough data for overall performance monitoring.

This is handled stochastically, so as to not require any coordination between
Java threads or GKE pods/clusters, as alternative approaches would (i.e. using a
counter and recording every Nth, or throttling to a max metrics qps).
2025-06-27 05:03:59 +00:00
gbrodman
2a1748ba9c Cache history values for RDAP domain requests (#2777)
In RDAP, domain queries are the most common by a factor of like 40,000
so we should optimize these as much as possible. We already have an EPP
resource / foreign key cache which does improve performance somewhat but
looking at some sample logs, it only cuts the RDAP request times by like
40% (looking at requests for the same domain a few seconds apart).

History entries don't change often, so we should cache them to make
subsequent queries faster as well. In addition, we're only caching two
fields per repo ID (modification time, registrar ID) so we can cache
more entries than we can for the EPP resource cache (which stores large
objects).
2025-06-25 19:33:36 +00:00
Weimin Yu
f4889191a4 Fix prober cert renewal scripts (#2776)
Scripts needed by cron jobs wrongly removed by PR 2661.

TESTED: in crash.
2025-06-25 13:51:06 +00:00
Weimin Yu
9eddecf70f Bypass config check for caching when safe (#2773)
Pubapi actions should always use cache, regardless of the config
settings on caching.

In EppResource.java, the original `loadCached(Iterable<VKey>)`
method is renamed to `loadByCacheIfEnabled`. The original
`loadCached(Vkey)` method is renamed to `loadByCache` and always
uses cache.

In EppResourceUtils.java, the original `loadByForeignKeyCached`
method is renamed to `loadByForeignKeyByCacheIfEnabled`. A new
`loadByForeignKeyByCache` method, which always uses cache.

In ForeighKeyUtils.java, the original `loadCached` method is
renamed to `loadByCacheIfEnabled`, and a new `loadCached` method
is added which always uses cache.

Also added a `getContactsFromReplica` method in Registrar,
for use by RDAP actions.
2025-06-20 21:25:02 +00:00
gbrodman
d4bcff0c31 Add password reset Java object (#2765)
A future PR will add the actions that save and use this object. That
future PR will also require loading RegistrarPoc objects given the
registrar ID, hence the change in that class.
2025-06-17 19:00:50 +00:00
Ben McIlwain
62065f88fb Remove spurious parenthesis in URS command output (#2767)
It was making the undo nomulus command look like this:

)nomulus ...
2025-06-16 20:23:48 +00:00
Pavlo Tkach
c9ac9437fd Add java code for RegitrarPoc id (#2770) 2025-06-14 17:37:11 +00:00
gbrodman
1f6a09182d Add some changes related to RDAP Feb 2024 profile (#2759)
This implements two type of changes:
1. changing the link type for things like the terms of service
2. adding the request URL to each and every link with the "value" field.
   This is a bit tricky to implement because the links are generated in
various places, but we can implement it by adding it to the results
after generation.

See b/418782147 for more information
2025-06-11 20:30:15 +00:00
Weimin Yu
a0eff00031 Add an aggregate module for DNS writers (#2769)
Add a new DnsWritersModule for use by the component classes.

To override the set of writers installed, we can easily overwrite this
file with a private version.
2025-06-09 14:46:54 +00:00
gbrodman
89698c6ed6 Update version of google-java-format (#2766)
This picks up a few changes including aligning the placement of quotes
in text blocks with the Google style guide.
2025-06-06 18:11:54 +00:00
gbrodman
a7696c3fac Add console action test base case (#2762)
We can probably improve on this in the future if we want, but there's a
lot of boilerplate that we don't need to repeat over and over
2025-06-04 15:36:22 +00:00
Weimin Yu
7ec599f849 Fix create_cdns_tld command (#2760)
The Cloud DNS rest api is now case-sensitive about enum names (must be
lower case, counterintuitively).
2025-06-03 15:17:43 +00:00
Pavlo Tkach
70291af9ad Add RegistrarPoc id column (#2761) 2025-06-02 15:43:03 +00:00
gbrodman
5fb95f38ed Don't always require contacts in CreateDomainCommand (#2755)
If contacts are optional, they should be optional in the command too.
2025-05-15 20:22:07 +00:00
gbrodman
dfe8e24761 Add registrar_id col to password reset requests (#2756)
This is just so that we can add an additional layer of security on
verification
2025-05-15 20:13:27 +00:00
Juan Celhay
bd30fcc81c Remove registrar id from invoice grouping key (#2749)
* Remove registrar id from invoice grouping key

* Fix formatting issues

* Update BillingEventTests
2025-05-13 20:29:25 +00:00
gbrodman
8cecc8d3a8 Use the primary DB for DomainInfoFlow (#2750)
This avoids potential replication lag issues when requesting info on
domains that were just created.
2025-05-13 18:00:30 +00:00
Pavlo Tkach
c5a39bccc5 Add Console POC reminder front-end (#2754) 2025-05-12 20:14:56 +00:00
gbrodman
a90a117341 Add SQL table for password resets (#2751)
We plan on using this for EPP password resets and registry lock password
resets for now.
2025-05-08 19:16:08 +00:00
Weimin Yu
b40ad54daf Hardcode beam pipelines to use GKE for tasks (#2753) 2025-05-08 17:29:30 +00:00
Pavlo Tkach
b4d239c329 Add console POC reminder backend support (#2747) 2025-04-30 14:15:43 +00:00
gbrodman
daa7ab3bfa Disable primary-contact editing in console (#2745)
This is necessary because we'll use primary-contact emails as a way of
resetting passwords.

In the UI, don't allow editing of email address for primary contacts,
and don't allow addition/removal of the primary contact field
post-creation.

In the backend, make sure that all emails previously added still exist.
2025-04-29 17:32:29 +00:00
407 changed files with 12371 additions and 13957 deletions

View File

@@ -3,7 +3,7 @@
# This file is expected to be part of source control.
aopalliance:aopalliance:1.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.github.ben-manes.caffeine:caffeine:3.0.5=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.github.ben-manes.caffeine:caffeine:3.2.0=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.github.ben-manes.caffeine:caffeine:3.2.2=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.auto.value:auto-value-annotations:1.11.0=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
@@ -12,13 +12,13 @@ com.google.auto:auto-common:1.2.1=annotationProcessor,errorprone,testAnnotationP
com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,checkstyle,compileClasspath,deploy_jar,errorprone,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testing,testingAnnotationProcessor,testingCompileClasspath
com.google.errorprone:error_prone_annotation:2.23.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.23.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.36.0=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.google.errorprone:error_prone_annotations:2.40.0=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.google.errorprone:error_prone_annotations:2.7.1=checkstyle
com.google.errorprone:error_prone_check_api:2.23.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.errorprone:error_prone_core:2.23.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.errorprone:error_prone_type_annotations:2.23.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.errorprone:javac:9+181-r4173-1=errorproneJavac
com.google.flogger:flogger:0.8=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.google.flogger:flogger:0.9=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.google.guava:failureaccess:1.0.1=annotationProcessor,checkstyle,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.guava:failureaccess:1.0.2=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.google.guava:guava-parent:32.1.1-jre=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
@@ -37,15 +37,14 @@ commons-collections:commons-collections:3.2.2=checkstyle
info.picocli:picocli:4.6.2=checkstyle
io.github.eisop:dataflow-errorprone:3.34.0-eisop1=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
io.github.java-diff-utils:java-diff-utils:4.15=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
io.github.java-diff-utils:java-diff-utils:4.16=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
jakarta.inject:jakarta.inject-api:2.0.1=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
javax.inject:javax.inject:1=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
joda-time:joda-time:2.13.1=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
joda-time:joda-time:2.14.0=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
junit:junit:4.13.2=testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
net.sf.saxon:Saxon-HE:10.6=checkstyle
org.antlr:antlr4-runtime:4.9.3=checkstyle
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath
org.checkerframework:checker-compat-qual:2.5.3=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
org.checkerframework:checker-qual:3.12.0=checkstyle
org.checkerframework:checker-qual:3.33.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
org.checkerframework:checker-qual:3.42.0=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
@@ -56,12 +55,12 @@ org.jacoco:org.jacoco.core:0.8.12=jacocoAnt
org.jacoco:org.jacoco.report:0.8.12=jacocoAnt
org.javassist:javassist:3.28.0-GA=checkstyle
org.jspecify:jspecify:1.0.0=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
org.junit.jupiter:junit-jupiter-api:5.12.1=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:5.12.1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-commons:1.12.1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-engine:1.12.1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-launcher:1.12.1=testCompileClasspath,testRuntimeClasspath
org.junit:junit-bom:5.12.1=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:5.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:5.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-commons:1.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-engine:1.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-launcher:1.13.4=testCompileClasspath,testRuntimeClasspath
org.junit:junit-bom:5.13.4=testCompileClasspath,testRuntimeClasspath
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm-commons:9.7=jacocoAnt
org.ow2.asm:asm-tree:9.7=jacocoAnt

View File

@@ -92,17 +92,19 @@ public class TextDiffSubject extends Subject {
private ImmutableList<String> filterComments(List<String> lines) {
return lines.stream()
.filter(line -> !line.isBlank())
.filter(line -> comments.stream().noneMatch(line::startsWith))
.collect(ImmutableList.toImmutableList());
}
public void hasSameContentAs(List<String> expectedContent) {
checkNotNull(expectedContent, "expectedContent");
ImmutableList<String> expected = filterComments(expectedContent);
if (filterComments(expected).equals(filterComments(actual))) {
ImmutableList<String> filteredExpected = filterComments(expectedContent);
ImmutableList<String> filteredActual = filterComments(actual);
if (filteredExpected.equals(filteredActual)) {
return;
}
String diffString = diffFormat.generateDiff(expected, actual);
String diffString = diffFormat.generateDiff(filteredExpected, filteredActual);
failWithoutActual(
Fact.simpleFact(
Joiner.on('\n')

View File

@@ -26,6 +26,7 @@ import SecurityComponent from './settings/security/security.component';
import { SettingsComponent } from './settings/settings.component';
import { SupportComponent } from './support/support.component';
import RdapComponent from './settings/rdap/rdap.component';
import { PasswordResetVerifyComponent } from './shared/components/passwordReset/passwordResetVerify.component';
export interface RouteWithIcon extends Route {
iconName?: string;
@@ -38,6 +39,10 @@ export const PATHS = {
};
export const routes: RouteWithIcon[] = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{
path: PasswordResetVerifyComponent.PATH,
component: PasswordResetVerifyComponent,
},
{
path: RegistryLockVerifyComponent.PATH,
component: RegistryLockVerifyComponent,

View File

@@ -14,30 +14,71 @@
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { AppComponent } from './app.component';
import { MaterialModule } from './material.module';
import { BackendService } from './shared/services/backend.service';
import { AppRoutingModule } from './app-routing.module';
import { routes } from './app-routing.module';
import { AppModule } from './app.module';
import { PocReminderComponent } from './shared/components/pocReminder/pocReminder.component';
import { RouterModule } from '@angular/router';
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
import { UserData, UserDataService } from './shared/services/userData.service';
import { Registrar, RegistrarService } from './registrar/registrar.service';
import { MatSidenavModule } from '@angular/material/sidenav';
import { signal, WritableSignal } from '@angular/core';
describe('AppComponent', () => {
let component: AppComponent;
let fixture: ComponentFixture<AppComponent>;
let mockRegistrarService: {
registrar: WritableSignal<Partial<Registrar> | null | undefined>;
registrarId: WritableSignal<string>;
registrars: WritableSignal<Array<Partial<Registrar>>>;
};
let mockUserDataService: { userData: WritableSignal<Partial<UserData>> };
let mockSnackBar: jasmine.SpyObj<MatSnackBar>;
const dummyPocReminderComponent = class {}; // Dummy class for type checking
beforeEach(async () => {
mockRegistrarService = {
registrar: signal<Registrar | null | undefined>(undefined),
registrarId: signal('123'),
registrars: signal([]),
};
mockUserDataService = {
userData: signal({
globalRole: 'NONE',
}),
};
mockSnackBar = jasmine.createSpyObj('MatSnackBar', ['openFromComponent']);
await TestBed.configureTestingModule({
declarations: [AppComponent],
imports: [
MaterialModule,
BrowserAnimationsModule,
AppRoutingModule,
MatSidenavModule,
NoopAnimationsModule,
MatSnackBarModule,
AppModule,
RouterModule.forRoot(routes),
],
providers: [
BackendService,
{ provide: RegistrarService, useValue: mockRegistrarService },
{ provide: UserDataService, useValue: mockUserDataService },
{ provide: MatSnackBar, useValue: mockSnackBar },
{ provide: PocReminderComponent, useClass: dummyPocReminderComponent },
provideHttpClient(),
provideHttpClientTesting(),
],
}).compileComponents();
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
});
afterEach(() => {
jasmine.clock().uninstall();
});
it('should create the app', () => {
@@ -46,4 +87,51 @@ describe('AppComponent', () => {
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
describe('PoC Verification Reminder', () => {
beforeEach(() => {
jasmine.clock().install();
});
it('should open snackbar if lastPocVerificationDate is older than one year', fakeAsync(() => {
const MOCK_TODAY = new Date('2024-07-15T10:00:00.000Z');
jasmine.clock().mockDate(MOCK_TODAY);
const twoYearsAgo = new Date(MOCK_TODAY);
twoYearsAgo.setFullYear(MOCK_TODAY.getFullYear() - 2);
mockRegistrarService.registrar.set({
lastPocVerificationDate: twoYearsAgo.toISOString(),
});
fixture.detectChanges();
TestBed.flushEffects();
expect(mockSnackBar.openFromComponent).toHaveBeenCalledWith(
PocReminderComponent,
{
horizontalPosition: 'center',
verticalPosition: 'top',
duration: 1000000000,
}
);
}));
it('should NOT open snackbar if lastPocVerificationDate is within last year', fakeAsync(() => {
const MOCK_TODAY = new Date('2024-07-15T10:00:00.000Z');
jasmine.clock().mockDate(MOCK_TODAY);
const sixMonthsAgo = new Date(MOCK_TODAY);
sixMonthsAgo.setMonth(MOCK_TODAY.getMonth() - 6);
mockRegistrarService.registrar.set({
lastPocVerificationDate: sixMonthsAgo.toISOString(),
});
fixture.detectChanges();
TestBed.flushEffects();
expect(mockSnackBar.openFromComponent).not.toHaveBeenCalled();
}));
});
});

View File

@@ -12,13 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { AfterViewInit, Component, effect, ViewChild } from '@angular/core';
import { MatSidenav } from '@angular/material/sidenav';
import { NavigationEnd, Router } from '@angular/router';
import { RegistrarService } from './registrar/registrar.service';
import { BreakPointObserverService } from './shared/services/breakPoint.service';
import { GlobalLoaderService } from './shared/services/globalLoader.service';
import { UserDataService } from './shared/services/userData.service';
import { MatSnackBar } from '@angular/material/snack-bar';
import { PocReminderComponent } from './shared/components/pocReminder/pocReminder.component';
@Component({
selector: 'app-root',
@@ -35,8 +37,28 @@ export class AppComponent implements AfterViewInit {
protected userDataService: UserDataService,
protected globalLoader: GlobalLoaderService,
protected breakpointObserver: BreakPointObserverService,
private router: Router
) {}
private router: Router,
private _snackBar: MatSnackBar
) {
effect(() => {
const registrar = this.registrarService.registrar();
const oneYearAgo = new Date();
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
oneYearAgo.setHours(0, 0, 0, 0);
if (
registrar &&
registrar.lastPocVerificationDate &&
new Date(registrar.lastPocVerificationDate) < oneYearAgo &&
this.userDataService?.userData()?.globalRole === 'NONE'
) {
this._snackBar.openFromComponent(PocReminderComponent, {
horizontalPosition: 'center',
verticalPosition: 'top',
duration: 1000000000,
});
}
});
}
ngAfterViewInit() {
this.router.events.subscribe((event) => {

View File

@@ -60,6 +60,9 @@ import { TldsComponent } from './tlds/tlds.component';
import { ForceFocusDirective } from './shared/directives/forceFocus.directive';
import RdapComponent from './settings/rdap/rdap.component';
import RdapEditComponent from './settings/rdap/rdapEdit.component';
import { PocReminderComponent } from './shared/components/pocReminder/pocReminder.component';
import { PasswordResetVerifyComponent } from './shared/components/passwordReset/passwordResetVerify.component';
import { PasswordInputForm } from './shared/components/passwordReset/passwordInputForm.component';
@NgModule({
declarations: [SelectedRegistrarWrapper],
@@ -83,6 +86,9 @@ export class SelectedRegistrarModule {}
NavigationComponent,
NewRegistrarComponent,
NotificationsComponent,
PasswordInputForm,
PasswordResetVerifyComponent,
PocReminderComponent,
RdapComponent,
RdapEditComponent,
ReasonDialogComponent,

View File

@@ -57,7 +57,7 @@ export class NavigationComponent {
}
ngOnDestroy() {
this.subscription.unsubscribe();
this.subscription && this.subscription.unsubscribe();
}
getElementId(node: RouteWithIcon) {

View File

@@ -71,6 +71,7 @@ export interface Registrar
registrarName: string;
registryLockAllowed?: boolean;
type?: string;
lastPocVerificationDate?: string;
}
@Injectable({

View File

@@ -16,11 +16,7 @@ import { Component, effect, ViewEncapsulation } from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';
import { take } from 'rxjs';
import { RegistrarService } from 'src/app/registrar/registrar.service';
import {
ContactService,
contactTypeToViewReadyContact,
ViewReadyContact,
} from './contact.service';
import { ContactService, ViewReadyContact } from './contact.service';
@Component({
selector: 'app-contact',

View File

@@ -24,7 +24,7 @@ export type contactType =
| 'LEGAL'
| 'MARKETING'
| 'TECH'
| 'RDAP';
| 'WHOIS';
type contactTypesToUserFriendlyTypes = { [type in contactType]: string };
@@ -35,7 +35,7 @@ export const contactTypeToTextMap: contactTypesToUserFriendlyTypes = {
LEGAL: 'Legal contact',
MARKETING: 'Marketing contact',
TECH: 'Technical contact',
RDAP: 'RDAP-Inquiry contact',
WHOIS: 'RDAP-Inquiry contact',
};
type UserFriendlyType = (typeof contactTypeToTextMap)[contactType];
@@ -59,7 +59,10 @@ export interface ViewReadyContact extends Contact {
export function contactTypeToViewReadyContact(c: Contact): ViewReadyContact {
return {
...c,
userFriendlyTypes: c.types?.map((cType) => contactTypeToTextMap[cType]),
userFriendlyTypes: (c.types || []).map(
(cType) => contactTypeToTextMap[cType]
),
types: c.types || [],
};
}
@@ -83,7 +86,7 @@ export class ContactService {
: contactTypeToViewReadyContact({
emailAddress: '',
name: '',
types: ['ADMIN'],
types: ['TECH'],
faxNumber: '',
phoneNumber: '',
registrarId: '',
@@ -98,19 +101,21 @@ export class ContactService {
);
}
saveContacts(contacts: ViewReadyContact[]): Observable<Contact[]> {
updateContact(contact: ViewReadyContact) {
return this.backend
.postContacts(this.registrarService.registrarId(), contacts)
.updateContact(this.registrarService.registrarId(), contact)
.pipe(switchMap((_) => this.fetchContacts()));
}
addContact(contact: ViewReadyContact) {
const newContacts = this.contacts().concat([contact]);
return this.saveContacts(newContacts);
return this.backend
.createContact(this.registrarService.registrarId(), contact)
.pipe(switchMap((_) => this.fetchContacts()));
}
deleteContact(contact: ViewReadyContact) {
const newContacts = this.contacts().filter((c) => c !== contact);
return this.saveContacts(newContacts);
return this.backend
.deleteContact(this.registrarService.registrarId(), contact)
.pipe(switchMap((_) => this.fetchContacts()));
}
}

View File

@@ -56,6 +56,7 @@
[required]="true"
[(ngModel)]="contactService.contactInEdit.emailAddress"
[ngModelOptions]="{ standalone: true }"
[disabled]="emailAddressIsDisabled()"
/>
</mat-form-field>
@@ -85,14 +86,18 @@
<mat-icon color="accent">error</mat-icon>Required to select at least one
</p>
<div class="">
<mat-checkbox
<ng-container
*ngFor="let contactType of contactTypeToTextMap | keyvalue"
[checked]="checkboxIsChecked(contactType.key)"
(change)="checkboxOnChange($event, contactType.key)"
[disabled]="checkboxIsDisabled(contactType.key)"
>
{{ contactType.value }}
</mat-checkbox>
<mat-checkbox
*ngIf="shouldDisplayCheckbox(contactType.key)"
[checked]="checkboxIsChecked(contactType.key)"
(change)="checkboxOnChange($event, contactType.key)"
[disabled]="checkboxIsDisabled(contactType.key)"
>
{{ contactType.value }}
</mat-checkbox>
</ng-container>
</div>
</section>

View File

@@ -69,9 +69,13 @@ export class ContactDetailsComponent {
save(e: SubmitEvent) {
e.preventDefault();
if ((this.contactService.contactInEdit.types || []).length === 0) {
this._snackBar.open('Required to select contact type');
return;
}
const request = this.contactService.isContactNewView
? this.contactService.addContact(this.contactService.contactInEdit)
: this.contactService.saveContacts(this.contactService.contacts());
: this.contactService.updateContact(this.contactService.contactInEdit);
request.subscribe({
complete: () => {
this.goBack();
@@ -82,6 +86,10 @@ export class ContactDetailsComponent {
});
}
shouldDisplayCheckbox(type: string) {
return type !== 'ADMIN' || this.checkboxIsChecked(type);
}
checkboxIsChecked(type: string) {
return this.contactService.contactInEdit.types.includes(
type as contactType
@@ -89,6 +97,9 @@ export class ContactDetailsComponent {
}
checkboxIsDisabled(type: string) {
if (type === 'ADMIN') {
return true;
}
return (
this.contactService.contactInEdit.types.length === 1 &&
this.contactService.contactInEdit.types[0] === (type as contactType)
@@ -105,4 +116,8 @@ export class ContactDetailsComponent {
);
}
}
emailAddressIsDisabled() {
return this.contactService.contactInEdit.types.includes('ADMIN');
}
}

View File

@@ -16,65 +16,22 @@
<p class="secondary-text">
Passwords must be between 6 and 16 alphanumeric characters
</p>
<form
(ngSubmit)="save()"
<password-input-form-component
[displayOldPasswordField]="true"
[formGroup]="passwordUpdateForm"
class="settings-security__edit-password-form"
>
<div class="settings-security__edit-password-field">
<mat-form-field appearance="outline">
<mat-label>Old password: </mat-label>
<input
matInput
type="text"
formControlName="oldPassword"
required
autocomplete="current-password"
/>
<mat-error *ngIf="hasError('oldPassword') as errorText">{{
errorText
}}</mat-error>
</mat-form-field>
</div>
<div class="settings-security__edit-password-field">
<mat-form-field appearance="outline">
<mat-label>New password: </mat-label>
<input
matInput
type="text"
formControlName="newPassword"
required
autocomplete="new-password"
/>
<mat-error *ngIf="hasError('newPassword') as errorText">{{
errorText
}}</mat-error>
</mat-form-field>
</div>
<div class="settings-security__edit-password-field">
<mat-form-field appearance="outline">
<mat-label>Confirm new password: </mat-label>
<input
matInput
type="text"
formControlName="newPasswordRepeat"
required
autocomplete="new-password"
/>
<mat-error *ngIf="hasError('newPasswordRepeat') as errorText">{{
errorText
}}</mat-error>
</mat-form-field>
</div>
(submitResults)="save($event)"
/>
@if(userDataService.userData()?.isAdmin) {
<div class="settings-security__reset-password-field">
<h2>Need to reset your EPP password?</h2>
<button
mat-flat-button
color="primary"
[disabled]="!passwordUpdateForm.valid"
aria-label="Save epp password update"
type="submit"
class="settings-security__edit-password-save"
aria-label="Reset EPP password via email"
(click)="requestEppPasswordReset()"
>
Save
Reset EPP password via email
</button>
</form>
</div>
}
</div>

View File

@@ -1,16 +1,19 @@
.settings-security__edit-password {
max-width: 616px;
&-field {
width: 100%;
mat-form-field {
margin-bottom: 20px;
width: 100%;
}
}
&-form {
margin-top: 30px;
}
&-save {
margin-top: 30px;
// Copyright 2025 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.
.settings-security {
&__reset-password-field {
margin-top: 60px;
}
}

View File

@@ -14,20 +14,46 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Component } from '@angular/core';
import {
AbstractControl,
FormControl,
FormGroup,
ValidatorFn,
Validators,
} from '@angular/forms';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { MatSnackBar } from '@angular/material/snack-bar';
import { RegistrarService } from 'src/app/registrar/registrar.service';
import { SecurityService } from './security.service';
import { UserDataService } from 'src/app/shared/services/userData.service';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { CommonModule } from '@angular/common';
import { MaterialModule } from 'src/app/material.module';
import { filter, switchMap, take } from 'rxjs';
import { BackendService } from 'src/app/shared/services/backend.service';
import {
PasswordInputForm,
PasswordResults,
} from 'src/app/shared/components/passwordReset/passwordInputForm.component';
type errorCode = 'required' | 'maxlength' | 'minlength' | 'passwordsDontMatch';
@Component({
selector: 'app-reset-epp-password-dialog',
template: `
<h2 mat-dialog-title>Please confirm the password reset:</h2>
<mat-dialog-content>
This will send an EPP password reset email to the admin POC.
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button (click)="onCancel()">Cancel</button>
<button mat-button color="warn" (click)="onSave()">Confirm</button>
</mat-dialog-actions>
`,
imports: [CommonModule, MaterialModule],
})
export class ResetEppPasswordComponent {
constructor(public dialogRef: MatDialogRef<ResetEppPasswordComponent>) {}
type errorFriendlyText = { [type in errorCode]: String };
onSave(): void {
this.dialogRef.close(true);
}
onCancel(): void {
this.dialogRef.close(false);
}
}
@Component({
selector: 'app-epp-password-edit',
@@ -36,76 +62,38 @@ type errorFriendlyText = { [type in errorCode]: String };
standalone: false,
})
export default class EppPasswordEditComponent {
MIN_MAX_LENGHT = new String(
'Passwords must be between 6 and 16 alphanumeric characters'
);
errorTextMap: errorFriendlyText = {
required: "This field can't be empty",
maxlength: this.MIN_MAX_LENGHT,
minlength: this.MIN_MAX_LENGHT,
passwordsDontMatch: "Passwords don't match",
};
constructor(
public securityService: SecurityService,
private _snackBar: MatSnackBar,
public registrarService: RegistrarService
) {}
hasError(controlName: string) {
const maybeErrors = this.passwordUpdateForm.get(controlName)?.errors;
const maybeError =
maybeErrors && (Object.keys(maybeErrors)[0] as errorCode);
if (maybeError) {
return this.errorTextMap[maybeError];
}
return '';
}
newPasswordsMatch: ValidatorFn = (control: AbstractControl) => {
if (
this.passwordUpdateForm?.get('newPassword')?.value ===
this.passwordUpdateForm?.get('newPasswordRepeat')?.value
) {
this.passwordUpdateForm?.get('newPasswordRepeat')?.setErrors(null);
} else {
// latest angular just won't detect the error without setTimeout
setTimeout(() => {
this.passwordUpdateForm
?.get('newPasswordRepeat')
?.setErrors({ passwordsDontMatch: control.value });
});
}
return null;
};
static EPP_VALIDATORS = [
Validators.required,
Validators.minLength(6),
Validators.maxLength(16),
PasswordInputForm.newPasswordsMatch,
];
passwordUpdateForm = new FormGroup({
oldPassword: new FormControl('', [Validators.required]),
newPassword: new FormControl('', [
Validators.required,
Validators.minLength(6),
Validators.maxLength(16),
this.newPasswordsMatch,
]),
newPasswordRepeat: new FormControl('', [
Validators.required,
Validators.minLength(6),
Validators.maxLength(16),
this.newPasswordsMatch,
]),
newPassword: new FormControl('', EppPasswordEditComponent.EPP_VALIDATORS),
newPasswordRepeat: new FormControl(
'',
EppPasswordEditComponent.EPP_VALIDATORS
),
});
save() {
const { oldPassword, newPassword, newPasswordRepeat } =
this.passwordUpdateForm.value;
if (!oldPassword || !newPassword || !newPasswordRepeat) return;
constructor(
public registrarService: RegistrarService,
public securityService: SecurityService,
protected userDataService: UserDataService,
private backendService: BackendService,
private resetPasswordDialog: MatDialog,
private _snackBar: MatSnackBar
) {}
save(passwordResults: PasswordResults) {
this.securityService
.saveEppPassword({
registrarId: this.registrarService.registrarId(),
oldPassword,
newPassword,
newPasswordRepeat,
oldPassword: passwordResults.oldPassword!,
newPassword: passwordResults.newPassword,
newPasswordRepeat: passwordResults.newPasswordRepeat,
})
.subscribe({
complete: () => {
@@ -120,4 +108,26 @@ export default class EppPasswordEditComponent {
goBack() {
this.securityService.isEditingPassword = false;
}
sendEppPasswordResetRequest() {
return this.backendService.requestEppPasswordReset(
this.registrarService.registrarId()
);
}
requestEppPasswordReset() {
const dialogRef = this.resetPasswordDialog.open(ResetEppPasswordComponent);
dialogRef
.afterClosed()
.pipe(
take(1),
filter((result) => !!result)
)
.pipe(switchMap((_) => this.sendEppPasswordResetRequest()))
.subscribe({
next: (_) => this.goBack(),
error: (err: HttpErrorResponse) =>
this._snackBar.open(err.error || err.message),
});
}
}

View File

@@ -0,0 +1,63 @@
<form
(ngSubmit)="save()"
[formGroup]="formGroup()!"
class="console-app__password-input-form"
>
@if (displayOldPasswordField()) {
<div class="console-app__password-input-form-field">
<mat-form-field appearance="outline">
<mat-label>Old password: </mat-label>
<input
matInput
type="text"
formControlName="oldPassword"
required
autocomplete="current-password"
/>
<mat-error *ngIf="hasError('oldPassword') as errorText">{{
errorText
}}</mat-error>
</mat-form-field>
</div>
}
<div class="console-app__password-input-form-field">
<mat-form-field appearance="outline">
<mat-label>New password: </mat-label>
<input
matInput
type="text"
formControlName="newPassword"
required
autocomplete="new-password"
/>
<mat-error *ngIf="hasError('newPassword') as errorText">{{
errorText
}}</mat-error>
</mat-form-field>
</div>
<div class="console-app__password-input-form-field">
<mat-form-field appearance="outline">
<mat-label>Confirm new password: </mat-label>
<input
matInput
type="text"
formControlName="newPasswordRepeat"
required
autocomplete="new-password"
/>
<mat-error *ngIf="hasError('newPasswordRepeat') as errorText">{{
errorText
}}</mat-error>
</mat-form-field>
</div>
<button
mat-flat-button
color="primary"
[disabled]="!formGroup()?.valid"
aria-label="Save new password"
type="submit"
class="console-app__password-input-form-save"
>
Save
</button>
</form>

View File

@@ -0,0 +1,30 @@
// Copyright 2025 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.
.console-app__password-input-form {
max-width: 450px;
&-field {
width: 100%;
mat-form-field {
margin-bottom: 20px;
width: 100%;
}
}
&-form {
margin-top: 30px;
}
&-save {
margin-top: 30px;
}
}

View File

@@ -0,0 +1,82 @@
// Copyright 2025 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.
import { Component, EventEmitter, input, Output } from '@angular/core';
import { AbstractControl, FormGroup, ValidatorFn } from '@angular/forms';
type errorCode = 'required' | 'maxlength' | 'minlength' | 'passwordsDontMatch';
type errorFriendlyText = { [type in errorCode]: String };
export interface PasswordResults {
oldPassword: string | null;
newPassword: string;
newPasswordRepeat: string;
}
@Component({
selector: 'password-input-form-component',
templateUrl: './passwordInputForm.component.html',
styleUrls: ['./passwordInputForm.component.scss'],
standalone: false,
})
export class PasswordInputForm {
static newPasswordsMatch: ValidatorFn = (control: AbstractControl) => {
const parent = control.parent;
if (
parent?.get('newPassword')?.value ===
parent?.get('newPasswordRepeat')?.value
) {
parent?.get('newPasswordRepeat')?.setErrors(null);
} else {
// latest angular just won't detect the error without setTimeout
setTimeout(() => {
parent
?.get('newPasswordRepeat')
?.setErrors({ passwordsDontMatch: control.value });
});
}
return null;
};
MIN_MAX_LENGTH = 'Passwords must be between 6 and 16 alphanumeric characters';
errorTextMap: errorFriendlyText = {
required: "This field can't be empty",
maxlength: this.MIN_MAX_LENGTH,
minlength: this.MIN_MAX_LENGTH,
passwordsDontMatch: "Passwords don't match",
};
displayOldPasswordField = input<boolean>(false);
formGroup = input<FormGroup>();
@Output() submitResults = new EventEmitter<PasswordResults>();
hasError(controlName: string) {
const maybeErrors = this.formGroup()!.get(controlName)?.errors;
const maybeError =
maybeErrors && (Object.keys(maybeErrors)[0] as errorCode);
if (maybeError) {
return this.errorTextMap[maybeError];
}
return '';
}
save() {
const results: PasswordResults = this.formGroup()!.value;
if (this.displayOldPasswordField() && !results.oldPassword) return;
if (!results.newPassword || !results.newPasswordRepeat) return;
this.submitResults.emit(results);
}
}

View File

@@ -0,0 +1,27 @@
<p>
<button mat-icon-button aria-label="Go home" [routerLink]="['']">
<mat-icon>arrow_back</mat-icon>
</button>
</p>
@if (isLoading) {
<div class="console-app__password-reset-verify-spinner">
<mat-spinner />
</div>
} @else if (errorMessage) {
<h1 class="mat-headline-4">Failure</h1>
<div class="console-app__password-reset-content">
<div class="console-app__password-reset-subhead">
An error occurred: {{ errorMessage }}.<br /><br />Please double-check the
verification code and try again.
</div>
</div>
} @else {
<div class="console-app__password-reset-verify">
<h1 class="mat-headline-4">{{ type }} password reset</h1>
<password-input-form-component
[displayOldPasswordField]="false"
[formGroup]="passwordUpdateForm!"
(submitResults)="save($event)"
/>
</div>
}

View File

@@ -0,0 +1,105 @@
// Copyright 2025 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.
import { Component } from '@angular/core';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { HttpErrorResponse } from '@angular/common/http';
import { take } from 'rxjs';
import { RegistrarService } from 'src/app/registrar/registrar.service';
import { BackendService } from '../../services/backend.service';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import {
PasswordInputForm,
PasswordResults,
} from './passwordInputForm.component';
import EppPasswordEditComponent from 'src/app/settings/security/eppPasswordEdit.component';
export interface PasswordResetVerifyResponse {
registrarId: string;
type: string;
}
@Component({
selector: 'app-password-reset-verify',
templateUrl: './passwordResetVerify.component.html',
standalone: false,
})
export class PasswordResetVerifyComponent {
public static PATH = 'password-reset-verify';
REGISTRY_LOCK_PASSWORD_VALIDATORS = [
Validators.required,
PasswordInputForm.newPasswordsMatch,
];
isLoading = true;
type?: string;
errorMessage?: string;
requestVerificationCode = '';
passwordUpdateForm: FormGroup<any> | null = null;
constructor(
protected backendService: BackendService,
protected registrarService: RegistrarService,
private route: ActivatedRoute,
private router: Router
) {}
ngOnInit() {
this.route.queryParamMap.pipe(take(1)).subscribe((params: ParamMap) => {
this.requestVerificationCode =
params.get('resetRequestVerificationCode') || '';
this.backendService
.getPasswordResetInformation(this.requestVerificationCode)
.subscribe({
error: (err: HttpErrorResponse) => {
this.isLoading = false;
this.errorMessage = err.error;
},
next: this.presentData.bind(this),
});
});
}
presentData(verificationResponse: PasswordResetVerifyResponse) {
this.type = verificationResponse.type === 'EPP' ? 'EPP' : 'Registry lock';
this.registrarService.registrarId.set(verificationResponse.registrarId);
const validators =
verificationResponse.type === 'EPP'
? EppPasswordEditComponent.EPP_VALIDATORS
: this.REGISTRY_LOCK_PASSWORD_VALIDATORS;
this.passwordUpdateForm = new FormGroup({
newPassword: new FormControl('', validators),
newPasswordRepeat: new FormControl('', validators),
});
this.isLoading = false;
}
save(passwordResults: PasswordResults) {
this.backendService
.finalizePasswordReset(
this.requestVerificationCode,
passwordResults.newPassword
)
.subscribe({
error: (err: HttpErrorResponse) => {
this.isLoading = false;
this.errorMessage = err.error;
},
next: (_) => this.router.navigate(['']),
});
}
}

View File

@@ -0,0 +1,14 @@
<div class="console-app__pocReminder">
<p class="">
Please take a moment to complete annual review of
<a routerLink="/settings">contacts</a>.
</p>
<span matSnackBarActions>
<button mat-button matSnackBarAction (click)="confirmReviewed()">
Confirm reviewed
</button>
<button mat-button matSnackBarAction (click)="snackBarRef.dismiss()">
Close
</button>
</span>
</div>

View File

@@ -0,0 +1,5 @@
.console-app__pocReminder {
a {
color: white !important;
}
}

View File

@@ -0,0 +1,53 @@
// Copyright 2025 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.
import { Component } from '@angular/core';
import { MatSnackBar, MatSnackBarRef } from '@angular/material/snack-bar';
import { RegistrarService } from '../../../registrar/registrar.service';
import { HttpErrorResponse } from '@angular/common/http';
@Component({
selector: 'app-poc-reminder',
templateUrl: './pocReminder.component.html',
styleUrls: ['./pocReminder.component.scss'],
standalone: false,
})
export class PocReminderComponent {
constructor(
public snackBarRef: MatSnackBarRef<PocReminderComponent>,
private registrarService: RegistrarService,
private _snackBar: MatSnackBar
) {}
confirmReviewed() {
if (this.registrarService.registrar()) {
const todayMidnight = new Date();
todayMidnight.setHours(0, 0, 0, 0);
this.registrarService
// @ts-ignore - if check above won't allow empty object to be submitted
.updateRegistrar({
...this.registrarService.registrar(),
lastPocVerificationDate: todayMidnight.toISOString(),
})
.subscribe({
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error || err.message);
},
next: () => {
this.snackBarRef.dismiss();
},
});
}
}
}

View File

@@ -30,6 +30,7 @@ import {
import { Contact } from '../../settings/contact/contact.service';
import { EppPasswordBackendModel } from '../../settings/security/security.service';
import { UserData } from './userData.service';
import { PasswordResetVerifyResponse } from '../components/passwordReset/passwordResetVerify.component';
@Injectable()
export class BackendService {
@@ -70,13 +71,26 @@ export class BackendService {
.pipe(catchError((err) => this.errorCatcher<Contact[]>(err)));
}
postContacts(
registrarId: string,
contacts: Contact[]
): Observable<Contact[]> {
return this.http.post<Contact[]>(
updateContact(registrarId: string, contact: Contact): Observable<Contact> {
return this.http.put<Contact>(
`/console-api/settings/contacts?registrarId=${registrarId}`,
contacts
contact
);
}
createContact(registrarId: string, contact: Contact): Observable<Contact> {
return this.http.post<Contact>(
`/console-api/settings/contacts?registrarId=${registrarId}`,
contact
);
}
deleteContact(registrarId: string, contact: Contact): Observable<Contact> {
return this.http.delete<Contact>(
`/console-api/settings/contacts?registrarId=${registrarId}`,
{
body: JSON.stringify(contact),
}
);
}
@@ -267,4 +281,37 @@ export class BackendService {
`/console-api/registry-lock-verify?lockVerificationCode=${lockVerificationCode}`
);
}
requestRegistryLockPasswordReset(
registrarId: string,
registryLockEmail: string
) {
return this.http.post('/console-api/password-reset-request', {
type: 'REGISTRY_LOCK',
registrarId,
registryLockEmail,
});
}
requestEppPasswordReset(registrarId: string) {
return this.http.post('/console-api/password-reset-request', {
type: 'EPP',
registrarId,
});
}
getPasswordResetInformation(
verificationCode: string
): Observable<PasswordResetVerifyResponse> {
return this.http.get<PasswordResetVerifyResponse>(
`/console-api/password-reset-verify?resetRequestVerificationCode=${verificationCode}`
);
}
finalizePasswordReset(verificationCode: string, newPassword: string) {
return this.http.post(
`/console-api/password-reset-verify?resetRequestVerificationCode=${verificationCode}`,
newPassword
);
}
}

View File

@@ -80,7 +80,15 @@
roleToDescription(userDetails().role)
}}</span>
</mat-list-item>
@if (userDetails().password) {
@if (userDetails().registryLockEmailAddress) {
<mat-divider></mat-divider>
<mat-list-item role="listitem">
<span class="console-app__list-key">Registry Lock email</span>
<span class="console-app__list-value">{{
userDetails().registryLockEmailAddress
}}</span>
</mat-list-item>
} @if (userDetails().password) {
<mat-divider></mat-divider>
<mat-list-item role="listitem">
<span class="console-app__list-key">Password</span>

View File

@@ -35,5 +35,8 @@
border: 1px solid #ddd;
border-radius: 10px;
}
.console-app__list-key {
width: 160px;
}
}
}

View File

@@ -1,45 +1,57 @@
<form (ngSubmit)="saveEdit($event)" #form>
<p *ngIf="isNew()">
<mat-form-field appearance="outline">
<mat-label
>User name prefix:
<mat-icon
matTooltip="Prefix will be combined with registrar ID to create a unique user name - {prefix}.{registrarId}@registry.google"
>help_outline</mat-icon
></mat-label
>
<input
matInput
minlength="3"
maxlength="3"
[required]="true"
[(ngModel)]="user().emailAddress"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</p>
<p>
<mat-form-field appearance="outline">
<mat-label
>User Role:
<mat-icon
matTooltip="Viewer role doesn't allow making updates; Editor role allows updates, like Contacts delete or SSL certificate change"
>help_outline</mat-icon
></mat-label
>
<mat-select [(ngModel)]="user().role" name="userRole">
<mat-option value="PRIMARY_CONTACT">Editor</mat-option>
<mat-option value="ACCOUNT_MANAGER">Viewer</mat-option>
</mat-select>
</mat-form-field>
</p>
<div class="console-app__user-edit">
<form (ngSubmit)="saveEdit($event)" #form>
<p *ngIf="isNew()">
<mat-form-field appearance="outline">
<mat-label
>User name prefix:
<mat-icon
matTooltip="Prefix will be combined with registrar ID to create a unique user name - {prefix}.{registrarId}@registry.google"
>help_outline</mat-icon
></mat-label
>
<input
matInput
minlength="3"
maxlength="3"
[required]="true"
[(ngModel)]="user().emailAddress"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</p>
<p>
<mat-form-field appearance="outline">
<mat-label
>User Role:
<mat-icon
matTooltip="Viewer role doesn't allow making updates; Editor role allows updates, like Contacts delete or SSL certificate change"
>help_outline</mat-icon
></mat-label
>
<mat-select [(ngModel)]="user().role" name="userRole">
<mat-option value="PRIMARY_CONTACT">Editor</mat-option>
<mat-option value="ACCOUNT_MANAGER">Viewer</mat-option>
</mat-select>
</mat-form-field>
</p>
<button
mat-flat-button
color="primary"
aria-label="Save user"
type="submit"
aria-label="Save changes to the user"
>
Save
</button>
</form>
@if(userDataService.userData()?.isAdmin) {
<button
mat-flat-button
color="primary"
aria-label="Save user"
type="submit"
aria-label="Save changes to the user"
aria-label="Reset registry lock password"
(click)="requestRegistryLockPasswordReset()"
>
Save
Reset registry lock password
</button>
</form>
}
</div>

View File

@@ -0,0 +1,20 @@
// Copyright 2025 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.
.console-app__user-edit {
button {
display: block;
margin-bottom: 5px;
}
}

View File

@@ -17,13 +17,56 @@ import {
Component,
ElementRef,
EventEmitter,
Inject,
input,
Output,
ViewChild,
} from '@angular/core';
import { MaterialModule } from '../material.module';
import { FormsModule } from '@angular/forms';
import { User } from './users.service';
import { User, UsersService } from './users.service';
import { UserDataService } from '../shared/services/userData.service';
import { BackendService } from '../shared/services/backend.service';
import { RegistrarService } from '../registrar/registrar.service';
import {
MAT_DIALOG_DATA,
MatDialog,
MatDialogRef,
} from '@angular/material/dialog';
import { filter, switchMap, take } from 'rxjs';
import { MatSnackBar } from '@angular/material/snack-bar';
import { HttpErrorResponse } from '@angular/common/http';
@Component({
selector: 'app-reset-lock-password-dialog',
template: `
<h2 mat-dialog-title>Please confirm the password reset:</h2>
<mat-dialog-content>
This will send a registry lock password reset email to
{{ data.registryLockEmailAddress }}.
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button (click)="onCancel()">Cancel</button>
<button mat-button color="warn" (click)="onSave()">Confirm</button>
</mat-dialog-actions>
`,
imports: [CommonModule, MaterialModule],
})
export class ResetRegistryLockPasswordComponent {
constructor(
public dialogRef: MatDialogRef<ResetRegistryLockPasswordComponent>,
@Inject(MAT_DIALOG_DATA)
public data: { registryLockEmailAddress: string }
) {}
onSave(): void {
this.dialogRef.close(true);
}
onCancel(): void {
this.dialogRef.close(false);
}
}
@Component({
selector: 'app-user-edit-form',
@@ -39,12 +82,22 @@ export class UserEditFormComponent {
{
emailAddress: '',
role: 'ACCOUNT_MANAGER',
registryLockEmailAddress: '',
},
{ transform: (user: User) => structuredClone(user) }
);
@Output() onEditComplete = new EventEmitter<User>();
constructor(
protected userDataService: UserDataService,
private backendService: BackendService,
private resetRegistryLockPasswordDialog: MatDialog,
private registrarService: RegistrarService,
private usersService: UsersService,
private _snackBar: MatSnackBar
) {}
saveEdit(e: SubmitEvent) {
e.preventDefault();
if (this.form.nativeElement.checkValidity()) {
@@ -53,4 +106,34 @@ export class UserEditFormComponent {
this.form.nativeElement.reportValidity();
}
}
sendRegistryLockPasswordResetRequest() {
return this.backendService.requestRegistryLockPasswordReset(
this.registrarService.registrarId(),
this.user().registryLockEmailAddress!
);
}
requestRegistryLockPasswordReset() {
const dialogRef = this.resetRegistryLockPasswordDialog.open(
ResetRegistryLockPasswordComponent,
{
data: {
registryLockEmailAddress: this.user().registryLockEmailAddress,
},
}
);
dialogRef
.afterClosed()
.pipe(
take(1),
filter((result) => !!result)
)
.pipe(switchMap((_) => this.sendRegistryLockPasswordResetRequest()))
.subscribe({
next: (_) => this.usersService.currentlyOpenUserEmail.set(''),
error: (err: HttpErrorResponse) =>
this._snackBar.open(err.error || err.message),
});
}
}

View File

@@ -22,7 +22,6 @@ import { RegistrarService } from '../registrar/registrar.service';
import { SnackBarModule } from '../snackbar.module';
import { UserDetailsComponent } from './userDetails.component';
import { User, UsersService } from './users.service';
import { UserDataService } from '../shared/services/userData.service';
import { FormsModule } from '@angular/forms';
import { UsersListComponent } from './usersList.component';
import { MatSelectChange } from '@angular/material/select';
@@ -55,7 +54,6 @@ export class UsersComponent {
constructor(
protected registrarService: RegistrarService,
protected usersService: UsersService,
private userDataService: UserDataService,
private _snackBar: MatSnackBar
) {
effect(() => {

View File

@@ -33,6 +33,7 @@ export interface User {
emailAddress: string;
role: string;
password?: string;
registryLockEmailAddress?: string;
}
@Injectable()

View File

@@ -58,9 +58,12 @@ def fragileTestPatterns = [
// Changes cache timeouts and for some reason appears to have contention
// with other tests.
"google/registry/whois/WhoisCommandFactoryTest.*",
// Breaks random other tests when running with standardTests.
"google/registry/bsa/UploadBsaUnavailableDomainsActionTest.*",
// Currently changes a global configuration parameter that for some reason
// results in timestamp inversions for other tests. TODO(mmuller): fix.
"google/registry/flows/host/HostInfoFlowTest.*",
"google/registry/beam/common/RegistryPipelineWorkerInitializerTest.*",
] + dockerIncompatibleTestPatterns
sourceSets {
@@ -271,7 +274,6 @@ dependencies {
testImplementation deps['org.hamcrest:hamcrest']
testImplementation deps['org.hamcrest:hamcrest-core']
testImplementation deps['org.hamcrest:hamcrest-library']
testImplementation deps['junit:junit']
testImplementation deps['org.junit.jupiter:junit-jupiter-api']
testImplementation deps['org.junit.jupiter:junit-jupiter-engine']
testImplementation deps['org.junit.jupiter:junit-jupiter-migrationsupport']

View File

@@ -4,20 +4,19 @@
aopalliance:aopalliance:1.0=annotationProcessor,compileClasspath,deploy_jar,errorprone,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
args4j:args4j:2.33=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
com.charleskorn.kaml:kaml:0.20.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-annotations:2.18.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-core:2.18.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-databind:2.18.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.dataformat:jackson-dataformat-toml:2.18.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.datatype:jackson-datatype-joda:2.18.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson:jackson-bom:2.18.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-annotations:2.20-rc1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-core:2.20.0-rc1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-databind:2.20.0-rc1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.20.0-rc1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.datatype:jackson-datatype-joda:2.20.0-rc1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.20.0-rc1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson:jackson-bom:2.20.0-rc1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml:classmate:1.5.1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.github.ben-manes.caffeine:caffeine:3.0.5=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.github.ben-manes.caffeine:caffeine:3.2.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.docker-java:docker-java-api:3.4.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.docker-java:docker-java-transport-zerodep:3.4.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.docker-java:docker-java-transport:3.4.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.ben-manes.caffeine:caffeine:3.2.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.docker-java:docker-java-api:3.4.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.docker-java:docker-java-transport-zerodep:3.4.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.docker-java:docker-java-transport:3.4.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.jnr:jffi:1.3.13=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.jnr:jnr-a64asm:1.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.jnr:jnr-constants:0.10.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -32,85 +31,80 @@ com.google.api-client:google-api-client-jackson2:2.0.1=compileClasspath,nonprodC
com.google.api-client:google-api-client-jackson2:2.7.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,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.7.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api-client:google-api-client-servlet:2.7.2=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api-client:google-api-client:2.7.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api-client:google-api-client-servlet:2.8.1=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api-client:google-api-client:2.8.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:gapic-google-cloud-storage-v2:2.44.1-beta=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api.grpc:gapic-google-cloud-storage-v2:2.50.0=testCompileClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:3.11.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.183.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.183.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigtable-v2:2.51.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-pubsub-v1:1.118.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-pubsublite-v1:1.15.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-spanner-admin-database-v1:6.85.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-spanner-admin-instance-v1:6.85.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-spanner-v1:6.85.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:gapic-google-cloud-storage-v2:2.55.0=testCompileClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:3.15.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.187.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.187.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigtable-v2:2.60.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-pubsub-v1:1.122.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-pubsublite-v1:1.15.9=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-spanner-admin-database-v1:6.95.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-spanner-admin-instance-v1:6.95.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-spanner-v1:6.95.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-storage-control-v2:2.44.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-storage-v2:2.44.1-beta=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-storage-v2:2.50.0=testCompileClasspath
com.google.api.grpc:grpc-google-common-protos:2.50.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1:3.11.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1alpha:3.11.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta1:0.183.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta2:0.183.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigtable-admin-v2:2.51.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigtable-v2:2.51.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-compute-v1:1.69.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-datastore-v1:0.116.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-firestore-v1:3.30.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-monitoring-v3:3.57.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-pubsub-v1:1.118.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-pubsublite-v1:1.15.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-storage-v2:2.55.0=testCompileClasspath
com.google.api.grpc:grpc-google-common-protos:2.58.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1:3.15.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1alpha:3.15.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta1:0.187.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta2:0.187.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta:3.15.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigtable-admin-v2:2.60.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigtable-v2:2.60.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-compute-v1:1.82.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-datastore-v1:0.120.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-firestore-v1:3.31.6=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-monitoring-v3:3.65.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-pubsub-v1:1.122.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-pubsublite-v1:1.15.9=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-secretmanager-v1:2.51.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-secretmanager-v1:2.59.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api.grpc:proto-google-cloud-secretmanager-v1beta1:2.59.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api.grpc:proto-google-cloud-secretmanager-v1:2.72.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api.grpc:proto-google-cloud-secretmanager-v1beta1:2.72.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api.grpc:proto-google-cloud-secretmanager-v1beta2:2.51.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-secretmanager-v1beta2:2.59.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api.grpc:proto-google-cloud-spanner-admin-database-v1:6.85.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-spanner-admin-instance-v1:6.85.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-spanner-v1:6.85.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-secretmanager-v1beta2:2.72.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api.grpc:proto-google-cloud-spanner-admin-database-v1:6.95.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-spanner-admin-instance-v1:6.95.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-spanner-v1:6.95.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-storage-control-v2:2.44.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-storage-v2:2.44.1-beta=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-storage-v2:2.50.0=testCompileClasspath
com.google.api.grpc:proto-google-cloud-storage-v2:2.55.0=testCompileClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2:2.51.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2:2.59.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2:2.72.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2beta2:0.141.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2beta2:0.149.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2beta2:0.162.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2beta3:0.141.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2beta3:0.149.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api.grpc:proto-google-common-protos:2.53.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-common-protos:2.54.1=testCompileClasspath
com.google.api.grpc:proto-google-iam-v1:1.45.1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-iam-v1:1.48.0=compileClasspath,nonprodCompileClasspath
com.google.api.grpc:proto-google-iam-v1:1.49.1=testCompileClasspath
com.google.api:api-common:2.45.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api:api-common:2.46.1=testCompileClasspath
com.google.api:gax-grpc:2.59.1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api:gax-grpc:2.62.0=compileClasspath,nonprodCompileClasspath
com.google.api:gax-grpc:2.63.1=testCompileClasspath
com.google.api:gax-httpjson:2.62.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api:gax-httpjson:2.63.1=testCompileClasspath
com.google.api:gax:2.62.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api:gax:2.63.1=testCompileClasspath
com.google.apis:google-api-services-admin-directory:directory_v1-rev20250217-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-bigquery:v2-rev20241222-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2beta3:0.162.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api.grpc:proto-google-common-protos:2.60.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-iam-v1:1.53.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-iam-v1:1.55.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api:api-common:2.52.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:gax-grpc:2.67.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api:gax-grpc:2.69.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api:gax-httpjson:2.69.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:gax:2.69.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-admin-directory:directory_v1-rev20250804-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-bigquery:v2-rev20250511-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-cloudresourcemanager:v1-rev20240310-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-dataflow:v1b3-rev20250310-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-dns:v1-rev20250227-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-drive:v3-rev20250220-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-dataflow:v1b3-rev20250812-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-dns:v1-rev20250411-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-drive:v3-rev20250723-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-gmail:v1-rev20250630-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-groupssettings:v1-rev20220614-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-rev20250213-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-iam:v2-rev20250502-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-rev20250227-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-monitoring:v3-rev20250731-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-rev20250211-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-sqladmin:v1beta4-rev20250205-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-storage:v1-rev20241206-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.apis:google-api-services-storage:v1-rev20250224-2.0.0=testCompileClasspath
com.google.auth:google-auth-library-credentials:1.33.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auth:google-auth-library-oauth2-http:1.33.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-sheets:v4-rev20250616-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-sqladmin:v1beta4-rev20250613-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-storage:v1-rev20250524-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.apis:google-api-services-storage:v1-rev20250718-2.0.0=testCompileClasspath
com.google.auth:google-auth-library-credentials:1.37.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auth:google-auth-library-oauth2-http:1.37.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auto.service:auto-service-annotations:1.0.1=errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.auto.service:auto-service-annotations:1.1.1=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auto.service:auto-service:1.1.1=annotationProcessor
@@ -121,50 +115,51 @@ com.google.auto:auto-common:1.2.1=annotationProcessor,errorprone,nonprodAnnotati
com.google.cloud.bigdataoss:gcsio:2.2.26=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.bigdataoss:util:2.2.26=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.25.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.datastore:datastore-v1-proto-client:2.29.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.opentelemetry:detector-resources-support:0.33.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.opentelemetry:exporter-metrics:0.33.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.opentelemetry:shared-resourcemapping:0.33.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud.sql:jdbc-socket-factory-core:1.23.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.sql:postgres-socket-factory:1.23.1=deploy_jar,runtimeClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-bigquerystorage:3.11.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-bigtable:2.51.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-compute:1.69.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core-grpc:2.49.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core-grpc:2.53.1=testCompileClasspath
com.google.cloud.sql:jdbc-socket-factory-core:1.25.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.sql:postgres-socket-factory:1.25.3=deploy_jar,runtimeClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-bigquerystorage:3.15.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-bigtable:2.60.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-compute:1.82.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core-grpc:2.57.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core-grpc:2.59.0=testCompileClasspath
com.google.cloud:google-cloud-core-http:2.47.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core-http:2.53.1=testCompileClasspath
com.google.cloud:google-cloud-core:2.49.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core:2.53.1=testCompileClasspath
com.google.cloud:google-cloud-firestore:3.30.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-monitoring:3.57.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core-http:2.59.0=testCompileClasspath
com.google.cloud:google-cloud-core:2.57.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core:2.59.0=testCompileClasspath
com.google.cloud:google-cloud-firestore:3.31.6=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-monitoring:3.65.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-nio:0.127.24=testRuntimeClasspath
com.google.cloud:google-cloud-nio:0.127.33=testCompileClasspath
com.google.cloud:google-cloud-pubsub:1.136.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-pubsublite:1.15.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-nio:0.128.2=testCompileClasspath
com.google.cloud:google-cloud-pubsub:1.140.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-pubsublite:1.15.9=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-secretmanager:2.51.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-secretmanager:2.59.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.cloud:google-cloud-spanner:6.85.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-secretmanager:2.72.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.cloud:google-cloud-spanner:6.95.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-storage-control:2.44.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-storage:2.44.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-storage:2.50.0=testCompileClasspath
com.google.cloud:google-cloud-storage:2.55.0=testCompileClasspath
com.google.cloud:google-cloud-tasks:2.51.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-tasks:2.59.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.cloud:google-cloud-tasks:2.72.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.cloud:grpc-gcp:1.6.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:libraries-bom:26.48.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.30.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.31.6=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,checkstyle,compileClasspath,deploy_jar,errorprone,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.code.gson:gson:2.10.1=soy
com.google.code.gson:gson:2.12.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.common.html.types:types:1.0.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
com.google.dagger:dagger-compiler:2.55=annotationProcessor,testAnnotationProcessor
com.google.dagger:dagger-spi:2.55=annotationProcessor,testAnnotationProcessor
com.google.dagger:dagger:2.55=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.devtools.ksp:symbol-processing-api:2.0.21-1.0.28=annotationProcessor,testAnnotationProcessor
com.google.dagger:dagger-compiler:2.57.1=annotationProcessor,testAnnotationProcessor
com.google.dagger:dagger-spi:2.57.1=annotationProcessor,testAnnotationProcessor
com.google.dagger:dagger:2.57=deploy_jar
com.google.dagger:dagger:2.57.1=annotationProcessor,compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.devtools.ksp:symbol-processing-api:2.1.21-2.0.2=annotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_annotation:2.23.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.20.0=soy
com.google.errorprone:error_prone_annotations:2.23.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.36.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.errorprone:error_prone_annotations:2.41.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.errorprone:error_prone_annotations:2.7.1=checkstyle
com.google.errorprone:error_prone_check_api:2.23.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_core:2.23.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
@@ -181,25 +176,25 @@ com.google.flogger:google-extensions:0.7.4=soy
com.google.flogger:google-extensions:0.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.googlejavaformat:google-java-format:1.5=annotationProcessor,testAnnotationProcessor
com.google.guava:failureaccess:1.0.1=checkstyle,errorprone,nonprodAnnotationProcessor,soy
com.google.guava:failureaccess:1.0.2=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.guava:failureaccess:1.0.2=annotationProcessor,testAnnotationProcessor
com.google.guava:failureaccess:1.0.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.guava:guava-parent:32.1.1-jre=errorprone,nonprodAnnotationProcessor,soy
com.google.guava:guava-testlib:33.3.0-jre=testRuntimeClasspath
com.google.guava:guava-testlib:33.4.0-jre=testCompileClasspath
com.google.guava:guava-testlib:33.4.8-jre=testCompileClasspath
com.google.guava:guava:31.0.1-jre=checkstyle
com.google.guava:guava:32.1.1-jre=errorprone,nonprodAnnotationProcessor,soy
com.google.guava:guava:33.0.0-jre=annotationProcessor,testAnnotationProcessor
com.google.guava:guava:33.4.0-jre=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.guava:guava:33.4.8-jre=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.gwt:gwt-user:2.10.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-apache-v2:1.45.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-apache-v2:1.46.3=testCompileClasspath
com.google.http-client:google-http-client-apache-v2:2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-appengine:1.45.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-appengine:1.46.3=testCompileClasspath
com.google.http-client:google-http-client-gson:1.46.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-appengine:1.47.1=testCompileClasspath
com.google.http-client:google-http-client-gson:2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-jackson2:1.45.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-jackson2:1.46.3=testCompileClasspath
com.google.http-client:google-http-client-protobuf:1.45.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client:1.46.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-jackson2:1.47.1=testCompileClasspath
com.google.http-client:google-http-client-protobuf:1.47.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client:2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.inject:guice:5.1.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.inject:guice:7.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
com.google.j2objc:j2objc-annotations:1.3=checkstyle
@@ -215,13 +210,12 @@ com.google.oauth-client:google-oauth-client-jetty:1.36.0=deploy_jar,nonprodRunti
com.google.oauth-client:google-oauth-client-jetty:1.39.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.oauth-client:google-oauth-client-servlet:1.36.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.oauth-client:google-oauth-client-servlet:1.39.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.oauth-client:google-oauth-client:1.37.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.oauth-client:google-oauth-client:1.39.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.protobuf:protobuf-java-util:4.29.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.oauth-client:google-oauth-client:1.39.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.protobuf:protobuf-java-util:4.29.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.protobuf:protobuf-java:3.19.6=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.protobuf:protobuf-java:3.21.7=soy
com.google.protobuf:protobuf-java:3.25.6=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.re2j:re2j:1.7=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.protobuf:protobuf-java:3.25.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.re2j:re2j:1.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.template:soy:2024-02-26=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
com.google.truth:truth:1.4.4=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.googlecode.json-simple:json-simple:1.1.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -255,11 +249,12 @@ com.sun.istack:istack-commons-tools:4.1.2=jaxb
com.sun.xml.bind.external:relaxng-datatype:4.0.5=jaxb
com.sun.xml.bind.external:rngom:4.0.5=jaxb
com.sun.xml.dtd-parser:dtd-parser:1.5.1=jaxb
com.zaxxer:HikariCP:6.2.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.zaxxer:HikariCP:7.0.1=deploy_jar
com.zaxxer:HikariCP:7.0.2=compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
commons-beanutils:commons-beanutils:1.9.4=checkstyle
commons-codec:commons-codec:1.18.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
commons-codec:commons-codec:1.19.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
commons-collections:commons-collections:3.2.2=checkstyle
commons-io:commons-io:2.18.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
commons-io:commons-io:2.20.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
commons-logging:commons-logging:1.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
dnsjava:dnsjava:3.6.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
guru.nidi.com.eclipsesource.j2v8:j2v8_linux_x86_64:4.6.0=testRuntimeClasspath
@@ -274,37 +269,27 @@ io.apicurio:apicurio-registry-protobuf-schema-utilities:3.0.0.M2=compileClasspat
io.github.classgraph:classgraph:4.8.162=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.github.eisop:dataflow-errorprone:3.34.0-eisop1=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
io.github.java-diff-utils:java-diff-utils:4.15=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-alts:1.69.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-alts:1.70.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
io.grpc:grpc-api:1.70.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-auth:1.69.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-auth:1.70.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
io.grpc:grpc-census:1.69.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-context:1.70.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-core:1.69.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-core:1.70.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
io.grpc:grpc-googleapis:1.69.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-grpclb:1.69.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-grpclb:1.70.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
io.grpc:grpc-inprocess:1.69.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-inprocess:1.70.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
io.grpc:grpc-netty-shaded:1.69.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-netty-shaded:1.70.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
io.grpc:grpc-netty:1.69.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-opentelemetry:1.67.1=compileClasspath,nonprodCompileClasspath
io.grpc:grpc-opentelemetry:1.68.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-opentelemetry:1.70.0=testCompileClasspath
io.github.java-diff-utils:java-diff-utils:4.16=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-alts:1.71.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-api:1.71.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-auth:1.71.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-census:1.71.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-context:1.71.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-core:1.71.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-googleapis:1.71.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-grpclb:1.71.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-inprocess:1.71.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-netty-shaded:1.71.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-netty:1.71.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-opentelemetry:1.71.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-protobuf-lite:1.67.1=compileClasspath,nonprodCompileClasspath
io.grpc:grpc-protobuf-lite:1.69.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-protobuf:1.69.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-protobuf:1.70.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
io.grpc:grpc-rls:1.69.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-services:1.69.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-stub:1.69.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-stub:1.70.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
io.grpc:grpc-util:1.69.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-xds:1.69.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-protobuf-lite:1.71.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-protobuf:1.71.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-rls:1.71.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-services:1.71.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-stub:1.71.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-util:1.71.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-xds:1.71.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-buffer:4.1.110.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-codec-http2:4.1.110.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-codec-http:4.1.110.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -334,38 +319,29 @@ io.opentelemetry.contrib:opentelemetry-gcp-resources:1.37.0-alpha=compileClasspa
io.opentelemetry.instrumentation:opentelemetry-grpc-1.6:2.1.0-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.1.0-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.opentelemetry.semconv:opentelemetry-semconv:1.27.0-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry.semconv:opentelemetry-semconv:1.28.0-alpha=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-api-incubator:1.45.0-alpha=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-api-incubator:1.46.0-alpha=testRuntimeClasspath
io.opentelemetry:opentelemetry-api:1.45.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-api:1.46.0=testRuntimeClasspath
io.opentelemetry:opentelemetry-api:1.47.0=testCompileClasspath
io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-api-incubator:1.42.1-alpha=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-api:1.47.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-api:1.53.0=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-bom:1.42.1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-context:1.45.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-context:1.46.0=testRuntimeClasspath
io.opentelemetry:opentelemetry-context:1.47.0=testCompileClasspath
io.opentelemetry:opentelemetry-exporter-logging:1.46.0=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-common:1.53.0=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-context:1.47.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-context:1.53.0=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-exporter-logging:1.53.0=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-extension-incubator:1.35.0-alpha=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-common:1.45.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-sdk-common:1.46.0=testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-common:1.47.0=testCompileClasspath
io.opentelemetry:opentelemetry-sdk-common:1.47.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-sdk-common:1.53.0=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.42.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.46.0=testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.47.0=testCompileClasspath
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.46.0=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-logs:1.45.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-sdk-logs:1.46.0=testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-logs:1.47.0=testCompileClasspath
io.opentelemetry:opentelemetry-sdk-metrics:1.45.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-sdk-metrics:1.46.0=testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-metrics:1.47.0=testCompileClasspath
io.opentelemetry:opentelemetry-sdk-trace:1.45.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-sdk-trace:1.46.0=testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-trace:1.47.0=testCompileClasspath
io.opentelemetry:opentelemetry-sdk:1.45.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-sdk:1.46.0=testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk:1.47.0=testCompileClasspath
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.53.0=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.53.0=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-logs:1.47.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-sdk-logs:1.53.0=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-metrics:1.47.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-sdk-metrics:1.53.0=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-trace:1.47.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-sdk-trace:1.53.0=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk:1.47.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-sdk:1.53.0=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-semconv:1.26.0-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.outfoxx:swiftpoet:1.3.1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.perfmark:perfmark-api:0.27.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
@@ -387,10 +363,10 @@ javax.validation:validation-api:1.0.0.GA=compileClasspath,deploy_jar,nonprodComp
joda-time:joda-time:2.12.7=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
junit:junit:4.13.2=nonprodCompileClasspath,nonprodRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
net.arnx:nashorn-promise:0.1.1=testRuntimeClasspath
net.bytebuddy:byte-buddy-agent:1.15.11=testCompileClasspath,testRuntimeClasspath
net.bytebuddy:byte-buddy-agent:1.17.6=testCompileClasspath,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.12=compileClasspath,nonprodCompileClasspath
net.bytebuddy:byte-buddy:1.14.15=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath
net.bytebuddy:byte-buddy:1.15.11=testCompileClasspath,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.17.6=testCompileClasspath,testRuntimeClasspath
net.java.dev.jna:jna:5.13.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
net.ltgt.gradle.incap:incap:0.2=annotationProcessor,testAnnotationProcessor
net.sf.saxon:Saxon-HE:10.6=checkstyle
@@ -404,33 +380,33 @@ org.apache.arrow:arrow-format:15.0.2=compileClasspath,deploy_jar,nonprodCompileC
org.apache.arrow:arrow-memory-core:15.0.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.arrow:arrow-vector:15.0.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.avro:avro:1.11.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-model-fn-execution:2.63.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-model-job-management:2.63.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-model-pipeline:2.63.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-model-fn-execution:2.67.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-model-job-management:2.67.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-model-pipeline:2.67.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-runners-core-construction-java:2.54.0=testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-runners-core-java:2.63.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-runners-direct-java:2.63.0=testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-runners-google-cloud-dataflow-java:2.63.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-runners-java-fn-execution:2.63.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-core:2.63.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-expansion-service:2.63.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-extensions-arrow:2.63.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-extensions-avro:2.63.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-extensions-google-cloud-platform-core:2.63.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-extensions-protobuf:2.63.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-runners-core-java:2.67.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-runners-direct-java:2.67.0=testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-runners-google-cloud-dataflow-java:2.67.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-runners-java-fn-execution:2.67.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-core:2.67.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-expansion-service:2.67.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-extensions-arrow:2.67.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-extensions-avro:2.67.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-extensions-google-cloud-platform-core:2.67.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-extensions-protobuf:2.67.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-fn-execution:2.54.0=testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-harness:2.63.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-io-google-cloud-platform:2.63.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-transform-service-launcher:2.63.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-harness:2.67.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-io-google-cloud-platform:2.67.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-transform-service-launcher:2.67.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-vendor-grpc-1_60_1:0.1=testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-vendor-grpc-1_69_0:0.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-vendor-guava-32_1_2-jre:0.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.commons:commons-compress:1.26.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.commons:commons-csv:1.13.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.commons:commons-exec:1.4.0=testCompileClasspath,testRuntimeClasspath
org.apache.commons:commons-csv:1.14.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.commons:commons-exec:1.5.0=testRuntimeClasspath
org.apache.commons:commons-lang3:3.14.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
org.apache.commons:commons-lang3:3.17.0=testCompileClasspath,testRuntimeClasspath
org.apache.commons:commons-text:1.13.0=testCompileClasspath,testRuntimeClasspath
org.apache.commons:commons-lang3:3.18.0=testCompileClasspath,testRuntimeClasspath
org.apache.commons:commons-text:1.14.0=testCompileClasspath,testRuntimeClasspath
org.apache.ftpserver:ftplet-api:1.2.1=testCompileClasspath,testRuntimeClasspath
org.apache.ftpserver:ftpserver-core:1.2.1=testCompileClasspath,testRuntimeClasspath
org.apache.httpcomponents:httpclient:4.5.14=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -440,37 +416,39 @@ org.apache.sshd:sshd-common:2.15.0=testCompileClasspath,testRuntimeClasspath
org.apache.sshd:sshd-core:2.15.0=testCompileClasspath,testRuntimeClasspath
org.apache.sshd:sshd-scp:2.15.0=testCompileClasspath,testRuntimeClasspath
org.apache.sshd:sshd-sftp:2.15.0=testCompileClasspath,testRuntimeClasspath
org.apache.tomcat:tomcat-annotations-api:11.0.5=testCompileClasspath,testRuntimeClasspath
org.apache.tomcat:tomcat-annotations-api:11.0.10=testCompileClasspath,testRuntimeClasspath
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath
org.bouncycastle:bcpg-jdk18on:1.80=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.bouncycastle:bcpkix-jdk18on:1.80=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.bouncycastle:bcprov-jdk18on:1.80=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.bouncycastle:bcutil-jdk18on:1.80=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.checkerframework:checker-compat-qual:2.5.3=compileClasspath,nonprodCompileClasspath,soy,testCompileClasspath
org.checkerframework:checker-compat-qual:2.5.5=annotationProcessor,testAnnotationProcessor
org.bouncycastle:bcpg-jdk18on:1.81=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.bouncycastle:bcpkix-jdk18on:1.81=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.bouncycastle:bcprov-jdk18on:1.81=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.bouncycastle:bcutil-jdk18on:1.81=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.checkerframework:checker-compat-qual:2.5.3=annotationProcessor,compileClasspath,nonprodCompileClasspath,soy,testAnnotationProcessor,testCompileClasspath
org.checkerframework:checker-compat-qual:2.5.6=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.checkerframework:checker-qual:3.12.0=checkstyle
org.checkerframework:checker-qual:3.33.0=errorprone,nonprodAnnotationProcessor,soy
org.checkerframework:checker-qual:3.41.0=annotationProcessor,testAnnotationProcessor
org.checkerframework:checker-qual:3.49.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.checkerframework:checker-qual:3.49.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
org.checkerframework:checker-qual:3.49.3=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.codehaus.mojo:animal-sniffer-annotations:1.24=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.conscrypt:conscrypt-openjdk-uber:2.5.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.eclipse.angus:angus-activation:2.0.2=deploy_jar,jaxb,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.eclipse.angus:jakarta.mail:2.0.3=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.eclipse.angus:jakarta.mail:2.0.4=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.eclipse.collections:eclipse-collections-api:11.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.eclipse.collections:eclipse-collections:11.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty.ee10:jetty-ee10-servlet:12.1.0.alpha1=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty.ee10:jetty-ee10-webapp:12.1.0.alpha1=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-ee:12.1.0.alpha1=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-http:12.1.0.alpha1=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-io:12.1.0.alpha1=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-security:12.1.0.alpha1=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-server:12.1.0.alpha1=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-session:12.1.0.alpha1=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-util:12.1.0.alpha1=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-xml:12.1.0.alpha1=testCompileClasspath,testRuntimeClasspath
org.flywaydb:flyway-core:11.4.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.flywaydb:flyway-database-postgresql:11.4.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty.ee10:jetty-ee10-servlet:12.1.0=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty.ee10:jetty-ee10-webapp:12.1.0=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty.ee:jetty-ee-webapp:12.1.0=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-http:12.1.0=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-io:12.1.0=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-security:12.1.0=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-server:12.1.0=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-session:12.1.0=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-util:12.1.0=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-xml:12.1.0=testCompileClasspath,testRuntimeClasspath
org.flywaydb:flyway-core:11.11.1=deploy_jar
org.flywaydb:flyway-core:11.11.2=compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.flywaydb:flyway-database-postgresql:11.11.1=deploy_jar
org.flywaydb:flyway-database-postgresql:11.11.2=compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.glassfish.jaxb:codemodel:4.0.5=jaxb
org.glassfish.jaxb:jaxb-core:4.0.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.glassfish.jaxb:jaxb-core:4.0.5=jaxb
@@ -498,6 +476,7 @@ org.javassist:javassist:3.28.0-GA=checkstyle
org.jboss.logging:jboss-logging:3.5.0.Final=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.jcommander:jcommander:2.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-bom:1.4.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-metadata-jvm:2.1.21=annotationProcessor,testAnnotationProcessor
org.jetbrains.kotlin:kotlin-reflect:1.6.10=annotationProcessor,testAnnotationProcessor
org.jetbrains.kotlin:kotlin-reflect:1.9.20=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-common:1.9.20=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -506,7 +485,7 @@ org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10=compileClasspath,deploy_jar,nonpr
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0=annotationProcessor,testAnnotationProcessor
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib:1.9.20=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib:2.0.21=annotationProcessor,testAnnotationProcessor
org.jetbrains.kotlin:kotlin-stdlib:2.1.21=annotationProcessor,testAnnotationProcessor
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.4.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -515,26 +494,26 @@ org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.0.1=deploy_jar,nonprodRun
org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.jetbrains:annotations:13.0=annotationProcessor,testAnnotationProcessor
org.jetbrains:annotations:17.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jline:jline:3.29.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.joda:joda-money:2.0.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jline:jline:3.30.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.joda:joda-money:2.0.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.json:json:20230618=soy
org.json:json:20250107=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jsoup:jsoup:1.19.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jsoup:jsoup:1.21.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jspecify:jspecify:1.0.0=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
org.junit-pioneer:junit-pioneer:2.3.0=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:5.12.1=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:5.12.1=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-migrationsupport:5.12.1=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-params:5.12.1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-commons:1.12.1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-engine:1.12.1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-launcher:1.12.1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-runner:1.12.1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-suite-api:1.12.1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-suite-commons:1.12.1=testRuntimeClasspath
org.junit:junit-bom:5.12.1=testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-core:5.16.0=testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-junit-jupiter:5.16.0=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:5.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:5.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-migrationsupport:5.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-params:5.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-commons:1.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-engine:1.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-launcher:1.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-runner:1.13.3=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-suite-api:1.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-suite-commons:1.13.4=testRuntimeClasspath
org.junit:junit-bom:5.13.4=testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-core:5.19.0=testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-junit-jupiter:5.19.0=testCompileClasspath,testRuntimeClasspath
org.objenesis:objenesis:3.3=testRuntimeClasspath
org.ogce:xpp3:1.1.6=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath
@@ -552,50 +531,58 @@ org.ow2.asm:asm:9.5=soy
org.ow2.asm:asm:9.7=jacocoAnt
org.ow2.asm:asm:9.7.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.pcollections:pcollections:3.1.4=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
org.postgresql:postgresql:42.7.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.postgresql:postgresql:42.7.7=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.reflections:reflections:0.10.2=checkstyle
org.rnorth.duct-tape:duct-tape:1.0.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-api:4.29.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-chrome-driver:4.29.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-chromium-driver:4.29.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-devtools-v131:4.29.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-devtools-v132:4.29.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-devtools-v133:4.29.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-devtools-v85:4.29.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-edge-driver:4.29.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-firefox-driver:4.29.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-http:4.29.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-ie-driver:4.29.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-java:4.29.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-json:4.29.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-manager:4.29.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-os:4.29.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-remote-driver:4.29.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-safari-driver:4.29.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-support:4.29.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-api:4.35.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-chrome-driver:4.35.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-chromium-driver:4.35.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-devtools-v137:4.35.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-devtools-v138:4.35.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-devtools-v139:4.35.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-edge-driver:4.35.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-firefox-driver:4.35.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-http:4.35.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-ie-driver:4.35.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-java:4.35.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-json:4.35.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-manager:4.35.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-os:4.35.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-remote-driver:4.35.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-safari-driver:4.35.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-support:4.35.0=testCompileClasspath,testRuntimeClasspath
org.slf4j:jcl-over-slf4j:1.7.36=testCompileClasspath,testRuntimeClasspath
org.slf4j:jul-to-slf4j:1.7.30=testRuntimeClasspath
org.slf4j:slf4j-api:2.0.16=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.slf4j:slf4j-jdk14:2.0.16=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.slf4j:slf4j-api:2.0.17=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.slf4j:slf4j-jdk14:2.0.17=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.snakeyaml:snakeyaml-engine:2.1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.testcontainers:database-commons:1.20.6=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.testcontainers:jdbc:1.20.6=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.testcontainers:junit-jupiter:1.20.6=testCompileClasspath,testRuntimeClasspath
org.testcontainers:postgresql:1.20.6=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.testcontainers:selenium:1.20.6=testCompileClasspath,testRuntimeClasspath
org.testcontainers:testcontainers:1.20.6=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.testcontainers:database-commons:1.21.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.testcontainers:jdbc:1.21.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.testcontainers:junit-jupiter:1.21.3=testCompileClasspath,testRuntimeClasspath
org.testcontainers:postgresql:1.21.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.testcontainers:selenium:1.21.3=testCompileClasspath,testRuntimeClasspath
org.testcontainers:testcontainers:1.21.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.threeten:threetenbp:1.7.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.w3c.css:sac:1.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.webjars.npm:viz.js-graphviz-java:2.1.3=testRuntimeClasspath
org.xerial.snappy:snappy-java:1.1.10.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.yaml:snakeyaml:2.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-api:16.25.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-diagram:16.25.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-loader:16.25.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-postgresql:16.25.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-text:16.25.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-tools:16.25.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-utility:16.25.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler:16.25.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.yaml:snakeyaml:2.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-api:16.26.3=deploy_jar
us.fatehi:schemacrawler-api:16.27.1=compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-diagram:16.26.3=deploy_jar
us.fatehi:schemacrawler-diagram:16.27.1=compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-loader:16.26.3=deploy_jar
us.fatehi:schemacrawler-loader:16.27.1=compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-operations:16.27.1=compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-postgresql:16.26.3=deploy_jar
us.fatehi:schemacrawler-postgresql:16.27.1=compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-text:16.26.3=deploy_jar
us.fatehi:schemacrawler-text:16.27.1=compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-tools:16.26.3=deploy_jar
us.fatehi:schemacrawler-tools:16.27.1=compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-utility:16.26.3=deploy_jar
us.fatehi:schemacrawler-utility:16.27.1=compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler:16.26.3=deploy_jar
us.fatehi:schemacrawler:16.27.1=compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
xerces:xmlParserAPIs:2.6.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
empty=devtool,nomulus_test

View File

@@ -275,7 +275,7 @@ public class SendExpiringCertificateNotificationEmailAction implements Runnable
*/
@VisibleForTesting
ImmutableSet<InternetAddress> getEmailAddresses(Registrar registrar, Type contactType) {
ImmutableSortedSet<RegistrarPoc> contacts = registrar.getContactsOfType(contactType);
ImmutableSortedSet<RegistrarPoc> contacts = registrar.getPocsOfType(contactType);
ImmutableSet.Builder<InternetAddress> recipientEmails = new ImmutableSet.Builder<>();
for (RegistrarPoc contact : contacts) {
try {

View File

@@ -172,7 +172,7 @@ public record BillingEvent(
.minusDays(1)
.toString(),
billingId(),
registrarId(),
"",
String.format("%s | TLD: %s | TERM: %d-year", action(), tld(), years()),
amount(),
currency(),

View File

@@ -40,6 +40,8 @@ public class RegistryPipelineWorkerInitializer implements JvmInitializer {
@Override
public void beforeProcessing(PipelineOptions options) {
// TODO(b/416299900): remove next line after GAE is removed.
System.setProperty("google.registry.jetty", "true");
RegistryPipelineOptions registryOptions = options.as(RegistryPipelineOptions.class);
RegistryEnvironment environment = registryOptions.getRegistryEnvironment();
if (environment == null || environment.equals(RegistryEnvironment.UNITTEST)) {

View File

@@ -215,10 +215,12 @@ public class BsaValidateAction implements Runnable {
if (Objects.equals(expectedReason, domain.reason())) {
return Optional.empty();
}
if (isRegistered || domain.reason().equals(Reason.REGISTERED)) {
if (isStalenessAllowed(isRegistered, activeDomains.get(domain.domainName()))) {
// Registered name still reported with other reasons: Don't report if registration is recent.
// Note that staleness is not tolerated if deregistered name is still reported as registered:
// in this case we do not have the VKey on hand, and it is not worth the effort to find it
// out.
if (isRegistered && isStalenessAllowed(activeDomains.get(domain.domainName()))) {
return Optional.empty();
}
}
return Optional.of(
String.format(
@@ -228,15 +230,10 @@ public class BsaValidateAction implements Runnable {
domain.reason()));
}
boolean isStalenessAllowed(boolean isNewDomain, VKey<Domain> domainVKey) {
boolean isStalenessAllowed(VKey<Domain> domainVKey) {
Domain domain = bsaQuery(() -> replicaTm().loadByKey(domainVKey));
var now = clock.nowUtc();
if (isNewDomain) {
return domain.getCreationTime().plus(maxStaleness).isAfter(now);
} else {
return domain.getDeletionTime().isBefore(now)
&& domain.getDeletionTime().plus(maxStaleness).isAfter(now);
}
return domain.getCreationTime().plus(maxStaleness).isAfter(now);
}
/** Returns unique labels across all block lists in the download specified by {@code jobName}. */

View File

@@ -25,16 +25,16 @@ 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 static java.nio.charset.StandardCharsets.UTF_8;
import com.google.cloud.storage.BlobId;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Ordering;
import com.google.common.flogger.FluentLogger;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import com.google.common.io.ByteSource;
import google.registry.bsa.api.BsaCredential;
import google.registry.config.RegistryConfig.Config;
import google.registry.gcs.GcsUtils;
@@ -47,10 +47,13 @@ import google.registry.request.auth.Auth;
import google.registry.util.Clock;
import jakarta.inject.Inject;
import jakarta.persistence.TypedQuery;
import java.io.ByteArrayOutputStream;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.Writer;
import java.util.Optional;
import java.util.zip.GZIPOutputStream;
@@ -60,14 +63,17 @@ import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okio.BufferedSink;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joda.time.DateTime;
/**
* Daily action that uploads unavailable domain names on applicable TLDs to BSA.
*
* <p>The upload is a single zipped text file containing combined details for all BSA-enrolled TLDs.
* The text is a newline-delimited list of punycoded fully qualified domain names, and contains all
* domains on each TLD that are registered and/or reserved.
* The text is a newline-delimited list of punycoded fully qualified domain names with a trailing
* newline at the end, and contains all domains on each TLD that are registered and/or reserved.
*
* <p>The file is also uploaded to GCS to preserve it as a record for ourselves.
*/
@@ -118,7 +124,7 @@ public class UploadBsaUnavailableDomainsAction implements Runnable {
// TODO(mcilwain): Implement a date Cursor, have the cronjob run frequently, and short-circuit
// the run if the daily upload is already completed.
DateTime runTime = clock.nowUtc();
String unavailableDomains = Joiner.on("\n").join(getUnavailableDomains(runTime));
ImmutableSortedSet<String> unavailableDomains = getUnavailableDomains(runTime);
if (unavailableDomains.isEmpty()) {
logger.atWarning().log("No unavailable domains found; terminating.");
emailSender.sendNotification(
@@ -136,12 +142,16 @@ public class UploadBsaUnavailableDomainsAction implements Runnable {
}
/** Uploads the unavailable domains list to GCS in the unavailable domains bucket. */
boolean uploadToGcs(String unavailableDomains, DateTime runTime) {
boolean uploadToGcs(ImmutableSortedSet<String> unavailableDomains, DateTime runTime) {
logger.atInfo().log("Uploading unavailable names file to GCS in bucket %s", gcsBucket);
BlobId blobId = BlobId.of(gcsBucket, createFilename(runTime));
// `gcsUtils.openOutputStream` returns a buffered stream
try (OutputStream gcsOutput = gcsUtils.openOutputStream(blobId);
Writer osWriter = new OutputStreamWriter(gcsOutput, US_ASCII)) {
osWriter.write(unavailableDomains);
for (var domainName : unavailableDomains) {
osWriter.write(domainName);
osWriter.write("\n");
}
return true;
} catch (Exception e) {
logger.atSevere().withCause(e).log(
@@ -150,10 +160,14 @@ public class UploadBsaUnavailableDomainsAction implements Runnable {
}
}
boolean uploadToBsa(String unavailableDomains, DateTime runTime) {
boolean uploadToBsa(ImmutableSortedSet<String> unavailableDomains, DateTime runTime) {
try {
byte[] gzippedContents = gzipUnavailableDomains(unavailableDomains);
String sha512Hash = ByteSource.wrap(gzippedContents).hash(Hashing.sha512()).toString();
Hasher sha512Hasher = Hashing.sha512().newHasher();
unavailableDomains.stream()
.map(name -> name + "\n")
.forEachOrdered(line -> sha512Hasher.putString(line, UTF_8));
String sha512Hash = sha512Hasher.hash().toString();
String filename = createFilename(runTime);
OkHttpClient client = new OkHttpClient().newBuilder().build();
@@ -169,7 +183,9 @@ public class UploadBsaUnavailableDomainsAction implements Runnable {
.addFormDataPart(
"file",
String.format("%s.gz", filename),
RequestBody.create(gzippedContents, MediaType.parse("application/octet-stream")))
new StreamingRequestBody(
gzippedStream(unavailableDomains),
MediaType.parse("application/octet-stream")))
.build();
Request request =
@@ -196,15 +212,6 @@ public class UploadBsaUnavailableDomainsAction implements Runnable {
}
}
private byte[] gzipUnavailableDomains(String unavailableDomains) throws IOException {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream)) {
gzipOutputStream.write(unavailableDomains.getBytes(US_ASCII));
}
return byteArrayOutputStream.toByteArray();
}
}
private static String createFilename(DateTime runTime) {
return String.format("unavailable_domains_%s.txt", runTime.toString());
}
@@ -280,4 +287,65 @@ public class UploadBsaUnavailableDomainsAction implements Runnable {
private static String toDomain(String domainLabel, Tld tld) {
return String.format("%s.%s", domainLabel, tld.getTldStr());
}
private InputStream gzippedStream(ImmutableSortedSet<String> unavailableDomains)
throws IOException {
PipedInputStream inputStream = new PipedInputStream();
PipedOutputStream outputStream = new PipedOutputStream(inputStream);
new Thread(
() -> {
try {
gzipUnavailableDomains(outputStream, unavailableDomains);
} catch (Throwable e) {
logger.atSevere().withCause(e).log("Failed to gzip unavailable domains.");
try {
// This will cause the next read to throw an IOException.
inputStream.close();
} catch (IOException ignore) {
// Won't happen for `PipedInputStream.close()`
}
}
})
.start();
return inputStream;
}
private void gzipUnavailableDomains(
PipedOutputStream outputStream, ImmutableSortedSet<String> unavailableDomains)
throws IOException {
// `GZIPOutputStream` is buffered.
try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream)) {
for (String name : unavailableDomains) {
var line = name + "\n";
gzipOutputStream.write(line.getBytes(US_ASCII));
}
}
}
private static class StreamingRequestBody extends RequestBody {
private final BufferedInputStream inputStream;
private final MediaType mediaType;
StreamingRequestBody(InputStream inputStream, MediaType mediaType) {
this.inputStream = new BufferedInputStream(inputStream);
this.mediaType = mediaType;
}
@Nullable
@Override
public MediaType contentType() {
return mediaType;
}
@Override
public void writeTo(@NotNull BufferedSink bufferedSink) throws IOException {
byte[] buffer = new byte[2048];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
bufferedSink.write(buffer, 0, bytesRead);
}
}
}
}

View File

@@ -243,7 +243,7 @@ hibernate:
# that BEAM pipelines are not subject to the maximumPoolSize value defined
# here. See PersistenceModule.java for more information.
hikariMinimumIdle: 1
hikariMaximumPoolSize: 20
hikariMaximumPoolSize: 40
hikariIdleTimeout: 300000
# The batch size is basically the number of insertions / updates in a single
# transaction that will be batched together into one INSERT/UPDATE statement.
@@ -255,7 +255,7 @@ hibernate:
# The fetch size is the number of entities retrieved at a time from the
# database cursor. Here we set a small default geared toward Nomulus server
# transactions. Large queries can override the defaults on a per-query basis.
jdbcFetchSize: 20
jdbcFetchSize: 40
cloudSql:
# jdbc url for the Cloud SQL database.

View File

@@ -88,6 +88,7 @@
should exist between the RECURRING_BILLING cursor's time and the execution
time of the action.
</description>
<!-- Runs shortly after DeleteExpiredDomainsAction so it can delete domains before they renew -->
<schedule>0 3 * * *</schedule>
</task>
@@ -98,7 +99,8 @@
This job runs an action that deletes domains that are past their
autorenew end date.
</description>
<schedule>7 3 * * *</schedule>
<!-- Runs shortly before ExpandBillingRecurrencesPipeline to catch and delete domains before they renew -->
<schedule>45 2 * * *</schedule>
</task>
<task>

View File

@@ -146,6 +146,7 @@
This job runs an action that deletes domains that are past their
autorenew end date.
</description>
<schedule>7 3 * * *</schedule>
<!-- Runs shortly before ExpandBillingRecurrencesPipeline to catch and delete domains before they renew -->
<schedule>45 2 * * *</schedule>
</task>
</entries>

View File

@@ -130,6 +130,7 @@
should exist between the RECURRING_BILLING cursor's time and the execution
time of the action.
</description>
<!-- Runs shortly after DeleteExpiredDomainsAction so it can delete domains before they renew -->
<schedule>0 3 * * *</schedule>
</task>
@@ -140,7 +141,8 @@
This job runs an action that deletes domains that are past their
autorenew end date.
</description>
<schedule>7 3 * * *</schedule>
<!-- Runs shortly before ExpandBillingRecurrencesPipeline to catch and delete domains before they renew -->
<schedule>45 2 * * *</schedule>
</task>
<task>

View File

@@ -57,6 +57,7 @@
This job runs an action that deletes domains that are past their
autorenew end date.
</description>
<schedule>7 3 * * *</schedule>
<!-- Runs shortly before ExpandBillingRecurrencesPipeline to catch and delete domains before they renew -->
<schedule>45 2 * * *</schedule>
</task>
</entries>

View File

@@ -90,6 +90,7 @@
should exist between the RECURRING_BILLING cursor's time and the execution
time of the action.
</description>
<!-- Runs shortly after DeleteExpiredDomainsAction so it can delete domains before they renew -->
<schedule>0 3 * * *</schedule>
</task>
@@ -113,7 +114,8 @@
This job runs an action that deletes domains that are past their
autorenew end date.
</description>
<schedule>7 3 * * *</schedule>
<!-- Runs shortly before ExpandBillingRecurrencesPipeline to catch and delete domains before they renew -->
<schedule>45 2 * * *</schedule>
</task>
<task>

View File

@@ -0,0 +1,29 @@
// Copyright 2025 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.dns.writer;
import dagger.Module;
import google.registry.dns.writer.clouddns.CloudDnsWriterModule;
import google.registry.dns.writer.dnsupdate.DnsUpdateWriterModule;
/**
* Groups all {@link DnsWriter} implementations to be installed.
*
* <p>To cherry-pick the DNS writers to install, overwrite this file with your private version in
* the release process.
*/
@Module(
includes = {CloudDnsWriterModule.class, DnsUpdateWriterModule.class, VoidDnsWriterModule.class})
public class DnsWritersModule {}

View File

@@ -15,6 +15,7 @@
package google.registry.export;
import static com.google.common.base.Verify.verifyNotNull;
import static google.registry.model.common.FeatureFlag.FeatureName.INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS;
import static google.registry.model.tld.Tlds.getTldsOfType;
import static google.registry.persistence.PersistenceModule.TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
@@ -75,7 +76,8 @@ public class ExportDomainListsAction implements Runnable {
ON d.repo_id = gp.domain_repo_id
WHERE d.tld = :tld
AND d.deletion_time > CAST(:now AS timestamptz)
ORDER BY d.domain_name""";
ORDER BY d.domain_name
""";
// This may be a CSV, but it is uses a .txt file extension for back-compatibility
static final String REGISTERED_DOMAINS_FILENAME_FORMAT = "registered_domains_%s.txt";
@@ -93,10 +95,7 @@ public class ExportDomainListsAction implements Runnable {
logger.atInfo().log("Exporting domain lists for TLDs %s.", realTlds);
boolean includeDeletionTimes =
tm().transact(
() ->
FeatureFlag.isActiveNowOrElse(
FeatureFlag.FeatureName.INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS, false));
tm().transact(() -> FeatureFlag.isActiveNow(INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS));
realTlds.forEach(
tld -> {
List<String> domainsList =

View File

@@ -185,7 +185,8 @@ public class CheckApiAction implements Runnable {
}
private boolean checkExists(String domainString, DateTime now) {
return !ForeignKeyUtils.loadCached(Domain.class, ImmutableList.of(domainString), now).isEmpty();
return !ForeignKeyUtils.loadByCache(Domain.class, ImmutableList.of(domainString), now)
.isEmpty();
}
private Optional<String> checkReserved(InternetDomainName domainName) {

View File

@@ -26,6 +26,7 @@ import com.google.common.net.MediaType;
import google.registry.model.eppoutput.EppOutput;
import google.registry.request.Response;
import google.registry.util.ProxyHttpHeaders;
import google.registry.util.StopwatchLogger;
import jakarta.inject.Inject;
/** Handle an EPP request and response. */
@@ -55,7 +56,10 @@ public class EppRequestHandler {
eppController.handleEppCommand(
sessionMetadata, credentials, eppRequestSource, isDryRun, isSuperuser, inputXmlBytes);
response.setContentType(APPLICATION_EPP_XML);
final StopwatchLogger stopwatch = new StopwatchLogger();
byte[] eppResponseXmlBytes = marshalWithLenientRetry(eppOutput);
stopwatch.tick("Completed EPP output marshaling.");
response.setPayload(new String(eppResponseXmlBytes, UTF_8));
logger.atInfo().log(
"EPP response: %s", prettyPrint(EppXmlSanitizer.sanitizeEppXml(eppResponseXmlBytes)));

View File

@@ -29,6 +29,7 @@ import google.registry.model.eppoutput.EppOutput;
import google.registry.monitoring.whitebox.EppMetric;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
import google.registry.persistence.transaction.JpaTransactionManager;
import google.registry.util.StopwatchLogger;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Optional;
@@ -77,23 +78,32 @@ public class FlowRunner {
flowReporter.recordToLogs();
}
eppMetricBuilder.setCommandNameFromFlow(flowClass.getSimpleName());
final StopwatchLogger stopwatch = new StopwatchLogger();
// We may already be in a transaction, e.g., when invoked by DeleteExpiredDomainsAction.
if (!isTransactional || jpaTransactionManager.inTransaction()) {
stopwatch.tick("We're in transaction, running the flow now.");
return EppOutput.create(flowProvider.get().run());
}
stopwatch.tick("We're not in transaction, calling transact.");
try {
return jpaTransactionManager.transact(
isolationLevelOverride.orElse(null),
() -> {
try {
stopwatch.tick("Running the flow in transaction.");
EppOutput output = EppOutput.create(flowProvider.get().run());
stopwatch.tick("Completed the flow in transaction.");
if (isDryRun) {
throw new DryRunException(output);
}
if (flowClass.equals(LoginFlow.class)) {
// In LoginFlow, registrarId isn't known until after the flow executes, so save
// it then.
stopwatch.tick("Login flow started setting registrar id.");
eppMetricBuilder.setRegistrarId(sessionMetadata.getRegistrarId());
stopwatch.tick("Login flow finished setting registrar id.");
}
return output;
} catch (EppException e) {

View File

@@ -21,6 +21,7 @@ import static google.registry.xml.ValidationMode.STRICT;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import google.registry.flows.EppException.CommandUseErrorException;
import google.registry.flows.EppException.ParameterValueRangeErrorException;
@@ -30,6 +31,7 @@ import google.registry.flows.custom.EntityChanges;
import google.registry.model.EppResource;
import google.registry.model.adapters.CurrencyUnitAdapter.UnknownCurrencyException;
import google.registry.model.eppcommon.EppXmlTransformer;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppinput.EppInput.WrongProtocolVersionException;
import google.registry.model.eppoutput.EppOutput;
import google.registry.model.host.InetAddressAdapter.IpVersionMismatchException;
@@ -40,6 +42,9 @@ import java.util.List;
/** Static utility functions for flows. */
public final class FlowUtils {
public static final ImmutableSet<StatusValue> DELETE_PROHIBITED_STATUSES =
ImmutableSet.of(StatusValue.CLIENT_DELETE_PROHIBITED, StatusValue.SERVER_DELETE_PROHIBITED);
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private FlowUtils() {}

View File

@@ -19,6 +19,7 @@ import static google.registry.flows.ResourceFlowUtils.verifyResourceDoesNotExist
import static google.registry.flows.contact.ContactFlowUtils.validateAsciiPostalInfo;
import static google.registry.flows.contact.ContactFlowUtils.validateContactAgainstPolicy;
import static google.registry.model.EppResourceUtils.createRepoId;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableSet;
@@ -29,8 +30,10 @@ import google.registry.flows.FlowModule.RegistrarId;
import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.MutatingFlow;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.flows.exceptions.ResourceAlreadyExistsForThisClientException;
import google.registry.flows.exceptions.ResourceCreateContentionException;
import google.registry.model.common.FeatureFlag;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactCommand.Create;
import google.registry.model.contact.ContactHistory;
@@ -47,6 +50,7 @@ import org.joda.time.DateTime;
* An EPP flow that creates a new contact.
*
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
* @error {@link ContactsProhibitedException}
* @error {@link ResourceAlreadyExistsForThisClientException}
* @error {@link ResourceCreateContentionException}
* @error {@link ContactFlowUtils.BadInternationalizedPostalInfoException}
@@ -69,6 +73,9 @@ public final class ContactCreateFlow implements MutatingFlow {
extensionManager.register(MetadataExtension.class);
validateRegistrarIsLoggedIn(registrarId);
extensionManager.validate();
if (FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_PROHIBITED)) {
throw new ContactsProhibitedException();
}
Create command = (Create) resourceCommand;
DateTime now = tm().getTransactionTime();
verifyResourceDoesNotExist(Contact.class, targetId, now, registrarId);

View File

@@ -14,6 +14,7 @@
package google.registry.flows.contact;
import static google.registry.flows.FlowUtils.DELETE_PROHIBITED_STATUSES;
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.checkLinkedDomains;
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
@@ -65,12 +66,6 @@ import org.joda.time.DateTime;
@ReportingSpec(ActivityReportField.CONTACT_DELETE)
public final class ContactDeleteFlow implements MutatingFlow {
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES =
ImmutableSet.of(
StatusValue.CLIENT_DELETE_PROHIBITED,
StatusValue.PENDING_DELETE,
StatusValue.SERVER_DELETE_PROHIBITED);
@Inject ExtensionManager extensionManager;
@Inject @RegistrarId String registrarId;
@Inject @TargetId String targetId;
@@ -91,9 +86,10 @@ public final class ContactDeleteFlow implements MutatingFlow {
DateTime now = tm().getTransactionTime();
checkLinkedDomains(targetId, now, Contact.class);
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
verifyNoDisallowedStatuses(existingContact, DISALLOWED_STATUSES);
verifyOptionalAuthInfo(authInfo, existingContact);
verifyNoDisallowedStatuses(existingContact, ImmutableSet.of(StatusValue.PENDING_DELETE));
if (!isSuperuser) {
verifyNoDisallowedStatuses(existingContact, DELETE_PROHIBITED_STATUSES);
verifyResourceOwnership(registrarId, existingContact);
}
// Handle pending transfers on contact deletion.

View File

@@ -24,6 +24,7 @@ import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
import static google.registry.flows.contact.ContactFlowUtils.validateAsciiPostalInfo;
import static google.registry.flows.contact.ContactFlowUtils.validateContactAgainstPolicy;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_UPDATE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
@@ -35,7 +36,9 @@ import google.registry.flows.FlowModule.Superuser;
import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.MutatingFlow;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
import google.registry.model.common.FeatureFlag;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactCommand.Update;
import google.registry.model.contact.ContactCommand.Update.Change;
@@ -55,6 +58,7 @@ import org.joda.time.DateTime;
/**
* An EPP flow that updates a contact.
*
* @error {@link ContactsProhibitedException}
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
* @error {@link google.registry.flows.ResourceFlowUtils.AddRemoveSameValueException}
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException}
@@ -92,6 +96,9 @@ public final class ContactUpdateFlow implements MutatingFlow {
extensionManager.register(MetadataExtension.class);
validateRegistrarIsLoggedIn(registrarId);
extensionManager.validate();
if (FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_PROHIBITED)) {
throw new ContactsProhibitedException();
}
Update command = (Update) resourceCommand;
DateTime now = tm().getTransactionTime();
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);

View File

@@ -432,7 +432,7 @@ public final class DomainCheckFlow implements TransactionalFlow {
.filter(existingDomains::containsKey)
.collect(toImmutableMap(d -> d, existingDomains::get));
ImmutableMap<VKey<? extends EppResource>, EppResource> loadedDomains =
EppResource.loadCached(ImmutableList.copyOf(existingDomainsToLoad.values()));
EppResource.loadByCacheIfEnabled(ImmutableList.copyOf(existingDomainsToLoad.values()));
return ImmutableMap.copyOf(
Maps.transformEntries(existingDomainsToLoad, (k, v) -> (Domain) loadedDomains.get(v)));
}

View File

@@ -72,7 +72,9 @@ import google.registry.flows.custom.DomainCreateFlowCustomLogic;
import google.registry.flows.custom.DomainCreateFlowCustomLogic.BeforeResponseParameters;
import google.registry.flows.custom.DomainCreateFlowCustomLogic.BeforeResponseReturnData;
import google.registry.flows.custom.EntityChanges;
import google.registry.flows.domain.DomainFlowUtils.RegistrantProhibitedException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.flows.exceptions.ResourceAlreadyExistsForThisClientException;
import google.registry.flows.exceptions.ResourceCreateContentionException;
import google.registry.model.ImmutableObject;
@@ -147,6 +149,7 @@ import org.joda.time.Duration;
* @error {@link DomainCreateFlow.NoGeneralRegistrationsInCurrentPhaseException}
* @error {@link DomainCreateFlow.NoTrademarkedRegistrationsBeforeSunriseException}
* @error {@link BulkDomainRegisteredForTooManyYearsException}
* @error {@link ContactsProhibitedException}
* @error {@link DomainCreateFlow.SignedMarksOnlyDuringSunriseException}
* @error {@link DomainFlowTmchUtils.NoMarksFoundMatchingDomainException}
* @error {@link DomainFlowTmchUtils.FoundMarkNotYetValidException}
@@ -194,6 +197,7 @@ import org.joda.time.Duration;
* @error {@link DomainFlowUtils.NameserversNotSpecifiedForTldWithNameserverAllowListException}
* @error {@link DomainFlowUtils.PremiumNameBlockedException}
* @error {@link DomainFlowUtils.RegistrantNotAllowedException}
* @error {@link RegistrantProhibitedException}
* @error {@link DomainFlowUtils.RegistrarMustBeActiveForThisOperationException}
* @error {@link DomainFlowUtils.TldDoesNotExistException}
* @error {@link DomainFlowUtils.TooManyDsRecordsException}
@@ -244,7 +248,7 @@ public final class DomainCreateFlow implements MutatingFlow {
verifyResourceDoesNotExist(Domain.class, targetId, now, registrarId);
// Validate that this is actually a legal domain name on a TLD that the registrar has access to.
InternetDomainName domainName = validateDomainName(command.getDomainName());
String domainLabel = domainName.parts().get(0);
String domainLabel = domainName.parts().getFirst();
Tld tld = Tld.get(domainName.parent().toString());
validateCreateCommandContactsAndNameservers(command, tld, domainName);
TldState tldState = tld.getTldState(now);

View File

@@ -17,6 +17,7 @@ package google.registry.flows.domain;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.dns.DnsUtils.requestDomainDnsRefresh;
import static google.registry.flows.FlowUtils.DELETE_PROHIBITED_STATUSES;
import static google.registry.flows.FlowUtils.createHistoryEntryId;
import static google.registry.flows.FlowUtils.persistEntityChanges;
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
@@ -122,11 +123,6 @@ public final class DomainDeleteFlow implements MutatingFlow, SqlStatementLogging
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES = ImmutableSet.of(
StatusValue.CLIENT_DELETE_PROHIBITED,
StatusValue.PENDING_DELETE,
StatusValue.SERVER_DELETE_PROHIBITED);
@Inject ExtensionManager extensionManager;
@Inject EppInput eppInput;
@Inject SessionMetadata sessionMetadata;
@@ -304,9 +300,10 @@ public final class DomainDeleteFlow implements MutatingFlow, SqlStatementLogging
private void verifyDeleteAllowed(Domain existingDomain, Tld tld, DateTime now)
throws EppException {
verifyNoDisallowedStatuses(existingDomain, DISALLOWED_STATUSES);
verifyOptionalAuthInfo(authInfo, existingDomain);
verifyNoDisallowedStatuses(existingDomain, ImmutableSet.of(StatusValue.PENDING_DELETE));
if (!isSuperuser) {
verifyNoDisallowedStatuses(existingDomain, DELETE_PROHIBITED_STATUSES);
verifyResourceOwnership(registrarId, existingDomain);
verifyNotInPredelegation(tld, now);
checkAllowedAccessToTld(registrarId, tld.getTld().toString());

View File

@@ -25,7 +25,7 @@ import static com.google.common.collect.Sets.intersection;
import static com.google.common.collect.Sets.union;
import static google.registry.bsa.persistence.BsaLabelUtils.isLabelBlocked;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_OPTIONAL;
import static google.registry.model.common.FeatureFlag.isActiveNow;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
import static google.registry.model.domain.Domain.MAX_REGISTRATION_YEARS;
import static google.registry.model.domain.token.AllocationToken.TokenType.REGISTER_BSA;
import static google.registry.model.tld.Tld.TldState.GENERAL_AVAILABILITY;
@@ -75,11 +75,13 @@ import google.registry.flows.EppException.ParameterValueSyntaxErrorException;
import google.registry.flows.EppException.RequiredParameterMissingException;
import google.registry.flows.EppException.StatusProhibitsOperationException;
import google.registry.flows.EppException.UnimplementedOptionException;
import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
import google.registry.model.EppResource;
import google.registry.model.billing.BillingBase.Flag;
import google.registry.model.billing.BillingBase.Reason;
import google.registry.model.billing.BillingRecurrence;
import google.registry.model.common.FeatureFlag;
import google.registry.model.contact.Contact;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.DesignatedContact.Type;
@@ -417,7 +419,7 @@ public class DomainFlowUtils {
contacts.stream().map(DesignatedContact::getContactKey).forEach(keysToLoad::add);
registrant.ifPresent(keysToLoad::add);
keysToLoad.addAll(nameservers);
verifyNotInPendingDelete(EppResource.loadCached(keysToLoad.build()).values());
verifyNotInPendingDelete(EppResource.loadByCacheIfEnabled(keysToLoad.build()).values());
}
private static void verifyNotInPendingDelete(Iterable<EppResource> resources)
@@ -478,27 +480,76 @@ public class DomainFlowUtils {
}
}
static void validateRequiredContactsPresentIfRequiredForDataset(
/**
* Enforces the presence/absence of contact data on domain creates depending on the minimum data
* set migration schedule.
*/
static void validateCreateContactData(
Optional<VKey<Contact>> registrant, Set<DesignatedContact> contacts)
throws RequiredParameterMissingException {
// TODO(b/353347632): Change this flag check to a registry config check.
if (isActiveNow(MINIMUM_DATASET_CONTACTS_OPTIONAL)) {
// Contacts are not required once we have begun the migration to the minimum dataset
return;
}
if (registrant.isEmpty()) {
throw new MissingRegistrantException();
}
throws RequiredParameterMissingException, ParameterValuePolicyErrorException {
// TODO(b/353347632): Change these flag checks to a registry config check once minimum data set
// migration is completed.
if (FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_PROHIBITED)) {
if (registrant.isPresent()) {
throw new RegistrantProhibitedException();
}
if (!contacts.isEmpty()) {
throw new ContactsProhibitedException();
}
} else if (!FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_OPTIONAL)) {
if (registrant.isEmpty()) {
throw new MissingRegistrantException();
}
Set<Type> roles = new HashSet<>();
for (DesignatedContact contact : contacts) {
roles.add(contact.getType());
Set<Type> roles = new HashSet<>();
for (DesignatedContact contact : contacts) {
roles.add(contact.getType());
}
if (!roles.contains(Type.ADMIN)) {
throw new MissingAdminContactException();
}
if (!roles.contains(Type.TECH)) {
throw new MissingTechnicalContactException();
}
}
if (!roles.contains(Type.ADMIN)) {
throw new MissingAdminContactException();
}
if (!roles.contains(Type.TECH)) {
throw new MissingTechnicalContactException();
}
/**
* Enforces the presence/absence of contact data on domain updates depending on the minimum data
* set migration schedule.
*/
static void validateUpdateContactData(
Optional<VKey<Contact>> existingRegistrant,
Optional<VKey<Contact>> newRegistrant,
Set<DesignatedContact> existingContacts,
Set<DesignatedContact> newContacts)
throws RequiredParameterMissingException, ParameterValuePolicyErrorException {
// TODO(b/353347632): Change these flag checks to a registry config check once minimum data set
// migration is completed.
if (FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_PROHIBITED)) {
// Throw if the update specifies a new registrant that is different from the existing one.
if (newRegistrant.isPresent() && !newRegistrant.equals(existingRegistrant)) {
throw new RegistrantProhibitedException();
}
// Throw if the update specifies any new contacts that weren't already present on the domain.
if (!Sets.difference(newContacts, existingContacts).isEmpty()) {
throw new ContactsProhibitedException();
}
} else if (!FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_OPTIONAL)) {
// Throw if the update empties out a registrant that had been present.
if (newRegistrant.isEmpty() && existingRegistrant.isPresent()) {
throw new MissingRegistrantException();
}
// Throw if the update contains no admin contact when one had been present.
if (existingContacts.stream().anyMatch(c -> c.getType().equals(Type.ADMIN))
&& newContacts.stream().noneMatch(c -> c.getType().equals(Type.ADMIN))) {
throw new MissingAdminContactException();
}
// Throw if the update contains no tech contact when one had been present.
if (existingContacts.stream().anyMatch(c -> c.getType().equals(Type.TECH))
&& newContacts.stream().noneMatch(c -> c.getType().equals(Type.TECH))) {
throw new MissingTechnicalContactException();
}
}
}
@@ -1042,8 +1093,7 @@ public class DomainFlowUtils {
String tldStr = tld.getTldStr();
validateRegistrantAllowedOnTld(tldStr, command.getRegistrantContactId());
validateNoDuplicateContacts(command.getContacts());
validateRequiredContactsPresentIfRequiredForDataset(
command.getRegistrant(), command.getContacts());
validateCreateContactData(command.getRegistrant(), command.getContacts());
ImmutableSet<String> hostNames = command.getNameserverHostNames();
validateNameserversCountForTld(tldStr, domainName, hostNames.size());
validateNameserversAllowedOnTld(tldStr, hostNames);
@@ -1347,7 +1397,7 @@ public class DomainFlowUtils {
}
/** Domain name is under tld which doesn't exist. */
static class TldDoesNotExistException extends ParameterValueRangeErrorException {
public static class TldDoesNotExistException extends ParameterValueRangeErrorException {
public TldDoesNotExistException(String tld) {
super(String.format("Domain name is under tld %s which doesn't exist", tld));
}
@@ -1367,6 +1417,13 @@ public class DomainFlowUtils {
}
}
/** Having a registrant is prohibited by registry policy. */
static class RegistrantProhibitedException extends ParameterValuePolicyErrorException {
public RegistrantProhibitedException() {
super("Having a registrant is prohibited by registry policy");
}
}
/** Admin contact is required. */
static class MissingAdminContactException extends RequiredParameterMissingException {
public MissingAdminContactException() {

View File

@@ -31,6 +31,7 @@ import google.registry.flows.ExtensionManager;
import google.registry.flows.FlowModule.RegistrarId;
import google.registry.flows.FlowModule.Superuser;
import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.MutatingFlow;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.custom.DomainInfoFlowCustomLogic;
@@ -53,6 +54,8 @@ import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.persistence.IsolationLevel;
import google.registry.persistence.PersistenceModule;
import google.registry.util.Clock;
import jakarta.inject.Inject;
import java.util.Optional;
@@ -62,8 +65,12 @@ import org.joda.time.DateTime;
* An EPP flow that returns information about a domain.
*
* <p>The registrar that owns the domain, and any registrar presenting a valid authInfo for the
* domain, will get a rich result with all of the domain's fields. All other requests will be
* answered with a minimal result containing only basic information about the domain.
* domain, will get a rich result with all the domain's fields. All other requests will be answered
* with a minimal result containing only basic information about the domain.
*
* <p>This implements {@link MutatingFlow} instead of {@link TransactionalFlow} as a workaround so
* that the common workflow of "create domain, then immediately get domain info" does not run into
* replication lag issues where the info command claims the domain does not exist.
*
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
* @error {@link google.registry.flows.FlowUtils.UnknownCurrencyEppException}
@@ -76,7 +83,8 @@ import org.joda.time.DateTime;
* @error {@link DomainFlowUtils.TransfersAreAlwaysForOneYearException}
*/
@ReportingSpec(ActivityReportField.DOMAIN_INFO)
public final class DomainInfoFlow implements TransactionalFlow {
@IsolationLevel(PersistenceModule.TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ)
public final class DomainInfoFlow implements MutatingFlow {
@Inject ExtensionManager extensionManager;
@Inject ResourceCommand resourceCommand;

View File

@@ -37,11 +37,11 @@ import static google.registry.flows.domain.DomainFlowUtils.validateNameserversAl
import static google.registry.flows.domain.DomainFlowUtils.validateNameserversCountForTld;
import static google.registry.flows.domain.DomainFlowUtils.validateNoDuplicateContacts;
import static google.registry.flows.domain.DomainFlowUtils.validateRegistrantAllowedOnTld;
import static google.registry.flows.domain.DomainFlowUtils.validateRequiredContactsPresentIfRequiredForDataset;
import static google.registry.flows.domain.DomainFlowUtils.validateUpdateContactData;
import static google.registry.flows.domain.DomainFlowUtils.verifyClientUpdateNotProhibited;
import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPendingDelete;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_OPTIONAL;
import static google.registry.model.common.FeatureFlag.isActiveNow;
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_UPDATE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
@@ -64,9 +64,11 @@ import google.registry.flows.custom.DomainUpdateFlowCustomLogic.BeforeSaveParame
import google.registry.flows.custom.EntityChanges;
import google.registry.flows.domain.DomainFlowUtils.MissingRegistrantException;
import google.registry.flows.domain.DomainFlowUtils.NameserversNotSpecifiedForTldWithNameserverAllowListException;
import google.registry.flows.domain.DomainFlowUtils.RegistrantProhibitedException;
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingBase.Reason;
import google.registry.model.billing.BillingEvent;
import google.registry.model.common.FeatureFlag;
import google.registry.model.contact.Contact;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.Domain;
@@ -130,6 +132,7 @@ import org.joda.time.DateTime;
* @error {@link NameserversNotSpecifiedForTldWithNameserverAllowListException}
* @error {@link DomainFlowUtils.NotAuthorizedForTldException}
* @error {@link DomainFlowUtils.RegistrantNotAllowedException}
* @error {@link RegistrantProhibitedException}
* @error {@link DomainFlowUtils.SecDnsAllUsageException}
* @error {@link DomainFlowUtils.TooManyDsRecordsException}
* @error {@link DomainFlowUtils.TooManyNameserversException}
@@ -183,7 +186,7 @@ public final class DomainUpdateFlow implements MutatingFlow {
Domain newDomain = performUpdate(command, existingDomain, now);
DomainHistory domainHistory =
historyBuilder.setType(DOMAIN_UPDATE).setDomain(newDomain).build();
validateNewState(newDomain);
validateNewState(existingDomain, newDomain);
if (requiresDnsUpdate(existingDomain, newDomain)) {
requestDomainDnsRefresh(targetId);
}
@@ -304,11 +307,12 @@ public final class DomainUpdateFlow implements MutatingFlow {
private Optional<VKey<Contact>> determineUpdatedRegistrant(Change change, Domain domain)
throws EppException {
// During phase 1 of minimum dataset transition, allow registrant to be removed
// During or after the minimum dataset transition, allow registrant to be removed.
if (change.getRegistrantContactId().isPresent()
&& change.getRegistrantContactId().get().isEmpty()) {
// TODO(b/353347632): Change this flag check to a registry config check.
if (isActiveNow(MINIMUM_DATASET_CONTACTS_OPTIONAL)) {
if (FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_OPTIONAL)
|| FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_PROHIBITED)) {
return Optional.empty();
} else {
throw new MissingRegistrantException();
@@ -324,9 +328,13 @@ public final class DomainUpdateFlow implements MutatingFlow {
* compliant with the additions or amendments, otherwise existing data can become invalid and
* cause Domain update failure.
*/
private static void validateNewState(Domain newDomain) throws EppException {
validateRequiredContactsPresentIfRequiredForDataset(
newDomain.getRegistrant(), newDomain.getContacts());
private static void validateNewState(Domain existingDomain, Domain newDomain)
throws EppException {
validateUpdateContactData(
existingDomain.getRegistrant(),
newDomain.getRegistrant(),
existingDomain.getContacts(),
newDomain.getContacts());
validateDsData(newDomain.getDsData());
validateNameserversCountForTld(
newDomain.getTld(),

View File

@@ -0,0 +1,24 @@
// Copyright 2025 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.flows.exceptions;
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
/** Having contacts is prohibited by registry policy */
public class ContactsProhibitedException extends ParameterValuePolicyErrorException {
public ContactsProhibitedException() {
super("Having contacts is prohibited by registry policy");
}
}

View File

@@ -15,6 +15,7 @@
package google.registry.flows.host;
import static google.registry.dns.DnsUtils.requestHostDnsRefresh;
import static google.registry.flows.FlowUtils.DELETE_PROHIBITED_STATUSES;
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.checkLinkedDomains;
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
@@ -65,12 +66,6 @@ import org.joda.time.DateTime;
@ReportingSpec(ActivityReportField.HOST_DELETE)
public final class HostDeleteFlow implements MutatingFlow {
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES =
ImmutableSet.of(
StatusValue.CLIENT_DELETE_PROHIBITED,
StatusValue.PENDING_DELETE,
StatusValue.SERVER_DELETE_PROHIBITED);
@Inject ExtensionManager extensionManager;
@Inject @RegistrarId String registrarId;
@Inject @TargetId String targetId;
@@ -91,8 +86,9 @@ public final class HostDeleteFlow implements MutatingFlow {
validateHostName(targetId);
checkLinkedDomains(targetId, now, Host.class);
Host existingHost = loadAndVerifyExistence(Host.class, targetId, now);
verifyNoDisallowedStatuses(existingHost, DISALLOWED_STATUSES);
verifyNoDisallowedStatuses(existingHost, ImmutableSet.of(StatusValue.PENDING_DELETE));
if (!isSuperuser) {
verifyNoDisallowedStatuses(existingHost, DELETE_PROHIBITED_STATUSES);
// Hosts transfer with their superordinate domains, so for hosts with a superordinate domain,
// the client id, needs to be read off of it.
EppResource owningResource =

View File

@@ -47,6 +47,7 @@ import google.registry.model.eppinput.EppInput.Options;
import google.registry.model.eppinput.EppInput.Services;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.registrar.Registrar;
import google.registry.util.StopwatchLogger;
import jakarta.inject.Inject;
import java.util.Optional;
import java.util.Set;
@@ -97,19 +98,25 @@ public class LoginFlow implements MutatingFlow {
/** Run the flow without bothering to log errors. The {@link #run} method will do that for us. */
private EppResponse runWithoutLogging() throws EppException {
final StopwatchLogger stopwatch = new StopwatchLogger();
extensionManager.validate(); // There are no legal extensions for this flow.
stopwatch.tick("LoginFlow extension validate");
Login login = (Login) eppInput.getCommandWrapper().getCommand();
stopwatch.tick("LoginFlow getCommand");
if (!registrarId.isEmpty()) {
throw new AlreadyLoggedInException();
}
Options options = login.getOptions();
stopwatch.tick("LoginFlow getOptions");
if (!ProtocolDefinition.LANGUAGE.equals(options.getLanguage())) {
throw new UnsupportedLanguageException();
}
Services services = login.getServices();
stopwatch.tick("LoginFlow getServices");
Set<String> unsupportedObjectServices = difference(
nullToEmpty(services.getObjectServices()),
ProtocolDefinition.SUPPORTED_OBJECT_SERVICES);
stopwatch.tick("LoginFlow difference unsupportedObjectServices");
if (!unsupportedObjectServices.isEmpty()) {
throw new UnimplementedObjectServiceException();
}
@@ -121,11 +128,12 @@ public class LoginFlow implements MutatingFlow {
}
serviceExtensionUrisBuilder.add(uri);
}
stopwatch.tick("LoginFlow serviceExtensionUrisBuilder");
Optional<Registrar> registrar = Registrar.loadByRegistrarIdCached(login.getClientId());
if (registrar.isEmpty()) {
throw new BadRegistrarIdException(login.getClientId());
}
stopwatch.tick("LoginFlow loadByRegistrarIdCached");
// AuthenticationErrorExceptions will propagate up through here.
try {
credentials.validate(registrar.get(), login.getPassword());
@@ -137,6 +145,7 @@ public class LoginFlow implements MutatingFlow {
throw e;
}
}
stopwatch.tick("LoginFlow credentials.validate");
if (!registrar.get().isLive()) {
throw new RegistrarAccountNotActiveException();
}
@@ -145,17 +154,24 @@ public class LoginFlow implements MutatingFlow {
String newPassword = login.getNewPassword().get();
// Load fresh from database (bypassing the cache) to ensure we don't save stale data.
Optional<Registrar> freshRegistrar = Registrar.loadByRegistrarId(login.getClientId());
stopwatch.tick("LoginFlow reload freshRegistrar");
if (freshRegistrar.isEmpty()) {
throw new BadRegistrarIdException(login.getClientId());
}
tm().put(freshRegistrar.get().asBuilder().setPassword(newPassword).build());
stopwatch.tick("LoginFlow updated password");
}
// We are in!
sessionMetadata.resetFailedLoginAttempts();
stopwatch.tick("LoginFlow resetFailedLoginAttempts");
sessionMetadata.setRegistrarId(login.getClientId());
stopwatch.tick("LoginFlow setRegistrarId");
sessionMetadata.setServiceExtensionUris(serviceExtensionUrisBuilder.build());
return responseBuilder.setIsLoginResponse().build();
stopwatch.tick("LoginFlow setServiceExtensionUris");
EppResponse eppResponse = responseBuilder.setIsLoginResponse().build();
stopwatch.tick("LoginFlow eppResponse build()");
return eppResponse;
}
/** Registrar with this ID could not be found. */

View File

@@ -17,10 +17,7 @@ package google.registry.loadtest;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.Lists.partition;
import static google.registry.security.XsrfTokenManager.X_CSRF_TOKEN;
import static google.registry.util.ResourceUtils.readResourceUtf8;
import static java.util.Arrays.asList;
import static org.joda.time.DateTimeZone.UTC;
import com.google.cloud.tasks.v2.Task;
import com.google.common.collect.ImmutableList;
@@ -34,7 +31,7 @@ import google.registry.request.Action;
import google.registry.request.Action.GaeService;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.security.XsrfTokenManager;
import google.registry.util.Clock;
import google.registry.util.RegistryEnvironment;
import jakarta.inject.Inject;
import java.time.Instant;
@@ -67,11 +64,9 @@ public class LoadTestAction implements Runnable {
private static final int NUM_QUEUES = 10;
private static final int MAX_TASKS_PER_LOAD = 100;
private static final int ARBITRARY_VALID_HOST_LENGTH = 40;
private static final int MAX_CONTACT_LENGTH = 13;
private static final int MAX_DOMAIN_LABEL_LENGTH = 63;
private static final String EXISTING_DOMAIN = "testdomain";
private static final String EXISTING_CONTACT = "contact";
private static final String EXISTING_HOST = "ns1";
private static final Random random = new Random();
@@ -85,8 +80,8 @@ public class LoadTestAction implements Runnable {
/**
* The number of seconds to delay the execution of the first load testing tasks by. Preparatory
* work of creating independent contacts and hosts that will be used for later domain creation
* testing occurs during this period, so make sure that it is long enough.
* work of creating independent hosts that will be used for later domain creation testing occurs
* during this period, so make sure that it is long enough.
*/
@Inject
@Parameter("delaySeconds")
@@ -120,21 +115,6 @@ public class LoadTestAction implements Runnable {
@Parameter("domainChecks")
int domainChecksPerSecond;
/** The number of successful contact creates to enqueue per second over the length of the test. */
@Inject
@Parameter("successfulContactCreates")
int successfulContactCreatesPerSecond;
/** The number of failed contact creates to enqueue per second over the length of the test. */
@Inject
@Parameter("failedContactCreates")
int failedContactCreatesPerSecond;
/** The number of successful contact infos to enqueue per second over the length of the test. */
@Inject
@Parameter("contactInfos")
int contactInfosPerSecond;
/** The number of successful host creates to enqueue per second over the length of the test. */
@Inject
@Parameter("successfulHostCreates")
@@ -152,9 +132,8 @@ public class LoadTestAction implements Runnable {
@Inject CloudTasksUtils cloudTasksUtils;
private final String xmlContactCreateTmpl;
private final String xmlContactCreateFail;
private final String xmlContactInfo;
@Inject Clock clock;
private final String xmlDomainCheck;
private final String xmlDomainCreateTmpl;
private final String xmlDomainCreateFail;
@@ -163,53 +142,35 @@ public class LoadTestAction implements Runnable {
private final String xmlHostCreateFail;
private final String xmlHostInfo;
/**
* The XSRF token to be used for making requests to the epptool endpoint.
*
* <p>Note that the email address is set to empty, because the logged-in user hitting this
* endpoint will not be the same as when the tasks themselves fire and hit the epptool endpoint.
*/
private final String xsrfToken;
@Inject
LoadTestAction(@Parameter("tld") String tld, XsrfTokenManager xsrfTokenManager) {
xmlContactCreateTmpl = loadXml("contact_create");
xmlContactCreateFail = xmlContactCreateTmpl.replace("%contact%", EXISTING_CONTACT);
xmlContactInfo = loadXml("contact_info").replace("%contact%", EXISTING_CONTACT);
LoadTestAction(@Parameter("tld") String tld) {
xmlDomainCheck =
loadXml("domain_check").replace("%tld%", tld).replace("%domain%", EXISTING_DOMAIN);
xmlDomainCreateTmpl = loadXml("domain_create").replace("%tld%", tld);
xmlDomainCreateFail =
xmlDomainCreateTmpl
.replace("%domain%", EXISTING_DOMAIN)
.replace("%contact%", EXISTING_CONTACT)
.replace("%host%", EXISTING_HOST);
xmlDomainInfo =
loadXml("domain_info").replace("%tld%", tld).replace("%domain%", EXISTING_DOMAIN);
xmlHostCreateTmpl = loadXml("host_create");
xmlHostCreateFail = xmlHostCreateTmpl.replace("%host%", EXISTING_HOST);
xmlHostInfo = loadXml("host_info").replace("%host%", EXISTING_HOST);
xsrfToken = xsrfTokenManager.generateToken("");
}
@Override
public void run() {
validateAndLogRequest();
DateTime initialStartSecond = DateTime.now(UTC).plusSeconds(delaySeconds);
DateTime initialStartSecond = clock.nowUtc().plusSeconds(delaySeconds);
ImmutableList.Builder<String> preTaskXmls = new ImmutableList.Builder<>();
ImmutableList.Builder<String> contactNamesBuilder = new ImmutableList.Builder<>();
ImmutableList.Builder<String> hostPrefixesBuilder = new ImmutableList.Builder<>();
for (int i = 0; i < successfulDomainCreatesPerSecond; i++) {
String contactName = getRandomLabel(MAX_CONTACT_LENGTH);
String hostPrefix = getRandomLabel(ARBITRARY_VALID_HOST_LENGTH);
contactNamesBuilder.add(contactName);
hostPrefixesBuilder.add(hostPrefix);
preTaskXmls.add(
xmlContactCreateTmpl.replace("%contact%", contactName),
xmlHostCreateTmpl.replace("%host%", hostPrefix));
}
enqueue(createTasks(preTaskXmls.build(), DateTime.now(UTC)));
ImmutableList<String> contactNames = contactNamesBuilder.build();
enqueue(createTasks(preTaskXmls.build(), clock.nowUtc()));
ImmutableList<String> hostPrefixes = hostPrefixesBuilder.build();
ImmutableList.Builder<Task> tasks = new ImmutableList.Builder<>();
@@ -217,30 +178,17 @@ public class LoadTestAction implements Runnable {
DateTime startSecond = initialStartSecond.plusSeconds(offsetSeconds);
// The first "failed" creates might actually succeed if the object doesn't already exist, but
// that shouldn't affect the load numbers.
tasks.addAll(
createTasks(
createNumCopies(xmlContactCreateFail, failedContactCreatesPerSecond), startSecond));
tasks.addAll(
createTasks(createNumCopies(xmlHostCreateFail, failedHostCreatesPerSecond), startSecond));
tasks.addAll(
createTasks(
createNumCopies(xmlDomainCreateFail, failedDomainCreatesPerSecond), startSecond));
// We can do infos on the known existing objects.
tasks.addAll(
createTasks(createNumCopies(xmlContactInfo, contactInfosPerSecond), startSecond));
tasks.addAll(createTasks(createNumCopies(xmlHostInfo, hostInfosPerSecond), startSecond));
tasks.addAll(createTasks(createNumCopies(xmlDomainInfo, domainInfosPerSecond), startSecond));
// The domain check template uses "example.TLD" which won't exist, and one existing domain.
tasks.addAll(
createTasks(createNumCopies(xmlDomainCheck, domainChecksPerSecond), startSecond));
// Do successful creates on random names
tasks.addAll(
createTasks(
createNumCopies(xmlContactCreateTmpl, successfulContactCreatesPerSecond)
.stream()
.map(randomNameReplacer("%contact%", MAX_CONTACT_LENGTH))
.collect(toImmutableList()),
startSecond));
tasks.addAll(
createTasks(
createNumCopies(xmlHostCreateTmpl, successfulHostCreatesPerSecond)
@@ -253,7 +201,6 @@ public class LoadTestAction implements Runnable {
createNumCopies(xmlDomainCreateTmpl, successfulDomainCreatesPerSecond)
.stream()
.map(randomNameReplacer("%domain%", MAX_DOMAIN_LABEL_LENGTH))
.map(listNameReplacer("%contact%", contactNames))
.map(listNameReplacer("%host%", hostPrefixes))
.collect(toImmutableList()),
startSecond));
@@ -272,9 +219,6 @@ public class LoadTestAction implements Runnable {
|| failedDomainCreatesPerSecond > 0
|| domainInfosPerSecond > 0
|| domainChecksPerSecond > 0
|| successfulContactCreatesPerSecond > 0
|| failedContactCreatesPerSecond > 0
|| contactInfosPerSecond > 0
|| successfulHostCreatesPerSecond > 0
|| failedHostCreatesPerSecond > 0
|| hostInfosPerSecond > 0,
@@ -282,8 +226,7 @@ public class LoadTestAction implements Runnable {
logger.atInfo().log(
"Running load test with the following params. registrarId: %s, delaySeconds: %d, "
+ "runSeconds: %d, successful|failed domain creates/s: %d|%d, domain infos/s: %d, "
+ "domain checks/s: %d, successful|failed contact creates/s: %d|%d, "
+ "contact infos/s: %d, successful|failed host creates/s: %d|%d, host infos/s: %d.",
+ "domain checks/s: %d, successful|failed host creates/s: %d|%d, host infos/s: %d.",
registrarId,
delaySeconds,
runSeconds,
@@ -291,9 +234,6 @@ public class LoadTestAction implements Runnable {
failedDomainCreatesPerSecond,
domainInfosPerSecond,
domainChecksPerSecond,
successfulContactCreatesPerSecond,
failedContactCreatesPerSecond,
contactInfosPerSecond,
successfulHostCreatesPerSecond,
failedHostCreatesPerSecond,
hostInfosPerSecond);
@@ -303,10 +243,10 @@ public class LoadTestAction implements Runnable {
return readResourceUtf8(LoadTestAction.class, String.format("templates/%s.xml", name));
}
private List<String> createNumCopies(String xml, int numCopies) {
private ImmutableList<String> createNumCopies(String xml, int numCopies) {
String[] xmls = new String[numCopies];
Arrays.fill(xmls, xml);
return asList(xmls);
return ImmutableList.copyOf(xmls);
}
private Function<String, String> listNameReplacer(final String toReplace, List<String> choices) {
@@ -326,35 +266,27 @@ public class LoadTestAction implements Runnable {
return name.toString();
}
private List<Task> createTasks(List<String> xmls, DateTime start) {
private ImmutableList<Task> createTasks(ImmutableList<String> xmls, DateTime start) {
ImmutableList.Builder<Task> tasks = new ImmutableList.Builder<>();
for (int i = 0; i < xmls.size(); i++) {
// Space tasks evenly within across a second.
Instant scheduleTime =
Instant.ofEpochMilli(start.plusMillis((int) (1000.0 / xmls.size() * i)).getMillis());
tasks.add(
Task.newBuilder()
.setAppEngineHttpRequest(
cloudTasksUtils
.createTask(
EppToolAction.class,
Action.Method.POST,
ImmutableMultimap.of(
"clientId",
registrarId,
"superuser",
Boolean.FALSE.toString(),
"dryRun",
Boolean.FALSE.toString(),
"xml",
xmls.get(i)))
.toBuilder()
.getAppEngineHttpRequest()
.toBuilder()
// TODO: investigate if the following is necessary now that
// LegacyAuthenticationMechanism is gone.
.putHeaders(X_CSRF_TOKEN, xsrfToken)
.build())
cloudTasksUtils
.createTask(
EppToolAction.class,
Action.Method.POST,
ImmutableMultimap.of(
"clientId",
registrarId,
"superuser",
Boolean.FALSE.toString(),
"dryRun",
Boolean.FALSE.toString(),
"xml",
xmls.get(i)))
.toBuilder()
.setScheduleTime(
Timestamp.newBuilder()
.setSeconds(scheduleTime.getEpochSecond())
@@ -365,7 +297,7 @@ public class LoadTestAction implements Runnable {
return tasks.build();
}
private void enqueue(List<Task> tasks) {
private void enqueue(ImmutableList<Task> tasks) {
List<List<Task>> chunks = partition(tasks, MAX_TASKS_PER_LOAD);
// Farm out tasks to multiple queues to work around queue qps quotas.
for (int i = 0; i < chunks.size(); i++) {

View File

@@ -1,33 +0,0 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<create>
<contact:create
xmlns:contact="urn:ietf:params:xml:ns:contact-1.0">
<contact:id>%contact%</contact:id>
<contact:postalInfo type="int">
<contact:name>John Doe</contact:name>
<contact:org>Example Inc.</contact:org>
<contact:addr>
<contact:street>123 Example Dr.</contact:street>
<contact:street>Suite 100</contact:street>
<contact:city>Dulles</contact:city>
<contact:sp>VA</contact:sp>
<contact:pc>20166-6503</contact:pc>
<contact:cc>US</contact:cc>
</contact:addr>
</contact:postalInfo>
<contact:voice x="1234">+1.7035555555</contact:voice>
<contact:fax>+1.7035555556</contact:fax>
<contact:email>jdoe@example.com</contact:email>
<contact:authInfo>
<contact:pw>2fooBAR</contact:pw>
</contact:authInfo>
<contact:disclose flag="1">
<contact:voice/>
<contact:email/>
</contact:disclose>
</contact:create>
</create>
<clTRID>trid</clTRID>
</command>
</epp>

View File

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

View File

@@ -8,9 +8,6 @@
<domain:ns>
<domain:hostObj>%host%.example.com</domain:hostObj>
</domain:ns>
<domain:registrant>%contact%</domain:registrant>
<domain:contact type="admin">%contact%</domain:contact>
<domain:contact type="tech">%contact%</domain:contact>
<domain:authInfo>
<domain:pw>2fooBAR</domain:pw>
</domain:authInfo>

View File

@@ -4,6 +4,7 @@
<host:create
xmlns:host="urn:ietf:params:xml:ns:host-1.0">
<host:name>%host%.example.com</host:name>
<host:addr ip="v4">8.8.8.8</host:addr>
</host:create>
</create>
<clTRID>trid</clTRID>

View File

@@ -404,7 +404,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
* <p>Don't use this unless you really need it for performance reasons, and be sure that you are
* OK with the trade-offs in loss of transactional consistency.
*/
public static ImmutableMap<VKey<? extends EppResource>, EppResource> loadCached(
public static ImmutableMap<VKey<? extends EppResource>, EppResource> loadByCacheIfEnabled(
Iterable<VKey<? extends EppResource>> keys) {
if (!RegistryConfig.isEppResourceCachingEnabled()) {
return tm().reTransact(() -> tm().loadByKeys(keys));
@@ -413,15 +413,12 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
}
/**
* Loads a given EppResource by its key using the cache (if enabled).
* Loads a given EppResource by its key using the cache.
*
* <p>Don't use this unless you really need it for performance reasons, and be sure that you are
* OK with the trade-offs in loss of transactional consistency.
* <p>This method ignores the `isEppResourceCachingEnabled` config setting. It is reserved for use
* cases that can tolerate slightly stale data, e.g., RDAP queries.
*/
public static <T extends EppResource> T loadCached(VKey<T> key) {
if (!RegistryConfig.isEppResourceCachingEnabled()) {
return tm().reTransact(() -> tm().loadByKey(key));
}
public static <T extends EppResource> T loadByCache(VKey<T> key) {
// Safe to cast because loading a Key<T> returns an entity of type T.
@SuppressWarnings("unchecked")
T resource = (T) cacheEppResources.get(key);

View File

@@ -16,6 +16,7 @@ package google.registry.model;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static google.registry.util.DateTimeUtils.isAtOrAfter;
@@ -40,6 +41,7 @@ import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferStatus;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.TransactionManager;
import jakarta.persistence.Query;
import java.util.Collection;
import java.util.Comparator;
@@ -109,12 +111,12 @@ public final class EppResourceUtils {
*/
public static <T extends EppResource> Optional<T> loadByForeignKey(
Class<T> clazz, String foreignKey, DateTime now) {
return loadByForeignKeyHelper(clazz, foreignKey, now, false);
return loadByForeignKeyHelper(tm(), clazz, foreignKey, now, false);
}
/**
* Loads the last created version of an {@link EppResource} from the database by foreign key,
* using a cache.
* using a cache, if caching is enabled in config settings.
*
* <p>Returns null if no resource with this foreign key was ever created, or if the most recently
* created resource was deleted before time "now".
@@ -134,20 +136,36 @@ public final class EppResourceUtils {
* @param foreignKey id to match
* @param now the current logical time to project resources at
*/
public static <T extends EppResource> Optional<T> loadByForeignKeyCached(
public static <T extends EppResource> Optional<T> loadByForeignKeyByCacheIfEnabled(
Class<T> clazz, String foreignKey, DateTime now) {
return loadByForeignKeyHelper(
clazz, foreignKey, now, RegistryConfig.isEppResourceCachingEnabled());
tm(), clazz, foreignKey, now, RegistryConfig.isEppResourceCachingEnabled());
}
/**
* Loads the last created version of an {@link EppResource} from the replica database by foreign
* key, using a cache.
*
* <p>This method ignores the config setting for caching, and is reserved for use cases that can
* tolerate slightly stale data.
*/
public static <T extends EppResource> Optional<T> loadByForeignKeyByCache(
Class<T> clazz, String foreignKey, DateTime now) {
return loadByForeignKeyHelper(replicaTm(), clazz, foreignKey, now, true);
}
private static <T extends EppResource> Optional<T> loadByForeignKeyHelper(
Class<T> clazz, String foreignKey, DateTime now, boolean useCache) {
TransactionManager txnManager,
Class<T> clazz,
String foreignKey,
DateTime now,
boolean useCache) {
checkArgument(
ForeignKeyedEppResource.class.isAssignableFrom(clazz),
"loadByForeignKey may only be called for foreign keyed EPP resources");
VKey<T> key =
useCache
? ForeignKeyUtils.loadCached(clazz, ImmutableList.of(foreignKey), now).get(foreignKey)
? ForeignKeyUtils.loadByCache(clazz, ImmutableList.of(foreignKey), now).get(foreignKey)
: ForeignKeyUtils.load(clazz, foreignKey, now);
// The returned key is null if the resource is hard deleted or soft deleted by the given time.
if (key == null) {
@@ -155,10 +173,10 @@ public final class EppResourceUtils {
}
T resource =
useCache
? EppResource.loadCached(key)
? EppResource.loadByCache(key)
// This transaction is buried very deeply inside many outer nested calls, hence merits
// the use of reTransact() for now pending a substantial refactoring.
: tm().reTransact(() -> tm().loadByKeyIfPresent(key).orElse(null));
: txnManager.reTransact(() -> txnManager.loadByKeyIfPresent(key).orElse(null));
if (resource == null || isAtOrAfter(now, resource.getDeletionTime())) {
return Optional.empty();
}

View File

@@ -204,11 +204,25 @@ public final class ForeignKeyUtils {
* <p>Don't use the cached version of this method unless you really need it for performance
* reasons, and are OK with the trade-offs in loss of transactional consistency.
*/
public static <E extends EppResource> ImmutableMap<String, VKey<E>> loadCached(
public static <E extends EppResource> ImmutableMap<String, VKey<E>> loadByCacheIfEnabled(
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
if (!RegistryConfig.isEppResourceCachingEnabled()) {
return load(clazz, foreignKeys, now);
}
return loadByCache(clazz, foreignKeys, now);
}
/**
* Load a list of {@link VKey} to {@link EppResource} instances by class and foreign key strings
* that are active at or after the specified moment in time, using the cache.
*
* <p>The returned map will omit any keys for which the {@link EppResource} doesn't exist or has
* been soft-deleted.
*
* <p>This method is reserved for use cases that can tolerate slightly stale data.
*/
public static <E extends EppResource> ImmutableMap<String, VKey<E>> loadByCache(
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
return foreignKeyCache
.getAll(foreignKeys.stream().map(fk -> VKey.create(clazz, fk)).collect(toImmutableList()))
.entrySet()

View File

@@ -62,11 +62,31 @@ public class FeatureFlag extends ImmutableObject implements Buildable {
INACTIVE
}
/** The names of the feature flags that can be individually set. */
public enum FeatureName {
TEST_FEATURE,
MINIMUM_DATASET_CONTACTS_OPTIONAL,
MINIMUM_DATASET_CONTACTS_PROHIBITED,
INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS
/** Feature flag name used for testing only. */
TEST_FEATURE(FeatureStatus.INACTIVE),
/** If we're not requiring the presence of contact data on domain EPP commands. */
MINIMUM_DATASET_CONTACTS_OPTIONAL(FeatureStatus.INACTIVE),
/** If we're not permitting the presence of contact data on any EPP commands. */
MINIMUM_DATASET_CONTACTS_PROHIBITED(FeatureStatus.INACTIVE),
/**
* If we're including the upcoming domain drop date in the exported list of registered domains.
*/
INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS(FeatureStatus.INACTIVE);
private final FeatureStatus defaultStatus;
FeatureName(FeatureStatus defaultStatus) {
this.defaultStatus = defaultStatus;
}
FeatureStatus getDefaultStatus() {
return this.defaultStatus;
}
}
/** The name of the flag/feature. */
@@ -155,24 +175,24 @@ public class FeatureFlag extends ImmutableObject implements Buildable {
return status.getValueAtTime(time);
}
/** Returns if the flag is active, or the default value if the flag does not exist. */
public static boolean isActiveNowOrElse(FeatureName featureName, boolean defaultValue) {
tm().assertInTransaction();
return CACHE
.get(featureName)
.map(flag -> flag.getStatus(tm().getTransactionTime()).equals(ACTIVE))
.orElse(defaultValue);
}
/** Returns if the FeatureFlag with the given FeatureName is active now. */
/**
* Returns whether the flag is active now, or else the flag's default value if it doesn't exist.
*/
public static boolean isActiveNow(FeatureName featureName) {
tm().assertInTransaction();
return isActiveAt(featureName, tm().getTransactionTime());
}
/** Returns if the FeatureFlag with the given FeatureName is active at a given time. */
/**
* Returns whether the flag is active at the given time, or else the flag's default value if it
* doesn't exist.
*/
public static boolean isActiveAt(FeatureName featureName, DateTime dateTime) {
return FeatureFlag.get(featureName).getStatus(dateTime).equals(ACTIVE);
tm().assertInTransaction();
return CACHE
.get(featureName)
.map(flag -> flag.getStatus(dateTime).equals(ACTIVE))
.orElse(featureName.getDefaultStatus().equals(ACTIVE));
}
@Override

View File

@@ -16,6 +16,8 @@ package google.registry.model.console;
/** Permissions that users may have in the UI, either per-registrar or globally. */
public enum ConsolePermission {
AUDIT_ACTIVITY_BY_USER,
AUDIT_ACTIVITY_BY_REGISTRAR,
/** View basic information about a registrar. */
VIEW_REGISTRAR_DETAILS,
/** Edit basic information about a registrar. */

View File

@@ -55,6 +55,8 @@ public class ConsoleRoleDefinitions {
new ImmutableSet.Builder<ConsolePermission>()
.addAll(SUPPORT_AGENT_PERMISSIONS)
.add(
ConsolePermission.AUDIT_ACTIVITY_BY_USER,
ConsolePermission.AUDIT_ACTIVITY_BY_REGISTRAR,
ConsolePermission.MANAGE_REGISTRARS,
ConsolePermission.GET_REGISTRANT_EMAIL,
ConsolePermission.SUSPEND_DOMAIN,
@@ -111,6 +113,7 @@ public class ConsoleRoleDefinitions {
new ImmutableSet.Builder<ConsolePermission>()
.addAll(TECH_CONTACT_PERMISSIONS)
.add(ConsolePermission.MANAGE_USERS)
.add(ConsolePermission.AUDIT_ACTIVITY_BY_REGISTRAR)
.build();
private ConsoleRoleDefinitions() {}

View File

@@ -16,6 +16,7 @@ package google.registry.model.console;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.gson.annotations.Expose;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.IdAllocation;
@@ -45,6 +46,7 @@ public class ConsoleUpdateHistory extends ImmutableObject implements Buildable {
@Id @IdAllocation @Column Long revisionId;
@Column(nullable = false)
@Expose
DateTime modificationTime;
/** The HTTP method (e.g. POST, PUT) used to make this modification. */
@@ -54,6 +56,7 @@ public class ConsoleUpdateHistory extends ImmutableObject implements Buildable {
/** The type of modification. */
@Column(nullable = false)
@Enumerated(EnumType.STRING)
@Expose
Type type;
/** The URL of the action that was used to make the modification. */
@@ -61,11 +64,12 @@ public class ConsoleUpdateHistory extends ImmutableObject implements Buildable {
String url;
/** An optional further description of the action. */
String description;
@Expose String description;
/** The user that performed the modification. */
@JoinColumn(name = "actingUser", referencedColumnName = "emailAddress", nullable = false)
@ManyToOne
@Expose
User actingUser;
public Long getRevisionId() {
@@ -102,18 +106,24 @@ public class ConsoleUpdateHistory extends ImmutableObject implements Buildable {
}
public enum Type {
DUM_DOWNLOAD,
DOMAIN_DELETE,
DOMAIN_SUSPEND,
DOMAIN_UNSUSPEND,
EPP_PASSWORD_UPDATE,
REGISTRAR_CREATE,
REGISTRAR_CONTACTS_UPDATE,
REGISTRAR_SECURITY_UPDATE,
REGISTRAR_UPDATE,
REGISTRY_LOCK,
REGISTRY_UNLOCK,
USER_CREATE,
USER_DELETE,
USER_UPDATE
USER_UPDATE,
}
public static final String DESCRIPTION_SEPARATOR = "|";
public static class Builder extends Buildable.Builder<ConsoleUpdateHistory> {
public Builder() {}

View File

@@ -0,0 +1,150 @@
// Copyright 2025 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.console;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import google.registry.model.Buildable;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.ImmutableObject;
import google.registry.persistence.WithVKey;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import java.util.Optional;
import java.util.UUID;
import org.joda.time.DateTime;
/**
* Represents a password reset request of some type.
*
* <p>Password reset requests must be performed within an hour of the time that they were requested,
* as well as requiring that the requester and the fulfiller have the proper respective permissions.
*/
@Entity
@WithVKey(String.class)
public class PasswordResetRequest extends ImmutableObject implements Buildable {
public enum Type {
EPP,
REGISTRY_LOCK
}
@Id private String verificationCode;
@Column(nullable = false)
@Enumerated(EnumType.STRING)
Type type;
@AttributeOverrides({
@AttributeOverride(
name = "creationTime",
column = @Column(name = "requestTime", nullable = false))
})
CreateAutoTimestamp requestTime = CreateAutoTimestamp.create(null);
@Column(nullable = false)
String requester;
@Column DateTime fulfillmentTime;
@Column(nullable = false)
String destinationEmail;
@Column(nullable = false)
String registrarId;
public String getVerificationCode() {
return verificationCode;
}
public Type getType() {
return type;
}
public DateTime getRequestTime() {
return requestTime.getTimestamp();
}
public String getRequester() {
return requester;
}
public Optional<DateTime> getFulfillmentTime() {
return Optional.ofNullable(fulfillmentTime);
}
public String getDestinationEmail() {
return destinationEmail;
}
public String getRegistrarId() {
return registrarId;
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** Builder for constructing immutable {@link PasswordResetRequest} objects. */
public static class Builder extends Buildable.Builder<PasswordResetRequest> {
public Builder() {}
private Builder(PasswordResetRequest instance) {
super(instance);
}
@Override
public PasswordResetRequest build() {
checkArgumentNotNull(getInstance().type, "Type must be specified");
checkArgumentNotNull(getInstance().requester, "Requester must be specified");
checkArgumentNotNull(getInstance().destinationEmail, "Destination email must be specified");
checkArgumentNotNull(getInstance().registrarId, "Registrar ID must be specified");
getInstance().verificationCode = UUID.randomUUID().toString();
return super.build();
}
public Builder setType(Type type) {
getInstance().type = type;
return this;
}
public Builder setRequester(String requester) {
getInstance().requester = requester;
return this;
}
public Builder setDestinationEmail(String destinationEmail) {
getInstance().destinationEmail = destinationEmail;
return this;
}
public Builder setRegistrarId(String registrarId) {
getInstance().registrarId = registrarId;
return this;
}
public Builder setFulfillmentTime(DateTime fulfillmentTime) {
getInstance().fulfillmentTime = fulfillmentTime;
return this;
}
}
}

View File

@@ -62,7 +62,7 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
@Id @Expose String emailAddress;
/** Optional external email address to use for registry lock confirmation emails. */
@Column String registryLockEmailAddress;
@Column @Expose String registryLockEmailAddress;
/** Roles (which grant permissions) associated with this user. */
@Expose
@@ -250,51 +250,50 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
}
@Override
public Builder<? extends User, ?> asBuilder() {
return new Builder<>(clone(this));
public Builder asBuilder() {
return new Builder(clone(this));
}
/** Builder for constructing immutable {@link User} objects. */
public static class Builder<T extends User, B extends Builder<T, B>>
extends GenericBuilder<T, B> {
public static class Builder extends Buildable.Builder<User> {
public Builder() {}
public Builder(T abstractUser) {
super(abstractUser);
public Builder(User user) {
super(user);
}
@Override
public T build() {
public User build() {
checkArgumentNotNull(getInstance().emailAddress, "Email address cannot be null");
checkArgumentNotNull(getInstance().userRoles, "User roles cannot be null");
return super.build();
}
public B setEmailAddress(String emailAddress) {
public Builder setEmailAddress(String emailAddress) {
getInstance().emailAddress = checkValidEmail(emailAddress);
return thisCastToDerived();
return this;
}
public B setRegistryLockEmailAddress(@Nullable String registryLockEmailAddress) {
public Builder setRegistryLockEmailAddress(@Nullable String registryLockEmailAddress) {
getInstance().registryLockEmailAddress =
registryLockEmailAddress == null ? null : checkValidEmail(registryLockEmailAddress);
return thisCastToDerived();
return this;
}
public B setUserRoles(UserRoles userRoles) {
public Builder setUserRoles(UserRoles userRoles) {
checkArgumentNotNull(userRoles, "User roles cannot be null");
getInstance().userRoles = userRoles;
return thisCastToDerived();
return this;
}
public B removeRegistryLockPassword() {
public Builder removeRegistryLockPassword() {
getInstance().registryLockPasswordHash = null;
getInstance().registryLockPasswordSalt = null;
return thisCastToDerived();
return this;
}
public B setRegistryLockPassword(String registryLockPassword) {
public Builder setRegistryLockPassword(String registryLockPassword) {
checkArgument(
getInstance().hasAnyRegistryLockPermission(), "User has no registry lock permission");
checkArgument(
@@ -304,7 +303,7 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
byte[] salt = SALT_SUPPLIER.get();
getInstance().registryLockPasswordSalt = base64().encode(salt);
getInstance().registryLockPasswordHash = hashPassword(registryLockPassword, salt);
return thisCastToDerived();
return this;
}
}
}

View File

@@ -441,7 +441,8 @@ public class DomainCommand {
private static <T extends EppResource> ImmutableMap<String, VKey<T>> loadByForeignKeysCached(
final Set<String> foreignKeys, final Class<T> clazz, final DateTime now)
throws InvalidReferencesException {
ImmutableMap<String, VKey<T>> fks = ForeignKeyUtils.loadCached(clazz, foreignKeys, now);
ImmutableMap<String, VKey<T>> fks =
ForeignKeyUtils.loadByCacheIfEnabled(clazz, foreignKeys, now);
if (!fks.keySet().equals(foreignKeys)) {
throw new InvalidReferencesException(
clazz, ImmutableSet.copyOf(difference(foreignKeys, fks.keySet())));

View File

@@ -18,7 +18,7 @@ import static google.registry.util.CollectionUtils.forceEmptyToNull;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
import google.registry.model.Buildable.GenericBuilder;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.domain.Period;
import google.registry.model.domain.fee.Fee;
@@ -77,8 +77,7 @@ public class FeeCheckResponseExtensionItemCommandV12 extends ImmutableObject {
}
/** Builder for {@link FeeCheckResponseExtensionItemCommandV12}. */
public static class Builder
extends GenericBuilder<FeeCheckResponseExtensionItemCommandV12, Builder> {
public static class Builder extends Buildable.Builder<FeeCheckResponseExtensionItemCommandV12> {
public Builder setCommandName(CommandName commandName) {
getInstance().commandName = Ascii.toLowerCase(commandName.name());

View File

@@ -27,6 +27,7 @@ import static com.google.common.io.BaseEncoding.base64;
import static google.registry.config.RegistryConfig.getDefaultRegistrarWhoisServer;
import static google.registry.model.CacheUtils.memoizeWithShortExpiration;
import static google.registry.model.tld.Tlds.assertTldsExist;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableSortedCopy;
@@ -62,6 +63,7 @@ import google.registry.model.tld.Tld.TldType;
import google.registry.persistence.VKey;
import google.registry.persistence.converter.CidrBlockListUserType;
import google.registry.persistence.converter.CurrencyToStringMapUserType;
import google.registry.persistence.transaction.TransactionManager;
import google.registry.util.CidrAddressBlock;
import google.registry.util.PasswordUtils;
import jakarta.mail.internet.AddressException;
@@ -403,6 +405,9 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
*/
DateTime lastExpiringFailoverCertNotificationSentDate = START_OF_TIME;
/** The time that the POCs have been reviewed last. */
@Expose DateTime lastPocVerificationDate = START_OF_TIME;
/** Telephone support passcode (5-digit numeric) */
String phonePasscode;
@@ -461,6 +466,10 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
return registrarName;
}
public DateTime getLastPocVerificationDate() {
return lastPocVerificationDate;
}
public Type getType() {
return type;
}
@@ -569,7 +578,20 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
* address.
*/
public ImmutableSortedSet<RegistrarPoc> getContacts() {
return getContactPocs().stream()
return getPocs(tm()).stream()
.filter(Objects::nonNull)
.collect(toImmutableSortedSet(CONTACT_EMAIL_COMPARATOR));
}
/**
* Returns a list of all {@link RegistrarPoc} objects for this registrar sorted by their email
* address.
*
* <p>This method queries the replica database. It is reserved for use cases that can tolerate
* slightly stale data.
*/
public ImmutableSortedSet<RegistrarPoc> getPocsFromReplica() {
return getPocs(replicaTm()).stream()
.filter(Objects::nonNull)
.collect(toImmutableSortedSet(CONTACT_EMAIL_COMPARATOR));
}
@@ -578,8 +600,8 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
* Returns a list of {@link RegistrarPoc} objects of a given type for this registrar sorted by
* their email address.
*/
public ImmutableSortedSet<RegistrarPoc> getContactsOfType(final RegistrarPoc.Type type) {
return getContactPocs().stream()
public ImmutableSortedSet<RegistrarPoc> getPocsOfType(final RegistrarPoc.Type type) {
return getPocs(tm()).stream()
.filter(Objects::nonNull)
.filter((@Nullable RegistrarPoc contact) -> contact.getTypes().contains(type))
.collect(toImmutableSortedSet(CONTACT_EMAIL_COMPARATOR));
@@ -593,13 +615,8 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
return getContacts().stream().filter(RegistrarPoc::getVisibleInDomainWhoisAsAbuse).findFirst();
}
private ImmutableSet<RegistrarPoc> getContactPocs() {
return tm().transact(
() ->
tm().query("FROM RegistrarPoc WHERE registrarId = :registrarId", RegistrarPoc.class)
.setParameter("registrarId", registrarId)
.getResultStream()
.collect(toImmutableSet()));
private ImmutableList<RegistrarPoc> getPocs(TransactionManager txnManager) {
return txnManager.transact(() -> RegistrarPoc.loadForRegistrar(registrarId));
}
@Override
@@ -614,6 +631,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
.putString(
"lastExpiringFailoverCertNotificationSentDate",
lastExpiringFailoverCertNotificationSentDate)
.putString("lastPocVerificationDate", lastPocVerificationDate)
.put("registrarName", registrarName)
.put("type", type)
.put("state", state)
@@ -672,8 +690,8 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
}
@Override
public Builder<? extends Registrar, ?> asBuilder() {
return new Builder<>(clone(this));
public Builder asBuilder() {
return new Builder(clone(this));
}
@Override
@@ -688,59 +706,58 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
}
/** A builder for constructing {@link Registrar}, since it is immutable. */
public static class Builder<T extends Registrar, B extends Builder<T, B>>
extends GenericBuilder<T, B> {
public static class Builder extends Buildable.Builder<Registrar> {
public Builder() {}
public Builder(T instance) {
public Builder(Registrar instance) {
super(instance);
}
public B setRegistrarId(String registrarId) {
public Builder setRegistrarId(String registrarId) {
// Registrar id must be [3,16] chars long. See "clIDType" in the base EPP schema of RFC 5730.
// (Need to validate this here as there's no matching EPP XSD for validation.)
checkArgument(
Range.closed(3, 16).contains(registrarId.length()),
"Registrar ID must be 3-16 characters long.");
getInstance().registrarId = registrarId;
return thisCastToDerived();
return this;
}
public B setIanaIdentifier(@Nullable Long ianaIdentifier) {
public Builder setIanaIdentifier(@Nullable Long ianaIdentifier) {
checkArgument(
ianaIdentifier == null || ianaIdentifier > 0, "IANA ID must be a positive number");
getInstance().ianaIdentifier = ianaIdentifier;
return thisCastToDerived();
return this;
}
public B setPoNumber(Optional<String> poNumber) {
public Builder setPoNumber(Optional<String> poNumber) {
getInstance().poNumber = poNumber.orElse(null);
return thisCastToDerived();
return this;
}
public B setBillingAccountMap(@Nullable Map<CurrencyUnit, String> billingAccountMap) {
public Builder setBillingAccountMap(@Nullable Map<CurrencyUnit, String> billingAccountMap) {
getInstance().billingAccountMap = nullToEmptyImmutableCopy(billingAccountMap);
return thisCastToDerived();
return this;
}
public B setRegistrarName(String registrarName) {
public Builder setRegistrarName(String registrarName) {
getInstance().registrarName = registrarName;
return thisCastToDerived();
return this;
}
public B setType(Type type) {
public Builder setType(Type type) {
getInstance().type = type;
return thisCastToDerived();
return this;
}
public B setState(State state) {
public Builder setState(State state) {
getInstance().state = state;
return thisCastToDerived();
return this;
}
public B setAllowedTlds(Set<String> allowedTlds) {
public Builder setAllowedTlds(Set<String> allowedTlds) {
getInstance().allowedTlds = ImmutableSortedSet.copyOf(assertTldsExist(allowedTlds));
return thisCastToDerived();
return this;
}
/**
@@ -753,7 +770,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
* {@code .now()} when saving the Registry entity to make sure it's actually saved before trying
* to set the allowed TLDs.
*/
public B setAllowedTldsUncached(Set<String> allowedTlds) {
public Builder setAllowedTldsUncached(Set<String> allowedTlds) {
ImmutableSet<VKey<Tld>> newTldKeys =
Sets.difference(allowedTlds, getInstance().getAllowedTlds()).stream()
.map(Tld::createVKey)
@@ -762,10 +779,10 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
Sets.difference(newTldKeys, tm().loadByKeysIfPresent(newTldKeys).keySet());
checkArgument(missingTldKeys.isEmpty(), "Trying to set nonexistent TLDs: %s", missingTldKeys);
getInstance().allowedTlds = ImmutableSortedSet.copyOf(allowedTlds);
return thisCastToDerived();
return this;
}
public B setClientCertificate(String clientCertificate, DateTime now) {
public Builder setClientCertificate(String clientCertificate, DateTime now) {
clientCertificate = emptyToNull(clientCertificate);
String clientCertificateHash = calculateHash(clientCertificate);
if (!Objects.equals(clientCertificate, getInstance().clientCertificate)
@@ -774,23 +791,23 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
getInstance().clientCertificateHash = clientCertificateHash;
getInstance().lastCertificateUpdateTime = now;
}
return thisCastToDerived();
return this;
}
public B setLastExpiringCertNotificationSentDate(DateTime now) {
public Builder setLastExpiringCertNotificationSentDate(DateTime now) {
checkArgumentNotNull(now, "Registrar lastExpiringCertNotificationSentDate cannot be null");
getInstance().lastExpiringCertNotificationSentDate = now;
return thisCastToDerived();
return this;
}
public B setLastExpiringFailoverCertNotificationSentDate(DateTime now) {
public Builder setLastExpiringFailoverCertNotificationSentDate(DateTime now) {
checkArgumentNotNull(
now, "Registrar lastExpiringFailoverCertNotificationSentDate cannot be null");
getInstance().lastExpiringFailoverCertNotificationSentDate = now;
return thisCastToDerived();
return this;
}
public B setFailoverClientCertificate(String clientCertificate, DateTime now) {
public Builder setFailoverClientCertificate(String clientCertificate, DateTime now) {
clientCertificate = emptyToNull(clientCertificate);
String clientCertificateHash = calculateHash(clientCertificate);
if (!Objects.equals(clientCertificate, getInstance().failoverClientCertificate)
@@ -799,7 +816,13 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
getInstance().failoverClientCertificateHash = clientCertificateHash;
getInstance().lastCertificateUpdateTime = now;
}
return thisCastToDerived();
return this;
}
public Builder setLastPocVerificationDate(DateTime now) {
checkArgumentNotNull(now, "Registrar lastPocVerificationDate cannot be null");
getInstance().lastPocVerificationDate = now;
return this;
}
private static String calculateHash(String clientCertificate) {
@@ -831,75 +854,75 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
Objects.equals(newInstance.ianaIdentifier, registrar.getIanaIdentifier()));
}
public B setContactsRequireSyncing(boolean contactsRequireSyncing) {
public Builder setContactsRequireSyncing(boolean contactsRequireSyncing) {
getInstance().contactsRequireSyncing = contactsRequireSyncing;
return thisCastToDerived();
return this;
}
public B setIpAddressAllowList(Iterable<CidrAddressBlock> ipAddressAllowList) {
public Builder setIpAddressAllowList(Iterable<CidrAddressBlock> ipAddressAllowList) {
getInstance().ipAddressAllowList = ImmutableList.copyOf(ipAddressAllowList);
return thisCastToDerived();
return this;
}
public B setLocalizedAddress(RegistrarAddress localizedAddress) {
public Builder setLocalizedAddress(RegistrarAddress localizedAddress) {
getInstance().localizedAddress = localizedAddress;
return thisCastToDerived();
return this;
}
public B setInternationalizedAddress(RegistrarAddress internationalizedAddress) {
public Builder setInternationalizedAddress(RegistrarAddress internationalizedAddress) {
getInstance().internationalizedAddress = internationalizedAddress;
return thisCastToDerived();
return this;
}
public B setPhoneNumber(String phoneNumber) {
public Builder setPhoneNumber(String phoneNumber) {
getInstance().phoneNumber = (phoneNumber == null) ? null : checkValidPhoneNumber(phoneNumber);
return thisCastToDerived();
return this;
}
public B setFaxNumber(String faxNumber) {
public Builder setFaxNumber(String faxNumber) {
getInstance().faxNumber = (faxNumber == null) ? null : checkValidPhoneNumber(faxNumber);
return thisCastToDerived();
return this;
}
public B setEmailAddress(String emailAddress) {
public Builder setEmailAddress(String emailAddress) {
getInstance().emailAddress = checkValidEmail(emailAddress);
return thisCastToDerived();
return this;
}
public B setWhoisServer(String whoisServer) {
public Builder setWhoisServer(String whoisServer) {
getInstance().whoisServer = whoisServer;
return thisCastToDerived();
return this;
}
public B setRdapBaseUrls(Set<String> rdapBaseUrls) {
public Builder setRdapBaseUrls(Set<String> rdapBaseUrls) {
getInstance().rdapBaseUrls = ImmutableSet.copyOf(rdapBaseUrls);
return thisCastToDerived();
return this;
}
public B setBlockPremiumNames(boolean blockPremiumNames) {
public Builder setBlockPremiumNames(boolean blockPremiumNames) {
getInstance().blockPremiumNames = blockPremiumNames;
return thisCastToDerived();
return this;
}
public B setUrl(String url) {
public Builder setUrl(String url) {
getInstance().url = url;
return thisCastToDerived();
return this;
}
public B setIcannReferralEmail(String icannReferralEmail) {
public Builder setIcannReferralEmail(String icannReferralEmail) {
getInstance().icannReferralEmail = checkValidEmail(icannReferralEmail);
return thisCastToDerived();
return this;
}
public B setDriveFolderId(@Nullable String driveFolderId) {
public Builder setDriveFolderId(@Nullable String driveFolderId) {
checkArgument(
driveFolderId == null || !driveFolderId.contains("/"),
"Drive folder ID must not be a full URL");
getInstance().driveFolderId = driveFolderId;
return thisCastToDerived();
return this;
}
public B setPassword(String password) {
public Builder setPassword(String password) {
// Passwords must be [6,16] chars long. See "pwType" in the base EPP schema of RFC 5730.
checkArgument(
Range.closed(6, 16).contains(nullToEmpty(password).length()),
@@ -907,7 +930,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
byte[] salt = SALT_SUPPLIER.get();
getInstance().salt = base64().encode(salt);
getInstance().passwordHash = hashPassword(password, salt);
return thisCastToDerived();
return this;
}
/**
@@ -915,18 +938,18 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
*
* @throws IllegalArgumentException if provided passcode is not 5-digit numeric
*/
public B setPhonePasscode(String phonePasscode) {
public Builder setPhonePasscode(String phonePasscode) {
checkArgument(
phonePasscode == null || PHONE_PASSCODE_PATTERN.matcher(phonePasscode).matches(),
"Not a valid telephone passcode (must be 5 digits long): %s",
phonePasscode);
getInstance().phonePasscode = phonePasscode;
return thisCastToDerived();
return this;
}
public B setRegistryLockAllowed(boolean registryLockAllowed) {
public Builder setRegistryLockAllowed(boolean registryLockAllowed) {
getInstance().registryLockAllowed = registryLockAllowed;
return thisCastToDerived();
return this;
}
/**
@@ -934,14 +957,14 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
* and breaks the verification that an object has not been updated since it was copied.
*/
@VisibleForTesting
public B setLastUpdateTime(DateTime timestamp) {
public Builder setLastUpdateTime(DateTime timestamp) {
getInstance().setUpdateTimestamp(UpdateAutoTimestamp.create(timestamp));
return thisCastToDerived();
return this;
}
/** Build the registrar, nullifying empty fields. */
@Override
public T build() {
public Registrar build() {
checkArgumentNotNull(getInstance().type, "Registrar type cannot be null");
checkArgumentNotNull(getInstance().registrarName, "Registrar name cannot be null");
checkArgument(

View File

@@ -27,15 +27,17 @@ import static google.registry.util.PasswordUtils.hashPassword;
import static java.util.stream.Collectors.joining;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.gson.annotations.Expose;
import google.registry.model.Buildable.GenericBuilder;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.JsonMapBuilder;
import google.registry.model.Jsonifiable;
import google.registry.model.UnsafeSerializable;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.QueryComposer;
import google.registry.util.PasswordUtils;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
@@ -93,6 +95,10 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
}
}
@Expose
@Column(insertable = false, updatable = false)
protected Long id;
/** The name of the contact. */
@Expose String name;
@@ -179,6 +185,10 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
tm().putAll(contacts);
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
@@ -215,8 +225,8 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
return visibleInDomainWhoisAsAbuse;
}
public Builder<? extends RegistrarPoc, ?> asBuilder() {
return new Builder<>(clone(this));
public Builder asBuilder() {
return new Builder(clone(this));
}
public boolean isAllowedToSetRegistryLockPassword() {
@@ -295,6 +305,7 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
.put("visibleInDomainWhoisAsAbuse", visibleInDomainWhoisAsAbuse)
.put("allowedToSetRegistryLockPassword", allowedToSetRegistryLockPassword)
.put("registryLockAllowed", isRegistryLockAllowed())
.put("id", getId())
.build();
}
@@ -321,17 +332,16 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
}
/** A builder for constructing a {@link RegistrarPoc}, since it is immutable. */
public static class Builder<T extends RegistrarPoc, B extends Builder<T, B>>
extends GenericBuilder<T, B> {
public static class Builder extends Buildable.Builder<RegistrarPoc> {
public Builder() {}
protected Builder(T instance) {
protected Builder(RegistrarPoc instance) {
super(instance);
}
/** Build the registrar, nullifying empty fields. */
@Override
public T build() {
public RegistrarPoc build() {
checkNotNull(getInstance().registrarId, "Registrar ID cannot be null");
checkValidEmail(getInstance().emailAddress);
// Check allowedToSetRegistryLockPassword here because if we want to allow the user to set
@@ -345,71 +355,71 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
return cloneEmptyToNull(super.build());
}
public B setName(String name) {
public Builder setName(String name) {
getInstance().name = name;
return thisCastToDerived();
return this;
}
public B setEmailAddress(String emailAddress) {
public Builder setEmailAddress(String emailAddress) {
getInstance().emailAddress = emailAddress;
return thisCastToDerived();
return this;
}
public B setRegistryLockEmailAddress(@Nullable String registryLockEmailAddress) {
public Builder setRegistryLockEmailAddress(@Nullable String registryLockEmailAddress) {
getInstance().registryLockEmailAddress = registryLockEmailAddress;
return thisCastToDerived();
return this;
}
public B setPhoneNumber(String phoneNumber) {
public Builder setPhoneNumber(String phoneNumber) {
getInstance().phoneNumber = phoneNumber;
return thisCastToDerived();
return this;
}
public B setRegistrarId(String registrarId) {
public Builder setRegistrarId(String registrarId) {
getInstance().registrarId = registrarId;
return thisCastToDerived();
return this;
}
public B setRegistrar(Registrar registrar) {
public Builder setRegistrar(Registrar registrar) {
getInstance().registrarId = registrar.getRegistrarId();
return thisCastToDerived();
return this;
}
public B setFaxNumber(String faxNumber) {
public Builder setFaxNumber(String faxNumber) {
getInstance().faxNumber = faxNumber;
return thisCastToDerived();
return this;
}
public B setTypes(Iterable<Type> types) {
public Builder setTypes(Iterable<Type> types) {
getInstance().types = ImmutableSet.copyOf(types);
return thisCastToDerived();
return this;
}
public B setVisibleInWhoisAsAdmin(boolean visible) {
public Builder setVisibleInWhoisAsAdmin(boolean visible) {
getInstance().visibleInWhoisAsAdmin = visible;
return thisCastToDerived();
return this;
}
public B setVisibleInWhoisAsTech(boolean visible) {
public Builder setVisibleInWhoisAsTech(boolean visible) {
getInstance().visibleInWhoisAsTech = visible;
return thisCastToDerived();
return this;
}
public B setVisibleInDomainWhoisAsAbuse(boolean visible) {
public Builder setVisibleInDomainWhoisAsAbuse(boolean visible) {
getInstance().visibleInDomainWhoisAsAbuse = visible;
return thisCastToDerived();
return this;
}
public B setAllowedToSetRegistryLockPassword(boolean allowedToSetRegistryLockPassword) {
public Builder setAllowedToSetRegistryLockPassword(boolean allowedToSetRegistryLockPassword) {
if (allowedToSetRegistryLockPassword) {
getInstance().registryLockPasswordSalt = null;
getInstance().registryLockPasswordHash = null;
}
getInstance().allowedToSetRegistryLockPassword = allowedToSetRegistryLockPassword;
return thisCastToDerived();
return this;
}
public B setRegistryLockPassword(String registryLockPassword) {
public Builder setRegistryLockPassword(String registryLockPassword) {
checkArgument(
getInstance().allowedToSetRegistryLockPassword,
"Not allowed to set registry lock password for this contact");
@@ -419,10 +429,16 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
getInstance().registryLockPasswordSalt = base64().encode(salt);
getInstance().registryLockPasswordHash = hashPassword(registryLockPassword, salt);
getInstance().allowedToSetRegistryLockPassword = false;
return thisCastToDerived();
return this;
}
}
public static ImmutableList<RegistrarPoc> loadForRegistrar(String registrarId) {
return tm().createQueryComposer(RegistrarPoc.class)
.where("registrarId", QueryComposer.Comparator.EQ, registrarId)
.list();
}
/** Class to represent the composite primary key for {@link RegistrarPoc} entity. */
@VisibleForTesting
public static class RegistrarPocId extends ImmutableObject implements Serializable {

View File

@@ -95,10 +95,4 @@ public class SignedMarkRevocationList extends ImmutableObject {
public int size() {
return revokes.size();
}
/** Save this list to Cloud SQL. Returns {@code this}. */
public SignedMarkRevocationList save() {
SignedMarkRevocationListDao.save(this);
return this;
}
}

View File

@@ -44,11 +44,25 @@ public class SignedMarkRevocationListDao {
return smdrl.orElseGet(() -> SignedMarkRevocationList.create(START_OF_TIME, ImmutableMap.of()));
}
/** Save the given {@link SignedMarkRevocationList} */
static void save(SignedMarkRevocationList signedMarkRevocationList) {
tm().transact(() -> tm().insert(signedMarkRevocationList));
/**
* Persists a {@link SignedMarkRevocationList} instance and returns the persisted entity.
*
* <p>Note that the input parameter is untouched. Use the returned object if metadata fields like
* {@code revisionId} are needed.
*/
public static SignedMarkRevocationList save(SignedMarkRevocationList signedMarkRevocationList) {
var persisted =
tm().transact(
() -> {
var entity =
SignedMarkRevocationList.create(
signedMarkRevocationList.getCreationTime(),
ImmutableMap.copyOf(signedMarkRevocationList.revokes));
tm().insert(entity);
return entity;
});
logger.atInfo().log(
"Inserted %,d signed mark revocations into Cloud SQL.",
signedMarkRevocationList.revokes.size());
"Inserted %,d signed mark revocations into Cloud SQL.", persisted.revokes.size());
return persisted;
}
}

View File

@@ -133,24 +133,30 @@ public final class PremiumListDao {
}
/** Saves the given premium list (and its premium list entries) to Cloud SQL. */
public static PremiumList save(PremiumList premiumList) {
tm().transact(
() -> {
tm().insert(premiumList);
tm().getEntityManager().flush(); // This populates the revisionId.
long revisionId = premiumList.getRevisionId();
public static PremiumList save(PremiumList premiumListToPersist) {
PremiumList persisted =
tm().transact(
() -> {
// Make a new copy in each attempt to insert. See javadoc of the insert method for
// more information.
PremiumList premiumList = premiumListToPersist.asBuilder().build();
tm().insert(premiumList);
tm().getEntityManager().flush(); // This populates the revisionId.
long revisionId = premiumList.getRevisionId();
if (!isNullOrEmpty(premiumList.getLabelsToPrices())) {
ImmutableSet.Builder<PremiumEntry> entries = new ImmutableSet.Builder<>();
premiumList
.getLabelsToPrices()
.forEach(
(key, value) -> entries.add(PremiumEntry.create(revisionId, value, key)));
tm().insertAll(entries.build());
}
});
premiumListCache.invalidate(premiumList.getName());
return premiumList;
if (!isNullOrEmpty(premiumList.getLabelsToPrices())) {
ImmutableSet.Builder<PremiumEntry> entries = new ImmutableSet.Builder<>();
premiumList
.getLabelsToPrices()
.forEach(
(key, value) ->
entries.add(PremiumEntry.create(revisionId, value, key)));
tm().insertAll(entries.build());
}
return premiumList;
});
premiumListCache.invalidate(persisted.getName());
return persisted;
}
public static void delete(PremiumList premiumList) {

View File

@@ -27,14 +27,26 @@ public class ReservedListDao {
private ReservedListDao() {}
/** Persist a new reserved list to Cloud SQL. */
public static void save(ReservedList reservedList) {
/**
* Persists a new reserved list to Cloud SQL and returns the persisted entity.
*
* <p>Note that the input parameter is untouched. Use the returned object if metadata fields like
* {@code revisionId} are needed.
*/
public static ReservedList save(ReservedList reservedList) {
checkArgumentNotNull(reservedList, "Must specify reservedList");
logger.atInfo().log("Saving reserved list %s to Cloud SQL.", reservedList.getName());
tm().transact(() -> tm().insert(reservedList));
var persisted =
tm().transact(
() -> {
var entity = reservedList.asBuilder().build();
tm().insert(entity);
return entity;
});
logger.atInfo().log(
"Saved reserved list %s with %d entries to Cloud SQL.",
reservedList.getName(), reservedList.getReservedListEntries().size());
return persisted;
}
/** Deletes a reserved list from Cloud SQL. */

View File

@@ -49,10 +49,23 @@ public class ClaimsListDao {
return CacheUtils.newCacheBuilder(expiry).build(ignored -> ClaimsListDao.getUncached());
}
/** Saves the given {@link ClaimsList} to Cloud SQL. */
public static void save(ClaimsList claimsList) {
tm().transact(() -> tm().insert(claimsList));
CACHE.put(ClaimsListDao.class, claimsList);
/**
* Persists a {@link ClaimsList} instance and returns the persisted entity.
*
* <p>Note that the input parameter is untouched. Use the returned object if metadata fields like
* {@code revisionId} are needed.
*/
public static ClaimsList save(ClaimsList claimsList) {
var persisted =
tm().transact(
() -> {
var entity =
ClaimsList.create(claimsList.tmdbGenerationTime, claimsList.labelsToKeys);
tm().insert(entity);
return entity;
});
CACHE.put(ClaimsListDao.class, persisted);
return persisted;
}
/** Returns the most recent revision of the {@link ClaimsList} from the cache. */

View File

@@ -38,10 +38,8 @@ import google.registry.dns.PublishDnsUpdatesAction;
import google.registry.dns.ReadDnsRefreshRequestsAction;
import google.registry.dns.RefreshDnsAction;
import google.registry.dns.RefreshDnsOnHostRenameAction;
import google.registry.dns.writer.VoidDnsWriterModule;
import google.registry.dns.writer.clouddns.CloudDnsWriterModule;
import google.registry.dns.writer.DnsWritersModule;
import google.registry.dns.writer.dnsupdate.DnsUpdateConfigModule;
import google.registry.dns.writer.dnsupdate.DnsUpdateWriterModule;
import google.registry.export.ExportDomainListsAction;
import google.registry.export.ExportPremiumTermsAction;
import google.registry.export.ExportReservedTermsAction;
@@ -117,6 +115,7 @@ import google.registry.ui.server.console.ConsoleDomainGetAction;
import google.registry.ui.server.console.ConsoleDomainListAction;
import google.registry.ui.server.console.ConsoleDumDownloadAction;
import google.registry.ui.server.console.ConsoleEppPasswordAction;
import google.registry.ui.server.console.ConsoleHistoryDataAction;
import google.registry.ui.server.console.ConsoleModule;
import google.registry.ui.server.console.ConsoleOteAction;
import google.registry.ui.server.console.ConsoleRegistryLockAction;
@@ -124,6 +123,8 @@ import google.registry.ui.server.console.ConsoleRegistryLockVerifyAction;
import google.registry.ui.server.console.ConsoleUpdateRegistrarAction;
import google.registry.ui.server.console.ConsoleUserDataAction;
import google.registry.ui.server.console.ConsoleUsersAction;
import google.registry.ui.server.console.PasswordResetRequestAction;
import google.registry.ui.server.console.PasswordResetVerifyAction;
import google.registry.ui.server.console.RegistrarsAction;
import google.registry.ui.server.console.domains.ConsoleBulkDomainAction;
import google.registry.ui.server.console.settings.ContactAction;
@@ -140,14 +141,13 @@ import google.registry.whois.WhoisModule;
BatchModule.class,
BillingModule.class,
CheckApiModule.class,
CloudDnsWriterModule.class,
ConsoleModule.class,
CronModule.class,
CustomLogicModule.class,
DnsCountQueryCoordinatorModule.class,
DnsModule.class,
DnsUpdateConfigModule.class,
DnsUpdateWriterModule.class,
DnsWritersModule.class,
EppTlsModule.class,
EppToolModule.class,
IcannReportingModule.class,
@@ -160,7 +160,6 @@ import google.registry.whois.WhoisModule;
Spec11Module.class,
TmchModule.class,
ToolsServerModule.class,
VoidDnsWriterModule.class,
WhiteboxModule.class,
WhoisModule.class,
})
@@ -187,6 +186,8 @@ interface RequestComponent {
ConsoleEppPasswordAction consoleEppPasswordAction();
ConsoleHistoryDataAction consoleHistoryDataAction();
ConsoleOteAction consoleOteAction();
ConsoleRegistryLockAction consoleRegistryLockAction();
@@ -253,6 +254,10 @@ interface RequestComponent {
NordnVerifyAction nordnVerifyAction();
PasswordResetRequestAction passwordResetRequestAction();
PasswordResetVerifyAction passwordResetVerifyAction();
PublishDnsUpdatesAction publishDnsUpdatesAction();
PublishInvoicesAction uploadInvoicesAction();
@@ -285,6 +290,8 @@ interface RequestComponent {
RdapNameserverSearchAction rdapNameserverSearchAction();
RdapRegistrarFieldsAction rdapRegistrarFieldsAction();
RdeReportAction rdeReportAction();
RdeReporter rdeReporter();
@@ -336,9 +343,7 @@ interface RequestComponent {
WhoisAction whoisAction();
WhoisHttpAction whoisHttpAction();
RdapRegistrarFieldsAction rdapRegistrarFieldsAction();
WipeOutContactHistoryPiiAction wipeOutContactHistoryPiiAction();
@Subcomponent.Builder

View File

@@ -22,7 +22,6 @@ import google.registry.bigquery.BigqueryModule;
import google.registry.config.CloudTasksUtilsModule;
import google.registry.config.CredentialModule;
import google.registry.config.RegistryConfig.ConfigModule;
import google.registry.dns.writer.VoidDnsWriterModule;
import google.registry.export.DriveModule;
import google.registry.export.sheet.SheetsServiceModule;
import google.registry.flows.ServerTridProviderModule;
@@ -73,7 +72,6 @@ import jakarta.inject.Singleton;
SheetsServiceModule.class,
StackdriverModule.class,
UrlConnectionServiceModule.class,
VoidDnsWriterModule.class,
UtilsModule.class
})
interface BackendComponent {

View File

@@ -34,10 +34,8 @@ import google.registry.dns.PublishDnsUpdatesAction;
import google.registry.dns.ReadDnsRefreshRequestsAction;
import google.registry.dns.RefreshDnsAction;
import google.registry.dns.RefreshDnsOnHostRenameAction;
import google.registry.dns.writer.VoidDnsWriterModule;
import google.registry.dns.writer.clouddns.CloudDnsWriterModule;
import google.registry.dns.writer.DnsWritersModule;
import google.registry.dns.writer.dnsupdate.DnsUpdateConfigModule;
import google.registry.dns.writer.dnsupdate.DnsUpdateWriterModule;
import google.registry.export.ExportDomainListsAction;
import google.registry.export.ExportPremiumTermsAction;
import google.registry.export.ExportReservedTermsAction;
@@ -82,13 +80,12 @@ import google.registry.tmch.TmchSmdrlAction;
modules = {
BatchModule.class,
BillingModule.class,
CloudDnsWriterModule.class,
CronModule.class,
CustomLogicModule.class,
DnsCountQueryCoordinatorModule.class,
DnsModule.class,
DnsUpdateConfigModule.class,
DnsUpdateWriterModule.class,
DnsWritersModule.class,
IcannReportingModule.class,
RdeModule.class,
ReportingModule.class,
@@ -96,7 +93,6 @@ import google.registry.tmch.TmchSmdrlAction;
SheetModule.class,
Spec11Module.class,
TmchModule.class,
VoidDnsWriterModule.class,
WhiteboxModule.class,
})
public interface BackendRequestComponent {

View File

@@ -38,6 +38,8 @@ import google.registry.ui.server.console.ConsoleRegistryLockVerifyAction;
import google.registry.ui.server.console.ConsoleUpdateRegistrarAction;
import google.registry.ui.server.console.ConsoleUserDataAction;
import google.registry.ui.server.console.ConsoleUsersAction;
import google.registry.ui.server.console.PasswordResetRequestAction;
import google.registry.ui.server.console.PasswordResetVerifyAction;
import google.registry.ui.server.console.RegistrarsAction;
import google.registry.ui.server.console.domains.ConsoleBulkDomainAction;
import google.registry.ui.server.console.settings.ContactAction;
@@ -84,6 +86,12 @@ public interface FrontendRequestComponent {
FlowComponent.Builder flowComponentBuilder();
PasswordResetRequestAction passwordResetRequestAction();
PasswordResetVerifyAction passwordResetVerifyAction();
RdapRegistrarFieldsAction rdapRegistrarFieldsAction();
ReadinessProbeActionFrontend readinessProbeActionFrontend();
ReadinessProbeConsoleAction readinessProbeConsoleAction();
@@ -92,8 +100,6 @@ public interface FrontendRequestComponent {
SecurityAction securityAction();
RdapRegistrarFieldsAction rdapRegistrarFieldsAction();
@Subcomponent.Builder
abstract class Builder implements RequestComponentBuilder<FrontendRequestComponent> {
@Override public abstract Builder requestModule(RequestModule requestModule);

View File

@@ -344,6 +344,18 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
return txnInfo.transactionTime;
}
/**
* Inserts an object into the database.
*
* <p>If {@code entity} has an auto-generated identity field (i.e., a field annotated with {@link
* jakarta.persistence.GeneratedValue}), the caller must not assign a value to this field,
* otherwise Hibernate would mistake the entity as detached and raise an error.
*
* <p>The practical implication of the above is that when inserting such an entity using a
* retriable transaction , the entity should be instantiated inside the transaction body. A failed
* attempt may still assign and ID to the entity, therefore reusing the same entity would cause
* retries to fail.
*/
@Override
public void insert(Object entity) {
checkArgumentNotNull(entity, "entity must be specified");

View File

@@ -336,7 +336,7 @@ abstract class AbstractJsonableObject implements Jsonable {
// According to RFC 9083 section 3, the syntax of dates and times is defined in RFC3339.
//
// According to RFC3339, we should use ISO8601, which is what DateTime.toString does!
return new JsonPrimitive(((DateTime) object).toString());
return new JsonPrimitive(object.toString());
}
if (object == null) {
return JsonNull.INSTANCE;

View File

@@ -24,10 +24,14 @@ import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.EppResource;
import google.registry.model.registrar.Registrar;
@@ -41,6 +45,7 @@ import google.registry.request.HttpException;
import google.registry.request.Parameter;
import google.registry.request.RequestMethod;
import google.registry.request.RequestPath;
import google.registry.request.RequestUrl;
import google.registry.request.Response;
import google.registry.util.Clock;
import jakarta.inject.Inject;
@@ -50,7 +55,7 @@ import java.util.Optional;
import org.joda.time.DateTime;
/**
* Base RDAP (new WHOIS) action for all requests.
* Base RDAP action for all requests.
*
* @see <a href="https://tools.ietf.org/html/rfc9082">RFC 9082: Registration Data Access Protocol
* (RDAP) Query Format</a>
@@ -75,6 +80,7 @@ public abstract class RdapActionBase implements Runnable {
@Inject Response response;
@Inject @RequestMethod Action.Method requestMethod;
@Inject @RequestPath String requestPath;
@Inject @RequestUrl String requestUrl;
@Inject RdapAuthorization rdapAuthorization;
@Inject RdapJsonFormatter rdapJsonFormatter;
@Inject @Parameter("includeDeleted") Optional<Boolean> includeDeletedParam;
@@ -132,7 +138,7 @@ public abstract class RdapActionBase implements Runnable {
// RFC7480 4.2 - servers receiving an RDAP request return an entity with a Content-Type header
// containing the RDAP-specific JSON media type.
response.setContentType(RESPONSE_MEDIA_TYPE);
// RDAP Technical Implementation Guide 1.13 - when responding to RDAP valid requests, we MUST
// RDAP Technical Implementation Guide 1.14 - when responding to RDAP valid requests, we MUST
// include the Access-Control-Allow-Origin, which MUST be "*" unless otherwise specified.
response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
try {
@@ -198,7 +204,9 @@ public abstract class RdapActionBase implements Runnable {
TopLevelReplyObject topLevelObject =
TopLevelReplyObject.create(replyObject, rdapJsonFormatter.createTosNotice());
Gson gson = formatOutputParam.orElse(false) ? FORMATTED_OUTPUT_GSON : GSON;
response.setPayload(gson.toJson(topLevelObject.toJson()));
JsonObject jsonResult = topLevelObject.toJson();
addLinkValuesRecursively(jsonResult);
response.setPayload(gson.toJson(jsonResult));
}
/**
@@ -264,4 +272,34 @@ public abstract class RdapActionBase implements Runnable {
return rdapJsonFormatter.getRequestTime();
}
/**
* Adds a request-referencing "value" to each link object.
*
* <p>This is the "context URI" as described in RFC 8288. Basically, this contains a reference to
* the request URL that generated this RDAP response.
*
* <p>This is required per the RDAP February 2024 response profile sections 2.6.3 and 2.10, and
* the technical implementation guide sections 3.2 and 3.3.2.
*
* <p>We must do this here (instead of where the links are generated) because many of the links
* (e.g. terms of service) are static constants, and thus cannot by default know what the request
* URL was.
*/
private void addLinkValuesRecursively(JsonElement jsonElement) {
if (jsonElement instanceof JsonArray jsonArray) {
jsonArray.forEach(this::addLinkValuesRecursively);
} else if (jsonElement instanceof JsonObject jsonObject) {
if (jsonObject.get("links") instanceof JsonArray linksArray) {
addLinkValues(linksArray);
}
jsonObject.entrySet().forEach(entry -> addLinkValuesRecursively(entry.getValue()));
}
}
private void addLinkValues(JsonArray linksArray) {
Streams.stream(linksArray)
.map(JsonElement::getAsJsonObject)
.filter(o -> !o.has("value"))
.forEach(o -> o.addProperty("value", requestUrl));
}
}

View File

@@ -26,7 +26,7 @@ import google.registry.request.auth.Auth;
import jakarta.inject.Inject;
/**
* RDAP (new WHOIS) action for RDAP autonomous system number requests.
* RDAP action for RDAP autonomous system number requests.
*
* <p>This feature is not implemented because it's only necessary for <i>address</i> registries like
* ARIN, not domain registries.

View File

@@ -41,14 +41,13 @@ final class RdapDataStructures {
// Conformance to RFC 9083
jsonArray.add("rdap_level_0");
// Conformance to the RDAP Response Profile V2.1
// Conformance to the RDAP Response Profile V2.2 (February 2024)
// (see section 1.2)
jsonArray.add("icann_rdap_response_profile_1");
// Conformance to the RDAP Technical Implementation Guide V2.2 (February 2024)
// (see section 1.3)
jsonArray.add("icann_rdap_response_profile_0");
// Conformance to the RDAP Technical Implementation Guide V2.1
// (see section 1.14)
jsonArray.add("icann_rdap_technical_implementation_guide_0");
jsonArray.add("icann_rdap_technical_implementation_guide_1");
return jsonArray;
}
}
@@ -334,7 +333,6 @@ final class RdapDataStructures {
*/
@RestrictJsonNames("status[]")
enum RdapStatus implements Jsonable {
// Status values specified in RFC 9083 § 10.2.2.
VALIDATED("validated"),
RENEW_PROHIBITED("renew prohibited"),

View File

@@ -15,7 +15,7 @@
package google.registry.rdap;
import static google.registry.flows.domain.DomainFlowUtils.validateDomainName;
import static google.registry.model.EppResourceUtils.loadByForeignKeyCached;
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCache;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
@@ -36,7 +36,7 @@ import google.registry.request.auth.Auth;
import jakarta.inject.Inject;
import java.util.Optional;
/** RDAP (new WHOIS) action for domain requests. */
/** RDAP action for domain requests. */
@Action(
service = GaeService.PUBAPI,
path = "/rdap/domain/",
@@ -57,6 +57,9 @@ public class RdapDomainAction extends RdapActionBase {
InternetDomainName domainName;
try {
domainName = validateDomainName(pathSearchString);
} catch (DomainFlowUtils.TldDoesNotExistException e) {
// A special case where a valid domain name on a nonexistent TLD should return 404
throw new NotFoundException(pathSearchString + " not found");
} catch (EppException e) {
throw new BadRequestException(
String.format(
@@ -65,7 +68,7 @@ public class RdapDomainAction extends RdapActionBase {
}
// The query string is not used; the RDAP syntax is /rdap/domain/mydomain.com.
Optional<Domain> domain =
loadByForeignKeyCached(
loadByForeignKeyByCache(
Domain.class,
pathSearchString,
shouldIncludeDeleted() ? START_OF_TIME : rdapJsonFormatter.getRequestTime());

View File

@@ -15,7 +15,7 @@
package google.registry.rdap;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.model.EppResourceUtils.loadByForeignKeyCached;
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCache;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD;
@@ -51,6 +51,7 @@ import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.util.NonFinalForTesting;
import jakarta.inject.Inject;
import jakarta.persistence.Query;
import jakarta.persistence.criteria.CriteriaBuilder;
import java.net.InetAddress;
import java.util.Comparator;
@@ -60,17 +61,15 @@ import java.util.stream.Stream;
import org.hibernate.Hibernate;
/**
* RDAP (new WHOIS) action for domain search requests.
* RDAP action for domain search requests.
*
* <p>All commands and responses conform to the RDAP spec as defined in RFCs 7480 through 7485.
* <p>All commands and responses conform to the RDAP spec as defined in STD 95 and its RFCs.
*
* @see <a href="http://tools.ietf.org/html/rfc9082">RFC 9082: Registration Data Access Protocol
* (RDAP) Query Format</a>
* @see <a href="http://tools.ietf.org/html/rfc9083">RFC 9083: JSON Responses for the Registration
* Data Access Protocol (RDAP)</a>
*/
// TODO: This isn't required by the RDAP Technical Implementation Guide, and hence should be
// deleted, at least until it's actually required.
@Action(
service = GaeService.PUBAPI,
path = "/rdap/domains",
@@ -184,7 +183,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
private DomainSearchResponse searchByDomainNameWithoutWildcard(
final RdapSearchPattern partialStringQuery) {
Optional<Domain> domain =
loadByForeignKeyCached(
loadByForeignKeyByCache(
Domain.class, partialStringQuery.getInitialString(), getRequestTime());
return makeSearchResults(
shouldBeVisible(domain) ? ImmutableList.of(domain.get()) : ImmutableList.of());
@@ -339,7 +338,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
Optional<String> desiredRegistrar = getDesiredRegistrar();
if (desiredRegistrar.isPresent()) {
Optional<Host> host =
loadByForeignKeyCached(
loadByForeignKeyByCache(
Host.class,
partialStringQuery.getInitialString(),
shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
@@ -364,7 +363,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
// through the subordinate hosts. This is more efficient, and lets us permit wildcard searches
// with no initial string.
Domain domain =
loadByForeignKeyCached(
loadByForeignKeyByCache(
Domain.class,
partialStringQuery.getSuffix(),
shouldIncludeDeleted() ? START_OF_TIME : getRequestTime())
@@ -381,7 +380,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
if (partialStringQuery.matches(fqhn)) {
if (desiredRegistrar.isPresent()) {
Optional<Host> host =
loadByForeignKeyCached(
loadByForeignKeyByCache(
Host.class, fqhn, shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
if (host.isPresent()
&& desiredRegistrar
@@ -442,7 +441,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
replicaTm()
.transact(
() -> {
jakarta.persistence.Query query =
Query query =
replicaTm()
.getEntityManager()
.createNativeQuery(queryBuilder.toString())

View File

@@ -14,18 +14,13 @@
package google.registry.rdap;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
import static google.registry.rdap.RdapUtils.getRegistrarByIanaIdentifier;
import static google.registry.rdap.RdapUtils.getRegistrarByName;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Longs;
import com.google.re2j.Pattern;
import google.registry.model.contact.Contact;
import google.registry.model.registrar.Registrar;
import google.registry.persistence.VKey;
import google.registry.rdap.RdapJsonFormatter.OutputDataType;
import google.registry.rdap.RdapMetrics.EndpointType;
import google.registry.rdap.RdapObjectClasses.RdapEntity;
@@ -37,14 +32,12 @@ import jakarta.inject.Inject;
import java.util.Optional;
/**
* RDAP (new WHOIS) action for entity (contact and registrar) requests. the ICANN operational
* profile dictates that the "handle" for registrars is to be the IANA registrar ID:
* RDAP action for entity (i.e. registrar) requests. the ICANN operational profile dictates that the
* "handle" for registrars is to be the IANA registrar ID:
*
* <p>2.8.3. Registries MUST support lookup for entities with the registrar role within other
* objects using the handle (as described in 3.1.5 of RFC 9082). The handle of the entity with the
* registrar role MUST be equal to IANA Registrar ID. The entity with the registrar role in the RDAP
* response MUST contain a publicIDs member to identify the IANA Registrar ID from the IANAs
* Registrar ID registry. The type value of the publicID object MUST be equal to IANA Registrar ID.
* <p>2.4.1.Registry RDAP servers MUST support Registrar object lookup using an entity path request
* for entities with the registrar role using the handle (as described in 3.1.5 of RFC9082) where
* the handle of the entity with the registrar role is be [sic] equal to the IANA Registrar ID.
*/
@Action(
service = GaeService.PUBAPI,
@@ -54,8 +47,6 @@ import java.util.Optional;
auth = Auth.AUTH_PUBLIC)
public class RdapEntityAction extends RdapActionBase {
private static final Pattern ROID_PATTERN = Pattern.compile("[-_.a-zA-Z0-9]+");
@Inject public RdapEntityAction() {
super("entity", EndpointType.ENTITY);
}
@@ -63,24 +54,6 @@ public class RdapEntityAction extends RdapActionBase {
@Override
public RdapEntity getJsonObjectForResource(
String pathSearchString, boolean isHeadRequest) {
// The query string is not used; the RDAP syntax is /rdap/entity/handle (the handle is the roid
// for contacts and the client identifier/fn for registrars). Since RDAP's concept of an entity
// includes both contacts and registrars, search for one first, then the other.
// RDAP Technical Implementation Guide 2.3.1 - MUST support contact entity lookup using the
// handle
if (ROID_PATTERN.matcher(pathSearchString).matches()) {
VKey<Contact> contactVKey = VKey.create(Contact.class, pathSearchString);
Optional<Contact> contact =
replicaTm().transact(() -> replicaTm().loadByKeyIfPresent(contactVKey));
// As per Andy Newton on the regext mailing list, contacts by themselves have no role, since
// they are global, and might have different roles for different domains.
if (contact.isPresent() && isAuthorized(contact.get())) {
return rdapJsonFormatter.createRdapContactEntity(
contact.get(), ImmutableSet.of(), OutputDataType.FULL);
}
}
// RDAP Technical Implementation Guide 2.4.1 - MUST support registrar entity lookup using the
// IANA ID as handle
Long ianaIdentifier = Longs.tryParse(pathSearchString);
@@ -98,13 +71,13 @@ public class RdapEntityAction extends RdapActionBase {
return rdapJsonFormatter.createRdapRegistrarEntity(registrar.get(), OutputDataType.FULL);
}
// At this point, we have failed to find either a contact or a registrar.
// At this point, we have failed to find a registrar.
//
// RFC7480 5.3 - if the server wishes to respond that it doesn't have data satisfying the
// query, it MUST reply with 404 response code.
//
// Note we don't do RFC7480 5.3 - returning a different code if we wish to say "this info
// exists but we don't want to show it to you", because we DON'T wish to say that.
// exists, but we don't want to show it to you", because we DON'T wish to say that.
throw new NotFoundException(pathSearchString + " not found");
}
}

View File

@@ -15,22 +15,16 @@
package google.registry.rdap;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
import static google.registry.rdap.RdapUtils.getRegistrarByIanaIdentifier;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
import com.google.common.primitives.Booleans;
import com.google.common.primitives.Longs;
import google.registry.model.contact.Contact;
import google.registry.model.registrar.Registrar;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.CriteriaQueryBuilder;
import google.registry.rdap.RdapAuthorization.Role;
import google.registry.rdap.RdapJsonFormatter.OutputDataType;
import google.registry.rdap.RdapMetrics.EndpointType;
import google.registry.rdap.RdapMetrics.SearchType;
@@ -49,35 +43,18 @@ import java.util.List;
import java.util.Optional;
/**
* RDAP (new WHOIS) action for entity (contact and registrar) search requests.
* RDAP action for entity (i.e. registrar) search requests.
*
* <p>All commands and responses conform to the RDAP spec as defined in RFCs 7480 through 7485.
* <p>All commands and responses conform to the RDAP spec as defined in STD 95 and its RFCs.
*
* <p>The RDAP specification lumps contacts and registrars together and calls them "entities", which
* is confusing for us, because "entity" means something else in SQL. But here, when we use the
* term, it means either a contact or registrar. When searching for entities, we always start by
* returning all matching contacts, and after that all matching registrars.
*
* <p>There are two ways to search for entities: by full name (for contacts, the search name, for
* registrars, the registrar name) or by handle (for contacts, the ROID, for registrars, the IANA
* number). The ICANN operational profile document specifies this meaning for handle searches.
*
* <p>Cursors are complicated by the fact that we are essentially doing two independent searches:
* one for contacts, and one for registrars. To accommodate this, the cursor has a prefix indicating
* the type of the last returned item. If the last item was a contact, we return c:{value}, where
* the value is either the search name or the ROID. If the last item was a registrar, we return
* r:{value}, where the value is either the registrar name or the IANA number. If we get a c:
* cursor, we use it to weed out contacts, and fetch all registrars. If we get an r: cursor, we know
* that we can skip the contact search altogether (because we returned a registrar, and all
* registrars come after all contacts).
* <p>There are two ways to search for entities: by full name (the registrar name) or by handle (the
* IANA number). The ICANN operational profile document specifies this meaning for handle searches.
*
* @see <a href="http://tools.ietf.org/html/rfc9082">RFC 9082: Registration Data Access Protocol
* (RDAP) Query Format</a>
* @see <a href="http://tools.ietf.org/html/rfc9083">RFC 9083: JSON Responses for the Registration
* Data Access Protocol (RDAP)</a>
*/
// TODO: This isn't required by the RDAP Technical Implementation Guide, and hence should be
// deleted, at least until it's actually required.
@Action(
service = GaeService.PUBAPI,
path = "/rdap/entities",
@@ -87,31 +64,10 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
@Inject @Parameter("fn") Optional<String> fnParam;
@Inject @Parameter("handle") Optional<String> handleParam;
@Inject @Parameter("subtype") Optional<String> subtypeParam;
@Inject public RdapEntitySearchAction() {
super("entity search", EndpointType.ENTITIES);
}
private enum QueryType {
FULL_NAME,
HANDLE
}
private enum Subtype {
ALL,
CONTACTS,
REGISTRARS
}
private enum CursorType {
NONE,
CONTACT,
REGISTRAR
}
private static final String CONTACT_CURSOR_PREFIX = "c:";
private static final String REGISTRAR_CURSOR_PREFIX = "r:";
/** Parses the parameters and calls the appropriate search function. */
@Override
public EntitySearchResponse getSearchResponse(boolean isHeadRequest) {
@@ -120,61 +76,23 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
throw new BadRequestException("You must specify either fn=XXXX or handle=YYYY");
}
// Check the subtype.
Subtype subtype;
if (subtypeParam.isEmpty() || subtypeParam.get().equalsIgnoreCase("all")) {
subtype = Subtype.ALL;
} else if (subtypeParam.get().equalsIgnoreCase("contacts")) {
subtype = Subtype.CONTACTS;
} else if (subtypeParam.get().equalsIgnoreCase("registrars")) {
subtype = Subtype.REGISTRARS;
} else {
throw new BadRequestException("Subtype parameter must specify contacts, registrars or all");
}
CursorType cursorType;
Optional<String> cursorQueryString;
if (cursorString.isEmpty()) {
cursorType = CursorType.NONE;
cursorQueryString = Optional.empty();
} else {
if (cursorString.get().startsWith(CONTACT_CURSOR_PREFIX)) {
cursorType = CursorType.CONTACT;
cursorQueryString =
Optional.of(cursorString.get().substring(CONTACT_CURSOR_PREFIX.length()));
} else if (cursorString.get().startsWith(REGISTRAR_CURSOR_PREFIX)) {
cursorType = CursorType.REGISTRAR;
cursorQueryString =
Optional.of(cursorString.get().substring(REGISTRAR_CURSOR_PREFIX.length()));
} else {
throw new BadRequestException(String.format("invalid cursor: %s", cursorTokenParam));
}
}
// Search by name.
EntitySearchResponse results;
if (fnParam.isPresent()) {
metricInformationBuilder.setSearchType(SearchType.BY_FULL_NAME);
// syntax: /rdap/entities?fn=Bobby%20Joe*
// The name is the contact name or registrar name (not registrar contact name).
// The name is the registrar name (not registrar contact name).
results =
searchByName(
recordWildcardType(RdapSearchPattern.createFromUnicodeString(fnParam.get())),
cursorType,
cursorQueryString,
subtype);
// Search by handle.
recordWildcardType(RdapSearchPattern.createFromUnicodeString(fnParam.get())));
} else {
// Search by handle.
metricInformationBuilder.setSearchType(SearchType.BY_HANDLE);
// syntax: /rdap/entities?handle=12345-*
// The handle is either the contact roid or the registrar clientId.
// The handle is the registrar ID.
results =
searchByHandle(
recordWildcardType(RdapSearchPattern.createFromUnicodeString(handleParam.get())),
cursorType,
cursorQueryString,
subtype);
recordWildcardType(RdapSearchPattern.createFromUnicodeString(handleParam.get())));
}
// Build the result object and return it.
@@ -196,91 +114,32 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
* <p>According to RFC 9082 section 6.1, punycode is only used for domain name labels, so we can
* assume that entity names are regular unicode.
*
* <p>The includeDeleted flag is ignored when searching for contacts, because contact names are
* set to null when the contact is deleted, so a deleted contact can never have a name.
*
* <p>Since we are restricting access to contact names, we don't want name searches to return
* contacts whose names are not visible. That would allow unscrupulous users to query by name and
* infer that all returned contacts contain that name string. So we check the authorization level
* to determine what to do.
*
* @see <a
* href="https://newgtlds.icann.org/sites/default/files/agreements/agreement-approved-09jan14-en.htm">1.6
* of Section 4 of the Base Registry Agreement</a>
*/
private EntitySearchResponse searchByName(
final RdapSearchPattern partialStringQuery,
CursorType cursorType,
Optional<String> cursorQueryString,
Subtype subtype) {
private EntitySearchResponse searchByName(final RdapSearchPattern partialStringQuery) {
// Don't allow wildcard suffixes when searching for entities.
if (partialStringQuery.getHasWildcard() && (partialStringQuery.getSuffix() != null)) {
throw new UnprocessableEntityException(
"Suffixes not allowed in wildcard entity name searches");
}
// For wildcards, make sure the initial string is long enough, except in the special case of
// searching for all registrars, where we aren't worried about inefficient searches.
if (partialStringQuery.getHasWildcard()
&& (subtype != Subtype.REGISTRARS)
&& (partialStringQuery.getInitialString().length()
< RdapSearchPattern.MIN_INITIAL_STRING_LENGTH)) {
throw new UnprocessableEntityException(
"Initial search string required in wildcard entity name searches");
}
// Get the registrar matches. If we have a registrar cursor, weed out registrars up to and
// including the one we ended with last time. We can skip registrars if subtype is CONTACTS.
ImmutableList<Registrar> registrars;
if (subtype == Subtype.CONTACTS) {
registrars = ImmutableList.of();
} else {
registrars =
Streams.stream(Registrar.loadAllCached())
.sorted(
Comparator.comparing(Registrar::getRegistrarName, String.CASE_INSENSITIVE_ORDER))
.filter(
registrar ->
partialStringQuery.matches(registrar.getRegistrarName())
&& ((cursorType != CursorType.REGISTRAR)
|| (registrar.getRegistrarName().compareTo(cursorQueryString.get())
> 0))
&& shouldBeVisible(registrar))
.limit(rdapResultSetMaxSize + 1)
.collect(toImmutableList());
}
// Get the contact matches and return the results, fetching an additional contact to detect
// truncation. Don't bother searching for contacts by name if the request would not be able to
// see any names anyway. Also, if a registrar cursor is present, we have already moved past the
// contacts, and don't need to fetch them this time. We can skip contacts if subtype is
// REGISTRARS.
RdapResultSet<Contact> resultSet;
if (subtype == Subtype.REGISTRARS) {
resultSet = RdapResultSet.create(ImmutableList.of());
} else {
if ((rdapAuthorization.role() == RdapAuthorization.Role.PUBLIC)
|| (cursorType == CursorType.REGISTRAR)) {
resultSet = RdapResultSet.create(ImmutableList.of());
} else {
resultSet =
replicaTm()
.transact(
() -> {
CriteriaQueryBuilder<Contact> builder =
queryItems(
Contact.class,
"searchName",
partialStringQuery,
cursorQueryString,
DeletedItemHandling.EXCLUDE);
if (!rdapAuthorization.role().equals(Role.ADMINISTRATOR)) {
builder =
builder.whereFieldIsIn(
"currentSponsorRegistrarId", rdapAuthorization.registrarIds());
}
return getMatchingResources(builder, false, rdapResultSetMaxSize + 1);
});
}
}
return makeSearchResults(resultSet, registrars, QueryType.FULL_NAME);
// Get the registrar matches. If we have a cursor, weed out registrars up to and including the
// one we ended with last time.
ImmutableList<Registrar> registrars =
Streams.stream(Registrar.loadAllCached())
.sorted(
Comparator.comparing(Registrar::getRegistrarName, String.CASE_INSENSITIVE_ORDER))
.filter(
registrar ->
partialStringQuery.matches(registrar.getRegistrarName())
&& (cursorString.isEmpty()
|| (registrar.getRegistrarName().compareTo(cursorString.get()) > 0))
&& shouldBeVisible(registrar))
.limit(rdapResultSetMaxSize + 1)
.collect(toImmutableList());
return makeSearchResults(registrars);
}
/**
@@ -290,101 +149,41 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
*
* <p>We don't allow suffixes after a wildcard in entity searches. Suffixes are used in domain
* searches to specify a TLD, and in nameserver searches to specify a locally managed domain name.
* In both cases, the suffix can be turned into an additional query filter field. For contacts,
* there is no equivalent string suffix that can be used as a query filter, so we disallow use.
* In both cases, the suffix can be turned into an additional query filter field.
*/
private EntitySearchResponse searchByHandle(
final RdapSearchPattern partialStringQuery,
CursorType cursorType,
Optional<String> cursorQueryString,
Subtype subtype) {
private EntitySearchResponse searchByHandle(final RdapSearchPattern partialStringQuery) {
if (partialStringQuery.getSuffix() != null) {
throw new UnprocessableEntityException("Suffixes not allowed in entity handle searches");
}
// Handle queries without a wildcard (and not including deleted) -- load by ID.
if (!partialStringQuery.getHasWildcard() && !shouldIncludeDeleted()) {
ImmutableList<Contact> contactList;
if (subtype == Subtype.REGISTRARS) {
contactList = ImmutableList.of();
} else {
Optional<Contact> contact =
replicaTm()
.transact(
() ->
replicaTm()
.loadByKeyIfPresent(
VKey.create(Contact.class, partialStringQuery.getInitialString())));
contactList =
(contact.isPresent() && shouldBeVisible(contact.get()))
? ImmutableList.of(contact.get())
: ImmutableList.of();
}
ImmutableList<Registrar> registrarList;
if (subtype == Subtype.CONTACTS) {
registrarList = ImmutableList.of();
} else {
registrarList = getMatchingRegistrars(partialStringQuery.getInitialString());
}
return makeSearchResults(
contactList,
IncompletenessWarningType.COMPLETE,
contactList.size(),
registrarList,
QueryType.HANDLE);
return makeSearchResults(getMatchingRegistrars(partialStringQuery.getInitialString()));
}
// Handle queries with a wildcard (or including deleted), but no suffix. Because the handle
// for registrars is the IANA identifier number, don't allow wildcard searches for registrars,
// by simply not searching for registrars if a wildcard is present (unless the request is for
// all registrars, in which case we know what to do). Fetch an extra contact to detect result
// set truncation.
// all registrars, in which case we know what to do).
ImmutableList<Registrar> registrars;
if (partialStringQuery.getHasWildcard() && partialStringQuery.getInitialString().isEmpty()) {
// Even though we are searching by IANA identifier, we should still sort by name, because
// the IANA identifier can by missing, and sorting on that would screw up our cursors.
registrars =
Streams.stream(Registrar.loadAllCached())
.sorted(
Comparator.comparing(Registrar::getRegistrarName, String.CASE_INSENSITIVE_ORDER))
.filter(
registrar ->
(cursorString.isEmpty()
|| (registrar.getRegistrarName().compareTo(cursorString.get()) > 0))
&& shouldBeVisible(registrar))
.limit(rdapResultSetMaxSize + 1)
.collect(toImmutableList());
} else if (partialStringQuery.getHasWildcard()) {
registrars = ImmutableList.of();
} else {
ImmutableList<Registrar> registrars;
if ((subtype == Subtype.REGISTRARS)
&& partialStringQuery.getHasWildcard()
&& partialStringQuery.getInitialString().isEmpty()) {
// Even though we are searching by IANA identifier, we should still sort by name, because
// the IANA identifier can by missing, and sorting on that would screw up our cursors.
registrars =
Streams.stream(Registrar.loadAllCached())
.sorted(
Comparator.comparing(
Registrar::getRegistrarName, String.CASE_INSENSITIVE_ORDER))
.filter(
registrar ->
((cursorType != CursorType.REGISTRAR)
|| (registrar.getRegistrarName().compareTo(cursorQueryString.get())
> 0))
&& shouldBeVisible(registrar))
.limit(rdapResultSetMaxSize + 1)
.collect(toImmutableList());
} else if ((subtype == Subtype.CONTACTS) || partialStringQuery.getHasWildcard()) {
registrars = ImmutableList.of();
} else {
registrars = getMatchingRegistrars(partialStringQuery.getInitialString());
}
// Get the contact matches and return the results, fetching an additional contact to detect
// truncation. If we are including deleted entries, we must fetch more entries, in case some
// get excluded due to permissioning. Any cursor present must be a contact cursor, because we
// would never return a registrar for this search.
int querySizeLimit = getStandardQuerySizeLimit();
RdapResultSet<Contact> contactResultSet;
if (subtype == Subtype.REGISTRARS) {
contactResultSet = RdapResultSet.create(ImmutableList.of());
} else {
contactResultSet =
replicaTm()
.transact(
() ->
getMatchingResources(
queryItemsByKey(
Contact.class,
partialStringQuery,
cursorQueryString,
getDeletedItemHandling()),
shouldIncludeDeleted(),
querySizeLimit));
}
return makeSearchResults(contactResultSet, registrars, QueryType.HANDLE);
registrars = getMatchingRegistrars(partialStringQuery.getInitialString());
}
return makeSearchResults(registrars);
}
/** Looks up registrars by handle (i.e. IANA identifier). */
@@ -399,90 +198,25 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
: ImmutableList.of();
}
/**
* Builds a JSON array of entity info maps based on the specified contacts and registrars.
*
* <p>This is a convenience wrapper for the four-argument makeSearchResults; it unpacks the
* properties of the {@link RdapResultSet} structure and passes them as separate arguments.
*/
private EntitySearchResponse makeSearchResults(
RdapResultSet<Contact> resultSet, List<Registrar> registrars, QueryType queryType) {
return makeSearchResults(
resultSet.resources(),
resultSet.incompletenessWarningType(),
resultSet.numResourcesRetrieved(),
registrars,
queryType);
}
/**
* Builds a JSON array of entity info maps based on the specified contacts and registrars.
*
* <p>The number of contacts retrieved is recorded for use by the metrics.
*
* @param contacts the list of contacts which can be returned
* @param incompletenessWarningType MIGHT_BE_INCOMPLETE if the list of contacts might be
* incomplete; this only matters if the total count of contacts and registrars combined is
* less than a full result set's worth
* @param numContactsRetrieved the number of contacts retrieved in the process of generating the
* results
* @param registrars the list of registrars which can be returned
* @param queryType whether the query was by full name or by handle
* @return an {@link RdapSearchResults} object
*/
private EntitySearchResponse makeSearchResults(
List<Contact> contacts,
IncompletenessWarningType incompletenessWarningType,
int numContactsRetrieved,
List<Registrar> registrars,
QueryType queryType) {
metricInformationBuilder.setNumContactsRetrieved(numContactsRetrieved);
/** Builds a JSON array of entity info maps based on the specified registrars. */
private EntitySearchResponse makeSearchResults(List<Registrar> registrars) {
// Determine what output data type to use, depending on whether more than one entity will be
// returned.
OutputDataType outputDataType =
(contacts.size() + registrars.size() > 1) ? OutputDataType.SUMMARY : OutputDataType.FULL;
// There can be more results than our max size, partially because we have two pools to draw from
// (contacts and registrars), and partially because we try to fetch one more than the max size,
// so we can tell whether to display the truncation notification.
//
// Each time we add a contact or registrar to the output data set, remember what the appropriate
// cursor would be if it were the last item returned. When we stop adding items, the last cursor
// value we remembered will be the right one to pass back.
EntitySearchResponse.Builder builder =
EntitySearchResponse.builder()
.setIncompletenessWarningType(incompletenessWarningType);
Optional<String> newCursor = Optional.empty();
for (Contact contact : Iterables.limit(contacts, rdapResultSetMaxSize)) {
// As per Andy Newton on the regext mailing list, contacts by themselves have no role, since
// they are global, and might have different roles for different domains.
registrars.size() > 1 ? OutputDataType.SUMMARY : OutputDataType.FULL;
EntitySearchResponse.Builder builder = EntitySearchResponse.builder();
Iterable<Registrar> limitedRegistrars = Iterables.limit(registrars, rdapResultSetMaxSize);
for (Registrar registrar : limitedRegistrars) {
builder
.entitySearchResultsBuilder()
.add(
rdapJsonFormatter.createRdapContactEntity(
contact, ImmutableSet.of(), outputDataType));
newCursor =
Optional.of(
CONTACT_CURSOR_PREFIX
+ ((queryType == QueryType.FULL_NAME)
? contact.getSearchName()
: contact.getRepoId()));
.add(rdapJsonFormatter.createRdapRegistrarEntity(registrar, outputDataType));
}
if (rdapResultSetMaxSize > contacts.size()) {
for (Registrar registrar :
Iterables.limit(registrars, rdapResultSetMaxSize - contacts.size())) {
builder
.entitySearchResultsBuilder()
.add(rdapJsonFormatter.createRdapRegistrarEntity(registrar, outputDataType));
newCursor = Optional.of(REGISTRAR_CURSOR_PREFIX + registrar.getRegistrarName());
}
}
if (rdapResultSetMaxSize < contacts.size() + registrars.size()) {
builder.setNextPageUri(createNavigationUri(newCursor.get()));
if (rdapResultSetMaxSize < registrars.size()) {
builder.setNextPageUri(
createNavigationUri(Iterables.getLast(limitedRegistrars).getRegistrarName()));
builder.setIncompletenessWarningType(IncompletenessWarningType.TRUNCATED);
return builder.build();
} else {
builder.setIncompletenessWarningType(IncompletenessWarningType.COMPLETE);
}
return builder.build();
}

View File

@@ -28,7 +28,7 @@ import google.registry.request.auth.Auth;
import jakarta.inject.Inject;
import java.util.Optional;
/** RDAP (new WHOIS) action for help requests. */
/** RDAP action for help requests. */
@Action(
service = GaeService.PUBAPI,
path = RdapHelpAction.PATH,

View File

@@ -22,42 +22,34 @@ import google.registry.rdap.RdapDataStructures.Remark;
/**
* This file contains boilerplate required by the ICANN RDAP Profile.
*
* @see <a href="https://www.icann.org/resources/pages/rdap-operational-profile-2016-07-26-en">RDAP
* Operational Profile for gTLD Registries and Registrars</a>
* @see <a
* href="https://itp.cdn.icann.org/en/files/registry-operators/rdap-response-profile-21feb24-en.pdf">
* RDAP Response Profile</a>
*/
public class RdapIcannStandardInformation {
/** Required by ICANN RDAP Profile section 1.4.10. */
private static final Notice CONFORMANCE_NOTICE =
Notice.builder()
.setDescription(
"This response conforms to the RDAP Operational Profile for gTLD Registries and"
+ " Registrars version 1.0")
.build();
/** Required by ICANN RDAP Profile section 1.5.18. */
/** Required by RDAP Response Profile section 2.6.3. */
private static final Notice DOMAIN_STATUS_CODES_NOTICE =
Notice.builder()
.setTitle("Status Codes")
.setDescription(
"For more information on domain status codes, please visit"
+ " https://icann.org/epp")
"For more information on domain status codes, please visit https://icann.org/epp")
.addLink(
Link.builder()
.setRel("alternate")
.setRel("glossary")
.setHref("https://icann.org/epp")
.setType("text/html")
.build())
.build();
/** Required by ICANN RDAP Response Profile section 2.11. */
/** Required by RDAP Response Profile section 2.10. */
private static final Notice INACCURACY_COMPLAINT_FORM_NOTICE =
Notice.builder()
.setTitle("RDDS Inaccuracy Complaint Form")
.setDescription("URL of the ICANN RDDS Inaccuracy Complaint Form: https://icann.org/wicf")
.addLink(
Link.builder()
.setRel("alternate")
.setRel("help")
.setHref("https://icann.org/wicf")
.setType("text/html")
.build())
@@ -79,28 +71,16 @@ public class RdapIcannStandardInformation {
/** Boilerplate notices required by domain responses. */
static final ImmutableList<Notice> DOMAIN_BOILERPLATE_NOTICES =
ImmutableList.of(
CONFORMANCE_NOTICE,
// RDAP Response Profile 2.6.3
DOMAIN_STATUS_CODES_NOTICE,
// RDAP Response Profile 2.11
// RDAP Response Profile 2.10
INACCURACY_COMPLAINT_FORM_NOTICE);
/** Boilerplate notice for when a domain is blocked by BSA. */
static final ImmutableList<Notice> DOMAIN_BLOCKED_BY_BSA_BOILERPLATE_NOTICES =
ImmutableList.of(DOMAIN_BLOCKED_BY_BSA_NOTICE);
/** Boilerplate remarks required by nameserver and entity responses. */
static final ImmutableList<Notice> NAMESERVER_AND_ENTITY_BOILERPLATE_NOTICES =
ImmutableList.of(CONFORMANCE_NOTICE);
/**
* Required by ICANN RDAP Profile section 1.4.9, as corrected by Gustavo Lozano of ICANN.
*
* <p>Also mentioned in the RDAP Technical Implementation Guide 3.6.
*
* @see <a href="http://mm.icann.org/pipermail/gtld-tech/2016-October/000822.html">Questions about
* the ICANN RDAP Profile</a>
*/
/** Required by the RDAP Technical Implementation Guide 3.6. */
static final Remark SUMMARY_DATA_REMARK =
Remark.builder()
.setTitle("Incomplete Data")
@@ -109,14 +89,7 @@ public class RdapIcannStandardInformation {
.setType(Remark.Type.OBJECT_TRUNCATED_UNEXPLAINABLE)
.build();
/**
* Required by ICANN RDAP Profile section 1.4.8, as corrected by Gustavo Lozano of ICANN.
*
* <p>Also mentioned in the RDAP Technical Implementation Guide 3.5.
*
* @see <a href="http://mm.icann.org/pipermail/gtld-tech/2016-October/000822.html">Questions about
* the ICANN RDAP Profile</a>
*/
/** Required by the RDAP Technical Implementation Guide 3.5. */
static final Notice TRUNCATED_RESULT_SET_NOTICE =
Notice.builder()
.setTitle("Search Policy")
@@ -144,43 +117,4 @@ public class RdapIcannStandardInformation {
/** Possibly incomplete notice as a singleton list, for easy use. */
static final ImmutableList<Notice> POSSIBLY_INCOMPLETE_NOTICES =
ImmutableList.of(POSSIBLY_INCOMPLETE_RESULT_SET_NOTICE);
/**
* Included when requester is not logged in as the owner of the contact being returned.
*
* <p>Format required by ICANN RDAP Response Profile 15feb19 section 2.7.4.3.
*/
static final Remark CONTACT_PERSONAL_DATA_HIDDEN_DATA_REMARK =
Remark.builder()
.setTitle("REDACTED FOR PRIVACY")
.setDescription(
"Some of the data in this object has been removed.",
"Contact personal data is visible only to the owning registrar.")
.setType(Remark.Type.OBJECT_REDACTED_AUTHORIZATION)
.addLink(
Link.builder()
.setRel("alternate")
.setHref(
"https://github.com/google/nomulus/blob/master/docs/rdap.md#authentication")
.setType("text/html")
.build())
.build();
/**
* Included in ALL contact responses, even if the user is authorized.
*
* <p>Format required by ICANN RDAP Response Profile 15feb19 section 2.7.5.3.
*
* <p>NOTE that unlike other redacted fields, there's no allowance to give the email to authorized
* users or allow for registrar consent.
*/
static final Remark CONTACT_EMAIL_REDACTED_FOR_DOMAIN =
Remark.builder()
.setTitle("EMAIL REDACTED FOR PRIVACY")
.setDescription(
"Please query the RDDS service of the Registrar of Record identifies in this output"
+ " for information on how to contact the Registrant of the queried domain"
+ " name.")
.setType(Remark.Type.OBJECT_REDACTED_AUTHORIZATION)
.build();
}

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