diff --git a/core/src/main/java/google/registry/flows/domain/DomainCheckFlow.java b/core/src/main/java/google/registry/flows/domain/DomainCheckFlow.java index 64c4016a9..b1df32887 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainCheckFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainCheckFlow.java @@ -20,6 +20,7 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap; import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn; import static google.registry.flows.ResourceFlowUtils.verifyTargetIdCount; import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld; +import static google.registry.flows.domain.DomainFlowUtils.checkHasBillingAccount; import static google.registry.flows.domain.DomainFlowUtils.getReservationTypes; import static google.registry.flows.domain.DomainFlowUtils.handleFeeRequest; import static google.registry.flows.domain.DomainFlowUtils.isAnchorTenant; @@ -100,6 +101,7 @@ import org.joda.time.DateTime; * @error {@link DomainFlowUtils.InvalidIdnDomainLabelException} * @error {@link DomainFlowUtils.InvalidPunycodeException} * @error {@link DomainFlowUtils.LeadingDashException} + * @error {@link DomainFlowUtils.MissingBillingAccountMapException} * @error {@link DomainFlowUtils.NotAuthorizedForTldException} * @error {@link DomainFlowUtils.RestoresAreAlwaysForOneYearException} * @error {@link DomainFlowUtils.TldDoesNotExistException} @@ -153,6 +155,7 @@ public final class DomainCheckFlow implements Flow { boolean tldFirstTimeSeen = seenTlds.add(tld); if (tldFirstTimeSeen && !isSuperuser) { checkAllowedAccessToTld(registrarId, tld); + checkHasBillingAccount(registrarId, tld); verifyNotInPredelegation(Registry.get(tld), now); } } diff --git a/core/src/main/java/google/registry/flows/domain/DomainClaimsCheckFlow.java b/core/src/main/java/google/registry/flows/domain/DomainClaimsCheckFlow.java index a64a6f000..814125ce2 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainClaimsCheckFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainClaimsCheckFlow.java @@ -17,6 +17,7 @@ package google.registry.flows.domain; import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn; import static google.registry.flows.ResourceFlowUtils.verifyTargetIdCount; import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld; +import static google.registry.flows.domain.DomainFlowUtils.checkHasBillingAccount; import static google.registry.flows.domain.DomainFlowUtils.validateDomainName; import static google.registry.flows.domain.DomainFlowUtils.validateDomainNameWithIdnTables; import static google.registry.flows.domain.DomainFlowUtils.verifyClaimsPeriodNotEnded; @@ -60,6 +61,7 @@ import org.joda.time.DateTime; * @error {@link google.registry.flows.FlowUtils.NotLoggedInException} * @error {@link DomainFlowUtils.BadCommandForRegistryPhaseException} * @error {@link DomainFlowUtils.ClaimsPeriodEndedException} + * @error {@link DomainFlowUtils.MissingBillingAccountMapException} * @error {@link DomainFlowUtils.NotAuthorizedForTldException} * @error {@link DomainFlowUtils.TldDoesNotExistException} * @error {@link DomainClaimsCheckNotAllowedWithAllocationTokens} @@ -99,6 +101,7 @@ public final class DomainClaimsCheckFlow implements Flow { if (seenTlds.add(tld)) { if (!isSuperuser) { checkAllowedAccessToTld(registrarId, tld); + checkHasBillingAccount(registrarId, tld); Registry registry = Registry.get(tld); DateTime now = clock.nowUtc(); verifyNotInPredelegation(registry, now); diff --git a/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java b/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java index 77605a4bf..aeeb096a8 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java @@ -20,6 +20,7 @@ import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn; import static google.registry.flows.ResourceFlowUtils.verifyResourceDoesNotExist; import static google.registry.flows.domain.DomainFlowUtils.COLLISION_MESSAGE; import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld; +import static google.registry.flows.domain.DomainFlowUtils.checkHasBillingAccount; import static google.registry.flows.domain.DomainFlowUtils.cloneAndLinkReferences; import static google.registry.flows.domain.DomainFlowUtils.createFeeCreateResponse; import static google.registry.flows.domain.DomainFlowUtils.getReservationTypes; @@ -180,6 +181,7 @@ import org.joda.time.Duration; * @error {@link DomainFlowUtils.MalformedTcnIdException} * @error {@link DomainFlowUtils.MaxSigLifeNotSupportedException} * @error {@link DomainFlowUtils.MissingAdminContactException} + * @error {@link DomainFlowUtils.MissingBillingAccountMapException} * @error {@link DomainFlowUtils.MissingClaimsNoticeException} * @error {@link DomainFlowUtils.MissingContactTypeException} * @error {@link DomainFlowUtils.MissingRegistrantException} @@ -265,6 +267,7 @@ public final class DomainCreateFlow implements TransactionalFlow { // registering premium domains. if (!isSuperuser) { checkAllowedAccessToTld(registrarId, registry.getTldStr()); + checkHasBillingAccount(registrarId, registry.getTldStr()); boolean isValidReservedCreate = isValidReservedCreate(domainName, allocationToken); verifyIsGaOrIsSpecialCase(tldState, isAnchorTenant, isValidReservedCreate, hasSignedMarks); if (launchCreate.isPresent()) { diff --git a/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java b/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java index 5857d398e..c6ddf68d5 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java +++ b/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java @@ -293,6 +293,17 @@ public class DomainFlowUtils { } } + /** Check if the registrar has the correct billing account map configured. */ + public static void checkHasBillingAccount(String registrarId, String tld) throws EppException { + CurrencyUnit currency = Registry.get(tld).getCurrency(); + if (!Registrar.loadByRegistrarIdCached(registrarId) + .get() + .getBillingAccountMap() + .containsKey(currency)) { + throw new DomainFlowUtils.MissingBillingAccountMapException(currency); + } + } + /** Check that the DS data that will be set on a domain is valid. */ static void validateDsData(Set dsData) throws EppException { if (dsData != null) { @@ -1501,6 +1512,13 @@ public class DomainFlowUtils { } } + /** Registrar is missing the billing account map for this currency type. */ + public static class MissingBillingAccountMapException extends AuthorizationErrorException { + public MissingBillingAccountMapException(CurrencyUnit currency) { + super("Registrar is not fully onboarded for TLDs that bill in " + currency); + } + } + /** Registrant is not allow-listed for this TLD. */ public static class RegistrantNotAllowedException extends StatusProhibitsOperationException { public RegistrantNotAllowedException(String contactId) { diff --git a/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java b/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java index 18af352f3..2d0e6bfd7 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java @@ -22,6 +22,7 @@ import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo; import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership; import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld; +import static google.registry.flows.domain.DomainFlowUtils.checkHasBillingAccount; import static google.registry.flows.domain.DomainFlowUtils.newAutorenewBillingEvent; import static google.registry.flows.domain.DomainFlowUtils.newAutorenewPollMessage; import static google.registry.flows.domain.DomainFlowUtils.updateAutorenewRecurrenceEndTime; @@ -106,6 +107,7 @@ import org.joda.time.Duration; * @error {@link DomainFlowUtils.ExceedsMaxRegistrationYearsException} * @error {@link DomainFlowUtils.FeesMismatchException} * @error {@link DomainFlowUtils.FeesRequiredForPremiumNameException} + * @error {@link DomainFlowUtils.MissingBillingAccountMapException} * @error {@link DomainFlowUtils.NotAuthorizedForTldException} * @error {@link DomainFlowUtils.RegistrarMustBeActiveForThisOperationException} * @error {@link DomainFlowUtils.UnsupportedFeeAttributeException} @@ -263,6 +265,7 @@ public final class DomainRenewFlow implements TransactionalFlow { if (!isSuperuser) { verifyResourceOwnership(registrarId, existingDomain); checkAllowedAccessToTld(registrarId, existingDomain.getTld()); + checkHasBillingAccount(registrarId, existingDomain.getTld()); } verifyUnitIsYears(command.getPeriod()); // If the date they specify doesn't match the expiration, fail. (This is an idempotence check). diff --git a/core/src/main/java/google/registry/flows/domain/DomainRestoreRequestFlow.java b/core/src/main/java/google/registry/flows/domain/DomainRestoreRequestFlow.java index e548cda8f..fac35b7c4 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainRestoreRequestFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainRestoreRequestFlow.java @@ -20,6 +20,7 @@ import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence; import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo; import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership; import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld; +import static google.registry.flows.domain.DomainFlowUtils.checkHasBillingAccount; import static google.registry.flows.domain.DomainFlowUtils.newAutorenewBillingEvent; import static google.registry.flows.domain.DomainFlowUtils.newAutorenewPollMessage; import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge; @@ -104,6 +105,7 @@ import org.joda.time.DateTime; * @error {@link DomainFlowUtils.DomainReservedException} * @error {@link DomainFlowUtils.FeesMismatchException} * @error {@link DomainFlowUtils.FeesRequiredForPremiumNameException} + * @error {@link DomainFlowUtils.MissingBillingAccountMapException} * @error {@link DomainFlowUtils.NotAuthorizedForTldException} * @error {@link DomainFlowUtils.PremiumNameBlockedException} * @error {@link DomainFlowUtils.RegistrarMustBeActiveForThisOperationException} @@ -216,6 +218,7 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow { verifyNotReserved(InternetDomainName.from(targetId), false); verifyPremiumNameIsNotBlocked(targetId, now, registrarId); checkAllowedAccessToTld(registrarId, existingDomain.getTld()); + checkHasBillingAccount(registrarId, existingDomain.getTld()); } // No other changes can be specified on a restore request. if (!command.noChangesPresent()) { diff --git a/core/src/main/java/google/registry/flows/domain/DomainTransferRequestFlow.java b/core/src/main/java/google/registry/flows/domain/DomainTransferRequestFlow.java index 1f93dc87c..913e32515 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainTransferRequestFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainTransferRequestFlow.java @@ -22,6 +22,7 @@ import static google.registry.flows.ResourceFlowUtils.verifyAuthInfo; import static google.registry.flows.ResourceFlowUtils.verifyAuthInfoPresentForResourceTransfer; import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses; import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld; +import static google.registry.flows.domain.DomainFlowUtils.checkHasBillingAccount; import static google.registry.flows.domain.DomainFlowUtils.updateAutorenewRecurrenceEndTime; import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge; import static google.registry.flows.domain.DomainFlowUtils.verifyPremiumNameIsNotBlocked; @@ -111,6 +112,7 @@ import org.joda.time.DateTime; * @error {@link DomainFlowUtils.CurrencyValueScaleException} * @error {@link DomainFlowUtils.FeesMismatchException} * @error {@link DomainFlowUtils.FeesRequiredForPremiumNameException} + * @error {@link DomainFlowUtils.MissingBillingAccountMapException} * @error {@link DomainFlowUtils.NotAuthorizedForTldException} * @error {@link DomainFlowUtils.PremiumNameBlockedException} * @error {@link DomainFlowUtils.RegistrarMustBeActiveForThisOperationException} @@ -273,6 +275,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow { verifyTransferPeriod(period, superuserExtension); if (!isSuperuser) { checkAllowedAccessToTld(gainingClientId, existingDomain.getTld()); + checkHasBillingAccount(gainingClientId, existingDomain.getTld()); verifyPremiumNameIsNotBlocked(targetId, now, gainingClientId); } } diff --git a/core/src/test/java/google/registry/flows/domain/DomainCheckFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainCheckFlowTest.java index dd1b8da45..438086bd4 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainCheckFlowTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainCheckFlowTest.java @@ -30,6 +30,7 @@ import static google.registry.testing.DatabaseHelper.persistReservedList; import static google.registry.testing.DatabaseHelper.persistResource; import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions; import static google.registry.util.DateTimeUtils.START_OF_TIME; +import static org.joda.money.CurrencyUnit.JPY; import static org.joda.money.CurrencyUnit.USD; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -57,6 +58,7 @@ import google.registry.flows.domain.DomainFlowUtils.FeeChecksDontSupportPhasesEx import google.registry.flows.domain.DomainFlowUtils.InvalidIdnDomainLabelException; import google.registry.flows.domain.DomainFlowUtils.InvalidPunycodeException; import google.registry.flows.domain.DomainFlowUtils.LeadingDashException; +import google.registry.flows.domain.DomainFlowUtils.MissingBillingAccountMapException; import google.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException; import google.registry.flows.domain.DomainFlowUtils.RestoresAreAlwaysForOneYearException; import google.registry.flows.domain.DomainFlowUtils.TldDoesNotExistException; @@ -600,6 +602,24 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase + doSuccessfulTest( + "domain_transfer_request.xml", "domain_transfer_request_response.xml")); + assertAboutEppExceptions().that(thrown).marshalsToXml(); + } + @TestOfyAndSql void testSuccess_superuserNotAuthorizedForTld() throws Exception { setupDomain("example", "tld"); diff --git a/docs/flows.md b/docs/flows.md index 2d8491f8d..d05f2fd46 100644 --- a/docs/flows.md +++ b/docs/flows.md @@ -269,6 +269,7 @@ information. * Domain name must have exactly one part above the TLD. * Domain name must not equal an existing multi-part TLD. * 2201 + * Registrar is missing the billing account map for this currency type. * Registrar is not authorized to access this TLD. * 2306 * Too many resource checks requested in one check command. @@ -304,6 +305,7 @@ An EPP flow that checks whether domain labels are trademarked. * 2004 * Domain name is under tld which doesn't exist. * 2201 + * Registrar is missing the billing account map for this currency type. * Registrar is not authorized to access this TLD. * 2304 * The claims period for this TLD has ended. @@ -361,6 +363,7 @@ An EPP flow that creates a new domain resource. * The allocation token is invalid. * Only a tool can pass a metadata extension. * Registrar is not authorized to access this TLD. + * Registrar is missing the billing account map for this currency type. * Registrar must be active in order to perform this operation. * 2302 * Resource with this id already exists. @@ -501,6 +504,7 @@ comes in at the exact millisecond that the domain would have expired. server policy. * 2201 * The specified resource belongs to another client. + * Registrar is missing the billing account map for this currency type. * Registrar is not authorized to access this TLD. * Registrar must be active in order to perform this operation. * 2303 @@ -561,6 +565,7 @@ regardless of what the original expiration time was. * Specified extension is not implemented. * 2201 * The specified resource belongs to another client. + * Registrar is missing the billing account map for this currency type. * Registrar is not authorized to access this TLD. * Registrar must be active in order to perform this operation. * 2303 @@ -736,6 +741,7 @@ new ones with the correct approval time). server policy. * 2201 * Authorization info is required to request a transfer. + * Registrar is missing the billing account map for this currency type. * Registrar is not authorized to access this TLD. * Registrar must be active in order to perform this operation. * 2202