[java-identity-provider] branch main updated: IDP-2047 - Get list of locked accounts
Scott Cantor
cantor.2 at osu.edu
Fri Aug 4 19:43:54 UTC 2023
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch main
in repository java-identity-provider.
View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=ae737d05b512aad63587b464c6bf0d0cc48899d2
The following commit(s) were added to refs/heads/main by this push:
new ae737d05b IDP-2047 - Get list of locked accounts
ae737d05b is described below
commit ae737d05b512aad63587b464c6bf0d0cc48899d2
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Fri Aug 4 15:42:59 2023 -0400
IDP-2047 - Get list of locked accounts
https://shibboleth.atlassian.net/browse/IDP-2047
Implemented enumeration-capable lockout manager.
Refactored some of the internals.
Added an inexact query param to REST API.
---
.../authn/EnumeratableAccountLockoutManager.java | 43 +++++
.../idp/authn/impl/DoLockoutManagerOperation.java | 99 +++++++----
.../impl/StorageBackedAccountLockoutManager.java | 190 ++++++++++++++++-----
.../StorageBackedAccountLockoutManagerTest.java | 3 +-
.../shibboleth/idp/flows/admin/lockout-flow.xml | 1 +
5 files changed, 258 insertions(+), 78 deletions(-)
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/EnumeratableAccountLockoutManager.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/EnumeratableAccountLockoutManager.java
new file mode 100644
index 000000000..8de82cb3a
--- /dev/null
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/EnumeratableAccountLockoutManager.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed 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 net.shibboleth.idp.authn;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.authn.context.LockoutManagerContext;
+
+/**
+ * An extension to {@link AccountLockoutManager} that allows for enumeration over
+ * partial matches of a key.
+ *
+ * @since 5.0.0
+ */
+public interface EnumeratableAccountLockoutManager extends AccountLockoutManager {
+
+ /**
+ * Return iterable collection of locked out keys that match a supplied partial key (i.e., are prefixed by it).
+ *
+ * <p>The key MUST be supplied via a {@link LockoutManagerContext} subcontext of the input context.</p>
+ *
+ * @param profileRequestContext current profile request context
+ *
+ * @return the locked out keys, or a null if an error occurs
+ */
+ @Nullable Iterable<String> enumerate(@Nonnull final ProfileRequestContext profileRequestContext);
+
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/DoLockoutManagerOperation.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/DoLockoutManagerOperation.java
index a904cc33c..2034c1457 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/DoLockoutManagerOperation.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/DoLockoutManagerOperation.java
@@ -15,6 +15,7 @@
package net.shibboleth.idp.authn.impl;
import java.io.IOException;
+import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -23,7 +24,6 @@ import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
-import org.springframework.beans.BeansException;
import org.springframework.webflow.execution.RequestContext;
import com.fasterxml.jackson.core.JsonFactory;
@@ -36,6 +36,7 @@ import com.google.common.base.Strings;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.shibboleth.idp.authn.AccountLockoutManager;
+import net.shibboleth.idp.authn.EnumeratableAccountLockoutManager;
import net.shibboleth.idp.authn.context.LockoutManagerContext;
import net.shibboleth.idp.profile.AbstractProfileAction;
import net.shibboleth.idp.profile.context.SpringRequestContext;
@@ -75,6 +76,9 @@ public class DoLockoutManagerOperation extends AbstractProfileAction {
/** Flow variable indicating ID of account key. */
@Nonnull @NotEmpty public static final String KEY = "key";
+ /** Flow variable indicating whether key should be inexactly matched. */
+ @Nonnull @NotEmpty public static final String INEXACT = "inexact";
+
/** Class logger. */
@Nonnull private Logger log = LoggerFactory.getLogger(DoLockoutManagerOperation.class);
@@ -87,6 +91,9 @@ public class DoLockoutManagerOperation extends AbstractProfileAction {
/** Account key to operate on. */
@NonnullBeforeExec @NotEmpty private String key;
+ /** Enumerating on inexact matches? */
+ private boolean inexact;
+
/** {@link AccountLockoutManager} to operate on. */
@NonnullBeforeExec private AccountLockoutManager lockoutManager;
@@ -110,6 +117,7 @@ public class DoLockoutManagerOperation extends AbstractProfileAction {
}
}
+// Checkstyle: CyclomaticComplexity|ReturnCount OFF
/** {@inheritDoc} */
@Override
protected boolean doPreExecute(final @Nonnull ProfileRequestContext profileRequestContext) {
@@ -153,6 +161,16 @@ public class DoLockoutManagerOperation extends AbstractProfileAction {
"Missing Account Key", "No account key specified.");
return false;
}
+
+ final String flag = (String) requestContext.getFlowScope().get(INEXACT);
+ if (flag != null) {
+ inexact = Boolean.valueOf(flag);
+ if (inexact && !(lockoutManager instanceof EnumeratableAccountLockoutManager)) {
+ sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ "Invalid Lockout Manager", "Lockout manager specified does not support inexact lookup.");
+ return false;
+ }
+ }
} catch (final IOException e) {
log.error("{} I/O error issuing API response", getLogPrefix(), e);
@@ -162,8 +180,9 @@ public class DoLockoutManagerOperation extends AbstractProfileAction {
return true;
}
+// Checkstyle: ReturnCount ON
-// Checkstyle: CyclomaticComplexity OFF
+// Checkstyle: MethodLength OFF
/** {@inheritDoc} */
@Override protected void doExecute(final @Nonnull ProfileRequestContext profileRequestContext) {
@@ -178,18 +197,48 @@ public class DoLockoutManagerOperation extends AbstractProfileAction {
if ("GET".equals(request.getMethod())) {
try {
- final boolean lockout = getLockoutManager().check(profileRequestContext);
- response.setStatus(HttpServletResponse.SC_OK);
- final JsonFactory jsonFactory = new JsonFactory();
- try (final JsonGenerator g = jsonFactory.createGenerator(
- response.getOutputStream()).useDefaultPrettyPrinter()) {
- g.setCodec(objectMapper);
- g.writeStartObject();
- g.writeObjectFieldStart("data");
- g.writeStringField("type", "lockout-statuses");
- g.writeStringField("id", managerId + '/' + key);
- g.writeObjectFieldStart("attributes");
- g.writeBooleanField("lockout", lockout);
+ if (inexact) {
+ final Iterable<String> lockedKeys =
+ ((EnumeratableAccountLockoutManager) getLockoutManager()).enumerate(
+ profileRequestContext);
+ if (lockedKeys != null) {
+ response.setStatus(HttpServletResponse.SC_OK);
+ final JsonFactory jsonFactory = new JsonFactory();
+ try (final JsonGenerator g = jsonFactory.createGenerator(
+ response.getOutputStream()).useDefaultPrettyPrinter()) {
+ g.setCodec(objectMapper);
+ g.writeStartObject();
+ g.writeObjectFieldStart("data");
+ g.writeStringField("id", managerId + '/' + key);
+ g.writeStringField("type", "lockout-keys");
+ g.writeArrayFieldStart("data");
+ for (final String k : lockedKeys) {
+ g.writeString(k);
+ }
+ g.writeEndArray();
+ g.writeEndObject();
+ g.writeEndObject();
+ }
+ } else {
+ sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal Server Error",
+ "Lockout manager error.");
+ }
+ } else {
+ final boolean lockout = getLockoutManager().check(profileRequestContext);
+ response.setStatus(HttpServletResponse.SC_OK);
+ final JsonFactory jsonFactory = new JsonFactory();
+ try (final JsonGenerator g = jsonFactory.createGenerator(
+ response.getOutputStream()).useDefaultPrettyPrinter()) {
+ g.setCodec(objectMapper);
+ g.writeStartObject();
+ g.writeObjectFieldStart("data");
+ g.writeStringField("type", "lockout-statuses");
+ g.writeStringField("id", managerId + '/' + key);
+ g.writeObjectFieldStart("attributes");
+ g.writeBooleanField("lockout", lockout);
+ g.writeEndObject();
+ g.writeEndObject();
+ }
}
} catch (final IOException e) {
sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal Server Error",
@@ -231,7 +280,7 @@ public class DoLockoutManagerOperation extends AbstractProfileAction {
ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
}
}
-// Checkstyle: CyclomaticComplexity ON
+// Checkstyle: CyclomaticComplexity|MethodLength ON
/**
* Helper method to get the manager bean to operate on.
@@ -242,24 +291,14 @@ public class DoLockoutManagerOperation extends AbstractProfileAction {
*/
@Nullable private AccountLockoutManager setupLockoutManager(@Nonnull final RequestContext requestContext) {
- final String mgrId = this.managerId = (String) requestContext.getFlowScope().get(MANAGER_ID);
- if (mgrId == null) {
+ managerId = (String) requestContext.getFlowScope().get(MANAGER_ID);
+ if (managerId == null) {
log.warn("{} No {} flow variable found in request", getLogPrefix(), MANAGER_ID);
return null;
}
-
- try {
- assert mgrId != null;
- final Object bean = requestContext.getActiveFlow().getApplicationContext().getBean(mgrId);
- if (bean != null && bean instanceof AccountLockoutManager) {
- return (AccountLockoutManager) bean;
- }
- } catch (final BeansException e) {
-
- }
-
- log.warn("{} No bean of the correct type found named {}", getLogPrefix(), mgrId);
- return null;
+
+ assert managerId != null;
+ return getBean(requestContext, managerId, AccountLockoutManager.class);
}
/** Null safe getter.
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/StorageBackedAccountLockoutManager.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/StorageBackedAccountLockoutManager.java
index adbdb057f..b47abc476 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/StorageBackedAccountLockoutManager.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/StorageBackedAccountLockoutManager.java
@@ -17,18 +17,21 @@ package net.shibboleth.idp.authn.impl;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.storage.EnumeratableStorageService;
import org.opensaml.storage.StorageCapabilities;
import org.opensaml.storage.StorageRecord;
import org.opensaml.storage.StorageService;
import org.slf4j.Logger;
-import net.shibboleth.idp.authn.AccountLockoutManager;
+import net.shibboleth.idp.authn.EnumeratableAccountLockoutManager;
import net.shibboleth.idp.authn.context.AuthenticationContext;
import net.shibboleth.idp.authn.context.LockoutManagerContext;
import net.shibboleth.idp.authn.context.UsernamePasswordContext;
@@ -46,11 +49,11 @@ import net.shibboleth.shared.servlet.HttpServletSupport;
import jakarta.servlet.http.HttpServletRequest;
/**
- * Implementation of {@link AccountLockoutManager} interface that relies on a {@link StorageService}
+ * Implementation of {@link EnumeratableAccountLockoutManager} interface that relies on a {@link StorageService}
* to track lockout state.
*/
public class StorageBackedAccountLockoutManager extends AbstractIdentifiableInitializableComponent
- implements AccountLockoutManager {
+ implements EnumeratableAccountLockoutManager {
/** Class logger. */
@Nonnull private Logger log = LoggerFactory.getLogger(StorageBackedAccountLockoutManager.class);
@@ -218,11 +221,138 @@ public class StorageBackedAccountLockoutManager extends AbstractIdentifiableInit
/** {@inheritDoc} */
public boolean check(@Nonnull final ProfileRequestContext profileRequestContext) {
- final String key = getLockoutKeyStrategy().apply(profileRequestContext);
+
+ final String key;
+ final boolean managerOp;
+ final LockoutManagerContext managerCtx = profileRequestContext.getSubcontext(LockoutManagerContext.class);
+ if (managerCtx != null) {
+ key = managerCtx.getKey();
+ managerOp = true;
+ } else {
+ key = getLockoutKeyStrategy().apply(profileRequestContext);
+ managerOp = false;
+ }
+
+ if (key == null) {
+ log.warn("No lockout key returned for request");
+ return false;
+ }
+
+ final long lockoutDuration = lockoutDurationLookupStrategy.apply(profileRequestContext).toMillis();
+ final long counterInterval = counterIntervalLookupStrategy.apply(profileRequestContext).toMillis();
+ final int maxAttempts = maxAttemptsLookupStrategy.apply(profileRequestContext);
+
+ return doCheck(profileRequestContext, key, maxAttempts, lockoutDuration, counterInterval,
+ extendLockoutDuration && !managerOp);
+ }
+
+ /** {@inheritDoc} */
+ public boolean increment(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final String key;
+ final LockoutManagerContext managerCtx = profileRequestContext.getSubcontext(LockoutManagerContext.class);
+ if (managerCtx != null) {
+ key = managerCtx.getKey();
+ } else {
+ key = getLockoutKeyStrategy().apply(profileRequestContext);
+ }
+
if (key == null) {
log.warn("No lockout key returned for request");
return false;
}
+
+ final long lockoutDuration = lockoutDurationLookupStrategy.apply(profileRequestContext).toMillis();
+ final long counterInterval = counterIntervalLookupStrategy.apply(profileRequestContext).toMillis();
+
+ return doIncrement(profileRequestContext, key, 10, lockoutDuration, counterInterval);
+ }
+
+ /** {@inheritDoc} */
+ public boolean clear(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final String key;
+ final LockoutManagerContext managerCtx = profileRequestContext.getSubcontext(LockoutManagerContext.class);
+ if (managerCtx != null) {
+ key = managerCtx.getKey();
+ } else {
+ key = getLockoutKeyStrategy().apply(profileRequestContext);
+ }
+
+ try {
+ if (key != null) {
+ log.debug("Clearing lockout state for '{}'", key);
+ storageService.delete(ensureId(), key);
+ return true;
+ }
+ log.warn("No lockout key returned for request");
+ } catch (final IOException e) {
+ log.error("Error deleting lockout entry", e);
+ }
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Nullable public Iterable<String> enumerate(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final String partialKey;
+ final LockoutManagerContext managerCtx = profileRequestContext.getSubcontext(LockoutManagerContext.class);
+ if (managerCtx != null) {
+ partialKey = managerCtx.getKey();
+ } else {
+ partialKey = null;
+ }
+
+ if (partialKey == null) {
+ log.warn("No lockout key provided for request");
+ return null;
+ }
+
+ if (storageService instanceof EnumeratableStorageService ess) {
+ final Iterable<String> keys;
+ try {
+ keys = ess.getContextKeys(ensureId());
+ } catch (final IOException e) {
+ log.error("Error enumerating lockout storage context", e);
+ return null;
+ }
+
+ final long lockoutDuration = lockoutDurationLookupStrategy.apply(profileRequestContext).toMillis();
+ final long counterInterval = counterIntervalLookupStrategy.apply(profileRequestContext).toMillis();
+ final int maxAttempts = maxAttemptsLookupStrategy.apply(profileRequestContext);
+
+ final Collection<String> results = new ArrayList<>();
+
+ for (final String key : keys) {
+ if (key.startsWith(partialKey)) {
+ if (doCheck(profileRequestContext, key, maxAttempts, lockoutDuration, counterInterval, false)) {
+ results.add(key);
+ }
+ }
+ }
+
+ return results;
+ } else {
+ throw new UnsupportedOperationException("Underlying storage service does not support enumeration of keys");
+ }
+
+ }
+
+ /**
+ * Helper method to perform a check operation against a specific key.
+ *
+ * @param profileRequestContext current profile request context
+ * @param key input key to check
+ * @param maxAttempts maximum allowable attempts before lockout
+ * @param lockoutDuration duration of lockout
+ * @param counterInterval interval before disregarding attempts
+ * @param increment whether to increment the counter if already locked out
+ *
+ * @return true iff the designated key is locked out
+ */
+// Checkstyle: ParameterNum OFF
+ protected boolean doCheck(@Nonnull final ProfileRequestContext profileRequestContext, @Nonnull final String key,
+ final int maxAttempts, final long lockoutDuration, final long counterInterval, final boolean increment) {
// Read back account state. No state obviously means no lockout, but in the case of errors
// that does fail open. Of course, in-memory won't fail...
StorageRecord<?> sr = null;
@@ -240,18 +370,16 @@ public class StorageBackedAccountLockoutManager extends AbstractIdentifiableInit
try {
// Read counter and check if we've exceeded the limit.
final int counter = Integer.parseInt(sr.getValue());
- if (counter >= maxAttemptsLookupStrategy.apply(profileRequestContext)) {
+ if (counter >= maxAttempts) {
// Recover time of last attempt from the record expiration and find the time elapsed since.
// If that's under the lockout duration, we're locked out.
- final long lockoutDuration = lockoutDurationLookupStrategy.apply(profileRequestContext).toMillis();
- final long counterInterval = counterIntervalLookupStrategy.apply(profileRequestContext).toMillis();
final Long exp = Constraint.isNotNull(sr.getExpiration(), "Stored expiration canot be null");
final long lastAttempt = exp - Math.max(lockoutDuration, counterInterval);
final long timeDifference = System.currentTimeMillis() - lastAttempt;
if (timeDifference <= lockoutDuration) {
log.info("Lockout threshold reached for '{}', invalid count is {}", key, counter);
- if (extendLockoutDuration) {
- doIncrement(profileRequestContext, key, 10);
+ if (increment) {
+ doIncrement(profileRequestContext, key, 10, lockoutDuration, counterInterval);
}
return true;
}
@@ -265,34 +393,7 @@ public class StorageBackedAccountLockoutManager extends AbstractIdentifiableInit
return false;
}
-
- /** {@inheritDoc} */
- public boolean increment(@Nonnull final ProfileRequestContext profileRequestContext) {
- // Work is done by helper method to track storage retries.
- final String key = getLockoutKeyStrategy().apply(profileRequestContext);
- if (key == null) {
- log.warn("No lockout key returned for request");
- return false;
- }
-
- return doIncrement(profileRequestContext, key, 10);
- }
-
- /** {@inheritDoc} */
- public boolean clear(@Nonnull final ProfileRequestContext profileRequestContext) {
- try {
- final String key = getLockoutKeyStrategy().apply(profileRequestContext);
- if (key != null) {
- log.debug("Clearing lockout state for '{}'", key);
- storageService.delete(ensureId(), key);
- return true;
- }
- log.warn("No lockout key returned for request");
- } catch (final IOException e) {
- log.error("Error deleting lockout entry", e);
- }
- return false;
- }
+// Checkstyle: ParameterNum ON
// Checkstyle: CyclomaticComplexity|MethodLength OFF
/**
@@ -301,11 +402,14 @@ public class StorageBackedAccountLockoutManager extends AbstractIdentifiableInit
* @param profileRequestContext current profile request context
* @param key account lockout key
* @param retries number of additional retries to allow
+ * @param lockoutDuration duration of lockout
+ * @param counterInterval interval before disregarding attempts
*
* @return true iff successful
*/
protected boolean doIncrement(@Nonnull final ProfileRequestContext profileRequestContext,
- @Nonnull @NotEmpty final String key, final int retries) {
+ @Nonnull @NotEmpty final String key, final int retries, final long lockoutDuration,
+ final long counterInterval) {
if (retries <= 0) {
log.error("Account lockout increment attempts for '{}' exceeded retry limit", key);
@@ -333,8 +437,6 @@ public class StorageBackedAccountLockoutManager extends AbstractIdentifiableInit
}
final long now = System.currentTimeMillis();
- final long lockoutDuration = lockoutDurationLookupStrategy.apply(profileRequestContext).toMillis();
- final long counterInterval = counterIntervalLookupStrategy.apply(profileRequestContext).toMillis();
// Compute last access time by backing off from record expiration.
long lastAccess = now;
@@ -374,7 +476,7 @@ public class StorageBackedAccountLockoutManager extends AbstractIdentifiableInit
}
}
- return doIncrement(profileRequestContext, key, retries-1);
+ return doIncrement(profileRequestContext, key, retries-1, lockoutDuration, counterInterval);
}
// Checkstyle: CyclomaticComplexity|MethodLength ON
@@ -417,12 +519,6 @@ public class StorageBackedAccountLockoutManager extends AbstractIdentifiableInit
return null;
}
- final LockoutManagerContext lockoutManagerContext =
- profileRequestContext.getSubcontext(LockoutManagerContext.class);
- if (lockoutManagerContext != null) {
- return lockoutManagerContext.getKey();
- }
-
if (getHttpServletRequest() == null) {
return null;
}
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/StorageBackedAccountLockoutManagerTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/StorageBackedAccountLockoutManagerTest.java
index 3a2d71c78..af066dcc9 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/StorageBackedAccountLockoutManagerTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/StorageBackedAccountLockoutManagerTest.java
@@ -32,6 +32,7 @@ import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.testing.ConstantSupplier;
/** {@link StorageBackedAccountLockoutManager} unit test. */
+ at SuppressWarnings("javadoc")
public class StorageBackedAccountLockoutManagerTest extends BaseAuthenticationContextTest {
private StorageBackedAccountLockoutManager manager;
@@ -114,4 +115,4 @@ public class StorageBackedAccountLockoutManagerTest extends BaseAuthenticationCo
Assert.assertFalse(manager.check(prc));
}
-}
+}
\ No newline at end of file
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/admin/lockout-flow.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/admin/lockout-flow.xml
index 9a1356025..c9ef89925 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/admin/lockout-flow.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/admin/lockout-flow.xml
@@ -6,6 +6,7 @@
<on-start>
<!-- Extract PATH_INFO containing lockout manager and key. -->
<evaluate expression="flowRequestContext.getActiveFlow().getId()" result="flowScope.flowId" />
+ <evaluate expression="externalContext.getNativeRequest().getParameter('inexact')" result="flowScope.inexact" />
<evaluate expression="externalContext.getNativeRequest().getPathInfo().length() gt flowId.length() + 2 ? externalContext.getNativeRequest().getPathInfo().substring(flowId.length() + 2) : ''" result="flowScope.pathInfo" />
<evaluate expression="pathInfo.split('/')" result="flowScope.pathInfoArray" />
<evaluate expression="pathInfoArray.length gt 0 ? T(net.shibboleth.shared.net.URISupport).doURLDecode(pathInfoArray[0]) : null" result="flowScope.lockoutManagerId" />
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list