diff --git a/java/google/registry/env/common/default/WEB-INF/web.xml b/java/google/registry/env/common/default/WEB-INF/web.xml index dc37a1e7f..de631d77a 100644 --- a/java/google/registry/env/common/default/WEB-INF/web.xml +++ b/java/google/registry/env/common/default/WEB-INF/web.xml @@ -49,6 +49,18 @@ /registrar-settings + + + frontend-servlet + /registrar-premium-price-ack + + frontend-servlet diff --git a/java/google/registry/model/registrar/Registrar.java b/java/google/registry/model/registrar/Registrar.java index 279ab2c18..af49e128e 100644 --- a/java/google/registry/model/registrar/Registrar.java +++ b/java/google/registry/model/registrar/Registrar.java @@ -615,6 +615,7 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable .put("emailAddress", emailAddress) .put("whoisServer", getWhoisServer()) .put("blockPremiumNames", blockPremiumNames) + .put("premiumPriceAckRequired", premiumPriceAckRequired) .put("url", url) .put("referralUrl", getReferralUrl()) .put("icannReferralEmail", getIcannReferralEmail()) diff --git a/java/google/registry/module/frontend/FrontendRequestComponent.java b/java/google/registry/module/frontend/FrontendRequestComponent.java index bd974e40b..f6ff6ad22 100644 --- a/java/google/registry/module/frontend/FrontendRequestComponent.java +++ b/java/google/registry/module/frontend/FrontendRequestComponent.java @@ -40,6 +40,7 @@ import google.registry.request.RequestScope; import google.registry.ui.server.registrar.ConsoleUiAction; import google.registry.ui.server.registrar.RegistrarPaymentAction; import google.registry.ui.server.registrar.RegistrarPaymentSetupAction; +import google.registry.ui.server.registrar.RegistrarPremiumPriceAckAction; import google.registry.ui.server.registrar.RegistrarSettingsAction; import google.registry.whois.WhoisHttpServer; import google.registry.whois.WhoisModule; @@ -66,6 +67,7 @@ interface FrontendRequestComponent { RdapAutnumAction rdapAutnumAction(); RegistrarPaymentAction registrarPaymentAction(); RegistrarPaymentSetupAction registrarPaymentSetupAction(); + RegistrarPremiumPriceAckAction registrarPremiumPriceAckAction(); RegistrarSettingsAction registrarSettingsAction(); RdapDomainAction rdapDomainAction(); RdapDomainSearchAction rdapDomainSearchAction(); diff --git a/java/google/registry/ui/externs/json.js b/java/google/registry/ui/externs/json.js index 057983d59..33355f0fe 100644 --- a/java/google/registry/ui/externs/json.js +++ b/java/google/registry/ui/externs/json.js @@ -84,7 +84,8 @@ registry.json.Response.prototype.results; * localizedAddress: registry.json.RegistrarAddress, * whoisServer: (string?|undefined), * referralUrl: (string?|undefined), - * contacts: !Array. + * contacts: !Array., + * premiumPriceAckRequired: boolean * }} */ registry.json.Registrar; diff --git a/java/google/registry/ui/js/registrar/resources.js b/java/google/registry/ui/js/registrar/resources.js index 00ffa163b..a5e393725 100644 --- a/java/google/registry/ui/js/registrar/resources.js +++ b/java/google/registry/ui/js/registrar/resources.js @@ -37,7 +37,7 @@ registry.registrar.Resources = function(console, xsrfToken) { this, 'constructor', console, - new registry.Resource(new goog.Uri('/registrar-settings'), xsrfToken), + new registry.Resource(new goog.Uri('/registrar-premium-price-ack'), xsrfToken), registry.soy.registrar.console.resources, null); }; @@ -47,6 +47,6 @@ goog.inherits(registry.registrar.Resources, /** @override */ registry.registrar.Resources.prototype.bindToDom = function(id) { - registry.registrar.Resources.base(this, 'bindToDom', ''); - goog.dom.removeChildren(goog.dom.getRequiredElement('reg-app-buttons')); + registry.registrar.Resources.base(this, 'bindToDom', 'fake'); + goog.dom.removeNode(goog.dom.getRequiredElement('reg-app-btn-back')); }; diff --git a/java/google/registry/ui/server/RegistrarFormFields.java b/java/google/registry/ui/server/RegistrarFormFields.java index bbb70032e..745b50988 100644 --- a/java/google/registry/ui/server/RegistrarFormFields.java +++ b/java/google/registry/ui/server/RegistrarFormFields.java @@ -315,6 +315,9 @@ public final class RegistrarFormFields { L10N_STREET_FIELD, L10N_CITY_FIELD, L10N_STATE_FIELD, L10N_ZIP_FIELD)) .build(); + public static final FormField PREMIUM_PRICE_ACK_REQUIRED = + FormField.named("premiumPriceAckRequired", Boolean.class).build(); + private static Function, RegistrarAddress> newAddressTransform( final FormField, List> streetField, final FormField cityField, diff --git a/java/google/registry/ui/server/registrar/ConsoleUiAction.java b/java/google/registry/ui/server/registrar/ConsoleUiAction.java index 53910d1d5..e89f493cb 100644 --- a/java/google/registry/ui/server/registrar/ConsoleUiAction.java +++ b/java/google/registry/ui/server/registrar/ConsoleUiAction.java @@ -138,6 +138,7 @@ public final class ConsoleUiAction implements Runnable { data.put("xsrfToken", xsrfTokenManager.generateToken(userAuthInfo.user().getEmail())); data.put("clientId", clientId); data.put("showPaymentLink", registrar.getBillingMethod() == Registrar.BillingMethod.BRAINTREE); + data.put("requireFeeExtension", registrar.getPremiumPriceAckRequired()); String payload = TOFU_SUPPLIER.get() .newRenderer(ConsoleSoyInfo.MAIN) diff --git a/java/google/registry/ui/server/registrar/RegistrarPaymentAction.java b/java/google/registry/ui/server/registrar/RegistrarPaymentAction.java index 4e6284bbd..ecf615750 100644 --- a/java/google/registry/ui/server/registrar/RegistrarPaymentAction.java +++ b/java/google/registry/ui/server/registrar/RegistrarPaymentAction.java @@ -157,7 +157,7 @@ public final class RegistrarPaymentAction implements Runnable, JsonAction { @Override public Map handleJsonRequest(Map json) { - Registrar registrar = sessionUtils.getRegistrarForAuthResult(request, authResult); + Registrar registrar = sessionUtils.getRegistrarForAuthResult(request, authResult, false); logger.infofmt("Processing payment: %s", json); String paymentMethodNonce; Money amount; diff --git a/java/google/registry/ui/server/registrar/RegistrarPaymentSetupAction.java b/java/google/registry/ui/server/registrar/RegistrarPaymentSetupAction.java index 3ccf791d4..6d735e240 100644 --- a/java/google/registry/ui/server/registrar/RegistrarPaymentSetupAction.java +++ b/java/google/registry/ui/server/registrar/RegistrarPaymentSetupAction.java @@ -91,7 +91,7 @@ public final class RegistrarPaymentSetupAction implements Runnable, JsonAction { @Override public Map handleJsonRequest(Map json) { - Registrar registrar = sessionUtils.getRegistrarForAuthResult(request, authResult); + Registrar registrar = sessionUtils.getRegistrarForAuthResult(request, authResult, false); if (!json.isEmpty()) { return JsonResponseHelper.create(ERROR, "JSON request object must be empty"); diff --git a/java/google/registry/ui/server/registrar/RegistrarPremiumPriceAckAction.java b/java/google/registry/ui/server/registrar/RegistrarPremiumPriceAckAction.java new file mode 100644 index 000000000..e6feaa392 --- /dev/null +++ b/java/google/registry/ui/server/registrar/RegistrarPremiumPriceAckAction.java @@ -0,0 +1,170 @@ +// 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.ui.server.registrar; + +import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.security.JsonResponseHelper.Status.ERROR; +import static google.registry.security.JsonResponseHelper.Status.SUCCESS; +import static google.registry.ui.server.registrar.RegistrarSettingsAction.ARGS_PARAM; +import static google.registry.ui.server.registrar.RegistrarSettingsAction.OP_PARAM; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import google.registry.config.RegistryConfig.Config; +import google.registry.export.sheet.SyncRegistrarsSheetAction; +import google.registry.model.registrar.Registrar; +import google.registry.request.Action; +import google.registry.request.HttpException.BadRequestException; +import google.registry.request.JsonActionRunner; +import google.registry.request.auth.Auth; +import google.registry.request.auth.AuthResult; +import google.registry.security.JsonResponseHelper; +import google.registry.ui.forms.FormException; +import google.registry.ui.forms.FormFieldException; +import google.registry.ui.server.RegistrarFormFields; +import google.registry.util.CollectionUtils; +import google.registry.util.DiffUtils; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; + +/** + * Action handler for toggling the "Premium Price Ack Required" flag. + * + *

This class exists to supplement RegistrarSettings, which is supposed to take care of + * everything but mostly doesn't work. + */ +@Action( + path = RegistrarPremiumPriceAckAction.PATH, + method = Action.Method.POST, + auth = Auth.AUTH_PUBLIC_LOGGED_IN +) +public class RegistrarPremiumPriceAckAction implements Runnable, JsonActionRunner.JsonAction { + public static final String PATH = "/registrar-premium-price-ack"; + + @Inject AuthResult authResult; + @Inject HttpServletRequest request; + @Inject JsonActionRunner jsonActionRunner; + @Inject SendEmailUtils sendEmailUtils; + @Inject SessionUtils sessionUtils; + + @Inject + @Config("registrarChangesNotificationEmailAddresses") + ImmutableList registrarChangesNotificationEmailAddresses; + + @Inject + RegistrarPremiumPriceAckAction() {} + + @Override + public void run() { + jsonActionRunner.run(this); + } + + @Override + public Map handleJsonRequest(Map input) { + if (input == null) { + throw new BadRequestException("Malformed JSON"); + } + + // Get the registrar + Registrar initialRegistrar = sessionUtils.getRegistrarForAuthResult(request, authResult, true); + + // Process the operation. Though originally derived from a CRUD handler, registrar-settings + // and registrar-premium-price-action really only support read and update. + String op = Optional.ofNullable((String) input.get(OP_PARAM)).orElse("read"); + @SuppressWarnings("unchecked") + Map args = + (Map) + Optional.ofNullable(input.get(ARGS_PARAM)).orElse(ImmutableMap.of()); + try { + switch (op) { + case "update": + return update(args, initialRegistrar); + case "read": + // Though this class only exists to support update of the premiumPriceAckRequired flag, + // "read" gives back a javascript representation of the entire registrar object to support + // the architecture of the console. + return JsonResponseHelper.create(SUCCESS, "Success", initialRegistrar.toJsonMap()); + default: + return JsonResponseHelper.create(ERROR, "Unknown or unsupported operation: " + op); + } + } catch (FormFieldException e) { + return JsonResponseHelper.createFormFieldError(e.getMessage(), e.getFieldName()); + } catch (FormException e) { + return JsonResponseHelper.create(ERROR, e.getMessage()); + } + } + + Map update(final Map args, final Registrar registrar) { + final String clientId = sessionUtils.getRegistrarClientId(request); + return ofy() + .transact( + () -> { + // Verify that the registrar hasn't been changed. + Optional latest = Registrar.loadByClientId(registrar.getClientId()); + if (!latest.isPresent() + || latest.get().getPremiumPriceAckRequired() + != registrar.getPremiumPriceAckRequired()) { + return JsonResponseHelper.create( + ERROR, + "Premium price ack has changed (or registrar no longer exists). " + + "Please reload."); + } + Registrar reg = latest.get(); + + Optional premiumPriceAckRequired = + RegistrarFormFields.PREMIUM_PRICE_ACK_REQUIRED.extractUntyped(args); + if (premiumPriceAckRequired.isPresent() + && premiumPriceAckRequired.get() != reg.getPremiumPriceAckRequired()) { + Registrar updatedRegistrar = + reg.asBuilder() + .setPremiumPriceAckRequired(premiumPriceAckRequired.get()) + .build(); + ofy().save().entity(updatedRegistrar); + sendExternalUpdatesIfNecessary(reg, updatedRegistrar); + return JsonResponseHelper.create( + SUCCESS, "Saved " + clientId, updatedRegistrar.toJsonMap()); + } else { + return JsonResponseHelper.create(SUCCESS, "Unchanged " + clientId, reg.toJsonMap()); + } + }); + } + + private void sendExternalUpdatesIfNecessary( + Registrar existingRegistrar, Registrar updatedRegistrar) { + if (registrarChangesNotificationEmailAddresses.isEmpty()) { + return; + } + + Map diffs = + DiffUtils.deepDiff( + existingRegistrar.toDiffableFieldMap(), updatedRegistrar.toDiffableFieldMap(), true); + @SuppressWarnings("unchecked") + Set changedKeys = (Set) diffs.keySet(); + if (CollectionUtils.difference(changedKeys, "lastUpdateTime").isEmpty()) { + return; + } + SyncRegistrarsSheetAction.enqueueBackendTask(); + if (!registrarChangesNotificationEmailAddresses.isEmpty()) { + sendEmailUtils.sendEmail( + registrarChangesNotificationEmailAddresses, + String.format("Registrar %s updated", existingRegistrar.getRegistrarName()), + "The following changes were made to the registrar:\n" + + DiffUtils.prettyPrintDiffedMap(diffs, null)); + } + } +} diff --git a/java/google/registry/ui/server/registrar/RegistrarSettingsAction.java b/java/google/registry/ui/server/registrar/RegistrarSettingsAction.java index 203dd311a..ec10a763c 100644 --- a/java/google/registry/ui/server/registrar/RegistrarSettingsAction.java +++ b/java/google/registry/ui/server/registrar/RegistrarSettingsAction.java @@ -67,8 +67,8 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA public static final String PATH = "/registrar-settings"; - private static final String OP_PARAM = "op"; - private static final String ARGS_PARAM = "args"; + static final String OP_PARAM = "op"; + static final String ARGS_PARAM = "args"; @Inject HttpServletRequest request; @Inject JsonActionRunner jsonActionRunner; @@ -93,7 +93,7 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA throw new BadRequestException("Malformed JSON"); } - Registrar initialRegistrar = sessionUtils.getRegistrarForAuthResult(request, authResult); + Registrar initialRegistrar = sessionUtils.getRegistrarForAuthResult(request, authResult, false); // Process the operation. Though originally derived from a CRUD // handler, registrar-settings really only supports read and update. String op = Optional.ofNullable((String) input.get(OP_PARAM)).orElse("read"); diff --git a/java/google/registry/ui/server/registrar/SessionUtils.java b/java/google/registry/ui/server/registrar/SessionUtils.java index e013352fc..cae043336 100644 --- a/java/google/registry/ui/server/registrar/SessionUtils.java +++ b/java/google/registry/ui/server/registrar/SessionUtils.java @@ -48,7 +48,8 @@ public class SessionUtils { @Config("registryAdminClientId") String registryAdminClientId; - @Inject public SessionUtils() {} + @Inject + public SessionUtils() {} /** * Checks that the authentication result indicates a user that has access to the registrar @@ -58,7 +59,8 @@ public class SessionUtils { * the registrar console. */ @CheckReturnValue - Registrar getRegistrarForAuthResult(HttpServletRequest request, AuthResult authResult) { + Registrar getRegistrarForAuthResult( + HttpServletRequest request, AuthResult authResult, boolean bypassCache) { if (!authResult.userAuthInfo().isPresent()) { throw new ForbiddenException("Not logged in"); } @@ -67,7 +69,11 @@ public class SessionUtils { } String clientId = getRegistrarClientId(request); return checkArgumentPresent( - Registrar.loadByClientIdCached(clientId), "Registrar %s not found", clientId); + bypassCache + ? Registrar.loadByClientId(clientId) + : Registrar.loadByClientIdCached(clientId), + "Registrar %s not found", + clientId); } /** @@ -77,11 +83,11 @@ public class SessionUtils { * {@code clientId} attribute: * *
    - *
  • If it does not exist, then we will attempt to guess the {@link Registrar} with which the - * user is associated. The {@code clientId} of the first matching {@code Registrar} will then - * be stored to the HTTP session. - *
  • If it does exist, then we'll fetch the Registrar from Datastore to make sure access - * wasn't revoked. + *
  • If it does not exist, then we will attempt to guess the {@link Registrar} with which the + * user is associated. The {@code clientId} of the first matching {@code Registrar} will + * then be stored to the HTTP session. + *
  • If it does exist, then we'll fetch the Registrar from Datastore to make sure access + * wasn't revoked. *
* *

Note: You must ensure the user has logged in before calling this method. @@ -162,10 +168,8 @@ public class SessionUtils { /** Returns first {@link Registrar} that {@code gaeUserId} is authorized to administer. */ private static Optional findRegistrarForUser(String gaeUserId) { - RegistrarContact contact = ofy().load() - .type(RegistrarContact.class) - .filter("gaeUserId", gaeUserId) - .first().now(); + RegistrarContact contact = + ofy().load().type(RegistrarContact.class).filter("gaeUserId", gaeUserId).first().now(); if (contact == null) { return Optional.empty(); } diff --git a/java/google/registry/ui/soy/registrar/Console.soy b/java/google/registry/ui/soy/registrar/Console.soy index 93c1e429d..278bc09d8 100644 --- a/java/google/registry/ui/soy/registrar/Console.soy +++ b/java/google/registry/ui/soy/registrar/Console.soy @@ -266,6 +266,8 @@ {template .resources} {@param? driveFolderId: string} {@param technicalDocsUrl: string} + {@param premiumPriceAckRequired: bool} + {@param readonly: bool}

Resources & billing

@@ -290,6 +292,15 @@ {else} Your billing folder is pending allocation. {/if} +

EPP Settings

+

Use the 'Edit' button above to switch to enable editing the information + below. Updates may require up to 10 minutes to take effect. +

+ + Require use of fee extension to acknowledge all premium domain prices. +
{/template} diff --git a/javatests/google/registry/module/frontend/testdata/frontend_routing.txt b/javatests/google/registry/module/frontend/testdata/frontend_routing.txt index 6f34d77d3..7c477095b 100644 --- a/javatests/google/registry/module/frontend/testdata/frontend_routing.txt +++ b/javatests/google/registry/module/frontend/testdata/frontend_routing.txt @@ -1,19 +1,20 @@ -PATH CLASS METHODS OK AUTH_METHODS MIN USER_POLICY -/_dr/epp EppTlsAction POST n INTERNAL,API APP ADMIN -/_dr/whois WhoisServer POST n INTERNAL,API APP ADMIN -/check CheckApiAction GET n INTERNAL NONE PUBLIC -/rdap/autnum/(*) RdapAutnumAction GET,HEAD n INTERNAL NONE PUBLIC -/rdap/domain/(*) RdapDomainAction GET,HEAD n INTERNAL,API,LEGACY NONE PUBLIC -/rdap/domains RdapDomainSearchAction GET,HEAD n INTERNAL,API,LEGACY NONE PUBLIC -/rdap/entities RdapEntitySearchAction GET,HEAD n INTERNAL,API,LEGACY NONE PUBLIC -/rdap/entity/(*) RdapEntityAction GET,HEAD n INTERNAL,API,LEGACY NONE PUBLIC -/rdap/help(*) RdapHelpAction GET,HEAD n INTERNAL NONE PUBLIC -/rdap/ip/(*) RdapIpAction GET,HEAD n INTERNAL NONE PUBLIC -/rdap/nameserver/(*) RdapNameserverAction GET,HEAD n INTERNAL NONE PUBLIC -/rdap/nameservers RdapNameserverSearchAction GET,HEAD n INTERNAL NONE PUBLIC -/registrar ConsoleUiAction GET n INTERNAL,API,LEGACY NONE PUBLIC -/registrar-payment RegistrarPaymentAction POST n API,LEGACY USER PUBLIC -/registrar-payment-setup RegistrarPaymentSetupAction POST n API,LEGACY USER PUBLIC -/registrar-settings RegistrarSettingsAction POST n API,LEGACY USER PUBLIC -/registrar-xhr EppConsoleAction POST n API,LEGACY USER PUBLIC -/whois/(*) WhoisHttpServer GET n INTERNAL NONE PUBLIC +PATH CLASS METHODS OK AUTH_METHODS MIN USER_POLICY +/_dr/epp EppTlsAction POST n INTERNAL,API APP ADMIN +/_dr/whois WhoisServer POST n INTERNAL,API APP ADMIN +/check CheckApiAction GET n INTERNAL NONE PUBLIC +/rdap/autnum/(*) RdapAutnumAction GET,HEAD n INTERNAL NONE PUBLIC +/rdap/domain/(*) RdapDomainAction GET,HEAD n INTERNAL,API,LEGACY NONE PUBLIC +/rdap/domains RdapDomainSearchAction GET,HEAD n INTERNAL,API,LEGACY NONE PUBLIC +/rdap/entities RdapEntitySearchAction GET,HEAD n INTERNAL,API,LEGACY NONE PUBLIC +/rdap/entity/(*) RdapEntityAction GET,HEAD n INTERNAL,API,LEGACY NONE PUBLIC +/rdap/help(*) RdapHelpAction GET,HEAD n INTERNAL NONE PUBLIC +/rdap/ip/(*) RdapIpAction GET,HEAD n INTERNAL NONE PUBLIC +/rdap/nameserver/(*) RdapNameserverAction GET,HEAD n INTERNAL NONE PUBLIC +/rdap/nameservers RdapNameserverSearchAction GET,HEAD n INTERNAL NONE PUBLIC +/registrar ConsoleUiAction GET n INTERNAL,API,LEGACY NONE PUBLIC +/registrar-payment RegistrarPaymentAction POST n API,LEGACY USER PUBLIC +/registrar-payment-setup RegistrarPaymentSetupAction POST n API,LEGACY USER PUBLIC +/registrar-premium-price-ack RegistrarPremiumPriceAckAction POST n API,LEGACY USER PUBLIC +/registrar-settings RegistrarSettingsAction POST n API,LEGACY USER PUBLIC +/registrar-xhr EppConsoleAction POST n API,LEGACY USER PUBLIC +/whois/(*) WhoisHttpServer GET n INTERNAL NONE PUBLIC diff --git a/javatests/google/registry/ui/js/registrar/console_test.js b/javatests/google/registry/ui/js/registrar/console_test.js index fcccfb558..b02b65238 100644 --- a/javatests/google/registry/ui/js/registrar/console_test.js +++ b/javatests/google/registry/ui/js/registrar/console_test.js @@ -128,11 +128,13 @@ function testNavToResources() { registry.registrar.ConsoleTestUtil.visit(test, { path: 'resources', xsrfToken: test.testXsrfToken, - technicalDocsUrl: 'http://example.com/techdocs' + technicalDocsUrl: 'http://example.com/techdocs', + premiumPriceAckRequired: false, + readonly: true, }); var xhr = goog.testing.net.XhrIo.getSendInstances().pop(); assertTrue(xhr.isActive()); - assertEquals('/registrar-settings', xhr.getLastUri()); + assertEquals('/registrar-premium-price-ack', xhr.getLastUri()); assertEquals(test.testXsrfToken, xhr.getLastRequestHeaders().get('X-CSRF-Token')); xhr.simulateResponse(200, goog.json.serialize({ diff --git a/javatests/google/registry/ui/server/registrar/RegistrarPaymentActionTest.java b/javatests/google/registry/ui/server/registrar/RegistrarPaymentActionTest.java index 73558cf21..0f68eaa86 100644 --- a/javatests/google/registry/ui/server/registrar/RegistrarPaymentActionTest.java +++ b/javatests/google/registry/ui/server/registrar/RegistrarPaymentActionTest.java @@ -19,6 +19,7 @@ import static google.registry.testing.DatastoreHelper.loadRegistrar; import static google.registry.testing.ReflectiveFieldExtractor.extractField; import static java.util.Arrays.asList; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -93,7 +94,7 @@ public class RegistrarPaymentActionTest { when(braintreeGateway.transaction()).thenReturn(transactionGateway); when(transactionGateway.sale(any(TransactionRequest.class))).thenReturn(result); when(sessionUtils.getRegistrarForAuthResult( - any(HttpServletRequest.class), any(AuthResult.class))) + any(HttpServletRequest.class), any(AuthResult.class), anyBoolean())) .thenReturn(loadRegistrar("TheRegistrar")); } diff --git a/javatests/google/registry/ui/server/registrar/RegistrarPaymentSetupActionTest.java b/javatests/google/registry/ui/server/registrar/RegistrarPaymentSetupActionTest.java index aad2ec8f0..f393009b1 100644 --- a/javatests/google/registry/ui/server/registrar/RegistrarPaymentSetupActionTest.java +++ b/javatests/google/registry/ui/server/registrar/RegistrarPaymentSetupActionTest.java @@ -19,6 +19,7 @@ import static google.registry.testing.DatastoreHelper.loadRegistrar; import static google.registry.testing.DatastoreHelper.persistResource; import static java.util.Arrays.asList; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -68,7 +69,7 @@ public class RegistrarPaymentSetupActionTest { .setBillingMethod(Registrar.BillingMethod.BRAINTREE) .build()); when(sessionUtils.getRegistrarForAuthResult( - any(HttpServletRequest.class), any(AuthResult.class))) + any(HttpServletRequest.class), any(AuthResult.class), anyBoolean())) .thenReturn(registrar); when(braintreeGateway.clientToken()).thenReturn(clientTokenGateway); } @@ -111,7 +112,7 @@ public class RegistrarPaymentSetupActionTest { .setBillingMethod(Registrar.BillingMethod.EXTERNAL) .build()); when(sessionUtils.getRegistrarForAuthResult( - any(HttpServletRequest.class), any(AuthResult.class))) + any(HttpServletRequest.class), any(AuthResult.class), anyBoolean())) .thenReturn(registrar); assertThat(action.handleJsonRequest(ImmutableMap.of())) .containsExactly( diff --git a/javatests/google/registry/ui/server/registrar/RegistrarPremiumPriceAckActionTest.java b/javatests/google/registry/ui/server/registrar/RegistrarPremiumPriceAckActionTest.java new file mode 100644 index 000000000..04748393c --- /dev/null +++ b/javatests/google/registry/ui/server/registrar/RegistrarPremiumPriceAckActionTest.java @@ -0,0 +1,160 @@ +// 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.ui.server.registrar; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.config.RegistryConfig.getGSuiteOutgoingEmailAddress; +import static google.registry.config.RegistryConfig.getGSuiteOutgoingEmailDisplayName; +import static google.registry.security.JsonHttpTestUtils.createJsonPayload; +import static google.registry.testing.DatastoreHelper.loadRegistrar; +import static google.registry.testing.DatastoreHelper.persistResource; +import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued; +import static google.registry.util.ResourceUtils.readResourceUtf8; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.appengine.api.modules.ModulesService; +import com.google.appengine.api.users.User; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import google.registry.export.sheet.SyncRegistrarsSheetAction; +import google.registry.model.registrar.Registrar; +import google.registry.model.registrar.RegistrarContact; +import google.registry.request.JsonActionRunner; +import google.registry.request.JsonResponse; +import google.registry.request.ResponseImpl; +import google.registry.request.auth.AuthLevel; +import google.registry.request.auth.AuthResult; +import google.registry.request.auth.UserAuthInfo; +import google.registry.testing.AppEngineRule; +import google.registry.testing.FakeHttpSession; +import google.registry.testing.InjectRule; +import google.registry.testing.TaskQueueHelper.TaskMatcher; +import google.registry.util.SendEmailService; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Map; +import java.util.Properties; +import javax.mail.Message; +import javax.mail.Session; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class RegistrarPremiumPriceAckActionTest { + + private static final String CLIENT_ID = "NewRegistrar"; + + private final RegistrarPremiumPriceAckAction action = new RegistrarPremiumPriceAckAction(); + + public RegistrarPremiumPriceAckActionTest() {} + + @Rule + public final AppEngineRule appEngine = + AppEngineRule.builder().withDatastore().withTaskQueue().build(); + + @Rule public final InjectRule inject = new InjectRule(); + + final HttpServletRequest req = mock(HttpServletRequest.class); + final HttpServletResponse rsp = mock(HttpServletResponse.class); + final SendEmailService emailService = mock(SendEmailService.class); + final ModulesService modulesService = mock(ModulesService.class); + final User user = + new User("janedoe", "theregistrar.com", AppEngineRule.NEW_REGISTRAR_GAE_USER_ID); + + Message message; + + final StringWriter writer = new StringWriter(); + + @Before + public void setUp() throws Exception { + action.request = req; + action.sessionUtils = new SessionUtils(); + action.sessionUtils.registryAdminClientId = CLIENT_ID; + action.authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user, false)); + action.jsonActionRunner = + new JsonActionRunner(ImmutableMap.of(), new JsonResponse(new ResponseImpl(rsp))); + action.registrarChangesNotificationEmailAddresses = + ImmutableList.of("notification@test.example", "notification2@test.example"); + action.sendEmailUtils = + new SendEmailUtils(getGSuiteOutgoingEmailAddress(), getGSuiteOutgoingEmailDisplayName()); + + message = new MimeMessage(Session.getDefaultInstance(new Properties(), null)); + when(emailService.createMessage()).thenReturn(message); + when(req.getMethod()).thenReturn("POST"); + when(rsp.getWriter()).thenReturn(new PrintWriter(writer)); + when(req.getContentType()).thenReturn("application/json"); + when(req.getReader()).thenReturn(createJsonPayload(ImmutableMap.of("op", "read"))); + when(modulesService.getVersionHostname("backend", null)).thenReturn("backend.hostname"); + FakeHttpSession session = new FakeHttpSession(); + when(req.getSession()).thenReturn(session); + session.setAttribute(SessionUtils.CLIENT_ID_ATTRIBUTE, CLIENT_ID); + + inject.setStaticField(SendEmailUtils.class, "emailService", emailService); + inject.setStaticField(SyncRegistrarsSheetAction.class, "modulesService", modulesService); + + Registrar registrar = AppEngineRule.makeRegistrar1(); + RegistrarContact contact = AppEngineRule.makeRegistrarContact1(); + persistResource(registrar); + persistResource(contact); + } + + @Test + public void testPost_updatePremiumPriceAckRequired() throws Exception { + Registrar registrar = loadRegistrar(CLIENT_ID); + + Registrar modified = registrar.asBuilder().setPremiumPriceAckRequired(true).build(); + Map reqJson = modified.toJsonMap(); + Map response = + action.handleJsonRequest(ImmutableMap.of("op", "update", "args", reqJson)); + assertThat(response).containsEntry("status", "SUCCESS"); + assertThat(loadRegistrar(CLIENT_ID).getPremiumPriceAckRequired()).isTrue(); + + // Verify that we sent notification emails. + String expectedEmailBody = readResourceUtf8(getClass(), "testdata/update_ppa_email.txt"); + verify(rsp, never()).setStatus(anyInt()); + verify(emailService).createMessage(); + verify(emailService).sendMessage(message); + assertThat(message.getAllRecipients()) + .asList() + .containsExactly( + new InternetAddress("notification@test.example"), + new InternetAddress("notification2@test.example")); + assertThat(message.getContent()).isEqualTo(expectedEmailBody); + assertTasksEnqueued( + "sheet", + new TaskMatcher() + .url(SyncRegistrarsSheetAction.PATH) + .method("GET") + .header("Host", "backend.hostname")); + + // Verify that switching back also works. + modified = modified.asBuilder().setPremiumPriceAckRequired(false).build(); + reqJson = modified.toJsonMap(); + response = action.handleJsonRequest(ImmutableMap.of("op", "update", "args", reqJson)); + assertThat(response).containsEntry("status", "SUCCESS"); + assertThat(loadRegistrar(CLIENT_ID).getPremiumPriceAckRequired()).isFalse(); + } +} diff --git a/javatests/google/registry/ui/server/registrar/RegistrarSettingsActionTest.java b/javatests/google/registry/ui/server/registrar/RegistrarSettingsActionTest.java index b1a639883..0ced02779 100644 --- a/javatests/google/registry/ui/server/registrar/RegistrarSettingsActionTest.java +++ b/javatests/google/registry/ui/server/registrar/RegistrarSettingsActionTest.java @@ -22,6 +22,7 @@ import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued; import static google.registry.util.ResourceUtils.readResourceUtf8; import static java.util.Arrays.asList; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -71,7 +72,7 @@ public class RegistrarSettingsActionTest extends RegistrarSettingsActionTestCase @Test public void testRead_notAuthorized_failure() throws Exception { when(sessionUtils.getRegistrarForAuthResult( - any(HttpServletRequest.class), any(AuthResult.class))) + any(HttpServletRequest.class), any(AuthResult.class), anyBoolean())) .thenThrow(new ForbiddenException("Not authorized to access Registrar Console")); assertThrows(ForbiddenException.class, () -> action.handleJsonRequest(ImmutableMap.of())); assertNoTasksEnqueued("sheet"); diff --git a/javatests/google/registry/ui/server/registrar/RegistrarSettingsActionTestCase.java b/javatests/google/registry/ui/server/registrar/RegistrarSettingsActionTestCase.java index 24046a13d..3db236aa4 100644 --- a/javatests/google/registry/ui/server/registrar/RegistrarSettingsActionTestCase.java +++ b/javatests/google/registry/ui/server/registrar/RegistrarSettingsActionTestCase.java @@ -106,13 +106,13 @@ public class RegistrarSettingsActionTestCase { when(rsp.getWriter()).thenReturn(new PrintWriter(writer)); when(req.getContentType()).thenReturn("application/json"); when(req.getReader()).thenReturn(createJsonPayload(ImmutableMap.of("op", "read"))); - when(sessionUtils.getRegistrarForAuthResult(req, action.authResult)) + when(sessionUtils.getRegistrarForAuthResult(req, action.authResult, false)) .thenReturn(loadRegistrar(CLIENT_ID)); when(modulesService.getVersionHostname("backend", null)).thenReturn("backend.hostname"); } - protected Map readJsonFromFile(String filename) { - String contents = readResourceUtf8(getClass(), filename); + static Map readJsonFromFile(String filename) { + String contents = readResourceUtf8(RegistrarSettingsActionTestCase.class, filename); try { @SuppressWarnings("unchecked") Map json = (Map) JSONValue.parseWithException(contents); diff --git a/javatests/google/registry/ui/server/registrar/SecuritySettingsTest.java b/javatests/google/registry/ui/server/registrar/SecuritySettingsTest.java index 12c59ae70..2b5cb8b1c 100644 --- a/javatests/google/registry/ui/server/registrar/SecuritySettingsTest.java +++ b/javatests/google/registry/ui/server/registrar/SecuritySettingsTest.java @@ -112,7 +112,7 @@ public class SecuritySettingsTest extends RegistrarSettingsActionTestCase { .setClientCertificate(SAMPLE_CERT, START_OF_TIME) .setFailoverClientCertificate(SAMPLE_CERT2, START_OF_TIME) .build()); - when(sessionUtils.getRegistrarForAuthResult(req, action.authResult)) + when(sessionUtils.getRegistrarForAuthResult(req, action.authResult, false)) .thenReturn(initialRegistrar); Map jsonMap = initialRegistrar.toJsonMap(); jsonMap.put("clientCertificate", null); diff --git a/javatests/google/registry/ui/server/registrar/testdata/update_ppa_email.txt b/javatests/google/registry/ui/server/registrar/testdata/update_ppa_email.txt new file mode 100644 index 000000000..4c3352f7b --- /dev/null +++ b/javatests/google/registry/ui/server/registrar/testdata/update_ppa_email.txt @@ -0,0 +1,2 @@ +The following changes were made to the registrar: +premiumPriceAckRequired: false -> true