[java-identity-provider] 01/02: IDP-995 - Administrative logout features
Scott Cantor
cantor.2 at osu.edu
Wed Aug 3 19:53:35 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=7310c914e4916b5a2cea1dd650eb8f332609afb0
commit 7310c914e4916b5a2cea1dd650eb8f332609afb0
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Aug 3 15:49:03 2022 -0400
IDP-995 - Administrative logout features
https://shibboleth.atlassian.net/browse/IDP-995
Switch key seperator to bang character.
Add admin flow to interact with revocation cache.
---
.../impl/DoRevocationCacheOperation.java | 309 +++++++++++++++++++++
.../revocation/impl/RevocationCacheCondition.java | 4 +-
.../net/shibboleth/idp/conf/admin-system.xml | 17 ++
.../net/shibboleth/idp/conf/mvc-beans.xml | 1 +
.../net/shibboleth/idp/conf/webflow-config.xml | 1 +
.../idp/flows/admin/revocation-beans.xml | 41 +++
.../shibboleth/idp/flows/admin/revocation-flow.xml | 45 +++
.../src/main/resources/conf/admin/admin.properties | 8 +
.../idp/profile/AbstractProfileAction.java | 60 ++++
9 files changed, 484 insertions(+), 2 deletions(-)
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/revocation/impl/DoRevocationCacheOperation.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/revocation/impl/DoRevocationCacheOperation.java
new file mode 100644
index 000000000..b9d16e121
--- /dev/null
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/revocation/impl/DoRevocationCacheOperation.java
@@ -0,0 +1,309 @@
+/*
+ * 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.io.IOException;
+import java.time.Duration;
+import java.util.Collections;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.servlet.http.HttpServletResponse;
+
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.idp.profile.context.SpringRequestContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+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 org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.storage.RevocationCache;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.webflow.execution.RequestContext;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.jasminb.jsonapi.models.errors.Error;
+import com.github.jasminb.jsonapi.models.errors.Errors;
+import com.google.common.base.Strings;
+
+/**
+ * Action that implements a JSON REST API for the {@link RevocationCache} interface.
+ *
+ * <p>The API supports GET, PUT/POST, and DELETE at the moment, using jsonapi.org conventions.</p>
+ *
+ * <dl>
+ * <dt>GET</dt>
+ * <dd>Return a revocation record.</dd>
+ *
+ * <dt>PUT/POST</dt>
+ * <dd>Insert or update a revocation record.</dd>
+ *
+ * <dt>DELETE</dt>
+ * <dd>Delete revocation record.</dd>
+ * </dl>
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link EventIds#IO_ERROR}
+ */
+public class DoRevocationCacheOperation extends AbstractProfileAction {
+
+ /** Flow variable indicating ID of cache bean to access. */
+ @Nonnull @NotEmpty public static final String CACHE_ID = "revocationCacheId";
+
+ /** Flow variable indicating ID of account context. */
+ @Nonnull @NotEmpty public static final String CONTEXT = "context";
+
+ /** Flow variable indicating ID of account key. */
+ @Nonnull @NotEmpty public static final String KEY = "key";
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DoRevocationCacheOperation.class);
+
+ /** JSON object mapper. */
+ @NonnullAfterInit private ObjectMapper objectMapper;
+
+ /** Revocation context to operate on. */
+ @Nullable @NotEmpty private String context;
+
+ /** Revocation key to operate on. */
+ @Nullable @NotEmpty private String key;
+
+ /** {@link AccountLockoutManager} to operate on. */
+ @Nullable private RevocationCache revocationCache;
+
+ /**
+ * Set the JSON {@link ObjectMapper} to use for serialization.
+ *
+ * @param mapper object mapper
+ */
+ public void setObjectMapper(@Nonnull final ObjectMapper mapper) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ objectMapper = Constraint.isNotNull(mapper, "ObjectMapper cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (objectMapper == null) {
+ throw new ComponentInitializationException("ObjectMapper cannot be null");
+ }
+ }
+
+// Checkstyle: CyclomaticComplexity OFF
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(final ProfileRequestContext profileRequestContext) {
+
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ } else if (getHttpServletRequest() == null || getHttpServletResponse() == null) {
+ log.warn("{} No HttpServletRequest or HttpServletResponse available", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ try {
+ final SpringRequestContext springRequestContext =
+ profileRequestContext.getSubcontext(SpringRequestContext.class);
+ if (springRequestContext == null) {
+ log.warn("{} Spring request context not found in profile request context", getLogPrefix());
+ sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ "Internal Server Error", "System misconfiguration.");
+ return false;
+ }
+
+ final RequestContext requestContext = springRequestContext.getRequestContext();
+ if (requestContext == null) {
+ log.warn("{} Web Flow request context not found in Spring request context", getLogPrefix());
+ sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ "Internal Server Error", "System misconfiguration.");
+ return false;
+ }
+
+
+ final String id = getParameter(requestContext, CACHE_ID);
+ context = getParameter(requestContext, CONTEXT);
+ key = getParameter(requestContext, KEY);
+
+ if (Strings.isNullOrEmpty(id) || Strings.isNullOrEmpty(context) || Strings.isNullOrEmpty(key)) {
+ sendError(HttpServletResponse.SC_NOT_FOUND,
+ "Missing revocation cache ID, context, or key",
+ "No revocation cache ID, context, key specified.");
+ return false;
+ }
+
+ revocationCache = getBean(requestContext, id, RevocationCache.class);
+ if (revocationCache == null) {
+ sendError(HttpServletResponse.SC_NOT_FOUND,
+ "Invalid Revocation Cache", "Invalid revocation cache identifier in path.");
+ return false;
+ }
+
+ } catch (final IOException e) {
+ log.error("{} I/O error issuing API response", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
+ return false;
+ }
+
+ return true;
+ }
+// Checkstyle: CyclomaticComplexity ON
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(final ProfileRequestContext profileRequestContext) {
+
+ try {
+ final String method = getHttpServletRequest().getMethod();
+ final HttpServletResponse response = getHttpServletResponse();
+
+ response.setContentType("application/json");
+ response.setHeader("Cache-Control", "must-revalidate,no-cache,no-store");
+
+ if ("GET".equals(method)) {
+ doGet();
+ } else if ("POST".equals(method) || "PUT".equals(method)) {
+ doPost();
+ } else if ("DELETE".equals(method)) {
+ doDelete();
+ } else {
+ log.warn("{} Invalid method: {}", getLogPrefix(), method);
+ sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED,
+ "Unknown Operation", "Only GET, POST, and DELETE are supported.");
+ }
+
+ } catch (final IOException e) {
+ log.error("{} I/O error responding to request", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
+ }
+ }
+
+ /**
+ * Get a revocation record.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ private void doGet() throws IOException {
+ try {
+ final String revocation = revocationCache.getRevocationRecord(context, key);
+ if (revocation != null) {
+ getHttpServletResponse().setStatus(HttpServletResponse.SC_OK);
+ final JsonFactory jsonFactory = new JsonFactory();
+ try (final JsonGenerator g = jsonFactory.createGenerator(
+ getHttpServletResponse().getOutputStream()).useDefaultPrettyPrinter()) {
+ g.setCodec(objectMapper);
+ g.writeStartObject();
+ g.writeObjectFieldStart("data");
+ g.writeStringField("type", "revocation-records");
+ g.writeStringField("id", revocationCache.getId() + '/' + context + '/' + key);
+ g.writeObjectFieldStart("attributes");
+ g.writeStringField("revocation", revocation);
+ }
+ } else {
+ getHttpServletResponse().setStatus(HttpServletResponse.SC_NOT_FOUND);
+ }
+ } catch (final IOException e) {
+ sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal Server Error",
+ "Revocation cache error.");
+ }
+ }
+
+ /**
+ * Insert a revocation record.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ private void doPost() throws IOException {
+
+ final String value = getHttpServletRequest().getParameter("value");
+ final String duration = getHttpServletRequest().getParameter("duration");
+
+ if (value == null || duration == null) {
+ sendError(HttpServletResponse.SC_BAD_REQUEST, "Bad Request", "Request missing value/duration parameters.");
+ return;
+ }
+
+ final Long durationSeconds;
+ try {
+ durationSeconds = Long.valueOf(duration);
+ } catch (final NumberFormatException e) {
+ sendError(HttpServletResponse.SC_BAD_REQUEST, "Bad Request", "Duration parameter was not a long integer.");
+ return;
+ }
+
+ if (revocationCache.revoke(context, key, value, Duration.ofSeconds(durationSeconds))) {
+ getHttpServletResponse().setStatus(HttpServletResponse.SC_ACCEPTED);
+ } else {
+ sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal Server Error",
+ "Attempt to insert revocation record failed.");
+ }
+ }
+
+ /**
+ * Delete a revocation record.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ private void doDelete() throws IOException {
+ if (revocationCache.unrevoke(context, key)) {
+ getHttpServletResponse().setStatus(HttpServletResponse.SC_NO_CONTENT);
+ } else {
+ getHttpServletResponse().setStatus(HttpServletResponse.SC_NOT_FOUND);
+ }
+ }
+
+ /**
+ * Output an error object.
+ *
+ * @param status HTTP status
+ * @param title fixed error description
+ * @param detail human-readable error description
+ *
+ * @throws IOException if unable to output the error
+ */
+ private void sendError(final int status, @Nonnull @NotEmpty final String title,
+ @Nonnull @NotEmpty final String detail) throws IOException {
+
+ final HttpServletResponse response = getHttpServletResponse();
+ response.setContentType("application/json");
+ response.setHeader("Cache-Control", "must-revalidate,no-cache,no-store");
+ response.setStatus(status);
+
+ final Error e = new Error();
+ final Errors errors = new Errors();
+ errors.setErrors(Collections.singletonList(e));
+ e.setStatus(Integer.toString(status));
+ e.setTitle(title);
+ e.setDetail(detail);
+
+ objectMapper.writer().withDefaultPrettyPrinter().writeValue(response.getOutputStream(), errors);
+ }
+
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/revocation/impl/RevocationCacheCondition.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/revocation/impl/RevocationCacheCondition.java
index f6c48eb23..7865195c8 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/revocation/impl/RevocationCacheCondition.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/revocation/impl/RevocationCacheCondition.java
@@ -54,10 +54,10 @@ public class RevocationCacheCondition extends AbstractInitializableComponent
@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:";
+ @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:";
+ @Nonnull @NotEmpty public static final String ADDRESS_REVOCATION_PREFIX = "addr!";
/** Class logger. */
@Nonnull private final Logger log = LoggerFactory.getLogger(RevocationCacheCondition.class);
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/admin-system.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/admin-system.xml
index aa19c34f3..9cdc7860c 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/admin-system.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/admin-system.xml
@@ -160,6 +160,23 @@
</property>
</bean>
+ <bean parent="shibboleth.AdminFlow"
+ c:id="http://shibboleth.net/ns/profiles/revocation"
+ p:loggingId="%{idp.revocation.logging:Revocation}"
+ p:policyName="%{idp.revocation.accessPolicy:AccessDenied}"
+ p:nonBrowserSupported="%{idp.revocation.nonBrowserSupported:false}"
+ p:authenticated="%{idp.revocation.authenticated:false}"
+ p:resolveAttributes="%{idp.revocation.resolveAttributes:false}">
+ <property name="postAuthenticationFlows">
+ <bean parent="shibboleth.CommaDelimStringArray"
+ c:_0="#{'%{idp.revocation.postAuthenticationFlows:}'.trim()}" />
+ </property>
+ <property name="defaultAuthenticationMethodsByString">
+ <bean parent="shibboleth.CommaDelimStringArray"
+ c:_0="#{'%{idp.revocation.defaultAuthenticationMethods:}'.trim()}" />
+ </property>
+ </bean>
+
<bean parent="shibboleth.AdminFlow"
c:id="http://shibboleth.net/ns/profiles/storage"
p:loggingId="%{idp.storage.logging:Storage}"
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/mvc-beans.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/mvc-beans.xml
index 49bce3584..543d6f490 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/mvc-beans.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/mvc-beans.xml
@@ -19,6 +19,7 @@
<list>
<value>admin/metrics</value>
<value>admin/lockout</value>
+ <value>admin/revocation</value>
<value>admin/storage</value>
</list>
</property>
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/webflow-config.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/webflow-config.xml
index 3a1f424da..b7e430bac 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/webflow-config.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/webflow-config.xml
@@ -30,6 +30,7 @@
<entry key="admin/lockout" value="classpath:/net/shibboleth/idp/flows/admin/lockout-flow.xml" />
<entry key="admin/mdquery" value="classpath:/net/shibboleth/idp/flows/admin/mdquery-flow.xml" />
<entry key="admin/metrics" value="classpath:/net/shibboleth/idp/flows/admin/metrics-flow.xml" />
+ <entry key="admin/revocation" value="classpath:/net/shibboleth/idp/flows/admin/revocation-flow.xml" />
<entry key="admin/storage" value="classpath:/net/shibboleth/idp/flows/admin/storage-flow.xml" />
<entry key="admin/unlock-keys" value="classpath:/net/shibboleth/idp/flows/admin/unlock-keys-flow.xml" />
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/admin/revocation-beans.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/admin/revocation-beans.xml
new file mode 100644
index 000000000..e3396293a
--- /dev/null
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/admin/revocation-beans.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans
+ xmlns="http://www.springframework.org/schema/beans"
+ xmlns:c="http://www.springframework.org/schema/c"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:util="http://www.springframework.org/schema/util"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+ default-init-method="initialize"
+ default-destroy-method="destroy">
+
+ <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"
+ p:placeholderPrefix="%{" p:placeholderSuffix="}" />
+
+ <!-- Profile ID for flow. -->
+ <bean id="shibboleth.AdminProfileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/revocation" />
+
+ <!-- Default operation/resource suppliers for access checks. -->
+
+ <bean id="shibboleth.AdminOperationLookupStrategy" parent="shibboleth.ContextFunctions.Expression"
+ c:outputType="#{T(java.lang.String)}"
+ c:expression="#custom.getMethod()"
+ p:customObject-ref="shibboleth.HttpServletRequest" />
+
+ <bean id="shibboleth.AdminResourceLookupStrategy" parent="shibboleth.ContextFunctions.Expression"
+ c:outputType="#{T(java.lang.String)}"
+ c:expression="#input.getSubcontext(T(net.shibboleth.idp.profile.context.SpringRequestContext)).getRequestContext().getFlowScope().get('context')" />
+
+ <!-- Work beans. -->
+
+ <bean id="DoRevocationCacheOperation" class="net.shibboleth.idp.authn.revocation.impl.DoRevocationCacheOperation" scope="prototype"
+ p:httpServletRequest-ref="shibboleth.HttpServletRequest"
+ p:httpServletResponse-ref="shibboleth.HttpServletResponse"
+ p:objectMapper-ref="shibboleth.JSONObjectMapper" />
+
+ <bean id="RecordResponseComplete" class="net.shibboleth.idp.profile.impl.RecordResponseComplete" scope="prototype" />
+
+</beans>
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/admin/revocation-flow.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/admin/revocation-flow.xml
new file mode 100644
index 000000000..4ff31b96c
--- /dev/null
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/admin/revocation-flow.xml
@@ -0,0 +1,45 @@
+<flow xmlns="http://www.springframework.org/schema/webflow"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
+ parent="admin.abstract">
+
+ <on-start>
+ <!-- Extract PATH_INFO containing revocation cache and context/key. -->
+ <evaluate expression="flowRequestContext.getActiveFlow().getId()" result="flowScope.flowId" />
+ <evaluate expression="externalContext.getNativeRequest().getPathInfo().length() gt flowId.length() + 2 ? externalContext.getNativeRequest().getPathInfo().substring(flowId.length() + 2) : ''" result="flowScope.pathInfo" />
+ <evaluate expression="pathInfo.split('/')" result="flowScope.pathInfoArray" />
+ <evaluate expression="pathInfoArray.length gt 0 ? T(net.shibboleth.utilities.java.support.net.URISupport).doURLDecode(pathInfoArray[0]) : null" result="flowScope.revocationCacheId" />
+ <evaluate expression="pathInfoArray.length gt 1 ? T(net.shibboleth.utilities.java.support.net.URISupport).doURLDecode(pathInfoArray[1]) : null" result="flowScope.context" />
+ <evaluate expression="pathInfoArray.length gt 2 ? T(net.shibboleth.utilities.java.support.net.URISupport).doURLDecode(pathInfoArray[2]) : null" result="flowScope.key" />
+ </on-start>
+
+ <!-- Start action. -->
+
+ <action-state id="InitializeProfileRequestContext">
+ <evaluate expression="InitializeProfileRequestContext" />
+ <evaluate expression="FlowStartPopulateAuditContext" />
+ <evaluate expression="'proceed'" />
+
+ <!-- Branch to determine if authentication is required. -->
+ <transition on="proceed" to="DoAdminPreamble" />
+ </action-state>
+
+ <!-- Resume actual flow processing. -->
+
+ <action-state id="DoProfileWork">
+ <evaluate expression="CheckAccess" />
+ <evaluate expression="WriteAuditLog" />
+ <evaluate expression="DoRevocationCacheOperation" />
+ <evaluate expression="RecordResponseComplete" />
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="end" />
+ </action-state>
+
+ <!-- Terminus -->
+
+ <end-state id="end" />
+
+ <bean-import resource="revocation-beans.xml" />
+
+</flow>
diff --git a/idp-conf/src/main/resources/conf/admin/admin.properties b/idp-conf/src/main/resources/conf/admin/admin.properties
index 1e4f3a90e..344cb7902 100644
--- a/idp-conf/src/main/resources/conf/admin/admin.properties
+++ b/idp-conf/src/main/resources/conf/admin/admin.properties
@@ -56,6 +56,14 @@
#idp.lockout.resolveAttributes = false
#idp.lockout.postAuthenticationFlows =
+#idp.revocation.logging = Revocation
+#idp.revocation.accessPolicy = AccessDenied
+#idp.revocation.authenticated = false
+#idp.revocation.nonBrowserSupported = false
+#idp.revocation.defaultAuthenticationMethods =
+#idp.revocation.resolveAttributes = false
+#idp.revocation.postAuthenticationFlows =
+
#idp.storage.logging = Storage
#idp.storage.accessPolicy = AccessDenied
#idp.storage.authenticated = false
diff --git a/idp-profile-api/src/main/java/net/shibboleth/idp/profile/AbstractProfileAction.java b/idp-profile-api/src/main/java/net/shibboleth/idp/profile/AbstractProfileAction.java
index 3e9700e9a..0192ca9aa 100644
--- a/idp-profile-api/src/main/java/net/shibboleth/idp/profile/AbstractProfileAction.java
+++ b/idp-profile-api/src/main/java/net/shibboleth/idp/profile/AbstractProfileAction.java
@@ -45,6 +45,7 @@ import org.springframework.context.MessageSourceAware;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.NoSuchMessageException;
import org.springframework.webflow.core.collection.AttributeMap;
+import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.execution.Action;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
@@ -254,6 +255,65 @@ public abstract class AbstractProfileAction
return null;
}
+ /**
+ * Return a casted parameter of a given name from the flow's flow or conversation scope (in that order).
+ *
+ * @param <T> the parameter type
+ *
+ * @param profileRequestContext profile request context
+ * @param name parameter name
+ *
+ * @return the parameter or null
+ *
+ * @since 4.3.0
+ */
+ @Nullable protected <T> T getParameter(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull @NotEmpty final String name) {
+
+ final SpringRequestContext springRequestContext =
+ profileRequestContext.getSubcontext(SpringRequestContext.class);
+ if (springRequestContext == null) {
+ log.warn("{} Spring request context not found in profile request context", getLogPrefix());
+ return null;
+ }
+
+ final RequestContext requestContext = springRequestContext.getRequestContext();
+ if (requestContext == null) {
+ log.warn("{} Web Flow request context not found in Spring request context", getLogPrefix());
+ return null;
+ }
+
+ return getParameter(requestContext, name);
+ }
+
+ /**
+ * Return a casted parameter of a given name from the flow's flow or conversation scope (in that order).
+ *
+ * @param <T> the parameter type
+ *
+ * @param flowRequestContext the active flow's request context
+ * @param name parameter name
+ *
+ * @return the parameter or null
+ *
+ * @since 4.3.0
+ */
+ @Nullable protected <T> T getParameter(@Nonnull final RequestContext flowRequestContext,
+ @Nonnull @NotEmpty final String name) {
+
+ MutableAttributeMap<Object> scope = flowRequestContext.getFlowScope();
+ if (scope != null && scope.contains(name)) {
+ return (T) scope.get(name);
+ }
+
+ scope = flowRequestContext.getConversationScope();
+ if (scope != null && scope.contains(name)) {
+ return (T) scope.get(name);
+ }
+
+ return null;
+ }
+
/** {@inheritDoc} */
@Override
public void setMessageSource(final MessageSource source) {
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list