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 b7d28f1cfd [SYNCOPE-1898] Reset password with WA if mustChangePassword
is true (#1142)
b7d28f1cfd is described below
commit b7d28f1cfdfd980600e2612e5152d9accd7462ac
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Thu Jul 31 17:51:23 2025 +0200
[SYNCOPE-1898] Reset password with WA if mustChangePassword is true (#1142)
---
.../src/main/resources/wa-embedded.properties | 5 +
.../apache/syncope/fit/ui/AbstractUIITCase.java | 54 ++++++
.../org/apache/syncope/fit/ui/OIDCC4UIITCase.java | 139 +++++++++++++++
.../org/apache/syncope/fit/ui/OpenFGAUIITCase.java | 10 ++
.../apache/syncope/fit/ui/SAML2SP4UIITCase.java | 194 +++++++++++++++++++++
.../wa/bootstrap/WAPropertySourceLocator.java | 3 +
6 files changed, 405 insertions(+)
diff --git a/fit/wa-reference/src/main/resources/wa-embedded.properties
b/fit/wa-reference/src/main/resources/wa-embedded.properties
index 35c7c8b939..35d6c68bb9 100644
--- a/fit/wa-reference/src/main/resources/wa-embedded.properties
+++ b/fit/wa-reference/src/main/resources/wa-embedded.properties
@@ -29,6 +29,11 @@ cas.server.prefix=${cas.server.name}/syncope-wa
cas.authn.syncope.url=${cas.server.name}/syncope
cas.authn.syncope.name=DefaultSyncopeAuthModule
+cas.authn.pm.core.enabled=true
+# TMP until SYNCOPE-1901
+cas.authn.pm.syncope.basic-auth-username=placeholder
+cas.authn.pm.syncope.basic-auth-password=placeholder
+
service.discovery.address=https://localhost:9443/syncope-wa/
##
diff --git
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/AbstractUIITCase.java
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/AbstractUIITCase.java
index 6378e72a94..351cd552f4 100644
---
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/AbstractUIITCase.java
+++
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/AbstractUIITCase.java
@@ -20,19 +20,28 @@ package org.apache.syncope.fit.ui;
import static org.junit.jupiter.api.Assertions.assertEquals;
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 jakarta.ws.rs.core.Response;
import java.io.IOException;
+import java.util.Optional;
+import org.apache.http.HttpStatus;
+import org.apache.syncope.common.lib.Attr;
import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.policy.AttrReleasePolicyTO;
import org.apache.syncope.common.lib.policy.AuthPolicyTO;
import org.apache.syncope.common.lib.policy.DefaultAttrReleasePolicyConf;
import org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf;
+import org.apache.syncope.common.lib.request.UserCR;
import org.apache.syncope.common.lib.types.ClientExceptionType;
import org.apache.syncope.common.lib.types.PolicyType;
import org.apache.syncope.common.rest.api.RESTHeaders;
import org.apache.syncope.fit.AbstractITCase;
+import org.jsoup.Connection;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.FormElement;
import org.junit.jupiter.api.Test;
public abstract class AbstractUIITCase extends AbstractITCase {
@@ -104,6 +113,19 @@ public abstract class AbstractUIITCase extends
AbstractITCase {
});
}
+ protected static String extractWACSRF(final String body) {
+ FormElement form = (FormElement)
Jsoup.parse(body).body().getElementsByTag("form").first();
+ assertNotNull(form);
+
+ Optional<String> execution = form.formData().stream().
+ filter(keyval -> "_csrf".equals(keyval.key())).
+ map(Connection.KeyVal::value).
+ findFirst();
+ assertTrue(execution.isPresent());
+
+ return execution.get();
+ }
+
protected abstract void sso(String baseURL, String username, String
password) throws IOException;
@Test
@@ -116,6 +138,38 @@ public abstract class AbstractUIITCase extends
AbstractITCase {
sso(ENDUSER_ADDRESS, "bellini", "password");
}
+ protected abstract void passwordManagement(String baseURL, String
username, String password) throws IOException;
+
+ protected void passwordManagement(final String baseURL) throws IOException
{
+ String key = null;
+ try {
+ UserCR userCR = new UserCR.Builder(SyncopeConstants.ROOT_REALM,
"mustChangePassword")
+ .password("Password123!")
+ .mustChangePassword(true)
+ .plainAttr(new
Attr.Builder("fullname").value("mustChangePassword").build())
+ .plainAttr(new
Attr.Builder("userId").value("[email protected]").build())
+ .plainAttr(new
Attr.Builder("surname").value("mustChangePassword").build()).build();
+
+ Response response = USER_SERVICE.create(userCR);
+ assertEquals(HttpStatus.SC_CREATED, response.getStatus());
+ key = response.getHeaderString(RESTHeaders.RESOURCE_KEY);
+
+ AbstractUIITCase.this.passwordManagement(baseURL,
"mustChangePassword", "Password123!");
+ } finally {
+ Optional.ofNullable(key).ifPresent(USER_SERVICE::delete);
+ }
+ }
+
+ @Test
+ public void passwordManagementConsole() throws IOException {
+ passwordManagement(CONSOLE_ADDRESS);
+ }
+
+ @Test
+ public void passwordManagementEnduser() throws IOException {
+ passwordManagement(ENDUSER_ADDRESS);
+ }
+
@Test
public void createUnmatching() throws IOException {
try {
diff --git
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java
index 6804426cf8..03af6ad87e 100644
---
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java
+++
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java
@@ -20,6 +20,7 @@ package org.apache.syncope.fit.ui;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@@ -53,6 +54,7 @@ import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.to.Item;
import org.apache.syncope.common.lib.to.OIDCC4UIProviderTO;
import org.apache.syncope.common.lib.to.OIDCRPClientAppTO;
+import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.lib.types.ClientAppType;
import org.apache.syncope.common.lib.types.LogoutType;
import org.apache.syncope.common.lib.types.OIDCResponseType;
@@ -287,6 +289,143 @@ public class OIDCC4UIITCase extends AbstractUIITCase {
httpclient.execute(get, context);
}
+ @Override
+ protected void passwordManagement(final String baseURL, final String
username, final String password)
+ throws IOException {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ HttpClientContext context = HttpClientContext.create();
+ context.setCookieStore(new BasicCookieStore());
+
+ // 1. fetch login page
+ HttpGet get = new HttpGet(baseURL);
+ CloseableHttpResponse response = httpclient.execute(get, context);
+ assertEquals(HttpStatus.SC_OK,
response.getStatusLine().getStatusCode());
+
+ // 2. click on the OpenID Connect Provider
+ get = new HttpGet(baseURL + OIDCC4UIConstants.URL_CONTEXT +
"/login?op=" + getAppName(baseURL));
+ 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());
+
+ // 2. redirected to WA login screen
+ String responseBody = EntityUtils.toString(response.getEntity());
+ response = authenticateToWA(username, password, responseBody,
httpclient, context);
+
+ assertEquals(HttpStatus.SC_UNAUTHORIZED,
response.getStatusLine().getStatusCode());
+
+ // 3. redirected to WA reset password screen
+ responseBody = EntityUtils.toString(response.getEntity());
+
+ // check WA reset password screen
+ assertTrue(responseBody.contains("password"));
+ assertTrue(responseBody.contains("confirmedPassword"));
+ assertTrue(responseBody.contains("execution"));
+
+ String execution = extractWAExecution(responseBody);
+
+ // 3a. change password request
+ List<NameValuePair> form = new ArrayList<>();
+ form.add(new BasicNameValuePair("_eventId", "submit"));
+ form.add(new BasicNameValuePair("execution", execution));
+ form.add(new BasicNameValuePair("password", "PasswordChanged123!"));
+ form.add(new BasicNameValuePair("confirmedPassword",
"PasswordChanged123!"));
+
+ 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_OK,
response.getStatusLine().getStatusCode());
+
+ UserTO userTO = USER_SERVICE.read("mustChangePassword");
+ assertFalse(userTO.isMustChangePassword());
+
+ responseBody = EntityUtils.toString(response.getEntity());
+
+ assertTrue(responseBody.contains("execution"));
+ assertTrue(responseBody.contains("_csrf"));
+
+ // 4. go to WA login screen
+ execution = extractWAExecution(responseBody);
+ String csrf = extractWACSRF(responseBody);
+ form.clear();
+ form.add(new BasicNameValuePair("_eventId", "proceed"));
+ form.add(new BasicNameValuePair("_csrf", csrf));
+ form.add(new BasicNameValuePair("execution", execution));
+
+ 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_OK,
response.getStatusLine().getStatusCode());
+
+ responseBody = EntityUtils.toString(response.getEntity());
+
+ assertTrue(responseBody.contains("username"));
+ assertTrue(responseBody.contains("password"));
+
+ response = authenticateToWA(username, "PasswordChanged123!",
responseBody, httpclient, context);
+
+ // 4a. WA attribute consent screen
+ responseBody = EntityUtils.toString(response.getEntity());
+
+ // check attribute repository
+ assertTrue(responseBody.contains("identifier"));
+ assertTrue(responseBody.contains("[value1]"));
+
+ execution = extractWAExecution(responseBody);
+
+ form.clear();
+ 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"));
+
+ 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());
+
+ // 4b. WA scope consent screen
+ get = new
HttpGet(response.getLastHeader(HttpHeaders.LOCATION).getValue());
+ 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());
+
+ responseBody = EntityUtils.toString(response.getEntity());
+
+ String allow = Jsoup.parse(responseBody).body().
+ getElementsByTag("a").select("a[name=allow]").first().
+ attr("href");
+ assertNotNull(allow);
+
+ // 4c. finally get requested content
+ get = new HttpGet(allow);
+ get.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
+ get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
+ response = httpclient.execute(get, context);
+
+ // 4d. verify that user is now authenticated
+ assertEquals(HttpStatus.SC_OK,
response.getStatusLine().getStatusCode());
+
assertTrue(EntityUtils.toString(response.getEntity()).contains(username));
+
+ // 5. logout
+ get = new HttpGet(CONSOLE_ADDRESS.equals(baseURL)
+ ? baseURL +
"wicket/bookmarkable/org.apache.syncope.client.console.pages.Logout"
+ : baseURL +
"wicket/bookmarkable/org.apache.syncope.client.enduser.pages.Logout");
+ httpclient.execute(get, context);
+ }
+
@Override
protected void doSelfReg(final Runnable runnable) {
runnable.run();
diff --git
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OpenFGAUIITCase.java
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OpenFGAUIITCase.java
index 0f6533985a..2bae4172d1 100644
---
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OpenFGAUIITCase.java
+++
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OpenFGAUIITCase.java
@@ -154,4 +154,14 @@ public class OpenFGAUIITCase extends OIDCC4UIITCase {
public void createUnmatching() throws IOException {
assumeFalse(true);
}
+
+ @Override
+ public void passwordManagementConsole() {
+ assumeFalse(true);
+ }
+
+ @Override
+ public void passwordManagementEnduser() {
+ assumeFalse(true);
+ }
}
diff --git
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/SAML2SP4UIITCase.java
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/SAML2SP4UIITCase.java
index 7462cf9485..d2dec2febd 100644
---
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/SAML2SP4UIITCase.java
+++
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/SAML2SP4UIITCase.java
@@ -53,6 +53,7 @@ import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.to.Item;
import org.apache.syncope.common.lib.to.SAML2SP4UIIdPTO;
import org.apache.syncope.common.lib.to.SAML2SPClientAppTO;
+import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.lib.types.ClientAppType;
import org.apache.syncope.common.lib.types.SAML2SPNameId;
import org.apache.syncope.common.rest.api.RESTHeaders;
@@ -302,6 +303,199 @@ public class SAML2SP4UIITCase extends AbstractUIITCase {
httpclient.execute(get, context);
}
+ @Override
+ protected void passwordManagement(final String baseURL, final String
username, final String password)
+ throws IOException {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ HttpClientContext context = HttpClientContext.create();
+ context.setCookieStore(new BasicCookieStore());
+
+ // 1. fetch login page
+ HttpGet get = new HttpGet(baseURL);
+ try (CloseableHttpResponse response = httpclient.execute(get,
context)) {
+ assertEquals(HttpStatus.SC_OK,
response.getStatusLine().getStatusCode());
+ }
+
+ // 2. click on the SAML 2.0 IdP
+ get = new HttpGet(baseURL + SAML2SP4UIConstants.URL_CONTEXT
+ + "/login?idp=https%3A//localhost%3A9443/syncope-wa/saml");
+ String responseBody;
+ try (CloseableHttpResponse response = httpclient.execute(get,
context)) {
+ responseBody = EntityUtils.toString(response.getEntity());
+ }
+ Triple<String, String, String> parsed =
parseSAMLRequestForm(responseBody);
+
+ // 2a. post SAML request
+ HttpPost post = new HttpPost(parsed.getLeft());
+ post.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
+ post.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
+ post.setEntity(new UrlEncodedFormEntity(
+ List.of(new BasicNameValuePair("RelayState",
parsed.getMiddle()),
+ new BasicNameValuePair("SAMLRequest",
parsed.getRight())), Consts.UTF_8));
+ String location;
+ try (CloseableHttpResponse response = httpclient.execute(post,
context)) {
+ assertEquals(HttpStatus.SC_MOVED_TEMPORARILY,
response.getStatusLine().getStatusCode());
+ location =
response.getFirstHeader(HttpHeaders.LOCATION).getValue();
+ }
+
+ // 2b. authenticate
+ post = new HttpPost(location);
+ post.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
+ post.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
+ post.addHeader(HttpHeaders.REFERER, get.getURI().toASCIIString());
+ try (CloseableHttpResponse response = httpclient.execute(post,
context)) {
+ assertEquals(HttpStatus.SC_OK,
response.getStatusLine().getStatusCode());
+ responseBody = EntityUtils.toString(response.getEntity());
+ }
+ try (CloseableHttpResponse response =
+ authenticateToWA(username, password, responseBody, httpclient,
context)) {
+ assertEquals(HttpStatus.SC_UNAUTHORIZED,
response.getStatusLine().getStatusCode());
+
+ // 3. redirected to WA reset password screen
+ responseBody = EntityUtils.toString(response.getEntity());
+
+ // check WA reset password screen
+ assertTrue(responseBody.contains("password"));
+ assertTrue(responseBody.contains("confirmedPassword"));
+ assertTrue(responseBody.contains("execution"));
+ }
+
+ String execution = extractWAExecution(responseBody);
+
+ // 3a. change password request
+ List<NameValuePair> form = new ArrayList<>();
+ form.add(new BasicNameValuePair("_eventId", "submit"));
+ form.add(new BasicNameValuePair("execution", execution));
+ form.add(new BasicNameValuePair("password", "PasswordChanged123!"));
+ form.add(new BasicNameValuePair("confirmedPassword",
"PasswordChanged123!"));
+
+ 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));
+ try (CloseableHttpResponse response = httpclient.execute(post,
context)) {
+ assertEquals(HttpStatus.SC_OK,
response.getStatusLine().getStatusCode());
+
+ UserTO userTO = USER_SERVICE.read("mustChangePassword");
+ assertFalse(userTO.isMustChangePassword());
+
+ responseBody = EntityUtils.toString(response.getEntity());
+
+ assertTrue(responseBody.contains("execution"));
+ assertTrue(responseBody.contains("_csrf"));
+ }
+
+ // 4. go to WA login screen
+ execution = extractWAExecution(responseBody);
+ String csrf = extractWACSRF(responseBody);
+ form.clear();
+ form.add(new BasicNameValuePair("_eventId", "proceed"));
+ form.add(new BasicNameValuePair("_csrf", csrf));
+ form.add(new BasicNameValuePair("execution", execution));
+
+ 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));
+ try (CloseableHttpResponse response = httpclient.execute(post,
context)) {
+ assertEquals(HttpStatus.SC_OK,
response.getStatusLine().getStatusCode());
+
+ responseBody = EntityUtils.toString(response.getEntity());
+
+ assertTrue(responseBody.contains("username"));
+ assertTrue(responseBody.contains("password"));
+ }
+
+ // 2b. authenticate
+ post = new HttpPost(location);
+ post.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
+ post.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
+ post.addHeader(HttpHeaders.REFERER, get.getURI().toASCIIString());
+ try (CloseableHttpResponse response = httpclient.execute(post,
context)) {
+ assertEquals(HttpStatus.SC_OK,
response.getStatusLine().getStatusCode());
+ responseBody = EntityUtils.toString(response.getEntity());
+ }
+ boolean isOk = false;
+ try (CloseableHttpResponse response =
+ authenticateToWA(username, "PasswordChanged123!",
responseBody, httpclient, context)) {
+
+ switch (response.getStatusLine().getStatusCode()) {
+ case HttpStatus.SC_OK:
+ isOk = true;
+ responseBody = EntityUtils.toString(response.getEntity());
+ break;
+
+ case HttpStatus.SC_MOVED_TEMPORARILY:
+ location =
response.getFirstHeader(HttpHeaders.LOCATION).getValue();
+ break;
+
+ default:
+ fail();
+ }
+ }
+
+ // 2c. WA attribute consent screen
+ if (isOk) {
+ execution = extractWAExecution(responseBody);
+
+ form.clear();
+ 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"));
+
+ 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));
+ try (CloseableHttpResponse response = httpclient.execute(post,
context)) {
+ assertEquals(HttpStatus.SC_MOVED_TEMPORARILY,
response.getStatusLine().getStatusCode());
+ location =
response.getFirstHeader(HttpHeaders.LOCATION).getValue();
+ }
+ }
+
+ if (location.startsWith("http://localhost:8080/syncope-wa")) {
+ location = WA_ADDRESS + StringUtils.substringAfter(location,
"http://localhost:8080/syncope-wa");
+ }
+
+ get = new HttpGet(location);
+ get.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
+ get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
+ try (CloseableHttpResponse response = httpclient.execute(get,
context)) {
+ assertEquals(HttpStatus.SC_OK,
response.getStatusLine().getStatusCode());
+ responseBody = EntityUtils.toString(response.getEntity());
+ }
+
+ // 2d. post SAML response
+ parsed = parseSAMLResponseForm(responseBody);
+
+ post = new HttpPost(parsed.getLeft());
+ post.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
+ post.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
+ post.setEntity(new UrlEncodedFormEntity(
+ List.of(new BasicNameValuePair("RelayState",
parsed.getMiddle()),
+ new BasicNameValuePair("SAMLResponse",
parsed.getRight())), Consts.UTF_8));
+ try (CloseableHttpResponse response = httpclient.execute(post,
context)) {
+ assertEquals(HttpStatus.SC_MOVED_TEMPORARILY,
response.getStatusLine().getStatusCode());
+ location =
response.getFirstHeader(HttpHeaders.LOCATION).getValue();
+ }
+
+ // 3. verify that user is now authenticated
+ get = new HttpGet(baseURL + Strings.CS.removeStart(location, "../"));
+ try (CloseableHttpResponse response = httpclient.execute(get,
context)) {
+ assertEquals(HttpStatus.SC_OK,
response.getStatusLine().getStatusCode());
+
assertTrue(EntityUtils.toString(response.getEntity()).contains(username));
+ }
+
+ // 4. logout
+ get = new HttpGet(CONSOLE_ADDRESS.equals(baseURL)
+ ? baseURL +
"wicket/bookmarkable/org.apache.syncope.client.console.pages.Logout"
+ : baseURL +
"wicket/bookmarkable/org.apache.syncope.client.enduser.pages.Logout");
+ httpclient.execute(get, context);
+ }
+
@Override
protected void doSelfReg(final Runnable runnable) {
List<SAML2SP4UIIdPTO> idps = SAML2SP4UI_IDP_SERVICE.list();
diff --git
a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WAPropertySourceLocator.java
b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WAPropertySourceLocator.java
index 3d5098012a..120652772a 100644
---
a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WAPropertySourceLocator.java
+++
b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WAPropertySourceLocator.java
@@ -124,6 +124,9 @@ public class WAPropertySourceLocator implements
PropertySourceLocator {
properties.putAll(index(map, prefixes));
});
+ properties.put("cas.authn.pm.syncope.url",
+ StringUtils.substringBefore(syncopeClient.getAddress(),
"/rest"));
+
Set<String> customClaims =
syncopeClient.getService(WAClientAppService.class).list().stream().
filter(app -> app.getClientAppTO() instanceof
OIDCRPClientAppTO && app.getAttrReleasePolicy() != null).
flatMap(app -> {