mirror of
https://github.com/google/nomulus
synced 2026-05-18 13:51:45 +00:00
Compare commits
26 Commits
nomulus-20
...
nomulus-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebf07833e5 | ||
|
|
ee3ece8c56 | ||
|
|
57592d787c | ||
|
|
e6f9b1c7e6 | ||
|
|
7b59c4abbf | ||
|
|
f01adfb060 | ||
|
|
739a15851d | ||
|
|
2c961b6283 | ||
|
|
bcb2b2c784 | ||
|
|
a91ed0f1ad | ||
|
|
da28a2021c | ||
|
|
ffd952a60e | ||
|
|
97676d1a1f | ||
|
|
1dcbc9e0cb | ||
|
|
f59c387b9c | ||
|
|
cfcafeefc6 | ||
|
|
c32d831dd6 | ||
|
|
b38e0efe9a | ||
|
|
67cb411c99 | ||
|
|
9f551eb552 | ||
|
|
655f05c58c | ||
|
|
95c810ddf4 | ||
|
|
ec9a220bc3 | ||
|
|
68d35d2d95 | ||
|
|
99840488a1 | ||
|
|
ee7c8fb018 |
@@ -109,13 +109,6 @@ PRESUBMITS = {
|
||||
"System.(out|err).println is only allowed in tools/ packages. Please "
|
||||
"use a logger instead.",
|
||||
|
||||
# PostgreSQLContainer instantiation must specify docker tag
|
||||
# TODO(b/204572437): Fix the pattern to pass DatabaseSnapshotTest.java
|
||||
PresubmitCheck(
|
||||
r"[\s\S]*new\s+PostgreSQLContainer(<[\s\S]*>)?\(\s*\)[\s\S]*",
|
||||
"java", {"DatabaseSnapshotTest.java"}):
|
||||
"PostgreSQLContainer instantiation must specify docker tag.",
|
||||
|
||||
# Various Soy linting checks
|
||||
PresubmitCheck(
|
||||
r".* (/\*)?\* {?@param ",
|
||||
|
||||
@@ -12,12 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
||||
import { Observable, catchError, of } from 'rxjs';
|
||||
import { Contact } from '../../settings/contact/contact.service';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { catchError, Observable, of } from 'rxjs';
|
||||
import { SecuritySettingsBackendModel } from 'src/app/settings/security/security.service';
|
||||
|
||||
import { Contact } from '../../settings/contact/contact.service';
|
||||
|
||||
@Injectable()
|
||||
export class BackendService {
|
||||
constructor(private http: HttpClient) {}
|
||||
@@ -55,7 +56,7 @@ export class BackendService {
|
||||
): Observable<Contact[]> {
|
||||
return this.http.post<Contact[]>(
|
||||
`/console-api/settings/contacts?registrarId=${registrarId}`,
|
||||
{ contacts }
|
||||
contacts
|
||||
);
|
||||
}
|
||||
|
||||
@@ -85,7 +86,7 @@ export class BackendService {
|
||||
): Observable<SecuritySettingsBackendModel> {
|
||||
return this.http.post<SecuritySettingsBackendModel>(
|
||||
`/console-api/settings/security?registrarId=${registrarId}`,
|
||||
{ registrar: securitySettings }
|
||||
securitySettings
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
// 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.batch;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.primitives.Ints;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.domain.token.BulkPricingPackage;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.Service;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.ui.server.SendEmailUtils;
|
||||
import google.registry.util.Clock;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import org.joda.time.Days;
|
||||
|
||||
/**
|
||||
* An action that checks all {@link BulkPricingPackage} objects for compliance with their max create
|
||||
* limit.
|
||||
*/
|
||||
@Action(
|
||||
service = Service.BACKEND,
|
||||
path = CheckBulkComplianceAction.PATH,
|
||||
auth = Auth.AUTH_API_ADMIN)
|
||||
public class CheckBulkComplianceAction implements Runnable {
|
||||
|
||||
public static final String PATH = "/_dr/task/checkBulkCompliance";
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
private final SendEmailUtils sendEmailUtils;
|
||||
private final Clock clock;
|
||||
private final String bulkPricingPackageCreateLimitEmailSubject;
|
||||
private final String bulkPricingPackageDomainLimitWarningEmailSubject;
|
||||
private final String bulkPricingPackageDomainLimitUpgradeEmailSubject;
|
||||
private final String bulkPricingPackageCreateLimitEmailBody;
|
||||
private final String bulkPricingPackageDomainLimitWarningEmailBody;
|
||||
private final String bulkPricingPackageDomainLimitUpgradeEmailBody;
|
||||
private final String registrySupportEmail;
|
||||
private static final int THIRTY_DAYS = 30;
|
||||
private static final int FORTY_DAYS = 40;
|
||||
|
||||
@Inject
|
||||
public CheckBulkComplianceAction(
|
||||
SendEmailUtils sendEmailUtils,
|
||||
Clock clock,
|
||||
@Config("bulkPricingPackageCreateLimitEmailSubject")
|
||||
String bulkPricingPackageCreateLimitEmailSubject,
|
||||
@Config("bulkPricingPackageDomainLimitWarningEmailSubject")
|
||||
String bulkPricingPackageDomainLimitWarningEmailSubject,
|
||||
@Config("bulkPricingPackageDomainLimitUpgradeEmailSubject")
|
||||
String bulkPricingPackageDomainLimitUpgradeEmailSubject,
|
||||
@Config("bulkPricingPackageCreateLimitEmailBody")
|
||||
String bulkPricingPackageCreateLimitEmailBody,
|
||||
@Config("bulkPricingPackageDomainLimitWarningEmailBody")
|
||||
String bulkPricingPackageDomainLimitWarningEmailBody,
|
||||
@Config("bulkPricingPackageDomainLimitUpgradeEmailBody")
|
||||
String bulkPricingPackageDomainLimitUpgradeEmailBody,
|
||||
@Config("registrySupportEmail") String registrySupportEmail) {
|
||||
this.sendEmailUtils = sendEmailUtils;
|
||||
this.clock = clock;
|
||||
this.bulkPricingPackageCreateLimitEmailSubject = bulkPricingPackageCreateLimitEmailSubject;
|
||||
this.bulkPricingPackageDomainLimitWarningEmailSubject =
|
||||
bulkPricingPackageDomainLimitWarningEmailSubject;
|
||||
this.bulkPricingPackageDomainLimitUpgradeEmailSubject =
|
||||
bulkPricingPackageDomainLimitUpgradeEmailSubject;
|
||||
this.bulkPricingPackageCreateLimitEmailBody = bulkPricingPackageCreateLimitEmailBody;
|
||||
this.bulkPricingPackageDomainLimitWarningEmailBody =
|
||||
bulkPricingPackageDomainLimitWarningEmailBody;
|
||||
this.bulkPricingPackageDomainLimitUpgradeEmailBody =
|
||||
bulkPricingPackageDomainLimitUpgradeEmailBody;
|
||||
this.registrySupportEmail = registrySupportEmail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
tm().transact(this::checkBulkPackages);
|
||||
}
|
||||
|
||||
private void checkBulkPackages() {
|
||||
ImmutableList<BulkPricingPackage> bulkPricingPackages =
|
||||
tm().loadAllOf(BulkPricingPackage.class);
|
||||
ImmutableMap.Builder<BulkPricingPackage, Long> bulkPricingPackagesOverCreateLimitBuilder =
|
||||
new ImmutableMap.Builder<>();
|
||||
ImmutableMap.Builder<BulkPricingPackage, Long>
|
||||
bulkPricingPackagesOverActiveDomainsLimitBuilder = new ImmutableMap.Builder<>();
|
||||
for (BulkPricingPackage bulkPricingPackage : bulkPricingPackages) {
|
||||
Long creates =
|
||||
(Long)
|
||||
tm().query(
|
||||
"SELECT COUNT(*) FROM DomainHistory WHERE current_package_token ="
|
||||
+ " :token AND modificationTime >= :lastBilling AND type ="
|
||||
+ " 'DOMAIN_CREATE'")
|
||||
.setParameter("token", bulkPricingPackage.getToken().getKey().toString())
|
||||
.setParameter(
|
||||
"lastBilling", bulkPricingPackage.getNextBillingDate().minusYears(1))
|
||||
.getSingleResult();
|
||||
if (creates > bulkPricingPackage.getMaxCreates()) {
|
||||
long overage = creates - bulkPricingPackage.getMaxCreates();
|
||||
logger.atInfo().log(
|
||||
"Bulk pricing package with bulk token %s has exceeded their max domain creation limit"
|
||||
+ " by %d name(s).",
|
||||
bulkPricingPackage.getToken().getKey(), overage);
|
||||
bulkPricingPackagesOverCreateLimitBuilder.put(bulkPricingPackage, creates);
|
||||
}
|
||||
|
||||
Long activeDomains =
|
||||
tm().query(
|
||||
"SELECT COUNT(*) FROM Domain WHERE currentBulkToken = :token"
|
||||
+ " AND deletionTime = :endOfTime",
|
||||
Long.class)
|
||||
.setParameter("token", bulkPricingPackage.getToken())
|
||||
.setParameter("endOfTime", END_OF_TIME)
|
||||
.getSingleResult();
|
||||
if (activeDomains > bulkPricingPackage.getMaxDomains()) {
|
||||
int overage = Ints.saturatedCast(activeDomains) - bulkPricingPackage.getMaxDomains();
|
||||
logger.atInfo().log(
|
||||
"Bulk pricing package with bulk token %s has exceed their max active domains limit by"
|
||||
+ " %d name(s).",
|
||||
bulkPricingPackage.getToken().getKey(), overage);
|
||||
bulkPricingPackagesOverActiveDomainsLimitBuilder.put(bulkPricingPackage, activeDomains);
|
||||
}
|
||||
}
|
||||
handleBulkPricingPackageCreationOverage(bulkPricingPackagesOverCreateLimitBuilder.build());
|
||||
handleActiveDomainOverage(bulkPricingPackagesOverActiveDomainsLimitBuilder.build());
|
||||
}
|
||||
|
||||
private void handleBulkPricingPackageCreationOverage(
|
||||
ImmutableMap<BulkPricingPackage, Long> overageList) {
|
||||
if (overageList.isEmpty()) {
|
||||
logger.atInfo().log("Found no bulk pricing packages over their create limit.");
|
||||
return;
|
||||
}
|
||||
logger.atInfo().log(
|
||||
"Found %d bulk pricing packages over their create limit.", overageList.size());
|
||||
for (BulkPricingPackage bulkPricingPackage : overageList.keySet()) {
|
||||
AllocationToken bulkToken = tm().loadByKey(bulkPricingPackage.getToken());
|
||||
Optional<Registrar> registrar =
|
||||
Registrar.loadByRegistrarIdCached(
|
||||
Iterables.getOnlyElement(bulkToken.getAllowedRegistrarIds()));
|
||||
if (registrar.isPresent()) {
|
||||
String body =
|
||||
String.format(
|
||||
bulkPricingPackageCreateLimitEmailBody,
|
||||
bulkPricingPackage.getId(),
|
||||
bulkToken.getToken(),
|
||||
registrar.get().getRegistrarName(),
|
||||
bulkPricingPackage.getMaxCreates(),
|
||||
overageList.get(bulkPricingPackage));
|
||||
sendNotification(
|
||||
bulkToken, bulkPricingPackageCreateLimitEmailSubject, body, registrar.get());
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
String.format("Could not find registrar for bulk token %s", bulkToken));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleActiveDomainOverage(ImmutableMap<BulkPricingPackage, Long> overageList) {
|
||||
if (overageList.isEmpty()) {
|
||||
logger.atInfo().log("Found no bulk pricing packages over their active domains limit.");
|
||||
return;
|
||||
}
|
||||
logger.atInfo().log(
|
||||
"Found %d bulk pricing packages over their active domains limit.", overageList.size());
|
||||
for (BulkPricingPackage bulkPricingPackage : overageList.keySet()) {
|
||||
int daysSinceLastNotification =
|
||||
bulkPricingPackage
|
||||
.getLastNotificationSent()
|
||||
.map(sentDate -> Days.daysBetween(sentDate, clock.nowUtc()).getDays())
|
||||
.orElse(Integer.MAX_VALUE);
|
||||
if (daysSinceLastNotification < THIRTY_DAYS) {
|
||||
// Don't send an email if notification was already sent within the last 30
|
||||
// days
|
||||
continue;
|
||||
} else if (daysSinceLastNotification < FORTY_DAYS) {
|
||||
// Send an upgrade email if last email was between 30 and 40 days ago
|
||||
sendActiveDomainOverageEmail(
|
||||
/* warning= */ false, bulkPricingPackage, overageList.get(bulkPricingPackage));
|
||||
} else {
|
||||
// Send a warning email
|
||||
sendActiveDomainOverageEmail(
|
||||
/* warning= */ true, bulkPricingPackage, overageList.get(bulkPricingPackage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendActiveDomainOverageEmail(
|
||||
boolean warning, BulkPricingPackage bulkPricingPackage, long activeDomains) {
|
||||
String emailSubject =
|
||||
warning
|
||||
? bulkPricingPackageDomainLimitWarningEmailSubject
|
||||
: bulkPricingPackageDomainLimitUpgradeEmailSubject;
|
||||
String emailTemplate =
|
||||
warning
|
||||
? bulkPricingPackageDomainLimitWarningEmailBody
|
||||
: bulkPricingPackageDomainLimitUpgradeEmailBody;
|
||||
AllocationToken bulkToken = tm().loadByKey(bulkPricingPackage.getToken());
|
||||
Optional<Registrar> registrar =
|
||||
Registrar.loadByRegistrarIdCached(
|
||||
Iterables.getOnlyElement(bulkToken.getAllowedRegistrarIds()));
|
||||
if (registrar.isPresent()) {
|
||||
String body =
|
||||
String.format(
|
||||
emailTemplate,
|
||||
bulkPricingPackage.getId(),
|
||||
bulkToken.getToken(),
|
||||
registrar.get().getRegistrarName(),
|
||||
bulkPricingPackage.getMaxDomains(),
|
||||
activeDomains);
|
||||
sendNotification(bulkToken, emailSubject, body, registrar.get());
|
||||
tm().put(bulkPricingPackage.asBuilder().setLastNotificationSent(clock.nowUtc()).build());
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
String.format("Could not find registrar for bulk token %s", bulkToken));
|
||||
}
|
||||
}
|
||||
|
||||
private void sendNotification(
|
||||
AllocationToken bulkToken, String subject, String body, Registrar registrar) {
|
||||
logger.atInfo().log(
|
||||
String.format(
|
||||
"Compliance email sent to support regarding the %s registrar and the bulk pricing"
|
||||
+ " package with token %s.",
|
||||
registrar.getRegistrarName(), bulkToken.getToken()));
|
||||
sendEmailUtils.sendEmail(subject, body, ImmutableList.of(registrySupportEmail));
|
||||
}
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
// 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.batch;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.primitives.Ints;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.domain.token.PackagePromotion;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.Service;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.ui.server.SendEmailUtils;
|
||||
import google.registry.util.Clock;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import org.joda.time.Days;
|
||||
|
||||
/**
|
||||
* An action that checks all {@link PackagePromotion} objects for compliance with their max create
|
||||
* limit.
|
||||
*/
|
||||
@Action(
|
||||
service = Service.BACKEND,
|
||||
path = CheckPackagesComplianceAction.PATH,
|
||||
auth = Auth.AUTH_API_ADMIN)
|
||||
public class CheckPackagesComplianceAction implements Runnable {
|
||||
|
||||
public static final String PATH = "/_dr/task/checkPackagesCompliance";
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
private final SendEmailUtils sendEmailUtils;
|
||||
private final Clock clock;
|
||||
private final String packageCreateLimitEmailSubject;
|
||||
private final String packageDomainLimitWarningEmailSubject;
|
||||
private final String packageDomainLimitUpgradeEmailSubject;
|
||||
private final String packageCreateLimitEmailBody;
|
||||
private final String packageDomainLimitWarningEmailBody;
|
||||
private final String packageDomainLimitUpgradeEmailBody;
|
||||
private final String registrySupportEmail;
|
||||
private static final int THIRTY_DAYS = 30;
|
||||
private static final int FORTY_DAYS = 40;
|
||||
|
||||
@Inject
|
||||
public CheckPackagesComplianceAction(
|
||||
SendEmailUtils sendEmailUtils,
|
||||
Clock clock,
|
||||
@Config("packageCreateLimitEmailSubject") String packageCreateLimitEmailSubject,
|
||||
@Config("packageDomainLimitWarningEmailSubject") String packageDomainLimitWarningEmailSubject,
|
||||
@Config("packageDomainLimitUpgradeEmailSubject") String packageDomainLimitUpgradeEmailSubject,
|
||||
@Config("packageCreateLimitEmailBody") String packageCreateLimitEmailBody,
|
||||
@Config("packageDomainLimitWarningEmailBody") String packageDomainLimitWarningEmailBody,
|
||||
@Config("packageDomainLimitUpgradeEmailBody") String packageDomainLimitUpgradeEmailBody,
|
||||
@Config("registrySupportEmail") String registrySupportEmail) {
|
||||
this.sendEmailUtils = sendEmailUtils;
|
||||
this.clock = clock;
|
||||
this.packageCreateLimitEmailSubject = packageCreateLimitEmailSubject;
|
||||
this.packageDomainLimitWarningEmailSubject = packageDomainLimitWarningEmailSubject;
|
||||
this.packageDomainLimitUpgradeEmailSubject = packageDomainLimitUpgradeEmailSubject;
|
||||
this.packageCreateLimitEmailBody = packageCreateLimitEmailBody;
|
||||
this.packageDomainLimitWarningEmailBody = packageDomainLimitWarningEmailBody;
|
||||
this.packageDomainLimitUpgradeEmailBody = packageDomainLimitUpgradeEmailBody;
|
||||
this.registrySupportEmail = registrySupportEmail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
tm().transact(this::checkPackages);
|
||||
}
|
||||
|
||||
private void checkPackages() {
|
||||
ImmutableList<PackagePromotion> packages = tm().loadAllOf(PackagePromotion.class);
|
||||
ImmutableMap.Builder<PackagePromotion, Long> packagesOverCreateLimitBuilder =
|
||||
new ImmutableMap.Builder<>();
|
||||
ImmutableMap.Builder<PackagePromotion, Long> packagesOverActiveDomainsLimitBuilder =
|
||||
new ImmutableMap.Builder<>();
|
||||
for (PackagePromotion packagePromo : packages) {
|
||||
Long creates =
|
||||
(Long)
|
||||
tm().query(
|
||||
"SELECT COUNT(*) FROM DomainHistory WHERE current_package_token ="
|
||||
+ " :token AND modificationTime >= :lastBilling AND type ="
|
||||
+ " 'DOMAIN_CREATE'")
|
||||
.setParameter("token", packagePromo.getToken().getKey().toString())
|
||||
.setParameter("lastBilling", packagePromo.getNextBillingDate().minusYears(1))
|
||||
.getSingleResult();
|
||||
if (creates > packagePromo.getMaxCreates()) {
|
||||
long overage = creates - packagePromo.getMaxCreates();
|
||||
logger.atInfo().log(
|
||||
"Package with package token %s has exceeded their max domain creation limit"
|
||||
+ " by %d name(s).",
|
||||
packagePromo.getToken().getKey(), overage);
|
||||
packagesOverCreateLimitBuilder.put(packagePromo, creates);
|
||||
}
|
||||
|
||||
Long activeDomains =
|
||||
tm().query(
|
||||
"SELECT COUNT(*) FROM Domain WHERE currentPackageToken = :token"
|
||||
+ " AND deletionTime = :endOfTime",
|
||||
Long.class)
|
||||
.setParameter("token", packagePromo.getToken())
|
||||
.setParameter("endOfTime", END_OF_TIME)
|
||||
.getSingleResult();
|
||||
if (activeDomains > packagePromo.getMaxDomains()) {
|
||||
int overage = Ints.saturatedCast(activeDomains) - packagePromo.getMaxDomains();
|
||||
logger.atInfo().log(
|
||||
"Package with package token %s has exceed their max active domains limit by"
|
||||
+ " %d name(s).",
|
||||
packagePromo.getToken().getKey(), overage);
|
||||
packagesOverActiveDomainsLimitBuilder.put(packagePromo, activeDomains);
|
||||
}
|
||||
}
|
||||
handlePackageCreationOverage(packagesOverCreateLimitBuilder.build());
|
||||
handleActiveDomainOverage(packagesOverActiveDomainsLimitBuilder.build());
|
||||
}
|
||||
|
||||
private void handlePackageCreationOverage(ImmutableMap<PackagePromotion, Long> overageList) {
|
||||
if (overageList.isEmpty()) {
|
||||
logger.atInfo().log("Found no packages over their create limit.");
|
||||
return;
|
||||
}
|
||||
logger.atInfo().log("Found %d packages over their create limit.", overageList.size());
|
||||
for (PackagePromotion packagePromotion : overageList.keySet()) {
|
||||
AllocationToken packageToken = tm().loadByKey(packagePromotion.getToken());
|
||||
Optional<Registrar> registrar =
|
||||
Registrar.loadByRegistrarIdCached(
|
||||
Iterables.getOnlyElement(packageToken.getAllowedRegistrarIds()));
|
||||
if (registrar.isPresent()) {
|
||||
String body =
|
||||
String.format(
|
||||
packageCreateLimitEmailBody,
|
||||
packagePromotion.getId(),
|
||||
packageToken.getToken(),
|
||||
registrar.get().getRegistrarName(),
|
||||
packagePromotion.getMaxCreates(),
|
||||
overageList.get(packagePromotion));
|
||||
sendNotification(packageToken, packageCreateLimitEmailSubject, body, registrar.get());
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
String.format("Could not find registrar for package token %s", packageToken));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleActiveDomainOverage(ImmutableMap<PackagePromotion, Long> overageList) {
|
||||
if (overageList.isEmpty()) {
|
||||
logger.atInfo().log("Found no packages over their active domains limit.");
|
||||
return;
|
||||
}
|
||||
logger.atInfo().log("Found %d packages over their active domains limit.", overageList.size());
|
||||
for (PackagePromotion packagePromotion : overageList.keySet()) {
|
||||
int daysSinceLastNotification =
|
||||
packagePromotion
|
||||
.getLastNotificationSent()
|
||||
.map(sentDate -> Days.daysBetween(sentDate, clock.nowUtc()).getDays())
|
||||
.orElse(Integer.MAX_VALUE);
|
||||
if (daysSinceLastNotification < THIRTY_DAYS) {
|
||||
// Don't send an email if notification was already sent within the last 30
|
||||
// days
|
||||
continue;
|
||||
} else if (daysSinceLastNotification < FORTY_DAYS) {
|
||||
// Send an upgrade email if last email was between 30 and 40 days ago
|
||||
sendActiveDomainOverageEmail(
|
||||
/* warning= */ false, packagePromotion, overageList.get(packagePromotion));
|
||||
} else {
|
||||
// Send a warning email
|
||||
sendActiveDomainOverageEmail(
|
||||
/* warning= */ true, packagePromotion, overageList.get(packagePromotion));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendActiveDomainOverageEmail(
|
||||
boolean warning, PackagePromotion packagePromotion, long activeDomains) {
|
||||
String emailSubject =
|
||||
warning ? packageDomainLimitWarningEmailSubject : packageDomainLimitUpgradeEmailSubject;
|
||||
String emailTemplate =
|
||||
warning ? packageDomainLimitWarningEmailBody : packageDomainLimitUpgradeEmailBody;
|
||||
AllocationToken packageToken = tm().loadByKey(packagePromotion.getToken());
|
||||
Optional<Registrar> registrar =
|
||||
Registrar.loadByRegistrarIdCached(
|
||||
Iterables.getOnlyElement(packageToken.getAllowedRegistrarIds()));
|
||||
if (registrar.isPresent()) {
|
||||
String body =
|
||||
String.format(
|
||||
emailTemplate,
|
||||
packagePromotion.getId(),
|
||||
packageToken.getToken(),
|
||||
registrar.get().getRegistrarName(),
|
||||
packagePromotion.getMaxDomains(),
|
||||
activeDomains);
|
||||
sendNotification(packageToken, emailSubject, body, registrar.get());
|
||||
tm().put(packagePromotion.asBuilder().setLastNotificationSent(clock.nowUtc()).build());
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
String.format("Could not find registrar for package token %s", packageToken));
|
||||
}
|
||||
}
|
||||
|
||||
private void sendNotification(
|
||||
AllocationToken packageToken, String subject, String body, Registrar registrar) {
|
||||
logger.atInfo().log(
|
||||
String.format(
|
||||
"Compliance email sent to support regarding the %s registrar and the package with token"
|
||||
+ " %s.",
|
||||
registrar.getRegistrarName(), packageToken.getToken()));
|
||||
sendEmailUtils.sendEmail(subject, body, ImmutableList.of(registrySupportEmail));
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.groups.GmailClient;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.RegistryLock;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
@@ -40,7 +41,6 @@ import google.registry.request.auth.Auth;
|
||||
import google.registry.tools.DomainLockUtils;
|
||||
import google.registry.util.DateTimeUtils;
|
||||
import google.registry.util.EmailMessage;
|
||||
import google.registry.util.SendEmailService;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import javax.mail.internet.AddressException;
|
||||
@@ -84,7 +84,7 @@ public class RelockDomainAction implements Runnable {
|
||||
private final InternetAddress alertRecipientAddress;
|
||||
private final InternetAddress gSuiteOutgoingEmailAddress;
|
||||
private final String supportEmail;
|
||||
private final SendEmailService sendEmailService;
|
||||
private final GmailClient gmailClient;
|
||||
private final DomainLockUtils domainLockUtils;
|
||||
private final Response response;
|
||||
|
||||
@@ -92,10 +92,10 @@ public class RelockDomainAction implements Runnable {
|
||||
public RelockDomainAction(
|
||||
@Parameter(OLD_UNLOCK_REVISION_ID_PARAM) long oldUnlockRevisionId,
|
||||
@Parameter(PREVIOUS_ATTEMPTS_PARAM) int previousAttempts,
|
||||
@Config("alertRecipientEmailAddress") InternetAddress alertRecipientAddress,
|
||||
@Config("newAlertRecipientEmailAddress") InternetAddress alertRecipientAddress,
|
||||
@Config("gSuiteOutgoingEmailAddress") InternetAddress gSuiteOutgoingEmailAddress,
|
||||
@Config("supportEmail") String supportEmail,
|
||||
SendEmailService sendEmailService,
|
||||
GmailClient gmailClient,
|
||||
DomainLockUtils domainLockUtils,
|
||||
Response response) {
|
||||
this.oldUnlockRevisionId = oldUnlockRevisionId;
|
||||
@@ -103,7 +103,7 @@ public class RelockDomainAction implements Runnable {
|
||||
this.alertRecipientAddress = alertRecipientAddress;
|
||||
this.gSuiteOutgoingEmailAddress = gSuiteOutgoingEmailAddress;
|
||||
this.supportEmail = supportEmail;
|
||||
this.sendEmailService = sendEmailService;
|
||||
this.gmailClient = gmailClient;
|
||||
this.domainLockUtils = domainLockUtils;
|
||||
this.response = response;
|
||||
}
|
||||
@@ -215,7 +215,7 @@ public class RelockDomainAction implements Runnable {
|
||||
oldLock.getDomainName(),
|
||||
t.getMessage(),
|
||||
supportEmail);
|
||||
sendEmailService.sendEmail(
|
||||
gmailClient.sendEmail(
|
||||
EmailMessage.newBuilder()
|
||||
.setFrom(gSuiteOutgoingEmailAddress)
|
||||
.setBody(body)
|
||||
@@ -245,7 +245,7 @@ public class RelockDomainAction implements Runnable {
|
||||
String body =
|
||||
String.format(RELOCK_SUCCESS_EMAIL_TEMPLATE, oldLock.getDomainName(), supportEmail);
|
||||
|
||||
sendEmailService.sendEmail(
|
||||
gmailClient.sendEmail(
|
||||
EmailMessage.newBuilder()
|
||||
.setFrom(gSuiteOutgoingEmailAddress)
|
||||
.setBody(body)
|
||||
@@ -264,7 +264,7 @@ public class RelockDomainAction implements Runnable {
|
||||
.addAll(getEmailRecipients(oldLock.getRegistrarId()))
|
||||
.add(alertRecipientAddress)
|
||||
.build();
|
||||
sendEmailService.sendEmail(
|
||||
gmailClient.sendEmail(
|
||||
EmailMessage.newBuilder()
|
||||
.setFrom(gSuiteOutgoingEmailAddress)
|
||||
.setBody(body)
|
||||
@@ -274,7 +274,7 @@ public class RelockDomainAction implements Runnable {
|
||||
}
|
||||
|
||||
private void sendUnknownRevisionIdAlertEmail() {
|
||||
sendEmailService.sendEmail(
|
||||
gmailClient.sendEmail(
|
||||
EmailMessage.newBuilder()
|
||||
.setFrom(gSuiteOutgoingEmailAddress)
|
||||
.setBody(String.format(RELOCK_UNKNOWN_ID_FAILURE_EMAIL_TEMPLATE, oldUnlockRevisionId))
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.groups.GmailClient;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.model.registrar.RegistrarPoc.Type;
|
||||
@@ -38,7 +39,6 @@ import google.registry.request.Action;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.EmailMessage;
|
||||
import google.registry.util.SendEmailService;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
@@ -72,7 +72,7 @@ public class SendExpiringCertificateNotificationEmailAction implements Runnable
|
||||
|
||||
private final CertificateChecker certificateChecker;
|
||||
private final String expirationWarningEmailBodyText;
|
||||
private final SendEmailService sendEmailService;
|
||||
private final GmailClient gmailClient;
|
||||
private final String expirationWarningEmailSubjectText;
|
||||
private final InternetAddress gSuiteOutgoingEmailAddress;
|
||||
private final Response response;
|
||||
@@ -82,12 +82,12 @@ public class SendExpiringCertificateNotificationEmailAction implements Runnable
|
||||
@Config("expirationWarningEmailBodyText") String expirationWarningEmailBodyText,
|
||||
@Config("expirationWarningEmailSubjectText") String expirationWarningEmailSubjectText,
|
||||
@Config("gSuiteOutgoingEmailAddress") InternetAddress gSuiteOutgoingEmailAddress,
|
||||
SendEmailService sendEmailService,
|
||||
GmailClient gmailClient,
|
||||
CertificateChecker certificateChecker,
|
||||
Response response) {
|
||||
this.certificateChecker = certificateChecker;
|
||||
this.expirationWarningEmailSubjectText = expirationWarningEmailSubjectText;
|
||||
this.sendEmailService = sendEmailService;
|
||||
this.gmailClient = gmailClient;
|
||||
this.gSuiteOutgoingEmailAddress = gSuiteOutgoingEmailAddress;
|
||||
this.expirationWarningEmailBodyText = expirationWarningEmailBodyText;
|
||||
this.response = response;
|
||||
@@ -173,7 +173,7 @@ public class SendExpiringCertificateNotificationEmailAction implements Runnable
|
||||
registrar.getRegistrarName());
|
||||
return false;
|
||||
}
|
||||
sendEmailService.sendEmail(
|
||||
gmailClient.sendEmail(
|
||||
EmailMessage.newBuilder()
|
||||
.setFrom(gSuiteOutgoingEmailAddress)
|
||||
.setSubject(expirationWarningEmailSubjectText)
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.beam.common;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import java.util.List;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityTransaction;
|
||||
|
||||
/**
|
||||
* A database snapshot shareable by concurrent queries from multiple database clients. A snapshot is
|
||||
* uniquely identified by its {@link #getSnapshotId snapshotId}, and must stay open until all
|
||||
* concurrent queries to this snapshot have attached to it by calling {@link
|
||||
* google.registry.persistence.transaction.JpaTransactionManager#setDatabaseSnapshot}. However, it
|
||||
* can be closed before those queries complete.
|
||||
*
|
||||
* <p>This feature is <em>Postgresql-only</em>.
|
||||
*
|
||||
* <p>To support large queries, transaction isolation level is fixed at the REPEATABLE_READ to avoid
|
||||
* exhausting predicate locks at the SERIALIZABLE level.
|
||||
*/
|
||||
// TODO(b/193662898): vendor-independent support for richer transaction semantics.
|
||||
public class DatabaseSnapshot implements AutoCloseable {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private String snapshotId;
|
||||
private EntityManager entityManager;
|
||||
private EntityTransaction transaction;
|
||||
|
||||
private DatabaseSnapshot() {}
|
||||
|
||||
public String getSnapshotId() {
|
||||
checkState(entityManager != null, "Snapshot not opened yet.");
|
||||
checkState(entityManager.isOpen(), "Snapshot already closed.");
|
||||
return snapshotId;
|
||||
}
|
||||
|
||||
private DatabaseSnapshot open() {
|
||||
entityManager = tm().getStandaloneEntityManager();
|
||||
transaction = entityManager.getTransaction();
|
||||
transaction.setRollbackOnly();
|
||||
transaction.begin();
|
||||
|
||||
entityManager
|
||||
.createNativeQuery("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ")
|
||||
.executeUpdate();
|
||||
|
||||
List<?> snapshotIds =
|
||||
entityManager.createNativeQuery("SELECT pg_export_snapshot();").getResultList();
|
||||
checkState(snapshotIds.size() == 1, "Unexpected number of snapshots: %s", snapshotIds.size());
|
||||
snapshotId = (String) snapshotIds.get(0);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (transaction != null && transaction.isActive()) {
|
||||
try {
|
||||
transaction.rollback();
|
||||
} catch (Exception e) {
|
||||
logger.atWarning().withCause(e).log("Failed to close a Database Snapshot");
|
||||
}
|
||||
}
|
||||
if (entityManager != null && entityManager.isOpen()) {
|
||||
entityManager.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static DatabaseSnapshot createSnapshot() {
|
||||
return new DatabaseSnapshot().open();
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,7 @@ public final class RegistryJpaIO {
|
||||
@AutoValue
|
||||
public abstract static class Read<R, T> extends PTransform<PBegin, PCollection<T>> {
|
||||
|
||||
private static final long serialVersionUID = 6906842877429561700L;
|
||||
public static final String DEFAULT_NAME = "RegistryJpaIO.Read";
|
||||
|
||||
abstract String name();
|
||||
@@ -133,9 +134,6 @@ public final class RegistryJpaIO {
|
||||
@Nullable
|
||||
abstract Coder<T> coder();
|
||||
|
||||
@Nullable
|
||||
abstract String snapshotId();
|
||||
|
||||
abstract Builder<R, T> toBuilder();
|
||||
|
||||
@Override
|
||||
@@ -145,8 +143,7 @@ public final class RegistryJpaIO {
|
||||
input
|
||||
.apply("Starting " + name(), Create.of((Void) null))
|
||||
.apply(
|
||||
"Run query for " + name(),
|
||||
ParDo.of(new QueryRunner<>(query(), resultMapper(), snapshotId())));
|
||||
"Run query for " + name(), ParDo.of(new QueryRunner<>(query(), resultMapper())));
|
||||
if (coder() != null) {
|
||||
output = output.setCoder(coder());
|
||||
}
|
||||
@@ -165,18 +162,6 @@ public final class RegistryJpaIO {
|
||||
return toBuilder().coder(coder).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the database snapshot to use for this query.
|
||||
*
|
||||
* <p>This feature is <em>Postgresql-only</em>. User is responsible for keeping the snapshot
|
||||
* available until all JVM workers have started using it by calling {@link
|
||||
* JpaTransactionManager#setDatabaseSnapshot}.
|
||||
*/
|
||||
// TODO(b/193662898): vendor-independent support for richer transaction semantics.
|
||||
public Read<R, T> withSnapshot(@Nullable String snapshotId) {
|
||||
return toBuilder().snapshotId(snapshotId).build();
|
||||
}
|
||||
|
||||
static <R, T> Builder<R, T> builder() {
|
||||
return new AutoValue_RegistryJpaIO_Read.Builder<R, T>().name(DEFAULT_NAME);
|
||||
}
|
||||
@@ -192,8 +177,6 @@ public final class RegistryJpaIO {
|
||||
|
||||
abstract Builder<R, T> coder(Coder<T> coder);
|
||||
|
||||
abstract Builder<R, T> snapshotId(@Nullable String sharedSnapshotId);
|
||||
|
||||
abstract Read<R, T> build();
|
||||
|
||||
Builder<R, T> criteriaQuery(CriteriaQuerySupplier<R> criteriaQuery) {
|
||||
@@ -214,27 +197,20 @@ public final class RegistryJpaIO {
|
||||
}
|
||||
|
||||
static class QueryRunner<R, T> extends DoFn<Void, T> {
|
||||
|
||||
private static final long serialVersionUID = 7293891513058653334L;
|
||||
private final RegistryQuery<R> query;
|
||||
private final SerializableFunction<R, T> resultMapper;
|
||||
// java.util.Optional is not serializable. Use of Guava Optional is discouraged.
|
||||
@Nullable private final String snapshotId;
|
||||
|
||||
QueryRunner(
|
||||
RegistryQuery<R> query,
|
||||
SerializableFunction<R, T> resultMapper,
|
||||
@Nullable String snapshotId) {
|
||||
QueryRunner(RegistryQuery<R> query, SerializableFunction<R, T> resultMapper) {
|
||||
this.query = query;
|
||||
this.resultMapper = resultMapper;
|
||||
this.snapshotId = snapshotId;
|
||||
}
|
||||
|
||||
@ProcessElement
|
||||
public void processElement(OutputReceiver<T> outputReceiver) {
|
||||
tm().transactNoRetry(
|
||||
() -> {
|
||||
if (snapshotId != null) {
|
||||
tm().setDatabaseSnapshot(snapshotId);
|
||||
}
|
||||
query.stream().map(resultMapper::apply).forEach(outputReceiver::output);
|
||||
});
|
||||
}
|
||||
@@ -256,8 +232,8 @@ public final class RegistryJpaIO {
|
||||
@AutoValue
|
||||
public abstract static class Write<T> extends PTransform<PCollection<T>, PCollection<Void>> {
|
||||
|
||||
private static final long serialVersionUID = -4023583243078410323L;
|
||||
public static final String DEFAULT_NAME = "RegistryJpaIO.Write";
|
||||
|
||||
public static final int DEFAULT_BATCH_SIZE = 1;
|
||||
|
||||
public abstract String name();
|
||||
@@ -321,6 +297,8 @@ public final class RegistryJpaIO {
|
||||
|
||||
/** Writes a batch of entities to a SQL database through a {@link JpaTransactionManager}. */
|
||||
private static class SqlBatchWriter<T> extends DoFn<KV<ShardedKey<Integer>, Iterable<T>>, Void> {
|
||||
|
||||
private static final long serialVersionUID = -7519944406319472690L;
|
||||
private final Counter counter;
|
||||
private final SerializableFunction<T, Object> jpaConverter;
|
||||
|
||||
@@ -337,7 +315,7 @@ public final class RegistryJpaIO {
|
||||
private void actuallyProcessElement(@Element KV<ShardedKey<Integer>, Iterable<T>> kv) {
|
||||
ImmutableList<Object> entities =
|
||||
Streams.stream(kv.getValue())
|
||||
.map(this.jpaConverter::apply)
|
||||
.map(jpaConverter::apply)
|
||||
// TODO(b/177340730): post migration delete the line below.
|
||||
.filter(Objects::nonNull)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
@@ -373,7 +351,7 @@ public final class RegistryJpaIO {
|
||||
}
|
||||
|
||||
/** Returns this entity's primary key field(s) in a string. */
|
||||
private String toEntityKeyString(Object entity) {
|
||||
private static String toEntityKeyString(Object entity) {
|
||||
try {
|
||||
return tm().transact(
|
||||
() ->
|
||||
|
||||
@@ -274,10 +274,7 @@ public class RdeIO {
|
||||
PendingDeposit key = input.getKey();
|
||||
Tld tld = Tld.get(key.tld());
|
||||
Optional<Cursor> cursor =
|
||||
tm().transact(
|
||||
() ->
|
||||
tm().loadByKeyIfPresent(
|
||||
Cursor.createScopedVKey(key.cursor(), tld)));
|
||||
tm().loadByKeyIfPresent(Cursor.createScopedVKey(key.cursor(), tld));
|
||||
DateTime position = getCursorTimeOrStartOfTime(cursor);
|
||||
checkState(key.interval() != null, "Interval must be present");
|
||||
DateTime newPosition = key.watermark().plus(key.interval());
|
||||
|
||||
@@ -723,6 +723,18 @@ public final class RegistryConfig {
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an optional return email address that overrides the default {@code reply-to} address
|
||||
* in outgoing invoicing email messages.
|
||||
*/
|
||||
@Provides
|
||||
@Config("invoiceReplyToEmailAddress")
|
||||
public static Optional<InternetAddress> provideInvoiceReplyToEmailAddress(
|
||||
RegistryConfigSettings config) {
|
||||
return Optional.ofNullable(config.billing.invoiceReplyToEmailAddress)
|
||||
.map(RegistryConfig::parseEmailAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file prefix for the invoice CSV file.
|
||||
*
|
||||
@@ -1371,41 +1383,45 @@ public final class RegistryConfig {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("packageCreateLimitEmailSubject")
|
||||
public static String providePackageCreateLimitEmailSubject(RegistryConfigSettings config) {
|
||||
return config.packageMonitoring.packageCreateLimitEmailSubject;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("packageCreateLimitEmailBody")
|
||||
public static String providePackageCreateLimitEmailBody(RegistryConfigSettings config) {
|
||||
return config.packageMonitoring.packageCreateLimitEmailBody;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("packageDomainLimitWarningEmailSubject")
|
||||
public static String providePackageDomainLimitWarningEmailSubject(
|
||||
@Config("bulkPricingPackageCreateLimitEmailSubject")
|
||||
public static String provideBulkPricingPackageCreateLimitEmailSubject(
|
||||
RegistryConfigSettings config) {
|
||||
return config.packageMonitoring.packageDomainLimitWarningEmailSubject;
|
||||
return config.bulkPricingPackageMonitoring.bulkPricingPackageCreateLimitEmailSubject;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("packageDomainLimitWarningEmailBody")
|
||||
public static String providePackageDomainLimitWarningEmailBody(RegistryConfigSettings config) {
|
||||
return config.packageMonitoring.packageDomainLimitWarningEmailBody;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("packageDomainLimitUpgradeEmailSubject")
|
||||
public static String providePackageDomainLimitUpgradeEmailSubject(
|
||||
@Config("bulkPricingPackageCreateLimitEmailBody")
|
||||
public static String provideBulkPricingPackageCreateLimitEmailBody(
|
||||
RegistryConfigSettings config) {
|
||||
return config.packageMonitoring.packageDomainLimitUpgradeEmailSubject;
|
||||
return config.bulkPricingPackageMonitoring.bulkPricingPackageCreateLimitEmailBody;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("packageDomainLimitUpgradeEmailBody")
|
||||
public static String providePackageDomainLimitUpgradeEmailBody(RegistryConfigSettings config) {
|
||||
return config.packageMonitoring.packageDomainLimitUpgradeEmailBody;
|
||||
@Config("bulkPricingPackageDomainLimitWarningEmailSubject")
|
||||
public static String provideBulkPricingPackageDomainLimitWarningEmailSubject(
|
||||
RegistryConfigSettings config) {
|
||||
return config.bulkPricingPackageMonitoring.bulkPricingPackageDomainLimitWarningEmailSubject;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("bulkPricingPackageDomainLimitWarningEmailBody")
|
||||
public static String provideBulkPricingPackageDomainLimitWarningEmailBody(
|
||||
RegistryConfigSettings config) {
|
||||
return config.bulkPricingPackageMonitoring.bulkPricingPackageDomainLimitWarningEmailBody;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("bulkPricingPackageDomainLimitUpgradeEmailSubject")
|
||||
public static String provideBulkPricingPackageDomainLimitUpgradeEmailSubject(
|
||||
RegistryConfigSettings config) {
|
||||
return config.bulkPricingPackageMonitoring.bulkPricingPackageDomainLimitUpgradeEmailSubject;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("bulkPricingPackageDomainLimitUpgradeEmailBody")
|
||||
public static String provideBulkPricingPackageDomainLimitUpgradeEmailBody(
|
||||
RegistryConfigSettings config) {
|
||||
return config.bulkPricingPackageMonitoring.bulkPricingPackageDomainLimitUpgradeEmailBody;
|
||||
}
|
||||
|
||||
private static String formatComments(String text) {
|
||||
@@ -1547,6 +1563,11 @@ public final class RegistryConfig {
|
||||
return CONFIG_SETTINGS.get().hibernate.connectionIsolation;
|
||||
}
|
||||
|
||||
/** Returns true if per-transaction isolation level is enabled. */
|
||||
public static boolean getHibernatePerTransactionIsolationEnabled() {
|
||||
return CONFIG_SETTINGS.get().hibernate.perTransactionIsolation;
|
||||
}
|
||||
|
||||
/** Returns true if hibernate.show_sql is enabled. */
|
||||
public static String getHibernateLogSqlQueries() {
|
||||
return CONFIG_SETTINGS.get().hibernate.logSqlQueries;
|
||||
|
||||
@@ -42,7 +42,7 @@ public class RegistryConfigSettings {
|
||||
public SslCertificateValidation sslCertificateValidation;
|
||||
public ContactHistory contactHistory;
|
||||
public DnsUpdate dnsUpdate;
|
||||
public PackageMonitoring packageMonitoring;
|
||||
public BulkPricingPackageMonitoring bulkPricingPackageMonitoring;
|
||||
|
||||
/** Configuration options that apply to the entire GCP project. */
|
||||
public static class GcpProject {
|
||||
@@ -115,6 +115,7 @@ public class RegistryConfigSettings {
|
||||
|
||||
/** Configuration for Hibernate. */
|
||||
public static class Hibernate {
|
||||
public boolean perTransactionIsolation;
|
||||
public String connectionIsolation;
|
||||
public String logSqlQueries;
|
||||
public String hikariConnectionTimeout;
|
||||
@@ -169,6 +170,7 @@ public class RegistryConfigSettings {
|
||||
/** Configuration for monthly invoices. */
|
||||
public static class Billing {
|
||||
public List<String> invoiceEmailRecipients;
|
||||
public String invoiceReplyToEmailAddress;
|
||||
public String invoiceFilePrefix;
|
||||
}
|
||||
|
||||
@@ -252,13 +254,13 @@ public class RegistryConfigSettings {
|
||||
public String registryCcEmail;
|
||||
}
|
||||
|
||||
/** Configuration for package compliance monitoring. */
|
||||
public static class PackageMonitoring {
|
||||
public String packageCreateLimitEmailSubject;
|
||||
public String packageCreateLimitEmailBody;
|
||||
public String packageDomainLimitWarningEmailSubject;
|
||||
public String packageDomainLimitWarningEmailBody;
|
||||
public String packageDomainLimitUpgradeEmailSubject;
|
||||
public String packageDomainLimitUpgradeEmailBody;
|
||||
/** Configuration for bulk pricing package compliance monitoring. */
|
||||
public static class BulkPricingPackageMonitoring {
|
||||
public String bulkPricingPackageCreateLimitEmailSubject;
|
||||
public String bulkPricingPackageCreateLimitEmailBody;
|
||||
public String bulkPricingPackageDomainLimitWarningEmailSubject;
|
||||
public String bulkPricingPackageDomainLimitWarningEmailBody;
|
||||
public String bulkPricingPackageDomainLimitUpgradeEmailSubject;
|
||||
public String bulkPricingPackageDomainLimitUpgradeEmailBody;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +189,12 @@ registryPolicy:
|
||||
sunriseDomainCreateDiscount: 0.15
|
||||
|
||||
hibernate:
|
||||
# Make it possible to specify the isolation level for each transaction. If set
|
||||
# to true, nested transactions will throw an exception. If set to false, a
|
||||
# transaction with the isolation override specified will still execute at the
|
||||
# default level (specified below).
|
||||
perTransactionIsolation: false
|
||||
|
||||
# Make 'SERIALIZABLE' the default isolation level to ensure correctness.
|
||||
#
|
||||
# Entities that are never involved in multi-table transactions may use optimistic
|
||||
@@ -376,6 +382,8 @@ icannReporting:
|
||||
|
||||
billing:
|
||||
invoiceEmailRecipients: []
|
||||
# Optional return address that overrides the default.
|
||||
invoiceReplyToEmailAddress: null
|
||||
invoiceFilePrefix: REG-INV
|
||||
|
||||
rde:
|
||||
@@ -479,7 +487,7 @@ keyring:
|
||||
|
||||
# Configuration options relevant to the "nomulus" registry tool.
|
||||
registryTool:
|
||||
# OAuth client Id used by the tool.
|
||||
# OAuth client ID used by the tool.
|
||||
clientId: YOUR_CLIENT_ID
|
||||
# OAuth client secret used by the tool.
|
||||
clientSecret: YOUR_CLIENT_SECRET
|
||||
@@ -553,55 +561,55 @@ sslCertificateValidation:
|
||||
- secp256r1
|
||||
- secp384r1
|
||||
|
||||
# Configuration options for the package compliance monitoring
|
||||
packageMonitoring:
|
||||
# Email subject text to notify tech support that a package has exceeded the limit for domain creates
|
||||
packageCreateLimitEmailSubject: "ACTION REQUIRED: Package needs to be upgraded"
|
||||
# Email body text template notify support that a package has exceeded the limit for domain creates
|
||||
packageCreateLimitEmailBody: >
|
||||
# Configuration options for the bulk pricing package compliance monitoring
|
||||
bulkPricingPackageMonitoring:
|
||||
# Email subject text to notify tech support that a bulk pricing package has exceeded the limit for domain creates
|
||||
bulkPricingPackageCreateLimitEmailSubject: "ACTION REQUIRED: Bulk pricing package needs to be upgraded"
|
||||
# Email body text template notify support that a bulk pricing package has exceeded the limit for domain creates
|
||||
bulkPricingPackageCreateLimitEmailBody: >
|
||||
Dear Support,
|
||||
|
||||
A package has exceeded its max create limit and needs to be upgraded to the
|
||||
A bulk pricing package has exceeded its max create limit and needs to be upgraded to the
|
||||
next tier.
|
||||
|
||||
Package ID: %1$s
|
||||
Package Token: %2$s
|
||||
Bulk Pricing ID: %1$s
|
||||
Bulk Token: %2$s
|
||||
Registrar: %3$s
|
||||
Current Max Create Limit: %4$s
|
||||
Creates Completed: %5$s
|
||||
|
||||
# Email subject text to notify support that a package has exceeded the limit
|
||||
# Email subject text to notify support that a bulk pricing package has exceeded the limit
|
||||
# for current active domains and a warning needs to be sent
|
||||
packageDomainLimitWarningEmailSubject: "ACTION REQUIRED: Package has exceeded the domain limit - send warning"
|
||||
# Email body text template to inform support that a package has exceeded the
|
||||
# limit for active domains and a warning needs to be sent that the package
|
||||
bulkPricingPackageDomainLimitWarningEmailSubject: "ACTION REQUIRED: Bulk pricing package has exceeded the domain limit - send warning"
|
||||
# Email body text template to inform support that a bulk pricing package has exceeded the
|
||||
# limit for active domains and a warning needs to be sent that the bulk pricing package
|
||||
# will be upgraded in 30 days
|
||||
packageDomainLimitWarningEmailBody: >
|
||||
bulkPricingPackageDomainLimitWarningEmailBody: >
|
||||
Dear Support,
|
||||
|
||||
A package has exceeded its max domain limit. Please send a warning to the
|
||||
registrar that their package will be upgraded to the next tier in 30 days if
|
||||
A bulk pricing package has exceeded its max domain limit. Please send a warning to the
|
||||
registrar that their bulk pricing package will be upgraded to the next tier in 30 days if
|
||||
the number of active domains does not return below the limit.
|
||||
|
||||
Package ID: %1$s
|
||||
Package Token: %2$s
|
||||
Bulk Pricing ID: %1$s
|
||||
Bulk Token: %2$s
|
||||
Registrar: %3$s
|
||||
Active Domain Limit: %4$s
|
||||
Current Active Domains: %5$s
|
||||
|
||||
# Email subject text to notify support that a package has exceeded the limit
|
||||
# Email subject text to notify support that a bulk pricing package has exceeded the limit
|
||||
# for current active domains for more than 30 days and needs to be upgraded
|
||||
packageDomainLimitUpgradeEmailSubject: "ACTION REQUIRED: Package has exceeded the domain limit - upgrade package"
|
||||
# Email body text template to inform support that a package has exceeded the
|
||||
bulkPricingPackageDomainLimitUpgradeEmailSubject: "ACTION REQUIRED: Bulk pricing package has exceeded the domain limit - upgrade package"
|
||||
# Email body text template to inform support that a bulk pricing package has exceeded the
|
||||
# limit for active domains for more than 30 days and needs to be upgraded
|
||||
packageDomainLimitUpgradeEmailBody: >
|
||||
bulkPricingPackageDomainLimitUpgradeEmailBody: >
|
||||
Dear Support,
|
||||
|
||||
A package has exceeded its max domain limit for over 30 days and needs to be
|
||||
A bulk pricing package has exceeded its max domain limit for over 30 days and needs to be
|
||||
upgraded to the next tier.
|
||||
|
||||
Package ID: %1$s
|
||||
Package Token: %2$s
|
||||
Bulk Pricing ID: %1$s
|
||||
Bulk Token: %2$s
|
||||
Registrar: %3$s
|
||||
Active Domain Limit: %4$s
|
||||
Current Active Domains: %5$s
|
||||
|
||||
@@ -26,3 +26,6 @@ gSuite:
|
||||
misc:
|
||||
# We would rather have failures than timeouts, so reduce the number of retries
|
||||
transientFailureRetries: 3
|
||||
|
||||
hibernate:
|
||||
perTransactionIsolation: true
|
||||
|
||||
@@ -45,6 +45,7 @@ import google.registry.dns.DnsMetrics.ActionStatus;
|
||||
import google.registry.dns.DnsMetrics.CommitStatus;
|
||||
import google.registry.dns.DnsMetrics.PublishStatus;
|
||||
import google.registry.dns.writer.DnsWriter;
|
||||
import google.registry.groups.GmailClient;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
@@ -61,7 +62,6 @@ import google.registry.request.lock.LockHandler;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.DomainNameUtils;
|
||||
import google.registry.util.EmailMessage;
|
||||
import google.registry.util.SendEmailService;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
@@ -112,7 +112,7 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
|
||||
private final Clock clock;
|
||||
private final CloudTasksUtils cloudTasksUtils;
|
||||
private final Response response;
|
||||
private final SendEmailService sendEmailService;
|
||||
private final GmailClient gmailClient;
|
||||
private final String dnsUpdateFailEmailSubjectText;
|
||||
private final String dnsUpdateFailEmailBodyText;
|
||||
private final String dnsUpdateFailRegistryName;
|
||||
@@ -143,12 +143,12 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
|
||||
LockHandler lockHandler,
|
||||
Clock clock,
|
||||
CloudTasksUtils cloudTasksUtils,
|
||||
SendEmailService sendEmailService,
|
||||
GmailClient gmailClient,
|
||||
Response response) {
|
||||
this.dnsWriterProxy = dnsWriterProxy;
|
||||
this.dnsMetrics = dnsMetrics;
|
||||
this.timeout = timeout;
|
||||
this.sendEmailService = sendEmailService;
|
||||
this.gmailClient = gmailClient;
|
||||
this.retryCount = retryCount;
|
||||
this.dnsWriter = dnsWriter;
|
||||
this.enqueuedTime = enqueuedTime;
|
||||
@@ -303,7 +303,7 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
|
||||
.map(PublishDnsUpdatesAction::emailToInternetAddress)
|
||||
.collect(toImmutableList());
|
||||
|
||||
sendEmailService.sendEmail(
|
||||
gmailClient.sendEmail(
|
||||
EmailMessage.newBuilder()
|
||||
.setBody(body)
|
||||
.setSubject(dnsUpdateFailEmailSubjectText)
|
||||
|
||||
@@ -60,18 +60,21 @@ public final class RefreshDnsAction implements Runnable {
|
||||
if (!domainOrHostName.contains(".")) {
|
||||
throw new BadRequestException("URL parameter 'name' must be fully qualified");
|
||||
}
|
||||
switch (type) {
|
||||
case DOMAIN:
|
||||
loadAndVerifyExistence(Domain.class, domainOrHostName);
|
||||
tm().transact(() -> requestDomainDnsRefresh(domainOrHostName));
|
||||
break;
|
||||
case HOST:
|
||||
verifyHostIsSubordinate(loadAndVerifyExistence(Host.class, domainOrHostName));
|
||||
tm().transact(() -> requestHostDnsRefresh(domainOrHostName));
|
||||
break;
|
||||
default:
|
||||
throw new BadRequestException("Unsupported type: " + type);
|
||||
}
|
||||
tm().transact(
|
||||
() -> {
|
||||
switch (type) {
|
||||
case DOMAIN:
|
||||
loadAndVerifyExistence(Domain.class, domainOrHostName);
|
||||
requestDomainDnsRefresh(domainOrHostName);
|
||||
break;
|
||||
case HOST:
|
||||
verifyHostIsSubordinate(loadAndVerifyExistence(Host.class, domainOrHostName));
|
||||
requestHostDnsRefresh(domainOrHostName);
|
||||
break;
|
||||
default:
|
||||
throw new BadRequestException("Unsupported type: " + type);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <T extends EppResource & ForeignKeyedEppResource>
|
||||
|
||||
@@ -35,6 +35,8 @@ import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.eppoutput.Result;
|
||||
import google.registry.model.host.HostHistory;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.persistence.IsolationLevel;
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Qualifier;
|
||||
@@ -135,6 +137,14 @@ public class FlowModule {
|
||||
return TransactionalFlow.class.isAssignableFrom(flowClass);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FlowScope
|
||||
Optional<TransactionIsolationLevel> provideIsolationLevelOverride(
|
||||
Class<? extends Flow> flowClass) {
|
||||
return Optional.ofNullable(flowClass.getAnnotation(IsolationLevel.class))
|
||||
.map(IsolationLevel::value);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FlowScope
|
||||
@Superuser
|
||||
@@ -166,7 +176,7 @@ public class FlowModule {
|
||||
@FlowScope
|
||||
@RegistrarId
|
||||
static String provideRegistrarId(SessionMetadata sessionMetadata) {
|
||||
// Treat a missing registrarId as null so we can always inject a non-null value. All we do with
|
||||
// Treat a missing registrarId as null, so we can always inject a non-null value. All we do with
|
||||
// the registrarId is log it (as "") or detect its absence, both of which work fine with empty.
|
||||
return Strings.nullToEmpty(sessionMetadata.getRegistrarId());
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ import google.registry.flows.session.LoginFlow;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.eppoutput.EppOutput;
|
||||
import google.registry.monitoring.whitebox.EppMetric;
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
|
||||
@@ -42,6 +44,7 @@ public class FlowRunner {
|
||||
@Inject TransportCredentials credentials;
|
||||
@Inject EppRequestSource eppRequestSource;
|
||||
@Inject Provider<Flow> flowProvider;
|
||||
@Inject Optional<TransactionIsolationLevel> isolationLevelOverride;
|
||||
@Inject Class<? extends Flow> flowClass;
|
||||
@Inject @InputXml byte[] inputXmlBytes;
|
||||
@Inject @DryRun boolean isDryRun;
|
||||
@@ -75,6 +78,8 @@ public class FlowRunner {
|
||||
return EppOutput.create(flowProvider.get().run());
|
||||
}
|
||||
try {
|
||||
// TODO(mcilwain/weiminyu): Use transactReadOnly() here for TransactionalFlow and transact()
|
||||
// for MutatingFlow.
|
||||
return tm().transact(
|
||||
() -> {
|
||||
try {
|
||||
@@ -91,7 +96,8 @@ public class FlowRunner {
|
||||
} catch (EppException e) {
|
||||
throw new EppRuntimeException(e);
|
||||
}
|
||||
});
|
||||
},
|
||||
isolationLevelOverride.orElse(null));
|
||||
} catch (DryRunException e) {
|
||||
return e.output;
|
||||
} catch (EppRuntimeException e) {
|
||||
|
||||
23
core/src/main/java/google/registry/flows/MutatingFlow.java
Normal file
23
core/src/main/java/google/registry/flows/MutatingFlow.java
Normal file
@@ -0,0 +1,23 @@
|
||||
// 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.flows;
|
||||
|
||||
/**
|
||||
* Interface for a {@link TransactionalFlow} that mutates the database (i.e. is not read-only).
|
||||
*
|
||||
* <p>Any flow that mutates the DB should implement this so that {@link FlowRunner} will know how to
|
||||
* run it.
|
||||
*/
|
||||
public interface MutatingFlow extends TransactionalFlow {}
|
||||
@@ -72,17 +72,12 @@ public final class ResourceFlowUtils {
|
||||
*/
|
||||
public static <R extends EppResource> void checkLinkedDomains(
|
||||
final String targetId, final DateTime now, final Class<R> resourceClass) throws EppException {
|
||||
EppException failfastException =
|
||||
tm().transact(
|
||||
() -> {
|
||||
VKey<R> key = ForeignKeyUtils.load(resourceClass, targetId, now);
|
||||
if (key == null) {
|
||||
return new ResourceDoesNotExistException(resourceClass, targetId);
|
||||
}
|
||||
return isLinked(key, now) ? new ResourceToDeleteIsReferencedException() : null;
|
||||
});
|
||||
if (failfastException != null) {
|
||||
throw failfastException;
|
||||
VKey<R> key = ForeignKeyUtils.load(resourceClass, targetId, now);
|
||||
if (key == null) {
|
||||
throw new ResourceDoesNotExistException(resourceClass, targetId);
|
||||
}
|
||||
if (isLinked(key, now)) {
|
||||
throw new ResourceToDeleteIsReferencedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +164,7 @@ public final class ResourceFlowUtils {
|
||||
throw new BadAuthInfoForResourceException();
|
||||
}
|
||||
// Check the authInfo against the contact.
|
||||
verifyAuthInfo(authInfo, tm().transact(() -> tm().loadByKey(foundContact.get())));
|
||||
verifyAuthInfo(authInfo, tm().loadByKey(foundContact.get()));
|
||||
}
|
||||
|
||||
/** Check that the given {@link AuthInfo} is valid for the given contact. */
|
||||
|
||||
@@ -23,8 +23,8 @@ import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.Flow;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactCommand.Check;
|
||||
@@ -45,7 +45,7 @@ import javax.inject.Inject;
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.CONTACT_CHECK)
|
||||
public final class ContactCheckFlow implements Flow {
|
||||
public final class ContactCheckFlow implements TransactionalFlow {
|
||||
|
||||
@Inject ResourceCommand resourceCommand;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
|
||||
@@ -28,7 +28,7 @@ import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ResourceAlreadyExistsForThisClientException;
|
||||
import google.registry.flows.exceptions.ResourceCreateContentionException;
|
||||
@@ -54,7 +54,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.CONTACT_CREATE)
|
||||
public final class ContactCreateFlow implements TransactionalFlow {
|
||||
public final class ContactCreateFlow implements MutatingFlow {
|
||||
|
||||
@Inject ResourceCommand resourceCommand;
|
||||
@Inject ExtensionManager extensionManager;
|
||||
|
||||
@@ -32,7 +32,7 @@ import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
@@ -63,7 +63,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link google.registry.flows.exceptions.ResourceToDeleteIsReferencedException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.CONTACT_DELETE)
|
||||
public final class ContactDeleteFlow implements TransactionalFlow {
|
||||
public final class ContactDeleteFlow implements MutatingFlow {
|
||||
|
||||
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES =
|
||||
ImmutableSet.of(
|
||||
|
||||
@@ -22,10 +22,10 @@ import static google.registry.model.EppResourceUtils.isLinked;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.Flow;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactInfoData;
|
||||
@@ -51,7 +51,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.CONTACT_INFO)
|
||||
public final class ContactInfoFlow implements Flow {
|
||||
public final class ContactInfoFlow implements TransactionalFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Clock clock;
|
||||
|
||||
@@ -30,7 +30,7 @@ import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
@@ -60,7 +60,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link google.registry.flows.exceptions.NotPendingTransferException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_APPROVE)
|
||||
public final class ContactTransferApproveFlow implements TransactionalFlow {
|
||||
public final class ContactTransferApproveFlow implements MutatingFlow {
|
||||
|
||||
@Inject ResourceCommand resourceCommand;
|
||||
@Inject ExtensionManager extensionManager;
|
||||
|
||||
@@ -30,7 +30,7 @@ import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
@@ -60,7 +60,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link google.registry.flows.exceptions.NotTransferInitiatorException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_CANCEL)
|
||||
public final class ContactTransferCancelFlow implements TransactionalFlow {
|
||||
public final class ContactTransferCancelFlow implements MutatingFlow {
|
||||
|
||||
@Inject ResourceCommand resourceCommand;
|
||||
@Inject ExtensionManager extensionManager;
|
||||
|
||||
@@ -21,9 +21,9 @@ import static google.registry.flows.contact.ContactFlowUtils.createTransferRespo
|
||||
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.Flow;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.NoTransferHistoryToQueryException;
|
||||
import google.registry.flows.exceptions.NotAuthorizedToViewTransferException;
|
||||
@@ -52,7 +52,7 @@ import javax.inject.Inject;
|
||||
* @error {@link google.registry.flows.exceptions.NotAuthorizedToViewTransferException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_QUERY)
|
||||
public final class ContactTransferQueryFlow implements Flow {
|
||||
public final class ContactTransferQueryFlow implements TransactionalFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
|
||||
@@ -30,7 +30,7 @@ import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
@@ -59,7 +59,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link google.registry.flows.exceptions.NotPendingTransferException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_REJECT)
|
||||
public final class ContactTransferRejectFlow implements TransactionalFlow {
|
||||
public final class ContactTransferRejectFlow implements MutatingFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
|
||||
@@ -33,7 +33,7 @@ import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.AlreadyPendingTransferException;
|
||||
import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
|
||||
@@ -72,7 +72,7 @@ import org.joda.time.Duration;
|
||||
* @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_REQUEST)
|
||||
public final class ContactTransferRequestFlow implements TransactionalFlow {
|
||||
public final class ContactTransferRequestFlow implements MutatingFlow {
|
||||
|
||||
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES =
|
||||
ImmutableSet.of(
|
||||
|
||||
@@ -33,7 +33,7 @@ import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
|
||||
import google.registry.model.contact.Contact;
|
||||
@@ -66,7 +66,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.CONTACT_UPDATE)
|
||||
public final class ContactUpdateFlow implements TransactionalFlow {
|
||||
public final class ContactUpdateFlow implements MutatingFlow {
|
||||
|
||||
/**
|
||||
* Note that CLIENT_UPDATE_PROHIBITED is intentionally not in this list. This is because it
|
||||
|
||||
@@ -43,9 +43,9 @@ import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.Flow;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.custom.DomainCheckFlowCustomLogic;
|
||||
import google.registry.flows.custom.DomainCheckFlowCustomLogic.BeforeResponseParameters;
|
||||
@@ -121,7 +121,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link OnlyCheckedNamesCanBeFeeCheckedException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.DOMAIN_CHECK)
|
||||
public final class DomainCheckFlow implements Flow {
|
||||
public final class DomainCheckFlow implements TransactionalFlow {
|
||||
|
||||
@Inject ResourceCommand resourceCommand;
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@@ -382,17 +382,13 @@ public final class DomainCheckFlow implements Flow {
|
||||
|
||||
private ImmutableMap<String, BillingRecurrence> loadRecurrencesForDomains(
|
||||
ImmutableMap<String, Domain> domainObjs) {
|
||||
return tm().transact(
|
||||
() -> {
|
||||
ImmutableMap<VKey<? extends BillingRecurrence>, BillingRecurrence> recurrences =
|
||||
tm().loadByKeys(
|
||||
domainObjs.values().stream()
|
||||
.map(Domain::getAutorenewBillingEvent)
|
||||
.collect(toImmutableSet()));
|
||||
return ImmutableMap.copyOf(
|
||||
Maps.transformValues(
|
||||
domainObjs, d -> recurrences.get(d.getAutorenewBillingEvent())));
|
||||
});
|
||||
ImmutableMap<VKey<? extends BillingRecurrence>, BillingRecurrence> recurrences =
|
||||
tm().loadByKeys(
|
||||
domainObjs.values().stream()
|
||||
.map(Domain::getAutorenewBillingEvent)
|
||||
.collect(toImmutableSet()));
|
||||
return ImmutableMap.copyOf(
|
||||
Maps.transformValues(domainObjs, d -> recurrences.get(d.getAutorenewBillingEvent())));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,9 +31,9 @@ import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppException.CommandUseErrorException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.Flow;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.domain.DomainCommand.Check;
|
||||
import google.registry.model.domain.launch.LaunchCheckExtension;
|
||||
@@ -67,7 +67,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link DomainClaimsCheckNotAllowedWithAllocationTokens}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.DOMAIN_CHECK) // Claims check is a special domain check.
|
||||
public final class DomainClaimsCheckFlow implements Flow {
|
||||
public final class DomainClaimsCheckFlow implements TransactionalFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject EppInput eppInput;
|
||||
|
||||
@@ -67,7 +67,7 @@ import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.custom.DomainCreateFlowCustomLogic;
|
||||
import google.registry.flows.custom.DomainCreateFlowCustomLogic.BeforeResponseParameters;
|
||||
@@ -152,7 +152,7 @@ import org.joda.time.Duration;
|
||||
* @error {@link DomainCreateFlow.MustHaveSignedMarksInCurrentPhaseException}
|
||||
* @error {@link DomainCreateFlow.NoGeneralRegistrationsInCurrentPhaseException}
|
||||
* @error {@link DomainCreateFlow.NoTrademarkedRegistrationsBeforeSunriseException}
|
||||
* @error {@link DomainCreateFlow.PackageDomainRegisteredForTooManyYearsException}
|
||||
* @error {@link BulkDomainRegisteredForTooManyYearsException}
|
||||
* @error {@link DomainCreateFlow.SignedMarksOnlyDuringSunriseException}
|
||||
* @error {@link DomainFlowTmchUtils.NoMarksFoundMatchingDomainException}
|
||||
* @error {@link DomainFlowTmchUtils.FoundMarkNotYetValidException}
|
||||
@@ -210,7 +210,7 @@ import org.joda.time.Duration;
|
||||
* @error {@link DomainPricingLogic.AllocationTokenInvalidForPremiumNameException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.DOMAIN_CREATE)
|
||||
public final class DomainCreateFlow implements TransactionalFlow {
|
||||
public final class DomainCreateFlow implements MutatingFlow {
|
||||
|
||||
/** Anchor tenant creates should always be for 2 years, since they get 2 years free. */
|
||||
private static final int ANCHOR_TENANT_CREATE_VALID_YEARS = 2;
|
||||
@@ -405,12 +405,11 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
: hasClaimsNotice ? LordnPhase.CLAIMS : LordnPhase.NONE);
|
||||
Domain domain = domainBuilder.build();
|
||||
if (allocationToken.isPresent()
|
||||
&& allocationToken.get().getTokenType().equals(TokenType.PACKAGE)) {
|
||||
&& allocationToken.get().getTokenType().equals(TokenType.BULK_PRICING)) {
|
||||
if (years > 1) {
|
||||
throw new PackageDomainRegisteredForTooManyYearsException(allocationToken.get().getToken());
|
||||
throw new BulkDomainRegisteredForTooManyYearsException(allocationToken.get().getToken());
|
||||
}
|
||||
domain =
|
||||
domain.asBuilder().setCurrentPackageToken(allocationToken.get().createVKey()).build();
|
||||
domain = domain.asBuilder().setCurrentBulkToken(allocationToken.get().createVKey()).build();
|
||||
}
|
||||
DomainHistory domainHistory =
|
||||
buildDomainHistory(domain, tld, now, period, tld.getAddGracePeriodLength());
|
||||
@@ -763,13 +762,12 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
}
|
||||
}
|
||||
|
||||
/** Package domain registered for too many years. */
|
||||
static class PackageDomainRegisteredForTooManyYearsException extends CommandUseErrorException {
|
||||
public PackageDomainRegisteredForTooManyYearsException(String token) {
|
||||
/** Bulk pricing domain registered for too many years. */
|
||||
static class BulkDomainRegisteredForTooManyYearsException extends CommandUseErrorException {
|
||||
public BulkDomainRegisteredForTooManyYearsException(String token) {
|
||||
super(
|
||||
String.format(
|
||||
"The package token %s cannot be used to register names for longer than 1 year.",
|
||||
token));
|
||||
"The bulk token %s cannot be used to register names for longer than 1 year.", token));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.SessionMetadata;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.custom.DomainDeleteFlowCustomLogic;
|
||||
import google.registry.flows.custom.DomainDeleteFlowCustomLogic.AfterValidationParameters;
|
||||
@@ -115,7 +115,7 @@ import org.joda.time.Duration;
|
||||
* @error {@link DomainFlowUtils.NotAuthorizedForTldException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.DOMAIN_DELETE)
|
||||
public final class DomainDeleteFlow implements TransactionalFlow {
|
||||
public final class DomainDeleteFlow implements MutatingFlow {
|
||||
|
||||
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES = ImmutableSet.of(
|
||||
StatusValue.CLIENT_DELETE_PROHIBITED,
|
||||
|
||||
@@ -28,10 +28,10 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.Flow;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.custom.DomainInfoFlowCustomLogic;
|
||||
import google.registry.flows.custom.DomainInfoFlowCustomLogic.AfterValidationParameters;
|
||||
@@ -76,7 +76,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link DomainFlowUtils.TransfersAreAlwaysForOneYearException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.DOMAIN_INFO)
|
||||
public final class DomainInfoFlow implements Flow {
|
||||
public final class DomainInfoFlow implements TransactionalFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject ResourceCommand resourceCommand;
|
||||
@@ -120,14 +120,12 @@ public final class DomainInfoFlow implements Flow {
|
||||
.setLastEppUpdateTime(domain.getLastEppUpdateTime())
|
||||
.setRegistrationExpirationTime(domain.getRegistrationExpirationTime())
|
||||
.setLastTransferTime(domain.getLastTransferTime())
|
||||
.setRegistrant(
|
||||
tm().transact(() -> tm().loadByKey(domain.getRegistrant())).getContactId());
|
||||
.setRegistrant(tm().loadByKey(domain.getRegistrant()).getContactId());
|
||||
// If authInfo is non-null, then the caller is authorized to see the full information since we
|
||||
// will have already verified the authInfo is valid.
|
||||
if (registrarId.equals(domain.getCurrentSponsorRegistrarId()) || authInfo.isPresent()) {
|
||||
infoBuilder
|
||||
.setContacts(
|
||||
tm().transact(() -> loadForeignKeyedDesignatedContacts(domain.getContacts())))
|
||||
.setContacts(loadForeignKeyedDesignatedContacts(domain.getContacts()))
|
||||
.setSubordinateHosts(
|
||||
hostsRequest.requestSubordinate() ? domain.getSubordinateHosts() : null)
|
||||
.setCreationRegistrarId(domain.getCreationRegistrarId())
|
||||
@@ -155,13 +153,13 @@ public final class DomainInfoFlow implements Flow {
|
||||
if (!gracePeriodStatuses.isEmpty()) {
|
||||
extensions.add(RgpInfoExtension.create(gracePeriodStatuses));
|
||||
}
|
||||
Optional<BulkTokenExtension> packageInfo =
|
||||
Optional<BulkTokenExtension> bulkPricingInfo =
|
||||
eppInput.getSingleExtension(BulkTokenExtension.class);
|
||||
if (packageInfo.isPresent()) {
|
||||
// Package info was requested.
|
||||
if (bulkPricingInfo.isPresent()) {
|
||||
// Bulk pricing info was requested.
|
||||
if (isSuperuser || registrarId.equals(domain.getCurrentSponsorRegistrarId())) {
|
||||
// Only show package info to owning registrar or superusers
|
||||
extensions.add(BulkTokenResponseExtension.create(domain.getCurrentPackageToken()));
|
||||
// Only show bulk pricing info to owning registrar or superusers
|
||||
extensions.add(BulkTokenResponseExtension.create(domain.getCurrentBulkToken()));
|
||||
}
|
||||
}
|
||||
Optional<FeeInfoCommandExtensionV06> feeInfo =
|
||||
@@ -178,7 +176,7 @@ public final class DomainInfoFlow implements Flow {
|
||||
pricingLogic,
|
||||
Optional.empty(),
|
||||
false,
|
||||
tm().transact(() -> tm().loadByKey(domain.getAutorenewBillingEvent())));
|
||||
tm().loadByKey(domain.getAutorenewBillingEvent()));
|
||||
extensions.add(builder.build());
|
||||
}
|
||||
return extensions.build();
|
||||
|
||||
@@ -30,7 +30,7 @@ import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateRegistrationPeriod;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.verifyRegistrarIsActive;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears;
|
||||
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.maybeApplyPackageRemovalToken;
|
||||
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.maybeApplyBulkPricingRemovalToken;
|
||||
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.verifyTokenAllowedOnDomain;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_RENEW;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
@@ -44,7 +44,7 @@ import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.custom.DomainRenewFlowCustomLogic;
|
||||
import google.registry.flows.custom.DomainRenewFlowCustomLogic.AfterValidationParameters;
|
||||
@@ -53,8 +53,8 @@ import google.registry.flows.custom.DomainRenewFlowCustomLogic.BeforeResponseRet
|
||||
import google.registry.flows.custom.DomainRenewFlowCustomLogic.BeforeSaveParameters;
|
||||
import google.registry.flows.custom.EntityChanges;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.MissingRemoveDomainTokenOnPackageDomainException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.RemoveDomainTokenOnNonPackageDomainException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.MissingRemoveDomainTokenOnBulkPricingDomainException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.RemoveDomainTokenOnNonBulkPricingDomainException;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.billing.BillingBase.Reason;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
@@ -121,8 +121,8 @@ import org.joda.time.Duration;
|
||||
* @error {@link DomainFlowUtils.RegistrarMustBeActiveForThisOperationException}
|
||||
* @error {@link DomainFlowUtils.UnsupportedFeeAttributeException}
|
||||
* @error {@link DomainRenewFlow.IncorrectCurrentExpirationDateException}
|
||||
* @error {@link MissingRemoveDomainTokenOnPackageDomainException}
|
||||
* @error {@link RemoveDomainTokenOnNonPackageDomainException}
|
||||
* @error {@link MissingRemoveDomainTokenOnBulkPricingDomainException}
|
||||
* @error {@link RemoveDomainTokenOnNonBulkPricingDomainException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForDomainException}
|
||||
* @error {@link
|
||||
@@ -137,7 +137,7 @@ import org.joda.time.Duration;
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.DOMAIN_RENEW)
|
||||
public final class DomainRenewFlow implements TransactionalFlow {
|
||||
public final class DomainRenewFlow implements MutatingFlow {
|
||||
|
||||
private static final ImmutableSet<StatusValue> RENEW_DISALLOWED_STATUSES = ImmutableSet.of(
|
||||
StatusValue.CLIENT_RENEW_PROHIBITED,
|
||||
@@ -194,7 +194,7 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
verifyRenewAllowed(authInfo, existingDomain, command, allocationToken);
|
||||
|
||||
// If client passed an applicable static token this updates the domain
|
||||
existingDomain = maybeApplyPackageRemovalToken(existingDomain, allocationToken);
|
||||
existingDomain = maybeApplyBulkPricingRemovalToken(existingDomain, allocationToken);
|
||||
|
||||
int years = command.getPeriod().getValue();
|
||||
DateTime newExpirationTime =
|
||||
@@ -328,7 +328,7 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
checkHasBillingAccount(registrarId, existingDomain.getTld());
|
||||
}
|
||||
verifyUnitIsYears(command.getPeriod());
|
||||
// We only allow __REMOVE_PACKAGE__ token on promo package domains for now
|
||||
// We only allow __REMOVEDOMAIN__ token on bulk pricing domains for now
|
||||
verifyTokenAllowedOnDomain(existingDomain, allocationToken);
|
||||
// If the date they specify doesn't match the expiration, fail. (This is an idempotence check).
|
||||
if (!command.getCurrentExpirationDate().equals(
|
||||
|
||||
@@ -42,7 +42,7 @@ import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.billing.BillingBase.Reason;
|
||||
@@ -112,7 +112,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link DomainRestoreRequestFlow.RestoreCommandIncludesChangesException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.DOMAIN_RGP_RESTORE_REQUEST)
|
||||
public final class DomainRestoreRequestFlow implements TransactionalFlow {
|
||||
public final class DomainRestoreRequestFlow implements MutatingFlow {
|
||||
|
||||
@Inject ResourceCommand resourceCommand;
|
||||
@Inject ExtensionManager extensionManager;
|
||||
|
||||
@@ -41,7 +41,7 @@ import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils;
|
||||
import google.registry.model.ImmutableObject;
|
||||
@@ -106,7 +106,7 @@ import org.joda.time.DateTime;
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.DOMAIN_TRANSFER_APPROVE)
|
||||
public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||
public final class DomainTransferApproveFlow implements MutatingFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
@@ -154,9 +154,9 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||
tm().loadByKey(existingDomain.getAutorenewBillingEvent());
|
||||
HistoryEntryId domainHistoryId = createHistoryEntryId(existingDomain);
|
||||
historyBuilder.setRevisionId(domainHistoryId.getRevisionId());
|
||||
boolean hasPackageToken = existingDomain.getCurrentPackageToken().isPresent();
|
||||
boolean hasBulkToken = existingDomain.getCurrentBulkToken().isPresent();
|
||||
Money renewalPrice =
|
||||
hasPackageToken ? null : existingBillingRecurrence.getRenewalPrice().orElse(null);
|
||||
hasBulkToken ? null : existingBillingRecurrence.getRenewalPrice().orElse(null);
|
||||
Optional<BillingEvent> billingEvent =
|
||||
transferData.getTransferPeriod().getValue() == 0
|
||||
? Optional.empty()
|
||||
@@ -172,10 +172,10 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||
Tld.get(tldStr),
|
||||
targetId,
|
||||
transferData.getTransferRequestTime(),
|
||||
// When removing a domain from a package it should return to the
|
||||
// When removing a domain from bulk pricing it should return to the
|
||||
// default recurrence billing behavior so the existing recurrence
|
||||
// billing event should not be passed in.
|
||||
hasPackageToken ? null : existingBillingRecurrence)
|
||||
hasBulkToken ? null : existingBillingRecurrence)
|
||||
.getRenewCost())
|
||||
.setEventTime(now)
|
||||
.setBillingTime(now.plus(Tld.get(tldStr).getTransferGracePeriodLength()))
|
||||
@@ -213,7 +213,7 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||
.setRegistrarId(gainingRegistrarId)
|
||||
.setEventTime(newExpirationTime)
|
||||
.setRenewalPriceBehavior(
|
||||
hasPackageToken
|
||||
hasBulkToken
|
||||
? RenewalPriceBehavior.DEFAULT
|
||||
: existingBillingRecurrence.getRenewalPriceBehavior())
|
||||
.setRenewalPrice(renewalPrice)
|
||||
@@ -258,9 +258,9 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||
.orElseGet(ImmutableSet::of))
|
||||
.setLastEppUpdateTime(now)
|
||||
.setLastEppUpdateRegistrarId(registrarId)
|
||||
// Even if the existing domain had a package token, that package token should be removed
|
||||
// Even if the existing domain had a bulk token, that bulk token should be removed
|
||||
// on transfer
|
||||
.setCurrentPackageToken(null)
|
||||
.setCurrentBulkToken(null)
|
||||
.build();
|
||||
|
||||
Tld tld = Tld.get(existingDomain.getTld());
|
||||
|
||||
@@ -37,7 +37,7 @@ import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.billing.BillingRecurrence;
|
||||
import google.registry.model.domain.Domain;
|
||||
@@ -74,7 +74,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link DomainFlowUtils.NotAuthorizedForTldException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.DOMAIN_TRANSFER_CANCEL)
|
||||
public final class DomainTransferCancelFlow implements TransactionalFlow {
|
||||
public final class DomainTransferCancelFlow implements MutatingFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
|
||||
@@ -21,10 +21,10 @@ import static google.registry.flows.domain.DomainTransferUtils.createTransferRes
|
||||
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.Flow;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.ResourceFlowUtils;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.NoTransferHistoryToQueryException;
|
||||
import google.registry.flows.exceptions.NotAuthorizedToViewTransferException;
|
||||
@@ -56,7 +56,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link google.registry.flows.exceptions.NotAuthorizedToViewTransferException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.DOMAIN_TRANSFER_QUERY)
|
||||
public final class DomainTransferQueryFlow implements Flow {
|
||||
public final class DomainTransferQueryFlow implements TransactionalFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
|
||||
@@ -39,7 +39,7 @@ import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.billing.BillingRecurrence;
|
||||
import google.registry.model.domain.Domain;
|
||||
@@ -76,7 +76,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link DomainFlowUtils.NotAuthorizedForTldException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.DOMAIN_TRANSFER_REJECT)
|
||||
public final class DomainTransferRejectFlow implements TransactionalFlow {
|
||||
public final class DomainTransferRejectFlow implements MutatingFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
|
||||
@@ -45,7 +45,7 @@ import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils;
|
||||
import google.registry.flows.exceptions.AlreadyPendingTransferException;
|
||||
@@ -135,7 +135,7 @@ import org.joda.time.DateTime;
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.DOMAIN_TRANSFER_REQUEST)
|
||||
public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
public final class DomainTransferRequestFlow implements MutatingFlow {
|
||||
|
||||
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES = ImmutableSet.of(
|
||||
StatusValue.CLIENT_TRANSFER_PROHIBITED,
|
||||
@@ -201,11 +201,12 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
Optional<FeesAndCredits> feesAndCredits;
|
||||
if (period.getValue() == 0) {
|
||||
feesAndCredits = Optional.empty();
|
||||
} else if (!existingDomain.getCurrentPackageToken().isPresent()) {
|
||||
} else if (!existingDomain.getCurrentBulkToken().isPresent()) {
|
||||
feesAndCredits =
|
||||
Optional.of(pricingLogic.getTransferPrice(tld, targetId, now, existingBillingRecurrence));
|
||||
} else {
|
||||
// If existing domain is in a package, calculate the transfer price with default renewal price
|
||||
// If existing domain is in a bulk pricing package, calculate the transfer price with default
|
||||
// renewal price
|
||||
// behavior
|
||||
feesAndCredits =
|
||||
period.getValue() == 0
|
||||
|
||||
@@ -146,7 +146,7 @@ public final class DomainTransferUtils {
|
||||
return builder
|
||||
.add(
|
||||
createGainingClientAutorenewEvent(
|
||||
existingDomain.getCurrentPackageToken().isPresent()
|
||||
existingDomain.getCurrentBulkToken().isPresent()
|
||||
? existingBillingRecurrence
|
||||
.asBuilder()
|
||||
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT)
|
||||
|
||||
@@ -55,7 +55,7 @@ import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.custom.DomainUpdateFlowCustomLogic;
|
||||
import google.registry.flows.custom.DomainUpdateFlowCustomLogic.AfterValidationParameters;
|
||||
@@ -133,7 +133,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link DomainFlowUtils.UrgentAttributeNotSupportedException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.DOMAIN_UPDATE)
|
||||
public final class DomainUpdateFlow implements TransactionalFlow {
|
||||
public final class DomainUpdateFlow implements MutatingFlow {
|
||||
|
||||
/**
|
||||
* A list of {@link StatusValue}s that prohibit updates.
|
||||
|
||||
@@ -242,19 +242,19 @@ public class AllocationTokenFlowUtils {
|
||||
public static void verifyTokenAllowedOnDomain(
|
||||
Domain domain, Optional<AllocationToken> allocationToken) throws EppException {
|
||||
|
||||
boolean domainHasPackageToken = domain.getCurrentPackageToken().isPresent();
|
||||
boolean domainHasBulkToken = domain.getCurrentBulkToken().isPresent();
|
||||
boolean hasRemoveDomainToken =
|
||||
allocationToken.isPresent()
|
||||
&& TokenBehavior.REMOVE_DOMAIN.equals(allocationToken.get().getTokenBehavior());
|
||||
|
||||
if (hasRemoveDomainToken && !domainHasPackageToken) {
|
||||
throw new RemoveDomainTokenOnNonPackageDomainException();
|
||||
} else if (!hasRemoveDomainToken && domainHasPackageToken) {
|
||||
throw new MissingRemoveDomainTokenOnPackageDomainException();
|
||||
if (hasRemoveDomainToken && !domainHasBulkToken) {
|
||||
throw new RemoveDomainTokenOnNonBulkPricingDomainException();
|
||||
} else if (!hasRemoveDomainToken && domainHasBulkToken) {
|
||||
throw new MissingRemoveDomainTokenOnBulkPricingDomainException();
|
||||
}
|
||||
}
|
||||
|
||||
public static Domain maybeApplyPackageRemovalToken(
|
||||
public static Domain maybeApplyBulkPricingRemovalToken(
|
||||
Domain domain, Optional<AllocationToken> allocationToken) {
|
||||
if (!allocationToken.isPresent()
|
||||
|| !TokenBehavior.REMOVE_DOMAIN.equals(allocationToken.get().getTokenBehavior())) {
|
||||
@@ -274,10 +274,10 @@ public class AllocationTokenFlowUtils {
|
||||
tm().getEntityManager().flush();
|
||||
tm().getEntityManager().clear();
|
||||
|
||||
// Remove current package token
|
||||
// Remove current bulk token
|
||||
return domain
|
||||
.asBuilder()
|
||||
.setCurrentPackageToken(null)
|
||||
.setCurrentBulkToken(null)
|
||||
.setAutorenewBillingEvent(newBillingRecurrence.createVKey())
|
||||
.build();
|
||||
}
|
||||
@@ -338,19 +338,19 @@ public class AllocationTokenFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** The __REMOVEDOMAIN__ token is missing on a package domain command */
|
||||
public static class MissingRemoveDomainTokenOnPackageDomainException
|
||||
/** The __REMOVEDOMAIN__ token is missing on a bulk pricing domain command */
|
||||
public static class MissingRemoveDomainTokenOnBulkPricingDomainException
|
||||
extends AssociationProhibitsOperationException {
|
||||
MissingRemoveDomainTokenOnPackageDomainException() {
|
||||
super("Domains that are inside packages cannot be explicitly renewed or transferred");
|
||||
MissingRemoveDomainTokenOnBulkPricingDomainException() {
|
||||
super("Domains that are inside bulk pricing cannot be explicitly renewed or transferred");
|
||||
}
|
||||
}
|
||||
|
||||
/** The __REMOVEDOMAIN__ token is not allowed on non package domains */
|
||||
public static class RemoveDomainTokenOnNonPackageDomainException
|
||||
/** The __REMOVEDOMAIN__ token is not allowed on non bulk pricing domains */
|
||||
public static class RemoveDomainTokenOnNonBulkPricingDomainException
|
||||
extends AssociationProhibitsOperationException {
|
||||
RemoveDomainTokenOnNonPackageDomainException() {
|
||||
super("__REMOVEDOMAIN__ token is not allowed on non package domains");
|
||||
RemoveDomainTokenOnNonBulkPricingDomainException() {
|
||||
super("__REMOVEDOMAIN__ token is not allowed on non bulk pricing domains");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.Flow;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
import google.registry.model.eppoutput.CheckData.HostCheck;
|
||||
@@ -45,7 +45,7 @@ import javax.inject.Inject;
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.HOST_CHECK)
|
||||
public final class HostCheckFlow implements Flow {
|
||||
public final class HostCheckFlow implements TransactionalFlow {
|
||||
|
||||
@Inject ResourceCommand resourceCommand;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
|
||||
@@ -35,7 +35,7 @@ import google.registry.flows.EppException.RequiredParameterMissingException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ResourceAlreadyExistsForThisClientException;
|
||||
import google.registry.flows.exceptions.ResourceCreateContentionException;
|
||||
@@ -78,7 +78,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link UnexpectedExternalHostIpException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.HOST_CREATE)
|
||||
public final class HostCreateFlow implements TransactionalFlow {
|
||||
public final class HostCreateFlow implements MutatingFlow {
|
||||
|
||||
@Inject ResourceCommand resourceCommand;
|
||||
@Inject ExtensionManager extensionManager;
|
||||
|
||||
@@ -30,7 +30,7 @@ import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
@@ -63,7 +63,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link HostFlowUtils.HostNameNotPunyCodedException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.HOST_DELETE)
|
||||
public final class HostDeleteFlow implements TransactionalFlow {
|
||||
public final class HostDeleteFlow implements MutatingFlow {
|
||||
|
||||
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES =
|
||||
ImmutableSet.of(
|
||||
|
||||
@@ -23,9 +23,9 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.Flow;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
@@ -50,7 +50,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link HostFlowUtils.HostNameNotPunyCodedException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.HOST_INFO)
|
||||
public final class HostInfoFlow implements Flow {
|
||||
public final class HostInfoFlow implements TransactionalFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@@ -77,8 +77,7 @@ public final class HostInfoFlow implements Flow {
|
||||
// there is no superordinate domain, the host's own values for these fields will be correct.
|
||||
if (host.isSubordinate()) {
|
||||
Domain superordinateDomain =
|
||||
tm().transact(
|
||||
() -> tm().loadByKey(host.getSuperordinateDomain()).cloneProjectedAtTime(now));
|
||||
tm().loadByKey(host.getSuperordinateDomain()).cloneProjectedAtTime(now);
|
||||
hostInfoDataBuilder
|
||||
.setCurrentSponsorRegistrarId(superordinateDomain.getCurrentSponsorRegistrarId())
|
||||
.setLastTransferTime(host.computeLastTransferTime(superordinateDomain));
|
||||
|
||||
@@ -47,7 +47,7 @@ import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
|
||||
import google.registry.model.EppResource;
|
||||
@@ -107,7 +107,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link RenameHostToExternalRemoveIpException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.HOST_UPDATE)
|
||||
public final class HostUpdateFlow implements TransactionalFlow {
|
||||
public final class HostUpdateFlow implements MutatingFlow {
|
||||
|
||||
/**
|
||||
* Note that CLIENT_UPDATE_PROHIBITED is intentionally not in this list. This is because it
|
||||
|
||||
@@ -31,7 +31,7 @@ import google.registry.flows.EppException.RequiredParameterMissingException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.PollMessageId;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.poll.MessageQueueInfo;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
@@ -55,7 +55,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link PollAckFlow.MissingMessageIdException}
|
||||
* @error {@link PollAckFlow.NotAuthorizedToAckMessageException}
|
||||
*/
|
||||
public final class PollAckFlow implements TransactionalFlow {
|
||||
public final class PollAckFlow implements MutatingFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@@ -108,7 +108,7 @@ public final class PollAckFlow implements TransactionalFlow {
|
||||
// acked, then we return a special status code indicating that. Note that the query will
|
||||
// include the message being acked.
|
||||
|
||||
int messageCount = tm().transact(() -> getPollMessageCount(registrarId, now));
|
||||
int messageCount = getPollMessageCount(registrarId, now);
|
||||
if (messageCount <= 0) {
|
||||
return responseBuilder.setResultFromCode(SUCCESS_WITH_NO_MESSAGES).build();
|
||||
}
|
||||
|
||||
@@ -30,13 +30,12 @@ public final class PollFlowUtils {
|
||||
|
||||
/** Returns the number of poll messages for the given registrar that are not in the future. */
|
||||
public static int getPollMessageCount(String registrarId, DateTime now) {
|
||||
return tm().transact(() -> createPollMessageQuery(registrarId, now).count()).intValue();
|
||||
return (int) createPollMessageQuery(registrarId, now).count();
|
||||
}
|
||||
|
||||
/** Returns the first (by event time) poll message not in the future for this registrar. */
|
||||
public static Optional<PollMessage> getFirstPollMessage(String registrarId, DateTime now) {
|
||||
return tm().transact(
|
||||
() -> createPollMessageQuery(registrarId, now).orderBy("eventTime").first());
|
||||
return createPollMessageQuery(registrarId, now).orderBy("eventTime").first();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,18 +20,18 @@ import static google.registry.flows.poll.PollFlowUtils.getPollMessageCount;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACK_MESSAGE;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_NO_MESSAGES;
|
||||
import static google.registry.model.poll.PollMessageExternalKeyConverter.makePollMessageExternalId;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppException.ParameterValueSyntaxErrorException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.Flow;
|
||||
import google.registry.flows.FlowModule.PollMessageId;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.poll.MessageQueueInfo;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.poll.PollMessageExternalKeyConverter;
|
||||
import google.registry.util.Clock;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import org.joda.time.DateTime;
|
||||
@@ -47,12 +47,11 @@ import org.joda.time.DateTime;
|
||||
*
|
||||
* @error {@link PollRequestFlow.UnexpectedMessageIdException}
|
||||
*/
|
||||
public final class PollRequestFlow implements Flow {
|
||||
public final class PollRequestFlow implements TransactionalFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject @PollMessageId String messageId;
|
||||
@Inject Clock clock;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
@Inject PollRequestFlow() {}
|
||||
|
||||
@@ -63,8 +62,9 @@ public final class PollRequestFlow implements Flow {
|
||||
if (!messageId.isEmpty()) {
|
||||
throw new UnexpectedMessageIdException();
|
||||
}
|
||||
|
||||
// Return the oldest message from the queue.
|
||||
DateTime now = clock.nowUtc();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
Optional<PollMessage> maybePollMessage = getFirstPollMessage(registrarId, now);
|
||||
if (!maybePollMessage.isPresent()) {
|
||||
return responseBuilder.setResultFromCode(SUCCESS_WITH_NO_MESSAGES).build();
|
||||
|
||||
@@ -30,8 +30,8 @@ import google.registry.flows.EppException.UnimplementedExtensionException;
|
||||
import google.registry.flows.EppException.UnimplementedObjectServiceException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.SessionMetadata;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.TransportCredentials;
|
||||
import google.registry.model.eppcommon.ProtocolDefinition;
|
||||
import google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension;
|
||||
@@ -62,7 +62,7 @@ import javax.inject.Inject;
|
||||
* @error {@link LoginFlow.RegistrarAccountNotActiveException}
|
||||
* @error {@link LoginFlow.UnsupportedLanguageException}
|
||||
*/
|
||||
public class LoginFlow implements TransactionalFlow {
|
||||
public class LoginFlow implements MutatingFlow {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.google.api.services.gmail.model.Message;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import dagger.Lazy;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.util.EmailMessage;
|
||||
import google.registry.util.EmailMessage.Attachment;
|
||||
@@ -49,7 +50,7 @@ public final class GmailClient {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private final Gmail gmail;
|
||||
private final Lazy<Gmail> gmail;
|
||||
private final Retrier retrier;
|
||||
private final boolean isEmailSendingEnabled;
|
||||
private final InternetAddress outgoingEmailAddressWithUsername;
|
||||
@@ -57,7 +58,7 @@ public final class GmailClient {
|
||||
|
||||
@Inject
|
||||
GmailClient(
|
||||
Gmail gmail,
|
||||
Lazy<Gmail> gmail,
|
||||
Retrier retrier,
|
||||
@Config("isEmailSendingEnabled") boolean isEmailSendingEnabled,
|
||||
@Config("gSuiteNewOutgoingEmailAddress") String gSuiteOutgoingEmailAddress,
|
||||
@@ -99,7 +100,7 @@ public final class GmailClient {
|
||||
// Unlike other Cloud APIs such as GCS and SecretManager, Gmail does not retry on errors.
|
||||
retrier.callWithRetry(
|
||||
// "me" is reserved word for the authorized user of the Gmail API.
|
||||
() -> this.gmail.users().messages().send("me", message).execute(),
|
||||
() -> this.gmail.get().users().messages().send("me", message).execute(),
|
||||
RetriableGmailExceptionPredicate.INSTANCE);
|
||||
}
|
||||
|
||||
@@ -119,7 +120,8 @@ public final class GmailClient {
|
||||
MimeMessage msg =
|
||||
new MimeMessage(Session.getDefaultInstance(new Properties(), /* authenticator= */ null));
|
||||
msg.setFrom(this.outgoingEmailAddressWithUsername);
|
||||
msg.setReplyTo(new InternetAddress[] {replyToEmailAddress});
|
||||
msg.setReplyTo(
|
||||
new InternetAddress[] {emailMessage.replyToEmailAddress().orElse(replyToEmailAddress)});
|
||||
msg.addRecipients(
|
||||
RecipientType.TO, toArray(emailMessage.recipients(), InternetAddress.class));
|
||||
msg.setSubject(emailMessage.subject());
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
package google.registry.model.tld;
|
||||
package google.registry.model;
|
||||
|
||||
import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap;
|
||||
import static com.google.common.collect.Ordering.natural;
|
||||
@@ -19,15 +19,16 @@ import static com.google.common.collect.Ordering.natural;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.MapperFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature;
|
||||
import google.registry.model.CreateAutoTimestamp;
|
||||
import google.registry.model.common.TimedTransitionProperty;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.tld.Tld.TldState;
|
||||
@@ -44,20 +45,21 @@ import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
/** A collection of static utility classes and functions for TLD YAML conversions. */
|
||||
public class TldYamlUtils {
|
||||
/** A collection of static utility classes/functions to convert entities to/from YAML files. */
|
||||
public class EntityYamlUtils {
|
||||
|
||||
/**
|
||||
* Returns an {@link ObjectMapper} object that can be used to convert a {@link Tld} object to and
|
||||
* from YAML.
|
||||
* Returns a new {@link ObjectMapper} object that can be used to convert an entity to/from YAML.
|
||||
*/
|
||||
public static ObjectMapper getObjectMapper() {
|
||||
public static ObjectMapper createObjectMapper() {
|
||||
SimpleModule module = new SimpleModule();
|
||||
module.addSerializer(Money.class, new MoneySerializer());
|
||||
module.addDeserializer(Money.class, new MoneyDeserializer());
|
||||
ObjectMapper mapper =
|
||||
new ObjectMapper(new YAMLFactory().disable(Feature.WRITE_DOC_START_MARKER))
|
||||
JsonMapper.builder(new YAMLFactory().disable(Feature.WRITE_DOC_START_MARKER))
|
||||
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
||||
.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY)
|
||||
.build()
|
||||
.registerModule(module);
|
||||
mapper.findAndRegisterModules();
|
||||
return mapper;
|
||||
@@ -86,6 +88,7 @@ public class TldYamlUtils {
|
||||
|
||||
/** A custom JSON deserializer for {@link Money}. */
|
||||
public static class MoneyDeserializer extends StdDeserializer<Money> {
|
||||
|
||||
public MoneyDeserializer() {
|
||||
this(null);
|
||||
}
|
||||
@@ -127,6 +130,7 @@ public class TldYamlUtils {
|
||||
|
||||
/** A custom JSON deserializer for {@link CurrencyUnit}. */
|
||||
public static class CurrencyDeserializer extends StdDeserializer<CurrencyUnit> {
|
||||
|
||||
public CurrencyDeserializer() {
|
||||
this(null);
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import static com.google.common.collect.Sets.difference;
|
||||
import static com.google.common.collect.Sets.union;
|
||||
import static google.registry.config.RegistryConfig.getEppResourceCachingDuration;
|
||||
import static google.registry.config.RegistryConfig.getEppResourceMaxCachedEntries;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
@@ -358,13 +357,13 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
|
||||
|
||||
@Override
|
||||
public EppResource load(VKey<? extends EppResource> key) {
|
||||
return replicaTm().transact(() -> replicaTm().loadByKey(key));
|
||||
return tm().reTransact(() -> tm().loadByKey(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<VKey<? extends EppResource>, EppResource> loadAll(
|
||||
Iterable<? extends VKey<? extends EppResource>> keys) {
|
||||
return replicaTm().transact(() -> replicaTm().loadByKeys(keys));
|
||||
return tm().reTransact(() -> tm().loadByKeys(keys));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -403,7 +402,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
|
||||
public static ImmutableMap<VKey<? extends EppResource>, EppResource> loadCached(
|
||||
Iterable<VKey<? extends EppResource>> keys) {
|
||||
if (!RegistryConfig.isEppResourceCachingEnabled()) {
|
||||
return tm().transact(() -> tm().loadByKeys(keys));
|
||||
return tm().reTransact(() -> tm().loadByKeys(keys));
|
||||
}
|
||||
return ImmutableMap.copyOf(cacheEppResources.getAll(keys));
|
||||
}
|
||||
@@ -416,7 +415,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
|
||||
*/
|
||||
public static <T extends EppResource> T loadCached(VKey<T> key) {
|
||||
if (!RegistryConfig.isEppResourceCachingEnabled()) {
|
||||
return tm().transact(() -> tm().loadByKey(key));
|
||||
return tm().reTransact(() -> tm().loadByKey(key));
|
||||
}
|
||||
// Safe to cast because loading a Key<T> returns an entity of type T.
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -156,7 +156,9 @@ public final class EppResourceUtils {
|
||||
T resource =
|
||||
useCache
|
||||
? EppResource.loadCached(key)
|
||||
: tm().transact(() -> tm().loadByKeyIfPresent(key).orElse(null));
|
||||
// This transaction is buried very deeply inside many outer nested calls, hence merits
|
||||
// the use of reTransact() for now pending a substantial refactoring.
|
||||
: tm().reTransact(() -> tm().loadByKeyIfPresent(key).orElse(null));
|
||||
if (resource == null || isAtOrAfter(now, resource.getDeletionTime())) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ public final class ForeignKeyUtils {
|
||||
Class<E> clazz, Collection<String> foreignKeys, boolean useReplicaTm) {
|
||||
String fkProperty = RESOURCE_TYPE_TO_FK_PROPERTY.get(clazz);
|
||||
JpaTransactionManager tmToUse = useReplicaTm ? replicaTm() : tm();
|
||||
return tmToUse.transact(
|
||||
return tmToUse.reTransact(
|
||||
() ->
|
||||
tmToUse
|
||||
.query(
|
||||
|
||||
30
core/src/main/java/google/registry/model/ModelModule.java
Normal file
30
core/src/main/java/google/registry/model/ModelModule.java
Normal file
@@ -0,0 +1,30 @@
|
||||
// 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.model;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
/** Dagger module for the entity (model) classes. */
|
||||
@Module
|
||||
public final class ModelModule {
|
||||
|
||||
/** Returns an {@link ObjectMapper} object that can be used to convert an entity to/from YAML. */
|
||||
@Provides
|
||||
public static ObjectMapper provideObjectMapper() {
|
||||
return EntityYamlUtils.createObjectMapper();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// 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.model.adapters;
|
||||
|
||||
import google.registry.model.adapters.CurrencyUnitAdapter.UnknownCurrencyException;
|
||||
import google.registry.util.StringBaseTypeAdapter;
|
||||
import java.io.IOException;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
|
||||
public class CurrencyJsonAdapter extends StringBaseTypeAdapter<CurrencyUnit> {
|
||||
|
||||
@Override
|
||||
protected CurrencyUnit fromString(String stringValue) throws IOException {
|
||||
try {
|
||||
return CurrencyUnitAdapter.convertFromString(stringValue);
|
||||
} catch (UnknownCurrencyException e) {
|
||||
throw new IOException("Unknown currency");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
// 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.
|
||||
@@ -22,9 +22,7 @@ import org.joda.money.CurrencyUnit;
|
||||
/** Adapter to use Joda {@link CurrencyUnit} when marshalling strings. */
|
||||
public class CurrencyUnitAdapter extends XmlAdapter<String, CurrencyUnit> {
|
||||
|
||||
/** Parses a string into a {@link CurrencyUnit} object. */
|
||||
@Override
|
||||
public CurrencyUnit unmarshal(String currency) throws UnknownCurrencyException {
|
||||
public static CurrencyUnit convertFromString(String currency) throws UnknownCurrencyException {
|
||||
try {
|
||||
return CurrencyUnit.of(nullToEmpty(currency).trim());
|
||||
} catch (IllegalArgumentException e) {
|
||||
@@ -32,10 +30,20 @@ public class CurrencyUnitAdapter extends XmlAdapter<String, CurrencyUnit> {
|
||||
}
|
||||
}
|
||||
|
||||
public static String convertFromCurrency(CurrencyUnit currency) {
|
||||
return currency == null ? null : currency.toString();
|
||||
}
|
||||
|
||||
/** Parses a string into a {@link CurrencyUnit} object. */
|
||||
@Override
|
||||
public CurrencyUnit unmarshal(String currency) throws UnknownCurrencyException {
|
||||
return convertFromString(currency);
|
||||
}
|
||||
|
||||
/** Converts {@link CurrencyUnit} to a string. */
|
||||
@Override
|
||||
public String marshal(CurrencyUnit currency) {
|
||||
return currency == null ? null : currency.toString();
|
||||
return convertFromCurrency(currency);
|
||||
}
|
||||
|
||||
/** Exception to throw when failing to parse a currency. */
|
||||
|
||||
@@ -200,7 +200,7 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
|
||||
.setStatusValues(domainBase.getStatusValues())
|
||||
.setTransferData(domainBase.getTransferData())
|
||||
.setLordnPhase(domainBase.getLordnPhase())
|
||||
.setCurrentPackageToken(domainBase.getCurrentPackageToken().orElse(null));
|
||||
.setCurrentBulkToken(domainBase.getCurrentBulkToken().orElse(null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,8 +267,12 @@ public class DomainBase extends EppResource
|
||||
@Enumerated(EnumType.STRING)
|
||||
LordnPhase lordnPhase = LordnPhase.NONE;
|
||||
|
||||
/** The {@link AllocationToken} for the package this domain is currently a part of. */
|
||||
@Nullable VKey<AllocationToken> currentPackageToken;
|
||||
/**
|
||||
* The {@link AllocationToken} for the bulk pricing package this domain is currently a part of.
|
||||
*/
|
||||
@Nullable
|
||||
@Column(name = "current_package_token")
|
||||
VKey<AllocationToken> currentBulkToken;
|
||||
|
||||
public LordnPhase getLordnPhase() {
|
||||
return lordnPhase;
|
||||
@@ -302,8 +306,8 @@ public class DomainBase extends EppResource
|
||||
return smdId;
|
||||
}
|
||||
|
||||
public Optional<VKey<AllocationToken>> getCurrentPackageToken() {
|
||||
return Optional.ofNullable(currentPackageToken);
|
||||
public Optional<VKey<AllocationToken>> getCurrentBulkToken() {
|
||||
return Optional.ofNullable(currentBulkToken);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -478,7 +482,7 @@ public class DomainBase extends EppResource
|
||||
// events.
|
||||
.setAutorenewBillingEvent(transferData.getServerApproveAutorenewEvent())
|
||||
.setAutorenewPollMessage(transferData.getServerApproveAutorenewPollMessage())
|
||||
.setCurrentPackageToken(null);
|
||||
.setCurrentBulkToken(null);
|
||||
if (transferData.getTransferPeriod().getValue() == 1) {
|
||||
// Set the grace period using a key to the pre-scheduled transfer billing event. Not using
|
||||
// GracePeriod.forBillingEvent() here in order to avoid the actual fetch.
|
||||
@@ -910,23 +914,22 @@ public class DomainBase extends EppResource
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setCurrentPackageToken(@Nullable VKey<AllocationToken> currentPackageToken) {
|
||||
if (currentPackageToken == null) {
|
||||
getInstance().currentPackageToken = currentPackageToken;
|
||||
public B setCurrentBulkToken(@Nullable VKey<AllocationToken> currentBulkToken) {
|
||||
if (currentBulkToken == null) {
|
||||
getInstance().currentBulkToken = currentBulkToken;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
AllocationToken token =
|
||||
tm().transact(() -> tm().loadByKeyIfPresent(currentPackageToken))
|
||||
tm().transact(() -> tm().loadByKeyIfPresent(currentBulkToken))
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalArgumentException(
|
||||
String.format(
|
||||
"The package token %s does not exist",
|
||||
currentPackageToken.getKey())));
|
||||
"The bulk token %s does not exist", currentBulkToken.getKey())));
|
||||
checkArgument(
|
||||
token.getTokenType().equals(TokenType.PACKAGE),
|
||||
"The currentPackageToken must have a PACKAGE TokenType");
|
||||
getInstance().currentPackageToken = currentPackageToken;
|
||||
token.getTokenType().equals(TokenType.BULK_PRICING),
|
||||
"The currentBulkToken must have a BULK_PRICING TokenType");
|
||||
getInstance().currentBulkToken = currentBulkToken;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,9 +119,14 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
|
||||
|
||||
/** Type of the token that indicates how and where it should be used. */
|
||||
public enum TokenType {
|
||||
/** Token used for bulk pricing */
|
||||
BULK_PRICING,
|
||||
/** Token saved on a TLD to use if no other token is passed from the client */
|
||||
DEFAULT_PROMO,
|
||||
/** Token used for package pricing */
|
||||
/** This is the old name for what is now BULK_PRICING. */
|
||||
// TODO(sarahbot@): Remove this type once all tokens of this type have been scrubbed from the
|
||||
// database
|
||||
@Deprecated
|
||||
PACKAGE,
|
||||
/** Invalid after use */
|
||||
SINGLE_USE,
|
||||
@@ -137,8 +142,8 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
|
||||
/** No special behavior */
|
||||
DEFAULT,
|
||||
/**
|
||||
* REMOVE_DOMAIN triggers domain removal from promotional bulk (package) pricing, bypasses
|
||||
* DEFAULT token validations.
|
||||
* REMOVE_DOMAIN triggers domain removal from a bulk pricing package, bypasses DEFAULT token
|
||||
* validations.
|
||||
*/
|
||||
REMOVE_DOMAIN
|
||||
}
|
||||
@@ -344,12 +349,17 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
|
||||
checkArgumentNotNull(getInstance().tokenType, "Token type must be specified");
|
||||
checkArgument(!Strings.isNullOrEmpty(getInstance().token), "Token must not be null or empty");
|
||||
checkArgument(
|
||||
!getInstance().tokenType.equals(TokenType.PACKAGE)
|
||||
!getInstance().tokenType.equals(TokenType.BULK_PRICING)
|
||||
|| getInstance().renewalPriceBehavior.equals(RenewalPriceBehavior.SPECIFIED),
|
||||
"Package tokens must have renewalPriceBehavior set to SPECIFIED");
|
||||
"Bulk tokens must have renewalPriceBehavior set to SPECIFIED");
|
||||
checkArgument(
|
||||
!getInstance().tokenType.equals(TokenType.PACKAGE) || !getInstance().discountPremiums,
|
||||
"Package tokens cannot discount premium names");
|
||||
!getInstance().tokenType.equals(TokenType.BULK_PRICING)
|
||||
|| ImmutableSet.of(CommandName.CREATE).equals(getInstance().allowedEppActions),
|
||||
"Bulk tokens may only be valid for CREATE actions");
|
||||
checkArgument(
|
||||
!getInstance().tokenType.equals(TokenType.BULK_PRICING)
|
||||
|| !getInstance().discountPremiums,
|
||||
"Bulk tokens cannot discount premium names");
|
||||
checkArgument(
|
||||
getInstance().domainName == null || TokenType.SINGLE_USE.equals(getInstance().tokenType),
|
||||
"Domain name can only be specified for SINGLE_USE tokens");
|
||||
@@ -358,10 +368,10 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
|
||||
|| TokenType.SINGLE_USE.equals(getInstance().tokenType),
|
||||
"Redemption history entry can only be specified for SINGLE_USE tokens");
|
||||
checkArgument(
|
||||
getInstance().tokenType != TokenType.PACKAGE
|
||||
getInstance().tokenType != TokenType.BULK_PRICING
|
||||
|| (getInstance().allowedClientIds != null
|
||||
&& getInstance().allowedClientIds.size() == 1),
|
||||
"PACKAGE tokens must have exactly one allowed client registrar");
|
||||
"BULK_PRICING tokens must have exactly one allowed client registrar");
|
||||
checkArgument(
|
||||
getInstance().discountFraction > 0 || !getInstance().discountPremiums,
|
||||
"Discount premiums can only be specified along with a discount fraction");
|
||||
|
||||
@@ -36,46 +36,53 @@ import org.hibernate.annotations.Type;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** An entity representing a package promotion. */
|
||||
@Entity
|
||||
/**
|
||||
* An entity representing a bulk pricing promotion. Note that this table is still called
|
||||
* PackagePromotion in Cloud SQL.
|
||||
*/
|
||||
@Entity(name = "PackagePromotion")
|
||||
@javax.persistence.Table(indexes = {@javax.persistence.Index(columnList = "token")})
|
||||
public class PackagePromotion extends ImmutableObject implements Buildable {
|
||||
public class BulkPricingPackage extends ImmutableObject implements Buildable {
|
||||
|
||||
/** An autogenerated identifier for the package promotion. */
|
||||
/** An autogenerated identifier for the bulk pricing promotion. */
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
long packagePromotionId;
|
||||
@Column(name = "package_promotion_id")
|
||||
long bulkPricingId;
|
||||
|
||||
/** The allocation token string for the package. */
|
||||
/** The allocation token string for the bulk pricing package. */
|
||||
@Column(nullable = false)
|
||||
VKey<AllocationToken> token;
|
||||
|
||||
/** The maximum number of active domains the package allows at any given time. */
|
||||
/** The maximum number of active domains the bulk pricing package allows at any given time. */
|
||||
@Column(nullable = false)
|
||||
int maxDomains;
|
||||
|
||||
/** The maximum number of domains that can be created in the package each year. */
|
||||
/** The maximum number of domains that can be created in the bulk pricing package each year. */
|
||||
@Column(nullable = false)
|
||||
int maxCreates;
|
||||
|
||||
/** The annual price of the package. */
|
||||
/** The annual price of the bulk pricing package. */
|
||||
@Type(type = JodaMoneyType.TYPE_NAME)
|
||||
@Columns(
|
||||
columns = {
|
||||
@Column(name = "package_price_amount", nullable = false),
|
||||
@Column(name = "package_price_currency", nullable = false)
|
||||
})
|
||||
Money packagePrice;
|
||||
Money bulkPrice;
|
||||
|
||||
/** The next billing date of the package. */
|
||||
/** The next billing date of the bulk pricing package. */
|
||||
@Column(nullable = false)
|
||||
DateTime nextBillingDate = END_OF_TIME;
|
||||
|
||||
/** Date the last warning email was sent that the package has exceeded the maxDomains limit. */
|
||||
/**
|
||||
* Date the last warning email was sent that the bulk pricing package has exceeded the maxDomains
|
||||
* limit.
|
||||
*/
|
||||
@Nullable DateTime lastNotificationSent;
|
||||
|
||||
public long getId() {
|
||||
return packagePromotionId;
|
||||
return bulkPricingId;
|
||||
}
|
||||
|
||||
public VKey<AllocationToken> getToken() {
|
||||
@@ -90,8 +97,8 @@ public class PackagePromotion extends ImmutableObject implements Buildable {
|
||||
return maxCreates;
|
||||
}
|
||||
|
||||
public Money getPackagePrice() {
|
||||
return packagePrice;
|
||||
public Money getBulkPrice() {
|
||||
return bulkPrice;
|
||||
}
|
||||
|
||||
public DateTime getNextBillingDate() {
|
||||
@@ -102,18 +109,18 @@ public class PackagePromotion extends ImmutableObject implements Buildable {
|
||||
return Optional.ofNullable(lastNotificationSent);
|
||||
}
|
||||
|
||||
/** Loads and returns a PackagePromotion entity by its token string directly from Cloud SQL. */
|
||||
public static Optional<PackagePromotion> loadByTokenString(String tokenString) {
|
||||
/** Loads and returns a BulkPricingPackage entity by its token string directly from Cloud SQL. */
|
||||
public static Optional<BulkPricingPackage> loadByTokenString(String tokenString) {
|
||||
tm().assertInTransaction();
|
||||
return tm().query("FROM PackagePromotion WHERE token = :token", PackagePromotion.class)
|
||||
return tm().query("FROM PackagePromotion WHERE token = :token", BulkPricingPackage.class)
|
||||
.setParameter("token", VKey.create(AllocationToken.class, tokenString))
|
||||
.getResultStream()
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VKey<PackagePromotion> createVKey() {
|
||||
return VKey.create(PackagePromotion.class, packagePromotionId);
|
||||
public VKey<BulkPricingPackage> createVKey() {
|
||||
return VKey.create(BulkPricingPackage.class, bulkPricingId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -121,28 +128,29 @@ public class PackagePromotion extends ImmutableObject implements Buildable {
|
||||
return new Builder(clone(this));
|
||||
}
|
||||
|
||||
/** A builder for constructing {@link PackagePromotion} objects, since they are immutable. */
|
||||
public static class Builder extends Buildable.Builder<PackagePromotion> {
|
||||
/** A builder for constructing {@link BulkPricingPackage} objects, since they are immutable. */
|
||||
public static class Builder extends Buildable.Builder<BulkPricingPackage> {
|
||||
public Builder() {}
|
||||
|
||||
private Builder(PackagePromotion instance) {
|
||||
private Builder(BulkPricingPackage instance) {
|
||||
super(instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackagePromotion build() {
|
||||
public BulkPricingPackage build() {
|
||||
checkArgumentNotNull(getInstance().token, "Allocation token must be specified");
|
||||
AllocationToken allocationToken = tm().transact(() -> tm().loadByKey(getInstance().token));
|
||||
checkArgument(
|
||||
allocationToken.tokenType == TokenType.PACKAGE,
|
||||
"Allocation token must be a PACKAGE type");
|
||||
allocationToken.tokenType == TokenType.BULK_PRICING,
|
||||
"Allocation token must be a BULK_PRICING type");
|
||||
return super.build();
|
||||
}
|
||||
|
||||
public Builder setToken(AllocationToken token) {
|
||||
checkArgumentNotNull(token, "Allocation token must not be null");
|
||||
checkArgument(
|
||||
token.tokenType == TokenType.PACKAGE, "Allocation token must be a PACKAGE type");
|
||||
token.tokenType == TokenType.BULK_PRICING,
|
||||
"Allocation token must be a BULK_PRICING type");
|
||||
getInstance().token = token.createVKey();
|
||||
return this;
|
||||
}
|
||||
@@ -159,9 +167,9 @@ public class PackagePromotion extends ImmutableObject implements Buildable {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPackagePrice(Money packagePrice) {
|
||||
checkArgumentNotNull(packagePrice, "Package price must not be null");
|
||||
getInstance().packagePrice = packagePrice;
|
||||
public Builder setBulkPrice(Money bulkPrice) {
|
||||
checkArgumentNotNull(bulkPrice, "Bulk price must not be null");
|
||||
getInstance().bulkPrice = bulkPrice;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -20,11 +20,13 @@ import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.JsonMapBuilder;
|
||||
import google.registry.model.Jsonifiable;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.tools.GsonUtils.GsonPostProcessable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -54,7 +56,8 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
@XmlTransient
|
||||
@Embeddable
|
||||
@MappedSuperclass
|
||||
public class Address extends ImmutableObject implements Jsonifiable, UnsafeSerializable {
|
||||
public class Address extends ImmutableObject
|
||||
implements Jsonifiable, UnsafeSerializable, GsonPostProcessable {
|
||||
|
||||
/**
|
||||
* At most three lines of addresses parsed from XML elements.
|
||||
@@ -87,6 +90,7 @@ public class Address extends ImmutableObject implements Jsonifiable, UnsafeSeria
|
||||
*/
|
||||
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
|
||||
@Transient
|
||||
@Expose
|
||||
protected List<String> street;
|
||||
|
||||
@XmlTransient @IgnoredInDiffableMap protected String streetLine1;
|
||||
@@ -96,18 +100,22 @@ public class Address extends ImmutableObject implements Jsonifiable, UnsafeSeria
|
||||
@XmlTransient @IgnoredInDiffableMap protected String streetLine3;
|
||||
|
||||
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
|
||||
@Expose
|
||||
protected String city;
|
||||
|
||||
@XmlElement(name = "sp")
|
||||
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
|
||||
@Expose
|
||||
protected String state;
|
||||
|
||||
@XmlElement(name = "pc")
|
||||
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
|
||||
@Expose
|
||||
protected String zip;
|
||||
|
||||
@XmlElement(name = "cc")
|
||||
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
|
||||
@Expose
|
||||
protected String countryCode;
|
||||
|
||||
public ImmutableList<String> getStreet() {
|
||||
@@ -146,6 +154,16 @@ public class Address extends ImmutableObject implements Jsonifiable, UnsafeSeria
|
||||
return new Builder<>(clone(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcess() {
|
||||
if (street == null || street.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
streetLine1 = street.get(0);
|
||||
streetLine2 = street.size() >= 2 ? street.get(1) : null;
|
||||
streetLine3 = street.size() >= 3 ? street.get(2) : null;
|
||||
}
|
||||
|
||||
/** A builder for constructing {@link Address}. */
|
||||
public static class Builder<T extends Address> extends Buildable.Builder<T> {
|
||||
|
||||
@@ -222,11 +240,6 @@ public class Address extends ImmutableObject implements Jsonifiable, UnsafeSeria
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
|
||||
if (street == null || street.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
streetLine1 = street.get(0);
|
||||
streetLine2 = street.size() >= 2 ? street.get(1) : null;
|
||||
streetLine3 = street.size() >= 3 ? street.get(2) : null;
|
||||
postProcess();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Strings.emptyToNull;
|
||||
import static com.google.common.base.Strings.nullToEmpty;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
|
||||
import static com.google.common.collect.Sets.immutableEnumSet;
|
||||
@@ -49,7 +48,6 @@ import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.re2j.Pattern;
|
||||
import google.registry.model.Buildable;
|
||||
@@ -211,6 +209,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
*/
|
||||
@Id
|
||||
@Column(nullable = false)
|
||||
@Expose
|
||||
String registrarId;
|
||||
|
||||
/**
|
||||
@@ -224,6 +223,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
* @see <a href="http://www.icann.org/registrar-reports/accredited-list.html">ICANN-Accredited
|
||||
* Registrars</a>
|
||||
*/
|
||||
@Expose
|
||||
@Column(nullable = false)
|
||||
String registrarName;
|
||||
|
||||
@@ -237,10 +237,10 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
State state;
|
||||
|
||||
/** The set of TLDs which this registrar is allowed to access. */
|
||||
Set<String> allowedTlds;
|
||||
@Expose Set<String> allowedTlds;
|
||||
|
||||
/** Host name of WHOIS server. */
|
||||
String whoisServer;
|
||||
@Expose String whoisServer;
|
||||
|
||||
/** Base URLs for the registrar's RDAP servers. */
|
||||
Set<String> rdapBaseUrls;
|
||||
@@ -287,6 +287,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
* unrestricted UTF-8.
|
||||
*/
|
||||
@Embedded
|
||||
@Expose
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(
|
||||
name = "streetLine1",
|
||||
@@ -323,13 +324,13 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
RegistrarAddress internationalizedAddress;
|
||||
|
||||
/** Voice number. */
|
||||
String phoneNumber;
|
||||
@Expose String phoneNumber;
|
||||
|
||||
/** Fax number. */
|
||||
String faxNumber;
|
||||
@Expose String faxNumber;
|
||||
|
||||
/** Email address. */
|
||||
String emailAddress;
|
||||
@Expose String emailAddress;
|
||||
|
||||
// External IDs.
|
||||
|
||||
@@ -345,7 +346,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
* @see <a href="http://www.iana.org/assignments/registrar-ids/registrar-ids.txt">Registrar
|
||||
* IDs</a>
|
||||
*/
|
||||
@Nullable Long ianaIdentifier;
|
||||
@Expose @Nullable Long ianaIdentifier;
|
||||
|
||||
/** Purchase Order number used for invoices in external billing system, if applicable. */
|
||||
@Nullable String poNumber;
|
||||
@@ -358,10 +359,10 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
* accessed by {@link #getBillingAccountMap}, a sorted map is returned to guarantee deterministic
|
||||
* behavior when serializing the map, for display purpose for instance.
|
||||
*/
|
||||
@Nullable Map<CurrencyUnit, String> billingAccountMap;
|
||||
@Expose @Nullable Map<CurrencyUnit, String> billingAccountMap;
|
||||
|
||||
/** URL of registrar's website. */
|
||||
String url;
|
||||
@Expose String url;
|
||||
|
||||
/**
|
||||
* ICANN referral email address.
|
||||
@@ -369,10 +370,10 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
* <p>This value is specified in the initial registrar contact. It can't be edited in the web GUI,
|
||||
* and it must be specified when the registrar account is created.
|
||||
*/
|
||||
String icannReferralEmail;
|
||||
@Expose String icannReferralEmail;
|
||||
|
||||
/** Id of the folder in drive used to publish information for this registrar. */
|
||||
String driveFolderId;
|
||||
@Expose String driveFolderId;
|
||||
|
||||
// Metadata.
|
||||
|
||||
@@ -400,7 +401,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
boolean contactsRequireSyncing = true;
|
||||
|
||||
/** Whether or not registry lock is allowed for this registrar. */
|
||||
boolean registryLockAllowed = false;
|
||||
@Expose boolean registryLockAllowed = false;
|
||||
|
||||
public String getRegistrarId() {
|
||||
return registrarId;
|
||||
@@ -553,7 +554,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
* address.
|
||||
*/
|
||||
public ImmutableSortedSet<RegistrarPoc> getContacts() {
|
||||
return Streams.stream(getContactsIterable())
|
||||
return getContactPocs().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(toImmutableSortedSet(CONTACT_EMAIL_COMPARATOR));
|
||||
}
|
||||
@@ -563,7 +564,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
* their email address.
|
||||
*/
|
||||
public ImmutableSortedSet<RegistrarPoc> getContactsOfType(final RegistrarPoc.Type type) {
|
||||
return Streams.stream(getContactsIterable())
|
||||
return getContactPocs().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.filter((@Nullable RegistrarPoc contact) -> contact.getTypes().contains(type))
|
||||
.collect(toImmutableSortedSet(CONTACT_EMAIL_COMPARATOR));
|
||||
@@ -577,13 +578,13 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
return getContacts().stream().filter(RegistrarPoc::getVisibleInDomainWhoisAsAbuse).findFirst();
|
||||
}
|
||||
|
||||
private Iterable<RegistrarPoc> getContactsIterable() {
|
||||
private ImmutableSet<RegistrarPoc> getContactPocs() {
|
||||
return tm().transact(
|
||||
() ->
|
||||
tm().query("FROM RegistrarPoc WHERE registrarId = :registrarId", RegistrarPoc.class)
|
||||
.setParameter("registrarId", registrarId)
|
||||
.getResultStream()
|
||||
.collect(toImmutableList()));
|
||||
.collect(toImmutableSet()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -729,8 +730,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
.map(Tld::createVKey)
|
||||
.collect(toImmutableSet());
|
||||
Set<VKey<Tld>> missingTldKeys =
|
||||
Sets.difference(
|
||||
newTldKeys, tm().transact(() -> tm().loadByKeysIfPresent(newTldKeys)).keySet());
|
||||
Sets.difference(newTldKeys, tm().loadByKeysIfPresent(newTldKeys).keySet());
|
||||
checkArgument(missingTldKeys.isEmpty(), "Trying to set nonexistent TLDs: %s", missingTldKeys);
|
||||
getInstance().allowedTlds = ImmutableSortedSet.copyOf(allowedTlds);
|
||||
return this;
|
||||
|
||||
@@ -45,6 +45,15 @@ import com.google.common.net.InternetDomainName;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.CacheUtils;
|
||||
import google.registry.model.CreateAutoTimestamp;
|
||||
import google.registry.model.EntityYamlUtils.CreateAutoTimestampDeserializer;
|
||||
import google.registry.model.EntityYamlUtils.CurrencyDeserializer;
|
||||
import google.registry.model.EntityYamlUtils.CurrencySerializer;
|
||||
import google.registry.model.EntityYamlUtils.OptionalDurationSerializer;
|
||||
import google.registry.model.EntityYamlUtils.OptionalStringSerializer;
|
||||
import google.registry.model.EntityYamlUtils.TimedTransitionPropertyMoneyDeserializer;
|
||||
import google.registry.model.EntityYamlUtils.TimedTransitionPropertyTldStateDeserializer;
|
||||
import google.registry.model.EntityYamlUtils.TokenVKeyListDeserializer;
|
||||
import google.registry.model.EntityYamlUtils.TokenVKeyListSerializer;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.common.TimedTransitionProperty;
|
||||
@@ -52,15 +61,6 @@ import google.registry.model.domain.fee.BaseFee.FeeType;
|
||||
import google.registry.model.domain.fee.Fee;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.domain.token.AllocationToken.TokenType;
|
||||
import google.registry.model.tld.TldYamlUtils.CreateAutoTimestampDeserializer;
|
||||
import google.registry.model.tld.TldYamlUtils.CurrencyDeserializer;
|
||||
import google.registry.model.tld.TldYamlUtils.CurrencySerializer;
|
||||
import google.registry.model.tld.TldYamlUtils.OptionalDurationSerializer;
|
||||
import google.registry.model.tld.TldYamlUtils.OptionalStringSerializer;
|
||||
import google.registry.model.tld.TldYamlUtils.TimedTransitionPropertyMoneyDeserializer;
|
||||
import google.registry.model.tld.TldYamlUtils.TimedTransitionPropertyTldStateDeserializer;
|
||||
import google.registry.model.tld.TldYamlUtils.TokenVKeyListDeserializer;
|
||||
import google.registry.model.tld.TldYamlUtils.TokenVKeyListSerializer;
|
||||
import google.registry.model.tld.label.PremiumList;
|
||||
import google.registry.model.tld.label.ReservedList;
|
||||
import google.registry.persistence.VKey;
|
||||
|
||||
@@ -206,13 +206,11 @@ public class ClaimsList extends ImmutableObject {
|
||||
if (labelsToKeys != null) {
|
||||
return Optional.ofNullable(labelsToKeys.get(label));
|
||||
}
|
||||
return tm().transact(
|
||||
() ->
|
||||
tm().createQueryComposer(ClaimsEntry.class)
|
||||
.where("revisionId", EQ, revisionId)
|
||||
.where("domainLabel", EQ, label)
|
||||
.first()
|
||||
.map(ClaimsEntry::getClaimKey));
|
||||
return tm().createQueryComposer(ClaimsEntry.class)
|
||||
.where("revisionId", EQ, revisionId)
|
||||
.where("domainLabel", EQ, label)
|
||||
.first()
|
||||
.map(ClaimsEntry::getClaimKey);
|
||||
}
|
||||
|
||||
public static ClaimsList create(
|
||||
|
||||
@@ -23,6 +23,7 @@ import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import google.registry.flows.ServerTridProviderModule;
|
||||
import google.registry.flows.custom.CustomLogicFactoryModule;
|
||||
import google.registry.groups.DirectoryModule;
|
||||
import google.registry.groups.GmailModule;
|
||||
import google.registry.groups.GroupsModule;
|
||||
import google.registry.groups.GroupssettingsModule;
|
||||
import google.registry.keyring.KeyringModule;
|
||||
@@ -54,6 +55,7 @@ import javax.inject.Singleton;
|
||||
DirectoryModule.class,
|
||||
DummyKeyringModule.class,
|
||||
FrontendRequestComponentModule.class,
|
||||
GmailModule.class,
|
||||
GroupsModule.class,
|
||||
GroupssettingsModule.class,
|
||||
GsonModule.class,
|
||||
|
||||
@@ -29,6 +29,7 @@ import google.registry.ui.server.console.ConsoleDomainGetAction;
|
||||
import google.registry.ui.server.console.RegistrarsAction;
|
||||
import google.registry.ui.server.console.settings.ContactAction;
|
||||
import google.registry.ui.server.console.settings.SecurityAction;
|
||||
import google.registry.ui.server.console.settings.WhoisRegistrarFieldsAction;
|
||||
import google.registry.ui.server.registrar.ConsoleOteSetupAction;
|
||||
import google.registry.ui.server.registrar.ConsoleRegistrarCreatorAction;
|
||||
import google.registry.ui.server.registrar.ConsoleUiAction;
|
||||
@@ -73,6 +74,8 @@ interface FrontendRequestComponent {
|
||||
|
||||
SecurityAction securityAction();
|
||||
|
||||
WhoisRegistrarFieldsAction whoisRegistrarFieldsAction();
|
||||
|
||||
@Subcomponent.Builder
|
||||
abstract class Builder implements RequestComponentBuilder<FrontendRequestComponent> {
|
||||
@Override public abstract Builder requestModule(RequestModule requestModule);
|
||||
|
||||
@@ -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.persistence;
|
||||
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicates which {@link TransactionIsolationLevel} that a {@link
|
||||
* google.registry.flows.TransactionalFlow} show run at.
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface IsolationLevel {
|
||||
TransactionIsolationLevel value();
|
||||
}
|
||||
@@ -27,9 +27,9 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
|
||||
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import dagger.BindsOptionalOf;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
@@ -65,7 +65,6 @@ import org.hibernate.cfg.Environment;
|
||||
/** Dagger module class for the persistence layer. */
|
||||
@Module
|
||||
public abstract class PersistenceModule {
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
// This name must be the same as the one defined in persistence.xml.
|
||||
public static final String PERSISTENCE_UNIT_NAME = "nomulus";
|
||||
@@ -166,12 +165,10 @@ public abstract class PersistenceModule {
|
||||
// Override the default minimum which is tuned for the Registry server. A worker VM should
|
||||
// release all connections if it no longer interacts with the database.
|
||||
overrides.put(HIKARI_MINIMUM_IDLE, "0");
|
||||
/**
|
||||
* Disable Hikari's maxPoolSize limit check by setting it to an absurdly large number. The
|
||||
* effective (and desirable) limit is the number of pipeline threads on the pipeline worker,
|
||||
* which can be configured using pipeline options. See {@link RegistryPipelineOptions} for more
|
||||
* information.
|
||||
*/
|
||||
// Disable Hikari's maxPoolSize limit check by setting it to an absurdly large number. The
|
||||
// effective (and desirable) limit is the number of pipeline threads on the pipeline worker,
|
||||
// which can be configured using pipeline options. See {@link RegistryPipelineOptions} for more
|
||||
// information.
|
||||
overrides.put(HIKARI_MAXIMUM_POOL_SIZE, String.valueOf(Integer.MAX_VALUE));
|
||||
instanceConnectionNameOverride
|
||||
.map(Provider::get)
|
||||
@@ -303,7 +300,7 @@ public abstract class PersistenceModule {
|
||||
|
||||
private static EntityManagerFactory create(Map<String, String> properties) {
|
||||
// If there are no annotated classes, we can create the EntityManagerFactory from the generic
|
||||
// method. Otherwise we have to use a more tailored approach. Note that this adds to the set
|
||||
// method. Otherwise, we have to use a more tailored approach. Note that this adds to the set
|
||||
// of annotated classes defined in the configuration, it does not override them.
|
||||
EntityManagerFactory emf =
|
||||
Persistence.createEntityManagerFactory(
|
||||
@@ -322,8 +319,7 @@ public abstract class PersistenceModule {
|
||||
overrides.put(Environment.USER, credential.login());
|
||||
overrides.put(Environment.PASS, credential.password());
|
||||
} catch (Throwable e) {
|
||||
// TODO(b/184631990): after SQL becomes primary, throw an exception to fail fast
|
||||
logger.atSevere().withCause(e).log("Failed to get SQL credential from Secret Manager.");
|
||||
throw new RuntimeException("Failed to get SQL credential from Secret Manager.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,11 +336,13 @@ public abstract class PersistenceModule {
|
||||
TRANSACTION_SERIALIZABLE;
|
||||
|
||||
private final int value;
|
||||
private final String mode;
|
||||
|
||||
TransactionIsolationLevel() {
|
||||
try {
|
||||
// name() is final in parent class (Enum.java), therefore safe to call in constructor.
|
||||
value = Connection.class.getField(name()).getInt(null);
|
||||
mode = name().substring(12).replace('_', ' ');
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(
|
||||
String.format(
|
||||
@@ -356,6 +354,14 @@ public abstract class PersistenceModule {
|
||||
public final int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public final String getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public static TransactionIsolationLevel fromMode(String mode) {
|
||||
return valueOf(String.format("TRANSACTION_%s", Ascii.toUpperCase(mode.replace(' ', '_'))));
|
||||
}
|
||||
}
|
||||
|
||||
/** Types of {@link JpaTransactionManager JpaTransactionManagers}. */
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.persistence.transaction;
|
||||
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.function.Supplier;
|
||||
import javax.persistence.EntityManager;
|
||||
@@ -31,21 +32,6 @@ public interface JpaTransactionManager extends TransactionManager {
|
||||
*/
|
||||
EntityManager getStandaloneEntityManager();
|
||||
|
||||
/**
|
||||
* Specifies a database snapshot exported by another transaction to use in the current
|
||||
* transaction.
|
||||
*
|
||||
* <p>This is a Postgresql-specific feature. This method must be called before any other SQL
|
||||
* commands in a transaction.
|
||||
*
|
||||
* <p>To support large queries, transaction isolation level is fixed at the REPEATABLE_READ to
|
||||
* avoid exhausting predicate locks at the SERIALIZABLE level.
|
||||
*
|
||||
* @see google.registry.beam.common.DatabaseSnapshot
|
||||
*/
|
||||
// TODO(b/193662898): vendor-independent support for richer transaction semantics.
|
||||
JpaTransactionManager setDatabaseSnapshot(String snapshotId);
|
||||
|
||||
/**
|
||||
* Returns the {@link EntityManager} for the current request.
|
||||
*
|
||||
@@ -56,8 +42,8 @@ public interface JpaTransactionManager extends TransactionManager {
|
||||
/**
|
||||
* Creates a JPA SQL query for the given query string and result class.
|
||||
*
|
||||
* <p>This is a convenience method for the longer <code>
|
||||
* jpaTm().getEntityManager().createQuery(...)</code>.
|
||||
* <p>This is a convenience method for the longer {@code
|
||||
* jpaTm().getEntityManager().createQuery(...)}.
|
||||
*/
|
||||
<T> TypedQuery<T> query(String sqlString, Class<T> resultClass);
|
||||
|
||||
@@ -67,8 +53,8 @@ public interface JpaTransactionManager extends TransactionManager {
|
||||
/**
|
||||
* Creates a JPA SQL query for the given query string.
|
||||
*
|
||||
* <p>This is a convenience method for the longer <code>
|
||||
* jpaTm().getEntityManager().createQuery(...)</code>.
|
||||
* <p>This is a convenience method for the longer {@code
|
||||
* jpaTm().getEntityManager().createQuery(...)}.
|
||||
*
|
||||
* <p>Note that while this method can legally be used for queries that return results, <u>it
|
||||
* should not be</u>, as it does not correctly detach entities as must be done for nomulus model
|
||||
@@ -79,9 +65,21 @@ public interface JpaTransactionManager extends TransactionManager {
|
||||
/** Executes the work in a transaction with no retries and returns the result. */
|
||||
<T> T transactNoRetry(Supplier<T> work);
|
||||
|
||||
/**
|
||||
* Executes the work in a transaction at the given {@link TransactionIsolationLevel} with no
|
||||
* retries and returns the result.
|
||||
*/
|
||||
<T> T transactNoRetry(Supplier<T> work, TransactionIsolationLevel isolationLevel);
|
||||
|
||||
/** Executes the work in a transaction with no retries. */
|
||||
void transactNoRetry(Runnable work);
|
||||
|
||||
/**
|
||||
* Executes the work in a transaction at the given {@link TransactionIsolationLevel} with no
|
||||
* retries.
|
||||
*/
|
||||
void transactNoRetry(Runnable work, TransactionIsolationLevel isolationLevel);
|
||||
|
||||
/** Deletes the entity by its id, throws exception if the entity is not deleted. */
|
||||
<T> void assertDelete(VKey<T> key);
|
||||
|
||||
@@ -99,4 +97,13 @@ public interface JpaTransactionManager extends TransactionManager {
|
||||
static Query setQueryFetchSize(Query query, int fetchSize) {
|
||||
return query.setHint("org.hibernate.fetchSize", fetchSize);
|
||||
}
|
||||
|
||||
/** Return the default {@link TransactionIsolationLevel} specified via the config file. */
|
||||
TransactionIsolationLevel getDefaultTransactionIsolationLevel();
|
||||
|
||||
/** Return the {@link TransactionIsolationLevel} used in the current transaction. */
|
||||
TransactionIsolationLevel getCurrentTransactionIsolationLevel();
|
||||
|
||||
/** Asserts that the current transaction runs at the given level. */
|
||||
void assertTransactionIsolationLevel(TransactionIsolationLevel expectedLevel);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.config.RegistryConfig.getHibernatePerTransactionIsolationEnabled;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
import static java.util.AbstractMap.SimpleEntry;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
@@ -31,6 +32,7 @@ import com.google.common.collect.Streams;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.persistence.JpaRetries;
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.Retrier;
|
||||
@@ -65,6 +67,7 @@ import javax.persistence.TemporalType;
|
||||
import javax.persistence.TypedQuery;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.metamodel.EntityType;
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Implementation of {@link JpaTransactionManager} for JPA compatible database. */
|
||||
@@ -77,9 +80,6 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
private final EntityManagerFactory emf;
|
||||
private final Clock clock;
|
||||
|
||||
// TODO(b/177588434): Investigate alternatives for managing transaction information. ThreadLocal
|
||||
// adds an unnecessary restriction that each request has to be processed by one thread
|
||||
// synchronously.
|
||||
private static final ThreadLocal<TransactionInfo> transactionInfo =
|
||||
ThreadLocal.withInitial(TransactionInfo::new);
|
||||
|
||||
@@ -109,22 +109,6 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
return entityManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaTransactionManager setDatabaseSnapshot(String snapshotId) {
|
||||
// Postgresql-specific: 'set transaction' command must be called inside a transaction
|
||||
assertInTransaction();
|
||||
|
||||
EntityManager entityManager = getEntityManager();
|
||||
// Isolation is hardcoded to REPEATABLE READ, as specified by parent's Javadoc.
|
||||
entityManager
|
||||
.createNativeQuery("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ")
|
||||
.executeUpdate();
|
||||
entityManager
|
||||
.createNativeQuery(String.format("SET TRANSACTION SNAPSHOT '%s'", snapshotId))
|
||||
.executeUpdate();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> TypedQuery<T> query(String sqlString, Class<T> resultClass) {
|
||||
return new DetachingTypedQuery<>(getEntityManager().createQuery(sqlString, resultClass));
|
||||
@@ -153,17 +137,51 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T transact(Supplier<T> work) {
|
||||
// This prevents inner transaction from retrying, thus avoiding a cascade retry effect.
|
||||
if (inTransaction()) {
|
||||
return transactNoRetry(work);
|
||||
public void assertTransactionIsolationLevel(TransactionIsolationLevel expectedLevel) {
|
||||
assertInTransaction();
|
||||
TransactionIsolationLevel currentLevel = getCurrentTransactionIsolationLevel();
|
||||
if (currentLevel != expectedLevel) {
|
||||
throw new IllegalStateException(
|
||||
String.format(
|
||||
"Current transaction isolation level (%s) is not as expected (%s)",
|
||||
currentLevel, expectedLevel));
|
||||
}
|
||||
return retrier.callWithRetry(() -> transactNoRetry(work), JpaRetries::isFailedTxnRetriable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T transactNoRetry(Supplier<T> work) {
|
||||
public <T> T transact(Supplier<T> work, TransactionIsolationLevel isolationLevel) {
|
||||
// This prevents inner transaction from retrying, thus avoiding a cascade retry effect.
|
||||
if (inTransaction()) {
|
||||
return transactNoRetry(work, isolationLevel);
|
||||
}
|
||||
return retrier.callWithRetry(
|
||||
() -> transactNoRetry(work, isolationLevel), JpaRetries::isFailedTxnRetriable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T reTransact(Supplier<T> work) {
|
||||
return transact(work);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T transact(Supplier<T> work) {
|
||||
return transact(work, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T transactNoRetry(
|
||||
Supplier<T> work, @Nullable TransactionIsolationLevel isolationLevel) {
|
||||
if (inTransaction()) {
|
||||
if (isolationLevel != null && getHibernatePerTransactionIsolationEnabled()) {
|
||||
TransactionIsolationLevel enclosingLevel = getCurrentTransactionIsolationLevel();
|
||||
if (isolationLevel != enclosingLevel) {
|
||||
throw new IllegalStateException(
|
||||
String.format(
|
||||
"Isolation level conflict detected in nested transactions.\n"
|
||||
+ "Enclosing transaction: %s\nCurrent transaction: %s",
|
||||
enclosingLevel, isolationLevel));
|
||||
}
|
||||
}
|
||||
return work.get();
|
||||
}
|
||||
TransactionInfo txnInfo = transactionInfo.get();
|
||||
@@ -172,6 +190,18 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
try {
|
||||
txn.begin();
|
||||
txnInfo.start(clock);
|
||||
if (isolationLevel != null) {
|
||||
if (getHibernatePerTransactionIsolationEnabled()) {
|
||||
getEntityManager()
|
||||
.createNativeQuery(
|
||||
String.format("SET TRANSACTION ISOLATION LEVEL %s", isolationLevel.getMode()))
|
||||
.executeUpdate();
|
||||
logger.atInfo().log("Running transaction at %s", isolationLevel);
|
||||
} else {
|
||||
logger.atWarning().log(
|
||||
"Per-transaction isolation level disabled, but %s was requested", isolationLevel);
|
||||
}
|
||||
}
|
||||
T result = work.get();
|
||||
txn.commit();
|
||||
return result;
|
||||
@@ -190,21 +220,60 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transact(Runnable work) {
|
||||
public <T> T transactNoRetry(Supplier<T> work) {
|
||||
return transactNoRetry(work, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transact(Runnable work, TransactionIsolationLevel isolationLevel) {
|
||||
transact(
|
||||
() -> {
|
||||
work.run();
|
||||
return null;
|
||||
});
|
||||
},
|
||||
isolationLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transactNoRetry(Runnable work) {
|
||||
public void reTransact(Runnable work) {
|
||||
transact(work);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transact(Runnable work) {
|
||||
transact(work, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transactNoRetry(Runnable work, TransactionIsolationLevel isolationLevel) {
|
||||
transactNoRetry(
|
||||
() -> {
|
||||
work.run();
|
||||
return null;
|
||||
});
|
||||
},
|
||||
isolationLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transactNoRetry(Runnable work) {
|
||||
transactNoRetry(work, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionIsolationLevel getDefaultTransactionIsolationLevel() {
|
||||
return TransactionIsolationLevel.valueOf(
|
||||
(String) emf.getProperties().get(Environment.ISOLATION));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionIsolationLevel getCurrentTransactionIsolationLevel() {
|
||||
assertInTransaction();
|
||||
String mode =
|
||||
(String)
|
||||
getEntityManager()
|
||||
.createNativeQuery("SHOW TRANSACTION ISOLATION LEVEL")
|
||||
.getSingleResult();
|
||||
return TransactionIsolationLevel.fromMode(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -303,7 +372,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
Integer.class)
|
||||
.setMaxResults(1);
|
||||
entityIds.forEach(entityId -> query.setParameter(entityId.name, entityId.value));
|
||||
return query.getResultList().size() > 0;
|
||||
return !query.getResultList().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -522,7 +591,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
.collect(toImmutableSet());
|
||||
}
|
||||
|
||||
private String getAndClause(ImmutableSet<EntityId> entityIds) {
|
||||
private static String getAndClause(ImmutableSet<EntityId> entityIds) {
|
||||
return entityIds.stream()
|
||||
.map(entityId -> String.format("%s = :%s", entityId.name, entityId.name))
|
||||
.collect(joining(" AND "));
|
||||
@@ -572,7 +641,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
try {
|
||||
getEntityManager().getMetamodel().entity(object.getClass());
|
||||
} catch (IllegalArgumentException e) {
|
||||
// The object is not an entity. Return without detaching.
|
||||
// The object is not an entity. Return without detaching.
|
||||
return object;
|
||||
}
|
||||
|
||||
@@ -653,7 +722,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
|
||||
TypedQuery<T> delegate;
|
||||
|
||||
public DetachingTypedQuery(TypedQuery<T> delegate) {
|
||||
DetachingTypedQuery(TypedQuery<T> delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@@ -882,7 +951,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
@Override
|
||||
public Optional<T> first() {
|
||||
List<T> results = buildQuery().setMaxResults(1).getResultList();
|
||||
return results.size() > 0 ? Optional.of(detach(results.get(0))) : Optional.empty();
|
||||
return !results.isEmpty() ? Optional.of(detach(results.get(0))) : Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -911,7 +980,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
public ImmutableList<T> list() {
|
||||
return buildQuery().getResultList().stream()
|
||||
.map(JpaTransactionManagerImpl.this::detach)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
@@ -49,9 +50,50 @@ public interface TransactionManager {
|
||||
/** Executes the work in a transaction and returns the result. */
|
||||
<T> T transact(Supplier<T> work);
|
||||
|
||||
/**
|
||||
* Executes the work in a transaction at the given {@link TransactionIsolationLevel} and returns
|
||||
* the result.
|
||||
*/
|
||||
<T> T transact(Supplier<T> work, TransactionIsolationLevel isolationLevel);
|
||||
|
||||
/**
|
||||
* Executes the work in a (potentially wrapped) transaction and returns the result.
|
||||
*
|
||||
* <p>Calls to this method are typically going to be in inner functions, that are called either as
|
||||
* top-level transactions themselves or are nested inside of larger transactions (e.g. a
|
||||
* transactional flow). Invocations of reTransact must be vetted to occur in both situations and
|
||||
* with such complexity that it is not trivial to refactor out the nested transaction calls. New
|
||||
* code should be written in such a way as to avoid requiring reTransact in the first place.
|
||||
*
|
||||
* <p>In the future we will be enforcing that {@link #transact(Supplier)} calls be top-level only,
|
||||
* with reTransact calls being the only ones that can potentially be an inner nested transaction
|
||||
* (which is a noop). Note that, as this can be a nested inner exception, there is no overload
|
||||
* provided to specify a (potentially conflicting) transaction isolation level.
|
||||
*/
|
||||
<T> T reTransact(Supplier<T> work);
|
||||
|
||||
/** Executes the work in a transaction. */
|
||||
void transact(Runnable work);
|
||||
|
||||
/** Executes the work in a transaction at the given {@link TransactionIsolationLevel}. */
|
||||
void transact(Runnable work, TransactionIsolationLevel isolationLevel);
|
||||
|
||||
/**
|
||||
* Executes the work in a (potentially wrapped) transaction and returns the result.
|
||||
*
|
||||
* <p>Calls to this method are typically going to be in inner functions, that are called either as
|
||||
* top-level transactions themselves or are nested inside of larger transactions (e.g. a
|
||||
* transactional flow). Invocations of reTransact must be vetted to occur in both situations and
|
||||
* with such complexity that it is not trivial to refactor out the nested transaction calls. New
|
||||
* code should be written in such a way as to avoid requiring reTransact in the first place.
|
||||
*
|
||||
* <p>In the future we will be enforcing that {@link #transact(Runnable)} calls be top-level only,
|
||||
* with reTransact calls being the only ones that can potentially be an inner nested transaction
|
||||
* (which is a noop). Note that, as this can be a nested inner exception, there is no overload *
|
||||
* provided to specify a (potentially conflicting) transaction isolation level.
|
||||
*/
|
||||
void reTransact(Runnable work);
|
||||
|
||||
/** Returns the time associated with the start of this particular transaction attempt. */
|
||||
DateTime getTransactionTime();
|
||||
|
||||
|
||||
@@ -23,12 +23,13 @@ import com.google.common.io.CharStreams;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.gcs.GcsUtils;
|
||||
import google.registry.groups.GmailClient;
|
||||
import google.registry.reporting.billing.BillingModule.InvoiceDirectoryPrefix;
|
||||
import google.registry.util.EmailMessage;
|
||||
import google.registry.util.EmailMessage.Attachment;
|
||||
import google.registry.util.SendEmailService;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import org.joda.time.YearMonth;
|
||||
@@ -36,11 +37,12 @@ import org.joda.time.YearMonth;
|
||||
/** Utility functions for sending emails involving monthly invoices. */
|
||||
public class BillingEmailUtils {
|
||||
|
||||
private final SendEmailService emailService;
|
||||
private final GmailClient gmailClient;
|
||||
private final YearMonth yearMonth;
|
||||
private final InternetAddress outgoingEmailAddress;
|
||||
private final InternetAddress alertRecipientAddress;
|
||||
private final ImmutableList<InternetAddress> invoiceEmailRecipients;
|
||||
private final Optional<InternetAddress> replyToEmailAddress;
|
||||
private final String billingBucket;
|
||||
private final String invoiceFilePrefix;
|
||||
private final String invoiceDirectoryPrefix;
|
||||
@@ -48,20 +50,22 @@ public class BillingEmailUtils {
|
||||
|
||||
@Inject
|
||||
BillingEmailUtils(
|
||||
SendEmailService emailService,
|
||||
GmailClient gmailClient,
|
||||
YearMonth yearMonth,
|
||||
@Config("gSuiteOutgoingEmailAddress") InternetAddress outgoingEmailAddress,
|
||||
@Config("alertRecipientEmailAddress") InternetAddress alertRecipientAddress,
|
||||
@Config("invoiceEmailRecipients") ImmutableList<InternetAddress> invoiceEmailRecipients,
|
||||
@Config("invoiceReplyToEmailAddress") Optional<InternetAddress> replyToEmailAddress,
|
||||
@Config("billingBucket") String billingBucket,
|
||||
@Config("invoiceFilePrefix") String invoiceFilePrefix,
|
||||
@InvoiceDirectoryPrefix String invoiceDirectoryPrefix,
|
||||
GcsUtils gcsUtils) {
|
||||
this.emailService = emailService;
|
||||
this.gmailClient = gmailClient;
|
||||
this.yearMonth = yearMonth;
|
||||
this.outgoingEmailAddress = outgoingEmailAddress;
|
||||
this.alertRecipientAddress = alertRecipientAddress;
|
||||
this.invoiceEmailRecipients = invoiceEmailRecipients;
|
||||
this.replyToEmailAddress = replyToEmailAddress;
|
||||
this.billingBucket = billingBucket;
|
||||
this.invoiceFilePrefix = invoiceFilePrefix;
|
||||
this.invoiceDirectoryPrefix = invoiceDirectoryPrefix;
|
||||
@@ -74,13 +78,14 @@ public class BillingEmailUtils {
|
||||
String invoiceFile = String.format("%s-%s.csv", invoiceFilePrefix, yearMonth);
|
||||
BlobId invoiceFilename = BlobId.of(billingBucket, invoiceDirectoryPrefix + invoiceFile);
|
||||
try (InputStream in = gcsUtils.openInputStream(invoiceFilename)) {
|
||||
emailService.sendEmail(
|
||||
gmailClient.sendEmail(
|
||||
EmailMessage.newBuilder()
|
||||
.setSubject(String.format("Domain Registry invoice data %s", yearMonth))
|
||||
.setBody(
|
||||
String.format("Attached is the %s invoice for the domain registry.", yearMonth))
|
||||
.setFrom(outgoingEmailAddress)
|
||||
.setRecipients(invoiceEmailRecipients)
|
||||
.setReplyToEmailAddress(replyToEmailAddress)
|
||||
.setAttachment(
|
||||
Attachment.newBuilder()
|
||||
.setContent(CharStreams.toString(new InputStreamReader(in, UTF_8)))
|
||||
@@ -100,7 +105,7 @@ public class BillingEmailUtils {
|
||||
/** Sends an e-mail to the provided alert e-mail address indicating a billing failure. */
|
||||
void sendAlertEmail(String body) {
|
||||
try {
|
||||
emailService.sendEmail(
|
||||
gmailClient.sendEmail(
|
||||
EmailMessage.newBuilder()
|
||||
.setSubject(String.format("Billing Pipeline Alert: %s", yearMonth))
|
||||
.setBody(body)
|
||||
|
||||
@@ -17,9 +17,9 @@ package google.registry.reporting.billing;
|
||||
import static google.registry.reporting.ReportingModule.PARAM_YEAR_MONTH;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
|
||||
|
||||
import com.google.api.services.dataflow.Dataflow;
|
||||
import com.google.api.services.dataflow.model.Job;
|
||||
@@ -111,7 +111,7 @@ public class PublishInvoicesAction implements Runnable {
|
||||
break;
|
||||
default:
|
||||
logger.atInfo().log("Job in non-terminal state %s, retrying:", state);
|
||||
response.setStatus(SC_NOT_MODIFIED);
|
||||
response.setStatus(SC_SERVICE_UNAVAILABLE);
|
||||
break;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.google.common.net.MediaType;
|
||||
import google.registry.batch.CloudTasksUtils;
|
||||
import google.registry.bigquery.BigqueryJobFailureException;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.groups.GmailClient;
|
||||
import google.registry.reporting.icann.IcannReportingModule.ReportType;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.Service;
|
||||
@@ -37,7 +38,6 @@ import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.EmailMessage;
|
||||
import google.registry.util.Retrier;
|
||||
import google.registry.util.SendEmailService;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
@@ -83,8 +83,12 @@ public final class IcannReportingStagingAction implements Runnable {
|
||||
@Inject Retrier retrier;
|
||||
@Inject Response response;
|
||||
@Inject @Config("gSuiteOutgoingEmailAddress") InternetAddress sender;
|
||||
@Inject @Config("alertRecipientEmailAddress") InternetAddress recipient;
|
||||
@Inject SendEmailService emailService;
|
||||
|
||||
@Inject
|
||||
@Config("newAlertRecipientEmailAddress")
|
||||
InternetAddress recipient;
|
||||
|
||||
@Inject GmailClient gmailClient;
|
||||
@Inject CloudTasksUtils cloudTasksUtils;
|
||||
|
||||
@Inject IcannReportingStagingAction() {}
|
||||
@@ -103,7 +107,7 @@ public final class IcannReportingStagingAction implements Runnable {
|
||||
stager.createAndUploadManifest(subdir, manifestedFiles);
|
||||
|
||||
logger.atInfo().log("Completed staging %d report files.", manifestedFiles.size());
|
||||
emailService.sendEmail(
|
||||
gmailClient.sendEmail(
|
||||
EmailMessage.newBuilder()
|
||||
.setSubject("ICANN Monthly report staging summary [SUCCESS]")
|
||||
.setBody(
|
||||
@@ -130,7 +134,7 @@ public final class IcannReportingStagingAction implements Runnable {
|
||||
},
|
||||
BigqueryJobFailureException.class);
|
||||
} catch (Throwable e) {
|
||||
emailService.sendEmail(
|
||||
gmailClient.sendEmail(
|
||||
EmailMessage.create(
|
||||
"ICANN Monthly report staging summary [FAILURE]",
|
||||
String.format(
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.gcs.GcsUtils;
|
||||
import google.registry.groups.GmailClient;
|
||||
import google.registry.model.common.Cursor;
|
||||
import google.registry.model.common.Cursor.CursorType;
|
||||
import google.registry.model.tld.Tld;
|
||||
@@ -41,7 +42,6 @@ import google.registry.request.lock.LockHandler;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.EmailMessage;
|
||||
import google.registry.util.Retrier;
|
||||
import google.registry.util.SendEmailService;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
@@ -87,8 +87,12 @@ public final class IcannReportingUploadAction implements Runnable {
|
||||
@Inject Retrier retrier;
|
||||
@Inject Response response;
|
||||
@Inject @Config("gSuiteOutgoingEmailAddress") InternetAddress sender;
|
||||
@Inject @Config("alertRecipientEmailAddress") InternetAddress recipient;
|
||||
@Inject SendEmailService emailService;
|
||||
|
||||
@Inject
|
||||
@Config("newAlertRecipientEmailAddress")
|
||||
InternetAddress recipient;
|
||||
|
||||
@Inject GmailClient gmailClient;
|
||||
@Inject Clock clock;
|
||||
@Inject LockHandler lockHandler;
|
||||
|
||||
@@ -293,7 +297,7 @@ public final class IcannReportingUploadAction implements Runnable {
|
||||
(e) ->
|
||||
String.format("%s - %s", e.getKey(), e.getValue() ? "SUCCESS" : "FAILURE"))
|
||||
.collect(Collectors.joining("\n")));
|
||||
emailService.sendEmail(EmailMessage.create(subject, body, recipient, sender));
|
||||
gmailClient.sendEmail(EmailMessage.create(subject, body, recipient, sender));
|
||||
}
|
||||
|
||||
private byte[] readBytesFromGcs(BlobId reportFilename) throws IOException {
|
||||
|
||||
@@ -18,9 +18,9 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.reporting.ReportingModule.PARAM_DATE;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
|
||||
|
||||
import com.google.api.services.dataflow.Dataflow;
|
||||
import com.google.api.services.dataflow.model.Job;
|
||||
@@ -135,7 +135,7 @@ public class PublishSpec11ReportAction implements Runnable {
|
||||
break;
|
||||
default:
|
||||
logger.atInfo().log("Job in non-terminal state %s, retrying:", state);
|
||||
response.setStatus(SC_NOT_MODIFIED);
|
||||
response.setStatus(SC_SERVICE_UNAVAILABLE);
|
||||
break;
|
||||
}
|
||||
} catch (IOException | JSONException e) {
|
||||
|
||||
@@ -90,6 +90,7 @@ public class Spec11EmailUtils {
|
||||
ImmutableSet<RegistrarThreatMatches> registrarThreatMatchesSet) {
|
||||
ImmutableMap.Builder<RegistrarThreatMatches, Throwable> failedMatchesBuilder =
|
||||
ImmutableMap.builder();
|
||||
int numRegistrarsEmailed = 0;
|
||||
for (RegistrarThreatMatches registrarThreatMatches : registrarThreatMatchesSet) {
|
||||
RegistrarThreatMatches filteredMatches = filterOutNonPublishedMatches(registrarThreatMatches);
|
||||
if (!filteredMatches.threatMatches().isEmpty()) {
|
||||
@@ -97,11 +98,13 @@ public class Spec11EmailUtils {
|
||||
// Handle exceptions individually per registrar so that one failed email doesn't prevent
|
||||
// the rest from being sent.
|
||||
emailRegistrar(date, soyTemplateInfo, subject, filteredMatches);
|
||||
numRegistrarsEmailed++;
|
||||
} catch (Throwable e) {
|
||||
failedMatchesBuilder.put(registrarThreatMatches, getRootCause(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.atInfo().log("Emailed daily diffs to %s registrars.", numRegistrarsEmailed);
|
||||
ImmutableMap<RegistrarThreatMatches, Throwable> failedMatches = failedMatchesBuilder.build();
|
||||
if (!failedMatches.isEmpty()) {
|
||||
ImmutableList<Map.Entry<RegistrarThreatMatches, Throwable>> failedMatchesList =
|
||||
|
||||
@@ -31,7 +31,7 @@ import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.CharStreams;
|
||||
import com.google.common.net.MediaType;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.protobuf.ByteString;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
@@ -40,6 +40,7 @@ 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.tools.GsonUtils;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -69,6 +70,13 @@ public final class RequestModule {
|
||||
this.authResult = authResult;
|
||||
}
|
||||
|
||||
@RequestScope
|
||||
@VisibleForTesting
|
||||
@Provides
|
||||
public static Gson provideGson() {
|
||||
return GsonUtils.provideGson();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter(RequestParameters.PARAM_TLD)
|
||||
static String provideTld(HttpServletRequest req) {
|
||||
@@ -244,11 +252,11 @@ public final class RequestModule {
|
||||
|
||||
@Provides
|
||||
@OptionalJsonPayload
|
||||
public static Optional<JsonObject> provideJsonBody(HttpServletRequest req, Gson gson) {
|
||||
public static Optional<JsonElement> provideJsonBody(HttpServletRequest req, Gson gson) {
|
||||
try {
|
||||
JsonObject body = gson.fromJson(req.getReader(), JsonObject.class);
|
||||
return Optional.of(body);
|
||||
} catch (Exception e) {
|
||||
// GET requests return a null reader and thus a null JsonObject, which is fine
|
||||
return Optional.ofNullable(gson.fromJson(req.getReader(), JsonElement.class));
|
||||
} catch (IOException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,23 +16,23 @@ package google.registry.tools;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.beust.jcommander.Parameters;
|
||||
import google.registry.model.domain.token.PackagePromotion;
|
||||
import google.registry.model.domain.token.BulkPricingPackage;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/** Command to create a PackagePromotion */
|
||||
/** Command to create a {@link BulkPricingPackage} */
|
||||
@Parameters(
|
||||
separators = " =",
|
||||
commandDescription =
|
||||
"Create new package promotion object(s) for registrars to register multiple names under"
|
||||
+ " one contractual annual package price using a package allocation token")
|
||||
public final class CreatePackagePromotionCommand extends CreateOrUpdatePackagePromotionCommand {
|
||||
"Create new bulk pricing package object(s) for registrars to register multiple names under"
|
||||
+ " one contractual annual bulk price using a bulk pricing allocation token")
|
||||
public final class CreateBulkPricingPackageCommand extends CreateOrUpdateBulkPricingPackageCommand {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
PackagePromotion getOldPackagePromotion(String tokenString) {
|
||||
BulkPricingPackage getOldBulkPricingPackage(String tokenString) {
|
||||
checkArgument(
|
||||
!PackagePromotion.loadByTokenString(tokenString).isPresent(),
|
||||
"PackagePromotion with token %s already exists",
|
||||
!BulkPricingPackage.loadByTokenString(tokenString).isPresent(),
|
||||
"BulkPricingPackage with token %s already exists",
|
||||
tokenString);
|
||||
return null;
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
|
||||
import com.beust.jcommander.Parameter;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.domain.token.AllocationToken.TokenType;
|
||||
import google.registry.model.domain.token.PackagePromotion;
|
||||
import google.registry.model.domain.token.BulkPricingPackage;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -29,39 +29,40 @@ import javax.annotation.Nullable;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Shared base class for commands to create or update a PackagePromotion object. */
|
||||
abstract class CreateOrUpdatePackagePromotionCommand extends MutatingCommand {
|
||||
/** Shared base class for commands to create or update a {@link BulkPricingPackage} object. */
|
||||
abstract class CreateOrUpdateBulkPricingPackageCommand extends MutatingCommand {
|
||||
|
||||
@Parameter(description = "Allocation token String of the package token", required = true)
|
||||
@Parameter(description = "Allocation token String of the bulk token", required = true)
|
||||
List<String> mainParameters;
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = {"-d", "--max_domains"},
|
||||
description = "Maximum concurrent active domains allowed in the package")
|
||||
description = "Maximum concurrent active domains allowed in the bulk pricing package")
|
||||
Integer maxDomains;
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = {"-c", "--max_creates"},
|
||||
description = "Maximum domain creations allowed in the package each year")
|
||||
description = "Maximum domain creations allowed in the bulk pricing package each year")
|
||||
Integer maxCreates;
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = {"-p", "--price"},
|
||||
description = "Annual price of the package")
|
||||
description = "Annual price of the bulk pricing package")
|
||||
Money price;
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = "--next_billing_date",
|
||||
description = "The next date that the package should be billed for its annual fee")
|
||||
description =
|
||||
"The next date that the bulk pricing package should be billed for its annual fee")
|
||||
Date nextBillingDate;
|
||||
|
||||
/** Returns the existing PackagePromotion or null if it does not exist. */
|
||||
/** Returns the existing BulkPricingPackage or null if it does not exist. */
|
||||
@Nullable
|
||||
abstract PackagePromotion getOldPackagePromotion(String token);
|
||||
abstract BulkPricingPackage getOldBulkPricingPackage(String token);
|
||||
|
||||
/** Returns the allocation token object. */
|
||||
AllocationToken getAndCheckAllocationToken(String token) {
|
||||
@@ -69,12 +70,12 @@ abstract class CreateOrUpdatePackagePromotionCommand extends MutatingCommand {
|
||||
tm().transact(() -> tm().loadByKeyIfPresent(VKey.create(AllocationToken.class, token)));
|
||||
checkArgument(
|
||||
allocationToken.isPresent(),
|
||||
"An allocation token with the token String %s does not exist. The package token must be"
|
||||
+ " created first before it can be used to create a PackagePromotion",
|
||||
"An allocation token with the token String %s does not exist. The bulk token must be"
|
||||
+ " created first before it can be used to create a BulkPricingPackage",
|
||||
token);
|
||||
checkArgument(
|
||||
allocationToken.get().getTokenType().equals(TokenType.PACKAGE),
|
||||
"The allocation token must be of the PACKAGE token type");
|
||||
allocationToken.get().getTokenType().equals(TokenType.BULK_PRICING),
|
||||
"The allocation token must be of the BULK_PRICING token type");
|
||||
return allocationToken.get();
|
||||
}
|
||||
|
||||
@@ -88,21 +89,21 @@ abstract class CreateOrUpdatePackagePromotionCommand extends MutatingCommand {
|
||||
for (String token : mainParameters) {
|
||||
tm().transact(
|
||||
() -> {
|
||||
PackagePromotion oldPackage = getOldPackagePromotion(token);
|
||||
BulkPricingPackage oldBulkPricingPackage = getOldBulkPricingPackage(token);
|
||||
checkArgument(
|
||||
oldPackage != null || price != null,
|
||||
"PackagePrice is required when creating a new package");
|
||||
oldBulkPricingPackage != null || price != null,
|
||||
"BulkPrice is required when creating a new bulk pricing package");
|
||||
|
||||
AllocationToken allocationToken = getAndCheckAllocationToken(token);
|
||||
|
||||
PackagePromotion.Builder builder =
|
||||
(oldPackage == null)
|
||||
? new PackagePromotion.Builder().setToken(allocationToken)
|
||||
: oldPackage.asBuilder();
|
||||
BulkPricingPackage.Builder builder =
|
||||
(oldBulkPricingPackage == null)
|
||||
? new BulkPricingPackage.Builder().setToken(allocationToken)
|
||||
: oldBulkPricingPackage.asBuilder();
|
||||
|
||||
Optional.ofNullable(maxDomains).ifPresent(builder::setMaxDomains);
|
||||
Optional.ofNullable(maxCreates).ifPresent(builder::setMaxCreates);
|
||||
Optional.ofNullable(price).ifPresent(builder::setPackagePrice);
|
||||
Optional.ofNullable(price).ifPresent(builder::setBulkPrice);
|
||||
Optional.ofNullable(nextBillingDate)
|
||||
.ifPresent(
|
||||
nextBillingDate ->
|
||||
@@ -110,8 +111,8 @@ abstract class CreateOrUpdatePackagePromotionCommand extends MutatingCommand {
|
||||
if (clearLastNotificationSent()) {
|
||||
builder.setLastNotificationSent(null);
|
||||
}
|
||||
PackagePromotion newPackage = builder.build();
|
||||
stageEntityChange(oldPackage, newPackage);
|
||||
BulkPricingPackage newBUlkPricingPackage = builder.build();
|
||||
stageEntityChange(oldBulkPricingPackage, newBUlkPricingPackage);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.collect.Sets.difference;
|
||||
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.DEFAULT;
|
||||
import static google.registry.model.domain.token.AllocationToken.TokenType.BULK_PRICING;
|
||||
import static google.registry.model.domain.token.AllocationToken.TokenType.DEFAULT_PROMO;
|
||||
import static google.registry.model.domain.token.AllocationToken.TokenType.PACKAGE;
|
||||
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
|
||||
import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
@@ -279,19 +279,20 @@ class GenerateAllocationTokensCommand implements Command {
|
||||
}
|
||||
|
||||
if (!isNullOrEmpty(tokenStatusTransitions)) {
|
||||
// Don't allow package tokens to be created with a scheduled end time since this could allow
|
||||
// future domains to be attributed to the package and never be billed. Package promotion
|
||||
// Don't allow bulk tokens to be created with a scheduled end time since this could allow
|
||||
// future domains to be attributed to the bulk pricing package and never be billed. Bulk
|
||||
// tokens should only be scheduled to end with a brief time period before the status
|
||||
// transition occurs so that no new domains are registered using that token between when the
|
||||
// status is scheduled and when the transition occurs.
|
||||
// TODO(@sarahbot): Create a cleaner way to handle ending packages once we actually have
|
||||
// customers using them
|
||||
// TODO(@sarahbot): Create a cleaner way to handle ending bulk pricing packages once we
|
||||
// actually have customers using them
|
||||
boolean hasEnding =
|
||||
tokenStatusTransitions.containsValue(TokenStatus.ENDED)
|
||||
|| tokenStatusTransitions.containsValue(TokenStatus.CANCELLED);
|
||||
checkArgument(
|
||||
!(PACKAGE.equals(tokenType) && hasEnding),
|
||||
"PACKAGE tokens should not be generated with ENDED or CANCELLED in their transition map");
|
||||
!(BULK_PRICING.equals(tokenType) && hasEnding),
|
||||
"BULK_PRICING tokens should not be generated with ENDED or CANCELLED in their transition"
|
||||
+ " map");
|
||||
}
|
||||
|
||||
if (tokenStrings != null) {
|
||||
|
||||
@@ -19,14 +19,14 @@ import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import google.registry.model.domain.token.PackagePromotion;
|
||||
import google.registry.model.domain.token.BulkPricingPackage;
|
||||
import java.util.List;
|
||||
|
||||
/** Command to show a {@link PackagePromotion} object. */
|
||||
@Parameters(separators = " =", commandDescription = "Show package promotion object(s)")
|
||||
public class GetPackagePromotionCommand extends GetEppResourceCommand {
|
||||
/** Command to show a {@link BulkPricingPackage} object. */
|
||||
@Parameters(separators = " =", commandDescription = "Show bulk pricing package object(s)")
|
||||
public class GetBulkPricingPackageCommand extends GetEppResourceCommand {
|
||||
|
||||
@Parameter(description = "Package token(s)", required = true)
|
||||
@Parameter(description = "Bulk pricing token(s)", required = true)
|
||||
private List<String> mainParameters;
|
||||
|
||||
@Override
|
||||
@@ -34,12 +34,12 @@ public class GetPackagePromotionCommand extends GetEppResourceCommand {
|
||||
for (String token : mainParameters) {
|
||||
tm().transact(
|
||||
() -> {
|
||||
PackagePromotion packagePromotion =
|
||||
BulkPricingPackage bulkPricingPackage =
|
||||
checkArgumentPresent(
|
||||
PackagePromotion.loadByTokenString(token),
|
||||
"PackagePromotion with package token %s does not exist",
|
||||
BulkPricingPackage.loadByTokenString(token),
|
||||
"BulkPricingPackage with token %s does not exist",
|
||||
token);
|
||||
System.out.println(packagePromotion);
|
||||
System.out.println(bulkPricingPackage);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -14,15 +14,18 @@
|
||||
|
||||
package google.registry.tools;
|
||||
|
||||
import static google.registry.model.tld.TldYamlUtils.getObjectMapper;
|
||||
import static google.registry.model.tld.Tlds.assertTldsExist;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import google.registry.model.tld.Tld;
|
||||
import java.io.PrintStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** Command to show a TLD record. */
|
||||
@Parameters(separators = " =", commandDescription = "Show TLD record(s)")
|
||||
@@ -33,11 +36,14 @@ final class GetTldCommand implements Command {
|
||||
required = true)
|
||||
private List<String> mainParameters;
|
||||
|
||||
@Inject ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public void run() throws JsonProcessingException {
|
||||
ObjectMapper mapper = getObjectMapper();
|
||||
for (String tld : assertTldsExist(mainParameters)) {
|
||||
System.out.println(mapper.writeValueAsString(Tld.get(tld)));
|
||||
public void run() throws JsonProcessingException, UnsupportedEncodingException {
|
||||
try (PrintStream printStream = new PrintStream(System.out, false, UTF_8.name())) {
|
||||
for (String tld : assertTldsExist(mainParameters)) {
|
||||
printStream.println(objectMapper.writeValueAsString(Tld.get(tld)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
81
core/src/main/java/google/registry/tools/GsonUtils.java
Normal file
81
core/src/main/java/google/registry/tools/GsonUtils.java
Normal file
@@ -0,0 +1,81 @@
|
||||
// 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.tools;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import google.registry.model.adapters.CurrencyJsonAdapter;
|
||||
import google.registry.util.CidrAddressBlock;
|
||||
import google.registry.util.CidrAddressBlock.CidrAddressBlockAdapter;
|
||||
import google.registry.util.DateTimeTypeAdapter;
|
||||
import java.io.IOException;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Utility class for methods related to GSON and necessary GSON processing. */
|
||||
public class GsonUtils {
|
||||
|
||||
/** Interface to enable GSON post-processing on a particular object after deserialization. */
|
||||
public interface GsonPostProcessable {
|
||||
void postProcess();
|
||||
}
|
||||
|
||||
/**
|
||||
* Some objects may require post-processing after deserialization from JSON.
|
||||
*
|
||||
* <p>We do this upon deserialization in order to make sure that the object matches the format
|
||||
* that we expect to be stored in the database. See {@link
|
||||
* google.registry.model.eppcommon.Address} for an example.
|
||||
*/
|
||||
public static class GsonPostProcessableTypeAdapterFactory implements TypeAdapterFactory {
|
||||
@Override
|
||||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
||||
TypeAdapter<T> originalAdapter = gson.getDelegateAdapter(this, type);
|
||||
if (!GsonPostProcessable.class.isAssignableFrom(type.getRawType())) {
|
||||
return originalAdapter;
|
||||
}
|
||||
return new TypeAdapter<T>() {
|
||||
@Override
|
||||
public void write(JsonWriter out, T value) throws IOException {
|
||||
originalAdapter.write(out, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T read(JsonReader in) throws IOException {
|
||||
T t = originalAdapter.read(in);
|
||||
((GsonPostProcessable) t).postProcess();
|
||||
return t;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static Gson provideGson() {
|
||||
return new GsonBuilder()
|
||||
.registerTypeAdapter(DateTime.class, new DateTimeTypeAdapter())
|
||||
.registerTypeAdapter(CidrAddressBlock.class, new CidrAddressBlockAdapter())
|
||||
.registerTypeAdapter(CurrencyUnit.class, new CurrencyJsonAdapter())
|
||||
.registerTypeAdapterFactory(new GsonPostProcessableTypeAdapterFactory())
|
||||
.excludeFieldsWithoutExposeAnnotation()
|
||||
.create();
|
||||
}
|
||||
|
||||
private GsonUtils() {}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ public final class RegistryTool {
|
||||
.put("convert_idn", ConvertIdnCommand.class)
|
||||
.put("count_domains", CountDomainsCommand.class)
|
||||
.put("create_anchor_tenant", CreateAnchorTenantCommand.class)
|
||||
.put("create_bulk_pricing_package", CreateBulkPricingPackageCommand.class)
|
||||
.put(
|
||||
"create_cancellations_for_billing_events",
|
||||
CreateCancellationsForBillingEventsCommand.class)
|
||||
@@ -43,7 +44,6 @@ public final class RegistryTool {
|
||||
.put("create_contact", CreateContactCommand.class)
|
||||
.put("create_domain", CreateDomainCommand.class)
|
||||
.put("create_host", CreateHostCommand.class)
|
||||
.put("create_package_promotion", CreatePackagePromotionCommand.class)
|
||||
.put("create_premium_list", CreatePremiumListCommand.class)
|
||||
.put("create_registrar", CreateRegistrarCommand.class)
|
||||
.put("create_registrar_groups", CreateRegistrarGroupsCommand.class)
|
||||
@@ -67,13 +67,13 @@ public final class RegistryTool {
|
||||
.put("generate_lordn", GenerateLordnCommand.class)
|
||||
.put("generate_zone_files", GenerateZoneFilesCommand.class)
|
||||
.put("get_allocation_token", GetAllocationTokenCommand.class)
|
||||
.put("get_bulk_pricing_package", GetBulkPricingPackageCommand.class)
|
||||
.put("get_claims_list", GetClaimsListCommand.class)
|
||||
.put("get_contact", GetContactCommand.class)
|
||||
.put("get_domain", GetDomainCommand.class)
|
||||
.put("get_history_entries", GetHistoryEntriesCommand.class)
|
||||
.put("get_host", GetHostCommand.class)
|
||||
.put("get_keyring_secret", GetKeyringSecretCommand.class)
|
||||
.put("get_package_promotion", GetPackagePromotionCommand.class)
|
||||
.put("get_premium_list", GetPremiumListCommand.class)
|
||||
.put("get_registrar", GetRegistrarCommand.class)
|
||||
.put("get_reserved_list", GetReservedListCommand.class)
|
||||
@@ -104,10 +104,10 @@ public final class RegistryTool {
|
||||
.put("unlock_domain", UnlockDomainCommand.class)
|
||||
.put("unrenew_domain", UnrenewDomainCommand.class)
|
||||
.put("update_allocation_tokens", UpdateAllocationTokensCommand.class)
|
||||
.put("update_bulk_pricing_package", UpdateBulkPricingPackageCommand.class)
|
||||
.put("update_cursors", UpdateCursorsCommand.class)
|
||||
.put("update_domain", UpdateDomainCommand.class)
|
||||
.put("update_keyring_secret", UpdateKeyringSecretCommand.class)
|
||||
.put("update_package_promotion", UpdatePackagePromotionCommand.class)
|
||||
.put("update_premium_list", UpdatePremiumListCommand.class)
|
||||
.put("update_recurrence", UpdateRecurrenceCommand.class)
|
||||
.put("update_registrar", UpdateRegistrarCommand.class)
|
||||
|
||||
@@ -30,10 +30,12 @@ import google.registry.keyring.KeyringModule;
|
||||
import google.registry.keyring.api.DummyKeyringModule;
|
||||
import google.registry.keyring.api.KeyModule;
|
||||
import google.registry.keyring.secretmanager.SecretManagerKeyringModule;
|
||||
import google.registry.model.ModelModule;
|
||||
import google.registry.persistence.PersistenceModule;
|
||||
import google.registry.persistence.PersistenceModule.NomulusToolJpaTm;
|
||||
import google.registry.persistence.PersistenceModule.ReadOnlyReplicaJpaTm;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
import google.registry.privileges.secretmanager.SecretManagerModule;
|
||||
import google.registry.rde.RdeModule;
|
||||
import google.registry.request.Modules.GsonModule;
|
||||
import google.registry.request.Modules.UrlConnectionServiceModule;
|
||||
@@ -66,13 +68,14 @@ import javax.inject.Singleton;
|
||||
GsonModule.class,
|
||||
KeyModule.class,
|
||||
KeyringModule.class,
|
||||
SecretManagerKeyringModule.class,
|
||||
LocalCredentialModule.class,
|
||||
ModelModule.class,
|
||||
PersistenceModule.class,
|
||||
RdeModule.class,
|
||||
RegistryToolDataflowModule.class,
|
||||
RequestFactoryModule.class,
|
||||
google.registry.privileges.secretmanager.SecretManagerModule.class,
|
||||
SecretManagerKeyringModule.class,
|
||||
SecretManagerModule.class,
|
||||
UrlConnectionServiceModule.class,
|
||||
UrlFetchServiceModule.class,
|
||||
UserServiceModule.class,
|
||||
@@ -111,18 +114,19 @@ interface RegistryToolComponent {
|
||||
|
||||
void inject(GenerateEscrowDepositCommand command);
|
||||
|
||||
void inject(GetBulkPricingPackageCommand command);
|
||||
|
||||
void inject(GetContactCommand command);
|
||||
|
||||
void inject(GetDomainCommand command);
|
||||
|
||||
void inject(GetHostCommand command);
|
||||
|
||||
void inject(GetPackagePromotionCommand command);
|
||||
|
||||
void inject(GetKeyringSecretCommand command);
|
||||
|
||||
void inject(GetSqlCredentialCommand command);
|
||||
|
||||
void inject(GetTldCommand command);
|
||||
|
||||
void inject(GhostrydeCommand command);
|
||||
|
||||
void inject(ListCursorsCommand command);
|
||||
|
||||
@@ -169,18 +169,18 @@ final class UpdateAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
|
||||
}
|
||||
|
||||
private AllocationToken updateToken(AllocationToken original) {
|
||||
if (endToken && original.getTokenType().equals(TokenType.PACKAGE)) {
|
||||
Long domainsInPackage =
|
||||
tm().query("SELECT COUNT(*) FROM Domain WHERE currentPackageToken = :token", Long.class)
|
||||
if (endToken && original.getTokenType().equals(TokenType.BULK_PRICING)) {
|
||||
Long domainsInBulkPackage =
|
||||
tm().query("SELECT COUNT(*) FROM Domain WHERE currentBulkToken = :token", Long.class)
|
||||
.setParameter("token", original.createVKey())
|
||||
.getSingleResult();
|
||||
|
||||
checkArgument(
|
||||
domainsInPackage == 0,
|
||||
"Package token %s can not end its promotion because it still has %s domains in the"
|
||||
+ " package",
|
||||
domainsInBulkPackage == 0,
|
||||
"Bulk token %s can not end its promotion because it still has %s domains in the"
|
||||
+ " promotion",
|
||||
original.getToken(),
|
||||
domainsInPackage);
|
||||
domainsInBulkPackage);
|
||||
}
|
||||
AllocationToken.Builder builder = original.asBuilder();
|
||||
Optional.ofNullable(allowedClientIds)
|
||||
|
||||
@@ -17,12 +17,12 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import google.registry.model.domain.token.PackagePromotion;
|
||||
import google.registry.model.domain.token.BulkPricingPackage;
|
||||
import java.util.Optional;
|
||||
|
||||
/** Command to update a PackagePromotion */
|
||||
@Parameters(separators = " =", commandDescription = "Update package promotion object(s)")
|
||||
public final class UpdatePackagePromotionCommand extends CreateOrUpdatePackagePromotionCommand {
|
||||
/** Command to update a {@link BulkPricingPackage} */
|
||||
@Parameters(separators = " =", commandDescription = "Update bulk pricing package object(s)")
|
||||
public final class UpdateBulkPricingPackageCommand extends CreateOrUpdateBulkPricingPackageCommand {
|
||||
|
||||
@Parameter(
|
||||
names = "--clear_last_notification_sent",
|
||||
@@ -32,10 +32,14 @@ public final class UpdatePackagePromotionCommand extends CreateOrUpdatePackagePr
|
||||
boolean clearLastNotificationSent;
|
||||
|
||||
@Override
|
||||
PackagePromotion getOldPackagePromotion(String token) {
|
||||
Optional<PackagePromotion> oldPackage = PackagePromotion.loadByTokenString(token);
|
||||
checkArgument(oldPackage.isPresent(), "PackagePromotion with token %s does not exist", token);
|
||||
return oldPackage.get();
|
||||
BulkPricingPackage getOldBulkPricingPackage(String token) {
|
||||
Optional<BulkPricingPackage> oldBulkPricingPackage =
|
||||
BulkPricingPackage.loadByTokenString(token);
|
||||
checkArgument(
|
||||
oldBulkPricingPackage.isPresent(),
|
||||
"BulkPricingPackage with token %s does not exist",
|
||||
token);
|
||||
return oldBulkPricingPackage.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -20,8 +20,8 @@ import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.groups.GmailClient;
|
||||
import google.registry.util.EmailMessage;
|
||||
import google.registry.util.SendEmailService;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
@@ -38,7 +38,7 @@ public class SendEmailUtils {
|
||||
|
||||
private final InternetAddress gSuiteOutgoingEmailAddress;
|
||||
private final String gSuiteOutgoingEmailDisplayName;
|
||||
private final SendEmailService emailService;
|
||||
private final GmailClient gmailClient;
|
||||
private final ImmutableList<String> registrarChangesNotificationEmailAddresses;
|
||||
|
||||
@Inject
|
||||
@@ -47,10 +47,10 @@ public class SendEmailUtils {
|
||||
@Config("gSuiteOutgoingEmailDisplayName") String gSuiteOutgoingEmailDisplayName,
|
||||
@Config("registrarChangesNotificationEmailAddresses")
|
||||
ImmutableList<String> registrarChangesNotificationEmailAddresses,
|
||||
SendEmailService emailService) {
|
||||
GmailClient gmailClient) {
|
||||
this.gSuiteOutgoingEmailAddress = gSuiteOutgoingEmailAddress;
|
||||
this.gSuiteOutgoingEmailDisplayName = gSuiteOutgoingEmailDisplayName;
|
||||
this.emailService = emailService;
|
||||
this.gmailClient = gmailClient;
|
||||
this.registrarChangesNotificationEmailAddresses = registrarChangesNotificationEmailAddresses;
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ public class SendEmailUtils {
|
||||
"Could not send email to %s with subject '%s'.", bcc, subject);
|
||||
}
|
||||
}
|
||||
emailService.sendEmail(emailMessage.build());
|
||||
gmailClient.sendEmail(emailMessage.build());
|
||||
return true;
|
||||
} catch (Throwable t) {
|
||||
logger.atSevere().withCause(t).log(
|
||||
|
||||
@@ -14,7 +14,11 @@
|
||||
|
||||
package google.registry.ui.server.console;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
|
||||
import com.google.api.client.http.HttpStatusCodes;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -23,46 +27,155 @@ import com.google.gson.Gson;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.Registrar.State;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
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.ui.server.registrar.JsonGetAction;
|
||||
import google.registry.util.StringGenerator;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@Action(
|
||||
service = Action.Service.DEFAULT,
|
||||
path = RegistrarsAction.PATH,
|
||||
method = {GET},
|
||||
method = {GET, POST},
|
||||
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
|
||||
public class RegistrarsAction implements JsonGetAction {
|
||||
private static final int PASSWORD_LENGTH = 16;
|
||||
private static final int PASSCODE_LENGTH = 5;
|
||||
static final String PATH = "/console-api/registrars";
|
||||
|
||||
private final AuthResult authResult;
|
||||
private final Response response;
|
||||
private final Gson gson;
|
||||
private final HttpServletRequest req;
|
||||
private Optional<Registrar> registrar;
|
||||
private StringGenerator passwordGenerator;
|
||||
private StringGenerator passcodeGenerator;
|
||||
|
||||
@Inject
|
||||
public RegistrarsAction(AuthResult authResult, Response response, Gson gson) {
|
||||
public RegistrarsAction(
|
||||
HttpServletRequest req,
|
||||
AuthResult authResult,
|
||||
Response response,
|
||||
Gson gson,
|
||||
@Parameter("registrar") Optional<Registrar> registrar,
|
||||
@Named("base58StringGenerator") StringGenerator passwordGenerator,
|
||||
@Named("digitOnlyStringGenerator") StringGenerator passcodeGenerator) {
|
||||
this.authResult = authResult;
|
||||
this.response = response;
|
||||
this.gson = gson;
|
||||
this.registrar = registrar;
|
||||
this.req = req;
|
||||
this.passcodeGenerator = passcodeGenerator;
|
||||
this.passwordGenerator = passwordGenerator;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
User user = authResult.userAuthInfo().get().consoleUser().get();
|
||||
if (req.getMethod().equals(GET.toString())) {
|
||||
getHandler(user);
|
||||
} else {
|
||||
postHandler(user);
|
||||
}
|
||||
}
|
||||
|
||||
private void getHandler(User user) {
|
||||
if (!user.getUserRoles().hasGlobalPermission(ConsolePermission.VIEW_REGISTRARS)) {
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
ImmutableList<String> registrarIds =
|
||||
ImmutableList<Registrar> registrars =
|
||||
Streams.stream(Registrar.loadAllCached())
|
||||
.filter(r -> r.getType() == Registrar.Type.REAL)
|
||||
.map(Registrar::getRegistrarId)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
|
||||
response.setPayload(gson.toJson(registrarIds));
|
||||
response.setPayload(gson.toJson(registrars));
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_OK);
|
||||
}
|
||||
|
||||
private void postHandler(User user) {
|
||||
if (!user.getUserRoles().isAdmin()) {
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!registrar.isPresent()) {
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_BAD_REQUEST);
|
||||
response.setPayload(gson.toJson("'registrar' parameter is not present"));
|
||||
return;
|
||||
}
|
||||
|
||||
Registrar registrarParam = registrar.get();
|
||||
String errorMsg = "Missing value for %s";
|
||||
try {
|
||||
checkArgument(!isNullOrEmpty(registrarParam.getRegistrarId()), errorMsg, "registrarId");
|
||||
checkArgument(!isNullOrEmpty(registrarParam.getRegistrarName()), errorMsg, "name");
|
||||
checkArgument(!registrarParam.getBillingAccountMap().isEmpty(), errorMsg, "billingAccount");
|
||||
checkArgument(registrarParam.getIanaIdentifier() != null, String.format(errorMsg, "ianaId"));
|
||||
checkArgument(
|
||||
!isNullOrEmpty(registrarParam.getIcannReferralEmail()), errorMsg, "referralEmail");
|
||||
checkArgument(!isNullOrEmpty(registrarParam.getDriveFolderId()), errorMsg, "driveId");
|
||||
checkArgument(!isNullOrEmpty(registrarParam.getEmailAddress()), errorMsg, "consoleUserEmail");
|
||||
checkArgument(
|
||||
registrarParam.getLocalizedAddress() != null
|
||||
&& !isNullOrEmpty(registrarParam.getLocalizedAddress().getState())
|
||||
&& !isNullOrEmpty(registrarParam.getLocalizedAddress().getCity())
|
||||
&& !isNullOrEmpty(registrarParam.getLocalizedAddress().getZip())
|
||||
&& !isNullOrEmpty(registrarParam.getLocalizedAddress().getCountryCode())
|
||||
&& !registrarParam.getLocalizedAddress().getStreet().isEmpty(),
|
||||
errorMsg,
|
||||
"address");
|
||||
|
||||
String password = passwordGenerator.createString(PASSWORD_LENGTH);
|
||||
String phonePasscode = passcodeGenerator.createString(PASSCODE_LENGTH);
|
||||
|
||||
Registrar registrar =
|
||||
new Registrar.Builder()
|
||||
.setRegistrarId(registrarParam.getRegistrarId())
|
||||
.setRegistrarName(registrarParam.getRegistrarName())
|
||||
.setBillingAccountMap(registrarParam.getBillingAccountMap())
|
||||
.setIanaIdentifier(Long.valueOf(registrarParam.getIanaIdentifier()))
|
||||
.setIcannReferralEmail(registrarParam.getIcannReferralEmail())
|
||||
.setEmailAddress(registrarParam.getIcannReferralEmail())
|
||||
.setDriveFolderId(registrarParam.getDriveFolderId())
|
||||
.setType(Registrar.Type.REAL)
|
||||
.setPassword(password)
|
||||
.setPhonePasscode(phonePasscode)
|
||||
.setState(State.PENDING)
|
||||
.setLocalizedAddress(registrarParam.getLocalizedAddress())
|
||||
.build();
|
||||
|
||||
RegistrarPoc contact =
|
||||
new RegistrarPoc.Builder()
|
||||
.setRegistrar(registrar)
|
||||
.setName(registrarParam.getEmailAddress())
|
||||
.setEmailAddress(registrarParam.getEmailAddress())
|
||||
.setLoginEmailAddress(registrarParam.getEmailAddress())
|
||||
.build();
|
||||
|
||||
tm().transact(
|
||||
() -> {
|
||||
checkArgument(
|
||||
!Registrar.loadByRegistrarId(registrar.getRegistrarId()).isPresent(),
|
||||
"Registrar with registrarId %s already exists",
|
||||
registrar.getRegistrarId());
|
||||
tm().putAll(registrar, contact);
|
||||
});
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_BAD_REQUEST);
|
||||
response.setPayload(gson.toJson(e.getMessage()));
|
||||
} catch (Throwable e) {
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_SERVER_ERROR);
|
||||
response.setPayload(gson.toJson(e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ public class ContactAction implements JsonGetAction {
|
||||
oldContacts,
|
||||
Collections.singletonMap(
|
||||
"contacts",
|
||||
contacts.get().stream().map(c -> c.toJsonMap()).collect(toImmutableList())));
|
||||
contacts.get().stream().map(RegistrarPoc::toJsonMap).collect(toImmutableList())));
|
||||
try {
|
||||
RegistrarSettingsAction.checkContactRequirements(oldContacts, updatedContacts);
|
||||
} catch (FormException e) {
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package google.registry.ui.server.console.settings;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
|
||||
import avro.shaded.com.google.common.collect.ImmutableList;
|
||||
@@ -36,28 +35,25 @@ import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAcce
|
||||
import google.registry.ui.server.registrar.JsonGetAction;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@Action(
|
||||
service = Action.Service.DEFAULT,
|
||||
path = SecurityAction.PATH,
|
||||
method = {GET, POST},
|
||||
method = {POST},
|
||||
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
|
||||
public class SecurityAction implements JsonGetAction {
|
||||
|
||||
static final String PATH = "/console-api/settings/security";
|
||||
private final HttpServletRequest req;
|
||||
private final AuthResult authResult;
|
||||
private final Response response;
|
||||
private final Gson gson;
|
||||
private final String registrarId;
|
||||
private AuthenticatedRegistrarAccessor registrarAccessor;
|
||||
private Optional<Registrar> registrar;
|
||||
private CertificateChecker certificateChecker;
|
||||
private final AuthenticatedRegistrarAccessor registrarAccessor;
|
||||
private final Optional<Registrar> registrar;
|
||||
private final CertificateChecker certificateChecker;
|
||||
|
||||
@Inject
|
||||
public SecurityAction(
|
||||
HttpServletRequest req,
|
||||
AuthResult authResult,
|
||||
Response response,
|
||||
Gson gson,
|
||||
@@ -65,7 +61,6 @@ public class SecurityAction implements JsonGetAction {
|
||||
AuthenticatedRegistrarAccessor registrarAccessor,
|
||||
@Parameter("registrarId") String registrarId,
|
||||
@Parameter("registrar") Optional<Registrar> registrar) {
|
||||
this.req = req;
|
||||
this.authResult = authResult;
|
||||
this.response = response;
|
||||
this.gson = gson;
|
||||
@@ -77,25 +72,6 @@ public class SecurityAction implements JsonGetAction {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (req.getMethod().equals(GET.toString())) {
|
||||
getHandler();
|
||||
} else {
|
||||
postHandler();
|
||||
}
|
||||
}
|
||||
|
||||
private void getHandler() {
|
||||
try {
|
||||
Registrar registrar = registrarAccessor.getRegistrar(registrarId);
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_OK);
|
||||
response.setPayload(gson.toJson(registrar));
|
||||
} catch (RegistrarAccessDeniedException e) {
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_FORBIDDEN);
|
||||
response.setPayload(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void postHandler() {
|
||||
User user = authResult.userAuthInfo().get().consoleUser().get();
|
||||
if (!user.getUserRoles().hasPermission(registrarId, ConsolePermission.EDIT_REGISTRAR_DETAILS)) {
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_FORBIDDEN);
|
||||
@@ -153,17 +129,15 @@ public class SecurityAction implements JsonGetAction {
|
||||
registrarParameter
|
||||
.getClientCertificate()
|
||||
.ifPresent(
|
||||
newClientCert -> {
|
||||
updatedRegistrar.setClientCertificate(newClientCert, tm().getTransactionTime());
|
||||
});
|
||||
newClientCert ->
|
||||
updatedRegistrar.setClientCertificate(newClientCert, tm().getTransactionTime()));
|
||||
|
||||
registrarParameter
|
||||
.getFailoverClientCertificate()
|
||||
.ifPresent(
|
||||
failoverCert -> {
|
||||
updatedRegistrar.setFailoverClientCertificate(
|
||||
failoverCert, tm().getTransactionTime());
|
||||
});
|
||||
failoverCert ->
|
||||
updatedRegistrar.setFailoverClientCertificate(
|
||||
failoverCert, tm().getTransactionTime()));
|
||||
|
||||
tm().put(updatedRegistrar.build());
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_OK);
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
// 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.settings;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
|
||||
import com.google.api.client.http.HttpStatusCodes;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.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.AuthenticatedRegistrarAccessor;
|
||||
import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException;
|
||||
import google.registry.ui.server.registrar.JsonGetAction;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Console action for editing fields on a registrar that are visible in WHOIS/RDAP.
|
||||
*
|
||||
* <p>This doesn't cover many of the registrar fields but rather only those that are visible in
|
||||
* WHOIS/RDAP and don't have any other obvious means of edit.
|
||||
*/
|
||||
@Action(
|
||||
service = Action.Service.DEFAULT,
|
||||
path = WhoisRegistrarFieldsAction.PATH,
|
||||
method = {POST},
|
||||
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
|
||||
public class WhoisRegistrarFieldsAction implements JsonGetAction {
|
||||
|
||||
static final String PATH = "/console-api/settings/whois-fields";
|
||||
private final AuthResult authResult;
|
||||
private final Response response;
|
||||
private final Gson gson;
|
||||
private AuthenticatedRegistrarAccessor registrarAccessor;
|
||||
private Optional<Registrar> registrar;
|
||||
|
||||
@Inject
|
||||
public WhoisRegistrarFieldsAction(
|
||||
AuthResult authResult,
|
||||
Response response,
|
||||
Gson gson,
|
||||
AuthenticatedRegistrarAccessor registrarAccessor,
|
||||
@Parameter("registrar") Optional<Registrar> registrar) {
|
||||
this.authResult = authResult;
|
||||
this.response = response;
|
||||
this.gson = gson;
|
||||
this.registrarAccessor = registrarAccessor;
|
||||
this.registrar = registrar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!registrar.isPresent()) {
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_BAD_REQUEST);
|
||||
response.setPayload(gson.toJson("'registrar' parameter is not present"));
|
||||
return;
|
||||
}
|
||||
|
||||
User user = authResult.userAuthInfo().get().consoleUser().get();
|
||||
if (!user.getUserRoles()
|
||||
.hasPermission(
|
||||
registrar.get().getRegistrarId(), ConsolePermission.EDIT_REGISTRAR_DETAILS)) {
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
|
||||
tm().transact(() -> loadAndModifyRegistrar(registrar.get()));
|
||||
}
|
||||
|
||||
private void loadAndModifyRegistrar(Registrar providedRegistrar) {
|
||||
Registrar savedRegistrar;
|
||||
try {
|
||||
// reload to make sure the object has all the correct fields
|
||||
savedRegistrar = registrarAccessor.getRegistrar(providedRegistrar.getRegistrarId());
|
||||
} catch (RegistrarAccessDeniedException e) {
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_FORBIDDEN);
|
||||
response.setPayload(e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
Registrar.Builder newRegistrar = savedRegistrar.asBuilder();
|
||||
newRegistrar.setWhoisServer(providedRegistrar.getWhoisServer());
|
||||
newRegistrar.setUrl(providedRegistrar.getUrl());
|
||||
newRegistrar.setLocalizedAddress(providedRegistrar.getLocalizedAddress());
|
||||
tm().put(newRegistrar.build());
|
||||
response.setStatus(HttpStatusCodes.STATUS_CODE_OK);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import static google.registry.request.RequestParameters.extractRequiredParameter
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonElement;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
@@ -172,15 +172,8 @@ public final class RegistrarConsoleModule {
|
||||
@Provides
|
||||
@Parameter("contacts")
|
||||
public static Optional<ImmutableSet<RegistrarPoc>> provideContacts(
|
||||
Gson gson, @OptionalJsonPayload Optional<JsonObject> payload) {
|
||||
|
||||
if (payload.isPresent() && payload.get().has("contacts")) {
|
||||
return Optional.of(
|
||||
ImmutableSet.copyOf(
|
||||
gson.fromJson(payload.get().get("contacts").getAsJsonArray(), RegistrarPoc[].class)));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
Gson gson, @OptionalJsonPayload Optional<JsonElement> payload) {
|
||||
return payload.map(s -> ImmutableSet.copyOf(gson.fromJson(s, RegistrarPoc[].class)));
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -192,11 +185,7 @@ public final class RegistrarConsoleModule {
|
||||
@Provides
|
||||
@Parameter("registrar")
|
||||
public static Optional<Registrar> provideRegistrar(
|
||||
Gson gson, @OptionalJsonPayload Optional<JsonObject> payload) {
|
||||
if (payload.isPresent() && payload.get().has("registrar")) {
|
||||
return Optional.of(gson.fromJson(payload.get().get("registrar"), Registrar.class));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
Gson gson, @OptionalJsonPayload Optional<JsonElement> payload) {
|
||||
return payload.map(s -> gson.fromJson(s, Registrar.class));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.google.common.flogger.FluentLogger;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.flows.domain.DomainFlowUtils;
|
||||
import google.registry.groups.GmailClient;
|
||||
import google.registry.model.domain.RegistryLock;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
@@ -46,7 +47,6 @@ import google.registry.request.auth.UserAuthInfo;
|
||||
import google.registry.security.JsonResponseHelper;
|
||||
import google.registry.tools.DomainLockUtils;
|
||||
import google.registry.util.EmailMessage;
|
||||
import google.registry.util.SendEmailService;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -83,7 +83,7 @@ public class RegistryLockPostAction implements Runnable, JsonActionRunner.JsonAc
|
||||
private final JsonActionRunner jsonActionRunner;
|
||||
private final AuthResult authResult;
|
||||
private final AuthenticatedRegistrarAccessor registrarAccessor;
|
||||
private final SendEmailService sendEmailService;
|
||||
private final GmailClient gmailClient;
|
||||
private final DomainLockUtils domainLockUtils;
|
||||
private final InternetAddress gSuiteOutgoingEmailAddress;
|
||||
|
||||
@@ -93,14 +93,14 @@ public class RegistryLockPostAction implements Runnable, JsonActionRunner.JsonAc
|
||||
JsonActionRunner jsonActionRunner,
|
||||
AuthResult authResult,
|
||||
AuthenticatedRegistrarAccessor registrarAccessor,
|
||||
SendEmailService sendEmailService,
|
||||
GmailClient gmailClient,
|
||||
DomainLockUtils domainLockUtils,
|
||||
@Config("gSuiteOutgoingEmailAddress") InternetAddress gSuiteOutgoingEmailAddress) {
|
||||
this.req = req;
|
||||
this.jsonActionRunner = jsonActionRunner;
|
||||
this.authResult = authResult;
|
||||
this.registrarAccessor = registrarAccessor;
|
||||
this.sendEmailService = sendEmailService;
|
||||
this.gmailClient = gmailClient;
|
||||
this.domainLockUtils = domainLockUtils;
|
||||
this.gSuiteOutgoingEmailAddress = gSuiteOutgoingEmailAddress;
|
||||
}
|
||||
@@ -168,7 +168,7 @@ public class RegistryLockPostAction implements Runnable, JsonActionRunner.JsonAc
|
||||
ImmutableList<InternetAddress> recipients =
|
||||
ImmutableList.of(new InternetAddress(userEmail, true));
|
||||
String action = isLock ? "lock" : "unlock";
|
||||
sendEmailService.sendEmail(
|
||||
gmailClient.sendEmail(
|
||||
EmailMessage.newBuilder()
|
||||
.setBody(body)
|
||||
.setSubject(String.format("Registry %s verification", action))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user