Skip to content

Commit 2f68b70

Browse files
alberto-bogialberto bogimmoayyed
authored
Manage security question for reset password on syncope (apereo#7378)
Co-authored-by: alberto bogi <[email protected]> Co-authored-by: Misagh Moayyed <[email protected]>
1 parent e2f683e commit 2f68b70

File tree

7 files changed

+50
-9
lines changed

7 files changed

+50
-9
lines changed

ci/tests/puppeteer/scenarios/pm-reset-password-syncope/script.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ const cas = require("../../cas.js");
5151
await cas.goto(page, link);
5252
await cas.sleep(2000);
5353

54+
await cas.assertInnerText(page, "#content h2", "Answer Security Questions");
55+
56+
await cas.type(page, "#q0", "Rome", true);
57+
await cas.pressEnter(page);
58+
await cas.waitForNavigation(page);
59+
await cas.sleep(2000);
5460
await cas.assertInnerText(page, "#pwdmain h3", `Hello, ${username}. You must change your password.`);
5561

5662
newPassword = await cas.randomWord();

ci/tests/puppeteer/scenarios/pm-reset-password-syncope/script.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
"--cas.authn.pm.reset.mail.text=${url}",
2828
"--cas.authn.pm.reset.mail.subject=Reset",
2929
"--cas.authn.pm.reset.mail.attribute-name=mail",
30-
"--cas.authn.pm.reset.security-questions-enabled=false",
3130

3231
"--cas.authn.pm.syncope.basic-auth-username=admin",
3332
"--cas.authn.pm.syncope.basic-auth-password=password",

ci/tests/syncope/docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ services:
1010
syncope:
1111
depends_on:
1212
- db
13-
image: apache/syncope:4.0.0
13+
image: apache/syncope:4.1.0-SNAPSHOT
1414
ports:
1515
- "18080:8080"
1616
restart: always
@@ -31,7 +31,7 @@ services:
3131
syncope-console:
3232
depends_on:
3333
- syncope
34-
image: apache/syncope-console:4.0.0
34+
image: apache/syncope-console:4.1.0-SNAPSHOT
3535
ports:
3636
- "28080:8080"
3737
restart: always

ci/tests/syncope/run-syncope-server.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ for i in {0..4}; do
261261
"username": '\""syncopepasschange${suffix}\""',
262262
"password": "Sync0pe",
263263
"mustChangePassword": true,
264+
"securityQuestion": "'"${SECURITY_QUESTION_KEY}"'",
265+
"securityAnswer": "Rome",
264266
"plainAttrs": [
265267
{
266268
"schema": "email",

core/cas-server-core-util-api/src/main/java/org/apereo/cas/util/http/HttpRequestUtils.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,17 @@ public class HttpRequestUtils {
5252
result.setHeaders(response.getHeaders());
5353
result.setLocale(ObjectUtils.getIfNull(response.getLocale(), Locale.getDefault()));
5454
result.setVersion(ObjectUtils.getIfNull(response.getVersion(), HttpVersion.HTTP_1_1));
55-
val output = new ByteArrayOutputStream();
56-
response.getEntity().writeTo(output);
57-
val contentTypeHeader = response.getFirstHeader(HttpHeaders.CONTENT_TYPE);
58-
val contentType = contentTypeHeader != null ? contentTypeHeader.getValue() : ContentType.APPLICATION_JSON.getMimeType();
59-
result.setEntity(new ByteArrayEntity(output.toByteArray(), ContentType.parseLenient(contentType)));
55+
56+
val entity = response.getEntity();
57+
if (entity != null) {
58+
try (val output = new ByteArrayOutputStream()) {
59+
entity.writeTo(output);
60+
val contentTypeHeader = response.getFirstHeader(HttpHeaders.CONTENT_TYPE);
61+
val contentType = contentTypeHeader != null ? contentTypeHeader.getValue() : ContentType.APPLICATION_JSON.getMimeType();
62+
result.setEntity(new ByteArrayEntity(output.toByteArray(), ContentType.parseLenient(contentType)));
63+
}
64+
}
65+
6066
return result;
6167
};
6268

support/cas-server-support-pm-webflow/src/test/java/org/apereo/cas/pm/web/flow/actions/PasswordChangeActionTests.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,4 @@ void verifyCurrentPasswordWrong() throws Throwable {
120120
PasswordManagementWebflowUtils.putPasswordResetUsername(context, changeReq.getUsername());
121121
assertEquals(CasWebflowConstants.TRANSITION_ID_ERROR, passwordChangeAction.execute(context).getId());
122122
}
123-
124123
}

support/cas-server-support-syncope-authentication/src/main/java/org/apereo/cas/syncope/pm/SyncopePasswordManagementService.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
import lombok.val;
1919
import org.apache.commons.lang3.StringUtils;
2020
import org.apache.commons.lang3.Strings;
21+
import org.apache.commons.lang3.tuple.Pair;
2122
import org.apache.hc.core5.http.HttpEntityContainer;
23+
import org.apache.hc.core5.http.HttpResponse;
2224
import org.apache.hc.core5.http.HttpStatus;
2325
import org.apache.hc.core5.http.io.entity.EntityUtils;
2426
import org.springframework.http.HttpHeaders;
@@ -196,6 +198,33 @@ public boolean unlockAccount(final Credential credential) throws Throwable {
196198
return false;
197199
}
198200

201+
@Override
202+
public boolean isAnswerValidForSecurityQuestion(final PasswordManagementQuery query, final String question, final String knownAnswer, final String givenAnswer) {
203+
HttpResponse response = null;
204+
try {
205+
val userSecurityAnswerUrl = Strings.CI.appendIfMissing(SpringExpressionLanguageValueResolver.getInstance()
206+
.resolve(casProperties.getAuthn().getPm().getSyncope().getUrl()),
207+
"/rest/users/verifySecurityAnswer");
208+
209+
LOGGER.debug("Check security answer validity for user [{}]", query.getUsername());
210+
val exec = HttpExecutionRequest.builder().method(HttpMethod.POST).url(userSecurityAnswerUrl)
211+
.basicAuthUsername(casProperties.getAuthn().getPm().getSyncope().getBasicAuthUsername())
212+
.basicAuthPassword(casProperties.getAuthn().getPm().getSyncope().getBasicAuthPassword())
213+
.headers(Map.of(
214+
SyncopeUtils.SYNCOPE_HEADER_DOMAIN, casProperties.getAuthn().getPm().getSyncope().getDomain(),
215+
HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE,
216+
HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE))
217+
.parameters(Map.of("username", query.getUsername()))
218+
.entity(givenAnswer)
219+
.maximumRetryAttempts(casProperties.getAuthn().getSyncope().getMaxRetryAttempts())
220+
.build();
221+
response = Objects.requireNonNull(HttpUtils.execute(exec));
222+
return org.springframework.http.HttpStatus.resolve(response.getCode()).is2xxSuccessful();
223+
} finally {
224+
HttpUtils.close(response);
225+
}
226+
}
227+
199228
protected String fetchSyncopeUserKey(final String username) {
200229
val query = PasswordManagementQuery.builder().username(username).build();
201230
return searchUser(query)

0 commit comments

Comments
 (0)