keycloak-uncached

broker logout

3/20/2015 1:13:33 PM

Changes

broker/saml/pom.xml 17(+17 -0)

Details

diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
index bc007ae..407c659 100755
--- a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
@@ -19,6 +19,7 @@ package org.keycloak.broker.provider;
 
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
 
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
@@ -48,4 +49,13 @@ public abstract class AbstractIdentityProvider<C extends IdentityProviderModel> 
         // no-op
     }
 
+    @Override
+    public Object callback(RealmModel realm, Callback callback) {
+        return null;
+    }
+
+    @Override
+    public Response logout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
+        return null;
+    }
 }
diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
index f754eeb..14d6b5f 100755
--- a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
@@ -20,16 +20,29 @@ package org.keycloak.broker.provider;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
 import org.keycloak.provider.Provider;
 
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
+import java.util.Map;
 
 /**
  * @author Pedro Igor
  */
 public interface IdentityProvider<C extends IdentityProviderModel> extends Provider {
 
+    public interface Callback {
+        public Response authenticated(Map<String, String> userNotes, IdentityProviderModel identityProviderConfig, FederatedIdentity federatedIdentity, String code);
+    }
+
+    /**
+     * JAXRS callback endpoint
+     *
+     * @return
+     */
+    Object callback(RealmModel realm, Callback callback);
+
     /**
      * <p>Initiates the authentication process by sending an authentication request to an identity provider. This method is called
      * only once during the authentication.</p>
@@ -79,6 +92,8 @@ public interface IdentityProvider<C extends IdentityProviderModel> extends Provi
      */
     Response retrieveToken(FederatedIdentityModel identity);
 
+    Response logout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm);
+
     /**
      * Export a representation of the IdentityProvider in a specific format.  For example, a SAML EntityDescriptor
      *

broker/saml/pom.xml 17(+17 -0)

diff --git a/broker/saml/pom.xml b/broker/saml/pom.xml
index ddd174c..300df94 100755
--- a/broker/saml/pom.xml
+++ b/broker/saml/pom.xml
@@ -31,6 +31,23 @@
             <groupId>org.picketlink</groupId>
             <artifactId>picketlink-federation</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-services</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-events-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
new file mode 100755
index 0000000..62decc4
--- /dev/null
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
@@ -0,0 +1,375 @@
+package org.keycloak.broker.saml;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.ClientConnection;
+import org.keycloak.VerificationException;
+import org.keycloak.broker.provider.AuthenticationResponse;
+import org.keycloak.broker.provider.FederatedIdentity;
+import org.keycloak.broker.provider.IdentityBrokerException;
+import org.keycloak.broker.provider.IdentityProvider;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.saml.SAMLRequestParser;
+import org.keycloak.protocol.saml.SamlProtocol;
+import org.keycloak.protocol.saml.SamlProtocolUtils;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.EventsManager;
+import org.keycloak.services.messages.Messages;
+import org.keycloak.services.resources.flows.Flows;
+import org.picketlink.common.constants.GeneralConstants;
+import org.picketlink.common.constants.JBossSAMLConstants;
+import org.picketlink.common.constants.JBossSAMLURIConstants;
+import org.picketlink.common.exceptions.ProcessingException;
+import org.picketlink.common.util.DocumentUtil;
+import org.picketlink.common.util.StaxParserUtil;
+import org.picketlink.identity.federation.api.saml.v2.response.SAML2Response;
+import org.picketlink.identity.federation.core.parsers.saml.SAMLParser;
+import org.picketlink.identity.federation.core.saml.v2.common.SAMLDocumentHolder;
+import org.picketlink.identity.federation.core.util.JAXPValidationUtil;
+import org.picketlink.identity.federation.core.util.XMLEncryptionUtil;
+import org.picketlink.identity.federation.core.util.XMLSignatureUtil;
+import org.picketlink.identity.federation.saml.v2.assertion.AssertionType;
+import org.picketlink.identity.federation.saml.v2.assertion.AuthnStatementType;
+import org.picketlink.identity.federation.saml.v2.assertion.EncryptedAssertionType;
+import org.picketlink.identity.federation.saml.v2.assertion.NameIDType;
+import org.picketlink.identity.federation.saml.v2.assertion.SubjectType;
+import org.picketlink.identity.federation.saml.v2.profiles.sso.ecp.RequestType;
+import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
+import org.picketlink.identity.federation.saml.v2.protocol.StatusResponseType;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import javax.xml.namespace.QName;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SAMLEndpoint {
+    protected static final Logger logger = Logger.getLogger(SAMLEndpoint.class);
+    protected RealmModel realm;
+    protected EventBuilder event;
+    protected SAMLIdentityProviderConfig config;
+    protected IdentityProvider.Callback callback;
+
+    @Context
+    private UriInfo uriInfo;
+
+    @Context
+    private KeycloakSession session;
+
+    @Context
+    private ClientConnection clientConnection;
+
+    @Context
+    private HttpRequest request;
+
+    @Context
+    private HttpHeaders headers;
+
+
+    public SAMLEndpoint(RealmModel realm, SAMLIdentityProviderConfig config, IdentityProvider.Callback callback) {
+        this.realm = realm;
+        this.config = config;
+        this.callback = callback;
+    }
+
+    @GET
+    public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
+                                    @QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
+                                    @QueryParam(GeneralConstants.RELAY_STATE) String relayState)  {
+        return new RedirectBinding().execute(samlRequest, samlResponse, relayState);
+    }
+
+
+    /**
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
+                                @FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
+                                @FormParam(GeneralConstants.RELAY_STATE) String relayState) {
+        return new PostBinding().execute(samlRequest, samlResponse, relayState);
+    }
+
+    protected abstract class Binding {
+        private boolean checkSsl() {
+            if (uriInfo.getBaseUri().getScheme().equals("https")) {
+                return true;
+            } else {
+                return !realm.getSslRequired().isRequired(clientConnection);
+            }
+        }
+
+        protected Response basicChecks(String samlRequest, String samlResponse) {
+            if (!checkSsl()) {
+                event.event(EventType.LOGIN);
+                event.error(Errors.SSL_REQUIRED);
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
+            }
+            if (!realm.isEnabled()) {
+                event.event(EventType.LOGIN_ERROR);
+                event.error(Errors.REALM_DISABLED);
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
+            }
+
+            if (samlRequest == null && samlResponse == null) {
+                event.event(EventType.LOGIN);
+                event.error(Errors.INVALID_REQUEST);
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST );
+
+            }
+            return null;
+        }
+
+        protected abstract String getBindingType();
+        protected abstract void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException;
+        protected abstract SAMLDocumentHolder extractRequestDocument(String samlRequest);
+        protected abstract SAMLDocumentHolder extractResponseDocument(String response);
+        protected PublicKey getIDPKey() {
+            X509Certificate certificate = null;
+            try {
+                certificate = XMLSignatureUtil.getX509CertificateFromKeyInfoString(config.getSigningCertificate().replaceAll("\\s", ""));
+            } catch (ProcessingException e) {
+                throw new RuntimeException(e);
+            }
+            return certificate.getPublicKey();
+        }
+
+        public Response execute(String samlRequest, String samlResponse, String relayState) {
+            event = new EventsManager(realm, session, clientConnection).createEventBuilder();
+            Response response = basicChecks(samlRequest, samlResponse);
+            if (response != null) return response;
+            if (samlRequest != null) throw new RuntimeException("NOT IMPLEMETED");//return handleSamlRequest(samlRequest, relayState);
+            else return handleSamlResponse(samlResponse, relayState);
+        }
+
+        protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState) {
+            if (config.isValidateSignature()) {
+                try {
+                    verifySignature(holder);
+                } catch (VerificationException e) {
+                    logger.error("validation failed", e);
+                    event.event(EventType.LOGIN);
+                    event.error(Errors.INVALID_SIGNATURE);
+                    return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUESTER);
+                }
+            }
+
+            try {
+                AssertionType assertion = getAssertion(responseType);
+                SubjectType subject = assertion.getSubject();
+                SubjectType.STSubType subType = subject.getSubType();
+                NameIDType subjectNameID = (NameIDType) subType.getBaseID();
+                Map<String, String> notes = new HashMap<>();
+                notes.put("SAML_FEDERATED_SUBJECT", subjectNameID.getValue());
+                if (subjectNameID.getFormat() != null) notes.put("SAML_FEDERATED_SUBJECT_NAMEFORMAT", subjectNameID.getFormat().toString());
+                FederatedIdentity identity = new FederatedIdentity(subjectNameID.getValue());
+
+                identity.setUsername(subjectNameID.getValue());
+
+                if (subjectNameID.getFormat().toString().equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get())) {
+                    identity.setEmail(subjectNameID.getValue());
+                }
+
+                if (config.isStoreToken()) {
+                    identity.setToken(samlResponse);
+                }
+
+                AuthnStatementType authn = null;
+                for (Object statement : assertion.getStatements()) {
+                    if (statement instanceof AuthnStatementType) {
+                        authn = (AuthnStatementType)statement;
+                        break;
+                    }
+                }
+                if (authn != null && authn.getSessionIndex() != null) {
+                    notes.put("SAML_FEDERATED_SESSION_INDEX", authn.getSessionIndex());
+                }
+                return callback.authenticated(notes, config, identity, relayState);
+
+            } catch (Exception e) {
+                throw new IdentityBrokerException("Could not process response from SAML identity provider.", e);
+            }
+
+
+        }
+
+        private AssertionType getAssertion(ResponseType responseType) throws ProcessingException {
+            List<ResponseType.RTChoiceType> assertions = responseType.getAssertions();
+
+            if (assertions.isEmpty()) {
+                throw new IdentityBrokerException("No assertion from response.");
+            }
+
+            ResponseType.RTChoiceType rtChoiceType = assertions.get(0);
+            EncryptedAssertionType encryptedAssertion = rtChoiceType.getEncryptedAssertion();
+
+            if (encryptedAssertion != null) {
+                decryptAssertion(responseType, realm.getPrivateKey());
+
+            }
+            return responseType.getAssertions().get(0).getAssertion();
+        }
+
+        public Response handleSamlResponse(String samlResponse, String relayState) {
+            SAMLDocumentHolder holder = extractResponseDocument(samlResponse);
+            StatusResponseType statusResponse = (StatusResponseType)holder.getSamlObject();
+            // validate destination
+            if (!uriInfo.getAbsolutePath().toString().equals(statusResponse.getDestination())) {
+                event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
+                event.error(Errors.INVALID_SAML_RESPONSE);
+                event.detail(Details.REASON, "invalid_destination");
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
+            }
+            if (statusResponse instanceof ResponseType) {
+                return handleLoginResponse(samlResponse, holder, (ResponseType)statusResponse, relayState);
+
+            } else {
+                // todo need to check that it is actually a LogoutResponse
+                return handleLogoutResponse(holder, statusResponse, relayState);
+            }
+            //throw new RuntimeException("Unknown response type");
+
+        }
+
+        protected Response handleLogoutResponse(SAMLDocumentHolder holder, StatusResponseType responseType, String relayState) {
+            if (config.isValidateSignature()) {
+                try {
+                    verifySignature(holder);
+                } catch (VerificationException e) {
+                    logger.error("logout response validation failed", e);
+                    event.event(EventType.LOGOUT);
+                    event.error(Errors.INVALID_SIGNATURE);
+                    return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUESTER);
+                }
+            }
+            if (relayState == null) {
+                logger.error("no valid user session");
+                event.event(EventType.LOGOUT);
+                event.error(Errors.USER_SESSION_NOT_FOUND);
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE);
+            }
+            UserSessionModel userSession = session.sessions().getUserSession(realm, relayState);
+            if (userSession == null) {
+                logger.error("no valid user session");
+                event.event(EventType.LOGOUT);
+                event.error(Errors.USER_SESSION_NOT_FOUND);
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE);
+            }
+            if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
+                logger.error("usersession in different state");
+                event.event(EventType.LOGOUT);
+                event.error(Errors.USER_SESSION_NOT_FOUND);
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE);
+            }
+            return AuthenticationManager.finishBrowserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
+        }
+
+
+        protected ResponseType decryptAssertion(ResponseType responseType, PrivateKey privateKey) throws ProcessingException {
+            SAML2Response saml2Response = new SAML2Response();
+
+            try {
+                Document doc = saml2Response.convert(responseType);
+                Element enc = DocumentUtil.getElement(doc, new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
+
+                if (enc == null) {
+                    throw new IdentityBrokerException("No encrypted assertion found.");
+                }
+
+                String oldID = enc.getAttribute(JBossSAMLConstants.ID.get());
+                Document newDoc = DocumentUtil.createDocument();
+                Node importedNode = newDoc.importNode(enc, true);
+                newDoc.appendChild(importedNode);
+
+                Element decryptedDocumentElement = XMLEncryptionUtil.decryptElementInDocument(newDoc, privateKey);
+                SAMLParser parser = new SAMLParser();
+
+                JAXPValidationUtil.checkSchemaValidation(decryptedDocumentElement);
+                AssertionType assertion = (AssertionType) parser.parse(StaxParserUtil.getXMLEventReader(DocumentUtil
+                        .getNodeAsStream(decryptedDocumentElement)));
+
+                responseType.replaceAssertion(oldID, new ResponseType.RTChoiceType(assertion));
+
+                return responseType;
+            } catch (Exception e) {
+                throw new IdentityBrokerException("Could not decrypt assertion.", e);
+            }
+        }
+
+
+    }
+
+    protected class PostBinding extends Binding {
+        @Override
+        protected void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException {
+            SamlProtocolUtils.verifyDocumentSignature(documentHolder.getSamlDocument(), getIDPKey());
+        }
+
+        @Override
+        protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
+            return SAMLRequestParser.parseRequestPostBinding(samlRequest);
+        }
+        @Override
+        protected SAMLDocumentHolder extractResponseDocument(String response) {
+            return SAMLRequestParser.parseResponsePostBinding(response);
+        }
+
+        @Override
+        protected String getBindingType() {
+            return SamlProtocol.SAML_POST_BINDING;
+        }
+    }
+
+    protected class RedirectBinding extends Binding {
+        @Override
+        protected void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException {
+            PublicKey publicKey = getIDPKey();
+            SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo);
+        }
+
+
+
+        @Override
+        protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
+            return SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
+        }
+
+        @Override
+        protected SAMLDocumentHolder extractResponseDocument(String response) {
+            return SAMLRequestParser.parseRequestRedirectBinding(response);
+        }
+
+        @Override
+        protected String getBindingType() {
+            return SamlProtocol.SAML_REDIRECT_BINDING;
+        }
+
+    }
+
+}
diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
index 7104cb2..7a9bc3b 100755
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -23,13 +23,19 @@ import org.keycloak.broker.provider.AuthenticationRequest;
 import org.keycloak.broker.provider.AuthenticationResponse;
 import org.keycloak.broker.provider.FederatedIdentity;
 import org.keycloak.broker.provider.IdentityBrokerException;
+import org.keycloak.broker.provider.IdentityProvider;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.saml.SAML2AuthnRequestBuilder;
+import org.keycloak.protocol.saml.SAML2LogoutRequestBuilder;
 import org.keycloak.protocol.saml.SAML2NameIDPolicyBuilder;
+import org.keycloak.services.managers.EventsManager;
 import org.picketlink.common.constants.JBossSAMLConstants;
 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.DocumentUtil;
 import org.picketlink.common.util.StaxParserUtil;
@@ -63,6 +69,7 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import javax.xml.namespace.QName;
+import java.io.IOException;
 import java.net.URLDecoder;
 import java.security.KeyPair;
 import java.security.PrivateKey;
@@ -85,6 +92,11 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
     }
 
     @Override
+    public Object callback(RealmModel realm, Callback callback) {
+        return new SAMLEndpoint(realm, getConfig(), callback);
+    }
+
+    @Override
     public AuthenticationResponse handleRequest(AuthenticationRequest request) {
         try {
             UriInfo uriInfo = request.getUriInfo();
@@ -99,12 +111,14 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
 
             String protocolBinding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get();
 
+            String assertionConsumerServiceUrl = UriBuilder.fromUri(request.getRedirectUri()).path("endpoint").build().toString();
+
             if (getConfig().isPostBindingResponse()) {
                 protocolBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
             }
 
             SAML2AuthnRequestBuilder authnRequestBuilder = new SAML2AuthnRequestBuilder()
-                    .assertionConsumerUrl(request.getRedirectUri())
+                    .assertionConsumerUrl(assertionConsumerServiceUrl)
                     .destination(destinationUrl)
                     .issuer(issuerURL)
                     .forceAuthn(getConfig().isForceAuthn())
@@ -293,6 +307,27 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
     }
 
     @Override
+    public Response logout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
+        if (getConfig().getSingleLogoutServiceUrl() == null) return null;
+
+        SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
+                .issuer(getEntityId(uriInfo, realm))
+                .sessionIndex(userSession.getNote("SAML_FEDERATED_SESSION_INDEX"))
+                .userPrincipal(userSession.getNote("SAML_FEDERATED_SUBJECT"), userSession.getNote("SAML_FEDERATED_SUBJECT_NAMEFORMAT"))
+                .destination(getConfig().getSingleLogoutServiceUrl());
+        if (getConfig().isWantAuthnRequestsSigned()) {
+            logoutBuilder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
+                    .signDocument();
+        }
+        try {
+            return logoutBuilder.relayState(userSession.getId()).postBinding().request();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    @Override
     public Response export(UriInfo uriInfo, RealmModel realm, String format) {
 
         String authnBinding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get();
@@ -301,7 +336,12 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
             authnBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
         }
 
-        String assertionConsumerService = uriInfo.getBaseUriBuilder().path("realms").path(realm.getName()).path("broker").path(getConfig().getAlias()).build().toString();
+        String endpoint = uriInfo.getBaseUriBuilder()
+                .path("realms").path(realm.getName())
+                .path("broker")
+                .path(getConfig().getAlias())
+                .path("endpoint")
+                .build().toString();
 
 
 
@@ -312,9 +352,9 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
                 "        <NameIDFormat>" + getConfig().getNameIDPolicyFormat() + "\n" +
                 "        </NameIDFormat>\n" +
 // todo single logout service description
-//                "        <SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8081/sales-metadata/\"/>\n" +
+                "        <SingleLogoutService Binding=\"" + authnBinding + "\" Location=\"" + endpoint + "\"/>\n" +
                 "        <AssertionConsumerService\n" +
-                "                Binding=\"" + authnBinding + "\" Location=\"" + assertionConsumerService + "\"\n" +
+                "                Binding=\"" + authnBinding + "\" Location=\"" + endpoint + "\"\n" +
                 "                index=\"1\" isDefault=\"true\" />\n";
         if (getConfig().isWantAuthnRequestsSigned()) {
             descriptor +=
diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
old mode 100644
new mode 100755
index 98ebb28..10ef464
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
@@ -39,6 +39,14 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
         getConfig().put("singleSignOnServiceUrl", singleSignOnServiceUrl);
     }
 
+    public String getSingleLogoutServiceUrl() {
+        return getConfig().get("singleLogoutServiceUrl");
+    }
+
+    public void setSingleLogoutServiceUrl(String singleLogoutServiceUrl) {
+        getConfig().put("singleLogoutServiceUrl", singleLogoutServiceUrl);
+    }
+
     public boolean isValidateSignature() {
         return Boolean.valueOf(getConfig().get("validateSignature"));
     }
diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
index 2370a16..559b415 100755
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
@@ -56,7 +56,7 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
     }
 
     @Override
-    public Map<String, String>  parseConfig(InputStream inputStream) {
+    public Map<String, String> parseConfig(InputStream inputStream) {
         try {
             Object parsedObject = new SAMLParser().parse(inputStream);
             EntityDescriptorType entityType;
@@ -90,6 +90,18 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
                                 singleSignOnServiceUrl = endpoint.getLocation().toString();
                             }
                         }
+                        String singleLogoutServiceUrl = null;
+                        for (EndpointType endpoint : idpDescriptor.getSingleLogoutService()) {
+                            if (postBinding && endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get())) {
+                                singleLogoutServiceUrl = endpoint.getLocation().toString();
+                                break;
+                            } else if (!postBinding && endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get())){
+                                singleLogoutServiceUrl = endpoint.getLocation().toString();
+                                break;
+                            }
+
+                        }
+                        samlIdentityProviderConfig.setSingleLogoutServiceUrl(singleLogoutServiceUrl);
                         samlIdentityProviderConfig.setSingleSignOnServiceUrl(singleSignOnServiceUrl);
                         samlIdentityProviderConfig.setWantAuthnRequestsSigned(idpDescriptor.isWantAuthnRequestsSigned());
                         samlIdentityProviderConfig.setValidateSignature(idpDescriptor.isWantAuthnRequestsSigned());
diff --git a/events/api/src/main/java/org/keycloak/events/Errors.java b/events/api/src/main/java/org/keycloak/events/Errors.java
index 282b5e4..a02dd9c 100755
--- a/events/api/src/main/java/org/keycloak/events/Errors.java
+++ b/events/api/src/main/java/org/keycloak/events/Errors.java
@@ -26,6 +26,7 @@ public interface Errors {
     String INVALID_REDIRECT_URI = "invalid_redirect_uri";
     String INVALID_CODE = "invalid_code";
     String INVALID_TOKEN = "invalid_token";
+    String INVALID_SAML_RESPONSE = "invalid_saml_response";
     String INVALID_SAML_AUTHN_REQUEST = "invalid_authn_request";
     String INVALID_SAML_LOGOUT_REQUEST = "invalid_logout_request";
     String INVALID_SAML_LOGOUT_RESPONSE = "invalid_logout_response";
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-saml.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-saml.html
index e17ef15..4e1d88c 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-saml.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-saml.html
@@ -45,6 +45,13 @@
                         <span tooltip-placement="right" tooltip="The Url that must be used to send authentication requests(SAML AuthnRequest)." class="fa fa-info-circle"></span>
                     </div>
                     <div class="form-group clearfix" data-ng-show="!importFile && !importUrl">
+                        <label class="col-sm-2 control-label" for="singleSignOnServiceUrl">Single Logout Service Url</label>
+                        <div class="col-sm-4">
+                            <input class="form-control" id="singleLogoutServiceUrl" type="text" ng-model="identityProvider.config.singleLogoutServiceUrl" required>
+                        </div>
+                        <span tooltip-placement="right" tooltip="The Url that must be used to send logout requests." class="fa fa-info-circle"></span>
+                    </div>
+                    <div class="form-group clearfix" data-ng-show="!importFile && !importUrl">
                         <label class="col-sm-2 control-label" for="nameIDPolicyFormat">NameID Policy Format</label>
                         <div class="col-sm-4">
                             <select id="nameIDPolicyFormat" ng-model="identityProvider.config.nameIDPolicyFormat"
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SALM2LoginResponseBuilder.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SALM2LoginResponseBuilder.java
index e5d12e8..cd494ee 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SALM2LoginResponseBuilder.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SALM2LoginResponseBuilder.java
@@ -43,8 +43,14 @@ public class SALM2LoginResponseBuilder {
     protected String requestID;
     protected String authMethod;
     protected String requestIssuer;
+    protected String sessionIndex;
 
 
+    public SALM2LoginResponseBuilder sessionIndex(String sessionIndex) {
+        this.sessionIndex = sessionIndex;
+        return this;
+    }
+
     public SALM2LoginResponseBuilder destination(String destination) {
         this.destination = destination;
         return this;
@@ -135,8 +141,8 @@ public class SALM2LoginResponseBuilder {
 
             AuthnStatementType authnStatement = StatementUtil.createAuthnStatement(XMLTimeUtil.getIssueInstant(),
                     authContextRef);
-
-            authnStatement.setSessionIndex(assertion.getID());
+            if (sessionIndex != null) authnStatement.setSessionIndex(sessionIndex);
+            else authnStatement.setSessionIndex(assertion.getID());
 
             assertion.addStatement(authnStatement);
         }
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2LogoutRequestBuilder.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2LogoutRequestBuilder.java
index 1d76e2f..5cf301f 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2LogoutRequestBuilder.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SAML2LogoutRequestBuilder.java
@@ -19,6 +19,7 @@ import java.net.URI;
 public class SAML2LogoutRequestBuilder extends SAML2BindingBuilder<SAML2LogoutRequestBuilder> {
     protected String userPrincipal;
     protected String userPrincipalFormat;
+    protected String sessionIndex;
 
     public SAML2LogoutRequestBuilder userPrincipal(String nameID, String nameIDformat) {
         this.userPrincipal = nameID;
@@ -26,6 +27,11 @@ public class SAML2LogoutRequestBuilder extends SAML2BindingBuilder<SAML2LogoutRe
         return this;
     }
 
+    public SAML2LogoutRequestBuilder sessionIndex(String index) {
+        this.sessionIndex = index;
+        return this;
+    }
+
     public RedirectBindingBuilder redirectBinding()  throws ConfigurationException, ProcessingException, ParsingException {
         Document samlResponseDocument = buildDocument();
         return new RedirectBindingBuilder(samlResponseDocument);
@@ -58,7 +64,7 @@ public class SAML2LogoutRequestBuilder extends SAML2BindingBuilder<SAML2LogoutRe
             issuerID.setValue(issuer);
             lort.setIssuer(issuerID);
         }
-
+        if (sessionIndex != null) lort.addSessionIndex(sessionIndex);
 
         long assertionValidity = PicketLinkCoreSTS.instance().getConfiguration().getIssuedTokenTimeout();
 
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 34b688c..5a55996 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
@@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
 import org.jboss.resteasy.client.ClientRequest;
 import org.jboss.resteasy.client.ClientResponse;
 import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
+import org.keycloak.events.EventBuilder;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.ClaimMask;
 import org.keycloak.models.ClientModel;
@@ -97,6 +98,8 @@ public class SamlProtocol implements LoginProtocol {
 
     protected HttpHeaders headers;
 
+    protected EventBuilder event;
+
 
     @Override
     public SamlProtocol setSession(KeycloakSession session) {
@@ -123,6 +126,13 @@ public class SamlProtocol implements LoginProtocol {
     }
 
     @Override
+    public SamlProtocol setEventBuilder(EventBuilder event) {
+        this.event = event;
+        return this;
+    }
+
+
+    @Override
     public Response cancelLogin(ClientSessionModel clientSession) {
         return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
     }
@@ -265,6 +275,7 @@ public class SamlProtocol implements LoginProtocol {
         builder.requestID(requestID)
                .destination(redirectUri)
                .issuer(responseIssuer)
+               .sessionIndex(clientSession.getId())
                .requestIssuer(clientSession.getClient().getClientId())
                .nameIdentifier(nameIdFormat, nameId)
                .authMethod(JBossSAMLURIConstants.AC_UNSPECIFIED.get());
@@ -353,10 +364,6 @@ public class SamlProtocol implements LoginProtocol {
         return "true".equals(client.getAttribute(SAML_AUTHNSTATEMENT));
     }
 
-    public static boolean multivaluedRoles(ClientModel client) {
-        return "true".equals(client.getAttribute(SAML_MULTIVALUED_ROLES));
-    }
-
     public static SignatureAlgorithm getSignatureAlgorithm(ClientModel client) {
         String alg = client.getAttribute(SAML_SIGNATURE_ALGORITHM);
         if (alg != null) {
@@ -460,10 +467,13 @@ public class SamlProtocol implements LoginProtocol {
     @Override
     public Response finishLogout(UserSessionModel userSession) {
         logger.debug("finishLogout");
+        String logoutBindingUri = userSession.getNote(SAML_LOGOUT_BINDING_URI);
+        String logoutRelayState = userSession.getNote(SAML_LOGOUT_RELAY_STATE);
         SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
         builder.logoutRequestID(userSession.getNote(SAML_LOGOUT_REQUEST_ID));
-        builder.destination(userSession.getNote(SAML_LOGOUT_ISSUER));
+        builder.destination(logoutBindingUri);
         builder.issuer(getResponseIssuer(realm));
+        builder.relayState(logoutRelayState);
         String signingAlgorithm = userSession.getNote(SAML_LOGOUT_SIGNATURE_ALGORITHM);
         if (signingAlgorithm != null) {
             SignatureAlgorithm algorithm = SignatureAlgorithm.valueOf(signingAlgorithm);
@@ -474,9 +484,9 @@ public class SamlProtocol implements LoginProtocol {
 
         try {
             if (isLogoutPostBindingForInitiator(userSession)) {
-                return builder.postBinding().response(userSession.getNote(SAML_LOGOUT_BINDING_URI));
+                return builder.postBinding().response(logoutBindingUri);
             } else {
-                return builder.redirectBinding().response(userSession.getNote(SAML_LOGOUT_BINDING_URI));
+                return builder.redirectBinding().response(logoutBindingUri);
             }
         } catch (ConfigurationException e) {
             throw new RuntimeException(e);
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
index 7ddabe9..0e8c3c2 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
@@ -3,11 +3,17 @@ package org.keycloak.protocol.saml;
 import org.keycloak.VerificationException;
 import org.keycloak.models.ClientModel;
 import org.keycloak.util.PemUtils;
+import org.picketlink.common.constants.GeneralConstants;
 import org.picketlink.common.exceptions.ProcessingException;
 import org.picketlink.identity.federation.api.saml.v2.sig.SAML2Signature;
+import org.picketlink.identity.federation.web.util.RedirectBindingUtil;
 import org.w3c.dom.Document;
 
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
 import java.security.PublicKey;
+import java.security.Signature;
 import java.security.cert.Certificate;
 
 /**
@@ -20,8 +26,12 @@ public class SamlProtocolUtils {
         if (!"true".equals(client.getAttribute(SamlProtocol.SAML_CLIENT_SIGNATURE_ATTRIBUTE))) {
             return;
         }
-        SAML2Signature saml2Signature = new SAML2Signature();
         PublicKey publicKey = getSignatureValidationKey(client);
+        verifyDocumentSignature(document, publicKey);
+    }
+
+    public static void verifyDocumentSignature(Document document, PublicKey publicKey) throws VerificationException {
+        SAML2Signature saml2Signature = new SAML2Signature();
         try {
             if (!saml2Signature.validate(document, publicKey)) {
                 throw new VerificationException("Invalid signature on document");
@@ -51,5 +61,43 @@ public class SamlProtocolUtils {
         return cert.getPublicKey();
     }
 
+    public static void verifyRedirectSignature(PublicKey publicKey, UriInfo uriInformation) throws VerificationException {
+        MultivaluedMap<String, String> encodedParams = uriInformation.getQueryParameters(false);
+        String request = encodedParams.getFirst(GeneralConstants.SAML_REQUEST_KEY);
+        String algorithm = encodedParams.getFirst(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
+        String signature = encodedParams.getFirst(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
+        String decodedAlgorithm = uriInformation.getQueryParameters(true).getFirst(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
+
+        if (request == null) throw new VerificationException("SAMLRequest as null");
+        if (algorithm == null) throw new VerificationException("SigAlg as null");
+        if (signature == null) throw new VerificationException("Signature as null");
+
+        // Shibboleth doesn't sign the document for redirect binding.
+        // todo maybe a flag?
+
+
+        UriBuilder builder = UriBuilder.fromPath("/")
+                .queryParam(GeneralConstants.SAML_REQUEST_KEY, request);
+        if (encodedParams.containsKey(GeneralConstants.RELAY_STATE)) {
+            builder.queryParam(GeneralConstants.RELAY_STATE, encodedParams.getFirst(GeneralConstants.RELAY_STATE));
+        }
+        builder.queryParam(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY, algorithm);
+        String rawQuery = builder.build().getRawQuery();
+
+        try {
+            byte[] decodedSignature = RedirectBindingUtil.urlBase64Decode(signature);
+
+            SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.getFromXmlMethod(decodedAlgorithm);
+            Signature validator = signatureAlgorithm.createSignature(); // todo plugin signature alg
+            validator.initVerify(publicKey);
+            validator.update(rawQuery.getBytes("UTF-8"));
+            if (!validator.verify(decodedSignature)) {
+                throw new VerificationException("Invalid query param signature");
+            }
+        } catch (Exception e) {
+            throw new VerificationException(e);
+        }
+    }
+
 
 }
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 3e7cd2f..3aa016c 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
@@ -358,7 +358,6 @@ public class SamlService {
                 if (relayState != null) userSession.setNote(SamlProtocol.SAML_LOGOUT_RELAY_STATE, relayState);
                 userSession.setNote(SamlProtocol.SAML_LOGOUT_REQUEST_ID, logoutRequest.getID());
                 userSession.setNote(SamlProtocol.SAML_LOGOUT_BINDING, logoutBinding);
-                userSession.setNote(SamlProtocol.SAML_LOGOUT_ISSUER, logoutRequest.getIssuer().getValue());
                 userSession.setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, SamlProtocol.LOGIN_PROTOCOL);
                 // remove client from logout requests
                 for (ClientSessionModel clientSession : userSession.getClientSessions()) {
@@ -446,46 +445,11 @@ public class SamlService {
             if (!"true".equals(client.getAttribute("saml.client.signature"))) {
                 return;
             }
-            MultivaluedMap<String, String> encodedParams = uriInfo.getQueryParameters(false);
-            String request = encodedParams.getFirst(GeneralConstants.SAML_REQUEST_KEY);
-            String algorithm = encodedParams.getFirst(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
-            String signature = encodedParams.getFirst(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
-
-            if (request == null) throw new VerificationException("SAMLRequest as null");
-            if (algorithm == null) throw new VerificationException("SigAlg as null");
-            if (signature == null) throw new VerificationException("Signature as null");
-
-            // Shibboleth doesn't sign the document for redirect binding.
-            // todo maybe a flag?
-            // SamlProtocolUtils.verifyDocumentSignature(client, documentHolder.getSamlDocument());
-
             PublicKey publicKey = SamlProtocolUtils.getSignatureValidationKey(client);
+            SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo);
+        }
 
 
-            UriBuilder builder = UriBuilder.fromPath("/")
-                    .queryParam(GeneralConstants.SAML_REQUEST_KEY, request);
-            if (encodedParams.containsKey(GeneralConstants.RELAY_STATE)) {
-                builder.queryParam(GeneralConstants.RELAY_STATE, encodedParams.getFirst(GeneralConstants.RELAY_STATE));
-            }
-            builder.queryParam(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY, algorithm);
-            String rawQuery = builder.build().getRawQuery();
-
-            try {
-                byte[] decodedSignature = RedirectBindingUtil.urlBase64Decode(signature);
-
-                SignatureAlgorithm signatureAlgorithm = SamlProtocol.getSignatureAlgorithm(client);
-                Signature validator = signatureAlgorithm.createSignature(); // todo plugin signature alg
-                validator.initVerify(publicKey);
-                validator.update(rawQuery.getBytes("UTF-8"));
-                if (!validator.verify(decodedSignature)) {
-                    throw new VerificationException("Invalid query param signature");
-                }
-            } catch (Exception e) {
-                throw new VerificationException(e);
-            }
-
-
-        }
 
         @Override
         protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SignatureAlgorithm.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SignatureAlgorithm.java
index 6e9f47e..e169201 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SignatureAlgorithm.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SignatureAlgorithm.java
@@ -1,6 +1,8 @@
 package org.keycloak.protocol.saml;
 
 import java.security.Signature;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -16,6 +18,29 @@ public enum SignatureAlgorithm {
     private final String xmlSignatureDigestMethod;
     private final String javaSignatureAlgorithm;
 
+    private static final Map<String, SignatureAlgorithm> signatureMethodMap = new HashMap<>();
+    private static final Map<String, SignatureAlgorithm> signatureDigestMethodMap = new HashMap<>();
+
+    static {
+        signatureMethodMap.put(RSA_SHA1.getXmlSignatureMethod(), RSA_SHA1);
+        signatureMethodMap.put(RSA_SHA256.getXmlSignatureMethod(), RSA_SHA256);
+        signatureMethodMap.put(RSA_SHA512.getXmlSignatureMethod(), RSA_SHA512);
+        signatureMethodMap.put(DSA_SHA1.getXmlSignatureMethod(), DSA_SHA1);
+
+        signatureDigestMethodMap.put(RSA_SHA1.getXmlSignatureDigestMethod(), RSA_SHA1);
+        signatureDigestMethodMap.put(RSA_SHA256.getXmlSignatureDigestMethod(), RSA_SHA256);
+        signatureDigestMethodMap.put(RSA_SHA512.getXmlSignatureDigestMethod(), RSA_SHA512);
+        signatureDigestMethodMap.put(DSA_SHA1.getXmlSignatureDigestMethod(), DSA_SHA1);
+    }
+
+    public static SignatureAlgorithm getFromXmlMethod(String xml) {
+        return signatureMethodMap.get(xml);
+    }
+
+    public static SignatureAlgorithm getFromXmlDigest(String xml) {
+        return signatureDigestMethodMap.get(xml);
+    }
+
     SignatureAlgorithm(String xmlSignatureMethod, String xmlSignatureDigestMethod, String javaSignatureAlgorithm) {
         this.xmlSignatureMethod = xmlSignatureMethod;
         this.xmlSignatureDigestMethod = xmlSignatureDigestMethod;
diff --git a/services/src/main/java/org/keycloak/protocol/LoginProtocol.java b/services/src/main/java/org/keycloak/protocol/LoginProtocol.java
index 0290771..be1711b 100755
--- a/services/src/main/java/org/keycloak/protocol/LoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/LoginProtocol.java
@@ -1,5 +1,6 @@
 package org.keycloak.protocol;
 
+import org.keycloak.events.EventBuilder;
 import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
@@ -24,6 +25,8 @@ public interface LoginProtocol extends Provider {
 
     LoginProtocol setHttpHeaders(HttpHeaders headers);
 
+    LoginProtocol setEventBuilder(EventBuilder event);
+
     Response cancelLogin(ClientSessionModel clientSession);
     Response invalidSessionError(ClientSessionModel clientSession);
     Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
index b19480c..dd06346 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
@@ -254,7 +254,7 @@ public class AuthorizationEndpoint {
         if (httpAuthOutput.getResponse() != null) return httpAuthOutput.getResponse();
 
         if (prompt != null && prompt.equals("none")) {
-            OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo, headers);
+            OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo, headers, event);
             return oauth.cancelLogin(clientSession);
         }
 
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
old mode 100644
new mode 100755
index a47aa6a..ad5b302
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
@@ -78,23 +78,28 @@ public class LogoutEndpoint {
      */
     @GET
     @NoCache
