1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.sra.security.saml2;
20
21 import java.util.Optional;
22 import org.apache.syncope.sra.SessionConfig;
23 import org.apache.syncope.sra.security.pac4j.NoOpSessionStore;
24 import org.apache.syncope.sra.security.pac4j.RedirectionActionUtils;
25 import org.apache.syncope.sra.security.pac4j.ServerWebExchangeContext;
26 import org.pac4j.core.exception.http.OkAction;
27 import org.pac4j.core.exception.http.RedirectionAction;
28 import org.pac4j.core.util.Pac4jConstants;
29 import org.pac4j.saml.client.SAML2Client;
30 import org.pac4j.saml.context.SAML2MessageContext;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33 import org.springframework.cache.CacheManager;
34 import org.springframework.http.HttpMethod;
35 import org.springframework.security.web.server.WebFilterExchange;
36 import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
37 import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
38 import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult;
39 import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
40 import org.springframework.web.server.ServerWebExchange;
41 import org.springframework.web.server.WebFilter;
42 import org.springframework.web.server.WebFilterChain;
43 import reactor.core.publisher.Mono;
44
45 public class SAML2LogoutResponseWebFilter implements WebFilter {
46
47 private static final Logger LOG = LoggerFactory.getLogger(SAML2LogoutResponseWebFilter.class);
48
49 public static final ServerWebExchangeMatcher MATCHER =
50 ServerWebExchangeMatchers.pathMatchers("/logout/saml2/sso");
51
52 private static class ServerWebExchangeLogoutContext extends ServerWebExchangeContext {
53
54 ServerWebExchangeLogoutContext(final ServerWebExchange exchange) {
55 super(exchange);
56 }
57
58 @Override
59 public Optional<String> getRequestParameter(final String name) {
60 return Pac4jConstants.LOGOUT_ENDPOINT_PARAMETER.equals(name)
61 ? Optional.of("true")
62 : super.getRequestParameter(name);
63 }
64 }
65
66 private final SAML2Client saml2Client;
67
68 private final ServerLogoutSuccessHandler logoutSuccessHandler;
69
70 private final CacheManager cacheManager;
71
72 public SAML2LogoutResponseWebFilter(
73 final SAML2Client saml2Client,
74 final SAML2ServerLogoutSuccessHandler logoutSuccessHandler,
75 final CacheManager cacheManager) {
76
77 this.saml2Client = saml2Client;
78 this.logoutSuccessHandler = logoutSuccessHandler;
79 this.cacheManager = cacheManager;
80 }
81
82 private Mono<Void> handleLogoutResponse(
83 final ServerWebExchange exchange, final WebFilterChain chain, final ServerWebExchangeContext swec) {
84
85 try {
86 SAML2MessageContext ctx = saml2Client.getContextProvider().
87 buildContext(this.saml2Client, swec, NoOpSessionStore.INSTANCE);
88 saml2Client.getLogoutProfileHandler().receive(ctx);
89 } catch (OkAction e) {
90 LOG.debug("LogoutResponse was actually validated but no postLogoutURL was set", e);
91 } catch (Exception e) {
92 LOG.error("Could not validate LogoutResponse", e);
93 }
94
95 return logoutSuccessHandler.onLogoutSuccess(new WebFilterExchange(exchange, chain), null);
96 }
97
98 private Mono<Void> handleLogoutRequest(
99 final ServerWebExchange exchange, final WebFilterChain chain, final ServerWebExchangeContext swec) {
100
101 return exchange.getSession().
102 switchIfEmpty(chain.filter(exchange).then(Mono.empty())).
103 flatMap(session -> {
104 cacheManager.getCache(SessionConfig.DEFAULT_CACHE).evictIfPresent(session.getId());
105
106 return session.invalidate().then(Mono.defer(() -> {
107 try {
108 saml2Client.getCredentialsExtractor().extract(swec, NoOpSessionStore.INSTANCE);
109 } catch (RedirectionAction action) {
110 return RedirectionActionUtils.handle(action, swec);
111 }
112
113 return chain.filter(exchange).then(Mono.empty());
114 }));
115 });
116 }
117
118 private Mono<Void> handleGET(final ServerWebExchange exchange, final WebFilterChain chain) {
119 if (exchange.getRequest().getQueryParams().getFirst("SAMLResponse") != null) {
120 return handleLogoutResponse(exchange, chain, new ServerWebExchangeContext(exchange));
121 } else if (exchange.getRequest().getQueryParams().getFirst("SAMLRequest") != null) {
122 return handleLogoutRequest(exchange, chain, new ServerWebExchangeLogoutContext(exchange));
123 }
124
125 return chain.filter(exchange).then(Mono.empty());
126 }
127
128 private Mono<Void> handlePOST(final ServerWebExchange exchange, final WebFilterChain chain) {
129 return exchange.getFormData().flatMap(form -> {
130 if (form.containsKey("SAMLResponse")) {
131 return handleLogoutResponse(exchange, chain, new ServerWebExchangeContext(exchange).setForm(form));
132 } else if (form.containsKey("SAMLRequest")) {
133 return handleLogoutRequest(exchange, chain, new ServerWebExchangeLogoutContext(exchange).setForm(form));
134 }
135
136 return chain.filter(exchange).then(Mono.empty());
137 });
138 }
139
140 @Override
141 public Mono<Void> filter(final ServerWebExchange exchange, final WebFilterChain chain) {
142 return MATCHER.matches(exchange).
143 filter(MatchResult::isMatch).
144 switchIfEmpty(chain.filter(exchange).then(Mono.empty())).
145 flatMap(matchResult -> {
146 return exchange.getRequest().getMethod() == HttpMethod.GET
147 ? handleGET(exchange, chain)
148 : exchange.getRequest().getMethod() == HttpMethod.POST
149 ? handlePOST(exchange, chain)
150 : Mono.error(() -> new UnsupportedOperationException(
151 "Unsupported HTTP method: " + exchange.getRequest().getMethod()));
152 });
153 }
154 }