Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions core/src/main/java/google/registry/tools/RdapQueryCommand.java
Original file line number Diff line number Diff line change
@@ -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<String> VALID_TYPES =
ImmutableSet.of("domain", "nameserver", "entity");

@Parameter(
description = "RDAP query string, in the format <type> <name>",
required = true)
private List<String> 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: <type> <name>");
}
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<String, JsonElement> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand All @@ -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<Registrar> maybeRegistrar =
Expand All @@ -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);
}
}
Loading