Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 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
5 changes: 5 additions & 0 deletions docs/modules/plugins/pages/plugin-web-app-to-rest-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,11 @@ Then SSL rating for URL `$url` is $comparisonRule `$gradeName`
|`https://api.ssllabs.com`
|SSL Labs endpoint.

|`ssl-labs.email`
|`string`
|`<empty>`
|*Required.* The registered email address to use for SSL Labs API v4 authentication. Must be registered via the https://github.com/ssllabs/ssllabs-scan/blob/master/ssllabs-api-docs-v4.md#register-for-scan-api-initiation-and-result-fetching-[SSL Labs registration API].

|===

.Validate SSL rating for `https://www.google.com`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 the original author or authors.
* Copyright 2019-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -23,6 +23,8 @@
import java.util.List;
import java.util.Optional;

import org.apache.commons.lang3.Validate;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.core5.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -37,7 +39,7 @@
private static final String SSL_SCAN_FAILURE =
"SSL scan has not been performed successfully during specified waiting period";
private static final String ERROR_MESSAGE = "Status message '{}' received for host {}";
private static final String API_VERSION = "/api/v3";
private static final String API_VERSION = "/api/v4";
private static final String ANALYZE_CALL = "/analyze?host=%s&fromCache=on&maxAge=1";
private static final int SERVICE_IS_OVERLOADED = 529;
private static final DurationBasedWaiter WAITER = new DurationBasedWaiter(Duration.ofMinutes(10),
Expand All @@ -46,12 +48,15 @@
private final IHttpClient httpClient;
private final JsonUtils jsonUtils;
private final String sslLabHost;
private final String email;

public SslLabsClient(IHttpClient httpClient, JsonUtils jsonUtils, String sslLabHost)
public SslLabsClient(IHttpClient httpClient, JsonUtils jsonUtils, String sslLabHost, String email)
{
this.httpClient = httpClient;
this.jsonUtils = jsonUtils;
this.sslLabHost = sslLabHost;
this.email = Validate.notBlank(email,
"SSL Labs API v4 requires a registered email address. Please set the 'ssl-labs.email' property.");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"SSL Labs API v4 requires a registered email address. Please set the 'ssl-labs.email' property.");
"SSL Labs API v4 requires a registered email address");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 27a6641.

}

