[java-plugin-shibd] branch main updated: WIP on cached authentication.

Scott Cantor cantor.2 at osu.edu
Tue May 21 20:54:54 UTC 2024


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch main
in repository java-plugin-shibd.

View the commit online:
http://git.shibboleth.net/view/?p=java-plugin-shibd.git;a=commit;h=c02548640ddc7d26a400b70e9509cbed284e9374

The following commit(s) were added to refs/heads/main by this push:
     new c025486  WIP on cached authentication.
c025486 is described below

commit c02548640ddc7d26a400b70e9509cbed284e9374
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue May 21 16:54:51 2024 -0400

    WIP on cached authentication.
---
 .../shibboleth/idp/module/conf/sp/sp.properties    |   2 +
 .../net/shibboleth/sp/conf/agents-system.xml       |   3 +-
 .../src/main/java/net/shibboleth/sp/Agent.java     |   7 +
 .../sp/authn/impl/ValidateAgentAddress.java        |   1 +
 .../authn/impl/ValidateCachedAuthentication.java   | 194 +++++++++++++++++++++
 .../java/net/shibboleth/sp/impl/BasicAgent.java    |  24 +++
 6 files changed, 230 insertions(+), 1 deletion(-)

diff --git a/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/sp.properties b/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/sp.properties
index 3f3c11b..4581ed4 100644
--- a/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/sp.properties
+++ b/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/sp.properties
@@ -23,3 +23,5 @@ sp.encryption.cert = %{idp.home}/credentials/sp/sp-encryption.crt
 
 # Set to Basic to require shared secret authentication
 #sp.agent.authentication = None
+# Set false to globally disable cookie-based authentication by agents
+#sp.agent.cachedAuhentication = true
\ No newline at end of file
diff --git a/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/agents-system.xml b/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/agents-system.xml
index 38da7b3..a4ac0e1 100644
--- a/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/agents-system.xml
+++ b/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/agents-system.xml
@@ -21,7 +21,8 @@
     <!-- Parent beans for Agents and Applications. -->
 
     <bean id="shibboleth.Agent" class="net.shibboleth.sp.impl.BasicAgent" abstract="true"
