From ec9a220bc3184d2393a8a20cb891ed358cb654b9 Mon Sep 17 00:00:00 2001 From: Pavlo Tkach <3469726+ptkach@users.noreply.github.com> Date: Thu, 17 Aug 2023 10:17:23 -0400 Subject: [PATCH] Add console registrars screen API support to /console-api/registrars endpoint (#2095) --- .../model/adapters/CurrencyJsonAdapter.java | 41 ++++ .../model/adapters/CurrencyUnitAdapter.java | 18 +- .../registry/model/eppcommon/Address.java | 6 + .../registry/model/registrar/Registrar.java | 17 +- .../registry/request/RequestModule.java | 19 ++ .../ui/server/console/RegistrarsAction.java | 125 +++++++++++- .../console/settings/SecurityAction.java | 44 +---- .../console/ConsoleDomainGetActionTest.java | 4 +- .../server/console/RegistrarsActionTest.java | 184 +++++++++++++++++- .../console/settings/ContactActionTest.java | 3 +- .../console/settings/SecurityActionTest.java | 56 +----- .../module/frontend/frontend_routing.txt | 4 +- .../google/registry/util/UtilsModule.java | 13 -- 13 files changed, 403 insertions(+), 131 deletions(-) create mode 100644 core/src/main/java/google/registry/model/adapters/CurrencyJsonAdapter.java diff --git a/core/src/main/java/google/registry/model/adapters/CurrencyJsonAdapter.java b/core/src/main/java/google/registry/model/adapters/CurrencyJsonAdapter.java new file mode 100644 index 000000000..0efeb8044 --- /dev/null +++ b/core/src/main/java/google/registry/model/adapters/CurrencyJsonAdapter.java @@ -0,0 +1,41 @@ +// 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 com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import google.registry.model.adapters.CurrencyUnitAdapter.UnknownCurrencyException; +import java.io.IOException; +import org.joda.money.CurrencyUnit; + +public class CurrencyJsonAdapter extends TypeAdapter { + + @Override + public void write(JsonWriter out, CurrencyUnit value) throws IOException { + String currency = CurrencyUnitAdapter.convertFromCurrency(value); + out.value(currency); + } + + @Override + public CurrencyUnit read(JsonReader in) throws IOException { + String currency = in.nextString(); + try { + return CurrencyUnitAdapter.convertFromString(currency); + } catch (UnknownCurrencyException e) { + throw new IOException("Unknown currency"); + } + } +} diff --git a/core/src/main/java/google/registry/model/adapters/CurrencyUnitAdapter.java b/core/src/main/java/google/registry/model/adapters/CurrencyUnitAdapter.java index 7668662cd..f2bcd5b29 100644 --- a/core/src/main/java/google/registry/model/adapters/CurrencyUnitAdapter.java +++ b/core/src/main/java/google/registry/model/adapters/CurrencyUnitAdapter.java @@ -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 { - /** 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 { } } + 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. */ diff --git a/core/src/main/java/google/registry/model/eppcommon/Address.java b/core/src/main/java/google/registry/model/eppcommon/Address.java index 3e656be76..91b97b88c 100644 --- a/core/src/main/java/google/registry/model/eppcommon/Address.java +++ b/core/src/main/java/google/registry/model/eppcommon/Address.java @@ -20,6 +20,7 @@ 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; @@ -87,6 +88,7 @@ public class Address extends ImmutableObject implements Jsonifiable, UnsafeSeria */ @XmlJavaTypeAdapter(NormalizedStringAdapter.class) @Transient + @Expose protected List street; @XmlTransient @IgnoredInDiffableMap protected String streetLine1; @@ -96,18 +98,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 getStreet() { diff --git a/core/src/main/java/google/registry/model/registrar/Registrar.java b/core/src/main/java/google/registry/model/registrar/Registrar.java index 6e837ca97..92f60e8fc 100644 --- a/core/src/main/java/google/registry/model/registrar/Registrar.java +++ b/core/src/main/java/google/registry/model/registrar/Registrar.java @@ -211,6 +211,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J */ @Id @Column(nullable = false) + @Expose String registrarId; /** @@ -224,6 +225,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J * @see ICANN-Accredited * Registrars */ + @Expose @Column(nullable = false) String registrarName; @@ -237,7 +239,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J State state; /** The set of TLDs which this registrar is allowed to access. */ - Set allowedTlds; + @Expose Set allowedTlds; /** Host name of WHOIS server. */ String whoisServer; @@ -287,6 +289,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J * unrestricted UTF-8. */ @Embedded + @Expose @AttributeOverrides({ @AttributeOverride( name = "streetLine1", @@ -329,7 +332,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J String faxNumber; /** Email address. */ - String emailAddress; + @Expose String emailAddress; // External IDs. @@ -345,7 +348,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J * @see Registrar * IDs */ - @Nullable Long ianaIdentifier; + @Expose @Nullable Long ianaIdentifier; /** Purchase Order number used for invoices in external billing system, if applicable. */ @Nullable String poNumber; @@ -358,7 +361,7 @@ 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 billingAccountMap; + @Expose @Nullable Map billingAccountMap; /** URL of registrar's website. */ String url; @@ -369,10 +372,10 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J *

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 +403,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; diff --git a/core/src/main/java/google/registry/request/RequestModule.java b/core/src/main/java/google/registry/request/RequestModule.java index a66f0f8a2..5406f49a4 100644 --- a/core/src/main/java/google/registry/request/RequestModule.java +++ b/core/src/main/java/google/registry/request/RequestModule.java @@ -31,21 +31,28 @@ 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.GsonBuilder; import com.google.gson.JsonObject; import com.google.protobuf.ByteString; import dagger.Module; import dagger.Provides; +import google.registry.model.adapters.CurrencyJsonAdapter; import google.registry.request.HttpException.BadRequestException; import google.registry.request.HttpException.UnsupportedMediaTypeException; import google.registry.request.auth.AuthResult; import google.registry.request.lock.LockHandler; import google.registry.request.lock.LockHandlerImpl; +import google.registry.util.CidrAddressBlock; +import google.registry.util.CidrAddressBlock.CidrAddressBlockAdapter; +import google.registry.util.DateTimeTypeAdapter; import java.io.IOException; import java.util.Map; import java.util.Optional; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import org.joda.money.CurrencyUnit; +import org.joda.time.DateTime; import org.json.simple.JSONValue; import org.json.simple.parser.ParseException; @@ -69,6 +76,18 @@ public final class RequestModule { this.authResult = authResult; } + @RequestScope + @VisibleForTesting + @Provides + public static Gson provideGson() { + return new GsonBuilder() + .registerTypeAdapter(DateTime.class, new DateTimeTypeAdapter()) + .registerTypeAdapter(CidrAddressBlock.class, new CidrAddressBlockAdapter()) + .registerTypeAdapter(CurrencyUnit.class, new CurrencyJsonAdapter()) + .excludeFieldsWithoutExposeAnnotation() + .create(); + } + @Provides @Parameter(RequestParameters.PARAM_TLD) static String provideTld(HttpServletRequest req) { diff --git a/core/src/main/java/google/registry/ui/server/console/RegistrarsAction.java b/core/src/main/java/google/registry/ui/server/console/RegistrarsAction.java index 18ba31ec1..93b2ac16f 100644 --- a/core/src/main/java/google/registry/ui/server/console/RegistrarsAction.java +++ b/core/src/main/java/google/registry/ui/server/console/RegistrarsAction.java @@ -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; + 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, + @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 registrarIds = + ImmutableList 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())); + } + } } diff --git a/core/src/main/java/google/registry/ui/server/console/settings/SecurityAction.java b/core/src/main/java/google/registry/ui/server/console/settings/SecurityAction.java index 0e69da19b..7cd251576 100644 --- a/core/src/main/java/google/registry/ui/server/console/settings/SecurityAction.java +++ b/core/src/main/java/google/registry/ui/server/console/settings/SecurityAction.java @@ -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; - private CertificateChecker certificateChecker; + private final AuthenticatedRegistrarAccessor registrarAccessor; + private final Optional 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) { - 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); diff --git a/core/src/test/java/google/registry/ui/server/console/ConsoleDomainGetActionTest.java b/core/src/test/java/google/registry/ui/server/console/ConsoleDomainGetActionTest.java index 9a64f69e1..94fc4022f 100644 --- a/core/src/test/java/google/registry/ui/server/console/ConsoleDomainGetActionTest.java +++ b/core/src/test/java/google/registry/ui/server/console/ConsoleDomainGetActionTest.java @@ -25,12 +25,12 @@ import google.registry.model.console.RegistrarRole; import google.registry.model.console.User; import google.registry.model.console.UserRoles; import google.registry.persistence.transaction.JpaTestExtensions; +import google.registry.request.RequestModule; import google.registry.request.auth.AuthResult; import google.registry.request.auth.AuthSettings.AuthLevel; import google.registry.request.auth.UserAuthInfo; import google.registry.testing.DatabaseHelper; import google.registry.testing.FakeResponse; -import google.registry.util.UtilsModule; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -38,7 +38,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; /** Tests for {@link google.registry.ui.server.console.ConsoleDomainGetAction}. */ public class ConsoleDomainGetActionTest { - private static final Gson GSON = UtilsModule.provideGson(); + private static final Gson GSON = RequestModule.provideGson(); private static final FakeResponse RESPONSE = new FakeResponse(); @RegisterExtension diff --git a/core/src/test/java/google/registry/ui/server/console/RegistrarsActionTest.java b/core/src/test/java/google/registry/ui/server/console/RegistrarsActionTest.java index f0cc5de2f..8aaf16f03 100644 --- a/core/src/test/java/google/registry/ui/server/console/RegistrarsActionTest.java +++ b/core/src/test/java/google/registry/ui/server/console/RegistrarsActionTest.java @@ -15,11 +15,17 @@ package google.registry.ui.server.console; import static com.google.common.truth.Truth.assertThat; +import static google.registry.testing.DatabaseHelper.loadAllOf; +import static google.registry.testing.DatabaseHelper.loadRegistrar; import static google.registry.testing.DatabaseHelper.persistNewRegistrar; import static google.registry.testing.DatabaseHelper.persistResource; import static google.registry.testing.SqlHelper.saveRegistrar; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.google.api.client.http.HttpStatusCodes; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import google.registry.model.console.GlobalRole; @@ -27,21 +33,69 @@ import google.registry.model.console.RegistrarRole; import google.registry.model.console.User; import google.registry.model.console.UserRoles; import google.registry.model.registrar.Registrar; +import google.registry.model.registrar.RegistrarPoc; import google.registry.persistence.transaction.JpaTestExtensions; +import google.registry.request.Action; +import google.registry.request.RequestModule; import google.registry.request.auth.AuthResult; import google.registry.request.auth.AuthSettings.AuthLevel; import google.registry.request.auth.UserAuthInfo; +import google.registry.testing.DeterministicStringGenerator; import google.registry.testing.FakeResponse; -import google.registry.util.UtilsModule; +import google.registry.ui.server.registrar.RegistrarConsoleModule; +import google.registry.util.StringGenerator; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletRequest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; /** Tests for {@link google.registry.ui.server.console.RegistrarsAction}. */ class RegistrarsActionTest { - private static final Gson GSON = UtilsModule.provideGson(); + private final HttpServletRequest request = mock(HttpServletRequest.class); + private static final Gson GSON = RequestModule.provideGson(); private FakeResponse response; + private StringGenerator passwordGenerator = + new DeterministicStringGenerator("abcdefghijklmnopqrstuvwxyz"); + private StringGenerator passcodeGenerator = new DeterministicStringGenerator("314159265"); + + private ImmutableMap userFriendlyKeysToRegistrarKeys = + ImmutableMap.of( + "registrarId", "registrarId", + "registrarName", "name", + "billingAccountMap", "billingAccount", + "ianaIdentifier", "ianaId", + "icannReferralEmail", "referralEmail", + "driveFolderId", "driveId", + "emailAddress", "consoleUserEmail", + "localizedAddress", "address"); + + private ImmutableMap registrarParamMap = + ImmutableMap.of( + "registrarId", + "regIdTest", + "registrarName", + "name", + "billingAccountMap", + "{\"USD\": \"789\"}", + "ianaIdentifier", + "123", + "icannReferralEmail", + "cannReferralEmail@gmail.com", + "driveFolderId", + "testDriveId", + "emailAddress", + "testEmailAddress@gmail.com", + "localizedAddress", + "{ \"street\": [\"test street\"], \"city\": \"test city\", \"state\": \"test state\"," + + " \"zip\": \"00700\", \"countryCode\": \"US\" }"); + @RegisterExtension final JpaTestExtensions.JpaIntegrationTestExtension jpa = new JpaTestExtensions.Builder().buildIntegrationTestExtension(); @@ -53,6 +107,7 @@ class RegistrarsActionTest { persistResource(registrar); RegistrarsAction action = createAction( + Action.Method.GET, AuthResult.create( AuthLevel.USER, UserAuthInfo.create( @@ -60,22 +115,98 @@ class RegistrarsActionTest { new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_LEAD).build())))); action.run(); assertThat(response.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_OK); - assertThat(response.getPayload()).isEqualTo("[\"NewRegistrar\",\"TheRegistrar\"]"); + String payload = response.getPayload(); + assertThat( + ImmutableList.of("\"registrarId\":\"NewRegistrar\"", "\"registrarId\":\"TheRegistrar\"") + .stream() + .allMatch(s -> payload.contains(s))) + .isTrue(); } @Test - void testSuccess_getRegistrarIds() { + void testSuccess_getRegistrars() { saveRegistrar("registrarId"); RegistrarsAction action = createAction( + Action.Method.GET, AuthResult.create( AuthLevel.USER, UserAuthInfo.create( createUser(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())))); action.run(); assertThat(response.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_OK); + String payload = response.getPayload(); + assertThat( + ImmutableList.of( + "\"registrarId\":\"NewRegistrar\"", + "\"registrarId\":\"TheRegistrar\"", + "\"registrarId\":\"registrarId\"") + .stream() + .allMatch(s -> payload.contains(s))) + .isTrue(); + } + + @Test + void testSuccess_createRegistrar() { + RegistrarsAction action = + createAction( + Action.Method.POST, + AuthResult.create( + AuthLevel.USER, + UserAuthInfo.create(createUser(new UserRoles.Builder().setIsAdmin(true).build())))); + action.run(); + assertThat(response.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_OK); + Registrar r = loadRegistrar("regIdTest"); + assertThat(r).isNotNull(); + assertThat( + loadAllOf(RegistrarPoc.class).stream() + .filter(rPOC -> rPOC.getEmailAddress().equals("testEmailAddress@gmail.com")) + .findAny() + .isPresent()) + .isTrue(); + } + + @Test + void testFailure_createRegistrar_missingValue() { + ImmutableMap copy = ImmutableMap.copyOf(registrarParamMap); + copy.keySet() + .forEach( + key -> { + registrarParamMap = + ImmutableMap.copyOf( + copy.entrySet().stream() + .filter(entry -> !entry.getKey().equals(key)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + RegistrarsAction action = + createAction( + Action.Method.POST, + AuthResult.create( + AuthLevel.USER, + UserAuthInfo.create( + createUser(new UserRoles.Builder().setIsAdmin(true).build())))); + action.run(); + assertThat(response.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_BAD_REQUEST); + assertThat(response.getPayload()) + .isEqualTo( + GSON.toJson( + String.format( + "Missing value for %s", userFriendlyKeysToRegistrarKeys.get(key)))); + }); + } + + @Test + void testFailure_createRegistrar_existingRegistrar() { + saveRegistrar("regIdTest"); + RegistrarsAction action = + createAction( + Action.Method.POST, + AuthResult.create( + AuthLevel.USER, + UserAuthInfo.create(createUser(new UserRoles.Builder().setIsAdmin(true).build())))); + action.run(); + assertThat(response.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_BAD_REQUEST); assertThat(response.getPayload()) - .isEqualTo("[\"NewRegistrar\",\"TheRegistrar\",\"registrarId\"]"); + .isEqualTo(GSON.toJson("Registrar with registrarId regIdTest already exists")); } @Test @@ -83,6 +214,7 @@ class RegistrarsActionTest { saveRegistrar("registrarId"); RegistrarsAction action = createAction( + Action.Method.GET, AuthResult.create( AuthLevel.USER, UserAuthInfo.create( @@ -105,8 +237,46 @@ class RegistrarsActionTest { .build(); } - private RegistrarsAction createAction(AuthResult authResult) { + private RegistrarsAction createAction(Action.Method method, AuthResult authResult) { response = new FakeResponse(); - return new RegistrarsAction(authResult, response, GSON); + when(request.getMethod()).thenReturn(method.toString()); + if (method.equals(Action.Method.GET)) { + return new RegistrarsAction( + request, + authResult, + response, + GSON, + Optional.ofNullable(null), + passwordGenerator, + passcodeGenerator); + } else { + try { + doReturn( + new BufferedReader( + new StringReader("{\"registrar\":" + registrarParamMap.toString() + "}"))) + .when(request) + .getReader(); + } catch (IOException e) { + return new RegistrarsAction( + request, + authResult, + response, + GSON, + Optional.ofNullable(null), + passwordGenerator, + passcodeGenerator); + } + Optional maybeRegistrar = + RegistrarConsoleModule.provideRegistrar( + GSON, RequestModule.provideJsonBody(request, GSON)); + return new RegistrarsAction( + request, + authResult, + response, + GSON, + maybeRegistrar, + passwordGenerator, + passcodeGenerator); + } } } diff --git a/core/src/test/java/google/registry/ui/server/console/settings/ContactActionTest.java b/core/src/test/java/google/registry/ui/server/console/settings/ContactActionTest.java index 44c87d080..ff0d39787 100644 --- a/core/src/test/java/google/registry/ui/server/console/settings/ContactActionTest.java +++ b/core/src/test/java/google/registry/ui/server/console/settings/ContactActionTest.java @@ -41,7 +41,6 @@ import google.registry.request.auth.AuthSettings.AuthLevel; import google.registry.request.auth.UserAuthInfo; import google.registry.testing.FakeResponse; import google.registry.ui.server.registrar.RegistrarConsoleModule; -import google.registry.util.UtilsModule; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; @@ -73,7 +72,7 @@ class ContactActionTest { private Registrar testRegistrar; private final HttpServletRequest request = mock(HttpServletRequest.class); private RegistrarPoc testRegistrarPoc; - private static final Gson GSON = UtilsModule.provideGson(); + private static final Gson GSON = RequestModule.provideGson(); private FakeResponse response; @RegisterExtension diff --git a/core/src/test/java/google/registry/ui/server/console/settings/SecurityActionTest.java b/core/src/test/java/google/registry/ui/server/console/settings/SecurityActionTest.java index b422a191e..dd70a5af8 100644 --- a/core/src/test/java/google/registry/ui/server/console/settings/SecurityActionTest.java +++ b/core/src/test/java/google/registry/ui/server/console/settings/SecurityActionTest.java @@ -15,21 +15,17 @@ package google.registry.ui.server.console.settings; import static com.google.common.truth.Truth.assertThat; -import static google.registry.testing.CertificateSamples.SAMPLE_CERT; import static google.registry.testing.CertificateSamples.SAMPLE_CERT2; import static google.registry.testing.DatabaseHelper.loadRegistrar; -import static google.registry.testing.DatabaseHelper.persistResource; import static google.registry.testing.SqlHelper.saveRegistrar; import static google.registry.util.DateTimeUtils.START_OF_TIME; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import com.google.api.client.http.HttpStatusCodes; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.ImmutableSortedMap; -import com.google.common.net.InetAddresses; import com.google.gson.Gson; import google.registry.flows.certs.CertificateChecker; import google.registry.model.console.GlobalRole; @@ -37,7 +33,6 @@ import google.registry.model.console.User; import google.registry.model.console.UserRoles; import google.registry.model.registrar.Registrar; import google.registry.persistence.transaction.JpaTestExtensions; -import google.registry.request.Action; import google.registry.request.RequestModule; import google.registry.request.auth.AuthResult; import google.registry.request.auth.AuthSettings.AuthLevel; @@ -46,8 +41,6 @@ import google.registry.request.auth.UserAuthInfo; import google.registry.testing.FakeClock; import google.registry.testing.FakeResponse; import google.registry.ui.server.registrar.RegistrarConsoleModule; -import google.registry.util.CidrAddressBlock; -import google.registry.util.UtilsModule; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; @@ -66,7 +59,7 @@ class SecurityActionTest { "{\"registrarId\": \"registrarId\", \"clientCertificate\": \"%s\"," + " \"ipAddressAllowList\": [\"192.168.1.1/32\"]}", SAMPLE_CERT2); - private static final Gson GSON = UtilsModule.provideGson(); + private static final Gson GSON = RequestModule.provideGson(); private final HttpServletRequest request = mock(HttpServletRequest.class); private final FakeClock clock = new FakeClock(); private Registrar testRegistrar; @@ -94,39 +87,11 @@ class SecurityActionTest { testRegistrar = saveRegistrar("registrarId"); } - @Test - void testSuccess_getRegistrarInfo() throws IOException { - persistResource( - testRegistrar - .asBuilder() - .setClientCertificate(SAMPLE_CERT, clock.nowUtc()) - .setIpAddressAllowList( - ImmutableSet.of( - CidrAddressBlock.create(InetAddresses.forString("192.168.1.1"), 32), - CidrAddressBlock.create(InetAddresses.forString("2001:db8::1"), 128))) - .build()); - SecurityAction action = - createAction( - Action.Method.GET, - AuthResult.create( - AuthLevel.USER, - UserAuthInfo.create( - createUser(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build()))), - testRegistrar.getRegistrarId()); - action.run(); - assertThat(response.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_OK); - String payload = response.getPayload().replace("\\n", "").replace("\\u003d", "="); - assertThat(payload).contains(SAMPLE_CERT.replace("\n", "")); - assertThat(payload).contains("192.168.1.1/32"); - assertThat(payload).contains("2001:db8:0:0:0:0:0:1/128"); - } - @Test void testSuccess_postRegistrarInfo() throws IOException { clock.setTo(DateTime.parse("2020-11-01T00:00:00Z")); SecurityAction action = createAction( - Action.Method.POST, AuthResult.create( AuthLevel.USER, UserAuthInfo.create( @@ -149,20 +114,8 @@ class SecurityActionTest { .build(); } - private SecurityAction createAction( - Action.Method method, AuthResult authResult, String registrarId) throws IOException { - when(request.getMethod()).thenReturn(method.toString()); - if (method.equals(Action.Method.GET)) { - return new SecurityAction( - request, - authResult, - response, - GSON, - certificateChecker, - registrarAccessor, - registrarId, - Optional.empty()); - } else { + private SecurityAction createAction(AuthResult authResult, String registrarId) + throws IOException { doReturn(new BufferedReader(new StringReader("{\"registrar\":" + jsonRegistrar1 + "}"))) .when(request) .getReader(); @@ -170,7 +123,6 @@ class SecurityActionTest { RegistrarConsoleModule.provideRegistrar( GSON, RequestModule.provideJsonBody(request, GSON)); return new SecurityAction( - request, authResult, response, GSON, @@ -178,6 +130,6 @@ class SecurityActionTest { registrarAccessor, registrarId, maybeRegistrar); - } + } } diff --git a/core/src/test/resources/google/registry/module/frontend/frontend_routing.txt b/core/src/test/resources/google/registry/module/frontend/frontend_routing.txt index 731e191eb..f2ec348b3 100644 --- a/core/src/test/resources/google/registry/module/frontend/frontend_routing.txt +++ b/core/src/test/resources/google/registry/module/frontend/frontend_routing.txt @@ -1,9 +1,9 @@ PATH CLASS METHODS OK AUTH_METHODS MIN USER_POLICY /_dr/epp EppTlsAction POST n API APP PUBLIC /console-api/domain ConsoleDomainGetAction GET n API,LEGACY USER PUBLIC -/console-api/registrars RegistrarsAction GET n API,LEGACY USER PUBLIC +/console-api/registrars RegistrarsAction GET,POST n API,LEGACY USER PUBLIC /console-api/settings/contacts ContactAction GET,POST n API,LEGACY USER PUBLIC -/console-api/settings/security SecurityAction GET,POST n API,LEGACY USER PUBLIC +/console-api/settings/security SecurityAction POST n API,LEGACY USER PUBLIC /registrar ConsoleUiAction GET n API,LEGACY NONE PUBLIC /registrar-create ConsoleRegistrarCreatorAction POST,GET n API,LEGACY NONE PUBLIC /registrar-ote-setup ConsoleOteSetupAction POST,GET n API,LEGACY NONE PUBLIC diff --git a/util/src/main/java/google/registry/util/UtilsModule.java b/util/src/main/java/google/registry/util/UtilsModule.java index 4ce5e5679..5f0e6d3f6 100644 --- a/util/src/main/java/google/registry/util/UtilsModule.java +++ b/util/src/main/java/google/registry/util/UtilsModule.java @@ -14,19 +14,15 @@ package google.registry.util; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import dagger.Binds; import dagger.Module; import dagger.Provides; -import google.registry.util.CidrAddressBlock.CidrAddressBlockAdapter; import java.security.NoSuchAlgorithmException; import java.security.ProviderException; import java.security.SecureRandom; import java.util.Random; import javax.inject.Named; import javax.inject.Singleton; -import org.joda.time.DateTime; /** Dagger module to provide instances of various utils classes. */ @Module @@ -75,13 +71,4 @@ public abstract class UtilsModule { return new RandomStringGenerator(StringGenerator.Alphabets.DIGITS_ONLY, secureRandom); } - @Singleton - @Provides - public static Gson provideGson() { - return new GsonBuilder() - .registerTypeAdapter(DateTime.class, new DateTimeTypeAdapter()) - .registerTypeAdapter(CidrAddressBlock.class, new CidrAddressBlockAdapter()) - .excludeFieldsWithoutExposeAnnotation() - .create(); - } }