public Optional<Grade> performSslScan(String host)
Expand Down Expand Up @@ -89,8 +94,10 @@
{
try
{
HttpResponse response = httpClient.doHttpGet(URI.create(
HttpGet httpGet = new HttpGet(URI.create(
String.format("%s%s%s", sslLabHost, API_VERSION, String.format(ANALYZE_CALL, host))));
httpGet.addHeader("email", email);

Check notice on line 99 in vividus-plugin-web-app-to-rest-api/src/main/java/org/vividus/ssllabs/SslLabsClient.java

View workflow job for this annotation

GitHub Actions / Qodana for JVM

Unknown HTTP header

Unknown HTTP header

Check notice

Code scanning / QDJVM

Unknown HTTP header Note

Unknown HTTP header
HttpResponse response = httpClient.execute(httpGet);
return Optional.of(response);
}
catch (IOException e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ resource-checker.uri-to-ignore-regex=
resource-checker.attributes-to-check=href,src

ssl-labs.api-endpoint=https://api.ssllabs.com
ssl-labs.email=
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
<property name="uriToIgnoreRegex" value="${resource-checker.uri-to-ignore-regex}" />
</bean>

<bean id="sslLabsClient" class="org.vividus.ssllabs.SslLabsClient">

Check failure on line 21 in vividus-plugin-web-app-to-rest-api/src/main/resources/vividus-plugin/spring.xml

View workflow job for this annotation

GitHub Actions / Qodana for JVM

Incorrect constructor injection in XML Spring bean

No matching constructor found in class 'SslLabsClient'#treeend *** ** * ** *** |-------------------------|---|-----------------------------------------------------------------| | **SslLabsClient(...):** | | **Bean:** | | IHttpClient httpClient | | Autowired: null(HttpClient); null(HttpClient); null(HttpClient) | | JsonUtils jsonUtils | | **???** | | String sslLabHost | | \<constructor-arg index="2" value="${ssl-labs.api-endpoint}"/\> | | String email | | \<constructor-arg index="3" value="${ssl-labs.email}"/\> |
<constructor-arg index="2" value="${ssl-labs.api-endpoint}"/>
<constructor-arg index="3" value="${ssl-labs.email}"/>

Check warning on line 23 in vividus-plugin-web-app-to-rest-api/src/main/resources/vividus-plugin/spring.xml

View workflow job for this annotation

GitHub Actions / Qodana for JVM

Unresolved placeholders configured in the Spring XML application context

Cannot resolve property key

Check warning

Code scanning / QDJVM

Unresolved placeholders configured in the Spring XML application context Warning

Cannot resolve property key
</bean>

<bean id="sslLabsSteps" class="org.vividus.ssllabs.SslLabsSteps" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 the original author or authors.
* Copyright 2019-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -22,6 +22,7 @@
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
Expand All @@ -30,7 +31,6 @@
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.List;
Expand All @@ -41,6 +41,7 @@
import com.github.valfirst.slf4jtest.TestLoggerFactory;
import com.github.valfirst.slf4jtest.TestLoggerFactoryExtension;

import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.HttpStatus;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -57,6 +58,7 @@
class SslLabsClientTests
{
private static final String SSL_LABS_HOST = "https://api.ssllabs.com";
private static final String SSL_LABS_EMAIL = "test@example.com";
private static final String ANALYZED_HOST = "www.example.com";
private static final int SERVICE_IS_OVERLOADED = 529;

Expand All @@ -75,7 +77,7 @@ public void beforeEach()
{
duration.when(() -> Duration.ofMinutes(10)).thenReturn(Duration.ZERO);
duration.when(() -> Duration.ofSeconds(30)).thenReturn(Duration.ZERO);
client = new SslLabsClient(httpClient, new JsonUtils(), SSL_LABS_HOST);
client = new SslLabsClient(httpClient, new JsonUtils(), SSL_LABS_HOST, SSL_LABS_EMAIL);
}
}

Expand All @@ -95,7 +97,7 @@ void shouldPerformSslScanWithIOException() throws IOException
try (var grade = mockStatic(Grade.class))
{
var ioException = mock(IOException.class);
when(httpClient.doHttpGet(any(URI.class))).thenThrow(ioException);
when(httpClient.execute(any(ClassicHttpRequest.class))).thenThrow(ioException);
assertEquals(Optional.empty(), client.performSslScan(ANALYZED_HOST));
grade.verify(() -> Grade.fromString(anyString()), never());
assertThat(logger.getLoggingEvents(),
Expand All @@ -119,7 +121,7 @@ void testPerformSslScanWithUnexpectedStatus(int statusCode) throws IOException
HttpResponse httpResponse = new HttpResponse();
httpResponse.setStatusCode(statusCode);
httpResponse.setResponseBody(response.getBytes(StandardCharsets.UTF_8));
when(httpClient.doHttpGet(any(URI.class))).thenReturn(httpResponse);
when(httpClient.execute(any(ClassicHttpRequest.class))).thenReturn(httpResponse);
assertEquals(Optional.empty(), client.performSslScan(ANALYZED_HOST));
grade.verify(() -> Grade.fromString(anyString()), never());
assertThat(logger.getLoggingEvents(), hasItems(
Expand Down Expand Up @@ -170,11 +172,26 @@ void shouldPerformSslScanEmptyEndpoints() throws IOException
}
}

@Test
void shouldThrowExceptionWhenEmailIsBlank()
{
try (var duration = mockStatic(Duration.class))
{
duration.when(() -> Duration.ofMinutes(10)).thenReturn(Duration.ZERO);
duration.when(() -> Duration.ofSeconds(30)).thenReturn(Duration.ZERO);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these mocks needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, since WAITER is a static final field already initialized by the time this test runs. Removed them in 27a6641.

var exception = assertThrows(IllegalArgumentException.class,
() -> new SslLabsClient(httpClient, new JsonUtils(), SSL_LABS_HOST, ""));
assertEquals(
"SSL Labs API v4 requires a registered email address. Please set the 'ssl-labs.email' property.",
exception.getMessage());
}
}

private void mockResponse(String response) throws IOException
{
HttpResponse httpResponse = new HttpResponse();
httpResponse.setStatusCode(HttpStatus.SC_OK);
httpResponse.setResponseBody(response.getBytes(StandardCharsets.UTF_8));
when(httpClient.doHttpGet(any(URI.class))).thenReturn(httpResponse);
when(httpClient.execute(any(ClassicHttpRequest.class))).thenReturn(httpResponse);
}
}
Loading