-    public Response logout(final @QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri) {
-        event.event(EventType.LOGOUT);
+    public Response logout(@QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri) {
         if (redirectUri != null) {
-            event.detail(Details.REDIRECT_URI, redirectUri);
+            String validatedUri = RedirectUtils.verifyRealmRedirectUri(uriInfo, redirectUri, realm);
+            if (validatedUri == null) {
+                event.event(EventType.LOGOUT);
+                event.detail(Details.REDIRECT_URI, redirectUri);
+                event.error(Errors.INVALID_REDIRECT_URI);
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REDIRECT_URI);
+            }
+            redirectUri = validatedUri;
         }
+
         // 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());
+            if (redirectUri != null) authResult.getSession().setNote(OIDCLoginProtocol.LOGOUT_REDIRECT_URI, redirectUri);
+            authResult.getSession().setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, OIDCLoginProtocol.LOGIN_PROTOCOL);
+            return AuthenticationManager.browserLogout(session, realm, authResult.getSession(), uriInfo, clientConnection, headers);
         }
 
         if (redirectUri != null) {
-            String validatedRedirect = RedirectUtils.verifyRealmRedirectUri(uriInfo, redirectUri, realm);
-            if (validatedRedirect == null) {
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REDIRECT_URI);
-            }
-            return Response.status(302).location(UriBuilder.fromUri(validatedRedirect).build()).build();
+            return Response.status(302).location(UriBuilder.fromUri(redirectUri).build()).build();
         } else {
             return Response.ok().build();
         }
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 1c473f9..cdbef96 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
@@ -24,6 +24,9 @@ package org.keycloak.protocol.oidc;
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
 import org.keycloak.OAuth2Constants;
