keycloak-aplcache

Merge pull request #1087 from patriot1burke/master broker

3/24/2015 5:58:22 PM

Details

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
index 849b2b5..23299bd 100755
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
@@ -13,17 +13,22 @@ import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventType;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.saml.SAML2LogoutResponseBuilder;
 import org.keycloak.protocol.saml.SAMLRequestParser;
 import org.keycloak.protocol.saml.SamlProtocol;
 import org.keycloak.protocol.saml.SamlProtocolUtils;
+import org.keycloak.protocol.saml.SignatureAlgorithm;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.EventsManager;
 import org.keycloak.services.messages.Messages;
+import org.keycloak.services.resources.IdentityBrokerService;
 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.ConfigurationException;
 import org.picketlink.common.exceptions.ProcessingException;
 import org.picketlink.common.util.DocumentUtil;
 import org.picketlink.common.util.StaxParserUtil;
@@ -38,6 +43,8 @@ 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.protocol.LogoutRequestType;
+import org.picketlink.identity.federation.saml.v2.protocol.RequestAbstractType;
 import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
 import org.picketlink.identity.federation.saml.v2.protocol.StatusResponseType;
 import org.w3c.dom.Document;
@@ -53,8 +60,10 @@ 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.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import javax.xml.namespace.QName;
+import java.io.IOException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.cert.X509Certificate;
@@ -68,6 +77,7 @@ import java.util.Map;
  */
 public class SAMLEndpoint {
     protected static final Logger logger = Logger.getLogger(SAMLEndpoint.class);
+    public static final String SAML_FEDERATED_SESSION_INDEX = "SAML_FEDERATED_SESSION_INDEX";
     protected RealmModel realm;
     protected EventBuilder event;
     protected SAMLIdentityProviderConfig config;
@@ -161,22 +171,112 @@ public class SAMLEndpoint {
             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);
+            if (samlRequest != null) return handleSamlRequest(samlRequest, relayState);
             else return handleSamlResponse(samlResponse, relayState);
         }
 
