Merge pull request #1297 from AOEpeople/KEYCLOAK-1350
KEYCLOAK-1350 client_session_state should be updated when refreshing a token
This commit is contained in:
commit
56c858d917
4 changed files with 155 additions and 10 deletions
|
@ -24,5 +24,7 @@ public interface Details {
|
|||
String NODE_HOST = "node_host";
|
||||
String REASON = "reason";
|
||||
String REVOKED_CLIENT = "revoked_client";
|
||||
String CLIENT_SESSION_STATE = "client_session_state";
|
||||
String CLIENT_SESSION_HOST = "client_session_host";
|
||||
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ import javax.ws.rs.core.MediaType;
|
|||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
@ -227,16 +228,7 @@ public class TokenEndpoint {
|
|||
throw new ErrorResponseException("invalid_grant", "Session not active", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
String adapterSessionId = formParams.getFirst(AdapterConstants.CLIENT_SESSION_STATE);
|
||||
if (adapterSessionId != null) {
|
||||
String adapterSessionHost = formParams.getFirst(AdapterConstants.CLIENT_SESSION_HOST);
|
||||
logger.debugf("Adapter Session '%s' saved in ClientSession for client '%s'. Host is '%s'", adapterSessionId, client.getClientId(), adapterSessionHost);
|
||||
|
||||
event.detail(AdapterConstants.CLIENT_SESSION_STATE, adapterSessionId);
|
||||
clientSession.setNote(AdapterConstants.CLIENT_SESSION_STATE, adapterSessionId);
|
||||
event.detail(AdapterConstants.CLIENT_SESSION_HOST, adapterSessionHost);
|
||||
clientSession.setNote(AdapterConstants.CLIENT_SESSION_HOST, adapterSessionHost);
|
||||
}
|
||||
updateClientSession(clientSession);
|
||||
|
||||
AccessToken token = tokenManager.createClientAccessToken(session, accessCode.getRequestedRoles(), realm, client, user, userSession, clientSession);
|
||||
|
||||
|
@ -259,6 +251,10 @@ public class TokenEndpoint {
|
|||
AccessTokenResponse res;
|
||||
try {
|
||||
res = tokenManager.refreshAccessToken(session, uriInfo, clientConnection, realm, client, refreshToken, event, headers);
|
||||
|
||||
UserSessionModel userSession = session.sessions().getUserSession(realm, res.getSessionState());
|
||||
updateClientSessions(userSession.getClientSessions());
|
||||
|
||||
} catch (OAuthErrorException e) {
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
throw new ErrorResponseException(e.getError(), e.getDescription(), Response.Status.BAD_REQUEST);
|
||||
|
@ -269,6 +265,45 @@ public class TokenEndpoint {
|
|||
return Cors.add(request, Response.ok(res, MediaType.APPLICATION_JSON_TYPE)).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
|
||||
}
|
||||
|
||||
private void updateClientSession(ClientSessionModel clientSession) {
|
||||
|
||||
if(clientSession == null) {
|
||||
logger.error("client session is null");
|
||||
return;
|
||||
}
|
||||
|
||||
String adapterSessionId = formParams.getFirst(AdapterConstants.CLIENT_SESSION_STATE);
|
||||
if (adapterSessionId != null) {
|
||||
String adapterSessionHost = formParams.getFirst(AdapterConstants.CLIENT_SESSION_HOST);
|
||||
logger.debugf("Adapter Session '%s' saved in ClientSession for client '%s'. Host is '%s'", adapterSessionId, client.getClientId(), adapterSessionHost);
|
||||
|
||||
event.detail(AdapterConstants.CLIENT_SESSION_STATE, adapterSessionId);
|
||||
clientSession.setNote(AdapterConstants.CLIENT_SESSION_STATE, adapterSessionId);
|
||||
event.detail(AdapterConstants.CLIENT_SESSION_HOST, adapterSessionHost);
|
||||
clientSession.setNote(AdapterConstants.CLIENT_SESSION_HOST, adapterSessionHost);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateClientSessions(List<ClientSessionModel> clientSessions) {
|
||||
if(clientSessions == null) {
|
||||
logger.error("client sessions is null");
|
||||
return;
|
||||
}
|
||||
for (ClientSessionModel clientSession : clientSessions) {
|
||||
if(clientSession == null) {
|
||||
logger.error("client session is null");
|
||||
continue;
|
||||
}
|
||||
if(clientSession.getClient() == null) {
|
||||
logger.error("client model in client session is null");
|
||||
continue;
|
||||
}
|
||||
if(client.getId().equals(clientSession.getClient().getId())) {
|
||||
updateClientSession(clientSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Response buildResourceOwnerPasswordCredentialsGrant() {
|
||||
if (!realm.isPasswordCredentialGrantAllowed()) {
|
||||
throw new ErrorResponseException("not_enabled", "Direct Grant REST API not enabled", Response.Status.FORBIDDEN);
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.junit.Assert;
|
|||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.VerificationException;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.freemarker.LocaleHelper;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
|
@ -78,6 +79,10 @@ public class OAuthClient {
|
|||
|
||||
private PublicKey realmPublicKey;
|
||||
|
||||
private String clientSessionState;
|
||||
|
||||
private String clientSessionHost;
|
||||
|
||||
public OAuthClient(WebDriver driver) {
|
||||
this.driver = driver;
|
||||
|
||||
|
@ -128,6 +133,14 @@ public class OAuthClient {
|
|||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, clientId));
|
||||
}
|
||||
|
||||
if(clientSessionState != null) {
|
||||
parameters.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_STATE, clientSessionState));
|
||||
}
|
||||
|
||||
if(clientSessionHost != null) {
|
||||
parameters.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_HOST, clientSessionHost));
|
||||
}
|
||||
|
||||
UrlEncodedFormEntity formEntity = null;
|
||||
try {
|
||||
formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
|
||||
|
@ -155,6 +168,13 @@ public class OAuthClient {
|
|||
parameters.add(new BasicNameValuePair("username", username));
|
||||
parameters.add(new BasicNameValuePair("password", password));
|
||||
|
||||
if(clientSessionState != null) {
|
||||
parameters.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_STATE, clientSessionState));
|
||||
}
|
||||
if(clientSessionHost != null) {
|
||||
parameters.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_HOST, clientSessionHost));
|
||||
}
|
||||
|
||||
UrlEncodedFormEntity formEntity;
|
||||
try {
|
||||
formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
|
||||
|
@ -211,6 +231,13 @@ public class OAuthClient {
|
|||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, clientId));
|
||||
}
|
||||
|
||||
if(clientSessionState != null) {
|
||||
parameters.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_STATE, clientSessionState));
|
||||
}
|
||||
if(clientSessionHost != null) {
|
||||
parameters.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_HOST, clientSessionHost));
|
||||
}
|
||||
|
||||
UrlEncodedFormEntity formEntity;
|
||||
try {
|
||||
formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
|
||||
|
@ -360,6 +387,16 @@ public class OAuthClient {
|
|||
return this;
|
||||
}
|
||||
|
||||
public OAuthClient clientSessionState(String client_session_state) {
|
||||
this.clientSessionState = client_session_state;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OAuthClient clientSessionHost(String client_session_host) {
|
||||
this.clientSessionHost = client_session_host;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
package org.keycloak.testsuite.oauth;
|
||||
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||
import org.keycloak.testsuite.rule.WebResource;
|
||||
import org.keycloak.testsuite.rule.WebRule;
|
||||
|
||||
/**
|
||||
* @author Sebastian Rose, AOE on 02.06.15.
|
||||
*/
|
||||
public class OAuthDanceClientSessionExtensionTest {
|
||||
|
||||
@ClassRule
|
||||
public static KeycloakRule keycloakRule = new KeycloakRule();
|
||||
|
||||
@Rule
|
||||
public WebRule webRule = new WebRule(this);
|
||||
|
||||
@WebResource
|
||||
protected OAuthClient oauth;
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||
|
||||
@Test
|
||||
public void doOauthDanceWithClientSessionStateAndHost() throws Exception {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
Event loginEvent = events.expectLogin().assertEvent();
|
||||
|
||||
String sessionId = loginEvent.getSessionId();
|
||||
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
|
||||
String clientSessionState = "1234";
|
||||
String clientSessionHost = "test-client-host";
|
||||
|
||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.clientSessionState(clientSessionState)
|
||||
.clientSessionHost(clientSessionHost)
|
||||
.doAccessTokenRequest(code, "password");
|
||||
|
||||
String refreshTokenString = tokenResponse.getRefreshToken();
|
||||
|
||||
Event tokenEvent = events.expectCodeToToken(codeId, sessionId)
|
||||
.detail(Details.CLIENT_SESSION_STATE, clientSessionState)
|
||||
.detail(Details.CLIENT_SESSION_HOST, clientSessionHost)
|
||||
.assertEvent();
|
||||
|
||||
|
||||
String updatedClientSessionState = "5678";
|
||||
|
||||
oauth.clientSessionState(updatedClientSessionState)
|
||||
.clientSessionHost(clientSessionHost)
|
||||
.doRefreshTokenRequest(refreshTokenString, "password");
|
||||
|
||||
events.expectRefresh(tokenEvent.getDetails().get(Details.REFRESH_TOKEN_ID), sessionId)
|
||||
.detail(Details.CLIENT_SESSION_STATE, updatedClientSessionState)
|
||||
.detail(Details.CLIENT_SESSION_HOST, clientSessionHost)
|
||||
.assertEvent();
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue