Implement "premium price ack required" checkbox

Implement a checkbox in the "Resources" tab to allow registrars to toggle
their "premium price ack required" flag.

Tested:
  Verfied the console functionality by hand.  I've started work on an
  automated test, but we can't actually test those from blaze and the
  kokoro tests are way too time-consuming to be practical for development, so
  we're going to have to either find a way to run those locally outside of
  the normal process or make do without a test.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=190212177
This commit is contained in:
mmuller 2018-03-23 06:42:32 -07:00 committed by jianglai
parent f47965d5f6
commit 785225fc28
22 changed files with 423 additions and 50 deletions

View file

@ -49,6 +49,18 @@
<url-pattern>/registrar-settings</url-pattern> <url-pattern>/registrar-settings</url-pattern>
</servlet-mapping> </servlet-mapping>
<!--
Registrar Self-serve console to support updating the premium price ack
flag. Ideally, this should be handled by /registrar-settings (above), but
unfortunately the console is broken and registrar-settings doesn't support
this use-case as it requires fields that are not universally sent during
updates.
-->
<servlet-mapping>
<servlet-name>frontend-servlet</servlet-name>
<url-pattern>/registrar-premium-price-ack</url-pattern>
</servlet-mapping>
<!-- HTTP WHOIS. --> <!-- HTTP WHOIS. -->
<servlet-mapping> <servlet-mapping>
<servlet-name>frontend-servlet</servlet-name> <servlet-name>frontend-servlet</servlet-name>

View file

@ -615,6 +615,7 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable
.put("emailAddress", emailAddress) .put("emailAddress", emailAddress)
.put("whoisServer", getWhoisServer()) .put("whoisServer", getWhoisServer())
.put("blockPremiumNames", blockPremiumNames) .put("blockPremiumNames", blockPremiumNames)
.put("premiumPriceAckRequired", premiumPriceAckRequired)
.put("url", url) .put("url", url)
.put("referralUrl", getReferralUrl()) .put("referralUrl", getReferralUrl())
.put("icannReferralEmail", getIcannReferralEmail()) .put("icannReferralEmail", getIcannReferralEmail())

View file

@ -40,6 +40,7 @@ import google.registry.request.RequestScope;
import google.registry.ui.server.registrar.ConsoleUiAction; import google.registry.ui.server.registrar.ConsoleUiAction;
import google.registry.ui.server.registrar.RegistrarPaymentAction; import google.registry.ui.server.registrar.RegistrarPaymentAction;
import google.registry.ui.server.registrar.RegistrarPaymentSetupAction; import google.registry.ui.server.registrar.RegistrarPaymentSetupAction;
import google.registry.ui.server.registrar.RegistrarPremiumPriceAckAction;
import google.registry.ui.server.registrar.RegistrarSettingsAction; import google.registry.ui.server.registrar.RegistrarSettingsAction;
import google.registry.whois.WhoisHttpServer; import google.registry.whois.WhoisHttpServer;
import google.registry.whois.WhoisModule; import google.registry.whois.WhoisModule;
@ -66,6 +67,7 @@ interface FrontendRequestComponent {
RdapAutnumAction rdapAutnumAction(); RdapAutnumAction rdapAutnumAction();
RegistrarPaymentAction registrarPaymentAction(); RegistrarPaymentAction registrarPaymentAction();
RegistrarPaymentSetupAction registrarPaymentSetupAction(); RegistrarPaymentSetupAction registrarPaymentSetupAction();
RegistrarPremiumPriceAckAction registrarPremiumPriceAckAction();
RegistrarSettingsAction registrarSettingsAction(); RegistrarSettingsAction registrarSettingsAction();
RdapDomainAction rdapDomainAction(); RdapDomainAction rdapDomainAction();
RdapDomainSearchAction rdapDomainSearchAction(); RdapDomainSearchAction rdapDomainSearchAction();

View file

@ -84,7 +84,8 @@ registry.json.Response.prototype.results;
* localizedAddress: registry.json.RegistrarAddress, * localizedAddress: registry.json.RegistrarAddress,
* whoisServer: (string?|undefined), * whoisServer: (string?|undefined),
* referralUrl: (string?|undefined), * referralUrl: (string?|undefined),
* contacts: !Array.<registry.json.RegistrarContact> * contacts: !Array.<registry.json.RegistrarContact>,
* premiumPriceAckRequired: boolean
* }} * }}
*/ */
registry.json.Registrar; registry.json.Registrar;

