Details
diff --git a/events/api/src/main/java/org/keycloak/events/Details.java b/events/api/src/main/java/org/keycloak/events/Details.java
index cee7475..1a9c479 100755
--- a/events/api/src/main/java/org/keycloak/events/Details.java
+++ b/events/api/src/main/java/org/keycloak/events/Details.java
@@ -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";
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index ffbc6a7..e29d5d4 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -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);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthDanceClientSessionExtensionTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthDanceClientSessionExtensionTest.java
new file mode 100644
index 0000000..f097892
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthDanceClientSessionExtensionTest.java
@@ -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();
+
+ }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
index 3c81b8a..4e4f57a 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
@@ -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;
}