+import org.keycloak.events.Details;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
@@ -54,6 +57,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
     public static final String CLIENT_ID_PARAM = "client_id";
     public static final String PROMPT_PARAM = "prompt";
     public static final String LOGIN_HINT_PARAM = "login_hint";
+    public static final String LOGOUT_REDIRECT_URI = "OIDC_LOGOUT_REDIRECT_URI";
 
     private static final Logger log = Logger.getLogger(OIDCLoginProtocol.class);
 
@@ -65,11 +69,14 @@ public class OIDCLoginProtocol implements LoginProtocol {
 
     protected HttpHeaders headers;
 
-    public OIDCLoginProtocol(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
+    protected EventBuilder event;
+
+    public OIDCLoginProtocol(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, EventBuilder event) {
         this.session = session;
         this.realm = realm;
         this.uriInfo = uriInfo;
         this.headers = headers;
+        this.event = event;
     }
 
     public OIDCLoginProtocol(){
@@ -101,6 +108,12 @@ public class OIDCLoginProtocol implements LoginProtocol {
     }
 
     @Override
+    public OIDCLoginProtocol setEventBuilder(EventBuilder event) {
+        this.event = event;
+        return this;
+    }
+
+    @Override
     public Response cancelLogin(ClientSessionModel clientSession) {
         String redirect = clientSession.getRedirectUri();
         String state = clientSession.getNote(OIDCLoginProtocol.STATE_PARAM);
@@ -168,7 +181,19 @@ public class OIDCLoginProtocol implements LoginProtocol {
 
     @Override
     public Response finishLogout(UserSessionModel userSession) {
-        throw new RuntimeException("NOT IMPLEMENTED");
+        String redirectUri = userSession.getNote(OIDCLoginProtocol.LOGOUT_REDIRECT_URI);
+        event.event(EventType.LOGOUT);
+        if (redirectUri != null) {
+            event.detail(Details.REDIRECT_URI, redirectUri);
+        }
+        event.user(userSession.getUser()).session(userSession).success();
+
+
+        if (redirectUri != null) {
+            return Response.status(302).location(UriBuilder.fromUri(redirectUri).build()).build();
+        } else {
+            return Response.ok().build();
+        }
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
old mode 100644
new mode 100755
index 54e4009..19ecd44
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
@@ -37,6 +37,7 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
         config.setAuthorizationEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "auth").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
         config.setTokenEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "token").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
         config.setUserinfoEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "issueUserInfo").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
+        config.setLogoutEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "logout").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
         config.setJwksUri(uriBuilder.clone().path(OIDCLoginProtocolService.class, "certs").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
 
         config.setIdTokenSigningAlgValuesSupported(DEFAULT_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
old mode 100644
new mode 100755
index 0760b64..0e3d4f3
--- a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
@@ -23,6 +23,9 @@ public class OIDCConfigurationRepresentation {
     @JsonProperty("userinfo_endpoint")
     private String userinfoEndpoint;
 
+    @JsonProperty("end_session_endpoint")
+    private String logoutEndpoint;
+
     @JsonProperty("jwks_uri")
     private String jwksUri;
 
@@ -81,6 +84,14 @@ public class OIDCConfigurationRepresentation {
         this.jwksUri = jwksUri;
     }
 
+    public String getLogoutEndpoint() {
+        return logoutEndpoint;
+    }
+
+    public void setLogoutEndpoint(String logoutEndpoint) {
+        this.logoutEndpoint = logoutEndpoint;
+    }
+
     public List<String> getGrantTypesSupported() {
         return grantTypesSupported;
     }
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 f37b837..c540baa 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -6,6 +6,7 @@ import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.ClientConnection;
 import org.keycloak.RSATokenVerifier;
 import org.keycloak.VerificationException;
+import org.keycloak.broker.provider.IdentityProvider;
 import org.keycloak.events.Details;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventType;
@@ -26,6 +27,7 @@ import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.protocol.oidc.TokenManager;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.resources.IdentityBrokerService;
 import org.keycloak.services.resources.LoginActionsService;
 import org.keycloak.services.resources.RealmsResource;
 import org.keycloak.services.resources.flows.Flows;
@@ -141,9 +143,6 @@ public class AuthenticationManager {
             }
         }
 
-        if (redirectClients.size() == 0) {
-            return finishBrowserLogout(session, realm, userSession, uriInfo, connection, headers);
-        }
         for (ClientSessionModel nextRedirectClient : redirectClients) {
             String authMethod = nextRedirectClient.getAuthMethod();
             LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod);
@@ -164,18 +163,26 @@ public class AuthenticationManager {
             }
 
         }
+        String brokerId = userSession.getNote(IdentityBrokerService.BROKER_PROVIDER_ID);
+        if (brokerId != null) {
+            IdentityProvider identityProvider = IdentityBrokerService.getIdentityProvider(session, realm, brokerId);
+            Response response = identityProvider.logout(userSession, uriInfo, realm);
+            if (response != null) return response;
+        }
         return finishBrowserLogout(session, realm, userSession, uriInfo, connection, headers);
     }
 
-    protected static Response finishBrowserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
+    public static Response finishBrowserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
         expireIdentityCookie(realm, uriInfo, connection);
         expireRememberMeCookie(realm, uriInfo, connection);
         userSession.setState(UserSessionModel.State.LOGGED_OUT);
         String method = userSession.getNote(KEYCLOAK_LOGOUT_PROTOCOL);
+        EventBuilder event = new EventsManager(realm, session, connection).createEventBuilder();
         LoginProtocol protocol = session.getProvider(LoginProtocol.class, method);
         protocol.setRealm(realm)
                 .setHttpHeaders(headers)
-                .setUriInfo(uriInfo);
+                .setUriInfo(uriInfo)
+                .setEventBuilder(event);
         Response response = protocol.finishLogout(userSession);
         session.sessions().removeUserSession(realm, userSession);
         return response;
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index 1d24fda..6eb5707 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -19,6 +19,7 @@ package org.keycloak.services.resources;
 
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.keycloak.ClientConnection;
 import org.keycloak.broker.provider.AuthenticationRequest;
 import org.keycloak.broker.provider.AuthenticationResponse;
@@ -76,9 +77,10 @@ import static org.keycloak.models.UserModel.RequiredAction.UPDATE_PROFILE;
  * @author Pedro Igor
  */
 @Path("/broker")
-public class IdentityBrokerService {
+public class IdentityBrokerService implements IdentityProvider.Callback {
 
     private static final Logger LOGGER = Logger.getLogger(IdentityBrokerService.class);
+    public static final String BROKER_PROVIDER_ID = "BROKER_PROVIDER_ID";
 
     private final RealmModel realmModel;
 
@@ -121,8 +123,8 @@ public class IdentityBrokerService {
         }
 
         try {
-            ClientSessionCode clientSessionCode = parseClientSessionCode(code, providerId);
-            IdentityProvider identityProvider = getIdentityProvider(providerId);
+            ClientSessionCode clientSessionCode = parseClientSessionCode(code);
+            IdentityProvider identityProvider = getIdentityProvider(session, realmModel, providerId);
             AuthenticationResponse authenticationResponse = identityProvider.handleRequest(createAuthenticationRequest(providerId, clientSessionCode));
 
             Response response = authenticationResponse.getResponse();
@@ -155,6 +157,17 @@ public class IdentityBrokerService {
         return handleResponse(providerId);
     }
 
+    @Path("{provider_id}/endpoint")
+    public Object getEndpoint(@PathParam("provider_id") String providerId) {
+        IdentityProvider identityProvider = getIdentityProvider(session, realmModel, providerId);
+        Object callback = identityProvider.callback(realmModel, this);
+        ResteasyProviderFactory.getInstance().injectProperties(callback);
+        //resourceContext.initResource(brokerService);
+        return callback;
+
+
+    }
+
     @Path("{provider_id}/token")
     @OPTIONS
     public Response retrieveTokenPreflight() {
@@ -195,7 +208,7 @@ public class IdentityBrokerService {
                             .createOAuthGrant(null), clientModel);
                 }
 
-                IdentityProvider identityProvider = getIdentityProvider(providerId);
+                IdentityProvider identityProvider = getIdentityProvider(session, realmModel, providerId);
                 IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(providerId);
 
                 if (identityProviderConfig.isStoreToken()) {
@@ -233,6 +246,79 @@ public class IdentityBrokerService {
         return getToken(providerId, true);
     }
 
+    public Response authenticated(Map<String, String> userNotes, IdentityProviderModel identityProviderConfig, FederatedIdentity federatedIdentity, String code) {
+        ClientSessionCode clientCode = null;
+        try {
+            clientCode = parseClientSessionCode(code);
+        } catch (Exception e) {
+            return redirectToErrorPage(Messages.IDENTITY_PROVIDER_AUTHENTICATION_FAILED, e, identityProviderConfig.getProviderId());
+
+        }
+        String providerId = identityProviderConfig.getAlias();
+        if (!identityProviderConfig.isStoreToken()) {
+            if (isDebugEnabled()) {
+                LOGGER.debugf("Token will not be stored for identity provider [%s].", providerId);
+            }
+            federatedIdentity.setToken(null);
+        }
+
+        federatedIdentity.setIdentityProviderId(providerId);
+        ClientSessionModel clientSession = clientCode.getClientSession();
+        FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(providerId, federatedIdentity.getId(),
+                federatedIdentity.getUsername(), federatedIdentity.getToken());
+
+        this.event.event(EventType.IDENTITY_PROVIDER_LOGIN)
+                .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
+                .detail(Details.IDENTITY_PROVIDER_IDENTITY, federatedIdentity.getUsername());
+
+        UserModel federatedUser = this.session.users().getUserByFederatedIdentity(federatedIdentityModel, this.realmModel);
+
+        // Check if federatedUser is already authenticated (this means linking social into existing federatedUser account)
+        if (clientSession.getUserSession() != null) {
+            UserSessionModel userSession = clientSession.getUserSession();
+            for (Map.Entry<String, String> entry : userNotes.entrySet()) {
+                userSession.setNote(entry.getKey(), entry.getValue());
+            }
+            return performAccountLinking(clientSession, providerId, federatedIdentityModel, federatedUser);
+        }
+
+        if (federatedUser == null) {
+            try {
+                federatedUser = createUser(federatedIdentity);
+
+                if (identityProviderConfig.isUpdateProfileFirstLogin()) {
+                    if (isDebugEnabled()) {
+                        LOGGER.debugf("Identity provider requires update profile action.", federatedUser);
+                    }
+                    federatedUser.addRequiredAction(UPDATE_PROFILE);
+                }
+            } catch (Exception e) {
+                return redirectToLoginPage(e, clientCode);
+            }
+        }
+
+        updateFederatedIdentity(federatedIdentity, federatedUser);
+
+        UserSessionModel userSession = this.session.sessions()
+                .createUserSession(this.realmModel, federatedUser, federatedUser.getUsername(), this.clientConnection.getRemoteAddr(), "broker", false);
+
+        this.event.user(federatedUser);
+        this.event.session(userSession);
+
+        TokenManager.attachClientSession(userSession, clientSession);
+        for (Map.Entry<String, String> entry : userNotes.entrySet()) {
+            userSession.setNote(entry.getKey(), entry.getValue());
+        }
+        userSession.setNote(BROKER_PROVIDER_ID, providerId);
+
+        if (isDebugEnabled()) {
+            LOGGER.debugf("Performing local authentication for user [%s].", federatedUser);
+        }
+
+        return AuthenticationManager.nextActionAfterAuthentication(this.session, userSession, clientSession, this.clientConnection, this.request,
+                this.uriInfo, event);
+    }
+
     private Response handleResponse(String providerId) {
         if (isDebugEnabled()) {
             LOGGER.debugf("Handling authentication response from identity provider [%s].", providerId);
@@ -242,7 +328,7 @@ public class IdentityBrokerService {
         IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(providerId);
 
         try {
-            IdentityProvider identityProvider = getIdentityProvider(providerId);
+            IdentityProvider identityProvider = getIdentityProvider(session, realmModel, providerId);
             String relayState = identityProvider.getRelayState(createAuthenticationRequest(providerId, null));
 
             if (relayState == null) {
@@ -253,7 +339,7 @@ public class IdentityBrokerService {
                 LOGGER.debugf("Relay state is valid: [%s].", relayState);
             }
 
-            ClientSessionCode clientSessionCode = parseClientSessionCode(relayState, providerId);
+            ClientSessionCode clientSessionCode = parseClientSessionCode(relayState);
             AuthenticationResponse authenticationResponse = identityProvider.handleResponse(createAuthenticationRequest(providerId, clientSessionCode));
             Response response = authenticationResponse.getResponse();
 
@@ -386,7 +472,7 @@ public class IdentityBrokerService {
         }
     }
 
-    private ClientSessionCode parseClientSessionCode(String code, String providerId) {
+    private ClientSessionCode parseClientSessionCode(String code) {
         ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realmModel);
 
         if (clientCode != null && clientCode.isValid(AUTHENTICATE)) {
@@ -465,11 +551,11 @@ public class IdentityBrokerService {
         return Flows.errors().error(message, Status.BAD_REQUEST);
     }
 
-    private IdentityProvider getIdentityProvider(String alias) {
-        IdentityProviderModel identityProviderModel = this.realmModel.getIdentityProviderByAlias(alias);
+    public static IdentityProvider getIdentityProvider(KeycloakSession session, RealmModel realm, String alias) {
+        IdentityProviderModel identityProviderModel = realm.getIdentityProviderByAlias(alias);
 
         if (identityProviderModel != null) {
-            IdentityProviderFactory providerFactory = getIdentityProviderFactory(identityProviderModel);
+            IdentityProviderFactory providerFactory = getIdentityProviderFactory(session, identityProviderModel);
 
             if (providerFactory == null) {
                 throw new IdentityBrokerException("Could not find factory for identity provider [" + alias + "].");
@@ -481,12 +567,12 @@ public class IdentityBrokerService {
         throw new IdentityBrokerException("Identity Provider [" + alias + "] not found.");
     }
 
-    private IdentityProviderFactory getIdentityProviderFactory(IdentityProviderModel model) {
+    private static IdentityProviderFactory getIdentityProviderFactory(KeycloakSession session, IdentityProviderModel model) {
         Map<String, IdentityProviderFactory> availableProviders = new HashMap<String, IdentityProviderFactory>();
         List<ProviderFactory> allProviders = new ArrayList<ProviderFactory>();
 
-        allProviders.addAll(this.session.getKeycloakSessionFactory().getProviderFactories(IdentityProvider.class));
-        allProviders.addAll(this.session.getKeycloakSessionFactory().getProviderFactories(SocialIdentityProvider.class));
+        allProviders.addAll(session.getKeycloakSessionFactory().getProviderFactories(IdentityProvider.class));
+        allProviders.addAll(session.getKeycloakSessionFactory().getProviderFactories(SocialIdentityProvider.class));
 
         for (ProviderFactory providerFactory : allProviders) {
             availableProviders.put(providerFactory.getId(), (IdentityProviderFactory) providerFactory);
diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
index 247a430..0feccb4 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -189,7 +189,8 @@ public class RealmsResource {
 
         IdentityBrokerService brokerService = new IdentityBrokerService(realm);
         ResteasyProviderFactory.getInstance().injectProperties(brokerService);
-        
+        //resourceContext.initResource(brokerService);
+
         brokerService.init();
 
         return brokerService;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index fb78299..4743826 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -157,7 +157,7 @@ public class AccountTest {
         });
     }
 
-    @Test @Ignore
+    //@Test @Ignore
     public void runit() throws Exception {
         Thread.sleep(10000000);
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
index ae5c9bd..7ea8348 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
@@ -546,7 +546,7 @@ public abstract class AbstractIdentityProviderTest {
         this.loginPage.clickSocial(getProviderId());
 
         assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
-
+        System.out.println(this.driver.getCurrentUrl());
         // log in to identity provider
         this.loginPage.login(username, "password");
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java
index 7783665..c77e57f 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java
@@ -1,6 +1,7 @@
 package org.keycloak.testsuite.broker;
 
 import org.junit.ClassRule;
+import org.junit.Test;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
@@ -60,7 +61,8 @@ public class SAMLKeyCloakServerBrokerBasicTest extends AbstractIdentityProviderT
         try {
             SAML2Request saml2Request = new SAML2Request();
             ResponseType responseType = (ResponseType) saml2Request
-                    .getSAML2ObjectFromStream(PostBindingUtil.base64DecodeAsStream(URLDecoder.decode(pageSource, "UTF-8")));
+                    .getSAML2ObjectFromStream(PostBindingUtil.base64DecodeAsStream(pageSource));
+                    //.getSAML2ObjectFromStream(PostBindingUtil.base64DecodeAsStream(URLDecoder.decode(pageSource, "UTF-8")));
 
             assertNotNull(responseType);
             assertFalse(responseType.getAssertions().isEmpty());
@@ -68,4 +70,16 @@ public class SAMLKeyCloakServerBrokerBasicTest extends AbstractIdentityProviderT
             fail("Could not parse token.");
         }
     }
+
+    @Override
+    @Test
+    public void testSuccessfulAuthenticationWithoutUpdateProfile() {
+        super.testSuccessfulAuthenticationWithoutUpdateProfile();
+    }
+
+    @Override
+    @Test
+    public void testTokenStorageAndRetrievalByOAuthClient() {
+        super.testTokenStorageAndRetrievalByOAuthClient();
+    }
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java
index e387097..1d8b264 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java
@@ -60,7 +60,7 @@ public class SAMLKeyCloakServerBrokerWithSignatureTest extends AbstractIdentityP
         try {
             SAML2Request saml2Request = new SAML2Request();
             ResponseType responseType = (ResponseType) saml2Request
-                        .getSAML2ObjectFromStream(PostBindingUtil.base64DecodeAsStream(URLDecoder.decode(pageSource, "UTF-8")));
+                        .getSAML2ObjectFromStream(PostBindingUtil.base64DecodeAsStream(pageSource));
 
             assertNotNull(responseType);
             assertFalse(responseType.getAssertions().isEmpty());
diff --git a/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-saml.json b/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-saml.json
index 5757b28..73d2b2d 100755
--- a/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-saml.json
+++ b/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-saml.json
@@ -11,7 +11,7 @@
             "name": "http://localhost:8081/auth/realms/realm-with-broker",
             "enabled": true,
             "redirectUris": [
-                "http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic"
+                "http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint"
             ],
             "attributes": {
                 "saml.authnstatement": "true"
diff --git a/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-saml-with-signature.json b/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-saml-with-signature.json
index db027d8..844bd5e 100755
--- a/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-saml-with-signature.json
+++ b/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-saml-with-signature.json
@@ -11,7 +11,7 @@
             "name": "http://localhost:8081/auth/realms/realm-with-broker",
             "enabled": true,
             "redirectUris": [
-                "http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-signed-idp"
+                "http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-signed-idp/endpoint"
             ],
             "attributes": {
                 "saml.assertion.signature": "true",
diff --git a/testsuite/integration/src/test/resources/saml/testsaml.json b/testsuite/integration/src/test/resources/saml/testsaml.json
index cac873b..ed67040 100755
--- a/testsuite/integration/src/test/resources/saml/testsaml.json
+++ b/testsuite/integration/src/test/resources/saml/testsaml.json
@@ -45,10 +45,10 @@
             ],
             "attributes": {
                 "saml.authnstatement": "true",
-                "saml_assertion_consumer_url_post": "http://localhost:8081/sales-post",
-                "saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post",
-                "saml_single_logout_service_url_post": "http://localhost:8081/sales-post",
-                "saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post"
+                "saml_assertion_consumer_url_post": "http://localhost:8081/sales-post/",
+                "saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post/",
+                "saml_single_logout_service_url_post": "http://localhost:8081/sales-post/",
+                "saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post/"
             }
         },
         {
@@ -61,10 +61,10 @@
                 "http://localhost:8081/sales-post-sig/*"
             ],
             "attributes": {
-                "saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig",
-                "saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig",
-                "saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig",
-                "saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-sig",
+                "saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig/",
+                "saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig/",
+                "saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig/",
+                "saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-sig/",
                 "saml.server.signature": "true",
                 "saml.signature.algorithm": "RSA_SHA256",
                 "saml.client.signature": "true",
@@ -84,10 +84,10 @@
                 "http://localhost:8081/sales-post-sig-transient/*"
             ],
             "attributes": {
-                "saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig-transient",
-                "saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig-transient",
-                "saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig-transient",
-                "saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-sig-transient",
+                "saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig-transient/",
+                "saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig-transient/",
+                "saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig-transient/",
+                "saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-sig-transient/",
                 "saml.server.signature": "true",
                 "saml.signature.algorithm": "RSA_SHA256",
                 "saml.client.signature": "true",
@@ -106,10 +106,10 @@
                 "http://localhost:8081/sales-post-sig-persistent/*"
             ],
             "attributes": {
-                "saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig-persistent",
-                "saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig-persistent",
-                "saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig-persistent",
-                "saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-sig-persistent",
+                "saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig-persistent/",
+                "saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig-persistent/",
+                "saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig-persistent/",
+                "saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-sig-persistent/",
                 "saml.server.signature": "true",
                 "saml.signature.algorithm": "RSA_SHA256",
                 "saml.client.signature": "true",
@@ -131,10 +131,10 @@
             "attributes": {
                 "saml_force_name_id_format": "true",
                 "saml_name_id_format": "email",
-                "saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig-email",
-                "saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig-email",
-                "saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig-email",
-                "saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-sig-email",
+                "saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig-email/",
+                "saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig-email/",
+                "saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig-email/",
+                "saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-sig-email/",
                 "saml.server.signature": "true",
                 "saml.signature.algorithm": "RSA_SHA256",
                 "saml.client.signature": "true",
@@ -148,8 +148,8 @@
             "enabled": true,
             "protocol": "saml",
             "fullScopeAllowed": true,
-            "baseUrl": "http://localhost:8081/bad-realm-sales-post-sig",
-            "adminUrl": "http://localhost:8081/bad-realm-sales-post-sig",
+            "baseUrl": "http://localhost:8081/bad-realm-sales-post-sig/",
+            "adminUrl": "http://localhost:8081/bad-realm-sales-post-sig/",
             "redirectUris": [
                 "http://localhost:8081/bad-realm-sales-post-sig/*"
             ],
@@ -166,8 +166,8 @@
             "enabled": true,
             "protocol": "saml",
             "fullScopeAllowed": true,
-            "baseUrl": "http://localhost:8081/bad-client-sales-post-sig",
-            "adminUrl": "http://localhost:8081/bad-client-sales-post-sig",
+            "baseUrl": "http://localhost:8081/bad-client-sales-post-sig/",
+            "adminUrl": "http://localhost:8081/bad-client-sales-post-sig/",
             "redirectUris": [
                 "http://localhost:8081/bad-client-sales-post-sig/*"
             ],
@@ -189,10 +189,10 @@
                 "http://localhost:8081/sales-post-enc/*"
             ],
             "attributes": {
-                "saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-enc",
-                "saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-enc",
-                "saml_single_logout_service_url_post": "http://localhost:8081/sales-post-enc",
-                "saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-enc",
+                "saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-enc/",
+                "saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-enc/",
+                "saml_single_logout_service_url_post": "http://localhost:8081/sales-post-enc/",
+                "saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-enc/",
                 "saml.server.signature": "true",
                 "saml.signature.algorithm": "RSA_SHA512",
                 "saml.client.signature": "true",
@@ -213,7 +213,7 @@
             "redirectUris": [
                 "http://localhost:8081/employee-sig/*"
             ],
-            "adminUrl": "http://localhost:8081/employee-sig",
+            "adminUrl": "http://localhost:8081/employee-sig/",
             "attributes": {
                 "saml.server.signature": "true",
                 "saml.client.signature": "true",
@@ -279,15 +279,15 @@
             "protocol": "saml",
             "fullScopeAllowed": true,
             "frontchannelLogout": true,
-            "baseUrl": "http://localhost:8081/employee-sig-front",
+            "baseUrl": "http://localhost:8081/employee-sig-front/",
             "redirectUris": [
                 "http://localhost:8081/employee-sig-front/*"
             ],
             "attributes": {
-                "saml_assertion_consumer_url_post": "http://localhost:8081/employee-sig-front",
-                "saml_assertion_consumer_url_redirect": "http://localhost:8081/employee-sig-front",
-                "saml_single_logout_service_url_post": "http://localhost:8081/employee-sig-front",
-                "saml_single_logout_service_url_redirect": "http://localhost:8081/employee-sig-front",
+                "saml_assertion_consumer_url_post": "http://localhost:8081/employee-sig-front/",
+                "saml_assertion_consumer_url_redirect": "http://localhost:8081/employee-sig-front/",
+                "saml_single_logout_service_url_post": "http://localhost:8081/employee-sig-front/",
+                "saml_single_logout_service_url_redirect": "http://localhost:8081/employee-sig-front/",
                 "saml.server.signature": "true",
                 "saml.client.signature": "true",
                 "saml.signature.algorithm": "RSA_SHA1",