All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opensaml.saml.saml2.profile.impl.AddStatusToResponse Maven / Gradle / Ivy

There is a newer version: 4.0.1
Show newest version
/*
 * 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 org.opensaml.saml.saml2.profile.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.opensaml.profile.action.AbstractProfileAction;
import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.EventContext;
import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.profile.context.navigate.CurrentOrPreviousEventLookup;
import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;

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.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
import net.shibboleth.utilities.java.support.primitive.StringSupport;

import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.messaging.context.navigate.MessageLookup;
import org.opensaml.saml.common.SAMLObjectBuilder;
import org.opensaml.saml.saml2.core.Status;
import org.opensaml.saml.saml2.core.StatusCode;
import org.opensaml.saml.saml2.core.StatusMessage;
import org.opensaml.saml.saml2.core.StatusResponseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;

/**
 * Action that sets {@link Status} content in a {@link Response} obtained from
 * a lookup strategy, typically from the outbound message context.
 * 
 * 

If the message already contains status information, this action will overwrite it.

* *

Options allows for the creation of a {@link StatusMessage} either explicitly, * or via lookup strategy.

* * @event {@link EventIds#PROCEED_EVENT_ID} * @event {@link EventIds#INVALID_MSG_CTX} */ public class AddStatusToResponse extends AbstractProfileAction { /** Class logger. */ @Nonnull private Logger log = LoggerFactory.getLogger(AddStatusToResponse.class); /** Strategy used to locate the {@link StatusResponseType} to operate on. */ @Nonnull private Function responseLookupStrategy; /** Predicate determining whether detailed error information is permitted. */ @Nonnull private Predicate detailedErrorsCondition; /** Optional method to obtain status codes. */ @Nullable private Function> statusCodesLookupStrategy; /** Optional method to obtain a status message. */ @Nullable private Function statusMessageLookupStrategy; /** One or more status codes to insert. */ @Nonnull @NonnullElements private List defaultStatusCodes; /** A default status message to include. */ @Nullable private String statusMessage; /** Whether to include detailed status information. */ private boolean detailedErrors; /** Response to modify. */ @Nullable private StatusResponseType response; /** Constructor. */ public AddStatusToResponse() { responseLookupStrategy = Functions.compose(new MessageLookup<>(StatusResponseType.class), new OutboundMessageContextLookup()); detailedErrorsCondition = Predicates.alwaysFalse(); defaultStatusCodes = Collections.emptyList(); detailedErrors = false; } /** * Set the strategy used to locate the {@link StatusResponseType} to operate on. * * @param strategy strategy used to locate the {@link StatusResponseType} to operate on */ public void setResponseLookupStrategy(@Nonnull final Function strategy) { ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this); responseLookupStrategy = Constraint.isNotNull(strategy, "Response lookup strategy cannot be null"); } /** * Set the predicate used to determine the detailed errors condition. * * @param condition predicate for detailed errors condition */ public void setDetailedErrorsCondition(@Nonnull final Predicate condition) { ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this); detailedErrorsCondition = Constraint.isNotNull(condition, "Detailed errors condition cannot be null"); } /** * Set the optional strategy used to obtain status codes to include. * * @param strategy strategy used to obtain status codes */ public void setStatusCodesLookupStrategy(@Nullable final Function> strategy) { ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this); statusCodesLookupStrategy = strategy; } /** * Set the optional strategy used to obtain a status message to include. * * @param strategy strategy used to obtain a status message */ public void setStatusMessageLookupStrategy(@Nullable final Function strategy) { ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this); statusMessageLookupStrategy = strategy; } /** * Set the list of status code values to insert, ordered such that the top level code is first * and every other code will be nested inside the previous one. * * @param codes list of status code values to insert */ public void setStatusCodes(@Nonnull @NonnullElements List codes) { ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this); Constraint.isNotNull(codes, "Status code list cannot be null"); defaultStatusCodes = new ArrayList<>(Collections2.filter(codes, Predicates.notNull())); } /** * Set a default status message to use in the event that error detail is off, * or no specific message is obtained. * * @param message default status message */ public void setStatusMessage(@Nullable final String message) { ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this); statusMessage = StringSupport.trimOrNull(message); } /** {@inheritDoc} */ @Override protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) { ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this); response = responseLookupStrategy.apply(profileRequestContext); if (response == null) { log.debug("{} Response message was not returned by lookup strategy", getLogPrefix()); ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX); return false; } detailedErrors = detailedErrorsCondition.apply(profileRequestContext); log.debug("{} Detailed errors are {}", getLogPrefix(), detailedErrors ? "enabled" : "disabled"); return super.doPreExecute(profileRequestContext); } /** {@inheritDoc} */ @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) { final SAMLObjectBuilder statusBuilder = (SAMLObjectBuilder) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilderOrThrow(Status.TYPE_NAME); final Status status = statusBuilder.buildObject(); response.setStatus(status); if (statusCodesLookupStrategy != null) { final List codes = statusCodesLookupStrategy.apply(profileRequestContext); if (codes == null || codes.isEmpty()) { buildStatusCode(status, defaultStatusCodes); } else { buildStatusCode(status, codes); } } else { buildStatusCode(status, defaultStatusCodes); } // StatusMessage processing. if (!detailedErrors || statusMessageLookupStrategy == null) { if (statusMessage != null) { log.debug("{} Setting StatusMessage to defaulted value", getLogPrefix()); buildStatusMessage(status, statusMessage); } } else if (statusMessageLookupStrategy != null) { final String message = statusMessageLookupStrategy.apply(profileRequestContext); if (message != null) { log.debug("{} Current state of request was mappable, setting StatusMessage to mapped value", getLogPrefix()); buildStatusMessage(status, message); } else if (statusMessage != null) { log.debug("{} Current state of request was not mappable, setting StatusMessage to defaulted value", getLogPrefix()); buildStatusMessage(status, statusMessage); } } } /** * Build and attach {@link StatusCode} element. * * @param status the element to attach to * @param codes the status codes to use */ private void buildStatusCode(@Nonnull final Status status, @Nonnull @NonnullElements final List codes) { final SAMLObjectBuilder statusCodeBuilder = (SAMLObjectBuilder) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilderOrThrow( StatusCode.TYPE_NAME); // Build nested StatusCodes. StatusCode statusCode = statusCodeBuilder.buildObject(); status.setStatusCode(statusCode); if (codes.isEmpty()) { statusCode.setValue(StatusCode.RESPONDER); } else { statusCode.setValue(codes.get(0)); final Iterator i = codes.iterator(); i.next(); while (i.hasNext()) { final StatusCode subcode = statusCodeBuilder.buildObject(); subcode.setValue(i.next()); statusCode.setStatusCode(subcode); statusCode = subcode; } } } /** * Build and attach {@link StatusMessage} element. * * @param status the element to attach to * @param message the message to set */ private void buildStatusMessage(@Nonnull final Status status, @Nonnull @NotEmpty final String message) { final SAMLObjectBuilder statusMessageBuilder = (SAMLObjectBuilder) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilderOrThrow( StatusMessage.DEFAULT_ELEMENT_NAME); final StatusMessage sm = statusMessageBuilder.buildObject(); sm.setMessage(message); status.setStatusMessage(sm); } /** A default method to map event IDs to SAML 2 StatusCode URIs based on {@link EventContext}. */ public static class StatusCodeMappingFunction implements Function> { /** Code mappings. */ @Nonnull @NonnullElements private Map> codeMappings; /** Strategy function for access to {@link EventContext} to check. */ @Nonnull private Function eventContextLookupStrategy; /** * Constructor. * * @param mappings the status code mappings to use */ public StatusCodeMappingFunction(@Nonnull @NonnullElements final Map> mappings) { Constraint.isNotNull(mappings, "Status code mappings cannot be null"); codeMappings = new HashMap<>(mappings.size()); for (Map.Entry> entry : mappings.entrySet()) { final String event = StringSupport.trimOrNull(entry.getKey()); if (event != null && entry.getValue() != null) { codeMappings.put(event, new ArrayList<>(Collections2.filter(entry.getValue(), Predicates.notNull()))); } } eventContextLookupStrategy = new CurrentOrPreviousEventLookup(); } /** * Set lookup strategy for {@link EventContext} to check. * * @param strategy lookup strategy */ public void setEventContextLookupStrategy( @Nonnull final Function strategy) { eventContextLookupStrategy = Constraint.isNotNull(strategy, "EventContext lookup strategy cannot be null"); } /** {@inheritDoc} */ @Override @Nullable public List apply(@Nullable final ProfileRequestContext input) { final EventContext eventCtx = eventContextLookupStrategy.apply(input); if (eventCtx != null && eventCtx.getEvent() != null) { return codeMappings.get(eventCtx.getEvent().toString()); } return Collections.emptyList(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy