This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git

commit 8ce9aa6141336ba22044270e60030875dd598b23
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Tue Aug 11 14:55:35 2020 +0200

    [SYNCOPE-1579] Finalizing SAML 2.0 logout
---
 .travis.yml                                        |   2 +-
 .../org/apache/syncope/fit/sra/SAML2SRAITCase.java |  13 +-
 .../test/resources/application-saml2.properties    |   2 +-
 .../org/apache/syncope/sra/SecurityConfig.java     |   4 +-
 .../NoOpLogoutHandler.java}                        |  14 +-
 ...{WebSessionStore.java => NoOpSessionStore.java} |  23 ++--
 .../RedirectionActionUtils.java}                   |  18 +--
 .../security/pac4j/ServerWebExchangeContext.java   |  29 +---
 .../security/saml2/SAML2AnonymousWebFilter.java    |   3 +-
 .../saml2/SAML2LogoutResponseWebFilter.java        | 152 +++++++++++++++++++++
 .../sra/security/saml2/SAML2MetadataEndpoint.java  |  23 +++-
 ...r.java => SAML2RequestServerLogoutHandler.java} |  15 +-
 .../security/saml2/SAML2SecurityConfigUtils.java   |  12 +-
 .../saml2/SAML2ServerLogoutSuccessHandler.java     |   3 +-
 .../SAML2WebSsoAuthenticationRequestWebFilter.java |  28 ++--
 .../saml2/SAML2WebSsoAuthenticationWebFilter.java  |  25 +---
 .../resources/debug/application-debug.properties   |  28 ++--
 17 files changed, 257 insertions(+), 137 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index f44f586..2beae45 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -120,6 +120,6 @@ jobs:
       name: "Integration Tests: SRA and WA"
       script: mvn -f fit/wa-reference/pom.xml verify -Dinvoker.streamLogs=true 
-Dmodernizer.skip=true -Dianal.skip=true -Drat.skip=true -Dcheckstyle.skip=true 
-Djacoco.skip=true
       after_failure:
+       - cat fit/wa-reference/target/log/*
        - cat fit/core-reference/target/log/*
-       - cat 
fit/core-reference/target/failsafe-reports/org.apache.syncope.fit.*-output.txt
     #####################################################
diff --git 
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java 
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java
index e4e11f2..2f1ff7c 100644
--- 
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java
+++ 
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java
@@ -22,7 +22,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 import static org.junit.jupiter.api.Assumptions.assumeTrue;
 
@@ -265,17 +264,7 @@ public class SAML2SRAITCase extends AbstractITCase {
         get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
         response = httpclient.execute(get, context);
 
-        // 3b. post SAML response
-        // this is missing as currently WA does not responde with form for 
SP's SingleLogoutService
+        // 3b. check logout
         checkLogout(response);
     }
-
-    @Override
-    protected void checkLogout(final CloseableHttpResponse response) throws 
IOException {
-        assertEquals(HttpStatus.SC_OK, 
response.getStatusLine().getStatusCode());
-
-        String responseBody = EntityUtils.toString(response.getEntity());
-        assertTrue(responseBody.contains("Logout successful"));
-        assertTrue(responseBody.contains("have successfully logged out of the 
Central Authentication Service"));
-    }
 }
diff --git a/fit/wa-reference/src/test/resources/application-saml2.properties 
b/fit/wa-reference/src/test/resources/application-saml2.properties
index fa9a8e4..70fd4d6 100644
--- a/fit/wa-reference/src/test/resources/application-saml2.properties
+++ b/fit/wa-reference/src/test/resources/application-saml2.properties
@@ -17,7 +17,7 @@
 am.type=SAML2
 am.saml2.sp.authnrequest.binding=POST
 am.saml2.sp.logout.request.binding=POST
-am.saml2.sp.logout.response.binding=POST
+am.saml2.sp.logout.response.binding=REDIRECT
 am.saml2.sp.entityId=http://localhost:8080
 am.saml2.sp.skew=300
 am.saml2.idp=http://localhost:9080/syncope-wa/idp/metadata
diff --git a/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java 
b/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java
index 9d9fda1..eeacaef 100644
--- a/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java
+++ b/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java
@@ -28,6 +28,7 @@ import org.apache.syncope.sra.security.LogoutRouteMatcher;
 import org.apache.syncope.sra.security.oauth2.OAuth2SecurityConfigUtils;
 import org.apache.syncope.sra.security.PublicRouteMatcher;
 import org.apache.syncope.sra.security.cas.CASSecurityConfigUtils;
+import org.apache.syncope.sra.security.pac4j.NoOpLogoutHandler;
 import org.apache.syncope.sra.security.saml2.SAML2BindingType;
 import org.apache.syncope.sra.security.saml2.SAML2MetadataEndpoint;
 import org.apache.syncope.sra.security.saml2.SAML2SecurityConfigUtils;
@@ -229,11 +230,12 @@ public class SecurityConfig {
         cfg.setAuthnRequestSigned(true);
         cfg.setSpLogoutRequestSigned(true);
         cfg.setAcceptedSkew(env.getProperty("am.saml2.sp.skew", int.class));
+        cfg.setLogoutHandler(new NoOpLogoutHandler());
 
         SAML2Client saml2Client = new SAML2Client(cfg);
         saml2Client.setName(AMType.SAML2.name());
         saml2Client.setCallbackUrl(env.getProperty("am.saml2.sp.entityId")
-                + 
SAML2WebSsoAuthenticationWebFilter.DEFAULT_FILTER_PROCESSES_URI);
+                + SAML2WebSsoAuthenticationWebFilter.FILTER_PROCESSES_URI);
         saml2Client.setCallbackUrlResolver(new 
NoParameterCallbackUrlResolver());
         saml2Client.init();
 
diff --git 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutSuccessHandler.java
 
b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/NoOpLogoutHandler.java
similarity index 55%
copy from 
sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutSuccessHandler.java
copy to 
sra/src/main/java/org/apache/syncope/sra/security/pac4j/NoOpLogoutHandler.java
index 3557eb5..e370826 100644
--- 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutSuccessHandler.java
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/NoOpLogoutHandler.java
@@ -16,18 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.sra.security.saml2;
+package org.apache.syncope.sra.security.pac4j;
 
-import org.apache.syncope.sra.security.AbstractServerLogoutSuccessHandler;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.web.server.WebFilterExchange;
-import reactor.core.publisher.Mono;
+import org.pac4j.core.logout.handler.LogoutHandler;
 
-public class SAML2ServerLogoutSuccessHandler extends 
AbstractServerLogoutSuccessHandler {
+public class NoOpLogoutHandler implements 
LogoutHandler<ServerWebExchangeContext> {
 
-    @Override
-    public Mono<Void> onLogoutSuccess(final WebFilterExchange exchange, final 
Authentication authentication) {
-        return Mono.just(authentication).
-                flatMap(auth -> 
redirectStrategy.sendRedirect(exchange.getExchange(), getPostLogout(exchange)));
-    }
 }
diff --git 
a/sra/src/main/java/org/apache/syncope/sra/security/pac4j/WebSessionStore.java 
b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/NoOpSessionStore.java
similarity index 72%
rename from 
sra/src/main/java/org/apache/syncope/sra/security/pac4j/WebSessionStore.java
rename to 
sra/src/main/java/org/apache/syncope/sra/security/pac4j/NoOpSessionStore.java
index c74336c..e9629f4 100644
--- 
a/sra/src/main/java/org/apache/syncope/sra/security/pac4j/WebSessionStore.java
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/NoOpSessionStore.java
@@ -20,43 +20,44 @@ package org.apache.syncope.sra.security.pac4j;
 
 import java.util.Optional;
 import org.pac4j.core.context.session.SessionStore;
-import org.springframework.web.server.WebSession;
 
-public class WebSessionStore implements SessionStore<ServerWebExchangeContext> 
{
+public final class NoOpSessionStore implements 
SessionStore<ServerWebExchangeContext> {
 
-    private final WebSession webSession;
+    public static final NoOpSessionStore INSTANCE = new NoOpSessionStore();
 
-    public WebSessionStore(final WebSession webSession) {
-        this.webSession = webSession;
+    private NoOpSessionStore() {
+        // private constructor for singleton
     }
 
     @Override
     public String getOrCreateSessionId(final ServerWebExchangeContext context) 
{
-        return this.webSession.getId();
+        return "<NO_KEY>";
     }
 
     @Override
     public Optional<Object> get(final ServerWebExchangeContext context, final 
String key) {
-        return Optional.ofNullable(this.webSession.getAttribute(key));
+        return Optional.empty();
     }
 
     @Override
     public void set(final ServerWebExchangeContext context, final String key, 
final Object value) {
+        // nothing to do
     }
 
     @Override
     public boolean destroySession(final ServerWebExchangeContext context) {
-        return false;
+        return true;
     }
 
     @Override
-    public Optional<WebSession> getTrackableSession(final 
ServerWebExchangeContext context) {
-        return Optional.ofNullable(this.webSession);
+    public Optional<?> getTrackableSession(final ServerWebExchangeContext 
context) {
+        return Optional.empty();
     }
 
     @Override
     public Optional<SessionStore<ServerWebExchangeContext>> 
buildFromTrackableSession(
-            final ServerWebExchangeContext context, final Object 
trackableSession) {
+            final ServerWebExchangeContext context,
+            final Object trackableSession) {
 
         return Optional.empty();
     }
diff --git 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2RequestGenerator.java
 
b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/RedirectionActionUtils.java
similarity index 86%
rename from 
sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2RequestGenerator.java
rename to 
sra/src/main/java/org/apache/syncope/sra/security/pac4j/RedirectionActionUtils.java
index b595d57..c56c23f 100644
--- 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2RequestGenerator.java
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/RedirectionActionUtils.java
@@ -16,27 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.sra.security.saml2;
+package org.apache.syncope.sra.security.pac4j;
 
-import org.apache.syncope.sra.security.pac4j.ServerWebExchangeContext;
 import java.net.URI;
 import org.pac4j.core.exception.http.RedirectionAction;
 import org.pac4j.core.exception.http.WithContentAction;
 import org.pac4j.core.exception.http.WithLocationAction;
-import org.pac4j.saml.client.SAML2Client;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import reactor.core.publisher.Mono;
 
-abstract class SAML2RequestGenerator {
+public final class RedirectionActionUtils {
 
-    protected final SAML2Client saml2Client;
-
-    protected SAML2RequestGenerator(final SAML2Client saml2Client) {
-        this.saml2Client = saml2Client;
-    }
-
-    protected Mono<Void> handle(
+    public static Mono<Void> handle(
             final RedirectionAction action,
             final ServerWebExchangeContext swec) {
 
@@ -62,4 +54,8 @@ abstract class SAML2RequestGenerator {
             throw new IllegalArgumentException("Unsupported Action: " + 
action.getClass().getName());
         }
     }
+
+    private RedirectionActionUtils() {
+        // private constructor for static utility class
+    }
 }
diff --git 
a/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerWebExchangeContext.java
 
b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerWebExchangeContext.java
index bf22e69..a5ed233 100644
--- 
a/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerWebExchangeContext.java
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerWebExchangeContext.java
@@ -33,52 +33,29 @@ import org.springframework.http.HttpHeaders;
 import org.springframework.http.ResponseCookie;
 import org.springframework.util.MultiValueMap;
 import org.springframework.web.server.ServerWebExchange;
-import org.springframework.web.server.WebSession;
 import org.springframework.web.util.UriComponentsBuilder;
 
 public class ServerWebExchangeContext implements WebContext {
 
     private final ServerWebExchange exchange;
 
-    private WebSessionStore sessionStore;
-
     private MultiValueMap<String, String> form;
 
     private String body;
 
     /**
-     * Build a WebFlux context from the current exchange and web session.
-     *
-     * @param exchange the current exchange
-     * @param webSession the current web session
-     */
-    public ServerWebExchangeContext(final ServerWebExchange exchange, final 
WebSession webSession) {
-        this(exchange, new WebSessionStore(webSession));
-    }
-
-    /**
-     * Build a WebFlux context from the current exhange and from a session 
store.
+     * Build a WebFlux context from the current exchange.
      *
      * @param exchange the current exchange
-     * @param sessionStore the session store to use
      */
