Skip to content

Commit c7fedb7

Browse files
committed
Skip processing HEAD requests for action tokens
Closes keycloak#41834 Signed-off-by: Pedro Igor <[email protected]>
1 parent 0ff7d55 commit c7fedb7

File tree

3 files changed

+50
-3
lines changed

3 files changed

+50
-3
lines changed

services/src/main/java/org/keycloak/headers/DefaultSecurityHeadersProvider.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import java.util.Collections;
3131
import java.util.Map;
3232

33+
import static jakarta.ws.rs.HttpMethod.HEAD;
34+
import static jakarta.ws.rs.HttpMethod.OPTIONS;
3335
import static org.keycloak.models.BrowserSecurityHeaders.CONTENT_SECURITY_POLICY;
3436

3537
public class DefaultSecurityHeadersProvider implements SecurityHeadersProvider {
@@ -147,10 +149,17 @@ private boolean isEmptyMediaTypeAllowed(ContainerRequestContext requestContext,
147149
status == 400 || status == 401 || status == 403 || status == 404) {
148150
return true;
149151
}
150-
if (requestContext.getMethod().equalsIgnoreCase("OPTIONS")) {
151-
return true;
152+
153+
String method = requestContext.getMethod().toUpperCase();
154+
155+
switch (method) {
156+
case OPTIONS:
157+
return true;
158+
case HEAD:
159+
return status == 200;
152160
}
153161
}
162+
154163
return false;
155164
}
156165

services/src/main/java/org/keycloak/services/resources/LoginActionsService.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
package org.keycloak.services.resources;
1818

19+
import jakarta.ws.rs.HEAD;
1920
import org.jboss.logging.Logger;
2021
import org.keycloak.common.Profile;
2122
import org.keycloak.common.Profile.Feature;
@@ -548,6 +549,19 @@ public Response executeActionToken(@QueryParam(AUTH_SESSION_ID) String authSessi
548549
return handleActionToken(key, execution, clientId, tabId, clientData, null);
549550
}
550551

552+
/**
553+
* Skip processing {@link jakarta.ws.rs.HttpMethod#HEAD} requests for action tokens
554+
* as they are usually used by mail servers to validate links. The actual request will eventually be
555+
* processed by the {@link #executeActionToken} method.
556+
*
557+
* @return a {@link Response.Status#OK} response with no message body
558+
*/
559+
@Path("action-token")
560+
@HEAD
561+
public Response executeActionTokenHead() {
562+
return Response.ok().build();
563+
}
564+
551565
protected <T extends JsonWebToken & SingleUseObjectKeyModel> Response handleActionToken(String tokenString, String execution, String clientId, String tabId, String clientData,
552566
TriFunction<ActionTokenHandler<T>, T, ActionTokenContext<T>, Response> preHandleToken) {
553567
T token;

testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateEmailTestWithVerificationTest.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,18 @@
3030
import java.util.List;
3131
import java.util.UUID;
3232

33+
import jakarta.ws.rs.core.Response.Status;
34+
import org.apache.http.impl.client.CloseableHttpClient;
35+
import org.apache.http.impl.client.HttpClientBuilder;
3336
import org.jboss.arquillian.graphene.page.Page;
34-
import org.jetbrains.annotations.NotNull;
3537
import org.junit.Assert;
3638
import org.junit.Rule;
3739
import org.junit.Test;
3840
import org.keycloak.admin.client.resource.AuthenticationManagementResource;
3941
import org.keycloak.admin.client.resource.UserResource;
4042
import org.keycloak.authentication.actiontoken.updateemail.UpdateEmailActionToken;
4143
import org.keycloak.authentication.requiredactions.UpdateEmail;
44+
import org.keycloak.broker.provider.util.SimpleHttp.Response;
4245
import org.keycloak.events.Details;
4346
import org.keycloak.events.EventType;
4447
import org.keycloak.models.UserModel;
@@ -48,6 +51,7 @@
4851
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
4952
import org.keycloak.representations.idm.UserRepresentation;
5053
import org.keycloak.representations.idm.UserSessionRepresentation;
54+
import org.keycloak.testsuite.broker.util.SimpleHttpDefault;
5155
import org.keycloak.testsuite.pages.ErrorPage;
5256
import org.keycloak.testsuite.pages.InfoPage;
5357
import org.keycloak.testsuite.util.GreenMailRule;
@@ -174,6 +178,26 @@ public void confirmEmailUpdateAfterThirdPartyEmailUpdate() throws MessagingExcep
174178
assertTrue(ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost").getRequiredActions().contains(UserModel.RequiredAction.UPDATE_EMAIL.name()));
175179
}
176180

181+
@Test
182+
public void testSkipHeadRequestWhenFollowingVerificationLink() throws MessagingException, IOException {
183+
oauth.openLoginForm();
184+
loginPage.login("test-user@localhost", "password");
185+
186+
updateEmailPage.assertCurrent();
187+
updateEmailPage.changeEmail("new@localhost");
188+
189+
String confirmationLink = fetchEmailConfirmationLink("new@localhost");
190+
191+
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
192+
try (Response response = SimpleHttpDefault.doHead(confirmationLink, httpClient).asResponse()) {
193+
assertEquals(Status.OK.getStatusCode(), response.getStatus());
194+
}
195+
}
196+
197+
driver.navigate().to(confirmationLink);
198+
infoPage.assertCurrent();
199+
}
200+
177201
@Test
178202
public void testForceEmailVerification() throws MessagingException, IOException {
179203
// disables verify email at the realm level

0 commit comments

Comments
 (0)