[java-identity-provider] branch maint-4 updated: IDP-1981 - Build Predicate based on RevocationCache
Scott Cantor
cantor.2 at osu.edu
Thu Jul 28 11:56:16 UTC 2022
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch maint-4
in repository java-identity-provider.
View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=ff62ec54b189cad1a5d8a38c46f79ebe97075bc9
The following commit(s) were added to refs/heads/maint-4 by this push:
new ff62ec54b IDP-1981 - Build Predicate based on RevocationCache
ff62ec54b is described below
commit ff62ec54b189cad1a5d8a38c46f79ebe97075bc9
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu Jul 28 07:56:09 2022 -0400
IDP-1981 - Build Predicate based on RevocationCache
https://shibboleth.atlassian.net/browse/IDP-1981
Implemented as a new feature for login flows.
Bumped IdP to OpenSAML 4.3.0 to pick up RevocationCache changes.
---
.../idp/authn/AuthenticationFlowDescriptor.java | 35 +++-
.../shibboleth/idp/authn/AuthenticationResult.java | 34 +++-
.../idp/authn/context/RevocationContext.java | 54 ++++++
.../authn/context/logic/RevocationCondition.java | 188 +++++++++++++++++++++
.../idp/authn/context/logic/package-info.java | 22 +++
.../idp/authn/impl/SelectAuthenticationFlow.java | 9 +-
.../impl/TransitionMultiFactorAuthentication.java | 2 +-
.../idp/authn/impl/RevocationConditionTest.java | 111 ++++++++++++
.../net/shibboleth/idp/conf/authn-system.xml | 19 ++-
.../src/main/resources/conf/authn/authn.properties | 14 ++
idp-parent/pom.xml | 2 +-
11 files changed, 478 insertions(+), 12 deletions(-)
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptor.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptor.java
index 49eaad7f4..1f5ba2007 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptor.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptor.java
@@ -28,6 +28,7 @@ import java.util.Comparator;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
+import java.util.function.BiPredicate;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
@@ -98,6 +99,9 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
/** Whether this flow allows reuse of its results. */
@Nonnull private Predicate<ProfileRequestContext> reuseCondition;
+
+ /** Whether a result from this flow should be considered revoked. */
+ @Nullable private BiPredicate<ProfileRequestContext,AuthenticationResult> revocationCondition;
/** Maximum amount of time since first usage that a flow should be considered active. */
@Nullable private Duration lifetime;
@@ -303,7 +307,7 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
@Nonnull public Predicate<ProfileRequestContext> getReuseCondition() {
return reuseCondition;
}
-
+
/**
* Set condition controlling whether results from this flow should be reused for SSO.
*
@@ -322,6 +326,31 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
Constraint.isNotNull(condition, "Predicate cannot be null"));
}
+ /**
+ * Get condition controlling whether a result from this flow should be considered revoked.
+ *
+ * @return condition
+ *
+ * @since 4.3.0
+ */
+ @Nonnull public BiPredicate<ProfileRequestContext,AuthenticationResult> getRevocationCondition() {
+ return revocationCondition;
+ }
+
+ /**
+ * Set condition controlling whether a result from this flow should be considered revoked.
+ *
+ * @param condition condition to set
+ *
+ * @since 4.3.0
+ */
+ public void setRevocationCondition(
+ @Nullable final BiPredicate<ProfileRequestContext,AuthenticationResult> condition) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ revocationCondition = condition;
+ }
+
/**
* Gets a subject decorating component called prior to completing authentication and passing
* control to subject canonicalization.
@@ -565,6 +594,8 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
} else {
result.setReuseCondition(reuseCondition);
}
+
+ result.setRevocationCondition(revocationCondition);
return result;
}
@@ -593,6 +624,8 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
} else {
result.setReuseCondition(reuseCondition);
}
+
+ result.setRevocationCondition(revocationCondition);
return result;
}
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationResult.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationResult.java
index c471955c3..0faf02887 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationResult.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationResult.java
@@ -24,6 +24,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.function.BiPredicate;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
@@ -54,7 +55,7 @@ import com.google.common.base.MoreObjects;
* that make up a single overall result, but the IdP always acts on a single result as the
* product of a given request for a login.</p>
*/
-public class AuthenticationResult implements PrincipalSupportingComponent {
+public class AuthenticationResult implements PrincipalSupportingComponent, Predicate<ProfileRequestContext> {
/** The Subject established by the authentication result. */
@Nonnull private final Subject subject;
@@ -77,6 +78,9 @@ public class AuthenticationResult implements PrincipalSupportingComponent {
/** Whether this result can be reused. */
@Nonnull private Predicate<ProfileRequestContext> reuseCondition;
+ /** Whether this result should be considered revoked. */
+ @Nonnull private BiPredicate<ProfileRequestContext,AuthenticationResult> revocationCondition;
+
/**
* Constructor.
*
@@ -109,7 +113,7 @@ public class AuthenticationResult implements PrincipalSupportingComponent {
}
/**
- * Get condition controlling whether this result should be reused for SSO.
+ * Gets condition controlling whether this result should be reused for SSO.
*
* @return condition controlling whether result should be reused for SSO
*
@@ -120,7 +124,7 @@ public class AuthenticationResult implements PrincipalSupportingComponent {
}
/**
- * Set condition controlling whether this result should be reused for SSO.
+ * Sets condition controlling whether this result should be reused for SSO.
*
* @param condition condition to set
*
@@ -131,7 +135,28 @@ public class AuthenticationResult implements PrincipalSupportingComponent {
}
/**
- * Get the Subject identifying the authenticated entity.
+ * Sets condition controlling whether this result has been revoked subsequent to creation.
+ *
+ * @param condition condition to set
+ *
+ * @since 4.3.0
+ */
+ public void setRevocationCondition(
+ @Nullable final BiPredicate<ProfileRequestContext,AuthenticationResult> condition) {
+ revocationCondition = condition;
+ }
+
+ /** {@inheritDoc} */
+ public boolean test(@Nullable final ProfileRequestContext input) {
+ if (reuseCondition.test(input)) {
+ return revocationCondition != null ? !revocationCondition.test(input, this) : true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets the Subject identifying the authenticated entity.
*
* @return a Subject identifying the authenticated entity
*/
@@ -341,4 +366,5 @@ public class AuthenticationResult implements PrincipalSupportingComponent {
}
}
+
}
\ No newline at end of file
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/RevocationContext.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/RevocationContext.java
new file mode 100644
index 000000000..f62bd07e5
--- /dev/null
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/RevocationContext.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.idp.authn.context;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.Live;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+
+/**
+ * Context for caching information about revocation of authentication results.
+ *
+ * @since 4.3.0
+ */
+public class RevocationContext extends BaseContext {
+
+ /** Revocation records. */
+ @Nonnull @NonnullElements private final Collection<String> revocationRecords;
+
+ /** Constructor. */
+ public RevocationContext() {
+ revocationRecords = new ArrayList<>(2);
+ }
+
+ /**
+ * Gets the modifiable collection of revocation records.
+ *
+ * @return modifiable revocation record collection
+ */
+ @Nonnull @NonnullElements @Live public Collection<String> getRevocationRecords() {
+ return revocationRecords;
+ }
+
+}
\ No newline at end of file
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/logic/RevocationCondition.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/logic/RevocationCondition.java
new file mode 100644
index 000000000..dbe54cbb1
--- /dev/null
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/logic/RevocationCondition.java
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.idp.authn.context.logic;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.util.Collection;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.servlet.http.HttpServletRequest;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.storage.RevocationCache;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.idp.authn.AuthenticationResult;
+import net.shibboleth.idp.authn.context.RevocationContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * A condition for login flows that checks for revocation.
+ *
+ * @since 4.3.0
+ */
+public class RevocationCondition extends AbstractInitializableComponent
+ implements BiPredicate<ProfileRequestContext,AuthenticationResult> {
+
+ /** Revocation context. */
+ @Nonnull @NotEmpty public static final String REVOCATION_CONTEXT = "LoginFlowRevocation";
+
+ /** Prefix of keys for principal-based revocation. */
+ @Nonnull @NotEmpty public static final String PRINCIPAL_REVOCATION_PREFIX = "prin:";
+
+ /** Prefix of keys for address-based revocation. */
+ @Nonnull @NotEmpty public static final String ADDRESS_REVOCATION_PREFIX = "addr:";
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(RevocationCondition.class);
+
+ /** Cache to use. */
+ @NonnullAfterInit private RevocationCache revocationCache;
+
+ /** Lookup strategy for principal name. */
+ @NonnullAfterInit private Function<ProfileRequestContext,String> principalNameLookupStrategy;
+
+ /** Servlet request. */
+ @Nullable private HttpServletRequest httpServletRequest;
+
+ /**
+ * Set {@link RevocationCache} to use.
+ *
+ * @param cache cache to use
+ */
+ public void setRevocationCache(@Nonnull final RevocationCache cache) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ revocationCache = Constraint.isNotNull(cache, "RevocationCache cannot be null");
+ }
+
+ /**
+ * Set lookup strategy for principal name.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setPrincipalNameLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ principalNameLookupStrategy = Constraint.isNotNull(strategy, "Principal name lookup strategy cannot be null");
+ }
+
+ /**
+ * Set {@link HttpServletRequest} in order to obtain client address.
+ *
+ * @param request servlet request interface
+ */
+ public void setHttpServletRequest(@Nullable final HttpServletRequest request) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ httpServletRequest = request;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (revocationCache == null) {
+ throw new ComponentInitializationException("RevocationCache cannot be null");
+ } else if (principalNameLookupStrategy == null) {
+ throw new ComponentInitializationException("Principal name lookup strategy cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ public boolean test(@Nullable final ProfileRequestContext input, @Nullable final AuthenticationResult input2) {
+
+ if (input == null || input2 == null) {
+ log.error("Called with null inputs");
+ return true;
+ }
+
+ final String principal = principalNameLookupStrategy.apply(input);
+ if (principal == null) {
+ log.error("Principal lookup strategy returned null value");
+ return true;
+ }
+
+ log.debug("Checking revocation for principal name {} for {} result", principal,
+ input2.getAuthenticationFlowId());
+
+ RevocationContext context = input.getSubcontext(RevocationContext.class);
+ if (context == null) {
+ try {
+ final String principalRecord = revocationCache.getRevocationRecord(REVOCATION_CONTEXT,
+ PRINCIPAL_REVOCATION_PREFIX + principal);
+ final String addressRecord = httpServletRequest != null ?
+ revocationCache.getRevocationRecord(REVOCATION_CONTEXT,
+ ADDRESS_REVOCATION_PREFIX + httpServletRequest.getRemoteAddr()) :
+ null;
+ context = input.getSubcontext(RevocationContext.class, true);
+ if (principalRecord != null) {
+ context.getRevocationRecords().add(principalRecord);
+ }
+ if (addressRecord != null) {
+ context.getRevocationRecords().add(addressRecord);
+ }
+ } catch (final IOException e) {
+ log.error("Error checking revocation cache for principal {}, treating as revoked",
+ principal, e);
+ return true;
+ }
+ }
+
+ return isRevoked(principal, input2, context.getRevocationRecords());
+ }
+
+ /**
+ * Check the revocation records' timestamps for applicability.
+ *
+ * @param principal name of principal
+ * @param result active result being checked
+ * @param revocationRecords the records from the cache
+ *
+ * @return true iff the revocation applies to this result
+ */
+ protected boolean isRevoked(@Nonnull @NotEmpty final String principal, @Nonnull final AuthenticationResult result,
+ @Nonnull @NonnullElements final Collection<String> revocationRecords) {
+
+ for (final String r : revocationRecords) {
+ if (result.getAuthenticationInstant().isBefore(Instant.ofEpochSecond(Long.valueOf(r)))) {
+ log.info("Authentication result {} for principal {} has been revoked", result.getAuthenticationFlowId(),
+ principal);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/logic/package-info.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/logic/package-info.java
new file mode 100644
index 000000000..6237f69a5
--- /dev/null
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/logic/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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.
+ */
+
+/**
+ * Predicates related to authentication.
+ */
+
+package net.shibboleth.idp.authn.context.logic;
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/SelectAuthenticationFlow.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/SelectAuthenticationFlow.java
index c530a1a04..be7925218 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/SelectAuthenticationFlow.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/SelectAuthenticationFlow.java
@@ -201,7 +201,7 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
AuthenticationResult activeResult = null;
if (!authenticationContext.isForceAuthn()) {
activeResult = authenticationContext.getActiveResults().get(flow.getId());
- if (!activeResult.getReuseCondition().test(profileRequestContext)) {
+ if (!activeResult.test(profileRequestContext)) {
log.debug("{} Active result for flow {} not reusable, ignoring", getLogPrefix(),
activeResult.getAuthenticationFlowId());
activeResult = null;
@@ -293,7 +293,7 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
AuthenticationResult resultToSelect = null;
for (final AuthenticationResult activeResult : authenticationContext.getActiveResults().values()) {
- if (activeResult.getReuseCondition().test(profileRequestContext)) {
+ if (activeResult.test(profileRequestContext)) {
resultToSelect = activeResult;
if (preferredPrincipalCtx == null || preferredPrincipalCtx.isAcceptable(activeResult)) {
break;
@@ -482,7 +482,8 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
final PrincipalEvalPredicate predicate = requestedPrincipalCtx.getPredicate(p);
if (predicate != null) {
for (final AuthenticationResult result : activeResults.values()) {
- if (result.getReuseCondition().test(profileRequestContext) && predicate.test(result)) {
+ if (result.test(profileRequestContext) &&
+ predicate.test(result)) {
selectActiveResult(profileRequestContext, authenticationContext, result);
return;
}
@@ -520,7 +521,7 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
// Now check for an active result we can use from this flow. Not all results from a flow
// will necessarily match the request just because the flow might.
final AuthenticationResult result = activeResults.get(descriptor.getId());
- if (result == null || !result.getReuseCondition().test(profileRequestContext)
+ if (result == null || !result.test(profileRequestContext)
|| !predicate.test(result)) {
if (result != null) {
log.debug("{} Active result for flow {} not usable, ignoring", getLogPrefix(),
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/TransitionMultiFactorAuthentication.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/TransitionMultiFactorAuthentication.java
index cb1b87659..c8ba2a46f 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/TransitionMultiFactorAuthentication.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/TransitionMultiFactorAuthentication.java
@@ -272,7 +272,7 @@ public class TransitionMultiFactorAuthentication extends AbstractAuthenticationA
// infinite recursion is the configuration of transitions supplied by the deployer.
final AuthenticationResult activeResult = mfaContext.getActiveResults().get(flowId);
if (activeResult != null) {
- if (activeResult.getReuseCondition().test(profileRequestContext)) {
+ if (activeResult.test(profileRequestContext)) {
log.debug("{} Reusing active result for flow {}", getLogPrefix(), flowId);
activeResult.setLastActivityInstantToNow();
previousEvent = EventIds.PROCEED_EVENT_ID;
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/RevocationConditionTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/RevocationConditionTest.java
new file mode 100644
index 000000000..5afc5f8c1
--- /dev/null
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/RevocationConditionTest.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.idp.authn.impl;
+
+import java.time.Duration;
+import java.util.Arrays;
+
+import javax.security.auth.Subject;
+
+import net.shibboleth.idp.authn.AuthenticationResult;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.logic.RevocationCondition;
+import net.shibboleth.idp.authn.impl.testing.BaseAuthenticationContextTest;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.logic.FunctionSupport;
+
+import org.joda.time.Instant;
+import org.opensaml.storage.RevocationCache;
+import org.opensaml.storage.impl.MemoryStorageService;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/** {@link RevocationCondition} unit test. */
+public class RevocationConditionTest extends BaseAuthenticationContextTest {
+
+ private MemoryStorageService storageService;
+ private RevocationCache revocationCache;
+ private RevocationCondition condition;
+
+ @BeforeMethod
+ public void setUp() throws ComponentInitializationException {
+ super.setUp();
+
+ storageService = new MemoryStorageService();
+ storageService.setId("test");
+ storageService.setCleanupInterval(Duration.ZERO);
+ storageService.initialize();
+
+ revocationCache = new RevocationCache();
+ revocationCache.setStorage(storageService);
+ revocationCache.setId("test");
+ revocationCache.initialize();
+
+ condition = new RevocationCondition();
+ condition.setRevocationCache(revocationCache);
+ condition.setPrincipalNameLookupStrategy(FunctionSupport.constant("jdoe"));
+ condition.initialize();
+
+ authenticationFlows.get(1).setRevocationCondition(condition);
+ }
+
+ @AfterMethod
+ public void tearDown() {
+ condition.destroy();
+ revocationCache.destroy();
+ storageService.destroy();
+ }
+
+
+ @Test public void testNotRevoked() {
+ final AuthenticationResult active = authenticationFlows.get(1).newAuthenticationResult(new Subject());
+ final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
+ authCtx.setActiveResults(Arrays.asList(active));
+
+ Assert.assertTrue(active.test(prc));
+ }
+
+ @Test public void testRevoked() {
+ final AuthenticationResult active = authenticationFlows.get(1).newAuthenticationResult(new Subject());
+ final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
+ authCtx.setActiveResults(Arrays.asList(active));
+
+ revocationCache.revoke(RevocationCondition.REVOCATION_CONTEXT,
+ RevocationCondition.PRINCIPAL_REVOCATION_PREFIX + "jdoe",
+ Long.toString(Instant.now().getMillis() / 1000 + 3600L),
+ Duration.ofDays(1));
+
+ Assert.assertFalse(active.test(prc));
+ }
+
+ @Test public void testPastRevoked() {
+ final AuthenticationResult active = authenticationFlows.get(1).newAuthenticationResult(new Subject());
+ final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
+ authCtx.setActiveResults(Arrays.asList(active));
+
+ revocationCache.revoke(RevocationCondition.REVOCATION_CONTEXT,
+ RevocationCondition.PRINCIPAL_REVOCATION_PREFIX + "jdoe",
+ Long.toString(Instant.now().getMillis() / 1000 - 3600L),
+ Duration.ofDays(1));
+
+ Assert.assertTrue(active.test(prc));
+ }
+
+}
\ No newline at end of file
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/authn-system.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/authn-system.xml
index c495e30e3..918cafb22 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/authn-system.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/authn-system.xml
@@ -27,7 +27,8 @@
p:lifetime="%{idp.authn.defaultLifetime:PT60M}"
p:inactivityTimeout="%{idp.authn.defaultTimeout:PT30M}"
p:principalWeightMap="#{getObject('shibboleth.AuthenticationPrincipalWeightMap') ?: getObject('shibboleth.DefaultAuthenticationPrincipalWeightMap')}"
- p:principalServiceManager-ref="shibboleth.PrincipalServiceManager">
+ p:principalServiceManager-ref="shibboleth.PrincipalServiceManager"
+ p:revocationCondition="#{%{idp.authn.revocation:false} ? getObject('%{idp.authn.revocation.Condition:shibboleth.RevocationCondition}'.trim()) : null}">
<property name="supportedPrincipals">
<list>
<bean parent="shibboleth.SAML2AuthnContextClassRef"
@@ -533,4 +534,20 @@
<bean id="shibboleth.SAMLACDeclRefBetter" parent="shibboleth.Pair"
p:first="#{ T(net.shibboleth.idp.saml.authn.principal.AuthnContextDeclRefPrincipal) }" p:second="better"/>
+ <!-- Revocation feature. -->
+
+ <bean id="shibboleth.RevocationCondition" class="net.shibboleth.idp.authn.context.logic.RevocationCondition" lazy-init="true"
+ p:revocationCache-ref="%{idp.authn.revocation.cache:shibboleth.AuthnRevocationCache}"
+ p:httpServletRequest="#{%{idp.authn.revocation.addressBased:false} ? getObject('shibboleth.HttpServletRequest') : null}"
+ p:principalNameLookupStrategy-ref="shibboleth.RevocationPrincipalLookupStrategy" />
+
+ <bean id="shibboleth.RevocationPrincipalLookupStrategy" parent="shibboleth.Functions.Compose" lazy-init="true"
+ c:g-ref="shibboleth.PrincipalNameLookup.Session"
+ c:f-ref="shibboleth.ChildLookup.SessionContext" />
+
+ <bean id="shibboleth.AuthnRevocationCache" class="org.opensaml.storage.RevocationCache" lazy-init="true"
+ p:entryExpiration="#{'%{idp.authn.revocation.lifetime:%{idp.authn.defaultLifetime:PT12H}}'}"
+ p:storage-ref="#{'%{idp.authn.revocation.StorageService:shibboleth.StorageService}'.trim()}"
+ p:strict="%{idp.authn.revocation.strict:false}" />
+
</beans>
diff --git a/idp-conf/src/main/resources/conf/authn/authn.properties b/idp-conf/src/main/resources/conf/authn/authn.properties
index 97a75257e..86de8ee7f 100644
--- a/idp-conf/src/main/resources/conf/authn/authn.properties
+++ b/idp-conf/src/main/resources/conf/authn/authn.properties
@@ -24,6 +24,20 @@
# If using IdP discovery feature, provides a discovery location to use.
#idp.authn.discoveryURL = https://ds.example.org/shibboleth-ds/index.html
+# Revocation (administrative logout)
+#idp.authn.revocation = false
+#idp.authn.revocation.lifetime = %{idp.authn.defaultAuthnLifetime:PT12H}
+# Name of BiCondition to apply for check
+#idp.authn.revocation.Condition = shibboleth.RevocationCondition
+# Set to true to treat lookup failures as being revoked.
+#idp.authn.revocation.strict = false
+# Set to true to check for address-based revocation.
+#idp.authn.revocation.addressBased = false
+# Default implementation based on a StorageService bean.
+#idp.authn.revocation.cache = shibboleth.AuthnRevocationCache
+#idp.authn.revocation.StorageService = shibboleth.StorageService
+
+
# Properties below override specific method behavior, as an alternative
# to defining Spring beans in XML. Refer to the documentation for a complete
# list. Many of the properties below are mentioned only because they are
diff --git a/idp-parent/pom.xml b/idp-parent/pom.xml
index d804f1970..c46a3ad77 100644
--- a/idp-parent/pom.xml
+++ b/idp-parent/pom.xml
@@ -70,7 +70,7 @@
<idwsfconsumer.version>2.1.0</idwsfconsumer.version>
<java-support.version>8.3.2-SNAPSHOT</java-support.version>
<opensaml.groupId>org.opensaml</opensaml.groupId>
- <opensaml.version>4.2.1-SNAPSHOT</opensaml.version>
+ <opensaml.version>4.3.0-SNAPSHOT</opensaml.version>
<spring-extensions.version>6.2.1-SNAPSHOT</spring-extensions.version>
<checkstyle.configLocation>${project.basedir}/../idp-parent/resources/checkstyle/checkstyle.xml</checkstyle.configLocation>
<idp-parent.site.url>${shibboleth.site.deploy.url}java-identity-provider/${project.version}/</idp-parent.site.url>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list