parent
c0fd3b89ea
commit
4222de8f41
4 changed files with 98 additions and 4 deletions
|
@ -289,6 +289,30 @@ public class LogoutEndpoint {
|
|||
.createLogoutConfirmPage();
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint can be used either as:
|
||||
* - OpenID Connect RP-Initiated Logout POST endpoint according to the specification https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout
|
||||
* - Legacy Logout endpoint with refresh_token as an argument and client authentication needed. See {@link #logoutToken} for more details
|
||||
*
|
||||
* @return response
|
||||
*/
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
public Response logout() {
|
||||
MultivaluedMap<String, String> form = request.getDecodedFormParameters();
|
||||
if (form.containsKey(OAuth2Constants.REFRESH_TOKEN)) {
|
||||
return logoutToken();
|
||||
} else {
|
||||
return logout(form.getFirst(OIDCLoginProtocol.REDIRECT_URI_PARAM),
|
||||
form.getFirst(OIDCLoginProtocol.ID_TOKEN_HINT),
|
||||
form.getFirst(OIDCLoginProtocol.CLIENT_ID_PARAM),
|
||||
form.getFirst(OIDCLoginProtocol.POST_LOGOUT_REDIRECT_URI_PARAM),
|
||||
form.getFirst(OIDCLoginProtocol.STATE_PARAM),
|
||||
form.getFirst(OIDCLoginProtocol.UI_LOCALES_PARAM),
|
||||
form.getFirst(AuthenticationManager.INITIATING_IDP_PARAM));
|
||||
}
|
||||
}
|
||||
|
||||
@Path("/logout-confirm")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
|
@ -389,9 +413,7 @@ public class LogoutEndpoint {
|
|||
*
|
||||
* @return
|
||||
*/
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
public Response logoutToken() {
|
||||
private Response logoutToken() {
|
||||
cors = Cors.add(request).auth().allowedMethods("POST").auth().exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS);
|
||||
|
||||
MultivaluedMap<String, String> form = request.getDecodedFormParameters();
|
||||
|
|
|
@ -1000,7 +1000,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
|||
* See URLUtils.sendPOSTWithWebDriver for more details
|
||||
*
|
||||
* @param postRequestUrl Absolute URL. It can include query parameters etc. The POST request will be send to this URL
|
||||
* @param encodedFormParameters Encoded parameters in the form of "param1=value1:param2=value2"
|
||||
* @param encodedFormParameters Encoded parameters in the form of "param1=value1¶m2=value2"
|
||||
* @return
|
||||
*/
|
||||
@GET
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.openqa.selenium.support.ui.WebDriverWait;
|
|||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.keycloak.testsuite.util.DroneUtils.getCurrentDriver;
|
||||
|
@ -96,6 +97,28 @@ public final class URLUtils {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #sendPOSTRequestWithWebDriver(String, String)
|
||||
*
|
||||
* @param postRequestUrl
|
||||
* @param formParams form params in key/value form
|
||||
*/
|
||||
public static void sendPOSTRequestWithWebDriver(String postRequestUrl, Map<String, String> formParams) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean first = true;
|
||||
for (Map.Entry<String, String> entry : formParams.entrySet()) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
sb.append("&");
|
||||
}
|
||||
sb.append(entry.getKey())
|
||||
.append("=")
|
||||
.append(entry.getValue());
|
||||
}
|
||||
sendPOSTRequestWithWebDriver(postRequestUrl, sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* This will send POST request to specified URL with specified form parameters. It's not easily possible to "trick" web driver to send POST
|
||||
* request with custom parameters, which are not directly available in the form.
|
||||
|
|
|
@ -36,6 +36,7 @@ import org.keycloak.events.Errors;
|
|||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.representations.LogoutToken;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
|
@ -52,6 +53,8 @@ import org.keycloak.testsuite.pages.LoginPage;
|
|||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.ws.rs.NotFoundException;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
|
@ -76,6 +79,7 @@ import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
|
|||
import org.keycloak.testsuite.util.InfinispanTestTimeServiceRule;
|
||||
import org.keycloak.testsuite.util.Matchers;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.URLUtils;
|
||||
import org.keycloak.testsuite.util.WaitUtils;
|
||||
import org.openqa.selenium.NoSuchElementException;
|
||||
|
||||
|
@ -721,6 +725,51 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
|
|||
}
|
||||
|
||||
|
||||
// Calling RP-Initiated Logout endpoint with POST request. This must be supported according to specification
|
||||
@Test
|
||||
public void logoutWithPostRequest() throws IOException {
|
||||
try (RealmAttributeUpdater updater = new RealmAttributeUpdater(testRealm()).addSupportedLocale("cs").update()) {
|
||||
OAuthClient.AccessTokenResponse tokenResponse = loginUser();
|
||||
|
||||
// Logout with POST request and automatic redirect after logout
|
||||
String redirectUri = APP_REDIRECT_URI + "?logout";
|
||||
|
||||
String idTokenString = tokenResponse.getIdToken();
|
||||
String sessionId = tokenResponse.getSessionState();
|
||||
|
||||
Map<String, String> postParams = new HashMap<>();
|
||||
postParams.put(OIDCLoginProtocol.POST_LOGOUT_REDIRECT_URI_PARAM, redirectUri);
|
||||
postParams.put(OIDCLoginProtocol.ID_TOKEN_HINT, idTokenString);
|
||||
postParams.put(OAuth2Constants.STATE, "my-state");
|
||||
URLUtils.sendPOSTRequestWithWebDriver(oauth.getLogoutUrl().build(), postParams);
|
||||
|
||||
events.expectLogout(tokenResponse.getSessionState()).detail(Details.REDIRECT_URI, redirectUri).assertEvent();
|
||||
Assert.assertThat(false, is(isSessionActive(sessionId)));
|
||||
assertCurrentUrlEquals(redirectUri + "&state=my-state");
|
||||
|
||||
// Logout with showing confirmation screen
|
||||
tokenResponse = loginUser();
|
||||
sessionId = tokenResponse.getSessionState();
|
||||
|
||||
postParams.clear();
|
||||
postParams.put(OIDCLoginProtocol.POST_LOGOUT_REDIRECT_URI_PARAM, redirectUri);
|
||||
postParams.put(OAuth2Constants.CLIENT_ID, "test-app");
|
||||
postParams.put(OAuth2Constants.STATE, "my-state-2");
|
||||
postParams.put(OIDCLoginProtocol.UI_LOCALES_PARAM, "cs");
|
||||
URLUtils.sendPOSTRequestWithWebDriver(oauth.getLogoutUrl().build(), postParams);
|
||||
|
||||
Assert.assertEquals("Odhlašování", PageUtils.getPageTitle(driver)); // Logging out
|
||||
Assert.assertEquals("Čeština", logoutConfirmPage.getLanguageDropdownText());
|
||||
logoutConfirmPage.confirmLogout();
|
||||
|
||||
WaitUtils.waitForPageToLoad();
|
||||
events.expectLogout(tokenResponse.getSessionState()).detail(Details.REDIRECT_URI, redirectUri).assertEvent();
|
||||
Assert.assertThat(false, is(isSessionActive(sessionId)));
|
||||
assertCurrentUrlEquals(redirectUri + "&state=my-state-2");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFrontChannelLogoutWithPostLogoutRedirectUri() throws Exception {
|
||||
ClientsResource clients = adminClient.realm(oauth.getRealm()).clients();
|
||||
|
|
Loading…
Reference in a new issue