[java-identity-provider] 01/02: IDP-1988 - Build Predicate based on AttributeResolver
Scott Cantor
cantor.2 at osu.edu
Mon Aug 1 17:17:03 UTC 2022
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=a1712ef4bb2e5add1e5da8fc15d7d5c09023a336
commit a1712ef4bb2e5add1e5da8fc15d7d5c09023a336
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Mon Aug 1 13:04:56 2022 -0400
IDP-1988 - Build Predicate based on AttributeResolver
https://shibboleth.atlassian.net/browse/IDP-1988
Also fix Instant class used in unit test.
---
.../impl/AttributeRevocationCondition.java | 265 +++++++++++++++++++++
.../impl/AttributeRevocationConditionTest.java | 172 +++++++++++++
.../impl/RevocationCacheConditionTest.java | 6 +-
.../net/shibboleth/idp/conf/authn-system.xml | 9 +-
.../src/main/resources/conf/authn/authn.properties | 2 +-
5 files changed, 448 insertions(+), 6 deletions(-)
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/revocation/impl/AttributeRevocationCondition.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/revocation/impl/AttributeRevocationCondition.java
new file mode 100644
index 000000000..311cd28e4
--- /dev/null
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/revocation/impl/AttributeRevocationCondition.java
@@ -0,0 +1,265 @@
+/*
+ * 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.revocation.impl;
+
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.ScratchContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.resolver.AttributeResolver;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.authn.AuthenticationResult;
+import net.shibboleth.idp.profile.context.navigate.RelyingPartyIdLookupFunction;
+import net.shibboleth.idp.profile.context.navigate.ResponderIdLookupFunction;
+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;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+import net.shibboleth.utilities.java.support.service.ReloadableService;
+
+/**
+ * A condition for login flows that checks for revocation against a resolved
+ * {@link IdPAttribute}.
+ *
+ * @since 4.3.0
+ */
+public class AttributeRevocationCondition extends AbstractInitializableComponent
+ implements BiPredicate<ProfileRequestContext,AuthenticationResult> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AttributeRevocationCondition.class);
+
+ /** Lookup strategy for principal name. */
+ @NonnullAfterInit private Function<ProfileRequestContext,String> principalNameLookupStrategy;
+
+ /** Strategy used to locate the identity of the issuer associated with the attribute resolution. */
+ @Nullable private Function<ProfileRequestContext,String> issuerLookupStrategy;
+
+ /** Strategy used to locate the identity of the recipient associated with the attribute resolution. */
+ @Nullable private Function<ProfileRequestContext,String> recipientLookupStrategy;
+
+ /** Attribute Resolver service. */
+ @NonnullAfterInit private ReloadableService<AttributeResolver> attributeResolver;
+
+ /** Attribute ID to resolve. */
+ @NonnullAfterInit @NotEmpty private String attributeId;
+
+ /** Constructor. */
+ public AttributeRevocationCondition() {
+ issuerLookupStrategy = new ResponderIdLookupFunction();
+ recipientLookupStrategy = new RelyingPartyIdLookupFunction();
+ }
+
+ /**
+ * 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 the strategy used to lookup the issuer for this attribute resolution.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setIssuerLookupStrategy(@Nullable final Function<ProfileRequestContext,String> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ issuerLookupStrategy = strategy;
+ }
+
+ /**
+ * Set the strategy used to lookup the recipient for this attribute resolution.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setRecipientLookupStrategy(@Nullable final Function<ProfileRequestContext,String> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ recipientLookupStrategy = strategy;
+ }
+
+ /**
+ * Set {@link AttributeResolver} to use.
+ *
+ * @param service attribute resolver service
+ */
+ public void setAttributeResolver(@Nonnull final ReloadableService<AttributeResolver> service) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ attributeResolver = Constraint.isNotNull(service, "ReloadableService<AttributeResolver> cannot be null");
+ }
+
+ /**
+ * Set the ID of an {@link IdPAttribute} to resolve to obtain revocation records for the principal.
+ *
+ * @param id attribute ID to resolve
+ */
+ public void setAttributeId(@Nonnull @NotEmpty final String id) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ attributeId = Constraint.isNotNull(StringSupport.trimOrNull(id), "Attribute ID cannot be null or empty");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (attributeResolver == null) {
+ throw new ComponentInitializationException("ReloadableService<AttributeResolver> cannot be null");
+ } else if (principalNameLookupStrategy == null) {
+ throw new ComponentInitializationException("Principal name lookup strategy cannot be null");
+ } else if (attributeId == null) {
+ throw new ComponentInitializationException("Attribute ID to resolve cannot be null or empty");
+ }
+ }
+
+ /** {@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 via attribute resolver", principal,
+ input2.getAuthenticationFlowId());
+
+ final ScratchContext context = input.getSubcontext(ScratchContext.class, true);
+
+ final AttributeResolutionContext resolutionContext = buildResolutionContext(input, principal);
+
+ if (!context.getMap().containsKey(getClass())) {
+ resolutionContext.resolveAttributes(attributeResolver);
+ final Collection<Instant> records = new ArrayList<>();
+ if (resolutionContext.getResolvedIdPAttributes().containsKey(attributeId)) {
+ for (final IdPAttributeValue value :
+ resolutionContext.getResolvedIdPAttributes().get(attributeId).getValues()) {
+ if (value instanceof StringAttributeValue) {
+ try {
+ records.add(Instant.ofEpochSecond(Long.valueOf(((StringAttributeValue) value).getValue())));
+
+ } catch (final NumberFormatException|DateTimeException e) {
+ log.error("Error parsing timestamp '{}' into epoch",
+ ((StringAttributeValue) value).getValue(), e);
+ }
+
+ } else {
+ log.warn("Ignoring non-string attribute value type: {}", value.getClass().getName());
+ }
+ }
+ } else {
+ log.debug("Resolver did not return an IdPAttribute named {} for principal {}", attributeId, principal);
+ }
+
+ context.getMap().put(getClass(), records);
+ resolutionContext.getParent().removeSubcontext(resolutionContext);
+ }
+
+ return isRevoked(principal, input2, (Collection<Instant>) context.getMap().get(getClass()));
+ }
+
+ /**
+ * Build an {@link AttributeResolutionContext} to use.
+ *
+ * @param profileRequestContext profile request context
+ * @param principal name of principal
+ *
+ * @return the attached context
+ */
+ @Nonnull private AttributeResolutionContext buildResolutionContext(
+ @Nonnull final ProfileRequestContext profileRequestContext, @Nonnull @NotEmpty final String principal) {
+
+ final AttributeResolutionContext resolutionContext = new AttributeResolutionContext();
+
+ resolutionContext
+ .setPrincipal(principal)
+ .setResolutionLabel("authn/revocation")
+ .setRequestedIdPAttributeNames(Collections.singletonList(attributeId));
+
+ if (recipientLookupStrategy != null) {
+ resolutionContext.setAttributeRecipientID(recipientLookupStrategy.apply(profileRequestContext));
+ }
+
+ if (issuerLookupStrategy != null) {
+ resolutionContext.setAttributeIssuerID(issuerLookupStrategy.apply(profileRequestContext));
+ }
+
+ profileRequestContext.addSubcontext(resolutionContext, true);
+ return resolutionContext;
+ }
+
+ /**
+ * 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<Instant> revocationRecords) {
+
+ for (final Instant i : revocationRecords) {
+ if (result.getAuthenticationInstant().isBefore(i)) {
+ 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-impl/src/test/java/net/shibboleth/idp/authn/revocation/impl/AttributeRevocationConditionTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/revocation/impl/AttributeRevocationConditionTest.java
new file mode 100644
index 000000000..db8763817
--- /dev/null
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/revocation/impl/AttributeRevocationConditionTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.revocation.impl;
+
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.stream.Collectors;
+
+import javax.security.auth.Subject;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.resolver.AttributeResolver;
+import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.authn.AuthenticationResult;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+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 net.shibboleth.utilities.java.support.service.ReloadableService;
+import net.shibboleth.utilities.java.support.service.ServiceableComponent;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/** {@link AttributeRevocationCondition} unit test. */
+public class AttributeRevocationConditionTest extends BaseAuthenticationContextTest {
+
+ private Collection<Instant> revocationsToResolve;
+
+ private AttributeRevocationCondition condition;
+
+ @BeforeMethod
+ public void setUp() throws ComponentInitializationException {
+ super.setUp();
+
+ condition = new AttributeRevocationCondition();
+ condition.setPrincipalNameLookupStrategy(FunctionSupport.constant("jdoe"));
+ condition.setAttributeResolver(new MockResolver());
+ condition.setAttributeId("revocation");
+ condition.initialize();
+
+ authenticationFlows.get(1).setRevocationCondition(condition);
+ }
+
+ @AfterMethod
+ public void tearDown() {
+ condition.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));
+
+ revocationsToResolve = Collections.singletonList(Instant.now().plusSeconds(3600));
+
+ 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));
+
+ revocationsToResolve = Collections.singletonList(Instant.now().minusSeconds(3600));
+
+ Assert.assertTrue(active.test(prc));
+ }
+
+ /**
+ * Mock attribute source.
+ */
+ private class MockResolver implements ReloadableService<AttributeResolver> {
+
+ /** {@inheritDoc} */
+ public boolean isInitialized() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ public void initialize() throws ComponentInitializationException {
+ }
+
+ /** {@inheritDoc} */
+ public Instant getLastSuccessfulReloadInstant() {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public Instant getLastReloadAttemptInstant() {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public Throwable getReloadFailureCause() {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public void reload() {
+ }
+
+ /** {@inheritDoc} */
+ public ServiceableComponent<AttributeResolver> getServiceableComponent() {
+ return new ServiceableComponent<AttributeResolver>() {
+
+ public AttributeResolver getComponent() {
+ return new AttributeResolver() {
+
+ public String getId() {
+ return "test";
+ }
+
+ public void resolveAttributes(AttributeResolutionContext resolutionContext)
+ throws ResolutionException {
+ if ("jdoe".equals(resolutionContext.getPrincipal()) && revocationsToResolve != null) {
+ final IdPAttribute attr = new IdPAttribute("revocation");
+ attr.setValues(
+ revocationsToResolve.stream()
+ .map(i -> StringAttributeValue.valueOf(Long.toString(i.getEpochSecond())))
+ .collect(Collectors.toUnmodifiableList())
+ );
+ resolutionContext.setResolvedIdPAttributes(Collections.singletonList(attr));
+ }
+ }
+ };
+ }
+
+ public void pinComponent() {
+ }
+
+ public void unpinComponent() {
+ }
+
+ public void unloadComponent() {
+ }
+ };
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/revocation/impl/RevocationCacheConditionTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/revocation/impl/RevocationCacheConditionTest.java
index 9a1021526..f0eec367e 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/revocation/impl/RevocationCacheConditionTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/revocation/impl/RevocationCacheConditionTest.java
@@ -18,6 +18,7 @@
package net.shibboleth.idp.authn.revocation.impl;
import java.time.Duration;
+import java.time.Instant;
import java.util.Arrays;
import javax.security.auth.Subject;
@@ -28,7 +29,6 @@ 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;
@@ -88,7 +88,7 @@ public class RevocationCacheConditionTest extends BaseAuthenticationContextTest
revocationCache.revoke(RevocationCacheCondition.REVOCATION_CONTEXT,
RevocationCacheCondition.PRINCIPAL_REVOCATION_PREFIX + "jdoe",
- Long.toString(Instant.now().getMillis() / 1000 + 3600L),
+ Long.toString(Instant.now().getEpochSecond() + 3600L),
Duration.ofDays(1));
Assert.assertFalse(active.test(prc));
@@ -101,7 +101,7 @@ public class RevocationCacheConditionTest extends BaseAuthenticationContextTest
revocationCache.revoke(RevocationCacheCondition.REVOCATION_CONTEXT,
RevocationCacheCondition.PRINCIPAL_REVOCATION_PREFIX + "jdoe",
- Long.toString(Instant.now().getMillis() / 1000 - 3600L),
+ Long.toString(Instant.now().getEpochSecond() - 3600L),
Duration.ofDays(1));
Assert.assertTrue(active.test(prc));
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 ab7d82961..8746328d7 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
@@ -28,7 +28,7 @@
p:inactivityTimeout="%{idp.authn.defaultTimeout:PT30M}"
p:principalWeightMap="#{getObject('shibboleth.AuthenticationPrincipalWeightMap') ?: getObject('shibboleth.DefaultAuthenticationPrincipalWeightMap')}"
p:principalServiceManager-ref="shibboleth.PrincipalServiceManager"
- p:revocationCondition="#{%{idp.authn.revocation:false} ? getObject('%{idp.authn.revocation.Condition:shibboleth.RevocationCondition}'.trim()) : null}">
+ p:revocationCondition="#{%{idp.authn.revocation:false} ? getObject('%{idp.authn.revocation.Condition:shibboleth.RevocationCacheCondition}'.trim()) : null}">
<property name="supportedPrincipals">
<list>
<bean parent="shibboleth.SAML2AuthnContextClassRef"
@@ -536,10 +536,15 @@
<!-- Revocation feature. -->
- <bean id="shibboleth.RevocationCondition" class="net.shibboleth.idp.authn.revocation.impl.RevocationCacheCondition" lazy-init="true"
+ <bean id="shibboleth.RevocationCacheCondition" class="net.shibboleth.idp.authn.revocation.impl.RevocationCacheCondition" 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.AttributeRevocationCondition" class="net.shibboleth.idp.authn.revocation.impl.AttributeRevocationCondition" lazy-init="true"
+ p:principalNameLookupStrategy-ref="shibboleth.RevocationPrincipalLookupStrategy"
+ p:attributeResolver-ref="shibboleth.AttributeResolverService"
+ p:attributeId="#{'%{idp.authn.revocation.attributeId:revocation}'.trim()}" />
<bean id="shibboleth.RevocationPrincipalLookupStrategy" parent="shibboleth.Functions.Compose" lazy-init="true"
c:g-ref="shibboleth.PrincipalNameLookup.Session"
diff --git a/idp-conf/src/main/resources/conf/authn/authn.properties b/idp-conf/src/main/resources/conf/authn/authn.properties
index 86de8ee7f..af2fdbc66 100644
--- a/idp-conf/src/main/resources/conf/authn/authn.properties
+++ b/idp-conf/src/main/resources/conf/authn/authn.properties
@@ -28,7 +28,7 @@
#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
+#idp.authn.revocation.Condition = shibboleth.RevocationCacheCondition
# Set to true to treat lookup failures as being revoked.
#idp.authn.revocation.strict = false
# Set to true to check for address-based revocation.
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list