keycloak-aplcache

saml backchannel logout

10/7/2014 7:06:02 PM

Details

diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-detail.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-detail.html
index 3706d74..e23199a 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-detail.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-detail.html
@@ -68,12 +68,12 @@
                     <span tooltip-placement="right" tooltip="Valid URI pattern a browser can redirect to after a successful login or logout.  Simple wildcards are allowed i.e. 'http://example.com/*'.  Relative path can be specified too i.e. /my/relative/path/*.  Relative paths will generate a redirect URI using the request's host and port." class="fa fa-info-circle"></span>
                 </div>
                 <div class="form-group" data-ng-show="!application.bearerOnly && !create">
-                    <label class="col-sm-2 control-label" for="baseUrl">Base URL</label>
+                    <label class="col-sm-2 control-label" for="baseUrl">Default Redirect URL</label>
                     <div class="col-sm-6">
                         <input class="form-control" type="text" name="baseUrl" id="baseUrl"
                                data-ng-model="application.baseUrl">
                     </div>
-                    <span tooltip-placement="right" tooltip="Optional URL to use when linking to this application. i.e. the welcome page." class="fa fa-info-circle"></span>
+                    <span tooltip-placement="right" tooltip="Default URL to use when no redirect URI is specified.  This URL will also be used when the auth server needs to link to the application for any reason." class="fa fa-info-circle"></span>
                 </div>
                 <div class="form-group" data-ng-hide="create">
                     <label class="col-sm-2 control-label" for="adminUrl">Admin URL</label>
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingResponseBuilder.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingResponseBuilder.java
index 0d7d4f5..a1145a1 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingResponseBuilder.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2PostBindingResponseBuilder.java
@@ -146,7 +146,7 @@ public class SAML2PostBindingResponseBuilder {
         return this;
     }
 
