1
0
mirror of https://github.com/google/nomulus synced 2026-01-16 02:33:16 +00:00

Compare commits

..

835 Commits

Author SHA1 Message Date
Ben McIlwain
c187c92ae4 Allow creation of hostnames on .zz-- style TLDs for RST (#2935)
This is a follow-on to PR #2909, which fixed the issue for domains, but
apparently not fully for hostnames.

BUG= http://b/476144993
2026-01-15 20:37:32 +00:00
gbrodman
22ca4e3f2b Disable old fee extensions in non-prod envs (#2933)
The primary annoyance with this is that it means we need (or at least,
should) split all tests that use the fee extension into two separate
tests -- one that simulates non-prod environments, and one that
simulates prod environments. This leads to duplication of many tests but
that's fine since this is theoretically temporary.
2026-01-14 19:04:22 +00:00
Nilay Shah
f27136458a Configure cloud scheduler to trigger MoSAPI SLA status to cloud monitoring (#2926)
* Configure cloud scheduler to trigger MoSAPI SLA status to cloud monitoring in production

- We have kept this job to trigger for every 3 minutes so that we get near to real time update for our task.
- This will not trigger metrics for now as we have not written Metrics triggering logic yet
- Logs are added

* Change Trigger scheduling from 3 minutes to 5 minutes
2026-01-13 18:48:43 +00:00
Ben McIlwain
d8e647316e Remove contact as a supported object type in EPP (#2932)
This primarily affects the EPP greeting. We already were erroring out when any
contact flows attempted to be run; this should just prevent registrars from even
trying them at all.

This PR is designed to be minimally invasive, and does not remove any of the
contact flows or Jakarta XML/XJC objects/files themselves. That can be done
later as a follow-up.

Also note that the contact namespace urn:ietf:params:xml:ns:contact-1.0 is still
present for now in RDE exports, but I'll remove that subsequently as well.

BUG= http://b/475506288
2026-01-13 17:21:03 +00:00
Ben McIlwain
d6e0a7b979 Change domain update commands to be varipotent by status (#2930)
This means that attempting to add a status that is already present will now
fail, and attempting to remove a status that is not present will also now fail.

This also refactors the existing checks into a single verify method, rather than
having to call three separate methods from every callsite.

BUG= http://b/474645068
2026-01-12 22:12:08 +00:00
Juan Celhay
5725eb95e0 Add Cloud java profiler to nomulus docker images (#2919)
* add cloud profiler to dockerfile and start script

* add apt-get update

* change in cb machine type for nomulus

* fix typo

* add max worker limit to gradle tests

* Switch to root before doing apt-get

* correct dockerfile

* jetty/Dockerfile

* profiler service conditional to kubernetes container name
2026-01-12 15:19:05 +00:00
Pavlo Tkach
aa12998276 Increase console workload memory allocation (#2929) 2026-01-09 19:27:07 +00:00
gbrodman
d415416bc5 Update the fee extension 1.0 and add some tests (#2925)
Many of the actual fee extension changes are based off Weimin's PR
https://github.com/google/nomulus/pull/2912, though this makes some
additional changes based on the XML schema and description from RFC 8748.

This adds tests for the DomainCheckFlow which is the most complex and
thorough user of the fee extension, but we'll want to add further tests
to the other domain flows to make sure they're handled correctly.
2026-01-09 18:09:17 +00:00
gbrodman
3a1068f313 Add indexes on current_package_token in Domain* (#2916)
It just makes it possible to delete allocation tokens, otherwise we need
to do a linear search over the entire Domain and DomainHistory tables if
we ever want to delete something.
2026-01-09 17:55:37 +00:00
gbrodman
69e5d40f04 Forbid no-op domain-NS and host-IP adds/removes (#2928)
The RST testing expects us to fail if they try to remove an IP from a
host that already doesn't that have that IP, or to add one that already
exists (ditto on both for a domain's nameservers). I don't really see an
issue with our previous no-op implementation, but we need to do this to
pass the tests.
2026-01-09 17:55:12 +00:00
gbrodman
64f6cd9af4 Only include fee 1.0 extension in nonprod envs (#2927)
We need to have this enabled in sandbox, but we wish to wait to enable
it for production to make sure that the implementation is correct and
that clients can use it.

Soon we'll want to do something similar (but the opposite) with the old
fee extensions, where we **only** serve them in production (or maybe
unit test as well). That will allow us to pass the RST tests that depend
on only having the fee extension 1.0.
2026-01-08 22:00:39 +00:00
gbrodman
40184689ca Allow for a currency unit in fee:check responses (#2922)
This is / will be required in https://datatracker.ietf.org/doc/rfc8748/.
I split this out from the rest of the fee-extension testing so that it
can be easily visible.
2026-01-07 21:12:20 +00:00
Nilay Shah
826ad85d20 Add endpoint to trigger MoSAPI metrics export (#2923)
This commit introduces a new backend endpoint at `/_dr/task/triggerMosApiServiceState` that initiates the process of fetching the latest service states for all TLDs from the MoSAPI endpoint and exporting them as metrics to Cloud Monitoring.

  The key changes include:
   - A new `TriggerServiceStateAction` class that handles the GET request to the new endpoint.
     - Logic within `MosApiStateService` to concurrently fetch states for all configured TLDs.
     - A new `MosApiMetrics` class (currently a placeholder) responsible for sending the collected states to the monitoring service.
     - Unit tests for the new action and the updated service logic.

This endpoint will be called periodically to ensure that the MosApi service health metrics are kept up-to-date.
2026-01-07 19:13:19 +00:00
gbrodman
2b47bc9b0a Move fee class from extension to item (#2924)
this is coming from the schema https://datatracker.ietf.org/doc/rfc8748/
section 6.1. The class, that we use for "premium" notes, moved from the
command to the object itself.
2026-01-06 19:00:19 +00:00
gbrodman
9555dca8c6 Don't allow loopback IP addresses for hosts (#2920)
I don't know where in the spec these are explicitly disallowed, but it
seems like good practice and we'll fail the RST tests if we don't
disallow them.
2026-01-05 21:29:15 +00:00
Ben McIlwain
49484c06d3 Filter out registrars of type OT&E from RDE escrow deposits (#2921)
The RDE XML schema (which is verified by ICANN's RST) requires the presence of a
numeric IANA identifier, which is always null for OT&E registrars. This change
synchronizes the three types of registrars that must have a null IANA identifier
(see
https://cs.opensource.google/nomulus/nomulus/+/master:core/src/main/java/google/registry/model/registrar/Registrar.java;l=109-142;drc=b1266c95e8d9f8206415d2821929d4161869b699
) with the registrars that are excluded from the RDE deposit. Note that there
are no registrars of type OT&E in prod and I can't think of a reason they would
need to be included in escrow deposits on sandbox.
2026-01-05 21:20:11 +00:00
Nilay Shah
81d222e7d6 Add GetServiceState action for MoSAPI service monitoring (#2906)
* Add GetServiceState action for MoSAPI service monitoring

Implements the `/api/mosapi/getServiceState` endpoint to retrieve service health summaries for TLDs from the MoSAPI system.

- Introduces `GetServiceStateAction` to fetch TLD service status.
- Implements `MosApiStateService` to transform raw MoSAPI responses into a curated `ServiceStateSummary`.
- Uses concurrent processing with a fixed thread pool to fetch states for all configured TLDs efficiently while respecting MoSAPI rate limits.

junit test added

* Refactor MoSAPI models to records and address review nits

- Convert model classes to Java records for conciseness and immutability.
- Update unit tests to use Java text blocks for improved JSON readability.
- Simplify service and action layers by removing redundant logic and logging.
- Fix configuration nits regarding primitive types and comment formatting.

* Consolidate MoSAPI models and enhance null-safety

- Moves model records into a single MosApiModels.java file.
- Switches to ImmutableList/ImmutableMap with non-null defaults in constructors.
- Removes redundant pass-through methods in MosApiStateService.
- Updates tests to use Java Text Blocks and non-null collection assertions.

* Improve MoSAPI client error handling and clean up data models

Refactors the MoSAPI monitoring client to be more robust against
infrastructure failures

* Refactor: use nullToEmptyImmutableCopy() for MoSAPI models

Standardize null-handling in model classes by using the Nomulus
`nullToEmptyImmutableCopy()` utility. This ensures consistent API
responses with empty lists instead of omitted fields.
2026-01-05 15:44:01 +00:00
Weimin Yu
7e9d4c27d1 Use downloaded Gradle distribution on Cloud Build (#2918)
This way we get around the http url and no longer needs public access on
the GCS bucket.
2025-12-30 21:08:04 +00:00
Weimin Yu
f9c22ff1c5 Add RST support in Sandbox (#2917)
* Add RST support in Sandbox

Added RST test label files as resources.

Added a RstTmchUtils class that loads appropriate labels according to
TLD pattern.

Temporarily changed label fetching in production to include the TLD
string, so that the new class may know which set of labels to use.

* Addressing comments

* Addressing comments
2025-12-30 20:59:28 +00:00
gbrodman
2562d582f3 Add more strict hostname validation on host:check flows (#2915)
We do most of these on host create already so we should also do them on
host checks. The only added change is the character validation (our
existing hostnames all match these).
2025-12-30 16:41:56 +00:00
Ben McIlwain
6f0bc1ded9 Add Augmented Latin IDN table to IDN enums (#2914)
This was added in https://github.com/google/nomulus/pull/2884 , but now as of
this PR it can actually be configured and used on a TLD.
2025-12-27 00:57:24 +00:00
gbrodman
db9fc3271d Change EPP errors 2306->2005 for some structural issues (#2911)
2306 signifies something that is syntactically valid but semantically
invalid (like if someone tried to register a .com domain). These errors
are for domain syntax that could never be valid, thus we should throw a
syntax exception instead of a policy exception.
2025-12-26 16:08:04 +00:00
Ben McIlwain
84491fde70 Don't allow underscores in TLD ROID suffixes (#2913)
Per ICANN it's a disallowed character.
2025-12-26 16:01:28 +00:00
Juan Celhay
0519e2ffcf Change gradle memory/workers to avoid OOM in CB (#2910) 2025-12-23 15:49:25 +00:00
gbrodman
85f75494ab Remove implementation of contact flows (#2896)
Now that we have transitioned to the minimum dataset, we no longer
support any actions on contacts (and by the time this is merged /
deployed, all contacts will be deleted). We should just throw an
appropriate exception on all contact-related flows. We don't delete the
flows themselves, so that we can have an appropriate error message.

We also keep all the flows and XML templates around individually for now because we may be
required to continue to differentiate the requests in ICANN activity
reporting (e.g. srs-cont-create vs srs-cont-delete)
2025-12-23 15:38:24 +00:00
Ben McIlwain
cbba91558a Allow double hyphens in 3rd&4th position in all domain operations (#2909)
This is a follow-up to PR #2908, which relaxed this restriction on bare TLDs
only, but now we also allow it systemwide on domains and hostnames as well.  The
rules against hyphens in these positions are still enforced on all parts of the
domain name except the last one. Correct handling of multi-part TLDs in this
regard is out of scope in this PR; a multi-part TLD that looked something like
".zz--foobar.foobar" would still fail validation. (But of course you cannot a
priori know just from looking at a 3-part string whether it might be a hostname
on a normal TLD, or a domain name on a 2-part TLD.)

This also has some annoying interactions with a trailing dot (indicating the
root), which need to be preserved, but otherwise don't affect how TLD validation
is handled.

BUG= http://b/471013082
2025-12-23 00:57:57 +00:00
Ben McIlwain
c24f09febc Don't call canonicalizeHostname() on nomulus command TLD args (#2908)
The canonicalizeHostname() helper method is only suitable for use with domain
names or host names. It does not work on bare TLDs, because a bare TLD can
have hyphens in the third and fourth position without necessarily being an IDN.
Note that the configure TLD command already correctly allows TLDs with such
names to be created.

Note that we are still enforcing that the TLDs to be added exist, so they have
to pass all TLD naming requirements that are enforced on creating TLDs, and we
are still lowercasing the TLD names passed as arguments here (though we're no
longer punycoding them, although arguably that's not super useful on
command-line params anyway).

BUG= http://b/471013082
2025-12-22 21:34:55 +00:00
Weimin Yu
fd51035f23 Stop depending on GCS public access for Kokoro (#2907)
We used to publish test artifacts to a Maven repo on GCS, for use by
schema tests. For this to work with Kokoro, the GCS bucket must be
accessible to all users.

To comply with the no-public-user requirement, we store the necessary
jars at at well-known bucket and map them into Kokoro. This strategy
cannot be used on the Maven repo because only a small number of files
with fixed names may be mapped. With the Maven repo, there are too many
files to map.
2025-12-17 20:55:03 +00:00
gbrodman
90eb078e3f Add a BulkDomainTransferCommand (#2898)
This is a decently simple wrapper around the previously-created
BulkDomainTransferAction that batches a provided list of domains up and
sends them along to be transferred.
2025-12-12 21:15:47 +00:00
gbrodman
2a94bdc257 Add a command to delete feature flags (#2904)
This allows us to delete old ones to avoid confusion, and so that we can
more easily clean up the codebase.
2025-12-11 21:52:59 +00:00
gbrodman
50fa49e0c0 Always act as if contacts are prohibited (#2897)
This PR finds instances where we previously checked if the feature flag
for contacts-prohibited was set and removes those checks, making the
contacts-prohibited behavior the only behavior. Because the tests didn't
have that feature flag set, this means we need to change a ton of tests
to remove contact references.
2025-12-11 19:48:26 +00:00
gbrodman
a581259edb Remove trailing slash in schema-deploy script (#2903) 2025-12-11 18:32:09 +00:00
Pavlo Tkach
fcdac3e86e Update nomulus-frontend.yaml memory requests (#2900) 2025-12-10 22:36:28 +00:00
Nilay Shah
b652f81193 Refactor MosApiTLSKeySecretName configuration to the correct name (#2899) 2025-12-10 11:28:46 +00:00
Nilay Shah
d98d65eee5 Add mosapi client to intract with ICANN's monitoring system (#2892)
* Add mosapi client to intract with ICANN's monitoring system

This change introduces a comprehensive client to interact with ICANN's Monitoring System API (MoSAPI). This provides direct, automated access to critical registry health and compliance data, moving Nomulus towards a more proactive monitoring posture.

A core, stateless MosApiClient that manages communication and authentication with the MoSAPI service using TLS client certificates.

* Resolve review feedback & upgrade to OkHttp3 client

This commit addresses and resolves all outstanding review comments, primarily encompassing a shift to OkHttp3, security configuration cleanup, and general code improvements.

* **Review:** Addressed and resolved all pending review comments.
* **Feature:** Switched the underlying HTTP client implementation to [OkHttp3](https://square.github.io/okhttp/).
* **Configuration:** Consolidated TLS Certificates-related configuration into the dedicated configuration area.
* **Cleanup:** Removed unused components (`HttpUtils` and `HttpModule`) and performed general code cleanup.
* **Quality:** Improved exception handling logic for better robustness.

* Refactor and fix Mosapi exception handling

Addresses code review feedback and resulting test failures.

- Flattens package structure by moving MosApiException and its test.
- Corrects exception handling to ensure MosApiAuthorizationException
  propagates correctly, before the general exception handler.
- Adds a default case to the MosApiException factory for robustness.
- Uses lowercase for placeholder TLDs in default-config.yaml.

* Refactor and improve Mosapi client implementation

Simplifying URL validation with Guava
Preconditions and refining exception handling to use `Throwables`.

* Refactor precondition checks using project specific utility
2025-12-09 16:29:05 +00:00
gbrodman
28e72bd0d0 Add a BulkDomainTransferAction (#2893)
This will be necessary if we wish to do larger BTAPPA transfers (or
other types of transfers, I suppose). The nomulus command-line tool is
not fast enough to quickly transfer thousands of domains within a
reasonable timeframe.
2025-12-08 20:28:25 +00:00
gbrodman
0777be3d6c Allow superuser ext to override client/server transfer prohibited (#2890)
The superuser can remove/add those statuses anyway, so there's not
really any point. This also saves us trouble if we need to do a BTAPPA
transfer.
2025-12-05 20:22:15 +00:00
Weimin Yu
f9cd167ae4 Copy artifacts for schema tests after deployment (#2895)
After each deployment in sandbox or production, move the artifacts from
the corresponding release to a well-known location so that they can be
mapped to Kokoro in presubmit tests. The Kokoro-mapping does not need
public access to the GCS bucket.

The artifacts include the  postgresql schema jar, the nomulus release
jar, and the uber jar of the nomulus schema integration test classes.

Every jar name consists of a fixed prefix and the environment. Each jar
of a new deployment overrides the previous copy.
2025-12-04 20:55:19 +00:00
sharma1210
eed1886121 Implement rdap_query command (#2886)
* Implement rdap_query command

* modifying and correcting issues

* modifying and correcting issues

* modifying and correcting issues

* resolving comments

* resolving comments

* resolving comments

* resolving comments

* resolving comments

* modifying and correcting issues

* resolving comments

* resolving comments

* resolving comments

* modifying and correcting issues

* modifying and correcting issues

* modifying and correcting issues

* resolving comments

* modifying and correcting issues

* resolving comments

* Fixing Deduplication in test

* Fixing Deduplication in test

* resolving comments
2025-12-01 20:45:57 +00:00
gbrodman
7149fd3307 Remove more references to GAE (#2894)
These are old/pointless now that we've migrated to GKE. Note that this
doesn't update anything in the docs/ folder, as that's a much larger
project that should be done on its own.
2025-12-01 16:43:49 +00:00
Weimin Yu
0dc7ab99d7 Update CreateCdnsTld command for RST Tests (#2891)
Add a flag indicating that a Sandbox TLD should use the production
servers.

No additional TLD name pattern checks. Cloud DNS has an allowlist for
names that may use production servers.

Also updated default descriptive name generation: dropping the trailing
'.', and replacing remaining dots with '_'.
2025-11-25 19:41:44 +00:00
Ben McIlwain
76d4dfbb04 Add "augmented_latin.txt" IDN table in existing txt table format (#2884)
This contains the same codepoints from the
core/src/main/java/google/registry/idn/Latin-IDN.xml file, just in the old .txt
IDN format that Nomulus actually ingests.
2025-11-24 21:26:05 +00:00
gbrodman
8547ad7941 Remove the concept of a GAE service endpoint (#2869)
We don't need to support the mix of GAE and GKE any more so we can get
rid of the GaeService bits and unify everything under one constant
service. This also allows us to reduce the number of services down to
four (FE, BE, PUBAPI, console) which is nice.
2025-11-18 19:31:40 +00:00
gbrodman
b1266c95e8 Add and default to Argon2 hashing (#2877)
We've previously been using Scrypt since PR #2191 which, while being a
memory-hard slow function, isn't the optimal solution according to the
OWASP recommendations. While we could get away with increasing the
parallelization parameter to 3, it's better to just switch to the
most-recommended solution if we're switching things up anyway.

For the transition, we do something similar to PR #2191 where if the
previous-algorithm's hash is successful, we re-hash with Argo2id and
store that version. By doing this, we should not need any intervention
for registrars who log in at any point during the transition period.

Much of this PR, especially the parts where we re-hash the passwords in
Argon2 instead of Scrypt upon login, is based on the code that was
eventually removed in #2310.
2025-11-17 20:11:22 +00:00
Weimin Yu
bc9aab6790 Reformat Fee extension v1.0 schema (#2888)
Reformat the current schema file for RFC 8748 final version. This was
adapted from v0.12 is not fully consistent with the final schema

This helps highlight the differences we missed in PR 2855 when we check
in the official schema.
2025-11-17 15:58:56 +00:00
Ben McIlwain
6cb669a5a7 Remove Tld table field allowed_registrant_contact_ids (#2871)
This is a follow-up to PR #2867, requiring merging/deployment in a subsequent release.

BUG= http://b/448619572
2025-11-14 21:32:36 +00:00
Weimin Yu
0f92e98028 Disable Fee version 1.0 (#2887)
The v1.0 support added in PR 2855 is buggy. Disable it for now.
2025-11-14 20:32:45 +00:00
Ben McIlwain
5f0526c07a Make RDE generation resilient to missing contact rows (#2883)
This will prevent RDE from failing once we delete all contacts, just as a
fail-safe.

BUG= http://b/439636188
2025-11-13 20:09:43 +00:00
gbrodman
759aaddb5f Replace Front/Back-end servlets with single TestServlet (#2874)
The servlets, at this point now that we're off GAE, are only used for
the test server (and, indirectly, in one BSA test). Instead of having
them all remain separate, we can unify them in one test servlet that
lives in the test/ folder.

This removes one avenue of potential confusion w/r/t how request routing
actually works and where we would want to add new routing.
2025-11-12 21:01:14 +00:00
Ben McIlwain
816180f3b3 Remove more vestiges of GAE build (#2881) 2025-11-12 20:31:54 +00:00
Ben McIlwain
bf66b374c6 Address ICANN feedback on proposed Latin IDN table (#2880) 2025-11-10 20:33:29 +00:00
Weimin Yu
666cee1d9f Fix broken release script (#2878)
go/r3pr/2870 deleted release/cloudbuild-deploy.yaml.

Remove references to the file in the release script.
2025-11-07 20:59:05 +00:00
Weimin Yu
d4a70c29a8 Upload compatibility test jars to release folder (#2873)
We need to stop using maven repo on GCS to store artifacts for the
schema compatibility tests. After public access is removed from GCS
buckets, Kokoro won't be able to access it: normal access will be
denied, and the repo is too large to map (copy) to Kokoro VM as a
resource.

This PR uploads the relevant jars to each release's folder. See
go/dr-gcs-public-access-prevention for details.
2025-11-06 15:33:02 +00:00
gbrodman
7b8d07954b Remove more old-console-related files (#2866) 2025-11-05 19:43:59 +00:00
gbrodman
34bea69a48 Remove no-longer-used servlets/components (#2868)
With GKE, we don't need the individual servlets because the services
aren't partitioned out the same way they were in GAE.

We keep FrontendServlet and BackendServlet around for now as they serve
as the backbone for the local RegistryTestServer (for testing things
like the console).

did some cursory tests on alpha and things seem to be unaffected -- I
was able to curl RDAP (pubapi) and create domains
2025-11-05 19:03:45 +00:00
Ben McIlwain
363800bd86 Remove App Engine build/environment scripts (#2870)
It's been over half a year now since we last used any of these and we definitely
no longer have any intentions of ever using App Engine again.

BUG= http://b/457471639
2025-11-05 19:02:24 +00:00
gbrodman
dee132d04b Rename visibleInWhois fields to visibleInRdap (#2863)
Still part of b/454947209, removing references to WHOIS where we can. We
keep the registrar type and the column names (at least for now) because
changing those is much more complicated.
2025-11-04 17:37:44 +00:00
Ben McIlwain
847ef12a4f Remove Tld.allowedRegistrantContactIds field (#2867)
We no longer need this now that no contacts can be applied to any domains at all.

A follow-up PR in subsequent weeks will delete the column from the DB schema.

BUG= http://b/448619572
2025-10-31 15:52:10 +00:00
gbrodman
d9349be18e Modify the way we load resources via foreign keys (#2852)
Previously, we would have separate database calls for mapping from
foreign key to repo ID and then from repo ID to object. This PR modifies
those calls to load the resource directly (the old system was an
artifact of the Datastore key-value storage system).

In this PR, we merge the load-resource-by-foreign-key calls into a
single database load, as well as adding a separate cache object for
(foreign key) -> (resource). Now we cache, and have separate cleaner
code paths, for fk -> resource, fk -> repo ID, and repo ID -> resource.

Also removes the unused RdeFragmenter class
2025-10-29 19:21:27 +00:00
gbrodman
0c74883428 Remove WHOIS references from activity reporting (#2865)
This also changes a screenshot test filename since there wasn't a great
other place to put that change
2025-10-29 16:42:16 +00:00
Weimin Yu
b357fc79f7 Support Fee Extension standard in rfc 8748 (#2855)
* Support Fee Extension standard in rfc 8748

Adding support to the final version of RFC 8748.

Compared with draft-0.12, the only meaningful change is in the namespace.
The rest is either schema-tightening that reflects actual usage, or
optional server-side features that we do not support.

We reuse draft-0.12 tests, only changing namespace uris in the input and
output files for the new version.

* Addressing reviews
2025-10-28 20:54:02 +00:00
gbrodman
754e7fbddc Remove old console soy/js and related files (#2861)
We haven't been serving this for a while, let's finally get rid of them.

We keep some Soy rules around in the presubmits file because we use some
Soy files as XML templates for EPP actions.
2025-10-28 20:34:34 +00:00
Ben McIlwain
ad07b32638 Refactor EppResourceUtils.loadByForeignKey(...) -> ForeignKeyUtils.loadResource(...) (#2864)
This doesn't make any underlying implementation details, and is mainly useful to
reduce the number of diffs in PR #2852 (which does change implementation
details) thus making that easier to review.
2025-10-28 19:47:37 +00:00
Ben McIlwain
8f69b48e87 Add a @GetterDelegate annotation for better handling of ImmutableObject fields (#2860)
This allows us to specify a getter delegation to bypass Hibernate's limitations
on field types for the purposes of, e.g., using a sorted set in toString()
output rather than the base Hibernate unsorted HashSet type.

BUG=http://b/448631639
2025-10-28 17:10:27 +00:00
Ben McIlwain
c33f0dc07f Remove all foreign key constraints pointing to contact tables (#2857)
This affects FKs pointing to both Contact and ContactHistory. This is in
preparation to us deleting all rows in those two tables, and then subsequently
removing all application logic having to do with contacts entirely.
2025-10-27 20:34:50 +00:00
Juan Celhay
969353d4e2 Fix documentation for renew probers cb file (#2862) 2025-10-27 20:24:07 +00:00
gbrodman
6cd351ec7c Remove WHOIS classes and configuration (#2859)
This is steps one and two of b/454947209

We already haven't been serving WHOIS for a while, so there's no point
in keeping the old code around. This can simplify some code paths in the
future (like, certain foreign-key-loads that are only used in WHOIS
queries).
2025-10-27 18:57:25 +00:00
Pavlo Tkach
19e03dbd2e Update proxy and nomulus cluster resources (#2858) 2025-10-24 20:19:41 +00:00
Pavlo Tkach
fc1eb162f2 Remove Primary Contact from users editing screen (#2856) 2025-10-24 20:12:18 +00:00
gbrodman
ed25854fbc Add unique index for not-deleted domain names (#2853)
This is a backstop against multiple domain creations for the same domain
name getting through
2025-10-24 15:38:23 +00:00
Juan Celhay
0aa6bc6aaa Change regex format in release cb file (#2854) 2025-10-23 19:49:29 +00:00
Juan Celhay
ff4c326ebe Delete step to push to release repo, trigger next release steps based on tag format (#2833)
* Change release cb file

* Add brackets around tag variable

* Redo tag matching

* Have tag matcher like the one in cb dev
2025-10-21 18:52:42 +00:00
Pavlo Tkach
51b579871a Anonymize support users in console history, add minor UI updates (#2851) 2025-10-17 18:57:40 +00:00
gbrodman
b144aafb22 Use transaction time for deletion time cache ticker (#2848)
Basically, what happened is that the cache's expireAfterWrite was being
called some number of milliseconds (say, 50-100) after the transaction
was started. That method used the transaction time instead of the
current time, so as a result the entries were sticking around 50-100ms
longer in the cache than they should have been.

This fix contains two parts, each of which I believe would be sufficient
on their own to fix the issue:
1. Use the currentTime passed in in Expiry::expireAfterCreate
2. Use the transaction time in the cache's Ticker. This keeps everything
   on the same schedule.
2025-10-16 20:01:17 +00:00
Weimin Yu
ddd955e156 Fix dependency of Gradle task for schema test (#2849)
Problem not showing up because all use cases run this test after
`build`.
2025-10-16 15:33:28 +00:00
gbrodman
6863f678f1 Allow Gradle to use more heap space (#2847)
During the release process, we are seeing the message "Gradle build daemon disappeared unexpectedly (it may have been killed or may have crashed)" which seemingly can be caused by OOMs
2025-10-13 18:25:08 +00:00
gbrodman
6bd90e967b Add more hash indexes used during common flows (#2845)
I analyzed SQL statements run during the following flows and EXPLAIN
ANALYZEd each of them to figure out if there are any additional hash
indexes we could add that could be particularly helpful. Note: it's not
worth adding a hash index on the host_repo_id field in DomainHost
because so many rows (domains) use the same host.

- domain create
- domain delete
- domain info
- domain renew
- domain update
- host create
- host delete
- host update

I skipped the ones that use the read-only replica, as well as contact
flows (we're getting rid of them), and domain transfer/restore-related
flows as those are extremely infrequent.
2025-10-13 18:07:47 +00:00
gbrodman
5faf3d283c Differentiate between inserts and updates in flows (#2846)
Updates (AKA merges) run an extra SELECT statement to figure out if the
resource exists so that it can merge the entity into the existing object
in Hibernate's schema. When we're inserting new rows (such as new poll
messages or resource creates), we know that we don't need to do that
merge. Doing this should save us some SELECT statements (this has borne
out to be the truth in alpha)
2025-10-13 15:43:18 +00:00
gbrodman
149fb66ac5 Add cache for deletion times of existing domains (#2840)
This should help in instances of popular domains dropping, since we
won't need to do an additional two database loads every time (assuming
the deletion time is in the future).
2025-10-09 17:22:24 +00:00
gbrodman
8c96940a27 Only load from ClaimsList once when filling the cache (#2843) 2025-10-09 16:57:21 +00:00
Ben McIlwain
9c5510f05d Add a rate limiter to remove all domain contacts action (#2838)
The maximum QPS defaults to 10, but can also be specified at runtime through
use of a query-string parameter.

BUG = http://b/439636188
2025-10-02 22:15:19 +00:00
gbrodman
84884de77b Verify existence of TLDs and registrars for tokens (#2837)
Just in case someone makes a typo when running the commands
2025-10-02 20:10:58 +00:00
Ben McIlwain
d6c35df9bc Ignore single domain failures in remove contacts from all domains action (#2836)
When running the action in sandbox on 1.5M domains, it failed a few times
updating individual domains (requiring a manual restart of the entire action).
It's better to just log the individual failures for manual inspection and then
otherwise continue running the action to process the vast majority of other
updates that won't fail.

BUG = http://b/439636188
2025-10-02 18:58:23 +00:00
Juan Celhay
7caa0ec9d6 Add environment configuration files to .gitignore (#2830)
* Add environment configuration files to .gitignore

* Delete config files from repo

* Refactor release cb file to delete config file lines from gitignore

* Reorder env files

* Add README for config files
2025-10-02 18:36:43 +00:00
Weimin Yu
ee3866ec4a Allow top level tld creation in Sandbox (#2835)
Add a flag to the CreateCdnsTld command to bypass the dns name format
check in Sandbox (limiting names to `*.test.`). With this flag, we
can create TLDs for RST testing in Sandbox.

Note that if the new flag is wrongly set for a disallowed name, the
request to the Cloud DNS API will fail. The format check in the command
just provides a user-friendly error message.
2025-10-01 14:20:33 +00:00
gbrodman
97d0b7680f Add hash indexes for common use cases (#2834)
I went through all the SQL statements generated by some sample
DomainCreateFlow and DomainDeleteFlow cases to find situations where we
were either SELECTing from, or UPDATEing, tables with a direct "field =
value" format. These are the situations that I found where we can add
hash indexes. This does two things:

1. Makes these queries slight faster, since these are usually queries on
   columns that are either unique or very close to unique, and O(1) is
   faster than O(log(n))
2. Spreads around the optimistic predicate locks on the previously-used
   btree indexes. Many of our serialization errors came from the fact
   that we were autogenerating incrementing ID values for various
   tables, meaning that SELECTs, INSERTs, and UPDATEs would all try to
   take predicate locks out on the same page of the btree index. Using a
   hash index means that the page locks will be spread out to various
   index pages, rather than conflicting with each other.

Running load tests on alpha I see significant improvements in speed and
error rates. Speed is hard to quantify due to the nature of the way the
load tests distribute tasks among the queues but it could be more than
50% improvement, and serialization errors in the logs drop by more than
90%.
2025-09-29 22:16:24 +00:00
Pavlo Tkach
5700a008d6 Add console history frontend (#2832) 2025-09-26 21:25:03 +00:00
Ben McIlwain
dc9f5b99bc Add a batch action to remove all contacts from domains (#2827)
This implements the first part of Minimum Data Set phase 3, wherein we delete
all contact data. This action is necessary to leave a permanent record on the
domain (in the form of a domain history entry) documenting when the contacts
were removed by the administrative user.

Then, after this has finished removing all contact assocations, we can simply
empty out or drop the Contact/ContactHistory tables and associated join tables.
2025-09-25 20:47:17 +00:00
Ben McIlwain
d3c6de7a38 Modify the base Latin LGR with our intended changes to improve security (#2829) 2025-09-24 21:04:37 +00:00
Ben McIlwain
3c3303c16a Add ICANN's reference Latin LGR in RFC 7940 XML format (#2828)
In the next commit I will make changes to this file so it supports just the
basic Latin characters that we want, but it's good to check the base version in
so that we can see diffs.

This was downloaded from https://www.icann.org/sites/default/files/packages/lgr/lgr-second-level-latin-script-25oct24-en.xml
2025-09-19 16:42:46 +00:00
Nilay Shah
2a86a1bbe9 Skip user loading for proxy service account (#2825)
* Skip user loading for proxy service account

Reduces database load by skipping the User entity lookup for the proxy
service account during OIDC authentication.

The high volume of EPP "hello" and "login" commands from the proxy
service account results in a constant database load. These lookups
are unnecessary as the proxy service account is not expected to have a
corresponding User object.

This change optimizes the authentication flow by checking for the proxy
service account email *before* attempting to load a User from the
database. This bypasses the database transaction entirely for these
high-volume requests.

This approach is more efficient than caching, as it eliminates the
database lookup for the proxy service account altogether, rather than
just caching the result.

* comment added and service account llokup time improved

* comment updated for more clarity
2025-09-16 18:48:39 +00:00
gbrodman
ea148ac13e Show success message on password reset (#2826) 2025-09-16 18:39:19 +00:00
Nilay Shah
06299ccb86 Add cache for User entities in OIDC auth flow (#2822)
* Add cache for User entities in OIDC auth flow

* refactor: Address review feedback

- Refactor database call into a single, reusable method
- Increase the default cache size to 200
- Remove .recordStats() and using spy for testing
- Split unit tests into separate implementation test that use Mockito spies instead of checking internal cache stats
2025-09-12 07:43:32 +00:00
gbrodman
732c30b359 Remove registry-lock-related fields from RegistrarPoc (#2818)
We've moved these over to the User class, so we should remove these for
clarity. In addition, we should make it clear (in Java at least) that
the field in the RegistryLock object refers to the email address used
for the lock in question.
2025-09-11 15:29:06 +00:00
gbrodman
ee5a2d3916 Include internal registrars in the console (#2821)
This allows us to also check / modify the CharlestonRoad registrar in
the console, and also allows us to test actions (like password reset)
using that registrar in the prod environment.
2025-09-05 20:37:23 +00:00
gbrodman
2b5643df4c Sort registrars list in console (#2820)
This was bugging me slightly
2025-09-05 18:44:17 +00:00
Pavlo Tkach
6bbd7a2290 Update proxy resources, increase ssl handshake timeout (#2819) 2025-09-05 18:09:55 +00:00
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
gbrodman
56cd2ad282 Change AllocationToken behavior in non-catastrophic situations (#2730)
We're changing the way that allocation tokens work in suboptimal (i.e. incorrect) situations in the domain check, creation, and renewal process. Currently, if a token is not applicable, in any way, to any of the operations (including when a check has multiple operations requested) we return some variation of "Allocation token not valid" for all of those options. We wish to allow for a more lenient process, where if a token is "not applicable" instead of "invalid", we just pass through that part of the request as if the token were not there.

Types of errors that will remain catastrophic, where we'll basically return a token error immediately in all cases:
- nonexistent or null token
- token is assigned to a particular domain and the request isn't for that domain
- token is not valid for this registrar
- token is a single-use token that has already been redeemed
- token has a promotional schedule and it's no longer valid

Types of errors that will now be a silent pass-through, as if the user did not issue a token:
- token is not allowed for this TLD
- token has a discount, is not valid for premium names, and the domain name is premium
- token does not allow the provided EPP action

Currently, the last three types of errors cause that generic "token invalid" message but in the future, we'll pass the requests through as if the user did not pass in a token. This does allow for a default token to apply to these requests if available, meaning that it's possible that a single DomainCheckFlow with multiple check requests could use the provided token for some check(s), and a default token for others.

The flip side of this is that if the user passes in a catastrophically invalid token (the first five error messages above), we will return that result to any/all checks that they request, even if there are other issues with that request (e.g. the domain is reserved or already registered).

See b/315504612 for more details and background
2025-04-23 15:09:37 +00:00
gbrodman
0472dda860 Remove transaction duration logging (#2748)
We suspected this could be a cause of optimistic locking failures
(because long transactions would lead to optimistic locks not being
released) but this didn't end up being the case. Let's remove this to
reduce log spam.
2025-04-22 18:53:21 +00:00
gbrodman
083a9dc8c9 Remove old console history Java classes (#2726)
1. This doesn't remove the SQL tables yet (this is necessary to pass
   tests and also good practice just in case we need or want to look at
history for a little bit)
2. This also removes the Registrar, RegistrarPoc, and User base classes
   that were only necessary because we were saving copies of those
objects in the old history classes.
2025-04-18 22:05:29 +00:00
gbrodman
0153c6284a Add user objects for local test server (#2744)
Also don't try to do anything related to Google admin directory objects
when running the local test server, for obvious reasons
2025-04-18 15:48:06 +00:00
Pavlo Tkach
ca240adfb6 Add new last_poc_verification_date field to Registrar object (#2746) 2025-04-17 19:41:10 +00:00
Pavlo Tkach
b17125ae9a Disable k8s whois routing (#2740) 2025-04-17 15:20:32 +00:00
Pavlo Tkach
dfef733360 Incerase memory request for pubapi and frontend to 1Gi (#2743) 2025-04-11 16:17:43 +00:00
Pavlo Tkach
04a0659197 Disable console whois (#2741) 2025-04-11 15:32:34 +00:00
Pavlo Tkach
70010886b1 Increase hikari maximum pool size to 20 (#2742) 2025-04-10 20:51:51 +00:00
gbrodman
3cd50dc929 Only use GKE logs in ICANN reports (#2738)
We no longer need to union GKE+GAE logs since we've moved all production
traffic to GKE only.

For testing, I copied the affected *_test.sql files to Bigquery, removed
all the "-alpha" bits, and changed the dates to 20250301 and 20250331
and ran them to make sure they returned the expected data.
2025-04-09 17:12:02 +00:00
Pavlo Tkach
03872b508f Exclude prober endoint from sed command canary (#2739) 2025-04-07 21:13:13 +00:00
Pavlo Tkach
1096f201cd Add GKE readiness probe (#2735) 2025-04-04 21:33:43 +00:00
gbrodman
9dc3215624 Redirect an empty RDAP path to the /help response (#2722)
The behavior when someone hits the plain RDAP base URL isn't specified
by the spec. Currently we just return a plain 404 which isn't
particularly nice or helpful -- so it would probably be nicer to just
redirect to the /help response instead.

tested on alpha,
https://pubapi-dot-domain-registry-alpha.appspot.com/rdap redirects to https://pubapi-dot-domain-registry-alpha.appspot.com/rdap/help
2025-04-03 15:37:23 +00:00
Lai Jiang
af321fb65e Make frontend deployment auto scale (#2736)
Now that we have effective global sessions thanks to #2734, there is no
longer a need to keep the number of pods on the EPP service static.

We are also not vulnerable to random pod restarts. K8s never guarantees
perpetual pod lifetime anyway, and not having to be at its mercy is
certainly a relief.
2025-04-02 18:58:52 +00:00
Lai Jiang
c5132c04be Use pipe as extension URI separator (#2737)
It turns out period can be used in the URI, such as in
"urn:ietf:params:xml:ns:fee-0.12". I don't think pipe is used, at least
not according to EPP URI namespace naming convention.

Ideally we'd use serialization, but using the default serialization runs
the risk of it being platform/JDK dependent, so a new deployment might
not be able to deserialize existing cookies. A custom serializer that
guarantees stability would have been needed.
2025-04-02 13:21:13 +00:00
Lai Jiang
a64dc21f96 make the deploy task deploy to GKE (#2734)
Also always pulls the latest images from repos instead of relying on
local cases. This makes it so that a local docker build is always fresh.
2025-03-31 22:38:53 +00:00
Pavlo Tkach
0381533a35 Set grace period to 1s for immediate pods restart (#2733) 2025-03-31 19:15:13 +00:00
Lai Jiang
4999a72d96 Save session data directly in a cookie (#2732) 2025-03-31 16:21:50 +00:00
Pavlo Tkach
2d072c3844 Update jetty console static files cache policies (#2731) 2025-03-28 19:53:02 +00:00
Pavlo Tkach
c15dec4419 Downgrade node type for pubapi and console, enable bursting for frontend and backend (#2723) 2025-03-28 19:14:33 +00:00
gbrodman
8340125bf4 Remove user FKs from console history tables (#2729)
This, obviously, can mess up user deletion
2025-03-25 20:47:47 +00:00
Pavlo Tkach
98ba80d94e Remove console security settings timeout (#2728) 2025-03-25 19:36:52 +00:00
gbrodman
967d04efce Include TLD in reserved/registered lists too (#2725)
We already do this for premium terms, but it's nice to do it for the
other list types too

https://b.corp.google.com/issues/390053672
2025-03-24 15:52:12 +00:00
gbrodman
20fd944e83 Remove allocation token custom logic (#2727)
This was added back in early 2018 long ago to enable promotions, but
since then (and for many years) we've added the ability to run
promotions on the tokens themselves, rather than relying on custom Java
classes.

This will make the changes for b/315504612 much easier, as that will
split up token validation into "is this token valid in general?" and "is
this token valid for this domain/action?"
2025-03-21 20:48:54 +00:00
gbrodman
daa56e6d85 Bump the number of retries in transaction failures and add skew (#2699)
This can potentially help even more with serializable transaction
failures (optimistic locking exceptions, which are expected to occur
somewhat frequently).

With six attempts, we will sleep at most five times, for
100+200+400+800+1600 ms each, for a total of at most 3.1 seconds (much
less than the EPP maximum which I believe (?) to be 30 seconds.

In addition, we add a 20% skew in an attempt to spread out
possibly-conflicting transaction retries.
2025-03-21 19:47:55 +00:00
gbrodman
ed33c7424d Add and use new SimpleConsoleUpdateHistory table (#2712)
This changes the code to only save console histories of this type. We
keep the old Java code (and, necessarily, the corresponding SQL code)
for now because there's no harm in doing so and we want to avoid hastily
deleting too much.
2025-03-21 14:46:16 +00:00
Ben McIlwain
04b30f5c04 Fix handling of negative values in monthly transaction reporting (#2704)
The SQL statement was incorrectly flooring to zero one layer too deep, which was
negating all negative transaction report rows (which occur most frequently when
a domain in the autorenew grace period is deleted). I've changed it so that it
now only floors to zero at the report level, which still solves the issue
reported in http://b/290228682 but whose original fix caused the issue
http://b/344645788

This bug was introduced in https://github.com/google/nomulus/pull/2074

I tested this by running the new query against the DB for 2024 Q4 using the
registrar that was having issues and confirmed that the total renewal numbers
for .app now match with the sum total of what we invoiced for the last three
months of 2024.
2025-03-20 21:13:08 +00:00
Lai Jiang
11702bc940 Revert "Add a redirect for the console bare domain (#2718)" (#2724)
This reverts commit 2a01c12b14.
2025-03-19 22:48:31 +00:00
Lai Jiang
2d82646421 Uncap Dagger version (#2721)
The latest version of Dagger (2.55) now supports jakarta.inject.
2025-03-17 14:51:04 +00:00
Lai Jiang
50260dca5f Upgrade to Gradle 8.13 (#2720) 2025-03-15 00:30:32 +00:00
gbrodman
3cc10bfe0d Add a GCB script for monitoring ZFA accessibility (#2719)
This doesn't check for correctness (we have other scripts that do that)
but just that the service is available at all (the other scripts do not
do that).

This should, and will, be configured with a scheduled trigger in GCB (for us, in
the domain-registry-dev project) and configuration to send some sort of
pub/sub notification on failure (for us, this is already set up on
domain-registry-dev and it sends messages to the "Domain Registry
Notifications" chat channel.
2025-03-14 20:35:39 +00:00
Pavlo Tkach
5645b2e218 Embed Google Sans font (#2716) 2025-03-14 19:08:12 +00:00
Lai Jiang
2a01c12b14 Add a redirect for the console bare domain (#2718) 2025-03-14 18:16:25 +00:00
Lai Jiang
93d77e558f Update README (#2717) 2025-03-14 15:46:42 +00:00
Lai Jiang
92ebd0dedb Build different console versions for different environments (#2715)
TESTED=deployed to alpha
2025-03-11 23:39:28 +00:00
Lai Jiang
b49e37feee Add a GCB job to delete GAE canary versions (#2714)
We've seen this issue happen more often than not recently, where GAE
canary deployment is stuck for about 10 min and the failed. The reason
is not clear, but delete the canary version prior to a deployment always
fixes the issue.
2025-03-11 14:14:11 +00:00
Lai Jiang
bede56598c Fix console build for GKE (#2713)
We use the $environment property to set the console config. If it is not
given, 'alpha' is used, which has the same effect as 'production'.

TESTED=ran :jetty:copyConsole with
-Penvironment=(sandbox|production|alpha) and checked the resulting js
file.
2025-03-11 00:03:12 +00:00
Lai Jiang
467d9c7bf1 Fix cookie logging logic (#2711)
Make the logic more robust by using regex capture groups.
2025-03-10 23:10:03 +00:00
gbrodman
e5ebe96c74 Add SQL code for simplified console update history table (#2710)
We'll remove the old ones, but this one adds the new simplified version
2025-03-07 19:40:19 +00:00
gbrodman
2ff4d97b0a Refactor console bulk domain action types (#2708)
This makes the action types a bit simpler -- this is possible because
we've reduced the scope of domain actions that we want to natively
support
2025-03-07 18:12:32 +00:00
gbrodman
6b0beeb477 Add BSA label to rdap-domain 404 responses for BSA domains (#2706) 2025-03-07 13:58:18 +00:00
Lai Jiang
d2d43f4115 Fix a Cloud Scheduler deployment bug (#2707)
For GKE all tasks should be on backend, BSA was on its own service
because of egress IP constraint.

Also made it possible to specify a timeout for the Cloud Scheduler job,
with the default (3m) suitable for most tasks.
2025-03-06 16:25:52 +00:00
Lai Jiang
12fd206c35 Update README.md (#2705) 2025-03-05 16:55:04 +00:00
Lai Jiang
a3f510d0db Log session cookies in metadata (#2703)
There are two session cookies, JSESSIONID, which is set by Jetty, and
GCLB, which is set by the Gateway.

In one session, every request other than the first one (the <hello>)
should have the same GCLB value, and every request after a successful
<login> should have the same JSESSIONID.

With these two metadata, we should be able to trace all requests that
*should* belong to the same session and debug issues with session
mismatch (if any).
2025-03-04 20:18:15 +00:00
gbrodman
fa54c26ee2 Log transaction durations (#2682)
There can be delays in releasing predicate locks when we have
transactions that are long-lived -- even delays in releasing predicate
locks acquired by shorter-lived transactions. Logging the transaction
duration will allow us to get a sense as to transaction durations during
busy times.
2025-03-04 13:15:15 +00:00
gbrodman
8896fb94f4 Use nomulus-gke tagging mechanism in sql-int tests (#2702)
Had to temporarily create the files in
gs://domain-registry-dev-deployed-tags but the automated release process
will take care of that soon
2025-03-04 04:05:53 +00:00
Pavlo Tkach
6c7bf5e5dd Enable Users and Domains actions, add email notification (#2700) 2025-02-28 21:57:49 +00:00
Pavlo Tkach
ea1e8d5cc5 Add console gzip compression to js,css and html files (#2696) 2025-02-27 22:52:10 +00:00
Lai Jiang
7fb846c5b0 Add headers to record WHOIS client IPs (#2695)
The headers can be used by Cloud Armor to perform IP-based rate
limiting.
2025-02-27 22:15:13 +00:00
Lai Jiang
5180095cb6 Reduce log level to info when no email is found from the OIDC token (#2694)
This can happen on public endpoints (in pubapi) where the service is
behind IAP but all users (including not-logged-in ones) are allowed. IAP
will add an OIDC token with no email field in the request header.
2025-02-26 22:17:45 +00:00
Lai Jiang
9fe64bf9ec Make ignoreLinesStartingWith varargs (#2691)
It still is a list, because we String::startsWith does not benefit from
the target being in a set.
2025-02-26 17:12:24 +00:00
Lai Jiang
0f3b62d5ce Change the sleep time between proxy rollout (#2689) 2025-02-26 04:48:52 +00:00
Ben McIlwain
bd4701647b Refactor logic out of domain create flow tests (#2688)
This removes logic from an inner helper method so that it becomes more clear
from callsites within each test exactly which behavior is expected from those
test conditions.
2025-02-25 19:54:56 +00:00
Lai Jiang
fb816d7a2c Make it possible to ignore comment lines when comparing schemas (#2690)
We now pin to postgreSQL v17 when running tests, which means that minor
version might increase without our intervention. This causes (at least)
the comment in the golden schema to change, and failing the test as a
result.

This PR adds the ability to strip lines that we deem as comment from the
comparison, so we don't have to do trivial upgrades to the gold schema
whenever there's minor version upgrade.
2025-02-25 16:58:26 +00:00
gbrodman
8fbf363195 Remove unused dummy PGP file (#2687)
This was previously used as a dummy value for testing / compilation but
it's not used any more.
2025-02-24 21:45:26 +00:00
Lai Jiang
397f800614 Connect to GKE by default from the tool (#2686) 2025-02-24 19:01:05 +00:00
Lai Jiang
bcf42bd287 Use static IPs for EPP endpoints (#2685)
These IPs are now provisioned by Terraform. Also delete the
get-endpoints.py script as it is no longer necessary.
2025-02-24 16:38:47 +00:00
Pavlo Tkach
ed95d19b93 Provide prompt for user deletion UI (#2684) 2025-02-21 20:30:03 +00:00
Lai Jiang
97fc2c0b66 Add an annotation to the deployment (#2683)
This allows us to easily tell which tag was deployed.

Also set the gateway to use named address so they are stable, and so
that we can attach an IPv6 record to it. Auto-provisioned addresses are
IPv4 only.
2025-02-21 16:30:32 +00:00
Weimin Yu
00728c40ba Abort schema verifier when pg_dump fails (#2681)
Failed pg_dump may not leave a file, failing the subsequent diffing and
causing the verifier to return success.

The verifier should abort in this case.
2025-02-20 17:35:47 +00:00
Lai Jiang
3f2a42ab8d Expose EPP via saidcar proxy (#2680) 2025-02-19 18:57:25 +00:00
Lai Jiang
b73e342820 Update PostgreSQL version in builder image and tests (#2667) 2025-02-18 17:34:41 +00:00
Lai Jiang
df7fec7a3e Update RDAP TOS link (#2678) 2025-02-18 17:00:26 +00:00
Lai Jiang
6f7ae1eabc Redirect HTTP to HTTPS (#2679)
This opens up port 80 on the load balancer IP and upgrades all HTTP
request to HTTPS.

TESTED=tested on alpha.
2025-02-18 16:57:18 +00:00
Lai Jiang
eb978ebbd5 Let nomulus tool connect to sandbox GKE by default (#2674) 2025-02-16 18:10:03 +00:00
Pavlo Tkach
95831bc8b7 Add suspend / unsuspend to the console (#2675) 2025-02-14 20:41:19 +00:00
Lai Jiang
538260521b Update Nomulus deployment script (#2677)
We only deploy to the us-central1 cluster in order to minimize database
locality issue.
2025-02-14 17:31:18 +00:00
Pavlo Tkach
612708f0a8 Fix console user creation role param (#2676) 2025-02-14 13:51:06 +00:00
Lai Jiang
e78de98060 Read GKE logs in ICANN reports (#2673)
GKE logs are routed to a different dataset and the table is different.
The structs to look for are also different (jsonPayload vs textPayload
or protoPayload).

TESTED=Ran the resulting query in crash.
2025-02-12 20:41:44 +00:00
Lai Jiang
c918258fb1 Make a best effort attempt to support multiple CPU architectures (#2672)
I obtained access to an IBM s390x VM so I thought I'd see how multi-arch
Nomulus is.

Our main application is in Java so it is already multi-arch, but several
tests use docker images that are by default x64. Luckily postgres has an
s390x port, but selenium does not. So I had to disable Screenshot tests
when the arch is not amd64.
2025-02-07 22:19:42 +00:00
gbrodman
34103ec815 Convert gsutil to gcloud storage (#2670)
Use of gsutil is discouraged / deprecated, see https://cloud.google.com/storage/docs/gsutil
2025-02-07 21:01:19 +00:00
Lai Jiang
a63812160e Upgrade to Gradle 8.12.1 (#2671) 2025-02-07 15:23:02 +00:00
gbrodman
9aaf7ee36a Allow for no fee extension with free premium domains (#2660)
This isn't a situation we'll encounter often, but if the client has an
allocation token that's valid for premium domains that gives a 0 cost,
we shouldn't require them to include the fee extension when creating the
domain. We already don't require it for standard domains.
2025-02-06 20:40:24 +00:00
gbrodman
96a864dbd6 Add pg_stat_statements extension to allowed diffs (#2662)
This is similar to pgaudit in that it doesn't need to exist in the
golden file.
2025-02-06 20:39:59 +00:00
Lai Jiang
8a36fb5f1f Update Cloud Scheduler and Cloud Tasks deployment process (#2666) 2025-02-06 18:53:50 +00:00
Pavlo Tkach
6c138420b0 Fix console nested routes a11y (#2669) 2025-02-05 20:45:21 +00:00
Lai Jiang
08570511f5 Update GCB scripts (#2661) 2025-02-04 19:27:44 +00:00
Pavlo Tkach
e62d970d34 Update console endpoints documentation (#2665) 2025-02-04 17:43:30 +00:00
Lai Jiang
067927b735 Fix GCB failures (#2664)
We start seeing failures such as this one:

https://pantheon.corp.google.com/cloud-build/builds;region=global/843b9bd7-9c09-4221-ae4c-6e2dd2918f04?inv=1&invt=Aborfg&project=domain-registry-alpha

It looks like the inclusion of gcompute-module which itself is a git
repo caused the problem. I don't understand why it wasn't an issue before.
My guess is that GCB started using a newer version of git which is more
strict about this.

TESTED=Tested the GCB build pipeline on alpha.
2025-02-04 17:12:43 +00:00
Pavlo Tkach
4ec2919ce3 Update console dependencies (#2659) 2025-01-31 21:40:37 +00:00
gbrodman
19422075fa Remove nested transactions from domain (un)locking (#2658) 2025-01-31 16:47:44 +00:00
Pavlo Tkach
40b6984ffb Improve console screen reader interaction (#2656) 2025-01-31 16:46:25 +00:00
Lai Jiang
6952e0f653 Fix a typo (#2657) 2025-01-31 02:44:28 +00:00
Lai Jiang
dcb55d27bb Upload gateway related manifests to GCS (#2655) 2025-01-30 16:12:31 +00:00
Pavlo Tkach
765bd9834a Add more accessible names to the console (#2652) 2025-01-29 20:19:00 +00:00
Lai Jiang
221088e738 Upload k8s manifests to GCS (#2654) 2025-01-29 17:07:10 +00:00
gbrodman
6649e00df7 Allow for particular flows to log all SQL statements executed (#2653)
We use this now for the DomainDeleteFlow in an attempt to figure out
what statements it's running (cross-referencing that with PSQL's own
statement logging to find slow statements).
2025-01-29 16:00:19 +00:00
gbrodman
2ceb52a7c4 Handle SPECIFIED renewal price w/token in check flow (#2651)
This is kinda nonsensical because this use case is trying to apply a
single use token multiple times in the same domain:check request --
like, trying to use a single-use token for both create, renew, and
transfer while having a $0 create price and a premium renewal price.

This change doesn't affect any actual business / costs, since SPECIFIED
token renewal prices were already set on the BillingRecurrence
2025-01-28 18:31:29 +00:00
Lai Jiang
120bcc33be Update cloud build configs to build nomulus images (#2650)
Also do appropriate text replacements for each environment.
2025-01-28 16:03:26 +00:00
Pavlo Tkach
8987fd37c2 Improve console accessibility (#2649) 2025-01-26 00:47:53 +00:00
gbrodman
653e092ad4 Add TLD identifier to premium terms filename and header (#2644)
https://b.corp.google.com/issues/390053672

This makes it easier to identify what file you're looking at, at a
glance
2025-01-24 19:54:35 +00:00
gbrodman
5e97a8b412 Refactor console domain actions to exist in separate files (#2638)
This means that we're not storing everything in one file, otherwise it
quickly becomes unwieldy
2025-01-23 16:46:53 +00:00
Weimin Yu
229fcf3946 UrlConnectionException loses error info (#2648)
It does not get the error message for 400+ status codes.

It fails to get the status code if the response has neither data nor
error.
2025-01-23 16:27:03 +00:00
Lai Jiang
b775e4a178 Pull credentials from fleet for all clusters (#2647)
All clusters have switched to using private APIs.
2025-01-22 16:58:56 +00:00
Pavlo Tkach
e3c386a8a7 Add console bulk delete (#2641)
* Add bulk actions to console

* Add console bulk delete

* Add console bulk delete
2025-01-22 15:54:59 +00:00
Lai Jiang
799f0449ad Only pull credential from the fleet on crash (#2645)
Only crash has the policy controller installed for now.
2025-01-21 18:40:52 +00:00
Lai Jiang
bf025445d5 Record http request parameters in log metadata (#2642)
This allows us to search for logs for a given path using a filter like
this:

jsonPayload.httpRequest.requestUrl="/_dr/blah"

TESTED=tested on crash
2025-01-16 17:27:53 +00:00
Lai Jiang
9f22f2e8ae Pull nomulus cluster credentials from the fleet (#2643)
After private endpoint is enabled, we cannot pull the credentials
directly via `gcloud containers cluster get-credentials`.
2025-01-16 15:06:02 +00:00
gbrodman
45c8b81823 Map token renewal behavior directly onto BillingRecurrence (#2635)
Instead of using a separate RenewalPriceInfo object, just map the
behavior (if it exists) onto the BillingRecurrence with a special
carve-out, as always, for anchor tenants (note: this shouldn't matter
much since anchor tenants *should* use NONPREMIUM renewal tokens anyway,
but just in case, double-check).

This also fixes DomainPricingLogic to treat a multiyear create as a
one-year-create + n-minus-1-year-renewal for cases where either the
creation or the renewal (or both) are nonpremium.
2025-01-15 19:55:34 +00:00
Weimin Yu
4cfcc60655 Clean up keyring bindings (#2640)
Remove the config file's `keyring` section and the binding in java code.
2025-01-14 22:06:05 +00:00
Lai Jiang
e4ee63b8f3 Make Cloud Tasks Utils canary-aware (#2639) 2025-01-14 17:39:51 +00:00
Weimin Yu
f8407c74bc Make SecretManagerkeyring the only allowed keyring (#2636)
Remove the support for custom keyrings. There is no pressing use case,
and can be error-prone.
2025-01-13 19:32:24 +00:00
gbrodman
693467a165 Remove duplicate transaction in updateAllocTokens (#2637) 2025-01-13 19:12:06 +00:00
Lai Jiang
cea3da01a0 Expose Web WHOIS redirects (#2634)
We are required to respond to HTTP(S) requests on port 80/443 on the
same domain where we serve port 43 WHOIS requests. The proxy already
does this by redirecting to the web WHOIS lookup page on the marketing
website.

This PR makes it so that requests to port 80/443 can be routed to the
proxy for redirect.

TESTED=tested on crash and the redirect works.
2025-01-10 17:25:16 +00:00
Weimin Yu
c2030e5859 Fix keyring in BEAM pipeline (#2632)
SecretManager based keyring not included in keyring bindings, resulting
in runtime failure.

We should simply keyring bindings. There is no use case for multiple
implementations. See b/388835696.
2025-01-09 20:01:32 +00:00
Lai Jiang
1cbbc660d2 Explicity specify deployment order for queues and scheduler tasks (#2631)
If we deploy Nomulus, we should do that before queues and the scheduler
tasks are updated.
2025-01-08 21:11:24 +00:00
Lai Jiang
e0bbff827e Upgrade to Gradle 8.12 (#2630) 2025-01-08 18:43:10 +00:00
Weimin Yu
10925f2447 Enable nested transaction warning in production (#2628)
Knonw nested transact calls found in sandbox have been refactored away.
Enable logging in production to catch any missing cases. Logging is
throttled at 1 message per minute per VM.
2025-01-03 20:52:25 +00:00
Lai Jiang
7641b05f12 Expose EPP and WHOIS endpoints on reginal load balancers (#2627)
k8s does not have a way to expose a global load balancer with TCP
endpoints, and setting up node port-based routing is a chore, even with
Terraform (which is what we did with the standalone proxy).

We will use Cloud DNS's geolocation routing policy to ensure that
clients connect to the endpoint closest to them.
2024-12-26 15:25:02 +00:00
Weimin Yu
d130e74004 Use sql instance name in SecretManager (#2625) 2024-12-18 18:39:23 +00:00
Lai Jiang
c9c61e4f17 Write GKE metrics with the apprioate labels (#2626)
Also makes preperations to expose the sidecar proxy.
2024-12-18 16:15:54 +00:00
Lai Jiang
da8df1f4d9 Make GKE the default in alpha and qa (#2624) 2024-12-17 17:40:03 +00:00
Pavlo Tkach
f649d960c1 Add user email prefix to the console user create (#2623) 2024-12-13 19:47:21 +00:00
Weimin Yu
e5ebc5a2bb Save Cloud SQL connection names in Keyring (#2622)
This eliminates the need to make a new release after database disaster
recovery.
2024-12-13 16:18:15 +00:00
Lai Jiang
f9d2839590 Add necessary changes to provision QA with Terraform (#2618)
Also programmatically determine backend service IDs.
2024-12-12 18:39:18 +00:00
gbrodman
c6a6bc7e25 Drop FKs referencing DomainHistory (#2621)
- We never delete rows from DomainHistory (and even if we do in the
  future, they'll be old / the references won't matter)
- This is likely creating lock contention when lots of requests come
  through at once for domains with many DomainHistory entries
2024-12-10 18:46:48 +00:00
gbrodman
fce126d426 Update Flyway versions to 11.x+ (#2620)
There are some breaking method changes in the 10.x.y versions and we're encountering exceptions when trying to run the flywayMigrate task thanks to those.
2024-12-10 03:02:02 +00:00
gbrodman
8e41278717 Include GP statuses in RDAP results (#2606)
We do this for WHOIS results so we should do it for RDAP results as well
(especially since they're mostly already included in the response
profile).
2024-12-09 19:55:16 +00:00
Lai Jiang
cb3738d540 Upgrade to Gradle 8.11.1 (#2619) 2024-12-09 18:29:57 +00:00
Pavlo Tkach
71afc25110 Fix console new user screen layout (#2617) 2024-12-05 18:17:52 +00:00
Pavlo Tkach
fa377733be Allow adding existing users to registrar (#2616) 2024-11-27 22:40:32 +00:00
gbrodman
21950f7d82 Add a bulk-domain-action console endpoint (#2611)
For now it only includes two options (domain deletion and domain
suspension). In the future, as necessary, we can add other actions but
this seems like a relatively simple starting point (actions like bulk
updates are much more conceptually complex).
2024-11-22 20:47:47 +00:00
Ben McIlwain
e66aee0416 Downgrade the tx isolation level of poll message ack flow (#2615)
This might help alleviate DB transaction contention on the PollMessage table. A
lower transaction isolation level is safe because acking a poll message is
idempotent: there are only two things it does, either delete a poll message or
take a recurring one from the past and set it to be a year in the future from
the date in the past. Both of these operations will always yield the same final
result even if executed multiple times simultaneously for some reason.
2024-11-22 19:48:19 +00:00
Ben McIlwain
c7e1fc17d2 Downgrade the tx isolation level of poll message request flow (#2614)
It doesn't need a higher transaction isolation level as it's only loading a given poll
message once, and we want to avoid putting any kind of locks on the PollMessage table
as it seems to be having contention issues. Note that the poll message request flow
is by far the most frequent code that touches the PollMessage table, as there are many
many requests every minute from dozens of registrars, but much fewer poll messages
than that to actually ACK.
2024-11-21 22:49:57 +00:00
gbrodman
0c0b0df36e Skip poll messages on deletions for configured registrars (#2613)
See b/379331882 for more details
2024-11-21 22:16:26 +00:00
Weimin Yu
304f0002b4 Refactor FlowRunner transaction invocation (#2612)
Stop calling `transact` if already in a transaction.
2024-11-21 15:58:26 +00:00
gbrodman
15cf3e1bc0 Add RegistrarUpdateHistory objects for console changes (#2585) 2024-11-19 21:03:48 +00:00
Pavlo Tkach
eeed166310 Add console user role update and minor fixes to delete (#2610) 2024-11-15 18:36:10 +00:00
gbrodman
e54075fea3 Allow for removal of registry lock passwords in User objects (#2609)
This essentially enables the "forgot password" flow
2024-11-14 21:01:17 +00:00
Ben McIlwain
78cc1b2937 Fix 'Domian' typo for Domain (#2608) 2024-11-12 18:41:56 +00:00
Pavlo Tkach
35f95bbbe4 Add delete user to the console (#2603)
* Add delete user to the console

* Add delete user to the console

* Add delete user to the console
2024-11-08 18:20:01 +00:00
gbrodman
ae61cd443d Sometimes include deletion times in domain-list exports (#2602)
We only include the deletion time if the domain is in the 5-day
PENDING_DELETE period after the 30 day REDEMPTION period. For all other
domains, we just have an empty string as that field.

This is behind a feature flag so that we can control when it is enabled
2024-11-06 17:59:30 +00:00
gbrodman
cc20f7d76d Add a simple toString for TimedTransitionProperty (#2604)
this means that we can actually see the transitions when running
GetAllocationTokenCommand, for instance
2024-11-05 18:26:36 +00:00
Ben McIlwain
5603b91526 Make nomulus update_recurrence command only fail on pending transfers (#2605)
It was failing when any kind of transfer data was present, even completed
transfer data. Note that completed transfer data persists on a domain
indefinitely until/unless a new transfer is requested.

BUG= http://b/377328244
2024-11-04 21:16:11 +00:00
Pavlo Tkach
332f491ac7 Fix cut off status list on domains page (#2601) 2024-10-28 18:20:04 +00:00
Pavlo Tkach
4bd7c18fe9 Add console settings update progress status (#2596) 2024-10-25 22:23:22 +00:00
Pavlo Tkach
fdb0664841 Add admin.directory.user.security scope (#2597) 2024-10-25 21:24:15 +00:00
Lai Jiang
a9ba770bfa Add canary service to GKE (#2594) 2024-10-22 17:12:00 +00:00
Lai Jiang
4d96e5a6b1 Remove cap on soy (#2592)
We still need to cap the protobuf version that soy depends on, but the
rest of nomulus can use the latest version of protobuf.
2024-10-18 17:21:26 +00:00
Lai Jiang
1171c5cfcb Delete legacy console (#2579) 2024-10-17 20:48:10 +00:00
Pavlo Tkach
91e241374d Add required fields to API users().insert (#2593) 2024-10-17 19:45:12 +00:00
Weimin Yu
634202c0e9 A batch query utility to replace TransactionManager's loadAllOf methods (#2589)
* Replace  with batch query

* Addressing CR
2024-10-14 20:11:29 +00:00
Lai Jiang
020ed33003 Fix releases (#2591)
It seems like `/usr/bin/python` is no longer symlinked to the `python3`
binary in the `gcr.io/cloud-builders/git` image.

I've sent out a separate fix to upstream to change the shebang.

https://gerrit-review.git.corp.google.com/c/gcompute-tools/+/439501

But in the meantime, we need this temporary fix for the release to
build.
2024-10-14 15:42:15 +00:00
dependabot[bot]
0f61066b1d Bump the npm_and_yarn group in /console-webapp with 3 updates (#2588)
Bumps the npm_and_yarn group in /console-webapp with 3 updates: [cookie](https://github.com/jshttp/cookie), [socket.io](https://github.com/socketio/socket.io) and [express](https://github.com/expressjs/express).


Updates `cookie` from 0.4.2 to 0.7.2
- [Release notes](https://github.com/jshttp/cookie/releases)
- [Commits](https://github.com/jshttp/cookie/compare/v0.4.2...v0.7.2)

Updates `socket.io` from 4.7.5 to 4.8.0
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/socket.io@4.7.5...socket.io@4.8.0)

Updates `express` from 4.21.0 to 4.21.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.21.0...4.21.1)

---
updated-dependencies:
- dependency-name: cookie
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: socket.io
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: express
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Lai Jiang <jianglai@google.com>
2024-10-11 01:38:56 +00:00
Pavlo Tkach
03711481cd Fix console screenshot tests flakiness (#2590) 2024-10-10 21:17:42 +00:00
gbrodman
c32fb2fc71 Add screenshot tests for the new registrar console (#2577)
This required updating to a newer version of Selenium, building the
console dist/ folder, and serving that folder.
2024-10-09 16:44:34 +00:00
Pavlo Tkach
6e77c89cd6 Add console users screen (#2576) 2024-10-08 16:00:47 +00:00
Lai Jiang
5e41e84b8d Upgrade avro version (#2587)
Per b/371714822.
2024-10-07 15:43:53 +00:00
Lai Jiang
bfd569ee44 Add Aman to CONTRIBUTORS (#2586)
Give credit where credit it due @sanger2000.
2024-10-04 22:46:25 +00:00
Lai Jiang
b13a33347f Add Juan to CONTRIBUTORS (#2584) 2024-10-03 23:59:10 +00:00
Lai Jiang
d17a6edf12 Try to fix CodeQL java actions (#2583)
These flags are suggested by GitHub support to disable reusing caches
during Gradle build. They think that could fix the intermittent error
message:

```
Encountered a fatal error while running "/opt/hostedtoolcache/CodeQL/2.19.0/x64/codeql/codeql database finalize --finalize-dataset --threads=4 --ram=14576 --verbosity=progress++ /home/runner/work/_temp/codeql_databases/java". Exit code was 32 and last log line was: CodeQL detected code written in Java/Kotlin but could not process any of it. For more information, review our troubleshooting guide at https://gh.io/troubleshooting-code-scanning/no-source-code-seen-during-build . See the logs for more details.
```
2024-10-03 19:54:26 +00:00
Lai Jiang
7255ebff29 Fix a redirect bug (#2582) 2024-10-03 16:51:54 +00:00
Pavlo Tkach
cacc90097a Set POST method to SyncRegistrarSheetAction invocation (#2580) 2024-10-02 15:32:38 +00:00
Lai Jiang
0ef8984767 Remove schedule CodeQL run (#2581)
This fails for me every day for some reason (starting about a month
ago). The same commit went through the workflow fine when the action was
triggered by a push.

I think there's no reason for us to have a cron run as the changes to the
master branch can only come from commit pushes.
2024-10-02 15:29:55 +00:00
Juan Celhay
7a4abd93dc Add discount price param to GenerateAllocationTokens command (#2578)
* Add discount price param to GenerateAlloCationTokens command

* add discount price param to UpdateAllocationTokens command
2024-10-01 22:20:21 +00:00
Lai Jiang
142c910e3b Disable legacy registrar console (#2575) 2024-10-01 19:42:35 +00:00
gbrodman
c68d54a5ed Don't show snackbar on rlock-load failure if 403 (#2574)
ACCOUNT_MANAGER users don't have permission to see locks so it'll throw
403s. That's OK, we don't need/want to display that error to the client.
2024-09-30 20:42:33 +00:00
Pavlo Tkach
d17188b820 Add console users action (#2573) 2024-09-30 15:39:38 +00:00
Lai Jiang
cbe59b6950 Upgrade to use Gradle setup-gradle v4 (#2569)
I have seen a lot of CodeQL errors recently for no apparent reason.
Hopefully this will fix them.
2024-09-26 20:45:07 +00:00
Ben McIlwain
2b3c6525ff Add some basic info logging to RefreshDnsForAllDomainsAction (#2572)
BUG=http://b/369842541
2024-09-26 19:03:08 +00:00
Lai Jiang
72dd8658cf Upgrade to Gradle 8.10.2 (#2571) 2024-09-26 17:53:45 +00:00
Lai Jiang
c0490f7777 Update google-java-format (#2570)
Also converted regex strings in the Python script to raw strings as
future Python versions will start to reject them.

See: https://docs.python.org/3/whatsnew/3.12.html#other-language-changes
2024-09-26 14:49:38 +00:00
Lai Jiang
a22a38527b Make GPG related tests work with the latest version of GPG (#2568)
Newer versions of GPG (v.2.4.5 in my case) has uses different wording
then what's available in our build image (and Ubuntu I suspect). For
example it says "rsa2048" instead of "2048-bit RSA".

Make the tests work in both cases. Admittedly we cannot check for the
string RSA/rsa easily, but I don't think it matters much for tests.
2024-09-26 14:10:07 +00:00
Lai Jiang
08203033a2 Make the db object sync job more reliable (#2567)
It looks like /usr/bin/python *may* no longer exists in the latest cloud
builder git image. I ran the latest image and logged into it to verify
that /usr/bin/python3 does exist on 9/25, and again on 9/26 where it
re-appeared.

I think it is generally a good idea to not rely on it being there going
forward.
2024-09-26 02:10:00 +00:00
dependabot[bot]
d0482a8f2c Bump rollup in /console-webapp in the npm_and_yarn group (#2566)
Bumps the npm_and_yarn group in /console-webapp with 1 update: [rollup](https://github.com/rollup/rollup).


Updates `rollup` from 4.16.4 to 4.22.4
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.16.4...v4.22.4)

---
updated-dependencies:
- dependency-name: rollup
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-25 18:47:21 +00:00
Juan Celhay
e6a2db8075 Add discount price to AllocationToken (#2559)
* Include discount price in domai n pricing

* Partial progress in logic

* Tests and logic passing

* Change pricing for multi year create

* Tests for discount pricing logic

* Token currency check

* Add some comments

* Java formatting

* Discount price to Optional

* Change discount price to be optional nullable

* Re-add deleted tests
2024-09-23 20:18:33 +00:00
Lai Jiang
7929322e95 Connect to the correct endpoints based on runtime (#2540)
* Connect to the correct endpoints based on runtime

* Address code review comments

* Add checks for HTTP methods
2024-09-20 18:39:54 +00:00
Lai Jiang
5c35811eb9 Upgrade protobuf (#2565)
Patched from https://github.com/google/nomulus/pull/2564.

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-20 17:56:53 +00:00
Weimin Yu
4ba0f4a2cd Change nested transact calls to retransact (#2563) 2024-09-20 15:16:52 +00:00
Lai Jiang
e167b4b753 Make the cloud scheduler deployer GKE-aware (#2562)
Depending on if a "--gke" parameter (must be the last one) is passed,
the deployer constructs the corresponding URIs for GAE or GKE
accordingly.

TESTED=Used the deployer to deploy tasks to alpha and verified that they
run on GKE.
2024-09-19 16:28:08 +00:00
Joaquin Gimenez
c47f821754 Fix typo in docs (#2520) 2024-09-18 18:57:27 +00:00
Weimin Yu
febdbc0468 Drop the transact call in IdService (#2561)
* Drop the `transact` call in Id services

All usages already routed through `tm().allocateId()`, which is
guaranteed to be in a transaction.

* Addressing reviews
2024-09-18 18:18:36 +00:00
dependabot[bot]
a988732d65 Bump the npm_and_yarn group in /console-webapp with 5 updates (#2560)
Bumps the npm_and_yarn group in /console-webapp with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [body-parser](https://github.com/expressjs/body-parser) | `1.20.2` | `1.20.3` |
| [express](https://github.com/expressjs/express) | `4.19.2` | `4.21.0` |
| [path-to-regexp](https://github.com/pillarjs/path-to-regexp) | `0.1.7` | `0.1.10` |
| [send](https://github.com/pillarjs/send) | `0.18.0` | `0.19.0` |
| [serve-static](https://github.com/expressjs/serve-static) | `1.15.0` | `1.16.2` |


Updates `body-parser` from 1.20.2 to 1.20.3
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/1.20.2...1.20.3)

Updates `express` from 4.19.2 to 4.21.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0)

Updates `express` from 4.19.2 to 4.21.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0)

Updates `path-to-regexp` from 0.1.7 to 0.1.10
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v0.1.7...v0.1.10)

Updates `send` from 0.18.0 to 0.19.0
- [Release notes](https://github.com/pillarjs/send/releases)
- [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md)
- [Commits](https://github.com/pillarjs/send/compare/0.18.0...0.19.0)

Updates `serve-static` from 1.15.0 to 1.16.2
- [Release notes](https://github.com/expressjs/serve-static/releases)
- [Changelog](https://github.com/expressjs/serve-static/blob/v1.16.2/HISTORY.md)
- [Commits](https://github.com/expressjs/serve-static/compare/v1.15.0...v1.16.2)

---
updated-dependencies:
- dependency-name: body-parser
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: express
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: express
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: path-to-regexp
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: send
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: serve-static
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-17 22:40:24 +00:00
Juan Celhay
7ee541e1b1 Add activeOrDeletedSince parameter to RefreshDnsForAllDomainsAction (#2556) 2024-09-17 16:02:25 +00:00
Lai Jiang
b07769bdee Switch to new jaxb runtime (#2558) 2024-09-16 22:39:59 +00:00
Weimin Yu
9db016638e Use repeatable-read in some batch actions (#2557) 2024-09-16 18:56:33 +00:00
Weimin Yu
c3d164d462 CheckApi should use replica (#2554) 2024-09-16 16:10:28 +00:00
Lai Jiang
352618b3b7 Use Jcommander 2.0 (#2552) 2024-09-13 16:06:50 +00:00
Lai Jiang
0389b0d2d9 Upgrade to Gradle 8.10.1 (#2533) 2024-09-11 21:36:12 +00:00
gbrodman
8906a82e3b Update dependencies esp. antlr+jcommander (#2550) 2024-09-11 15:49:54 +00:00
Weimin Yu
f6e42896c3 Build uber jars as multi-release (#2549)
Several jars in our dependencies are now multi-release, including
dnsjava and snakeyaml, and a few more. Such jars include
jvm-version-specific classes that will only be loaded by the vm that can
handle them. All it takes is a new manifest attribute.

This change allows us to upgrade to dnsjava3.6+: the base (java 8) version of
this jar breaks java21. The correct manifest allows java21 to find the
classes it needs.
2024-09-10 18:12:25 +00:00
Juan Celhay
4d3dec54cf First PR for adding discount price into AllocationToken. (#2547) 2024-09-06 20:46:04 +00:00
Weimin Yu
f082ffffe3 Suppress misleading error-level logs by hibernate (#2546)
Update logging configs after Hibernate 6 migration.
2024-09-06 18:10:04 +00:00
Ben McIlwain
5f23f2a15a Reduce cardinality of reserved list processing time metric (#2542)
This single metric currently accounts for 22.2% of our total metrics bill,
almost double the size of our EPP requests metric, while also simultaneously
being much less useful. This change reduces the cardinality by removing two
parameters we don't care that much about, which should significantly reduce the
size and thus the cost. If after this change the metric is still too large, I'll
also then remove the matchCount parameter from this metric. We could possibly
even consider deleting the metric in its entirety, as we hardly ever use it.

This PR also removes unused code for premium list metrics that have never
actually been written out (and that we won't bother with at this point).
2024-09-05 19:41:20 +00:00
Weimin Yu
7ed7cf3340 Fix BSA batch query for all unblockables (#2544)
Typo in sql script resulting in some unblockables not loaded.

See b/361770793
2024-09-05 19:33:26 +00:00
gbrodman
ab60ac44fd Migrate DNS query table (#2543)
Co-authored-by: Lai Jiang <jianglai@google.com>
2024-09-04 17:42:12 +00:00
Weimin Yu
d9ad39cdad Upgrade to Hibernate6 and Jaxb4 (#2526)
* Rebase

* Addressing commetns

* Addressing comments

* Auto convert Duration and Money
2024-09-03 18:02:53 +00:00
Juan Celhay
bac4e22bff Add retries to DriveConnection.listFiles() on GoogleJsonResponseExceptions (#2541)
* Add retries to DriveConnection.listFiles() on GoogleJsonResponseExceptions

* Remove unused import

* Remove unread variable

* Add comment inputs

* fix formatting

* Remove period from error message.
2024-09-03 14:27:00 +00:00
Pavlo Tkach
ab5f6cc229 Add environment support to the console build (#2539) 2024-08-30 18:31:28 +00:00
gbrodman
1765f4f0b4 Allow skip of emailing/uploading for activity reports (#2538)
This will help us if/when we need to run the report generation multiple
times, or for past dates and we don't want to send extra emails or
upload any extra reports to ICANN.
2024-08-26 20:25:31 +00:00
gbrodman
e88c6e1550 Update activity/txn reporting to use new GAE log format (#2535)
Instead of having to parse the protoPayload.line from the request logs,
we just want to inspect the textPayload from the app logs (stored in a
separate table). This applies to the EPP metrics from the activity
reporting and the attempted-adds column for the transaction reporting.
2024-08-26 19:41:40 +00:00
Pavlo Tkach
1739c6d74f Update node.js to v22 (#2537) 2024-08-26 18:15:39 +00:00
Pavlo Tkach
66513a114e Add OT&E UI to the new console (#2536) 2024-08-23 20:53:45 +00:00
Pavlo Tkach
0e808a4c01 Add OT&E create and status to the new console (#2534) 2024-08-22 20:03:56 +00:00
Lai Jiang
4e013603be Make GKE networking work more properly (#2531) 2024-08-22 13:10:56 +00:00
gbrodman
730585cd14 Fix front-end unit tests (#2529)
This doesn't really add any tests, and we'll require many more additions
if we actually want to have full unit testing, but this at least makes
the tests pass when running `npm test`.
2024-08-21 16:39:29 +00:00
gbrodman
fd7820759d Use token's renewalPrice if renewalBehavior is SPECIFIED (#2502)
Previous PRs and token changes (see b/332928676) have made it so that
SPECIFIED renewalPriceBehavior tokens must have a renewal price. As
such, we can now use that renewalPrice when creating domains with
SPECIFIED tokens.
2024-08-15 19:06:32 +00:00
sarahcaseybot
69359bb1e6 Add QPS and incomplete connections metrics to load test client (#2487)
* Add QPS and incomplete connections metrics to load test client

* Add a failed request count

* Add todos

* Reuse contact

* Add bugs to todos

* small fix

* Clarify QPS
2024-08-14 18:14:17 +00:00
gbrodman
35b602a76e Remove User ID field from SQL (#2523)
This will fail tests until the corresponding PR in Java is deployed.
2024-08-14 17:51:15 +00:00
dependabot[bot]
82002d1f75 Bump axios in /console-webapp in the npm_and_yarn group (#2532)
Bumps the npm_and_yarn group in /console-webapp with 1 update: [axios](https://github.com/axios/axios).


Updates `axios` from 1.7.2 to 1.7.4
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.7.2...v1.7.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-14 15:44:14 +00:00
Lai Jiang
2fd9b062df Make Nomulus work on GKE with external load balancer (#2527)
This will create a multi-cluster external load balancer exposing HTTP
traffic to nomulus running in clusters in the fleet.
2024-08-14 14:32:39 +00:00
Ben McIlwain
ec3804e87e Make domain update flow handle null auth data (#2530)
It's valid for the auth data to be null (although it only happens 10 times
across our entire registry), so the domain update flow should not fail out with
a NullPointerException when the existing state of the data is null and the
update isn't adding that data either.

BUG=http://b/359264787
2024-08-13 18:19:44 +00:00
Pavlo Tkach
d0d28cc7e6 Fix console contact delete button not working (#2528) 2024-08-09 16:42:39 +00:00
Pavlo Tkach
2d1260c01b Allow updating icannReferralEmail through the new console ui (#2525) 2024-08-07 16:28:08 +00:00
Pavlo Tkach
06da6a2cc6 Make ContactActionTest deterministic for stop fail under new Hibernate (#2524) 2024-08-07 13:37:13 +00:00
Lai Jiang
858a22f82e Delete a duplicate resource file (#2522)
It already exists under the resources folder.
2024-08-06 18:42:29 +00:00
gbrodman
3c126ddfd4 Remove ID field from User in Java classes and remove UserDao (#2517)
This is the first step in the field removal (second will be removing the
column from SQL once this is deployed).

There's no point in using a UserDao versus just doing the standard
loading-from-DB that we do everywhere else. No need to special-case it.
2024-08-05 20:36:17 +00:00
gbrodman
2b98e6f177 Add deprecation message to old console (#2516) 2024-08-02 15:59:08 +00:00
gbrodman
20036b6a74 Fix wording on registry lock verification (#2518) 2024-08-01 20:17:46 +00:00
Lai Jiang
396cbd6bd3 Remove login_email_address from RegistrarPoc (part 2) (#2510)
Remove the field from the schema.
2024-08-01 17:07:03 +00:00
Lai Jiang
71ea16ff69 Call Workspace Groups API directly from nomulus tool (#2515)
When creating/deleting users, we need to add/remove the emails in
question to/from the console email group (if it exists). This used to be
done synchronously by calling the Groups API directly from the nomulus
tool. However #2488 made it so that in all cases where group membership
is modified, a Cloud Tasks task is created to execute the change on
the server side asynchronously (because there are multiple places where
this change needs to be done, and it is easier to make it all happen on the
server side).

Alas, as it turns out, Cloud Tasks tasks need to be created with a
service account's credential (which is trivially done on the server side
because the ADC is a service account). Nomulus command runs with a user
credential, and we need to grant the relevant user permission to
masquerade as a service account, in order to enqueue tasks from the
nomulus tool. It is therefore easier to just revert to the old behavior.
2024-08-01 15:29:57 +00:00
Pavlo Tkach
45331be166 Add redirect to the new console from the old console for tech support (#2514) 2024-07-31 17:16:12 +00:00
gbrodman
beb7c14adb Drop not-null constraint on UserUpdateHistory:user_id (#2513)
Some checks failed
Dependency Submission / dependency-submission (push) Successful in 3m55s
CodeQL / Analyze (java) (push) Failing after 3m42s
CodeQL / Analyze (javascript) (push) Failing after 52s
CodeQL / Analyze (python) (push) Failing after 50s
This is nullable now that we're switching from using an ID field to
using the email address as the primary identifier.
2024-07-30 19:19:29 +00:00
gbrodman
d33571dde3 Change pkey of User to emailAddress (#2505)
Some checks failed
CodeQL / Analyze (java) (push) Failing after 1m22s
CodeQL / Analyze (javascript) (push) Failing after 1m13s
CodeQL / Analyze (python) (push) Failing after 51s
Dependency Submission / dependency-submission (push) Successful in 2m11s
Originally, we though that User entities were going to have mutable
email addresses, and thus would require a non-changing primary key. This
proved to not be the case. It'll simplify the User loading/saving code
if we just do everything by email address.

Obviously this doesn't change much functionality, but it prepares us for
removing the id field down the line once the changes propagate.
2024-07-29 22:27:06 +00:00
gbrodman
53a7d1b66c Add feature flag for new console release #2511 (#2512)
* Add feature flag for new console release

* Run feature flag query in a transaction

---------

Co-authored-by: ptkach <ptkach@google.com>
2024-07-29 21:55:12 +00:00
Pavlo Tkach
fa721e82ff Mark console state field on new registrar form as required (#2509)
Some checks failed
CodeQL / Analyze (java) (push) Failing after 58s
CodeQL / Analyze (javascript) (push) Failing after 56s
CodeQL / Analyze (python) (push) Failing after 53s
Dependency Submission / dependency-submission (push) Successful in 2m12s
2024-07-26 18:46:43 +00:00
Lai Jiang
d4faa77ee4 Remove login_email_address from RegistrarPoc (#2507) 2024-07-26 17:56:34 +00:00
sarahcaseybot
96d3d88c2f Remove TODOs assigned to sarahbot (#2508) 2024-07-26 17:50:35 +00:00
Pavlo Tkach
213e06f02e Add registry lock ui (#2500) 2024-07-26 16:02:19 +00:00
gbrodman
d5445dd049 Allow new-console use for users with perm to the admin registrar ID (#2506)
For instance, on sandbox this will allow us to remove our global roles
but keep roles to the CharlestonRoad admin registrar. Then, when we view
the console, it will be as if we were a registrar user.
2024-07-25 20:25:55 +00:00
Lai Jiang
af5adcb0ba Upgrade to Gradle 8.9 (#2504) 2024-07-25 19:01:06 +00:00
gbrodman
ca238a8578 Change RL input to be a POST body (#2503) 2024-07-25 18:18:10 +00:00
Pavlo Tkach
1a8f133d54 Filter console registrars per user role (#2501) 2024-07-24 18:31:23 +00:00
gbrodman
233ee09efe Add simple registry-lock-verification page (#2499)
This is a fairly simple page that solely exists to display the result
from the action, and to link the user back to the domain list.
2024-07-23 19:04:35 +00:00
sarahcaseybot
35ff768176 Fix bug with removing registrant on update command (#2498)
* Fix bug with removing registrant on update command

* fix comment

* Change method name
2024-07-18 20:21:49 +00:00
Ben McIlwain
c4e5bc913e Remove contact entities from RDAP entirely when they don't exist in DB (#2497)
This is consistent with how other registries are handling RDAP and is also consistent
with overall behavior in WHOIS and domain info flows as implemented in my previous
PRs #2477 and #2490.
2024-07-18 19:33:52 +00:00
sarahcaseybot
0241937dee Use feature flag for minimum dataset in domain flows to decide when to check for required contacts (#2486)
* Check FeatureFlag in domain flows before checking contacts

Check if phase 1 has begun of the transition to the minimum registry dataset, and if it has, do not require the presence of contacts in domain flows.

* Add tests

* Small test fixes

* rename flag

* Fix merge conflicts

* Change todo

* Add isActive methods

* Add javadocs

* small fix
2024-07-17 22:06:09 +00:00
Pavlo Tkach
68b46735cd Prevent focus from being lost on console domains search (#2496) 2024-07-15 18:46:18 +00:00
Pavlo Tkach
bfeaf4a23e Add ability to remove elements from console UI per user role (#2495) 2024-07-15 17:45:46 +00:00
Pavlo Tkach
5f9f157494 Move console global loader, fix table scroll bars (#2494) 2024-07-12 18:57:26 +00:00
gbrodman
c23eed6ec4 Change domain-create fee response for tiered promos (#2491)
As requested, for registrars participating in these tiered pricing
promos that wish to receive this type of response, we make the following
changes:

1. The pre-promotional (i.e. base tier) price is returned as the
   standard domain-create fee when running a domain check.
2. The promotional (i.e. correct) price is returned as a special custom
   command class with a name of "STANDARD PROMO" when running a domain
   check
3. Domain creates will return the non-promotional (i.e. incorrect) price
   rather than the actual promotional price.

This PR does only number 3. See PR #2489 for the others.
2024-07-12 18:47:15 +00:00
gbrodman
04a4431d6a Change domain-check fee responses for registrars in tiered promos (#2489)
As requested, for registrars participaing in these tiered pricing promos
that wish to receive this type of response, we make the following
changes:

1. The non-promotional (i.e. incorrect) price is returned as the
   standard domain-create fee when running a domain check.
2. The promotional (i.e. correct) price is returned as a special custom
   command class with a name of "STANDARD PROMO" when running a domain
   check.
3. Domain creates will return the non-promotional (i.e. incorrect) price
   rather than the actual promotional price. This is not implemented in
   this PR.
2024-07-12 15:50:39 +00:00
Weimin Yu
d9c5d71f40 Add jackson-dataformat-yaml as direct dependency (#2493)
Required when upgrading to jackson 2.17.2.
2024-07-10 20:21:05 +00:00
Ben McIlwain
75f09c2fdf Fail permamently in re-save entity action when entity doesn't exist (#2492)
Our logs are getting gummed up with an indefinitely failing and retrying task to
re-save a prober domain that doesn't exist (likely because it was hard-deleted
by delete prober data action), so this makes the re-save action resilient to
that failure case so that it stops assuming every enqueued re-save actually
corresponds to an entity that exists, thus allowing it to fail permanently if
the entity doesn't exist.  Failing permanently is the right thing to do as if
the entity doesn't exist now there's no reason to think it will in the future,
plus all re-saves are optimistic rather than guaranteed anyway.

This should fix http://b/350530720
2024-07-10 19:03:42 +00:00
sarahcaseybot
74f0a8dd7b Add nomulus tool command for FeatureFlags (#2480)
* Add registryTool commands for FeatureFlags

* Fix merge conflicts

* Add required parameters and inject mapper

* Use optionals in cache to negative cahe missing objects

* Fix spelling

* Change back to bulk load in cache

* Add FeatureName enum

* Change variable name

* Use FeatureName in main parameter
2024-07-09 20:05:15 +00:00
gbrodman
092e3dca47 Add a renewal cost for ATs when renewal is SPECIFIED (#2484)
Note: this is not used yet
2024-07-09 18:39:48 +00:00
gbrodman
b8a6ac72dd Add a reg-lock verification action to the new console (#2467)
The front end will have a (hidden) page that passes the verification
code to this API endpoint and displays the result.
2024-07-08 21:25:22 +00:00
Ben McIlwain
b602aac09a Make all contacts nullable in the data model (#2490)
This doesn't yet allow them to be absent in EPP flows, but it should make the
code not break if they happen to be null in the database. This is a follow-up to
PR #2477, which ends up being a bit easier because whereas the registrant is
used in more parts of the codebase, the other contact types (admin, technical,
billing) are really only used in RDE, WHOIS, and RDAP, and because they were
already being used as a collection anyway, the handling for if that collection
contains fewer elements or is empty happened to already be mostly correct.
2024-07-03 21:42:20 +00:00
Lai Jiang
d86c002132 Create Users when setting up OT&E and Production registrars (#2488) 2024-07-03 18:31:07 +00:00
gbrodman
54c5a9450d Add RegistrationBehavior.NONPREMIUM_CREATE (#2481)
When using this token (which must be tied to a particular domain), the
first year price (and only the first year price, i.e. the creation
price) will be the standard price for this TLD. Future years (i.e.
renewals) will continue at the normal premium price.
2024-06-26 20:01:32 +00:00
gbrodman
0f0097c15c Wait to load domain list until a registrar is selected (#2485)
This isn't the worst thing in the world but it does result in a bad
request to the server otherwise, and log/error spam. So, only load the
domains list if we have a registrar selected.
2024-06-25 18:39:53 +00:00
Ben McIlwain
c9437d8c72 Make registrant nullable on domains (#2477)
This is the first step in migrating to the minimum registration data set. Note
that our database model already permits null domain registrants, so this just
makes the code accept it as well. Note that I haven't changed any requirements
in EPP flows yet; a later step will be to check the migration schedule and then
not require the registrant to be present if in a suitable state.

This does potentially affect the output of WHOIS/RDAP, but that's a NOOP so long
as EPP commands and other tools continue to enforce the requirement of a
registrant.
2024-06-20 15:22:38 +00:00
Weimin Yu
19819444af Set upper bound of netty version (#2482)
A new alpha version is introduced and breaks our tests.
2024-06-17 19:43:36 +00:00
Pavlo Tkach
15df3aea44 Update Angular 17 -> 18 (#2479) 2024-06-14 23:09:34 +00:00
Weimin Yu
d000a5dff8 Use replica db for non-mutating epp flows (#2478)
* Use replica db for non-mutating epp flows

* Add a test
2024-06-13 23:18:56 +00:00
sarahcaseybot
34694b4aef Add the FeatureFlag entity (#2464)
* Add FeatureFlag entity

* Add converter

* Add loading cache

* Add more tests

* Fix NPE in cache

* small fixes
2024-06-12 16:44:08 +00:00
dependabot[bot]
7ce7b23450 Bump braces (#2476)
Bumps the npm_and_yarn group with 1 update in the /console-webapp directory: [braces](https://github.com/micromatch/braces).


Updates `braces` from 3.0.2 to 3.0.3
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-11 19:19:16 +00:00
Lai Jiang
a5d1469281 Upgrade to Gradle 8.8 (#2475) 2024-06-10 14:56:10 +00:00
Pavlo Tkach
a90a85afae Fix domain page "not found" layout issue (#2474) 2024-06-08 11:36:23 +00:00
Weimin Yu
6e68876a14 Use replica for whois/rdap (#2470) 2024-06-07 17:03:55 +00:00
Lai Jiang
11231703d5 Upgrade to jakarta mail (#2473) 2024-06-07 15:28:36 +00:00
gbrodman
b77a219e19 Move domain-list search+download outside of loading bar (#2457)
This means that they'll stick around even while we're loading domains
from the server.

https://b.corp.google.com/issues/343213150
2024-06-06 20:35:20 +00:00
Pavlo Tkach
bd8e6354b5 Add new registrar screen to the console (#2469) 2024-06-07 00:21:53 +00:00
Weimin Yu
361094f537 BSA check in DomainCheckFlow should check TLD (#2472)
Should not block labels if the tld is not enrolled with BSA.
2024-06-06 19:30:36 +00:00
sarahcaseybot
d53177e44c Add domain creates to the load testing client (#2458)
* Add domain creates to the load testing client

* Update contact create
2024-06-06 17:30:12 +00:00
sarahcaseybot
e73f646e1f Add FeatureFlag table to the database (#2463)
* Add FeatureFlag table to the database

* Change status to hstore
2024-06-06 17:17:11 +00:00
Lai Jiang
1a5dfb0ac2 Upgrade schemacrawler (#2471) 2024-06-06 14:51:13 +00:00
Lai Jiang
49cb1875d1 Upgrade dependencies (#2468) 2024-06-05 15:50:42 +00:00
gbrodman
61eee45ad0 Add renewalPrice fields to AllocationToken in SQL (#2462)
This is an optional field (will be required when the renewal price
behavior is SPECIFIED). This will allow us to set arbitrary renewal
prices for domains as part of one-off negotiations.

https://b.corp.google.com/issues/332928676
2024-06-03 19:50:58 +00:00
Weimin Yu
e99a18f54f Pass log trace_id to TimeLimiter task (#2466)
Code executed by TimeLimiter is in another thread. Pass on the log
trace_id if exists.
2024-06-03 19:38:17 +00:00
Pavlo Tkach
0c123e1676 Unify email notifications for console updates (#2459) 2024-05-31 19:20:56 +00:00
Lai Jiang
81b239c6b3 Add a presubmit test to catch accidental usage of javax.servlet (#2461) 2024-05-31 13:34:50 +00:00
Weimin Yu
ea8c34bf8b Fix Flyway Gradle tasks (#2460) 2024-05-30 19:23:29 +00:00
Weimin Yu
b3e67e58b5 Change billing for multi-year domain creation (#2446)
* Change billing for multi-year domain creation

From the second year on, charge the renewal price.

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

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

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

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

There are several behavioral changes that are worth noting:

The XsrfTokenManager now uses the console user's email address to mint and verify the token. Previously, only email addresses returned by the GAE Users service are used, whereas a blank email address will be used if the user is logged in as a console user. I believe this was an oversight that is now corrected.
The legacy console will return 401 when no user is logged in, instead of redirecting to the Users service login flow.
The logout URL in the legacy console is changed to use the IAP logout flow. It will clear the cookie and redirect the users to IAP login page (tested on QA).
The screenshot changes are mostly due to the console users lacking a display name and therefore showing the email address instead. Some changes are due to using the console user's email address as the registry lock email address, which is being fixed in Add DB column for separate rlock email address #2413 and its follow-up RPs.
2024-05-29 12:37:44 -04:00
Lai Jiang
d90bc1a3e4 Update db README (#2449) 2024-05-29 11:54:31 -04:00
Lai Jiang
0e3875c1ff Removing leading newline from GKE log messages (#2454)
GKE now displays log messages correctly. There is no need for an extra
leading newline, which now results in a useless blank line for each log
entry in Log Explorer.
2024-05-29 11:54:19 -04:00
Lai Jiang
3b565b96b7 Add the ability to add/remove console users from a Google Group (#2450)
# Conflicts:
#	config/presubmits.py
2024-05-28 17:00:37 +00:00
Pavlo Tkach
ec6c77927f Add console backend for editing registrar (#2452) 2024-05-28 00:53:32 +00:00
Lai Jiang
e88ff77ecb Harmonize http status code usage (#2451)
Given that we run servlets, it makes sense to always use the status
code contants from the servlet class.
2024-05-24 18:46:37 +00:00
sarahcaseybot
0781010b16 Create a load testing EPP client (#2415)
* Create a load testing EPP client.

This code is mostly based off of what was used for a past EPP load testing client that can be found in Google3 at https://source.corp.google.com/piper///depot/google3/experimental/users/jianglai/proxy/java/google/registry/proxy/client/

I modified the old client to be open-source friendly and use Gradle.

For now, this only performs a login and logout command, I will further expand on this in later PRs to add other EPP commands so that we can truly load test the system.

* Small changes

* Remove unnecessary build dep

* Add gradle build tasks

* Small fixes

* Add an instances setUp and cleanUp script

* More modifications to instance setup scripts

* change to ubuntu instance

* Add comment to make ssh work
2024-05-23 21:37:34 +00:00
Pavlo Tkach
ab4bac05d1 Replace RegistryTestServerMain start address with ipv6 loopback (#2448) 2024-05-21 19:00:25 +00:00
Pavlo Tkach
8e22ce7c70 Add phone and fax number to console whois endpoint (#2447) 2024-05-20 20:32:23 +00:00
Lai Jiang
d96a5547ce Store stack trace in a separate filed during logging (#2444)
For reasons unclear to me, if the stack trace is appended directly to
the message, the log entry will be lumped together with following logs
on GKE.

Also updated the GKE service account for Nomulus in the manifest so we
can use workload identity just for Nomulus, not other pods on the same
cluster.
2024-05-20 16:17:56 +00:00
Pavlo Tkach
05b43965d1 Fix console EPP password form, minor adjustments (#2445) 2024-05-17 18:44:44 +00:00
gbrodman
43000a5f80 Refactor common exception handling in ConsoleApiAction (#2443)
There are a bunch of cases where we want common exception handling and
it's annoying to have to deal with the common "set failed response and
make sure to return" a bunch of times.

This allows us to break up request methods more easily, since we can now
often throw exceptions that will break all the way back up to
ConsoleApiAction. Previously, any error handling had to exist in the
primary handler method so it could return.
2024-05-16 18:29:14 +00:00
Pavlo Tkach
a66b9ea749 Add OTE registrars to console /registrars response (#2440) 2024-05-16 14:49:32 +00:00
sarahcaseybot
36a660a8ad Remove createBillingCost columns from db schema (#2438) 2024-05-15 18:19:36 +00:00
gbrodman
d09bb4ff74 Refactor some registry lock verification code (#2434)
The user, on the front end, should not be required to provide whether or
not they're trying to verify a lock or an unlock. They should only need
the verification code. We can inspect the lock object itself (and the
domain in question) to see whether or not we're verifying a lock or an
unlock.
2024-05-14 16:56:32 +00:00
Lai Jiang
6ca3cc230f Make logging work correctly on Jetty (#2442) 2024-05-14 14:36:26 +00:00
Pavlo Tkach
6a5d8ed3b5 Allow console access for all not NONE-type global users (#2441) 2024-05-13 19:24:11 +00:00
Pavlo Tkach
53dcba1189 Update jetty's console build path (#2439) 2024-05-13 16:08:57 +00:00
Pavlo Tkach
3e77004274 Create gcp console service (#2433) 2024-05-10 21:29:58 +00:00
gbrodman
fd21fcdb84 Add a GetUserCommand (#2435)
This is fairly simple, similar to most of the other Get*Command classes
2024-05-10 21:25:52 +00:00
gbrodman
ae14e35df7 Change a few wording bits in the console (#2436)
These probably aren't perfect but they seemed to make sense given the
old console. Nothing major.
2024-05-10 18:27:55 +00:00
Lai Jiang
94dc9fd0d5 Update GcpJsonFormatter (#2437)
Use the correct JSON field to store the source location info so it can
be parsed by Stackdriver.
2024-05-10 16:47:52 +00:00
gbrodman
7b34659a8f Add registryLockEmailAddress field to User object (#2418)
We've added the field in the database in a previous PR. This is only
used in the old console for now because the new console does not have
registry lock functionality yet
2024-05-09 21:42:45 +00:00
sarahcaseybot
808432e709 Remove the createBillingCost field from Tld (#2425)
* Remove the createBillingCost field from Tld

* fix spacing

* Change field name of map

* Rename getter

* Fix formatting

* Fix todo

* unchange column name
2024-05-08 18:14:03 +00:00
Lai Jiang
73d3b76a89 Remove more usage of AutoValue (#2432)
This PR also removes `SerializedForm` used to serialize
`PendingDeposit`, as it is now a simple record.
2024-05-08 00:50:01 +00:00
Weimin Yu
ca072b4861 Add log traces to Nomulus service on GKE (#2427)
* Add log traces to Nomulus service on GKE

Add request-scope log traces to Nomulus on GKE which, unlike
AppEngine and Cloud Run etc, does not generate traces for hosted
applications. This change only affects the GKE image. It does not affect
the AppEngine services.

Log traces are added to Nomulus-generated logs in request-processing
threads. Forked threads are not covered yet. The single relevant use
case (TimeLimiter) will be addressed in a followup PR.

The main change is in the logging configuration:

*  Use gcp-cloud-logging's LoggingHandler

*  Add gcp-cloud-logging's TraceLoggingEnhancer to the handler.

*  Set a thread-local trace id through the TraceLoggingEnhancer in
   ServletBase on request's entry and clear it on completion.

Also removed an unused class (`RequestLogId`).

* CR

* CR
2024-05-07 19:15:46 +00:00
Pavlo Tkach
54c896cbb9 Add console epp password integration (#2426) 2024-05-06 18:32:54 +00:00
Pavlo Tkach
2c7bf2cfdb Update cloudbuild-release.yaml with nokeycheck option (#2431) 2024-05-06 18:01:31 +00:00
gbrodman
49d2e34e12 Add a separate RegistryLock action for the console (#2411)
This handles both GET and POST requests. For POST requests it doesn't
actually change anything about the domains because we will need to add a
verification action (this will be done in a future PR).
2024-05-03 22:37:22 +00:00
Weimin Yu
5511b41f93 Avoid contention over the RefreshDnsRequest table (#2428)
* Avoid contention over the RefreshDnsRequest table

This table can be small at times, when PSQL may use table scan in
queries by keys. At the SERIALIZABLE isolation level, updates to
unrelated rows may conflict due to this `optimization`.

Lower the isolation level to repeatable read.

* Code review
2024-05-03 20:31:54 +00:00
gbrodman
147cdff555 Add registry lock email address col to UserUpdateHistory (#2424) 2024-05-02 15:51:48 +00:00
Ben McIlwain
4b6ade0b14 Bring codebase up to more recent Java standards (#2422)
This includes using the new switch format (though IntelliJ does not yet
understand patterns including default so those aren't used), multiline strings,
replacing some unnecessary type declarations with <>, converting some classes to
records, replacing some Guava predicates with native Java code, and some other
miscellaneous Code Inspection fixes.
2024-05-01 20:48:38 +00:00
Pavlo Tkach
570618705e Allow console access for FTE globar role (#2419) 2024-05-01 16:19:29 +00:00
sarahcaseybot
e791608098 Add more indexes to speed up deleteProberDataAction (#2423)
This adds an index on transfer_billing_cancellation_id to Domain and superordinate_domain to Host. When tested on crash with the action limited to only delete 10,000 domains, before these indexes were added the action took about 2 hours to delete 10,000 domains. Once these indexes were added, the action was able to delete the 10,000 domains in a little under 2 minutes.
2024-05-01 15:44:08 +00:00
gbrodman
03b358726a Add Java classes for console history objects (#2350)
This also creates base classes for the objects contained within the
history classes, e.g. RegistrarBase. This is the same way that objects
stored in the HistoryEntry subclasses have base classes, e.g.
DomainBase.
2024-04-30 20:42:40 +00:00
gbrodman
d121f8f547 Generate fake XSRF token in FakeConsoleApiParams for tests (#2421) 2024-04-30 17:47:53 +00:00
gbrodman
b27218d799 Fix a couple Checkstyle warnings (#2420) 2024-04-29 20:08:53 +00:00
Pavlo Tkach
e78ce42dd5 Add console DUM download (#2402)
* Add console DUM download

* Add console DUM download
2024-04-26 15:56:50 +00:00
Ben McIlwain
55fade497d Convert a bunch more @AutoValues to records (#2412) 2024-04-25 16:59:31 +00:00
gbrodman
e7501b621a Add DB column for separate rlock email address (#2413)
We cannot rely on the user checking their login email, so we'll want to
send the emails to the other address if configured. This is already the
case in RegistrarPoc.
2024-04-25 15:38:57 +00:00
Weimin Yu
9c443bede1 Fix conflicts between locks (#2407)
Use REPEATABLE READ for lock acquire/release operation to avoid conlicts
between locks.

Postgresql uses table scan on small tables, causing false sharing at
the SERIALIZABLE isolation level.

See b/333537928 for details.
2024-04-24 18:51:18 +00:00
Lai Jiang
6d0a746b76 Bind console users to the appropriate IAP roles upon creation (#2403)
Console users need IAP to inject the necessary OIDC tokens into their
request headers and therefore need to be bound to appropriate roles. Note
that in environments managed by latchkey, the bindings will need to be
present in latchkey config files as well, otherwise the changes made by
the nomulus tool will be reverted.

TESTED=ran the nomulus command against alpha and verified that the
bindings are created/removed upon console user creation/deletion.
2024-04-24 15:03:43 +00:00
Pavlo Tkach
0765e7b209 Console deps update (#2409)
* Update angular/core to 17.3.5

* Update angular/material 17.3.5

* Update angular/cli 17.3.5

* Update angular-eslint 17.3.0

* Disable cli cache

* General console deps update
2024-04-23 19:38:32 +00:00
sarahcaseybot
f729802094 Make createBillingCostTransitions not null (#2405)
* Make createBillingCostTransitions not null

* Set up createBillingCost field to be removed form config files

* Add clarifying comment
2024-04-23 18:22:45 +00:00
Ben McIlwain
e809e967a3 Convert more @AutoValues to records, particularly in custom flow classes (#2408) 2024-04-22 20:25:33 +00:00
Pavlo Tkach
4de2bd5901 Add console backend for EPP password change (#2396) 2024-04-20 10:44:26 +00:00
sarahcaseybot
b5629ff16f Run deleteProberData cron job daily (#2406)
* Run deleteProberData cron job daily

* Sign the commits

* try signing again
2024-04-19 19:32:14 +00:00
Ben McIlwain
91615aef54 Handle bad header names in registrar sheet syncing action (#2404)
The existing behavior was to ignore bad header names, in a way that was
counter-intuitive as a user of the Google Sheet. If a header name was bad (which
could just be someone accidentally changing it not realizing it needs to
correspond exactly to the name of the field on the Java object), then all of the
data in that column was just silently left as-is and never updated. This led to
gradually worsening sync and offset shift errors over time.

Now, it will write out an error message into every single cell in the bad
column, so it's clear that the column name is wrong and does not correspond to any
actual data in the DB.

BUG=http://b/332336068
2024-04-19 17:59:58 +00:00
Ben McIlwain
fa6898167b Convert more @AutoValues to Java records (#2378) 2024-04-17 19:30:23 +00:00
Lai Jiang
903b7979de Upgrade to jline 3 (#2400)
jline 3 contains API breaking changes, necessitating changes in
ShellCommand.
2024-04-12 19:57:02 +00:00
Weimin Yu
8721085d14 Fix BSA validation (#2401)
Unblocked reserved names wrongly reported as missing unblockable domain.
2024-04-12 19:54:59 +00:00
Lai Jiang
e434528cd3 Add nomulus deployment and service manifests (#2389) 2024-04-11 19:01:09 +00:00
Pavlo Tkach
9ca54e4364 Add UI for EPP Password update (#2393) 2024-04-10 22:29:52 +00:00
Weimin Yu
a16794e2af Run BSA Validate without lock (#2399)
As a read-only action that tolerates staleness, locking is unnecessary.
This should help with the lock contention we are observing.

Also reduces the number of VM instances provisioned for BSA and increase
the idle timeout. This should reduce invocation delay. Longer delay may
cause AppEngine to return `Timeout` status to Cloud Scheduler even
though the cron job succeeds.
2024-04-10 19:58:24 +00:00
Lai Jiang
496a781572 Upgrade jcommander (#2398) 2024-04-10 17:34:11 +00:00
Ben McIlwain
2df583df1a Statically import Truth.assertThat() in tests (#2395)
This also involved breaking out an improperly done assertThat() helper overload
method for JsonObjects into a proper Subject that doesn't further overload
assertThat().
2024-04-09 16:27:26 +00:00
sarahcaseybot
4f1ca920a7 Use the createBillingCostTransitions map to get the create cost for a domain (#2390)
* Use the createBillingCostTransitions map to get the create cost for a domain

* Add comment

* Add some TODOs

* use streams to check currency unit
2024-04-05 21:27:55 +00:00
Weimin Yu
96e33f5b4f Check for missing BSA unblockable domains (#2394)
* Check for missing BSA unblockable domains

All unblockable domains created before the last refresh run should be
reported as unblockable (registered).

All reserved domains that are not registered should be reported as
unblockable (reserved). Note that transient errors may be reported for
newly added reserved domains since we do not maintain update time for
when a reserved label is associated with a TLD. However, this scenario
is extremely rare in operations.

* Addressing review
2024-04-03 00:44:05 +00:00
sarahcaseybot
dff2d90325 Add batching to DeleteProberDataAction (#2322)
* Add batching to DeleteProberDataAction

* Only get time once

* Add separate query for dry run

* Update querries to actually properly delete all the data

* Fix merge conflicts

* Add test for foreign key constraints

* Make transaction repeatable read

* Make queries to subtables native

* Add native query for GracePeriodHistory

* Kill job after 20 hours

* remove extra time check from read query
2024-03-29 20:51:19 +00:00
sarahcaseybot
fa344776a1 Drop the should_publish column from ReservedList (#2392) 2024-03-29 16:21:11 +00:00
Pavlo Tkach
eb164809de Add console favicon (#2391) 2024-03-27 20:32:01 +00:00
Pavlo Tkach
4ddbeb6d06 Add console registrar field focus handler, split whois address field (#2388) 2024-03-27 18:55:57 +00:00
dependabot[bot]
fa53391395 Bump express from 4.19.1 to 4.19.2 in /console-webapp (#2387)
Bumps [express](https://github.com/expressjs/express) from 4.19.1 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.1...4.19.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-26 17:28:43 +00:00
sarahcaseybot
856e70cf8e Add indexes on domainRepoId to DomainHistoryHost and PollMessage (#2380)
* Add index for domainRepoId to PollMessage and DomainHistoryHost

* Add flyway fix for Concurrent

* fix gradle.properties

* Modify lockfiles

* Update the release tool and add IF NOT EXISTS

* Test removing transactional lock from deploy script

* Add transactional lock flag to actual flyway commands in script

* Remove flag from info command

* Add configuration for integration test
2024-03-26 16:44:14 +00:00
Lai Jiang
2037611931 Upgrade to Gradle 8.7 (#2386) 2024-03-25 23:57:40 +00:00
Lai Jiang
af1f6e5708 Compile to Java 21 bytecode (#2374)
We have been running in Java 21 runtime for a couple of weeks and every
works as expected.
2024-03-25 13:50:39 +00:00
Weimin Yu
0df8372407 Change BSA job status notifications (#2385)
Add error notifications for BsaDownload.

Stop sending success notifications.
2024-03-22 19:27:25 +00:00
Pavlo Tkach
59f4129ee0 Restyle registrar console based on the new design proposal (#2336) 2024-03-21 22:05:09 +00:00
Weimin Yu
de3af34b66 Verify unblockables are truly unblockable (#2381)
* Verify unblockables are truly unblockable

Unblockable domains may become blockable due to deregistration or
removal from the reserved list. The BSA refresh job is responsible
for removing them from the database. This PR verifies that the refreshes
are correct.

Note that recent changes since last refresh are not reflected in the
result, and inconsistency due to recent deregistrations are ignored.
Changes in reserved status or IDN validity are not timestamped,
therefore we cannot ignore recent inconsistencies. However, these
changes are rare.

* Addressing code review

* Addressing code review
2024-03-20 18:52:17 +00:00
Weimin Yu
5c62dc78ba Fix nom_build command (#2383) 2024-03-20 13:12:59 +00:00
Lai Jiang
3fdecde6e9 Add a method to test if Nomulus is running on Jetty (#2382) 2024-03-19 15:43:52 +00:00
Lai Jiang
e7bf74d91d Upgrade to Jakarta EE 10 Servlet (#2362)
Upgrade to using Jakarta EE 10 from Java EE 8 by mostly following the upgrade instructions. Only the servlet package is upgrade. Other Jakarta EE components (like the persistence package that Hibernate depends on) need to be upgraded separately.

TESTED=deployed and successfully communicated with the pubapi endpoint for web WHOIS.

Note that this currently requires packaing the App Engine runtime per instructions here due to GoogleCloudPlatform/appengine-java-standard#98. This PR will only be merged until the fix is deployed to production (https://rapid.corp.google.com/#/release/serverless_runtimes_run_java/java21_20240310_21_0).
2024-03-18 18:00:55 +00:00
Lai Jiang
ff211fb4f9 Remove buildSrc (#2379)
We don't use the upload results feature (kokoro picks the results
artifacts directly and uploads them).

Keeping it around is a maintenance burden.

Also fixed a deprecation warning.
2024-03-18 14:29:51 +00:00
dependabot[bot]
3a7c53d895 Bump follow-redirects from 1.15.4 to 1.15.6 in /docs/console-endpoints (#2375)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.4 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.4...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-15 19:06:26 -04:00
Weimin Yu
f5b279a288 Add IDN check to BSA validate (#2370)
Labels that are not in any supported IDN are not added to the database.

Remove such labels from those loaded from the block list files before
comparing with DB.
2024-03-15 19:06:12 -04:00
Lai Jiang
c68583f666 Remove java.util.Date (#2373)
There is one remaining instance in JpaTransactionManagerImpl that cannot
be removed because DetachingTypedQuery is implementing TypedQuery, which has
a method that expectred java.util.Date.
2024-03-15 19:06:00 -04:00
Pavlo Tkach
6d2eb2e140 Update build.gradle (#2377)
Console tests fail for the files that are affected by redesign. There's no point in fixing it here. I will reenable the task after the console redesign PR is merged
2024-03-15 17:23:02 +00:00
dependabot[bot]
00a2022292 Bump follow-redirects from 1.15.5 to 1.15.6 in /console-webapp (#2376)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.5 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.5...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-15 14:21:46 +00:00
Lai Jiang
de372c4d47 Replace javax.inject with jakarta.inject (#2372)
Note that Dagger currently doesn't work with the Jakarta namespace and
we have to cap the jakarta inject package version below 2.0 so that it
sill provides classes in the old namespace.
2024-03-15 03:26:53 +00:00
sarahcaseybot
5f9c7de516 Drop should publish field from ReservedList entity (#2369)
* Remove the shouldPublish field from the reservedLIst entity

* Add generated schema file
2024-03-14 22:04:18 +00:00
Lai Jiang
6e57d93507 Upgrade a few more dependencies (#2371)
* jaxb and gmail

* admin-directory

* Upgrade a few more dependencies
2024-03-14 17:37:54 +00:00
Lai Jiang
b9cfa65546 Refactor some code related to the transaction manager (#2366)
Removed the deprecation mark as it is natural to expose methods related
to a transaction like getting the entity manager or checking if one is
in a transaction through the transaction manager interface.
2024-03-14 14:37:44 +00:00
Weimin Yu
9af006836c Add email notification of BSA job status (#2368) 2024-03-13 19:14:02 +00:00
Lai Jiang
cd95be4776 Upgrade a few pinned dependencies (#2359) 2024-03-13 11:52:34 +00:00
Lai Jiang
bdc9a1fd1d Fix nomulus tool when the environment is localhost (#2365)
Also only caches/resets the original TM when in unit tests (TBT I'm not so sure
that even this is necessary as we don't seem to call the tool from tests
that often. There is only ShellCommandTest that calls the run() function
in RegistryCli and we could just put these tests in fragileTest and make
them run sequentially and fork every time to get around issue with
inference).

The issue with caching is that it tries to first create the to-be-cached
TM, and when the environment given is prod/sandbox/... It will try to
retrieve SQL credentials from prod/sandbox/... secret manager. This
works fine locally as we all have access to prod/sandbox/..., but fails
in Cloud Build jobs such as sync-db-objects where it provides it own
credential that has direct SQL access, but not access to
prod/sandbox/... secret manager.

TESTED=ran `./gradlew devTool --args="-e localhost generate_sql_er_diagram -o ../db/src/main/resources/sql/er_diagram"`
2024-03-13 04:49:07 +00:00
Lai Jiang
d0b036227a Add a GitHub action to block merging based on the labels (#2367) 2024-03-13 02:47:37 +00:00
gbrodman
0f02858965 Rename console update mod time to history_modification_time (#2363) 2024-03-12 20:38:15 +00:00
Lai Jiang
6acb14c60d Add a test to ensure all actions are routable by the RegistryServlet (#2361) 2024-03-12 17:18:44 +00:00
Lai Jiang
e881f254f8 Add a GitHub Action (#2360)
* Add a GitHub Action workflow

This allows us to create Gradle depedency graphs for Dependabot analysis (as the ones we already get for Javascript dependencies).

* Update Java version

* Add build scan

* codeql 3

* run with gradle

* exclude jIFC

* build scan

* Finalize
2024-03-11 18:55:13 +00:00
Lai Jiang
1fb27fcf8e Make nomulus work locally (#2349)
Chose the default transaction manager based on RegistryEnvironment. This
makes it possible to run nomulus on Jetty locally. Tested with the
following:

```bash
./gradle :jetty:run -Penvironment=alpha
curl http://localhost:8080/beta.app
```

The docker image is also updated to take an argument that specifies the
environment. It runs locally as well but the container doesn't get
access to locally stored credentials, so it fails to initialize the
transaction manager.
2024-03-11 16:05:44 +00:00
Weimin Yu
34a8a94083 Add BSA validation job (#2356)
* Add BSA validation job

Add the BsaValidateAction class with a first check (for inconsistency
between downloaded and persisted labels).

* Addressing comments

* Addressing reviews
2024-03-08 22:08:09 +00:00
Lai Jiang
779dc36858 Remove some dead gradle code (#2358)
runtime is not actually a predefined confiugration and it doesn't affect
the runtime classpath in anyway.

See: https://docs.gradle.org/current/userguide/java_plugin.html#tab:configurations
2024-03-08 15:12:11 +00:00
Lai Jiang
40174b825a Change ICANN upload cursor time (#2346)
The staging job runs at 9AM on the 2nd day of each month, we should set
the cursor to be after that time, otherwise we attempt to upload reports
on the 1st day of each month before they are ready, causing an error
email to be sent to us.
2024-03-07 15:52:14 +00:00
Weimin Yu
df4e345961 Remove appengine-based email client (#2354)
Remove email classes that depend on AppEngine API. They have been
replaced by the gmail-based client.

Remove `EmailMessage.from` method, which is no longer used.
There is a fixed sender address for the entire domain, and is
set by the gmail client.

The configs remain to be cleaned up. There is a bug (b/279671974) that
tracks it.
2024-03-07 03:26:12 +00:00
Lai Jiang
1cac9c9684 Make Kythe work with JDK 21 (#2355) 2024-03-06 20:23:53 +00:00
Lai Jiang
11883812b3 Update runtime to Java 21 (#2353)
This PR makes the runtime of most of our workload Java 21.

1. App Engine. Java 21 is in GA and it supports Java EE 8. I had to add
   an environmental variable so that we don't get an
   AppEngineCredentails by default (we have been using
   ComputeEngineCredentials for a couple of years). The uprade to Java
   21 runtime changed a system property that controls how jetty logging
   works, which also control if AppEngineCredential is return. Tested by
   deploying to alpha.
2. Proxy base image upgradedd to Java 21 (distroless still doesn't
   support Java 21 and it looks like Temurin is the way to go
   b/306728455). Tested by deploying to alpha.
3. Nomulus tool image upgrade to Temurin 21 as well. Tested locally.
4. Beam pipeline base image upgrade to Java 21. The JAVA21 flag is not
   supported by gcloud yet, but specifying the image URL directly works
   (and is supported). Tested by running in alpha.
5. Jetty base image upgraded to Java 21. Tested locally.
2024-03-06 15:10:11 +00:00
Lai Jiang
742481932e Upgrade builder base image (#2352)
This allows us to install Java 21 in the image.
2024-03-05 23:17:13 +00:00
Lai Jiang
37e4607c91 Temporarily revert builder to Java 17 (#2351)
Debian 11 repo does not have Java 21. Revert to Java 17 for now so we
can build the builder image, which is needed for release.
2024-03-05 21:30:23 +00:00
Lai Jiang
c896c022a6 Remove caps/pins on some dependencies (#2348)
Also re-organized the dependencies.gradle file.

Not all caps/pins are removed at this point, but I think this is enough
change for one PR.
2024-03-05 17:00:40 +00:00
Lai Jiang
805a34be96 Fix gax and gax-grpc dependency mismatch (#2347)
Also address a deprecation warning.

TESTED=build the nomulus tool locally and it run the `list_cursors`
command correctly. It used to fail.
2024-03-04 23:39:42 +00:00
Lai Jiang
dcf0412f11 Compile Nomulus with Java 21 (#2344)
Make the necessary changes for the code base to compile with JDK 21.

Other changes:

1. Upgraded testcontainer version and the SQL image version (to be the
   same as what we use in Cloud SQL). This led to some schema changes and
   also changed the order of results in some test queries (for the
   better I think, as the new order appears to be alphabetical).
2. Remove dependency on Truth8, which is deprecated.
3. Enable parallel Gradle task execution and greatly increased the
   number of parallel tests in standardTest. Removed outcastTest.
2024-03-04 19:31:08 +00:00
Lai Jiang
fbe0f4e0f2 Do not use shaded dependencies from testcontainers (#2343)
Also fixed a flaky test where it depends on the current date. On a leap
day, now + 1 year - 1 year results in 2/28 instead of 2/29.
2024-02-29 19:02:02 +00:00
Lai Jiang
d1f678bba7 Set up a unified registry servlet for Jetty (#2338)
This PR creates a unified RegistryServlet that will serve all
non-console traffic. It also creates a jetty subproject that allows one
to run Nomulus on top of a standard Jetty 12 runtime.

`./gradlew :jetty:stage` will create a jetty base folder at
`jetty/build/jetty-base` where one is able spin up a local Nomulus server
by running the following command inside the folder:

```bash
java -jar ${JETTY_HOME}/start.jar
```

`JETTY_HOME` is a folder where the [Jetty runtime](https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-home/12.0.6/jetty-home-12.0.6.zip) is located.

This PR also adds a Gradle task to create a Nomulus image based on the
official Jetty image:

```bash
./gradlew :jetty:buildNomulusImage
```
2024-02-29 17:37:51 +00:00
gbrodman
78c7d44546 Add SQL code for ConsoleUpdateHistory subclasses (#2337)
https://github.com/google/nomulus/pull/2330/ has an example of what this
will look like in Java
2024-02-29 17:05:45 +00:00
Lai Jiang
af2a7540d9 Upgrade to Gradle 8.6 (#2340) 2024-02-28 16:39:38 -05:00
Lai Jiang
f82e8e006d Use Java 17 feature (#2341)
This was somehow missed in #2333

<!-- Reviewable:start -->
- - -
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/google/nomulus/2341)
<!-- Reviewable:end -->
2024-02-28 16:39:31 -05:00
Lai Jiang
bf877f469c Revert "Include a better error message to debug nomulus tool not working (#2275)" (#2342)
This reverts commit 64f5971275.

The catch block is too broad and most of the times the errors caught is
because `command.run()` failed and it had nothing to do with getting
the transaction manager. The `runCommand` method is already wrapped in a try
block that checks for `LoginRequiredException` and gives the appropriate
error message.

We need to re-assess the situation when the next time we encounter a
login issue that did not trigger `LoginRequiredException`. A blanket try
catch block is not the solution and only makes the situation more
confusing.

<!-- Reviewable:start -->
- - -
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/google/nomulus/2342)
<!-- Reviewable:end -->
2024-02-28 16:39:15 -05:00
sarahcaseybot
02fd6d4756 Add a check so newly saved createCostTransitions get recognized and saved to the database (#2335)
* Add a check so newly saved createCostTransitions get recognized and saved to the database

* Fix equals check

* Rename equals method

* Add comment explaining need for createBillingCostTransitionEqualCheck
2024-02-28 19:21:58 +00:00
sarahcaseybot
a4bd85068b Remove use of shouldPublishField from ReservedList (#2324)
* Remove use of shouldPublishField from ReservedList

* Remove from tests

* Update test comment

* Fix indentation

* fix test comment

* Fix test

* fix test

* Make shouldPublish column nullable
2024-02-27 20:39:58 +00:00
Pavlo Tkach
15368ee1c6 Console webapp dependencies update (#2339) 2024-02-27 19:07:43 +00:00
dependabot[bot]
f13fda2c15 Bump ip from 2.0.0 to 2.0.1 in /console-webapp (#2331)
Bumps [ip](https://github.com/indutny/node-ip) from 2.0.0 to 2.0.1.
- [Commits](https://github.com/indutny/node-ip/compare/v2.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: ip
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-26 23:03:36 +00:00
Lai Jiang
f72a0d2f16 Remove SHA256 as a supported password hashing algorithm (#2310)
We introduced Scrypt as the default password hashing algorithm in
November 2023 and have been auto-converting saved hashes whenever a
successful EPP login or registry lock/unlock request is processed.

We will send comms to registrars to inform them the upcoming removal of
SHA256 support and urge them to log in at least once before the change.
Otherwise, they will need to contact support to reset the password out of
band after the change.

This PR will NOT be submitted until comms are out and the effective date
is immediate.

Co-authored-by: Weimin Yu <weiminyu@google.com>
2024-02-26 15:28:12 +00:00
Ben McIlwain
1eef260da9 Convert some more @AutoValues to records (#2334) 2024-02-23 18:56:40 +00:00
Lai Jiang
9d0ff74377 Re-enable Java 17 features (#2333) 2024-02-21 20:04:07 +00:00
Ben McIlwain
7a301edab7 Make transaction isolation level the first argument to transact() (#2329)
This makes the callsites look neater, as the work to execute itself is often a
many line lambda, whereas the transaction isolation level is not more than a
couple dozen characters.
2024-02-17 00:07:48 +00:00
Lai Jiang
08bcf579a5 Remove Duplicate billing events from the invoicing pipeline (#2326)
The Distinct transform removes duplicates based on the serialized format
of the elements. By providing a deterministic coder, we can guarantee
that no duplicates exist.
2024-02-16 20:43:40 +00:00
Lai Jiang
7d2330c943 Update beam pipeline base Java version to Java 17 (#2328) 2024-02-16 17:57:14 +00:00
Ben McIlwain
670941bec8 Convert a couple of @AutoValue classes to Java 15 Records (#2327)
This is the start of a long and low priority migration, but for now I wanted to do a couple of them just to see what it looks like.

This also demonstrates the pattern for use of an @AutoBuilder to replace an @AutoValue.Builder. See https://github.com/google/auto/blob/main/value/userguide/records.md#builders for full details on that.
2024-02-16 16:14:24 +00:00
Ben McIlwain
1f516e34b6 Add some shortcut flags to update allocation tokens command (#2321) 2024-02-15 23:25:14 +00:00
Ben McIlwain
70942c87d1 Change !Optional.isPresent() to Optional.isEmpty() (#2325)
Also uses the new Optional.stream() in one class.

Thank you Java 17!
2024-02-15 17:55:09 +00:00
Lai Jiang
406059db72 Use standard JVM shutdown hook (#2323)
This removes a dependency on the App Engine SDK. It also looks like
(from the logs at least) that shutdown hooks registered the old  way stopped
working after the runtime is upgraded to Java 17.

Also removed some random leftover dependencies on the App Engine SKD
that are not needed any more.
2024-02-14 21:36:25 +00:00
sarahcaseybot
abc1a0ef3d Add java changes for createBillingCostTransitions (#2314)
* Add java changes for createBillingCostTransitions

* Add negative cost test

* Remove default value

* remove unused variable

* Add check that create cost and trnasitions map are the same

* inject clock, only use key set when checking for missing fields

* Add test for removing map
2024-02-09 17:08:51 +00:00
Weimin Yu
7b47ecb1f1 Add REGISTER_BSA allocation type (#2319)
* Add ALLOW_BSA allocation type

Add a new type to allow creation of domains blocked by BSA.
Except for the BSA semantics, the new type behaves exactly
like SINGLE_USE.

* Addressing reviews

* Addressing review
2024-02-08 21:45:13 +00:00
Ben McIlwain
469d62703a Fix the test class name for UpdateRecurrenceCommand (#2320)
It looks like the command was renamed at some point to be shorter but then the test class itself was forgotten.
2024-02-08 19:34:18 +00:00
Lai Jiang
009fda67b7 Do not retry transactions inside Beam (#2318) 2024-02-05 18:40:56 +00:00
sarahcaseybot
e492936cec Add check for build_environment flag in updateReservedListCommand and updatePremiumListCommand (#2317)
* Add check for build_environment flag in updateReservedListCommand

* Do the same for premium list
2024-02-02 16:43:45 -05:00
Weimin Yu
d1d59c1afd Add a reminder for BEAM at Java version declaration (#2316) 2024-02-02 12:05:10 -05:00
Weimin Yu
7b786eaf1f Update dataflow java runtime to 17 (#2315) 2024-02-01 15:37:21 -05:00
Pavlo Tkach
45c5d12743 Add angular signals and computed to the console (#2308) 2024-02-01 14:15:05 -05:00
sarahcaseybot
73ab95bd9d Add Cloud Build sync job for reserved and premium lists (#2302)
* Change tld-update to db-object-updater

* rename sync_tlds.sh to sync_db_objects.sh

* Change to configured command name

* Change environment to sandbox explicitly for testing on alpha

* Add remaining object steps and change cloudbuild-tld-sync to cloudbuild-sync-db-objects

* Add build_environment flag

* Change order of command and directory

* Uncomment out reserved list part
2024-01-31 14:50:54 -05:00
Weimin Yu
f85cf57e36 Reduce query batch size for BSA unavailables (#2313)
Query size is borderline too-large for the replica.

At 50000, about 2 out of 30 took more than 30 seconds and were retried.
Lower to 40000 and we will keep monitoring the executions.
2024-01-30 13:18:41 -05:00
Ben McIlwain
5e36cf30c3 Don't override existing registrar email address when setting referral email (#2300)
The fallback should only apply on creates, not on updates, otherwise it can
override an existing value for the email address when only the referral email
should be what's updated.

This fixes a bug introduced back in commit in 0ead4f8d9d.

BUG= http://b/322026165
2024-01-30 18:31:54 +01:00
sarahcaseybot
829be0777b Add a createBillingCostTransitions column to TLD (#2312) 2024-01-29 18:06:02 -05:00
Lai Jiang
c0ac9bdba4 Compile to Java 17 bytecode (#2304)
Also fix a linter warning.
2024-01-25 18:29:07 -05:00
Weimin Yu
58ec0f826d Stop saving BSA empty refresh changes (#2307)
* Stop saving BSA empty refresh changes

We thought that as a way to verify the refresh job to be running, browsing
the GCS bucket with empty files is easier than quering the DB or go to GCP
logging dashboard, but there are too many of them to be useful.
2024-01-25 16:02:04 -05:00
Pavlo Tkach
f9e0908022 Replace invoice email attachement with bucket link (#2299) 2024-01-25 14:08:08 -05:00
sarahcaseybot
b21e1a1935 Add required --build_environment flag to tld-sync Cloud Build job (#2306) 2024-01-25 12:27:05 -05:00
Lai Jiang
0112b3ae06 Make the formatting tasks work with Java 17 (take 2) (#2305)
We should not assume org.gradle.java.home to exist on kokoro or GCB.
2024-01-25 12:08:30 -05:00
Lai Jiang
a4903c27b9 Make the formatting tasks work with Java 17 (#2301)
TESTED=ran gradle jIFA locally after intentionally mis-formatting a Java
file.
2024-01-24 17:15:13 -05:00
sarahcaseybot
2166c28d6d Update to only include changes to check for production required tags (#2273) 2024-01-24 17:12:46 -05:00
Lai Jiang
891e7c0174 Make Kythe work with Java 17 (#2293)
TESTED=submitted a GCB job locally and it ran successfully.
2024-01-24 13:26:45 -05:00
Ben McIlwain
64f5971275 Include a better error message to debug nomulus tool not working (#2275)
Failures to initialize the tool transaction manager seem to often be caused by
stale local credentials.
2024-01-24 13:08:33 -05:00
sarahcaseybot
818944317f Add some updates to UpdateReservedListCommand to facilitate internal config presubmits and syncing (#2292)
* Add some updates to UpdateReservedListCommand to facilitate internal config presubmits and syncing

Added a dry-run tag for presubmit tests

Added early exit behavior when there are no new changes to the list

Added a new --build_environment tag to be used to indicate command runs from build tools. This tag was also added to UpdatePremiumListCommand. Once this new tag is deployed, and break glass behavior is added, these commands will be modified to prevent runs on the command line in the production environment unless the --build_environment or --break_glass flag is used.

* Fix capitalization

* Added in commented out production environment check for buildEnv flag
2024-01-23 17:32:33 -05:00
Weimin Yu
ea96ed300f Drop the BsaDomainInUse table (#2298)
Already renamed to BsaUnlockableDomain table.
2024-01-23 17:07:35 -05:00
Weimin Yu
8415c8bbe4 Fix typo in BsaRefreshAction (#2297) 2024-01-22 16:03:35 -05:00
Lai Jiang
dc48c257b5 Use Java 17 runtime on sandbox and production (#2296)
The blocking issue is fixed in
https://github.com/google/nomulus/pull/2224.

Java 8 support is being deprecated on 2024-01-31 and no further deployment is
possible afterwards without exception:

https://cloud.google.com/appengine/docs/legacy/standard/java/deprecations

We have been using Java 17 on alpha/crash/qa for several months and have
not oberved any other blocking issue other than possible missing email
attachements, which is being mitigated by including a link to the
attachments saved in GCS.
2024-01-22 15:21:17 -05:00
sarahcaseybot
2bf3867532 Add an example tld YAML config file (#2295) 2024-01-22 13:32:36 -05:00
Weimin Yu
44f44be643 Add bsa-refresh cron job to sandbox and prod (#2290)
This is the job that updates the unblockable domains according to recent
changes in domain registration and reservation.
2024-01-22 12:24:09 -05:00
Weimin Yu
f61579b350 Fix BsaRefreshAction bugs (#2294)
* Fix BsaRefreshAction bugs

Added functional tests for BsaRefreshAction, which checks for changes in
domain registration and reservation, and apply them to the Unblockable
domain list.

Fixed a few bugs exposed by the tests.

Also refactored a few other tests.
2024-01-22 12:23:29 -05:00
Ben McIlwain
c414e38a98 Add batching to BSA unavailable domains list generation (#2282)
This also moves it back to the replica transaction manager now that it shouldn't be timing
out its queries.

And this adds a test as well (more to come!).
2024-01-19 14:58:09 -05:00
sarahcaseybot
2cf2d7e7b1 Define the --build_environment flag and change --break_glass flag to a Boolean type (#2277)
* Define the --end_breakglass and --build_environment flags

It is necessary to define these flags in a deployment before merging go/r3pr/2273 in order to prevent breaking the exisitng TLD syncing and entity presubmit testing that has already been enabled

* make break glass 2 words

* Change break_glass flag to take a Boolean and use false value to end break glass mode

* small fixes

* Fix spacing

* Add missing G

* Add clarifying comment
2024-01-19 14:23:13 -05:00
Weimin Yu
432871add9 Fix a BSA bug and refactor some unit tests (#2291)
* Refactor a few BSA unit tests

Added a few helpers for managing reserved list in tests and updated the
tests to use them.

Also fixed a bug: when quering for newly created domains, the query
should be restricted to bsa-enrolled tlds.
2024-01-18 16:12:59 -05:00
dependabot[bot]
2621b2d679 Bump follow-redirects from 1.15.2 to 1.15.4 in /docs/console-endpoints (#2278)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Lai Jiang <jianglai@google.com>
2024-01-17 09:20:29 -05:00
Lai Jiang
7a5db3b8fe Upgrade builder image to use Java 17 (#2289)
TESTED=ran nomulus-release on alpha with the new image
2024-01-16 17:05:11 -05:00
dependabot[bot]
055f9c012c Bump follow-redirects from 1.15.3 to 1.15.4 in /console-webapp (#2283)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-16 15:34:45 -05:00
Pavlo Tkach
14ab9423f8 Update Angular to v17 (#2260) 2024-01-16 13:45:56 -05:00
sarahcaseybot
9223b81ab3 Remove create_tld and update_tld commands (#2261)
* Remove create_tld and update_tld commands

These commands are no longer necessary now that configure_tld command is available. However, the configure_tld command should only be used for crash, QA, and alpha environments. TLDs in production and sandbox must be modified using modifications to their config files in Gerrit unless using the configure_tld command in breakglass mode. Check the "How to configure TLDs" procedure doc for more info.

* re-delete file
2024-01-16 11:32:59 -05:00
Weimin Yu
1dcf34ccc2 Report BSA block status in DomainCheckFlow (#2288)
- Registered names are not affected.

- Reserved names are not affected.

- Names that are none of the above and match some BSA labels are
  reported as blocked.
2024-01-12 17:17:51 -05:00
Weimin Yu
9273d2bf15 Remove deleted BSA labels from database (#2286)
Fixed the bug that retains deleted BSA labels in the database.

Added a few simple end-to-end tests for BSA download.
2024-01-12 14:20:56 -05:00
Ben McIlwain
036d35c11a Make the BSA upload unvailable domains task work with HTTP GET as well (#2287)
Apparently Google Cloud Scheduler can only do GET, not POST, for some reason.
2024-01-12 12:17:52 -05:00
Ben McIlwain
a8ce34586d Add production cronjobs for BSA download/upload actions (#2285)
* Add production cronjob for uploading BSA unavailable names

* Add production cronjob for BSA download action
2024-01-11 18:38:52 -05:00
Ben McIlwain
26fb04f00c Add sandbox cronjob for upload BSA unavailable names (#2284) 2024-01-11 12:21:40 -05:00
Ben McIlwain
9d4c38684a Add a cron schedule for the BSA upload unavailable domains task (#2280)
Also fixes the action taken in the case where zero unavailable domains are
found, and temporarily changes over to using the primary DB (because the replica
transaction was timing out at 30 seconds on large databases). I'll switch this
over to use batching and move it back to replica afterwards, but this should
unblock us temporarily.
2024-01-10 14:34:06 -05:00
Pavlo Tkach
d7edd27cdd Add support for Ubuntu20 on kokoro (#2279) 2024-01-10 14:32:34 -05:00
Ben McIlwain
265d69051b Map /_dr/task/uploadBsaUnavailableNames in BSA service's web.xml (#2276)
This should have been part of PR #2265 but we all missed it.

Also adds a couple of useful logging statements.
2024-01-09 18:51:23 -05:00
Lai Jiang
b5d2b56426 Build Nomulus with Java 17 (#2255)
This PR makes it possible to build the Nomulus code base using Java 17.
Building with Java 11 continue to be possible and the resulting bytecodes are
still at Java 8 level. Also upgraded Gradle to 8.5.

There are several necessary changes to make this happen:

1. Some Gradle plugins need to be upgraded to support Java 17, notably
errorprone. As a result, a lot more "errors" were caught and corrected.

2. All test code are now built and run at Java 8 level. Previously it was left
undefined (which defaults to the version of the compiler) and had led to
situations where we inadvertently called Java 8+ features in production that
are not caught by tests. The change also made the java8compatibility subproject
obsolete, which is therefore removed.

3. Removed the docs subproject. Its main use is to generate flows.md, but it
relies heavily on Java internal APIs that have changed significant with each
version. Upgrading to Java 11 required extensive refactoring of the code there,
and Java 17 again removed many APIs that were used. I don't think it is worth
the maintenance effort just to have a tool to generate flows.md which no one
actually reads.

4. Capped a few GCP dependencies because the latest version depends on
 grpc-java >= 1.59.0, which includes a runtime incompatibility
 (https://github.com/grpc/grpc-java/releases/tag/v1.59.0).
2024-01-09 15:56:37 -05:00
Ben McIlwain
e79c63142a Add a daily batch action to upload unavailable domains to BSA (#2265) 2024-01-09 14:52:07 -05:00
Weimin Yu
f8ac7afc33 Check BSA block status in CheckApi (#2271)
* Check BSA block status in CheckApi

Checks for and reports BSA block status if the name is not registered or
reserved.

Also moves CheckApiActionTest to standardTest. Whatever problem forcing
it to another suite has apparently disappeared.
2024-01-09 13:19:07 -05:00
Ben McIlwain
e56e751652 Fix build warnings (#2274)
All of these were causing warnings to appear during a build of the codebase.
2024-01-09 13:15:54 -05:00
Ben McIlwain
cfdf12aa7d Add OkHttp as a library used by the core Nomulus project (#2272)
This will be used in a subsequent PR (see #2265) to upload the unavailable
domain names list to the BSA endpoint.
2024-01-09 12:53:15 -05:00
Weimin Yu
811b385544 Add cron config for the bsaDownload job in Sandbox (#2267)
* Add cron config for the bsaDownload job in Sandbox
2024-01-05 11:10:48 -05:00
Weimin Yu
3f5c9d1246 BSA for integration test (#2256)
Supports the full blocklist download cycle (download, diffing, diff-apply, and order-status reporting) and the refreshing of unblockable domains.

Submitted due to tight deadline. We will conduct post-submit review and refactoring.
2024-01-05 11:09:40 -05:00
Pavlo Tkach
5315752bc0 Add ICANN csv response GZIP decoding (#2269) 2024-01-04 18:35:21 -05:00
Pavlo Tkach
4eee7b8c0d Add support for bsa service to cloud tasks config (#2268) 2024-01-03 17:38:42 -05:00
Weimin Yu
ecb39d5899 Use custom whois message for bsa-blocked domain (#2241)
* Use custom whois message for bsa-blocked domain
2024-01-02 14:40:34 -05:00
Lai Jiang
42b508427b Bypass SCRYPT hashing in tests (#2262)
SCRYPT is much computationally heavier than SHA265 (by design), which
resulted in test run time doubling due to most tests initializing canned
data that uses hashing.

Since out tests are not verifying the correctness of a specific hashing
algorithm anyway, this PR makes it so that simple concatenation is used
in tests.

Also moved RegistryEnvironment to the util subproject so it can be called by
PasswordUtils, which makes sense as it is a utility class.
2023-12-21 16:17:37 -05:00
sarahcaseybot
20b5b43501 Add type conversion to TimedTransitionProperty<Money> deserializer to handle JPY currency (#2258)
* Add BigInt conversion to TimedTransitionProperty<Money> deserializer to handle JPY currency

* Remove unnecessary lines in test

* Add eap schedule check

* Don't use raw LinkedHashMap type

* add timezone
2023-12-21 12:59:54 -05:00
Lai Jiang
08285f5de7 Greatly increase the upper limit of proxy instances in production (#2259)
From our investigation, the Monday night WHOIS storm does not cause any
strain to the backend system. The backend latency metrics are all well within
the limits. The latency measured from the proxy matches observed latency
by the prober, and we see that the "used" CPU is 1.5x of "requested" CPU
during the time when the latency is above the threshold.

Making this change hopefully removes the proxy as the bottleneck and
ameliorate the pages.
2023-12-20 15:37:29 -05:00
Pavlo Tkach
fb4c5b457d Prevent reusing ianaId for real registrars (#2257) 2023-12-20 15:20:04 -05:00
Pavlo Tkach
781c212275 Add IcannHttpReporter failed response logging (#2252) 2023-12-18 11:03:33 -05:00
Weimin Yu
c73f7a6bd3 Add the BsaDomainRefresh entity (#2250)
Add the BsaDomainRefresh class which tracks the refresh actions.

The refresh actions checks for changes in the set of registered and
reserved domains, which are called unblockables to BSA.
2023-12-13 16:08:37 -05:00
Lai Jiang
8d793b2349 Do not double-enqueue NordnVerifyAction (#2253)
Currently, a verify action is enqueued every time the upload method
succeeds. Because the upload job is wrapped in a transaction, the
same task will be enqueued again if the transaction retries.

We cannot move the upload method outside the transaction because the
read-upload-write logic needs to be atomic, and the upload part itself
is idempotent (therefore retri-able). We can, however, move the
enqueuing part outside the transaction as we only need to enqueue the
verify task once the transaction succeeds. This should fix the issue
where multiple verify jobs try to hit the same marksdb endpoints,
resulting in 429 (Too Many Requests) errors.
2023-12-12 16:00:35 -05:00
Weimin Yu
55d5f8c6f8 Forbid domain creation with label blocked by BSA (#2236)
* Forbid domain creation with label blocked by BSA

Add a BSA label check in the DomainCreation flow.
2023-12-11 22:14:12 -05:00
Pavlo Tkach
9006312253 Create reusable dialog / bottom sheet component (#2237) 2023-12-08 17:52:57 -05:00
gbrodman
e5e2370923 Debouncedly use a search term in console domain list (#2242) 2023-12-08 15:37:30 -05:00
sarahcaseybot
b3b0efd47e Add a dryrun tag to UpdatePremiumListCommand and early exit command if no new changes to the list (#2246)
* Add a dryrun tag to UpdatePremiumListCommand and early exit command if no new changes to the list

* Change prompt string when no change to list to reflect that there is no actual prompted user input

* Add camelCase and correct flag name
2023-12-08 14:35:05 -05:00
Lai Jiang
e82cbe60a9 Do not log nested transactions in production (#2251)
This might be the cause of the SQL performance degradation that we are
observing during the recent launch. The change went in a month ago but
there hasn't been enough increase in mutating traffic to make it
problematic until the launch.

Note that presubmits should run faster too with this chance, which
serves as an evidence that excessive logging is the culprit.
2023-12-07 19:02:16 -05:00
Weimin Yu
923bc13e3a Start using Tld's bsaEnrollStartTime field (#2239)
* Start using Tld's bsaEnrollStartTime field

    Longer-term change is tracked in b/309175410
2023-12-06 17:11:36 -05:00
Lai Jiang
4893ea307b Check for null error stream (#2249) 2023-12-06 13:30:37 -05:00
Pavlo Tkach
01f868cefc Increase number of service to 5 in cloudbuild-deploy (#2248) 2023-12-06 13:21:17 -05:00
Weimin Yu
1b0919eaff Add the BsaDomainRefresh table (#2247)
Add the BsaDomainRefresh table which tracks the refresh actions.

The refresh actions checks for changes in the set of registered and
reserved domains, which are called unblockables to BSA.
2023-12-06 11:55:42 -05:00
Lai Jiang
92b23bac16 Use the error stream when HTTP response code is non-200 (#2245) 2023-12-06 10:42:19 -05:00
gbrodman
cc9b3f5965 Filter in SQL when updating/deleting alloc tokens (#2244)
This doesn't fix any issues with dead/livelocks when deleting or
updating allocation tokens, but it at least will significantly reduce
the time to load the tokens that we'll want to update/delete.
2023-12-04 19:24:17 -05:00
gbrodman
dd86c56ddc Return the correct renewal fee for anchor tenants in domain checks (#2238)
The code as previously written assumed that creation fees would be the
same as renewal fees -- this is not the case for anchor tenants, where
the renewal fee is always the standard cost for the TLD (instead of any
premium cost). This was already handled properly in the actual billing
implementation, but we didn't tell the user the right renewal cost in
domain checks.

This also removes some warning logs related to nested transactions
2023-12-01 15:37:05 -05:00
Pavlo Tkach
08551f7bc7 Enable static ip for bsa service production (#2240) 2023-12-01 14:25:38 -05:00
Lai Jiang
e7171a326b Use reTransact when loading caches (#2234)
Similar to #2179, but adds a few calls missed in that PR.
2023-11-30 15:13:36 -05:00
gbrodman
c3eae7b76f Add an optional search term for ConsoleDomainListAction (#2225)
It's a case-insensitive query and it can appear anywhere (including
TLDs)
2023-11-30 11:42:50 -05:00
Pavlo Tkach
2687181045 Update console file naming to be camelCase like (#2235) 2023-11-30 11:42:36 -05:00
gbrodman
68750569db Pretty-print reserved list updates in the CLI (#2226)
We shouldn't have to parse through every single entry to see what
changed

Note: we don't do this for premium lists because those can be HUGE and
we don't want/need to load and display every entry. This was an explicit
choice made in https://github.com/google/nomulus/pull/1482
2023-11-30 11:32:12 -05:00
Lai Jiang
028e5cc958 Make read-only transactions more performant (#2233)
Since the replica SQL instance is read-only, any transaction performed
on it should be explicitly read-only, which would allow PostgreSQL to
optimize away (some) use of predicate locks.

Also changed the EPP cache to read from the replica. The foreign key
cache already behaves this way.

See: https://www.postgresql.org/docs/current/transaction-iso.html
2023-11-29 15:55:50 -05:00
Weimin Yu
853e571d01 Add more BSA configs (#2230)
* Add more BSA configs

Added urls for reporting order and domains to BSA.

Also added operational configs.
2023-11-28 16:40:36 -05:00
Lai Jiang
9b79f5af2c Add a dedicated IP header to accommodate Java 17 on GAE (#2224)
For reasons unclear at this point, Java 17's servlet implementation on
GAE injects IP addresses (including unroutable private IPs) into the
standard X-Forwarded-For header, which we currently use to embed
registrar IP addresses to check against the allow list. This results in
the server not properly parsing the header and rejecting legitimate
connections.

This PR sets a custom header that should not be interfered with by any
JVM implementation to store the IP address, while maintaining the old
header as a fallback. The proxy will set both headers to allow the
server to gracefully migrate from Java 8 and Java 17 (and potentially
rollback).

Also removed some headers and logic that are not used.
2023-11-28 13:20:01 -05:00
Weimin Yu
4195871541 Fix misconfiguration in new BSA service (#2227)
Also add dependency locking to services:bsa
2023-11-27 20:18:34 -05:00
Weimin Yu
504d7ccaac Preparing renaming BsaDomainInUse table (#2228)
Add the replacement table: BsaUnblockableDomain
2023-11-27 19:55:47 -05:00
gbrodman
36a8908712 Add a basic domain-list page to the new console (#2219)
This does not include any styling for now, just wanted to make sure
we're all good with regards to the basic approach. I'm open to suggestion on
which columns to include.

Note: filter searching is not implemented yet because the backend does
not allow for it (yet)
2023-11-27 14:58:48 -05:00
Weimin Yu
e42c11051e Download scheduler for BSA (#2209)
* Add BSA download scheduler
2023-11-17 16:15:14 -05:00
Weimin Yu
85b588b51f Add a disposition header to email attachments (#2223)
This may help with the billing-team with attached invoices.

This is a standard header that should do no harm.
2023-11-16 13:31:12 -05:00
Pavlo Tkach
572b7101cb Create separate BSA service (#2221) 2023-11-15 18:38:26 -05:00
Weimin Yu
445825957d Bsa Persistence entity classes (#2205)
* Add persistence model object
2023-11-15 16:43:22 -05:00
Weimin Yu
7ab76f3573 Pin Flyway tool jar to 9.22.3 (#2222)
Flyway 10+ is not compatible with Java 8.

Rollback this change after we upgrade to Java 11.
2023-11-15 14:48:55 -05:00
Weimin Yu
9e3c58989a Add an IDN helper (#2217)
* Add an IDN helper

Add a helper that checks the validity of labels in IDNs.
All organizes TLDs according to the IDNs they support.
2023-11-10 19:55:04 -05:00
Lai Jiang
cf9c1ec7c3 Use Java 8 runtime on sandbox and production (#2218)
Java 17 injects unexpected headers to X-Forwarded-For, which causes
issues with validating incoming IP addresses.

This is a partial reversion of #2201. We are still keeping Java 17 in other environment but sandbox and production needs to be able to parse the header to accept incoming EPP connections from registrars. Once we fix it we will re-enable Java 17 in these environment.
2023-11-10 14:39:16 -05:00
Pavlo Tkach
69ea87be31 Add handler for Console API requests and XSRF token creation and verification (#2211) 2023-11-09 17:51:53 -05:00
Lai Jiang
779d0c9d37 Add a fallback token verifier (#2216)
This allows us to switch the proxy to a different client ID without
disrupting the service. This is a temporary measure and will be removed
once the switch is complete.
2023-11-09 16:05:14 -05:00
Weimin Yu
2855944214 Add TLD BSA enroll start date to schema (#2215)
Also adds a placeholder getter in the Tld class, so that it can be
mocked/spied in tests. This way more BSA related code can be submitted
before the schema is deployed to prod.
2023-11-09 13:52:45 -05:00
Ben McIlwain
992d1c1349 Reduce the QPS of the refresh DNS for all domains action (#2212)
This also adds a targeted QPS as a parameter in case we need to manually bump it
up (or down) for some reason without having to make code changes and re-deploy.
2023-11-08 13:47:37 -05:00
Pavlo Tkach
f50290ce1d Add static IP connector to crash and alpha configs (#2213) 2023-11-08 13:26:32 -05:00
Pavlo Tkach
e647d4e215 Add retry to cloud build node installation (#2210) 2023-11-06 09:15:36 -05:00
Lai Jiang
08471242df Refactor transact() related methods. (#2195)
This PR makes a few changes to make it possible to turn on
per-transaction isolation level with minimal disruption:

1) Changed the signatures of transact() and reTransact() methods to allow
passing in lambdas that throw checked exceptions. Previously one has
always to wrap such lambdas in try-and-retrow blocks, which wasn't a
big issue when one can liberally open nested transactions around small
lambdas and keeps the "throwing" part outside the lambda. This becomes a
much bigger hassle when the goal is to eliminate nested transactions and
put as much code as possible within the top-level lambda. As a result,
the transactNoRetry() method now handles checked exceptions by re-throwing
them as runtime exceptions.

2) Changed the name and meaning of the config file field that used to
indicate if per-transaction isolation level is enabled or not. Now it
decides if transact() is called within a transaction, whether to
throw or to log, regardless whether the transaction could have
succeeded based on the isolation override level (if provided). The
flag will initially be set to false and would help us identify all
instances of nested calls and either refactor them or use reTransact()
instead. Once we are fairly certain that no nested calls to transact()
exists, we flip the flag to true and start enforcing this logic.
Eventually the flag will go away and nested calls to transact() will
always throw.

3) Per-transaction isolation level will now always be applied, if an
override is provided. Because currently there should be no actual
use of such feature (except for places where we explicitly use an
override and have ensured no nested transactions exist, like in
RefreshDnsForAllDomainsAction), we do not expect any issues with
conflicting isolation levels, which would resulted in failure.

3) transactNoRetry() is made package private and removed from the
exposed API of JpaTransactionManager. This saves a lot of redundant
methods that do not have a practical use. The only instances where this
method was called outside the package was in the reader of
RegistryJpaIO, which should have no problem with retrying.
2023-11-03 17:43:27 -04:00
Lai Jiang
cd23fea698 Switch to a stronger algorithm for password hashing (#2191)
We have been using SHA256 to hash passwords (for both EPP and registry lock),
which is now considered too weak.

This PR switches to using Scrypt, a memory-hard slow hash function, with
recommended parameters per go/crypto-password-hash.

To ease the transition, when a password is being verified, both Scrypt
and SHA256 are tried. If SHA256 verification is successful, we re-hash
the verified password with Scrypt and replace the stored SHA256 hash
with the new one. This way, as long as a user uses the password once
before the transition period ends (when Scrypt becomes the only valid
algorithm), there would be no need for manual intervention from them.

We will send out notifications to users to remind them of the transition
and urge them to use the password (which should not be a problem with
EPP, but less so with the registry lock). After the transition,
out-of-band reset for EPP password, or remove-and-add on the console for
registry lock password, would be required for the hashes that have not
been re-saved.

Note that the re-save logic is not present for console user's registry
lock password, as there is no production data for console users yet.
Only legacy GAE user's password requires re-save.
2023-11-03 17:29:01 -04:00
Ben McIlwain
ba54208dad Also load domains for domain checks of type renew/transfer (#2207)
The domains (and their associated billing recurrences) were already being loaded
to check restores, but they also now need to be loaded for renews and transfers
as well, as the billing renewal behavior on the recurrence could be modifying
the relevant renew price that should be shown. (The renew price is used for
transfers as well.)

See https://buganizer.corp.google.com/issues/306212810
2023-11-03 14:33:34 -04:00
Weimin Yu
b5e131ecba Add BSA schema (#2204)
* Add BSA schema

Also lock down flyway due to java8 compatiblity
2023-11-02 15:38:23 -04:00
Pavlo Tkach
87e99f59bc Replace node.js installation method in build.sh (#2206) 2023-11-02 14:17:18 -04:00
Weimin Yu
30accea383 Add keyring support for BSA API key (#2208)
* Add keyring support for BSA API key

Also removing JSON_CREDENTIAL. It is an exported service account key,
which we no longer use.
2023-11-02 14:08:50 -04:00
Lai Jiang
72e0101746 Delete unused actions (#2197)
Both actions have not been used for a while (the wipe out action
actually caused problems when it ran unintentionally and wiped out QA).
Keeping them around is a burden when refactoring efforts have to take
them into consideration.

It is always possible to resurrect them form git history should the need
arises.
2023-11-02 11:41:03 -04:00
Lai Jiang
3090df9a78 Upgrade to Java 17 runtime (#2201)
We finally fixed Spinnaker (I hope) to deploy bundled services with Java
17 runtime. Note that the bytecodes are still targeting Java 8. The only
change this PR introduces is to switch the runtime environment to Java
17.

TESTED=deployed to crash.
2023-11-02 10:08:14 -04:00
gbrodman
7332b1fa38 Add TypeAdapters for VKey objects (#2194)
GSON doesn't allow for clean (de)serialization of Class or Serializable
objects which we'll need for converting VKeys to/from JSON.
2023-10-31 15:14:41 -04:00
Lai Jiang
9330e3a50d Move truely public endpoints to a separate Auth (#2200)
This allows us to more easily refactor public endpoints that still use
the legacy auth mechanism to identify logged-in users (for the legacy
console).
2023-10-31 13:58:45 -04:00
gbrodman
1d6b119340 Add a console action to retrieve a paged list of domains (#2193)
In the future we'll want to add searching capability but for now we can
go with straightforward pagination.
2023-10-30 17:01:31 -04:00
Weimin Yu
8158f761c8 Add BSA configurations (#2202) 2023-10-30 16:44:28 -04:00
Pavlo Tkach
08838e091f Enable BACKEND service to route external traffic through VPC on Sandbox (#2199) 2023-10-30 13:36:04 -04:00
sarahcaseybot
59720a207d Change the default config for perTransactionIsolation to true (#2196)
This was already set to true in all environments except prod last week. Now that the release has gone out and we have not seen any issues, we should feel safe turning this on in production as well.
2023-10-26 17:16:02 -04:00
Pavlo Tkach
26bae65e1e Add registrar details view (#2186) 2023-10-26 09:14:09 -04:00
Pavlo Tkach
23a2861b37 Remove node.js download instruction (#2192) 2023-10-25 14:48:35 -04:00
Pavlo Tkach
341238305d Update console versions (#2190) 2023-10-24 09:34:02 -04:00
Lai Jiang
d210bed744 Add connection.disconnect() in finally blocks (#2189) 2023-10-23 16:38:16 -04:00
dependabot[bot]
fe710e5510 Bump postcss from 8.4.21 to 8.4.31 in /console-webapp (#2187)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.21 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.21...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 10:29:49 -04:00
sarahcaseybot
8f8ffe7020 Add a dryrun flag to configure_tld command (#2188)
This will be used for presubmit testing.
2023-10-20 16:16:05 -04:00
Lai Jiang
16e5018489 Update postcss version (#2185)
Per https://github.com/google/nomulus/security/dependabot/17
2023-10-20 13:30:40 -04:00
Lai Jiang
af303bd26f Remove URLFetch (#2181)
We previously needed to use URLFetch in some instances where TLS 1.3 is
required (mostly when connecting to ICANN servers),and the JDK-bundled SSL
engine that came with App Engine runtime did not support TLS 1.3.

It appears now that the Java 8 runtime on App Engine supports TLS 1.3
out of the box, which allows us to get rid of URLFetch, which depends on
App Engine APIs.

Also removed some redundant retry and logging logic, now that we know
the HTTP client behaves correctly.

TESTED=modified the CannedScriptExecutionAction and deployed to alpha, used the
new HTTP client to connect to the three URL endpoints that were
problematic before and confirmed that TLS connections can be established. HTTP
sessions were rejected in some cases when authentication failed, but
that was expected.
2023-10-19 14:51:51 -04:00
sarahcaseybot
bf3bb5d804 Add a Cloud Build job for syncing Tld configuration files from the internal repo with the database (#2174)
* Add a cloudbuild-tld-sync job

This job checks the Tld config files in the internal repo and syncs them with the actual Tld objects in the database using the configure_tld numulus command.

* Add the dockerfile and shell script

* Force the command

* Add comments

* add newline

* Create a separate copy of the job for each environment

* fix file name

* Fix indentation
2023-10-19 14:01:40 -04:00
dependabot[bot]
dcb16e05bd Bump @babel/traverse from 7.22.10 to 7.23.2 in /console-webapp (#2184)
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.22.10 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-19 11:46:02 -04:00
sarahcaseybot
2facedd60f Lower the isolation level for RefreshDnsForAllDomainsAction (#2182)
* Lower the isolation level for RefreshDnsForAllDomainsAction

This lowers the isolation level to TRANSACTION_REPEATABLE_READ which will hopefully allow the action to run the entire action without timing out on our larger TLDs.

* Unchange default config
2023-10-17 16:58:37 -04:00
Lai Jiang
b1ec81f054 Remove the wipeout job on QA (#2183) 2023-10-17 13:05:31 -04:00
gbrodman
779da518df Pass name/email/phone info to the new console front end (#2180) 2023-10-16 16:51:35 -04:00
sarahcaseybot
4f53ae0e89 Use reTransact when loading the cache for database objects (#2179)
Cache loads will likely always be inner transactions, if they have a transaction at all. Cache loads do not always call a transaction since they are only necessary if the cache is not fresh at the time it is called. Since the cache itself needs to decide whether or not a DB transaction is necessary, it should use the reTransact method to safely indicate that the isolation level of the outer transaction is what should be used.
2023-10-16 15:22:22 -04:00
gbrodman
da04caeea2 Don't check cert validation if we're not changing the certs in the console (#2178)
If the cert(s) are invalid or expired that's a problem, but that
shouldn't necessarily prevent us from changing other things. If we're
not changing the certs, leave them alone.
2023-10-16 13:37:57 -04:00
gbrodman
a63916b08e Refine error handling in RequestHandler and the console slightly (#2177)
If we don't explicitly handle random unexpected exceptions, the error
that the front end receives includes a big ole stacktrace, which is
unhelpful for regular users and possibly bad to expose. Instead, we
should provide a vague "something went wrong" message.

Separately, we can create a default SnackBar options and use that (we
want it longer than 1.5 seconds because that's pretty short).
2023-10-12 14:03:12 -04:00
Lai Jiang
36bd508bf9 Remove OAuthAuthenticationMechanism (#2171)
Also made some refactoring to various Auth related classes to clean up things a bit and make the logic less convoluted:

1. In Auth, remove AUTH_API_PUBLIC as it is only used by the WHOIS and EPP endpoints accessed by the proxy. Previously, the proxy relies on OAuth and its service account is not given admin role (in OAuth parlance), so we made them accessible by a public user, deferring authorization to the actions themselves. In practice, OAuth checks for allowlisted client IDs and only the proxy client ID was allowlisted, which effectively limited access to only the proxy anyway.

2. In AuthResult, expose the service account email if it is at APP level. RequestAuthenticator will print out the auth result and therefore log the email, making it easy to identify which account was used. This field is mutually exclusive to the user auth info field. As a result, the factory methods are refactored to explicitly create either APP or USER level auth result.

3. Completely re-wrote RequestAuthenticatorTest. Previously, the test mingled testing functionalities of the target class with testing how various authentication mechanisms work. Now they are cleanly decoupled, and each method in RequestAuthenticator is tested individually.

4. Removed nomulus-config-production-sample.yaml as it is vastly out of date.
2023-10-11 19:12:26 -04:00
Lai Jiang
bbdbfe85ed Remove the GAIA ID column from the User table (#2172)
The field has already been removed from the Java code base in #2170.
2023-10-11 12:47:48 -04:00
gbrodman
2a7e9a266a Fix minor alignment issue on console WHOIS page (#2166) 2023-10-11 09:25:05 -04:00
Weimin Yu
bd0d8af7b3 Make sure unsafe names can be sent in emails (#2169)
Surround the dot in unsafe domain names with a square bracket. This
is suggested by Gmail abuse-detection and allows outgoing messages
to pass Gmail's check. This should also help with recipients' checks.
2023-10-05 11:19:31 -04:00
Lai Jiang
2da8ea0185 Replace JacksonFactory with GsonFactory (#2173)
JacksonFactory is deprecated and GsonFactory is the recommended
replacement.
2023-10-04 17:02:13 -04:00
Lai Jiang
7a84844000 Remove the GAIA ID field from User (#2170)
It is not used and it is not possible to derive the GAIA ID when
creating a new User from the email address alone.
2023-10-04 15:32:03 -04:00
Weimin Yu
1580555d30 Throttle outgoing emails (#2168)
Adds a delay between emails sent in a tight loop. This helps avoid
triggering Gmail abuse detections.

Also updated the recipient address for billing alerts.
2023-10-04 11:16:56 -04:00
Pavlo Tkach
4fb8a1b50b Add dark theme support to the console (#2167) 2023-10-03 15:54:25 -04:00
Pavlo Tkach
e07f25000d Add console registrars paging, fix empty registrars mobile (#2162) 2023-10-03 15:51:48 -04:00
sarahcaseybot
cc1777af0c Add custom YAML serializer for Duration (#2161)
* Add custom YAML serializer for Duration

This addresses b/301119144. This changes the YAML representation of a TLD to show Duration fields as a String reperesntation using the Java Duration object's toString() format. This eliminates the previous ambiguity over the time unit that is being used for each duration.

* change standardSeconds to standardMinutes in test

* Add custom serializer to the entire mapper
2023-10-03 13:46:19 -04:00
Lai Jiang
87e54c001f Remove unused fields to make the linter happy (#2165) 2023-10-03 13:25:07 -04:00
Pavlo Tkach
2dc87d42b4 Fix console nextUrl stacking routes (#2164) 2023-10-02 17:38:03 -04:00
Lai Jiang
1eed9c82dc Deprecate the OAuth header in Nomulus tool (#2160)
Unless an --oauth flag is used, the nomulus tool will only send the OIDC
header. The server still accepts both headers and the user should use
`create_user` command to create an admin User (with the --oauth flag on), which
will then allow one to use the nomulus tool without the --oauth flag.

The --oauth flag and the server's ability to support OAuth-based
authentication will be removed soon. Users are urged to create the User
object in time to avoid service interruption.

TESTED=verified on alpha.
2023-10-02 15:50:30 -04:00
gbrodman
cf43de7755 Open resources link in new tab (#2163)
We want to do this because it takes the user to an external site, which
could potentially lead to confusion if they tried to use the back button
without a new tab.
2023-10-02 15:06:33 -04:00
Weimin Yu
f54bec7553 Add docs for Cloud Build status notification (#2157)
Add documentation that describes the current Cloud Build status notification
to Google Chat, as well as how to update the configuration and the
notifier service.
2023-09-29 10:49:15 -04:00
gbrodman
cf698c2586 Add page for WHOIS-editable fields in the console (#2155)
This isn't the prettiest thing, but it replicates the type of view /
edit functionality that we had in the original console.

Of note: this doesn't include input field validation, which would
probably be a good idea to add at some point.
2023-09-28 22:46:18 -04:00
Lai Jiang
cb240a8f03 Use equals() method to compare equality (#2158)
It will call equalsImmutableObject(), which seems the right thing to do.
We only care if the two Tld objects have the same fields, not if they
are the same object. ErrorProne complained about comparison by identity.
2023-09-28 13:27:36 -04:00
gbrodman
0801679173 Close sidenav on click (#2156)
It shouldn't stick around after we've clicked on one of the links
2023-09-25 14:43:07 -04:00
sarahcaseybot
a87c4a31a3 Add breakglass handling to configureTldCommand (#2154)
* Add a breakglass flag to configureTldCommand

* Add tests

* small fixes
2023-09-22 11:51:02 -04:00
sarahcaseybot
58c7e3a52c Change __REMOVEDOMAIN__ token to __REMOVE_BULK_PRICING__ (#2152) 2023-09-21 16:03:39 -04:00
Pavlo Tkach
dded258864 Add resources widget front-end (#2151) 2023-09-21 13:59:40 -04:00
Lai Jiang
759143535f Update proxy k8s manifest (#2153)
The beta API is deprecated.

TESTED=deployed the new manifest to alpha. Without the change, deploying
resulted in an error.

<!-- Reviewable:start -->
- - -
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/google/nomulus/2153)
<!-- Reviewable:end -->
2023-09-21 10:53:39 -04:00
Weimin Yu
46fdf2c996 Defend against deserialization-based attacks (#2150)
* Defend against deserialization-based attacks

Added the `SafeObjectInputStream` class that defends attacks using
malformed serialized data, including remote code execution and
denial-of-service attacks.

Started using the new class to handle EPP resource VKeys and
PendingDeposits, which are passed across credential-boundaries: between
TaskQueue and AppEngine server, and between AppEngine server and the RDE
pipeline on GCE. Note that the wireformat of VKeys do not change,
therefore existing tasks sitting in the TaskQueue are not affected.

Also removed an unused class: JaxbFragment.
2023-09-20 16:56:56 -04:00
sarahcaseybot
fc1857717d Use PrintStream in ConfirmingCommand (#2140)
* Use PrintStream in ConfirmingCommand

* Add errorPrintStream

* remove unneccesary line
2023-09-19 12:11:18 -04:00
sarahcaseybot
e182692a5f Check for diffs in ConfigureTldCommand (#2146)
* Check for diffs in ConfigureTldCommand

* undo override

* Add handling for ordering sets

* Fix comments

* fix formatting

* fix test
2023-09-19 12:10:26 -04:00
gbrodman
a65e85f9e1 Don't include a nextUrl when accessing the console homepage (#2149)
In this case we should just display the standard page, no need to
redirect anywhere since there's nothing to redirect to.
2023-09-15 12:28:04 -04:00
Lai Jiang
2713a10a07 Redact OAuth access token in prod (#2148)
This token is only ever used for logging. The GAE OAuth service will
parse the header directly when called to retrieve the current user and
user id. Logging it in prod could be a security risk if the logs are
leaked.
2023-09-14 13:53:56 -04:00
Pavlo Tkach
5eb44c165c Add settings to console home page, update settings->security styles (#2144) 2023-09-14 12:37:54 -04:00
Lai Jiang
6c18ea9cff Use constant-time comparison when validating client cert hashes (#2147)
Per b/298447714, non-constant-time comparison is prone to brute-force
attacks.
2023-09-14 12:37:20 -04:00
Lai Jiang
43692d3409 Use Java 11 for CodeQL (#2145) 2023-09-13 12:08:33 -04:00
Lai Jiang
38b73b9ecd Upgrade to gradle 8.3 (#2142) 2023-09-13 11:11:49 -04:00
sarahcaseybot
954537291f Disable test failing in cloudbuild (#2143) 2023-09-12 17:06:18 -04:00
Pavlo Tkach
9434d01234 Add /console/userdata endpoint (#2137)
Provides initial set of data, necessary to start the UI
2023-09-12 16:29:53 -04:00
gbrodman
3dafaff2c0 Pass around the full URL in RegistrarGuard (#2139)
Previously this didn't properly deal with nested routings, e.g.
"settings/whois". It tried to just pass "whois" as the next url which
doesn't work with the router because it's nested under the settings.
Using all parts of the URL allows us to handle the nesting.
2023-09-12 15:37:12 -04:00
gbrodman
ca25e4dfbd Use registrar ID, not name, in selector (#2138) 2023-09-12 14:11:31 -04:00
Lai Jiang
6047c16f3e Make Kythe work with Gradle 8 (#2141)
Mostly implementing the fix suggested by b/294850265. Tested by
submitting a job to GCB which ran successfully.
2023-09-12 10:47:57 -04:00
sarahcaseybot
1248c25041 Add a configureTld command that uses YAML files for configuration (#2117)
* Add a configureTld command that uses YAML

* Add more tests and edge case handling

* Add out of order test and fix wrong inject

* small changes

* Add check for ascii

* Add check for ROID suffix
2023-09-06 16:17:22 -04:00
Pavlo Tkach
001e9363a1 Add billing details (#2136)
This adds functionality to billing details widget on home screen
2023-09-06 14:37:58 -04:00
Weimin Yu
9a6a7116da Disable Hibernate error logging (#2134)
Hibernate logs certain information at the ERROR level, which for the
purpose of troubleshooting is misleading, since most affected operations
succeed after retry. ERROR-level logging should only be added by Nomulus
code.

This PR does two things:
1. Disable all logging in two Hibernate classes: we cannot disable
   logging at a finer granularity, and we cannot preserve lower-level
   logging while disabling ERROR.
2. Adds a DatabaseException which captures all error details that may
   escape the typical loggers' attention: SQLException instances can be
   chained in a different way from Throwable's `getCause()` method.
2023-09-06 13:25:21 -04:00
Pavlo Tkach
335af52112 Allow homepage widgets to rearrange on mobile (#2135) 2023-09-05 20:45:13 -04:00
Pavlo Tkach
1929654f8c Replace less with scss, add homepage widgets, add registrars selector and registrars page (#2114)
* Replace less compiler with scss

* Replace less with scss, add homepage widgets, add registrars selector and registrars page
2023-09-01 11:48:30 -04:00
Ben McIlwain
6b5ec36eed Better configure DocumentBuilderFactory to help prevent XXE (#2132)
For more information see: https://community.veracode.com/s/article/Java-Remediation-Guidance-for-XXE
2023-08-30 10:17:37 -04:00
sarahcaseybot
ebf07833e5 Only allow CREATE EPP commands on BULK_PRICING tokens (#2131)
* Only allow CREATE EPP commands on BULK_PRICING tokens

* small fixes
2023-08-29 16:00:29 -04:00
Weimin Yu
ee3ece8c56 Use gmail to send invoices (#2130) 2023-08-29 14:25:54 -04:00
Ben McIlwain
57592d787c Add a new MutatingFlow interface and make most flows use TransactionalFlow (#2129)
The old semantics for TransactionalFlow meant "anything that needs to mutate the
database", but then FlowRunner was not creating transactions for
non-transactional flows even though nearly every flow needs a transaction (as
nearly every flow needs to hit the database for some purpose).  So now
TransactionalFlow simply means "any flow that needs the database", and
MutatingFlow means "a flow that mutates the database". In the future we will
have FlowRunner use a read-only transaction for TransactionalFlow and then a
normal writes-allowed transaction for MutatingFlow. That is a TODO.

This also fixes up some transact() calls inside caches to be reTransact(), as we
rightly can't move the transaction outside them as from some callsites we
legitimately do not know whether a transaction will be needed at all (depending
on whether said data is already in memory). And it removes the replicaTm() calls
which weren't actually doing anything as they were always nested inside of normal
tm()s, thus causing confusion.
2023-08-28 17:04:41 -04:00
Weimin Yu
e6f9b1c7e6 Using Gmail for most use cases (#2126)
* Using Gmail for most use cases
2023-08-28 11:11:59 -04:00
gbrodman
7b59c4abbf Alphabetize YAML output by property name (#2128)
This makes printing the TLDs prettier and makes it easier to find
fields.
2023-08-25 16:30:37 -04:00
Ben McIlwain
f01adfb060 Add tm().reTransact() methods and refactor away some inner transactions (#2125)
In the future, reTransact() will be the only way to initiate a transaction that
doesn't fail when called inside an outer wrapping transaction (when wrapped,
it's a no-op). It should be used sparingly, with a preference towards
refactoring the code to move transactions outwards (which this PR also
contains).

Note that this PR includes some potential efficiency gains caused by existing
poor use of transactions. E.g. in the file RefreshDnsAction, the existing code
was using two separate transactions to refresh the DNS for domains and hosts
(one is hidden in loadAndVerifyExistence(), whereas now as of this PR it has a
single wrapping transaction to do so.
2023-08-25 14:03:25 -04:00
Ben McIlwain
739a15851d Remove a couple unnecessary inner transact() calls (#2124)
Also refactors a function to no longer unnecessarily return a low level Iterable
type.
2023-08-24 18:10:44 -04:00
sarahcaseybot
2c961b6283 Inject getTldCommand in RegistryToolComponent (#2123) 2023-08-24 16:56:40 -04:00
Weimin Yu
bcb2b2c784 Use Gmail for RegistryLock emails (#2122) 2023-08-24 15:18:47 -04:00
Lai Jiang
a91ed0f1ad Allow nested transactions when per-transaction isolation level is on (#2121)
It turns out that disallowing all nested transaction is impractical. So
in this PR we make it possible to run nested transactions (which are not
really nested as far as SQL is concerned, but rather lexically nested
calls to tm().transact() which will NOT open new transactions when
called within a transaction) as long as there is no conflict between the
specified isolation levels between the enclosing and the enclosed
transactions.

Note that this will change the behavior of calling tm().transact() with
no isolation level override, or a null override INSIDE a transaction.
The lack of the override will allow the nested transaction to run at
whatever level the enclosing transaction runs at, instead of at the
default level specified in the config file.
2023-08-24 14:35:59 -04:00
Weimin Yu
da28a2021c Use Gmail in Icann reporting actions (#2119) 2023-08-23 14:39:26 -04:00
Weimin Yu
ffd952a60e Fix Cloud Tasks retry failure (#2118)
* Fix Cloud Tasks failure to retry

Replace `SC_NOT_MODIFIED` (304) with `SC_SERVICE_UNAVAILABLE` (503) when
data is not available yet. Affected actions are invoice- and spec11-publishing.

It is confirmed that Cloud Tasks currently does not retry with code 304,
despite the public documentation stating so. We will use 503 for now,
pending the decision by Cloud Tasks whether to change behavior or
documentation.

The code `TOO_EARLY` (425) is another alternative. It is not meant for
our use case but at least sounds like it is. However, it is not in any
javax.servlet jar. We don't want to define our own constant, and we cannot upgrade
to jakarta.servlet yet.

Also revert previous mitigation.
2023-08-23 10:55:51 -04:00
gbrodman
97676d1a1f Add backend for editing whois-visible fields (#2100)
This includes a bit of refactoring of the GSON creation. There can exist
some objects (e.g. Address) where the JSON representation is not equal to the
representation that we store in the database. For these objects, when
deserializing, we should update the objects so that they reflect the
proper DB structure (indeed, this is already what we do for the XML
parsing of Address).
2023-08-22 16:40:02 -04:00
sarahcaseybot
1dcbc9e0cb Change PackagePromotion to BulkPricingPackage (#2096)
* Change PackagePromotion to BulkPricingPackage

* More name changes

* Fix some test names

* Change token type "BULK" to "BULK_PRICING"

* Fix missed token_type reference

* Add todo to remove package type
2023-08-22 16:39:24 -04:00
Lai Jiang
f59c387b9c Add the ability to specify per-transaction isolation level (#2104)
A config file field is added to control if per-transaction isolation
level is actually used. If set to true, nested transactions will throw
a runtime exception as the enclosing transaction may run at a different
isolation level.

In this PR we only add the ability to specify the isolation level,
without enabling it in any environment (including unit tests), or
actually specifying one for any query. This should allow us to set up
the system without impacting anything currently working.
2023-08-21 18:48:34 -04:00
Weimin Yu
cfcafeefc6 Mitigate Cloud task retry problem (#2116)
* Mitigate Cloud task retry problem

Increase PublishSpec11Action start delay to avoid the need to retry.

The only other use case is invoice, which typically does not retry:
delay is 10 minutes, pipeline finishes within 7 minutes.
2023-08-18 17:14:03 -04:00
Weimin Yu
c32d831dd6 Add logging about Spec11 email sending (#2115) 2023-08-18 15:02:03 -04:00
Ben McIlwain
b38e0efe9a Refactor the way that the console BE parses POST bodies (#2113) (#2109)
This includes two changes:
1. Creating a base string-type adapter for use parsing to/from JSON
   classes that are represented as simple strings
2. Changing the object-provider methods so that the POST bodies should
   contain precisely the expected object(s) and nothing else. This way,
   it's easier for the frontend and backend to agree that, for instance,
   one POST endpoint might accept exactly a Registrar object, or a list
   of Contact objects.

Co-Authored-By: gbrodman <gbrodman@google.com>
2023-08-18 12:30:35 -04:00
Lai Jiang
67cb411c99 Remove DatabaseSnapshot (#2105)
It is no longer being used.
2023-08-17 22:40:54 -04:00
gbrodman
9f551eb552 Refactor the way that the console BE parses POST bodies (#2113)
This includes two changes:
1. Creating a base string-type adapter for use parsing to/from JSON
   classes that are represented as simple strings
2. Changing the object-provider methods so that the POST bodies should
   contain precisely the expected object(s) and nothing else. This way,
   it's easier for the frontend and backend to agree that, for instance,
   one POST endpoint might accept exactly a Registrar object, or a list
   of Contact objects.
2023-08-17 15:51:21 -04:00
sarahcaseybot
655f05c58c Remove references to cloud-build-local (#2111)
* Update cloudbuild-nomulus to save standardTest logs to GCS

* Remove step changes from cloudbuild-nomulus
2023-08-17 15:26:41 -04:00
Pavlo Tkach
95c810ddf4 Add script to allow quickly update number of instances (#2112)
This is a fast and easy way to update number of instances for the service deployed to app engine. Works with manual-scaling types services.
2023-08-17 12:33:35 -04:00
Pavlo Tkach
ec9a220bc3 Add console registrars screen API support to /console-api/registrars endpoint (#2095) 2023-08-17 10:17:23 -04:00
Weimin Yu
68d35d2d95 Fix unicode issue in GetTldCommand (#2108) 2023-08-16 12:24:35 -04:00
Weimin Yu
99840488a1 Fix TldTest (#2107)
Test data should be loaded from resources, not source tree.
2023-08-16 11:31:21 -04:00
Lai Jiang
ee7c8fb018 Disable flaky tests temporarily (#2106) 2023-08-15 13:59:56 -04:00
Weimin Yu
c6f62dcffd Fix the alert recipient address for Spec11 (#2103)
During email migration, alerts should be sent to the address
annotated with `newAlertRecipientEmailAddress`.
2023-08-10 15:38:13 -04:00
sarahcaseybot
ee66805d2e Modify getTldCommand to return TLD in YAML (#2102) 2023-08-10 14:20:56 -04:00
Weimin Yu
d7a3c0c439 Send Spec11 emails using Gmail (#2101)
First of a series of migrations to Gmail.

This can only be verified in production.
2023-08-10 10:26:28 -04:00
Weimin Yu
45666773ee Enable/disable email sending by environments (#2099) 2023-08-09 10:46:48 -04:00
Lai Jiang
b8b5152336 Bump PUBAPI instance number to 24 (#2098) 2023-08-08 11:23:50 -04:00
Weimin Yu
0f6302e92b Add tests to GmailClient (#2097)
Also make GmailClient do retries on transit errors.
2023-08-07 16:05:15 -04:00
Lai Jiang
e594bd13a1 Revert google-cloud-storage to 2.22.6 (#2093)
2.25.0 contains a breaking change that made HttpStorageOptions not
serializeable, which breaks RDE as it needs to access GCS from Beam.

2.22.6 was the last version that was used before the Gradle upgrade.

Also had to downgrade google-cloud-nio to pass the tests.

For some inexplicable reason, I had to manually add
guava-listenablefuture as
testRuntimeClasspath/runtimeClasspath/deploy_jar dependencies to the
networking, docs and prober subprojects' lock files, as running
`gradle test --write-locks` would NOT add them and succeed; but without
`--write-locks`, running the corresponding tests would fail.

See: b/294378137.
2023-08-07 12:21:08 -04:00
gbrodman
00051dbc0f Ignore/exclude IDEA-generated classpath index files (#2094)
See
https://youtrack.jetbrains.com/issue/IDEA-305759/Gradle-cannot-handle-classpath.index-duplicates
for more info -- it looks like this is fixed in some recent versions but
as of right now it doesn't seem like it's fixed on the Google-managed
version
2023-08-07 11:10:25 -04:00
Weimin Yu
aab89fb816 Re-enable flyway deadlock check (#2092)
Use a system property to specify whether this check should be executed.

We will update the presubmit test script to run this check only during
foss-pr.
2023-08-03 15:34:30 -04:00
sarahcaseybot
6ea548a35d Change static __REMOVEPACKAGE__ token to __REMOVEDOMAIN__ token (#2090)
* Change static __REMOVEPACKAGE__ token to __REMOVEDOMAIN__ token

* FIx some references

* Fix variable name

* Update docs
2023-08-03 14:09:43 -04:00
sarahcaseybot
733e9a4a6a Change packageToken extension to bulkToken extension (#2091)
* Change packageToken extension to bulkToken extension

* Small fixes
2023-08-02 17:06:20 -04:00
Weimin Yu
10d28efa1c Add placeholder configs for Gmail (#2089)
Add placeholder configs for sending emails using Gmail in GSuite.

The names of the new configs are temporary. After migration they
will revert to the names currently in use by the AppEngine email API.
2023-08-02 16:09:45 -04:00
Weimin Yu
1e0a0cf29e Temporarily disable flyway single-table check (#2088) 2023-07-31 14:34:53 -04:00
Lai Jiang
0c824fed5a Fix time inversion error when writing metrics (#2086)
The instance ID used to be uniquely determined by App Engine SDK. Since
we no longer calls the SDK, we need a way to distinguish instances so
that their metrics would not stump on each other and result in a time
inversion error (as we have seen frequently in the logs since the
removal of the App Engine SDK).
2023-07-27 13:05:11 -04:00
gbrodman
4aa1bd0856 Update Gradle to 8.2.1 (#2087)
This includes removing (hopefully temporarily) the gradle-lint plugin as
it is incompatible with various Gradle versions (see
https://github.com/nebula-plugins/gradle-lint-plugin/issues/393). This
is somewhat unfortunate since the plugin is useful for removing unused
dependencies, though with the relatively small amount of Gradle code we
write hopefully it will not be missed much. If Nebula changes their
code to be compatible with Gradle 8+, we can re-add it easily.

This upgrade means we can remove the code added in 342051e1.
2023-07-27 12:59:42 -04:00
sarahcaseybot
f5839777d1 Use Jackson to create and read Tld YAML files (#2082)
* Use Jackson to create and read Tld YAML files

* Add getObjectMapper to TldYamlUtils

* revert lockfiles

* Fix optionals

* Add more tests and javadocs

* small fixes
2023-07-26 16:25:03 -04:00
Weimin Yu
43d325d2a5 Checks flyway deadlock risk for new schema chagnes (#2078)
* Checks flyway deadlock risk for new schema chagnes
2023-07-26 14:35:48 -04:00
Pavlo Tkach
9b17adcb28 Add Console Settings -> Security front-end (#2079) 2023-07-26 12:50:31 -04:00
Ben McIlwain
9873772150 Allow EPP password to be set during login flow (#2080)
This is part of the spec in RFC 5730 that we hadn't implemented until now. Note
that this requires changing LoginFlow to be transactional, but I don't think
that should cause any issues.
2023-07-25 18:15:45 -04:00
Lai Jiang
342051e11d Fix the build due to jackson-core incompatibility (#2085) 2023-07-25 11:09:38 -04:00
Ben McIlwain
5f5cb8df9f Remove unnecessary overload of AsyncTaskEnqueuer.enqueueAsyncResave() (#2083)
It was only called in one place (in actual production code), and it was just
slightly obscuring the fact that re-saves can be scheduled for multiple points
in the future in a way that wasn't amazingly helpful to understanding of the
system logic at the callsite.
2023-07-24 13:37:36 -04:00
gbrodman
311d5ac9b6 Fix ICANN reporting and add rdap-queries field (#2081)
This includes two changes, the second necessary for testing the first.
1. We add the rdap-queries field as mandated by the amendment to the
   registry agreement,
   https://itp.cdn.icann.org/en/files/registry-agreement/proposed-global-amendment-base-gtld-registry-agreement-12-04-2023-en.pdf.
   This is fairly similar to the whois-queries field where we just query
   the logs, but instead of searching for "whois" we search for "rdap".
2. BigQuery doesn't use MAX to refer to the bigger of two fields; MAX
   accepts an array as an argument. In order to do what we want (and to
   have the BigQuery statements succeed), we need to use GREATEST.
   Tested both versions in alpha and production BigQuery instances.
2023-07-21 14:28:14 -04:00
gbrodman
3403399f38 Create a scrap command to re-enable billing recurrences that were closed (#2077)
This is part of b/247839944 as a followup to the large bug from
September 2022. As a result of that, there are domains whose
BillingRecurrence objects were closed but the domain wasn't deleted. In
order to avoid having these domains stick around forever without being
billed, we want to restart billing on them whenever their next billing
cycle would have been.
2023-07-14 16:38:17 -04:00
Lai Jiang
7a386c4577 Remove App Engine request retry headers (#2068)
Cloud Tasks now sends standard HTTP requests.
2023-07-14 12:07:54 -04:00
sarahcaseybot
dfc7947a2f Fix small bug in getting retry header in publishDnsUpdates (#2076) 2023-07-13 12:02:37 -04:00
Weimin Yu
c33d2cb0b8 Stop invoking npm when formatting Java (#2075)
Move console-webapp:(check,apply)Formatting tasks the task graph so that
the Java formatting tasks do not have to invoke npm.
2023-07-13 11:30:33 -04:00
Pavlo Tkach
304e7c9726 Add console-api/settings/security endpoint (#2057) 2023-07-12 16:19:20 -04:00
Lai Jiang
3ea31d024e Add a floor of zero to transaction report counts (#2074)
See b/290228682, there are edge cases in which the net_renew would be negative when
a domain is cancelled by superusers during renew grace period. The correct thing
to do is attribute the cancellation to the owning registrar, but that would require
changing the owing registrar of the the corresponding cancellation DomainHistory,
which has cascading effects that we don't want to deal with. As such we simply
floor the number here to zero to prevent any negative value from appearing, which
should have negligible impact as the edge cage happens very rarely, more specifically
when a cancellation happens during grace period by a registrar other than the the
owning one. All the numbers here should be positive to pass ICANN validation.
2023-07-12 12:56:09 -04:00
gbrodman
c24177e8a6 Add Flyway file for lastManualUpdateTime (#1828)
See b/248035435 for more details / reasoning, but basically this will
make it easier if we ever need to restore user actions in the future (or
figure out which user actions went wrong)
2023-07-11 13:51:37 -04:00
Lai Jiang
bef28d2e34 Remove internal auth mechanism (#2066)
It was used by cron job and task queues, which now use OIDC-based auth.

Also renamed and consolidated auth enums to make them easier to
understand. Ultimately we should get rid of the AuthMethod part as OIDC
will be the only auth method used.

Based on the updated routing map:

Backend and tools: the only change is that INTERNAL is removed from allowed
auth methods. Should be an no-op.

Pubapi: INTERNAL is removed from allowed auth. For endpoints that only
allowed INTERNAL before, API and LEGACY become the allowed methods.
However this should not affect anything because regardless of which auth
method is ultimately used, the required auth level is always NONE for
pubapi endpoints. Therefore any auth result is discarded anyway.

Frontend: INTERNAL is removed. RegistryLockVerifyAction has lowered
its required auth level to NONE because it extends HtmlAction, which can
redirect the user to login if necessary. All other endpoints extending
HtmlAction require NONE, so it's better to keep things consistent.
2023-07-11 11:49:16 -04:00
Weimin Yu
cc3901691c Upgrade Guava to v32 (#2073)
* Upgrade Guava to v32

This requires a custom resolution strategy since `listenablefuture`
is folded into the main jar.
2023-07-10 16:00:07 -04:00
Ben McIlwain
8d22c2a8d8 Remove unnecessary if statements in CloudDnsWriter (#2071)
The condition they are guarding against cannot possibly occur.
2023-07-07 13:15:02 -04:00
gbrodman
fe19f0fe78 Clean up issues with RDAP redaction (#2067)
Instead of using REDACTED FOR PRIVACY everywhere we should just include
the empty string (this is what the spec says, what other gTLD registrars
do, and what the RDAP conformance tool at
https://github.com/icann/rdap-conformance-tool says to do.

In the contact VCards, we omit redacted fields entirely unless the spec
requires that they exist (the version number and an empty 'fn' field).
This also applies to the "handle" field.

Eventually we will probably want to add the redaction extension but
that's not RFCed yet and isn't required for the August RDAP conformance
deadline.
2023-07-06 14:48:51 -04:00
Ben McIlwain
599a55d5b1 Fix the output slightly when running nomulus update_premium_list (#2065)
It was previously calling toString() on an Optional<PremiumList> which was
unnecessarily verbose. The existing premium list is required to be present
anyway.
2023-07-06 13:46:28 -04:00
Pavlo Tkach
845f792044 Add better integration for console formatting check and apply (#2070) 2023-07-06 12:33:32 -04:00
Ben McIlwain
ad68052524 Add minor refactoring follow-up for RefreshDnsForAllDomainsAction (#2063)
This is a follow-on to comments in PR #2037. It makes the main loop cleaner and
also removes ambiguities around database handling when the first query is run
with the cursor still empty because no results have been found yet.
2023-07-05 15:09:20 -04:00
gbrodman
04c6652793 Fix minor RDAP typos (#2062)
See https://buganizer.corp.google.com/issues/252317192 for more info,
these are just the low-hanging fruit (removing a www and fixing a typo
in a status)
2023-06-30 12:24:39 -04:00
Lai Jiang
5658fbe8bd Remove stale references to App Engine in CloudTasksUtils (#2064)
<!-- Reviewable:start -->
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/google/nomulus/2064)
<!-- Reviewable:end -->
2023-06-29 16:48:44 -04:00
gbrodman
a4540a847a Add configurable discount on sunrise domain creates (#2056)
Previously we had a 15% discount applied at invoicing time. We got rid of
that inadvertently in 2022 and we want to add it back, but instead of
being applied at invoicing time we'll just apply it directly to the
creation cost when creating the billing events.

Note: previous behavior didn't care about standard vs premium pricing so
we don't either

https://buganizer.corp.google.com/issues/287070313 is a bug for the
issue, and
https://github.com/google/nomulus/pull/1710/files#diff-5097b0ef57578718444ea6b9d4c6cb32f655686a37e2ca3dd96ad2db86a77f06L151-L170
is the section of the pull request that inadvertently removed it
2023-06-27 18:58:44 -04:00
Lai Jiang
fdfbb9572d Refactor OIDC-based auth mechanism (#2049)
This PR changes the two flavors of OIDC authentication mechanisms to
verify the same audience. This allows the same token to pass both
mechanisms. Previously the regular OIDC flavor uses the project id as
its required audience, which does not work for local user credentials
(such as ones used by the nomulus tool), which requires a valid OAuth
client ID as audience when minting the token (project id is NOT a valid
OAuth client ID).

I considered allowing multiple audiences, but the result is not as clean
as just using the same everywhere, because the fall-through logic would
have generated a lot of noises for failed attempts.

This PR also changes the client side to solely use OIDC token whenever
possible, including the proxy, cloud scheduler and cloud tasks. The nomulus
tool still uses OAuth access token by default because it requires USER level
authentication, which in turn requires us to fill the User table with objects
corresponding to the email address of everyone needing access to the tool.

TESTED=verified each client is able to make authenticated calls on QA with or
without IAP.
2023-06-27 13:10:31 -04:00
sarahcaseybot
cf1a148208 Add Java changes for new breakglass_mode column on Tld (#2053)
* Add Java changes for new breakglass_mode column on Tld

* Add generated sql schema
2023-06-22 12:55:42 -04:00
sarahcaseybot
6b54b69163 Add batching to the RefreshDnsForAllDomainsAction (#2037)
* Add an includeDeleted option to RefreshDnsForAllDomainsAction

* Add batching to the query

* Some refactoring

* Make batch size configurable

* Set status to ok

* Combine into one transaction

* Remove smear mintes parameter

* Only pass in lastInPreviousBatch
2023-06-22 12:54:40 -04:00
Weimin Yu
a839ec434e Add CurlCommand option to connect to canary (#2060)
Add a --canary option (default to false) to the CurlCommand that allows
connection to the canary endpoints.

During canary analysis, only the DEFAULT-canary receives traffic. This
new flag allows use to test other canary services manually using the
curl command.
2023-06-22 11:20:41 -04:00
Pavlo Tkach
86b62ebe76 Add registrar selection functionality (#2054) 2023-06-14 16:51:54 -04:00
sarahcaseybot
952a92a5db Separate load and verify transaction from refresh transaction in RefreshDnsAction (#2055) 2023-06-13 18:12:08 -04:00
Pavlo Tkach
bc57f319e5 Add console /registrars GET endpoint (#2050) 2023-06-09 16:57:26 -04:00
Pavlo Tkach
a9aaa11801 Remove contacts with empty type from console GET /contacts response (#2052) 2023-06-09 15:11:05 -04:00
Pavlo Tkach
b319eff7cd Add console UI main layout, settings page and contact settings (#1989)
* Header initialized

* Added settings page

* switch history mode to hash

* Add eslint

* Add prettier and reformat

* Contact details in a bottom sheet for mobile devices

* Add contact details events abstraction

* Fix formatting issue and update deps versions
2023-06-09 14:20:08 -04:00
Weimin Yu
894d05ce4e Add Gmail Client and set up tests (#2048)
* Add Gmail Client and set up tests

Add a Gmail client and manually triggered email tests in
CannedScriptExecutionActon.

We will test Gmail with Google Workspace in Sandbox, since Alpha and
Crash are not properly set up for Google Workspace, and we have not
figured out why.
2023-06-09 13:06:21 -04:00
Lai Jiang
cf0486a5d3 Remove unused fields in config (#2051) 2023-06-08 15:54:20 -04:00
sarahcaseybot
798a6ffc74 Remove nested transaction from requestDnsRefresh (#2044)
* Remove nested transaction from requestDnsRefresh

* Add a bulk version

* Remove transaction time as field

* Only add delay once

* have PublishDnsUpdatesAction use bulk refresh
2023-06-07 16:00:50 -04:00
sarahcaseybot
fe86ef0a7d Add breakglass_mode to Tld table (#2046)
* Add breakglass_mode to Tld table

* Add a default value
2023-06-06 16:13:08 -04:00
Weimin Yu
9dd41947e0 Add gmail dependency to project (#2047)
The Java code will be added in a followup PR.

Also fixed tests failing due to org.json upgrade: decimal whole numbers
no longer have their fractional parts removed, so currency value strings
must end with ".00" instead of ".0".
2023-06-05 16:48:30 -04:00
gbrodman
931a350f3d Remove slash from console contacts endpoint (#2045)
Endpoints shouldn't themselves end in slashes
2023-06-02 15:32:18 -04:00
Pavlo Tkach
db1b92638f Create console settings contact endpoints (#2033) 2023-05-31 16:34:57 -04:00
Lai Jiang
74baae397a Find the most recent prefix for RdeReportAction (#2043)
When RdeReportAction is invoked without a prefix parameter (as in the
case when it is kicked off by cron jobs for potential catch ups), we
need to used the same heuristics that's employed in RdeUploadAction to
find the most recent prefix for the given watermark, otherwise the job
will not find any deposits to upload.

Also renamed RdeUtil to RdeUtils, to be consistent with our naming
conventions.
2023-05-25 14:57:03 -04:00
sarahcaseybot
fddecea18e Rename Registries to Tlds (#2042)
* Rename Registries to Tlds

* Change Tlds to TLDs in comments
2023-05-24 17:08:09 -04:00
Pavlo Tkach
36a60bdf8b Add swagger API documentation (#2035) 2023-05-24 16:10:50 -04:00
dependabot[bot]
58ed53314c Bump socket.io-parser from 4.2.1 to 4.2.3 in /console-webapp (#2040)
Bumps [socket.io-parser](https://github.com/socketio/socket.io-parser) from 4.2.1 to 4.2.3.
- [Release notes](https://github.com/socketio/socket.io-parser/releases)
- [Changelog](https://github.com/socketio/socket.io-parser/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io-parser/compare/4.2.1...4.2.3)

---
updated-dependencies:
- dependency-name: socket.io-parser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-24 07:23:15 -04:00
Lai Jiang
5eaf99e02a Show HTTP response code when PUT fails (#2038) 2023-05-23 17:01:56 -04:00
Pavlo Tkach
9a5f094d1d Remove unused queue.xml file left after Cloud Tasks Queue migration (#2039) 2023-05-23 13:59:21 -04:00
Lai Jiang
6cbc2fa5ef Wrap tm().loadByKey() in a transaction when caching is not enabled. (#2030)
We have caching enabled so we never exercised this line.
2023-05-19 14:21:48 -04:00
Lai Jiang
6883093735 Drop DatabaseMigrationStateSchedule table (#2002) 2023-05-18 13:44:24 -04:00
Lai Jiang
a6078bc4f4 Refactor OIDC-based auth mechanism (#2025)
IAP and regular OIDC auth mechanisms are unified under a base class that
produces either APP or USER level AuthResult based on the principal email
found in the OIDC token.

Also moved some enum classes to better organize code structure.
2023-05-16 16:43:11 -04:00
gbrodman
6b75cf8496 Add view/edit basic registrar details permissions (#2036)
This encompasses most of the basic information that is viewable in the
existing console, basically, just viewing the base info of the Registrar
object.
2023-05-16 15:32:25 -04:00
Lai Jiang
219e9d3afb Update install.md (#2029) 2023-05-16 10:07:20 -04:00
sarahcaseybot
acdbc65c51 Change Registry object reference to Tld in configuration.md (#2021) 2023-05-12 12:32:02 -04:00
Weimin Yu
d510531f65 Remove the deprecatd DefaultCredential (#2032)
Use the ApplicationDefaultCredential annotation instead.

The new annotation has been verified in sandbox and production using the
'executeCannedScript' endpoint. The verification code is removed in this
PR too.
2023-05-11 13:46:36 -04:00
Lai Jiang
0d4dd57fe7 Fix a typo (#2031) 2023-05-11 13:26:07 -04:00
Pavlo Tkach
2667a0e977 Expand nomulus get_domain command to load up deleted domain data too (#2018) 2023-05-10 16:05:03 -04:00
gbrodman
1aef31efff Allow usage of standard HTTP requests in CloudTasksUtils (#2013)
This adds a possible configuration point "defaultServiceAccount" (which
in GAE will be the standard GAE service account). If this is configured,
CloudTasksUtils can create tasks with standard HTTP requests with an
OIDC token corresponding to that service account, as opposed to using
the AppEngine-specific request methods.

This also works with IAP, in that if IAP is on and we specify the IAP
client ID in the config, CloudTasksUtils will use the IAP client ID as
the token audience and the request will successfully be passed through
the IAP layer.

Tetsted in QA.
2023-05-09 16:02:12 -04:00
Lai Jiang
4d19245c29 Change usage grouping key in the invoice CSV (#2024)
This column is used by the billing team to create invoices. Registrars
have asked that a single invoice be created for a given registrar,
instead of one per registrar-tld pair. This should have no other effect
on the billing pipeline as the invoice grouping key has a description
field that also contains the TLD, so the granularity as a whole does not
change.
2023-05-09 11:25:11 -04:00
Lai Jiang
4b34307a6e Delete DatabaseMigrationStateSchedule (#2001)
We have been using it as a poor man's timed flag that triggers a system
behavior change after a certain time. We have no foreseeable future use
for it now that the DNS pull queue related code is deleted. If in the
future a need for such a flag arises, we are better off implementing a
proper flag system than hijacking this class any way.
2023-05-08 14:36:28 -04:00
Pavlo Tkach
55243e7cf6 Adds cloud scheduler and tasks deployer (#1999) 2023-05-04 15:57:32 -04:00
Lai Jiang
e14764b4c8 Remove DNS pull queue (#2000)
This is the last dependency on GAE pull queue, therefore we can delete
the pull queue config from queue.xml as well.
2023-05-04 13:21:53 -04:00
dependabot[bot]
68810f7a30 Bump engine.io and socket.io in /console-webapp (#2022)
Bumps [engine.io](https://github.com/socketio/engine.io) and [socket.io](https://github.com/socketio/socket.io). These dependencies needed to be updated together.

Updates `engine.io` from 6.2.1 to 6.4.2
- [Release notes](https://github.com/socketio/engine.io/releases)
- [Changelog](https://github.com/socketio/engine.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/engine.io/compare/6.2.1...6.4.2)

Updates `socket.io` from 4.5.2 to 4.6.1
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/4.5.2...4.6.1)

---
updated-dependencies:
- dependency-name: engine.io
  dependency-type: indirect
- dependency-name: socket.io
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-04 12:50:19 -04:00
Ben McIlwain
14d245b1e3 Remove duplicate info from create/update reserved list command output (#2020)
It was repeating the domain label twice for every reserved list entry. It used
to look like this:

baddies=baddies,FULLY_BLOCKED
2023-05-03 17:31:23 -04:00
Weimin Yu
61ab29ae9e Prober ssl cert update automation (#2019)
Defined CloudBuild script and docker image that automatically
updates probers' SSL certs
2023-05-03 15:57:50 -04:00
Weimin Yu
6742e5bf23 Remove CloudSql wipeout cron job in crash (#2017)
No more production data in crash. This allows us to repopulate crash
with test data.
2023-05-02 14:44:09 -04:00
Weimin Yu
c7f69eba1d Prepare switch of credential annotation (#2014)
* Prepare switch of credential annotation

Prepare the switch from DefaultCredential to ApplicationCredential.

In nomulus tools, start using the new annotation. This is tested by
successfully using the nomulus curl command, which actually needs a
valid credential to work.

For remaining use cases of the old annotation in Nomulus server, add
some code that relies on the new credential to work. Once these code
are tested in sandbox and production, we will switch the annotations.
2023-05-01 11:23:19 -04:00
gbrodman
578988d5ea Don't allow a list of the empty string in List<String> fields (#2011)
If the user does, e.g. `--allowed_nameservers=` (or contact ids) that
shouldn't mean a list consisting solely of the empty string.

Using this parameter / converter allows us to ensure that lists of
strings look reasonable.
2023-04-28 17:59:17 -04:00
sarahcaseybot
c17b8285f9 Don't apply non-premium default tokens to premium names (#2007)
* Don't apply non-premium default tokens to premium names

* Add test for renew

* Remove premium check from try/catch block

* Add check in validateToken

* Update docs

* Add validateForPremiums

* Better method name

* Shorten error message to fit as reason

* Add missing extension catch

* Remove extra javadoc

* Fix merge conflicts and change error message

* Update flow docs
2023-04-28 17:56:15 -04:00
gbrodman
ff8a08f40e Fix typo in pipeline name (#2016) 2023-04-28 17:05:24 -04:00
gbrodman
a341058282 Refactor / rename Billing object classes (#1993)
This includes renaming the billing classes to match the SQL table names,
as well as splitting them out into their own separate top-level classes.
The rest of the changes are mostly renaming variables and comments etc.

We now use `BillingBase` as the name of the common billing superclass,
because one-time events are called BillingEvents
2023-04-28 14:27:37 -04:00
Weimin Yu
16758879f0 Allow rotation when updating registrar cert (#2012)
* Allow rotation when updating registrar cert

When updating a registrar's primary cert, add a flag to activate
rotation of previous primary cert to failover.

This functionality is part of the prober ssl cert renewal automation.
2023-04-27 14:42:11 -04:00
Lai Jiang
2021247ab4 Update README on how to manually push schema (#2009) 2023-04-26 16:32:15 -04:00
Lai Jiang
4fc7038690 Make a few minor changes to make the linter happy (#2010)
<!-- Reviewable:start -->
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/google/nomulus/2010)
<!-- Reviewable:end -->
2023-04-26 15:49:32 -04:00
Weimin Yu
9272e7fd14 Add a test of failover certificate (#2008)
Verifies that client can log in with correct failover certificate.
2023-04-26 15:47:47 -04:00
sarahcaseybot
e1afe00758 Require token transition schedules for default tokens (#2005) 2023-04-21 17:38:10 -04:00
sarahcaseybot
203c20c040 Use a TLD's configured TTLs if they are present (#1992)
* Use tld's configured TTLs if they are present

* Change to optional

* Use optionals better
2023-04-21 13:47:10 -04:00
Lai Jiang
bd0cea0d87 Remove AppEngineServiceUtils (#2003)
The only method that is called from this class is setNumInstances. However we
don't current use `nomulus set_num_instances` anywhere. If we need to change
the number of instances, it is either done by updating appengine-web.xml, which
is deployed by Spinnaker, or doing it manually as a break-glass fix via gcloud
or on Pantheon.
2023-04-21 10:11:12 -04:00
sarahcaseybot
23fb69a682 Fix parameter description for type in GenerateAllocationTokensCommand (#1998) 2023-04-19 17:32:09 -04:00
Lai Jiang
597f63a603 Fix URL parameter to the DNS refresh fanout job (#1997)
<!-- Reviewable:start -->
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/google/nomulus/1997)
<!-- Reviewable:end -->
2023-04-19 15:32:41 -04:00
Lai Jiang
5ec73f3809 Refactor contact history PII wipeout logic into a Beam pipeline (#1994)
Because we need to check if a contact history is the most recent for its
underlying contact resource, the query-wipe out-repeat loop no longer works
ideally due to the added overhead with the query.

Instead, we refactor the logic into a Beam pipeline where the query only
needs to be performed once and history entries eligible for wipe out are
handled individually in their own transforms. Because history entries
are otherwise immutable, we can run the pipeline in relatively relaxed
repeatable read isolation level. We also do not worry about batching for
performance, as we do not anticipate this operation to put a lot of
strains on the particular table.
2023-04-19 13:04:45 -04:00
Ben McIlwain
b474e50e87 Update IDN tables with latest approved by ICANN (#1995)
This also adds README files to explain the two different IDN table locations
(which have different purposes). See http://b/278565478 for more information.
2023-04-18 17:23:12 -04:00
sarahcaseybot
6f3d062c32 Change Registry class name to Tld (#1991)
* Change Registry class name to Tld

* Fix merge conflict

* Some capitalization fixes
2023-04-18 12:26:14 -04:00
gbrodman
371d83b4cc Add a command to update Recurrence objects' behavior (#1987)
We want to basically be able to change the renewal behavior, either
setting the behavior type (e.g. NONPREMIUM) or the specified renewal
price.
2023-04-17 11:36:12 -04:00
2782 changed files with 115621 additions and 127730 deletions

View File

@@ -6,8 +6,6 @@ on:
pull_request:
# The branches below must be a subset of the branches above
branches: [ 'master' ]
schedule:
- cron: '24 4 * * 2'
jobs:
analyze:
@@ -27,11 +25,17 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set Java version
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -41,11 +45,20 @@ jobs:
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
queries: security-and-quality
# Build with Gradle
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
build-scan-publish: true
build-scan-terms-of-use-url: "https://gradle.com/terms-of-service"
build-scan-terms-of-use-agree: "yes"
- name: Execute Gradle build
run: ./gradlew --no-daemon --no-build-cache --no-configuration-cache --rerun-tasks clean build -x test -x jIFC
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
#- name: Autobuild
# uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -58,6 +71,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

View File

@@ -0,0 +1,25 @@
name: Dependency Submission
on:
push:
branches: [ 'master' ]
schedule:
- cron: '24 3 * * *'
permissions:
contents: write
jobs:
dependency-submission:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Set Java version
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- name: Generate and submit dependency graph
uses: gradle/actions/dependency-submission@v3

23
.github/workflows/do-not-merge.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: "Check labels"
on:
pull_request:
branches: ["master"]
types:
- opened
- synchronize
- labeled
- unlabeled
merge_group:
branches: ["master"]
types: [checks_requested]
jobs:
fail-by-label:
runs-on: ubuntu-latest
steps:
- name: Fail if PR is labeled as "do not merge"
if: contains(github.event.pull_request.labels.*.name, 'do not merge')
run: |
echo "This PR is labeled as do not merge!"
exit 1

16
.gitignore vendored
View File

@@ -18,6 +18,13 @@ gjf.out
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# Environment-specific configuration files
core/src/main/java/google/registry/config/files/nomulus-config-alpha.yaml
core/src/main/java/google/registry/config/files/nomulus-config-crash.yaml
core/src/main/java/google/registry/config/files/nomulus-config-production.yaml
core/src/main/java/google/registry/config/files/nomulus-config-qa.yaml
core/src/main/java/google/registry/config/files/nomulus-config-sandbox.yaml
######################################################################
# Eclipse Ignores
@@ -31,6 +38,7 @@ tmp/
local.properties
.settings/
.loadpath
.DS_Store
# Eclipse Core
.project
@@ -78,6 +86,7 @@ autogenerated/
**/*.iml
nomulus.ipr
nomulus.iws
**/classpath.index
# Auto-generated java classes by Intellij
*/src/main/generated/
@@ -103,7 +112,7 @@ nomulus.iws
.gradle/
**/build
cloudbuild-caches/
node_modules/**
**/node_modules/**
/repos/
# Compiled JS/CSS code
@@ -112,6 +121,5 @@ core/**/registrar_dbg*.js
core/**/registrar_bin*.css
core/**/registrar_dbg*.css
# Appengine generated files
core/WEB-INF/appengine-generated/*.bin
core/WEB-INF/appengine-generated/*.xml
# jEnv
.java-version

1
.java-version Normal file
View File

@@ -0,0 +1 @@
21

View File

@@ -34,6 +34,8 @@ Guy Bensky <guyben@google.com>
Weimin Yu <weiminyu@google.com>
Shicong Huang <shicong@google.com>
Gustav Brodman <gbrodman@google.com>
Aman Sanger <sangera@google.com>
Sarah Botwinick <sarahbot@google.com>
Legina Chen <legina@google.com>
Rachel Guan <rachelguan@google.com>
Juan Celhay <jicelhay@google.com>

View File

@@ -12,16 +12,16 @@ Nomulus is an open source, scalable, cloud-based service for operating
[top-level domains](https://en.wikipedia.org/wiki/Top-level_domain) (TLDs). It
is the authoritative source for the TLDs that it runs, meaning that it is
responsible for tracking domain name ownership and handling registrations,
renewals, availability checks, and WHOIS requests. End-user registrants (i.e.
renewals, availability checks, and WHOIS requests. End-user registrants (i.e.,
people or companies that want to register a domain name) use an intermediate
domain name registrar acting on their behalf to interact with the registry.
Nomulus runs on [Google App Engine][gae] and is written primarily in Java. It is
the software that [Google Registry](https://www.registry.google/) uses to
operate TLDs such as .google, .app, .how, .soy, and .みんな. It can run any
number of TLDs in a single shared registry system using horizontal scaling. Its
source code is publicly available in this repository under the [Apache 2.0 free
and open source license](https://www.apache.org/licenses/LICENSE-2.0).
Nomulus runs on [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine)
and is written primarily in Java. It is the software that
[Google Registry](https://www.registry.google/) uses to operate TLDs such as .google,
.app, .how, .soy, and .みんな. It can run any number of TLDs in a single shared registry
system using horizontal scaling. Its source code is publicly available in this
repository under the [Apache 2.0 free and open source license](https://www.apache.org/licenses/LICENSE-2.0).
## Getting started
@@ -30,8 +30,8 @@ running system:
* [Install
guide](https://github.com/google/nomulus/blob/master/docs/install.md)
* View the source code for the [GAE app](https://github.com/google/nomulus/tree/master/core/src/main/java/google/registry)
and for the [GKE proxy](https://github.com/google/nomulus/tree/master/proxy/src/main/java/google/registry)
* View the source code for the [Main HTTP server](https://github.com/google/nomulus/tree/master/core/src/main/java/google/registry)
and for the [EPP proxy](https://github.com/google/nomulus/tree/master/proxy/src/main/java/google/registry)
* [Other docs](https://github.com/google/nomulus/tree/master/docs)
* [Javadoc](https://javadoc.nomulus.foo/)
* [Nomulus discussion
@@ -54,9 +54,11 @@ Nomulus has the following capabilities:
checking, updating, and transferring domain names.
* **[DNS](https://en.wikipedia.org/wiki/Domain_Name_System) interface**: The
registry provides a pluggable interface that can be implemented to handle
different DNS providers. It includes a sample implementation using Google
Cloud DNS as well as an RFC 2136 compliant implementation that works with
BIND.
different DNS providers. It includes a sample implementation using [Google
Cloud DNS](https://cloud.google.com/dns/), as well as an RFC 2136 compliant
implementation that works with BIND. If you are using Google Cloud DNS, you
may need to understand its capabilities and provide your own
multi-[AS](https://en.wikipedia.org/wiki/Autonomous_system_\(Internet\)) solution.
* **[WHOIS](https://en.wikipedia.org/wiki/WHOIS)**: A text-based protocol that
returns ownership and contact information on registered domain names.
* **[Registration Data Access Protocol
@@ -68,7 +70,7 @@ Nomulus has the following capabilities:
provider to allow take-over by another registry operator in the event of
serious failure. This is required by ICANN for all [new
gTLDs](https://newgtlds.icann.org/).
* **Premium pricing**: Communicates prices for premium domain names (i.e.
* **Premium pricing**: Communicates prices for premium domain names (i.e.,
those that are highly desirable) and supports configurable premium
registration and renewal prices. An extensible interface allows fully
programmatic pricing.
@@ -91,56 +93,50 @@ Nomulus has the following capabilities:
* **Administrative tool**: Performs the full range of administrative tasks
needed to manage a running registry system, including creating and
configuring new TLDs.
* **DNS interface**: An interface for DNS operations is provided so you can
write an implementation for your chosen provider, along with a sample
implementation that uses [Google Cloud DNS](https://cloud.google.com/dns/).
If you are using Google Cloud DNS you may need to understand its
capabilities and provide your own
multi-[AS](https://en.wikipedia.org/wiki/Autonomous_system_\(Internet\))
solution.
* **GAE Proxy**: App Engine Standard only serves HTTP/S traffic. A proxy to
forward traffic on EPP and WHOIS ports to App Engine via HTTPS is provided.
Instructions on setting up the proxy on
[Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/)
is [available](https://github.com/google/nomulus/blob/master/docs/proxy-setup.md).
Running the proxy on GKE supports IPv4 and IPv6 access, per ICANN's
requirements for gTLDs. The proxy can also run as a single jar file, or on
other Kubernetes providers, with modifications.
* **Secure storage of cryptographic keys**: A keyring interface is
provided for plugging in your own implementation (see [configuration
doc](https://github.com/google/nomulus/blob/master/docs/configuration.md)
for details), and an implementation based on
[Google Cloud Secret Manager](https://cloud.google.com/security/products/secret-manager) is
available.
* **TPC Proxy**: Nomulus is built on top of the [Jetty](https://jetty.org/)
container that implements the [Jakarta Servlet](https://jakarta.ee/specifications/servlet/)
specification and only serves HTTP/S traffic. A proxy to translate raw TCP traffic (e.g., EPP)
to and from HTTP is provided.
Instructions on setting up the proxy
are [available](https://github.com/google/nomulus/blob/master/docs/proxy-setup.md).
The proxy can either run in a separate cluster and communicate to Nomulus public HTTP
endpoints via the Internet, or as a sidecar with the Nomulus image in the same pod and
communicate to it via loopback.
## Additional components
Registry operators interested in deploying Nomulus will likely require some
additional components that are need to be configured separately.
additional components that need to be configured separately.
* A way to invoice registrars for domain name registrations and accept
payments. Nomulus records the information required to generate invoices in
[billing
events](https://github.com/google/nomulus/blob/master/docs/code-structure.md#billing-events).
* Fully automated reporting to meet ICANN's requirements for gTLDs. Nomulus
includes substantial reporting functionality but some additional work will
includes substantial reporting functionality, but some additional work will
be required by the operator in this area.
* A secure method for storing cryptographic keys. A keyring interface is
provided for plugging in your own implementation (see [configuration
doc](https://github.com/google/nomulus/blob/master/docs/configuration.md)
for details).
* System status and uptime monitoring.
## Outside references
* [Donuts](http://donuts.domains) Registry has helped review the code and
provided valuable feedback
* [Identity Digital](http://identity.digital) has helped review the code and
provided valuable feedback.
* [CoCCa](http://cocca.org.nz) and [FRED](https://fred.nic.cz) are other
open-source registry platforms in use by many TLDs
open-source registry platforms in use by many TLDs.
* We are not aware of any fully open source domain registrar projects, but
open source EPP Toolkits (not yet tested with Nomulus; may require
integration work) include:
* [EPP RTK Project](http://epp-rtk.sourceforge.net/)
* [CentralNic](https://www.centralnic.com/registry/labs)
* [Universal Registry/Registrar Toolkit](https://sourceforge.net/projects/epp-rtk/)
* [ari-toolkit](https://github.com/AusRegistry/ari-toolkit)
* [Net::DRI](https://metacpan.org/pod/Net::DRI)
* Some Open Source DNS Projects that may be useful, but which we have not
tested:
* [AtomiaDNS](http://atomiadns.com/)
* [PowerDNS](https://doc.powerdns.com/md/)
[gae]:https://cloud.google.com/appengine/docs/about-the-standard-environment
* [AtomiaDNS](https://github.com/atomia/atomiadns)
* [PowerDNS](https://github.com/PowerDNS/pdns)

View File

@@ -1,124 +0,0 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
apply plugin: 'war'
def environment = rootProject.environment
def gcpProject = rootProject.gcpProject
// Set this directory before applying the appengine plugin so that the
// plugin will recognize this as an app-engine standard app (and also
// obtains the appengine-web.xml from the correct location)
project.convention.plugins['war'].webAppDirName =
"../../core/src/main/java/google/registry/env/${environment}/${project.name}"
apply plugin: 'com.google.cloud.tools.appengine'
def coreResourcesDir = "${rootDir}/core/build/resources/main"
def coreLibsDir = "${rootDir}/core/build/libs"
// Get the web.xml file for the service.
war {
webInf {
from "../../core/src/main/java/google/registry/env/common/${project.name}/WEB-INF"
}
}
war {
from("${coreResourcesDir}/google/registry/ui/html") {
include "*.html"
}
from("${coreLibsDir}") {
include "core.jar"
into("WEB-INF/lib")
}
}
if (project.path == ":services:default") {
war {
from("${rootDir}/console-webapp/dist/console-webapp") {
include "**/*"
into("console")
}
from("${coreResourcesDir}/google/registry/ui") {
include "registrar_bin.js"
if (environment != "production") {
include "registrar_bin.js.map"
}
into("assets/js")
}
from("${coreResourcesDir}/google/registry/ui/css") {
include "registrar*"
into("assets/css")
}
from("${coreResourcesDir}/google/registry/ui/assets/images") {
include "**/*"
into("assets/images")
}
}
}
appengine {
deploy {
// appengineDeployAll task requires the version to be set. So,
// this config lets gcloud select a version name when deploying
// to alpha or sandbox from our workstation.
if (!rootProject.prodOrSandboxEnv) {
version = 'GCLOUD_CONFIG'
}
// Don't set gcpProject directly, it gets overriden in ./build.gradle.
// Do -P environment={crash,alpha} instead. For sandbox/production,
// use Spinnaker.
projectId = gcpProject
}
}
dependencies {
implementation project(path: ':core', configuration: 'deploy_jar')
}
// The tools.jar file gets pulled in from the java environment and for some
// reason gets exploded "readonly", causing subsequent builds to fail when
// they can't overwrite it. The hack below makes the file writable after
// we're done exploding it.
//
// Fun fact: We only use this jar for documentation generation and as such we
// don't need it in our warfile, as it is not used by the application at
// runtime. But it's not clear how to exclude it, as we seem to be
// constructing the jar from the entire WEB-INF directory and per-file
// exclude rules don't seem to work on it. Better solutions are welcome :-)
explodeWar.doLast {
file("${it.explodedAppDirectory}/WEB-INF/lib/tools.jar").setWritable(true)
}
appengineDeployAll.finalizedBy ':cloudSchedulerDeployer'
rootProject.deploy.dependsOn appengineDeployAll
rootProject.stage.dependsOn appengineStage
tasks['war'].dependsOn ':console-webapp:buildConsoleWebappProd'
tasks['war'].dependsOn ':core:compileProdJS'
tasks['war'].dependsOn ':core:processResources'
tasks['war'].dependsOn ':core:jar'
// Impose verification for all of the deployment tasks. We haven't found a
// better way to do this other than to apply to each of them independently.
// If a new task gets added, it will still fail if "environment" is not defined
// because gcpProject is null. We just won't get as friendly an error message.
appengineDeployAll.configure rootProject.verifyDeploymentConfig
appengineDeploy.configure rootProject.verifyDeploymentConfig
appengineDeployCron.configure rootProject.verifyDeploymentConfig
appengineDeployDispatch.configure rootProject.verifyDeploymentConfig
appengineDeployDos.configure rootProject.verifyDeploymentConfig
appengineDeployIndex.configure rootProject.verifyDeploymentConfig
appengineDeployQueue.configure rootProject.verifyDeploymentConfig

View File

@@ -29,25 +29,26 @@ buildscript {
dependencies {
classpath 'com.google.cloud.tools:appengine-gradle-plugin:2.4.1'
classpath 'net.ltgt.gradle:gradle-errorprone-plugin:2.0.2'
classpath 'net.ltgt.gradle:gradle-errorprone-plugin:3.1.0'
classpath 'org.sonatype.aether:aether-api:1.13.1'
classpath 'org.sonatype.aether:aether-impl:1.13.1'
}
}
plugins {
// Java static analysis plugins. Keep versions consistent with
// ./buildSrc/build.gradle
id 'nebula.lint' version '16.0.2'
id 'net.ltgt.errorprone' version '2.0.2'
// Java static analysis plugins.
// Re-enable when compatible with Gradle 8
// id 'nebula.lint' version '16.0.2'
id 'net.ltgt.errorprone' version '3.1.0'
id 'checkstyle'
id 'com.github.johnrengelman.shadow' version '5.1.0'
id 'com.github.johnrengelman.shadow' version '8.1.1'
// NodeJs plugin
id "com.github.node-gradle.node" version "3.0.1"
id 'idea'
id 'com.diffplug.gradle.spotless' version '3.25.0'
id 'com.diffplug.spotless' version '6.20.0'
id 'jacoco'
id 'com.dorongold.task-tree' version '2.1.0'
@@ -58,67 +59,35 @@ dependencyLocking {
}
node {
download = true
version = "16.14.0"
npmVersion = "6.14.11"
download = false
version = "22.7.0"
}
wrapper {
distributionType = Wrapper.DistributionType.ALL
}
apply plugin: google.registry.gradle.plugin.ReportUploaderPlugin
reportUploader {
// Set the location where we want to upload the build results.
// e.g. -P uploaderDestination=gcs://domain-registry-alpha-build-result-test
//
// If not set - the upload will be skipped
destination = uploaderDestination
// The location of the file containing the OAuth2 Google Cloud credentials.
//
// The file can contain a Service Account key file in JSON format from the
// Google Developers Console or a stored user credential using the format
// supported by the Cloud SDK.
//
// If no file is given - the default credentials are used.
credentialsFile = uploaderCredentialsFile
// If set to 'yes', each file will be uploaded to GCS in a separate thread.
// This is MUCH faster.
multithreadedUpload = uploaderMultithreadedUpload
}
apply from: 'dependencies.gradle'
apply from: 'dependency_lic.gradle'
apply from: 'utils.gradle'
// Custom task to run checkLicense in buildSrc, which is not triggered
// by root project tasks. A shell task is used because buildSrc tasks
// cannot be referenced in the same way as tasks from a regular included
// build.
task checkBuildSrcLicense(type:Exec) {
workingDir "${rootDir}/buildSrc"
commandLine '../gradlew', 'checkLicense'
}
tasks.checkLicense.dependsOn(tasks.checkBuildSrcLicense)
tasks.build.dependsOn(tasks.checkLicense)
// Provide defaults for all of the project properties.
// Only do linting if the build is successful.
gradleLint.autoLintAfterFailure = false
// Re-enable when compatible with Gradle 8
// gradleLint.autoLintAfterFailure = false
// Paths to main and test sources.
ext.projectRootDir = "${rootDir}"
// Tasks to deploy/stage all App Engine services
// Tasks to deploy/stage all services
task deploy {
group = 'deployment'
description = 'Deploys all services to App Engine.'
description = 'Deploys all services.'
}
task stage {
@@ -126,30 +95,27 @@ task stage {
description = 'Generates application directories for all services.'
}
// App-engine environment configuration. We set up all of the variables in
// the root project.
def environments = ['production', 'sandbox', 'alpha', 'crash', 'qa']
def gcpProject = null
apply from: "${rootDir.path}/projects.gradle"
if (environment == '') {
// Keep the project null, this will prevent deployment. Set the
// Keep the project null, this will prevent deployment. Set the
// environment to "alpha" because other code needs this property to
// explode the war file.
environment = 'alpha'
} else if (environment != 'production' && environment != 'sandbox') {
} else {
gcpProject = projects[environment]
if (gcpProject == null) {
throw new GradleException("-Penvironment must be one of " +
"${projects.keySet()}.")
}
project(':console-webapp').setProperty('configuration', environment)
}
rootProject.ext.environment = environment
rootProject.ext.gcpProject = gcpProject
rootProject.ext.baseDomain = baseDomains[environment]
rootProject.ext.prodOrSandboxEnv = environment in ['production', 'sandbox']
// Function to verify that the deployment parameters have been set.
@@ -207,8 +173,32 @@ allprojects {
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
options.fork = true
options.forkOptions.javaHome =
file("${System.env.REAL_JAVA_HOME}")
options.forkOptions.executable =
file("${System.env.JAVA_HOME}/bin/javac")
options.compilerArgs = ["--add-exports",
"jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
"--add-exports",
"jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
"--add-exports",
"jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
"--add-exports",
"jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
"--add-exports",
"jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
"--add-exports",
"jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED",
"--add-exports",
"jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED",
"--add-exports",
"jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED"]
options.forkOptions.jvmArgs = ["-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
"-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
"-J--add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
"-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
"-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
"-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED",
"-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED",
"-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED"]
}
}
}
@@ -255,6 +245,12 @@ def javadocSource = []
def javadocClasspath = []
def javadocDependentTasks = []
def services = [':services:default',
':services:backend',
':services:bsa',
':services:tools',
':services:pubapi']
subprojects {
// Skip no-op project
if (project.name == 'services') return
@@ -269,14 +265,19 @@ subprojects {
project.tasks.create(
taskName, com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) {
mergeServiceFiles()
baseName = binaryName
archiveBaseName = binaryName
if (mainClass != '') {
manifest {
attributes 'Main-Class': mainClass
}
}
// Build as a multi-release jar since we've got member jars (e.g., dnsjava
// and snakeyaml) that are multi-release.
manifest {
attributes 'Multi-Release': true
}
zip64 = true
classifier = ''
archiveClassifier = ''
archiveVersion = ''
configurations = configs
from srcOutput
@@ -305,8 +306,7 @@ subprojects {
afterEvaluate {
if (rootProject.enableDependencyLocking.toBoolean()
&& project.name != 'integration'
&& project.name != 'java8compatibility') {
&& project.name != 'integration') {
// The ':integration' project runs server/schema integration tests using
// dynamically specified jars with no transitive dependency. Therefore
// dependency-locking does not make sense. Furthermore, during
@@ -314,9 +314,6 @@ subprojects {
// immutable. Locking activation would trigger an invalid operation
// exception.
//
// The ':java8compatibility' project is test-only. Its source does not go
// into production.
//
// For all other projects, due to problem with the gradle-license-report
// plugin, the dependencyLicenseReport configuration must opt out of
// dependency-locking. See dependency_lic.gradle for the reason why.
@@ -332,28 +329,20 @@ subprojects {
}
}
def services = [':services:default',
':services:backend',
':services:tools',
':services:pubapi']
// Set up all of the deployment projects.
if (services.contains(project.path)) {
apply from: "${rootDir.path}/appengine_war.gradle"
// Return early, do not apply the settings below.
return
}
apply from: "${rootDir.path}/java_common.gradle"
if (project.name != 'docs') {
compileJava {
// TODO: Remove this once we migrate off AppEngine.
options.release = 8
}
}
// When changing Java version here, be sure to update BEAM Java runtime:
// search for `flex-template-base-image` and update the parameter value.
// There are at least two instances, one in core/build.gradle, one in
// release/stage_beam_pipeline.sh
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
project.tasks.test.dependsOn runPresubmits
@@ -378,9 +367,9 @@ subprojects {
}
}
// No need to produce javadoc for the docs subproject, which has no APIs to
// No need to produce javadoc for the jetty subproject, which has no APIs to
// expose to users.
if (project.name != 'docs') {
if (project.name != 'jetty') {
javadocSource << project.sourceSets.main.allJava
javadocClasspath << project.sourceSets.main.runtimeClasspath
javadocClasspath << "${buildDir}/generated/sources/annotationProcessor/java/main"
@@ -418,9 +407,6 @@ if (verboseTestOutput.toBoolean()) {
}
task checkDependenciesDotGradle {
def buildSrcDepsFile = File.createTempFile('buildSrc', 'deps')
buildSrcDepsFile.deleteOnExit()
dependsOn createGetBuildSrcDirectDepsTask(buildSrcDepsFile)
doLast {
Set<String> depsInUse = []
@@ -433,9 +419,7 @@ task checkDependenciesDotGradle {
}
}
}
if (buildSrcDepsFile.exists()) {
depsInUse.addAll(buildSrcDepsFile.readLines())
}
def unusedDeps =
rootProject.dependencyMap.keySet()
.findAll { !depsInUse.contains(it) }
@@ -452,34 +436,23 @@ task checkDependenciesDotGradle {
}
tasks.build.dependsOn(tasks.checkDependenciesDotGradle)
def createGetBuildSrcDirectDepsTask(outputFileName) {
return tasks
.create(
"getBuildSrcDeps_${java.util.UUID.randomUUID()}".toString(),
Exec) {
workingDir "${rootDir}/buildSrc"
commandLine '../gradlew', 'exportDependencies',
"-PdependencyExportFile=${outputFileName}"
}
}
rootProject.ext {
invokeJavaDiffFormatScript = { action ->
println("JAVA_HOME=${System.env.JAVA_HOME}")
println("PATH=${System.env.PATH}")
println(ext.execInBash("type java", "${rootDir}"))
println(ext.execInBash("java -version", "${rootDir}"))
def scriptDir = rootDir.path.endsWith('buildSrc')
? "${rootDir}/../java-format"
: "${rootDir}/java-format"
def workingDir = rootDir.path.endsWith('buildSrc')
? "${rootDir}/.."
: rootDir
def javaHome = project.findProperty('org.gradle.java.home')
def javaBin
if (javaHome != null) {
javaBin = "$javaHome/bin/java"
} else {
javaBin = ext.execInBash("which java", rootDir)
}
println("Running the formatting tool with $javaBin")
def scriptDir = "${rootDir}/java-format"
def workingDir = rootDir
def formatDiffScript = "${scriptDir}/google-java-format-git-diff.sh"
def pythonExe = getPythonExecutable()
return ext.execInBash(
"PYTHON=${pythonExe} ${formatDiffScript} ${action}", "${workingDir}")
"JAVA=${javaBin} PYTHON=${pythonExe} ${formatDiffScript} ${action}", "${workingDir}")
}
}
@@ -534,7 +507,9 @@ task javadoc(type: Javadoc) {
destinationDir = file("${buildDir}/docs/javadoc")
options.encoding = "UTF-8"
// In a lot of places we don't write @return so suppress warnings about that.
options.addBooleanOption('Xdoclint:all,-missing', true)
// We don't report HTML lint errors because XJB-generated POJO files have
// incorrect tags (like dangling </p> without the corresponding open tag.
options.addBooleanOption('Xdoclint:all,-missing,-html', true)
options.addBooleanOption("-allow-script-in-comments",true)
options.tags = ["type:a:Generic Type",
"error:a:Expected Error",
@@ -548,25 +523,35 @@ tasks.build.dependsOn(tasks.javadoc)
// core Nomulus codebase, and runs all presubmits.
task coreDev {
dependsOn 'javaIncrementalFormatApply'
dependsOn 'console-webapp:applyFormatting'
dependsOn 'javadoc'
dependsOn 'checkDependenciesDotGradle'
dependsOn 'checkLicense'
dependsOn ':console-webapp:runConsoleWebappUnitTests'
dependsOn ':core:check'
dependsOn 'assemble'
}
javadocDependentTasks.each { tasks.javadoc.dependsOn(it) }
// Runs the script, which deploys cloud scheduler tasks based on the config
task cloudSchedulerDeployer {
// Runs the script, which deploys cloud scheduler and tasks based on the config
task deployCloudSchedulerAndQueue {
doLast {
def env = environment
if (!prodOrSandboxEnv) {
exec {
workingDir "${rootDir}/release/builder/"
commandLine 'go', 'run',
"${rootDir}/release/builder/cloudSchedulerDeployer.go",
"${rootDir}/core/src/main/java/google/registry/env/${env}/default/WEB-INF/cloud-scheduler-tasks.xml",
"./deployCloudSchedulerAndQueue.go",
"${rootDir}/core/src/main/java/google/registry/config/files/nomulus-config-${env}.yaml",
"${rootDir}/core/src/main/java/google/registry/config/files/tasks/cloud-scheduler-tasks-${env}.xml",
"domain-registry-${env}"
}
exec {
workingDir "${rootDir}/release/builder/"
commandLine 'go', 'run',
"./deployCloudSchedulerAndQueue.go",
"${rootDir}/core/src/main/java/google/registry/config/files/nomulus-config-${env}.yaml",
"${rootDir}/core/src/main/java/google/registry/config/files/cloud-tasks-queue.xml",
"domain-registry-${env}"
}
}

View File

@@ -1,128 +0,0 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import java.io.PrintStream;
val enableDependencyLocking: String by project
val allowInsecureProtocol: String by project
val allowInsecure = allowInsecureProtocol
buildscript {
// We need to do this again within "buildscript" because setting it in the
// main script doesn't affect build dependencies.
val enableDependencyLocking: String by project
if (enableDependencyLocking.toBoolean()) {
// Lock application dependencies.
dependencyLocking {
lockAllConfigurations()
}
}
}
plugins {
// Java static analysis plugins. Keep versions consistent with ../build.gradle
// id("nebula.lint") version "16.0.2" // unsupported for kotlin
id("net.ltgt.errorprone") version "2.0.2"
checkstyle
id("com.diffplug.gradle.spotless") version "3.25.0"
}
checkstyle {
configDirectory.set(file("../config/checkstyle"))
}
println("enableDependencyLocking is $enableDependencyLocking")
if (enableDependencyLocking.toBoolean()) {
// Lock application dependencies.
dependencyLocking {
lockAllConfigurations()
}
}
repositories {
val mavenUrl = (project.ext.properties.get("mavenUrl") ?: "") as String
if (mavenUrl.isEmpty()) {
println("Java dependencies: Using Maven central...")
mavenCentral()
google()
} else {
maven {
println("Java dependencies: Using repo ${mavenUrl}...")
url = uri(mavenUrl)
isAllowInsecureProtocol = allowInsecureProtocol == "true"
}
}
}
apply(from = "../dependencies.gradle")
apply(from = "../dependency_lic.gradle")
apply(from = "../java_common.gradle")
project.the<SourceSetContainer>()["main"].java {
srcDir("${project.buildDir}/generated/source/apt/main")
}
// checkstyle {
// configDir file("../config/checkstyle")
// }
dependencies {
val deps = project.ext["dependencyMap"] as Map<String, String>
val implementation by configurations
val testImplementation by configurations
val annotationProcessor by configurations
implementation(deps["com.google.auth:google-auth-library-credentials"]!!)
implementation(deps["com.google.auth:google-auth-library-oauth2-http"]!!)
implementation(deps["com.google.auto.value:auto-value-annotations"]!!)
// implementation(deps["com.google.common.html.types:types"]!!)
implementation(deps["com.google.cloud:google-cloud-core"]!!)
implementation(deps["com.google.cloud:google-cloud-storage"]!!)
implementation(deps["com.google.guava:guava"]!!)
implementation(deps["com.google.protobuf:protobuf-java"]!!)
implementation(deps["com.google.template:soy"]!!)
implementation(deps["org.apache.commons:commons-text"]!!)
annotationProcessor(deps["com.google.auto.value:auto-value"]!!)
testImplementation(deps["com.google.truth:truth"]!!)
testImplementation(
deps["com.google.truth.extensions:truth-java8-extension"]!!)
testImplementation(deps["org.junit.jupiter:junit-jupiter-api"]!!)
testImplementation(deps["org.junit.jupiter:junit-jupiter-engine"]!!)
testImplementation(deps["org.mockito:mockito-core"]!!)
}
gradle.projectsEvaluated {
tasks.withType<JavaCompile> {
options.compilerArgs.add("-Xlint:unchecked")
}
}
tasks.register("exportDependencies") {
val outputFileProperty = "dependencyExportFile"
val output = if (project.hasProperty(outputFileProperty)) {
PrintStream(file(project.ext.properties[outputFileProperty]))
} else {
System.out
}
doLast {
project.configurations.forEach {
println("dependency is $it")
// it.dependencies.findAll {
// it.group != null
// }.each {
// output.println("${it.group}:${it.name}")
// }
}
}
}

View File

@@ -1,24 +0,0 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.diffplug.durian:durian-collect:1.2.0=classpath
com.diffplug.durian:durian-core:1.2.0=classpath
com.diffplug.durian:durian-io:1.2.0=classpath
com.diffplug.gradle.spotless:com.diffplug.gradle.spotless.gradle.plugin:3.25.0=classpath
com.diffplug.spotless:spotless-lib-extra:1.25.0=classpath
com.diffplug.spotless:spotless-lib:1.25.0=classpath
com.diffplug.spotless:spotless-plugin-gradle:3.25.0=classpath
com.googlecode.concurrent-trees:concurrent-trees:2.6.1=classpath
com.googlecode.javaewah:JavaEWAH:1.1.6=classpath
com.jcraft:jsch:0.1.55=classpath
com.jcraft:jzlib:1.1.1=classpath
net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:2.0.2=classpath
net.ltgt.gradle:gradle-errorprone-plugin:2.0.2=classpath
org.bouncycastle:bcpg-jdk15on:1.61=classpath
org.bouncycastle:bcpkix-jdk15on:1.61=classpath
org.bouncycastle:bcprov-jdk15on:1.61=classpath
org.codehaus.groovy:groovy-xml:2.4.7=classpath
org.codehaus.groovy:groovy:2.4.7=classpath
org.eclipse.jgit:org.eclipse.jgit:5.5.0.201909110433-r=classpath
org.slf4j:slf4j-api:1.7.2=classpath
empty=

View File

@@ -1,142 +0,0 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
antlr:antlr:2.7.7=checkstyle
aopalliance:aopalliance:1.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
args4j:args4j:2.0.23=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-core:2.14.1=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson:jackson-bom:2.14.1=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.github.ben-manes.caffeine:caffeine:2.7.0=annotationProcessor,testAnnotationProcessor
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,testAnnotationProcessor
com.google.android:annotations:4.1.1.4=testRuntimeClasspath
com.google.api-client:google-api-client:2.1.2=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:gapic-google-cloud-storage-v2:2.17.2-alpha=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-storage-v2:2.17.2-alpha=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-storage-v2:2.17.2-alpha=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-common-protos:2.13.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-iam-v1:1.8.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:api-common:2.5.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:gax-grpc:2.22.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:gax-httpjson:0.107.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:gax:2.22.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-storage:v1-rev20220705-2.0.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auth:google-auth-library-credentials:1.14.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auth:google-auth-library-oauth2-http:1.14.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auto.value:auto-value-annotations:1.10.1=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auto.value:auto-value:1.10.1=annotationProcessor,compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auto:auto-common:0.10=annotationProcessor,testAnnotationProcessor
com.google.cloud:google-cloud-core-grpc:2.9.4=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core-http:2.9.4=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core:2.9.4=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-storage:2.17.2=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.code.findbugs:jFormatString:3.0.0=annotationProcessor,testAnnotationProcessor
com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,checkstyle,compileClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.code.gson:gson:2.10.1=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.common.html.types:types:1.0.6=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.errorprone:error_prone_annotation:2.3.4=annotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.18.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.errorprone:error_prone_annotations:2.3.4=annotationProcessor,checkstyle,testAnnotationProcessor
com.google.errorprone:error_prone_check_api:2.3.4=annotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_core:2.3.4=annotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_type_annotations:2.3.4=annotationProcessor,testAnnotationProcessor
com.google.escapevelocity:escapevelocity:0.9.1=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.guava:failureaccess:1.0.1=annotationProcessor,checkstyle,compileClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.guava:guava:27.0.1-jre=annotationProcessor,testAnnotationProcessor
com.google.guava:guava:29.0-jre=checkstyle
com.google.guava:guava:31.1-jre=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,compileClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-apache-v2:1.42.3=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-appengine:1.42.3=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-gson:1.42.3=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-jackson2:1.42.3=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client:1.42.3=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.inject.extensions:guice-multibindings:4.1.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.inject:guice:4.1.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.j2objc:j2objc-annotations:1.1=annotationProcessor,testAnnotationProcessor
com.google.j2objc:j2objc-annotations:1.3=checkstyle,compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.jsinterop:jsinterop-annotations:1.0.1=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.oauth-client:google-oauth-client:1.34.1=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.protobuf:protobuf-java-util:3.21.12=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.protobuf:protobuf-java:3.21.12=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.protobuf:protobuf-java:3.4.0=annotationProcessor,testAnnotationProcessor
com.google.re2j:re2j:1.6=testRuntimeClasspath
com.google.template:soy:2021-02-01=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.truth.extensions:truth-java8-extension:1.1.3=testCompileClasspath,testRuntimeClasspath
com.google.truth:truth:1.1.3=testCompileClasspath,testRuntimeClasspath
com.googlecode.java-diff-utils:diffutils:1.3.0=annotationProcessor,testAnnotationProcessor
com.ibm.icu:icu4j:57.1=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.puppycrawl.tools:checkstyle:8.37=checkstyle
commons-beanutils:commons-beanutils:1.9.4=checkstyle
commons-codec:commons-codec:1.15=compileClasspath,testCompileClasspath,testRuntimeClasspath
commons-collections:commons-collections:3.2.2=checkstyle
commons-logging:commons-logging:1.2=compileClasspath,testCompileClasspath,testRuntimeClasspath
info.picocli:picocli:4.5.2=checkstyle
io.grpc:grpc-alts:1.52.1=compileClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-api:1.52.1=compileClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-auth:1.52.1=compileClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-context:1.52.1=compileClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-core:1.52.1=testRuntimeClasspath
io.grpc:grpc-googleapis:1.52.1=testRuntimeClasspath
io.grpc:grpc-grpclb:1.52.1=compileClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-netty-shaded:1.52.1=testRuntimeClasspath
io.grpc:grpc-protobuf-lite:1.52.1=compileClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-protobuf:1.52.1=compileClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-services:1.52.1=testRuntimeClasspath
io.grpc:grpc-stub:1.52.1=compileClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-xds:1.52.1=testRuntimeClasspath
io.opencensus:opencensus-api:0.31.1=compileClasspath,testCompileClasspath,testRuntimeClasspath
io.opencensus:opencensus-contrib-http-util:0.31.1=compileClasspath,testCompileClasspath,testRuntimeClasspath
io.opencensus:opencensus-proto:0.2.0=testRuntimeClasspath
io.perfmark:perfmark-api:0.26.0=testRuntimeClasspath
javax.annotation:javax.annotation-api:1.3.2=compileClasspath,testCompileClasspath,testRuntimeClasspath
javax.annotation:jsr250-api:1.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
javax.inject:javax.inject:1=compileClasspath,testCompileClasspath,testRuntimeClasspath
junit:junit:4.13.2=testCompileClasspath,testRuntimeClasspath
net.bytebuddy:byte-buddy-agent:1.12.22=testCompileClasspath,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.12.22=testCompileClasspath,testRuntimeClasspath
net.sf.saxon:Saxon-HE:10.3=checkstyle
org.antlr:antlr4-runtime:4.8-1=checkstyle
org.apache.commons:commons-lang3:3.12.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.commons:commons-text:1.10.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.httpcomponents:httpclient:4.5.13=compileClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.httpcomponents:httpcore:4.4.15=compileClasspath,testCompileClasspath,testRuntimeClasspath
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath
org.checkerframework:checker-qual:2.11.1=checkstyle
org.checkerframework:checker-qual:3.0.0=annotationProcessor,testAnnotationProcessor
org.checkerframework:checker-qual:3.29.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
org.checkerframework:dataflow:3.0.0=annotationProcessor,testAnnotationProcessor
org.checkerframework:javacutil:3.0.0=annotationProcessor,testAnnotationProcessor
org.codehaus.mojo:animal-sniffer-annotations:1.17=annotationProcessor,testAnnotationProcessor
org.codehaus.mojo:animal-sniffer-annotations:1.22=testRuntimeClasspath
org.conscrypt:conscrypt-openjdk-uber:2.5.2=compileClasspath,testCompileClasspath,testRuntimeClasspath
org.hamcrest:hamcrest-core:1.3=testCompileClasspath,testRuntimeClasspath
org.jacoco:org.jacoco.agent:0.8.7=jacocoAgent,jacocoAnt
org.jacoco:org.jacoco.ant:0.8.7=jacocoAnt
org.jacoco:org.jacoco.core:0.8.7=jacocoAnt
org.jacoco:org.jacoco.report:0.8.7=jacocoAnt
org.javassist:javassist:3.26.0-GA=checkstyle
org.json:json:20160212=compileClasspath,testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:5.9.2=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:5.9.2=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-commons:1.9.2=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-engine:1.9.2=testCompileClasspath,testRuntimeClasspath
org.junit:junit-bom:5.9.2=testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-core:5.0.0=testCompileClasspath,testRuntimeClasspath
org.objenesis:objenesis:3.3=testRuntimeClasspath
org.opentest4j:opentest4j:1.2.0=testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm-analysis:7.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm-analysis:9.1=jacocoAnt
org.ow2.asm:asm-commons:7.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm-commons:9.1=jacocoAnt
org.ow2.asm:asm-tree:7.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm-tree:9.1=jacocoAnt
org.ow2.asm:asm-util:7.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm:7.0=compileClasspath
org.ow2.asm:asm:9.1=jacocoAnt,testCompileClasspath,testRuntimeClasspath
org.pcollections:pcollections:2.1.2=annotationProcessor,testAnnotationProcessor
org.plumelib:plume-util:1.0.6=annotationProcessor,testAnnotationProcessor
org.plumelib:reflection-util:0.0.2=annotationProcessor,testAnnotationProcessor
org.plumelib:require-javadoc:0.1.0=annotationProcessor,testAnnotationProcessor
org.reflections:reflections:0.9.12=checkstyle
org.threeten:threetenbp:1.6.5=compileClasspath,testCompileClasspath,testRuntimeClasspath
empty=

View File

@@ -1,3 +0,0 @@
pluginsUrl=https://plugins.gradle.org/m2/
allowInsecureProtocol=
enableDependencyLocking=true

View File

@@ -1,211 +0,0 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.gradle.plugin;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
import static com.google.common.io.Resources.getResource;
import static google.registry.gradle.plugin.GcsPluginUtils.toByteArraySupplier;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.html.types.TrustedResourceUrls;
import com.google.template.soy.SoyFileSet;
import com.google.template.soy.tofu.SoyTofu;
import google.registry.gradle.plugin.ProjectData.TaskData;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* Creates the files for a web-page summary of a given {@Link ProjectData}.
*
* <p>The main job of this class is rendering a tailored cover page that includes information about
* the project and any task that ran.
*
* <p>It returns all the files that need uploading for the cover page to work. This includes any
* report and log files linked to in the ProjectData, as well as a cover page (and associated
* resources such as CSS files).
*/
final class CoverPageGenerator {
/** List of all resource files that will be uploaded as-is. */
private static final ImmutableSet<Path> STATIC_RESOURCE_FILES =
ImmutableSet.of(Paths.get("css", "style.css"));
/** Name of the entry-point file that will be created. */
private static final Path ENTRY_POINT = Paths.get("index.html");
private final ProjectData projectData;
private final ImmutableSetMultimap<TaskData.State, TaskData> tasksByState;
/**
* The compiled SOY files.
*
* <p>Will be generated only when actually needed, because it takes a while to compile and we
* don't want that to happen unless we actually use it.
*/
private SoyTofu tofu = null;
CoverPageGenerator(ProjectData projectData) {
this.projectData = projectData;
this.tasksByState =
projectData.tasks().stream().collect(toImmutableSetMultimap(TaskData::state, task -> task));
}
/**
* Returns all the files that need uploading for the cover page to work.
*
* <p>This includes all the report files as well, to make sure that the link works.
*/
FilesWithEntryPoint getFilesToUpload() {
ImmutableMap.Builder<Path, Supplier<byte[]>> builder = new ImmutableMap.Builder<>();
// Add all the static resource pages
STATIC_RESOURCE_FILES.stream().forEach(file -> builder.put(file, resourceLoader(file)));
// Create the cover page
// Note that the ByteArraySupplier here is lazy - the createCoverPage function is only called
// when the resulting Supplier's get function is called.
builder.put(ENTRY_POINT, toByteArraySupplier(this::createCoverPage));
// Add all the files from the tasks
tasksByState.values().stream()
.flatMap(task -> task.reports().values().stream())
.forEach(reportFiles -> builder.putAll(reportFiles.files()));
// Add the logs of every test
tasksByState.values().stream()
.filter(task -> task.log().isPresent())
.forEach(task -> builder.put(getLogPath(task), task.log().get()));
return FilesWithEntryPoint.create(builder.build(), ENTRY_POINT);
}
/** Renders the cover page. */
private String createCoverPage() {
return getTofu()
.newRenderer("google.registry.gradle.plugin.coverPage")
.setData(getSoyData())
.render();
}
/** Converts the projectData and all taskData into all the data the soy template needs. */
private ImmutableMap<String, Object> getSoyData() {
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
TaskData.State state =
tasksByState.containsKey(TaskData.State.FAILURE)
? TaskData.State.FAILURE
: TaskData.State.SUCCESS;
String title =
state != TaskData.State.FAILURE
? "Success!"
: "Failed: "
+ tasksByState.get(state).stream()
.map(TaskData::uniqueName)
.collect(Collectors.joining(", "));
builder.put("projectState", state.toString());
builder.put("title", title);
builder.put("cssFiles", ImmutableSet.of(TrustedResourceUrls.fromConstant("css/style.css")));
builder.put("invocation", getInvocation());
builder.put("tasksByState", getTasksByStateSoyData());
return builder.build();
}
/**
* Returns a soy-friendly map from the TaskData.State to the task itslef.
*
* <p>The key order in the resulting map is always the same (the order from the enum definition)
* no matter the key order in the original tasksByState map.
*/
private ImmutableMap<String, Object> getTasksByStateSoyData() {
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
// We go over the States in the order they are defined rather than the order in which they
// happen to be in the tasksByState Map.
//
// That way we guarantee a consistent order.
for (TaskData.State state : TaskData.State.values()) {
builder.put(
state.toString(),
tasksByState.get(state).stream()
.map(task -> taskDataToSoy(task))
.collect(toImmutableList()));
}
return builder.build();
}
/** returns a soy-friendly version of the given task data. */
static ImmutableMap<String, Object> taskDataToSoy(TaskData task) {
// Note that all instances of File.separator are replaced with forward slashes so that we can
// generate a valid href on Windows.
return new ImmutableMap.Builder<String, Object>()
.put("uniqueName", task.uniqueName())
.put("description", task.description())
.put(
"log",
task.log().isPresent() ? getLogPath(task).toString().replace(File.separator, "/") : "")
.put(
"reports",
task.reports().entrySet().stream()
.collect(
toImmutableMap(
Map.Entry::getKey,
entry ->
entry.getValue().files().isEmpty()
? ""
: entry
.getValue()
.entryPoint()
.toString()
.replace(File.separator, "/"))))
.build();
}
private String getInvocation() {
StringBuilder builder = new StringBuilder();
builder.append("./gradlew");
projectData.tasksRequested().forEach(task -> builder.append(" ").append(task));
projectData
.projectProperties()
.forEach((key, value) -> builder.append(String.format(" -P %s=%s", key, value)));
return builder.toString();
}
/** Returns a lazily created soy renderer */
private SoyTofu getTofu() {
if (tofu == null) {
tofu =
SoyFileSet.builder()
.add(getResource(CoverPageGenerator.class, "soy/coverpage.soy"))
.build()
.compileToTofu();
}
return tofu;
}
private static Path getLogPath(TaskData task) {
// We replace colons with dashes so that the resulting filename is always valid, even in
// Windows. As a dash is not a valid character in Java identifies, a task name cannot include
// it, so the uniqueness of the name is perserved.
return Paths.get("logs", task.uniqueName().replace(":", "-") + ".log");
}
private static Supplier<byte[]> resourceLoader(Path path) {
return toByteArraySupplier(getResource(CoverPageGenerator.class, path.toString()));
}
}

View File

@@ -1,59 +0,0 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.gradle.plugin;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableMap;
import java.nio.file.Path;
import java.util.function.Supplier;
/**
* Holds a set of files with a browser-friendly entry point to those files.
*
* <p>The file data is lazily generated.
*
* <p>If there is at least one file, it's guaranteed that the entry point is one of these files.
*/
@AutoValue
abstract class FilesWithEntryPoint {
/**
* All files that are part of this report, keyed from their path to a supplier of their content.
*
* <p>The reason we use a supplier instead of loading the content is in case the content is very
* large...
*
* <p>Also, no point in doing IO before we need it!
*/
abstract ImmutableMap<Path, Supplier<byte[]>> files();
/**
* The file that gives access (links...) to all the data in the report.
*
* <p>Guaranteed to be a key in {@link #files} if and only if files isn't empty.
*/
abstract Path entryPoint();
static FilesWithEntryPoint create(ImmutableMap<Path, Supplier<byte[]>> files, Path entryPoint) {
checkArgument(files.isEmpty() || files.containsKey(entryPoint));
return new AutoValue_FilesWithEntryPoint(files, entryPoint);
}
static FilesWithEntryPoint createSingleFile(Path path, Supplier<byte[]> data) {
return create(ImmutableMap.of(path, data), path);
}
}

View File

@@ -1,221 +0,0 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.gradle.plugin;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.Iterables.getOnlyElement;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.cloud.storage.BlobInfo;
import com.google.cloud.storage.Storage;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Streams;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/** Utility functions used in the GCS plugin. */
final class GcsPluginUtils {
private static final ImmutableMap<String, String> EXTENSION_TO_CONTENT_TYPE =
new ImmutableMap.Builder<String, String>()
.put("html", "text/html")
.put("htm", "text/html")
.put("log", "text/plain")
.put("txt", "text/plain")
.put("css", "text/css")
.put("xml", "text/xml")
.put("zip", "application/zip")
.put("js", "text/javascript")
.build();
private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
static Path toNormalizedPath(File file) {
return file.toPath().toAbsolutePath().normalize();
}
static Path toNormalizedPath(Path file) {
return file.toAbsolutePath().normalize();
}
static String getContentType(String fileName) {
return EXTENSION_TO_CONTENT_TYPE.getOrDefault(
Files.getFileExtension(fileName), DEFAULT_CONTENT_TYPE);
}
static void uploadFileToGcs(
Storage storage, String bucket, Path path, Supplier<byte[]> dataSupplier) {
// Replace Windows file separators with forward slashes.
String filename = path.toString().replace(File.separator, "/");
storage.create(
BlobInfo.newBuilder(bucket, filename).setContentType(getContentType(filename)).build(),
dataSupplier.get());
}
static void uploadFilesToGcsMultithread(
Storage storage, String bucket, Path folder, Map<Path, Supplier<byte[]>> files) {
ImmutableMap.Builder<Path, Thread> threads = new ImmutableMap.Builder<>();
files.forEach(
(path, dataSupplier) -> {
Thread thread =
new Thread(
() -> uploadFileToGcs(storage, bucket, folder.resolve(path), dataSupplier));
thread.start();
threads.put(path, thread);
});
threads
.build()
.forEach(
(path, thread) -> {
try {
thread.join();
} catch (InterruptedException e) {
System.out.format("Upload of %s interrupted", path);
}
});
}
static Supplier<byte[]> toByteArraySupplier(String data) {
return () -> data.getBytes(UTF_8);
}
static Supplier<byte[]> toByteArraySupplier(Supplier<String> dataSupplier) {
return () -> dataSupplier.get().getBytes(UTF_8);
}
static Supplier<byte[]> toByteArraySupplier(File file) {
return () -> {
try {
return Files.toByteArray(file);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
};
}
static Supplier<byte[]> toByteArraySupplier(URL url) {
return () -> {
try {
return Resources.toByteArray(url);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
};
}
/**
* Reads all the files generated by a Report into a FilesWithEntryPoint object.
*
* <p>Every FilesWithEntryPoint must have a single link "entry point" that gives users access to
* all the files. If the report generated just one file - we will just link to that file.
*
* <p>However, if the report generated more than one file - the only thing we can safely do is to
* zip all the files and link to the zip file.
*
* <p>As an alternative to using a zip file, we allow the caller to supply an optional "entry
* point" file that will link to all the other files. If that file is given and is "appropriate"
* (exists and is in the correct location) - we will upload all the report files "as is" and link
* to the entry file.
*
* @param destination the location of the output. Either a file or a directory. If a directory -
* then all the files inside that directory are the outputs we're looking for.
* @param entryPointHint If present - a hint to what the entry point to this directory tree is.
* Will only be used if all of the following apply: (a) {@code
* destination.isDirectory()==true}, (b) there are 2 or more files in the {@code destination}
* directory, and (c) {@code entryPointHint.get()} is one of the files nested inside of the
* {@code destination} directory.
*/
static FilesWithEntryPoint readFilesWithEntryPoint(
File destination, Optional<File> entryPointHint, Path rootDir) {
Path destinationPath = rootDir.relativize(toNormalizedPath(destination));
if (destination.isFile()) {
// The destination is a single file - find its root, and add this single file to the
// FilesWithEntryPoint.
return FilesWithEntryPoint.createSingleFile(
destinationPath, toByteArraySupplier(destination));
}
if (!destination.isDirectory()) {
// This isn't a file nor a directory - so it doesn't exist! Return empty FilesWithEntryPoint
return FilesWithEntryPoint.create(ImmutableMap.of(), destinationPath);
}
// The destination is a directory - find all the actual files first
ImmutableMap<Path, Supplier<byte[]>> files =
Streams.stream(Files.fileTraverser().depthFirstPreOrder(destination))
.filter(File::isFile)
.collect(
toImmutableMap(
file -> rootDir.relativize(toNormalizedPath(file)),
GcsPluginUtils::toByteArraySupplier));
if (files.isEmpty()) {
// The directory exists, but is empty. Return empty FilesWithEntryPoint
return FilesWithEntryPoint.create(ImmutableMap.of(), destinationPath);
}
if (files.size() == 1) {
// We got a directory, but it only has a single file. We can link to that.
return FilesWithEntryPoint.create(files, getOnlyElement(files.keySet()));
}
// There are multiple files in the report! We need to check the entryPointHint
Optional<Path> entryPointPath =
entryPointHint.map(file -> rootDir.relativize(toNormalizedPath(file)));
if (entryPointPath.isPresent() && files.containsKey(entryPointPath.get())) {
// We were given the entry point! Use it!
return FilesWithEntryPoint.create(files, entryPointPath.get());
}
// We weren't given an appropriate entry point. But we still need a single link to all this data
// - so we'll zip it and just host a single file.
Path zipFilePath = destinationPath.resolve(destinationPath.getFileName().toString() + ".zip");
return FilesWithEntryPoint.createSingleFile(zipFilePath, createZippedByteArraySupplier(files));
}
static Supplier<byte[]> createZippedByteArraySupplier(Map<Path, Supplier<byte[]>> files) {
return () -> zipFiles(files);
}
private static byte[] zipFiles(Map<Path, Supplier<byte[]>> files) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (ZipOutputStream zip = new ZipOutputStream(output)) {
for (Path path : files.keySet()) {
zip.putNextEntry(new ZipEntry(path.toString()));
zip.write(files.get(path).get());
zip.closeEntry();
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return output.toByteArray();
}
private GcsPluginUtils() {}
}

View File

@@ -1,137 +0,0 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.gradle.plugin;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
/**
* All the data of a root Gradle project.
*
* <p>This is basically all the "relevant" data from a Gradle Project, arranged in an immutable and
* more convenient way.
*/
@AutoValue
abstract class ProjectData {
abstract String name();
abstract String description();
abstract String gradleVersion();
abstract ImmutableMap<String, String> projectProperties();
abstract ImmutableMap<String, String> systemProperties();
abstract ImmutableSet<String> tasksRequested();
abstract ImmutableSet<TaskData> tasks();
abstract Builder toBuilder();
static Builder builder() {
return new AutoValue_ProjectData.Builder();
}
@AutoValue.Builder
abstract static class Builder {
abstract Builder setName(String name);
abstract Builder setDescription(String description);
abstract Builder setGradleVersion(String gradleVersion);
abstract Builder setProjectProperties(Map<String, String> projectProperties);
abstract Builder setSystemProperties(Map<String, String> systemProperties);
abstract Builder setTasksRequested(Iterable<String> tasksRequested);
abstract ImmutableSet.Builder<TaskData> tasksBuilder();
Builder addTask(TaskData task) {
tasksBuilder().add(task);
return this;
}
abstract ProjectData build();
}
/**
* Relevant data to a single Task's.
*
* <p>Some Tasks are also "Reporting", meaning they create file outputs we want to upload in
* various formats. The format that interests us the most is "html", as that's nicely browsable,
* but they might also have other formats.
*/
@AutoValue
abstract static class TaskData {
enum State {
/** The task has failed for some reason. */
FAILURE,
/** The task was actually run and has finished successfully. */
SUCCESS,
/** The task was up-to-date and successful, and hence didn't need to run again. */
UP_TO_DATE
}
abstract String uniqueName();
abstract String description();
abstract State state();
abstract Optional<Supplier<byte[]>> log();
/**
* Returns the FilesWithEntryPoint for every report, keyed on the report type.
*
* <p>The "html" report type is the most interesting, but there are other report formats.
*/
abstract ImmutableMap<String, FilesWithEntryPoint> reports();
abstract Builder toBuilder();
static Builder builder() {
return new AutoValue_ProjectData_TaskData.Builder();
}
@AutoValue.Builder
abstract static class Builder {
abstract Builder setUniqueName(String name);
abstract Builder setDescription(String description);
abstract Builder setState(State state);
abstract Builder setLog(Supplier<byte[]> log);
abstract ImmutableMap.Builder<String, FilesWithEntryPoint> reportsBuilder();
Builder putReport(String type, FilesWithEntryPoint reportFiles) {
reportsBuilder().put(type, reportFiles);
return this;
}
abstract TaskData build();
}
}
}

View File

@@ -1,311 +0,0 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.gradle.plugin;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.gradle.plugin.GcsPluginUtils.readFilesWithEntryPoint;
import static google.registry.gradle.plugin.GcsPluginUtils.toByteArraySupplier;
import static google.registry.gradle.plugin.GcsPluginUtils.toNormalizedPath;
import static google.registry.gradle.plugin.GcsPluginUtils.uploadFileToGcs;
import static google.registry.gradle.plugin.GcsPluginUtils.uploadFilesToGcsMultithread;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageOptions;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Files;
import google.registry.gradle.plugin.ProjectData.TaskData;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import org.gradle.api.DefaultTask;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.reporting.DirectoryReport;
import org.gradle.api.reporting.Report;
import org.gradle.api.reporting.ReportContainer;
import org.gradle.api.reporting.Reporting;
import org.gradle.api.tasks.TaskAction;
/** A task that uploads the Reports generated by other tasks to GCS. */
public class ReportUploader extends DefaultTask {
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
private static final ImmutableMap<String, BiConsumer<ReportUploader, String>> UPLOAD_FUNCTIONS =
ImmutableMap.of(
"file://", ReportUploader::saveResultsToLocalFolder,
"gcs://", ReportUploader::uploadResultsToGcs);
private final ArrayList<Task> tasks = new ArrayList<>();
private final HashMap<String, StringBuilder> logs = new HashMap<>();
private Project project;
private String destination = null;
private String credentialsFile = null;
private String multithreadedUpload = null;
/**
* Sets the destination of the reports.
*
* <p>Currently supports two types of destinations:
*
* <ul>
* <li>file://[absulute local path], e.g. file:///tmp/buildOutputs/
* <li>gcs://[bucket name]/[optional path], e.g. gcs://my-bucket/buildOutputs/
* </ul>
*/
public void setDestination(String destination) {
this.destination = destination;
}
public void setCredentialsFile(String credentialsFile) {
this.credentialsFile = credentialsFile;
}
public void setMultithreadedUpload(String multithreadedUpload) {
this.multithreadedUpload = multithreadedUpload;
}
/** Converts the given Gradle Project into a ProjectData. */
private ProjectData createProjectData() {
ProjectData.Builder builder =
ProjectData.builder()
.setName(project.getPath() + project.getName())
.setDescription(
Optional.ofNullable(project.getDescription()).orElse("[No description available]"))
.setGradleVersion(project.getGradle().getGradleVersion())
.setProjectProperties(project.getGradle().getStartParameter().getProjectProperties())
.setSystemProperties(project.getGradle().getStartParameter().getSystemPropertiesArgs())
.setTasksRequested(project.getGradle().getStartParameter().getTaskNames());
Path rootDir = toNormalizedPath(project.getRootDir());
tasks.stream()
.filter(task -> task.getState().getExecuted() || task.getState().getUpToDate())
.map(task -> createTaskData(task, rootDir))
.forEach(builder.tasksBuilder()::add);
return builder.build();
}
/**
* Converts a Gradle Task into a TaskData.
*
* @param rootDir the root directory of the main Project - used to get the relative path of any
* Task files.
*/
private TaskData createTaskData(Task task, Path rootDir) {
TaskData.State state =
task.getState().getFailure() != null
? TaskData.State.FAILURE
: task.getState().getUpToDate() ? TaskData.State.UP_TO_DATE : TaskData.State.SUCCESS;
String log = logs.get(task.getPath()).toString();
TaskData.Builder builder =
TaskData.builder()
.setState(state)
.setUniqueName(task.getPath())
.setDescription(
Optional.ofNullable(task.getDescription()).orElse("[No description available]"));
if (!log.isEmpty()) {
builder.setLog(toByteArraySupplier(log));
}
Reporting<? extends ReportContainer<? extends Report>> reporting = asReporting(task);
if (reporting != null) {
// This Task is also a Reporting task! It has a destination file/directory for every supported
// format.
// Add the files for each of the formats into the ReportData.
reporting
.getReports()
.getAsMap()
.forEach(
(type, report) -> {
File destination = report.getDestination();
// The destination could be a file, or a directory. If it's a directory - the Report
// could have created multiple files - and we need to know to which one of those to
// link.
//
// If we're lucky, whoever implemented the Report made sure to extend
// DirectoryReport, which gives us the entry point to all the files.
//
// This isn't guaranteed though, as it depends on the implementer.
Optional<File> entryPointHint =
destination.isDirectory() && (report instanceof DirectoryReport)
? Optional.ofNullable(((DirectoryReport) report).getEntryPoint())
: Optional.empty();
builder
.reportsBuilder()
.put(type, readFilesWithEntryPoint(destination, entryPointHint, rootDir));
});
}
return builder.build();
}
private FilesWithEntryPoint generateFilesToUpload() {
ProjectData projectData = createProjectData();
CoverPageGenerator coverPageGenerator = new CoverPageGenerator(projectData);
return coverPageGenerator.getFilesToUpload();
}
@TaskAction
void uploadResults() {
System.out.format("ReportUploader: destination= '%s'\n", destination);
try {
if (isNullOrEmpty(destination)) {
System.out.format("ReportUploader: no destination given, skipping...\n");
return;
}
for (String key : UPLOAD_FUNCTIONS.keySet()) {
if (destination.startsWith(key)) {
UPLOAD_FUNCTIONS.get(key).accept(this, destination.substring(key.length()));
return;
}
}
System.out.format(
"ReportUploader: given destination '%s' doesn't start with one of %s."
+ " Defaulting to saving in /tmp\n",
destination, UPLOAD_FUNCTIONS.keySet());
saveResultsToLocalFolder("/tmp/");
} catch (Throwable e) {
System.out.format("ReportUploader: Encountered error %s\n", e);
e.printStackTrace(System.out);
System.out.format("ReportUploader: skipping upload\n");
}
}
private void saveResultsToLocalFolder(String absoluteFolderName) {
Path folder = Paths.get(absoluteFolderName, createUniqueFolderName());
checkArgument(
folder.isAbsolute(),
"Local files destination must be an absolute path, but is %s",
absoluteFolderName);
FilesWithEntryPoint filesToUpload = generateFilesToUpload();
System.out.format(
"ReportUploader: going to save %s files to %s\n", filesToUpload.files().size(), folder);
filesToUpload
.files()
.forEach((path, dataSupplier) -> saveFile(folder.resolve(path), dataSupplier));
System.out.format(
"ReportUploader: report saved to file://%s\n", folder.resolve(filesToUpload.entryPoint()));
}
private void saveFile(Path path, Supplier<byte[]> dataSupplier) {
File dir = path.getParent().toFile();
if (!dir.isDirectory()) {
checkState(dir.mkdirs(), "Couldn't create directory %s", dir);
}
try {
Files.write(dataSupplier.get(), path.toFile());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private void uploadResultsToGcs(String destination) {
checkArgument(
!destination.isEmpty(), "destination must include at least the bucket name, but is empty");
Path bucketWithFolder = Paths.get(destination, createUniqueFolderName());
String bucket = bucketWithFolder.getName(0).toString();
Path folder = bucketWithFolder.subpath(1, bucketWithFolder.getNameCount());
StorageOptions.Builder storageOptions = StorageOptions.newBuilder();
if (!isNullOrEmpty(credentialsFile)) {
try {
storageOptions.setCredentials(
GoogleCredentials.fromStream(new FileInputStream(credentialsFile)));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
Storage storage = storageOptions.build().getService();
FilesWithEntryPoint filesToUpload = generateFilesToUpload();
System.out.format(
"ReportUploader: going to upload %s files to %s/%s\n",
filesToUpload.files().size(), bucket, folder);
if ("yes".equals(multithreadedUpload)) {
System.out.format("ReportUploader: multi-threaded upload\n");
uploadFilesToGcsMultithread(storage, bucket, folder, filesToUpload.files());
} else {
System.out.format("ReportUploader: single threaded upload\n");
filesToUpload
.files()
.forEach(
(path, dataSupplier) -> {
System.out.format("ReportUploader: Uploading %s\n", path);
uploadFileToGcs(storage, bucket, folder.resolve(path), dataSupplier);
});
}
System.out.format(
"ReportUploader: report uploaded to https://storage.googleapis.com/%s/%s\n",
bucket, folder.resolve(filesToUpload.entryPoint()));
}
void setProject(Project project) {
this.project = project;
for (Project subProject : project.getAllprojects()) {
subProject.getTasks().all(this::addTask);
}
}
private void addTask(Task task) {
if (task instanceof ReportUploader) {
return;
}
tasks.add(task);
StringBuilder log = new StringBuilder();
checkArgument(
!logs.containsKey(task.getPath()),
"Multiple tasks with the same .getPath()=%s",
task.getPath());
logs.put(task.getPath(), log);
task.getLogging().addStandardOutputListener(output -> log.append(output));
task.getLogging().addStandardErrorListener(output -> log.append(output));
task.finalizedBy(this);
}
@SuppressWarnings("unchecked")
private static Reporting<? extends ReportContainer<? extends Report>> asReporting(Task task) {
if (task instanceof Reporting) {
return (Reporting<? extends ReportContainer<? extends Report>>) task;
}
return null;
}
private String createUniqueFolderName() {
return String.format(
"%h-%h-%h-%h",
SECURE_RANDOM.nextInt(),
SECURE_RANDOM.nextInt(),
SECURE_RANDOM.nextInt(),
SECURE_RANDOM.nextInt());
}
}

View File

@@ -1,47 +0,0 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.gradle.plugin;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
/**
* Plugin setting up the ReportUploader task.
*
* <p>It goes over all the tasks in a project and pass them on to the ReportUploader task for set
* up.
*
* <p>Note that since we're passing in all the projects' tasks - this includes the ReportUploader
* itself! It's up to the ReportUploader to take care of not having "infinite loops" caused by
* waiting for itself to end before finishing.
*/
public class ReportUploaderPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
ReportUploader reportUploader =
project
.getTasks()
.create(
"reportUploader",
ReportUploader.class,
task -> {
task.setDescription("Uploads the reports to GCS bucket");
task.setGroup("uploads");
});
reportUploader.setProject(project);
}
}

View File

@@ -1,27 +0,0 @@
body {
font-family: sans-serif;
}
.task_state_SUCCESS {
color: green;
}
.task_state_FAILURE {
color: red;
}
.task_name {
display: block;
font-size: larger;
font-weight: bold;
}
.task_description {
display: block;
margin-left: 1em;
color: gray;
}
.report_links {
margin-left: 1em;
}
.report_link_broken {
text-decoration: line-through;
color: gray;
}

View File

@@ -1,107 +0,0 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
{namespace google.registry.gradle.plugin}
{template .coverPage}
{@param title: string}
{@param cssFiles: list<trusted_resource_uri>}
{@param projectState: string}
{@param invocation: string}
{@param tasksByState: map<string, list<[uniqueName: string, description: string, log: string, reports: map<string, string>]>>}
<title>{$title}</title>
{for $cssFile in $cssFiles}
<link rel="stylesheet" type="text/css" href="{$cssFile}">
{/for}
<body>
<div class="project">
<h1 class="project_title {'task_state_' + $projectState}">{$title}</h1>
<span class="project_subtitle">
Build results for <span class="project_invocation">{$invocation}</span>
</span>
{for $taskState in mapKeys($tasksByState)}
{if length($tasksByState[$taskState]) > 0}
{call .tasksOfState}
{param state: $taskState /}
{param tasks: $tasksByState[$taskState] /}
{/call}
{/if}
{/for}
</div>
</body>
{/template}
{template .tasksOfState}
{@param state: string}
{@param tasks: list<[uniqueName: string, description: string, log: string, reports: map<string, string>]>}
<div class="{'task_state_' + $state}">
<p>{$state}</p>
// Place the tasks with actual reports first, since those are more likely to be useful
{for $task in $tasks}
{if length(mapKeys($task.reports)) > 0}
{call .task}
{param task: $task /}
{/call}
{/if}
{/for}
// Followup with reports without links
{for $task in $tasks}
{if length(mapKeys($task.reports)) == 0}
{call .task}
{param task: $task /}
{/call}
{/if}
{/for}
</div>
{/template}
{template .task}
{@param task: [uniqueName: string, description: string, log: string, reports: map<string, string>]}
{call .taskInternal}
{param uniqueName: $task.uniqueName /}
{param description: $task.description /}
{param log: $task.log /}
{param reports: $task.reports /}
{/call}
{/template}
{template .taskInternal}
{@param uniqueName: string}
{@param description: string}
{@param log: string}
{@param reports: map<string, string>}
<div class="task">
<span class="task_name">{$uniqueName}</span>
<span class="task_description">{$description}</span>
<span class="report_links">
{if $log}
<a href="{$log}">[log]</a>
{else}
<span class="report_link_broken">[log]</span>
{/if}
{for $type in mapKeys($reports)}
{if $reports[$type]}
<a href="{$reports[$type]}">[{$type}]</a>
{else}
<span class="report_link_broken">[{$type}]</span>
{/if}
{/for}
</span>
</div>
{/template}

View File

@@ -1,279 +0,0 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.gradle.plugin;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.gradle.plugin.GcsPluginUtils.toByteArraySupplier;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import google.registry.gradle.plugin.ProjectData.TaskData;
import java.io.File;
import java.nio.file.Paths;
import org.junit.jupiter.api.Test;
/** Tests for {@link CoverPageGenerator} */
final class CoverPageGeneratorTest {
private static final ProjectData EMPTY_PROJECT =
ProjectData.builder()
.setName("project-name")
.setDescription("project-description")
.setGradleVersion("gradle-version")
.setProjectProperties(ImmutableMap.of("key", "value"))
.setSystemProperties(ImmutableMap.of())
.setTasksRequested(ImmutableSet.of(":a:task1", ":a:task2"))
.build();
private static final TaskData EMPTY_TASK_SUCCESS =
TaskData.builder()
.setUniqueName("task-success")
.setDescription("a successful task")
.setState(TaskData.State.SUCCESS)
.build();
private static final TaskData EMPTY_TASK_FAILURE =
TaskData.builder()
.setUniqueName("task-failure")
.setDescription("a failed task")
.setState(TaskData.State.FAILURE)
.build();
private static final TaskData EMPTY_TASK_UP_TO_DATE =
TaskData.builder()
.setUniqueName("task-up-to-date")
.setDescription("an up-to-date task")
.setState(TaskData.State.UP_TO_DATE)
.build();
private static final Joiner filenameJoiner = Joiner.on(File.separator);
private ImmutableMap<String, String> getGeneratedFiles(ProjectData project) {
CoverPageGenerator coverPageGenerator = new CoverPageGenerator(project);
FilesWithEntryPoint files = coverPageGenerator.getFilesToUpload();
return files.files().entrySet().stream()
.collect(
toImmutableMap(
entry -> entry.getKey().toString(),
entry -> new String(entry.getValue().get(), UTF_8)));
}
private String getContentOfGeneratedFile(ProjectData project, String expectedPath) {
ImmutableMap<String, String> files = getGeneratedFiles(project);
assertThat(files).containsKey(expectedPath);
return files.get(expectedPath);
}
private String getCoverPage(ProjectData project) {
return getContentOfGeneratedFile(project, "index.html");
}
@Test
void testGetFilesToUpload_entryPoint_isIndexHtml() {
CoverPageGenerator coverPageGenerator = new CoverPageGenerator(EMPTY_PROJECT);
assertThat(coverPageGenerator.getFilesToUpload().entryPoint())
.isEqualTo(Paths.get("index.html"));
}
@Test
void testGetFilesToUpload_containsEntryFile() {
String content = getContentOfGeneratedFile(EMPTY_PROJECT, "index.html");
assertThat(content)
.contains(
"<span class=\"project_invocation\">./gradlew :a:task1 :a:task2 -P key=value</span>");
}
@Test
void testCoverPage_showsFailedTask() {
String content = getCoverPage(EMPTY_PROJECT.toBuilder().addTask(EMPTY_TASK_FAILURE).build());
assertThat(content).contains("task-failure");
assertThat(content).contains("<p>FAILURE</p>");
assertThat(content).doesNotContain("<p>SUCCESS</p>");
assertThat(content).doesNotContain("<p>UP_TO_DATE</p>");
}
@Test
void testCoverPage_showsSuccessfulTask() {
String content = getCoverPage(EMPTY_PROJECT.toBuilder().addTask(EMPTY_TASK_SUCCESS).build());
assertThat(content).contains("task-success");
assertThat(content).doesNotContain("<p>FAILURE</p>");
assertThat(content).contains("<p>SUCCESS</p>");
assertThat(content).doesNotContain("<p>UP_TO_DATE</p>");
}
@Test
void testCoverPage_showsUpToDateTask() {
String content = getCoverPage(EMPTY_PROJECT.toBuilder().addTask(EMPTY_TASK_UP_TO_DATE).build());
assertThat(content).contains("task-up-to-date");
assertThat(content).doesNotContain("<p>FAILURE</p>");
assertThat(content).doesNotContain("<p>SUCCESS</p>");
assertThat(content).contains("<p>UP_TO_DATE</p>");
}
@Test
void testCoverPage_failedAreFirst() {
String content =
getCoverPage(
EMPTY_PROJECT.toBuilder()
.addTask(EMPTY_TASK_UP_TO_DATE)
.addTask(EMPTY_TASK_FAILURE)
.addTask(EMPTY_TASK_SUCCESS)
.build());
assertThat(content).contains("<p>FAILURE</p>");
assertThat(content).contains("<p>SUCCESS</p>");
assertThat(content).contains("<p>UP_TO_DATE</p>");
assertThat(content).containsMatch("(?s)<p>FAILURE</p>.*<p>SUCCESS</p>");
assertThat(content).containsMatch("(?s)<p>FAILURE</p>.*<p>UP_TO_DATE</p>");
assertThat(content).doesNotContainMatch("(?s)<p>SUCCESS</p>.*<p>FAILURE</p>");
assertThat(content).doesNotContainMatch("(?s)<p>UP_TO_DATE</p>.*<p>FAILURE</p>");
}
@Test
void testCoverPage_failingTask_statusIsFailure() {
String content =
getCoverPage(
EMPTY_PROJECT.toBuilder()
.addTask(EMPTY_TASK_UP_TO_DATE)
.addTask(EMPTY_TASK_FAILURE)
.addTask(EMPTY_TASK_SUCCESS)
.build());
assertThat(content).contains("<title>Failed: task-failure</title>");
}
@Test
void testCoverPage_noFailingTask_statusIsSuccess() {
String content =
getCoverPage(
EMPTY_PROJECT.toBuilder()
.addTask(EMPTY_TASK_UP_TO_DATE)
.addTask(EMPTY_TASK_SUCCESS)
.build());
assertThat(content).contains("<title>Success!</title>");
}
@Test
void testGetFilesToUpload_containsCssFile() {
ImmutableMap<String, String> files = getGeneratedFiles(EMPTY_PROJECT);
assertThat(files).containsKey(filenameJoiner.join("css", "style.css"));
assertThat(files.get(filenameJoiner.join("css", "style.css"))).contains("body {");
assertThat(files.get("index.html"))
.contains("<link rel=\"stylesheet\" type=\"text/css\" href=\"css/style.css\">");
}
@Test
void testCreateReportFiles_taskWithLog() {
ImmutableMap<String, String> files =
getGeneratedFiles(
EMPTY_PROJECT.toBuilder()
.addTask(
EMPTY_TASK_SUCCESS.toBuilder()
.setUniqueName("my:name")
.setLog(toByteArraySupplier("my log data"))
.build())
.build());
assertThat(files).containsEntry(filenameJoiner.join("logs", "my-name.log"), "my log data");
assertThat(files.get("index.html")).contains("<a href=\"logs/my-name.log\">[log]</a>");
}
@Test
void testCreateReportFiles_taskWithoutLog() {
ImmutableMap<String, String> files =
getGeneratedFiles(
EMPTY_PROJECT.toBuilder()
.addTask(EMPTY_TASK_SUCCESS.toBuilder().setUniqueName("my:name").build())
.build());
assertThat(files).doesNotContainKey("logs/my-name.log");
assertThat(files.get("index.html")).contains("<span class=\"report_link_broken\">[log]</span>");
}
@Test
void testCreateReportFiles_taskWithFilledReport() {
ImmutableMap<String, String> files =
getGeneratedFiles(
EMPTY_PROJECT.toBuilder()
.addTask(
EMPTY_TASK_SUCCESS.toBuilder()
.putReport(
"someReport",
FilesWithEntryPoint.create(
ImmutableMap.of(
Paths.get("path", "report.txt"),
toByteArraySupplier("report content")),
Paths.get("path", "report.txt")))
.build())
.build());
assertThat(files).containsEntry(filenameJoiner.join("path", "report.txt"), "report content");
assertThat(files.get("index.html")).contains("<a href=\"path/report.txt\">[someReport]</a>");
}
@Test
void testCreateReportFiles_taskWithEmptyReport() {
ImmutableMap<String, String> files =
getGeneratedFiles(
EMPTY_PROJECT.toBuilder()
.addTask(
EMPTY_TASK_SUCCESS.toBuilder()
.putReport(
"someReport",
FilesWithEntryPoint.create(
ImmutableMap.of(), Paths.get("path", "report.txt")))
.build())
.build());
assertThat(files).doesNotContainKey(filenameJoiner.join("path", "report.txt"));
assertThat(files.get("index.html"))
.contains("<span class=\"report_link_broken\">[someReport]</span>");
}
@Test
void testCreateReportFiles_taskWithLogAndMultipleReports() {
ImmutableMap<String, String> files =
getGeneratedFiles(
EMPTY_PROJECT.toBuilder()
.addTask(
EMPTY_TASK_SUCCESS.toBuilder()
.setUniqueName("my:name")
.setLog(toByteArraySupplier("log data"))
.putReport(
"filledReport",
FilesWithEntryPoint.create(
ImmutableMap.of(
Paths.get("path-filled", "report.txt"),
toByteArraySupplier("report content"),
Paths.get("path-filled", "other", "file.txt"),
toByteArraySupplier("some other content")),
Paths.get("path-filled", "report.txt")))
.putReport(
"emptyReport",
FilesWithEntryPoint.create(
ImmutableMap.of(), Paths.get("path-empty", "report.txt")))
.build())
.build());
assertThat(files)
.containsEntry(filenameJoiner.join("path-filled", "report.txt"), "report content");
assertThat(files)
.containsEntry(
filenameJoiner.join("path-filled", "other", "file.txt"), "some other content");
assertThat(files).containsEntry(filenameJoiner.join("logs", "my-name.log"), "log data");
assertThat(files.get("index.html"))
.contains("<a href=\"path-filled/report.txt\">[filledReport]</a>");
assertThat(files.get("index.html")).contains("<a href=\"logs/my-name.log\">[log]</a>");
assertThat(files.get("index.html"))
.contains("<span class=\"report_link_broken\">[emptyReport]</span>");
}
}

View File

@@ -1,298 +0,0 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.gradle.plugin;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.gradle.plugin.GcsPluginUtils.getContentType;
import static google.registry.gradle.plugin.GcsPluginUtils.readFilesWithEntryPoint;
import static google.registry.gradle.plugin.GcsPluginUtils.toByteArraySupplier;
import static google.registry.gradle.plugin.GcsPluginUtils.toNormalizedPath;
import static google.registry.gradle.plugin.GcsPluginUtils.uploadFileToGcs;
import static google.registry.gradle.plugin.GcsPluginUtils.uploadFilesToGcsMultithread;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.Files.createDirectories;
import static java.nio.file.Files.createDirectory;
import static java.nio.file.Files.createFile;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import com.google.cloud.storage.BlobInfo;
import com.google.cloud.storage.Storage;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
/** Tests for {@link GcsPluginUtilsTest} */
final class GcsPluginUtilsTest {
private static final Joiner filenameJoiner = Joiner.on(File.separator);
@SuppressWarnings("WeakerAccess")
@TempDir
Path tmpDir;
@Test
void testGetContentType_knownTypes() {
assertThat(getContentType("path/to/file.html")).isEqualTo("text/html");
assertThat(getContentType("path/to/file.htm")).isEqualTo("text/html");
assertThat(getContentType("path/to/file.log")).isEqualTo("text/plain");
assertThat(getContentType("path/to/file.txt")).isEqualTo("text/plain");
assertThat(getContentType("path/to/file.css")).isEqualTo("text/css");
assertThat(getContentType("path/to/file.xml")).isEqualTo("text/xml");
assertThat(getContentType("path/to/file.zip")).isEqualTo("application/zip");
assertThat(getContentType("path/to/file.js")).isEqualTo("text/javascript");
}
@Test
void testGetContentType_unknownTypes() {
assertThat(getContentType("path/to/file.unknown")).isEqualTo("application/octet-stream");
}
@Test
void testUploadFileToGcs() {
Storage storage = mock(Storage.class);
uploadFileToGcs(
storage, "my-bucket", Paths.get("my", "filename.txt"), toByteArraySupplier("my data"));
verify(storage)
.create(
BlobInfo.newBuilder("my-bucket", "my/filename.txt")
.setContentType("text/plain")
.build(),
"my data".getBytes(UTF_8));
verifyNoMoreInteractions(storage);
}
@Test
void testUploadFilesToGcsMultithread() {
Storage storage = mock(Storage.class);
uploadFilesToGcsMultithread(
storage,
"my-bucket",
Paths.get("my", "folder"),
ImmutableMap.of(
Paths.get("some", "index.html"), toByteArraySupplier("some web page"),
Paths.get("some", "style.css"), toByteArraySupplier("some style"),
Paths.get("other", "index.html"), toByteArraySupplier("other web page"),
Paths.get("other", "style.css"), toByteArraySupplier("other style")));
verify(storage)
.create(
BlobInfo.newBuilder("my-bucket", "my/folder/some/index.html")
.setContentType("text/html")
.build(),
"some web page".getBytes(UTF_8));
verify(storage)
.create(
BlobInfo.newBuilder("my-bucket", "my/folder/some/style.css")
.setContentType("text/css")
.build(),
"some style".getBytes(UTF_8));
verify(storage)
.create(
BlobInfo.newBuilder("my-bucket", "my/folder/other/index.html")
.setContentType("text/html")
.build(),
"other web page".getBytes(UTF_8));
verify(storage)
.create(
BlobInfo.newBuilder("my-bucket", "my/folder/other/style.css")
.setContentType("text/css")
.build(),
"other style".getBytes(UTF_8));
verifyNoMoreInteractions(storage);
}
@Test
void testToByteArraySupplier_string() {
assertThat(toByteArraySupplier("my string").get()).isEqualTo("my string".getBytes(UTF_8));
}
@Test
void testToByteArraySupplier_stringSupplier() {
assertThat(toByteArraySupplier(() -> "my string").get()).isEqualTo("my string".getBytes(UTF_8));
}
@Test
void testToByteArraySupplier_file() throws Exception {
Path dir = createDirectory(tmpDir.resolve("arbitrary"));
Path file = createFile(dir.resolve("file.txt"));
Files.write(file, "some data".getBytes(UTF_8));
assertThat(toByteArraySupplier(file.toFile()).get()).isEqualTo("some data".getBytes(UTF_8));
}
private ImmutableMap<String, String> readAllFiles(FilesWithEntryPoint reportFiles) {
return reportFiles.files().entrySet().stream()
.collect(
toImmutableMap(
entry -> entry.getKey().toString(),
entry -> new String(entry.getValue().get(), UTF_8)));
}
@Test
void testCreateReportFiles_destinationIsFile() throws Exception {
Path root = toNormalizedPath(createDirectories(tmpDir.resolve("my/root")).toAbsolutePath());
Path somePath = createDirectories(root.resolve("some/path"));
Path destination = createFile(somePath.resolve("file.txt"));
Files.write(destination, "some data".getBytes(UTF_8));
// Since the entry point is obvious here - any hint given is just ignored.
File ignoredHint = createFile(root.resolve("ignored.txt")).toFile();
FilesWithEntryPoint files =
readFilesWithEntryPoint(destination.toFile(), Optional.of(ignoredHint), root);
assertThat(files.entryPoint().toString())
.isEqualTo(filenameJoiner.join("some", "path", "file.txt"));
assertThat(readAllFiles(files))
.containsExactly(filenameJoiner.join("some", "path", "file.txt"), "some data");
}
@Test
void testCreateReportFiles_destinationDoesntExist() throws Exception {
Path root = toNormalizedPath(createDirectories(tmpDir.resolve("my/root")).toAbsolutePath());
File destination = root.resolve("non/existing.txt").toFile();
assertThat(destination.isFile()).isFalse();
assertThat(destination.isDirectory()).isFalse();
// Since there are no files, any hint given is obviously wrong and will be ignored.
File ignoredHint = createFile(root.resolve("ignored.txt")).toFile();
FilesWithEntryPoint files =
readFilesWithEntryPoint(destination, Optional.of(ignoredHint), root);
assertThat(files.entryPoint().toString()).isEqualTo(filenameJoiner.join("non", "existing.txt"));
assertThat(files.files()).isEmpty();
}
@Test
void testCreateReportFiles_noFiles() throws Exception {
Path root = toNormalizedPath(createDirectories(tmpDir.resolve("my/root")).toAbsolutePath());
Path destination = createDirectories(root.resolve("some/path"));
createDirectories(destination.resolve("a/b"));
createDirectory(destination.resolve("c"));
// Since there are not files, any hint given is obviously wrong and will be ignored.
File ignoredHint = createFile(root.resolve("ignored.txt")).toFile();
FilesWithEntryPoint files =
readFilesWithEntryPoint(destination.toFile(), Optional.of(ignoredHint), root);
assertThat(files.entryPoint().toString()).isEqualTo(filenameJoiner.join("some", "path"));
assertThat(files.files()).isEmpty();
}
@Test
void testCreateReportFiles_oneFile() throws Exception {
Path root = toNormalizedPath(createDirectories(tmpDir.resolve("my/root")).toAbsolutePath());
Path destination = createDirectories(root.resolve("some/path"));
createDirectories(destination.resolve("a/b"));
createDirectory(destination.resolve("c"));
Files.write(createFile(destination.resolve("a/file.txt")), "some data".getBytes(UTF_8));
// Since the entry point is obvious here - any hint given is just ignored.
File ignoredHint = createFile(root.resolve("ignored.txt")).toFile();
FilesWithEntryPoint files =
readFilesWithEntryPoint(destination.toFile(), Optional.of(ignoredHint), root);
assertThat(files.entryPoint().toString())
.isEqualTo(filenameJoiner.join("some", "path", "a", "file.txt"));
assertThat(readAllFiles(files))
.containsExactly(filenameJoiner.join("some", "path", "a", "file.txt"), "some data");
}
/**
* Currently tests the "unimplemented" behavior.
*
* <p>TODO(guyben): switch to checking zip file instead.
*/
@Test
void testCreateReportFiles_multipleFiles_noHint() throws Exception {
Path root = toNormalizedPath(createDirectories(tmpDir.resolve("my/root")).toAbsolutePath());
Path destination = createDirectories(root.resolve("some/path"));
createDirectories(destination.resolve("a/b"));
createDirectory(destination.resolve("c"));
Files.write(createFile(destination.resolve("index.html")), "some data".getBytes(UTF_8));
Files.write(createFile(destination.resolve("a/index.html")), "wrong index".getBytes(UTF_8));
Files.write(createFile(destination.resolve("c/style.css")), "css file".getBytes(UTF_8));
Files.write(createFile(destination.resolve("my_image.png")), "images".getBytes(UTF_8));
FilesWithEntryPoint files =
readFilesWithEntryPoint(destination.toFile(), Optional.empty(), root);
assertThat(files.entryPoint().toString())
.isEqualTo(filenameJoiner.join("some", "path", "path.zip"));
assertThat(readAllFiles(files).keySet())
.containsExactly(filenameJoiner.join("some", "path", "path.zip"));
}
/**
* Currently tests the "unimplemented" behavior.
*
* <p>TODO(guyben): switch to checking zip file instead.
*/
@Test
void testCreateReportFiles_multipleFiles_withBadHint() throws Exception {
Path root = toNormalizedPath(createDirectories(tmpDir.resolve("my/root")).toAbsolutePath());
Path destination = createDirectories(root.resolve("some/path"));
// This entry point points to a directory, which isn't an appropriate entry point
File badEntryPoint = createDirectories(destination.resolve("a/b")).toFile();
createDirectory(destination.resolve("c"));
Files.write(createFile(destination.resolve("index.html")), "some data".getBytes(UTF_8));
Files.write(createFile(destination.resolve("a/index.html")), "wrong index".getBytes(UTF_8));
Files.write(createFile(destination.resolve("c/style.css")), "css file".getBytes(UTF_8));
Files.write(createFile(destination.resolve("my_image.png")), "images".getBytes(UTF_8));
FilesWithEntryPoint files =
readFilesWithEntryPoint(destination.toFile(), Optional.of(badEntryPoint), root);
assertThat(files.entryPoint().toString())
.isEqualTo(filenameJoiner.join("some", "path", "path.zip"));
assertThat(readAllFiles(files).keySet())
.containsExactly(filenameJoiner.join("some", "path", "path.zip"));
}
@Test
void testCreateReportFiles_multipleFiles_withGoodHint() throws Exception {
Path root = toNormalizedPath(createDirectories(tmpDir.resolve("my/root")).toAbsolutePath());
Path destination = createDirectories(root.resolve("some/path"));
createDirectories(destination.resolve("a/b"));
createDirectory(destination.resolve("c"));
// The hint is an actual file nested in the destination directory!
Path goodEntryPoint = createFile(destination.resolve("index.html"));
Files.write(goodEntryPoint, "some data".getBytes(UTF_8));
Files.write(createFile(destination.resolve("a/index.html")), "wrong index".getBytes(UTF_8));
Files.write(createFile(destination.resolve("c/style.css")), "css file".getBytes(UTF_8));
Files.write(createFile(destination.resolve("my_image.png")), "images".getBytes(UTF_8));
FilesWithEntryPoint files =
readFilesWithEntryPoint(destination.toFile(), Optional.of(goodEntryPoint.toFile()), root);
assertThat(files.entryPoint().toString())
.isEqualTo(filenameJoiner.join("some", "path", "index.html"));
assertThat(readAllFiles(files))
.containsExactly(
filenameJoiner.join("some", "path", "index.html"), "some data",
filenameJoiner.join("some", "path", "a", "index.html"), "wrong index",
filenameJoiner.join("some", "path", "c", "style.css"), "css file",
filenameJoiner.join("some", "path", "my_image.png"), "images");
}
}

View File

@@ -4,14 +4,15 @@
com.diffplug.durian:durian-collect:1.2.0=classpath
com.diffplug.durian:durian-core:1.2.0=classpath
com.diffplug.durian:durian-io:1.2.0=classpath
com.diffplug.gradle.spotless:com.diffplug.gradle.spotless.gradle.plugin:3.25.0=classpath
com.diffplug.spotless:spotless-lib-extra:1.25.0=classpath
com.diffplug.spotless:spotless-lib:1.25.0=classpath
com.diffplug.spotless:spotless-plugin-gradle:3.25.0=classpath
com.diffplug.durian:durian-swt.os:4.2.0=classpath
com.diffplug.spotless:com.diffplug.spotless.gradle.plugin:6.20.0=classpath
com.diffplug.spotless:spotless-lib-extra:2.40.0=classpath
com.diffplug.spotless:spotless-lib:2.40.0=classpath
com.diffplug.spotless:spotless-plugin-gradle:6.20.0=classpath
com.dorongold.plugins:task-tree:2.1.0=classpath
com.dorongold.task-tree:com.dorongold.task-tree.gradle.plugin:2.1.0=classpath
com.github.jengelman.gradle.plugins:shadow:5.1.0=classpath
com.github.johnrengelman.shadow:com.github.johnrengelman.shadow.gradle.plugin:5.1.0=classpath
com.github.johnrengelman.shadow:com.github.johnrengelman.shadow.gradle.plugin:8.1.1=classpath
com.github.johnrengelman:shadow:8.1.1=classpath
com.github.node-gradle.node:com.github.node-gradle.node.gradle.plugin:3.0.1=classpath
com.github.node-gradle:gradle-node-plugin:3.0.1=classpath
com.google.cloud.tools:appengine-gradle-plugin:2.4.1=classpath
@@ -24,56 +25,38 @@ com.google.guava:guava:28.2-jre=classpath
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath
com.google.j2objc:j2objc-annotations:1.3=classpath
com.googlecode.concurrent-trees:concurrent-trees:2.6.1=classpath
com.googlecode.javaewah:JavaEWAH:1.1.6=classpath
com.jcraft:jsch:0.1.55=classpath
com.jcraft:jzlib:1.1.1=classpath
com.netflix.nebula:gradle-lint-plugin:16.0.2=classpath
com.netflix.nebula:nebula-gradle-interop:1.0.11=classpath
commons-io:commons-io:2.6=classpath
commons-lang:commons-lang:2.6=classpath
javax.inject:javax.inject:1=classpath
junit:junit:4.12=classpath
nebula.lint:nebula.lint.gradle.plugin:16.0.2=classpath
net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:2.0.2=classpath
net.ltgt.gradle:gradle-errorprone-plugin:2.0.2=classpath
org.apache.ant:ant-launcher:1.9.7=classpath
org.apache.ant:ant:1.9.7=classpath
com.googlecode.javaewah:JavaEWAH:1.2.3=classpath
com.squareup.okhttp3:okhttp:4.10.0=classpath
com.squareup.okio:okio-jvm:3.0.0=classpath
com.squareup.okio:okio:3.0.0=classpath
commons-io:commons-io:2.11.0=classpath
dev.equo.ide:solstice:1.3.1=classpath
net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:3.1.0=classpath
net.ltgt.gradle:gradle-errorprone-plugin:3.1.0=classpath
org.apache.ant:ant-launcher:1.10.13=classpath
org.apache.ant:ant:1.10.13=classpath
org.apache.commons:commons-compress:1.20=classpath
org.apache.commons:commons-lang3:3.8.1=classpath
org.apache.maven:maven-artifact:3.6.2=classpath
org.apache.maven:maven-builder-support:3.6.2=classpath
org.apache.maven:maven-model-builder:3.6.2=classpath
org.apache.maven:maven-model:3.6.2=classpath
org.bouncycastle:bcpg-jdk15on:1.61=classpath
org.bouncycastle:bcpkix-jdk15on:1.61=classpath
org.bouncycastle:bcprov-jdk15on:1.61=classpath
org.apache.commons:commons-lang3:3.5=classpath
org.checkerframework:checker-qual:2.10.0=classpath
org.codehaus.gpars:gpars:1.2.1=classpath
org.codehaus.groovy:groovy-xml:2.4.7=classpath
org.codehaus.groovy:groovy:2.4.7=classpath
org.codehaus.jsr166-mirror:jsr166y:1.7.0=classpath
org.codehaus.plexus:plexus-interpolation:1.25=classpath
org.codehaus.plexus:plexus-utils:3.2.1=classpath
org.eclipse.jgit:org.eclipse.jgit:5.5.0.201909110433-r=classpath
org.eclipse.sisu:org.eclipse.sisu.inject:0.3.3=classpath
org.codehaus.plexus:plexus-utils:3.5.1=classpath
org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r=classpath
org.eclipse.platform:org.eclipse.osgi:3.18.300=classpath
org.glassfish:javax.json:1.0.4=classpath
org.hamcrest:hamcrest-core:1.3=classpath
org.jdom:jdom2:2.0.6=classpath
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.50=classpath
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.50=classpath
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.50=classpath
org.jetbrains.kotlin:kotlin-stdlib:1.3.50=classpath
org.jdom:jdom2:2.0.6.1=classpath
org.jetbrains.kotlin:kotlin-stdlib-common:1.6.20=classpath
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=classpath
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=classpath
org.jetbrains.kotlin:kotlin-stdlib:1.6.20=classpath
org.jetbrains:annotations:13.0=classpath
org.multiverse:multiverse-core:0.7.0=classpath
org.ow2.asm:asm-analysis:7.1=classpath
org.ow2.asm:asm-commons:7.1=classpath
org.ow2.asm:asm-tree:7.1=classpath
org.ow2.asm:asm:7.1=classpath
org.slf4j:slf4j-api:1.7.2=classpath
org.ow2.asm:asm-commons:9.4=classpath
org.ow2.asm:asm-tree:9.4=classpath
org.ow2.asm:asm:9.4=classpath
org.slf4j:slf4j-api:1.7.36=classpath
org.sonatype.aether:aether-api:1.13.1=classpath
org.sonatype.aether:aether-impl:1.13.1=classpath
org.sonatype.aether:aether-spi:1.13.1=classpath
org.sonatype.aether:aether-util:1.13.1=classpath
org.vafer:jdependency:2.1.1=classpath
org.tukaani:xz:1.9=classpath
org.vafer:jdependency:2.8.0=classpath
org.yaml:snakeyaml:1.21=classpath
empty=

View File

@@ -57,7 +57,7 @@ dependencies {
implementation deps['com.github.ben-manes.caffeine:caffeine']
implementation deps['com.google.code.findbugs:jsr305']
implementation deps['com.google.guava:guava']
implementation deps['javax.inject:javax.inject']
implementation deps['jakarta.inject:jakarta.inject-api']
implementation deps['joda-time:joda-time']
implementation deps['com.google.flogger:flogger']
implementation deps['io.github.java-diff-utils:java-diff-utils']
@@ -65,4 +65,5 @@ dependencies {
testImplementation deps['org.junit.jupiter:junit-jupiter-api']
testImplementation deps['org.junit.jupiter:junit-jupiter-engine']
testImplementation deps['org.junit.platform:junit-platform-launcher']
}

View File

@@ -1,68 +1,70 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
antlr:antlr:2.7.7=checkstyle
com.github.ben-manes.caffeine:caffeine:2.7.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.github.ben-manes.caffeine:caffeine:2.9.3=compileClasspath,default,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
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.2=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.auto.value:auto-value-annotations:1.8.1=compileClasspath,default,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.google.auto:auto-common:0.10=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.code.findbugs:jFormatString:3.0.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,checkstyle,compileClasspath,default,deploy_jar,errorprone,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testing,testingAnnotationProcessor,testingCompileClasspath
com.google.errorprone:error_prone_annotation:2.3.4=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.11.0=compileClasspath,default,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.google.errorprone:error_prone_annotations:2.3.4=annotationProcessor,checkstyle,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.errorprone:error_prone_check_api:2.3.4=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.errorprone:error_prone_core:2.3.4=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.errorprone:error_prone_type_annotations:2.3.4=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.flogger:flogger:0.7.4=compileClasspath,default,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.google.guava:failureaccess:1.0.1=annotationProcessor,checkstyle,compileClasspath,default,deploy_jar,errorprone,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testing,testingAnnotationProcessor,testingCompileClasspath
com.google.guava:guava:27.0.1-jre=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.guava:guava:29.0-jre=checkstyle
com.google.guava:guava:31.1-jre=compileClasspath,default,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,compileClasspath,default,deploy_jar,errorprone,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testing,testingAnnotationProcessor,testingCompileClasspath
com.google.j2objc:j2objc-annotations:1.1=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.j2objc:j2objc-annotations:1.3=checkstyle,compileClasspath,default,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.google.protobuf:protobuf-java:3.4.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.truth:truth:1.1.3=compileClasspath,default,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.googlecode.java-diff-utils:diffutils:1.3.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.puppycrawl.tools:checkstyle:8.37=checkstyle
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
com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.auto:auto-common:1.2.1=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
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.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.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
com.google.guava:guava:31.0.1-jre=checkstyle
com.google.guava:guava:32.1.1-jre=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.guava:guava:33.2.1-android=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=checkstyle,compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.google.inject:guice:5.1.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.j2objc:j2objc-annotations:1.3=checkstyle
com.google.j2objc:j2objc-annotations:3.0.0=compileClasspath,testCompileClasspath,testingCompileClasspath
com.google.protobuf:protobuf-java:3.19.6=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.truth:truth:1.4.4=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.puppycrawl.tools:checkstyle:9.3=checkstyle
commons-beanutils:commons-beanutils:1.9.4=checkstyle
commons-collections:commons-collections:3.2.2=checkstyle
info.picocli:picocli:4.5.2=checkstyle
io.github.java-diff-utils:java-diff-utils:4.12=compileClasspath,default,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
javax.inject:javax.inject:1=compileClasspath,default,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
joda-time:joda-time:2.12.2=compileClasspath,default,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
junit:junit:4.13.2=default,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
net.sf.saxon:Saxon-HE:10.3=checkstyle
org.antlr:antlr4-runtime:4.8-1=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.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.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,default,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
org.checkerframework:checker-qual:2.11.1=checkstyle
org.checkerframework:checker-qual:3.0.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
org.checkerframework:checker-qual:3.19.0=compileClasspath,default,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
org.checkerframework:dataflow:3.0.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
org.checkerframework:javacutil:3.0.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
org.codehaus.mojo:animal-sniffer-annotations:1.17=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
org.hamcrest:hamcrest-core:1.3=default,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
org.jacoco:org.jacoco.agent:0.8.7=jacocoAgent,jacocoAnt
org.jacoco:org.jacoco.ant:0.8.7=jacocoAnt
org.jacoco:org.jacoco.core:0.8.7=jacocoAnt
org.jacoco:org.jacoco.report:0.8.7=jacocoAnt
org.javassist:javassist:3.26.0-GA=checkstyle
org.junit.jupiter:junit-jupiter-api:5.9.2=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:5.9.2=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-commons:1.9.2=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-engine:1.9.2=testCompileClasspath,testRuntimeClasspath
org.junit:junit-bom:5.9.2=testCompileClasspath,testRuntimeClasspath
org.opentest4j:opentest4j:1.2.0=testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm-analysis:9.1=jacocoAnt
org.ow2.asm:asm-commons:9.1=jacocoAnt
org.ow2.asm:asm-tree:9.1=jacocoAnt
org.ow2.asm:asm:9.1=compileClasspath,default,deploy_jar,jacocoAnt,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
org.pcollections:pcollections:2.1.2=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
org.plumelib:plume-util:1.0.6=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
org.plumelib:reflection-util:0.0.2=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
org.plumelib:require-javadoc:0.1.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
org.reflections:reflections:0.9.12=checkstyle
empty=archives,errorproneJavac,testingCompile,testingRuntime,testingRuntimeClasspath
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
org.hamcrest:hamcrest-core:1.3=testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
org.jacoco:org.jacoco.agent:0.8.12=jacocoAgent,jacocoAnt
org.jacoco:org.jacoco.ant:0.8.12=jacocoAnt
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.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
org.ow2.asm:asm:9.7=compileClasspath,deploy_jar,jacocoAnt,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
org.pcollections:pcollections:3.1.4=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
org.reflections:reflections:0.10.2=checkstyle
empty=testingCompile,testingRuntime,testingRuntimeClasspath

View File

@@ -0,0 +1,43 @@
// Copyright 2023 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.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Iterators.partition;
import static com.google.common.collect.Iterators.transform;
import static com.google.common.collect.Streams.stream;
import static java.lang.Math.min;
import com.google.common.collect.ImmutableList;
import java.util.stream.Stream;
/** Utilities for breaking up a {@link Stream} into batches. */
public final class BatchedStreams {
static final int MAX_BATCH = 1024 * 1024;
private BatchedStreams() {}
/**
* Transform a flat {@link Stream} into a {@code Stream} of batches.
*
* <p>Closing the returned stream does not close the original stream.
*/
public static <T> Stream<ImmutableList<T>> toBatches(Stream<T> stream, int batchSize) {
checkArgument(batchSize > 0, "batchSize must be a positive integer.");
return stream(
transform(partition(stream.iterator(), min(MAX_BATCH, batchSize)), ImmutableList::copyOf));
}
}

View File

@@ -33,8 +33,8 @@ public abstract class DateTimeUtils {
/**
* A date in the far future that we can treat as infinity.
*
* <p>This value is (2^63-1)/1000 rounded down. AppEngine stores dates as 64 bit microseconds, but
* Java uses milliseconds, so this is the largest representable date that will survive a
* <p>This value is (2^63-1)/1000 rounded down. Postgres can store dates as 64 bit microseconds,
* but Java uses milliseconds, so this is the largest representable date that will survive a
* round-trip through the database.
*/
public static final DateTime END_OF_TIME = new DateTime(Long.MAX_VALUE / 1000, DateTimeZone.UTC);

View File

@@ -16,8 +16,8 @@ package google.registry.util;
import static org.joda.time.DateTimeZone.UTC;
import jakarta.inject.Inject;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import org.joda.time.DateTime;
/** Clock implementation that proxies to the real system clock. */

View File

@@ -17,9 +17,9 @@ package google.registry.util;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.util.concurrent.Uninterruptibles;
import jakarta.inject.Inject;
import java.io.Serializable;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import org.joda.time.ReadableDuration;
/** Implementation of {@link Sleeper} for production use. */

View File

@@ -0,0 +1,65 @@
// Copyright 2023 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.util;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.util.BatchedStreams.toBatches;
import static java.util.stream.Collectors.counting;
import static java.util.stream.Collectors.groupingBy;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableList;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link BatchedStreams}. */
public class BatchedStreamsTest {
@Test
void invalidBatchSize() {
assertThat(assertThrows(IllegalArgumentException.class, () -> toBatches(Stream.of(), 0)))
.hasMessageThat()
.contains("must be a positive integer");
}
@Test
void batch_success() {
// 900_002 elements -> 900 1K-batches + 1 2-element-batch
Stream<Integer> data = IntStream.rangeClosed(0, 900_001).boxed();
assertThat(
toBatches(data, 1000).map(ImmutableList::size).collect(groupingBy(x -> x, counting())))
.containsExactly(1000, 900L, 2, 1L);
}
@Test
void batch_partialBatch() {
Stream<Integer> data = Stream.of(1, 2, 3);
assertThat(
toBatches(data, 1000).map(ImmutableList::size).collect(groupingBy(x -> x, counting())))
.containsExactly(3, 1L);
}
@Test
void batch_truncateBatchSize() {
// 2M elements -> 2 1M-batches despite the user-specified 2M batch size.
Stream<Integer> data = IntStream.range(0, 1024 * 2048).boxed();
assertThat(
toBatches(data, 2_000_000)
.map(ImmutableList::size)
.collect(groupingBy(x -> x, counting())))
.containsExactly(1024 * 1024, 2L);
}
}

View File

@@ -14,7 +14,6 @@
package google.registry.testing;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.flogger.FluentLogger;
@@ -31,31 +30,26 @@ public final class SystemInfo {
private static final LoadingCache<String, Boolean> hasCommandCache =
Caffeine.newBuilder()
.build(
new CacheLoader<String, Boolean>() {
@Override
public Boolean load(String cmd) throws InterruptedException {
try {
Process pid = Runtime.getRuntime().exec(cmd);
pid.getOutputStream().close();
pid.waitFor();
} catch (IOException e) {
logger.atWarning().withCause(e).log("%s command not available.", cmd);
return false;
}
return true;
cmd -> {
try {
Process pid = Runtime.getRuntime().exec(cmd.split(" "));
pid.getOutputStream().close();
pid.waitFor();
} catch (IOException e) {
logger.atWarning().withCause(e).log("%s command not available.", cmd);
return false;
}
return true;
});
/**
* Returns {@code true} if system command can be run from path.
* Returns {@code true} if system command can be run from the path.
*
* <p><b>Warning:</b> The command is actually run! So there could be side-effects. You might
* need to specify a version flag or something. Return code is ignored.
* <p><b>Warning:</b> The command is actually run! So there could be side effects. You might need
* to specify a version flag or something. Return code is ignored.
*
* <p>This result is a memoized. If multiple therads try to get the same result at once, the
* heavy lifting will only be performed by the first thread and the rest will wait.
*
* @throws ExecutionException
* <p>This result is a memoized. If multiple threads try to get the same result at once, the heavy
* lifting will only be performed by the first thread and the rest will wait.
*/
public static boolean hasCommand(String cmd) throws ExecutionException {
return hasCommandCache.get(cmd);

View File

@@ -63,6 +63,7 @@ public class TextDiffSubject extends Subject {
private final ImmutableList<String> actual;
private DiffFormat diffFormat = DiffFormat.SIDE_BY_SIDE_MARKDOWN;
private ImmutableList<String> comments = ImmutableList.of();
protected TextDiffSubject(FailureMetadata metadata, List<String> actual) {
super(metadata, actual);
@@ -83,13 +84,27 @@ public class TextDiffSubject extends Subject {
return this;
}
/** If set, ignore lines that start with the given string. */
public TextDiffSubject ignoringLinesStartingWith(String... comments) {
this.comments = ImmutableList.copyOf(comments);
return this;
}
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 = ImmutableList.copyOf(expectedContent);
if (expected.equals(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')
@@ -175,14 +190,7 @@ public class TextDiffSubject extends Subject {
.orElse(0);
}
private static class SideBySideRowFormatter {
private final int maxExpectedLineLength;
private final int maxActualLineLength;
private SideBySideRowFormatter(int maxExpectedLineLength, int maxActualLineLength) {
this.maxExpectedLineLength = maxExpectedLineLength;
this.maxActualLineLength = maxActualLineLength;
}
private record SideBySideRowFormatter(int maxExpectedLineLength, int maxActualLineLength) {
public String formatRow(String expected, String actual, char padChar) {
return String.format(

View File

@@ -27,6 +27,9 @@
{
"moduleLicense": "Apache License V2.0"
},
{
"moduleLicense": "Apache License Version 2.0"
},
{
"moduleLicense": "Apache License, Version 2.0"
},
@@ -102,6 +105,12 @@
{
"moduleLicense": "BSD-2-Clause"
},
{
"moduleLicense": "BSD 3-Clause \"New\" or \"Revised\" License (BSD-3-Clause)"
},
{
"moduleLicense": "BSD licence"
},
{
"moduleLicense": "New BSD License"
},
@@ -141,6 +150,9 @@
{
"moduleLicense": "\\n Dual license consisting of the CDDL v1.1 and GPL v2\\n "
},
{
"moduleLicense": "EPL-2.0"
},
{
"moduleLicense": "Eclipse Distribution License (New BSD License)"
},
@@ -192,6 +204,9 @@
{
"moduleLicense": "GNU GENERAL PUBLIC LICENSE, Version 2 + Classpath Exception"
},
{
"moduleLicense": "(GPL-2.0-only WITH Classpath-exception-2.0)"
},
{
"moduleLicense": "GNU Library General Public License v2.1 or later"
},
@@ -262,6 +277,12 @@
{
"moduleLicense": "Unicode/ICU License"
},
{
"moduleLicense": "Unicode-3.0"
},
{
"moduleLicense": "The W3C Software License"
},
{
"moduleLicense": "Public Domain",
"moduleName": "aopalliance:aopalliance"
@@ -274,6 +295,24 @@
"moduleLicense": "Public Domain",
"moduleName": "org.json:json"
},
{
// "Apache License, Version 2.0". The plugin is able to parse up to
// 2.11.3 correctly but then something changed with 2.12.* and it no
// longer parses correctly, even though it's still Apache 2.0.
"moduleLicense": null,
"moduleName": "com.fasterxml.jackson:jackson-bom"
},
{
// "Apache License, Version 2.0".
"moduleLicense": null,
"moduleName": "com.google.cloud:libraries-bom"
},
{
// Part of Guava with "Apache License, Version 2.0". The plugin is unable
// to parse its license for unknown reason.
"moduleLicense": null,
"moduleName": "com.google.guava:guava-parent"
},
{
// "Apache License, Version 2.0".
"moduleLicense": null,
@@ -281,11 +320,44 @@
"moduleName": "com.google.gwt:gwt-user"
},
{
// "Apache License, Version 2.0". The plugin is able to parse up to
// 2.11.3 correctly but then something changed with 2.12.* and it no
// longer parses correctly, even though it's still Apache 2.0.
// "Apache License, Version 2.0".
"moduleLicense": null,
"moduleName": "com.fasterxml.jackson:jackson-bom"
"moduleName": "com.squareup.okhttp3:okhttp"
},
{
// "Apache License, Version 2.0".
"moduleLicense": null,
"moduleVersion": "1.15.1",
"moduleName": "com.squareup:kotlinpoet"
},
{
// "Apache License, Version 2.0".
"moduleLicense": null,
"moduleName": "com.squareup.okio:okio"
},
{
// "Apache License, Version 2.0".
"moduleLicense": null,
"moduleVersion": "3.0.0",
"moduleName": "com.squareup.okio:okio-bom"
},
{
// "Apache License, Version 2.0".
"moduleLicense": null,
"moduleVersion": "3.4.0",
"moduleName": "com.squareup.okio:okio-fakefilesystem"
},
{
// "Apache License, Version 2.0".
"moduleLicense": null,
"moduleVersion": "4.9.3",
"moduleName": "com.squareup.wire:wire-runtime"
},
{
// "Apache License, Version 2.0".
"moduleLicense": null,
"moduleVersion": "4.8.0",
"moduleName": "com.squareup.wire:wire-schema"
},
{
// "Apache License, Version 2.0". The plugin is able to parse up to
@@ -294,13 +366,51 @@
"moduleVersion": "2.0.46.Final",
"moduleName": "io.netty:netty-tcnative-classes"
},
// "Apache License, Version 2.0".
{
"moduleLicense": null,
"moduleName": "io.opentelemetry:opentelemetry-bom"
},
{
// "Apache License, Version 2.0".
"moduleLicense": null,
"moduleVersion": "1.4",
"moduleName": "jakarta-regexp:jakarta-regexp"
},
{
// Actually Eclipse Public License v2.0
"moduleLicense": null,
"moduleName": "org.junit:junit-bom"
},
{
"moduleLicense": "The W3C Software License"
// "Apache License, Version 2.0".
"moduleLicense": null,
"moduleVersion": "1.4.0",
"moduleName": "org.jetbrains.kotlin:kotlin-bom"
},
{
// "Apache License, Version 2.0".
"moduleLicense": null,
"moduleVersion": "1.9.20",
"moduleName": "org.jetbrains.kotlin:kotlin-stdlib-common"
},
{
// "Apache License, Version 2.0".
"moduleLicense": null,
"moduleVersion": "1.5.2",
"moduleName": "org.jetbrains.kotlinx:kotlinx-coroutines-core"
},
{
// "Apache License, Version 2.0".
"moduleLicense": null,
"moduleVersion": "0.4.0",
"moduleName": "org.jetbrains.kotlinx:kotlinx-datetime"
},
{
// "Apache License, Version 2.0".
"moduleLicense": null,
"moduleVersion": "1.0.1",
"moduleName": "org.jetbrains.kotlinx:kotlinx-serialization-core"
}
]
}

View File

@@ -47,7 +47,7 @@ class GradleFlag:
PROPERTIES_HEADER = """\
# This file defines properties used by the gradle build. It must be kept in
# This file defines properties used by the gradle build. It must be kept in
# sync with config/nom_build.py.
#
# To regenerate, run ./nom_build --generate-gradle-properties
@@ -56,8 +56,9 @@ PROPERTIES_HEADER = """\
# nom_build), run ./nom_build --help.
#
# DO NOT EDIT THIS FILE BY HAND
org.gradle.jvmargs=-Xmx1024m
org.gradle.jvmargs=-Xmx4096m
org.gradle.caching=true
org.gradle.parallel=true
"""
# Help text to be displayed (in addition to the synopsis and flag help, which
@@ -88,19 +89,10 @@ PROPERTIES = [
'Allow connecting to plain HTTP repositories. This is provided '
'to allow us to communicate to a local proxy when doing '
'dependency updates.'),
Property('uploaderDestination',
'Location to upload test reports to. Normally this should be a '
'GCS url (see also uploaderCredentialsFile)'),
Property('uploaderCredentialsFile',
'json credentials file to use to upload test reports.'),
Property('uploaderMultithreadedUpload',
'Whether to enable multithread upload.'),
Property('verboseTestOutput',
'If true, show all test output in near-realtime.',
'false',
bool),
Property('flowDocsFile',
'Output filename for the flowDocsTool command.'),
Property('enableDependencyLocking',
'Enables dependency locking.',
'true',
@@ -112,7 +104,7 @@ PROPERTIES = [
Property('testFilter',
'Comma separated list of test patterns, if specified run only '
'these.'),
Property('environment', 'GAE Environment for deployment and staging.'),
Property('environment', 'Environment for deployment and staging.'),
# Cloud SQL properties
Property('dbServer',
@@ -125,28 +117,19 @@ PROPERTIES = [
Property('dbUser', 'Database user name for use in connection'),
Property('dbPassword', 'Database password for use in connection'),
Property('publish_repo',
'Maven repository that hosts the Cloud SQL schema jar and the '
'registry server test jars. Such jars are needed for '
'server/schema integration tests. Please refer to <a '
'href="./integration/README.md">integration project</a> for more '
'information.'),
Property('baseSchemaTag',
'The nomulus version tag of the schema for use in the schema'
'deployment integration test (:db:schemaIncrementalDeployTest)'),
Property('schema_version',
'The nomulus version tag of the schema for use in a database'
'integration test.'),
Property('nomulus_version',
'The version of nomulus to test against in a database '
'integration test.'),
Property('dot_path',
'The path to "dot", part of the graphviz package that converts '
'a BEAM pipeline to image. Setting this property to empty string '
'will disable image generation.',
'/usr/bin/dot'),
Property('pipeline',
'The name of the Beam pipeline being staged.')
'The name of the Beam pipeline being staged.'),
Property('nomulus_env',
'For use by scripts. Normally not set manually.'),
Property('schema_env',
'For use by scripts. Normally not set manually.'),
Property('schemaTestArtifactsDir',
'For use by scripts. Normally not set manually.')
]
GRADLE_FLAGS = [
@@ -278,8 +261,7 @@ def generate_gradle_properties() -> str:
def get_root() -> str:
"""Returns the root of the nomulus build tree."""
cur_dir = os.getcwd()
if not os.path.exists(os.path.join(cur_dir, 'buildSrc')) or \
not os.path.exists(os.path.join(cur_dir, 'core')) or \
if not os.path.exists(os.path.join(cur_dir, 'core')) or \
not os.path.exists(os.path.join(cur_dir, 'gradle.properties')):
raise Exception('You must run this script from the root directory')
return cur_dir

View File

@@ -25,7 +25,11 @@ import textwrap
import re
# We should never analyze any generated files
UNIVERSALLY_SKIPPED_PATTERNS = {"/build/", "cloudbuild-caches", "/out/", ".git/", ".gradle/", "/dist/", "karma.conf.js", "polyfills.ts", "test.ts"}
UNIVERSALLY_SKIPPED_PATTERNS = {"/build/", "cloudbuild-caches", "/out/", ".git/",
".gradle/", "/dist/", "/console-alpha/", "/console-crash/", "/console-qa",
"/console-sandbox", "/console-production", "karma.conf.js", "polyfills.ts",
"test.ts", "/docs/console-endpoints/", "/bin/generated-sources/",
"/bin/generated-test-sources/", "src/main/generated", "src/test/generated"}
# We can't rely on CI to have the Enum package installed so we do this instead.
FORBIDDEN = 1
REQUIRED = 2
@@ -87,35 +91,26 @@ PRESUBMITS = {
PresubmitCheck(
r".*Copyright 20\d{2} The Nomulus Authors\. All Rights Reserved\.",
("java", "js", "soy", "sql", "py", "sh", "gradle", "ts"), {
".git", "/build/", "/generated/", "/generated_tests/",
"node_modules/", "LoggerConfig.java", "registrar_bin.",
".git", "/build/", "node_modules/", "LoggerConfig.java", "registrar_bin.",
"registrar_dbg.", "google-java-format-diff.py",
"nomulus.golden.sql", "soyutils_usegoog.js", "javascript/checks.js"
}, REQUIRED):
"File did not include the license header.",
# Files must end in a newline
PresubmitCheck(r".*\n$", ("java", "js", "soy", "sql", "py", "sh", "gradle", "ts"),
{"node_modules/"}, REQUIRED):
PresubmitCheck(r".*\n$", ("java", "js", "soy", "sql", "py", "sh", "gradle", "ts", "xml"),
{"node_modules/", ".idea"}, REQUIRED):
"Source files must end in a newline.",
# System.(out|err).println should only appear in tools/
# System.(out|err).println should only appear in tools/ or load-testing/
PresubmitCheck(
r".*\bSystem\.(out|err)\.print", "java", {
"StackdriverDashboardBuilder.java", "/tools/", "/example/",
"RegistryTestServerMain.java", "TestServerExtension.java",
"FlowDocumentationTool.java"
"/tools/", "/example/", "/load-testing/",
"RegistryTestServerMain.java", "TestServerExtension.java"
}):
"System.(out|err).println is only allowed in tools/ packages. Please "
"use a logger instead.",
# PostgreSQLContainer instantiation must specify docker tag
# TODO(b/204572437): Fix the pattern to pass DatabaseSnapshotTest.java
PresubmitCheck(
r"[\s\S]*new\s+PostgreSQLContainer(<[\s\S]*>)?\(\s*\)[\s\S]*",
"java", {"DatabaseSnapshotTest.java"}):
"PostgreSQLContainer instantiation must specify docker tag.",
# Various Soy linting checks
PresubmitCheck(
r".* (/\*)?\* {?@param ",
@@ -124,7 +119,7 @@ PRESUBMITS = {
):
"In SOY please use the ({@param name: string} /** User name. */) style"
" parameter passing instead of the ( * @param name User name.) style "
"parameter pasing.",
"parameter passing.",
PresubmitCheck(
r'.*\{[^}]+\w+:\s+"',
"soy",
@@ -143,41 +138,48 @@ PRESUBMITS = {
{},
):
"All soy templates must use strict autoescaping",
# various JS linting checks
PresubmitCheck(
r".*goog\.base\(",
"js",
r".*\nimport (static )?.*\.shaded\..*",
"java",
{"/node_modules/"},
):
"Use of goog.base is not allowed.",
"Do not use shaded dependencies",
PresubmitCheck(
r".*goog\.dom\.classes",
"js",
r".*com\.google\.common\.truth\.Truth8.*",
"java",
{"/node_modules/"},
):
"Instead of goog.dom.classes, use goog.dom.classlist which is smaller "
"and faster.",
"Truth8 is deprecated. Use Truth instead.",
PresubmitCheck(
r".*goog\.getMsg",
"js",
r".*java\.util\.Date.*",
"java",
{"/node_modules/", "JpaTransactionManagerImpl.java"},
):
"Do not use java.util.Date. Use classes in java.time package instead.",
PresubmitCheck(
r".*com\.google\.api\.client\.http\.HttpStatusCodes.*",
"java",
{"/node_modules/"},
):
"Put messages in Soy, instead of using goog.getMsg().",
"Use status code from jakarta.servlet.http.HttpServletResponse.",
PresubmitCheck(
r".*(innerHTML|outerHTML)\s*(=|[+]=)([^=]|$)",
"js",
{"/node_modules/", "registrar_bin."},
r".*mock\(Response\.class\).*",
"java",
{"/node_modules/"},
):
"Do not assign directly to the dom. Use goog.dom.setTextContent to set"
" to plain text, goog.dom.removeChildren to clear, or "
"soy.renderElement to render anything else",
"Do not mock Response, use FakeResponse.",
PresubmitCheck(
r".*console\.(log|info|warn|error)",
"js",
{"/node_modules/", "google/registry/ui/js/util.js", "registrar_bin."},
r".*javax\.servlet\..*",
"java",
{"/node_modules/"},
):
"JavaScript files should not include console logging.",
"Do not use javax.servlet.* Use jakarta.servlet.* instead.",
PresubmitCheck(
r".*javax\.inject\..*",
"java",
{"/node_modules/"},
):
"Do not use javax.inject.* Use jakarta.inject.* instead.",
}
# Note that this regex only works for one kind of Flyway file. If we want to
@@ -265,26 +267,6 @@ def verify_flyway_index():
return not success
def verify_javascript_deps():
"""Verifies that we haven't introduced any new javascript dependencies."""
with open('package.json') as f:
package = json.load(f)
deps = list(package['dependencies'].keys())
if deps != EXPECTED_JS_PACKAGES:
print('Unexpected javascript dependencies. Was expecting '
'%s, got %s.' % (EXPECTED_JS_PACKAGES, deps))
print(textwrap.dedent("""
* If the new dependencies are intentional, please verify that the
* license is one of the allowed licenses (see
* config/dependency-license/allowed_licenses.json) and add an entry
* for the package (with the license in a comment) to the
* EXPECTED_JS_PACKAGES variable in config/presubmits.py.
"""))
return True
return False
def get_files():
for root, dirnames, filenames in os.walk("."):
for filename in filenames:
@@ -309,8 +291,5 @@ if __name__ == "__main__":
# when we put it here it fails fast before all of the tests are run.
failed |= verify_flyway_index()
# Make sure we haven't introduced any javascript dependencies.
failed |= verify_javascript_deps()
if failed:
sys.exit(1)

View File

@@ -0,0 +1,50 @@
{
"root": true,
"ignorePatterns": [
"projects/**/*"
],
"overrides": [
{
"files": [
"*.ts"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@angular-eslint/recommended",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "app",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "app",
"style": "kebab-case"
}
],
"eol-last": [
"error",
"always"
]
}
},
{
"files": [
"*.html"
],
"extends": [
"plugin:@angular-eslint/template/recommended"
],
"rules": {}
}
]
}

View File

@@ -36,7 +36,12 @@ yarn-error.log
/libpeerconnection.log
testem.log
/typings
.nx/
# System files
.DS_Store
Thumbs.db
# Build artifact
/staged/dist
/staged/console-*

View File

View File

@@ -0,0 +1 @@
{}

View File

@@ -9,11 +9,11 @@ expected to change.
## Deployment
Webapp is deployed with the nomulus default service war to Google App Engine.
The webapp is deployed with the nomulus default service war to GKE.
During nomulus default service war build task, gradle script triggers the
following:
1) Console webapp build script `buildConsoleWebappProd`, which installs
1) Console webapp build script `buildConsoleWebapp`, which installs
dependencies, assembles a compiled ts -> js, minified, optimized static
artifact (html, css, js)
2) Artifact assembled in step 1 then gets copied to core project web artifact

View File

@@ -7,7 +7,7 @@
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "less"
"style": "scss"
}
},
"root": "",
@@ -15,31 +15,39 @@
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/console-webapp",
"outputPath": {
"base": "staged/dist/",
"browser": ""
},
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"polyfills": [
"src/polyfills.ts"
],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "less",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.less"
"src/theme.scss",
"src/styles.scss"
],
"scripts": []
"stylePreprocessorOptions": {
"includePaths": ["node_modules/"]
},
"scripts": [],
"browser": "src/main.ts"
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
@@ -55,13 +63,50 @@
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"sandbox": {
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.sandbox.ts"
}
],
"outputHashing": "all"
},
"crash": {
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
},
"alpha": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
},
"qa": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true,
}
},
"defaultConfiguration": "production"
@@ -70,10 +115,22 @@
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "console-webapp:build:production"
"buildTarget": "console-webapp:build:production"
},
"alpha": {
"buildTarget": "console-webapp:build:alpha"
},
"crash": {
"buildTarget": "console-webapp:build:crash"
},
"sandbox": {
"buildTarget": "console-webapp:build:sandbox"
},
"qa": {
"buildTarget": "console-webapp:build:qa"
},
"development": {
"browserTarget": "console-webapp:build:development"
"buildTarget": "console-webapp:build:development"
}
},
"defaultConfiguration": "development"
@@ -81,7 +138,7 @@
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "console-webapp:build"
"buildTarget": "console-webapp:build"
}
},
"test": {
@@ -91,19 +148,40 @@
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"inlineStyleLanguage": "less",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.less"
"src/theme.scss",
"src/styles.scss"
],
"stylePreprocessorOptions": {
"includePaths": ["node_modules/"]
},
"scripts": []
}
},
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": [
"src/**/*.ts",
"src/**/*.html"
]
}
}
}
}
},
"cli": {
"cache": {
"enabled": false
},
"analytics": false,
"schematicCollections": [
"@angular-eslint/schematics"
]
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,12 +11,12 @@
// 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.
def consoleDir = "${rootDir}/console-webapp"
def projectParam = "--project=${rootProject.gcpProject}"
clean {
delete "${consoleDir}/node_modules"
delete "${consoleDir}/dist"
delete "${consoleDir}/staged/dist"
}
task npmInstallDeps(type: Exec) {
@@ -37,18 +37,73 @@ task runConsoleWebappUnitTests(type: Exec) {
args 'run', 'test'
}
task buildConsoleWebappNonProd(type: Exec) {
task buildConsoleWebapp(type: Exec) {
workingDir "${consoleDir}/"
executable 'npm'
args 'run', 'build'
def configuration = project.getProperty('configuration')
args 'run', "build", "--configuration=${configuration}"
doFirst {
println "Building console for environment: ${configuration}"
}
}
// Keeping the same as non prod for now before we figure out optimization we want to include
task buildConsoleWebappProd(type: Exec) {
task buildConsoleForAll() {}
def createConsoleTask = { env ->
project.tasks.register("buildConsoleFor${env.capitalize()}", Exec) {
workingDir "${consoleDir}/"
executable 'npm'
args 'run', 'build', "--configuration=${env}"
doFirst {
println "Building console for environment: ${env}"
}
doLast {
copy {
from "${consoleDir}/staged/dist/"
into "${consoleDir}/staged/console-${env}"
}
delete "${consoleDir}/staged/dist"
}
dependsOn(tasks.npmInstallDeps)
}
project.tasks.register("deleteConsoleFor${env.capitalize()}", Delete) {
delete "${consoleDir}/staged/console-${env}"
}
tasks.named('clean') {
dependsOn(tasks.named("deleteConsoleFor${env.capitalize()}"))
}
tasks.named('buildConsoleForAll') {
dependsOn(tasks.named("buildConsoleFor${env.capitalize()}"))
}
}
['alpha', 'crash', 'qa', 'sandbox', 'production'].forEach {env ->
createConsoleTask(env)
}
// Force an order so we don't run these tasks in parallel.
tasks.buildConsoleForCrash.mustRunAfter(tasks.buildConsoleForAlpha)
tasks.buildConsoleForQa.mustRunAfter(tasks.buildConsoleForCrash)
tasks.buildConsoleForSandbox.mustRunAfter(tasks.buildConsoleForQa)
tasks.buildConsoleForProduction.mustRunAfter(tasks.buildConsoleForSandbox)
// This task must run last, otherwise the previous tasks will have deleted the "dist" folder.
tasks.buildConsoleWebapp.mustRunAfter(tasks.buildConsoleForProduction)
task applyFormatting(type: Exec) {
workingDir "${consoleDir}/"
executable 'npm'
args 'run', 'build'
args 'run', 'prettify'
}
task checkFormatting(type: Exec) {
workingDir "${consoleDir}/"
executable 'npm'
args 'run', 'prettify:check'
}
tasks.buildConsoleWebapp.dependsOn(tasks.npmInstallDeps)
tasks.runConsoleWebappUnitTests.dependsOn(tasks.npmInstallDeps)
tasks.buildConsoleWebappProd.dependsOn(tasks.npmInstallDeps)
tasks.applyFormatting.dependsOn(tasks.npmInstallDeps)
tasks.checkFormatting.dependsOn(tasks.npmInstallDeps)
tasks.build.dependsOn(tasks.checkFormatting)
tasks.build.dependsOn(tasks.runConsoleWebappUnitTests)

View File

@@ -1,7 +1,9 @@
{
"/registrar":
"/console-api":
{
"target": "http://localhost:8080/registrar",
"secure": false
"target": "http://[::1]:8080",
"secure": false,
"logLevel": "debug",
"changeOrigin": true
}
}

View File

@@ -1,48 +1,47 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
antlr:antlr:2.7.7=checkstyle
com.github.ben-manes.caffeine:caffeine:2.7.0=annotationProcessor,errorprone,testAnnotationProcessor
aopalliance:aopalliance:1.0=annotationProcessor,errorprone,testAnnotationProcessor
com.github.ben-manes.caffeine:caffeine:3.0.5=annotationProcessor,errorprone,testAnnotationProcessor
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,errorprone,testAnnotationProcessor
com.google.auto:auto-common:0.10=annotationProcessor,errorprone,testAnnotationProcessor
com.google.code.findbugs:jFormatString:3.0.0=annotationProcessor,errorprone,testAnnotationProcessor
com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,errorprone,testAnnotationProcessor
com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,errorprone,testAnnotationProcessor
com.google.auto:auto-common:1.2.1=annotationProcessor,errorprone,testAnnotationProcessor
com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,checkstyle,errorprone,testAnnotationProcessor
com.google.errorprone:error_prone_annotation:2.3.4=annotationProcessor,errorprone,testAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.3.4=annotationProcessor,checkstyle,errorprone,testAnnotationProcessor
com.google.errorprone:error_prone_check_api:2.3.4=annotationProcessor,errorprone,testAnnotationProcessor
com.google.errorprone:error_prone_core:2.3.4=annotationProcessor,errorprone,testAnnotationProcessor
com.google.errorprone:error_prone_type_annotations:2.3.4=annotationProcessor,errorprone,testAnnotationProcessor
com.google.errorprone:error_prone_annotation:2.23.0=annotationProcessor,errorprone,testAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.23.0=annotationProcessor,errorprone,testAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.7.1=checkstyle
com.google.errorprone:error_prone_check_api:2.23.0=annotationProcessor,errorprone,testAnnotationProcessor
com.google.errorprone:error_prone_core:2.23.0=annotationProcessor,errorprone,testAnnotationProcessor
com.google.errorprone:error_prone_type_annotations:2.23.0=annotationProcessor,errorprone,testAnnotationProcessor
com.google.errorprone:javac:9+181-r4173-1=errorproneJavac
com.google.guava:failureaccess:1.0.1=annotationProcessor,checkstyle,errorprone,testAnnotationProcessor
com.google.guava:guava:27.0.1-jre=annotationProcessor,errorprone,testAnnotationProcessor
com.google.guava:guava:29.0-jre=checkstyle
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,errorprone,testAnnotationProcessor
com.google.j2objc:j2objc-annotations:1.1=annotationProcessor,errorprone,testAnnotationProcessor
com.google.guava:guava-parent:32.1.1-jre=annotationProcessor,errorprone,testAnnotationProcessor
com.google.guava:guava:31.0.1-jre=checkstyle
com.google.guava:guava:32.1.1-jre=annotationProcessor,errorprone,testAnnotationProcessor
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=checkstyle
com.google.inject:guice:5.1.0=annotationProcessor,errorprone,testAnnotationProcessor
com.google.j2objc:j2objc-annotations:1.3=checkstyle
com.google.protobuf:protobuf-java:3.4.0=annotationProcessor,errorprone,testAnnotationProcessor
com.googlecode.java-diff-utils:diffutils:1.3.0=annotationProcessor,errorprone,testAnnotationProcessor
com.puppycrawl.tools:checkstyle:8.37=checkstyle
com.google.protobuf:protobuf-java:3.19.6=annotationProcessor,errorprone,testAnnotationProcessor
com.puppycrawl.tools:checkstyle:9.3=checkstyle
commons-beanutils:commons-beanutils:1.9.4=checkstyle
commons-collections:commons-collections:3.2.2=checkstyle
info.picocli:picocli:4.5.2=checkstyle
net.sf.saxon:Saxon-HE:10.3=checkstyle
org.antlr:antlr4-runtime:4.8-1=checkstyle
org.checkerframework:checker-qual:2.11.1=checkstyle
org.checkerframework:checker-qual:3.0.0=annotationProcessor,errorprone,testAnnotationProcessor
org.checkerframework:dataflow:3.0.0=annotationProcessor,errorprone,testAnnotationProcessor
org.checkerframework:javacutil:3.0.0=annotationProcessor,errorprone,testAnnotationProcessor
org.codehaus.mojo:animal-sniffer-annotations:1.17=annotationProcessor,errorprone,testAnnotationProcessor
org.jacoco:org.jacoco.agent:0.8.7=jacocoAgent,jacocoAnt
org.jacoco:org.jacoco.ant:0.8.7=jacocoAnt
org.jacoco:org.jacoco.core:0.8.7=jacocoAnt
org.jacoco:org.jacoco.report:0.8.7=jacocoAnt
org.javassist:javassist:3.26.0-GA=checkstyle
org.ow2.asm:asm-analysis:9.1=jacocoAnt
org.ow2.asm:asm-commons:9.1=jacocoAnt
org.ow2.asm:asm-tree:9.1=jacocoAnt
org.ow2.asm:asm:9.1=jacocoAnt
org.pcollections:pcollections:2.1.2=annotationProcessor,errorprone,testAnnotationProcessor
org.plumelib:plume-util:1.0.6=annotationProcessor,errorprone,testAnnotationProcessor
org.plumelib:reflection-util:0.0.2=annotationProcessor,errorprone,testAnnotationProcessor
org.plumelib:require-javadoc:0.1.0=annotationProcessor,errorprone,testAnnotationProcessor
org.reflections:reflections:0.9.12=checkstyle
empty=archives,compileClasspath,default,deploy_jar,errorproneJavac,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
info.picocli:picocli:4.6.2=checkstyle
io.github.eisop:dataflow-errorprone:3.34.0-eisop1=annotationProcessor,errorprone,testAnnotationProcessor
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,errorprone,testAnnotationProcessor
javax.inject:javax.inject:1=annotationProcessor,errorprone,testAnnotationProcessor
net.sf.saxon:Saxon-HE:10.6=checkstyle
org.antlr:antlr4-runtime:4.9.3=checkstyle
org.checkerframework:checker-qual:3.12.0=checkstyle
org.checkerframework:checker-qual:3.33.0=annotationProcessor,errorprone,testAnnotationProcessor
org.jacoco:org.jacoco.agent:0.8.12=jacocoAgent,jacocoAnt
org.jacoco:org.jacoco.ant:0.8.12=jacocoAnt
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.ow2.asm:asm-commons:9.7=jacocoAnt
org.ow2.asm:asm-tree:9.7=jacocoAnt
org.ow2.asm:asm:9.7=jacocoAnt
org.pcollections:pcollections:3.1.4=annotationProcessor,errorprone,testAnnotationProcessor
org.reflections:reflections:0.10.2=checkstyle
empty=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath

View File

@@ -0,0 +1 @@
configuration=production

View File

@@ -3,6 +3,17 @@
module.exports = function (config) {
config.set({
customLaunchers: {
ChromeHeadless: {
base: 'Chrome',
flags: [
'--no-sandbox',
'--disable-gpu',
'--headless',
'--remote-debugging-port=9222'
]
}
},
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [

File diff suppressed because it is too large Load Diff

View File

@@ -3,43 +3,55 @@
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build --base-href=/console/",
"start": "ng serve --proxy-config dev-proxy.config.json",
"build": "ng build --base-href=/console/ --configuration=$npm_config_configuration",
"build:local": "ng build --base-href=/default/console/",
"watch": "ng build --watch --configuration development",
"watch": "ng build --watch --configuration=development",
"test": "ng test --browsers=ChromeHeadless --watch=false",
"run:dev": "",
"start:dev": "concurrently \"./../gradlew :core:runTestServer\" \"ng serve --proxy-config dev-proxy.config.json\""
"prettify": "npx prettier --write ./src/",
"prettify:check": "npx prettier --check ./src/",
"start:dev": "concurrently \"./../gradlew :core:runTestServer\" \"ng serve --proxy-config dev-proxy.config.json\"",
"lint": "ng lint"
},
"private": true,
"dependencies": {
"@angular/animations": "^15.2.2",
"@angular/cdk": "^15.2.2",
"@angular/common": "^15.2.2",
"@angular/compiler": "^15.2.2",
"@angular/core": "^15.2.2",
"@angular/forms": "^15.2.2",
"@angular/material": "^15.2.2",
"@angular/platform-browser": "^15.2.2",
"@angular/platform-browser-dynamic": "^15.2.2",
"@angular/router": "^15.2.2",
"@angular/animations": "^19.1.4",
"@angular/cdk": "^19.1.2",
"@angular/common": "^19.1.4",
"@angular/compiler": "^19.1.4",
"@angular/core": "^19.1.4",
"@angular/forms": "^19.1.4",
"@angular/material": "^19.1.2",
"@angular/platform-browser": "^19.1.4",
"@angular/platform-browser-dynamic": "^19.1.4",
"@angular/router": "^19.1.4",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^15.2.4",
"@angular/cli": "~15.2.4",
"@angular/compiler-cli": "^15.2.2",
"@angular-devkit/build-angular": "^19.1.5",
"@angular-eslint/builder": "19.0.2",
"@angular-eslint/eslint-plugin": "19.0.2",
"@angular-eslint/eslint-plugin-template": "19.0.2",
"@angular-eslint/schematics": "19.0.2",
"@angular-eslint/template-parser": "19.0.2",
"@angular/cli": "~19.1.5",
"@angular/compiler-cli": "^19.1.4",
"@types/jasmine": "~4.0.0",
"@types/node": "^18.11.18",
"@types/node": "^18.19.74",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"concurrently": "^7.6.0",
"eslint": "^8.57.0",
"jasmine-core": "~4.3.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
"typescript": "~4.9.4"
"prettier": "2.8.7",
"typescript": "^5.7.3"
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -13,17 +13,143 @@
// limitations under the License.
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {TldsComponent} from './tlds/tlds.component';
import {HomeComponent} from './home/home.component';
import { Route, RouterModule } from '@angular/router';
import { BillingInfoComponent } from './billingInfo/billingInfo.component';
import { DomainListComponent } from './domains/domainList.component';
import { HomeComponent } from './home/home.component';
import { RegistryLockVerifyComponent } from './lock/registryLockVerify.component';
import { RegistrarDetailsComponent } from './registrar/registrarDetails.component';
import { RegistrarComponent } from './registrar/registrarsTable.component';
import { ResourcesComponent } from './resources/resources.component';
import ContactComponent from './settings/contact/contact.component';
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 { HistoryComponent } from './history/history.component';
import { PasswordResetVerifyComponent } from './shared/components/passwordReset/passwordResetVerify.component';
const routes: Routes = [
{ path: 'home', component: HomeComponent },
{ path: 'tlds', component: TldsComponent },
export interface RouteWithIcon extends Route {
iconName?: string;
}
export const PATHS = {
NewOteComponent: 'new-ote',
OteStatusComponent: 'ote-status/:registrarId',
UsersComponent: 'users',
};
export const routes: RouteWithIcon[] = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{
path: PasswordResetVerifyComponent.PATH,
component: PasswordResetVerifyComponent,
},
{
path: RegistryLockVerifyComponent.PATH,
component: RegistryLockVerifyComponent,
},
{
path: PATHS.NewOteComponent,
loadComponent: () =>
import('./ote/newOte.component').then((mod) => mod.NewOteComponent),
},
{
path: PATHS.OteStatusComponent,
loadComponent: () =>
import('./ote/oteStatus.component').then((mod) => mod.OteStatusComponent),
},
{ path: 'registrars', component: RegistrarComponent },
{
path: 'home',
component: HomeComponent,
title: 'Dashboard',
iconName: 'view_comfy_alt',
},
{
path: DomainListComponent.PATH,
component: DomainListComponent,
title: 'Domains',
iconName: 'view_list',
},
{
path: HistoryComponent.PATH,
component: HistoryComponent,
// title: 'History',
// iconName: 'history',
},
{
path: SettingsComponent.PATH,
component: SettingsComponent,
title: 'Settings',
iconName: 'settings',
children: [
{
path: '',
redirectTo: ContactComponent.PATH,
pathMatch: 'full',
},
{
path: ContactComponent.PATH,
component: ContactComponent,
title: 'Contacts',
},
{
path: RdapComponent.PATH,
component: RdapComponent,
title: 'RDAP Info',
},
{
path: SecurityComponent.PATH,
component: SecurityComponent,
title: 'Security',
},
],
},
// {
// path: EppConsole.PATH,
// component: EppConsoleComponent,
// title: "EPP Console",
// iconName: "upgrade"
// },
{
path: RegistrarComponent.PATH,
component: RegistrarComponent,
title: 'Registrars',
iconName: 'account_circle',
},
{
path: RegistrarDetailsComponent.PATH,
component: RegistrarDetailsComponent,
},
{
path: BillingInfoComponent.PATH,
component: BillingInfoComponent,
title: 'Billing Info',
iconName: 'credit_card',
},
{
path: ResourcesComponent.PATH,
component: ResourcesComponent,
title: 'Resources',
iconName: 'description',
},
{
path: PATHS.UsersComponent,
title: 'Users',
iconName: 'manage_accounts',
loadComponent: () =>
import('./users/users.component').then((mod) => mod.UsersComponent),
},
{
path: SupportComponent.PATH,
component: SupportComponent,
title: 'Support',
iconName: 'help',
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
imports: [RouterModule.forRoot(routes, { useHash: true })],
exports: [RouterModule],
})
export class AppRoutingModule { }
export class AppRoutingModule {}

View File

@@ -1,14 +1,25 @@
<div class="toolbar" role="banner">
Nomulus Console
<div class="console-app mat-typography">
<app-header (toggleNavOpen)="toggleSidenav()"></app-header>
<div class="console-app__global-spinner">
<mat-progress-bar
mode="indeterminate"
*ngIf="globalLoader.isLoading"
></mat-progress-bar>
</div>
<mat-sidenav-container class="console-app__container">
<mat-sidenav-content class="console-app__content-wrapper">
<div class="console-app__content" role="main">
<router-outlet></router-outlet>
</div>
</mat-sidenav-content>
<mat-sidenav
[mode]="breakpointObserver.isMobileView() ? 'over' : 'side'"
[opened]="!breakpointObserver.isMobileView()"
[disableClose]="!breakpointObserver.isMobileView()"
#sidenav
class="console-app__sidebar"
>
<app-navigation />
</mat-sidenav>
</mat-sidenav-container>
</div>
<div class="content" role="main">
<nav>
<ul>
<li><a routerLink="/home" routerLinkActive="active" ariaCurrentWhenActive="page">Home page</a></li>
<li><a routerLink="/tlds" routerLinkActive="active" ariaCurrentWhenActive="page">TLDs</a></li>
</ul>
</nav>
</div>
<router-outlet></router-outlet>

View File

@@ -0,0 +1,46 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
:host {
font-family: "Google Sans", Roboto, Helvetica, Arial, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !important;
font-size: 14px;
color: #333;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.console-app {
display: flex;
flex-direction: column;
height: 100%;
background: #fff;
&__container {
flex: 1;
padding-bottom: 36px;
background: transparent;
}
&__sidebar {
min-width: 240px;
border: 0;
}
&__content {
padding: 0 16px;
}
&__global-spinner {
position: absolute;
width: 100%;
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,26 +12,126 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { AppComponent } from './app.component';
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({
imports: [
RouterTestingModule
MatSidenavModule,
NoopAnimationsModule,
MatSnackBarModule,
AppModule,
RouterModule.forRoot(routes),
],
declarations: [
AppComponent
providers: [
{ 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', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
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

@@ -1,4 +1,4 @@
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,13 +12,69 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component } 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',
templateUrl: './app.component.html',
styleUrls: ['./app.component.less']
styleUrls: ['./app.component.scss'],
standalone: false,
})
export class AppComponent {
export class AppComponent implements AfterViewInit {
@ViewChild(MatSidenav)
sidenav!: MatSidenav;
constructor(
protected registrarService: RegistrarService,
protected userDataService: UserDataService,
protected globalLoader: GlobalLoaderService,
protected breakpointObserver: BreakPointObserverService,
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) => {
if (event instanceof NavigationEnd) {
if (this.breakpointObserver.isMobileView()) {
this.sidenav.close();
}
}
});
}
toggleSidenav() {
if (this.sidenav.opened) {
this.sidenav.close();
} else {
this.sidenav.open();
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -13,29 +13,124 @@
// limitations under the License.
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {MaterialModule} from './material.module';
import { MaterialModule } from './material.module';
import { BackendService } from './shared/services/backend.service';
import { provideHttpClient } from '@angular/common/http';
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
import { BillingInfoComponent } from './billingInfo/billingInfo.component';
import {
DomainListComponent,
ReasonDialogComponent,
ResponseDialogComponent,
} from './domains/domainList.component';
import { RegistryLockComponent } from './domains/registryLock.component';
import { HeaderComponent } from './header/header.component';
import { HomeComponent } from './home/home.component';
import { TldsComponent } from './tlds/tlds.component';
import { RegistryLockVerifyComponent } from './lock/registryLockVerify.component';
import { NavigationComponent } from './navigation/navigation.component';
import NewRegistrarComponent from './registrar/newRegistrar.component';
import { RegistrarDetailsComponent } from './registrar/registrarDetails.component';
import { RegistrarSelectorComponent } from './registrar/registrarSelector.component';
import { RegistrarComponent } from './registrar/registrarsTable.component';
import { ResourcesComponent } from './resources/resources.component';
import SettingsContactComponent from './settings/contact/contact.component';
import { ContactDetailsComponent } from './settings/contact/contactDetails.component';
import EppPasswordEditComponent from './settings/security/eppPasswordEdit.component';
import SecurityComponent from './settings/security/security.component';
import SecurityEditComponent from './settings/security/securityEdit.component';
import { SettingsComponent } from './settings/settings.component';
import { NotificationsComponent } from './shared/components/notifications/notifications.component';
import { SelectedRegistrarWrapper } from './shared/components/selectedRegistrarWrapper/selectedRegistrarWrapper.component';
import { LocationBackDirective } from './shared/directives/locationBack.directive';
import { UserLevelVisibility } from './shared/directives/userLevelVisiblity.directive';
import { BreakPointObserverService } from './shared/services/breakPoint.service';
import { GlobalLoaderService } from './shared/services/globalLoader.service';
import { UserDataService } from './shared/services/userData.service';
import { SnackBarModule } from './snackbar.module';
import { SupportComponent } from './support/support.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';
import { HistoryComponent } from './history/history.component';
import { HistoryListComponent } from './history/historyList.component';
@NgModule({
declarations: [SelectedRegistrarWrapper],
imports: [MaterialModule],
exports: [SelectedRegistrarWrapper],
providers: [],
})
export class SelectedRegistrarModule {}
@NgModule({
declarations: [
AppComponent,
BillingInfoComponent,
ContactDetailsComponent,
DomainListComponent,
EppPasswordEditComponent,
ForceFocusDirective,
HeaderComponent,
HistoryComponent,
HistoryListComponent,
HomeComponent,
TldsComponent,
LocationBackDirective,
NavigationComponent,
NewRegistrarComponent,
NotificationsComponent,
PasswordInputForm,
PasswordResetVerifyComponent,
PocReminderComponent,
RdapComponent,
RdapEditComponent,
ReasonDialogComponent,
RegistrarComponent,
RegistrarDetailsComponent,
RegistrarSelectorComponent,
RegistryLockComponent,
RegistryLockVerifyComponent,
ResourcesComponent,
ResponseDialogComponent,
SecurityComponent,
SecurityEditComponent,
SettingsComponent,
SettingsContactComponent,
SupportComponent,
UserLevelVisibility,
],
bootstrap: [AppComponent],
imports: [
MaterialModule,
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule
BrowserAnimationsModule,
BrowserModule,
FormsModule,
MaterialModule,
SelectedRegistrarModule,
SnackBarModule,
],
providers: [
BackendService,
BreakPointObserverService,
GlobalLoaderService,
UserDataService,
{
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
useValue: {
subscriptSizing: 'dynamic',
},
},
provideHttpClient(),
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
export class AppModule {}

View File

@@ -0,0 +1,20 @@
<app-selected-registrar-wrapper>
<h1 class="mat-headline-4" forceFocus>Billing Info</h1>
<div class="console-app__billing">
<div>
<div class="console-app__billing-subhead">
Billing records and information
</div>
<a
class="text-l"
href="{{ driveFolderUrl() }}"
target="_blank"
aria-label="View billing records on Google Drive"
>View on Google Drive</a
>
</div>
<div>
<img src="./assets/billing.png" alt="Generic billing image" />
</div>
</div>
</app-selected-registrar-wrapper>

View File

@@ -0,0 +1,22 @@
.console-app__billing {
display: flex;
flex-wrap: wrap-reverse;
> div {
flex: 1;
display: flex;
justify-content: center;
flex-direction: column;
text-align: left;
max-width: 300px;
min-width: 200px;
margin-top: -40px;
}
img {
aspect-ratio: 1 / 1;
width: 100%;
}
&-subhead {
font-size: 1.25rem;
margin-bottom: 20px;
}
}

View File

@@ -0,0 +1,48 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, computed } from '@angular/core';
import { RegistrarService } from '../registrar/registrar.service';
import { MatSnackBar } from '@angular/material/snack-bar';
@Component({
selector: 'app-billingInfo',
templateUrl: './billingInfo.component.html',
styleUrls: ['./billingInfo.component.scss'],
standalone: false,
})
export class BillingInfoComponent {
public static PATH = 'billingInfo';
constructor(
public registrarService: RegistrarService,
private _snackBar: MatSnackBar
) {}
driveFolderUrl = computed<string>(() => {
if (this.registrarService.registrar()?.driveFolderId) {
return (
'https://drive.google.com/drive/folders/' +
this.registrarService.registrar()?.driveFolderId
);
}
return '';
});
openBillingDetails(e: MouseEvent) {
if (!this.registrarService.registrar()?.driveFolderId) {
e.preventDefault();
this._snackBar.open('Billing Folder ID has not been assigned');
}
}
}

View File

@@ -0,0 +1,230 @@
<app-selected-registrar-wrapper>
<div class="console-app-domains">
<h1 class="mat-headline-4" forceFocus>Domains</h1>
<div
class="console-app-domains__actions-wrapper"
[hidden]="!domainListService.activeActionComponent"
>
<ng-container
v-if="domainListService.activeActionComponent"
*ngComponentOutlet="domainListService.activeActionComponent"
>
</ng-container>
</div>
@if (!isLoading && totalResults == 0) {
<div class="console-app__empty-domains">
<h1>
<mat-icon class="console-app__empty-domains-icon secondary-text"
>apps_outage</mat-icon
>
</h1>
<h1>No domains found</h1>
</div>
} @else {
<mat-menu #actions="matMenu">
<ng-template
matMenuContent
let-domainName="domainName"
let-domain="domain"
>
<button
mat-menu-item
(click)="openRegistryLock(domainName)"
aria-label="Access registry lock for domain"
>
<mat-icon>key</mat-icon>
<span>Registry Lock</span>
</button>
<button
mat-menu-item
(click)="onSuspendClick(domainName)"
[elementId]="getElementIdForSuspendUnsuspend()"
[disabled]="isDomainUnsuspendable(domain)"
>
<mat-icon>lock_clock</mat-icon>
<span>Suspend</span>
</button>
<button
mat-menu-item
(click)="onUnsuspendClick(domainName)"
[elementId]="getElementIdForSuspendUnsuspend()"
[disabled]="!isDomainUnsuspendable(domain)"
>
<mat-icon>lock_open</mat-icon>
<span>Unsuspend</span>
</button>
</ng-template>
</mat-menu>
<div
class="console-app__domains-table-parent"
[hidden]="domainListService.activeActionComponent"
>
<div class="console-app__scrollable-wrapper">
<div class="console-app__scrollable">
@if (isLoading) {
<div class="console-app__domains-spinner">
<mat-spinner />
</div>
}
<a
mat-stroked-button
color="primary"
href="/console-api/dum-download?registrarId={{
registrarService.registrarId()
}}"
class="console-app-domains__download"
>
<mat-icon>download</mat-icon>
Download domains (.csv)
</a>
<mat-form-field class="console-app__domains-filter">
<mat-label>Filter</mat-label>
<input
type="search"
matInput
[(ngModel)]="searchTerm"
(ngModelChange)="sendInput()"
#input
/>
</mat-form-field>
<div
class="console-app__domains-selection"
[elementId]="getElementIdForBulkDelete()"
[ngClass]="{ active: selection.hasValue() }"
>
<div class="console-app__domains-selection-text">
{{ selection.selected.length }} Selected
</div>
<div class="console-app__domains-selection-actions">
<button
mat-flat-button
aria-label="Delete Selected Domains"
[attr.aria-hidden]="!selection.hasValue()"
(click)="deleteSelectedDomains()"
>
Delete Selected Domains
</button>
</div>
</div>
<mat-table
[dataSource]="dataSource"
class="mat-elevation-z0"
class="console-app__domains-table"
>
<!-- Checkbox Column -->
<ng-container matColumnDef="select">
<mat-header-cell *matHeaderCellDef>
<mat-checkbox
(change)="$event ? toggleAllRows() : null"
[checked]="selection.hasValue() && isAllSelected"
[indeterminate]="selection.hasValue() && !isAllSelected"
[aria-label]="checkboxLabel()"
[elementId]="getElementIdForBulkDelete()"
>
</mat-checkbox>
</mat-header-cell>
<mat-cell *matCellDef="let row">
<mat-checkbox
(click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null"
[checked]="selection.isSelected(row)"
[aria-label]="checkboxLabel(row)"
[elementId]="getElementIdForBulkDelete()"
>
</mat-checkbox>
</mat-cell>
</ng-container>
<ng-container matColumnDef="domainName">
<mat-header-cell *matHeaderCellDef>Domain Name</mat-header-cell>
<mat-cell *matCellDef="let element">
<mat-icon
*ngIf="getOperationMessage(element.domainName)"
[matTooltip]="getOperationMessage(element.domainName)"
matTooltipPosition="above"
class="primary-text"
>info</mat-icon
>
<span>{{ element.domainName }}</span>
</mat-cell>
</ng-container>
<ng-container matColumnDef="creationTime">
<mat-header-cell *matHeaderCellDef>Creation Time</mat-header-cell>
<mat-cell *matCellDef="let element">
{{ element.creationTime.creationTime }}
</mat-cell>
</ng-container>
<ng-container matColumnDef="registrationExpirationTime">
<mat-header-cell *matHeaderCellDef
>Expiration Time</mat-header-cell
>
<mat-cell *matCellDef="let element">
{{ element.registrationExpirationTime }}
</mat-cell>
</ng-container>
<ng-container matColumnDef="statuses">
<mat-header-cell *matHeaderCellDef>Statuses</mat-header-cell>
<mat-cell *matCellDef="let element">
<span>{{ element.statuses?.join(", ") }}</span>
</mat-cell>
</ng-container>
<ng-container matColumnDef="registryLock">
<mat-header-cell *matHeaderCellDef
>Registry-Locked</mat-header-cell
>
<mat-cell *matCellDef="let element">{{
isDomainLocked(element.domainName)
}}</mat-cell>
</ng-container>
<ng-container matColumnDef="actions">
<mat-header-cell *matHeaderCellDef>Actions</mat-header-cell>
<mat-cell *matCellDef="let element">
<button
mat-icon-button
[matMenuTriggerFor]="actions"
[matMenuTriggerData]="{
domainName: element.domainName,
domain: element
}"
aria-label="Domain actions"
>
<mat-icon>more_horiz</mat-icon>
</button>
</mat-cell>
</ng-container>
<mat-header-row
*matHeaderRowDef="displayedColumns"
></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
<!-- Row shown when there is no matching data. -->
<mat-row *matNoDataRow>
<mat-cell colspan="6">No domains found</mat-cell>
</mat-row>
</mat-table>
<mat-paginator
[length]="totalResults"
[pageIndex]="pageNumber"
[pageSize]="resultsPerPage"
[pageSizeOptions]="[10, 25, 50, 100, 500]"
(page)="onPageChange($event)"
aria-label="Select page of domain results"
showFirstLastButtons
></mat-paginator>
</div>
</div>
</div>
}
</div>
</app-selected-registrar-wrapper>

View File

@@ -0,0 +1,92 @@
.console-app {
$min-width: 756px;
&__empty-domains {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
&-icon {
transform: scale(3);
margin-bottom: 0.5rem;
}
}
&__domains-selection {
height: 60px;
max-height: 0;
transition: max-height 0.2s linear;
display: flex;
align-items: center;
overflow: hidden;
gap: 20px;
&-text {
font-weight: bold;
}
&.active {
max-height: 60px;
}
}
&-domains__download {
position: absolute;
top: -55px;
right: 0;
}
&__domains-filter {
min-width: $min-width !important;
width: 100%;
}
&__domains-table-parent {
position: relative;
min-width: 100%;
}
&__domains-table {
min-width: $min-width !important;
.mat-column-actions {
max-width: 100px;
}
.mat-column-registryLock {
max-width: 150px;
}
.mat-column-statuses span {
padding: 10px 0;
overflow: hidden;
word-break: break-word;
}
.mat-column-select {
max-width: 60px;
padding-left: 15px;
}
.mat-column-domainName {
position: relative;
padding-left: 25px;
mat-icon {
position: absolute;
left: 0;
}
}
mat-cell:has([style*="display: none"]),
mat-header-cell:has([style*="display: none"]) {
display: none;
}
}
&__domains-spinner {
align-items: center;
display: flex;
justify-content: center;
position: absolute;
height: 100%;
width: 100%;
background: rgba(255, 255, 255, 0.6);
z-index: 2;
}
.mat-mdc-paginator {
min-width: $min-width !important;
}
}

View File

@@ -0,0 +1,54 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MaterialModule } from '../material.module';
import { BackendService } from '../shared/services/backend.service';
import { DomainListComponent } from './domainList.component';
import { FormsModule } from '@angular/forms';
import { AppModule } from '../app.module';
describe('DomainListComponent', () => {
let component: DomainListComponent;
let fixture: ComponentFixture<DomainListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [DomainListComponent],
imports: [
MaterialModule,
BrowserAnimationsModule,
FormsModule,
AppModule,
],
providers: [
BackendService,
provideHttpClient(),
provideHttpClientTesting(),
],
}).compileComponents();
fixture = TestBed.createComponent(DomainListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,419 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { SelectionModel } from '@angular/cdk/collections';
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
import { Component, effect, Inject, ViewChild } from '@angular/core';
import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { MatSnackBar } from '@angular/material/snack-bar';
import { MatTableDataSource } from '@angular/material/table';
import { debounceTime, filter, Subject, take } from 'rxjs';
import { RegistrarService } from '../registrar/registrar.service';
import {
BULK_ACTION_NAME,
Domain,
DomainListService,
} from './domainList.service';
import { RegistryLockComponent } from './registryLock.component';
import { RegistryLockService } from './registryLock.service';
import {
MAT_DIALOG_DATA,
MatDialog,
MatDialogRef,
} from '@angular/material/dialog';
import { RESTRICTED_ELEMENTS } from '../shared/directives/userLevelVisiblity.directive';
interface DomainResponse {
message: string;
responseCode: string;
}
interface DomainData {
[domain: string]: DomainResponse;
}
@Component({
selector: 'app-response-dialog',
template: `
<h2 mat-dialog-title>{{ data.title }}</h2>
<mat-dialog-content [innerHTML]="data.content" />
<mat-dialog-actions>
<button mat-button (click)="onClose()">Close</button>
</mat-dialog-actions>
`,
standalone: false,
})
export class ResponseDialogComponent {
constructor(
public dialogRef: MatDialogRef<ReasonDialogComponent>,
@Inject(MAT_DIALOG_DATA)
public data: { title: string; content: string }
) {}
onClose(): void {
this.dialogRef.close();
}
}
enum Operation {
deleting = 'deleting',
suspending = 'suspending',
unsuspending = 'unsuspending',
}
@Component({
selector: 'app-reason-dialog',
template: `
<h2 mat-dialog-title>
Please provide the (EPP) reason for {{ data.operation }} the domain(s):
</h2>
<mat-dialog-content>
<mat-form-field appearance="outline" style="width:100%">
<textarea matInput [(ngModel)]="reason" rows="4"></textarea>
</mat-form-field>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button (click)="onCancel()">Cancel</button>
<button mat-button color="warn" (click)="onSave()" [disabled]="!reason">
Save
</button>
</mat-dialog-actions>
`,
standalone: false,
})
export class ReasonDialogComponent {
reason: string = '';
constructor(
public dialogRef: MatDialogRef<ReasonDialogComponent>,
@Inject(MAT_DIALOG_DATA)
public data: { operation: Operation }
) {}
onSave(): void {
this.dialogRef.close(this.reason);
}
onCancel(): void {
this.dialogRef.close();
}
}
@Component({
selector: 'app-domain-list',
templateUrl: './domainList.component.html',
styleUrls: ['./domainList.component.scss'],
standalone: false,
})
export class DomainListComponent {
public static PATH = 'domain-list';
private static SUSPENDED_STATUSES = [
'SERVER_RENEW_PROHIBITED',
'SERVER_TRANSFER_PROHIBITED',
'SERVER_UPDATE_PROHIBITED',
'SERVER_DELETE_PROHIBITED',
'SERVER_HOLD',
];
private readonly DEBOUNCE_MS = 500;
isAllSelected = false;
displayedColumns: string[] = [
'select',
'domainName',
'creationTime',
'registrationExpirationTime',
'statuses',
'registryLock',
'actions',
];
dataSource: MatTableDataSource<Domain> = new MatTableDataSource();
selection = new SelectionModel<Domain>(true, [], undefined, this.isChecked());
isLoading = true;
searchTermSubject = new Subject<string>();
searchTerm?: string;
pageNumber?: number;
resultsPerPage = 50;
totalResults?: number = 0;
reason: string = '';
operationResult: DomainData | undefined;
@ViewChild(MatPaginator, { static: true }) paginator!: MatPaginator;
constructor(
protected domainListService: DomainListService,
protected registrarService: RegistrarService,
protected registryLockService: RegistryLockService,
private _snackBar: MatSnackBar,
private dialog: MatDialog
) {
effect(() => {
this.pageNumber = 0;
this.totalResults = 0;
if (this.registrarService.registrarId()) {
this.loadLocks();
this.reloadData();
}
});
}
ngOnInit() {
this.dataSource.paginator = this.paginator;
// Don't spam the server unnecessarily while the user is typing
this.searchTermSubject
.pipe(debounceTime(this.DEBOUNCE_MS))
.subscribe((searchTermValue) => {
this.reloadData();
});
}
ngOnDestroy() {
this.searchTermSubject.complete();
}
openRegistryLock(domainName: string) {
this.domainListService.selectedDomain = domainName;
this.domainListService.activeActionComponent = RegistryLockComponent;
}
loadLocks() {
this.registryLockService.retrieveLocks().subscribe({
error: (err: HttpErrorResponse) => {
if (err.status !== HttpStatusCode.Forbidden) {
// Some users may not have registry lock permissions and that's OK
this._snackBar.open(err.message);
}
},
});
}
isDomainLocked(domainName: string) {
return this.registryLockService.domainsLocks.some(
(d) => d.domainName === domainName
);
}
reloadData() {
this.isLoading = true;
this.domainListService
.retrieveDomains(
this.pageNumber,
this.resultsPerPage,
this.totalResults,
this.searchTerm
)
.subscribe({
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.message);
this.isLoading = false;
},
next: (domainListResult) => {
this.dataSource.data = this.domainListService.domainsList;
this.totalResults = (domainListResult || {}).totalResults || 0;
this.isLoading = false;
},
});
}
sendInput() {
this.searchTermSubject.next(this.searchTerm!);
}
onPageChange(event: PageEvent) {
this.pageNumber = event.pageIndex;
this.resultsPerPage = event.pageSize;
this.selection.clear();
this.reloadData();
}
toggleAllRows() {
if (this.isAllSelected) {
this.selection.clear();
this.isAllSelected = false;
return;
}
this.selection.select(...this.dataSource.data);
this.isAllSelected = true;
}
checkboxLabel(row?: Domain): string {
if (!row) {
return `${this.isAllSelected ? 'deselect' : 'select'} all`;
}
return `${this.selection.isSelected(row) ? 'deselect' : 'select'} row ${
row.domainName
}`;
}
private isChecked(): ((o1: Domain, o2: Domain) => boolean) | undefined {
return (o1: Domain, o2: Domain) => {
if (!o1.domainName || !o2.domainName) {
return false;
}
return this.isAllSelected || o1.domainName === o2.domainName;
};
}
getElementIdForBulkDelete() {
return RESTRICTED_ELEMENTS.BULK_DELETE;
}
getElementIdForSuspendUnsuspend() {
return RESTRICTED_ELEMENTS.SUSPEND;
}
getOperationMessage(domain: string) {
if (this.operationResult && this.operationResult[domain])
return this.operationResult[domain].message;
return '';
}
isDomainUnsuspendable(domain: Domain) {
return DomainListComponent.SUSPENDED_STATUSES.every((s) =>
domain.statuses.includes(s)
);
}
sendDeleteRequest(reason: string) {
this.isLoading = true;
this.domainListService
.bulkDomainAction(
this.selection.selected.map((d) => d.domainName),
reason,
this.registrarService.registrarId(),
BULK_ACTION_NAME.DELETE
)
.pipe(take(1))
.subscribe({
next: (result: DomainData) => {
this.isLoading = false;
const successCount = Object.keys(result).filter((domainName) =>
result[domainName].responseCode.toString().startsWith('1')
).length;
const failureCount = Object.keys(result).length - successCount;
this.dialog.open(ResponseDialogComponent, {
data: {
title: 'Domain Deletion Results',
content: `Successfully deleted - ${successCount} domain(s)<br/>Failed to delete - ${failureCount} domain(s)<br/>${
failureCount
? 'Some domains could not be deleted due to ongoing processes or server errors. '
: ''
}Please check the table for more information.`,
},
});
this.selection.clear();
this.operationResult = result;
this.reloadData();
},
error: (err: HttpErrorResponse) => {
this.isLoading = false;
this._snackBar.open(err.error || err.message);
},
});
}
deleteSelectedDomains() {
const dialogRef = this.dialog.open(ReasonDialogComponent, {
data: {
operation: Operation.deleting,
},
});
dialogRef
.afterClosed()
.pipe(
take(1),
filter((reason) => !!reason)
)
.subscribe(this.sendDeleteRequest.bind(this));
}
sendSuspendUnsuspendRequest(
domainName: string,
reason: string,
actionName: BULK_ACTION_NAME
) {
this.isLoading = true;
this.domainListService
.bulkDomainAction(
[domainName],
reason,
this.registrarService.registrarId(),
actionName
)
.pipe(take(1))
.subscribe({
next: (result: DomainData) => {
this.isLoading = false;
if (result[domainName].responseCode.toString().startsWith('2')) {
this._snackBar.open(result[domainName].message);
} else {
this.reloadData();
}
},
error: (err: HttpErrorResponse) => {
this.isLoading = false;
this._snackBar.open(err.error || err.message);
},
});
}
onSuspendClick(domainName: string) {
const dialogRef = this.dialog.open(ReasonDialogComponent, {
data: {
operation: Operation.suspending,
},
});
dialogRef
.afterClosed()
.pipe(
take(1),
filter((reason) => !!reason)
)
.subscribe((reason) => {
this.sendSuspendUnsuspendRequest(
domainName,
reason,
BULK_ACTION_NAME.SUSPEND
);
});
}
onUnsuspendClick(domainName: string) {
const dialogRef = this.dialog.open(ReasonDialogComponent, {
data: {
operation: Operation.unsuspending,
},
});
dialogRef
.afterClosed()
.pipe(
take(1),
filter((reason) => !!reason)
)
.subscribe((reason) => {
this.sendSuspendUnsuspendRequest(
domainName,
reason,
BULK_ACTION_NAME.UNSUSPEND
);
});
}
}

View File

@@ -0,0 +1,93 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable, Type } from '@angular/core';
import { tap } from 'rxjs';
import { RegistrarService } from '../registrar/registrar.service';
import { BackendService } from '../shared/services/backend.service';
export interface CreateAutoTimestamp {
creationTime: string;
}
export interface Domain {
creationTime: CreateAutoTimestamp;
currentSponsorRegistrarId: string;
domainName: string;
registrationExpirationTime: string;
statuses: string[];
}
export interface DomainListResult {
checkpointTime: string;
domains: Domain[];
totalResults: number;
}
export enum BULK_ACTION_NAME {
DELETE = 'DELETE',
SUSPEND = 'SUSPEND',
UNSUSPEND = 'UNSUSPEND',
}
@Injectable({
providedIn: 'root',
})
export class DomainListService {
checkpointTime?: string;
selectedDomain?: string;
public activeActionComponent: Type<any> | null = null;
public domainsList: Domain[] = [];
constructor(
private backendService: BackendService,
private registrarService: RegistrarService
) {}
retrieveDomains(
pageNumber?: number,
resultsPerPage?: number,
totalResults?: number,
searchTerm?: string
) {
return this.backendService
.getDomains(
this.registrarService.registrarId(),
this.checkpointTime,
pageNumber,
resultsPerPage,
totalResults,
searchTerm
)
.pipe(
tap((domainListResult: DomainListResult) => {
this.checkpointTime = domainListResult?.checkpointTime;
this.domainsList = domainListResult?.domains;
})
);
}
bulkDomainAction(
domains: string[],
reason: string,
registrarId: string,
actionName: BULK_ACTION_NAME
) {
return this.backendService.bulkDomainAction(
domains,
reason,
actionName,
registrarId
);
}
}

View File

@@ -0,0 +1,83 @@
<div class="console-app__registry-lock">
<p>
<button
mat-icon-button
aria-label="Back to domains list"
(click)="goBack()"
>
<mat-icon>arrow_back</mat-icon>
</button>
</p>
@if(!registrarService.registrar()?.registryLockAllowed) {
<h1>
Sorry, your registrar hasn't enrolled in registry lock yet. To do so, please
contact {{ userDataService.userData()?.supportEmail }}.
</h1>
} @else if (isLocked()) {
<h1>Unlock the domain {{ domainListService.selectedDomain }}</h1>
<form (ngSubmit)="save(false)" [formGroup]="unlockDomain">
<p>
<mat-label for="password">Password: </mat-label>
<mat-form-field name="password" appearance="outline">
<input matInput type="text" formControlName="password" required />
</mat-form-field>
</p>
<p>
<mat-label for="relockTime"
>Automatically re-lock the domain after:</mat-label
>
<mat-radio-group
name="relockTime"
formControlName="relockTime"
aria-label="Automatically relock option"
>
@for (option of relockOptions; track option.name) {
<mat-radio-button [value]="option.duration">{{
option.name
}}</mat-radio-button>
}
</mat-radio-group>
</p>
<div class="console-app__registry-lock-notification">
<mat-icon>priority_high</mat-icon>Confirmation email will be sent to your
email address to confirm the unlock
</div>
<button
mat-flat-button
color="primary"
type="submit"
[disabled]="!unlockDomain.valid"
aria-label="Submit domain unlock request"
>
Save
</button>
</form>
} @else {
<h1>Lock the domain {{ domainListService.selectedDomain }}</h1>
<form (ngSubmit)="save(true)" [formGroup]="lockDomain">
<p>
<mat-label for="password">Password: </mat-label>
<mat-form-field name="password" appearance="outline">
<input matInput type="text" formControlName="password" required />
</mat-form-field>
</p>
<div class="console-app__registry-lock-notification">
<mat-icon>priority_high</mat-icon>The lock will not take effect until you
click the confirmation link that will be emailed to you. When it takes
effect, you will be billed the standard server status change billing cost.
</div>
<button
mat-flat-button
color="primary"
type="submit"
[disabled]="!lockDomain.valid"
aria-label="Submit domain lock request"
>
Save
</button>
</form>
}
</div>

View File

@@ -0,0 +1,20 @@
.console-app {
&__registry-lock {
mat-label {
display: block;
margin-bottom: 10px;
}
p {
margin-bottom: 40px;
}
}
&__registry-lock-notification {
padding: 20px;
border-radius: 10px;
background-color: var(--light-highlight);
margin-bottom: 20px;
width: max-content;
display: flex;
align-items: center;
}
}

View File

@@ -0,0 +1,93 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { HttpErrorResponse } from '@angular/common/http';
import { Component, computed } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { MatSnackBar } from '@angular/material/snack-bar';
import { RegistrarService } from '../registrar/registrar.service';
import { UserDataService } from '../shared/services/userData.service';
import { DomainListService } from './domainList.service';
import { RegistryLockService } from './registryLock.service';
@Component({
selector: 'app-registry-lock',
templateUrl: './registryLock.component.html',
styleUrls: ['./registryLock.component.scss'],
standalone: false,
})
export class RegistryLockComponent {
readonly isLocked = computed(() =>
this.registryLockService.domainsLocks.some(
(dl) => dl.domainName === this.domainListService.selectedDomain
)
);
relockOptions = [
{ name: '1 hour', duration: 3600000 },
{ name: '6 hours', duration: 21600000 },
{ name: '24 hours', duration: 86400000 },
{ name: 'Never', duration: undefined },
];
lockDomain = new FormGroup({
password: new FormControl(''),
});
unlockDomain = new FormGroup({
password: new FormControl(''),
relockTime: new FormControl(undefined),
});
constructor(
protected registrarService: RegistrarService,
protected domainListService: DomainListService,
protected registryLockService: RegistryLockService,
protected userDataService: UserDataService,
private _snackBar: MatSnackBar
) {}
goBack() {
this.domainListService.selectedDomain = undefined;
this.domainListService.activeActionComponent = null;
}
save(isLock: boolean) {
let request;
if (!isLock) {
request = this.registryLockService.registryLockDomain(
this.domainListService.selectedDomain || '',
this.unlockDomain.value.password || '',
this.unlockDomain.value.relockTime || undefined,
isLock
);
} else {
request = this.registryLockService.registryLockDomain(
this.domainListService.selectedDomain || '',
this.lockDomain.value.password || '',
undefined,
isLock
);
}
request.subscribe({
complete: () => {
this.goBack();
},
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error);
},
});
}
}

View File

@@ -0,0 +1,59 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { tap } from 'rxjs';
import { RegistrarService } from '../registrar/registrar.service';
import { BackendService } from '../shared/services/backend.service';
export interface DomainLocksResult {
domainName: string;
}
@Injectable({
providedIn: 'root',
})
export class RegistryLockService {
public domainsLocks: DomainLocksResult[] = [];
constructor(
private backendService: BackendService,
private registrarService: RegistrarService
) {}
retrieveLocks() {
return this.backendService
.getLocks(this.registrarService.registrarId())
.pipe(
tap((domainLocksResult) => {
this.domainsLocks = domainLocksResult;
})
);
}
registryLockDomain(
domainName: string,
password: string,
relockDurationMillis: number | undefined,
isLock: boolean
) {
return this.backendService.registryLockDomain(
domainName,
password,
relockDurationMillis,
this.registrarService.registrarId(),
isLock
);
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,18 +12,28 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Alias this since it collides with the closure variable name
def allowInsecure = allowInsecureProtocol
.console-app {
&__logo {
color: inherit;
text-decoration: none;
margin-left: -15px;
}
&__menu-btn {
width: 30px;
height: 30px;
padding: 0;
}
&__header {
margin-top: 0;
margin-bottom: 15px;
if (!pluginsUrl.isEmpty()) {
pluginManagement {
repositories {
maven {
url pluginsUrl
allowInsecureProtocol = allowInsecure == "true"
}
.mat-toolbar {
background: transparent;
margin-bottom: 10px;
}
&-user-icon {
margin-left: 20px;
}
}
} else {
println "Plugins: Using default repo..."
}

View File

@@ -0,0 +1,57 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HeaderComponent } from './header.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MaterialModule } from '../material.module';
import { ActivatedRoute } from '@angular/router';
import { AppModule, SelectedRegistrarModule } from '../app.module';
import { AppRoutingModule } from '../app-routing.module';
import { BackendService } from '../shared/services/backend.service';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
describe('HeaderComponent', () => {
let component: HeaderComponent;
let fixture: ComponentFixture<HeaderComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
SelectedRegistrarModule,
MaterialModule,
BrowserAnimationsModule,
AppRoutingModule,
AppModule,
],
providers: [
BackendService,
{ provide: ActivatedRoute, useValue: {} as ActivatedRoute },
provideHttpClient(),
provideHttpClientTesting(),
],
declarations: [HeaderComponent],
}).compileComponents();
fixture = TestBed.createComponent(HeaderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,39 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, EventEmitter, Output } from '@angular/core';
import { BreakPointObserverService } from '../shared/services/breakPoint.service';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss'],
standalone: false,
})
export class HeaderComponent {
private isNavOpen = false;
constructor(protected breakpointObserver: BreakPointObserverService) {}
@Output() toggleNavOpen = new EventEmitter<boolean>();
toggleNavPane() {
this.isNavOpen = !this.isNavOpen;
this.toggleNavOpen.emit(this.isNavOpen);
}
logOut() {
window.open('/console?gcp-iap-mode=CLEAR_LOGIN_COOKIE', '_self');
}
}

View File

@@ -0,0 +1,62 @@
<app-selected-registrar-wrapper>
<div class="history-log">
<h1 class="mat-headline-4" forceFocus>
Registrar Console Activity History
</h1>
<mat-tab-group
[elementId]="getElementIdForUserLog()"
class="history-log__tabs"
>
<mat-tab label="Registrar Activity">
<div class="spacer"></div>
<app-history-list
[historyRecords]="historyService.historyRecordsRegistrar()"
[isLoading]="isLoading"
/>
</mat-tab>
<mat-tab label="User Activity">
<div class="spacer"></div>
<form (ngSubmit)="loadHistory()" #form="ngForm">
<section>
<mat-form-field appearance="outline">
<mat-label>Console User Email: </mat-label>
<input
matInput
id="email"
type="email"
name="consoleUserEmail"
required
email
[(ngModel)]="consoleUserEmail"
#emailControl="ngModel"
/>
</mat-form-field>
</section>
<div class="spacer"></div>
<button
mat-flat-button
color="primary"
type="submit"
aria-label="Search user history"
[disabled]="!form.valid"
>
Search
</button>
</form>
<div class="spacer"></div>
<app-history-list
[historyRecords]="historyService.historyRecordsUser()"
[isLoading]="isLoading"
/>
</mat-tab>
</mat-tab-group>
</div>
<app-history-list
[elementId]="getElementIdForUserLog()"
[isReverse]="true"
[historyRecords]="historyService.historyRecordsUser()"
[isLoading]="isLoading"
/>
</app-selected-registrar-wrapper>

View File

@@ -1,4 +1,4 @@
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
// 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.
@@ -12,7 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
.history-log {
font-family: "Roboto", sans-serif;
max-width: 760px;
html, body { height: 100%; }
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
.spacer {
margin: 20px 0;
}
}

View File

@@ -0,0 +1,80 @@
// 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, effect } from '@angular/core';
import { UserDataService } from '../shared/services/userData.service';
import { BackendService } from '../shared/services/backend.service';
import { RegistrarService } from '../registrar/registrar.service';
import { HistoryService } from './history.service';
import { MatSnackBar } from '@angular/material/snack-bar';
import {
GlobalLoader,
GlobalLoaderService,
} from '../shared/services/globalLoader.service';
import { HttpErrorResponse } from '@angular/common/http';
import { RESTRICTED_ELEMENTS } from '../shared/directives/userLevelVisiblity.directive';
@Component({
selector: 'app-history',
templateUrl: './history.component.html',
styleUrls: ['./history.component.scss'],
providers: [HistoryService],
standalone: false,
})
export class HistoryComponent implements GlobalLoader {
public static PATH = 'history';
consoleUserEmail: string = '';
isLoading: boolean = false;
constructor(
private backendService: BackendService,
private registrarService: RegistrarService,
protected historyService: HistoryService,
protected globalLoader: GlobalLoaderService,
protected userDataService: UserDataService,
private _snackBar: MatSnackBar
) {
effect(() => {
if (registrarService.registrarId()) {
this.loadHistory();
}
});
}
getElementIdForUserLog() {
return RESTRICTED_ELEMENTS.ACTIVITY_PER_USER;
}
loadingTimeout() {
this._snackBar.open('Timeout loading records history');
}
loadHistory() {
this.globalLoader.startGlobalLoader(this);
this.isLoading = true;
this.historyService
.getHistoryLog(this.registrarService.registrarId(), this.consoleUserEmail)
.subscribe({
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error || err.message);
this.isLoading = false;
},
next: () => {
this.globalLoader.stopGlobalLoader(this);
this.isLoading = false;
},
});
}
}

View File

@@ -0,0 +1,46 @@
// 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 { Injectable, signal } from '@angular/core';
import { BackendService } from '../shared/services/backend.service';
import { tap } from 'rxjs';
export interface HistoryRecord {
modificationTime: string;
type: string;
description: string;
actingUser: {
emailAddress: string;
};
}
@Injectable()
export class HistoryService {
historyRecordsRegistrar = signal<HistoryRecord[]>([]);
historyRecordsUser = signal<HistoryRecord[]>([]);
constructor(private backendService: BackendService) {}
getHistoryLog(registrarId: string, userEmail?: string) {
return this.backendService.getHistoryLog(registrarId, userEmail).pipe(
tap((historyRecords: HistoryRecord[]) => {
if (userEmail) {
this.historyRecordsUser.set(historyRecords);
} else {
this.historyRecordsRegistrar.set(historyRecords);
}
})
);
}
}

View File

@@ -0,0 +1,50 @@
@if (!isLoading && historyRecords.length == 0) {
<div class="history-list__no-records">
<mat-icon class="history-list__no-records-icon secondary-text"
>apps_outage</mat-icon
>
<h1>No records found</h1>
</div>
} @else {
<mat-card>
<mat-card-content>
<mat-list role="list">
<ng-container *ngFor="let item of historyRecords; let last = last">
<mat-list-item class="history-list__item">
<mat-icon
[ngClass]="getIconClass(item.type)"
class="history-list__icon"
>
{{ getIconForType(item.type) }}
</mat-icon>
<div class="history-list__content">
<div class="history-list__description">
<span class="history-list__description--main">{{
item.type
}}</span>
<div>
<mat-chip
*ngIf="parseDescription(item.description).detail"
class="history-list__chip"
>
{{ parseDescription(item.description).detail }}
</mat-chip>
</div>
</div>
<div class="history-list__user">
<b>User - {{ item.actingUser.emailAddress }}</b>
</div>
</div>
<span class="history-list__timestamp">
{{ item.modificationTime | date : "MMM d, y, h:mm a" }}
</span>
</mat-list-item>
<mat-divider *ngIf="!last"></mat-divider>
</ng-container>
</mat-list>
</mat-card-content>
</mat-card>
}

View File

@@ -0,0 +1,81 @@
// 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.
.history-list {
font-family: "Roboto", sans-serif;
&__item {
display: flex;
align-items: center;
// Override default mat-list-item height to fit content
height: auto !important;
padding: 16px 0;
}
&__no-records {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
&__no-records-icon {
width: 4rem;
height: 4rem;
font-size: 4rem;
margin-top: 1.5rem;
}
&__icon {
margin-right: 16px;
&--update {
color: #1976d2;
}
&--security {
color: #d32f2f;
}
}
&__description {
&--main {
font-size: 1rem;
font-weight: 500;
color: rgba(0, 0, 0, 0.87);
margin-bottom: 1em;
}
}
&__content {
flex-grow: 1;
display: flex;
flex-direction: column;
gap: 4px;
margin-right: 16px;
}
&__chip {
margin: 0.5rem 0;
}
&__user {
font-size: 0.9rem;
color: rgba(0, 0, 0, 0.6);
}
&__timestamp {
color: rgba(0, 0, 0, 0.6);
white-space: nowrap;
text-align: right;
}
}

View File

@@ -0,0 +1,66 @@
// 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 { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { HistoryRecord } from './history.service';
@Component({
selector: 'app-history-list',
templateUrl: './historyList.component.html',
styleUrls: ['./historyList.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
export class HistoryListComponent {
@Input() historyRecords: HistoryRecord[] = [];
@Input() isLoading: boolean = false;
getIconForType(type: string): string {
switch (type) {
case 'REGISTRAR_UPDATE':
return 'edit';
case 'REGISTRAR_SECURITY_UPDATE':
return 'security';
default:
return 'history'; // A fallback icon
}
}
getIconClass(type: string): string {
switch (type) {
case 'REGISTRAR_UPDATE':
return 'history-log__icon--update';
case 'REGISTRAR_SECURITY_UPDATE':
return 'history-log__icon--security';
default:
return '';
}
}
parseDescription(description: string): {
main: string;
detail: string | null;
} {
if (!description) {
return { main: 'N/A', detail: null };
}
const parts = description.split('|');
const detail = parts.length > 1 ? parts[1].replace(/_/g, ' ') : parts[0];
return {
main: parts[0],
detail: detail,
};
}
}

View File

@@ -1,14 +1,55 @@
<h3>Recent Activity</h3>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8 console-home__activity">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef>
{{column.header}}
</th>
<td mat-cell *matCellDef="let row">
{{column.cell(row)}}
</td>
</ng-container>
<div
class="console-app__home"
[class.console-app__home_tablet]="breakPointObserverService.isTabletView()"
>
<h1 class="mat-headline-4">Dashboard</h1>
<div class="console-app__home-widgets">
<mat-card appearance="outlined">
<mat-card-content>
<h3>
<mat-icon class="secondary-text">view_list</mat-icon>
DUMs
</h3>
<p class="secondary-text">View Domains Under Management</p>
</mat-card-content>
<mat-card-actions>
<button mat-button color="primary" (click)="viewDums()">
View DUMs
</button>
</mat-card-actions>
</mat-card>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-card appearance="outlined">
<mat-card-content>
<h3>
<mat-icon class="secondary-text">settings</mat-icon>
EPP Passwords
</h3>
<p class="secondary-text">View / Update EPP Password</p>
</mat-card-content>
<mat-card-actions>
<button mat-button color="primary" (click)="updateEppPassword()">
Update EPP Password
</button>
</mat-card-actions>
</mat-card>
<mat-card
appearance="outlined"
[elementId]="getElementIdForRegistrarsBlock()"
>
<mat-card-content>
<h3>
<mat-icon class="secondary-text">account_circle</mat-icon>
Registrars
</h3>
<p class="secondary-text">View all registrars</p>
</mat-card-content>
<mat-card-actions>
<button mat-button color="primary" (click)="viewRegistrars()">
Manage Registrars
</button>
</mat-card-actions>
</mat-card>
</div>
</div>

View File

@@ -0,0 +1,35 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
.console-app {
&__home-widgets {
display: flex;
gap: 1rem;
h3 {
padding: 0;
margin: 0;
display: flex;
align-items: center;
gap: 10px;
}
mat-card {
flex: 1;
}
}
&__home_tablet {
.console-app__home-widgets {
flex-direction: column;
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,7 +15,8 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HomeComponent } from './home.component';
import {MaterialModule} from '../material.module';
import { MaterialModule } from '../material.module';
import { AppModule } from '../app.module';
describe('HomeComponent', () => {
let component: HomeComponent;
@@ -23,10 +24,9 @@ describe('HomeComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MaterialModule],
declarations: [ HomeComponent ]
})
.compileComponents();
imports: [MaterialModule, AppModule],
declarations: [HomeComponent],
}).compileComponents();
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;

View File

@@ -1,4 +1,4 @@
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -13,82 +13,42 @@
// limitations under the License.
import { Component } from '@angular/core';
export interface ActivityRecord {
eventType: string;
userName: string;
registrarName: string;
timestamp: string;
details: string
}
const MOCK_DATA: ActivityRecord[] = [
{eventType: "Export DUMS", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Update Contact", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Delete Domain", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Export DUMS", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Update Contact", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Delete Domain", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Export DUMS", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Update Contact", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Delete Domain", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Export DUMS", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Update Contact", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Delete Domain", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Export DUMS", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Update Contact", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Delete Domain", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Export DUMS", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Update Contact", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Delete Domain", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Export DUMS", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Update Contact", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Delete Domain", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Export DUMS", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Update Contact", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
{eventType: "Delete Domain", userName:"user3", registrarName: "registrar1", timestamp: "2022-03-15T19:46:39.007", details: "All Domains under management exported as .csv file" },
];
import { Router } from '@angular/router';
import { DomainListComponent } from '../domains/domainList.component';
import { RegistrarComponent } from '../registrar/registrarsTable.component';
import SecurityComponent from '../settings/security/security.component';
import { SettingsComponent } from '../settings/settings.component';
import { RESTRICTED_ELEMENTS } from '../shared/directives/userLevelVisiblity.directive';
import { BreakPointObserverService } from '../shared/services/breakPoint.service';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.less']
styleUrls: ['./home.component.scss'],
standalone: false,
})
export class HomeComponent {
columns = [
{
columnDef: 'eventType',
header: 'Event Type',
cell:(record: ActivityRecord) => `${record.eventType}`,
},
{
columnDef: 'userName',
header: 'User',
cell: (record: ActivityRecord) => `${record.userName}`,
},
{
columnDef: 'registrarName',
header: 'Registrar',
cell: (record: ActivityRecord) => `${record.registrarName}`,
},
{
columnDef: 'timestamp',
header: 'Timestamp',
cell: (record: ActivityRecord) => `${record.timestamp}`,
},
{
columnDef: 'details',
header: 'Details',
cell: (record: ActivityRecord) => `${record.details}`,
},
];
dataSource = MOCK_DATA;
displayedColumns = this.columns.map(c => c.columnDef);
constructor() {
constructor(
protected breakPointObserverService: BreakPointObserverService,
private router: Router
) {}
getElementIdForRegistrarsBlock() {
return RESTRICTED_ELEMENTS.REGISTRAR_ELEMENT;
}
viewRegistrars() {
this.router.navigate([RegistrarComponent.PATH], {
queryParamsHandling: 'merge',
});
}
updateEppPassword() {
this.router.navigate(
[SettingsComponent.PATH + '/' + SecurityComponent.PATH],
{ queryParamsHandling: 'merge' }
);
}
viewDums() {
this.router.navigate([DomainListComponent.PATH], {
queryParamsHandling: 'merge',
});
}
}

View File

@@ -0,0 +1,28 @@
@if (isLoading) {
<div class="console-app__registry-lock-verify-spinner">
<mat-spinner />
</div>
} @else if (domainName) {
<h1 class="mat-headline-4">Success!</h1>
<div class="console-app__registry-lock-content">
<div class="console-app__registry-lock-subhead">
The domain {{ domainName }} has been successfully {{ action }}ed.
</div>
</div>
<div>
<a
class="text-l"
routerLink="{{ DOMAIN_LIST_COMPONENT_PATH }}"
[queryParams]="{ registrarId: this.registrarService.registrarId() }"
>Return to the list of domains</a
>
</div>
} @else {
<h1 class="mat-headline-4">Failure</h1>
<div class="console-app__registry-lock-content">
<div class="console-app__registry-lock-subhead">
An error occurred: {{ errorMessage }}.<br /><br />Please double-check the
verification code and try again.
</div>
</div>
}

View File

@@ -0,0 +1,9 @@
.console-app__registry-lock {
&-content {
margin-top: 30px;
}
&-subhead {
font-size: 1.25rem;
margin-bottom: 20px;
}
}

View File

@@ -0,0 +1,66 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component } from '@angular/core';
import { RegistrarService } from '../registrar/registrar.service';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { RegistryLockVerifyService } from './registryLockVerify.service';
import { HttpErrorResponse } from '@angular/common/http';
import { take } from 'rxjs';
import { DomainListComponent } from '../domains/domainList.component';
@Component({
selector: 'app-registry-lock-verify',
templateUrl: './registryLockVerify.component.html',
styleUrls: ['./registryLockVerify.component.scss'],
providers: [RegistryLockVerifyService],
standalone: false,
})
export class RegistryLockVerifyComponent {
public static PATH = 'registry-lock-verify';
readonly DOMAIN_LIST_COMPONENT_PATH = `/${DomainListComponent.PATH}`;
isLoading = true;
domainName?: string;
action?: string;
errorMessage?: string;
constructor(
protected registrarService: RegistrarService,
protected registryLockVerifyService: RegistryLockVerifyService,
private route: ActivatedRoute
) {}
ngOnInit() {
this.route.queryParamMap.pipe(take(1)).subscribe((params: ParamMap) => {
this.registryLockVerifyService
.verifyRequest(params.get('lockVerificationCode') || '')
.subscribe({
error: (err: HttpErrorResponse) => {
this.isLoading = false;
this.errorMessage = err.error;
},
next: (verificationResponse) => {
this.domainName = verificationResponse.domainName;
this.action = verificationResponse.action;
this.registrarService.registrarId.set(
verificationResponse.registrarId
);
this.isLoading = false;
},
});
});
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,22 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.persistence.converter;
import { Injectable } from '@angular/core';
import { BackendService } from '../shared/services/backend.service';
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
export interface RegistryLockVerificationResponse {
action: string;
domainName: string;
registrarId: string;
}
/** JPA {@link AttributeConverter} for storing/retrieving {@code Set<String>}. */
@Converter(autoApply = true)
public class StringSetConverter extends StringSetConverterBase<String> {
@Injectable()
export class RegistryLockVerifyService {
constructor(private backendService: BackendService) {}
@Override
String toString(String element) {
return element;
}
@Override
String fromString(String value) {
return value;
verifyRequest(lockVerificationCode: string) {
return this.backendService.verifyRegistryLockRequest(lockVerificationCode);
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,15 +12,81 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {NgModule} from '@angular/core';
import {MatCardModule} from '@angular/material/card';
import {MatTableModule} from '@angular/material/table';
import { A11yModule } from '@angular/cdk/a11y';
import { DialogModule } from '@angular/cdk/dialog';
import { CdkMenuModule } from '@angular/cdk/menu';
import { OverlayModule } from '@angular/cdk/overlay';
import { CdkTableModule } from '@angular/cdk/table';
import { CdkTreeModule } from '@angular/cdk/tree';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatBadgeModule } from '@angular/material/badge';
import { MatBottomSheetModule } from '@angular/material/bottom-sheet';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatChipsModule } from '@angular/material/chips';
import { MatNativeDateModule, MatRippleModule } from '@angular/material/core';
import { MatDialogModule } from '@angular/material/dialog';
import { MatDividerModule } from '@angular/material/divider';
import { MatGridListModule } from '@angular/material/grid-list';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatListModule } from '@angular/material/list';
import { MatMenuModule } from '@angular/material/menu';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatTreeModule } from '@angular/material/tree';
const MATERIAL_MODULES = [
MatCardModule,
MatTableModule,
];
@NgModule({imports: MATERIAL_MODULES, exports: MATERIAL_MODULES})
export class MaterialModule {
}
@NgModule({
exports: [
A11yModule,
CdkMenuModule,
CdkTableModule,
CdkTreeModule,
MatBadgeModule,
MatButtonModule,
MatButtonToggleModule,
MatBottomSheetModule,
MatCardModule,
MatCheckboxModule,
MatDialogModule,
MatDividerModule,
MatGridListModule,
MatIconModule,
MatInputModule,
MatListModule,
MatMenuModule,
MatNativeDateModule,
MatProgressBarModule,
MatProgressSpinnerModule,
MatRadioModule,
MatRippleModule,
MatSelectModule,
MatSidenavModule,
MatTableModule,
MatTabsModule,
MatToolbarModule,
MatTooltipModule,
MatTreeModule,
OverlayModule,
DialogModule,
MatSnackBarModule,
MatPaginatorModule,
MatChipsModule,
MatAutocompleteModule,
ReactiveFormsModule,
],
})
export class MaterialModule {}

View File

@@ -0,0 +1,49 @@
<mat-tree
[dataSource]="dataSource"
[treeControl]="treeControl"
class="console-app__nav-tree"
>
<mat-tree-node
*matTreeNodeDef="let node"
matTreeNodeToggle
tabindex="0"
(click)="onClick(node)"
(keyup.enter)="onClick(node)"
[class.active]="router.url.includes(node.path)"
[elementId]="getElementId(node)"
>
<mat-icon class="console-app__nav-icon" *ngIf="node.iconName">
{{ node.iconName }}
</mat-icon>
{{ node.title }}
</mat-tree-node>
<mat-nested-tree-node
*matTreeNodeDef="let node; when: hasChild"
(click)="onClick(node)"
tabindex="0"
(keyup.enter)="onClick(node)"
>
<div class="mat-tree-node" [class.active]="router.url.includes(node.path)">
<button
class="console-app__nav-icon_expand"
mat-icon-button
matTreeNodeToggle
[attr.aria-label]="'Toggle ' + node.title"
>
<mat-icon>
{{ treeControl.isExpanded(node) ? "expand_more" : "chevron_right" }}
</mat-icon>
</button>
<mat-icon class="console-app__nav-icon" *ngIf="node.iconName">
{{ node.iconName }}
</mat-icon>
{{ node.title }}
</div>
<div
[class.console-app__nav-tree_invisible]="!treeControl.isExpanded(node)"
role="group"
>
<ng-container matTreeNodeOutlet></ng-container>
</div>
</mat-nested-tree-node>
</mat-tree>

View File

@@ -0,0 +1,71 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
$expand-icon-size: 26px;
.console-app {
&__sidebar {
min-width: 300px;
border: 0;
}
&__nav-icon {
width: $expand-icon-size;
height: $expand-icon-size;
color: var(--secondary) !important;
margin-right: $expand-icon-size;
&_expand {
position: absolute;
left: 0;
margin: auto;
padding: 0px;
width: $expand-icon-size;
height: $expand-icon-size;
}
}
&__nav-tree {
.mat-tree-node {
cursor: pointer;
padding-left: $expand-icon-size;
position: relative;
&:hover {
background-color: var(--light-highlight);
border-radius: 0 15px 15px 0;
}
&.active {
border-radius: 0 15px 15px 0;
background-color: var(--lightest);
}
}
div[role="group"] > .mat-tree-node {
// expand icon + regular icon + spacing = 3 * $expand-icon-size
padding-left: calc($expand-icon-size * 3);
}
ul,
li {
margin-top: 0;
margin-bottom: 0;
list-style-type: none;
}
&_invisible {
display: none;
}
}
}

View File

@@ -0,0 +1,120 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { NestedTreeControl } from '@angular/cdk/tree';
import { Component } from '@angular/core';
import { MatTreeNestedDataSource } from '@angular/material/tree';
import { NavigationEnd, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { RouteWithIcon, routes, PATHS } from '../app-routing.module';
import { RESTRICTED_ELEMENTS } from '../shared/directives/userLevelVisiblity.directive';
import { RegistrarComponent } from '../registrar/registrarsTable.component';
interface NavMenuNode extends RouteWithIcon {
parentRoute?: RouteWithIcon;
}
/**
* This component is responsible for rendering navigation menu based on the allowed routes
* and keeping UI in sync when route changes (eg highlights selected route in the menu).
*/
@Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.scss'],
standalone: false,
})
export class NavigationComponent {
renderRouter: boolean = true;
treeControl = new NestedTreeControl<RouteWithIcon>((node) => node.children);
dataSource = new MatTreeNestedDataSource<RouteWithIcon>();
private subscription!: Subscription;
hasChild = (_: number, node: RouteWithIcon) =>
!!node.children && node.children.length > 0;
constructor(protected router: Router) {
this.dataSource.data = this.ngRoutesToNavMenuNodes(routes);
}
ngOnInit() {
this.subscription = this.router.events.subscribe((navigationParams) => {
if (navigationParams instanceof NavigationEnd) {
this.syncExpandedNavigationWithRoute(navigationParams.url);
}
});
}
ngOnDestroy() {
this.subscription && this.subscription.unsubscribe();
}
getElementId(node: RouteWithIcon) {
if (node.path === RegistrarComponent.PATH) {
return RESTRICTED_ELEMENTS.REGISTRAR_ELEMENT;
} else if (node.path === PATHS.UsersComponent) {
return RESTRICTED_ELEMENTS.USERS;
}
return null;
}
syncExpandedNavigationWithRoute(url: string) {
const maybeComponentWithChildren = this.dataSource.data.find((menuNode) => {
return (
// @ts-ignore - optional function added to components with children,
// there's no availble tools to get current active router component
typeof menuNode.component?.matchesUrl === 'function' &&
// @ts-ignore
menuNode.component?.matchesUrl(url)
);
});
if (maybeComponentWithChildren) {
this.treeControl.expand(maybeComponentWithChildren);
}
}
onClick(node: NavMenuNode) {
if (node.parentRoute) {
this.router.navigate([node.parentRoute.path + '/' + node.path], {
queryParamsHandling: 'merge',
});
} else {
this.router.navigate([node.path], { queryParamsHandling: 'merge' });
}
}
/**
* We only want to use routes with titles and we want to provide easy reference to parent node
*/
ngRoutesToNavMenuNodes(routes: RouteWithIcon[]): NavMenuNode[] {
return routes
.filter((r) => r.title)
.map((r) => {
if (r.children) {
return {
...r,
children: r.children
.filter((r) => r.title)
.map((childRoute) => {
return {
...childRoute,
parentRoute: r,
};
}),
};
}
return r;
});
}
}

View File

@@ -0,0 +1,43 @@
<h1 class="mat-headline-4">Generate OT&E Accounts</h1>
<div class="console-app__new-ote">
@if (oteCreateResponseFormatted()) {
<h1>Generated Successfully</h1>
<mat-card appearance="outlined">
<mat-card-header>
<mat-card-title>Epp Credentials</mat-card-title>
<mat-card-subtitle
>Copy and paste this into an email to the registrars</mat-card-subtitle
>
</mat-card-header>
<mat-card-content>
<p>{{ oteCreateResponseFormatted() }}</p>
</mat-card-content>
</mat-card>
} @else {
<form (ngSubmit)="onSubmit()" [formGroup]="createOte">
<p>
<mat-form-field name="registrarId" appearance="outline">
<mat-label>Base Registrar Id: </mat-label>
<input matInput type="text" formControlName="registrarId" required />
</mat-form-field>
</p>
<p>
<mat-form-field name="registrarEmail" appearance="outline">
<mat-label>Contact Email: </mat-label>
<input matInput type="text" formControlName="registrarEmail" required />
<mat-hint
>Will be granted web-console access to the OTE registrars.</mat-hint
>
</mat-form-field>
</p>
<button
mat-flat-button
color="primary"
type="submit"
aria-label="Submit new OT&E account"
>
Save
</button>
</form>
}
</div>

View File

@@ -0,0 +1,11 @@
.console-app__new-ote {
max-width: 720px;
mat-card-content {
white-space: break-spaces;
padding: 20px;
}
mat-form-field {
width: 100%;
max-width: 350px;
}
}

View File

@@ -0,0 +1,82 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { HttpErrorResponse } from '@angular/common/http';
import { Component, computed, signal } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { MatSnackBar } from '@angular/material/snack-bar';
import { RegistrarService } from '../registrar/registrar.service';
import { MaterialModule } from '../material.module';
import { SnackBarModule } from '../snackbar.module';
export interface OteCreateResponse extends Map<string, string> {
password: string;
}
@Component({
selector: 'app-ote',
imports: [MaterialModule, SnackBarModule],
templateUrl: './newOte.component.html',
styleUrls: ['./newOte.component.scss'],
})
export class NewOteComponent {
oteCreateResponse = signal<OteCreateResponse | undefined>(undefined);
readonly oteCreateResponseFormatted = computed(() => {
const oteCreateResponse = this.oteCreateResponse();
if (oteCreateResponse) {
const { password } = oteCreateResponse;
return Object.entries(oteCreateResponse)
.filter((entry) => entry[0] !== 'password')
.map(
([login, tld]) =>
`Login: ${login}\t\tPassword: ${password}\t\tTLD: ${tld}`
)
.join('\n');
}
return undefined;
});
createOte = new FormGroup({
registrarId: new FormControl('', [Validators.required]),
registrarEmail: new FormControl('', [Validators.required]),
});
constructor(
protected registrarService: RegistrarService,
private _snackBar: MatSnackBar
) {}
onSubmit() {
if (this.createOte.valid) {
const { registrarId, registrarEmail } = this.createOte.value;
this.registrarService
.generateOte(
{
registrarId,
registrarEmail,
},
registrarId || ''
)
.subscribe({
next: (oteCreateResponse: OteCreateResponse) => {
this.oteCreateResponse.set(oteCreateResponse);
},
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error || err.message);
},
});
}
}
}

View File

@@ -0,0 +1,28 @@
<h1 class="mat-headline-4">OT&E Status Check</h1>
@if(registrarId() === null) {
<h1>Missing registrarId param</h1>
} @else if(isOte()) {
<h1 *ngIf="oteStatusResponse().length">
Status:
<span>{{ oteStatusUnfinished().length ? "Unfinished" : "Completed" }}</span>
</h1>
<div class="console-app__ote-status">
@if(oteStatusCompleted().length) {
<div class="console-app__ote-status_completed">
<h1>Completed</h1>
<div *ngFor="let entry of oteStatusCompleted()">
<mat-icon>check_box</mat-icon>{{ entry.description }}
</div>
</div>
} @if(oteStatusUnfinished().length) {
<div class="console-app__ote-status_unfinished">
<h1>Unfinished</h1>
<div *ngFor="let entry of oteStatusUnfinished()">
<mat-icon>check_box_outline_blank</mat-icon>{{ entry.description }}
</div>
</div>
}
</div>
} @else {
<h1>Registrar {{ registrarId() }} is not an OT&E registrar</h1>
}

View File

@@ -0,0 +1,28 @@
.console-app__ote-status {
max-width: 730px;
display: flex;
flex-wrap: wrap;
&_completed,
&_unfinished {
border: 1px solid #ddd;
padding: 20px;
border-radius: 10px;
margin: 0 20px 30px 0;
div {
display: flex;
min-width: 300px;
align-items: flex-start;
max-width: 300px;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px solid #ddd;
&:last-child {
border: none;
}
}
mat-icon {
min-width: 30px;
}
}
}

View File

@@ -0,0 +1,78 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { HttpErrorResponse } from '@angular/common/http';
import { Component, computed, OnInit, signal } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { RegistrarService } from '../registrar/registrar.service';
import { MaterialModule } from '../material.module';
import { SnackBarModule } from '../snackbar.module';
import { CommonModule } from '@angular/common';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { take } from 'rxjs';
export interface OteStatusResponse {
description: string;
requirement: number;
timesPerformed: number;
completed: boolean;
}
@Component({
selector: 'app-ote-status',
imports: [MaterialModule, SnackBarModule, CommonModule],
templateUrl: './oteStatus.component.html',
styleUrls: ['./oteStatus.component.scss'],
})
export class OteStatusComponent implements OnInit {
registrarId = signal<string | null>(null);
oteStatusResponse = signal<OteStatusResponse[]>([]);
oteStatusCompleted = computed(() =>
this.oteStatusResponse().filter((v) => v.completed)
);
oteStatusUnfinished = computed(() =>
this.oteStatusResponse().filter((v) => !v.completed)
);
isOte = computed(
() =>
this.registrarService
.registrars()
.find((r) => r.registrarId === this.registrarId())
?.type?.toLowerCase() === 'ote'
);
constructor(
private route: ActivatedRoute,
protected registrarService: RegistrarService,
private _snackBar: MatSnackBar
) {}
ngOnInit(): void {
this.route.paramMap.pipe(take(1)).subscribe((params: ParamMap) => {
this.registrarId.set(params.get('registrarId'));
const registrarId = this.registrarId();
if (!registrarId) throw 'Missing registrarId param';
this.registrarService.oteStatus(registrarId).subscribe({
next: (oteStatusResponse: OteStatusResponse[]) => {
this.oteStatusResponse.set(oteStatusResponse);
},
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error || err.message);
},
});
});
}
}

View File

@@ -0,0 +1,183 @@
<div class="console-new-registrar">
<button
mat-icon-button
aria-label="Back to registrars list"
(click)="goBack()"
>
<mat-icon>arrow_back</mat-icon>
</button>
<div class="spacer"></div>
<h1>Create a registrar</h1>
<form (ngSubmit)="save($event)" #form>
<h2>General</h2>
<section>
<mat-form-field appearance="outline">
<mat-label>Registrar Name: </mat-label>
<input
matInput
[required]="true"
[(ngModel)]="newRegistrar.registrarName"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>Registrar ID: </mat-label>
<input
matInput
[required]="true"
[(ngModel)]="newRegistrar.registrarId"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>Registrar email address: </mat-label>
<input
matInput
type="email"
[required]="true"
[(ngModel)]="newRegistrar.emailAddress"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>Billing Accounts: </mat-label>
<textarea
matInput
required="true"
placeholder="USD=billing-id-for-usd
JPY=billing-id-for-yen"
[ngModel]="billingAccountMap"
(ngModelChange)="onBillingAccountMapChange($event)"
[ngModelOptions]="{ standalone: true }"
></textarea>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>IANA ID: </mat-label>
<input
matInput
[required]="true"
[(ngModel)]="newRegistrar.ianaIdentifier"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>ICANN referral email: </mat-label>
<input
matInput
[required]="true"
type="email"
[(ngModel)]="newRegistrar.icannReferralEmail"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>Drive ID: </mat-label>
<input
matInput
[required]="true"
[(ngModel)]="newRegistrar.driveFolderId"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<h2>Contact Info</h2>
<section>
<mat-form-field appearance="outline">
<mat-label>Street address (Line 1): </mat-label>
<input
matInput
[required]="true"
[(ngModel)]="localizedAddressStreet.line1"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>Street address (Line 2)</mat-label>
<input
matInput
[required]="false"
[(ngModel)]="localizedAddressStreet.line2"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>Street address (Line 3)</mat-label>
<input
matInput
[required]="false"
[(ngModel)]="localizedAddressStreet.line3"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>City: </mat-label>
<input
matInput
[required]="true"
[(ngModel)]="newRegistrar.localizedAddress.city"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>State/Region: </mat-label>
<input
matInput
[required]="true"
[(ngModel)]="newRegistrar.localizedAddress.state"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>ZIP/Postal Code: </mat-label>
<input
matInput
[required]="true"
[(ngModel)]="newRegistrar.localizedAddress.zip"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>Country Code (e.g. US): </mat-label>
<input
matInput
[required]="true"
[(ngModel)]="newRegistrar.localizedAddress.countryCode"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<button
class="console-new-registrar__submit"
mat-flat-button
color="primary"
type="submit"
aria-label="Submit new registrar request"
>
Save
</button>
</form>
</div>

View File

@@ -0,0 +1,20 @@
.console-new-registrar {
max-width: 616px;
h2 {
margin: 40px 0 25px 0 !important;
}
section {
margin-bottom: 20px;
}
mat-form-field {
display: block;
width: 100%;
}
&__submit {
margin: 30px 0;
}
}

View File

@@ -0,0 +1,99 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { HttpErrorResponse } from '@angular/common/http';
import {
Component,
ElementRef,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Registrar, RegistrarService } from './registrar.service';
interface LocalizedAddressStreet {
line1: string;
line2: string;
line3: string;
}
@Component({
selector: 'app-new-registrar',
templateUrl: './newRegistrar.component.html',
styleUrls: ['./newRegistrar.component.scss'],
encapsulation: ViewEncapsulation.None,
standalone: false,
})
export default class NewRegistrarComponent {
protected newRegistrar: Registrar;
protected localizedAddressStreet: LocalizedAddressStreet;
protected billingAccountMap: String = '';
@ViewChild('form') form!: ElementRef;
constructor(
private registrarService: RegistrarService,
private _snackBar: MatSnackBar
) {
this.newRegistrar = {
registrarId: '',
url: '',
registrarName: '',
icannReferralEmail: '',
localizedAddress: {
city: '',
state: '',
zip: '',
countryCode: '',
},
};
this.localizedAddressStreet = {
line1: '',
line2: '',
line3: '',
};
}
onBillingAccountMapChange(val: String) {
const billingAccountMap: { [key: string]: string } = {};
this.newRegistrar.billingAccountMap = val.split('\n').reduce((acc, val) => {
const [currency, billingCode] = val.split('=');
acc[currency] = billingCode;
return acc;
}, billingAccountMap);
}
save(e: SubmitEvent) {
e.preventDefault();
if (this.form.nativeElement.checkValidity()) {
const { line1, line2, line3 } = this.localizedAddressStreet;
this.newRegistrar.localizedAddress.street = [line1, line2, line3].filter(
(v) => !!v
);
this.registrarService.createRegistrar(this.newRegistrar).subscribe({
complete: () => {
this.goBack();
},
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error);
},
});
} else {
this.form.nativeElement.reportValidity();
}
}
goBack() {
this.registrarService.inNewRegistrarMode.set(false);
}
}

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