net.shibboleth.idp.saml.saml2.profile.impl.PopulateEncryptionParameters.java Source code

Java tutorial

Introduction

Here is the source code for net.shibboleth.idp.saml.saml2.profile.impl.PopulateEncryptionParameters.java

Source

/*
 * 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.saml.saml2.profile.impl;

import java.util.Collections;
import java.util.List;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.xml.namespace.QName;

import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
import org.opensaml.saml.common.messaging.context.SAMLMetadataContext;
import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext;
import org.opensaml.saml.criterion.EntityRoleCriterion;
import org.opensaml.saml.criterion.ProtocolCriterion;
import org.opensaml.saml.criterion.RoleDescriptorCriterion;
import org.opensaml.saml.saml2.core.AuthnRequest;
import org.opensaml.saml.saml2.core.NameID;
import org.opensaml.saml.saml2.profile.context.EncryptionContext;
import org.opensaml.xmlsec.EncryptionConfiguration;
import org.opensaml.xmlsec.EncryptionParameters;
import org.opensaml.xmlsec.EncryptionParametersResolver;
import org.opensaml.xmlsec.SecurityConfigurationSupport;
import org.opensaml.xmlsec.criterion.EncryptionConfigurationCriterion;

import net.shibboleth.idp.profile.AbstractProfileAction;
import net.shibboleth.idp.profile.IdPEventIds;
import net.shibboleth.idp.profile.context.RelyingPartyContext;
import net.shibboleth.idp.saml.saml2.profile.config.SAML2ProfileConfiguration;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
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.resolver.CriteriaSet;
import net.shibboleth.utilities.java.support.resolver.ResolverException;

import org.opensaml.core.criterion.EntityIdCriterion;
import org.opensaml.messaging.context.navigate.ChildContextLookup;
import org.opensaml.messaging.context.navigate.MessageLookup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Function;
import com.google.common.base.Functions;

/**
 * Action that resolves and populates {@link EncryptionParameters} on an {@link EncryptionContext}
 * created/accessed via a lookup function, by default on a {@link RelyingPartyContext} child of the
 * profile request context.
 * 
 * <p>The resolution process is contingent on the active profile configuration requesting encryption
 * of some kind, and an {@link EncryptionContext} is also created to capture these requirements.</p>
 * 
 * <p>The OpenSAML default, per-RelyingParty, and default per-profile {@link EncryptionConfiguration}
 * objects are input to the resolution process, along with the relying party's SAML metadata, which in
 * most cases will be the source of the eventual encryption key.</p>
 * 
 * @event {@link EventIds#PROCEED_EVENT_ID}
 * @event {@link EventIds#INVALID_PROFILE_CTX}
 * @event {@link EventIds#INVALID_SEC_CFG}
 * @event {@link IdPEventIds#INVALID_RELYING_PARTY_CTX}
 * @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
 */
public class PopulateEncryptionParameters extends AbstractProfileAction {

    /** Class logger. */
    @Nonnull
    private final Logger log = LoggerFactory.getLogger(PopulateEncryptionParameters.class);

    /** Strategy used to locate the {@link AuthnRequest} to operate on, if any. */
    @Nonnull
    private Function<ProfileRequestContext, AuthnRequest> requestLookupStrategy;

    /** Strategy used to look up a {@link RelyingPartyContext} for configuration options. */
    @Nonnull
    private Function<ProfileRequestContext, RelyingPartyContext> relyingPartyContextLookupStrategy;

    /** Strategy used to look up the {@link EncryptionContext} to store parameters in. */
    @Nonnull
    private Function<ProfileRequestContext, EncryptionContext> encryptionContextLookupStrategy;

    /** Strategy used to look up a SAML peer context. */
    @Nullable
    private Function<ProfileRequestContext, SAMLPeerEntityContext> peerContextLookupStrategy;

    /** Metadata protocolSupportEnumeration value to provide to resolver. */
    @Nullable
    private String samlProtocol;

    /** Metadata role type to provide to resolver. */
    @Nullable
    private QName peerRole;

    /** Strategy used to look up a per-request {@link EncryptionConfiguration} list. */
    @NonnullAfterInit
    private Function<ProfileRequestContext, List<EncryptionConfiguration>> configurationLookupStrategy;

    /** Resolver for parameters to store into context. */
    @NonnullAfterInit
    private EncryptionParametersResolver encParamsresolver;

    /** Active configurations to feed into resolver. */
    @Nullable
    @NonnullElements
    private List<EncryptionConfiguration> encryptionConfigurations;

    /** Is encryption optional in the case no parameters can be resolved? */
    private boolean encryptionOptional;

    /** Flag tracking whether assertion encryption is required. */
    private boolean encryptAssertions;

    /** Flag tracking whether assertion encryption is required. */
    private boolean encryptIdentifiers;

    /** Flag tracking whether assertion encryption is required. */
    private boolean encryptAttributes;

