keycloak-aplcache

Merge pull request #1074 from patriot1burke/master oidc

3/22/2015 2:59:50 PM

Details

diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index 2ec8ec6..5072e1c 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -71,13 +71,11 @@ public class SamlProtocol implements LoginProtocol {
     public static final String SAML_SERVER_SIGNATURE = "saml.server.signature";
     public static final String SAML_ASSERTION_SIGNATURE = "saml.assertion.signature";
     public static final String SAML_AUTHNSTATEMENT = "saml.authnstatement";
-    public static final String SAML_MULTIVALUED_ROLES = "saml.multivalued.roles";
     public static final String SAML_SIGNATURE_ALGORITHM = "saml.signature.algorithm";
     public static final String SAML_ENCRYPT = "saml.encrypt";
     public static final String SAML_FORCE_POST_BINDING = "saml.force.post.binding";
     public static final String SAML_REQUEST_ID = "SAML_REQUEST_ID";
     public static final String SAML_LOGOUT_BINDING = "saml.logout.binding";
-    public static final String SAML_LOGOUT_ISSUER = "saml.logout.issuer";
     public static final String SAML_LOGOUT_REQUEST_ID = "SAML_LOGOUT_REQUEST_ID";
     public static final String SAML_LOGOUT_RELAY_STATE = "SAML_LOGOUT_RELAY_STATE";
     public static final String SAML_LOGOUT_BINDING_URI = "SAML_LOGOUT_BINDING_URI";
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
index ad5b302..07b9d00 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
@@ -1,5 +1,6 @@
 package org.keycloak.protocol.oidc.endpoints;
 
+import org.jboss.logging.Logger;
 import org.jboss.resteasy.annotations.cache.NoCache;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.ClientConnection;
@@ -18,6 +19,7 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.protocol.oidc.TokenManager;
 import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
 import org.keycloak.protocol.oidc.utils.RedirectUtils;
+import org.keycloak.representations.IDToken;
 import org.keycloak.representations.RefreshToken;
 import org.keycloak.services.ErrorResponseException;
 import org.keycloak.services.managers.AuthenticationManager;
@@ -42,6 +44,7 @@ import javax.ws.rs.core.UriInfo;
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
 public class LogoutEndpoint {
+    protected static Logger logger = Logger.getLogger(LogoutEndpoint.class);
 
     @Context
     private KeycloakSession session;
@@ -78,28 +81,60 @@ public class LogoutEndpoint {
      */
     @GET
     @NoCache
-    public Response logout(@QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri) {
-        if (redirectUri != null) {
-            String validatedUri = RedirectUtils.verifyRealmRedirectUri(uriInfo, redirectUri, realm);
+    public Response logout(@QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri, // deprecated
+                           @QueryParam("id_token_hint") String encodedIdToken,
+                           @QueryParam("post_logout_redirect_uri") String postLogoutRedirectUri,
+                           @QueryParam("state") String state) {
+        String redirect = postLogoutRedirectUri != null ? postLogoutRedirectUri : redirectUri;
+
+        if (redirect != null) {
+            String validatedUri = RedirectUtils.verifyRealmRedirectUri(uriInfo, redirect, realm);
             if (validatedUri == null) {
                 event.event(EventType.LOGOUT);
-                event.detail(Details.REDIRECT_URI, redirectUri);
+                event.detail(Details.REDIRECT_URI, redirect);
                 event.error(Errors.INVALID_REDIRECT_URI);
                 return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REDIRECT_URI);
             }
-            redirectUri = validatedUri;
+            redirect = validatedUri;
+        }
+
+        UserSessionModel userSession = null;
+        boolean error = false;
+        if (encodedIdToken != null) {
+            try {
+                IDToken idToken = tokenManager.verifyIDToken(realm, encodedIdToken);
+                userSession = session.sessions().getUserSession(realm, idToken.getSessionState());
+                if (userSession == null) {
+                    error = true;
+                }
+            } catch (OAuthErrorException e) {
+                error = true;
+            }
+            if (error) {
+                event.event(EventType.LOGOUT);
+                event.error(Errors.INVALID_TOKEN);
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE);
+            }
         }
 
         // authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
         AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false);
         if (authResult != null) {
-            if (redirectUri != null) authResult.getSession().setNote(OIDCLoginProtocol.LOGOUT_REDIRECT_URI, redirectUri);
-            authResult.getSession().setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, OIDCLoginProtocol.LOGIN_PROTOCOL);
+            userSession = userSession != null ? userSession : authResult.getSession();
+            if (redirectUri != null) userSession.setNote(OIDCLoginProtocol.LOGOUT_REDIRECT_URI, redirect);
+            if (state != null) userSession.setNote(OIDCLoginProtocol.LOGOUT_STATE_PARAM, state);
+            userSession.setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, OIDCLoginProtocol.LOGIN_PROTOCOL);
             return AuthenticationManager.browserLogout(session, realm, authResult.getSession(), uriInfo, clientConnection, headers);
+        } else if (userSession != null) { // non browser logout
+            event.event(EventType.LOGOUT);
+            authManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
+            event.user(userSession.getUser()).session(userSession).success();
         }
 
         if (redirectUri != null) {
-            return Response.status(302).location(UriBuilder.fromUri(redirectUri).build()).build();
+            UriBuilder uriBuilder = UriBuilder.fromUri(redirect);
+            if (state != null) uriBuilder.queryParam(OIDCLoginProtocol.STATE_PARAM, state);
+            return Response.status(302).location(uriBuilder.build()).build();
         } else {
             return Response.ok().build();
         }
@@ -149,7 +184,7 @@ public class LogoutEndpoint {
     }
 
     private void logout(UserSessionModel userSession) {
-        authManager.logout(session, realm, userSession, uriInfo, clientConnection, headers);
+        authManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
         event.user(userSession.getUser()).session(userSession).success();
     }
 
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
index cdbef96..022b6ac 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
@@ -49,6 +49,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
 
     public static final String LOGIN_PROTOCOL = "openid-connect";
     public static final String STATE_PARAM = "state";
+    public static final String LOGOUT_STATE_PARAM = "OIDC_LOGOUT_STATE_PARAM";
     public static final String SCOPE_PARAM = "scope";
     public static final String CODE_PARAM = "code";
     public static final String RESPONSE_TYPE_PARAM = "response_type";
@@ -182,6 +183,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
     @Override
     public Response finishLogout(UserSessionModel userSession) {
         String redirectUri = userSession.getNote(OIDCLoginProtocol.LOGOUT_REDIRECT_URI);
+        String state = userSession.getNote(OIDCLoginProtocol.LOGOUT_STATE_PARAM);
         event.event(EventType.LOGOUT);
         if (redirectUri != null) {
             event.detail(Details.REDIRECT_URI, redirectUri);
@@ -190,7 +192,9 @@ public class OIDCLoginProtocol implements LoginProtocol {
 
 
         if (redirectUri != null) {
-            return Response.status(302).location(UriBuilder.fromUri(redirectUri).build()).build();
+            UriBuilder uriBuilder = UriBuilder.fromUri(redirectUri);
+            if (state != null) uriBuilder.queryParam(STATE_PARAM, state);
+            return Response.status(302).location(uriBuilder.build()).build();
         } else {
             return Response.ok().build();
         }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index da9fdd8..01e244c 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -86,7 +86,7 @@ public class TokenManager {
 
         UserSessionModel userSession = session.sessions().getUserSession(realm, oldToken.getSessionState());
         if (!AuthenticationManager.isSessionValid(realm, userSession)) {
-            AuthenticationManager.logout(session, realm, userSession, uriInfo, connection, headers);
+            AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, connection, headers);
             throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session not active", "Session not active");
         }
         ClientSessionModel clientSession = null;
@@ -166,6 +166,26 @@ public class TokenManager {
         }
         return refreshToken;
     }
+    public IDToken verifyIDToken(RealmModel realm, String encodedIDToken) throws OAuthErrorException {
+        JWSInput jws = new JWSInput(encodedIDToken);
+        IDToken idToken = null;
+        try {
+            if (!RSAProvider.verify(jws, realm.getPublicKey())) {
+                throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token");
+            }
+            idToken = jws.readJsonContent(IDToken.class);
+        } catch (IOException e) {
+            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", e);
+        }
+        if (idToken.isExpired()) {
+            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Refresh token expired");
+        }
+
+        if (idToken.getIssuedAt() < realm.getNotBefore()) {
+            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token");
+        }
+        return idToken;
+    }
 
     public AccessToken createClientAccessToken(KeycloakSession session, Set<RoleModel> requestedRoles, RealmModel realm, ClientModel client, UserModel user, UserSessionModel userSession, ClientSessionModel clientSession) {
         AccessToken token = initToken(realm, client, user, userSession, clientSession);
@@ -405,6 +425,7 @@ public class TokenManager {
             idToken.issuedNow();
             idToken.issuedFor(accessToken.getIssuedFor());
             idToken.issuer(accessToken.getIssuer());
+            idToken.setSessionState(accessToken.getSessionState());
             if (realm.getAccessTokenLifespan() > 0) {
                 idToken.expiration(Time.currentTime() + realm.getAccessTokenLifespan());
             }
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 383f5e3..625845f 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -84,7 +84,7 @@ public class AuthenticationManager {
         return userSession != null && userSession.getLastSessionRefresh() + realm.getSsoSessionIdleTimeout() > currentTime && max > currentTime;
     }
 
-    public static void logout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
+    public static void backchannelLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
         if (userSession == null) return;
         UserModel user = userSession.getUser();
         userSession.setState(UserSessionModel.State.LOGGING_OUT);
@@ -461,7 +461,7 @@ public class AuthenticationManager {
 
             UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState());
             if (!isSessionValid(realm, userSession)) {
-                if (userSession != null) logout(session, realm, userSession, uriInfo, connection, headers);
+                if (userSession != null) backchannelLogout(session, realm, userSession, uriInfo, connection, headers);
                 logger.debug("User session not active");
                 return null;
             }
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index b4554ba..f9ee68b 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -586,7 +586,7 @@ public class LoginActionsService {
         }
 
         if (!AuthenticationManager.isSessionValid(realm, userSession)) {
-            AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection, headers);
+            AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
             event.error(Errors.INVALID_CODE);
             return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE);
         }