-        p:allowedAddressRanges="#{{ '127.0.0.1/32', '::1/128' }}" />
+        p:allowedAddressRanges="#{{ '127.0.0.1/32', '::1/128' }}"
+        p:supportsCachedAuthentication="%{sp.agent.cachedAuhentication:true}" />
 
     <bean id="shibboleth.Application" class="net.shibboleth.sp.impl.BasicApplication" abstract="true"
         p:metadataResolver-ref="shibboleth.MetadataResolverService"
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/Agent.java b/sp-server-api/src/main/java/net/shibboleth/sp/Agent.java
index 04e4a3d..981e94d 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/Agent.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/Agent.java
@@ -55,6 +55,13 @@ public interface Agent extends IdentifiedComponent {
      * @return true iff the supplied address matches one of the allowed ranges
      */
     boolean isAllowed(@Nonnull final InetAddress address);
+    
+    /**
+     * Returns true iff the agent supports cached authentication via cookie.
+     * 
+     * @return true iff the agent supports cached authentication via cookie
+     */
+    boolean isSupportsCachedAuthentication();
 
     /**
      * Get the issuer value to use in various identity protocols when identifying this agent.
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/authn/impl/ValidateAgentAddress.java b/sp-server-impl/src/main/java/net/shibboleth/sp/authn/impl/ValidateAgentAddress.java
index ff93203..6256a03 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/authn/impl/ValidateAgentAddress.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/authn/impl/ValidateAgentAddress.java
@@ -86,6 +86,7 @@ public class ValidateAgentAddress extends AbstractProfileAction {
 
         agent = agentCtx != null ? agentCtx.getAgent() : null;
         if (agent == null) {
+            log.error("{} No Agent found in context", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return false;
         }
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/authn/impl/ValidateCachedAuthentication.java b/sp-server-impl/src/main/java/net/shibboleth/sp/authn/impl/ValidateCachedAuthentication.java
new file mode 100644
index 0000000..6358694
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/authn/impl/ValidateCachedAuthentication.java
@@ -0,0 +1,194 @@
+/*
+ * 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.sp.authn.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import jakarta.servlet.http.HttpServletRequest;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.net.CookieManager;
+import net.shibboleth.shared.net.URISupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.security.DataSealer;
+import net.shibboleth.shared.security.DataSealerException;
+import net.shibboleth.shared.servlet.HttpServletSupport;
+import net.shibboleth.sp.Agent;
+import net.shibboleth.sp.context.AgentRequestContext;
+
+/**
+ * An action that checks for a sealed cookie authenticating request without the need for
+ * validating a shared secret or other credentials.
+ * 
+ *  <p>The cookie is an address-bound bearer token containing the agent authorized to use it,
+ *  the destination, the address, an expiration, etc.</p>
+ *  
+ *  <p>TODO: adding a key proof via MAC in some way</p>
+ * 
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @pre <pre>ProfileRequestContext.ensureSubcontext(AgentRequestContext.class).getAgent() != null</pre>
+ */
+public class ValidateCachedAuthentication extends AbstractProfileAction {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateCachedAuthentication.class);
+    
+    /** Lookup strategy for {@link AgentRequestContext}. */
+    @Nonnull private Function<ProfileRequestContext,AgentRequestContext> agentRequestContextLookupStrategy;
+
+    /** Cookie name to use. */
+    @NonnullAfterInit private String cookieName;
+
+    /** CookieManager to use. */
+    @NonnullAfterInit private CookieManager cookieManager;
+
+    /** DataSealer to use. */
+    @NonnullAfterInit private DataSealer dataSealer;
+    
+    /** Cached agent from context. */
+    @NonnullBeforeExec private Agent agent;
+
+    /** Constructor. */
+    public ValidateCachedAuthentication() {
+        agentRequestContextLookupStrategy = new ChildContextLookup<>(AgentRequestContext.class);
+    }
+    
+    /**
+     * Sets the lookup strategy for the {@link AgentRequestContext}.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setAgentRequestContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,AgentRequestContext> strategy) {
+        checkSetterPreconditions();
+        
+        agentRequestContextLookupStrategy = Constraint.isNotNull(strategy,
+                "AgentRequestContext lookup strategy cannot be null");
+    }
+    
+    /**
+     * Sets the cookie name to use for cached authentication.
+     * 
+     * @param name cookie name
+     */
+    public void setCookieName(@Nonnull @NotEmpty final String name) {
+        checkSetterPreconditions();
+        
+        cookieName = Constraint.isNotNull(StringSupport.trimOrNull(name), "Cookie name cannot be null or empty");
+    }
+    
+    /**
+     * Sets the {@link CookieManager} to use.
+     * 
+     * @param manager cookie manager
+     */
+    public void setCookieManager(@Nonnull final CookieManager manager) {
+        checkSetterPreconditions();
+        
+        cookieManager = Constraint.isNotNull(manager, "CookieManager cannot be null");
+    }
+
+    /**
+     * Sets the {@link DataSealer} to use.
+     * 
+     * @param sealer data sealer
+     */
+    public void setDataSealer(@Nonnull final DataSealer sealer) {
+        checkSetterPreconditions();
+        
+        dataSealer = Constraint.isNotNull(sealer, "DataSealer cannot be null");
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        if (cookieName == null || cookieManager == null || dataSealer == null) {
+            throw new ComponentInitializationException("CookieManager, DataSealer, and cookie name must be set");
+        }
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+        
+        final AgentRequestContext agentCtx = agentRequestContextLookupStrategy.apply(profileRequestContext);
+        agent = agentCtx != null ? agentCtx.getAgent() : null;
+        if (agent == null) {
+            log.error("{} No Agent found in context", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+        
+        if (!agent.isSupportsCachedAuthentication()) {
+            log.debug("{} Agent '{}' does not support cached authentication, skipping", getLogPrefix(), agent.getId());
+            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+            return false;
+        }
+        
+        return true;
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        final HttpServletRequest request = getHttpServletRequest();
+        final String addr = request != null ? HttpServletSupport.getRemoteAddr(request) : null;
+        if (addr == null) {
+            log.warn("{} No client address for request from agent '{}', skipping cached authentication check", getLogPrefix(),
+                    agent.getId());
+            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+            return;
+        }
+        
+        try {
+            assert cookieName != null;
+            final String wrapped = cookieManager.getCookieValue(cookieName, null);
+            if (wrapped == null) {
+                log.debug("{} No cookie in request from agent '{}', skipping cached authentication check", getLogPrefix(),
+                        agent.getId());
+                ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+                return;
+            }
+            
+            final String unwrapped = dataSealer.unwrap(URISupport.doURLDecode(wrapped));
+            
+            // TODO parse and validate...
+            
+        } catch (final DataSealerException e) {
+            log.warn("{} Error decrypting cookie from agent '{}', authentication not bypassed", getLogPrefix(), agent.getId(), e);
+            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicAgent.java b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicAgent.java
index 8b93a4b..191ad54 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicAgent.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicAgent.java
@@ -45,6 +45,9 @@ import net.shibboleth.sp.Application;
  */
 public class BasicAgent extends DefaultRelyingPartyConfigurationResolver implements Agent {
 
+    /** Whether cached authentication is supported. */
+    private boolean supportsCachedAuthentication;
+
     /** Allowed address ranges. */
     @Nonnull private Set<IPRange> allowedAddressRanges;
     
@@ -56,6 +59,7 @@ public class BasicAgent extends DefaultRelyingPartyConfigurationResolver impleme
     
     /** Constructor. */
     public BasicAgent() {
+        supportsCachedAuthentication = true;
         allowedAddressRanges = CollectionSupport.emptySet();
         applicationMap = CollectionSupport.emptyMap();
         issuerLookupStrategy = FunctionSupport.constant(null);
@@ -79,9 +83,29 @@ public class BasicAgent extends DefaultRelyingPartyConfigurationResolver impleme
 
     /** {@inheritDoc} */
     public boolean isAllowed(@Nonnull final InetAddress address) {
+        checkComponentActive();
+        
         return allowedAddressRanges.stream().anyMatch(r -> r.contains(address));
     }
 
+    /** {@inheritDoc} */
+    public boolean isSupportsCachedAuthentication() {
+        return supportsCachedAuthentication;
+    }
+    
+    /**
+     * Sets whether this agent supports cached authentication via cookie.
+     * 
+     * <p>Defaults to true.</p>
+     * 
+     * @param flag flag to set
+     */
+    public void setSupportsCachedAuthentication(final boolean flag) {
+        checkSetterPreconditions();
+        
+        supportsCachedAuthentication = flag;
+    }
+    
     /** {@inheritDoc} */
     @Nullable @NotEmpty public String getIssuer(@Nullable final ProfileRequestContext profileRequestContext) {
         return issuerLookupStrategy.apply(profileRequestContext);

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list