mirror of
https://github.com/google/nomulus
synced 2026-07-15 04:22:24 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 83e9e7fb5c | |||
| 438c523fcb | |||
| 025a2faff2 | |||
| fd822dd333 | |||
| 9b93749d43 | |||
| 71a8579ece | |||
| cda51f13dc | |||
| 1de5b5dcc1 | |||
| 32279e42e4 | |||
| ba0f90bdaf | |||
| 85308eb975 | |||
| ed62f27a4a | |||
| 75851399ba | |||
| 6d54c8d113 | |||
| 34dfa2760e | |||
| ff39a4a763 | |||
| b1cd8c5a6f | |||
| 28c7bc3085 | |||
| f36d22f4b1 | |||
| ef3ce79b8a |
@@ -270,6 +270,10 @@
|
||||
"moduleLicense": "Public Domain",
|
||||
"moduleName": "org.tukaani:xz"
|
||||
},
|
||||
{
|
||||
"moduleLicense": "Public Domain",
|
||||
"moduleName": "org.json:json"
|
||||
},
|
||||
{
|
||||
// "Apache License, Version 2.0".
|
||||
"moduleLicense": null,
|
||||
|
||||
+11
-8
@@ -138,9 +138,15 @@ public class ExpandRecurringBillingEventsPipeline implements Serializable {
|
||||
private final boolean isDryRun;
|
||||
private final boolean advanceCursor;
|
||||
private final Counter recurringsInScopeCounter =
|
||||
Metrics.counter("ExpandBilling", "RecurringsInScope");
|
||||
private final Counter expandedOneTimeCounter =
|
||||
Metrics.counter("ExpandBilling", "ExpandedOneTime");
|
||||
Metrics.counter("ExpandBilling", "Recurrings in scope for expansion");
|
||||
// Note that this counter is only accurate when running in dry run mode. Because SQL persistence
|
||||
// is a side effect and not idempotent, a transaction to save OneTimes could be successful but the
|
||||
// transform that contains it could be still be retried, rolling back the counter increment. The
|
||||
// same transform, when retried, would skip the already expanded OneTime, causing this counter to
|
||||
// be lower than it should be when not in dry run mode.
|
||||
// See: https://beam.apache.org/documentation/programming-guide/#user-code-idempotence
|
||||
private final Counter oneTimesToExpandCounter =
|
||||
Metrics.counter("ExpandBilling", "OneTimes that would be expanded");
|
||||
|
||||
ExpandRecurringBillingEventsPipeline(
|
||||
ExpandRecurringBillingEventsPipelineOptions options, Clock clock) {
|
||||
@@ -195,6 +201,7 @@ public class ExpandRecurringBillingEventsPipeline implements Serializable {
|
||||
endTime.minusYears(1)),
|
||||
true,
|
||||
(BigInteger id) -> {
|
||||
recurringsInScopeCounter.inc();
|
||||
// Note that because all elements are mapped to the same dummy key, the next
|
||||
// batching transform will effectively be serial. This however does not matter for
|
||||
// our use case because the elements were obtained from a SQL read query, which
|
||||
@@ -271,13 +278,9 @@ public class ExpandRecurringBillingEventsPipeline implements Serializable {
|
||||
} catch (IllegalArgumentException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
recurringsInScopeCounter.inc();
|
||||
Domain domain = tm().loadByKey(Domain.createVKey(recurring.getDomainRepoId()));
|
||||
Registry tld = Registry.get(domain.getTld());
|
||||
|
||||
|
||||
|
||||
// Find the times for which the OneTime billing event are already created, making this expansion
|
||||
// idempotent. There is no need to match to the domain repo ID as the cancellation matching
|
||||
// billing event itself can only be for a single domain.
|
||||
@@ -301,7 +304,7 @@ public class ExpandRecurringBillingEventsPipeline implements Serializable {
|
||||
// Create new OneTime and DomainHistory for EventTimes that needs to be expanded.
|
||||
for (DateTime eventTime : eventTimesToExpand) {
|
||||
recurrenceLastExpansionTime = latestOf(recurrenceLastExpansionTime, eventTime);
|
||||
expandedOneTimeCounter.inc();
|
||||
oneTimesToExpandCounter.inc();
|
||||
DateTime billingTime = eventTime.plus(tld.getAutoRenewGracePeriodLength());
|
||||
// Note that the DomainHistory is created as of transaction time, as opposed to event time.
|
||||
// This might be counterintuitive because other DomainHistories are created at the time
|
||||
|
||||
@@ -106,6 +106,12 @@ public final class RegistryConfig {
|
||||
return config.gcpProject.projectId;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("cloudSchedulerServiceAccountEmail")
|
||||
public static String provideCloudSchedulerServiceAccountEmail(RegistryConfigSettings config) {
|
||||
return config.gcpProject.cloudSchedulerServiceAccountEmail;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("projectIdNumber")
|
||||
public static long provideProjectIdNumber(RegistryConfigSettings config) {
|
||||
|
||||
@@ -54,6 +54,7 @@ public class RegistryConfigSettings {
|
||||
public String backendServiceUrl;
|
||||
public String toolsServiceUrl;
|
||||
public String pubapiServiceUrl;
|
||||
public String cloudSchedulerServiceAccountEmail;
|
||||
}
|
||||
|
||||
/** Configuration options for OAuth settings for authenticating users. */
|
||||
|
||||
@@ -22,6 +22,8 @@ gcpProject:
|
||||
backendServiceUrl: https://localhost
|
||||
toolsServiceUrl: https://localhost
|
||||
pubapiServiceUrl: https://localhost
|
||||
# Service account used by Cloud Scheduler to send authenticated requests.
|
||||
cloudSchedulerServiceAccountEmail: cloud-scheduler-email@email.com
|
||||
|
||||
gSuite:
|
||||
# Publicly accessible domain name of the running G Suite instance.
|
||||
@@ -427,7 +429,7 @@ misc:
|
||||
|
||||
beam:
|
||||
# The default region to run Apache Beam (Cloud Dataflow) jobs in.
|
||||
defaultJobRegion: us-east1
|
||||
defaultJobRegion: us-central1
|
||||
# The GCE machine type to use when a job is CPU-intensive (e. g. RDE). Be sure
|
||||
# to check the VM CPU quota for the job region. In a massively parallel
|
||||
# pipeline this quota can be easily reached and needs to be raised, otherwise
|
||||
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<taskentries>
|
||||
<task>
|
||||
<url>/_dr/task/rdeStaging</url>
|
||||
<name>rdeStaging</name>
|
||||
<description>
|
||||
This job generates a full RDE escrow deposit as a single gigantic XML document
|
||||
and streams it to cloud storage. When this job has finished successfully, it'll
|
||||
launch a separate task that uploads the deposit file to Iron Mountain via SFTP.
|
||||
</description>
|
||||
<schedule>7 0 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=rde-upload&endpoint=/_dr/task/rdeUpload&forEachRealTld]]></url>
|
||||
<name>rdeUpload</name>
|
||||
<description>
|
||||
This job is a no-op unless RdeUploadCursor falls behind for some reason.
|
||||
</description>
|
||||
<schedule>0 */4 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchDnl&runInEmpty]]></url>
|
||||
<name>tmchDnl</name>
|
||||
<description>
|
||||
This job downloads the latest DNL from MarksDB and inserts it into the database.
|
||||
(See: TmchDnlAction, ClaimsList)
|
||||
</description>
|
||||
<schedule>0 */12 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchSmdrl&runInEmpty]]></url>
|
||||
<name>tmchSmdrl</name>
|
||||
<description>
|
||||
This job downloads the latest SMDRL from MarksDB and inserts it into the database.
|
||||
(See: TmchSmdrlAction, SignedMarkRevocationList)
|
||||
</description>
|
||||
<schedule>15 */12 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchCrl&runInEmpty]]></url>
|
||||
<name>tmchCrl</name>
|
||||
<description>
|
||||
This job downloads the latest CRL from MarksDB and inserts it into the database.
|
||||
(See: TmchCrlAction)
|
||||
</description>
|
||||
<schedule>0 */12 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/syncGroupMembers&runInEmpty]]></url>
|
||||
<name>syncGroupMembers</name>
|
||||
<description>
|
||||
Syncs RegistrarContact changes in the past hour to Google Groups.
|
||||
</description>
|
||||
<schedule>0 */1 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
|
||||
<name>syncRegistrarsSheet</name>
|
||||
<description>
|
||||
Synchronize Registrar entities to Google Spreadsheets.
|
||||
</description>
|
||||
<schedule>0 */1 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
|
||||
<name>resaveAllEppResourcesPipeline</name>
|
||||
<description>
|
||||
This job resaves all our resources, projected in time to "now".
|
||||
</description>
|
||||
<!--Deviation from cron tasks schedule: 1st monday of month 09:00 is replaced
|
||||
with 1st of the month 09:00 -->
|
||||
<schedule>0 9 1 * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/expandRecurringBillingEvents?advanceCursor]]></url>
|
||||
<name>expandRecurringBillingEvents</name>
|
||||
<description>
|
||||
This job runs an action that creates synthetic OneTime billing events from Recurring billing
|
||||
events. Events are created for all instances of Recurring billing events that should exist
|
||||
between the RECURRING_BILLING cursor's time and the execution time of the action.
|
||||
</description>
|
||||
<schedule>0 3 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
|
||||
<name>deleteExpiredDomains</name>
|
||||
<description>
|
||||
This job runs an action that deletes domains that are past their
|
||||
autorenew end date.
|
||||
</description>
|
||||
<schedule>7 3 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/deleteProberData&runInEmpty]]></url>
|
||||
<name>deleteProberData</name>
|
||||
<description>
|
||||
This job clears out data from probers and runs once a week.
|
||||
</description>
|
||||
<schedule>0 14 * * 1</schedule>
|
||||
</task>
|
||||
|
||||
<!-- TODO: Add borgmon job to check that these files are created and updated successfully. -->
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportReservedTerms&forEachRealTld]]></url>
|
||||
<name>exportReservedTerms</name>
|
||||
<description>
|
||||
Reserved terms export to Google Drive job for creating once-daily exports.
|
||||
</description>
|
||||
<schedule>30 5 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportPremiumTerms&forEachRealTld]]></url>
|
||||
<name>exportPremiumTerms</name>
|
||||
<description>
|
||||
Premium terms export to Google Drive job for creating once-daily exports.
|
||||
</description>
|
||||
<schedule>0 5 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
|
||||
<name>readDnsQueue</name>
|
||||
<description>
|
||||
Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each
|
||||
group.
|
||||
</description>
|
||||
<schedule>*/1 * * * *</schedule>
|
||||
</task>
|
||||
</taskentries>
|
||||
@@ -1,150 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--TODO: ptkach - Remove this file after cloud scheduler is fully up and running -->
|
||||
<cronentries>
|
||||
|
||||
<!--
|
||||
/cron/fanout params:
|
||||
queue=<QUEUE_NAME>
|
||||
endpoint=<ENDPOINT_NAME> // URL Path of servlet, which may contain placeholders:
|
||||
runInEmpty // Run once, with no tld parameter
|
||||
forEachRealTld // Run for tlds with getTldType() == TldType.REAL
|
||||
forEachTestTld // Run for tlds with getTldType() == TldType.TEST
|
||||
exclude=TLD1[,TLD2] // exclude something otherwise included
|
||||
-->
|
||||
|
||||
<cron>
|
||||
<url>/_dr/task/rdeStaging</url>
|
||||
<description>
|
||||
This job generates a full RDE escrow deposit as a single gigantic XML document
|
||||
and streams it to cloud storage. When this job has finished successfully, it'll
|
||||
launch a separate task that uploads the deposit file to Iron Mountain via SFTP.
|
||||
</description>
|
||||
<schedule>every day 00:07</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=rde-upload&endpoint=/_dr/task/rdeUpload&forEachRealTld]]></url>
|
||||
<description>
|
||||
This job is a no-op unless RdeUploadCursor falls behind for some reason.
|
||||
</description>
|
||||
<schedule>every 4 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchDnl&runInEmpty]]></url>
|
||||
<description>
|
||||
This job downloads the latest DNL from MarksDB and inserts it into the database.
|
||||
(See: TmchDnlAction, ClaimsList)
|
||||
</description>
|
||||
<schedule>every 12 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchSmdrl&runInEmpty]]></url>
|
||||
<description>
|
||||
This job downloads the latest SMDRL from MarksDB and inserts it into the database.
|
||||
(See: TmchSmdrlAction, SignedMarkRevocationList)
|
||||
</description>
|
||||
<schedule>every 12 hours from 00:15 to 12:15</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchCrl&runInEmpty]]></url>
|
||||
<description>
|
||||
This job downloads the latest CRL from MarksDB and inserts it into the database.
|
||||
(See: TmchCrlAction)
|
||||
</description>
|
||||
<schedule>every 12 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/syncGroupMembers&runInEmpty]]></url>
|
||||
<description>
|
||||
Syncs RegistrarContact changes in the past hour to Google Groups.
|
||||
</description>
|
||||
<schedule>every 1 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
|
||||
<description>
|
||||
Synchronize Registrar entities to Google Spreadsheets.
|
||||
</description>
|
||||
<schedule>every 1 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
|
||||
<description>
|
||||
This job resaves all our resources, projected in time to "now".
|
||||
</description>
|
||||
<schedule>1st monday of month 09:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/expandRecurringBillingEvents?advanceCursor]]></url>
|
||||
<description>
|
||||
This job runs an action that creates synthetic OneTime billing events from Recurring billing
|
||||
events. Events are created for all instances of Recurring billing events that should exist
|
||||
between the RECURRING_BILLING cursor's time and the execution time of the action.
|
||||
</description>
|
||||
<schedule>every day 03:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
|
||||
<description>
|
||||
This job runs an action that deletes domains that are past their
|
||||
autorenew end date.
|
||||
</description>
|
||||
<schedule>every day 03:07</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/deleteProberData&runInEmpty]]></url>
|
||||
<description>
|
||||
This job clears out data from probers and runs once a week.
|
||||
</description>
|
||||
<schedule>every monday 14:00</schedule>
|
||||
<timezone>UTC</timezone>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<!-- TODO: Add borgmon job to check that these files are created and updated successfully. -->
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportReservedTerms&forEachRealTld]]></url>
|
||||
<description>
|
||||
Reserved terms export to Google Drive job for creating once-daily exports.
|
||||
</description>
|
||||
<schedule>every day 05:30</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportPremiumTerms&forEachRealTld]]></url>
|
||||
<description>
|
||||
Premium terms export to Google Drive job for creating once-daily exports.
|
||||
</description>
|
||||
<schedule>every day 05:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
|
||||
<description>
|
||||
Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each
|
||||
group.
|
||||
</description>
|
||||
<schedule>every 1 minutes synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
</cronentries>
|
||||
|
||||
@@ -94,6 +94,12 @@
|
||||
<url-pattern>/registry-lock-verify</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<!-- Registrar console endpoints -->
|
||||
<servlet-mapping>
|
||||
<servlet-name>frontend-servlet</servlet-name>
|
||||
<url-pattern>/console-api/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<!-- Security config -->
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<taskentries>
|
||||
|
||||
<!--
|
||||
/cron/fanout params:
|
||||
queue=<QUEUE_NAME>
|
||||
endpoint=<ENDPOINT_NAME> // URL Path of servlet, which may contain placeholders:
|
||||
runInEmpty // Run once, with no tld parameter
|
||||
forEachRealTld // Run for tlds with getTldType() == TldType.REAL
|
||||
forEachTestTld // Run for tlds with getTldType() == TldType.TEST
|
||||
exclude=TLD1[,TLD2] // exclude something otherwise included
|
||||
-->
|
||||
|
||||
<task>
|
||||
<url>/_dr/task/rdeStaging</url>
|
||||
<name>rdeStaging</name>
|
||||
<description>
|
||||
This job generates a full RDE escrow deposit as a single gigantic XML document
|
||||
and streams it to cloud storage. When this job has finished successfully, it'll
|
||||
launch a separate task that uploads the deposit file to Iron Mountain via SFTP.
|
||||
</description>
|
||||
<!--
|
||||
This only needs to run once per day, but we launch additional jobs in case the
|
||||
cursor is lagging behind, so it'll catch up to the current date as quickly as
|
||||
possible. The only job that'll run under normal circumstances is the one that's
|
||||
close to midnight, since if the cursor is up-to-date, the task is a no-op.
|
||||
We want it to be close to midnight because that reduces the chance that the
|
||||
point-in-time code won't have to go to the extra trouble of fetching old
|
||||
versions of objects from the database. However, we don't want it to run too
|
||||
close to midnight, because there's always a chance that a change which was
|
||||
timestamped before midnight hasn't fully been committed to the database. So
|
||||
we add a 4+ minute grace period to ensure the transactions cool down, since
|
||||
our queries are not transactional.
|
||||
-->
|
||||
<schedule>7 */4 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=rde-upload&endpoint=/_dr/task/rdeUpload&forEachRealTld]]></url>
|
||||
<name>rdeUpload</name>
|
||||
<description>
|
||||
This job is a no-op unless RdeUploadCursor falls behind for some reason.
|
||||
</description>
|
||||
<schedule>0 */4 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=rde-report&endpoint=/_dr/task/rdeReport&forEachRealTld]]></url>
|
||||
<name>rdeReport</name>
|
||||
<description>
|
||||
This job is a no-op unless RdeReportCursor falls behind for some reason.
|
||||
</description>
|
||||
<schedule>0 */4 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchDnl&runInEmpty]]></url>
|
||||
<name>tmchDnl</name>
|
||||
<description>
|
||||
This job downloads the latest DNL from MarksDB and inserts it into the database.
|
||||
(See: TmchDnlAction, ClaimsList)
|
||||
</description>
|
||||
<schedule>0 */12 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchSmdrl&runInEmpty]]></url>
|
||||
<name>tmchSmdrl</name>
|
||||
<description>
|
||||
This job downloads the latest SMDRL from MarksDB and inserts it into the database.
|
||||
(See: TmchSmdrlAction, SignedMarkRevocationList)
|
||||
</description>
|
||||
<schedule>15 */12 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchCrl&runInEmpty]]></url>
|
||||
<name>tmchCrl</name>
|
||||
<description>
|
||||
This job downloads the latest CRL from MarksDB and inserts it into the database.
|
||||
(See: TmchCrlAction)
|
||||
</description>
|
||||
<schedule>0 */12 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/syncGroupMembers&runInEmpty]]></url>
|
||||
<name>syncGroupMembers</name>
|
||||
<description>
|
||||
Syncs RegistrarContact changes in the past hour to Google Groups.
|
||||
</description>
|
||||
<schedule>0 */1 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
|
||||
<name>syncRegistrarsSheet</name>
|
||||
<description>
|
||||
Synchronize Registrar entities to Google Spreadsheets.
|
||||
</description>
|
||||
<schedule>0 */1 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/deleteProberData&runInEmpty]]></url>
|
||||
<name>deleteProberData</name>
|
||||
<description>
|
||||
This job clears out data from probers and runs once a week.
|
||||
</description>
|
||||
<schedule>0 14 * * 1</schedule>
|
||||
</task>
|
||||
|
||||
<!-- TODO: Add borgmon job to check that these files are created and updated successfully. -->
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportReservedTerms&forEachRealTld]]></url>
|
||||
<name>exportReservedTerms</name>
|
||||
<description>
|
||||
Reserved terms export to Google Drive job for creating once-daily exports.
|
||||
</description>
|
||||
<schedule>30 5 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportPremiumTerms&forEachRealTld]]></url>
|
||||
<name>exportPremiumTerms</name>
|
||||
<description>
|
||||
Exports premium price lists to the Google Drive folders for each TLD once per day.
|
||||
</description>
|
||||
<schedule>0 5 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
|
||||
<name>readDnsQueue</name>
|
||||
<description>
|
||||
Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each
|
||||
group.
|
||||
</description>
|
||||
<schedule>*/1 * * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
|
||||
<name>deleteExpiredDomains</name>
|
||||
<description>
|
||||
This job runs an action that deletes domains that are past their
|
||||
autorenew end date.
|
||||
</description>
|
||||
<schedule>7 3 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<!--
|
||||
The next two wipeout jobs are required when crash has production data.
|
||||
-->
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/wipeOutCloudSql]]></url>
|
||||
<name>wipeOutCloudSql</name>
|
||||
<description>
|
||||
This job runs an action that deletes all data in Cloud SQL.
|
||||
</description>
|
||||
<schedule>7 3 * * 6</schedule>
|
||||
</task>
|
||||
</taskentries>
|
||||
@@ -1,165 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--TODO: ptkach - Remove this file after cloud scheduler is fully up and running -->
|
||||
<cronentries>
|
||||
|
||||
<!--
|
||||
/cron/fanout params:
|
||||
queue=<QUEUE_NAME>
|
||||
endpoint=<ENDPOINT_NAME> // URL Path of servlet, which may contain placeholders:
|
||||
runInEmpty // Run once, with no tld parameter
|
||||
forEachRealTld // Run for tlds with getTldType() == TldType.REAL
|
||||
forEachTestTld // Run for tlds with getTldType() == TldType.TEST
|
||||
exclude=TLD1[,TLD2] // exclude something otherwise included
|
||||
-->
|
||||
|
||||
<cron>
|
||||
<url>/_dr/task/rdeStaging</url>
|
||||
<description>
|
||||
This job generates a full RDE escrow deposit as a single gigantic XML document
|
||||
and streams it to cloud storage. When this job has finished successfully, it'll
|
||||
launch a separate task that uploads the deposit file to Iron Mountain via SFTP.
|
||||
</description>
|
||||
<!--
|
||||
This only needs to run once per day, but we launch additional jobs in case the
|
||||
cursor is lagging behind, so it'll catch up to the current date as quickly as
|
||||
possible. The only job that'll run under normal circumstances is the one that's
|
||||
close to midnight, since if the cursor is up-to-date, the task is a no-op.
|
||||
|
||||
We want it to be close to midnight because that reduces the chance that the
|
||||
point-in-time code won't have to go to the extra trouble of fetching old
|
||||
versions of objects from the database. However, we don't want it to run too
|
||||
close to midnight, because there's always a chance that a change which was
|
||||
timestamped before midnight hasn't fully been committed to the database. So
|
||||
we add a 4+ minute grace period to ensure the transactions cool down, since
|
||||
our queries are not transactional.
|
||||
-->
|
||||
<schedule>every 4 hours from 00:07 to 20:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=rde-upload&endpoint=/_dr/task/rdeUpload&forEachRealTld]]></url>
|
||||
<description>
|
||||
This job is a no-op unless RdeUploadCursor falls behind for some reason.
|
||||
</description>
|
||||
<schedule>every 4 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=rde-report&endpoint=/_dr/task/rdeReport&forEachRealTld]]></url>
|
||||
<description>
|
||||
This job is a no-op unless RdeReportCursor falls behind for some reason.
|
||||
</description>
|
||||
<schedule>every 4 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchDnl&runInEmpty]]></url>
|
||||
<description>
|
||||
This job downloads the latest DNL from MarksDB and inserts it into the database.
|
||||
(See: TmchDnlAction, ClaimsList)
|
||||
</description>
|
||||
<schedule>every 12 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchSmdrl&runInEmpty]]></url>
|
||||
<description>
|
||||
This job downloads the latest SMDRL from MarksDB and inserts it into the database.
|
||||
(See: TmchSmdrlAction, SignedMarkRevocationList)
|
||||
</description>
|
||||
<schedule>every 12 hours from 00:15 to 12:15</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchCrl&runInEmpty]]></url>
|
||||
<description>
|
||||
This job downloads the latest CRL from MarksDB and inserts it into the database.
|
||||
(See: TmchCrlAction)
|
||||
</description>
|
||||
<schedule>every 12 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/syncGroupMembers&runInEmpty]]></url>
|
||||
<description>
|
||||
Syncs RegistrarContact changes in the past hour to Google Groups.
|
||||
</description>
|
||||
<schedule>every 1 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
|
||||
<description>
|
||||
Synchronize Registrar entities to Google Spreadsheets.
|
||||
</description>
|
||||
<schedule>every 1 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/deleteProberData&runInEmpty]]></url>
|
||||
<description>
|
||||
This job clears out data from probers and runs once a week.
|
||||
</description>
|
||||
<schedule>every monday 14:00</schedule>
|
||||
<timezone>UTC</timezone>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<!-- TODO: Add borgmon job to check that these files are created and updated successfully. -->
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportReservedTerms&forEachRealTld]]></url>
|
||||
<description>
|
||||
Reserved terms export to Google Drive job for creating once-daily exports.
|
||||
</description>
|
||||
<schedule>every day 05:30</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportPremiumTerms&forEachRealTld]]></url>
|
||||
<description>
|
||||
Exports premium price lists to the Google Drive folders for each TLD once per day.
|
||||
</description>
|
||||
<schedule>every day 05:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
|
||||
<description>
|
||||
Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each
|
||||
group.
|
||||
</description>
|
||||
<schedule>every 1 minutes synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
|
||||
<description>
|
||||
This job runs an action that deletes domains that are past their
|
||||
autorenew end date.
|
||||
</description>
|
||||
<schedule>every day 03:07</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<!--
|
||||
The next two wipeout jobs are required when crash has production data.
|
||||
-->
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/wipeOutCloudSql]]></url>
|
||||
<description>
|
||||
This job runs an action that deletes all data in Cloud SQL.
|
||||
</description>
|
||||
<schedule>every saturday 03:07</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
</cronentries>
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
<sessions-enabled>true</sessions-enabled>
|
||||
<instance-class>B4_1G</instance-class>
|
||||
<manual-scaling>
|
||||
<instances>12</instances>
|
||||
<instances>24</instances>
|
||||
</manual-scaling>
|
||||
|
||||
<system-properties>
|
||||
|
||||
Vendored
+288
@@ -0,0 +1,288 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<taskentries>
|
||||
<task>
|
||||
<url>/_dr/task/rdeStaging</url>
|
||||
<name>rdeStaging</name>
|
||||
<description>
|
||||
This job generates a full RDE escrow deposit as a single gigantic XML document
|
||||
and streams it to cloud storage. When this job has finished successfully, it'll
|
||||
launch a separate task that uploads the deposit file to Iron Mountain via SFTP.
|
||||
</description>
|
||||
<!--
|
||||
This only needs to run once per day, but we launch additional jobs in case the
|
||||
cursor is lagging behind, so it'll catch up to the current date as quickly as
|
||||
possible. The only job that'll run under normal circumstances is the one that's
|
||||
close to midnight, since if the cursor is up-to-date, the task is a no-op.
|
||||
We want it to be close to midnight because that reduces the chance that the
|
||||
point-in-time code won't have to go to the extra trouble of fetching old
|
||||
versions of objects from the database. However, we don't want it to run too
|
||||
close to midnight, because there's always a chance that a change which was
|
||||
timestamped before midnight hasn't fully been committed to the database. So
|
||||
we add a 4+ minute grace period to ensure the transactions cool down, since
|
||||
our queries are not transactional.
|
||||
-->
|
||||
<schedule>7 */8 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=rde-upload&endpoint=/_dr/task/rdeUpload&forEachRealTld]]></url>
|
||||
<name>rdeUpload</name>
|
||||
<description>
|
||||
This job is a no-op unless RdeUploadCursor falls behind for some reason.
|
||||
</description>
|
||||
<schedule>0 */4 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=rde-report&endpoint=/_dr/task/rdeReport&forEachRealTld]]></url>
|
||||
<name>rdeReport</name>
|
||||
<description>
|
||||
This job is a no-op unless RdeReportCursor falls behind for some reason.
|
||||
</description>
|
||||
<schedule>0 */4 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchDnl&runInEmpty]]></url>
|
||||
<name>tmchDnl</name>
|
||||
<description>
|
||||
This job downloads the latest DNL from MarksDB and inserts it into the database.
|
||||
(See: TmchDnlAction, ClaimsList)
|
||||
</description>
|
||||
<schedule>0 0,12 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchSmdrl&runInEmpty]]></url>
|
||||
<name>tmchSmdrl</name>
|
||||
<description>
|
||||
This job downloads the latest SMDRL from MarksDB and inserts it into the database.
|
||||
(See: TmchSmdrlAction, SignedMarkRevocationList)
|
||||
</description>
|
||||
<schedule>15 */12 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchCrl&runInEmpty]]></url>
|
||||
<name>tmchCrl</name>
|
||||
<description>
|
||||
This job downloads the latest CRL from MarksDB and inserts it into the database.
|
||||
(See: TmchCrlAction)
|
||||
</description>
|
||||
<schedule>0 */12 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/syncGroupMembers&runInEmpty]]></url>
|
||||
<name>syncGroupMembers</name>
|
||||
<description>
|
||||
Syncs RegistrarContact changes in the past hour to Google Groups.
|
||||
</description>
|
||||
<schedule>0 */1 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
|
||||
<name>syncRegistrarsSheet</name>
|
||||
<description>
|
||||
Synchronize Registrar entities to Google Spreadsheets.
|
||||
</description>
|
||||
<schedule>0 */1 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<!-- TODO: @ptkach add resaveAllEppResourcesPipelineAction https://cs.opensource.google/nomulus/nomulus/+/master:core/src/main/java/google/registry/env/production/default/WEB-INF/cron.xml;l=105 -->
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/updateRegistrarRdapBaseUrls]]></url>
|
||||
<name>updateRegistrarRdapBaseUrls</name>
|
||||
<description>
|
||||
This job reloads all registrar RDAP base URLs from ICANN.
|
||||
</description>
|
||||
<schedule>34 2 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportDomainLists&runInEmpty]]></url>
|
||||
<name>exportDomainLists</name>
|
||||
<description>
|
||||
This job exports lists of all active domain names to Google Drive and Google Cloud Storage.
|
||||
</description>
|
||||
<schedule>0 */12 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/expandRecurringBillingEvents?advanceCursor]]></url>
|
||||
<name>expandRecurringBillingEvents</name>
|
||||
<description>
|
||||
This job runs an action that creates synthetic OneTime billing events from Recurring billing
|
||||
events. Events are created for all instances of Recurring billing events that should exist
|
||||
between the RECURRING_BILLING cursor's time and the execution time of the action.
|
||||
</description>
|
||||
<schedule>0 3 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
|
||||
<name>deleteExpiredDomains</name>
|
||||
<description>
|
||||
This job runs an action that deletes domains that are past their
|
||||
autorenew end date.
|
||||
</description>
|
||||
<schedule>7 3 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/sendExpiringCertificateNotificationEmail]]></url>
|
||||
<name>sendExpiringCertificateNotificationEmail</name>
|
||||
<description>
|
||||
This job runs an action that sends emails to partners if their certificates are expiring soon.
|
||||
</description>
|
||||
<schedule>30 4 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=nordn&endpoint=/_dr/task/nordnUpload&forEachRealTld&lordnPhase=sunrise]]></url>
|
||||
<name>nordnUploadSunrise</name>
|
||||
<description>
|
||||
This job uploads LORDN Sunrise CSV files for each TLD to MarksDB. It should be
|
||||
run at most every three hours, or at absolute minimum every 26 hours.
|
||||
</description>
|
||||
<!-- This may be set anywhere between "every 3 hours" and "every 25 hours". -->
|
||||
<schedule>0 */12 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=nordn&endpoint=/_dr/task/nordnUpload&forEachRealTld&lordnPhase=claims]]></url>
|
||||
<name>nordnUploadClaims</name>
|
||||
<description>
|
||||
This job uploads LORDN Claims CSV files for each TLD to MarksDB. It should be
|
||||
run at most every three hours, or at absolute minimum every 26 hours.
|
||||
</description>
|
||||
<!-- This may be set anywhere between "every 3 hours" and "every 25 hours". -->
|
||||
<schedule>0 */12 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=nordn&endpoint=/_dr/task/nordnUpload&forEachRealTld&lordnPhase=sunrise&pullQueue]]></url>
|
||||
<name>nordnUploadSunrisePullQueue</name>
|
||||
<description>
|
||||
This job uploads LORDN Sunrise CSV files for each TLD to MarksDB using
|
||||
pull queue. It should be run at most every three hours, or at absolute
|
||||
minimum every 26 hours.
|
||||
</description>
|
||||
<!-- This may be set anywhere between "every 3 hours" and "every 25 hours". -->
|
||||
<schedule>0 */12 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=nordn&endpoint=/_dr/task/nordnUpload&forEachRealTld&lordnPhase=claims&pullQueue]]></url>
|
||||
<name>nordnUploadClaimsPullQueue</name>
|
||||
<description>
|
||||
This job uploads LORDN Claims CSV files for each TLD to MarksDB using pull
|
||||
queue. It should be run at most every three hours, or at absolute minimum
|
||||
every 26 hours.
|
||||
</description>
|
||||
<!-- This may be set anywhere between "every 3 hours" and "every 25 hours". -->
|
||||
<schedule>0 */12 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/deleteProberData&runInEmpty]]></url>
|
||||
<name>deleteProberData</name>
|
||||
<description>
|
||||
This job clears out data from probers and runs once a week.
|
||||
</description>
|
||||
<schedule>0 14 * * 1</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportReservedTerms&forEachRealTld]]></url>
|
||||
<name>exportReservedTerms</name>
|
||||
<description>
|
||||
Reserved terms export to Google Drive job for creating once-daily exports.
|
||||
</description>
|
||||
<schedule>30 5 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportPremiumTerms&forEachRealTld]]></url>
|
||||
<name>exportPremiumTerms</name>
|
||||
<description>
|
||||
Exports premium price lists to the Google Drive folders for each TLD once per day.
|
||||
</description>
|
||||
<schedule>0 5 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
|
||||
<name>readDnsQueue</name>
|
||||
<description>
|
||||
Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each
|
||||
group.
|
||||
</description>
|
||||
<schedule>*/1 * * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/icannReportingStaging&runInEmpty]]></url>
|
||||
<name>icannReportingStaging</name>
|
||||
<description>
|
||||
Create ICANN activity and transaction reports for last month, storing them in
|
||||
gs://[PROJECT-ID]-reporting/icann/monthly/yyyy-MM
|
||||
Upon success, enqueues the icannReportingUpload task to POST these files to ICANN.
|
||||
</description>
|
||||
<schedule>0 9 2 * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/icannReportingUpload&runInEmpty]]></url>
|
||||
<name>icannReportingUpload</name>
|
||||
<description>
|
||||
Checks if the monthly ICANN reports have been successfully uploaded. If they have not, attempts to upload them again.
|
||||
Most of the time, this job should not do anything since the uploads are triggered when the reports are staged.
|
||||
However, in the event that an upload failed for any reason (e.g. ICANN server is down, IP allow list issues),
|
||||
this cron job will continue to retry uploads daily until they succeed.
|
||||
</description>
|
||||
<schedule>0 15 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/generateInvoices?shouldPublish=true&runInEmpty]]></url>
|
||||
<name>generateInvoices</name>
|
||||
<description>
|
||||
Starts the beam/billing/InvoicingPipeline Dataflow template, which creates the overall invoice and
|
||||
detail report CSVs for last month, storing them in gs://[PROJECT-ID]-billing/invoices/yyyy-MM.
|
||||
Upon success, sends an e-mail copy of the invoice to billing personnel, and copies detail
|
||||
reports to the associated registrars' drive folders.
|
||||
See GenerateInvoicesAction for more details.
|
||||
</description>
|
||||
<!--WARNING: This must occur AFTER expandRecurringBillingEvents and AFTER exportSnapshot, as
|
||||
it uses Bigquery as the source of truth for billable events. ExportSnapshot usually takes
|
||||
about 2 hours to complete, so we give 11 hours to be safe. Normally, we give 24+ hours (see
|
||||
icannReportingStaging), but the invoicing team prefers receiving the e-mail on the first of
|
||||
each month. -->
|
||||
<schedule>0 19 1 * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/generateSpec11&runInEmpty]]></url>
|
||||
<name>generateSpec11</name>
|
||||
<description>
|
||||
Starts the beam/spec11/Spec11Pipeline Dataflow template, which creates today's Spec11
|
||||
report. This report is stored in gs://[PROJECT-ID]-reporting/icann/spec11/yyyy-MM/.
|
||||
This job will only send email notifications on the second of every month.
|
||||
See GenerateSpec11ReportAction for more details.
|
||||
</description>
|
||||
<schedule>0 15 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/wipeOutContactHistoryPii]]></url>
|
||||
<name>wipeOutContactHistoryPii</name>
|
||||
<description>
|
||||
This job runs weekly to wipe out PII fields of ContactHistory entities
|
||||
that have been in the database for a certain period of time.
|
||||
</description>
|
||||
<schedule>0 15 * * 1</schedule>
|
||||
</task>
|
||||
</taskentries>
|
||||
+1
-285
@@ -1,288 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--TODO: ptkach - Remove this file after cloud scheduler is fully up and running -->
|
||||
<cronentries>
|
||||
|
||||
<!--
|
||||
/cron/fanout params:
|
||||
queue=<QUEUE_NAME>
|
||||
endpoint=<ENDPOINT_NAME> // URL Path of servlet, which may contain placeholders:
|
||||
runInEmpty // Run once, with no tld parameter
|
||||
forEachRealTld // Run for tlds with getTldType() == TldType.REAL
|
||||
forEachTestTld // Run for tlds with getTldType() == TldType.TEST
|
||||
exclude=TLD1[,TLD2] // exclude something otherwise included
|
||||
-->
|
||||
|
||||
<cron>
|
||||
<url>/_dr/task/rdeStaging</url>
|
||||
<description>
|
||||
This job generates a full RDE escrow deposit as a single gigantic XML document
|
||||
and streams it to cloud storage. When this job has finished successfully, it'll
|
||||
launch a separate task that uploads the deposit file to Iron Mountain via SFTP.
|
||||
</description>
|
||||
<!--
|
||||
This only needs to run once per day, but we launch additional jobs in case the
|
||||
cursor is lagging behind, so it'll catch up to the current date as quickly as
|
||||
possible. The only job that'll run under normal circumstances is the one that's
|
||||
close to midnight, since if the cursor is up-to-date, the task is a no-op.
|
||||
|
||||
We want it to be close to midnight because that reduces the chance that the
|
||||
point-in-time code won't have to go to the extra trouble of fetching old
|
||||
versions of objects from the database. However, we don't want it to run too
|
||||
close to midnight, because there's always a chance that a change which was
|
||||
timestamped before midnight hasn't fully been committed to the database. So
|
||||
we add a 4+ minute grace period to ensure the transactions cool down, since
|
||||
our queries are not transactional.
|
||||
-->
|
||||
<schedule>every 8 hours from 00:07 to 20:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=rde-upload&endpoint=/_dr/task/rdeUpload&forEachRealTld]]></url>
|
||||
<description>
|
||||
This job is a no-op unless RdeUploadCursor falls behind for some reason.
|
||||
</description>
|
||||
<schedule>every 4 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=rde-report&endpoint=/_dr/task/rdeReport&forEachRealTld]]></url>
|
||||
<description>
|
||||
This job is a no-op unless RdeReportCursor falls behind for some reason.
|
||||
</description>
|
||||
<schedule>every 4 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchDnl&runInEmpty]]></url>
|
||||
<description>
|
||||
This job downloads the latest DNL from MarksDB and inserts it into the database.
|
||||
(See: TmchDnlAction, ClaimsList)
|
||||
</description>
|
||||
<schedule>every 12 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchSmdrl&runInEmpty]]></url>
|
||||
<description>
|
||||
This job downloads the latest SMDRL from MarksDB and inserts it into the database.
|
||||
(See: TmchSmdrlAction, SignedMarkRevocationList)
|
||||
</description>
|
||||
<schedule>every 12 hours from 00:15 to 12:15</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchCrl&runInEmpty]]></url>
|
||||
<description>
|
||||
This job downloads the latest CRL from MarksDB and inserts it into the database.
|
||||
(See: TmchCrlAction)
|
||||
</description>
|
||||
<schedule>every 12 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/syncGroupMembers&runInEmpty]]></url>
|
||||
<description>
|
||||
Syncs RegistrarContact changes in the past hour to Google Groups.
|
||||
</description>
|
||||
<schedule>every 1 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
|
||||
<description>
|
||||
Synchronize Registrar entities to Google Spreadsheets.
|
||||
</description>
|
||||
<schedule>every 1 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<!-- TODO(b/249863289): disable until it is safe to run this pipeline
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
|
||||
<description>
|
||||
This job resaves all our resources, projected in time to "now".
|
||||
</description>
|
||||
<schedule>1st monday of month 09:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
-->
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/updateRegistrarRdapBaseUrls]]></url>
|
||||
<description>
|
||||
This job reloads all registrar RDAP base URLs from ICANN.
|
||||
</description>
|
||||
<schedule>every day 02:34</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportDomainLists&runInEmpty]]></url>
|
||||
<description>
|
||||
This job exports lists of all active domain names to Google Drive and Google Cloud Storage.
|
||||
</description>
|
||||
<schedule>every 12 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/expandRecurringBillingEvents?advanceCursor]]></url>
|
||||
<description>
|
||||
This job runs an action that creates synthetic OneTime billing events from Recurring billing
|
||||
events. Events are created for all instances of Recurring billing events that should exist
|
||||
between the RECURRING_BILLING cursor's time and the execution time of the action.
|
||||
</description>
|
||||
<schedule>every day 03:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
|
||||
<description>
|
||||
This job runs an action that deletes domains that are past their
|
||||
autorenew end date.
|
||||
</description>
|
||||
<schedule>every day 03:07</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/sendExpiringCertificateNotificationEmail]]></url>
|
||||
<description>
|
||||
This job runs an action that sends emails to partners if their certificates are expiring soon.
|
||||
</description>
|
||||
<schedule>every day 04:30</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=nordn&endpoint=/_dr/task/nordnUpload&forEachRealTld&lordn-phase=sunrise]]></url>
|
||||
<description>
|
||||
This job uploads LORDN Sunrise CSV files for each TLD to MarksDB. It should be
|
||||
run at most every three hours, or at absolute minimum every 26 hours.
|
||||
</description>
|
||||
<!-- This may be set anywhere between "every 3 hours" and "every 25 hours". -->
|
||||
<schedule>every 12 hours synchronized</schedule>
|
||||
<timezone>UTC</timezone>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=nordn&endpoint=/_dr/task/nordnUpload&forEachRealTld&lordn-phase=claims]]></url>
|
||||
<description>
|
||||
This job uploads LORDN Claims CSV files for each TLD to MarksDB. It should be
|
||||
run at most every three hours, or at absolute minimum every 26 hours.
|
||||
</description>
|
||||
<!-- This may be set anywhere between "every 3 hours" and "every 25 hours". -->
|
||||
<schedule>every 12 hours synchronized</schedule>
|
||||
<timezone>UTC</timezone>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/deleteProberData&runInEmpty]]></url>
|
||||
<description>
|
||||
This job clears out data from probers and runs once a week.
|
||||
</description>
|
||||
<schedule>every monday 14:00</schedule>
|
||||
<timezone>UTC</timezone>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportReservedTerms&forEachRealTld]]></url>
|
||||
<description>
|
||||
Reserved terms export to Google Drive job for creating once-daily exports.
|
||||
</description>
|
||||
<schedule>every day 05:30</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportPremiumTerms&forEachRealTld]]></url>
|
||||
<description>
|
||||
Exports premium price lists to the Google Drive folders for each TLD once per day.
|
||||
</description>
|
||||
<schedule>every day 05:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
|
||||
<description>
|
||||
Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each
|
||||
group.
|
||||
</description>
|
||||
<schedule>every 1 minutes synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/icannReportingStaging&runInEmpty]]></url>
|
||||
<description>
|
||||
Create ICANN activity and transaction reports for last month, storing them in
|
||||
gs://[PROJECT-ID]-reporting/icann/monthly/yyyy-MM
|
||||
Upon success, enqueues the icannReportingUpload task to POST these files to ICANN.
|
||||
</description>
|
||||
<schedule>2 of month 09:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/icannReportingUpload&runInEmpty]]></url>
|
||||
<description>
|
||||
Checks if the monthly ICANN reports have been successfully uploaded. If they have not, attempts to upload them again.
|
||||
Most of the time, this job should not do anything since the uploads are triggered when the reports are staged.
|
||||
However, in the event that an upload failed for any reason (e.g. ICANN server is down, IP allow list issues),
|
||||
this cron job will continue to retry uploads daily until they succeed.
|
||||
</description>
|
||||
<schedule>every day 15:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/generateInvoices?shouldPublish=true&runInEmpty]]></url>
|
||||
<description>
|
||||
Starts the beam/billing/InvoicingPipeline Dataflow template, which creates the overall invoice and
|
||||
detail report CSVs for last month, storing them in gs://[PROJECT-ID]-billing/invoices/yyyy-MM.
|
||||
Upon success, sends an e-mail copy of the invoice to billing personnel, and copies detail
|
||||
reports to the associated registrars' drive folders.
|
||||
See GenerateInvoicesAction for more details.
|
||||
</description>
|
||||
<!--WARNING: This must occur AFTER expandRecurringBillingEvents and AFTER exportSnapshot, as
|
||||
it uses Bigquery as the source of truth for billable events. ExportSnapshot usually takes
|
||||
about 2 hours to complete, so we give 11 hours to be safe. Normally, we give 24+ hours (see
|
||||
icannReportingStaging), but the invoicing team prefers receiving the e-mail on the first of
|
||||
each month. -->
|
||||
<schedule>1 of month 19:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/generateSpec11&runInEmpty]]></url>
|
||||
<description>
|
||||
Starts the beam/spec11/Spec11Pipeline Dataflow template, which creates today's Spec11
|
||||
report. This report is stored in gs://[PROJECT-ID]-reporting/icann/spec11/yyyy-MM/.
|
||||
This job will only send email notifications on the second of every month.
|
||||
See GenerateSpec11ReportAction for more details.
|
||||
</description>
|
||||
<schedule>every day 15:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/wipeOutContactHistoryPii]]></url>
|
||||
<description>
|
||||
This job runs weekly to wipe out PII fields of ContactHistory entities
|
||||
that have been in the database for a certain period of time.
|
||||
</description>
|
||||
<schedule>every monday 15:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
</cronentries>
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<taskentries>
|
||||
<task>
|
||||
<url>/_dr/task/rdeStaging</url>
|
||||
<name>rdeStaging</name>
|
||||
<description>
|
||||
This job generates a full RDE escrow deposit as a single gigantic XML document
|
||||
and streams it to cloud storage. When this job has finished successfully, it'll
|
||||
launch a separate task that uploads the deposit file to Iron Mountain via SFTP.
|
||||
</description>
|
||||
<schedule>7 0 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
|
||||
<name>readDnsQueue</name>
|
||||
<description>
|
||||
Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each
|
||||
group.
|
||||
</description>
|
||||
<schedule>*/1 * * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
|
||||
<name>syncRegistrarsSheet</name>
|
||||
<description>
|
||||
Synchronize Registrar entities to Google Spreadsheets.
|
||||
</description>
|
||||
<schedule>0 */1 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
|
||||
<name>resaveAllEppResourcesPipeline</name>
|
||||
<description>
|
||||
This job resaves all our resources, projected in time to "now".
|
||||
</description>
|
||||
<!--Deviation from cron tasks schedule: 1st monday of month 09:00 is replaced
|
||||
with 1st of the month 09:00 -->
|
||||
<schedule>0 9 1 * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/syncGroupMembers&runInEmpty]]></url>
|
||||
<name>syncGroupMembers</name>
|
||||
<description>
|
||||
Syncs RegistrarContact changes in the past hour to Google Groups.
|
||||
</description>
|
||||
<schedule>0 */1 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
|
||||
<name>deleteExpiredDomains</name>
|
||||
<description>
|
||||
This job runs an action that deletes domains that are past their
|
||||
autorenew end date.
|
||||
</description>
|
||||
<schedule>7 3 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/wipeOutCloudSql]]></url>
|
||||
<name>wipeOutCloudSql</name>
|
||||
<description>
|
||||
This job runs an action that deletes all data in Cloud SQL.
|
||||
</description>
|
||||
<schedule>7 3 * * 6</schedule>
|
||||
</task>
|
||||
</taskentries>
|
||||
@@ -1,70 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--TODO: ptkach - Remove this file after cloud scheduler is fully up and running -->
|
||||
<cronentries>
|
||||
|
||||
<cron>
|
||||
<url>/_dr/task/rdeStaging</url>
|
||||
<description>
|
||||
This job generates a full RDE escrow deposit as a single gigantic XML document
|
||||
and streams it to cloud storage. When this job has finished successfully, it'll
|
||||
launch a separate task that uploads the deposit file to Iron Mountain via SFTP.
|
||||
</description>
|
||||
<schedule>every day 00:07</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
|
||||
<description>
|
||||
Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each
|
||||
group.
|
||||
</description>
|
||||
<schedule>every 1 minutes synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
|
||||
<description>
|
||||
Synchronize Registrar entities to Google Spreadsheets.
|
||||
</description>
|
||||
<schedule>every 1 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
|
||||
<description>
|
||||
This job resaves all our resources, projected in time to "now".
|
||||
</description>
|
||||
<schedule>1st monday of month 09:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/syncGroupMembers&runInEmpty]]></url>
|
||||
<description>
|
||||
Syncs RegistrarContact changes in the past hour to Google Groups.
|
||||
</description>
|
||||
<schedule>every 1 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
|
||||
<description>
|
||||
This job runs an action that deletes domains that are past their
|
||||
autorenew end date.
|
||||
</description>
|
||||
<schedule>every day 03:07</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/wipeOutCloudSql]]></url>
|
||||
<description>
|
||||
This job runs an action that deletes all data in Cloud SQL.
|
||||
</description>
|
||||
<schedule>every saturday 03:07</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
</cronentries>
|
||||
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<taskentries>
|
||||
<task>
|
||||
<url>/_dr/task/rdeStaging</url>
|
||||
<name>rdeStaging</name>
|
||||
<description>
|
||||
This job generates a full RDE escrow deposit as a single gigantic XML document
|
||||
and streams it to cloud storage. When this job has finished successfully, it'll
|
||||
launch a separate task that uploads the deposit file to Iron Mountain via SFTP.
|
||||
</description>
|
||||
<!--
|
||||
This only needs to run once per day, but we launch additional jobs in case the
|
||||
cursor is lagging behind, so it'll catch up to the current date eventually.
|
||||
|
||||
See <a href="../../../production/default/WEB-INF/cron.xml">production config</a> for an
|
||||
explanation of job starting times.
|
||||
-->
|
||||
<schedule>7 */12 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=rde-upload&endpoint=/_dr/task/rdeUpload&forEachRealTld]]></url>
|
||||
<name>rdeUpload</name>
|
||||
<description>
|
||||
This job is a no-op unless RdeUploadCursor falls behind for some reason.
|
||||
</description>
|
||||
<schedule>0 */4 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchDnl&runInEmpty]]></url>
|
||||
<name>tmchDnl</name>
|
||||
<description>
|
||||
This job downloads the latest DNL from MarksDB and inserts it into the database.
|
||||
(See: TmchDnlAction, ClaimsList)
|
||||
</description>
|
||||
<schedule>0 */12 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchSmdrl&runInEmpty]]></url>
|
||||
<name>tmchSmdrl</name>
|
||||
<description>
|
||||
This job downloads the latest SMDRL from MarksDB and inserts it into the database.
|
||||
(See: TmchSmdrlAction, SignedMarkRevocationList)
|
||||
</description>
|
||||
<schedule>15 */12 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchCrl&runInEmpty]]></url>
|
||||
<name>tmchCrl</name>
|
||||
<description>
|
||||
This job downloads the latest CRL from MarksDB and inserts it into the database.
|
||||
(See: TmchCrlAction)
|
||||
</description>
|
||||
<schedule>0 */12 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/syncGroupMembers&runInEmpty]]></url>
|
||||
<name>syncGroupMembers</name>
|
||||
<description>
|
||||
Syncs RegistrarContact changes in the past hour to Google Groups.
|
||||
</description>
|
||||
<schedule>0 */1 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
|
||||
<name>syncRegistrarsSheet</name>
|
||||
<description>
|
||||
Synchronize Registrar entities to Google Spreadsheets.
|
||||
</description>
|
||||
<schedule>0 */1 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportDomainLists&runInEmpty]]></url>
|
||||
<name>exportDomainLists</name>
|
||||
<description>
|
||||
This job exports lists of all active domain names to Google Drive and Google Cloud Storage.
|
||||
</description>
|
||||
<schedule>0 */12 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/expandRecurringBillingEvents?advanceCursor]]></url>
|
||||
<name>expandRecurringBillingEvents</name>
|
||||
<description>
|
||||
This job runs an action that creates synthetic OneTime billing events from Recurring billing
|
||||
events. Events are created for all instances of Recurring billing events that should exist
|
||||
between the RECURRING_BILLING cursor's time and the execution time of the action.
|
||||
</description>
|
||||
<schedule>0 3 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
|
||||
<name>deleteExpiredDomains</name>
|
||||
<description>
|
||||
This job runs an action that deletes domains that are past their
|
||||
autorenew end date.
|
||||
</description>
|
||||
<schedule>7 3 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/deleteProberData&runInEmpty]]></url>
|
||||
<name>deleteProberData</name>
|
||||
<description>
|
||||
This job clears out data from probers and runs once a week.
|
||||
</description>
|
||||
<schedule>0 14 * * 1</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportReservedTerms&forEachRealTld]]></url>
|
||||
<name>exportReservedTerms</name>
|
||||
<description>
|
||||
Reserved terms export to Google Drive job for creating once-daily exports.
|
||||
</description>
|
||||
<schedule>30 5 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportPremiumTerms&forEachRealTld]]></url>
|
||||
<name>exportPremiumTerms</name>
|
||||
<description>
|
||||
Exports premium price lists to the Google Drive folders for each TLD once per day.
|
||||
</description>
|
||||
<schedule>0 5 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<!-- TODO: @ptkach add resaveAllEppResourcesPipelineAction https://cs.opensource.google/nomulus/nomulus/+/master:core/src/main/java/google/registry/env/sandbox/default/WEB-INF/cron.xml;l=89 -->
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
|
||||
<name>readDnsQueue</name>
|
||||
<description>
|
||||
Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each
|
||||
group.
|
||||
</description>
|
||||
<schedule>*/1 * * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/wipeOutContactHistoryPii]]></url>
|
||||
<name>wipeOutContactHistoryPii</name>
|
||||
<description>
|
||||
This job runs weekly to wipe out PII fields of ContactHistory entities
|
||||
that have been in the database for a certain period of time.
|
||||
</description>
|
||||
<schedule>0 15 * * 1</schedule>
|
||||
</task>
|
||||
</taskentries>
|
||||
@@ -1,177 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--TODO: ptkach - Remove this file after cloud scheduler is fully up and running -->
|
||||
<cronentries>
|
||||
|
||||
<!--
|
||||
/cron/fanout params:
|
||||
queue=<QUEUE_NAME>
|
||||
endpoint=<ENDPOINT_NAME> // URL Path of servlet, which may contain placeholders:
|
||||
runInEmpty // Run once, with no tld parameter
|
||||
forEachRealTld // Run for tlds with getTldType() == TldType.REAL
|
||||
forEachTestTld // Run for tlds with getTldType() == TldType.TEST
|
||||
exclude=TLD1[,TLD2] // exclude something otherwise included
|
||||
-->
|
||||
|
||||
<cron>
|
||||
<url>/_dr/task/rdeStaging</url>
|
||||
<description>
|
||||
This job generates a full RDE escrow deposit as a single gigantic XML document
|
||||
and streams it to cloud storage. When this job has finished successfully, it'll
|
||||
launch a separate task that uploads the deposit file to Iron Mountain via SFTP.
|
||||
</description>
|
||||
<!--
|
||||
This only needs to run once per day, but we launch additional jobs in case the
|
||||
cursor is lagging behind, so it'll catch up to the current date eventually.
|
||||
|
||||
See <a href="../../../production/default/WEB-INF/cron.xml">production config</a> for an
|
||||
explanation of job starting times.
|
||||
-->
|
||||
<schedule>every 12 hours from 00:07 to 12:07</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=rde-upload&endpoint=/_dr/task/rdeUpload&forEachRealTld]]></url>
|
||||
<description>
|
||||
This job is a no-op unless RdeUploadCursor falls behind for some reason.
|
||||
</description>
|
||||
<schedule>every 4 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchDnl&runInEmpty]]></url>
|
||||
<description>
|
||||
This job downloads the latest DNL from MarksDB and inserts it into the database.
|
||||
(See: TmchDnlAction, ClaimsList)
|
||||
</description>
|
||||
<schedule>every 12 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchSmdrl&runInEmpty]]></url>
|
||||
<description>
|
||||
This job downloads the latest SMDRL from MarksDB and inserts it into the database.
|
||||
(See: TmchSmdrlAction, SignedMarkRevocationList)
|
||||
</description>
|
||||
<schedule>every 12 hours from 00:15 to 12:15</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=marksdb&endpoint=/_dr/task/tmchCrl&runInEmpty]]></url>
|
||||
<description>
|
||||
This job downloads the latest CRL from MarksDB and inserts it into the database.
|
||||
(See: TmchCrlAction)
|
||||
</description>
|
||||
<schedule>every 12 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/syncGroupMembers&runInEmpty]]></url>
|
||||
<description>
|
||||
Syncs RegistrarContact changes in the past hour to Google Groups.
|
||||
</description>
|
||||
<schedule>every 1 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=sheet&endpoint=/_dr/task/syncRegistrarsSheet&runInEmpty]]></url>
|
||||
<description>
|
||||
Synchronize Registrar entities to Google Spreadsheets.
|
||||
</description>
|
||||
<schedule>every 1 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<!-- TODO(b/249863289): disable until it is safe to run this pipeline
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
|
||||
<description>
|
||||
This job resaves all our resources, projected in time to "now".
|
||||
</description>
|
||||
<schedule>1st monday of month 09:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
-->
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportDomainLists&runInEmpty]]></url>
|
||||
<description>
|
||||
This job exports lists of all active domain names to Google Drive and Google Cloud Storage.
|
||||
</description>
|
||||
<schedule>every 12 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/expandRecurringBillingEvents?advanceCursor]]></url>
|
||||
<description>
|
||||
This job runs an action that creates synthetic OneTime billing events from Recurring billing
|
||||
events. Events are created for all instances of Recurring billing events that should exist
|
||||
between the RECURRING_BILLING cursor's time and the execution time of the action.
|
||||
</description>
|
||||
<schedule>every day 03:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
|
||||
<description>
|
||||
This job runs an action that deletes domains that are past their
|
||||
autorenew end date.
|
||||
</description>
|
||||
<schedule>every day 03:07</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/deleteProberData&runInEmpty]]></url>
|
||||
<description>
|
||||
This job clears out data from probers and runs once a week.
|
||||
</description>
|
||||
<schedule>every monday 14:00</schedule>
|
||||
<timezone>UTC</timezone>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportReservedTerms&forEachRealTld]]></url>
|
||||
<description>
|
||||
Reserved terms export to Google Drive job for creating once-daily exports.
|
||||
</description>
|
||||
<schedule>every day 05:30</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportPremiumTerms&forEachRealTld]]></url>
|
||||
<description>
|
||||
Exports premium price lists to the Google Drive folders for each TLD once per day.
|
||||
</description>
|
||||
<schedule>every day 05:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
|
||||
<description>
|
||||
Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each
|
||||
group.
|
||||
</description>
|
||||
<schedule>every 1 minutes synchronized</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/wipeOutContactHistoryPii]]></url>
|
||||
<description>
|
||||
This job runs weekly to wipe out PII fields of ContactHistory entities
|
||||
that have been in the database for a certain period of time.
|
||||
</description>
|
||||
<schedule>every monday 15:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
</cronentries>
|
||||
|
||||
@@ -86,6 +86,8 @@ import google.registry.model.billing.BillingEvent.Flag;
|
||||
import google.registry.model.billing.BillingEvent.Reason;
|
||||
import google.registry.model.billing.BillingEvent.Recurring;
|
||||
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
|
||||
import google.registry.model.common.DatabaseMigrationStateSchedule;
|
||||
import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainCommand;
|
||||
import google.registry.model.domain.DomainCommand.Create;
|
||||
@@ -123,6 +125,7 @@ import google.registry.model.tmch.ClaimsList;
|
||||
import google.registry.model.tmch.ClaimsListDao;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.tmch.LordnTaskUtils;
|
||||
import google.registry.tmch.LordnTaskUtils.LordnPhase;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -277,8 +280,12 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
registrarId,
|
||||
now,
|
||||
eppInput.getSingleExtension(AllocationTokenExtension.class));
|
||||
boolean defaultTokenUsed = false;
|
||||
if (!allocationToken.isPresent() && !registry.getDefaultPromoTokens().isEmpty()) {
|
||||
allocationToken = checkForDefaultToken(registry, command);
|
||||
if (allocationToken.isPresent()) {
|
||||
defaultTokenUsed = true;
|
||||
}
|
||||
}
|
||||
boolean isAnchorTenant =
|
||||
isAnchorTenant(
|
||||
@@ -337,7 +344,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
FeesAndCredits feesAndCredits =
|
||||
pricingLogic.getCreatePrice(
|
||||
registry, targetId, now, years, isAnchorTenant, allocationToken);
|
||||
validateFeeChallenge(feeCreate, feesAndCredits);
|
||||
validateFeeChallenge(feeCreate, feesAndCredits, defaultTokenUsed);
|
||||
Optional<SecDnsCreateExtension> secDnsCreate =
|
||||
validateSecDnsExtension(eppInput.getSingleExtension(SecDnsCreateExtension.class));
|
||||
DateTime registrationExpirationTime = leapSafeAddYears(now, years);
|
||||
@@ -377,7 +384,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
reservationTypes.contains(NAME_COLLISION)
|
||||
? ImmutableSet.of(SERVER_HOLD)
|
||||
: ImmutableSet.of();
|
||||
Domain domain =
|
||||
Domain.Builder domainBuilder =
|
||||
new Domain.Builder()
|
||||
.setCreationRegistrarId(registrarId)
|
||||
.setPersistedCurrentSponsorRegistrarId(registrarId)
|
||||
@@ -397,7 +404,14 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
.setContacts(command.getContacts())
|
||||
.addGracePeriod(
|
||||
GracePeriod.forBillingEvent(GracePeriodStatus.ADD, repoId, createBillingEvent))
|
||||
.build();
|
||||
.setLordnPhase(
|
||||
!DatabaseMigrationStateSchedule.getValueAtTime(tm().getTransactionTime())
|
||||
.equals(MigrationState.NORDN_SQL)
|
||||
? LordnPhase.NONE
|
||||
: hasSignedMarks
|
||||
? LordnPhase.SUNRISE
|
||||
: hasClaimsNotice ? LordnPhase.CLAIMS : LordnPhase.NONE);
|
||||
Domain domain = domainBuilder.build();
|
||||
if (allocationToken.isPresent()
|
||||
&& allocationToken.get().getTokenType().equals(TokenType.PACKAGE)) {
|
||||
if (years > 1) {
|
||||
@@ -697,7 +711,9 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
if (newDomain.shouldPublishToDns()) {
|
||||
dnsQueue.addDomainRefreshTask(newDomain.getDomainName());
|
||||
}
|
||||
if (hasClaimsNotice || hasSignedMarks) {
|
||||
if (!DatabaseMigrationStateSchedule.getValueAtTime(tm().getTransactionTime())
|
||||
.equals(MigrationState.NORDN_SQL)
|
||||
&& (hasClaimsNotice || hasSignedMarks)) {
|
||||
LordnTaskUtils.enqueueDomainTask(newDomain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -778,12 +778,13 @@ public class DomainFlowUtils {
|
||||
*/
|
||||
public static void validateFeeChallenge(
|
||||
final Optional<? extends FeeTransformCommandExtension> feeCommand,
|
||||
FeesAndCredits feesAndCredits)
|
||||
FeesAndCredits feesAndCredits,
|
||||
boolean defaultTokenUsed)
|
||||
throws EppException {
|
||||
if (feesAndCredits.hasAnyPremiumFees() && !feeCommand.isPresent()) {
|
||||
throw new FeesRequiredForPremiumNameException();
|
||||
}
|
||||
validateFeesAckedIfPresent(feeCommand, feesAndCredits);
|
||||
validateFeesAckedIfPresent(feeCommand, feesAndCredits, defaultTokenUsed);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -795,7 +796,8 @@ public class DomainFlowUtils {
|
||||
*/
|
||||
public static void validateFeesAckedIfPresent(
|
||||
final Optional<? extends FeeTransformCommandExtension> feeCommand,
|
||||
FeesAndCredits feesAndCredits)
|
||||
FeesAndCredits feesAndCredits,
|
||||
boolean defaultTokenUsed)
|
||||
throws EppException {
|
||||
// Check for the case where a fee command extension was required but not provided.
|
||||
// This only happens when the total fees are non-zero and include custom fees requiring the
|
||||
@@ -856,7 +858,9 @@ public class DomainFlowUtils {
|
||||
// Checking if total amount is expected. Extra fees that we are not expecting may be passed in.
|
||||
// Or if there is only a single fee type expected.
|
||||
if (!feeTotal.equals(feesAndCredits.getTotalCost())) {
|
||||
throw new FeesMismatchException(feesAndCredits.getTotalCost());
|
||||
if (!defaultTokenUsed || feeTotal.isLessThan(feesAndCredits.getTotalCost())) {
|
||||
throw new FeesMismatchException(feesAndCredits.getTotalCost());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
now,
|
||||
years,
|
||||
existingRecurringBillingEvent);
|
||||
validateFeeChallenge(feeRenew, feesAndCredits);
|
||||
validateFeeChallenge(feeRenew, feesAndCredits, false);
|
||||
flowCustomLogic.afterValidation(
|
||||
AfterValidationParameters.newBuilder()
|
||||
.setExistingDomain(existingDomain)
|
||||
|
||||
@@ -226,7 +226,7 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
|
||||
if (!existingDomain.getGracePeriodStatuses().contains(GracePeriodStatus.REDEMPTION)) {
|
||||
throw new DomainNotEligibleForRestoreException();
|
||||
}
|
||||
validateFeeChallenge(feeUpdate, feesAndCredits);
|
||||
validateFeeChallenge(feeUpdate, feesAndCredits, false);
|
||||
}
|
||||
|
||||
private static Domain performRestore(
|
||||
|
||||
@@ -210,7 +210,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
}
|
||||
|
||||
if (feesAndCredits.isPresent()) {
|
||||
validateFeeChallenge(feeTransfer, feesAndCredits.get());
|
||||
validateFeeChallenge(feeTransfer, feesAndCredits.get(), false);
|
||||
}
|
||||
HistoryEntryId domainHistoryId = createHistoryEntryId(existingDomain);
|
||||
historyBuilder
|
||||
|
||||
@@ -231,7 +231,7 @@ public final class DomainUpdateFlow implements TransactionalFlow {
|
||||
Optional<FeeUpdateCommandExtension> feeUpdate =
|
||||
eppInput.getSingleExtension(FeeUpdateCommandExtension.class);
|
||||
FeesAndCredits feesAndCredits = pricingLogic.getUpdatePrice(registry, targetId, now);
|
||||
validateFeesAckedIfPresent(feeUpdate, feesAndCredits);
|
||||
validateFeesAckedIfPresent(feeUpdate, feesAndCredits, false);
|
||||
verifyNotInPendingDelete(
|
||||
add.getContacts(),
|
||||
command.getInnerChange().getRegistrant(),
|
||||
|
||||
@@ -68,7 +68,6 @@ U+011F # LATIN SMALL LETTER G WITH BREVE
|
||||
U+01E7 # LATIN SMALL LETTER G WITH CARON
|
||||
U+0121 # LATIN SMALL LETTER G WITH DOT ABOVE
|
||||
U+0123 # LATIN SMALL LETTER G WITH CEDILLA
|
||||
U+01E5 # LATIN SMALL LETTER G WITH STROKE
|
||||
U+0068 # LATIN SMALL LETTER H
|
||||
U+0127 # LATIN SMALL LETTER H WITH STROKE
|
||||
U+0069 # LATIN SMALL LETTER I
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.model;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
@@ -28,6 +29,7 @@ import org.joda.time.DateTime;
|
||||
public class CreateAutoTimestamp extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
@Column(nullable = false)
|
||||
@Expose
|
||||
DateTime creationTime;
|
||||
|
||||
@PrePersist
|
||||
|
||||
@@ -31,8 +31,8 @@ import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.config.RegistryConfig;
|
||||
import google.registry.dns.RefreshDnsAction;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.transfer.TransferData;
|
||||
import google.registry.persistence.VKey;
|
||||
@@ -41,7 +41,6 @@ import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.AttributeOverride;
|
||||
@@ -67,7 +66,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc5730">RFC 5730</a>
|
||||
*/
|
||||
@Transient String repoId;
|
||||
@Expose @Transient String repoId;
|
||||
|
||||
/**
|
||||
* The ID of the registrar that is currently sponsoring this resource.
|
||||
@@ -75,7 +74,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
|
||||
* <p>This can be null in the case of pre-Registry-3.0-migration history objects with null
|
||||
* resource fields.
|
||||
*/
|
||||
String currentSponsorRegistrarId;
|
||||
@Expose String currentSponsorRegistrarId;
|
||||
|
||||
/**
|
||||
* The ID of the registrar that created this resource.
|
||||
@@ -83,7 +82,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
|
||||
* <p>This can be null in the case of pre-Registry-3.0-migration history objects with null
|
||||
* resource fields.
|
||||
*/
|
||||
String creationRegistrarId;
|
||||
@Expose String creationRegistrarId;
|
||||
|
||||
/**
|
||||
* The ID of the registrar that last updated this resource.
|
||||
@@ -92,7 +91,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
|
||||
* edits; it only includes EPP-visible modifications such as {@literal <update>}. Can be null if
|
||||
* the resource has never been modified.
|
||||
*/
|
||||
String lastEppUpdateRegistrarId;
|
||||
@Expose String lastEppUpdateRegistrarId;
|
||||
|
||||
/**
|
||||
* The time when this resource was created.
|
||||
@@ -106,6 +105,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
|
||||
*/
|
||||
// Need to override the default non-null column attribute.
|
||||
@AttributeOverride(name = "creationTime", column = @Column)
|
||||
@Expose
|
||||
CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
|
||||
|
||||
/**
|
||||
@@ -130,27 +130,10 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
|
||||
* edits; it only includes EPP-visible modifications such as {@literal <update>}. Can be null if
|
||||
* the resource has never been modified.
|
||||
*/
|
||||
DateTime lastEppUpdateTime;
|
||||
@Expose DateTime lastEppUpdateTime;
|
||||
|
||||
/** Status values associated with this resource. */
|
||||
Set<StatusValue> statuses;
|
||||
|
||||
/**
|
||||
* When this domain/host's DNS was requested to be refreshed, or null if its DNS is up-to-date.
|
||||
*
|
||||
* <p>This will almost always be null except in the couple of minutes' interval between when a
|
||||
* DNS-affecting create or update operation takes place and when the {@link RefreshDnsAction}
|
||||
* runs, which resets this back to null upon completion of the DNS refresh task. This is a {@link
|
||||
* DateTime} rather than a simple dirty boolean so that the DNS refresh action can order by the
|
||||
* DNS refresh request time and take action on the oldest ones first.
|
||||
*
|
||||
* <p>Note that in the {@code DomainHistory}/{@code HostHistory} table this value means something
|
||||
* slightly different: It means that the given domain/host action requested a DNS update. Unlike
|
||||
* on the {@code Domain}/{code Host} table, this value is not then subsequently nulled out once
|
||||
* the DNS refresh is complete; rather, it remains as a permanent record of which actions were
|
||||
* DNS-affecting and which were not.
|
||||
*/
|
||||
@Transient @Nullable protected DateTime dnsRefreshRequestTime;
|
||||
@Expose Set<StatusValue> statuses;
|
||||
|
||||
public String getRepoId() {
|
||||
return repoId;
|
||||
@@ -203,19 +186,6 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
|
||||
return deletionTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DNS refresh request time iff this domain/host's DNS needs refreshing, otherwise
|
||||
* absent.
|
||||
*/
|
||||
public Optional<DateTime> getDnsRefreshRequestTime() {
|
||||
return Optional.ofNullable(dnsRefreshRequestTime);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void setInternalDnsRefreshRequestTime(DateTime time) {
|
||||
dnsRefreshRequestTime = time;
|
||||
}
|
||||
|
||||
/** Return a clone of the resource with timed status values modified using the given time. */
|
||||
public abstract EppResource cloneProjectedAtTime(DateTime now);
|
||||
|
||||
@@ -369,11 +339,6 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setDnsRefreshRequestTime(Optional<DateTime> dnsRefreshRequestTime) {
|
||||
getInstance().dnsRefreshRequestTime = dnsRefreshRequestTime.orElse(null);
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
/** Build the resource, nullifying empty strings and sets and setting defaults. */
|
||||
@Override
|
||||
public T build() {
|
||||
|
||||
+19
-5
@@ -44,6 +44,8 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private static boolean useUncachedForTest = false;
|
||||
|
||||
public enum PrimaryDatabase {
|
||||
CLOUD_SQL,
|
||||
DATASTORE
|
||||
@@ -85,7 +87,10 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
|
||||
SQL_ONLY(PrimaryDatabase.CLOUD_SQL, false, ReplayDirection.NO_REPLAY),
|
||||
|
||||
/** Toggles SQL Sequence based allocateId */
|
||||
SEQUENCE_BASED_ALLOCATE_ID(PrimaryDatabase.CLOUD_SQL, false, ReplayDirection.NO_REPLAY);
|
||||
SEQUENCE_BASED_ALLOCATE_ID(PrimaryDatabase.CLOUD_SQL, false, ReplayDirection.NO_REPLAY),
|
||||
|
||||
/** Use SQL-based Nordn upload flow instead of the pull queue-based one. */
|
||||
NORDN_SQL(PrimaryDatabase.CLOUD_SQL, false, ReplayDirection.NO_REPLAY);
|
||||
|
||||
private final PrimaryDatabase primaryDatabase;
|
||||
private final boolean isReadOnly;
|
||||
@@ -164,7 +169,9 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
|
||||
MigrationState.SQL_ONLY,
|
||||
MigrationState.SQL_PRIMARY_READ_ONLY,
|
||||
MigrationState.SQL_PRIMARY)
|
||||
.putAll(MigrationState.SQL_ONLY, MigrationState.SEQUENCE_BASED_ALLOCATE_ID);
|
||||
.putAll(MigrationState.SQL_ONLY, MigrationState.SEQUENCE_BASED_ALLOCATE_ID)
|
||||
.putAll(MigrationState.SEQUENCE_BASED_ALLOCATE_ID, MigrationState.NORDN_SQL)
|
||||
.putAll(MigrationState.NORDN_SQL, MigrationState.SEQUENCE_BASED_ALLOCATE_ID);
|
||||
|
||||
// In addition, we can always transition from a state to itself (useful when updating the map).
|
||||
Arrays.stream(MigrationState.values()).forEach(state -> builder.put(state, state));
|
||||
@@ -181,8 +188,8 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
|
||||
public TimedTransitionProperty<MigrationState> migrationTransitions =
|
||||
TimedTransitionProperty.withInitialValue(MigrationState.DATASTORE_ONLY);
|
||||
|
||||
// Required for Objectify initialization
|
||||
private DatabaseMigrationStateSchedule() {}
|
||||
// Required for Hibernate initialization
|
||||
protected DatabaseMigrationStateSchedule() {}
|
||||
|
||||
@VisibleForTesting
|
||||
public DatabaseMigrationStateSchedule(
|
||||
@@ -205,6 +212,11 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
|
||||
CACHE.invalidateAll();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void useUncachedForTest() {
|
||||
useUncachedForTest = true;
|
||||
}
|
||||
|
||||
/** Loads the currently-set migration schedule from the cache, or the default if none exists. */
|
||||
public static TimedTransitionProperty<MigrationState> get() {
|
||||
return CACHE.get(DatabaseMigrationStateSchedule.class);
|
||||
@@ -212,7 +224,9 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
|
||||
|
||||
/** Returns the database migration status at the given time. */
|
||||
public static MigrationState getValueAtTime(DateTime dateTime) {
|
||||
return get().getValueAtTime(dateTime);
|
||||
return useUncachedForTest
|
||||
? getUncached().getValueAtTime(dateTime)
|
||||
: get().getValueAtTime(dateTime);
|
||||
}
|
||||
|
||||
/** Loads the currently-set migration schedule from SQL, or the default if none exists. */
|
||||
|
||||
@@ -24,6 +24,7 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.UpdateAutoTimestampEntity;
|
||||
import google.registry.persistence.VKey;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
@@ -47,7 +48,6 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
|
||||
private Long id;
|
||||
|
||||
/** GAIA ID associated with the user in question. */
|
||||
@Column(nullable = false)
|
||||
private String gaiaId;
|
||||
|
||||
/** Email address of the user in question. */
|
||||
@@ -118,6 +118,11 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
|
||||
return new Builder(clone(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public VKey<User> createVKey() {
|
||||
return VKey.create(User.class, getId());
|
||||
}
|
||||
|
||||
/** Builder for constructing immutable {@link User} objects. */
|
||||
public static class Builder extends Buildable.Builder<User> {
|
||||
|
||||
@@ -129,7 +134,6 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
|
||||
|
||||
@Override
|
||||
public User build() {
|
||||
checkArgumentNotNull(getInstance().gaiaId, "Gaia ID cannot be null");
|
||||
checkArgumentNotNull(getInstance().emailAddress, "Email address cannot be null");
|
||||
checkArgumentNotNull(getInstance().userRoles, "User roles cannot be null");
|
||||
return super.build();
|
||||
|
||||
@@ -58,7 +58,6 @@ import org.joda.time.DateTime;
|
||||
@Index(columnList = "techContact"),
|
||||
@Index(columnList = "tld"),
|
||||
@Index(columnList = "registrantContact"),
|
||||
@Index(columnList = "dnsRefreshRequestTime"),
|
||||
@Index(columnList = "lordnPhase"),
|
||||
@Index(columnList = "billing_recurrence_id"),
|
||||
@Index(columnList = "transfer_billing_event_id"),
|
||||
@@ -200,7 +199,6 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
|
||||
.setSubordinateHosts(domainBase.getSubordinateHosts())
|
||||
.setStatusValues(domainBase.getStatusValues())
|
||||
.setTransferData(domainBase.getTransferData())
|
||||
.setDnsRefreshRequestTime(domainBase.getDnsRefreshRequestTime())
|
||||
.setLordnPhase(domainBase.getLordnPhase())
|
||||
.setCurrentPackageToken(domainBase.getCurrentPackageToken().orElse(null));
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.flows.ResourceFlowUtils;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.EppResource.ResourceWithTransferData;
|
||||
@@ -121,20 +122,20 @@ public class DomainBase extends EppResource
|
||||
*
|
||||
* @invariant domainName == domainName.toLowerCase(Locale.ENGLISH)
|
||||
*/
|
||||
String domainName;
|
||||
@Expose String domainName;
|
||||
|
||||
/** The top level domain this is under, de-normalized from {@link #domainName}. */
|
||||
String tld;
|
||||
|
||||
/** References to hosts that are the nameservers for the domain. */
|
||||
@Transient Set<VKey<Host>> nsHosts;
|
||||
@Expose @Transient Set<VKey<Host>> nsHosts;
|
||||
|
||||
/** Contacts. */
|
||||
VKey<Contact> adminContact;
|
||||
@Expose VKey<Contact> adminContact;
|
||||
|
||||
VKey<Contact> billingContact;
|
||||
VKey<Contact> techContact;
|
||||
VKey<Contact> registrantContact;
|
||||
@Expose VKey<Contact> billingContact;
|
||||
@Expose VKey<Contact> techContact;
|
||||
@Expose VKey<Contact> registrantContact;
|
||||
|
||||
/** Authorization info (aka transfer secret) of the domain. */
|
||||
@Embedded
|
||||
@@ -175,10 +176,10 @@ public class DomainBase extends EppResource
|
||||
String idnTableName;
|
||||
|
||||
/** Fully qualified host names of this domain's active subordinate hosts. */
|
||||
Set<String> subordinateHosts;
|
||||
@Expose Set<String> subordinateHosts;
|
||||
|
||||
/** When this domain's registration will expire. */
|
||||
DateTime registrationExpirationTime;
|
||||
@Expose DateTime registrationExpirationTime;
|
||||
|
||||
/**
|
||||
* The poll message associated with this domain being deleted.
|
||||
@@ -230,7 +231,7 @@ public class DomainBase extends EppResource
|
||||
*
|
||||
* <p>Can be null if the resource has never been transferred.
|
||||
*/
|
||||
DateTime lastTransferTime;
|
||||
@Expose DateTime lastTransferTime;
|
||||
|
||||
/**
|
||||
* When the domain's autorenewal status will expire.
|
||||
@@ -273,13 +274,6 @@ public class DomainBase extends EppResource
|
||||
return lordnPhase;
|
||||
}
|
||||
|
||||
@Access(AccessType.PROPERTY)
|
||||
@SuppressWarnings("unused")
|
||||
@Column(name = "dnsRefreshRequestTime")
|
||||
private DateTime getInternalDnsRefreshRequestTime() {
|
||||
return getDnsRefreshRequestTime().orElse(null);
|
||||
}
|
||||
|
||||
public ImmutableSet<String> getSubordinateHosts() {
|
||||
return nullToEmptyImmutableCopy(subordinateHosts);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ import javax.persistence.Table;
|
||||
@Index(columnList = "creationTime"),
|
||||
@Index(columnList = "deletionTime"),
|
||||
@Index(columnList = "currentSponsorRegistrarId"),
|
||||
@Index(columnList = "dnsRefreshRequestTime")
|
||||
})
|
||||
@ExternalMessagingName("host")
|
||||
@WithVKey(String.class)
|
||||
@@ -94,7 +93,6 @@ public class Host extends HostBase implements ForeignKeyedEppResource {
|
||||
.setPersistedCurrentSponsorRegistrarId(hostBase.getPersistedCurrentSponsorRegistrarId())
|
||||
.setRepoId(hostBase.getRepoId())
|
||||
.setSuperordinateDomain(hostBase.getSuperordinateDomain())
|
||||
.setDnsRefreshRequestTime(hostBase.getDnsRefreshRequestTime())
|
||||
.setStatusValues(hostBase.getStatusValues());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
import org.joda.time.DateTime;
|
||||
@@ -86,13 +85,6 @@ public class HostBase extends EppResource {
|
||||
*/
|
||||
DateTime lastSuperordinateChange;
|
||||
|
||||
@Access(AccessType.PROPERTY)
|
||||
@SuppressWarnings("unused")
|
||||
@Column(name = "dnsRefreshRequestTime")
|
||||
private DateTime getInternalDnsRefreshRequestTime() {
|
||||
return getDnsRefreshRequestTime().orElse(null);
|
||||
}
|
||||
|
||||
public String getHostName() {
|
||||
return hostName;
|
||||
}
|
||||
|
||||
@@ -25,8 +25,6 @@ import com.google.common.base.Strings;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.util.RequestStatusChecker;
|
||||
import google.registry.util.RequestStatusCheckerImpl;
|
||||
import java.io.Serializable;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
@@ -70,8 +68,7 @@ public class Lock extends ImmutableObject implements Serializable {
|
||||
enum LockState {
|
||||
IN_USE,
|
||||
FREE,
|
||||
TIMED_OUT,
|
||||
OWNER_DIED
|
||||
TIMED_OUT
|
||||
}
|
||||
|
||||
@VisibleForTesting static LockMetrics lockMetrics = new LockMetrics();
|
||||
@@ -79,17 +76,6 @@ public class Lock extends ImmutableObject implements Serializable {
|
||||
/** The name of the locked resource. */
|
||||
@Transient @Id String lockId;
|
||||
|
||||
/**
|
||||
* Unique log ID of the request that owns this lock.
|
||||
*
|
||||
* <p>When that request is no longer running (is finished), the lock can be considered implicitly
|
||||
* released.
|
||||
*
|
||||
* <p>See {@link RequestStatusCheckerImpl#getLogId} for details about how it's created in
|
||||
* practice.
|
||||
*/
|
||||
@Column String requestLogId;
|
||||
|
||||
/** When the lock can be considered implicitly released. */
|
||||
@Column(nullable = false)
|
||||
DateTime expirationTime;
|
||||
@@ -124,7 +110,6 @@ public class Lock extends ImmutableObject implements Serializable {
|
||||
private static Lock create(
|
||||
String resourceName,
|
||||
String scope,
|
||||
String requestLogId,
|
||||
DateTime acquiredTime,
|
||||
Duration leaseLength) {
|
||||
checkArgument(!Strings.isNullOrEmpty(resourceName), "resourceName cannot be null or empty");
|
||||
@@ -132,7 +117,6 @@ public class Lock extends ImmutableObject implements Serializable {
|
||||
// Add the scope to the Lock's id so that it is unique for locks acquiring the same resource
|
||||
// across different TLDs.
|
||||
instance.lockId = makeLockId(resourceName, scope);
|
||||
instance.requestLogId = requestLogId;
|
||||
instance.expirationTime = acquiredTime.plus(leaseLength);
|
||||
instance.acquiredTime = acquiredTime;
|
||||
instance.resourceName = resourceName;
|
||||
@@ -172,18 +156,13 @@ public class Lock extends ImmutableObject implements Serializable {
|
||||
switch (acquireResult.lockState()) {
|
||||
case IN_USE:
|
||||
logger.atInfo().log(
|
||||
"Existing lock by request %s is still valid now %s (until %s) lock: %s",
|
||||
lock.requestLogId, now, lock.expirationTime, lock.lockId);
|
||||
"Existing lock by request is still valid now %s (until %s) lock: %s",
|
||||
now, lock.expirationTime, lock.lockId);
|
||||
break;
|
||||
case TIMED_OUT:
|
||||
logger.atInfo().log(
|
||||
"Existing lock by request %s is timed out now %s (was valid until %s) lock: %s",
|
||||
lock.requestLogId, now, lock.expirationTime, lock.lockId);
|
||||
break;
|
||||
case OWNER_DIED:
|
||||
logger.atInfo().log(
|
||||
"Existing lock is valid now %s (until %s), but owner (%s) isn't running lock: %s",
|
||||
now, lock.expirationTime, lock.requestLogId, lock.lockId);
|
||||
"Existing lock by request is timed out now %s (was valid until %s) lock: %s",
|
||||
now, lock.expirationTime, lock.lockId);
|
||||
break;
|
||||
case FREE:
|
||||
// There was no existing lock
|
||||
@@ -203,11 +182,7 @@ public class Lock extends ImmutableObject implements Serializable {
|
||||
|
||||
/** Try to acquire a lock. Returns absent if it can't be acquired. */
|
||||
public static Optional<Lock> acquire(
|
||||
String resourceName,
|
||||
@Nullable String tld,
|
||||
Duration leaseLength,
|
||||
RequestStatusChecker requestStatusChecker,
|
||||
boolean checkThreadRunning) {
|
||||
String resourceName, @Nullable String tld, Duration leaseLength) {
|
||||
String scope = tld != null ? tld : GLOBAL;
|
||||
Supplier<AcquireResult> lockAcquirer =
|
||||
() -> {
|
||||
@@ -219,22 +194,19 @@ public class Lock extends ImmutableObject implements Serializable {
|
||||
.orElse(null);
|
||||
if (lock != null) {
|
||||
logger.atInfo().log(
|
||||
"Loaded existing lock: %s for request: %s", lock.lockId, lock.requestLogId);
|
||||
"Loaded existing lock: %s for resource: %s", lock.lockId, lock.resourceName);
|
||||
}
|
||||
LockState lockState;
|
||||
if (lock == null) {
|
||||
lockState = LockState.FREE;
|
||||
} else if (isAtOrAfter(now, lock.expirationTime)) {
|
||||
lockState = LockState.TIMED_OUT;
|
||||
} else if (checkThreadRunning && !requestStatusChecker.isRunning(lock.requestLogId)) {
|
||||
lockState = LockState.OWNER_DIED;
|
||||
} else {
|
||||
lockState = LockState.IN_USE;
|
||||
return AcquireResult.create(now, lock, null, lockState);
|
||||
}
|
||||
|
||||
Lock newLock =
|
||||
create(resourceName, scope, requestStatusChecker.getLogId(), now, leaseLength);
|
||||
Lock newLock = create(resourceName, scope, now, leaseLength);
|
||||
tm().put(newLock);
|
||||
|
||||
return AcquireResult.create(now, lock, newLock, lockState);
|
||||
|
||||
@@ -25,6 +25,7 @@ import google.registry.monitoring.whitebox.WhiteboxModule;
|
||||
import google.registry.request.RequestComponentBuilder;
|
||||
import google.registry.request.RequestModule;
|
||||
import google.registry.request.RequestScope;
|
||||
import google.registry.ui.server.console.ConsoleDomainGetAction;
|
||||
import google.registry.ui.server.registrar.ConsoleOteSetupAction;
|
||||
import google.registry.ui.server.registrar.ConsoleRegistrarCreatorAction;
|
||||
import google.registry.ui.server.registrar.ConsoleUiAction;
|
||||
@@ -61,6 +62,8 @@ interface FrontendRequestComponent {
|
||||
|
||||
RegistryLockVerifyAction registryLockVerifyAction();
|
||||
|
||||
ConsoleDomainGetAction consoleDomainGetAction();
|
||||
|
||||
@Subcomponent.Builder
|
||||
abstract class Builder implements RequestComponentBuilder<FrontendRequestComponent> {
|
||||
@Override public abstract Builder requestModule(RequestModule requestModule);
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.contact.Contact;
|
||||
@@ -52,7 +53,7 @@ public class VKey<T> extends ImmutableObject implements Serializable {
|
||||
.collect(toImmutableMap(Class::getSimpleName, identity()));
|
||||
|
||||
// The primary key for the referenced entity.
|
||||
Serializable key;
|
||||
@Expose Serializable key;
|
||||
|
||||
Class<? extends T> kind;
|
||||
|
||||
|
||||
@@ -39,8 +39,6 @@ import google.registry.request.HttpException.UnsupportedMediaTypeException;
|
||||
import google.registry.request.auth.AuthResult;
|
||||
import google.registry.request.lock.LockHandler;
|
||||
import google.registry.request.lock.LockHandlerImpl;
|
||||
import google.registry.util.RequestStatusChecker;
|
||||
import google.registry.util.RequestStatusCheckerImpl;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -192,18 +190,6 @@ public final class RequestModule {
|
||||
return lockHandler;
|
||||
}
|
||||
|
||||
@Provides
|
||||
static RequestStatusChecker provideRequestStatusChecker(
|
||||
RequestStatusCheckerImpl requestStatusChecker) {
|
||||
return requestStatusChecker;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@RequestLogId
|
||||
static String provideRequestLogId(RequestStatusChecker requestStatusChecker) {
|
||||
return requestStatusChecker.getLogId();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@JsonPayload
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableList;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import javax.inject.Qualifier;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
@@ -30,15 +31,26 @@ import javax.inject.Singleton;
|
||||
public class AuthModule {
|
||||
|
||||
private static final String IAP_ISSUER_URL = "https://cloud.google.com/iap";
|
||||
private static final String SA_ISSUER_URL = "https://accounts.google.com";
|
||||
|
||||
/** Provides the custom authentication mechanisms (including OAuth). */
|
||||
@Provides
|
||||
ImmutableList<AuthenticationMechanism> provideApiAuthenticationMechanisms(
|
||||
OAuthAuthenticationMechanism oauthAuthenticationMechanism,
|
||||
IapHeaderAuthenticationMechanism iapHeaderAuthenticationMechanism) {
|
||||
return ImmutableList.of(oauthAuthenticationMechanism, iapHeaderAuthenticationMechanism);
|
||||
IapHeaderAuthenticationMechanism iapHeaderAuthenticationMechanism,
|
||||
ServiceAccountAuthenticationMechanism serviceAccountAuthenticationMechanism) {
|
||||
return ImmutableList.of(
|
||||
oauthAuthenticationMechanism,
|
||||
iapHeaderAuthenticationMechanism,
|
||||
serviceAccountAuthenticationMechanism);
|
||||
}
|
||||
|
||||
@Qualifier
|
||||
@interface IAP {}
|
||||
|
||||
@Qualifier
|
||||
@interface ServiceAccount {}
|
||||
|
||||
/** Provides the OAuthService instance. */
|
||||
@Provides
|
||||
OAuthService provideOauthService() {
|
||||
@@ -46,10 +58,18 @@ public class AuthModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IAP
|
||||
@Singleton
|
||||
TokenVerifier provideTokenVerifier(
|
||||
@Config("projectId") String projectId, @Config("projectIdNumber") long projectIdNumber) {
|
||||
String audience = String.format("/projects/%d/apps/%s", projectIdNumber, projectId);
|
||||
return TokenVerifier.newBuilder().setAudience(audience).setIssuer(IAP_ISSUER_URL).build();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ServiceAccount
|
||||
@Singleton
|
||||
TokenVerifier provideServiceAccountTokenVerifier(@Config("projectId") String projectId) {
|
||||
return TokenVerifier.newBuilder().setAudience(projectId).setIssuer(SA_ISSUER_URL).build();
|
||||
}
|
||||
}
|
||||
|
||||
+10
-37
@@ -14,15 +14,11 @@
|
||||
|
||||
package google.registry.request.auth;
|
||||
|
||||
import com.google.api.client.json.webtoken.JsonWebSignature;
|
||||
import com.google.auth.oauth2.TokenVerifier;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserDao;
|
||||
import google.registry.request.auth.AuthModule.IAP;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@@ -37,41 +33,22 @@ import javax.servlet.http.HttpServletRequest;
|
||||
* @see <a href="https://cloud.google.com/iap/docs/signed-headers-howto">the documentation on GCP
|
||||
* IAP's signed headers for more information.</a>
|
||||
*/
|
||||
public class IapHeaderAuthenticationMechanism implements AuthenticationMechanism {
|
||||
public class IapHeaderAuthenticationMechanism extends IdTokenAuthenticationBase {
|
||||
|
||||
private static final String ID_TOKEN_HEADER_NAME = "X-Goog-IAP-JWT-Assertion";
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
// A workaround that allows "use" of the IAP-based authenticator when running local testing, i.e.
|
||||
// the RegistryTestServer
|
||||
private static Optional<User> userForTesting = Optional.empty();
|
||||
|
||||
private final TokenVerifier tokenVerifier;
|
||||
|
||||
@Inject
|
||||
public IapHeaderAuthenticationMechanism(TokenVerifier tokenVerifier) {
|
||||
this.tokenVerifier = tokenVerifier;
|
||||
public IapHeaderAuthenticationMechanism(@IAP TokenVerifier tokenVerifier) {
|
||||
super(tokenVerifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthResult authenticate(HttpServletRequest request) {
|
||||
if (RegistryEnvironment.get().equals(RegistryEnvironment.UNITTEST)
|
||||
&& userForTesting.isPresent()) {
|
||||
return AuthResult.create(AuthLevel.USER, UserAuthInfo.create(userForTesting.get()));
|
||||
}
|
||||
String rawIdToken = request.getHeader(ID_TOKEN_HEADER_NAME);
|
||||
if (rawIdToken == null) {
|
||||
return AuthResult.NOT_AUTHENTICATED;
|
||||
}
|
||||
JsonWebSignature token;
|
||||
try {
|
||||
token = tokenVerifier.verify(rawIdToken);
|
||||
} catch (TokenVerifier.VerificationException e) {
|
||||
logger.atInfo().withCause(e).log("Error when verifying access token");
|
||||
return AuthResult.NOT_AUTHENTICATED;
|
||||
}
|
||||
String emailAddress = (String) token.getPayload().get("email");
|
||||
String rawTokenFromRequest(HttpServletRequest request) {
|
||||
return request.getHeader(ID_TOKEN_HEADER_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
AuthResult authResultFromEmail(String emailAddress) {
|
||||
Optional<User> maybeUser = UserDao.loadUser(emailAddress);
|
||||
if (!maybeUser.isPresent()) {
|
||||
logger.atInfo().log("No user found for email address %s", emailAddress);
|
||||
@@ -80,8 +57,4 @@ public class IapHeaderAuthenticationMechanism implements AuthenticationMechanism
|
||||
return AuthResult.create(AuthLevel.USER, UserAuthInfo.create(maybeUser.get()));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void setUserAuthInfoForTestServer(@Nullable User user) {
|
||||
userForTesting = Optional.ofNullable(user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.request.auth;
|
||||
|
||||
import com.google.api.client.json.webtoken.JsonWebSignature;
|
||||
import com.google.auth.oauth2.TokenVerifier;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.model.console.User;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
public abstract class IdTokenAuthenticationBase implements AuthenticationMechanism {
|
||||
|
||||
public static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
// A workaround that allows "use" of the IAP-based authenticator when running local testing, i.e.
|
||||
// the RegistryTestServer
|
||||
private static Optional<User> userForTesting = Optional.empty();
|
||||
|
||||
private final TokenVerifier tokenVerifier;
|
||||
|
||||
public IdTokenAuthenticationBase(TokenVerifier tokenVerifier) {
|
||||
this.tokenVerifier = tokenVerifier;
|
||||
}
|
||||
|
||||
abstract String rawTokenFromRequest(HttpServletRequest request);
|
||||
|
||||
abstract AuthResult authResultFromEmail(String email);
|
||||
|
||||
@Override
|
||||
public AuthResult authenticate(HttpServletRequest request) {
|
||||
if (RegistryEnvironment.get().equals(RegistryEnvironment.UNITTEST)
|
||||
&& userForTesting.isPresent()) {
|
||||
return AuthResult.create(AuthLevel.USER, UserAuthInfo.create(userForTesting.get()));
|
||||
}
|
||||
String rawIdToken = rawTokenFromRequest(request);
|
||||
if (rawIdToken == null) {
|
||||
return AuthResult.NOT_AUTHENTICATED;
|
||||
}
|
||||
JsonWebSignature token;
|
||||
try {
|
||||
token = tokenVerifier.verify(rawIdToken);
|
||||
} catch (TokenVerifier.VerificationException e) {
|
||||
logger.atInfo().withCause(e).log("Error when verifying access token");
|
||||
return AuthResult.NOT_AUTHENTICATED;
|
||||
}
|
||||
String emailAddress = (String) token.getPayload().get("email");
|
||||
return authResultFromEmail(emailAddress);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void setUserAuthInfoForTestServer(@Nullable User user) {
|
||||
userForTesting = Optional.ofNullable(user);
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
// 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.request.auth;
|
||||
|
||||
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
|
||||
import static google.registry.request.auth.AuthLevel.APP;
|
||||
|
||||
import com.google.auth.oauth2.TokenVerifier;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.request.auth.AuthModule.ServiceAccount;
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* A way to authenticate HTTP requests signed by Service Account
|
||||
*
|
||||
* <p>Currently used by cloud scheduler service account
|
||||
*/
|
||||
public class ServiceAccountAuthenticationMechanism extends IdTokenAuthenticationBase {
|
||||
|
||||
private final String cloudSchedulerEmailPrefix;
|
||||
private static final String BEARER_PREFIX = "Bearer ";
|
||||
|
||||
@Inject
|
||||
public ServiceAccountAuthenticationMechanism(
|
||||
@ServiceAccount TokenVerifier tokenVerifier,
|
||||
@Config("cloudSchedulerServiceAccountEmail") String cloudSchedulerEmailPrefix) {
|
||||
|
||||
super(tokenVerifier);
|
||||
this.cloudSchedulerEmailPrefix = cloudSchedulerEmailPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
String rawTokenFromRequest(HttpServletRequest request) {
|
||||
String rawToken = request.getHeader(AUTHORIZATION);
|
||||
if (rawToken != null && rawToken.startsWith(BEARER_PREFIX)) {
|
||||
return rawToken.substring(BEARER_PREFIX.length());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
AuthResult authResultFromEmail(String emailAddress) {
|
||||
if (emailAddress.equals(cloudSchedulerEmailPrefix)) {
|
||||
return AuthResult.create(APP);
|
||||
} else {
|
||||
return AuthResult.NOT_AUTHENTICATED;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||
import google.registry.model.server.Lock;
|
||||
import google.registry.util.AppEngineTimeLimiter;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.RequestStatusChecker;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@@ -48,12 +47,10 @@ public class LockHandlerImpl implements LockHandler {
|
||||
/** Fudge factor to make sure we kill threads before a lock actually expires. */
|
||||
private static final Duration LOCK_TIMEOUT_FUDGE = Duration.standardSeconds(5);
|
||||
|
||||
private final RequestStatusChecker requestStatusChecker;
|
||||
private final Clock clock;
|
||||
|
||||
@Inject
|
||||
public LockHandlerImpl(RequestStatusChecker requestStatusChecker, Clock clock) {
|
||||
this.requestStatusChecker = requestStatusChecker;
|
||||
public LockHandlerImpl(Clock clock) {
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@@ -114,7 +111,7 @@ public class LockHandlerImpl implements LockHandler {
|
||||
/** Allows injection of mock Lock in tests. */
|
||||
@VisibleForTesting
|
||||
Optional<Lock> acquire(String lockName, @Nullable String tld, Duration leaseLength) {
|
||||
return Lock.acquire(lockName, tld, leaseLength, requestStatusChecker, true);
|
||||
return Lock.acquire(lockName, tld, leaseLength);
|
||||
}
|
||||
|
||||
private interface LockAcquirer {
|
||||
|
||||
@@ -49,7 +49,6 @@ U+011F # LATIN SMALL LETTER G WITH BREVE
|
||||
U+01E7 # LATIN SMALL LETTER G WITH CARON
|
||||
U+0121 # LATIN SMALL LETTER G WITH DOT ABOVE
|
||||
U+0123 # LATIN SMALL LETTER G WITH CEDILLA
|
||||
U+01E5 # LATIN SMALL LETTER G WITH STROKE
|
||||
U+0068 # LATIN SMALL LETTER H
|
||||
U+0127 # LATIN SMALL LETTER H WITH STROKE
|
||||
U+0069 # LATIN SMALL LETTER I
|
||||
|
||||
@@ -66,7 +66,12 @@ public final class LordnTaskUtils {
|
||||
}
|
||||
|
||||
/** Returns the corresponding CSV LORDN line for a sunrise domain. */
|
||||
public static String getCsvLineForSunriseDomain(Domain domain, DateTime transactionTime) {
|
||||
public static String getCsvLineForSunriseDomain(Domain domain) {
|
||||
return getCsvLineForSunriseDomain(domain, domain.getCreationTime());
|
||||
}
|
||||
|
||||
// TODO: Merge into the function above after pull queue migration.
|
||||
private static String getCsvLineForSunriseDomain(Domain domain, DateTime transactionTime) {
|
||||
return Joiner.on(',')
|
||||
.join(
|
||||
domain.getRepoId(),
|
||||
@@ -77,7 +82,12 @@ public final class LordnTaskUtils {
|
||||
}
|
||||
|
||||
/** Returns the corresponding CSV LORDN line for a claims domain. */
|
||||
public static String getCsvLineForClaimsDomain(Domain domain, DateTime transactionTime) {
|
||||
public static String getCsvLineForClaimsDomain(Domain domain) {
|
||||
return getCsvLineForClaimsDomain(domain, domain.getCreationTime());
|
||||
}
|
||||
|
||||
// TODO: Merge into the function above after pull queue migration.
|
||||
private static String getCsvLineForClaimsDomain(Domain domain, DateTime transactionTime) {
|
||||
return Joiner.on(',')
|
||||
.join(
|
||||
domain.getRepoId(),
|
||||
@@ -100,16 +110,8 @@ public final class LordnTaskUtils {
|
||||
private LordnTaskUtils() {}
|
||||
|
||||
public enum LordnPhase {
|
||||
SUNRISE(QUEUE_SUNRISE),
|
||||
|
||||
CLAIMS(QUEUE_CLAIMS),
|
||||
|
||||
NONE(null);
|
||||
|
||||
final String queue;
|
||||
|
||||
LordnPhase(String queue) {
|
||||
this.queue = queue;
|
||||
}
|
||||
SUNRISE,
|
||||
CLAIMS,
|
||||
NONE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,13 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.net.HttpHeaders.LOCATION;
|
||||
import static com.google.common.net.MediaType.CSV_UTF_8;
|
||||
import static google.registry.persistence.transaction.QueryComposer.Comparator.EQ;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.request.UrlConnectionUtils.getResponseBytes;
|
||||
import static google.registry.tmch.LordnTaskUtils.COLUMNS_CLAIMS;
|
||||
import static google.registry.tmch.LordnTaskUtils.COLUMNS_SUNRISE;
|
||||
import static google.registry.tmch.LordnTaskUtils.getCsvLineForClaimsDomain;
|
||||
import static google.registry.tmch.LordnTaskUtils.getCsvLineForSunriseDomain;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED;
|
||||
@@ -33,6 +37,7 @@ import com.google.appengine.api.taskqueue.TaskHandle;
|
||||
import com.google.appengine.api.taskqueue.TransientFailureException;
|
||||
import com.google.apphosting.api.DeadlineExceededException;
|
||||
import com.google.cloud.tasks.v2.Task;
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -42,6 +47,7 @@ import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.Service;
|
||||
import google.registry.request.Parameter;
|
||||
@@ -49,6 +55,7 @@ import google.registry.request.RequestParameters;
|
||||
import google.registry.request.UrlConnectionService;
|
||||
import google.registry.request.UrlConnectionUtils;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.tmch.LordnTaskUtils.LordnPhase;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.CloudTasksUtils;
|
||||
import google.registry.util.Retrier;
|
||||
@@ -59,6 +66,7 @@ import java.net.URL;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.inject.Inject;
|
||||
@@ -81,9 +89,11 @@ import org.joda.time.Duration;
|
||||
public final class NordnUploadAction implements Runnable {
|
||||
|
||||
static final String PATH = "/_dr/task/nordnUpload";
|
||||
static final String LORDN_PHASE_PARAM = "lordn-phase";
|
||||
static final String LORDN_PHASE_PARAM = "lordnPhase";
|
||||
// TODO: Delete after migrating off of pull queue.
|
||||
static final String PULL_QUEUE_PARAM = "pullQueue";
|
||||
|
||||
private static final int QUEUE_BATCH_SIZE = 1000;
|
||||
private static final int BATCH_SIZE = 1000;
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
private static final Duration LEASE_PERIOD = Duration.standardHours(1);
|
||||
|
||||
@@ -100,31 +110,102 @@ public final class NordnUploadAction implements Runnable {
|
||||
@Inject LordnRequestInitializer lordnRequestInitializer;
|
||||
@Inject UrlConnectionService urlConnectionService;
|
||||
|
||||
@Inject @Config("tmchMarksdbUrl") String tmchMarksdbUrl;
|
||||
@Inject @Parameter(LORDN_PHASE_PARAM) String phase;
|
||||
@Inject @Parameter(RequestParameters.PARAM_TLD) String tld;
|
||||
@Inject
|
||||
@Config("tmchMarksdbUrl")
|
||||
String tmchMarksdbUrl;
|
||||
|
||||
@Inject
|
||||
@Parameter(LORDN_PHASE_PARAM)
|
||||
String phase;
|
||||
|
||||
@Inject
|
||||
@Parameter(RequestParameters.PARAM_TLD)
|
||||
String tld;
|
||||
|
||||
@Inject
|
||||
@Parameter(PULL_QUEUE_PARAM)
|
||||
Optional<Boolean> usePullQueue;
|
||||
|
||||
@Inject CloudTasksUtils cloudTasksUtils;
|
||||
|
||||
@Inject NordnUploadAction() {}
|
||||
@Inject
|
||||
NordnUploadAction() {}
|
||||
|
||||
/**
|
||||
* These LORDN parameter names correspond to the relative paths in LORDN URLs and cannot be
|
||||
* changed on our end.
|
||||
*/
|
||||
private static final String PARAM_LORDN_PHASE_SUNRISE = "sunrise";
|
||||
private static final String PARAM_LORDN_PHASE_SUNRISE =
|
||||
Ascii.toLowerCase(LordnPhase.SUNRISE.toString());
|
||||
|
||||
private static final String PARAM_LORDN_PHASE_CLAIMS = "claims";
|
||||
private static final String PARAM_LORDN_PHASE_CLAIMS =
|
||||
Ascii.toLowerCase(LordnPhase.CLAIMS.toString());
|
||||
|
||||
/** How long to wait before attempting to verify an upload by fetching the log. */
|
||||
private static final Duration VERIFY_DELAY = Duration.standardMinutes(30);
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
processLordnTasks();
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
if (usePullQueue.orElse(false)) {
|
||||
try {
|
||||
processLordnTasks();
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
checkArgument(
|
||||
phase.equals(PARAM_LORDN_PHASE_SUNRISE) || phase.equals(PARAM_LORDN_PHASE_CLAIMS),
|
||||
"Invalid phase specified to NordnUploadAction: %s.",
|
||||
phase);
|
||||
tm().transact(
|
||||
() -> {
|
||||
// Note here that we load all domains pending Nordn in one batch, which should not
|
||||
// be a problem for the rate of domain registration that we see. If we anticipate
|
||||
// a peak in claims during TLD launch (sunrise is NOT first-come-first-serve, so
|
||||
// there should be no expectation of a peak during it), we can consider temporarily
|
||||
// increasing the frequency of Nordn upload to reduce the size of each batch.
|
||||
//
|
||||
// We did not further divide the domains into smaller batches because the
|
||||
// read-upload-write operation per small batch needs to be inside a single
|
||||
// transaction to prevent race conditions, and running several uploads in rapid
|
||||
// sucession will likely overwhelm the MarksDB upload server, which recommands a
|
||||
// maximum upload frequency of every 3 hours.
|
||||
//
|
||||
// See:
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-regext-tmch-func-spec-01#section-5.2.3.3
|
||||
List<Domain> domains =
|
||||
tm().createQueryComposer(Domain.class)
|
||||
.where("lordnPhase", EQ, LordnPhase.valueOf(Ascii.toUpperCase(phase)))
|
||||
.where("tld", EQ, tld)
|
||||
.orderBy("creationTime")
|
||||
.list();
|
||||
if (domains.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
StringBuilder csv = new StringBuilder();
|
||||
ImmutableList.Builder<Domain> newDomains = new ImmutableList.Builder<>();
|
||||
|
||||
domains.forEach(
|
||||
domain -> {
|
||||
if (phase.equals(PARAM_LORDN_PHASE_SUNRISE)) {
|
||||
csv.append(getCsvLineForSunriseDomain(domain)).append('\n');
|
||||
} else {
|
||||
csv.append(getCsvLineForClaimsDomain(domain)).append('\n');
|
||||
}
|
||||
Domain newDomain = domain.asBuilder().setLordnPhase(LordnPhase.NONE).build();
|
||||
newDomains.add(newDomain);
|
||||
});
|
||||
String columns =
|
||||
phase.equals(PARAM_LORDN_PHASE_SUNRISE) ? COLUMNS_SUNRISE : COLUMNS_CLAIMS;
|
||||
String header =
|
||||
String.format("1,%s,%d\n%s\n", clock.nowUtc(), domains.size(), columns);
|
||||
try {
|
||||
uploadCsvToLordn(String.format("/LORDN/%s/%s", tld, phase), header + csv);
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
tm().updateAll(newDomains.build());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +239,7 @@ public final class NordnUploadAction implements Runnable {
|
||||
queue.leaseTasks(
|
||||
LeaseOptions.Builder.withTag(tld)
|
||||
.leasePeriod(LEASE_PERIOD.getMillis(), TimeUnit.MILLISECONDS)
|
||||
.countLimit(QUEUE_BATCH_SIZE)),
|
||||
.countLimit(BATCH_SIZE)),
|
||||
TransientFailureException.class,
|
||||
DeadlineExceededException.class);
|
||||
if (tasks.isEmpty()) {
|
||||
@@ -171,7 +252,7 @@ public final class NordnUploadAction implements Runnable {
|
||||
private void processLordnTasks() throws IOException, GeneralSecurityException {
|
||||
checkArgument(
|
||||
phase.equals(PARAM_LORDN_PHASE_SUNRISE) || phase.equals(PARAM_LORDN_PHASE_CLAIMS),
|
||||
"Invalid phase specified to Nordn servlet: %s.",
|
||||
"Invalid phase specified to NordnUploadAction: %s.",
|
||||
phase);
|
||||
DateTime now = clock.nowUtc();
|
||||
Queue queue =
|
||||
@@ -184,12 +265,12 @@ public final class NordnUploadAction implements Runnable {
|
||||
// Note: This upload/task deletion isn't done atomically (it's not clear how one would do so
|
||||
// anyway). As a result, it is possible that the upload might succeed yet the deletion of
|
||||
// enqueued tasks might fail. If so, this would result in the same lines being uploaded to NORDN
|
||||
// across mulitple uploads. This is probably OK; all that we really cannot have is a missing
|
||||
// across multiple uploads. This is probably OK; all that we really cannot have is a missing
|
||||
// line.
|
||||
if (!tasks.isEmpty()) {
|
||||
String csvData = convertTasksToCsv(tasks, now, columns);
|
||||
uploadCsvToLordn(String.format("/LORDN/%s/%s", tld, phase), csvData);
|
||||
Lists.partition(tasks, QUEUE_BATCH_SIZE)
|
||||
Lists.partition(tasks, BATCH_SIZE)
|
||||
.forEach(
|
||||
batch ->
|
||||
retrier.callWithRetry(
|
||||
|
||||
@@ -22,6 +22,7 @@ import static google.registry.util.ResourceUtils.readResourceUtf8;
|
||||
import com.github.benmanes.caffeine.cache.CacheLoader;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.config.RegistryConfig.ConfigModule.TmchCaMode;
|
||||
import google.registry.model.CacheUtils;
|
||||
@@ -55,6 +56,7 @@ public final class TmchCertificateAuthority {
|
||||
private static final String ROOT_CRT_PILOT_FILE = "icann-tmch-pilot.crt";
|
||||
private static final String CRL_FILE = "icann-tmch.crl";
|
||||
private static final String CRL_PILOT_FILE = "icann-tmch-pilot.crl";
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private final TmchCaMode tmchCaMode;
|
||||
private final Clock clock;
|
||||
@@ -142,8 +144,14 @@ public final class TmchCertificateAuthority {
|
||||
* @see X509Utils#verifyCrl
|
||||
*/
|
||||
public void updateCrl(String asciiCrl, String url) throws GeneralSecurityException {
|
||||
X509CRL crl = X509Utils.loadCrl(asciiCrl);
|
||||
X509Utils.verifyCrl(getAndValidateRoot(), getCrl(), crl, clock.nowUtc().toDate());
|
||||
X509CRL newCrl = X509Utils.loadCrl(asciiCrl);
|
||||
X509CRL oldCrl = null;
|
||||
try {
|
||||
oldCrl = getCrl();
|
||||
} catch (Exception e) {
|
||||
logger.atWarning().withCause(e).log("Old CRL is invalid, ignored during CRL update.");
|
||||
}
|
||||
X509Utils.verifyCrl(getAndValidateRoot(), oldCrl, newCrl, clock.nowUtc().toDate());
|
||||
TmchCrl.set(asciiCrl, url);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.tmch;
|
||||
|
||||
import static com.google.common.io.Resources.asByteSource;
|
||||
import static com.google.common.io.Resources.getResource;
|
||||
import static google.registry.request.RequestParameters.extractOptionalBooleanParameter;
|
||||
import static google.registry.request.RequestParameters.extractRequiredParameter;
|
||||
|
||||
import dagger.Module;
|
||||
@@ -25,6 +26,7 @@ import google.registry.request.HttpException.BadRequestException;
|
||||
import google.registry.request.Parameter;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Optional;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
|
||||
@@ -64,4 +66,10 @@ public final class TmchModule {
|
||||
static String provideNordnLogId(HttpServletRequest req) {
|
||||
return extractRequiredParameter(req, NordnVerifyAction.NORDN_LOG_ID_PARAM);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter(NordnUploadAction.PULL_QUEUE_PARAM)
|
||||
static Optional<Boolean> provideUsePullQueue(HttpServletRequest req) {
|
||||
return extractOptionalBooleanParameter(req, NordnUploadAction.PULL_QUEUE_PARAM);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// 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.tools;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.console.GlobalRole;
|
||||
import google.registry.model.console.RegistrarRole;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserDao;
|
||||
import google.registry.model.console.UserRoles;
|
||||
import google.registry.tools.params.KeyValueMapParameter.StringToRegistrarRoleMap;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Shared base class for commands that create or modify a {@link User}. */
|
||||
public abstract class CreateOrUpdateUserCommand extends ConfirmingCommand {
|
||||
|
||||
@Nullable
|
||||
@Parameter(names = "--email", description = "Email address of the user", required = true)
|
||||
String email;
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = "--admin",
|
||||
description = "Whether or not the user in question is an admin",
|
||||
arity = 1)
|
||||
private Boolean isAdmin;
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = "--global_role",
|
||||
description = "Global role, e.g. SUPPORT_LEAD, to apply to the user")
|
||||
private GlobalRole globalRole;
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = "--registrar_roles",
|
||||
converter = StringToRegistrarRoleMap.class,
|
||||
validateWith = StringToRegistrarRoleMap.class,
|
||||
description =
|
||||
"Comma-delimited mapping of registrar name to role that the user has on that registrar")
|
||||
private ImmutableMap<String, RegistrarRole> registrarRolesMap;
|
||||
|
||||
@Nullable
|
||||
abstract User getExistingUser(String email);
|
||||
|
||||
@Override
|
||||
protected final String execute() throws Exception {
|
||||
checkArgumentNotNull(email, "Email must be provided");
|
||||
tm().transact(this::executeInTransaction);
|
||||
return String.format("Saved user with email %s", email);
|
||||
}
|
||||
|
||||
private void executeInTransaction() {
|
||||
User user = getExistingUser(email);
|
||||
UserRoles.Builder userRolesBuilder =
|
||||
(user == null) ? new UserRoles.Builder() : user.getUserRoles().asBuilder();
|
||||
|
||||
Optional.ofNullable(globalRole).ifPresent(userRolesBuilder::setGlobalRole);
|
||||
Optional.ofNullable(registrarRolesMap).ifPresent(userRolesBuilder::setRegistrarRoles);
|
||||
Optional.ofNullable(isAdmin).ifPresent(userRolesBuilder::setIsAdmin);
|
||||
|
||||
User.Builder builder =
|
||||
(user == null) ? new User.Builder().setEmailAddress(email) : user.asBuilder();
|
||||
builder.setUserRoles(userRolesBuilder.build());
|
||||
User newUser = builder.build();
|
||||
UserDao.saveUser(newUser);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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.tools;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.beust.jcommander.Parameters;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserDao;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Command to create a new User. */
|
||||
@Parameters(separators = " =", commandDescription = "Update a user account")
|
||||
public class CreateUserCommand extends CreateOrUpdateUserCommand {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
User getExistingUser(String email) {
|
||||
checkArgument(
|
||||
!UserDao.loadUser(email).isPresent(), "A user with email %s already exists", email);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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.tools;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserDao;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Deletes a {@link User}. */
|
||||
@Parameters(separators = " =", commandDescription = "Delete a user account")
|
||||
public class DeleteUserCommand extends ConfirmingCommand {
|
||||
|
||||
@Nullable
|
||||
@Parameter(names = "--email", description = "Email address of the user", required = true)
|
||||
String email;
|
||||
|
||||
@Override
|
||||
protected String prompt() {
|
||||
checkArgumentNotNull(email, "Email must be provided");
|
||||
checkArgumentPresent(UserDao.loadUser(email), "Email does not correspond to a valid user");
|
||||
return String.format("Delete user with email %s?", email);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String execute() throws Exception {
|
||||
tm().transact(
|
||||
() -> {
|
||||
Optional<User> optionalUser = UserDao.loadUser(email);
|
||||
checkArgumentPresent(optionalUser, "Email no longer corresponds to a valid user");
|
||||
tm().delete(optionalUser.get());
|
||||
});
|
||||
return String.format("Deleted user with email %s", email);
|
||||
}
|
||||
}
|
||||
@@ -94,10 +94,10 @@ final class GenerateLordnCommand implements Command {
|
||||
Domain domain) {
|
||||
String status = " ";
|
||||
if (domain.getLaunchNotice() == null && domain.getSmdId() != null) {
|
||||
sunriseCsv.add(LordnTaskUtils.getCsvLineForSunriseDomain(domain, domain.getCreationTime()));
|
||||
sunriseCsv.add(LordnTaskUtils.getCsvLineForSunriseDomain(domain));
|
||||
status = "S";
|
||||
} else if (domain.getLaunchNotice() != null || domain.getSmdId() != null) {
|
||||
claimsCsv.add(LordnTaskUtils.getCsvLineForClaimsDomain(domain, domain.getCreationTime()));
|
||||
claimsCsv.add(LordnTaskUtils.getCsvLineForClaimsDomain(domain));
|
||||
status = "C";
|
||||
}
|
||||
System.out.printf("%s[%s] ", domain.getDomainName(), status);
|
||||
|
||||
@@ -46,6 +46,7 @@ public final class RegistryTool {
|
||||
.put("create_registrar_groups", CreateRegistrarGroupsCommand.class)
|
||||
.put("create_reserved_list", CreateReservedListCommand.class)
|
||||
.put("create_tld", CreateTldCommand.class)
|
||||
.put("create_user", CreateUserCommand.class)
|
||||
.put("curl", CurlCommand.class)
|
||||
.put("delete_allocation_tokens", DeleteAllocationTokensCommand.class)
|
||||
.put("delete_domain", DeleteDomainCommand.class)
|
||||
@@ -53,6 +54,7 @@ public final class RegistryTool {
|
||||
.put("delete_premium_list", DeletePremiumListCommand.class)
|
||||
.put("delete_reserved_list", DeleteReservedListCommand.class)
|
||||
.put("delete_tld", DeleteTldCommand.class)
|
||||
.put("delete_user", DeleteUserCommand.class)
|
||||
.put("encrypt_escrow_deposit", EncryptEscrowDepositCommand.class)
|
||||
.put("enqueue_poll_message", EnqueuePollMessageCommand.class)
|
||||
.put("execute_epp", ExecuteEppCommand.class)
|
||||
@@ -110,6 +112,7 @@ public final class RegistryTool {
|
||||
.put("update_reserved_list", UpdateReservedListCommand.class)
|
||||
.put("update_server_locks", UpdateServerLocksCommand.class)
|
||||
.put("update_tld", UpdateTldCommand.class)
|
||||
.put("update_user", UpdateUserCommand.class)
|
||||
.put("upload_claims_list", UploadClaimsListCommand.class)
|
||||
.put("validate_escrow_deposit", ValidateEscrowDepositCommand.class)
|
||||
.put("validate_login_credentials", ValidateLoginCredentialsCommand.class)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// 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.tools;
|
||||
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
|
||||
|
||||
import com.beust.jcommander.Parameters;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserDao;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Updates a user, assuming that the user in question already exists. */
|
||||
@Parameters(separators = " =", commandDescription = "Update a user account")
|
||||
public class UpdateUserCommand extends CreateOrUpdateUserCommand {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
User getExistingUser(String email) {
|
||||
return checkArgumentPresent(UserDao.loadUser(email), "User %s not found", email);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ package google.registry.tools.params;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.console.RegistrarRole;
|
||||
import java.util.Map;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
|
||||
@@ -107,4 +108,18 @@ public abstract class KeyValueMapParameter<K, V>
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/** Combined converter/validator class for maps of registrar names to registrar roles. */
|
||||
public static class StringToRegistrarRoleMap extends KeyValueMapParameter<String, RegistrarRole> {
|
||||
|
||||
@Override
|
||||
protected String parseKey(String rawKey) {
|
||||
return rawKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RegistrarRole parseValue(String rawValue) {
|
||||
return RegistrarRole.valueOf(rawValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// 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.ui.server.console;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.api.client.http.HttpStatusCodes;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.model.EppResourceUtils;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.request.auth.AuthResult;
|
||||
import google.registry.request.auth.UserAuthInfo;
|
||||
import google.registry.ui.server.registrar.JsonGetAction;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** Returns a JSON representation of a domain to the registrar console. */
|
||||
@Action(
|
||||
service = Action.Service.DEFAULT,
|
||||
path = ConsoleDomainGetAction.PATH,
|
||||
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
|
||||
public class ConsoleDomainGetAction implements JsonGetAction {
|
||||
|
||||
public static final String PATH = "/console-api/domain";
|
||||
|
||||
private final AuthResult authResult;
|
||||
private final Response response;
|
||||
private final Gson gson;
|
||||
private final String paramDomain;
|
||||
|
||||
@Inject
|
||||
public ConsoleDomainGetAction(
|
||||
AuthResult authResult,
|
||||
Response response,
|
||||
Gson gson,
|
||||
@Parameter("domain") String paramDomain) {
|
||||
this.authResult = authResult;
|
||||
this.response = response;
|
||||
this.gson = gson;
|
||||
this.paramDomain = paramDomain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!authResult.isAuthenticated() || !authResult.userAuthInfo().isPresent()) {
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
|
||||
return;
|
||||
}
|
||||
UserAuthInfo authInfo = authResult.userAuthInfo().get();
|
||||
if (!authInfo.consoleUser().isPresent()) {
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
|
||||
return;
|
||||
}
|
||||
User user = authInfo.consoleUser().get();
|
||||
Optional<Domain> possibleDomain =
|
||||
tm().transact(
|
||||
() ->
|
||||
EppResourceUtils.loadByForeignKeyCached(
|
||||
Domain.class, paramDomain, tm().getTransactionTime()));
|
||||
if (!possibleDomain.isPresent()) {
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
Domain domain = possibleDomain.get();
|
||||
if (!user.getUserRoles()
|
||||
.hasPermission(domain.getCurrentSponsorRegistrarId(), ConsolePermission.DOWNLOAD_DOMAINS)) {
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_OK);
|
||||
response.setPayload(gson.toJson(domain));
|
||||
}
|
||||
}
|
||||
@@ -156,4 +156,10 @@ public final class RegistrarConsoleModule {
|
||||
static Boolean provideIsLock(HttpServletRequest req) {
|
||||
return extractBooleanParameter(req, "isLock");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("domain")
|
||||
static String provideDomain(HttpServletRequest req) {
|
||||
return extractRequiredParameter(req, "domain");
|
||||
}
|
||||
}
|
||||
|
||||
-6
@@ -23,12 +23,6 @@
|
||||
"helpText": "The exclusive upper bound of the operation window, in ISO 8601 format.",
|
||||
"is_optional": false
|
||||
},
|
||||
{
|
||||
"name": "shard",
|
||||
"label": "The exclusive upper bound of the operation window.",
|
||||
"helpText": "The exclusive upper bound of the operation window, in ISO 8601 format.",
|
||||
"is_optional": true
|
||||
},
|
||||
{
|
||||
"name": "isDryRun",
|
||||
"label": "Whether this job is a dry run.",
|
||||
|
||||
@@ -155,6 +155,8 @@ import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.billing.BillingEvent.Flag;
|
||||
import google.registry.model.billing.BillingEvent.Reason;
|
||||
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
|
||||
import google.registry.model.common.DatabaseMigrationStateSchedule;
|
||||
import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
@@ -182,6 +184,7 @@ import google.registry.persistence.VKey;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.testing.TaskQueueExtension;
|
||||
import google.registry.testing.TaskQueueHelper.TaskMatcher;
|
||||
import google.registry.tmch.LordnTaskUtils.LordnPhase;
|
||||
import google.registry.tmch.SmdrlCsvParser;
|
||||
import google.registry.tmch.TmchData;
|
||||
import google.registry.tmch.TmchTestData;
|
||||
@@ -192,9 +195,12 @@ import javax.annotation.Nullable;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.junitpioneer.jupiter.cartesian.CartesianTest;
|
||||
import org.junitpioneer.jupiter.cartesian.CartesianTest.Values;
|
||||
|
||||
@@ -224,6 +230,11 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
clock.setTo(DateTime.parse("1999-04-03T22:00:00.0Z").minus(Duration.millis(1)));
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
DatabaseMigrationStateSchedule.useUncachedForTest();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void initCreateTest() throws Exception {
|
||||
createTld("tld");
|
||||
@@ -390,7 +401,10 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
.that(reloadResourceByForeignKey())
|
||||
.hasSmdId(null)
|
||||
.and()
|
||||
.hasLaunchNotice(null);
|
||||
.hasLaunchNotice(null)
|
||||
.and()
|
||||
.hasLordnPhase(LordnPhase.NONE);
|
||||
assertNoTasksEnqueued(QUEUE_CLAIMS, QUEUE_SUNRISE);
|
||||
assertNoTasksEnqueued(QUEUE_CLAIMS, QUEUE_SUNRISE);
|
||||
}
|
||||
|
||||
@@ -400,14 +414,20 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
.hasSmdId(SMD_ID)
|
||||
.and()
|
||||
.hasLaunchNotice(null);
|
||||
String expectedPayload =
|
||||
String.format(
|
||||
"%s,%s,%s,1,%s",
|
||||
reloadResourceByForeignKey().getRepoId(),
|
||||
domainName,
|
||||
SMD_ID,
|
||||
SMD_VALID_TIME.plusMillis(17));
|
||||
assertTasksEnqueued(QUEUE_SUNRISE, new TaskMatcher().payload(expectedPayload));
|
||||
if (DatabaseMigrationStateSchedule.getValueAtTime(clock.nowUtc())
|
||||
.equals(MigrationState.NORDN_SQL)) {
|
||||
assertAboutDomains().that(reloadResourceByForeignKey()).hasLordnPhase(LordnPhase.SUNRISE);
|
||||
} else {
|
||||
String expectedPayload =
|
||||
String.format(
|
||||
"%s,%s,%s,1,%s",
|
||||
reloadResourceByForeignKey().getRepoId(),
|
||||
domainName,
|
||||
SMD_ID,
|
||||
SMD_VALID_TIME.plusMillis(17));
|
||||
assertTasksEnqueued(QUEUE_SUNRISE, new TaskMatcher().payload(expectedPayload));
|
||||
assertAboutDomains().that(reloadResourceByForeignKey()).hasLordnPhase(LordnPhase.NONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertClaimsLordn() throws Exception {
|
||||
@@ -421,13 +441,19 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
"tmch",
|
||||
DateTime.parse("2010-08-16T09:00:00.0Z"),
|
||||
DateTime.parse("2009-08-16T09:00:00.0Z")));
|
||||
TaskMatcher task =
|
||||
new TaskMatcher()
|
||||
.payload(
|
||||
reloadResourceByForeignKey().getRepoId()
|
||||
+ ",example-one.tld,370d0b7c9223372036854775807,1,"
|
||||
+ "2009-08-16T09:00:00.017Z,2009-08-16T09:00:00.000Z");
|
||||
assertTasksEnqueued(QUEUE_CLAIMS, task);
|
||||
if (DatabaseMigrationStateSchedule.getValueAtTime(clock.nowUtc())
|
||||
.equals(MigrationState.NORDN_SQL)) {
|
||||
assertAboutDomains().that(reloadResourceByForeignKey()).hasLordnPhase(LordnPhase.CLAIMS);
|
||||
} else {
|
||||
TaskMatcher task =
|
||||
new TaskMatcher()
|
||||
.payload(
|
||||
reloadResourceByForeignKey().getRepoId()
|
||||
+ ",example-one.tld,370d0b7c9223372036854775807,1,"
|
||||
+ "2009-08-16T09:00:00.017Z,2009-08-16T09:00:00.000Z");
|
||||
assertTasksEnqueued(QUEUE_CLAIMS, task);
|
||||
assertAboutDomains().that(reloadResourceByForeignKey()).hasLordnPhase(LordnPhase.NONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void doSuccessfulTest(
|
||||
@@ -677,7 +703,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
doSuccessfulTest(
|
||||
"tld", "domain_create_response_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6"));
|
||||
"tld",
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.6", "FEE", "26.00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -685,7 +713,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
doSuccessfulTest(
|
||||
"tld", "domain_create_response_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11"));
|
||||
"tld",
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.11", "FEE", "26.00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -693,7 +723,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
doSuccessfulTest(
|
||||
"tld", "domain_create_response_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12"));
|
||||
"tld",
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.12", "FEE", "26.00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -701,7 +733,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
setEppInput("domain_create_fee_defaults.xml", ImmutableMap.of("FEE_VERSION", "0.6"));
|
||||
persistContactsAndHosts();
|
||||
doSuccessfulTest(
|
||||
"tld", "domain_create_response_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6"));
|
||||
"tld",
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.6", "FEE", "26.00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -709,7 +743,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
setEppInput("domain_create_fee_defaults.xml", ImmutableMap.of("FEE_VERSION", "0.11"));
|
||||
persistContactsAndHosts();
|
||||
doSuccessfulTest(
|
||||
"tld", "domain_create_response_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11"));
|
||||
"tld",
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.11", "FEE", "26.00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -717,7 +753,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
setEppInput("domain_create_fee_defaults.xml", ImmutableMap.of("FEE_VERSION", "0.12"));
|
||||
persistContactsAndHosts();
|
||||
doSuccessfulTest(
|
||||
"tld", "domain_create_response_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12"));
|
||||
"tld",
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.12", "FEE", "26.00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -939,8 +977,12 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_claimsNotice() throws Exception {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testSuccess_claimsNotice(boolean usePullQueue) throws Exception {
|
||||
if (!usePullQueue) {
|
||||
useNordnSql();
|
||||
}
|
||||
clock.setTo(DateTime.parse("2009-08-16T09:00:00.0Z"));
|
||||
setEppInput("domain_create_claim_notice.xml");
|
||||
persistContactsAndHosts();
|
||||
@@ -950,8 +992,12 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
assertClaimsLordn();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_claimsNoticeInQuietPeriod() throws Exception {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testSuccess_claimsNoticeInQuietPeriod(boolean usePullQueue) throws Exception {
|
||||
if (!usePullQueue) {
|
||||
useNordnSql();
|
||||
}
|
||||
allocationToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
@@ -1077,6 +1123,57 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_wrongFeeAmountTooHigh_defaultToken_v06() throws Exception {
|
||||
AllocationToken defaultToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("bbbbb")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.setDiscountFraction(0.5)
|
||||
.build());
|
||||
persistResource(
|
||||
Registry.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
|
||||
.setCreateBillingCost(Money.of(USD, 8))
|
||||
.build());
|
||||
// Expects fee of $26
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
// $12 is equal to 50% off the first year registration and 0% 0ff the 2nd year
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.6", "FEE", "12.00")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmountTooLow_defaultToken_v06() throws Exception {
|
||||
AllocationToken defaultToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("bbbbb")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.setDiscountFraction(0.5)
|
||||
.build());
|
||||
persistResource(
|
||||
Registry.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
|
||||
.setCreateBillingCost(Money.of(USD, 100))
|
||||
.build());
|
||||
// Expects fee of $26
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v11() {
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11", "CURRENCY", "USD"));
|
||||
@@ -1087,6 +1184,57 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_wrongFeeAmountTooHigh_defaultToken_v11() throws Exception {
|
||||
AllocationToken defaultToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("bbbbb")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.setDiscountFraction(0.5)
|
||||
.build());
|
||||
persistResource(
|
||||
Registry.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
|
||||
.setCreateBillingCost(Money.of(USD, 8))
|
||||
.build());
|
||||
// Expects fee of $26
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
// $12 is equal to 50% off the first year registration and 0% 0ff the 2nd year
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.11", "FEE", "12.00")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmountTooLow_defaultToken_v11() throws Exception {
|
||||
AllocationToken defaultToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("bbbbb")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.setDiscountFraction(0.5)
|
||||
.build());
|
||||
persistResource(
|
||||
Registry.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
|
||||
.setCreateBillingCost(Money.of(USD, 100))
|
||||
.build());
|
||||
// Expects fee of $26
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v12() {
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "USD"));
|
||||
@@ -1097,6 +1245,57 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_wrongFeeAmountTooHigh_defaultToken_v12() throws Exception {
|
||||
AllocationToken defaultToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("bbbbb")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.setDiscountFraction(0.5)
|
||||
.build());
|
||||
persistResource(
|
||||
Registry.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
|
||||
.setCreateBillingCost(Money.of(USD, 8))
|
||||
.build());
|
||||
// Expects fee of $26
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
// $12 is equal to 50% off the first year registration and 0% 0ff the 2nd year
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.12", "FEE", "12.00")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmountTooLow_defaultToken_v12() throws Exception {
|
||||
AllocationToken defaultToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("bbbbb")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.setDiscountFraction(0.5)
|
||||
.build());
|
||||
persistResource(
|
||||
Registry.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
|
||||
.setCreateBillingCost(Money.of(USD, 100))
|
||||
.build());
|
||||
// Expects fee of $26
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongCurrency_v06() {
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6", "CURRENCY", "EUR"));
|
||||
@@ -1220,8 +1419,12 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
assertAllocationTokenWasRedeemed("abcDEF23456");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_anchorTenant_withClaims() throws Exception {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testSuccess_anchorTenant_withClaims(boolean usePullQueue) throws Exception {
|
||||
if (!usePullQueue) {
|
||||
useNordnSql();
|
||||
}
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setDomainName("example-one.tld")
|
||||
@@ -1289,8 +1492,12 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
assertSuccessfulCreate("tld", ImmutableSet.of(SUNRISE, ANCHOR_TENANT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_anchorTenantInSunrise_withSignedMark() throws Exception {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testSuccess_anchorTenantInSunrise_withSignedMark(boolean usePullQueue) throws Exception {
|
||||
if (!usePullQueue) {
|
||||
useNordnSql();
|
||||
}
|
||||
allocationToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
@@ -1890,9 +2097,13 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
assertSuccessfulCreate("tld", ImmutableSet.of(RESERVED));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_reservedNameCollisionDomain_inSunrise_setsServerHoldAndPollMessage()
|
||||
throws Exception {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testSuccess_reservedNameCollisionDomain_inSunrise_setsServerHoldAndPollMessage(
|
||||
boolean usePullQueue) throws Exception {
|
||||
if (!usePullQueue) {
|
||||
useNordnSql();
|
||||
}
|
||||
persistResource(
|
||||
Registry.get("tld")
|
||||
.asBuilder()
|
||||
@@ -2435,8 +2646,13 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
"tld", "domain_create_response.xml", SUPERUSER, ImmutableMap.of("DOMAIN", "example.tld"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_startDateSunriseRegistration_withEncodedSignedMark() throws Exception {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testSuccess_startDateSunriseRegistration_withEncodedSignedMark(boolean usePullQueue)
|
||||
throws Exception {
|
||||
if (!usePullQueue) {
|
||||
useNordnSql();
|
||||
}
|
||||
createTld("tld", START_DATE_SUNRISE);
|
||||
clock.setTo(SMD_VALID_TIME);
|
||||
setEppInput(
|
||||
@@ -2458,8 +2674,13 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
}
|
||||
|
||||
/** Test that missing type= argument on launch create works in start-date sunrise. */
|
||||
@Test
|
||||
void testSuccess_startDateSunriseRegistration_withEncodedSignedMark_noType() throws Exception {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testSuccess_startDateSunriseRegistration_withEncodedSignedMark_noType(boolean usePullQueue)
|
||||
throws Exception {
|
||||
if (!usePullQueue) {
|
||||
useNordnSql();
|
||||
}
|
||||
createTld("tld", START_DATE_SUNRISE);
|
||||
clock.setTo(SMD_VALID_TIME);
|
||||
setEppInput(
|
||||
@@ -3512,4 +3733,97 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
.isEqualTo(
|
||||
"The package token abc123 cannot be used to register names for longer than 1 year.");
|
||||
}
|
||||
|
||||
private static void useNordnSql() {
|
||||
tm().transact(
|
||||
() ->
|
||||
DatabaseMigrationStateSchedule.set(
|
||||
new ImmutableSortedMap.Builder<DateTime, MigrationState>(Ordering.natural())
|
||||
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
|
||||
.put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
|
||||
.build()));
|
||||
tm().transact(
|
||||
() ->
|
||||
DatabaseMigrationStateSchedule.set(
|
||||
new ImmutableSortedMap.Builder<DateTime, MigrationState>(Ordering.natural())
|
||||
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
|
||||
.put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
|
||||
.put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
|
||||
.build()));
|
||||
tm().transact(
|
||||
() ->
|
||||
DatabaseMigrationStateSchedule.set(
|
||||
new ImmutableSortedMap.Builder<DateTime, MigrationState>(Ordering.natural())
|
||||
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
|
||||
.put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
|
||||
.put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
|
||||
.put(
|
||||
START_OF_TIME.plusMillis(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
|
||||
.build()));
|
||||
tm().transact(
|
||||
() ->
|
||||
DatabaseMigrationStateSchedule.set(
|
||||
new ImmutableSortedMap.Builder<DateTime, MigrationState>(Ordering.natural())
|
||||
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
|
||||
.put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
|
||||
.put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
|
||||
.put(
|
||||
START_OF_TIME.plusMillis(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
|
||||
.put(START_OF_TIME.plusMillis(4), MigrationState.SQL_PRIMARY_READ_ONLY)
|
||||
.build()));
|
||||
tm().transact(
|
||||
() ->
|
||||
DatabaseMigrationStateSchedule.set(
|
||||
new ImmutableSortedMap.Builder<DateTime, MigrationState>(Ordering.natural())
|
||||
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
|
||||
.put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
|
||||
.put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
|
||||
.put(
|
||||
START_OF_TIME.plusMillis(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
|
||||
.put(START_OF_TIME.plusMillis(4), MigrationState.SQL_PRIMARY_READ_ONLY)
|
||||
.put(START_OF_TIME.plusMillis(5), MigrationState.SQL_PRIMARY)
|
||||
.build()));
|
||||
tm().transact(
|
||||
() ->
|
||||
DatabaseMigrationStateSchedule.set(
|
||||
new ImmutableSortedMap.Builder<DateTime, MigrationState>(Ordering.natural())
|
||||
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
|
||||
.put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
|
||||
.put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
|
||||
.put(
|
||||
START_OF_TIME.plusMillis(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
|
||||
.put(START_OF_TIME.plusMillis(4), MigrationState.SQL_PRIMARY_READ_ONLY)
|
||||
.put(START_OF_TIME.plusMillis(5), MigrationState.SQL_PRIMARY)
|
||||
.put(START_OF_TIME.plusMillis(6), MigrationState.SQL_ONLY)
|
||||
.build()));
|
||||
tm().transact(
|
||||
() ->
|
||||
DatabaseMigrationStateSchedule.set(
|
||||
new ImmutableSortedMap.Builder<DateTime, MigrationState>(Ordering.natural())
|
||||
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
|
||||
.put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
|
||||
.put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
|
||||
.put(
|
||||
START_OF_TIME.plusMillis(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
|
||||
.put(START_OF_TIME.plusMillis(4), MigrationState.SQL_PRIMARY_READ_ONLY)
|
||||
.put(START_OF_TIME.plusMillis(5), MigrationState.SQL_PRIMARY)
|
||||
.put(START_OF_TIME.plusMillis(6), MigrationState.SQL_ONLY)
|
||||
.put(START_OF_TIME.plusMillis(7), MigrationState.SEQUENCE_BASED_ALLOCATE_ID)
|
||||
.build()));
|
||||
tm().transact(
|
||||
() ->
|
||||
DatabaseMigrationStateSchedule.set(
|
||||
new ImmutableSortedMap.Builder<DateTime, MigrationState>(Ordering.natural())
|
||||
.put(START_OF_TIME, MigrationState.DATASTORE_ONLY)
|
||||
.put(START_OF_TIME.plusMillis(1), MigrationState.DATASTORE_PRIMARY)
|
||||
.put(START_OF_TIME.plusMillis(2), MigrationState.DATASTORE_PRIMARY_NO_ASYNC)
|
||||
.put(
|
||||
START_OF_TIME.plusMillis(3), MigrationState.DATASTORE_PRIMARY_READ_ONLY)
|
||||
.put(START_OF_TIME.plusMillis(4), MigrationState.SQL_PRIMARY_READ_ONLY)
|
||||
.put(START_OF_TIME.plusMillis(5), MigrationState.SQL_PRIMARY)
|
||||
.put(START_OF_TIME.plusMillis(6), MigrationState.SQL_ONLY)
|
||||
.put(START_OF_TIME.plusMillis(7), MigrationState.SEQUENCE_BASED_ALLOCATE_ID)
|
||||
.put(START_OF_TIME.plusMillis(8), MigrationState.NORDN_SQL)
|
||||
.build()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,11 +72,7 @@ public class UserTest extends EntityTestCase {
|
||||
assertThat(assertThrows(IllegalArgumentException.class, () -> builder.setUserRoles(null)))
|
||||
.hasMessageThat()
|
||||
.isEqualTo("User roles cannot be null");
|
||||
|
||||
assertThat(assertThrows(IllegalArgumentException.class, builder::build))
|
||||
.hasMessageThat()
|
||||
.isEqualTo("Gaia ID cannot be null");
|
||||
builder.setGaiaId("gaiaId");
|
||||
|
||||
assertThat(assertThrows(IllegalArgumentException.class, builder::build))
|
||||
.hasMessageThat()
|
||||
.isEqualTo("Email address cannot be null");
|
||||
|
||||
@@ -221,7 +221,6 @@ public class DomainTest {
|
||||
"TheRegistrar",
|
||||
oneTimeBillKey))
|
||||
.setAutorenewEndTime(Optional.of(fakeClock.nowUtc().plusYears(2)))
|
||||
.setDnsRefreshRequestTime(Optional.of(fakeClock.nowUtc()))
|
||||
.build()));
|
||||
}
|
||||
|
||||
|
||||
@@ -41,8 +41,6 @@ import google.registry.model.reporting.DomainTransactionRecord;
|
||||
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.util.SerializeUtils;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -97,7 +95,6 @@ public class DomainHistoryTest extends EntityTestCase {
|
||||
.asBuilder()
|
||||
.setNameservers(host.createVKey())
|
||||
.setDsData(ImmutableSet.of(DomainDsData.create(1, 2, 3, new byte[] {0, 1, 2})))
|
||||
.setDnsRefreshRequestTime(Optional.of(DateTime.parse("2020-03-09T16:40:00Z")))
|
||||
.build();
|
||||
insertInDb(domain);
|
||||
return domain;
|
||||
|
||||
@@ -18,17 +18,14 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth8.assertThat;
|
||||
import static google.registry.model.server.Lock.LockState.FREE;
|
||||
import static google.registry.model.server.Lock.LockState.IN_USE;
|
||||
import static google.registry.model.server.Lock.LockState.OWNER_DIED;
|
||||
import static google.registry.model.server.Lock.LockState.TIMED_OUT;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import google.registry.model.EntityTestCase;
|
||||
import google.registry.model.server.Lock.LockState;
|
||||
import google.registry.util.RequestStatusChecker;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.Duration;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@@ -41,7 +38,6 @@ public class LockTest extends EntityTestCase {
|
||||
private static final String RESOURCE_NAME = "foo";
|
||||
private static final Duration ONE_DAY = Duration.standardDays(1);
|
||||
private static final Duration TWO_MILLIS = Duration.millis(2);
|
||||
private static final RequestStatusChecker requestStatusChecker = mock(RequestStatusChecker.class);
|
||||
|
||||
private LockMetrics origLockMetrics;
|
||||
|
||||
@@ -52,7 +48,7 @@ public class LockTest extends EntityTestCase {
|
||||
private static Optional<Lock> acquire(
|
||||
String tld, Duration leaseLength, LockState expectedLockState) {
|
||||
Lock.lockMetrics = mock(LockMetrics.class);
|
||||
Optional<Lock> lock = Lock.acquire(RESOURCE_NAME, tld, leaseLength, requestStatusChecker, true);
|
||||
Optional<Lock> lock = Lock.acquire(RESOURCE_NAME, tld, leaseLength);
|
||||
verify(Lock.lockMetrics).recordAcquire(RESOURCE_NAME, tld, expectedLockState);
|
||||
verifyNoMoreInteractions(Lock.lockMetrics);
|
||||
Lock.lockMetrics = null;
|
||||
@@ -72,8 +68,6 @@ public class LockTest extends EntityTestCase {
|
||||
void beforeEach() {
|
||||
origLockMetrics = Lock.lockMetrics;
|
||||
Lock.lockMetrics = null;
|
||||
when(requestStatusChecker.getLogId()).thenReturn("current-request-id");
|
||||
when(requestStatusChecker.isRunning("current-request-id")).thenReturn(true);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@@ -111,9 +105,6 @@ public class LockTest extends EntityTestCase {
|
||||
assertThat(acquire("", ONE_DAY, FREE)).isPresent();
|
||||
// We can't get it again while request is active
|
||||
assertThat(acquire("", ONE_DAY, IN_USE)).isEmpty();
|
||||
// But if request is finished, we can get it.
|
||||
when(requestStatusChecker.isRunning("current-request-id")).thenReturn(false);
|
||||
assertThat(acquire("", ONE_DAY, OWNER_DIED)).isPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -134,9 +125,7 @@ public class LockTest extends EntityTestCase {
|
||||
@Test
|
||||
void testFailure_emptyResourceName() {
|
||||
IllegalArgumentException thrown =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> Lock.acquire("", "", TWO_MILLIS, requestStatusChecker, true));
|
||||
assertThrows(IllegalArgumentException.class, () -> Lock.acquire("", "", TWO_MILLIS));
|
||||
assertThat(thrown).hasMessageThat().contains("resourceName cannot be null or empty");
|
||||
}
|
||||
}
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
// 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.request.auth;
|
||||
|
||||
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload;
|
||||
import com.google.api.client.json.webtoken.JsonWebSignature;
|
||||
import com.google.api.client.json.webtoken.JsonWebSignature.Header;
|
||||
import com.google.auth.oauth2.TokenVerifier;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class ServiceAccountAuthenticationMechanismTest {
|
||||
|
||||
@Mock private TokenVerifier tokenVerifier;
|
||||
@Mock private HttpServletRequest request;
|
||||
|
||||
private JsonWebSignature token;
|
||||
private ServiceAccountAuthenticationMechanism serviceAccountAuthenticationMechanism;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() throws Exception {
|
||||
serviceAccountAuthenticationMechanism =
|
||||
new ServiceAccountAuthenticationMechanism(tokenVerifier, "sa-prefix@email.com");
|
||||
when(request.getHeader(AUTHORIZATION)).thenReturn("Bearer jwtValue");
|
||||
Payload payload = new Payload();
|
||||
payload.setEmail("sa-prefix@email.com");
|
||||
token = new JsonWebSignature(new Header(), payload, new byte[0], new byte[0]);
|
||||
when(tokenVerifier.verify("jwtValue")).thenReturn(token);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_authenticates() throws Exception {
|
||||
AuthResult authResult = serviceAccountAuthenticationMechanism.authenticate(request);
|
||||
assertThat(authResult.isAuthenticated()).isTrue();
|
||||
assertThat(authResult.authLevel()).isEqualTo(AuthLevel.APP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFails_authenticateWrongEmail() throws Exception {
|
||||
token.getPayload().set("email", "not-service-account-email@email.com");
|
||||
AuthResult authResult = serviceAccountAuthenticationMechanism.authenticate(request);
|
||||
assertThat(authResult.isAuthenticated()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFails_authenticateWrongHeader() throws Exception {
|
||||
when(request.getHeader(AUTHORIZATION)).thenReturn("BEARER asd");
|
||||
AuthResult authResult = serviceAccountAuthenticationMechanism.authenticate(request);
|
||||
assertThat(authResult.isAuthenticated()).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import static org.mockito.Mockito.verify;
|
||||
import google.registry.model.server.Lock;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.UserServiceExtension;
|
||||
import google.registry.util.RequestStatusCheckerImpl;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
@@ -134,7 +133,7 @@ final class LockHandlerImplTest {
|
||||
}
|
||||
|
||||
private LockHandler createTestLockHandler(@Nullable Lock acquiredLock) {
|
||||
return new LockHandlerImpl(new RequestStatusCheckerImpl(), clock) {
|
||||
return new LockHandlerImpl(clock) {
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
@Override
|
||||
|
||||
@@ -312,6 +312,7 @@ public final class DatabaseHelper {
|
||||
return persistResource(domain.asBuilder().setDeletionTime(deletionTime).build());
|
||||
}
|
||||
|
||||
// TODO: delete after pull queue migration.
|
||||
/** Persists a domain and enqueues a LORDN task of the appropriate type for it. */
|
||||
public static Domain persistDomainAndEnqueueLordn(final Domain domain) {
|
||||
final Domain persistedDomain = persistResource(domain);
|
||||
|
||||
@@ -27,6 +27,7 @@ import google.registry.model.domain.launch.LaunchNotice;
|
||||
import google.registry.model.domain.secdns.DomainDsData;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.testing.TruthChainer.And;
|
||||
import google.registry.tmch.LordnTaskUtils.LordnPhase;
|
||||
import java.util.Set;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
@@ -41,7 +42,7 @@ public final class DomainSubject extends AbstractEppResourceSubject<Domain, Doma
|
||||
}
|
||||
|
||||
public And<DomainSubject> hasDomainName(String domainName) {
|
||||
return hasValue(domainName, actual.getDomainName(), "has domainName");
|
||||
return hasValue(domainName, actual.getDomainName(), "domainName");
|
||||
}
|
||||
|
||||
public And<DomainSubject> hasExactlyDsData(DomainDsData... dsData) {
|
||||
@@ -49,15 +50,19 @@ public final class DomainSubject extends AbstractEppResourceSubject<Domain, Doma
|
||||
}
|
||||
|
||||
public And<DomainSubject> hasExactlyDsData(Set<DomainDsData> dsData) {
|
||||
return hasValue(dsData, actual.getDsData(), "has dsData");
|
||||
return hasValue(dsData, actual.getDsData(), "dsData");
|
||||
}
|
||||
|
||||
public And<DomainSubject> hasNumDsData(int num) {
|
||||
return hasValue(num, actual.getDsData().size(), "has num dsData");
|
||||
return hasValue(num, actual.getDsData().size(), "dsData.size()");
|
||||
}
|
||||
|
||||
public And<DomainSubject> hasLaunchNotice(LaunchNotice launchNotice) {
|
||||
return hasValue(launchNotice, actual.getLaunchNotice(), "has launchNotice");
|
||||
return hasValue(launchNotice, actual.getLaunchNotice(), "launchNotice");
|
||||
}
|
||||
|
||||
public And<DomainSubject> hasLordnPhase(LordnPhase lordnPhase) {
|
||||
return hasValue(lordnPhase, actual.getLordnPhase(), "lordnPhase");
|
||||
}
|
||||
|
||||
public And<DomainSubject> hasAuthInfoPwd(String pw) {
|
||||
@@ -67,7 +72,7 @@ public final class DomainSubject extends AbstractEppResourceSubject<Domain, Doma
|
||||
|
||||
public And<DomainSubject> hasCurrentSponsorRegistrarId(String registrarId) {
|
||||
return hasValue(
|
||||
registrarId, actual.getCurrentSponsorRegistrarId(), "has currentSponsorRegistrarId");
|
||||
registrarId, actual.getCurrentSponsorRegistrarId(), "currentSponsorRegistrarId");
|
||||
}
|
||||
|
||||
public And<DomainSubject> hasRegistrationExpirationTime(DateTime expiration) {
|
||||
|
||||
@@ -14,13 +14,17 @@
|
||||
|
||||
package google.registry.tmch;
|
||||
|
||||
import static com.google.appengine.api.taskqueue.QueueFactory.getQueue;
|
||||
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
|
||||
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
|
||||
import static com.google.common.net.HttpHeaders.LOCATION;
|
||||
import static com.google.common.net.MediaType.FORM_DATA;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.ForeignKeyUtils.load;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.loadByKey;
|
||||
import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.newDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistDomainAndEnqueueLordn;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
@@ -34,6 +38,7 @@ import static org.mockito.ArgumentMatchers.startsWith;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.appengine.api.taskqueue.LeaseOptions;
|
||||
@@ -48,15 +53,17 @@ import com.google.common.collect.ImmutableList;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.launch.LaunchNotice;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.request.RequestParameters;
|
||||
import google.registry.testing.CloudTasksHelper;
|
||||
import google.registry.testing.CloudTasksHelper.TaskMatcher;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeSleeper;
|
||||
import google.registry.testing.FakeUrlConnectionService;
|
||||
import google.registry.testing.TaskQueueExtension;
|
||||
import google.registry.tmch.LordnTaskUtils.LordnPhase;
|
||||
import google.registry.util.CloudTasksUtils;
|
||||
import google.registry.util.Retrier;
|
||||
import google.registry.util.UrlConnectionException;
|
||||
@@ -67,25 +74,32 @@ import java.net.URL;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
/** Unit tests for {@link NordnUploadAction}. */
|
||||
class NordnUploadActionTest {
|
||||
|
||||
private static final String CLAIMS_CSV =
|
||||
"1,2010-05-01T10:11:12.000Z,1\n"
|
||||
"1,2010-05-04T10:11:12.000Z,2\n"
|
||||
+ "roid,domain-name,notice-id,registrar-id,registration-datetime,ack-datetime,"
|
||||
+ "application-datetime\n"
|
||||
+ "2-TLD,claims-landrush1.tld,landrush1tcn,99999,2010-05-01T10:11:12.000Z,"
|
||||
+ "1969-12-31T23:00:00.000Z\n";
|
||||
+ "6-TLD,claims-landrush2.tld,landrush2tcn,88888,2010-05-03T10:11:12.000Z,"
|
||||
+ "2010-05-03T08:11:12.000Z\n"
|
||||
+ "8-TLD,claims-landrush1.tld,landrush1tcn,99999,2010-05-04T10:11:12.000Z,"
|
||||
+ "2010-05-04T09:11:12.000Z\n";
|
||||
|
||||
private static final String SUNRISE_CSV =
|
||||
"1,2010-05-01T10:11:12.000Z,1\n"
|
||||
"1,2010-05-04T10:11:12.000Z,2\n"
|
||||
+ "roid,domain-name,SMD-id,registrar-id,registration-datetime,application-datetime\n"
|
||||
+ "2-TLD,sunrise1.tld,my-smdid,99999,2010-05-01T10:11:12.000Z\n";
|
||||
+ "2-TLD,sunrise2.tld,new-smdid,88888,2010-05-01T10:11:12.000Z\n"
|
||||
+ "4-TLD,sunrise1.tld,my-smdid,99999,2010-05-02T10:11:12.000Z\n";
|
||||
|
||||
private static final String LOCATION_URL = "http://trololol";
|
||||
|
||||
@@ -116,8 +130,12 @@ class NordnUploadActionTest {
|
||||
when(httpUrlConnection.getHeaderField(LOCATION)).thenReturn("http://trololol");
|
||||
when(httpUrlConnection.getOutputStream()).thenReturn(connectionOutputStream);
|
||||
persistResource(loadRegistrar("TheRegistrar").asBuilder().setIanaIdentifier(99999L).build());
|
||||
persistResource(loadRegistrar("NewRegistrar").asBuilder().setIanaIdentifier(88888L).build());
|
||||
createTld("tld");
|
||||
persistResource(Registry.get("tld").asBuilder().setLordnUsername("lolcat").build());
|
||||
persistSunriseModeDomain();
|
||||
clock.advanceBy(Duration.standardDays(1));
|
||||
persistClaimsModeDomain();
|
||||
action.clock = clock;
|
||||
action.cloudTasksUtils = cloudTasksUtils;
|
||||
action.urlConnectionService = urlConnectionService;
|
||||
@@ -127,6 +145,7 @@ class NordnUploadActionTest {
|
||||
action.tmchMarksdbUrl = "http://127.0.0.1";
|
||||
action.random = new SecureRandom();
|
||||
action.retrier = new Retrier(new FakeSleeper(clock), 3);
|
||||
action.usePullQueue = Optional.empty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -137,7 +156,7 @@ class NordnUploadActionTest {
|
||||
makeTaskHandle("task1", "example", "csvLine1", "lordn-sunrise"),
|
||||
makeTaskHandle("task3", "example", "ending", "lordn-sunrise"));
|
||||
assertThat(NordnUploadAction.convertTasksToCsv(tasks, clock.nowUtc(), "col1,col2"))
|
||||
.isEqualTo("1,2010-05-01T10:11:12.000Z,3\ncol1,col2\ncsvLine1\ncsvLine2\nending\n");
|
||||
.isEqualTo("1,2010-05-04T10:11:12.000Z,3\ncol1,col2\ncsvLine1\ncsvLine2\nending\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -149,13 +168,13 @@ class NordnUploadActionTest {
|
||||
makeTaskHandle("task3", "example", "ending", "lordn-sunrise"),
|
||||
makeTaskHandle("task1", "example", "csvLine1", "lordn-sunrise"));
|
||||
assertThat(NordnUploadAction.convertTasksToCsv(tasks, clock.nowUtc(), "col1,col2"))
|
||||
.isEqualTo("1,2010-05-01T10:11:12.000Z,3\ncol1,col2\ncsvLine1\ncsvLine2\nending\n");
|
||||
.isEqualTo("1,2010-05-04T10:11:12.000Z,3\ncol1,col2\ncsvLine1\ncsvLine2\nending\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_convertTasksToCsv_doesntFailOnEmptyTasks() {
|
||||
assertThat(NordnUploadAction.convertTasksToCsv(ImmutableList.of(), clock.nowUtc(), "col1,col2"))
|
||||
.isEqualTo("1,2010-05-01T10:11:12.000Z,0\ncol1,col2\n");
|
||||
.isEqualTo("1,2010-05-04T10:11:12.000Z,0\ncol1,col2\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -187,122 +206,105 @@ class NordnUploadActionTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_claimsMode_appendsTldAndClaimsToRequestUrl() throws Exception {
|
||||
persistClaimsModeDomain();
|
||||
action.run();
|
||||
assertThat(httpUrlConnection.getURL()).isEqualTo(new URL("http://127.0.0.1/LORDN/tld/claims"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_sunriseMode_appendsTldAndClaimsToRequestUrl() throws Exception {
|
||||
persistSunriseModeDomain();
|
||||
action.run();
|
||||
assertThat(httpUrlConnection.getURL()).isEqualTo(new URL("http://127.0.0.1/LORDN/tld/sunrise"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_usesMultipartContentType() throws Exception {
|
||||
persistClaimsModeDomain();
|
||||
action.run();
|
||||
verify(httpUrlConnection)
|
||||
.setRequestProperty(eq(CONTENT_TYPE), startsWith("multipart/form-data; boundary="));
|
||||
verify(httpUrlConnection).setRequestMethod("POST");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_hasPassword_setsAuthorizationHeader() {
|
||||
persistClaimsModeDomain();
|
||||
action.run();
|
||||
verify(httpUrlConnection)
|
||||
.setRequestProperty(
|
||||
AUTHORIZATION, "Basic bG9sY2F0OmF0dGFjaw=="); // echo -n lolcat:attack | base64
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_noPassword_doesntSendAuthorizationHeader() {
|
||||
void testSuccess_noPassword_doesntSendAuthorizationHeader() {
|
||||
action.lordnRequestInitializer = new LordnRequestInitializer(Optional.empty());
|
||||
persistClaimsModeDomain();
|
||||
action.run();
|
||||
verify(httpUrlConnection, times(0)).setRequestProperty(eq(AUTHORIZATION), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_claimsMode_payloadMatchesClaimsCsv() {
|
||||
persistClaimsModeDomain();
|
||||
void testSuccess_nothingScheduled() {
|
||||
persistResource(
|
||||
loadByKey(load(Domain.class, "claims-landrush1.tld", clock.nowUtc()))
|
||||
.asBuilder()
|
||||
.setLordnPhase(LordnPhase.NONE)
|
||||
.build());
|
||||
persistResource(
|
||||
loadByKey(load(Domain.class, "claims-landrush2.tld", clock.nowUtc()))
|
||||
.asBuilder()
|
||||
.setLordnPhase(LordnPhase.NONE)
|
||||
.build());
|
||||
action.run();
|
||||
assertThat(connectionOutputStream.toString(UTF_8)).contains(CLAIMS_CSV);
|
||||
verifyNoInteractions(httpUrlConnection);
|
||||
cloudTasksHelper.assertNoTasksEnqueued(NordnVerifyAction.QUEUE);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testSuccess_claimsMode(boolean usePullQueue) throws Exception {
|
||||
testRun("claims", "claims-landrush1.tld", "claims-landrush2.tld", CLAIMS_CSV, usePullQueue);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testSuccess_sunriseMode(boolean usePullQueue) throws Exception {
|
||||
testRun("sunrise", "sunrise1.tld", "sunrise2.tld", SUNRISE_CSV, usePullQueue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_claimsMode_verifyTaskGetsEnqueuedWithClaimsCsv() {
|
||||
persistClaimsModeDomain();
|
||||
action.run();
|
||||
cloudTasksHelper.assertTasksEnqueued(
|
||||
NordnVerifyAction.QUEUE,
|
||||
new TaskMatcher()
|
||||
.url(NordnVerifyAction.PATH)
|
||||
.param(NordnVerifyAction.NORDN_URL_PARAM, LOCATION_URL)
|
||||
.header(CONTENT_TYPE, FORM_DATA.toString()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_sunriseMode_payloadMatchesSunriseCsv() {
|
||||
persistSunriseModeDomain();
|
||||
action.run();
|
||||
assertThat(connectionOutputStream.toString(UTF_8)).contains(SUNRISE_CSV);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_noResponseContent_stillWorksNormally() throws Exception {
|
||||
void testSuccess_noResponseContent_stillWorksNormally() throws Exception {
|
||||
// Returning null only affects logging.
|
||||
when(httpUrlConnection.getInputStream()).thenReturn(new ByteArrayInputStream(new byte[] {}));
|
||||
persistSunriseModeDomain();
|
||||
action.run();
|
||||
assertThat(connectionOutputStream.toString(UTF_8)).contains(SUNRISE_CSV);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_sunriseMode_verifyTaskGetsEnqueuedWithSunriseCsv() {
|
||||
persistSunriseModeDomain();
|
||||
action.run();
|
||||
cloudTasksHelper.assertTasksEnqueued(
|
||||
NordnVerifyAction.QUEUE,
|
||||
new TaskMatcher()
|
||||
.url(NordnVerifyAction.PATH)
|
||||
.param(NordnVerifyAction.NORDN_URL_PARAM, LOCATION_URL)
|
||||
.header(CONTENT_TYPE, FORM_DATA.toString()));
|
||||
testRun("claims", "claims-landrush1.tld", "claims-landrush2.tld", CLAIMS_CSV, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_nullRegistryUser() {
|
||||
persistClaimsModeDomain();
|
||||
persistResource(Registry.get("tld").asBuilder().setLordnUsername(null).build());
|
||||
VerifyException thrown = assertThrows(VerifyException.class, action::run);
|
||||
assertThat(thrown).hasMessageThat().contains("lordnUsername is not set for tld.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFetchFailure() throws Exception {
|
||||
persistClaimsModeDomain();
|
||||
void testFailure_errorResponseCode() throws Exception {
|
||||
when(httpUrlConnection.getResponseCode()).thenReturn(SC_INTERNAL_SERVER_ERROR);
|
||||
assertThrows(UrlConnectionException.class, action::run);
|
||||
}
|
||||
|
||||
private static void persistClaimsModeDomain() {
|
||||
Domain domain = DatabaseHelper.newDomain("claims-landrush1.tld");
|
||||
@Test
|
||||
void testFailure_noLocationHeaderInResponse() throws Exception {
|
||||
when(httpUrlConnection.getHeaderField(LOCATION)).thenReturn(null);
|
||||
assertThrows(UrlConnectionException.class, action::run);
|
||||
}
|
||||
|
||||
private void persistClaimsModeDomain() {
|
||||
persistDomainAndEnqueueLordn(
|
||||
domain
|
||||
newDomain("claims-landrush2.tld")
|
||||
.asBuilder()
|
||||
.setCreationTimeForTest(clock.nowUtc())
|
||||
.setCreationRegistrarId("NewRegistrar")
|
||||
.setLaunchNotice(
|
||||
LaunchNotice.create(
|
||||
"landrush1tcn", null, null, domain.getCreationTime().minusHours(1)))
|
||||
LaunchNotice.create("landrush2tcn", null, null, clock.nowUtc().minusHours(2)))
|
||||
.setLordnPhase(LordnPhase.CLAIMS)
|
||||
.build());
|
||||
clock.advanceBy(Duration.standardDays(1));
|
||||
persistDomainAndEnqueueLordn(
|
||||
newDomain("claims-landrush1.tld")
|
||||
.asBuilder()
|
||||
.setCreationTimeForTest(clock.nowUtc())
|
||||
.setLaunchNotice(
|
||||
LaunchNotice.create("landrush1tcn", null, null, clock.nowUtc().minusHours(1)))
|
||||
.setLordnPhase(LordnPhase.CLAIMS)
|
||||
.build());
|
||||
}
|
||||
|
||||
private void persistSunriseModeDomain() {
|
||||
action.phase = "sunrise";
|
||||
Domain domain = DatabaseHelper.newDomain("sunrise1.tld");
|
||||
persistDomainAndEnqueueLordn(domain.asBuilder().setSmdId("my-smdid").build());
|
||||
persistDomainAndEnqueueLordn(
|
||||
newDomain("sunrise2.tld")
|
||||
.asBuilder()
|
||||
.setCreationTimeForTest(clock.nowUtc())
|
||||
.setCreationRegistrarId("NewRegistrar")
|
||||
.setSmdId("new-smdid")
|
||||
.setLordnPhase(LordnPhase.SUNRISE)
|
||||
.build());
|
||||
clock.advanceBy(Duration.standardDays(1));
|
||||
persistDomainAndEnqueueLordn(
|
||||
newDomain("sunrise1.tld")
|
||||
.asBuilder()
|
||||
.setCreationTimeForTest(clock.nowUtc())
|
||||
.setSmdId("my-smdid")
|
||||
.setLordnPhase(LordnPhase.SUNRISE)
|
||||
.build());
|
||||
}
|
||||
|
||||
private static TaskHandle makeTaskHandle(
|
||||
@@ -311,4 +313,46 @@ class NordnUploadActionTest {
|
||||
TaskOptions.Builder.withPayload(payload).method(Method.PULL).tag(tag).taskName(taskName),
|
||||
queue);
|
||||
}
|
||||
|
||||
private void verifyColumnCleared(String domainName) {
|
||||
VKey<Domain> domainKey = load(Domain.class, domainName, clock.nowUtc());
|
||||
Domain domain = loadByKey(domainKey);
|
||||
assertThat(domain.getLordnPhase()).isEqualTo(LordnPhase.NONE);
|
||||
}
|
||||
|
||||
private void testRun(
|
||||
String phase, String domain1, String domain2, String csv, boolean usePullQueue)
|
||||
throws Exception {
|
||||
action.usePullQueue = Optional.of(usePullQueue);
|
||||
action.phase = phase;
|
||||
action.run();
|
||||
verify(httpUrlConnection)
|
||||
.setRequestProperty(
|
||||
AUTHORIZATION, "Basic bG9sY2F0OmF0dGFjaw=="); // echo -n lolcat:attack | base64
|
||||
verify(httpUrlConnection)
|
||||
.setRequestProperty(eq(CONTENT_TYPE), startsWith("multipart/form-data; boundary="));
|
||||
verify(httpUrlConnection).setRequestMethod("POST");
|
||||
assertThat(httpUrlConnection.getURL())
|
||||
.isEqualTo(new URL("http://127.0.0.1/LORDN/tld/" + phase));
|
||||
assertThat(connectionOutputStream.toString(UTF_8)).contains(csv);
|
||||
if (!usePullQueue) {
|
||||
verifyColumnCleared(domain1);
|
||||
verifyColumnCleared(domain2);
|
||||
} else {
|
||||
assertThat(
|
||||
getQueue("lordn-" + phase)
|
||||
.leaseTasks(
|
||||
LeaseOptions.Builder.withTag("tld")
|
||||
.leasePeriod(1, TimeUnit.HOURS)
|
||||
.countLimit(100)))
|
||||
.isEmpty();
|
||||
}
|
||||
cloudTasksHelper.assertTasksEnqueued(
|
||||
NordnVerifyAction.QUEUE,
|
||||
new TaskMatcher()
|
||||
.url(NordnVerifyAction.PATH)
|
||||
.param(NordnVerifyAction.NORDN_URL_PARAM, LOCATION_URL)
|
||||
.param(RequestParameters.PARAM_TLD, "tld")
|
||||
.header(CONTENT_TYPE, FORM_DATA.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
// 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.tools;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import google.registry.model.console.GlobalRole;
|
||||
import google.registry.model.console.RegistrarRole;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserDao;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Tests for {@link CreateUserCommand}. */
|
||||
public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
|
||||
|
||||
@Test
|
||||
void testSuccess() throws Exception {
|
||||
runCommandForced("--email", "user@example.test");
|
||||
User onlyUser = Iterables.getOnlyElement(DatabaseHelper.loadAllOf(User.class));
|
||||
assertThat(onlyUser.getEmailAddress()).isEqualTo("user@example.test");
|
||||
assertThat(onlyUser.getUserRoles().isAdmin()).isFalse();
|
||||
assertThat(onlyUser.getUserRoles().getGlobalRole()).isEqualTo(GlobalRole.NONE);
|
||||
assertThat(onlyUser.getUserRoles().getRegistrarRoles()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_admin() throws Exception {
|
||||
runCommandForced("--email", "user@example.test", "--admin", "true");
|
||||
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_globalRole() throws Exception {
|
||||
runCommandForced("--email", "user@example.test", "--global_role", "FTE");
|
||||
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getGlobalRole())
|
||||
.isEqualTo(GlobalRole.FTE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_registrarRoles() throws Exception {
|
||||
runCommandForced(
|
||||
"--email",
|
||||
"user@example.test",
|
||||
"--registrar_roles",
|
||||
"TheRegistrar=ACCOUNT_MANAGER,NewRegistrar=PRIMARY_CONTACT");
|
||||
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getRegistrarRoles())
|
||||
.isEqualTo(
|
||||
ImmutableMap.of(
|
||||
"TheRegistrar",
|
||||
RegistrarRole.ACCOUNT_MANAGER,
|
||||
"NewRegistrar",
|
||||
RegistrarRole.PRIMARY_CONTACT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_alreadyExists() throws Exception {
|
||||
runCommandForced("--email", "user@example.test");
|
||||
assertThat(
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> runCommandForced("--email", "user@example.test")))
|
||||
.hasMessageThat()
|
||||
.isEqualTo("A user with email user@example.test already exists");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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.tools;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth8.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import google.registry.model.console.GlobalRole;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserDao;
|
||||
import google.registry.model.console.UserRoles;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Tests for {@link DeleteUserCommand}. */
|
||||
public class DeleteUserCommandTest extends CommandTestCase<DeleteUserCommand> {
|
||||
|
||||
@Test
|
||||
void testSuccess_deletesUser() throws Exception {
|
||||
User user =
|
||||
new User.Builder()
|
||||
.setEmailAddress("email@example.test")
|
||||
.setUserRoles(
|
||||
new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).setIsAdmin(true).build())
|
||||
.build();
|
||||
UserDao.saveUser(user);
|
||||
assertThat(UserDao.loadUser("email@example.test")).isPresent();
|
||||
runCommandForced("--email", "email@example.test");
|
||||
assertThat(UserDao.loadUser("email@example.test")).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_nonexistent() {
|
||||
assertThat(
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> runCommandForced("--email", "nonexistent@example.test")))
|
||||
.hasMessageThat()
|
||||
.isEqualTo("Email does not correspond to a valid user");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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.tools;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.console.GlobalRole;
|
||||
import google.registry.model.console.RegistrarRole;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserDao;
|
||||
import google.registry.model.console.UserRoles;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Tests for {@link UpdateUserCommand}. */
|
||||
public class UpdateUserCommandTest extends CommandTestCase<UpdateUserCommand> {
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() throws Exception {
|
||||
UserDao.saveUser(
|
||||
new User.Builder()
|
||||
.setEmailAddress("user@example.test")
|
||||
.setUserRoles(new UserRoles.Builder().build())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_admin() throws Exception {
|
||||
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isFalse();
|
||||
runCommandForced("--email", "user@example.test", "--admin", "true");
|
||||
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isTrue();
|
||||
runCommandForced("--email", "user@example.test", "--admin", "false");
|
||||
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_registrarRoles() throws Exception {
|
||||
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getRegistrarRoles())
|
||||
.isEmpty();
|
||||
runCommandForced(
|
||||
"--email",
|
||||
"user@example.test",
|
||||
"--registrar_roles",
|
||||
"TheRegistrar=ACCOUNT_MANAGER,NewRegistrar=PRIMARY_CONTACT");
|
||||
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getRegistrarRoles())
|
||||
.isEqualTo(
|
||||
ImmutableMap.of(
|
||||
"TheRegistrar",
|
||||
RegistrarRole.ACCOUNT_MANAGER,
|
||||
"NewRegistrar",
|
||||
RegistrarRole.PRIMARY_CONTACT));
|
||||
runCommandForced("--email", "user@example.test", "--registrar_roles", "");
|
||||
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getRegistrarRoles())
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_globalRole() throws Exception {
|
||||
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getGlobalRole())
|
||||
.isEqualTo(GlobalRole.NONE);
|
||||
runCommandForced("--email", "user@example.test", "--global_role", "FTE");
|
||||
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getGlobalRole())
|
||||
.isEqualTo(GlobalRole.FTE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_doesntExist() {
|
||||
assertThat(
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> runCommandForced("--email", "nonexistent@example.test")))
|
||||
.hasMessageThat()
|
||||
.isEqualTo("User nonexistent@example.test not found");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
// 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.ui.server.console;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import com.google.api.client.http.HttpStatusCodes;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.model.console.RegistrarRole;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.console.UserRoles;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.request.auth.AuthLevel;
|
||||
import google.registry.request.auth.AuthResult;
|
||||
import google.registry.request.auth.UserAuthInfo;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.util.UtilsModule;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
/** Tests for {@link google.registry.ui.server.console.ConsoleDomainGetAction}. */
|
||||
public class ConsoleDomainGetActionTest {
|
||||
|
||||
private static final Gson GSON = UtilsModule.provideGson();
|
||||
private static final FakeResponse RESPONSE = new FakeResponse();
|
||||
|
||||
@RegisterExtension
|
||||
final JpaTestExtensions.JpaIntegrationTestExtension jpa =
|
||||
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
createTld("tld");
|
||||
DatabaseHelper.persistActiveDomain("exists.tld");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fullJsonRepresentation() {
|
||||
ConsoleDomainGetAction action =
|
||||
createAction(
|
||||
AuthResult.create(
|
||||
AuthLevel.USER,
|
||||
UserAuthInfo.create(
|
||||
createUser(
|
||||
new UserRoles.Builder()
|
||||
.setRegistrarRoles(
|
||||
ImmutableMap.of("TheRegistrar", RegistrarRole.ACCOUNT_MANAGER))
|
||||
.build()))),
|
||||
"exists.tld");
|
||||
action.run();
|
||||
assertThat(RESPONSE.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_OK);
|
||||
assertThat(RESPONSE.getPayload())
|
||||
.isEqualTo(
|
||||
"{\"domainName\":\"exists.tld\",\"adminContact\":{\"key\":\"3-ROID\"},\"techContact\":"
|
||||
+ "{\"key\":\"3-ROID\"},\"registrantContact\":{\"key\":\"3-ROID\"},\"registrationExpirationTime\":"
|
||||
+ "\"294247-01-10T04:00:54.775Z\",\"lastTransferTime\":\"null\",\"repoId\":\"2-TLD\","
|
||||
+ "\"currentSponsorRegistrarId\":\"TheRegistrar\",\"creationRegistrarId\":\"TheRegistrar\","
|
||||
+ "\"creationTime\":{\"creationTime\":\"1970-01-01T00:00:00.000Z\"},\"lastEppUpdateTime\":\"null\","
|
||||
+ "\"statuses\":[\"INACTIVE\"]}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_emptyAuth() {
|
||||
ConsoleDomainGetAction action = createAction(AuthResult.NOT_AUTHENTICATED, "exists.tld");
|
||||
action.run();
|
||||
assertThat(RESPONSE.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appAuth() {
|
||||
ConsoleDomainGetAction action = createAction(AuthResult.create(AuthLevel.APP), "exists.tld");
|
||||
action.run();
|
||||
assertThat(RESPONSE.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongTypeOfUser() {
|
||||
ConsoleDomainGetAction action =
|
||||
createAction(
|
||||
AuthResult.create(
|
||||
AuthLevel.USER,
|
||||
UserAuthInfo.create(mock(com.google.appengine.api.users.User.class), false)),
|
||||
"exists.tld");
|
||||
action.run();
|
||||
assertThat(RESPONSE.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_noAccessToRegistrar() {
|
||||
ConsoleDomainGetAction action =
|
||||
createAction(
|
||||
AuthResult.create(
|
||||
AuthLevel.USER, UserAuthInfo.create(createUser(new UserRoles.Builder().build()))),
|
||||
"exists.tld");
|
||||
action.run();
|
||||
assertThat(RESPONSE.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_NOT_FOUND);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_nonexistentDomain() {
|
||||
ConsoleDomainGetAction action =
|
||||
createAction(
|
||||
AuthResult.create(
|
||||
AuthLevel.USER,
|
||||
UserAuthInfo.create(createUser(new UserRoles.Builder().setIsAdmin(true).build()))),
|
||||
"nonexistent.tld");
|
||||
action.run();
|
||||
assertThat(RESPONSE.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_NOT_FOUND);
|
||||
}
|
||||
|
||||
private User createUser(UserRoles userRoles) {
|
||||
return new User.Builder()
|
||||
.setEmailAddress("email@email.com")
|
||||
.setGaiaId("gaiaId")
|
||||
.setUserRoles(userRoles)
|
||||
.build();
|
||||
}
|
||||
|
||||
private ConsoleDomainGetAction createAction(AuthResult authResult, String domain) {
|
||||
return new ConsoleDomainGetAction(authResult, RESPONSE, GSON, domain);
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.util;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.LogsSubject.assertAboutLogs;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.appengine.api.log.LogQuery;
|
||||
import com.google.appengine.api.log.LogService;
|
||||
import com.google.appengine.api.log.RequestLogs;
|
||||
import com.google.apphosting.api.ApiProxy;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.testing.TestLogHandler;
|
||||
import google.registry.testing.UserServiceExtension;
|
||||
import java.util.logging.Level;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
/** Unit tests for {@link RequestStatusCheckerImpl}. */
|
||||
final class RequestStatusCheckerImplTest {
|
||||
|
||||
private static final TestLogHandler logHandler = new TestLogHandler();
|
||||
|
||||
private static final RequestStatusChecker requestStatusChecker = new RequestStatusCheckerImpl();
|
||||
|
||||
/**
|
||||
* Matcher for the expected LogQuery in {@link RequestStatusCheckerImpl#isRunning}.
|
||||
*
|
||||
* Because LogQuery doesn't have a .equals function, we have to create an actual matcher to make
|
||||
* sure we have the right argument in our mocks.
|
||||
*/
|
||||
private static LogQuery expectedLogQuery(final String requestLogId) {
|
||||
return argThat(
|
||||
object -> {
|
||||
assertThat(object).isInstanceOf(LogQuery.class);
|
||||
assertThat(object.getRequestIds()).containsExactly(requestLogId);
|
||||
assertThat(object.getIncludeAppLogs()).isFalse();
|
||||
assertThat(object.getIncludeIncomplete()).isTrue();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// We do not actually need to set up user service, rather, we just need this extension to set up
|
||||
// App Engine environment so the status checker can make an App Engine API call.
|
||||
@RegisterExtension UserServiceExtension userService = new UserServiceExtension("");
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
JdkLoggerConfig.getConfig(RequestStatusCheckerImpl.class).addHandler(logHandler);
|
||||
RequestStatusCheckerImpl.logService = mock(LogService.class);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void afterEach() {
|
||||
JdkLoggerConfig.getConfig(RequestStatusCheckerImpl.class).removeHandler(logHandler);
|
||||
}
|
||||
|
||||
// If a logId is unrecognized, it could be that the log hasn't been uploaded yet - so we assume
|
||||
// it's a request that has just started running recently.
|
||||
@Test
|
||||
void testIsRunning_unrecognized() {
|
||||
when(RequestStatusCheckerImpl.logService.fetch(expectedLogQuery("12345678")))
|
||||
.thenReturn(ImmutableList.of());
|
||||
assertThat(requestStatusChecker.isRunning("12345678")).isTrue();
|
||||
assertAboutLogs()
|
||||
.that(logHandler)
|
||||
.hasLogAtLevelWithMessage(Level.INFO, "Queried an unrecognized requestLogId");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsRunning_notFinished() {
|
||||
RequestLogs requestLogs = new RequestLogs();
|
||||
requestLogs.setFinished(false);
|
||||
|
||||
when(RequestStatusCheckerImpl.logService.fetch(expectedLogQuery("12345678")))
|
||||
.thenReturn(ImmutableList.of(requestLogs));
|
||||
|
||||
assertThat(requestStatusChecker.isRunning("12345678")).isTrue();
|
||||
assertAboutLogs()
|
||||
.that(logHandler)
|
||||
.hasLogAtLevelWithMessage(Level.INFO, "isFinished: false");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsRunning_finished() {
|
||||
RequestLogs requestLogs = new RequestLogs();
|
||||
requestLogs.setFinished(true);
|
||||
|
||||
when(RequestStatusCheckerImpl.logService.fetch(expectedLogQuery("12345678")))
|
||||
.thenReturn(ImmutableList.of(requestLogs));
|
||||
|
||||
assertThat(requestStatusChecker.isRunning("12345678")).isFalse();
|
||||
assertAboutLogs()
|
||||
.that(logHandler)
|
||||
.hasLogAtLevelWithMessage(Level.INFO, "isFinished: true");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetLogId_returnsRequestLogId() {
|
||||
String expectedLogId = ApiProxy.getCurrentEnvironment().getAttributes().get(
|
||||
"com.google.appengine.runtime.request_log_id").toString();
|
||||
assertThat(requestStatusChecker.getLogId()).isEqualTo(expectedLogId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetLogId_createsLog() {
|
||||
requestStatusChecker.getLogId();
|
||||
assertAboutLogs()
|
||||
.that(logHandler)
|
||||
.hasLogAtLevelWithMessage(Level.INFO, "Current requestLogId: ");
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
<extension>
|
||||
<fee:creData xmlns:fee="urn:ietf:params:xml:ns:fee-%FEE_VERSION%">
|
||||
<fee:currency>USD</fee:currency>
|
||||
<fee:fee description="create">26.00</fee:fee>
|
||||
<fee:fee description="create">%FEE%</fee:fee>
|
||||
</fee:creData>
|
||||
</extension>
|
||||
<trID>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
PATH CLASS METHODS OK AUTH_METHODS MIN USER_POLICY
|
||||
/_dr/epp EppTlsAction POST n INTERNAL,API APP PUBLIC
|
||||
/console-api/domain ConsoleDomainGetAction GET n API,LEGACY USER PUBLIC
|
||||
/registrar ConsoleUiAction GET n INTERNAL,API,LEGACY NONE PUBLIC
|
||||
/registrar-create ConsoleRegistrarCreatorAction POST,GET n INTERNAL,API,LEGACY NONE PUBLIC
|
||||
/registrar-ote-setup ConsoleOteSetupAction POST,GET n INTERNAL,API,LEGACY NONE PUBLIC
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -132,3 +132,8 @@ V131__add_default_promo_tokens_to_registry.sql
|
||||
V132__drop_gae_user_id_from_registrar_poc.sql
|
||||
V133__add_pull_queue_replace_columns.sql
|
||||
V134__drop_not_null_request_id_lock_table.sql
|
||||
V135__null_gaia_id_user.sql
|
||||
V136__add_dns_refresh_request_table.sql
|
||||
V137__add_process_time_column.sql
|
||||
V138__drop_dns_refresh_request_time_column.sql
|
||||
V139__add_allowed_epp_actions_column.sql
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
-- 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.
|
||||
|
||||
ALTER TABLE "User" ALTER COLUMN gaia_id DROP NOT NULL;
|
||||
@@ -0,0 +1,25 @@
|
||||
-- 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.
|
||||
|
||||
create table "DnsRefreshRequest"
|
||||
(
|
||||
id bigserial not null,
|
||||
name text not null,
|
||||
request_time timestamptz not null,
|
||||
tld text not null,
|
||||
type text not null,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create index IDX8gtvnbk64yskcvrdp61f5ied3 on "DnsRefreshRequest" (request_time);
|
||||
@@ -0,0 +1,18 @@
|
||||
-- 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.
|
||||
|
||||
ALTER TABLE "DnsRefreshRequest"
|
||||
ADD COLUMN IF NOT EXISTS process_time timestamptz NOT NULL;
|
||||
|
||||
CREATE INDEX IDX3i7i2ktts9d7lcjbs34h0pvwo ON "DnsRefreshRequest" (process_time);
|
||||
@@ -0,0 +1,19 @@
|
||||
-- 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.
|
||||
|
||||
ALTER TABLE "Domain" DROP COLUMN IF EXISTS dns_refresh_request_time;
|
||||
ALTER TABLE "DomainHistory" DROP COLUMN IF EXISTS dns_refresh_request_time;
|
||||
|
||||
ALTER TABLE "Host" DROP COLUMN IF EXISTS dns_refresh_request_time;
|
||||
ALTER TABLE "HostHistory" DROP COLUMN IF EXISTS dns_refresh_request_time;
|
||||
@@ -0,0 +1,15 @@
|
||||
-- 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.
|
||||
|
||||
ALTER TABLE "AllocationToken" ADD COLUMN allowed_epp_actions text[];
|
||||
@@ -276,7 +276,6 @@
|
||||
deletion_poll_message_id int8,
|
||||
domain_name text,
|
||||
idn_table_name text,
|
||||
dns_refresh_request_time timestamptz,
|
||||
last_transfer_time timestamptz,
|
||||
launch_notice_accepted_time timestamptz,
|
||||
launch_notice_expiration_time timestamptz,
|
||||
@@ -349,7 +348,6 @@
|
||||
deletion_poll_message_id int8,
|
||||
domain_name text,
|
||||
idn_table_name text,
|
||||
dns_refresh_request_time timestamptz,
|
||||
last_transfer_time timestamptz,
|
||||
launch_notice_accepted_time timestamptz,
|
||||
launch_notice_expiration_time timestamptz,
|
||||
@@ -451,7 +449,6 @@
|
||||
statuses text[],
|
||||
host_name text,
|
||||
inet_addresses text[],
|
||||
dns_refresh_request_time timestamptz,
|
||||
last_superordinate_change timestamptz,
|
||||
last_transfer_time timestamptz,
|
||||
superordinate_domain text,
|
||||
@@ -472,7 +469,6 @@
|
||||
history_xml_bytes bytea,
|
||||
host_name text,
|
||||
inet_addresses text[],
|
||||
dns_refresh_request_time timestamptz,
|
||||
last_superordinate_change timestamptz,
|
||||
last_transfer_time timestamptz,
|
||||
superordinate_domain text,
|
||||
@@ -492,7 +488,6 @@
|
||||
scope text not null,
|
||||
acquired_time timestamptz not null,
|
||||
expiration_time timestamptz not null,
|
||||
request_log_id text,
|
||||
primary key (resource_name, scope)
|
||||
);
|
||||
|
||||
@@ -751,7 +746,7 @@
|
||||
id bigserial not null,
|
||||
update_timestamp timestamptz,
|
||||
email_address text not null,
|
||||
gaia_id text not null,
|
||||
gaia_id text,
|
||||
registry_lock_password_hash text,
|
||||
registry_lock_password_salt text,
|
||||
global_role text not null,
|
||||
@@ -801,7 +796,6 @@ create index IDXc5aw4pk1vkd6ymhvkpanmoadv on "Domain" (domain_name);
|
||||
create index IDXr22ciyccwi9rrqmt1ro0s59qf on "Domain" (tech_contact);
|
||||
create index IDXrwl38wwkli1j7gkvtywi9jokq on "Domain" (tld);
|
||||
create index IDXa7fu0bqynfb79rr80528b4jqt on "Domain" (registrant_contact);
|
||||
create index IDXcws5mvmpl8o10wrhde780ors2 on "Domain" (dns_refresh_request_time);
|
||||
create index IDXnjhib7v6fj7dhj5qydkefkl2u on "Domain" (lordn_phase);
|
||||
create index IDXsfci08jgsymxy6ovh4k7r358c on "Domain" (billing_recurrence_id);
|
||||
create index IDX3y3k7m2bkgahm9sixiohgyrga on "Domain" (transfer_billing_event_id);
|
||||
@@ -825,7 +819,6 @@ create index IDXkpkh68n6dy5v51047yr6b0e9l on "Host" (host_name);
|
||||
create index IDXy98mebut8ix1v07fjxxdkqcx on "Host" (creation_time);
|
||||
create index IDXovmntef6l45tw2bsfl56tcugx on "Host" (deletion_time);
|
||||
create index IDXl49vydnq0h5j1piefwjy4i8er on "Host" (current_sponsor_registrar_id);
|
||||
create index IDX7wg0yn3wdux3xsc4pfaljqf08 on "Host" (dns_refresh_request_time);
|
||||
create index IDXfg2nnjlujxo6cb9fha971bq2n on "HostHistory" (creation_time);
|
||||
create index IDX1iy7njgb7wjmj9piml4l2g0qi on "HostHistory" (history_registrar_id);
|
||||
create index IDXkkwbwcwvrdkkqothkiye4jiff on "HostHistory" (host_name);
|
||||
|
||||
@@ -53,7 +53,8 @@ CREATE TABLE public."AllocationToken" (
|
||||
token_type text,
|
||||
redemption_domain_history_id bigint,
|
||||
renewal_price_behavior text DEFAULT 'DEFAULT'::text NOT NULL,
|
||||
registration_behavior text DEFAULT 'DEFAULT'::text NOT NULL
|
||||
registration_behavior text DEFAULT 'DEFAULT'::text NOT NULL,
|
||||
allowed_epp_actions text[]
|
||||
);
|
||||
|
||||
|
||||
@@ -340,6 +341,39 @@ CREATE TABLE public."DelegationSignerData" (
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: DnsRefreshRequest; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public."DnsRefreshRequest" (
|
||||
id bigint NOT NULL,
|
||||
name text NOT NULL,
|
||||
request_time timestamp with time zone NOT NULL,
|
||||
tld text NOT NULL,
|
||||
type text NOT NULL,
|
||||
process_time timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: DnsRefreshRequest_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public."DnsRefreshRequest_id_seq"
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: DnsRefreshRequest_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public."DnsRefreshRequest_id_seq" OWNED BY public."DnsRefreshRequest".id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: Domain; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
@@ -395,7 +429,6 @@ CREATE TABLE public."Domain" (
|
||||
transfer_history_entry_id bigint,
|
||||
transfer_repo_id text,
|
||||
transfer_poll_message_id_3 bigint,
|
||||
dns_refresh_request_time timestamp with time zone,
|
||||
current_package_token text,
|
||||
lordn_phase text DEFAULT 'NONE'::text NOT NULL
|
||||
);
|
||||
@@ -485,7 +518,6 @@ CREATE TABLE public."DomainHistory" (
|
||||
transfer_history_entry_id bigint,
|
||||
transfer_repo_id text,
|
||||
transfer_poll_message_id_3 bigint,
|
||||
dns_refresh_request_time timestamp with time zone,
|
||||
current_package_token text,
|
||||
lordn_phase text DEFAULT 'NONE'::text NOT NULL
|
||||
);
|
||||
@@ -597,8 +629,7 @@ CREATE TABLE public."Host" (
|
||||
superordinate_domain text,
|
||||
inet_addresses text[],
|
||||
update_timestamp timestamp with time zone,
|
||||
transfer_poll_message_id_3 bigint,
|
||||
dns_refresh_request_time timestamp with time zone
|
||||
transfer_poll_message_id_3 bigint
|
||||
);
|
||||
|
||||
|
||||
@@ -631,8 +662,7 @@ CREATE TABLE public."HostHistory" (
|
||||
statuses text[],
|
||||
host_repo_id text NOT NULL,
|
||||
update_timestamp timestamp with time zone,
|
||||
transfer_poll_message_id_3 bigint,
|
||||
dns_refresh_request_time timestamp with time zone
|
||||
transfer_poll_message_id_3 bigint
|
||||
);
|
||||
|
||||
|
||||
@@ -1085,7 +1115,7 @@ CREATE TABLE public."TmchCrl" (
|
||||
CREATE TABLE public."User" (
|
||||
id bigint NOT NULL,
|
||||
email_address text NOT NULL,
|
||||
gaia_id text NOT NULL,
|
||||
gaia_id text,
|
||||
registry_lock_password_hash text,
|
||||
registry_lock_password_salt text,
|
||||
global_role text NOT NULL,
|
||||
@@ -1133,6 +1163,13 @@ CREATE SEQUENCE public.project_wide_unique_id_seq
|
||||
ALTER TABLE ONLY public."ClaimsList" ALTER COLUMN revision_id SET DEFAULT nextval('public."ClaimsList_revision_id_seq"'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: DnsRefreshRequest id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public."DnsRefreshRequest" ALTER COLUMN id SET DEFAULT nextval('public."DnsRefreshRequest_id_seq"'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: DomainTransactionRecord id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
@@ -1277,6 +1314,14 @@ ALTER TABLE ONLY public."DelegationSignerData"
|
||||
ADD CONSTRAINT "DelegationSignerData_pkey" PRIMARY KEY (domain_repo_id, key_tag, algorithm, digest_type, digest);
|
||||
|
||||
|
||||
--
|
||||
-- Name: DnsRefreshRequest DnsRefreshRequest_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public."DnsRefreshRequest"
|
||||
ADD CONSTRAINT "DnsRefreshRequest_pkey" PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: DomainDsDataHistory DomainDsDataHistory_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
@@ -1531,13 +1576,6 @@ CREATE INDEX allocation_token_domain_name_idx ON public."AllocationToken" USING
|
||||
CREATE UNIQUE INDEX database_migration_state_schedule_singleton ON public."DatabaseMigrationStateSchedule" USING btree ((true));
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_dns_refresh_request_time_idx; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX domain_dns_refresh_request_time_idx ON public."Domain" USING btree (dns_refresh_request_time);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_history_to_ds_data_history_idx; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
@@ -1580,6 +1618,13 @@ CREATE INDEX idx1rcgkdd777bpvj0r94sltwd5y ON public."Domain" USING btree (domain
|
||||
CREATE INDEX idx2exdfbx6oiiwnhr8j6gjpqt2j ON public."BillingCancellation" USING btree (event_time);
|
||||
|
||||
|
||||
--
|
||||
-- Name: idx3i7i2ktts9d7lcjbs34h0pvwo; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX idx3i7i2ktts9d7lcjbs34h0pvwo ON public."DnsRefreshRequest" USING btree (process_time);
|
||||
|
||||
|
||||
--
|
||||
-- Name: idx3y3k7m2bkgahm9sixiohgyrga; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
@@ -1665,10 +1710,10 @@ CREATE INDEX idx73l103vc5900ig3p4odf0cngt ON public."BillingEvent" USING btree (
|
||||
|
||||
|
||||
--
|
||||
-- Name: idx7wg0yn3wdux3xsc4pfaljqf08; Type: INDEX; Schema: public; Owner: -
|
||||
-- Name: idx8gtvnbk64yskcvrdp61f5ied3; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX idx7wg0yn3wdux3xsc4pfaljqf08 ON public."Host" USING btree (dns_refresh_request_time) WHERE (dns_refresh_request_time IS NOT NULL);
|
||||
CREATE INDEX idx8gtvnbk64yskcvrdp61f5ied3 ON public."DnsRefreshRequest" USING btree (request_time);
|
||||
|
||||
|
||||
--
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.proxy;
|
||||
|
||||
import static google.registry.util.ResourceUtils.readResourceBytes;
|
||||
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
@@ -43,6 +44,7 @@ import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import java.io.IOException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.function.Supplier;
|
||||
@@ -147,12 +149,18 @@ public final class EppProtocolModule {
|
||||
|
||||
@Provides
|
||||
static EppServiceHandler provideEppServiceHandler(
|
||||
@Named("accessToken") Supplier<String> accessTokenSupplier,
|
||||
Supplier<GoogleCredentials> refreshedCredentialsSupplier,
|
||||
@Named("iapClientId") Optional<String> iapClientId,
|
||||
@Named("hello") byte[] helloBytes,
|
||||
FrontendMetrics metrics,
|
||||
ProxyConfig config) {
|
||||
return new EppServiceHandler(
|
||||
config.epp.relayHost, config.epp.relayPath, accessTokenSupplier, helloBytes, metrics);
|
||||
config.epp.relayHost,
|
||||
config.epp.relayPath,
|
||||
refreshedCredentialsSupplier,
|
||||
iapClientId,
|
||||
helloBytes,
|
||||
metrics);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
|
||||
@@ -40,6 +40,7 @@ public class ProxyConfig {
|
||||
private static final String CUSTOM_CONFIG_FORMATTER = "config/proxy-config-%s.yaml";
|
||||
|
||||
public String projectId;
|
||||
public String iapClientId;
|
||||
public List<String> gcpScopes;
|
||||
public int serverCertificateCacheSeconds;
|
||||
public Gcs gcs;
|
||||
|
||||
@@ -157,6 +157,13 @@ public class ProxyModule {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("iapClientId")
|
||||
@Singleton
|
||||
Optional<String> provideIapClientId(ProxyConfig config) {
|
||||
return Optional.ofNullable(config.iapClientId);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@WhoisProtocol
|
||||
int provideWhoisPort(ProxyConfig config) {
|
||||
@@ -207,7 +214,7 @@ public class ProxyModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static GoogleCredentialsBundle provideCredential(ProxyConfig config) {
|
||||
static GoogleCredentialsBundle provideCredentialsBundle(ProxyConfig config) {
|
||||
try {
|
||||
GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
|
||||
if (credentials.createScopedRequired()) {
|
||||
@@ -219,19 +226,19 @@ public class ProxyModule {
|
||||
}
|
||||
}
|
||||
|
||||
/** Access token supplier that auto refreshes 1 minute before expiry. */
|
||||
/** Provides a set of credentials that auto refreshes 1 minute before expiry. */
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named("accessToken")
|
||||
static Supplier<String> provideAccessTokenSupplier(GoogleCredentialsBundle credentialsBundle) {
|
||||
static Supplier<GoogleCredentials> provideRefreshedCredentialsSupplier(
|
||||
GoogleCredentialsBundle credentialsBundle) {
|
||||
return () -> {
|
||||
GoogleCredentials credentials = credentialsBundle.getGoogleCredentials();
|
||||
try {
|
||||
credentials.refreshIfExpired();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot refresh access token.", e);
|
||||
throw new RuntimeException("Cannot refresh credentials.", e);
|
||||
}
|
||||
return credentials.getAccessToken().getTokenValue();
|
||||
return credentials;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.proxy;
|
||||
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
@@ -34,6 +35,7 @@ import google.registry.util.Clock;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.handler.codec.LineBasedFrameDecoder;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.function.Supplier;
|
||||
@@ -91,10 +93,15 @@ public class WhoisProtocolModule {
|
||||
@Provides
|
||||
static WhoisServiceHandler provideWhoisServiceHandler(
|
||||
ProxyConfig config,
|
||||
@Named("accessToken") Supplier<String> accessTokenSupplier,
|
||||
Supplier<GoogleCredentials> refreshedCredentialsSupplier,
|
||||
@Named("iapClientId") Optional<String> iapClientId,
|
||||
FrontendMetrics metrics) {
|
||||
return new WhoisServiceHandler(
|
||||
config.whois.relayHost, config.whois.relayPath, accessTokenSupplier, metrics);
|
||||
config.whois.relayHost,
|
||||
config.whois.relayPath,
|
||||
refreshedCredentialsSupplier,
|
||||
iapClientId,
|
||||
metrics);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
# GCP project ID
|
||||
projectId: your-gcp-project-id
|
||||
|
||||
# IAP client ID, if IAP is enabled for this project
|
||||
iapClientId: null
|
||||
|
||||
# OAuth scope that the GoogleCredential will be constructed with. This list
|
||||
# should include all service scopes that the proxy depends on.
|
||||
gcpScopes:
|
||||
|
||||
@@ -20,6 +20,7 @@ import static google.registry.networking.handler.SslServerInitializer.CLIENT_CER
|
||||
import static google.registry.proxy.handler.ProxyProtocolHandler.REMOTE_ADDRESS_KEY;
|
||||
import static google.registry.util.X509Utils.getCertificateHash;
|
||||
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.proxy.metric.FrontendMetrics;
|
||||
import google.registry.util.ProxyHttpHeaders;
|
||||
@@ -36,6 +37,7 @@ import io.netty.handler.ssl.SslHandshakeCompletionEvent;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/** Handler that processes EPP protocol logic. */
|
||||
@@ -60,10 +62,11 @@ public class EppServiceHandler extends HttpsRelayServiceHandler {
|
||||
public EppServiceHandler(
|
||||
String relayHost,
|
||||
String relayPath,
|
||||
Supplier<String> accessTokenSupplier,
|
||||
Supplier<GoogleCredentials> refreshedCredentialsSupplier,
|
||||
Optional<String> iapClientId,
|
||||
byte[] helloBytes,
|
||||
FrontendMetrics metrics) {
|
||||
super(relayHost, relayPath, accessTokenSupplier, metrics);
|
||||
super(relayHost, relayPath, refreshedCredentialsSupplier, iapClientId, metrics);
|
||||
this.helloBytes = helloBytes;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,12 @@ package google.registry.proxy.handler;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import com.google.auth.oauth2.IdToken;
|
||||
import com.google.auth.oauth2.IdTokenProvider;
|
||||
import com.google.auth.oauth2.IdTokenProvider.Option;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.proxy.metric.FrontendMetrics;
|
||||
@@ -37,9 +42,11 @@ import io.netty.handler.codec.http.cookie.ClientCookieDecoder;
|
||||
import io.netty.handler.codec.http.cookie.ClientCookieEncoder;
|
||||
import io.netty.handler.codec.http.cookie.Cookie;
|
||||
import io.netty.handler.timeout.ReadTimeoutException;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
|
||||
@@ -72,18 +79,21 @@ public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHt
|
||||
private final Map<String, Cookie> cookieStore = new LinkedHashMap<>();
|
||||
private final String relayHost;
|
||||
private final String relayPath;
|
||||
private final Supplier<String> accessTokenSupplier;
|
||||
private final Supplier<GoogleCredentials> refreshedCredentialsSupplier;
|
||||
private final Optional<String> iapClientId;
|
||||
|
||||
protected final FrontendMetrics metrics;
|
||||
|
||||
HttpsRelayServiceHandler(
|
||||
String relayHost,
|
||||
String relayPath,
|
||||
Supplier<String> accessTokenSupplier,
|
||||
Supplier<GoogleCredentials> refreshedCredentialsSupplier,
|
||||
Optional<String> iapClientId,
|
||||
FrontendMetrics metrics) {
|
||||
this.relayHost = relayHost;
|
||||
this.relayPath = relayPath;
|
||||
this.accessTokenSupplier = accessTokenSupplier;
|
||||
this.refreshedCredentialsSupplier = refreshedCredentialsSupplier;
|
||||
this.iapClientId = iapClientId;
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
@@ -91,19 +101,37 @@ public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHt
|
||||
* Construct the {@link FullHttpRequest}.
|
||||
*
|
||||
* <p>This default method creates a bare-bone {@link FullHttpRequest} that may need to be
|
||||
* modified, e. g. adding headers specific for each protocol.
|
||||
* modified, e.g. adding headers specific for each protocol.
|
||||
*
|
||||
* @param byteBuf inbound message.
|
||||
*/
|
||||
protected FullHttpRequest decodeFullHttpRequest(ByteBuf byteBuf) {
|
||||
FullHttpRequest request =
|
||||
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, relayPath);
|
||||
GoogleCredentials credentials = refreshedCredentialsSupplier.get();
|
||||
request
|
||||
.headers()
|
||||
.set(HttpHeaderNames.USER_AGENT, "Proxy")
|
||||
.set(HttpHeaderNames.HOST, relayHost)
|
||||
.set(HttpHeaderNames.AUTHORIZATION, "Bearer " + accessTokenSupplier.get())
|
||||
.set(
|
||||
HttpHeaderNames.AUTHORIZATION, "Bearer " + credentials.getAccessToken().getTokenValue())
|
||||
.setInt(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
|
||||
// Set the Proxy-Authorization header if using IAP
|
||||
if (iapClientId.isPresent()) {
|
||||
IdTokenProvider idTokenProvider = (IdTokenProvider) credentials;
|
||||
try {
|
||||
// Note: we use Option.FORMAT_FULL to make sure the JWT we receive contains the email
|
||||
// address (as is required by IAP)
|
||||
IdToken idToken =
|
||||
idTokenProvider.idTokenWithAudience(
|
||||
iapClientId.get(), ImmutableList.of(Option.FORMAT_FULL));
|
||||
request
|
||||
.headers()
|
||||
.set(HttpHeaderNames.PROXY_AUTHORIZATION, "Bearer " + idToken.getTokenValue());
|
||||
} catch (IOException e) {
|
||||
logger.atSevere().withCause(e).log("Error when attempting to retrieve IAP ID token");
|
||||
}
|
||||
}
|
||||
request.content().writeBytes(byteBuf);
|
||||
return request;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.proxy.handler;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import google.registry.proxy.metric.FrontendMetrics;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
@@ -25,6 +26,7 @@ import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpHeaderValues;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/** Handler that processes WHOIS protocol logic. */
|
||||
@@ -33,9 +35,10 @@ public final class WhoisServiceHandler extends HttpsRelayServiceHandler {
|
||||
public WhoisServiceHandler(
|
||||
String relayHost,
|
||||
String relayPath,
|
||||
Supplier<String> accessTokenSupplier,
|
||||
Supplier<GoogleCredentials> refreshedCredentialsSupplier,
|
||||
Optional<String> iapClientId,
|
||||
FrontendMetrics metrics) {
|
||||
super(relayHost, relayPath, accessTokenSupplier, metrics);
|
||||
super(relayHost, relayPath, refreshedCredentialsSupplier, iapClientId, metrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -36,6 +36,7 @@ import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.cookie.Cookie;
|
||||
import io.netty.handler.codec.http.cookie.DefaultCookie;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import java.io.IOException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -96,14 +97,15 @@ class EppProtocolModuleTest extends ProtocolModuleTest {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private FullHttpRequest makeEppHttpRequest(byte[] content, Cookie... cookies) {
|
||||
private FullHttpRequest makeEppHttpRequest(byte[] content, Cookie... cookies) throws IOException {
|
||||
return TestUtils.makeEppHttpRequest(
|
||||
new String(content, UTF_8),
|
||||
PROXY_CONFIG.epp.relayHost,
|
||||
PROXY_CONFIG.epp.relayPath,
|
||||
TestModule.provideFakeAccessToken().get(),
|
||||
TestModule.provideFakeCredentials().get(),
|
||||
getCertificateHash(certificate),
|
||||
CLIENT_ADDRESS,
|
||||
TestModule.provideIapClientId(),
|
||||
cookies);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,14 @@ package google.registry.proxy;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.proxy.ProxyConfig.Environment.LOCAL;
|
||||
import static google.registry.proxy.ProxyConfig.getProxyConfig;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.auth.oauth2.AccessToken;
|
||||
import com.google.auth.oauth2.ComputeEngineCredentials;
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import com.google.auth.oauth2.IdToken;
|
||||
import com.google.auth.oauth2.IdTokenProvider.Option;
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -52,7 +59,9 @@ import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
@@ -223,7 +232,7 @@ public abstract class ProtocolModuleTest {
|
||||
* should be provided in the respective {@code ProtocolModule} instead.
|
||||
*/
|
||||
@Module
|
||||
static class TestModule {
|
||||
public static class TestModule {
|
||||
|
||||
/**
|
||||
* A fake clock that is explicitly provided. Users can construct a module with a controller
|
||||
@@ -235,6 +244,12 @@ public abstract class ProtocolModuleTest {
|
||||
this.fakeClock = fakeClock;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("iapClientId")
|
||||
public static Optional<String> provideIapClientId() {
|
||||
return Optional.of("iapClientId");
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static ProxyConfig provideProxyConfig() {
|
||||
@@ -249,9 +264,19 @@ public abstract class ProtocolModuleTest {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named("accessToken")
|
||||
static Supplier<String> provideFakeAccessToken() {
|
||||
return Suppliers.ofInstance("fake.test.token");
|
||||
static Supplier<GoogleCredentials> provideFakeCredentials() {
|
||||
ComputeEngineCredentials mockCredentials = mock(ComputeEngineCredentials.class);
|
||||
when(mockCredentials.getAccessToken()).thenReturn(new AccessToken("fake.test.token", null));
|
||||
IdToken mockIdToken = mock(IdToken.class);
|
||||
when(mockIdToken.getTokenValue()).thenReturn("fake.test.id.token");
|
||||
try {
|
||||
when(mockCredentials.idTokenWithAudience(
|
||||
"iapClientId", ImmutableList.of(Option.FORMAT_FULL)))
|
||||
.thenReturn(mockIdToken);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return Suppliers.ofInstance(mockCredentials);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
|
||||
@@ -17,6 +17,10 @@ package google.registry.proxy;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import com.google.auth.oauth2.IdTokenProvider;
|
||||
import com.google.auth.oauth2.IdTokenProvider.Option;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.util.ProxyHttpHeaders;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
@@ -34,6 +38,8 @@ import io.netty.handler.codec.http.HttpVersion;
|
||||
import io.netty.handler.codec.http.cookie.ClientCookieEncoder;
|
||||
import io.netty.handler.codec.http.cookie.Cookie;
|
||||
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
/** Utility class for various helper methods used in testing. */
|
||||
public class TestUtils {
|
||||
@@ -71,13 +77,19 @@ public class TestUtils {
|
||||
}
|
||||
|
||||
public static FullHttpRequest makeWhoisHttpRequest(
|
||||
String content, String host, String path, String accessToken) {
|
||||
String content,
|
||||
String host,
|
||||
String path,
|
||||
GoogleCredentials credentials,
|
||||
Optional<String> iapClientId)
|
||||
throws IOException {
|
||||
FullHttpRequest request = makeHttpPostRequest(content, host, path);
|
||||
request
|
||||
.headers()
|
||||
.set("authorization", "Bearer " + accessToken)
|
||||
.set("authorization", "Bearer " + credentials.getAccessToken().getTokenValue())
|
||||
.set(HttpHeaderNames.CONTENT_TYPE, "text/plain")
|
||||
.set("accept", "text/plain");
|
||||
maybeSetProxyAuthForIap(request, credentials, iapClientId);
|
||||
return request;
|
||||
}
|
||||
|
||||
@@ -85,18 +97,21 @@ public class TestUtils {
|
||||
String content,
|
||||
String host,
|
||||
String path,
|
||||
String accessToken,
|
||||
GoogleCredentials credentials,
|
||||
String sslClientCertificateHash,
|
||||
String clientAddress,
|
||||
Cookie... cookies) {
|
||||
Optional<String> iapClientId,
|
||||
Cookie... cookies)
|
||||
throws IOException {
|
||||
FullHttpRequest request = makeHttpPostRequest(content, host, path);
|
||||
request
|
||||
.headers()
|
||||
.set("authorization", "Bearer " + accessToken)
|
||||
.set("authorization", "Bearer " + credentials.getAccessToken().getTokenValue())
|
||||
.set(HttpHeaderNames.CONTENT_TYPE, "application/epp+xml")
|
||||
.set("accept", "application/epp+xml")
|
||||
.set(ProxyHttpHeaders.CERTIFICATE_HASH, sslClientCertificateHash)
|
||||
.set(ProxyHttpHeaders.IP_ADDRESS, clientAddress);
|
||||
maybeSetProxyAuthForIap(request, credentials, iapClientId);
|
||||
if (cookies.length != 0) {
|
||||
request.headers().set("cookie", ClientCookieEncoder.STRICT.encode(cookies));
|
||||
}
|
||||
@@ -146,4 +161,16 @@ public class TestUtils {
|
||||
public static void assertHttpRequestEquivalent(HttpRequest req1, HttpRequest req2) {
|
||||
assertHttpMessageEquivalent(req1, req2);
|
||||
}
|
||||
|
||||
private static void maybeSetProxyAuthForIap(
|
||||
FullHttpRequest request, GoogleCredentials credentials, Optional<String> iapClientId)
|
||||
throws IOException {
|
||||
if (iapClientId.isPresent()) {
|
||||
String idTokenValue =
|
||||
((IdTokenProvider) credentials)
|
||||
.idTokenWithAudience(iapClientId.get(), ImmutableList.of(Option.FORMAT_FULL))
|
||||
.getTokenValue();
|
||||
request.headers().set("proxy-authorization", "Bearer " + idTokenValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ class WhoisProtocolModuleTest extends ProtocolModuleTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_singleFrameInboundMessage() {
|
||||
void testSuccess_singleFrameInboundMessage() throws Exception {
|
||||
String inputString = "test.tld\r\n";
|
||||
// Inbound message processed and passed along.
|
||||
assertThat(channel.writeInbound(Unpooled.wrappedBuffer(inputString.getBytes(US_ASCII))))
|
||||
@@ -53,7 +53,8 @@ class WhoisProtocolModuleTest extends ProtocolModuleTest {
|
||||
"test.tld",
|
||||
PROXY_CONFIG.whois.relayHost,
|
||||
PROXY_CONFIG.whois.relayPath,
|
||||
TestModule.provideFakeAccessToken().get());
|
||||
TestModule.provideFakeCredentials().get(),
|
||||
TestModule.provideIapClientId());
|
||||
assertThat(actualRequest).isEqualTo(expectedRequest);
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
// Nothing more to read.
|
||||
@@ -70,7 +71,7 @@ class WhoisProtocolModuleTest extends ProtocolModuleTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_multiFrameInboundMessage() {
|
||||
void testSuccess_multiFrameInboundMessage() throws Exception {
|
||||
String frame1 = "test";
|
||||
String frame2 = "1.tld";
|
||||
String frame3 = "\r\nte";
|
||||
@@ -88,7 +89,8 @@ class WhoisProtocolModuleTest extends ProtocolModuleTest {
|
||||
"test1.tld",
|
||||
PROXY_CONFIG.whois.relayHost,
|
||||
PROXY_CONFIG.whois.relayPath,
|
||||
TestModule.provideFakeAccessToken().get());
|
||||
TestModule.provideFakeCredentials().get(),
|
||||
TestModule.provideIapClientId());
|
||||
assertThat(actualRequest1).isEqualTo(expectedRequest1);
|
||||
// No more message at this point.
|
||||
assertThat((Object) channel.readInbound()).isNull();
|
||||
@@ -102,7 +104,8 @@ class WhoisProtocolModuleTest extends ProtocolModuleTest {
|
||||
"test2.tld",
|
||||
PROXY_CONFIG.whois.relayHost,
|
||||
PROXY_CONFIG.whois.relayPath,
|
||||
TestModule.provideFakeAccessToken().get());
|
||||
TestModule.provideFakeCredentials().get(),
|
||||
TestModule.provideIapClientId());
|
||||
assertThat(actualRequest2).isEqualTo(expectedRequest2);
|
||||
// The third message is not complete yet.
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
|
||||
@@ -25,8 +25,14 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.auth.oauth2.AccessToken;
|
||||
import com.google.auth.oauth2.ComputeEngineCredentials;
|
||||
import com.google.auth.oauth2.IdToken;
|
||||
import com.google.auth.oauth2.IdTokenProvider.Option;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.proxy.TestUtils;
|
||||
import google.registry.proxy.handler.HttpsRelayServiceHandler.NonOkHttpResponseException;
|
||||
import google.registry.proxy.metric.FrontendMetrics;
|
||||
@@ -44,7 +50,9 @@ import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.cookie.Cookie;
|
||||
import io.netty.handler.codec.http.cookie.DefaultCookie;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import java.io.IOException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -59,9 +67,12 @@ class EppServiceHandlerTest {
|
||||
|
||||
private static final String RELAY_HOST = "registry.example.tld";
|
||||
private static final String RELAY_PATH = "/epp";
|
||||
private static final String ACCESS_TOKEN = "this.access.token";
|
||||
private static final String CLIENT_ADDRESS = "epp.client.tld";
|
||||
private static final String PROTOCOL = "epp";
|
||||
private static final String IAP_CLIENT_ID = "iapClientId";
|
||||
|
||||
private static final ComputeEngineCredentials mockCredentials =
|
||||
mock(ComputeEngineCredentials.class);
|
||||
|
||||
private X509Certificate clientCertificate;
|
||||
|
||||
@@ -69,7 +80,12 @@ class EppServiceHandlerTest {
|
||||
|
||||
private final EppServiceHandler eppServiceHandler =
|
||||
new EppServiceHandler(
|
||||
RELAY_HOST, RELAY_PATH, () -> ACCESS_TOKEN, HELLO.getBytes(UTF_8), metrics);
|
||||
RELAY_HOST,
|
||||
RELAY_PATH,
|
||||
() -> mockCredentials,
|
||||
Optional.of(IAP_CLIENT_ID),
|
||||
HELLO.getBytes(UTF_8),
|
||||
metrics);
|
||||
|
||||
private EmbeddedChannel channel;
|
||||
|
||||
@@ -79,7 +95,7 @@ class EppServiceHandlerTest {
|
||||
channel.attr(CLIENT_CERTIFICATE_PROMISE_KEY).get().setSuccess(certificate);
|
||||
}
|
||||
|
||||
private void setHandshakeSuccess() throws Exception {
|
||||
private void setHandshakeSuccess() {
|
||||
setHandshakeSuccess(channel, clientCertificate);
|
||||
}
|
||||
|
||||
@@ -91,23 +107,29 @@ class EppServiceHandlerTest {
|
||||
.setFailure(new Exception("Handshake Failure"));
|
||||
}
|
||||
|
||||
private void setHandshakeFailure() throws Exception {
|
||||
private void setHandshakeFailure() {
|
||||
setHandshakeFailure(channel);
|
||||
}
|
||||
|
||||
private FullHttpRequest makeEppHttpRequest(String content, Cookie... cookies) {
|
||||
private FullHttpRequest makeEppHttpRequest(String content, Cookie... cookies) throws IOException {
|
||||
return TestUtils.makeEppHttpRequest(
|
||||
content,
|
||||
RELAY_HOST,
|
||||
RELAY_PATH,
|
||||
ACCESS_TOKEN,
|
||||
mockCredentials,
|
||||
getCertificateHash(clientCertificate),
|
||||
CLIENT_ADDRESS,
|
||||
Optional.of(IAP_CLIENT_ID),
|
||||
cookies);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() throws Exception {
|
||||
when(mockCredentials.getAccessToken()).thenReturn(new AccessToken("this.access.token", null));
|
||||
IdToken mockIdToken = mock(IdToken.class);
|
||||
when(mockIdToken.getTokenValue()).thenReturn("fake.test.id.token");
|
||||
when(mockCredentials.idTokenWithAudience(IAP_CLIENT_ID, ImmutableList.of(Option.FORMAT_FULL)))
|
||||
.thenReturn(mockIdToken);
|
||||
clientCertificate = SelfSignedCaCertificate.create().cert();
|
||||
channel = setUpNewChannel(eppServiceHandler);
|
||||
}
|
||||
@@ -140,10 +162,15 @@ class EppServiceHandlerTest {
|
||||
String certHash = getCertificateHash(clientCertificate);
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
|
||||
// Setup the second channel.
|
||||
// Set up the second channel.
|
||||
EppServiceHandler eppServiceHandler2 =
|
||||
new EppServiceHandler(
|
||||
RELAY_HOST, RELAY_PATH, () -> ACCESS_TOKEN, HELLO.getBytes(UTF_8), metrics);
|
||||
RELAY_HOST,
|
||||
RELAY_PATH,
|
||||
() -> mockCredentials,
|
||||
Optional.empty(),
|
||||
HELLO.getBytes(UTF_8),
|
||||
metrics);
|
||||
EmbeddedChannel channel2 = setUpNewChannel(eppServiceHandler2);
|
||||
setHandshakeSuccess(channel2, clientCertificate);
|
||||
|
||||
@@ -160,10 +187,15 @@ class EppServiceHandlerTest {
|
||||
String certHash = getCertificateHash(clientCertificate);
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
|
||||
// Setup the second channel.
|
||||
// Set up the second channel.
|
||||
EppServiceHandler eppServiceHandler2 =
|
||||
new EppServiceHandler(
|
||||
RELAY_HOST, RELAY_PATH, () -> ACCESS_TOKEN, HELLO.getBytes(UTF_8), metrics);
|
||||
RELAY_HOST,
|
||||
RELAY_PATH,
|
||||
() -> mockCredentials,
|
||||
Optional.empty(),
|
||||
HELLO.getBytes(UTF_8),
|
||||
metrics);
|
||||
EmbeddedChannel channel2 = setUpNewChannel(eppServiceHandler2);
|
||||
X509Certificate clientCertificate2 = SelfSignedCaCertificate.create().cert();
|
||||
setHandshakeSuccess(channel2, clientCertificate2);
|
||||
@@ -326,4 +358,38 @@ class EppServiceHandlerTest {
|
||||
assertThat((Object) channel.readOutbound()).isNull();
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_withoutIapClientId() throws Exception {
|
||||
// Without an IAP client ID configured, we shouldn't include the proxy-authorization header
|
||||
EppServiceHandler nonIapServiceHandler =
|
||||
new EppServiceHandler(
|
||||
RELAY_HOST,
|
||||
RELAY_PATH,
|
||||
() -> mockCredentials,
|
||||
Optional.empty(),
|
||||
HELLO.getBytes(UTF_8),
|
||||
metrics);
|
||||
channel = setUpNewChannel(nonIapServiceHandler);
|
||||
|
||||
setHandshakeSuccess();
|
||||
// First inbound message is hello.
|
||||
channel.readInbound();
|
||||
String content = "<epp>stuff</epp>";
|
||||
channel.writeInbound(Unpooled.wrappedBuffer(content.getBytes(UTF_8)));
|
||||
FullHttpRequest request = channel.readInbound();
|
||||
assertThat(request)
|
||||
.isEqualTo(
|
||||
TestUtils.makeEppHttpRequest(
|
||||
content,
|
||||
RELAY_HOST,
|
||||
RELAY_PATH,
|
||||
mockCredentials,
|
||||
getCertificateHash(clientCertificate),
|
||||
CLIENT_ADDRESS,
|
||||
Optional.empty()));
|
||||
// Nothing further to pass to the next handler.
|
||||
assertThat((Object) channel.readInbound()).isNull();
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,14 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.auth.oauth2.AccessToken;
|
||||
import com.google.auth.oauth2.ComputeEngineCredentials;
|
||||
import com.google.auth.oauth2.IdToken;
|
||||
import com.google.auth.oauth2.IdTokenProvider.Option;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.proxy.handler.HttpsRelayServiceHandler.NonOkHttpResponseException;
|
||||
import google.registry.proxy.metric.FrontendMetrics;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
@@ -34,6 +40,7 @@ import io.netty.handler.codec.EncoderException;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -43,18 +50,26 @@ class WhoisServiceHandlerTest {
|
||||
private static final String RELAY_HOST = "www.example.tld";
|
||||
private static final String RELAY_PATH = "/test";
|
||||
private static final String QUERY_CONTENT = "test.tld";
|
||||
private static final String ACCESS_TOKEN = "this.access.token";
|
||||
private static final String PROTOCOL = "whois";
|
||||
private static final String CLIENT_HASH = "none";
|
||||
private static final String IAP_CLIENT_ID = "iapClientId";
|
||||
|
||||
private static final ComputeEngineCredentials mockCredentials =
|
||||
mock(ComputeEngineCredentials.class);
|
||||
private final FrontendMetrics metrics = mock(FrontendMetrics.class);
|
||||
|
||||
private final WhoisServiceHandler whoisServiceHandler =
|
||||
new WhoisServiceHandler(RELAY_HOST, RELAY_PATH, () -> ACCESS_TOKEN, metrics);
|
||||
new WhoisServiceHandler(
|
||||
RELAY_HOST, RELAY_PATH, () -> mockCredentials, Optional.of(IAP_CLIENT_ID), metrics);
|
||||
private EmbeddedChannel channel;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
void beforeEach() throws Exception {
|
||||
when(mockCredentials.getAccessToken()).thenReturn(new AccessToken("this.access.token", null));
|
||||
IdToken mockIdToken = mock(IdToken.class);
|
||||
when(mockIdToken.getTokenValue()).thenReturn("fake.test.id.token");
|
||||
when(mockCredentials.idTokenWithAudience(IAP_CLIENT_ID, ImmutableList.of(Option.FORMAT_FULL)))
|
||||
.thenReturn(mockIdToken);
|
||||
// Need to reset metrics for each test method, since they are static fields on the class and
|
||||
// shared between each run.
|
||||
channel = new EmbeddedChannel(whoisServiceHandler);
|
||||
@@ -74,7 +89,8 @@ class WhoisServiceHandlerTest {
|
||||
|
||||
// Setup second channel.
|
||||
WhoisServiceHandler whoisServiceHandler2 =
|
||||
new WhoisServiceHandler(RELAY_HOST, RELAY_PATH, () -> ACCESS_TOKEN, metrics);
|
||||
new WhoisServiceHandler(
|
||||
RELAY_HOST, RELAY_PATH, () -> mockCredentials, Optional.empty(), metrics);
|
||||
EmbeddedChannel channel2 =
|
||||
// We need a new channel id so that it has a different hash code.
|
||||
// This only is needed for EmbeddedChannel because it has a dummy hash code implementation.
|
||||
@@ -85,10 +101,11 @@ class WhoisServiceHandlerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fireInboundHttpRequest() {
|
||||
void testSuccess_fireInboundHttpRequest() throws Exception {
|
||||
ByteBuf inputBuffer = Unpooled.wrappedBuffer(QUERY_CONTENT.getBytes(US_ASCII));
|
||||
FullHttpRequest expectedRequest =
|
||||
makeWhoisHttpRequest(QUERY_CONTENT, RELAY_HOST, RELAY_PATH, ACCESS_TOKEN);
|
||||
makeWhoisHttpRequest(
|
||||
QUERY_CONTENT, RELAY_HOST, RELAY_PATH, mockCredentials, Optional.of(IAP_CLIENT_ID));
|
||||
// Input data passed to next handler
|
||||
assertThat(channel.writeInbound(inputBuffer)).isTrue();
|
||||
FullHttpRequest inputRequest = channel.readInbound();
|
||||
@@ -111,6 +128,27 @@ class WhoisServiceHandlerTest {
|
||||
assertThat(channel.isActive()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_withoutIapClientId() throws Exception {
|
||||
// Without an IAP client ID configured, we shouldn't include the proxy-authorization header
|
||||
WhoisServiceHandler nonIapHandler =
|
||||
new WhoisServiceHandler(
|
||||
RELAY_HOST, RELAY_PATH, () -> mockCredentials, Optional.empty(), metrics);
|
||||
channel = new EmbeddedChannel(nonIapHandler);
|
||||
|
||||
ByteBuf inputBuffer = Unpooled.wrappedBuffer(QUERY_CONTENT.getBytes(US_ASCII));
|
||||
FullHttpRequest expectedRequest =
|
||||
makeWhoisHttpRequest(
|
||||
QUERY_CONTENT, RELAY_HOST, RELAY_PATH, mockCredentials, Optional.empty());
|
||||
// Input data passed to next handler
|
||||
assertThat(channel.writeInbound(inputBuffer)).isTrue();
|
||||
FullHttpRequest inputRequest = channel.readInbound();
|
||||
assertThat(inputRequest).isEqualTo(expectedRequest);
|
||||
// The channel is still open, and nothing else is to be read from it.
|
||||
assertThat((Object) channel.readInbound()).isNull();
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_OutboundHttpResponseNotOK() {
|
||||
String outputString = "line1\r\nline2\r\n";
|
||||
|
||||
@@ -19,7 +19,16 @@
|
||||
# 3. Google Cloud SDK for generating the WARs.
|
||||
# 4. Git to manipulate the private and the merged repos.
|
||||
# 5. Docker to build and push images.
|
||||
# 6. cloudSchedulerDeployer for deploying cloud scheduler tasks based on config
|
||||
|
||||
FROM golang:1.19 as cloudSchedulerBuilder
|
||||
WORKDIR /usr/src/cloudSchedulerDeployer
|
||||
RUN go mod init cloudSchedulerDeployer
|
||||
COPY *.go ./
|
||||
RUN go build -o /cloudSchedulerDeployer
|
||||
|
||||
FROM marketplace.gcr.io/google/debian10
|
||||
ENV DEBIAN_FRONTEND=noninteractive LANG=en_US.UTF-8
|
||||
COPY --from=cloudSchedulerBuilder /cloudSchedulerDeployer /usr/local/bin/cloudSchedulerDeployer
|
||||
ADD ./build.sh .
|
||||
RUN ["bash", "./build.sh"]
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
// 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.
|
||||
|
||||
// The cloudScheduler tool allows creating, updating and deleting cloud
|
||||
// scheduler jobs from xml config file
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 3 || os.Args[1] == "" || os.Args[2] == "" {
|
||||
panic("Error - invalid parameters:\nFirst parameter required - config file path;\nSecond parameter required - project name")
|
||||
}
|
||||
|
||||
// Config file path
|
||||
configFileLocation := os.Args[1]
|
||||
// Project name where to submit the tasks
|
||||
projectName := os.Args[2]
|
||||
|
||||
log.Default().Println("Filepath " + configFileLocation)
|
||||
|
||||
xmlFile, err := os.Open(configFileLocation)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer xmlFile.Close()
|
||||
|
||||
type Task struct {
|
||||
XMLName xml.Name `xml:"task"`
|
||||
URL string `xml:"url"`
|
||||
Description string `xml:"description"`
|
||||
Schedule string `xml:"schedule"`
|
||||
Name string `xml:"name"`
|
||||
}
|
||||
|
||||
type Taskentries struct {
|
||||
XMLName xml.Name `xml:"taskentries"`
|
||||
Task []Task `xml:"task"`
|
||||
}
|
||||
|
||||
type ServiceAccount struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
type ExistingJob struct {
|
||||
Name string `json:"name"`
|
||||
State string `json:"state"`
|
||||
}
|
||||
|
||||
byteValue, _ := io.ReadAll(xmlFile)
|
||||
|
||||
var taskEntries Taskentries
|
||||
|
||||
if err := xml.Unmarshal(byteValue, &taskEntries); err != nil {
|
||||
panic("Failed to unmarshal taskentries: " + err.Error())
|
||||
}
|
||||
|
||||
getArgs := func(taskRecord Task, operationType string, serviceAccountEmail string) []string {
|
||||
// Cloud Schedule doesn't allow description of more than 499 chars and \n
|
||||
var description string
|
||||
if len(taskRecord.Description) > 499 {
|
||||
description = taskRecord.Description[:499]
|
||||
} else {
|
||||
description = taskRecord.Description
|
||||
}
|
||||
description = strings.ReplaceAll(description, "\n", " ")
|
||||
|
||||
return []string{
|
||||
"--project", projectName,
|
||||
"scheduler", "jobs", operationType,
|
||||
"http", taskRecord.Name,
|
||||
"--location", "us-central1",
|
||||
"--schedule", taskRecord.Schedule,
|
||||
"--uri", fmt.Sprintf("https://backend-dot-%s.appspot.com%s", projectName, taskRecord.URL),
|
||||
"--description", description,
|
||||
"--http-method", "get",
|
||||
"--oidc-service-account-email", serviceAccountEmail,
|
||||
"--oidc-token-audience", projectName,
|
||||
}
|
||||
}
|
||||
|
||||
// Get existing jobs from Cloud Scheduler
|
||||
var allExistingJobs []ExistingJob
|
||||
cmdGetExistingList := exec.Command("gcloud", "scheduler", "jobs", "list", "--project="+projectName, "--location=us-central1", "--format=json")
|
||||
cmdGetExistingListOutput, cmdGetExistingListError := cmdGetExistingList.CombinedOutput()
|
||||
if cmdGetExistingListError != nil {
|
||||
panic("Can't obtain existing cloud scheduler jobs for " + projectName)
|
||||
}
|
||||
err = json.Unmarshal(cmdGetExistingListOutput, &allExistingJobs)
|
||||
if err != nil {
|
||||
panic("Failed to parse existing jobs from cloud schedule: " + err.Error())
|
||||
}
|
||||
|
||||
// Sync deleted jobs
|
||||
enabledExistingJobs := map[string]bool{}
|
||||
for i := 0; i < len(allExistingJobs); i++ {
|
||||
jobName := strings.Split(allExistingJobs[i].Name, "jobs/")[1]
|
||||
toBeDeleted := true
|
||||
if allExistingJobs[i].State == "ENABLED" {
|
||||
enabledExistingJobs[jobName] = true
|
||||
}
|
||||
for i := 0; i < len(taskEntries.Task); i++ {
|
||||
if taskEntries.Task[i].Name == jobName {
|
||||
toBeDeleted = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if toBeDeleted {
|
||||
cmdDelete := exec.Command("gcloud", "scheduler", "jobs", "delete", jobName, "--project="+projectName, "--quiet")
|
||||
cmdDeleteOutput, cmdDeleteError := cmdDelete.CombinedOutput()
|
||||
log.Default().Println("Deleting cloud scheduler job " + jobName)
|
||||
if cmdDeleteError != nil {
|
||||
panic(string(cmdDeleteOutput))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find service account email
|
||||
var serviceAccounts []ServiceAccount
|
||||
var serviceAccountEmail string
|
||||
cmdGetServiceAccounts := exec.Command("gcloud", "iam", "service-accounts", "list", "--project="+projectName, "--format=json")
|
||||
cmdGetServiceAccountsOutput, cmdGetServiceAccountsError := cmdGetServiceAccounts.CombinedOutput()
|
||||
if cmdGetServiceAccountsError != nil {
|
||||
panic(cmdGetServiceAccountsError)
|
||||
}
|
||||
err = json.Unmarshal(cmdGetServiceAccountsOutput, &serviceAccounts)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
for i := 0; i < len(serviceAccounts); i++ {
|
||||
if serviceAccounts[i].DisplayName == "cloud-scheduler" {
|
||||
serviceAccountEmail = serviceAccounts[i].Email
|
||||
break
|
||||
}
|
||||
}
|
||||
if serviceAccountEmail == "" {
|
||||
panic("Service account for cloud scheduler is not created for " + projectName)
|
||||
}
|
||||
|
||||
// Sync created and updated jobs
|
||||
for i := 0; i < len(taskEntries.Task); i++ {
|
||||
cmdType := "update"
|
||||
if enabledExistingJobs[taskEntries.Task[i].Name] != true {
|
||||
cmdType = "create"
|
||||
}
|
||||
|
||||
syncCommand := exec.Command("gcloud", getArgs(taskEntries.Task[i], cmdType, serviceAccountEmail)...)
|
||||
syncCommandOutput, syncCommandError := syncCommand.CombinedOutput()
|
||||
log.Default().Println(cmdType + " cloud scheduler job " + taskEntries.Task[i].Name)
|
||||
if syncCommandError != nil {
|
||||
panic(string(syncCommandOutput))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,22 @@ steps:
|
||||
set -e
|
||||
gcloud secrets versions access latest \
|
||||
--secret nomulus-tool-cloudbuild-credential > tool-credential.json
|
||||
# Create/Update cloud scheduler jobs based on a cloud-scheduler-tasks.xml
|
||||
- name: 'gcr.io/$PROJECT_ID/builder:latest'
|
||||
entrypoint: /bin/bash
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
gcloud auth activate-service-account --key-file=tool-credential.json
|
||||
if [ ${_ENV} == production ]; then
|
||||
project_id="domain-registry"
|
||||
else
|
||||
project_id="domain-registry-${_ENV}"
|
||||
fi
|
||||
gsutil cp gs://$PROJECT_ID-deploy/${TAG_NAME}/${_ENV}.tar .
|
||||
tar -xvf ${_ENV}.tar
|
||||
cloudSchedulerDeployer default/WEB-INF/cloud-scheduler-tasks.xml $project_id
|
||||
# Deploy the GAE config files.
|
||||
# First authorize the gcloud tool to use the credential json file, then
|
||||
# download and unzip the tarball that contains the relevant config files
|
||||
@@ -43,8 +59,6 @@ steps:
|
||||
else
|
||||
project_id="domain-registry-${_ENV}"
|
||||
fi
|
||||
gsutil cp gs://$PROJECT_ID-deploy/${TAG_NAME}/${_ENV}.tar .
|
||||
tar -xvf ${_ENV}.tar
|
||||
for filename in cron dispatch queue; do
|
||||
gcloud -q --project $project_id app deploy \
|
||||
default/WEB-INF/appengine-generated/$filename.yaml
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user