-        protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState) {
+        protected Response handleSamlRequest(String samlRequest, String relayState) {
+            SAMLDocumentHolder holder = extractRequestDocument(samlRequest);
+            RequestAbstractType requestAbstractType = (RequestAbstractType) holder.getSamlObject();
+            // validate destination
+            if (!uriInfo.getAbsolutePath().toString().equals(requestAbstractType.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 (config.isValidateSignature()) {
                 try {
                     verifySignature(holder);
                 } catch (VerificationException e) {
                     logger.error("validation failed", e);
-                    event.event(EventType.LOGIN);
+                    event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
                     event.error(Errors.INVALID_SIGNATURE);
                     return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUESTER);
                 }
             }
 
+            if (requestAbstractType instanceof LogoutRequestType) {
+                logger.debug("** logout request");
+                event.event(EventType.LOGOUT);
+                LogoutRequestType logout = (LogoutRequestType) requestAbstractType;
+                return logoutRequest(logout, relayState);
+
+            } else {
+                event.event(EventType.LOGIN);
+                event.error(Errors.INVALID_TOKEN);
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
+            }
+        }
+
+        protected Response logoutRequest(LogoutRequestType request, String relayState) {
+            UserModel user = session.users().getUserByUsername(request.getNameID().getValue(), realm);
+            if (user == null) {
+                event.event(EventType.LOGOUT);
+                event.error(Errors.USER_SESSION_NOT_FOUND);
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
+            }
+            List<UserSessionModel> sessions = session.sessions().getUserSessions(realm, user);
+            if (sessions == null || sessions.size() == 0) {
+                event.event(EventType.LOGOUT);
+                event.error(Errors.USER_SESSION_NOT_FOUND);
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
+            }
+            for (UserSessionModel userSession : sessions) {
+                String brokerId = userSession.getNote(IdentityBrokerService.BROKER_PROVIDER_ID);
+                if (!config.getAlias().equals(brokerId)) continue;
+                boolean logout = false;
+                if (request.getSessionIndex() == null || request.getSessionIndex().size() == 0) {
+                    logout = true;
+                } else {
+                    for (String sessionIndex : request.getSessionIndex()) {
+                        if (sessionIndex.equals(userSession.getNote(SAML_FEDERATED_SESSION_INDEX))) {
+                            logout = true;
+                            break;
+                        }
+                    }
+                }
+                if (logout) {
+                    try {
+                        AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
+                    } catch (Exception e) {
+                        logger.error("Failed to logout", e);
+                    }
+                }
+
+                String issuerURL = getEntityId(uriInfo, realm);
+                SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
+                builder.logoutRequestID(request.getID());
+                builder.destination(config.getSingleLogoutServiceUrl());
+                builder.issuer(issuerURL);
+                builder.relayState(relayState);
+                if (config.isWantAuthnRequestsSigned()) {
+                    builder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
+                           .signDocument();
+                }
+                try {
+                    if (config.isPostBindingResponse()) {
+                        return builder.postBinding().response();
+                    } else {
+                        return builder.redirectBinding().response();
+                    }
+                } catch (ConfigurationException e) {
+                    throw new RuntimeException(e);
+                } catch (ProcessingException e) {
+                    throw new RuntimeException(e);
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+
+            }
+            throw new RuntimeException("Unreachable");
+        }
+
+        private String getEntityId(UriInfo uriInfo, RealmModel realm) {
+            return UriBuilder.fromUri(uriInfo.getBaseUri()).path("realms").path(realm.getName()).build().toString();
+        }
+        protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState) {
+
             try {
                 AssertionType assertion = getAssertion(responseType);
                 SubjectType subject = assertion.getSubject();
@@ -205,7 +305,7 @@ public class SAMLEndpoint {
                     }
                 }
                 if (authn != null && authn.getSessionIndex() != null) {
-                    notes.put("SAML_FEDERATED_SESSION_INDEX", authn.getSessionIndex());
+                    notes.put(SAML_FEDERATED_SESSION_INDEX, authn.getSessionIndex());
                 }
                 return callback.authenticated(notes, config, identity, relayState);
 
@@ -241,7 +341,17 @@ public class SAMLEndpoint {
                 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);
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_FEDERATED_IDENTITY_ACTION);
+            }
+            if (config.isValidateSignature()) {
+                try {
+                    verifySignature(holder);
+                } catch (VerificationException e) {
+                    logger.error("validation failed", e);
+                    event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
+                    event.error(Errors.INVALID_SIGNATURE);
+                    return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_FEDERATED_IDENTITY_ACTION);
+                }
             }
             if (statusResponse instanceof ResponseType) {
                 return handleLoginResponse(samlResponse, holder, (ResponseType)statusResponse, relayState);
@@ -255,16 +365,6 @@ public class SAMLEndpoint {
         }
 
         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.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
-                }
-            }
             if (relayState == null) {
                 logger.error("no valid user session");
                 event.event(EventType.LOGOUT);
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index 7493e66..e2d089a 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -654,10 +654,12 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
 
     $scope.initSamlProvider = function() {
         $scope.nameIdFormats = [
+            /*
             {
                 format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
                 name: "Transient"
             },
+            */
             {
                 format: "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
                 name: "Persistent"
diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java b/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
index a316f6f..56d4335 100755
--- a/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
@@ -3,6 +3,7 @@ package org.keycloak.models;
 import org.keycloak.provider.Provider;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -19,6 +20,7 @@ public interface UserSessionProvider extends Provider {
     List<UserSessionModel> getUserSessions(RealmModel realm, UserModel user);
     List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client);
     List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults);
+
     int getActiveUserSessions(RealmModel realm, ClientModel client);
     void removeUserSession(RealmModel realm, UserSessionModel session);
     void removeUserSessions(RealmModel realm, UserModel user);
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
old mode 100644
new mode 100755
index 42dc3aa..85d7a60
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -21,6 +21,7 @@ import org.keycloak.models.sessions.infinispan.mapreduce.FirstResultReducer;
 import org.keycloak.models.sessions.infinispan.mapreduce.LargestResultReducer;
 import org.keycloak.models.sessions.infinispan.mapreduce.SessionMapper;
 import org.keycloak.models.sessions.infinispan.mapreduce.UserSessionMapper;
+import org.keycloak.models.sessions.infinispan.mapreduce.UserSessionNoteMapper;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.RealmInfoUtil;
 import org.keycloak.util.Time;
@@ -172,6 +173,16 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         return userSessions;
     }
 
+    public List<UserSessionModel> getUserSessionsByNote(RealmModel realm, Map<String, String> notes) {
+        Map<String, UserSessionEntity> sessions = new MapReduceTask(sessionCache)
+                .mappedWith(UserSessionNoteMapper.create(realm.getId()).notes(notes))
+                .reducedWith(new FirstResultReducer())
+                .execute();
+
+        return wrapUserSessions(realm, sessions.values());
+
+    }
+
     @Override
     public int getActiveUserSessions(RealmModel realm, ClientModel client) {
         Map map = new MapReduceTask(sessionCache)
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/UserSessionNoteMapper.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/UserSessionNoteMapper.java
new file mode 100755
index 0000000..4a235b5
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/UserSessionNoteMapper.java
@@ -0,0 +1,71 @@
+package org.keycloak.models.sessions.infinispan.mapreduce;
+
+import org.infinispan.distexec.mapreduce.Collector;
+import org.infinispan.distexec.mapreduce.Mapper;
+import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UserSessionNoteMapper implements Mapper<String, SessionEntity, String, Object>, Serializable {
+
+    public UserSessionNoteMapper(String realm) {
+        this.realm = realm;
+    }
+
+    private enum EmitValue {
+        KEY, ENTITY
+    }
+
+    private String realm;
+
+    private EmitValue emit = EmitValue.ENTITY;
+    private Map<String, String> notes;
+
+    public static UserSessionNoteMapper create(String realm) {
+        return new UserSessionNoteMapper(realm);
+    }
+
+    public UserSessionNoteMapper emitKey() {
+        emit = EmitValue.KEY;
+        return this;
+    }
+
+    public UserSessionNoteMapper notes(Map<String, String> notes) {
+        this.notes = notes;
+        return this;
+    }
+
+    @Override
+    public void map(String key, SessionEntity e, Collector collector) {
+        if (!(e instanceof UserSessionEntity)) {
+            return;
+        }
+
+        UserSessionEntity entity = (UserSessionEntity) e;
+
+        if (!realm.equals(entity.getRealm())) {
+            return;
+        }
+
+        for (Map.Entry<String, String> entry : notes.entrySet()) {
+            String note = entity.getNotes().get(entry.getKey());
+            if (note == null) return;
+            if (!note.equals(entry.getValue())) return;
+        }
+
+        switch (emit) {
+            case KEY:
+                collector.emit(key, key);
+                break;
+            case ENTITY:
+                collector.emit(key, entity);
+                break;
+        }
+    }
+
+}