-    public Response error(String status)  throws ConfigurationException, ProcessingException, IOException {
+    public Response buildErrorResponse(String status)  throws ConfigurationException, ProcessingException, IOException {
         Document doc = getErrorResponse(status);
         return buildResponse(doc);
 
@@ -207,7 +207,7 @@ public class SAML2PostBindingResponseBuilder {
         return samlResponse;
     }
 
-    public Response build() throws ConfigurationException, ProcessingException, IOException {
+    public Response buildLoginResponse() throws ConfigurationException, ProcessingException, IOException {
         Document responseDoc = getResponse();
         return buildResponse(responseDoc);
     }
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlLogin.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlLogin.java
index 8344170..dfc4081 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlLogin.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlLogin.java
@@ -1,8 +1,10 @@
 package org.keycloak.protocol.saml;
 
 import org.jboss.logging.Logger;
-import org.jboss.resteasy.spi.HttpRequest;
-import org.keycloak.ClientConnection;
+import org.jboss.resteasy.client.ClientRequest;
+import org.jboss.resteasy.client.ClientResponse;
+import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
+import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.ClaimMask;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
@@ -13,17 +15,31 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.managers.ResourceAdminManager;
 import org.keycloak.services.resources.RealmsResource;
 import org.keycloak.services.resources.flows.Flows;
 import org.picketlink.common.constants.GeneralConstants;
 import org.picketlink.common.constants.JBossSAMLURIConstants;
 import org.picketlink.common.exceptions.ConfigurationException;
+import org.picketlink.common.exceptions.ParsingException;
 import org.picketlink.common.exceptions.ProcessingException;
+import org.picketlink.common.util.StringUtil;
+import org.picketlink.identity.federation.api.saml.v2.request.SAML2Request;
 import org.picketlink.identity.federation.core.saml.v2.constants.X500SAMLProfileConstants;
+import org.picketlink.identity.federation.core.saml.v2.util.DocumentUtil;
+import org.picketlink.identity.federation.core.saml.v2.util.XMLTimeUtil;
+import org.picketlink.identity.federation.core.sts.PicketLinkCoreSTS;
+import org.picketlink.identity.federation.saml.v2.assertion.NameIDType;
+import org.picketlink.identity.federation.saml.v2.protocol.LogoutRequestType;
+import org.picketlink.identity.federation.web.handlers.saml2.SAML2LogOutHandler;
+import org.picketlink.identity.federation.web.util.PostBindingUtil;
+import org.w3c.dom.Document;
 
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 import java.io.IOException;
+import java.net.URI;
+import java.security.Principal;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -39,11 +55,8 @@ public class SamlLogin implements LoginProtocol {
 
     protected RealmModel realm;
 
-    protected HttpRequest request;
-
     protected UriInfo uriInfo;
 
-    protected ClientConnection clientConnection;
 
 
     @Override
@@ -59,24 +72,12 @@ public class SamlLogin implements LoginProtocol {
     }
 
     @Override
-    public SamlLogin setRequest(HttpRequest request) {
-        this.request = request;
-        return this;
-    }
-
-    @Override
     public SamlLogin setUriInfo(UriInfo uriInfo) {
         this.uriInfo = uriInfo;
         return this;
     }
 
     @Override
-    public SamlLogin setClientConnection(ClientConnection clientConnection) {
-        this.clientConnection = clientConnection;
-        return this;
-    }
-
-    @Override
     public Response cancelLogin(ClientSessionModel clientSession) {
         return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
     }
@@ -100,7 +101,7 @@ public class SamlLogin implements LoginProtocol {
                 .responseIssuer(responseIssuer)
                 .requestIssuer(clientSession.getClient().getClientId());
         try {
-            return builder.error(status);
+            return builder.buildErrorResponse(status);
         } catch (Exception e) {
             return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response");
         }
@@ -140,7 +141,7 @@ public class SamlLogin implements LoginProtocol {
         }
 
         try {
-            return builder.build();
+            return builder.buildLoginResponse();
         } catch (Exception e) {
             logger.error("failed", e);
             return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response");
@@ -164,6 +165,66 @@ public class SamlLogin implements LoginProtocol {
     }
 
     @Override
+    public void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
+        ClientModel client = clientSession.getClient();
+        if (!(client instanceof ApplicationModel)) return;
+        ApplicationModel app = (ApplicationModel)client;
+        if (app.getManagementUrl() == null) return;
+
+        String logoutRequestString = null;
+        try {
+            LogoutRequestType logoutRequest = createLogoutRequest(userSession.getUser(), client);
+            Document logoutRequestDocument = new SAML2Request().convert(logoutRequest);
+
+            byte[] responseBytes = DocumentUtil.getDocumentAsString(logoutRequestDocument).getBytes("UTF-8");
+            logoutRequestString = PostBindingUtil.base64Encode(new String(responseBytes));
+        } catch (Exception e) {
+            logger.warn("failed to send saml logout", e);
+        }
+
+
+        String adminUrl = ResourceAdminManager.getManagementUrl(uriInfo.getRequestUri(), app);
+
+        ApacheHttpClient4Executor executor = ResourceAdminManager.createExecutor();
+
+
+        try {
+            ClientRequest request = executor.createRequest(adminUrl);
+            request.formParameter(GeneralConstants.SAML_REQUEST_KEY, logoutRequestString);
+            request.formParameter(SAML2LogOutHandler.BACK_CHANNEL_LOGOUT, SAML2LogOutHandler.BACK_CHANNEL_LOGOUT);
+            ClientResponse response = null;
+            try {
+                response = request.post();
+            } catch (Exception e) {
+                logger.warn("failed to send saml logout", e);
+            }
+            response.releaseConnection();
+
+        } finally {
+            executor.getHttpClient().getConnectionManager().shutdown();
+        }
+
+    }
+
+    private LogoutRequestType createLogoutRequest(UserModel user, ClientModel client) throws ConfigurationException, ProcessingException {
+        LogoutRequestType lort = new SAML2Request().createLogoutRequest(getResponseIssuer(realm));
+
+        NameIDType nameID = new NameIDType();
+        nameID.setValue(user.getUsername());
+        //Deal with NameID Format
+        String nameIDFormat = JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get();
+        nameID.setFormat(URI.create(nameIDFormat));
+        lort.setNameID(nameID);
+
+        long assertionValidity = PicketLinkCoreSTS.instance().getConfiguration().getIssuedTokenTimeout();
+
+        lort.setNotOnOrAfter(XMLTimeUtil.add(lort.getIssueInstant(), assertionValidity));
+        lort.setDestination(URI.create(client.getClientId()));
+        return lort;
+    }
+
+
+    @Override
     public void close() {
 
     }
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
index a5ed37d..3449d65 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -26,6 +26,7 @@ import org.picketlink.common.constants.GeneralConstants;
 import org.picketlink.identity.federation.core.saml.v2.common.SAMLDocumentHolder;
 import org.picketlink.identity.federation.saml.v2.SAML2Object;
 import org.picketlink.identity.federation.saml.v2.protocol.AuthnRequestType;
+import org.picketlink.identity.federation.saml.v2.protocol.LogoutRequestType;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.FormParam;
@@ -94,38 +95,66 @@ public class SamlService {
     @Path("POST")
     @POST
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response loginPage(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
-                              @FormParam(GeneralConstants.RELAY_STATE) String relayState) {
-        event.event(EventType.LOGIN);
+    public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
+                                @FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
+                                @FormParam(GeneralConstants.RELAY_STATE) String relayState) {
         if (!checkSsl()) {
+            event.event(EventType.LOGIN_ERROR);
             event.error(Errors.SSL_REQUIRED);
             return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
         }
         if (!realm.isEnabled()) {
+            event.event(EventType.LOGIN_ERROR);
             event.error(Errors.REALM_DISABLED);
             return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled");
         }
 
-        if (samlRequest == null) {
+        if (samlRequest == null && samlResponse == null) {
+            event.event(EventType.LOGIN_ERROR);
             event.error(Errors.INVALID_TOKEN);
             return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
 
         }
 
+        if (samlRequest != null) return handleSamlRequest(samlRequest, relayState);
+        else  return handleSamlResponse(samlResponse, relayState);
+    }
+
+    protected Response handleSamlResponse(String samleResponse, String relayState) {
+        event.event(EventType.LOGIN_ERROR);
+        event.error(Errors.INVALID_TOKEN);
+        return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
+    }
+
+
+    protected Response handleSamlRequest(String samlRequest, String relayState) {
         SAMLDocumentHolder documentHolder = SAMLRequestParser.parsePostBinding(samlRequest);
         if (documentHolder == null) {
+            event.event(EventType.LOGIN_ERROR);
             event.error(Errors.INVALID_TOKEN);
             return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
         }
 
         SAML2Object samlObject = documentHolder.getSamlObject();
-        if (!(samlObject instanceof AuthnRequestType)) {
+
+        if (samlObject instanceof AuthnRequestType) {
+            event.event(EventType.LOGIN);
+            // Get the SAML Request Message
+            AuthnRequestType requestAbstractType = (AuthnRequestType) samlObject;
+            return loginRequest(relayState, requestAbstractType);
+        } else if (samlObject instanceof LogoutRequestType) {
+            event.event(EventType.LOGOUT);
+            LogoutRequestType requestAbstractType = (LogoutRequestType) samlObject;
+            return logoutRequest(relayState, requestAbstractType);
+
+        } else {
+            event.event(EventType.LOGIN_ERROR);
             event.error(Errors.INVALID_TOKEN);
             return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
         }
+    }
 
-        // Get the SAML Request Message
-        AuthnRequestType requestAbstractType = (AuthnRequestType) samlObject;
+    protected Response loginRequest(String relayState, AuthnRequestType requestAbstractType) {
         String issuer = requestAbstractType.getIssuer().getValue();
         ClientModel client = realm.findClient(issuer);
 
@@ -189,29 +218,42 @@ public class SamlService {
         return forms.createLogin();
     }
 
+    protected Response logoutRequest(String relayState, LogoutRequestType requestAbstractType) {
+        String issuer = requestAbstractType.getIssuer().getValue();
+        ClientModel client = realm.findClient(issuer);
 
-    /**
-     * Logout user session.  User must be logged in via a session cookie.
-     *
-     * @param redirectUri
-     * @return
-     */
-    @Path("logout")
-    @GET
-    @NoCache
-    public Response logout(final @QueryParam("shit") String redirectUri) {
-        event.event(EventType.LOGOUT);
-        if (redirectUri != null) {
-            event.detail(Details.REDIRECT_URI, redirectUri);
+        if (client == null) {
+            event.error(Errors.CLIENT_NOT_FOUND);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester.");
         }
+
+        if (!client.isEnabled()) {
+            event.error(Errors.CLIENT_DISABLED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled.");
+        }
+        if ((client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) {
+            event.error(Errors.NOT_ALLOWED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Bearer-only applications are not allowed to initiate browser login");
+        }
+        if (client.isDirectGrantsOnly()) {
+            event.error(Errors.NOT_ALLOWED);
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "direct-grants-only clients are not allowed to initiate browser login");
+        }
+
         // 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) {
             logout(authResult.getSession());
         }
 
+        String redirectUri = null;
+
+        if (client instanceof ApplicationModel) {
+            redirectUri = ((ApplicationModel)client).getBaseUrl();
+        }
+
         if (redirectUri != null) {
-            String validatedRedirect = OpenIDConnectService.verifyRealmRedirectUri(uriInfo, redirectUri, realm);
+            String validatedRedirect = OpenIDConnectService.verifyRedirectUri(uriInfo, redirectUri, realm, client);;
             if (validatedRedirect == null) {
                 return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri.");
             }
@@ -219,6 +261,7 @@ public class SamlService {
         } else {
             return Response.ok().build();
         }
+
     }
 
     private void logout(UserSessionModel userSession) {
@@ -233,14 +276,4 @@ public class SamlService {
             return !realm.getSslRequired().isRequired(clientConnection);
         }
     }
-
-    private Response createError(String error, String errorDescription, Response.Status status) {
-        Map<String, String> e = new HashMap<String, String>();
-        e.put(OAuth2Constants.ERROR, error);
-        if (errorDescription != null) {
-            e.put(OAuth2Constants.ERROR_DESCRIPTION, errorDescription);
-        }
-        return Response.status(status).entity(e).type("application/json").build();
-    }
-
 }
diff --git a/services/src/main/java/org/keycloak/protocol/LoginProtocol.java b/services/src/main/java/org/keycloak/protocol/LoginProtocol.java
index 86de30a..47e672a 100755
--- a/services/src/main/java/org/keycloak/protocol/LoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/LoginProtocol.java
@@ -22,14 +22,12 @@ public interface LoginProtocol extends Provider {
 
     LoginProtocol setRealm(RealmModel realm);
 
-    LoginProtocol setRequest(HttpRequest request);
-
     LoginProtocol setUriInfo(UriInfo uriInfo);
 
-    LoginProtocol setClientConnection(ClientConnection clientConnection);
-
     Response cancelLogin(ClientSessionModel clientSession);
     Response invalidSessionError(ClientSessionModel clientSession);
     Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode);
     Response consentDenied(ClientSessionModel clientSession);
+
+    void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession);
 }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnect.java b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnect.java
index b3afe30..c67960c 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnect.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnect.java
@@ -22,15 +22,18 @@
 package org.keycloak.protocol.oidc;
 
 import org.jboss.logging.Logger;
+import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.ClientConnection;
 import org.keycloak.OAuth2Constants;
+import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.managers.ResourceAdminManager;
 
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
@@ -56,19 +59,12 @@ public class OpenIDConnect implements LoginProtocol {
 
     protected RealmModel realm;
 
-    protected HttpRequest request;
-
     protected UriInfo uriInfo;
 
-    protected ClientConnection clientConnection;
-
-    public OpenIDConnect(KeycloakSession session, RealmModel realm, HttpRequest request, UriInfo uriInfo,
-                         ClientConnection clientConnection) {
+    public OpenIDConnect(KeycloakSession session, RealmModel realm, UriInfo uriInfo) {
         this.session = session;
         this.realm = realm;
-        this.request = request;
         this.uriInfo = uriInfo;
-        this.clientConnection = clientConnection;
     }
 
     public OpenIDConnect() {
@@ -87,24 +83,12 @@ public class OpenIDConnect implements LoginProtocol {
     }
 
     @Override
-    public OpenIDConnect setRequest(HttpRequest request) {
-        this.request = request;
-        return this;
-    }
-
-    @Override
     public OpenIDConnect setUriInfo(UriInfo uriInfo) {
         this.uriInfo = uriInfo;
         return this;
     }
 
     @Override
-    public OpenIDConnect setClientConnection(ClientConnection clientConnection) {
-        this.clientConnection = clientConnection;
-        return this;
-    }
-
-    @Override
     public Response cancelLogin(ClientSessionModel clientSession) {
         String redirect = clientSession.getRedirectUri();
         String state = clientSession.getNote(OpenIDConnect.STATE_PARAM);
@@ -152,6 +136,19 @@ public class OpenIDConnect implements LoginProtocol {
     }
 
     @Override
+    public void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
+        if (!(clientSession.getClient() instanceof ApplicationModel)) return;
+        ApplicationModel app = (ApplicationModel)clientSession.getClient();
+        ApacheHttpClient4Executor executor = ResourceAdminManager.createExecutor();
+
+        try {
+            new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, app, null, userSession.getId(), executor, 0);
+        } finally {
+            executor.getHttpClient().getConnectionManager().shutdown();
+        }
+    }
+
+    @Override
     public void close() {
 
     }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
index 7ee021b..b310403 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
@@ -800,7 +800,7 @@ public class OpenIDConnectService {
         if (response != null) return response;
 
         if (prompt != null && prompt.equals("none")) {
-            OpenIDConnect oauth = new OpenIDConnect(session, realm, request, uriInfo, clientConnection);
+            OpenIDConnect oauth = new OpenIDConnect(session, realm, uriInfo);
             return oauth.cancelLogin(clientSession);
         }
 
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 2061687..450dcf6 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -86,7 +86,17 @@ public class AuthenticationManager {
         expireIdentityCookie(realm, uriInfo, connection);
         expireRememberMeCookie(realm, uriInfo, connection);
 
-        new ResourceAdminManager().logoutUser(uriInfo.getRequestUri(), realm, user.getId(), userSession);
+        for (ClientSessionModel clientSession : userSession.getClientSessions()) {
+            ClientModel client = clientSession.getClient();
+            if (client instanceof ApplicationModel) {
+                String authMethod = clientSession.getAuthMethod();
+                if (authMethod == null) continue; // must be a keycloak service like account
+                LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod);
+                protocol.setRealm(realm)
+                        .setUriInfo(uriInfo);
+                protocol.backchannelLogout(userSession, clientSession);
+            }
+        }
 
         session.sessions().removeUserSession(realm, userSession);
     }
@@ -235,9 +245,7 @@ public class AuthenticationManager {
         if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection);
         LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
         protocol.setRealm(realm)
-                .setRequest(request)
-                .setUriInfo(uriInfo)
-                .setClientConnection(clientConnection);
+                .setUriInfo(uriInfo);
         return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession));
 
     }
diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
index d754b7f..8b55f00 100755
--- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
@@ -101,7 +101,7 @@ public class ResourceAdminManager {
 
     }
 
-    protected String getManagementUrl(URI requestUri, ApplicationModel application) {
+    public static String getManagementUrl(URI requestUri, ApplicationModel application) {
         String mgmtUrl = application.getManagementUrl();
         if (mgmtUrl == null || mgmtUrl.equals("")) {
             return null;
@@ -231,7 +231,7 @@ public class ResourceAdminManager {
     }
 
 
-    protected boolean logoutApplication(URI requestUri, RealmModel realm, ApplicationModel resource, String user, String session, ApacheHttpClient4Executor client, int notBefore) {
+    public boolean logoutApplication(URI requestUri, RealmModel realm, ApplicationModel resource, String user, String session, ApacheHttpClient4Executor client, int notBefore) {
         String managementUrl = getManagementUrl(requestUri, resource);
         if (managementUrl != null) {
             LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getName(), user, session, notBefore);
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 c6bcc8c..7310d74 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -309,9 +309,7 @@ public class LoginActionsService {
             event.error(Errors.REJECTED_BY_USER);
             LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
             protocol.setRealm(realm)
-                    .setRequest(request)
-                    .setUriInfo(uriInfo)
-                    .setClientConnection(clientConnection);
+                    .setUriInfo(uriInfo);
             return protocol.cancelLogin(clientSession);
         }
 
@@ -565,9 +563,7 @@ public class LoginActionsService {
 
         LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
         protocol.setRealm(realm)
-                .setRequest(request)
-                .setUriInfo(uriInfo)
-                .setClientConnection(clientConnection);
+                .setUriInfo(uriInfo);
         if (formData.containsKey("cancel")) {
             event.error(Errors.REJECTED_BY_USER);
             return protocol.consentDenied(clientSession);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
index ce0d51f..4001dfa 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
@@ -1,421 +1,429 @@
-/*
- * JBoss, Home of Professional Open Source.
- * Copyright 2012, Red Hat, Inc., and individual contributors
- * as indicated by the @author tags. See the copyright.txt file in the
- * distribution for a full listing of individual contributors.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
- */
-package org.keycloak.testsuite.adapter;
-
-import org.junit.Assert;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.keycloak.Config;
-import org.keycloak.OAuth2Constants;
-import org.keycloak.Version;
-import org.keycloak.adapters.AdapterConstants;
-import org.keycloak.models.ApplicationModel;
-import org.keycloak.models.Constants;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.UserSessionModel;
-import org.keycloak.protocol.oidc.OpenIDConnectService;
-import org.keycloak.protocol.oidc.TokenManager;
-import org.keycloak.representations.AccessToken;
-import org.keycloak.representations.adapters.action.SessionStats;
-import org.keycloak.representations.idm.RealmRepresentation;
-import org.keycloak.services.managers.RealmManager;
-import org.keycloak.services.resources.admin.AdminRoot;
-import org.keycloak.testsuite.OAuthClient;
-import org.keycloak.testsuite.pages.LoginPage;
-import org.keycloak.testsuite.rule.AbstractKeycloakRule;
-import org.keycloak.testsuite.rule.WebResource;
-import org.keycloak.testsuite.rule.WebRule;
-import org.keycloak.testutils.KeycloakServer;
-import org.keycloak.util.BasicAuthHelper;
-import org.openqa.selenium.WebDriver;
-
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.ClientBuilder;
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.client.WebTarget;
-import javax.ws.rs.core.Form;
-import javax.ws.rs.core.GenericType;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriBuilder;
-import java.net.URI;
-import java.net.URL;
-import java.security.PublicKey;
-import java.util.Map;
-
-/**
- * Tests Undertow Adapter
- *
- * @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
- */
-public class AdapterTest {
-
-    public static final String LOGIN_URL = OpenIDConnectService.loginPageUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString();
-    public static PublicKey realmPublicKey;
-    @ClassRule
-    public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() {
-        @Override
-        protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
-            RealmRepresentation representation = KeycloakServer.loadJson(getClass().getResourceAsStream("/adapter-test/demorealm.json"), RealmRepresentation.class);
-            RealmModel realm = manager.importRealm(representation);
-
-            realmPublicKey = realm.getPublicKey();
-
-            URL url = getClass().getResource("/adapter-test/cust-app-keycloak.json");
-            deployApplication("customer-portal", "/customer-portal", CustomerServlet.class, url.getPath(), "user");
-            url = getClass().getResource("/adapter-test/secure-portal-keycloak.json");
-            deployApplication("secure-portal", "/secure-portal", CallAuthenticatedServlet.class, url.getPath(), "user", false);
-            url = getClass().getResource("/adapter-test/customer-db-keycloak.json");
-            deployApplication("customer-db", "/customer-db", CustomerDatabaseServlet.class, url.getPath(), "user");
-            url = getClass().getResource("/adapter-test/product-keycloak.json");
-            deployApplication("product-portal", "/product-portal", ProductServlet.class, url.getPath(), "user");
-
-        }
-    };
-
-    private static String createToken() {
-        KeycloakSession session = keycloakRule.startSession();
-        try {
-            RealmManager manager = new RealmManager(session);
-
-            RealmModel adminRealm = manager.getRealm(Config.getAdminRealm());
-            ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
-            TokenManager tm = new TokenManager();
-            UserModel admin = session.users().getUserByUsername("admin", adminRealm);
-            UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false);
-            AccessToken token = tm.createClientAccessToken(tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession);
-            return tm.encodeToken(adminRealm, token);
-        } finally {
-            keycloakRule.stopSession(session, true);
-        }
-    }
-
-
-    @Rule
-    public WebRule webRule = new WebRule(this);
-
-    @WebResource
-    protected WebDriver driver;
-
-    @WebResource
-    protected OAuthClient oauth;
-
-    @WebResource
-    protected LoginPage loginPage;
-
-    @Test
-    public void testLoginSSOAndLogout() throws Exception {
-        // test login to customer-portal which does a bearer request to customer-db
-        driver.navigate().to("http://localhost:8081/customer-portal");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-        loginPage.login("bburke@redhat.com", "password");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
-        String pageSource = driver.getPageSource();
-        System.out.println(pageSource);
-        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
-
-        // test SSO
-        driver.navigate().to("http://localhost:8081/product-portal");
-        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/product-portal");
-        pageSource = driver.getPageSource();
-        System.out.println(pageSource);
-        Assert.assertTrue(pageSource.contains("iPhone") && pageSource.contains("iPad"));
-
-        // View stats
-        String adminToken = createToken();
-
-        Client client = ClientBuilder.newClient();
-        UriBuilder authBase = UriBuilder.fromUri("http://localhost:8081/auth");
-        WebTarget adminTarget = client.target(AdminRoot.realmsUrl(authBase)).path("demo");
-        Map<String, SessionStats> stats = adminTarget.path("session-stats").request()
-                .header(HttpHeaders.AUTHORIZATION, "Bearer " + adminToken)
-                .get(new GenericType<Map<String, SessionStats>>() {
-                });
-
-        SessionStats custStats = stats.get("customer-portal");
-        Assert.assertNotNull(custStats);
-        Assert.assertEquals(1, custStats.getActiveSessions());
-        SessionStats prodStats = stats.get("product-portal");
-        Assert.assertNotNull(prodStats);
-        Assert.assertEquals(1, prodStats.getActiveSessions());
-
-        client.close();
-
-
-        // test logout
-
-        String logoutUri = OpenIDConnectService.logoutUrl(UriBuilder.fromUri("http://localhost:8081/auth"))
-                .queryParam(OAuth2Constants.REDIRECT_URI, "http://localhost:8081/customer-portal").build("demo").toString();
-        driver.navigate().to(logoutUri);
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-        driver.navigate().to("http://localhost:8081/product-portal");
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-        driver.navigate().to("http://localhost:8081/customer-portal");
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-
-
-    }
-
-    @Test
-    public void testServletRequestLogout() throws Exception {
-        // test login to customer-portal which does a bearer request to customer-db
-        driver.navigate().to("http://localhost:8081/customer-portal");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-        loginPage.login("bburke@redhat.com", "password");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
-        String pageSource = driver.getPageSource();
-        System.out.println(pageSource);
-        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
-
-        // test SSO
-        driver.navigate().to("http://localhost:8081/product-portal");
-        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/product-portal");
-        pageSource = driver.getPageSource();
-        System.out.println(pageSource);
-        Assert.assertTrue(pageSource.contains("iPhone") && pageSource.contains("iPad"));
-
-        // back
-        driver.navigate().to("http://localhost:8081/customer-portal");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
-        pageSource = driver.getPageSource();
-        System.out.println(pageSource);
-        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
-        // test logout
-
-        driver.navigate().to("http://localhost:8081/customer-portal/logout");
-
-
-
-        driver.navigate().to("http://localhost:8081/customer-portal");
-        String currentUrl = driver.getCurrentUrl();
-        Assert.assertTrue(currentUrl.startsWith(LOGIN_URL));
-        driver.navigate().to("http://localhost:8081/product-portal");
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-
-
-    }
-
-    @Test
-    public void testLoginSSOIdle() throws Exception {
-        // test login to customer-portal which does a bearer request to customer-db
-        driver.navigate().to("http://localhost:8081/customer-portal");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-        loginPage.login("bburke@redhat.com", "password");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
-        String pageSource = driver.getPageSource();
-        System.out.println(pageSource);
-        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
-
-        KeycloakSession session = keycloakRule.startSession();
-        RealmModel realm = session.realms().getRealmByName("demo");
-        int originalIdle = realm.getSsoSessionIdleTimeout();
-        realm.setSsoSessionIdleTimeout(1);
-        session.getTransaction().commit();
-        session.close();
-
-        Thread.sleep(2000);
-
-
-        // test SSO
-        driver.navigate().to("http://localhost:8081/product-portal");
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-
-        session = keycloakRule.startSession();
-        realm = session.realms().getRealmByName("demo");
-        realm.setSsoSessionIdleTimeout(originalIdle);
-        session.getTransaction().commit();
-        session.close();
-    }
-
-    @Test
-    public void testLoginSSOIdleRemoveExpiredUserSessions() throws Exception {
-        // test login to customer-portal which does a bearer request to customer-db
-        driver.navigate().to("http://localhost:8081/customer-portal");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-        loginPage.login("bburke@redhat.com", "password");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
-        String pageSource = driver.getPageSource();
-        System.out.println(pageSource);
-        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
-
-        KeycloakSession session = keycloakRule.startSession();
-        RealmModel realm = session.realms().getRealmByName("demo");
-        int originalIdle = realm.getSsoSessionIdleTimeout();
-        realm.setSsoSessionIdleTimeout(1);
-        session.getTransaction().commit();
-        session.close();
-
-        Thread.sleep(2000);
-
-        session = keycloakRule.startSession();
-        realm = session.realms().getRealmByName("demo");
-        session.sessions().removeExpiredUserSessions(realm);
-        session.getTransaction().commit();
-        session.close();
-
-        // test SSO
-        driver.navigate().to("http://localhost:8081/product-portal");
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-
-        session = keycloakRule.startSession();
-        realm = session.realms().getRealmByName("demo");
-        realm.setSsoSessionIdleTimeout(originalIdle);
-        session.getTransaction().commit();
-        session.close();
-    }
-
-    @Test
-    public void testLoginSSOMax() throws Exception {
-        // test login to customer-portal which does a bearer request to customer-db
-        driver.navigate().to("http://localhost:8081/customer-portal");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-        loginPage.login("bburke@redhat.com", "password");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
-        String pageSource = driver.getPageSource();
-        System.out.println(pageSource);
-        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
-
-        KeycloakSession session = keycloakRule.startSession();
-        RealmModel realm = session.realms().getRealmByName("demo");
-        int original = realm.getSsoSessionMaxLifespan();
-        realm.setSsoSessionMaxLifespan(1);
-        session.getTransaction().commit();
-        session.close();
-
-        Thread.sleep(2000);
-
-
-        // test SSO
-        driver.navigate().to("http://localhost:8081/product-portal");
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-
-        session = keycloakRule.startSession();
-        realm = session.realms().getRealmByName("demo");
-        realm.setSsoSessionMaxLifespan(original);
-        session.getTransaction().commit();
-        session.close();
-    }
-
-    /**
-     * KEYCLOAK-518
-     * @throws Exception
-     */
-    @Test
-    public void testNullBearerToken() throws Exception {
-        Client client = ClientBuilder.newClient();
-        WebTarget target = client.target("http://localhost:8081/customer-db");
-        Response response = target.request().get();
-        Assert.assertEquals(401, response.getStatus());
-        response.close();
-        response = target.request().header(HttpHeaders.AUTHORIZATION, "Bearer null").get();
-        Assert.assertEquals(401, response.getStatus());
-        response.close();
-        client.close();
-
-    }
-
-    /**
-     * KEYCLOAK-518
-     * @throws Exception
-     */
-    @Test
-    public void testBadUser() throws Exception {
-        Client client = ClientBuilder.newClient();
-        UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT);
-        URI uri = OpenIDConnectService.grantAccessTokenUrl(builder).build("demo");
-        WebTarget target = client.target(uri);
-        String header = BasicAuthHelper.createHeader("customer-portal", "password");
-        Form form = new Form();
-        form.param("username", "monkey@redhat.com")
-            .param("password", "password");
-        Response response = target.request()
-                .header(HttpHeaders.AUTHORIZATION, header)
-                .post(Entity.form(form));
-        Assert.assertEquals(400, response.getStatus());
-        response.close();
-        client.close();
-
-    }
-
-    @Test
-    public void testVersion() throws Exception {
-        Client client = ClientBuilder.newClient();
-        WebTarget target = client.target(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT).path("version");
-        Version version = target.request().get(Version.class);
-        Assert.assertNotNull(version);
-        Assert.assertNotNull(version.getVersion());
-        Assert.assertNotNull(version.getBuildTime());
-        Assert.assertNotEquals(version.getVersion(), Version.UNKNOWN);
-        Assert.assertNotEquals(version.getBuildTime(), Version.UNKNOWN);
-
-        Version version2 = client.target("http://localhost:8081/secure-portal").path(AdapterConstants.K_VERSION).request().get(Version.class);
-        Assert.assertNotNull(version2);
-        Assert.assertNotNull(version2.getVersion());
-        Assert.assertNotNull(version2.getBuildTime());
-        Assert.assertEquals(version.getVersion(), version2.getVersion());
-        Assert.assertEquals(version.getBuildTime(), version2.getBuildTime());
-        client.close();
-
-    }
-
-
-
-    @Test
-    public void testAuthenticated() throws Exception {
-        // test login to customer-portal which does a bearer request to customer-db
-        driver.navigate().to("http://localhost:8081/secure-portal");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-        loginPage.login("bburke@redhat.com", "password");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/secure-portal");
-        String pageSource = driver.getPageSource();
-        System.out.println(pageSource);
-        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
-
-        // test logout
-
-        String logoutUri = OpenIDConnectService.logoutUrl(UriBuilder.fromUri("http://localhost:8081/auth"))
-                .queryParam(OAuth2Constants.REDIRECT_URI, "http://localhost:8081/secure-portal").build("demo").toString();
-        driver.navigate().to(logoutUri);
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-        driver.navigate().to("http://localhost:8081/secure-portal");
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-    }
-
-
-
-}
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.adapter;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.Config;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.Version;
+import org.keycloak.adapters.AdapterConstants;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.oidc.OpenIDConnectService;
+import org.keycloak.protocol.oidc.TokenManager;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.adapters.action.SessionStats;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.managers.ResourceAdminManager;
+import org.keycloak.services.resources.admin.AdminRoot;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.rule.AbstractKeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.keycloak.testutils.KeycloakServer;
+import org.keycloak.util.BasicAuthHelper;
+import org.openqa.selenium.WebDriver;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import java.net.URI;
+import java.net.URL;
+import java.security.PublicKey;
+import java.util.Map;
+
+/**
+ * Tests Undertow Adapter
+ *
+ * @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
+ */
+public class AdapterTest {
+
+    public static final String LOGIN_URL = OpenIDConnectService.loginPageUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString();
+    public static PublicKey realmPublicKey;
+    @ClassRule
+    public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() {
+        @Override
+        protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
+            RealmRepresentation representation = KeycloakServer.loadJson(getClass().getResourceAsStream("/adapter-test/demorealm.json"), RealmRepresentation.class);
+            RealmModel realm = manager.importRealm(representation);
+
+            realmPublicKey = realm.getPublicKey();
+
+            URL url = getClass().getResource("/adapter-test/cust-app-keycloak.json");
+            deployApplication("customer-portal", "/customer-portal", CustomerServlet.class, url.getPath(), "user");
+            url = getClass().getResource("/adapter-test/secure-portal-keycloak.json");
+            deployApplication("secure-portal", "/secure-portal", CallAuthenticatedServlet.class, url.getPath(), "user", false);
+            url = getClass().getResource("/adapter-test/customer-db-keycloak.json");
+            deployApplication("customer-db", "/customer-db", CustomerDatabaseServlet.class, url.getPath(), "user");
+            url = getClass().getResource("/adapter-test/product-keycloak.json");
+            deployApplication("product-portal", "/product-portal", ProductServlet.class, url.getPath(), "user");
+
+        }
+    };
+
+    private static String createToken() {
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            RealmManager manager = new RealmManager(session);
+
+            RealmModel adminRealm = manager.getRealm(Config.getAdminRealm());
+            ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
+            TokenManager tm = new TokenManager();
+            UserModel admin = session.users().getUserByUsername("admin", adminRealm);
+            UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false);
+            AccessToken token = tm.createClientAccessToken(tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession);
+            return tm.encodeToken(adminRealm, token);
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+    }
+
+
+    @Rule
+    public WebRule webRule = new WebRule(this);
+
+    @WebResource
+    protected WebDriver driver;
+
+    @WebResource
+    protected OAuthClient oauth;
+
+    @WebResource
+    protected LoginPage loginPage;
+
+    @Test
+    public void testLoginSSOAndLogout() throws Exception {
+        // test login to customer-portal which does a bearer request to customer-db
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        loginPage.login("bburke@redhat.com", "password");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
+        String pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
+
+        // test SSO
+        driver.navigate().to("http://localhost:8081/product-portal");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/product-portal");
+        pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("iPhone") && pageSource.contains("iPad"));
+
+        // View stats
+        String adminToken = createToken();
+
+        Client client = ClientBuilder.newClient();
+        UriBuilder authBase = UriBuilder.fromUri("http://localhost:8081/auth");
+        WebTarget adminTarget = client.target(AdminRoot.realmsUrl(authBase)).path("demo");
+        Map<String, SessionStats> stats = adminTarget.path("session-stats").request()
+                .header(HttpHeaders.AUTHORIZATION, "Bearer " + adminToken)
+                .get(new GenericType<Map<String, SessionStats>>() {
+                });
+
+        SessionStats custStats = stats.get("customer-portal");
+        Assert.assertNotNull(custStats);
+        Assert.assertEquals(1, custStats.getActiveSessions());
+        SessionStats prodStats = stats.get("product-portal");
+        Assert.assertNotNull(prodStats);
+        Assert.assertEquals(1, prodStats.getActiveSessions());
+
+        client.close();
+
+
+        // test logout
+
+        String logoutUri = OpenIDConnectService.logoutUrl(UriBuilder.fromUri("http://localhost:8081/auth"))
+                .queryParam(OAuth2Constants.REDIRECT_URI, "http://localhost:8081/customer-portal").build("demo").toString();
+        driver.navigate().to(logoutUri);
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        driver.navigate().to("http://localhost:8081/product-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+
+
+    }
+
+    @Test
+    public void testServletRequestLogout() throws Exception {
+        // test login to customer-portal which does a bearer request to customer-db
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        loginPage.login("bburke@redhat.com", "password");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
+        String pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
+
+        // test SSO
+        driver.navigate().to("http://localhost:8081/product-portal");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/product-portal");
+        pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("iPhone") && pageSource.contains("iPad"));
+
+        // back
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
+        pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
+        // test logout
+
+        driver.navigate().to("http://localhost:8081/customer-portal/logout");
+
+
+
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        String currentUrl = driver.getCurrentUrl();
+        Assert.assertTrue(currentUrl.startsWith(LOGIN_URL));
+        driver.navigate().to("http://localhost:8081/product-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+
+
+    }
+
+    @Test
+    public void testLoginSSOIdle() throws Exception {
+        // test login to customer-portal which does a bearer request to customer-db
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        loginPage.login("bburke@redhat.com", "password");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
+        String pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
+
+        KeycloakSession session = keycloakRule.startSession();
+        RealmModel realm = session.realms().getRealmByName("demo");
+        int originalIdle = realm.getSsoSessionIdleTimeout();
+        realm.setSsoSessionIdleTimeout(1);
+        session.getTransaction().commit();
+        session.close();
+
+        Thread.sleep(2000);
+
+
+        // test SSO
+        driver.navigate().to("http://localhost:8081/product-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+
+        session = keycloakRule.startSession();
+        realm = session.realms().getRealmByName("demo");
+        realm.setSsoSessionIdleTimeout(originalIdle);
+        session.getTransaction().commit();
+        session.close();
+    }
+
+    @Test
+    public void testLoginSSOIdleRemoveExpiredUserSessions() throws Exception {
+        // test login to customer-portal which does a bearer request to customer-db
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        loginPage.login("bburke@redhat.com", "password");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
+        String pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
+
+        KeycloakSession session = keycloakRule.startSession();
+        RealmModel realm = session.realms().getRealmByName("demo");
+        int originalIdle = realm.getSsoSessionIdleTimeout();
+        realm.setSsoSessionIdleTimeout(1);
+        session.getTransaction().commit();
+        session.close();
+
+        Thread.sleep(2000);
+
+        session = keycloakRule.startSession();
+        realm = session.realms().getRealmByName("demo");
+        session.sessions().removeExpiredUserSessions(realm);
+        session.getTransaction().commit();
+        session.close();
+
+        // test SSO
+        driver.navigate().to("http://localhost:8081/product-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+
+
+
+        session = keycloakRule.startSession();
+        realm = session.realms().getRealmByName("demo");
+        // need to cleanup so other tests don't fail, so invalidate http sessions on remote clients.
+        UserModel user = session.users().getUserByUsername("bburke@redhat.com", realm);
+        new ResourceAdminManager().logoutUser(null, realm, user.getId(), null);
+        realm.setSsoSessionIdleTimeout(originalIdle);
+        session.getTransaction().commit();
+        session.close();
+
+
+    }
+
+    @Test
+    public void testLoginSSOMax() throws Exception {
+        // test login to customer-portal which does a bearer request to customer-db
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        loginPage.login("bburke@redhat.com", "password");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
+        String pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
+
+        KeycloakSession session = keycloakRule.startSession();
+        RealmModel realm = session.realms().getRealmByName("demo");
+        int original = realm.getSsoSessionMaxLifespan();
+        realm.setSsoSessionMaxLifespan(1);
+        session.getTransaction().commit();
+        session.close();
+
+        Thread.sleep(2000);
+
+
+        // test SSO
+        driver.navigate().to("http://localhost:8081/product-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+
+        session = keycloakRule.startSession();
+        realm = session.realms().getRealmByName("demo");
+        realm.setSsoSessionMaxLifespan(original);
+        session.getTransaction().commit();
+        session.close();
+    }
+
+    /**
+     * KEYCLOAK-518
+     * @throws Exception
+     */
+    @Test
+    public void testNullBearerToken() throws Exception {
+        Client client = ClientBuilder.newClient();
+        WebTarget target = client.target("http://localhost:8081/customer-db");
+        Response response = target.request().get();
+        Assert.assertEquals(401, response.getStatus());
+        response.close();
+        response = target.request().header(HttpHeaders.AUTHORIZATION, "Bearer null").get();
+        Assert.assertEquals(401, response.getStatus());
+        response.close();
+        client.close();
+
+    }
+
+    /**
+     * KEYCLOAK-518
+     * @throws Exception
+     */
+    @Test
+    public void testBadUser() throws Exception {
+        Client client = ClientBuilder.newClient();
+        UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT);
+        URI uri = OpenIDConnectService.grantAccessTokenUrl(builder).build("demo");
+        WebTarget target = client.target(uri);
+        String header = BasicAuthHelper.createHeader("customer-portal", "password");
+        Form form = new Form();
+        form.param("username", "monkey@redhat.com")
+            .param("password", "password");
+        Response response = target.request()
+                .header(HttpHeaders.AUTHORIZATION, header)
+                .post(Entity.form(form));
+        Assert.assertEquals(400, response.getStatus());
+        response.close();
+        client.close();
+
+    }
+
+    @Test
+    public void testVersion() throws Exception {
+        Client client = ClientBuilder.newClient();
+        WebTarget target = client.target(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT).path("version");
+        Version version = target.request().get(Version.class);
+        Assert.assertNotNull(version);
+        Assert.assertNotNull(version.getVersion());
+        Assert.assertNotNull(version.getBuildTime());
+        Assert.assertNotEquals(version.getVersion(), Version.UNKNOWN);
+        Assert.assertNotEquals(version.getBuildTime(), Version.UNKNOWN);
+
+        Version version2 = client.target("http://localhost:8081/secure-portal").path(AdapterConstants.K_VERSION).request().get(Version.class);
+        Assert.assertNotNull(version2);
+        Assert.assertNotNull(version2.getVersion());
+        Assert.assertNotNull(version2.getBuildTime());
+        Assert.assertEquals(version.getVersion(), version2.getVersion());
+        Assert.assertEquals(version.getBuildTime(), version2.getBuildTime());
+        client.close();
+
+    }
+
+
+
+    @Test
+    public void testAuthenticated() throws Exception {
+        // test login to customer-portal which does a bearer request to customer-db
+        driver.navigate().to("http://localhost:8081/secure-portal");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        loginPage.login("bburke@redhat.com", "password");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/secure-portal");
+        String pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
+
+        // test logout
+
+        String logoutUri = OpenIDConnectService.logoutUrl(UriBuilder.fromUri("http://localhost:8081/auth"))
+                .queryParam(OAuth2Constants.REDIRECT_URI, "http://localhost:8081/secure-portal").build("demo").toString();
+        driver.navigate().to(logoutUri);
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        driver.navigate().to("http://localhost:8081/secure-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+    }
+
+
+
+}
diff --git a/testsuite/integration/src/test/resources/testsaml.json b/testsuite/integration/src/test/resources/testsaml.json
index 6dc9d71..4c06875 100755
--- a/testsuite/integration/src/test/resources/testsaml.json
+++ b/testsuite/integration/src/test/resources/testsaml.json
@@ -32,6 +32,8 @@
             "name": "http://localhost:8080/sales-post/",
             "enabled": true,
             "fullScopeAllowed": true,
+            "baseUrl": "http://localhost:8080/sales-post/",
+            "adminUrl": "http://localhost:8080/sales-post/",
             "redirectUris": [
                 "http://localhost:8080/sales-post/*"
             ]