-    public ServerWebExchangeContext(
-            final ServerWebExchange exchange,
-            final WebSessionStore sessionStore) {
-
+    public ServerWebExchangeContext(final ServerWebExchange exchange) {
         CommonHelper.assertNotNull("exchange", exchange);
-        CommonHelper.assertNotNull("sessionStore", sessionStore);
         this.exchange = exchange;
-        this.sessionStore = sessionStore;
-    }
-
-    public WebSessionStore getNativeSessionStore() {
-        return this.sessionStore;
     }
 
     @Override
     public SessionStore<ServerWebExchangeContext> getSessionStore() {
-        return this.sessionStore;
+        return NoOpSessionStore.INSTANCE;
     }
 
     @Override
diff --git 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2AnonymousWebFilter.java
 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2AnonymousWebFilter.java
index 6cf7be5..b5f10c4 100644
--- 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2AnonymousWebFilter.java
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2AnonymousWebFilter.java
@@ -36,7 +36,8 @@ public class SAML2AnonymousWebFilter implements WebFilter {
     public SAML2AnonymousWebFilter(final PublicRouteMatcher 
publicRouteMatcher) {
         this.matcher = ServerWebExchangeMatchers.matchers(
                 publicRouteMatcher,
-                SessionUtils.authInSession());
+                SessionUtils.authInSession(),
+                SAML2LogoutResponseWebFilter.MATCHER);
     }
 
     @Override
diff --git 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2LogoutResponseWebFilter.java
 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2LogoutResponseWebFilter.java
new file mode 100644
index 0000000..79733ef
--- /dev/null
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2LogoutResponseWebFilter.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.sra.security.saml2;
+
+import java.util.Optional;
+import org.apache.syncope.sra.SessionConfig;
+import org.apache.syncope.sra.security.pac4j.RedirectionActionUtils;
+import org.apache.syncope.sra.security.pac4j.ServerWebExchangeContext;
+import org.pac4j.core.exception.http.OkAction;
+import org.pac4j.core.exception.http.RedirectionAction;
+import org.pac4j.core.util.Pac4jConstants;
+import org.pac4j.saml.client.SAML2Client;
+import org.pac4j.saml.context.SAML2MessageContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.cache.CacheManager;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.web.server.WebFilterExchange;
+import 
org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
+import 
org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
+import 
org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult;
+import 
org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Mono;
+
+public class SAML2LogoutResponseWebFilter implements WebFilter {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(SAML2LogoutResponseWebFilter.class);
+
+    public static final ServerWebExchangeMatcher MATCHER =
+            ServerWebExchangeMatchers.pathMatchers("/logout/saml2/sso");
+
+    private static class ServerWebExchangeLogoutContext extends 
ServerWebExchangeContext {
+
+        ServerWebExchangeLogoutContext(final ServerWebExchange exchange) {
+            super(exchange);
+        }
+
+        @Override
+        public Optional<String> getRequestParameter(final String name) {
+            return Pac4jConstants.LOGOUT_ENDPOINT_PARAMETER.equals(name)
+                    ? Optional.of("true")
+                    : super.getRequestParameter(name);
+        }
+    }
+
+    private final SAML2Client saml2Client;
+
+    private final ServerLogoutSuccessHandler logoutSuccessHandler;
+
+    private final CacheManager cacheManager;
+
+    public SAML2LogoutResponseWebFilter(
+            final SAML2Client saml2Client,
+            final SAML2ServerLogoutSuccessHandler logoutSuccessHandler,
+            final CacheManager cacheManager) {
+
+        this.saml2Client = saml2Client;
+        this.logoutSuccessHandler = logoutSuccessHandler;
+        this.cacheManager = cacheManager;
+    }
+
+    private Mono<Void> handleLogoutResponse(
+            final ServerWebExchange exchange, final WebFilterChain chain, 
final ServerWebExchangeContext swec) {
+
+        try {
+            SAML2MessageContext ctx = 
saml2Client.getContextProvider().buildContext(swec);
+            saml2Client.getLogoutProfileHandler().receive(ctx);
+        } catch (OkAction e) {
+            LOG.debug("LogoutResponse was actually validated but no 
postLogoutURL was set", e);
+        } catch (Exception e) {
+            LOG.error("Could not validate LogoutResponse", e);
+        }
+
+        return logoutSuccessHandler.onLogoutSuccess(new 
WebFilterExchange(exchange, chain), null);
+    }
+
+    private Mono<Void> handleLogoutRequest(
+            final ServerWebExchange exchange, final WebFilterChain chain, 
final ServerWebExchangeContext swec) {
+
+        return exchange.getSession().
+                switchIfEmpty(chain.filter(exchange).then(Mono.empty())).
+                flatMap(session -> {
+                    
cacheManager.getCache(SessionConfig.DEFAULT_CACHE).evictIfPresent(session.getId());
+
+                    return session.invalidate().then(Mono.defer(() -> {
+                        try {
+                            
saml2Client.getCredentialsExtractor().extract(swec);
+                        } catch (RedirectionAction action) {
+                            return RedirectionActionUtils.handle(action, swec);
+                        }
+
+                        return chain.filter(exchange).then(Mono.empty());
+                    }));
+                });
+    }
+
+    private Mono<Void> handleGET(final ServerWebExchange exchange, final 
WebFilterChain chain) {
+        if (exchange.getRequest().getQueryParams().getFirst("SAMLResponse") != 
null) {
+            return handleLogoutResponse(exchange, chain, new 
ServerWebExchangeContext(exchange));
+        } else if 
(exchange.getRequest().getQueryParams().getFirst("SAMLRequest") != null) {
+            return handleLogoutRequest(exchange, chain, new 
ServerWebExchangeLogoutContext(exchange));
+        }
+
+        return chain.filter(exchange).then(Mono.empty());
+    }
+
+    private Mono<Void> handlePOST(final ServerWebExchange exchange, final 
WebFilterChain chain) {
+        return exchange.getFormData().flatMap(form -> {
+            if (form.containsKey("SAMLResponse")) {
+                return handleLogoutResponse(exchange, chain, new 
ServerWebExchangeContext(exchange).setForm(form));
+            } else if (form.containsKey("SAMLRequest")) {
+                return handleLogoutRequest(exchange, chain, new 
ServerWebExchangeLogoutContext(exchange).setForm(form));
+            }
+
+            return chain.filter(exchange).then(Mono.empty());
+        });
+    }
+
+    @Override
+    public Mono<Void> filter(final ServerWebExchange exchange, final 
WebFilterChain chain) {
+        return MATCHER.matches(exchange).
+                filter(MatchResult::isMatch).
+                switchIfEmpty(chain.filter(exchange).then(Mono.empty())).
+                flatMap(matchResult -> {
+                    return exchange.getRequest().getMethod() == HttpMethod.GET
+                            ? handleGET(exchange, chain)
+                            : exchange.getRequest().getMethod() == 
HttpMethod.POST
+                            ? handlePOST(exchange, chain)
+                            : Mono.error(() -> new 
UnsupportedOperationException(
+                            "Unsupported HTTP method: " + 
exchange.getRequest().getMethod()));
+                });
+    }
+}
diff --git 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2MetadataEndpoint.java
 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2MetadataEndpoint.java
index 229d232..6c8efa6 100644
--- 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2MetadataEndpoint.java
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2MetadataEndpoint.java
@@ -19,8 +19,12 @@
 package org.apache.syncope.sra.security.saml2;
 
 import com.google.common.net.HttpHeaders;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.sra.SecurityConfig;
+import org.opensaml.saml.common.xml.SAMLConstants;
+import org.opensaml.saml.saml2.metadata.EntityDescriptor;
 import org.pac4j.saml.client.SAML2Client;
+import org.pac4j.saml.exceptions.SAMLException;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
@@ -38,10 +42,23 @@ public class SAML2MetadataEndpoint {
 
     public static final String METADATA_URL = "/saml2/metadata";
 
-    private final SAML2Client saml2Client;
+    private final String metadata;
 
     public SAML2MetadataEndpoint(final SAML2Client saml2Client) {
-        this.saml2Client = saml2Client;
+        EntityDescriptor entityDescriptor = (EntityDescriptor) 
saml2Client.getServiceProviderMetadataResolver().
+                getEntityDescriptorElement();
+        
entityDescriptor.getSPSSODescriptor(SAMLConstants.SAML20P_NS).getSingleLogoutServices().
+                removeIf(slo -> !saml2Client.getConfiguration().
+                getSpLogoutResponseBindingType().equals(slo.getBinding()));
+        
entityDescriptor.getSPSSODescriptor(SAMLConstants.SAML20P_NS).getSingleLogoutServices().
+                forEach(slo -> slo.setLocation(
+                StringUtils.substringBefore(slo.getLocation(), 
"?").replace("login", "logout")));
+
+        try {
+            this.metadata = 
saml2Client.getConfiguration().toMetadataGenerator().getMetadata(entityDescriptor);
+        } catch (Exception e) {
+            throw new SAMLException("Unable to fetch metadata", e);
+        }
     }
 
     @GetMapping(produces = { MediaType.APPLICATION_XML_VALUE })
@@ -49,6 +66,6 @@ public class SAML2MetadataEndpoint {
     public Mono<ResponseEntity<String>> metadata(final ServerHttpRequest 
request) {
         return Mono.just(ResponseEntity.ok().
                 header(HttpHeaders.CONTENT_TYPE, 
MediaType.APPLICATION_XML_VALUE).
-                
body(saml2Client.getServiceProviderMetadataResolver().getMetadata()));
+                body(metadata));
     }
 }
diff --git 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutHandler.java
 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2RequestServerLogoutHandler.java
similarity index 83%
rename from 
sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutHandler.java
rename to 
sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2RequestServerLogoutHandler.java
index 0959065..973bd20 100644
--- 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutHandler.java
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2RequestServerLogoutHandler.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.sra.security.saml2;
 
+import org.apache.syncope.sra.security.pac4j.RedirectionActionUtils;
 import org.apache.syncope.sra.SessionConfig;
 import org.apache.syncope.sra.security.pac4j.ServerWebExchangeContext;
 import org.pac4j.saml.client.SAML2Client;
@@ -30,14 +31,16 @@ import 
org.springframework.security.web.server.WebFilterExchange;
 import 
org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
 import reactor.core.publisher.Mono;
 
-public class SAML2ServerLogoutHandler extends SAML2RequestGenerator implements 
ServerLogoutHandler {
+public class SAML2RequestServerLogoutHandler implements ServerLogoutHandler {
 
-    private static final Logger LOG = 
LoggerFactory.getLogger(SAML2ServerLogoutHandler.class);
+    private static final Logger LOG = 
LoggerFactory.getLogger(SAML2RequestServerLogoutHandler.class);
+
+    private final SAML2Client saml2Client;
 
     private final CacheManager cacheManager;
 
-    public SAML2ServerLogoutHandler(final SAML2Client saml2Client, final 
CacheManager cacheManager) {
-        super(saml2Client);
+    public SAML2RequestServerLogoutHandler(final SAML2Client saml2Client, 
final CacheManager cacheManager) {
+        this.saml2Client = saml2Client;
         this.cacheManager = cacheManager;
     }
 
@@ -50,12 +53,12 @@ public class SAML2ServerLogoutHandler extends 
SAML2RequestGenerator implements S
                     LOG.debug("Creating SAML2 SP Logout Request for IDP[{}] 
and Profile[{}]",
                             saml2Client.getIdentityProviderResolvedEntityId(), 
credentials.getUserProfile());
 
-                    ServerWebExchangeContext swec = new 
ServerWebExchangeContext(exchange.getExchange(), session);
+                    ServerWebExchangeContext swec = new 
ServerWebExchangeContext(exchange.getExchange());
 
                     
cacheManager.getCache(SessionConfig.DEFAULT_CACHE).evictIfPresent(session.getId());
                     return session.invalidate().then(
                             saml2Client.getLogoutAction(swec, 
credentials.getUserProfile(), null).
-                                    map(action -> handle(action, swec)).
+                                    map(action -> 
RedirectionActionUtils.handle(action, swec)).
                                     orElseThrow(() -> new 
IllegalStateException("No action generated")));
                 }).onErrorResume(Mono::error);
     }
diff --git 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2SecurityConfigUtils.java
 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2SecurityConfigUtils.java
index 63001da..a7be69c 100644
--- 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2SecurityConfigUtils.java
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2SecurityConfigUtils.java
@@ -74,14 +74,18 @@ public final class SAML2SecurityConfigUtils {
 
         LogoutWebFilter logoutWebFilter = new LogoutWebFilter();
         logoutWebFilter.setRequiresLogoutMatcher(logoutRouteMatcher);
-
-        logoutWebFilter.setLogoutHandler(new 
SAML2ServerLogoutHandler(saml2Client, cacheManager));
+        logoutWebFilter.setLogoutHandler(new 
SAML2RequestServerLogoutHandler(saml2Client, cacheManager));
+        logoutWebFilter.setLogoutSuccessHandler((exchange, authentication) -> 
Mono.empty());
 
         try {
-            SAML2ServerLogoutSuccessHandler handler = 
ApplicationContextUtils.getOrCreateBean(ctx,
+            SAML2ServerLogoutSuccessHandler logoutSuccessHandler = 
ApplicationContextUtils.getOrCreateBean(
+                    ctx,
                     SAML2ServerLogoutSuccessHandler.class.getName(),
                     SAML2ServerLogoutSuccessHandler.class);
-            logoutWebFilter.setLogoutSuccessHandler(handler);
+
+            SAML2LogoutResponseWebFilter logoutResponseWebFilter =
+                    new SAML2LogoutResponseWebFilter(saml2Client, 
logoutSuccessHandler, cacheManager);
+            builder.and().addFilterAt(logoutResponseWebFilter, 
SecurityWebFiltersOrder.LOGOUT);
         } catch (ClassNotFoundException e) {
             LOG.error("While creating instance of {}", 
SAML2ServerLogoutSuccessHandler.class.getName(), e);
         }
diff --git 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutSuccessHandler.java
 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutSuccessHandler.java
index 3557eb5..f10e388 100644
--- 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutSuccessHandler.java
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutSuccessHandler.java
@@ -27,7 +27,6 @@ public class SAML2ServerLogoutSuccessHandler extends 
AbstractServerLogoutSuccess
 
     @Override
     public Mono<Void> onLogoutSuccess(final WebFilterExchange exchange, final 
Authentication authentication) {
-        return Mono.just(authentication).
-                flatMap(auth -> 
redirectStrategy.sendRedirect(exchange.getExchange(), getPostLogout(exchange)));
+        return redirectStrategy.sendRedirect(exchange.getExchange(), 
getPostLogout(exchange));
     }
 }
diff --git 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationRequestWebFilter.java
 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationRequestWebFilter.java
index ab1819c..14a5b8d 100644
--- 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationRequestWebFilter.java
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationRequestWebFilter.java
@@ -18,49 +18,47 @@
  */
 package org.apache.syncope.sra.security.saml2;
 
+import org.apache.syncope.sra.security.pac4j.RedirectionActionUtils;
 import org.apache.syncope.sra.security.pac4j.ServerWebExchangeContext;
 import org.pac4j.saml.client.SAML2Client;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import 
org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
+import 
org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult;
 import 
org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
-import org.springframework.util.Assert;
 import org.springframework.web.server.ServerWebExchange;
 import org.springframework.web.server.WebFilter;
 import org.springframework.web.server.WebFilterChain;
 import reactor.core.publisher.Mono;
 
-public class SAML2WebSsoAuthenticationRequestWebFilter extends 
SAML2RequestGenerator implements WebFilter {
+public class SAML2WebSsoAuthenticationRequestWebFilter implements WebFilter {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(SAML2WebSsoAuthenticationRequestWebFilter.class);
 
     public static final String AUTHENTICATE_URL = "/saml2/authenticate";
 
-    private ServerWebExchangeMatcher redirectMatcher = 
ServerWebExchangeMatchers.pathMatchers(AUTHENTICATE_URL);
+    private static final ServerWebExchangeMatcher MATCHER =
+            ServerWebExchangeMatchers.pathMatchers(AUTHENTICATE_URL);
 
-    public SAML2WebSsoAuthenticationRequestWebFilter(final SAML2Client 
saml2Client) {
-        super(saml2Client);
-    }
+    private final SAML2Client saml2Client;
 
-    public void setRedirectMatcher(final ServerWebExchangeMatcher 
redirectMatcher) {
-        Assert.notNull(redirectMatcher, "redirectMatcher cannot be null");
-        this.redirectMatcher = redirectMatcher;
+    public SAML2WebSsoAuthenticationRequestWebFilter(final SAML2Client 
saml2Client) {
+        this.saml2Client = saml2Client;
     }
 
     @Override
     public Mono<Void> filter(final ServerWebExchange exchange, final 
WebFilterChain chain) {
-        return redirectMatcher.matches(exchange).
-                filter(matchResult -> matchResult.isMatch()).
+        return MATCHER.matches(exchange).
+                filter(MatchResult::isMatch).
                 switchIfEmpty(chain.filter(exchange).then(Mono.empty())).
-                flatMap(matchResult -> exchange.getSession()).
-                flatMap(session -> {
+                flatMap(matchResult -> {
                     LOG.debug("Creating SAML2 SP Authentication Request for 
IDP[{}]",
                             saml2Client.getIdentityProviderResolvedEntityId());
 
-                    ServerWebExchangeContext swec = new 
ServerWebExchangeContext(exchange, session);
+                    ServerWebExchangeContext swec = new 
ServerWebExchangeContext(exchange);
 
                     return saml2Client.getRedirectionAction(swec).
-                            map(action -> handle(action, swec)).
+                            map(action -> 
RedirectionActionUtils.handle(action, swec)).
                             orElseThrow(() -> new IllegalStateException("No 
action generated"));
                 }).onErrorResume(Mono::error);
     }
diff --git 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationWebFilter.java
 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationWebFilter.java
index 0cbd9f7..5dc59b3 100644
--- 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationWebFilter.java
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationWebFilter.java
@@ -22,7 +22,6 @@ import java.net.URI;
 import org.apache.syncope.sra.security.pac4j.ServerWebExchangeContext;
 import 
org.apache.syncope.sra.security.web.server.DoNothingIfCommittedServerRedirectStrategy;
 import org.apache.syncope.sra.session.SessionUtils;
-import org.pac4j.core.util.Pac4jConstants;
 import org.pac4j.saml.client.SAML2Client;
 import org.pac4j.saml.credentials.SAML2Credentials;
 import 
org.springframework.security.authentication.ReactiveAuthenticationManager;
@@ -32,7 +31,6 @@ import 
org.springframework.security.web.server.WebFilterExchange;
 import 
org.springframework.security.web.server.authentication.AuthenticationWebFilter;
 import 
org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
 import 
org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
-import 
org.springframework.security.web.server.util.matcher.AndServerWebExchangeMatcher;
 import 
org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
 import 
org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
 import org.springframework.web.server.ServerWebExchange;
@@ -41,16 +39,12 @@ import reactor.core.publisher.Mono;
 
 public class SAML2WebSsoAuthenticationWebFilter extends 
AuthenticationWebFilter {
 
-    public static final String DEFAULT_FILTER_PROCESSES_URI = 
"/login/saml2/sso";
+    public static final String FILTER_PROCESSES_URI = "/login/saml2/sso";
 
-    private final SAML2Client saml2Client;
+    private static final ServerWebExchangeMatcher MATCHER =
+            ServerWebExchangeMatchers.pathMatchers(FILTER_PROCESSES_URI);
 
-    private ServerWebExchangeMatcher matcher = new AndServerWebExchangeMatcher(
-            
ServerWebExchangeMatchers.pathMatchers(DEFAULT_FILTER_PROCESSES_URI),
-            exchange -> exchange.getRequest().getQueryParams().
-                    containsKey(Pac4jConstants.LOGOUT_ENDPOINT_PARAMETER)
-            ? ServerWebExchangeMatcher.MatchResult.notMatch()
-            : ServerWebExchangeMatcher.MatchResult.match());
+    private final SAML2Client saml2Client;
 
     public SAML2WebSsoAuthenticationWebFilter(
             final ReactiveAuthenticationManager authenticationManager,
@@ -67,10 +61,6 @@ public class SAML2WebSsoAuthenticationWebFilter extends 
AuthenticationWebFilter
         setAuthenticationSuccessHandler(redirectToInitialRequestURI());
     }
 
-    public void setMatcher(final ServerWebExchangeMatcher matcher) {
-        this.matcher = matcher;
-    }
-
     @Override
     public Mono<Void> filter(final ServerWebExchange exchange, final 
WebFilterChain chain) {
         return super.filter(exchange, 
chain).then(Mono.defer(exchange.getResponse()::setComplete));
@@ -85,10 +75,9 @@ public class SAML2WebSsoAuthenticationWebFilter extends 
AuthenticationWebFilter
 
     private ServerAuthenticationConverter convertSamlResponse() {
         return exchange -> exchange.getFormData().
-                flatMap(form -> this.matcher.matches(exchange).
-                flatMap(matchResult -> exchange.getSession()).
-                flatMap(session -> {
-                    ServerWebExchangeContext swec = new 
ServerWebExchangeContext(exchange, session).setForm(form);
+                flatMap(form -> this.MATCHER.matches(exchange).
+                flatMap(matchResult -> {
+                    ServerWebExchangeContext swec = new 
ServerWebExchangeContext(exchange).setForm(form);
 
                     SAML2Credentials credentials = 
saml2Client.getCredentialsExtractor().extract(swec).
                             orElseThrow(() -> new IllegalStateException("No 
AuthnResponse found"));
diff --git a/sra/src/test/resources/debug/application-debug.properties 
b/sra/src/test/resources/debug/application-debug.properties
index b74d3a4..c029337 100644
--- a/sra/src/test/resources/debug/application-debug.properties
+++ b/sra/src/test/resources/debug/application-debug.properties
@@ -30,20 +30,20 @@
 #am.oauth2.client.id=oauth2TestClientId
 #am.oauth2.client.secret=oauth2TestClientSecret
 
-#am.type=SAML2
-#am.saml2.sp.authnrequest.binding=POST
-#am.saml2.sp.logout.request.binding=POST
-#am.saml2.sp.logout.response.binding=POST
-#am.saml2.sp.entityId=http://localhost:8080
-#am.saml2.sp.skew=300
-#am.saml2.idp=http://localhost:9080/syncope-wa/idp/metadata
-#am.saml2.keystore=classpath:/saml.keystore.jks
-#am.saml2.keystore.type=jks
-#am.saml2.keystore.storepass=changeit
-#am.saml2.keystore.keypass=changeit
+am.type=SAML2
+am.saml2.sp.authnrequest.binding=POST
+am.saml2.sp.logout.request.binding=POST
+am.saml2.sp.logout.response.binding=REDIRECT
+am.saml2.sp.entityId=http://localhost:8080
+am.saml2.sp.skew=300
+am.saml2.idp=http://localhost:9080/syncope-wa/idp/metadata
+am.saml2.keystore=classpath:/saml.keystore.jks
+am.saml2.keystore.type=jks
+am.saml2.keystore.storepass=changeit
+am.saml2.keystore.keypass=changeit
 
-am.type=CAS
-am.cas.server.name=http://localhost:80
-am.cas.url.prefix=http://localhost:9080/syncope-wa/
+#am.type=CAS
+#am.cas.server.name=http://localhost:80
+#am.cas.url.prefix=http://localhost:9080/syncope-wa/
 
 global.postLogout=http://localhost:8080/logout

Reply via email to