View file

@ -37,7 +37,7 @@ registry.registrar.Resources = function(console, xsrfToken) {
this, this,
'constructor', 'constructor',
console, 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, registry.soy.registrar.console.resources,
null); null);
}; };
@ -47,6 +47,6 @@ goog.inherits(registry.registrar.Resources,
/** @override */ /** @override */
registry.registrar.Resources.prototype.bindToDom = function(id) { registry.registrar.Resources.prototype.bindToDom = function(id) {
registry.registrar.Resources.base(this, 'bindToDom', ''); registry.registrar.Resources.base(this, 'bindToDom', 'fake');
goog.dom.removeChildren(goog.dom.getRequiredElement('reg-app-buttons')); goog.dom.removeNode(goog.dom.getRequiredElement('reg-app-btn-back'));
}; };

View file

@ -315,6 +315,9 @@ public final class RegistrarFormFields {
L10N_STREET_FIELD, L10N_CITY_FIELD, L10N_STATE_FIELD, L10N_ZIP_FIELD)) L10N_STREET_FIELD, L10N_CITY_FIELD, L10N_STATE_FIELD, L10N_ZIP_FIELD))
.build(); .build();
public static final FormField<Boolean, Boolean> PREMIUM_PRICE_ACK_REQUIRED =
FormField.named("premiumPriceAckRequired", Boolean.class).build();
private static Function<Map<String, ?>, RegistrarAddress> newAddressTransform( private static Function<Map<String, ?>, RegistrarAddress> newAddressTransform(
final FormField<List<String>, List<String>> streetField, final FormField<List<String>, List<String>> streetField,
final FormField<String, String> cityField, final FormField<String, String> cityField,

View file

@ -138,6 +138,7 @@ public final class ConsoleUiAction implements Runnable {
data.put("xsrfToken", xsrfTokenManager.generateToken(userAuthInfo.user().getEmail())); data.put("xsrfToken", xsrfTokenManager.generateToken(userAuthInfo.user().getEmail()));
data.put("clientId", clientId); data.put("clientId", clientId);
data.put("showPaymentLink", registrar.getBillingMethod() == Registrar.BillingMethod.BRAINTREE); data.put("showPaymentLink", registrar.getBillingMethod() == Registrar.BillingMethod.BRAINTREE);
data.put("requireFeeExtension", registrar.getPremiumPriceAckRequired());
String payload = TOFU_SUPPLIER.get() String payload = TOFU_SUPPLIER.get()
.newRenderer(ConsoleSoyInfo.MAIN) .newRenderer(ConsoleSoyInfo.MAIN)

View file

@ -157,7 +157,7 @@ public final class RegistrarPaymentAction implements Runnable, JsonAction {
@Override @Override
public Map<String, Object> handleJsonRequest(Map<String, ?> json) { public Map<String, Object> handleJsonRequest(Map<String, ?> json) {
Registrar registrar = sessionUtils.getRegistrarForAuthResult(request, authResult); Registrar registrar = sessionUtils.getRegistrarForAuthResult(request, authResult, false);
logger.infofmt("Processing payment: %s", json); logger.infofmt("Processing payment: %s", json);
String paymentMethodNonce; String paymentMethodNonce;
Money amount; Money amount;

View file

@ -91,7 +91,7 @@ public final class RegistrarPaymentSetupAction implements Runnable, JsonAction {
@Override @Override
public Map<String, Object> handleJsonRequest(Map<String, ?> json) { public Map<String, Object> handleJsonRequest(Map<String, ?> json) {
Registrar registrar = sessionUtils.getRegistrarForAuthResult(request, authResult); Registrar registrar = sessionUtils.getRegistrarForAuthResult(request, authResult, false);
if (!json.isEmpty()) { if (!json.isEmpty()) {
return JsonResponseHelper.create(ERROR, "JSON request object must be empty"); return JsonResponseHelper.create(ERROR, "JSON request object must be empty");

View file

@ -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.
*
* <p>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<String> registrarChangesNotificationEmailAddresses;
@Inject
RegistrarPremiumPriceAckAction() {}
@Override
public void run() {
jsonActionRunner.run(this);
}
@Override
public Map<String, Object> handleJsonRequest(Map<String, ?> 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<String, ?> args =
(Map<String, Object>)
Optional.<Object>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<String, Object> update(final Map<String, ?> args, final Registrar registrar) {
final String clientId = sessionUtils.getRegistrarClientId(request);
return ofy()
.transact(
() -> {
// Verify that the registrar hasn't been changed.
Optional<Registrar> 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<Boolean> 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<String> changedKeys = (Set<String>) 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));
}
}
}

View file

@ -67,8 +67,8 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
public static final String PATH = "/registrar-settings"; public static final String PATH = "/registrar-settings";
private static final String OP_PARAM = "op"; static final String OP_PARAM = "op";
private static final String ARGS_PARAM = "args"; static final String ARGS_PARAM = "args";
@Inject HttpServletRequest request; @Inject HttpServletRequest request;
@Inject JsonActionRunner jsonActionRunner; @Inject JsonActionRunner jsonActionRunner;
@ -93,7 +93,7 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
throw new BadRequestException("Malformed JSON"); 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 // Process the operation. Though originally derived from a CRUD
// handler, registrar-settings really only supports read and update. // handler, registrar-settings really only supports read and update.
String op = Optional.ofNullable((String) input.get(OP_PARAM)).orElse("read"); String op = Optional.ofNullable((String) input.get(OP_PARAM)).orElse("read");

View file

@ -48,7 +48,8 @@ public class SessionUtils {
@Config("registryAdminClientId") @Config("registryAdminClientId")
String registryAdminClientId; String registryAdminClientId;
@Inject public SessionUtils() {} @Inject
public SessionUtils() {}
/** /**
* Checks that the authentication result indicates a user that has access to the registrar * Checks that the authentication result indicates a user that has access to the registrar
@ -58,7 +59,8 @@ public class SessionUtils {
* the registrar console. * the registrar console.
*/ */
@CheckReturnValue @CheckReturnValue
Registrar getRegistrarForAuthResult(HttpServletRequest request, AuthResult authResult) { Registrar getRegistrarForAuthResult(
HttpServletRequest request, AuthResult authResult, boolean bypassCache) {
if (!authResult.userAuthInfo().isPresent()) { if (!authResult.userAuthInfo().isPresent()) {
throw new ForbiddenException("Not logged in"); throw new ForbiddenException("Not logged in");
} }
@ -67,7 +69,11 @@ public class SessionUtils {
} }
String clientId = getRegistrarClientId(request); String clientId = getRegistrarClientId(request);
return checkArgumentPresent( return checkArgumentPresent(
Registrar.loadByClientIdCached(clientId), "Registrar %s not found", clientId); bypassCache
? Registrar.loadByClientId(clientId)
: Registrar.loadByClientIdCached(clientId),
"Registrar %s not found",
clientId);
} }
/** /**
@ -78,8 +84,8 @@ public class SessionUtils {
* *
* <ul> * <ul>
* <li>If it does not exist, then we will attempt to guess the {@link Registrar} with which the * <li>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 * user is associated. The {@code clientId} of the first matching {@code Registrar} will
* be stored to the HTTP session. * then be stored to the HTTP session.
* <li>If it does exist, then we'll fetch the Registrar from Datastore to make sure access * <li>If it does exist, then we'll fetch the Registrar from Datastore to make sure access
* wasn't revoked. * wasn't revoked.
* </ul> * </ul>
@ -162,10 +168,8 @@ public class SessionUtils {
/** Returns first {@link Registrar} that {@code gaeUserId} is authorized to administer. */ /** Returns first {@link Registrar} that {@code gaeUserId} is authorized to administer. */
private static Optional<Registrar> findRegistrarForUser(String gaeUserId) { private static Optional<Registrar> findRegistrarForUser(String gaeUserId) {
RegistrarContact contact = ofy().load() RegistrarContact contact =
.type(RegistrarContact.class) ofy().load().type(RegistrarContact.class).filter("gaeUserId", gaeUserId).first().now();
.filter("gaeUserId", gaeUserId)
.first().now();
if (contact == null) { if (contact == null) {
return Optional.empty(); return Optional.empty();
} }

View file

@ -266,6 +266,8 @@
{template .resources} {template .resources}
{@param? driveFolderId: string} {@param? driveFolderId: string}
{@param technicalDocsUrl: string} {@param technicalDocsUrl: string}
{@param premiumPriceAckRequired: bool}
{@param readonly: bool}
<div id="domain-registrar-resources"> <div id="domain-registrar-resources">
<h1>Resources &amp; billing</h1> <h1>Resources &amp; billing</h1>
<p> <p>
@ -290,6 +292,15 @@
{else} {else}
<em>Your billing folder is pending allocation.</em> <em>Your billing folder is pending allocation.</em>
{/if} {/if}
<h2><img src="/assets/images/folder.png">EPP Settings</h2>
<p>Use the 'Edit' button above to switch to enable editing the information
below. <i>Updates may require up to 10 minutes to take effect.</i>
<form name="item" class="{css('item')} {css('registrar')}">
<input type="checkbox" id="premiumPriceAckRequired" name="premiumPriceAckRequired"
{if $premiumPriceAckRequired} checked{/if}
{if $readonly} disabled{/if}>
Require use of fee extension to acknowledge all premium domain prices.
</form>
</div> </div>
{/template} {/template}

View file

@ -14,6 +14,7 @@ PATH CLASS METHODS OK AUTH_METHODS
/registrar ConsoleUiAction GET n INTERNAL,API,LEGACY NONE PUBLIC /registrar ConsoleUiAction GET n INTERNAL,API,LEGACY NONE PUBLIC
/registrar-payment RegistrarPaymentAction POST n API,LEGACY USER PUBLIC /registrar-payment RegistrarPaymentAction POST n API,LEGACY USER PUBLIC
/registrar-payment-setup RegistrarPaymentSetupAction 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-settings RegistrarSettingsAction POST n API,LEGACY USER PUBLIC
/registrar-xhr EppConsoleAction POST n API,LEGACY USER PUBLIC /registrar-xhr EppConsoleAction POST n API,LEGACY USER PUBLIC
/whois/(*) WhoisHttpServer GET n INTERNAL NONE PUBLIC /whois/(*) WhoisHttpServer GET n INTERNAL NONE PUBLIC

View file

@ -128,11 +128,13 @@ function testNavToResources() {
registry.registrar.ConsoleTestUtil.visit(test, { registry.registrar.ConsoleTestUtil.visit(test, {
path: 'resources', path: 'resources',
xsrfToken: test.testXsrfToken, 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(); var xhr = goog.testing.net.XhrIo.getSendInstances().pop();
assertTrue(xhr.isActive()); assertTrue(xhr.isActive());
assertEquals('/registrar-settings', xhr.getLastUri()); assertEquals('/registrar-premium-price-ack', xhr.getLastUri());
assertEquals(test.testXsrfToken, assertEquals(test.testXsrfToken,
xhr.getLastRequestHeaders().get('X-CSRF-Token')); xhr.getLastRequestHeaders().get('X-CSRF-Token'));
xhr.simulateResponse(200, goog.json.serialize({ xhr.simulateResponse(200, goog.json.serialize({

View file

@ -19,6 +19,7 @@ import static google.registry.testing.DatastoreHelper.loadRegistrar;
import static google.registry.testing.ReflectiveFieldExtractor.extractField; import static google.registry.testing.ReflectiveFieldExtractor.extractField;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -93,7 +94,7 @@ public class RegistrarPaymentActionTest {
when(braintreeGateway.transaction()).thenReturn(transactionGateway); when(braintreeGateway.transaction()).thenReturn(transactionGateway);
when(transactionGateway.sale(any(TransactionRequest.class))).thenReturn(result); when(transactionGateway.sale(any(TransactionRequest.class))).thenReturn(result);
when(sessionUtils.getRegistrarForAuthResult( when(sessionUtils.getRegistrarForAuthResult(
any(HttpServletRequest.class), any(AuthResult.class))) any(HttpServletRequest.class), any(AuthResult.class), anyBoolean()))
.thenReturn(loadRegistrar("TheRegistrar")); .thenReturn(loadRegistrar("TheRegistrar"));
} }

View file

@ -19,6 +19,7 @@ import static google.registry.testing.DatastoreHelper.loadRegistrar;
import static google.registry.testing.DatastoreHelper.persistResource; import static google.registry.testing.DatastoreHelper.persistResource;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@ -68,7 +69,7 @@ public class RegistrarPaymentSetupActionTest {
.setBillingMethod(Registrar.BillingMethod.BRAINTREE) .setBillingMethod(Registrar.BillingMethod.BRAINTREE)
.build()); .build());
when(sessionUtils.getRegistrarForAuthResult( when(sessionUtils.getRegistrarForAuthResult(
any(HttpServletRequest.class), any(AuthResult.class))) any(HttpServletRequest.class), any(AuthResult.class), anyBoolean()))
.thenReturn(registrar); .thenReturn(registrar);
when(braintreeGateway.clientToken()).thenReturn(clientTokenGateway); when(braintreeGateway.clientToken()).thenReturn(clientTokenGateway);
} }
@ -111,7 +112,7 @@ public class RegistrarPaymentSetupActionTest {
.setBillingMethod(Registrar.BillingMethod.EXTERNAL) .setBillingMethod(Registrar.BillingMethod.EXTERNAL)
.build()); .build());
when(sessionUtils.getRegistrarForAuthResult( when(sessionUtils.getRegistrarForAuthResult(
any(HttpServletRequest.class), any(AuthResult.class))) any(HttpServletRequest.class), any(AuthResult.class), anyBoolean()))
.thenReturn(registrar); .thenReturn(registrar);
assertThat(action.handleJsonRequest(ImmutableMap.of())) assertThat(action.handleJsonRequest(ImmutableMap.of()))
.containsExactly( .containsExactly(

View file

@ -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<String, Object> reqJson = modified.toJsonMap();
Map<String, Object> 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();
}
}

View file

@ -22,6 +22,7 @@ import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued;
import static google.registry.util.ResourceUtils.readResourceUtf8; import static google.registry.util.ResourceUtils.readResourceUtf8;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@ -71,7 +72,7 @@ public class RegistrarSettingsActionTest extends RegistrarSettingsActionTestCase
@Test @Test
public void testRead_notAuthorized_failure() throws Exception { public void testRead_notAuthorized_failure() throws Exception {
when(sessionUtils.getRegistrarForAuthResult( 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")); .thenThrow(new ForbiddenException("Not authorized to access Registrar Console"));
assertThrows(ForbiddenException.class, () -> action.handleJsonRequest(ImmutableMap.of())); assertThrows(ForbiddenException.class, () -> action.handleJsonRequest(ImmutableMap.of()));
assertNoTasksEnqueued("sheet"); assertNoTasksEnqueued("sheet");

View file

@ -106,13 +106,13 @@ public class RegistrarSettingsActionTestCase {
when(rsp.getWriter()).thenReturn(new PrintWriter(writer)); when(rsp.getWriter()).thenReturn(new PrintWriter(writer));
when(req.getContentType()).thenReturn("application/json"); when(req.getContentType()).thenReturn("application/json");
when(req.getReader()).thenReturn(createJsonPayload(ImmutableMap.of("op", "read"))); 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)); .thenReturn(loadRegistrar(CLIENT_ID));
when(modulesService.getVersionHostname("backend", null)).thenReturn("backend.hostname"); when(modulesService.getVersionHostname("backend", null)).thenReturn("backend.hostname");
} }
protected Map<String, Object> readJsonFromFile(String filename) { static Map<String, Object> readJsonFromFile(String filename) {
String contents = readResourceUtf8(getClass(), filename); String contents = readResourceUtf8(RegistrarSettingsActionTestCase.class, filename);
try { try {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, Object> json = (Map<String, Object>) JSONValue.parseWithException(contents); Map<String, Object> json = (Map<String, Object>) JSONValue.parseWithException(contents);

View file

@ -112,7 +112,7 @@ public class SecuritySettingsTest extends RegistrarSettingsActionTestCase {
.setClientCertificate(SAMPLE_CERT, START_OF_TIME) .setClientCertificate(SAMPLE_CERT, START_OF_TIME)
.setFailoverClientCertificate(SAMPLE_CERT2, START_OF_TIME) .setFailoverClientCertificate(SAMPLE_CERT2, START_OF_TIME)
.build()); .build());
when(sessionUtils.getRegistrarForAuthResult(req, action.authResult)) when(sessionUtils.getRegistrarForAuthResult(req, action.authResult, false))
.thenReturn(initialRegistrar); .thenReturn(initialRegistrar);
Map<String, Object> jsonMap = initialRegistrar.toJsonMap(); Map<String, Object> jsonMap = initialRegistrar.toJsonMap();
jsonMap.put("clientCertificate", null); jsonMap.put("clientCertificate", null);

View file

@ -0,0 +1,2 @@
The following changes were made to the registrar:
premiumPriceAckRequired: false -> true