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 91b97b88c..dcd3ec7fb 100644 --- a/core/src/main/java/google/registry/model/eppcommon/Address.java +++ b/core/src/main/java/google/registry/model/eppcommon/Address.java @@ -26,6 +26,7 @@ import google.registry.model.ImmutableObject; import google.registry.model.JsonMapBuilder; import google.registry.model.Jsonifiable; import google.registry.model.UnsafeSerializable; +import google.registry.tools.GsonUtils.GsonPostProcessable; import java.util.List; import java.util.Map; import java.util.Objects; @@ -55,7 +56,8 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; @XmlTransient @Embeddable @MappedSuperclass -public class Address extends ImmutableObject implements Jsonifiable, UnsafeSerializable { +public class Address extends ImmutableObject + implements Jsonifiable, UnsafeSerializable, GsonPostProcessable { /** * At most three lines of addresses parsed from XML elements. @@ -152,6 +154,16 @@ public class Address extends ImmutableObject implements Jsonifiable, UnsafeSeria return new Builder<>(clone(this)); } + @Override + public void postProcess() { + if (street == null || street.isEmpty()) { + return; + } + streetLine1 = street.get(0); + streetLine2 = street.size() >= 2 ? street.get(1) : null; + streetLine3 = street.size() >= 3 ? street.get(2) : null; + } + /** A builder for constructing {@link Address}. */ public static class Builder extends Buildable.Builder { @@ -228,11 +240,6 @@ public class Address extends ImmutableObject implements Jsonifiable, UnsafeSeria */ @SuppressWarnings("unused") void afterUnmarshal(Unmarshaller unmarshaller, Object parent) { - if (street == null || street.isEmpty()) { - return; - } - streetLine1 = street.get(0); - streetLine2 = street.size() >= 2 ? street.get(1) : null; - streetLine3 = street.size() >= 3 ? street.get(2) : null; + postProcess(); } } 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 92f60e8fc..b1eea643a 100644 --- a/core/src/main/java/google/registry/model/registrar/Registrar.java +++ b/core/src/main/java/google/registry/model/registrar/Registrar.java @@ -242,7 +242,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J @Expose Set allowedTlds; /** Host name of WHOIS server. */ - String whoisServer; + @Expose String whoisServer; /** Base URLs for the registrar's RDAP servers. */ Set rdapBaseUrls; @@ -326,10 +326,10 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J RegistrarAddress internationalizedAddress; /** Voice number. */ - String phoneNumber; + @Expose String phoneNumber; /** Fax number. */ - String faxNumber; + @Expose String faxNumber; /** Email address. */ @Expose String emailAddress; @@ -364,7 +364,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J @Expose @Nullable Map billingAccountMap; /** URL of registrar's website. */ - String url; + @Expose String url; /** * ICANN referral email address. diff --git a/core/src/main/java/google/registry/module/frontend/FrontendRequestComponent.java b/core/src/main/java/google/registry/module/frontend/FrontendRequestComponent.java index 53841e6ac..f9cb09fe8 100644 --- a/core/src/main/java/google/registry/module/frontend/FrontendRequestComponent.java +++ b/core/src/main/java/google/registry/module/frontend/FrontendRequestComponent.java @@ -29,6 +29,7 @@ import google.registry.ui.server.console.ConsoleDomainGetAction; import google.registry.ui.server.console.RegistrarsAction; import google.registry.ui.server.console.settings.ContactAction; import google.registry.ui.server.console.settings.SecurityAction; +import google.registry.ui.server.console.settings.WhoisRegistrarFieldsAction; import google.registry.ui.server.registrar.ConsoleOteSetupAction; import google.registry.ui.server.registrar.ConsoleRegistrarCreatorAction; import google.registry.ui.server.registrar.ConsoleUiAction; @@ -73,6 +74,8 @@ interface FrontendRequestComponent { SecurityAction securityAction(); + WhoisRegistrarFieldsAction whoisRegistrarFieldsAction(); + @Subcomponent.Builder abstract class Builder implements RequestComponentBuilder { @Override public abstract Builder requestModule(RequestModule requestModule); diff --git a/core/src/main/java/google/registry/request/RequestModule.java b/core/src/main/java/google/registry/request/RequestModule.java index 6a56fb945..0699e2644 100644 --- a/core/src/main/java/google/registry/request/RequestModule.java +++ b/core/src/main/java/google/registry/request/RequestModule.java @@ -31,28 +31,22 @@ 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.JsonElement; 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 google.registry.tools.GsonUtils; 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; @@ -80,12 +74,7 @@ public final class RequestModule { @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(); + return GsonUtils.provideGson(); } @Provides @@ -265,7 +254,8 @@ public final class RequestModule { @OptionalJsonPayload public static Optional provideJsonBody(HttpServletRequest req, Gson gson) { try { - return Optional.of(gson.fromJson(req.getReader(), JsonElement.class)); + // GET requests return a null reader and thus a null JsonObject, which is fine + return Optional.ofNullable(gson.fromJson(req.getReader(), JsonElement.class)); } catch (IOException e) { return Optional.empty(); } diff --git a/core/src/main/java/google/registry/tools/GsonUtils.java b/core/src/main/java/google/registry/tools/GsonUtils.java new file mode 100644 index 000000000..be85145e2 --- /dev/null +++ b/core/src/main/java/google/registry/tools/GsonUtils.java @@ -0,0 +1,81 @@ +// Copyright 2017 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google.registry.tools; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import google.registry.model.adapters.CurrencyJsonAdapter; +import google.registry.util.CidrAddressBlock; +import google.registry.util.CidrAddressBlock.CidrAddressBlockAdapter; +import google.registry.util.DateTimeTypeAdapter; +import java.io.IOException; +import org.joda.money.CurrencyUnit; +import org.joda.time.DateTime; + +/** Utility class for methods related to GSON and necessary GSON processing. */ +public class GsonUtils { + + /** Interface to enable GSON post-processing on a particular object after deserialization. */ + public interface GsonPostProcessable { + void postProcess(); + } + + /** + * Some objects may require post-processing after deserialization from JSON. + * + *

We do this upon deserialization in order to make sure that the object matches the format + * that we expect to be stored in the database. See {@link + * google.registry.model.eppcommon.Address} for an example. + */ + public static class GsonPostProcessableTypeAdapterFactory implements TypeAdapterFactory { + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + TypeAdapter originalAdapter = gson.getDelegateAdapter(this, type); + if (!GsonPostProcessable.class.isAssignableFrom(type.getRawType())) { + return originalAdapter; + } + return new TypeAdapter() { + @Override + public void write(JsonWriter out, T value) throws IOException { + originalAdapter.write(out, value); + } + + @Override + public T read(JsonReader in) throws IOException { + T t = originalAdapter.read(in); + ((GsonPostProcessable) t).postProcess(); + return t; + } + }; + } + } + + public static Gson provideGson() { + return new GsonBuilder() + .registerTypeAdapter(DateTime.class, new DateTimeTypeAdapter()) + .registerTypeAdapter(CidrAddressBlock.class, new CidrAddressBlockAdapter()) + .registerTypeAdapter(CurrencyUnit.class, new CurrencyJsonAdapter()) + .registerTypeAdapterFactory(new GsonPostProcessableTypeAdapterFactory()) + .excludeFieldsWithoutExposeAnnotation() + .create(); + } + + private GsonUtils() {} +} diff --git a/core/src/main/java/google/registry/ui/server/console/settings/ContactAction.java b/core/src/main/java/google/registry/ui/server/console/settings/ContactAction.java index 6f770cb04..e0cd3b35f 100644 --- a/core/src/main/java/google/registry/ui/server/console/settings/ContactAction.java +++ b/core/src/main/java/google/registry/ui/server/console/settings/ContactAction.java @@ -131,7 +131,7 @@ public class ContactAction implements JsonGetAction { oldContacts, Collections.singletonMap( "contacts", - contacts.get().stream().map(c -> c.toJsonMap()).collect(toImmutableList()))); + contacts.get().stream().map(RegistrarPoc::toJsonMap).collect(toImmutableList()))); try { RegistrarSettingsAction.checkContactRequirements(oldContacts, updatedContacts); } catch (FormException e) { diff --git a/core/src/main/java/google/registry/ui/server/console/settings/WhoisRegistrarFieldsAction.java b/core/src/main/java/google/registry/ui/server/console/settings/WhoisRegistrarFieldsAction.java new file mode 100644 index 000000000..4b37ce5b1 --- /dev/null +++ b/core/src/main/java/google/registry/ui/server/console/settings/WhoisRegistrarFieldsAction.java @@ -0,0 +1,107 @@ +// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google.registry.ui.server.console.settings; + +import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import static google.registry.request.Action.Method.POST; + +import com.google.api.client.http.HttpStatusCodes; +import com.google.gson.Gson; +import google.registry.model.console.ConsolePermission; +import google.registry.model.console.User; +import google.registry.model.registrar.Registrar; +import google.registry.request.Action; +import google.registry.request.Parameter; +import google.registry.request.Response; +import google.registry.request.auth.Auth; +import google.registry.request.auth.AuthResult; +import google.registry.request.auth.AuthenticatedRegistrarAccessor; +import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException; +import google.registry.ui.server.registrar.JsonGetAction; +import java.util.Optional; +import javax.inject.Inject; + +/** + * Console action for editing fields on a registrar that are visible in WHOIS/RDAP. + * + *

This doesn't cover many of the registrar fields but rather only those that are visible in + * WHOIS/RDAP and don't have any other obvious means of edit. + */ +@Action( + service = Action.Service.DEFAULT, + path = WhoisRegistrarFieldsAction.PATH, + method = {POST}, + auth = Auth.AUTH_PUBLIC_LOGGED_IN) +public class WhoisRegistrarFieldsAction implements JsonGetAction { + + static final String PATH = "/console-api/settings/whois-fields"; + private final AuthResult authResult; + private final Response response; + private final Gson gson; + private AuthenticatedRegistrarAccessor registrarAccessor; + private Optional registrar; + + @Inject + public WhoisRegistrarFieldsAction( + AuthResult authResult, + Response response, + Gson gson, + AuthenticatedRegistrarAccessor registrarAccessor, + @Parameter("registrar") Optional registrar) { + this.authResult = authResult; + this.response = response; + this.gson = gson; + this.registrarAccessor = registrarAccessor; + this.registrar = registrar; + } + + @Override + public void run() { + if (!registrar.isPresent()) { + response.setStatus(HttpStatusCodes.STATUS_CODE_BAD_REQUEST); + response.setPayload(gson.toJson("'registrar' parameter is not present")); + return; + } + + User user = authResult.userAuthInfo().get().consoleUser().get(); + if (!user.getUserRoles() + .hasPermission( + registrar.get().getRegistrarId(), ConsolePermission.EDIT_REGISTRAR_DETAILS)) { + response.setStatus(HttpStatusCodes.STATUS_CODE_FORBIDDEN); + return; + } + + tm().transact(() -> loadAndModifyRegistrar(registrar.get())); + } + + private void loadAndModifyRegistrar(Registrar providedRegistrar) { + Registrar savedRegistrar; + try { + // reload to make sure the object has all the correct fields + savedRegistrar = registrarAccessor.getRegistrar(providedRegistrar.getRegistrarId()); + } catch (RegistrarAccessDeniedException e) { + response.setStatus(HttpStatusCodes.STATUS_CODE_FORBIDDEN); + response.setPayload(e.getMessage()); + return; + } + + Registrar.Builder newRegistrar = savedRegistrar.asBuilder(); + newRegistrar.setWhoisServer(providedRegistrar.getWhoisServer()); + newRegistrar.setUrl(providedRegistrar.getUrl()); + newRegistrar.setLocalizedAddress(providedRegistrar.getLocalizedAddress()); + tm().put(newRegistrar.build()); + response.setStatus(HttpStatusCodes.STATUS_CODE_OK); + } +} diff --git a/core/src/test/java/google/registry/ui/server/console/settings/WhoisRegistrarFieldsActionTest.java b/core/src/test/java/google/registry/ui/server/console/settings/WhoisRegistrarFieldsActionTest.java new file mode 100644 index 000000000..535b5d8f7 --- /dev/null +++ b/core/src/test/java/google/registry/ui/server/console/settings/WhoisRegistrarFieldsActionTest.java @@ -0,0 +1,174 @@ +// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google.registry.ui.server.console.settings; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects; +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.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Maps; +import com.google.gson.Gson; +import google.registry.model.console.GlobalRole; +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.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.AuthenticatedRegistrarAccessor; +import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role; +import google.registry.request.auth.UserAuthInfo; +import google.registry.testing.DatabaseHelper; +import google.registry.testing.FakeClock; +import google.registry.testing.FakeResponse; +import google.registry.ui.server.registrar.RegistrarConsoleModule; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.util.HashMap; +import javax.servlet.http.HttpServletRequest; +import org.joda.time.DateTime; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** Tests for {@link WhoisRegistrarFieldsAction}. */ +public class WhoisRegistrarFieldsActionTest { + + private static final Gson GSON = RequestModule.provideGson(); + private final FakeClock clock = new FakeClock(DateTime.parse("2023-08-01T00:00:00.000Z")); + private final FakeResponse fakeResponse = new FakeResponse(); + private final HttpServletRequest request = mock(HttpServletRequest.class); + private final AuthenticatedRegistrarAccessor registrarAccessor = + AuthenticatedRegistrarAccessor.createForTesting( + ImmutableSetMultimap.of("TheRegistrar", Role.OWNER, "NewRegistrar", Role.OWNER)); + + private final HashMap uiRegistrarMap = + Maps.newHashMap( + ImmutableMap.of( + "registrarId", + "TheRegistrar", + "whoisServer", + "whois.nic.google", + "type", + "REAL", + "emailAddress", + "the.registrar@example.com", + "state", + "ACTIVE", + "url", + "\"http://my.fake.url\"", + "localizedAddress", + "{\"street\": [\"123 Example Boulevard\"], \"city\": \"Williamsburg\", \"state\":" + + " \"NY\", \"zip\": \"11201\", \"countryCode\": \"US\"}")); + + @RegisterExtension + final JpaTestExtensions.JpaIntegrationTestExtension jpa = + new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension(); + + @Test + void testSuccess_setsAllFields() throws Exception { + Registrar oldRegistrar = Registrar.loadRequiredRegistrarCached("TheRegistrar"); + assertThat(oldRegistrar.getWhoisServer()).isEqualTo("whois.nic.fakewhois.example"); + assertThat(oldRegistrar.getUrl()).isEqualTo("http://my.fake.url"); + ImmutableMap addressMap = + ImmutableMap.of( + "street", + ImmutableList.of("123 Fake St"), + "city", + "Fakeville", + "state", + "NL", + "zip", + "10011", + "countryCode", + "CA"); + uiRegistrarMap.putAll( + ImmutableMap.of( + "whoisServer", + "whois.nic.google", + "url", + "\"https://newurl.example\"", + "localizedAddress", + "{\"street\": [\"123 Fake St\"], \"city\": \"Fakeville\", \"state\":" + + " \"NL\", \"zip\": \"10011\", \"countryCode\": \"CA\"}")); + WhoisRegistrarFieldsAction action = createAction(); + action.run(); + assertThat(fakeResponse.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_OK); + Registrar newRegistrar = Registrar.loadByRegistrarId("TheRegistrar").get(); // skip cache + assertThat(newRegistrar.getWhoisServer()).isEqualTo("whois.nic.google"); + assertThat(newRegistrar.getUrl()).isEqualTo("https://newurl.example"); + assertThat(newRegistrar.getLocalizedAddress().toJsonMap()).isEqualTo(addressMap); + // the non-changed fields should be the same + assertAboutImmutableObjects() + .that(newRegistrar) + .isEqualExceptFields(oldRegistrar, "whoisServer", "url", "localizedAddress"); + } + + @Test + void testFailure_noAccessToRegistrar() throws Exception { + Registrar newRegistrar = Registrar.loadByRegistrarIdCached("NewRegistrar").get(); + AuthResult onlyTheRegistrar = + AuthResult.create( + AuthLevel.USER, + UserAuthInfo.create( + new User.Builder() + .setEmailAddress("email@email.example") + .setUserRoles( + new UserRoles.Builder() + .setRegistrarRoles( + ImmutableMap.of("TheRegistrar", RegistrarRole.PRIMARY_CONTACT)) + .build()) + .build())); + uiRegistrarMap.put("registrarId", "NewRegistrar"); + WhoisRegistrarFieldsAction action = createAction(onlyTheRegistrar); + action.run(); + assertThat(fakeResponse.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_FORBIDDEN); + // should be no change + assertThat(DatabaseHelper.loadByEntity(newRegistrar)).isEqualTo(newRegistrar); + } + + private AuthResult defaultUserAuth() { + return AuthResult.create( + AuthLevel.USER, + UserAuthInfo.create( + new User.Builder() + .setEmailAddress("email@email.example") + .setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build()) + .build())); + } + + private WhoisRegistrarFieldsAction createAction() throws IOException { + return createAction(defaultUserAuth()); + } + + private WhoisRegistrarFieldsAction createAction(AuthResult authResult) throws IOException { + when(request.getReader()) + .thenReturn(new BufferedReader(new StringReader(uiRegistrarMap.toString()))); + return new WhoisRegistrarFieldsAction( + authResult, + fakeResponse, + GSON, + registrarAccessor, + RegistrarConsoleModule.provideRegistrar( + GSON, RequestModule.provideJsonBody(request, GSON))); + } +} 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 f2ec348b3..4e802554d 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,14 +1,15 @@ -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,POST n API,LEGACY USER PUBLIC -/console-api/settings/contacts ContactAction 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 -/registrar-ote-status OteStatusAction POST n API,LEGACY USER PUBLIC -/registrar-settings RegistrarSettingsAction POST n API,LEGACY USER PUBLIC -/registry-lock-get RegistryLockGetAction GET n API,LEGACY USER PUBLIC -/registry-lock-post RegistryLockPostAction POST n API,LEGACY USER PUBLIC -/registry-lock-verify RegistryLockVerifyAction GET n API,LEGACY NONE PUBLIC +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,POST n API,LEGACY USER PUBLIC +/console-api/settings/contacts ContactAction GET,POST n API,LEGACY USER PUBLIC +/console-api/settings/security SecurityAction POST n API,LEGACY USER PUBLIC +/console-api/settings/whois-fields WhoisRegistrarFieldsAction 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 +/registrar-ote-status OteStatusAction POST n API,LEGACY USER PUBLIC +/registrar-settings RegistrarSettingsAction POST n API,LEGACY USER PUBLIC +/registry-lock-get RegistryLockGetAction GET n API,LEGACY USER PUBLIC +/registry-lock-post RegistryLockPostAction POST n API,LEGACY USER PUBLIC +/registry-lock-verify RegistryLockVerifyAction GET n API,LEGACY NONE PUBLIC \ No newline at end of file