diff --git a/core/src/main/java/google/registry/tools/RdapQueryCommand.java b/core/src/main/java/google/registry/tools/RdapQueryCommand.java new file mode 100644 index 00000000000..0e164bea16c --- /dev/null +++ b/core/src/main/java/google/registry/tools/RdapQueryCommand.java @@ -0,0 +1,71 @@ +package google.registry.tools; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.google.common.collect.ImmutableSet; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import java.util.List; +import java.util.Map; + +/** Command to execute an authenticated RDAP query. */ +@Parameters(separators = " =", commandDescription = "Manually perform an authenticated RDAP query") +public final class RdapQueryCommand implements CommandWithConnection { + + private static final ImmutableSet VALID_TYPES = + ImmutableSet.of("domain", "nameserver", "entity"); + + @Parameter( + description = "RDAP query string, in the format ", + required = true) + private List mainParameters; + + private Connection connection; + + @Override + public void setConnection(Connection connection) { + this.connection = connection; + } + + @Override + public void run() throws Exception { + if (mainParameters.size() != 2) { + throw new IllegalArgumentException("Expected 2 arguments: "); + } + String type = mainParameters.get(0).toLowerCase(); + if (!VALID_TYPES.contains(type)) { + throw new IllegalArgumentException( + String.format("Invalid object type '%s'. Must be one of %s", type, VALID_TYPES)); + } + String name = mainParameters.get(1); + String path = String.format("/rdap/%s/%s", type, name); + + // Use the connection object to make an authenticated GET request. + String rdapResponse = connection.sendGetRequest(path); + + // Parse and format the JSON response. + JsonElement rdapJson = new Gson().fromJson(rdapResponse, JsonElement.class); + System.out.println(formatJson(rdapJson, "")); + } + + /** + * Recursively formats a JSON object into indented key-value pairs. + */ + private String formatJson(JsonElement jsonElement, String indent) { + StringBuilder sb = new StringBuilder(); + if (jsonElement.isJsonObject()) { + for (Map.Entry entry : jsonElement.getAsJsonObject().entrySet()) { + sb.append(indent).append(entry.getKey()).append(":\n"); + sb.append(formatJson(entry.getValue(), indent + " ")); + } + } else if (jsonElement.isJsonArray()) { + for (JsonElement element : jsonElement.getAsJsonArray()) { + sb.append(formatJson(element, indent + "- ")); + } + } else if (jsonElement.isJsonPrimitive()) { + sb.append(indent).append(jsonElement.getAsString()).append("\n"); + } + return sb.toString(); + } +} diff --git a/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java b/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java index c244adca4bb..bc893245310 100644 --- a/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java +++ b/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java @@ -74,7 +74,13 @@ public ConsoleApiAction(ConsoleApiParams consoleApiParams) { @Override public final void run() { // Shouldn't be even possible because of Auth annotations on the various implementing classes + logger.atInfo().log("Har AuthResult received in ConsoleApiAction."); + logger.atInfo().log("Har AuthLevel: %s", consoleApiParams.authResult().authLevel()); + logger.atInfo().log("User object: %s", consoleApiParams.authResult().user()); + logger.atInfo().log("ServiceAccountEmail: %s", consoleApiParams.authResult().serviceAccountEmail()); if (consoleApiParams.authResult().user().isEmpty()) { + logger.atWarning().log("harshta User object is empty, failing with UNAUTHORIZED."); + logger.atInfo().log("harshita logs: %s", consoleApiParams.authResult()); consoleApiParams.response().setStatus(SC_UNAUTHORIZED); return; } @@ -211,7 +217,8 @@ protected void sendExternalUpdates( The following changes were made in registry %s environment to the registrar %s by\ %s: - %s""", + %s\ + """, environment, registrar.getRegistrarId(), consoleApiParams.authResult().userIdForLogging(), 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 80341d567ef..1bb00146793 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 @@ -120,7 +120,7 @@ private void setResponse(Registrar savedRegistrar) { } } } catch (InsecureCertificateException e) { - setFailedResponse("Invalid certificate in parameter", SC_BAD_REQUEST); + setFailedResponse(e.getMessage(), SC_BAD_REQUEST); return; } 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 350a8e944c6..0e328dfb89f 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 @@ -20,6 +20,7 @@ import static google.registry.testing.DatabaseHelper.loadSingleton; import static google.registry.testing.SqlHelper.saveRegistrar; import static google.registry.util.DateTimeUtils.START_OF_TIME; +import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST; import static jakarta.servlet.http.HttpServletResponse.SC_OK; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; @@ -54,30 +55,52 @@ class SecurityActionTest extends ConsoleActionBaseTestCase { SAMPLE_CERT2); private Registrar testRegistrar; + private static final String VALIDITY_TOO_LONG_CERT_PEM = + "-----BEGIN CERTIFICATE-----\n" + + "MIIDejCCAv+gAwIBAgIQHNcSEt4VENkSgtozEEoQLzAKBggqhkjOPQQDAzB8MQsw\n" + + "CQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xGDAW\n" + + "BgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBSb290IENl\n" + + "cnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzAeFw0xOTAzMDcxOTQyNDJaFw0zNDAz\n" + + "MDMxOTQyNDJaMG8xCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UE\n" + + "BwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxKzApBgNVBAMMIlNTTC5jb20g\n" + + "U1NMIEludGVybWVkaWF0ZSBDQSBFQ0MgUjIwdjAQBgcqhkjOPQIBBgUrgQQAIgNi\n" + + "AASEOWn30uEYKDLFu4sCjFQ1VupFaeMtQjqVWyWSA7+KFljnsVaFQ2hgs4cQk1f/\n" + + "RQ2INSwdVCYU0i5qsbom20rigUhDh9dM/r6bEZ75eFE899kSCI14xqThYVLPdLEl\n" + + "+dyjggFRMIIBTTASBgNVHRMBAf8ECDAGAQH/AgEAMB8GA1UdIwQYMBaAFILRhXMw\n" + + "5zUE044CkvvlpNHEIejNMHgGCCsGAQUFBwEBBGwwajBGBggrBgEFBQcwAoY6aHR0\n" + + "cDovL3d3dy5zc2wuY29tL3JlcG9zaXRvcnkvU1NMY29tLVJvb3RDQS1FQ0MtMzg0\n" + + "LVIxLmNydDAgBggrBgEFBQcwAYYUaHR0cDovL29jc3BzLnNzbC5jb20wEQYDVR0g\n" + + "BAowCDAGBgRVHSAAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATA7BgNV\n" + + "HR8ENDAyMDCgLqAshipodHRwOi8vY3Jscy5zc2wuY29tL3NzbC5jb20tZWNjLVJv\n" + + "b3RDQS5jcmwwHQYDVR0OBBYEFA10Zgpen+Is7NXCXSUEf3Uyuv99MA4GA1UdDwEB\n" + + "/wQEAwIBhjAKBggqhkjOPQQDAwNpADBmAjEAxYt6Ylk/N8Fch/3fgKYKwI5A011Q\n" + + "MKW0h3F9JW/NX/F7oYtWrxljheH8n2BrkDybAjEAlCxkLE0vQTYcFzrR24oogyw6\n" + + "VkgTm92+jiqJTO5SSA9QUa092S5cTKiHkH2cOM6m\n" + + "-----END CERTIFICATE-----"; + private AuthenticatedRegistrarAccessor registrarAccessor = AuthenticatedRegistrarAccessor.createForTesting( ImmutableSetMultimap.of("registrarId", AuthenticatedRegistrarAccessor.Role.ADMIN)); - private CertificateChecker certificateChecker = - new CertificateChecker( - ImmutableSortedMap.of(START_OF_TIME, 20825, DateTime.parse("2020-09-01T00:00:00Z"), 398), - 30, - 15, - 2048, - ImmutableSet.of("secp256r1", "secp384r1"), - clock); @BeforeEach void beforeEach() { testRegistrar = saveRegistrar("registrarId"); } - @Test void testSuccess_postRegistrarInfo() throws IOException { + CertificateChecker lenientChecker = + new CertificateChecker( + ImmutableSortedMap.of( + START_OF_TIME, 20825, DateTime.parse("2020-09-01T00:00:00Z"), 398), + 30, + 15, + 2048, + ImmutableSet.of("secp256r1", "secp384r1"), + clock); + clock.setTo(DateTime.parse("2020-11-01T00:00:00Z")); - SecurityAction action = - createAction( - testRegistrar.getRegistrarId()); + SecurityAction action = createAction(testRegistrar.getRegistrarId(), lenientChecker); action.run(); assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK); Registrar r = loadRegistrar(testRegistrar.getRegistrarId()); @@ -90,9 +113,39 @@ void testSuccess_postRegistrarInfo() throws IOException { assertThat(history.getDescription()).hasValue("registrarId|IP_CHANGE,PRIMARY_SSL_CERT_CHANGE"); } - private SecurityAction createAction(String registrarId) throws IOException { + @Test + void testFailure_validityPeriodTooLong_returnsSpecificError() throws IOException { + CertificateChecker strictChecker = + new CertificateChecker( + ImmutableSortedMap.of(START_OF_TIME, 398), + 30, + 15, + 2048, + ImmutableSet.of("secp256r1", "secp384r1"), + clock); + + clock.setTo(DateTime.parse("2025-01-01T00:00:00Z")); + String escapedCert = VALIDITY_TOO_LONG_CERT_PEM.replace("\n", "\\n"); + String jsonWithBadCert = + String.format( + "{\"registrarId\": \"registrarId\", \"clientCertificate\": \"%s\"}", escapedCert); + + SecurityAction action = + createAction(testRegistrar.getRegistrarId(), jsonWithBadCert, strictChecker); + action.run(); + + String expectedError = + "Certificate validity period is too long; it must be less than or equal to 398 days."; + FakeResponse response = (FakeResponse) consoleApiParams.response(); + assertThat(response.getStatus()).isEqualTo(SC_BAD_REQUEST); + assertThat(response.getPayload()).isEqualTo(expectedError); + } + + private SecurityAction createAction( + String registrarId, String jsonBody, CertificateChecker certificateChecker) + throws IOException { when(consoleApiParams.request().getMethod()).thenReturn(Action.Method.POST.toString()); - doReturn(new BufferedReader(new StringReader(jsonRegistrar1))) + doReturn(new BufferedReader(new StringReader(jsonBody))) .when(consoleApiParams.request()) .getReader(); Optional maybeRegistrar = @@ -101,4 +154,9 @@ private SecurityAction createAction(String registrarId) throws IOException { return new SecurityAction( consoleApiParams, certificateChecker, registrarAccessor, registrarId, maybeRegistrar); } + + private SecurityAction createAction(String registrarId, CertificateChecker certificateChecker) + throws IOException { + return createAction(registrarId, jsonRegistrar1, certificateChecker); + } }