[java-plugin-shibd] branch main updated: JSHIBD-28 - Implement some form of flow permissioning
Codeberg
noreply at shibboleth.net
Wed Sep 16 01:07:11 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch main
in repository java-plugin-shibd.
View the commit online:
https://codeberg.org/Shibboleth/java-plugin-shibd/commit/8de7261d7a0892589109480bac3b520091408961
The following commit(s) were added to refs/heads/main by this push:
new 8de7261 JSHIBD-28 - Implement some form of flow permissioning
8de7261 is described below
commit 8de7261d7a0892589109480bac3b520091408961
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Tue Sep 15 21:06:59 2026 -0400
JSHIBD-28 - Implement some form of flow permissioning
https://shibboleth.atlassian.net/browse/JSHIBD-28
Also defaulted in storage beans for flows now that access is blocked.
---
.../idp/flows/sp/abstract/sp-abstract-beans.xml | 3 +
.../idp/flows/sp/sealer/sealer-beans.xml | 2 +-
.../shibboleth/idp/flows/sp/sealer/sealer-flow.xml | 1 +
.../flows/sp/session-cache/session-cache-beans.xml | 6 +-
.../flows/sp/session-cache/session-cache-flow.xml | 1 +
.../idp/flows/sp/storage/storage-beans.xml | 4 +-
.../idp/flows/sp/storage/storage-flow.xml | 1 +
.../shibboleth/idp/module/conf/sp/sp.properties | 12 ++--
.../net/shibboleth/sp/conf/agents-system.xml | 4 ++
.../net/shibboleth/sp/flows/SealerFlowTest.java | 23 +++++--
.../shibboleth/sp/flows/SessionCacheFlowTest.java | 45 ++++++++----
.../net/shibboleth/sp/flows/StorageFlowTest.java | 39 +++++++----
.../net/shibboleth/idp/module/conf/sp/agents.xml | 6 +-
.../src/main/java/net/shibboleth/sp/Agent.java | 15 +++-
.../sp/authn/impl/ValidateAgentOperation.java | 79 ++++++++++++++++++++++
.../java/net/shibboleth/sp/impl/BasicAgent.java | 28 +++++++-
.../sp/profile/impl/DecodeAgentRequest.java | 4 +-
17 files changed, 225 insertions(+), 48 deletions(-)
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/abstract/sp-abstract-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/abstract/sp-abstract-beans.xml
index 4956ae1..1e9fa90 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/abstract/sp-abstract-beans.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/abstract/sp-abstract-beans.xml
@@ -63,6 +63,9 @@
<bean id="DefaultCleanupHook"
class="net.shibboleth.idp.authn.impl.ValidateCredentials.UsernamePasswordCleanupHook" />
+ <bean id="ValidateAgentOperation"
+ class="net.shibboleth.sp.authn.impl.ValidateAgentOperation" scope="prototype" />
+
<bean id="DecodeAgentRequest" class="net.shibboleth.sp.profile.impl.DecodeAgentRequest" scope="prototype"
p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
p:populateMDC="%{idp.logging.MDC.enabled:true}" />
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/sealer/sealer-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/sealer/sealer-beans.xml
index a3b1094..10955a7 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/sealer/sealer-beans.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/sealer/sealer-beans.xml
@@ -7,7 +7,7 @@
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
default-init-method="initialize" default-destroy-method="destroy">
- <bean id="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/parse-request-map" />
+ <bean id="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/sealer" />
<bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.sealer:Sealer}" />
<bean id="DoSealerOperation"
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/sealer/sealer-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/sealer/sealer-flow.xml
index 7758c50..0d6a680 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/sealer/sealer-flow.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/sealer/sealer-flow.xml
@@ -13,6 +13,7 @@
<!-- Resume flow operation after set up by parent. -->
<action-state id="DoOperation">
+ <evaluate expression="ValidateAgentOperation" />
<evaluate expression="DoSealerOperation" />
<evaluate expression="'proceed'" />
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-cache/session-cache-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-cache/session-cache-beans.xml
index 3f6c316..754e839 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-cache/session-cache-beans.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-cache/session-cache-beans.xml
@@ -7,12 +7,12 @@
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
default-init-method="initialize" default-destroy-method="destroy">
- <bean id="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/parse-request-map" />
- <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.storage:Storage}" />
+ <bean id="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/session-cache" />
+ <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.session-cache:SessionCache}" />
<bean id="DoSessionCacheOperation"
class="net.shibboleth.sp.profile.impl.DoSessionCacheOperation" scope="prototype"
p:maxStorageTimeout="%{sp.session.maxStorageTimeout:P1D}"
- p:storageService-ref="#{'%{sp.session.storageService}'.trim()}" />
+ p:storageService-ref="#{'%{sp.session.storageService:shibboleth.StorageService}'.trim()}" />
</beans>
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-cache/session-cache-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-cache/session-cache-flow.xml
index 8d1c7d2..0cafacd 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-cache/session-cache-flow.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-cache/session-cache-flow.xml
@@ -13,6 +13,7 @@
<!-- Resume flow operation after set up by parent. -->
<action-state id="DoOperation">
+ <evaluate expression="ValidateAgentOperation" />
<evaluate expression="DoSessionCacheOperation" />
<evaluate expression="'proceed'" />
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/storage/storage-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/storage/storage-beans.xml
index 6d20521..fa3d2a5 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/storage/storage-beans.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/storage/storage-beans.xml
@@ -7,12 +7,12 @@
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
default-init-method="initialize" default-destroy-method="destroy">
- <bean id="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/parse-request-map" />
+ <bean id="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/storage" />
<bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.storage:Storage}" />
<bean id="DoStorageOperation"
class="net.shibboleth.sp.profile.impl.DoStorageOperation" scope="prototype"
p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
- p:storageService-ref="#{'%{sp.storageService}'.trim()}" />
+ p:storageService-ref="#{'%{sp.storageService:shibboleth.StorageService}'.trim()}" />
</beans>
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/storage/storage-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/storage/storage-flow.xml
index fed19fd..8307e2e 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/storage/storage-flow.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/storage/storage-flow.xml
@@ -13,6 +13,7 @@
<!-- Resume flow operation after set up by parent. -->
<action-state id="DoOperation">
+ <evaluate expression="ValidateAgentOperation" />
<evaluate expression="DoStorageOperation" />
<evaluate expression="'proceed'" />
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 7d8a730..c3bff7b 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
@@ -63,12 +63,12 @@ sp.service.credential.checkInterval = PT5M
#sp.discoveryURLFunction =
# Settings for Hub-mediated session storage
-#sp.session.storageService =
+#sp.session.storageService = shibboleth.StorageService
#sp.session.maxStorageTimeout = P1D
-###############################
-# Agent Authentication Settings
-###############################
+#########################################
+# Agent Authentication and Authz Settings
+#########################################
# Default agent IP address filter
#sp.agent.authn.allowedAddressRanges = 127.0.0.1/32, ::1/128
@@ -78,6 +78,10 @@ sp.service.credential.checkInterval = PT5M
#sp.agent.authn.cached = true
#sp.agent.authn.cacheDuration = PT1H
+# Default utility flows to permit/enable, e.g.:
+# session-cache, sealer
+#sp.agent.authz.utilityOperations =
+
# Properties exist for optional "enterprise" agent secret validation. Most of them
# are for LDAP, replicating the large range of properties available in the IdP "proper"
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 a71ce38..56c72ae 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
@@ -36,6 +36,9 @@
<bean id="DefaultAllowedAddressRanges" parent="shibboleth.CommaDelimStringArray">
<constructor-arg type="java.lang.String" value="%{sp.agent.authn.allowedAddressRanges:127.0.0.1/32,::1/128}" />
</bean>
+ <bean id="DefaultAllowedOperations" parent="shibboleth.CommaDelimStringArray">
+ <constructor-arg type="java.lang.String" value="%{sp.agent.authz.utilityOperations:}" />
+ </bean>
<bean id="DefaultDiscoveryServiceStrategy" parent="shibboleth.Functions.Constant"
c:_0="#{environment.containsProperty('sp.discoveryURL') ? '%{sp.discoveryURL:}'.trim() : null}" />
@@ -45,6 +48,7 @@
p:authenticationMethod="%{sp.agent.authn.method:}"
p:allowedAddressRanges-ref="DefaultAllowedAddressRanges"
p:supportsCachedAuthentication="%{sp.agent.authn.cached:true}"
+ p:authorizedUtilityOperations-ref="DefaultAllowedOperations"
p:supportsPostPreservation="%{sp.postData.preservation:false}"
p:postLimit="%{sp.postData.limit:1048576}" />
diff --git a/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/SealerFlowTest.java b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/SealerFlowTest.java
index fcc3289..c8add88 100644
--- a/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/SealerFlowTest.java
+++ b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/SealerFlowTest.java
@@ -57,6 +57,21 @@ public class SealerFlowTest extends AbstractSPFlowTest {
return dataSealer;
}
+ /**
+ * Test unauthorized.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void unauthorized() throws IOException {
+ setBasicAuth("unauthorized.example.org", "foo");
+ setRequest("POST", new DDF("sealer"));
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertFlowExecutionResult(result, FLOW_ID);
+ assertFlowExecutionOutcome(result.getOutcome());
+ assertOutputMessageEvent(result, EventIds.ACCESS_DENIED);
+ }
+
/**
* Test wrap operation with no input.
*/
@@ -80,7 +95,7 @@ public class SealerFlowTest extends AbstractSPFlowTest {
public void testWrapExpired() throws IOException, DataSealerException {
setDefaultAuth();
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("sealer").structure();
input.addmember(DoSealerOperation.VALUE).string(TEST_VALUE);
input.addmember(DoSealerOperation.EXP).longinteger(Instant.now().minusSeconds(3600).toEpochMilli() / 1000);
setRequest("POST", input);
@@ -111,7 +126,7 @@ public class SealerFlowTest extends AbstractSPFlowTest {
public void testWrapSuccess() throws IOException, DataSealerException {
setDefaultAuth();
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("sealer").structure();
input.addmember(DoSealerOperation.VALUE).string(TEST_VALUE);
setRequest("POST", input);
@@ -151,7 +166,7 @@ public class SealerFlowTest extends AbstractSPFlowTest {
final String wrapped = getDataSealer().wrap(AGENT_ID + '!' + TEST_VALUE, Instant.now().minusSeconds(3600));
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("sealer").structure();
input.addmember(DoSealerOperation.VALUE).string(wrapped);
setRequest("GET", input);
@@ -173,7 +188,7 @@ public class SealerFlowTest extends AbstractSPFlowTest {
final String wrapped = getDataSealer().wrap(AGENT_ID + '!' + TEST_VALUE, Instant.now().plusSeconds(3600));
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("sealer").structure();
input.addmember(DoSealerOperation.VALUE).string(wrapped);
setRequest("GET", input);
diff --git a/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/SessionCacheFlowTest.java b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/SessionCacheFlowTest.java
index 52e4e82..403b63b 100644
--- a/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/SessionCacheFlowTest.java
+++ b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/SessionCacheFlowTest.java
@@ -78,6 +78,21 @@ public class SessionCacheFlowTest extends AbstractSPFlowTest {
getStorageService().deleteContext(TEST_CONTEXT);
}
+ /**
+ * Test unauthorized.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void unauthorized() throws IOException {
+ setBasicAuth("unauthorized.example.org", "foo");
+ setRequest("POST", new DDF("session-cache"));
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertFlowExecutionResult(result, FLOW_ID);
+ assertFlowExecutionOutcome(result.getOutcome());
+ assertOutputMessageEvent(result, EventIds.ACCESS_DENIED);
+ }
+
/**
* Test no operation.
*
@@ -86,7 +101,7 @@ public class SessionCacheFlowTest extends AbstractSPFlowTest {
@Test
public void noOperation() throws IOException {
setDefaultAuth();
- setRequest("POST", new DDF(null));
+ setRequest("POST", new DDF("session-cache"));
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertFlowExecutionResult(result, FLOW_ID);
assertFlowExecutionOutcome(result.getOutcome());
@@ -102,7 +117,7 @@ public class SessionCacheFlowTest extends AbstractSPFlowTest {
public void testReadMissingKey() throws IOException {
setDefaultAuth();
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("session-cache").structure();
input.addmember(DoSessionCacheOperation.OP).string("R");
setRequest("POST", input);
@@ -121,7 +136,7 @@ public class SessionCacheFlowTest extends AbstractSPFlowTest {
public void testReadMissingSession() throws IOException {
setDefaultAuth();
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("session-cache").structure();
input.addmember(DoSessionCacheOperation.OP).string("R");
input.addmember(DoSessionCacheOperation.KEY).string("foo");
setRequest("POST", input);
@@ -148,7 +163,7 @@ public class SessionCacheFlowTest extends AbstractSPFlowTest {
getStorageService().create(TEST_CONTEXT, "foo", Base64Support.encode("foo".getBytes(StandardCharsets.UTF_8), false), exp);
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("session-cache").structure();
input.addmember(DoSessionCacheOperation.OP).string("R");
input.addmember(DoSessionCacheOperation.KEY).string("foo");
setRequest("POST", input);
@@ -173,7 +188,7 @@ public class SessionCacheFlowTest extends AbstractSPFlowTest {
getStorageService().create(TEST_CONTEXT, "foo", getEncodedSession(), exp);
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("session-cache").structure();
input.addmember(DoSessionCacheOperation.OP).string("R");
input.addmember(DoSessionCacheOperation.KEY).string("foo");
setRequest("POST", input);
@@ -202,7 +217,7 @@ public class SessionCacheFlowTest extends AbstractSPFlowTest {
getStorageService().create(TEST_CONTEXT, "foo", getEncodedSession(), exp);
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("session-cache").structure();
input.addmember(DoSessionCacheOperation.OP).string("R");
input.addmember(DoSessionCacheOperation.KEY).string("foo");
input.addmember(DoSessionCacheOperation.STORAGE_TIMEOUT).longinteger(900);
@@ -226,7 +241,7 @@ public class SessionCacheFlowTest extends AbstractSPFlowTest {
public void testDeleteMissing() throws IOException {
setDefaultAuth();
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("session-cache").structure();
input.addmember(DoSessionCacheOperation.OP).string("D");
input.addmember(DoSessionCacheOperation.KEY).string("foo");
setRequest("POST", input);
@@ -251,7 +266,7 @@ public class SessionCacheFlowTest extends AbstractSPFlowTest {
getStorageService().create(TEST_CONTEXT, "foo", getEncodedSession(), exp);
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("session-cache").structure();
input.addmember(DoSessionCacheOperation.OP).string("D");
input.addmember(DoSessionCacheOperation.KEY).string("foo");
setRequest("POST", input);
@@ -274,7 +289,7 @@ public class SessionCacheFlowTest extends AbstractSPFlowTest {
public void testCreateMissingInput() throws IOException, DecodingException {
setDefaultAuth();
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("session-cache").structure();
input.addmember(DoSessionCacheOperation.OP).string("C");
setRequest("POST", input);
@@ -295,7 +310,7 @@ public class SessionCacheFlowTest extends AbstractSPFlowTest {
public void testCreateSuccess() throws IOException, DecodingException {
setDefaultAuth();
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("session-cache").structure();
input.addmember(DoSessionCacheOperation.OP).string("C");
input.addmember(DoSessionCacheOperation.SESSION).structure().addmember("foo").integer(42);
@@ -334,7 +349,7 @@ public class SessionCacheFlowTest extends AbstractSPFlowTest {
getStorageService().create(TEST_CONTEXT, "foo", getEncodedSession(), exp.toEpochMilli());
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("session-cache").structure();
input.addmember(DoSessionCacheOperation.OP).string("T");
setRequest("POST", input);
@@ -358,7 +373,7 @@ public class SessionCacheFlowTest extends AbstractSPFlowTest {
getStorageService().create(TEST_CONTEXT, "foo", getEncodedSession(), exp.toEpochMilli());
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("session-cache").structure();
input.addmember(DoSessionCacheOperation.OP).string("T");
input.addmember(DoSessionCacheOperation.KEY).string("foo");
input.addmember(DoSessionCacheOperation.STORAGE_TIMEOUT).longinteger(900);
@@ -391,7 +406,7 @@ public class SessionCacheFlowTest extends AbstractSPFlowTest {
getStorageService().create(TEST_CONTEXT, "foo", getEncodedSession(), exp);
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("session-cache").structure();
input.addmember(DoSessionCacheOperation.OP).string("U");
input.addmember(DoSessionCacheOperation.KEY).string("foo");
input.addmember(DoSessionCacheOperation.VERSION).integer(1);
@@ -425,7 +440,7 @@ public class SessionCacheFlowTest extends AbstractSPFlowTest {
getStorageService().create(TEST_CONTEXT, "foo", getEncodedSession(), exp);
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("session-cache").structure();
input.addmember(DoSessionCacheOperation.OP).string("U");
input.addmember(DoSessionCacheOperation.KEY).string("foo");
input.addmember(DoSessionCacheOperation.VERSION).integer(2);
@@ -448,7 +463,7 @@ public class SessionCacheFlowTest extends AbstractSPFlowTest {
public void testUpdateMissing() throws IOException {
setDefaultAuth();
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("session-cache").structure();
input.addmember(DoSessionCacheOperation.OP).string("U");
input.addmember(DoSessionCacheOperation.KEY).string("foo");
input.addmember(DoSessionCacheOperation.VERSION).longinteger(1);
diff --git a/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/StorageFlowTest.java b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/StorageFlowTest.java
index 8cd53fd..2332a2c 100644
--- a/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/StorageFlowTest.java
+++ b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/StorageFlowTest.java
@@ -82,6 +82,21 @@ public class StorageFlowTest extends AbstractSPFlowTest {
getStorageService().deleteContext(AGENT_CONTEXT);
}
+ /**
+ * Test unauthorized.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void unauthorized() throws IOException {
+ setBasicAuth("unauthorized.example.org", "foo");
+ setRequest("POST", new DDF("storage"));
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertFlowExecutionResult(result, FLOW_ID);
+ assertFlowExecutionOutcome(result.getOutcome());
+ assertOutputMessageEvent(result, EventIds.ACCESS_DENIED);
+ }
+
/**
* Test no operation.
*
@@ -90,7 +105,7 @@ public class StorageFlowTest extends AbstractSPFlowTest {
@Test
public void noOperation() throws IOException {
setDefaultAuth();
- setRequest("POST", new DDF(null));
+ setRequest("POST", new DDF("storage"));
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertFlowExecutionResult(result, FLOW_ID);
assertFlowExecutionOutcome(result.getOutcome());
@@ -106,7 +121,7 @@ public class StorageFlowTest extends AbstractSPFlowTest {
public void testMissingRead() throws IOException {
setDefaultAuth();
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("storage").structure();
input.addmember(DoStorageOperation.OP).string("R");
input.addmember(DoStorageOperation.CONTEXT).string(TEST_CONTEXT);
input.addmember(DoStorageOperation.KEY).string(TEST_KEY);
@@ -131,7 +146,7 @@ public class StorageFlowTest extends AbstractSPFlowTest {
getStorageService().create(AGENT_CONTEXT, TEST_KEY, TEST_VALUE, exp);
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("storage").structure();
input.addmember(DoStorageOperation.OP).string("R");
input.addmember(DoStorageOperation.CONTEXT).string(TEST_CONTEXT);
input.addmember(DoStorageOperation.KEY).string(TEST_KEY);
@@ -159,7 +174,7 @@ public class StorageFlowTest extends AbstractSPFlowTest {
getStorageService().create(AGENT_CONTEXT, TEST_KEY, TEST_VALUE, null);
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("storage").structure();
input.addmember(DoStorageOperation.OP).string("D");
input.addmember(DoStorageOperation.CONTEXT).string(TEST_CONTEXT);
input.addmember(DoStorageOperation.KEY).string(TEST_KEY + "2");
@@ -185,7 +200,7 @@ public class StorageFlowTest extends AbstractSPFlowTest {
getStorageService().create(AGENT_CONTEXT, TEST_KEY, TEST_VALUE, null);
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("storage").structure();
input.addmember(DoStorageOperation.OP).string("D");
input.addmember(DoStorageOperation.CONTEXT).string(TEST_CONTEXT);
input.addmember(DoStorageOperation.KEY).string(TEST_KEY);
@@ -209,7 +224,7 @@ public class StorageFlowTest extends AbstractSPFlowTest {
public void successCreate() throws IOException {
setDefaultAuth();
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("storage").structure();
input.addmember(DoStorageOperation.OP).string("C");
input.addmember(DoStorageOperation.CONTEXT).string(TEST_CONTEXT);
input.addmember(DoStorageOperation.KEY).string(TEST_KEY);
@@ -239,7 +254,7 @@ public class StorageFlowTest extends AbstractSPFlowTest {
getStorageService().create(AGENT_CONTEXT, TEST_KEY, TEST_VALUE, null);
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("storage").structure();
input.addmember(DoStorageOperation.OP).string("C");
input.addmember(DoStorageOperation.CONTEXT).string(TEST_CONTEXT);
input.addmember(DoStorageOperation.KEY).string(TEST_KEY);
@@ -265,7 +280,7 @@ public class StorageFlowTest extends AbstractSPFlowTest {
final Instant now = Instant.now();
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("storage").structure();
input.addmember(DoStorageOperation.OP).string("U");
input.addmember(DoStorageOperation.CONTEXT).string(TEST_CONTEXT);
input.addmember(DoStorageOperation.KEY).string(TEST_KEY);
@@ -297,7 +312,7 @@ public class StorageFlowTest extends AbstractSPFlowTest {
getStorageService().create(AGENT_CONTEXT, TEST_KEY, TEST_VALUE, null);
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("storage").structure();
input.addmember(DoStorageOperation.OP).string("U");
input.addmember(DoStorageOperation.CONTEXT).string(TEST_CONTEXT);
input.addmember(DoStorageOperation.KEY).string(TEST_KEY);
@@ -327,7 +342,7 @@ public class StorageFlowTest extends AbstractSPFlowTest {
getStorageService().create(AGENT_CONTEXT, TEST_KEY, TEST_VALUE, null);
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("storage").structure();
input.addmember(DoStorageOperation.OP).string("U");
input.addmember(DoStorageOperation.CONTEXT).string(TEST_CONTEXT);
input.addmember(DoStorageOperation.KEY).string(TEST_KEY);
@@ -360,7 +375,7 @@ public class StorageFlowTest extends AbstractSPFlowTest {
getStorageService().create(AGENT_CONTEXT, TEST_KEY, TEST_VALUE, null);
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("storage").structure();
input.addmember(DoStorageOperation.OP).string("U");
input.addmember(DoStorageOperation.CONTEXT).string(TEST_CONTEXT);
input.addmember(DoStorageOperation.KEY).string(TEST_KEY);
@@ -389,7 +404,7 @@ public class StorageFlowTest extends AbstractSPFlowTest {
public void missingUpdateWithVersion() throws IOException {
setDefaultAuth();
- final DDF input = new DDF().structure();
+ final DDF input = new DDF("storage").structure();
input.addmember(DoStorageOperation.OP).string("U");
input.addmember(DoStorageOperation.CONTEXT).string(TEST_CONTEXT);
input.addmember(DoStorageOperation.KEY).string(TEST_KEY);
diff --git a/sp-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/agents.xml b/sp-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/agents.xml
index 2d22c0b..2dc9056 100644
--- a/sp-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/agents.xml
+++ b/sp-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/agents.xml
@@ -24,7 +24,11 @@
<!-- ============ Agents and their Applications ============ -->
<bean p:id="testsp.example.org" parent="shibboleth.sp.Agent" p:authenticationMethod="basic"
- p:credentials="#{ {null : 'foo'} }" />
+ p:credentials="#{{null : 'foo'}}"
+ p:authorizedUtilityOperations="#{{ 'storage', 'sealer', 'session-cache' }}" />
+
+ <bean p:id="unauthorized.example.org" parent="shibboleth.sp.Agent" p:authenticationMethod="basic"
+ p:credentials="#{{null : 'foo'}}" />
<!-- ============ RelyingParty Overrides ============ -->
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 bc18fbd..854c356 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
@@ -27,6 +27,8 @@ import javax.annotation.concurrent.ThreadSafe;
import net.shibboleth.shared.annotation.constraint.NonNegative;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
import net.shibboleth.shared.component.IdentifiedComponent;
import net.shibboleth.shared.net.IPRange;
@@ -52,7 +54,7 @@ public interface Agent extends IdentifiedComponent {
*
* @return set of address ranges
*/
- @Nonnull Set<IPRange> getAllowedAddressRanges();
+ @Nonnull @NotLive @Unmodifiable Set<IPRange> getAllowedAddressRanges();
/**
* Get the authentication method to use for the agent.
@@ -77,7 +79,7 @@ public interface Agent extends IdentifiedComponent {
*
* @return set of username/shared secret mappings, either of which may be null
*/
- @Nonnull Map<String,String> getCredentials();
+ @Nonnull @NotLive @Unmodifiable Map<String,String> getCredentials();
/**
* Returns true iff the supplied address matches one of the allowed ranges.
@@ -95,6 +97,13 @@ public interface Agent extends IdentifiedComponent {
*/
boolean isSupportsCachedAuthentication();
+ /**
+ * Gets the set of "utility" operations authorized for use by this Agent.
+ *
+ * @return the set of authorized operations to permit
+ */
+ @Nonnull @NotLive @Unmodifiable Set<String> getAuthorizedUtilityOperations();
+
/**
* Returns true iff the agent is permitted to submit form data to preserve
* during SSO.
@@ -139,6 +148,6 @@ public interface Agent extends IdentifiedComponent {
*
* @return collection of applications
*/
- @Nonnull Collection<Application> getApplications();
+ @Nonnull @NotLive @Unmodifiable Collection<Application> getApplications();
}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/authn/impl/ValidateAgentOperation.java b/sp-server-impl/src/main/java/net/shibboleth/sp/authn/impl/ValidateAgentOperation.java
new file mode 100644
index 0000000..ac787ac
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/authn/impl/ValidateAgentOperation.java
@@ -0,0 +1,79 @@
+/*
+ * 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 javax.annotation.Nonnull;
+
+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.webflow.execution.RequestContext;
+
+import net.shibboleth.idp.profile.context.SpringRequestContext;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.sp.Agent;
+import net.shibboleth.sp.profile.AbstractAgentAction;
+
+/**
+ * An action that authorizes the operation being attempted on behalf of a requesting {@link Agent}.
+ *
+ * <p>This operates for utility flows that are not assumed to be active in general unless configured
+ * to be.</p>
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#ACCESS_DENIED}
+ */
+public class ValidateAgentOperation extends AbstractAgentAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateAgentOperation.class);
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final Agent agent = ensureAgent();
+
+ final SpringRequestContext springContext = profileRequestContext.getSubcontext(SpringRequestContext.class);
+ final RequestContext requestContext = springContext != null ? springContext.getRequestContext() : null;
+ if (requestContext == null) {
+ log.warn("{} Request denied from agent '{}', no Spring WebFlow RequestContext available", getLogPrefix(),
+ agent.getId());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.ACCESS_DENIED);
+ return;
+ }
+
+ final String flowID = requestContext.getActiveFlow().getId();
+ if (flowID == null || !flowID.startsWith("sp/")) {
+ log.warn("{} Request denied from agent '{}', unexpected flow ID: {}", getLogPrefix(),
+ agent.getId(), flowID);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.ACCESS_DENIED);
+ return;
+ }
+
+ final String op = flowID.substring(3);
+
+ if (!agent.getAuthorizedUtilityOperations().contains(op)) {
+ log.warn("{} Request denied from agent '{}' for unauthorized operation: {}", getLogPrefix(), agent.getId(),
+ op);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.ACCESS_DENIED);
+ return;
+ }
+
+ log.debug("{} Request permitted from agent '{}' for operation: {}", getLogPrefix(), agent.getId(), op);
+ }
+
+}
\ 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 467efeb..c9319c3 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
@@ -58,7 +58,7 @@ public class BasicAgent extends BasicApplication implements Agent {
/** Authentication method. */
@Nullable @NotEmpty String authenticationMethod;
-
+
/** Whether to support form submission preservation and recovery. */
private boolean supportsPostPreservation;
@@ -74,6 +74,9 @@ public class BasicAgent extends BasicApplication implements Agent {
/** Application map. */
@Nonnull private Map<String,Application> applicationMap;
+ /** Authorized utility flows. */
+ @Nonnull private Set<String> authorizedOperations;
+
/**
* Constructor.
*
@@ -91,6 +94,7 @@ public class BasicAgent extends BasicApplication implements Agent {
encoding = StandardCharsets.UTF_8;
credentials = CollectionSupport.emptyMap();
applicationMap = CollectionSupport.emptyMap();
+ authorizedOperations = CollectionSupport.emptySet();
setAllowInheritance(false);
}
@@ -200,6 +204,28 @@ public class BasicAgent extends BasicApplication implements Agent {
supportsCachedAuthentication = flag;
}
+ /** {@inheritDoc} */
+ @Nonnull @NotLive @Unmodifiable public Set<String> getAuthorizedUtilityOperations() {
+ return authorizedOperations;
+ }
+
+ /**
+ * Sets the set of "utility" operations authorized for use by this Agent.
+ *
+ * <p>Defaults to empty.</p>
+ *
+ * @param ops authorized operation names
+ */
+ public void setAuthorizedUtilityOperations(@Nullable final Collection<String> ops) {
+ checkSetterPreconditions();
+
+ if (ops != null) {
+ authorizedOperations = CollectionSupport.copyToSet(StringSupport.normalizeStringCollection(ops));
+ } else {
+ authorizedOperations = CollectionSupport.emptySet();
+ }
+ }
+
/** {@inheritDoc} */
public boolean isSupportsPostPreservation() {
return supportsPostPreservation;
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/DecodeAgentRequest.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/DecodeAgentRequest.java
index 098a837..8473575 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/DecodeAgentRequest.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/DecodeAgentRequest.java
@@ -31,7 +31,6 @@ import org.slf4j.MDC;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.sp.Agent;
import net.shibboleth.sp.context.AgentRequestContext;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
@@ -126,7 +125,8 @@ public class DecodeAgentRequest extends AbstractAgentAction {
}
final String txid = msg.getmember(SPConstants.TXID).string();
- log.debug("{} Decoded message with transaction ID '{}'", getLogPrefix(), txid);
+ log.debug("{} Decoded message with operation '{}', transaction ID '{}'", getLogPrefix(), msg.name(),
+ txid);
if (populateMDC) {
MDC.put(TX_ID_MDC_ATTRIBUTE, txid);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list