You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2020/08/05 06:11:37 UTC
[syncope] branch master updated: [SYNCOPE-1582] Adding CAS client
support (#209)
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
The following commit(s) were added to refs/heads/master by this push:
new aaaced8 [SYNCOPE-1582] Adding CAS client support (#209)
aaaced8 is described below
commit aaaced89f94f30c3280d4cc3d57a880a52a94571
Author: Francesco Chicchiriccò <il...@users.noreply.github.com>
AuthorDate: Wed Aug 5 08:11:26 2020 +0200
[SYNCOPE-1582] Adding CAS client support (#209)
---
.../syncope/core/logic/wa/WAClientAppLogic.java | 26 +++-
.../org/apache/syncope/fit/sra/AbstractITCase.java | 10 +-
.../org/apache/syncope/fit/sra/CASSRAITCase.java | 154 +++++++++++++++++++++
.../org/apache/syncope/fit/sra/OIDCSRAITCase.java | 4 +-
.../org/apache/syncope/fit/sra/SAML2SRAITCase.java | 5 +-
.../src/test/resources/application-cas.properties | 21 +++
pom.xml | 13 ++
sra/pom.xml | 7 +-
.../org/apache/syncope/sra/SecurityConfig.java | 18 ++-
.../cas/CASAuthenticationRequestWebFilter.java | 105 ++++++++++++++
.../sra/security/cas/CASAuthenticationToken.java | 46 ++++++
.../security/cas/CASAuthenticationWebFilter.java | 105 ++++++++++++++
.../CASSecurityConfigUtils.java} | 50 +++----
.../CASServerLogoutHandler.java} | 32 +++--
.../cas/CASServerLogoutSuccessHandler.java | 33 +++++
.../apache/syncope/sra/security/cas/CASUtils.java | 111 +++++++++++++++
.../security/oauth2/OAuth2SecurityConfigUtils.java | 3 +-
.../OAuth2SessionRemovalServerLogoutHandler.java} | 17 +--
...pContext.java => ServerWebExchangeContext.java} | 20 +--
...rHttpSessionStore.java => WebSessionStore.java} | 20 +--
.../security/saml2/SAML2AnonymousWebFilter.java | 20 +--
.../sra/security/saml2/SAML2RequestGenerator.java | 16 +--
.../security/saml2/SAML2SecurityConfigUtils.java | 11 +-
.../security/saml2/SAML2ServerLogoutHandler.java | 16 ++-
.../SAML2WebSsoAuthenticationRequestWebFilter.java | 8 +-
.../saml2/SAML2WebSsoAuthenticationWebFilter.java | 11 +-
.../apache/syncope/sra/session/SessionUtils.java | 39 ++++++
.../resources/debug/application-debug.properties | 26 ++--
28 files changed, 821 insertions(+), 126 deletions(-)
diff --git a/core/am/logic/src/main/java/org/apache/syncope/core/logic/wa/WAClientAppLogic.java b/core/am/logic/src/main/java/org/apache/syncope/core/logic/wa/WAClientAppLogic.java
index a1e6765..2d05503 100644
--- a/core/am/logic/src/main/java/org/apache/syncope/core/logic/wa/WAClientAppLogic.java
+++ b/core/am/logic/src/main/java/org/apache/syncope/core/logic/wa/WAClientAppLogic.java
@@ -29,8 +29,10 @@ import org.apache.syncope.common.lib.wa.WAClientApp;
import org.apache.syncope.common.lib.types.ClientAppType;
import org.apache.syncope.common.lib.types.IdRepoEntitlement;
import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.auth.CASSPDAO;
import org.apache.syncope.core.persistence.api.dao.auth.OIDCRPDAO;
import org.apache.syncope.core.persistence.api.dao.auth.SAML2SPDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.CASSP;
import org.apache.syncope.core.persistence.api.entity.auth.OIDCRP;
import org.apache.syncope.core.persistence.api.entity.auth.SAML2SP;
import org.springframework.beans.factory.annotation.Autowired;
@@ -51,6 +53,9 @@ public class WAClientAppLogic {
@Autowired
private OIDCRPDAO oidcrpDAO;
+ @Autowired
+ private CASSPDAO casspDAO;
+
@PreAuthorize("hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
@Transactional(readOnly = true)
public List<WAClientApp> list() {
@@ -64,9 +69,14 @@ public class WAClientAppLogic {
break;
case SAML2SP:
- default:
clientApps.addAll(saml2spDAO.findAll().stream().
map(binder::getWAClientApp).collect(Collectors.toList()));
+ break;
+
+ case CASSP:
+ default:
+ clientApps.addAll(casspDAO.findAll().stream().
+ map(binder::getWAClientApp).collect(Collectors.toList()));
}
});
@@ -91,6 +101,13 @@ public class WAClientAppLogic {
}
break;
+ case CASSP:
+ CASSP cassp = casspDAO.findByClientAppId(clientAppId);
+ if (cassp != null) {
+ clientApp = binder.getWAClientApp(cassp);
+ }
+ break;
+
default:
}
@@ -134,6 +151,13 @@ public class WAClientAppLogic {
}
break;
+ case CASSP:
+ CASSP cassp = casspDAO.findByName(name);
+ if (cassp != null) {
+ clientApp = binder.getWAClientApp(cassp);
+ }
+ break;
+
default:
}
diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/AbstractITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/AbstractITCase.java
index db6e6a0..9ff4b97 100644
--- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/AbstractITCase.java
+++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/AbstractITCase.java
@@ -44,6 +44,7 @@ import java.util.concurrent.TimeoutException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import org.apache.commons.lang3.StringUtils;
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.http.Consts;
import org.apache.http.HttpStatus;
@@ -96,6 +97,9 @@ public abstract class AbstractITCase {
protected static final String SRA_ADDRESS = "http://localhost:" + PORT;
+ protected static final String QUERY_STRING =
+ "key1=value1&key2=value2&key2=value3&key3=an%20url%20encoded%20value%3A%20this%21";
+
protected static final String LOGGED_OUT_HEADER = "X-LOGGED-OUT";
protected static SyncopeClientFactoryBean clientFactory;
@@ -332,12 +336,16 @@ public abstract class AbstractITCase {
assertEquals("value2", key2.get(0).asText());
assertEquals("value3", key2.get(1).asText());
+ assertEquals("an url encoded value: this!", args.get("key3").asText());
+
ObjectNode headers = (ObjectNode) json.get("headers");
assertEquals(MediaType.TEXT_HTML, headers.get(HttpHeaders.ACCEPT).asText());
assertEquals(EN_LANGUAGE, headers.get(HttpHeaders.ACCEPT_LANGUAGE).asText());
assertEquals("localhost:" + PORT, headers.get("X-Forwarded-Host").asText());
- assertEquals(originalRequestURI, json.get("url").asText());
+ assertEquals(
+ StringUtils.substringBefore(originalRequestURI, "?"),
+ StringUtils.substringBefore(json.get("url").asText(), "?"));
return headers;
}
diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/CASSRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/CASSRAITCase.java
new file mode 100644
index 0000000..2b0e59a
--- /dev/null
+++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/CASSRAITCase.java
@@ -0,0 +1,154 @@
+/*
+ * 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.fit.sra;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.http.Consts;
+import org.apache.http.HttpStatus;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.impl.client.BasicCookieStore;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+import org.apache.syncope.common.lib.to.client.CASSPTO;
+import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class CASSRAITCase extends AbstractITCase {
+
+ @BeforeAll
+ public static void startSRA() throws IOException, InterruptedException, TimeoutException {
+ assumeTrue(CASSRAITCase.class.equals(MethodHandles.lookup().lookupClass()));
+
+ doStartSRA("cas");
+ }
+
+ @BeforeAll
+ public static void clientAppSetup() {
+ String appName = CASSRAITCase.class.getName();
+ CASSPTO clientApp = clientAppService.list(ClientAppType.CASSP).stream().
+ filter(app -> appName.equals(app.getName())).
+ map(CASSPTO.class::cast).
+ findFirst().
+ orElseGet(() -> {
+ CASSPTO app = new CASSPTO();
+ app.setName(appName);
+ app.setClientAppId(4L);
+ app.setServiceId("http://localhost:8080/.*");
+
+ Response response = clientAppService.create(ClientAppType.CASSP, app);
+ if (response.getStatusInfo().getStatusCode() != Response.Status.CREATED.getStatusCode()) {
+ fail("Could not create CAS Client App");
+ }
+
+ return clientAppService.read(
+ ClientAppType.CASSP, response.getHeaderString(RESTHeaders.RESOURCE_KEY));
+ });
+
+ clientApp.setAuthPolicy(getAuthPolicy().getKey());
+
+ clientAppService.update(ClientAppType.CASSP, clientApp);
+ clientAppService.pushToWA();
+ }
+
+ @Test
+ public void web() throws IOException {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ HttpClientContext context = HttpClientContext.create();
+ context.setCookieStore(new BasicCookieStore());
+
+ // 1. public
+ HttpGet get = new HttpGet(SRA_ADDRESS + "/public/get?" + QUERY_STRING);
+ get.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
+ get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
+ CloseableHttpResponse response = httpclient.execute(get, context);
+
+ ObjectNode headers = checkGetResponse(response, get.getURI().toASCIIString().replace("/public", ""));
+ assertFalse(headers.has(HttpHeaders.COOKIE));
+
+ // 2. protected
+ get = new HttpGet(SRA_ADDRESS + "/protected/get?" + QUERY_STRING);
+ String originalRequestURI = get.getURI().toASCIIString();
+ get.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
+ get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
+ response = httpclient.execute(get, context);
+ assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
+
+ // 2a. authenticate
+ String responseBody = EntityUtils.toString(response.getEntity());
+ response = authenticateToCas(responseBody, httpclient, context);
+
+ // 2b. WA attribute consent screen
+ if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+ responseBody = EntityUtils.toString(response.getEntity());
+ String execution = extractCASExecution(responseBody);
+
+ List<NameValuePair> form = new ArrayList<>();
+ form.add(new BasicNameValuePair("_eventId", "confirm"));
+ form.add(new BasicNameValuePair("execution", execution));
+ form.add(new BasicNameValuePair("option", "1"));
+ form.add(new BasicNameValuePair("reminder", "30"));
+ form.add(new BasicNameValuePair("reminderTimeUnit", "days"));
+
+ HttpPost post = new HttpPost(WA_ADDRESS + "/login");
+ post.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
+ post.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
+ post.setEntity(new UrlEncodedFormEntity(form, Consts.UTF_8));
+ response = httpclient.execute(post, context);
+ }
+ assertEquals(HttpStatus.SC_MOVED_TEMPORARILY, response.getStatusLine().getStatusCode());
+
+ // 2c. finally get requested content
+ get = new HttpGet(response.getFirstHeader(HttpHeaders.LOCATION).getValue());
+ get.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
+ get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
+ response = httpclient.execute(get, context);
+
+ headers = checkGetResponse(response, originalRequestURI.replace("/protected", ""));
+ assertFalse(headers.get(HttpHeaders.COOKIE).asText().isBlank());
+
+ // 3. logout
+ get = new HttpGet(SRA_ADDRESS + "/protected/logout");
+ get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
+ response = httpclient.execute(get, context);
+
+ checkLogout(response);
+ }
+}
diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java
index 191cd32..d0b98f1 100644
--- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java
+++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java
@@ -144,7 +144,7 @@ public class OIDCSRAITCase extends AbstractITCase {
context.setCookieStore(new BasicCookieStore());
// 1. public
- HttpGet get = new HttpGet(SRA_ADDRESS + "/public/get?key1=value1&key2=value2&key2=value3");
+ HttpGet get = new HttpGet(SRA_ADDRESS + "/public/get?" + QUERY_STRING);
get.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
CloseableHttpResponse response = httpclient.execute(get, context);
@@ -153,7 +153,7 @@ public class OIDCSRAITCase extends AbstractITCase {
assertFalse(headers.has(HttpHeaders.COOKIE));
// 2. protected
- get = new HttpGet(SRA_ADDRESS + "/protected/get?key1=value1&key2=value2&key2=value3");
+ get = new HttpGet(SRA_ADDRESS + "/protected/get?" + QUERY_STRING);
String originalRequestURI = get.getURI().toASCIIString();
get.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
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 762f2b4..e4e11f2 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
@@ -35,7 +35,6 @@ import java.util.concurrent.TimeoutException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
-import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.http.Consts;
@@ -156,7 +155,7 @@ public class SAML2SRAITCase extends AbstractITCase {
context.setCookieStore(new BasicCookieStore());
// 1. public
- HttpGet get = new HttpGet(SRA_ADDRESS + "/public/get?key1=value1&key2=value2&key2=value3");
+ HttpGet get = new HttpGet(SRA_ADDRESS + "/public/get?" + QUERY_STRING);
get.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
CloseableHttpResponse response = httpclient.execute(get, context);
@@ -165,7 +164,7 @@ public class SAML2SRAITCase extends AbstractITCase {
assertFalse(headers.has(HttpHeaders.COOKIE));
// 2. protected
- get = new HttpGet(SRA_ADDRESS + "/protected/get?key1=value1&key2=value2&key2=value3");
+ get = new HttpGet(SRA_ADDRESS + "/protected/get?" + QUERY_STRING);
String originalRequestURI = get.getURI().toASCIIString();
get.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
diff --git a/fit/wa-reference/src/test/resources/application-cas.properties b/fit/wa-reference/src/test/resources/application-cas.properties
new file mode 100644
index 0000000..acc6237
--- /dev/null
+++ b/fit/wa-reference/src/test/resources/application-cas.properties
@@ -0,0 +1,21 @@
+# 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.
+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
diff --git a/pom.xml b/pom.xml
index 6d819b4..27c2985 100644
--- a/pom.xml
+++ b/pom.xml
@@ -450,6 +450,7 @@ under the License.
<pac4j.version>4.0.3</pac4j.version>
<cas.version>6.3.0-SNAPSHOT</cas.version>
+ <cas-client.version>3.6.1</cas-client.version>
<h2.version>1.4.200</h2.version>
@@ -1374,6 +1375,12 @@ under the License.
<groupId>org.pac4j</groupId>
<artifactId>pac4j-saml</artifactId>
<version>${pac4j.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.velocity</groupId>
+ <artifactId>velocity</artifactId>
+ </exclusion>
+ </exclusions>
</dependency>
<dependency>
<groupId>org.pac4j</groupId>
@@ -1643,6 +1650,12 @@ under the License.
<artifactId>cas-server-webapp-config</artifactId>
<version>${cas.version}</version>
</dependency>
+
+ <dependency>
+ <groupId>org.jasig.cas.client</groupId>
+ <artifactId>cas-client-core</artifactId>
+ <version>${cas-client.version}</version>
+ </dependency>
<!-- /CAS -->
<!-- Wicket -->
diff --git a/sra/pom.xml b/sra/pom.xml
index 51a114e..183b170 100644
--- a/sra/pom.xml
+++ b/sra/pom.xml
@@ -82,7 +82,12 @@ under the License.
<artifactId>pac4j-saml</artifactId>
</dependency>
- <dependency>
+ <dependency>
+ <groupId>org.jasig.cas.client</groupId>
+ <artifactId>cas-client-core</artifactId>
+ </dependency>
+
+ <dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
</dependency>
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 89646f3..9d9fda1 100644
--- a/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java
+++ b/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java
@@ -27,10 +27,12 @@ import org.apache.syncope.sra.security.CsrfRouteMatcher;
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.saml2.SAML2BindingType;
import org.apache.syncope.sra.security.saml2.SAML2MetadataEndpoint;
import org.apache.syncope.sra.security.saml2.SAML2SecurityConfigUtils;
import org.apache.syncope.sra.security.saml2.SAML2WebSsoAuthenticationWebFilter;
+import org.jasig.cas.client.Protocol;
import org.pac4j.core.http.callback.NoParameterCallbackUrlResolver;
import org.pac4j.saml.client.SAML2Client;
import org.pac4j.saml.config.SAML2Configuration;
@@ -266,10 +268,24 @@ public class SecurityConfig {
case SAML2:
SAML2Client saml2Client = saml2Client();
SAML2SecurityConfigUtils.forLogin(http, saml2Client, publicRouteMatcher);
- SAML2SecurityConfigUtils.forLogout(builder, saml2Client, logoutRouteMatcher, ctx);
+ SAML2SecurityConfigUtils.forLogout(builder, saml2Client, cacheManager, logoutRouteMatcher, ctx);
break;
case CAS:
+ CASSecurityConfigUtils.forLogin(
+ http,
+ env.getProperty("am.cas.server.name"),
+ Protocol.CAS3,
+ env.getProperty("am.cas.url.prefix"),
+ publicRouteMatcher);
+ CASSecurityConfigUtils.forLogout(
+ builder,
+ cacheManager,
+ env.getProperty("am.cas.url.prefix"),
+ logoutRouteMatcher,
+ ctx);
+ break;
+
default:
}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/cas/CASAuthenticationRequestWebFilter.java b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASAuthenticationRequestWebFilter.java
new file mode 100644
index 0000000..7d741fd
--- /dev/null
+++ b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASAuthenticationRequestWebFilter.java
@@ -0,0 +1,105 @@
+/*
+ * 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.cas;
+
+import java.net.URI;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.sra.security.PublicRouteMatcher;
+import org.apache.syncope.sra.security.web.server.DoNothingIfCommittedServerRedirectStrategy;
+import org.apache.syncope.sra.session.SessionUtils;
+import org.jasig.cas.client.Protocol;
+import org.jasig.cas.client.util.CommonUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.web.server.ServerRedirectStrategy;
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
+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 CASAuthenticationRequestWebFilter implements WebFilter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(CASAuthenticationRequestWebFilter.class);
+
+ private final ServerWebExchangeMatcher matcher;
+
+ /**
+ * The name of the server. Should be in the following format: {protocol}:{hostName}:{port}.
+ * Standard ports can be excluded.
+ */
+ private final String serverName;
+
+ private final Protocol protocol;
+
+ /**
+ * The URL to the CAS Server login.
+ */
+ private final String casServerLoginUrl;
+
+ private ServerRedirectStrategy authenticationRedirectStrategy = new DoNothingIfCommittedServerRedirectStrategy();
+
+ public CASAuthenticationRequestWebFilter(
+ final PublicRouteMatcher publicRouteMatcher,
+ final String serverName,
+ final Protocol protocol,
+ final String casServerUrlPrefix) {
+
+ this.matcher = ServerWebExchangeMatchers.matchers(
+ publicRouteMatcher,
+ CASUtils.ticketAvailable(protocol),
+ SessionUtils.authInSession());
+ this.serverName = serverName;
+ this.protocol = protocol;
+ this.casServerLoginUrl = StringUtils.appendIfMissing(casServerUrlPrefix, "/") + "login";
+ }
+
+ public void setAuthenticationRedirectStrategy(final ServerRedirectStrategy authenticationRedirectStrategy) {
+ this.authenticationRedirectStrategy = authenticationRedirectStrategy;
+ }
+
+ @Override
+ public Mono<Void> filter(final ServerWebExchange exchange, final WebFilterChain chain) {
+ return matcher.matches(exchange).
+ filter(matchResult -> !matchResult.isMatch()).
+ switchIfEmpty(chain.filter(exchange).then(Mono.empty())).
+ flatMap(r -> exchange.getSession()).
+ flatMap(session -> {
+ session.getAttributes().
+ put(SessionUtils.INITIAL_REQUEST_URI, exchange.getRequest().getURI());
+
+ LOG.debug("no ticket and no assertion found");
+
+ String serviceUrl = CASUtils.constructServiceUrl(exchange, this.serverName, this.protocol);
+ LOG.debug("Constructed service url: {}", serviceUrl);
+
+ String urlToRedirectTo = CommonUtils.constructRedirectUrl(
+ this.casServerLoginUrl,
+ this.protocol.getServiceParameterName(),
+ serviceUrl,
+ false,
+ false,
+ null);
+ LOG.debug("redirecting to \"{}\"", urlToRedirectTo);
+
+ return authenticationRedirectStrategy.sendRedirect(exchange, URI.create(urlToRedirectTo));
+ });
+ }
+}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/cas/CASAuthenticationToken.java b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASAuthenticationToken.java
new file mode 100644
index 0000000..bf5d23c
--- /dev/null
+++ b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASAuthenticationToken.java
@@ -0,0 +1,46 @@
+/*
+ * 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.cas;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jasig.cas.client.validation.Assertion;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+
+public class CASAuthenticationToken extends AbstractAuthenticationToken {
+
+ private static final long serialVersionUID = 6333776590644298469L;
+
+ private final Assertion assertion;
+
+ public CASAuthenticationToken(final Assertion assertion) {
+ super(null);
+ this.assertion = assertion;
+ this.setAuthenticated(true);
+ }
+
+ @Override
+ public Object getCredentials() {
+ return StringUtils.EMPTY;
+ }
+
+ @Override
+ public Assertion getPrincipal() {
+ return assertion;
+ }
+}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/cas/CASAuthenticationWebFilter.java b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASAuthenticationWebFilter.java
new file mode 100644
index 0000000..224c1b2
--- /dev/null
+++ b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASAuthenticationWebFilter.java
@@ -0,0 +1,105 @@
+/*
+ * 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.cas;
+
+import java.net.URI;
+import org.apache.syncope.sra.security.web.server.DoNothingIfCommittedServerRedirectStrategy;
+import org.apache.syncope.sra.session.SessionUtils;
+import org.jasig.cas.client.Protocol;
+import org.jasig.cas.client.validation.Assertion;
+import org.jasig.cas.client.validation.TicketValidationException;
+import org.jasig.cas.client.validation.TicketValidator;
+import org.jasig.cas.client.validation.json.Cas30JsonServiceTicketValidator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.ReactiveAuthenticationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.server.ServerRedirectStrategy;
+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.NegatedServerWebExchangeMatcher;
+import reactor.core.publisher.Mono;
+
+public class CASAuthenticationWebFilter extends AuthenticationWebFilter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(CASAuthenticationWebFilter.class);
+
+ private final String serverName;
+
+ private final Protocol protocol;
+
+ private final TicketValidator ticketValidator;
+
+ public CASAuthenticationWebFilter(
+ final ReactiveAuthenticationManager authenticationManager,
+ final String serverName,
+ final Protocol protocol,
+ final String casServerUrlPrefix) {
+
+ super(authenticationManager);
+
+ this.serverName = serverName;
+ this.protocol = protocol;
+ this.ticketValidator = new Cas30JsonServiceTicketValidator(casServerUrlPrefix);
+
+ setRequiresAuthenticationMatcher(new AndServerWebExchangeMatcher(
+ CASUtils.ticketAvailable(protocol),
+ new NegatedServerWebExchangeMatcher(SessionUtils.authInSession())));
+
+ setServerAuthenticationConverter(validateAssertion());
+
+ setAuthenticationSuccessHandler(redirectToInitialRequestURI());
+ }
+
+ private ServerAuthenticationConverter validateAssertion() {
+ return exchange -> CASUtils.retrieveTicketFromRequest(exchange, this.protocol).
+ flatMap(ticket -> {
+ try {
+ Assertion assertion = this.ticketValidator.validate(
+ ticket,
+ CASUtils.constructServiceUrl(exchange, this.serverName, this.protocol));
+ return Mono.just(new CASAuthenticationToken(assertion));
+ } catch (TicketValidationException e) {
+ LOG.error("Could not validate {}", ticket, e);
+ throw new BadCredentialsException("Could not validate " + ticket);
+ }
+ });
+ }
+
+ private ServerAuthenticationSuccessHandler redirectToInitialRequestURI() {
+ return new ServerAuthenticationSuccessHandler() {
+
+ private final ServerRedirectStrategy redirectStrategy = new DoNothingIfCommittedServerRedirectStrategy();
+
+ @Override
+ public Mono<Void> onAuthenticationSuccess(
+ final WebFilterExchange webFilterExchange, final Authentication authentication) {
+
+ return webFilterExchange.getExchange().getSession().
+ flatMap(session -> this.redirectStrategy.sendRedirect(
+ webFilterExchange.getExchange(),
+ session.<URI>getRequiredAttribute(SessionUtils.INITIAL_REQUEST_URI)));
+ }
+ };
+ }
+}
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/cas/CASSecurityConfigUtils.java
similarity index 66%
copy from sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2SecurityConfigUtils.java
copy to sra/src/main/java/org/apache/syncope/sra/security/cas/CASSecurityConfigUtils.java
index 4b71109..1c159ba 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2SecurityConfigUtils.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASSecurityConfigUtils.java
@@ -16,14 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.syncope.sra.security.saml2;
+package org.apache.syncope.sra.security.cas;
import org.apache.syncope.sra.ApplicationContextUtils;
import org.apache.syncope.sra.security.LogoutRouteMatcher;
import org.apache.syncope.sra.security.PublicRouteMatcher;
-import org.pac4j.saml.client.SAML2Client;
+import org.jasig.cas.client.Protocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.cache.CacheManager;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
@@ -32,65 +33,66 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.authentication.logout.LogoutWebFilter;
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
-import org.springframework.web.server.WebFilter;
import reactor.core.publisher.Mono;
-public final class SAML2SecurityConfigUtils {
+public final class CASSecurityConfigUtils {
- private static final Logger LOG = LoggerFactory.getLogger(SAML2SecurityConfigUtils.class);
+ private static final Logger LOG = LoggerFactory.getLogger(CASSecurityConfigUtils.class);
private static ReactiveAuthenticationManager authenticationManager() {
- return authentication -> Mono.just(authentication).
- filter(Authentication::isAuthenticated);
+ return authentication -> Mono.just(authentication).filter(Authentication::isAuthenticated);
}
public static void forLogin(
final ServerHttpSecurity http,
- final SAML2Client saml2Client,
+ final String serverName,
+ final Protocol protocol,
+ final String casServerUrlPrefix,
final PublicRouteMatcher publicRouteMatcher) {
ReactiveAuthenticationManager authenticationManager = authenticationManager();
- SAML2WebSsoAuthenticationRequestWebFilter authRequestFilter =
- new SAML2WebSsoAuthenticationRequestWebFilter(saml2Client);
+ CASAuthenticationRequestWebFilter authRequestFilter = new CASAuthenticationRequestWebFilter(
+ publicRouteMatcher,
+ serverName,
+ protocol,
+ casServerUrlPrefix);
http.addFilterAt(authRequestFilter, SecurityWebFiltersOrder.HTTP_BASIC);
- AuthenticationWebFilter authenticationFilter =
- new SAML2WebSsoAuthenticationWebFilter(authenticationManager, saml2Client);
+ AuthenticationWebFilter authenticationFilter = new CASAuthenticationWebFilter(
+ authenticationManager,
+ serverName, protocol,
+ casServerUrlPrefix);
authenticationFilter.setAuthenticationFailureHandler((exchange, ex) -> Mono.error(ex));
authenticationFilter.setSecurityContextRepository(new WebSessionServerSecurityContextRepository());
http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION);
-
- WebFilter anonymousRedirectFilter = new SAML2AnonymousWebFilter(publicRouteMatcher);
- http.addFilterAt(anonymousRedirectFilter, SecurityWebFiltersOrder.AUTHENTICATION);
}
public static void forLogout(
final ServerHttpSecurity.AuthorizeExchangeSpec builder,
- final SAML2Client saml2Client,
+ final CacheManager cacheManager,
+ final String casServerUrlPrefix,
final LogoutRouteMatcher logoutRouteMatcher,
final ConfigurableApplicationContext ctx) {
LogoutWebFilter logoutWebFilter = new LogoutWebFilter();
logoutWebFilter.setRequiresLogoutMatcher(logoutRouteMatcher);
- SAML2ServerLogoutHandler logoutHandler = new SAML2ServerLogoutHandler(saml2Client);
- logoutWebFilter.setLogoutHandler(logoutHandler);
+ logoutWebFilter.setLogoutHandler(new CASServerLogoutHandler(cacheManager, casServerUrlPrefix));
try {
- SAML2ServerLogoutSuccessHandler handler = ApplicationContextUtils.getOrCreateBean(ctx,
- SAML2ServerLogoutSuccessHandler.class.getName(),
- SAML2ServerLogoutSuccessHandler.class);
+ CASServerLogoutSuccessHandler handler = ApplicationContextUtils.getOrCreateBean(ctx,
+ CASServerLogoutSuccessHandler.class.getName(),
+ CASServerLogoutSuccessHandler.class);
logoutWebFilter.setLogoutSuccessHandler(handler);
} catch (ClassNotFoundException e) {
- LOG.error("While creating instance of {}",
- SAML2ServerLogoutSuccessHandler.class.getName(), e);
+ LOG.error("While creating instance of {}", CASServerLogoutSuccessHandler.class.getName(), e);
}
builder.and().addFilterAt(logoutWebFilter, SecurityWebFiltersOrder.LOGOUT);
}
- private SAML2SecurityConfigUtils() {
+ private CASSecurityConfigUtils() {
// private constructor for static utility class
}
}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/SessionRemovalServerLogoutHandler.java b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASServerLogoutHandler.java
similarity index 53%
copy from sra/src/main/java/org/apache/syncope/sra/security/SessionRemovalServerLogoutHandler.java
copy to sra/src/main/java/org/apache/syncope/sra/security/cas/CASServerLogoutHandler.java
index 0c25586..3c62df0 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/SessionRemovalServerLogoutHandler.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASServerLogoutHandler.java
@@ -16,33 +16,43 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.syncope.sra.security;
+package org.apache.syncope.sra.security.cas;
+import java.net.URI;
+import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.sra.SessionConfig;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.apache.syncope.sra.security.web.server.DoNothingIfCommittedServerRedirectStrategy;
import org.springframework.cache.CacheManager;
import org.springframework.security.core.Authentication;
+import org.springframework.security.web.server.ServerRedirectStrategy;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
import reactor.core.publisher.Mono;
-public class SessionRemovalServerLogoutHandler implements ServerLogoutHandler {
+public class CASServerLogoutHandler implements ServerLogoutHandler {
- private static final Logger EVENTS = LoggerFactory.getLogger("events");
+ private final ServerRedirectStrategy redirectStrategy = new DoNothingIfCommittedServerRedirectStrategy();
private final CacheManager cacheManager;
- public SessionRemovalServerLogoutHandler(final CacheManager cacheManager) {
+ /**
+ * The URL to the CAS Server logout.
+ */
+ private final String casServerLogoutUrl;
+
+ public CASServerLogoutHandler(final CacheManager cacheManager, final String casServerUrlPrefix) {
this.cacheManager = cacheManager;
+ this.casServerLogoutUrl = StringUtils.appendIfMissing(casServerUrlPrefix, "/") + "logout";
}
@Override
public Mono<Void> logout(final WebFilterExchange exchange, final Authentication authentication) {
- return exchange.getExchange().getSession().doOnNext(session -> {
- session.invalidate();
- EVENTS.debug("Invalidate session {}", (authentication == null) ? null : authentication.getPrincipal());
- cacheManager.getCache(SessionConfig.DEFAULT_CACHE).evictIfPresent(session.getId());
- }).flatMap(session -> Mono.empty());
+ return exchange.getExchange().getSession().
+ flatMap(session -> {
+ cacheManager.getCache(SessionConfig.DEFAULT_CACHE).evictIfPresent(session.getId());
+
+ return session.invalidate().then(
+ redirectStrategy.sendRedirect(exchange.getExchange(), URI.create(this.casServerLogoutUrl)));
+ }).onErrorResume(Mono::error);
}
}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/cas/CASServerLogoutSuccessHandler.java b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASServerLogoutSuccessHandler.java
new file mode 100644
index 0000000..9d5c4b6
--- /dev/null
+++ b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASServerLogoutSuccessHandler.java
@@ -0,0 +1,33 @@
+/*
+ * 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.cas;
+
+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;
+
+public class CASServerLogoutSuccessHandler extends AbstractServerLogoutSuccessHandler {
+
+ @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/cas/CASUtils.java b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASUtils.java
new file mode 100644
index 0000000..1f1931f
--- /dev/null
+++ b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASUtils.java
@@ -0,0 +1,111 @@
+/*
+ * 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.cas;
+
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import org.jasig.cas.client.Protocol;
+import org.jasig.cas.client.util.URIBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.util.UriComponents;
+import org.springframework.web.util.UriComponentsBuilder;
+import reactor.core.publisher.Mono;
+
+public final class CASUtils {
+
+ private static final Logger LOG = LoggerFactory.getLogger(CASUtils.class);
+
+ public static Mono<String> safeGetParameter(final ServerWebExchange exchange, final String parameter) {
+ if (exchange.getRequest().getMethod() == HttpMethod.POST) {
+ LOG.debug(
+ "safeGetParameter called on a POST ServerHttpRequest for Restricted Parameters. "
+ + "Cannot complete check safely. "
+ + "Reverting to standard behavior for this Parameter");
+ return exchange.getFormData().
+ flatMap(form -> Mono.justOrEmpty(form.getFirst(parameter)));
+ }
+ return Mono.justOrEmpty(exchange.getRequest().getQueryParams().getFirst(parameter));
+ }
+
+ public static Mono<String> retrieveTicketFromRequest(final ServerWebExchange exchange, final Protocol protocol) {
+ return safeGetParameter(exchange, protocol.getArtifactParameterName());
+ }
+
+ public static ServerWebExchangeMatcher ticketAvailable(final Protocol protocol) {
+ return exchange -> CASUtils.retrieveTicketFromRequest(exchange, protocol).
+ flatMap(ticket -> ServerWebExchangeMatcher.MatchResult.match()).
+ switchIfEmpty(ServerWebExchangeMatcher.MatchResult.notMatch());
+ }
+
+ public static String constructServiceUrl(
+ final ServerWebExchange exchange,
+ final String serverName,
+ final Protocol protocol) {
+
+ UriComponents requestURI = UriComponentsBuilder.fromHttpRequest(exchange.getRequest()).build();
+
+ URIBuilder originalRequestUrl = new URIBuilder(
+ StringUtils.substringBefore(requestURI.toUriString(), "?"), true);
+ originalRequestUrl.setParameters(requestURI.getQuery());
+
+ URIBuilder builder;
+ if (!serverName.startsWith("https://") && !serverName.startsWith("http://")) {
+ String scheme = exchange.getRequest().getSslInfo() == null ? "http://" : "https://";
+ builder = new URIBuilder(scheme + serverName, true);
+ } else {
+ builder = new URIBuilder(serverName, true);
+ }
+
+ builder.setPort(requestURI.getPort());
+
+ builder.setEncodedPath(builder.getEncodedPath() + requestURI.getPath());
+
+ List<String> serviceParameterNames = List.of(protocol.getServiceParameterName().split(","));
+ if (!serviceParameterNames.isEmpty() && !originalRequestUrl.getQueryParams().isEmpty()) {
+ originalRequestUrl.getQueryParams().stream().
+ filter(pair -> !pair.getName().equals(protocol.getArtifactParameterName())
+ && !serviceParameterNames.contains(pair.getName())).
+ forEach(pair -> {
+ String name = pair.getName();
+ if (name.contains("&") || name.contains("=")) {
+ URIBuilder encodedParamBuilder = new URIBuilder();
+ encodedParamBuilder.setParameters(name);
+ encodedParamBuilder.getQueryParams().stream().
+ filter(pair2 -> !pair2.getName().equals(protocol.getArtifactParameterName())
+ && !serviceParameterNames.contains(pair2.getName())).
+ forEach(pair2 -> builder.addParameter(pair2.getName(), pair2.getValue()));
+ } else {
+ builder.addParameter(name, pair.getValue());
+ }
+ });
+ }
+
+ String result = builder.toString();
+ LOG.debug("serviceUrl generated: {}", result);
+ return result;
+ }
+
+ private CASUtils() {
+ // private constructor for static utility class
+ }
+}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/oauth2/OAuth2SecurityConfigUtils.java b/sra/src/main/java/org/apache/syncope/sra/security/oauth2/OAuth2SecurityConfigUtils.java
index cf6bbb4..6a92551 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/oauth2/OAuth2SecurityConfigUtils.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/oauth2/OAuth2SecurityConfigUtils.java
@@ -22,7 +22,6 @@ import java.util.Set;
import org.apache.syncope.sra.ApplicationContextUtils;
import org.apache.syncope.sra.SecurityConfig.AMType;
import org.apache.syncope.sra.security.LogoutRouteMatcher;
-import org.apache.syncope.sra.security.SessionRemovalServerLogoutHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.CacheManager;
@@ -122,7 +121,7 @@ public final class OAuth2SecurityConfigUtils {
LogoutWebFilter logoutWebFilter = new LogoutWebFilter();
logoutWebFilter.setRequiresLogoutMatcher(logoutRouteMatcher);
- logoutWebFilter.setLogoutHandler(new SessionRemovalServerLogoutHandler(cacheManager));
+ logoutWebFilter.setLogoutHandler(new OAuth2SessionRemovalServerLogoutHandler(cacheManager));
if (AMType.OIDC == amType) {
try {
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/SessionRemovalServerLogoutHandler.java b/sra/src/main/java/org/apache/syncope/sra/security/oauth2/OAuth2SessionRemovalServerLogoutHandler.java
similarity index 70%
rename from sra/src/main/java/org/apache/syncope/sra/security/SessionRemovalServerLogoutHandler.java
rename to sra/src/main/java/org/apache/syncope/sra/security/oauth2/OAuth2SessionRemovalServerLogoutHandler.java
index 0c25586..9cf3d0e 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/SessionRemovalServerLogoutHandler.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/oauth2/OAuth2SessionRemovalServerLogoutHandler.java
@@ -16,33 +16,28 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.syncope.sra.security;
+package org.apache.syncope.sra.security.oauth2;
import org.apache.syncope.sra.SessionConfig;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.cache.CacheManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
import reactor.core.publisher.Mono;
-public class SessionRemovalServerLogoutHandler implements ServerLogoutHandler {
-
- private static final Logger EVENTS = LoggerFactory.getLogger("events");
+public class OAuth2SessionRemovalServerLogoutHandler implements ServerLogoutHandler {
private final CacheManager cacheManager;
- public SessionRemovalServerLogoutHandler(final CacheManager cacheManager) {
+ public OAuth2SessionRemovalServerLogoutHandler(final CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
@Override
public Mono<Void> logout(final WebFilterExchange exchange, final Authentication authentication) {
- return exchange.getExchange().getSession().doOnNext(session -> {
- session.invalidate();
- EVENTS.debug("Invalidate session {}", (authentication == null) ? null : authentication.getPrincipal());
+ return exchange.getExchange().getSession().flatMap(session -> {
cacheManager.getCache(SessionConfig.DEFAULT_CACHE).evictIfPresent(session.getId());
- }).flatMap(session -> Mono.empty());
+ return session.invalidate();
+ });
}
}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerHttpContext.java b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerWebExchangeContext.java
similarity index 91%
rename from sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerHttpContext.java
rename to sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerWebExchangeContext.java
index 1e0b3ab..bf22e69 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerHttpContext.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerWebExchangeContext.java
@@ -36,11 +36,11 @@ import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
import org.springframework.web.util.UriComponentsBuilder;
-public class ServerHttpContext implements WebContext {
+public class ServerWebExchangeContext implements WebContext {
private final ServerWebExchange exchange;
- private ServerHttpSessionStore sessionStore;
+ private WebSessionStore sessionStore;
private MultiValueMap<String, String> form;
@@ -52,8 +52,8 @@ public class ServerHttpContext implements WebContext {
* @param exchange the current exchange
* @param webSession the current web session
*/
- public ServerHttpContext(final ServerWebExchange exchange, final WebSession webSession) {
- this(exchange, new ServerHttpSessionStore(webSession));
+ public ServerWebExchangeContext(final ServerWebExchange exchange, final WebSession webSession) {
+ this(exchange, new WebSessionStore(webSession));
}
/**
@@ -62,9 +62,9 @@ public class ServerHttpContext implements WebContext {
* @param exchange the current exchange
* @param sessionStore the session store to use
*/
- public ServerHttpContext(
+ public ServerWebExchangeContext(
final ServerWebExchange exchange,
- final ServerHttpSessionStore sessionStore) {
+ final WebSessionStore sessionStore) {
CommonHelper.assertNotNull("exchange", exchange);
CommonHelper.assertNotNull("sessionStore", sessionStore);
@@ -72,12 +72,12 @@ public class ServerHttpContext implements WebContext {
this.sessionStore = sessionStore;
}
- public ServerHttpSessionStore getNativeSessionStore() {
+ public WebSessionStore getNativeSessionStore() {
return this.sessionStore;
}
@Override
- public SessionStore<ServerHttpContext> getSessionStore() {
+ public SessionStore<ServerWebExchangeContext> getSessionStore() {
return this.sessionStore;
}
@@ -103,7 +103,7 @@ public class ServerHttpContext implements WebContext {
return Optional.empty();
}
- public ServerHttpContext setForm(final MultiValueMap<String, String> form) {
+ public ServerWebExchangeContext setForm(final MultiValueMap<String, String> form) {
this.form = form;
return this;
}
@@ -214,7 +214,7 @@ public class ServerHttpContext implements WebContext {
return exchange.getRequest().getPath().value();
}
- public ServerHttpContext setBody(final String body) {
+ public ServerWebExchangeContext setBody(final String body) {
this.body = body;
return this;
}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerHttpSessionStore.java b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/WebSessionStore.java
similarity index 67%
rename from sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerHttpSessionStore.java
rename to sra/src/main/java/org/apache/syncope/sra/security/pac4j/WebSessionStore.java
index 622a92c..c74336c 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerHttpSessionStore.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/WebSessionStore.java
@@ -22,47 +22,47 @@ import java.util.Optional;
import org.pac4j.core.context.session.SessionStore;
import org.springframework.web.server.WebSession;
-public class ServerHttpSessionStore implements SessionStore<ServerHttpContext> {
+public class WebSessionStore implements SessionStore<ServerWebExchangeContext> {
private final WebSession webSession;
- public ServerHttpSessionStore(final WebSession webSession) {
+ public WebSessionStore(final WebSession webSession) {
this.webSession = webSession;
}
@Override
- public String getOrCreateSessionId(final ServerHttpContext context) {
+ public String getOrCreateSessionId(final ServerWebExchangeContext context) {
return this.webSession.getId();
}
@Override
- public Optional<Object> get(final ServerHttpContext context, final String key) {
+ public Optional<Object> get(final ServerWebExchangeContext context, final String key) {
return Optional.ofNullable(this.webSession.getAttribute(key));
}
@Override
- public void set(final ServerHttpContext context, final String key, final Object value) {
+ public void set(final ServerWebExchangeContext context, final String key, final Object value) {
}
@Override
- public boolean destroySession(final ServerHttpContext context) {
+ public boolean destroySession(final ServerWebExchangeContext context) {
return false;
}
@Override
- public Optional<WebSession> getTrackableSession(final ServerHttpContext context) {
+ public Optional<WebSession> getTrackableSession(final ServerWebExchangeContext context) {
return Optional.ofNullable(this.webSession);
}
@Override
- public Optional<SessionStore<ServerHttpContext>> buildFromTrackableSession(
- final ServerHttpContext context, final Object trackableSession) {
+ public Optional<SessionStore<ServerWebExchangeContext>> buildFromTrackableSession(
+ final ServerWebExchangeContext context, final Object trackableSession) {
return Optional.empty();
}
@Override
- public boolean renewSession(final ServerHttpContext context) {
+ public boolean renewSession(final ServerWebExchangeContext context) {
return false;
}
}
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 29d0fa5..6cf7be5 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
@@ -20,8 +20,10 @@ package org.apache.syncope.sra.security.saml2;
import java.net.URI;
import org.apache.syncope.sra.security.PublicRouteMatcher;
+import org.apache.syncope.sra.session.SessionUtils;
import org.springframework.http.HttpStatus;
-import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
+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;
@@ -29,24 +31,22 @@ import reactor.core.publisher.Mono;
public class SAML2AnonymousWebFilter implements WebFilter {
- public static final String INITIAL_REQUEST_URI = "INITIAL_REQUEST_URI";
-
- private final PublicRouteMatcher publicRouteMatcher;
+ private final ServerWebExchangeMatcher matcher;
public SAML2AnonymousWebFilter(final PublicRouteMatcher publicRouteMatcher) {
- this.publicRouteMatcher = publicRouteMatcher;
+ this.matcher = ServerWebExchangeMatchers.matchers(
+ publicRouteMatcher,
+ SessionUtils.authInSession());
}
@Override
public Mono<Void> filter(final ServerWebExchange exchange, final WebFilterChain chain) {
- return publicRouteMatcher.matches(exchange).
+ return matcher.matches(exchange).
filter(matchResult -> !matchResult.isMatch()).
- flatMap(r -> exchange.getSession()).flatMap(r -> exchange.getSession()).
- filter(s -> !s.getAttributes().containsKey(
- WebSessionServerSecurityContextRepository.DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME)).
switchIfEmpty(chain.filter(exchange).then(Mono.empty())).
+ flatMap(r -> exchange.getSession()).
flatMap(session -> {
- session.getAttributes().put(INITIAL_REQUEST_URI, exchange.getRequest().getURI());
+ session.getAttributes().put(SessionUtils.INITIAL_REQUEST_URI, exchange.getRequest().getURI());
exchange.getResponse().setStatusCode(HttpStatus.SEE_OTHER);
exchange.getResponse().getHeaders().
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/saml2/SAML2RequestGenerator.java
index fd2a924..b595d57 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2RequestGenerator.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2RequestGenerator.java
@@ -18,7 +18,7 @@
*/
package org.apache.syncope.sra.security.saml2;
-import org.apache.syncope.sra.security.pac4j.ServerHttpContext;
+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;
@@ -38,13 +38,13 @@ abstract class SAML2RequestGenerator {
protected Mono<Void> handle(
final RedirectionAction action,
- final ServerHttpContext shc) {
+ final ServerWebExchangeContext swec) {
if (action instanceof WithLocationAction) {
WithLocationAction withLocationAction = (WithLocationAction) action;
- shc.getNative().getResponse().setStatusCode(HttpStatus.FOUND);
- shc.getNative().getResponse().getHeaders().setLocation(URI.create(withLocationAction.getLocation()));
- return shc.getNative().getResponse().setComplete();
+ swec.getNative().getResponse().setStatusCode(HttpStatus.FOUND);
+ swec.getNative().getResponse().getHeaders().setLocation(URI.create(withLocationAction.getLocation()));
+ return swec.getNative().getResponse().setComplete();
} else if (action instanceof WithContentAction) {
WithContentAction withContentAction = (WithContentAction) action;
String content = withContentAction.getContent();
@@ -54,9 +54,9 @@ abstract class SAML2RequestGenerator {
}
return Mono.defer(() -> {
- shc.getNative().getResponse().getHeaders().setContentType(MediaType.TEXT_HTML);
- return shc.getNative().getResponse().
- writeWith(Mono.just(shc.getNative().getResponse().bufferFactory().wrap(content.getBytes())));
+ swec.getNative().getResponse().getHeaders().setContentType(MediaType.TEXT_HTML);
+ return swec.getNative().getResponse().
+ writeWith(Mono.just(swec.getNative().getResponse().bufferFactory().wrap(content.getBytes())));
});
} else {
throw new IllegalArgumentException("Unsupported Action: " + action.getClass().getName());
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 4b71109..63001da 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
@@ -24,6 +24,7 @@ import org.apache.syncope.sra.security.PublicRouteMatcher;
import org.pac4j.saml.client.SAML2Client;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.cache.CacheManager;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
@@ -40,8 +41,7 @@ public final class SAML2SecurityConfigUtils {
private static final Logger LOG = LoggerFactory.getLogger(SAML2SecurityConfigUtils.class);
private static ReactiveAuthenticationManager authenticationManager() {
- return authentication -> Mono.just(authentication).
- filter(Authentication::isAuthenticated);
+ return authentication -> Mono.just(authentication).filter(Authentication::isAuthenticated);
}
public static void forLogin(
@@ -68,14 +68,14 @@ public final class SAML2SecurityConfigUtils {
public static void forLogout(
final ServerHttpSecurity.AuthorizeExchangeSpec builder,
final SAML2Client saml2Client,
+ final CacheManager cacheManager,
final LogoutRouteMatcher logoutRouteMatcher,
final ConfigurableApplicationContext ctx) {
LogoutWebFilter logoutWebFilter = new LogoutWebFilter();
logoutWebFilter.setRequiresLogoutMatcher(logoutRouteMatcher);
- SAML2ServerLogoutHandler logoutHandler = new SAML2ServerLogoutHandler(saml2Client);
- logoutWebFilter.setLogoutHandler(logoutHandler);
+ logoutWebFilter.setLogoutHandler(new SAML2ServerLogoutHandler(saml2Client, cacheManager));
try {
SAML2ServerLogoutSuccessHandler handler = ApplicationContextUtils.getOrCreateBean(ctx,
@@ -83,8 +83,7 @@ public final class SAML2SecurityConfigUtils {
SAML2ServerLogoutSuccessHandler.class);
logoutWebFilter.setLogoutSuccessHandler(handler);
} catch (ClassNotFoundException e) {
- LOG.error("While creating instance of {}",
- SAML2ServerLogoutSuccessHandler.class.getName(), e);
+ LOG.error("While creating instance of {}", SAML2ServerLogoutSuccessHandler.class.getName(), e);
}
builder.and().addFilterAt(logoutWebFilter, SecurityWebFiltersOrder.LOGOUT);
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/SAML2ServerLogoutHandler.java
index 0bcba31..0959065 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/SAML2ServerLogoutHandler.java
@@ -18,11 +18,13 @@
*/
package org.apache.syncope.sra.security.saml2;
-import org.apache.syncope.sra.security.pac4j.ServerHttpContext;
+import org.apache.syncope.sra.SessionConfig;
+import org.apache.syncope.sra.security.pac4j.ServerWebExchangeContext;
import org.pac4j.saml.client.SAML2Client;
import org.pac4j.saml.credentials.SAML2Credentials;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.cache.CacheManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
@@ -32,8 +34,11 @@ public class SAML2ServerLogoutHandler extends SAML2RequestGenerator implements S
private static final Logger LOG = LoggerFactory.getLogger(SAML2ServerLogoutHandler.class);
- public SAML2ServerLogoutHandler(final SAML2Client saml2Client) {
+ private final CacheManager cacheManager;
+
+ public SAML2ServerLogoutHandler(final SAML2Client saml2Client, final CacheManager cacheManager) {
super(saml2Client);
+ this.cacheManager = cacheManager;
}
@Override
@@ -45,11 +50,12 @@ public class SAML2ServerLogoutHandler extends SAML2RequestGenerator implements S
LOG.debug("Creating SAML2 SP Logout Request for IDP[{}] and Profile[{}]",
saml2Client.getIdentityProviderResolvedEntityId(), credentials.getUserProfile());
- ServerHttpContext shc = new ServerHttpContext(exchange.getExchange(), session);
+ ServerWebExchangeContext swec = new ServerWebExchangeContext(exchange.getExchange(), session);
+ cacheManager.getCache(SessionConfig.DEFAULT_CACHE).evictIfPresent(session.getId());
return session.invalidate().then(
- saml2Client.getLogoutAction(shc, credentials.getUserProfile(), null).
- map(action -> handle(action, shc)).
+ saml2Client.getLogoutAction(swec, credentials.getUserProfile(), null).
+ map(action -> 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/SAML2WebSsoAuthenticationRequestWebFilter.java b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationRequestWebFilter.java
index e57faad..ab1819c 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,7 +18,7 @@
*/
package org.apache.syncope.sra.security.saml2;
-import org.apache.syncope.sra.security.pac4j.ServerHttpContext;
+import org.apache.syncope.sra.security.pac4j.ServerWebExchangeContext;
import org.pac4j.saml.client.SAML2Client;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -57,10 +57,10 @@ public class SAML2WebSsoAuthenticationRequestWebFilter extends SAML2RequestGener
LOG.debug("Creating SAML2 SP Authentication Request for IDP[{}]",
saml2Client.getIdentityProviderResolvedEntityId());
- ServerHttpContext shc = new ServerHttpContext(exchange, session);
+ ServerWebExchangeContext swec = new ServerWebExchangeContext(exchange, session);
- return saml2Client.getRedirectionAction(shc).
- map(action -> handle(action, shc)).
+ return saml2Client.getRedirectionAction(swec).
+ map(action -> 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 79ef94b..0cbd9f7 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
@@ -18,9 +18,10 @@
*/
package org.apache.syncope.sra.security.saml2;
-import org.apache.syncope.sra.security.pac4j.ServerHttpContext;
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;
@@ -87,12 +88,12 @@ public class SAML2WebSsoAuthenticationWebFilter extends AuthenticationWebFilter
flatMap(form -> this.matcher.matches(exchange).
flatMap(matchResult -> exchange.getSession()).
flatMap(session -> {
- ServerHttpContext shc = new ServerHttpContext(exchange, session).setForm(form);
+ ServerWebExchangeContext swec = new ServerWebExchangeContext(exchange, session).setForm(form);
- SAML2Credentials credentials = saml2Client.getCredentialsExtractor().extract(shc).
+ SAML2Credentials credentials = saml2Client.getCredentialsExtractor().extract(swec).
orElseThrow(() -> new IllegalStateException("No AuthnResponse found"));
- saml2Client.getAuthenticator().validate(credentials, shc);
+ saml2Client.getAuthenticator().validate(credentials, swec);
return Mono.just(new SAML2AuthenticationToken(credentials));
}));
@@ -110,7 +111,7 @@ public class SAML2WebSsoAuthenticationWebFilter extends AuthenticationWebFilter
return webFilterExchange.getExchange().getSession().
flatMap(session -> this.redirectStrategy.sendRedirect(
webFilterExchange.getExchange(),
- (URI) session.getRequiredAttribute(SAML2AnonymousWebFilter.INITIAL_REQUEST_URI)));
+ session.<URI>getRequiredAttribute(SessionUtils.INITIAL_REQUEST_URI)));
}
};
}
diff --git a/sra/src/main/java/org/apache/syncope/sra/session/SessionUtils.java b/sra/src/main/java/org/apache/syncope/sra/session/SessionUtils.java
new file mode 100644
index 0000000..0f0b490
--- /dev/null
+++ b/sra/src/main/java/org/apache/syncope/sra/session/SessionUtils.java
@@ -0,0 +1,39 @@
+/*
+ * 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.session;
+
+import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
+
+public final class SessionUtils {
+
+ public static final String INITIAL_REQUEST_URI = "INITIAL_REQUEST_URI";
+
+ public static ServerWebExchangeMatcher authInSession() {
+ return exchange -> exchange.getSession().
+ filter(session -> session.getAttributes().containsKey(
+ WebSessionServerSecurityContextRepository.DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME)).
+ flatMap(session -> ServerWebExchangeMatcher.MatchResult.match()).
+ switchIfEmpty(ServerWebExchangeMatcher.MatchResult.notMatch());
+ }
+
+ private SessionUtils() {
+ // private constructor for static utility class
+ }
+}
diff --git a/sra/src/test/resources/debug/application-debug.properties b/sra/src/test/resources/debug/application-debug.properties
index 5af528f..b74d3a4 100644
--- a/sra/src/test/resources/debug/application-debug.properties
+++ b/sra/src/test/resources/debug/application-debug.properties
@@ -30,16 +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=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=CAS
+am.cas.server.name=http://localhost:80
+am.cas.url.prefix=http://localhost:9080/syncope-wa/
global.postLogout=http://localhost:8080/logout