Do not challenge inside spnego authenticator is FORKED_FLOW

Closes #20637

Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
rmartinc 2024-03-11 16:55:07 +01:00 committed by Marek Posolda
parent a681d8ce28
commit 43a5779f6e
3 changed files with 75 additions and 3 deletions

View file

@ -21,6 +21,7 @@ import org.jboss.logging.Logger;
import org.keycloak.http.HttpRequest; import org.keycloak.http.HttpRequest;
import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError; import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.authentication.Authenticator; import org.keycloak.authentication.Authenticator;
import org.keycloak.common.constants.KerberosConstants; import org.keycloak.common.constants.KerberosConstants;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
@ -61,6 +62,11 @@ public class SpnegoAuthenticator extends AbstractUsernameFormAuthenticator imple
HttpRequest request = context.getHttpRequest(); HttpRequest request = context.getHttpRequest();
String authHeader = request.getHttpHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION); String authHeader = request.getHttpHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (authHeader == null) { if (authHeader == null) {
if (context.getAuthenticationSession().getAuthNote(AuthenticationProcessor.FORKED_FROM) != null) {
// skip spnego authentication if it was forked (reset-credentials)
context.attempted();
return;
}
Response challenge = challengeNegotiation(context, null); Response challenge = challengeNegotiation(context, null);
context.forceChallenge(challenge); context.forceChallenge(challenge);
return; return;

View file

@ -17,19 +17,30 @@
package org.keycloak.testsuite.federation.kerberos; package org.keycloak.testsuite.federation.kerberos;
import java.net.URI; import jakarta.mail.internet.MimeMessage;
import java.util.List;
import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.Form;
import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import org.keycloak.testsuite.Assert; import java.net.URI;
import java.util.List;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.jboss.arquillian.graphene.page.Page;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import org.junit.ClassRule; import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.constants.KerberosConstants; import org.keycloak.common.constants.KerberosConstants;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.federation.kerberos.CommonKerberosConfig; import org.keycloak.federation.kerberos.CommonKerberosConfig;
import org.keycloak.federation.kerberos.KerberosConfig; import org.keycloak.federation.kerberos.KerberosConfig;
import org.keycloak.federation.kerberos.KerberosFederationProviderFactory; import org.keycloak.federation.kerberos.KerberosFederationProviderFactory;
import org.keycloak.models.Constants;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.ErrorRepresentation; import org.keycloak.representations.idm.ErrorRepresentation;
@ -37,10 +48,16 @@ import org.keycloak.representations.idm.UserProfileAttributeMetadata;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProvider;
import org.keycloak.testsuite.ActionURIUtils; import org.keycloak.testsuite.ActionURIUtils;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.KerberosEmbeddedServer; import org.keycloak.testsuite.KerberosEmbeddedServer;
import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected; import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected;
import org.keycloak.testsuite.pages.InfoPage;
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
import org.keycloak.testsuite.util.GreenMailRule;
import org.keycloak.testsuite.util.KerberosRule; import org.keycloak.testsuite.util.KerberosRule;
import org.keycloak.testsuite.util.MailUtils;
import org.keycloak.testsuite.util.OAuthClient;
import static org.keycloak.userprofile.UserProfileUtil.USER_METADATA_GROUP; import static org.keycloak.userprofile.UserProfileUtil.USER_METADATA_GROUP;
@ -56,6 +73,14 @@ public class KerberosStandaloneTest extends AbstractKerberosSingleRealmTest {
@ClassRule @ClassRule
public static KerberosRule kerberosRule = new KerberosRule(PROVIDER_CONFIG_LOCATION, KerberosEmbeddedServer.DEFAULT_KERBEROS_REALM); public static KerberosRule kerberosRule = new KerberosRule(PROVIDER_CONFIG_LOCATION, KerberosEmbeddedServer.DEFAULT_KERBEROS_REALM);
@Rule
public GreenMailRule greenMail = new GreenMailRule();
@Page
protected LoginPasswordUpdatePage loginPasswordUpdatePage;
@Page
protected InfoPage infoPage;
@Override @Override
protected KerberosRule getKerberosRule() { protected KerberosRule getKerberosRule() {
@ -213,4 +238,40 @@ public class KerberosStandaloneTest extends AbstractKerberosSingleRealmTest {
johnResource.update(john); johnResource.update(john);
} }
@Test
public void testResetCredentials() throws Exception {
// request reset-credentials
String resetUri = OAuthClient.AUTH_SERVER_ROOT + "/realms/test/login-actions/reset-credentials";
String actionUri;
try (Response response = client.target(resetUri).queryParam(Constants.CLIENT_ID, oauth.getClientId()).request().get()) {
Assert.assertEquals(200, response.getStatus());
Document theResponsePage = Jsoup.parse(response.readEntity(String.class));
Elements forms = theResponsePage.select("form[id=kc-reset-password-form]");
Assert.assertEquals(1, forms.size());
actionUri = forms.get(0).attr("action");
Assert.assertNotNull(actionUri);
}
// continue the reset providing the user to change email
spnegoSchemeFactory.setCredentials("hnelson", "incorrectpassword"); // this should not be used, error if auth requested
Form form = new Form();
form.param("username", "test-user@localhost");
try (Response response = client.target(actionUri).request().post(Entity.form(form))) {
Assert.assertEquals(200, response.getStatus());
MatcherAssert.assertThat(response.readEntity(String.class), Matchers.containsString("You should receive an email shortly with further instructions."));
}
// get the email from green mail
MimeMessage message = greenMail.getLastReceivedMessage();
Assert.assertNotNull(message);
String changePasswordUrl = MailUtils.getPasswordResetEmailLink(message);
// perform the password change using the email url
driver.navigate().to(changePasswordUrl.trim());
loginPasswordUpdatePage.assertCurrent();
loginPasswordUpdatePage.changePassword("resetPassword", "resetPassword");
events.expectRequiredAction(EventType.UPDATE_PASSWORD).client(oauth.getClientId()).detail(Details.USERNAME, "test-user@localhost");
infoPage.assertCurrent();
Assert.assertEquals("Your account has been updated.", infoPage.getInfo());
}
} }

View file

@ -50,5 +50,10 @@
} }
] ]
}, },
"smtpServer": {
"from": "auto@keycloak.org",
"host": "localhost",
"port": "3025"
},
"eventsListeners": ["jboss-logging", "event-queue"] "eventsListeners": ["jboss-logging", "event-queue"]
} }