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

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


The following commit(s) were added to refs/heads/4_0_X by this push:
     new c6de21359e [SYNCOPE-1898] Reset password with WA if mustChangePassword 
is true (#1141)
c6de21359e is described below

commit c6de21359e0611a262fdfe7d9e5d22870800e30f
Author: alberto bogi <35602311+alberto-b...@users.noreply.github.com>
AuthorDate: Thu Jul 24 17:08:42 2025 +0200

    [SYNCOPE-1898] Reset password with WA if mustChangePassword is true (#1141)
---
 .../src/main/resources/wa-embedded.properties      |   2 +
 .../apache/syncope/fit/ui/AbstractUIITCase.java    |  68 +++++++
 .../org/apache/syncope/fit/ui/OIDCC4UIITCase.java  | 140 +++++++++++++++
 .../org/apache/syncope/fit/ui/OpenFGAUIITCase.java |  13 ++
 .../apache/syncope/fit/ui/SAML2SP4UIITCase.java    | 196 +++++++++++++++++++++
 .../wa/bootstrap/WAPropertySourceLocator.java      |   3 +
 6 files changed, 422 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..d8118ebf77 100644
--- a/fit/wa-reference/src/main/resources/wa-embedded.properties
+++ b/fit/wa-reference/src/main/resources/wa-embedded.properties
@@ -29,6 +29,8 @@ 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
+
 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..ce61575e56 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,8 +113,23 @@ 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;
 
+    protected abstract void passwordManagement(String baseURL, String 
username, String password) throws IOException;
+
     @Test
     public void sso2Console() throws IOException {
         sso(CONSOLE_ADDRESS, "bellini", "password");
@@ -116,6 +140,50 @@ public abstract class AbstractUIITCase extends 
AbstractITCase {
         sso(ENDUSER_ADDRESS, "bellini", "password");
     }
 
+    @Test
+    public void passwordManagementConsole() throws IOException {
+        String key = null;
+        try {
+            UserCR userCR = new UserCR.Builder(SyncopeConstants.ROOT_REALM, 
"mustChangePassword").mustChangePassword(
+                            Boolean.TRUE).password("Password123!")
+                    .plainAttr(new 
Attr.Builder("fullname").value("mustChangePassword").build())
+                    .plainAttr(new 
Attr.Builder("userId").value("mustchangepassw...@syncope.org").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);
+
+            passwordManagement(CONSOLE_ADDRESS, "mustChangePassword", 
"Password123!");
+        } finally {
+            if (key != null) {
+                USER_SERVICE.delete(key);
+            }
+        }
+    }
+
+    @Test
+    public void passwordManagementEnduser() throws IOException {
+        String key = null;
+        try {
+            UserCR userCR = new UserCR.Builder(SyncopeConstants.ROOT_REALM, 
"mustChangePassword").mustChangePassword(
+                            Boolean.TRUE).password("Password123!")
+                    .plainAttr(new 
Attr.Builder("fullname").value("mustChangePassword").build())
+                    .plainAttr(new 
Attr.Builder("userId").value("mustchangepassw...@syncope.org").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);
+
+            passwordManagement(ENDUSER_ADDRESS, "mustChangePassword", 
"Password123!");
+        } finally {
+            if (key != null) {
+                USER_SERVICE.delete(key);
+            }
+        }
+    }
+
     @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 841804acb1..b4c4047b12 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
@@ -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.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.OIDCResponseType;
 import org.apache.syncope.common.lib.types.OIDCSubjectType;
@@ -289,6 +290,145 @@ 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");
+
+        assertNotNull(userTO);
+        assertEquals(userTO.isMustChangePassword(), Boolean.FALSE);
+
+        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..20984326ad 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
@@ -47,6 +47,7 @@ import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.beans.ExecSpecs;
 import org.apache.syncope.common.rest.api.service.wa.WAConfigService;
 import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
 
 public class OpenFGAUIITCase extends OIDCC4UIITCase {
 
@@ -154,4 +155,16 @@ public class OpenFGAUIITCase extends OIDCC4UIITCase {
     public void createUnmatching() throws IOException {
         assumeFalse(true);
     }
+
+    @Disabled
+    @Override
+    public void passwordManagementConsole() {
+        // ignore
+    }
+
+    @Disabled
+    @Override
+    public void passwordManagementEnduser() {
+        // ignore
+    }
 }
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..d3fd41dc15 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
@@ -20,6 +20,7 @@ package org.apache.syncope.fit.ui;
 
 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.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 +304,200 @@ 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)) {
+            UserTO userTO = USER_SERVICE.read("mustChangePassword");
+
+            assertEquals(HttpStatus.SC_OK, 
response.getStatusLine().getStatusCode());
+            assertNotNull(userTO);
+            assertEquals(userTO.isMustChangePassword(), Boolean.FALSE);
+
+            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 -> {

Reply via email to