    /** Constructor. */
    public PopulateEncryptionParameters() {

        requestLookupStrategy = Functions.compose(new MessageLookup<>(AuthnRequest.class),
                new InboundMessageContextLookup());

        relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);

        // Create context by default.
        encryptionContextLookupStrategy = Functions.compose(new ChildContextLookup<>(EncryptionContext.class, true),
                new ChildContextLookup<ProfileRequestContext, RelyingPartyContext>(RelyingPartyContext.class));

        // Default: outbound msg context -> SAMLPeerEntityContext
        peerContextLookupStrategy = Functions.compose(new ChildContextLookup<>(SAMLPeerEntityContext.class),
                new OutboundMessageContextLookup());
    }

    /**
     * Set the strategy used to locate the {@link AuthnRequest} to examine, if any.
     * 
     * @param strategy strategy used to locate the {@link AuthnRequest}
     */
    public void setRequestLookupStrategy(@Nonnull final Function<ProfileRequestContext, AuthnRequest> strategy) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);

        requestLookupStrategy = Constraint.isNotNull(strategy, "AuthnRequest lookup strategy cannot be null");
    }

    /**
     * Set the strategy used to return the {@link RelyingPartyContext} for configuration options.
     * 
     * @param strategy lookup strategy
     */
    public void setRelyingPartyContextLookupStrategy(
            @Nonnull final Function<ProfileRequestContext, RelyingPartyContext> strategy) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);

        relyingPartyContextLookupStrategy = Constraint.isNotNull(strategy,
                "RelyingPartyContext lookup strategy cannot be null");
    }

    /**
     * Set the strategy used to look up the {@link EncryptionContext} to set the flags for.
     * 
     * @param strategy lookup strategy
     */
    public void setEncryptionContextLookupStrategy(
            @Nonnull final Function<ProfileRequestContext, EncryptionContext> strategy) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);

        encryptionContextLookupStrategy = Constraint.isNotNull(strategy,
                "EncryptionContext lookup strategy cannot be null");
    }

    /**
     * Set the protocol constant to use during resolution.
     * 
     * @param protocol the protocol constant to set
     */
    public void setProtocol(@Nullable final String protocol) {
        samlProtocol = StringSupport.trimOrNull(protocol);
    }

    /**
     * Set the operational role to use during resolution.
     * 
     * @param role the operational role to set
     */
    public void setRole(@Nullable final QName role) {
        peerRole = role;
    }

    /**
     * Set the strategy used to look up a per-request {@link EncryptionConfiguration} list.
     * 
     * @param strategy lookup strategy
     */
    public void setConfigurationLookupStrategy(
            @Nonnull final Function<ProfileRequestContext, List<EncryptionConfiguration>> strategy) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);

        configurationLookupStrategy = Constraint.isNotNull(strategy,
                "EncryptionConfiguration lookup strategy cannot be null");
    }

    /**
     * Set lookup strategy for {@link SAMLPeerEntityContext} for input to resolution.
     * 
     * @param strategy  lookup strategy
     */
    public void setPeerContextLookupStrategy(
            @Nullable final Function<ProfileRequestContext, SAMLPeerEntityContext> strategy) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);

        peerContextLookupStrategy = strategy;
    }

    /**
     * Set the encParamsresolver to use for the parameters to store into the context.
     * 
     * @param newResolver   encParamsresolver to use
     */
    public void setEncryptionParametersResolver(@Nonnull final EncryptionParametersResolver newResolver) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);

        encParamsresolver = Constraint.isNotNull(newResolver, "EncryptionParametersResolver cannot be null");
    }

    /** {@inheritDoc} */
    @Override
    protected void doInitialize() throws ComponentInitializationException {
        super.doInitialize();

        if (encParamsresolver == null) {
            throw new ComponentInitializationException("EncryptionParametersResolver cannot be null");
        } else if (configurationLookupStrategy == null) {
            configurationLookupStrategy = new Function<ProfileRequestContext, List<EncryptionConfiguration>>() {
                public List<EncryptionConfiguration> apply(ProfileRequestContext input) {
                    return Collections
                            .singletonList(SecurityConfigurationSupport.getGlobalEncryptionConfiguration());
                }
            };
        }
    }

    // Checkstyle: CyclomaticComplexity OFF
    /** {@inheritDoc} */
    @Override
    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {

        final RelyingPartyContext rpContext = relyingPartyContextLookupStrategy.apply(profileRequestContext);
        if (rpContext == null) {
            log.debug("{} Unable to locate RelyingPartyContext", getLogPrefix());
            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
            return false;
        } else if (rpContext.getProfileConfig() == null) {
            log.debug("{} Unable to locate RelyingPartyContext", getLogPrefix());
            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
            return false;
        } else if (!(rpContext.getProfileConfig() instanceof SAML2ProfileConfiguration)) {
            log.debug("{} Not a SAML 2 profile configuration, nothing to do", getLogPrefix());
            return false;
        }

        final SAML2ProfileConfiguration profileConfiguration = (SAML2ProfileConfiguration) rpContext
                .getProfileConfig();

        final AuthnRequest request = requestLookupStrategy.apply(profileRequestContext);
        if (request != null && request.getNameIDPolicy() != null) {
            final String requestedFormat = request.getNameIDPolicy().getFormat();
            if (requestedFormat != null && NameID.ENCRYPTED.equals(requestedFormat)) {
                log.debug("{} Request asked for encrypted identifier, disregarding installed predicate");
                encryptIdentifiers = true;
            }
        }

        if (!encryptIdentifiers) {
            encryptIdentifiers = profileConfiguration.getEncryptNameIDs().apply(profileRequestContext);
            // Encryption can only be optional if the request didn't specify it above.
            encryptionOptional = profileConfiguration.isEncryptionOptional();
        }

        encryptAssertions = profileConfiguration.getEncryptAssertions().apply(profileRequestContext);
        encryptAttributes = profileConfiguration.getEncryptAttributes().apply(profileRequestContext);

        if (!encryptAssertions && !encryptIdentifiers && !encryptAttributes) {
            log.debug("{} No encryption requested, nothing to do", getLogPrefix());
            return false;
        }

        encryptionConfigurations = configurationLookupStrategy.apply(profileRequestContext);

        log.debug("{} Encryption for assertions ({}), identifiers ({}), attributes({})", getLogPrefix(),
                encryptAssertions, encryptIdentifiers, encryptAttributes);

        return super.doPreExecute(profileRequestContext);
    }

    /** {@inheritDoc} */
    @Override
    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {

        log.debug("{} Resolving EncryptionParameters for request", getLogPrefix());

        final EncryptionContext encryptCtx = encryptionContextLookupStrategy.apply(profileRequestContext);
        if (encryptCtx == null) {
            log.debug("{} No EncryptionContext returned by lookup strategy", getLogPrefix());
            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
            return;
        }

        try {
            if (encryptionConfigurations == null || encryptionConfigurations.isEmpty()) {
                throw new ResolverException("No EncryptionConfigurations returned by lookup strategy");
            }

            final EncryptionParameters params = encParamsresolver
                    .resolveSingle(buildCriteriaSet(profileRequestContext));
            log.debug("{} {} EncryptionParameters", getLogPrefix(),
                    params != null ? "Resolved" : "Failed to resolve");
            if (params != null) {
                if (encryptAssertions) {
                    encryptCtx.setAssertionEncryptionParameters(params);
                }
                if (encryptIdentifiers) {
                    encryptCtx.setIdentifierEncryptionParameters(params);
                }
                if (encryptAttributes) {
                    encryptCtx.setAttributeEncryptionParameters(params);
                }
            } else {
                log.warn("{} Resolver returned no EncryptionParameters", getLogPrefix());
                if (encryptionOptional) {
                    log.info("{} Encryption is optional, ignoring inability to encrypt", getLogPrefix());
                } else {
                    ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_SEC_CFG);
                }
            }
        } catch (final ResolverException e) {
            log.error("{} Error resolving EncryptionParameters", getLogPrefix(), e);
            if (encryptionOptional) {
                log.info("{} Encryption is optional, ignoring inability to encrypt", getLogPrefix());
            } else {
                ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_SEC_CFG);
            }
        }
    }
    // Checkstyle: CyclomaticComplexity ON

    /**
     * Build the criteria used as input to the {@link EncryptionParametersResolver}.
     * 
     * @param profileRequestContext current profile request context
     * 
     * @return  the criteria set to use
     */
    @Nonnull
    private CriteriaSet buildCriteriaSet(@Nonnull final ProfileRequestContext profileRequestContext) {

        final CriteriaSet criteria = new CriteriaSet(
                new EncryptionConfigurationCriterion(encryptionConfigurations));

        if (peerContextLookupStrategy != null) {
            final SAMLPeerEntityContext peerCtx = peerContextLookupStrategy.apply(profileRequestContext);
            if (peerCtx != null) {
                if (peerCtx.getEntityId() != null) {
                    log.debug("{} Adding entityID to resolution criteria", getLogPrefix());
                    criteria.add(new EntityIdCriterion(peerCtx.getEntityId()));
                    if (samlProtocol != null) {
                        criteria.add(new ProtocolCriterion(samlProtocol));
                    }
                    if (peerRole != null) {
                        criteria.add(new EntityRoleCriterion(peerRole));
                    }
                }
                final SAMLMetadataContext metadataCtx = peerCtx.getSubcontext(SAMLMetadataContext.class);
                if (metadataCtx != null && metadataCtx.getRoleDescriptor() != null) {
                    log.debug("{} Adding role metadata to resolution criteria", getLogPrefix());
                    criteria.add(new RoleDescriptorCriterion(metadataCtx.getRoleDescriptor()));
                }
            }
        }

        return criteria;
    }

}