net.shibboleth.idp.session.impl.UpdateSessionWithAuthenticationResult.java Source code

Java tutorial

Introduction

Here is the source code for net.shibboleth.idp.session.impl.UpdateSessionWithAuthenticationResult.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.session.impl;

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

import net.shibboleth.idp.authn.AbstractAuthenticationAction;
import net.shibboleth.idp.authn.context.AuthenticationContext;
import net.shibboleth.idp.authn.context.SubjectContext;
import net.shibboleth.idp.session.IdPSession;
import net.shibboleth.idp.session.SessionException;
import net.shibboleth.idp.session.SessionManager;
import net.shibboleth.idp.session.context.SessionContext;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
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.messaging.context.navigate.ChildContextLookup;
import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * An authentication action that establishes a record of the {@link net.shibboleth.idp.authn.AuthenticationResult}
 * in an {@link IdPSession} for the client, either by updating an existing session or creating a new one.
 * 
 * <p>A new {@link net.shibboleth.idp.authn.AuthenticationResult} may be added to the session, or the last activity
 * time of an existing one updated. A new one will only be added if the authentication context indicates that the
 * result is "cacheable".</p>
 * 
 * <p>An existing session is identified via a {@link SessionContext} attached to the {@link ProfileRequestContext}.
 * If a new session is created, it will be placed into a {@link SessionContext}, creating it if necessary, with the
 * principal name coming from a {@link SubjectContext}.</p>
 * 
 * <p>An error interacting with the session layer will result in an {@link EventIds#IO_ERROR} event.</p>
 * 
 * @event {@link EventIds#PROCEED_EVENT_ID}
 * @event {@link EventIds#INVALID_PROFILE_CTX}
 * @event {@link EventIds#IO_ERROR}
 * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class) != null</pre>
 * @post If AuthenticationContext.getAuthenticationResult() != null and
 * SubjectContext.getPrincipalName() != null then the steps above are performed,
 * and ProfileRequestContext.getSubcontext(SessionContext.class).getIdPSession() != null
 */
public class UpdateSessionWithAuthenticationResult extends AbstractAuthenticationAction {

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

    /** SessionManager. */
    @NonnullAfterInit
    private SessionManager sessionManager;

    /** Lookup/creation function for SessionContext. */
    @Nonnull
    private Function<ProfileRequestContext, SessionContext> sessionContextCreationStrategy;

    /** Lookup function for SubjectContext. */
    @Nonnull
    private Function<ProfileRequestContext, SubjectContext> subjectContextLookupStrategy;

    /** Existing or newly created SessionContext. */
    @Nullable
    private SessionContext sessionCtx;

    /** Existing SubjectContext. */
    @Nullable
    private SubjectContext subjectCtx;

    /** Constructor. */
    public UpdateSessionWithAuthenticationResult() {
        sessionContextCreationStrategy = new ChildContextLookup<>(SessionContext.class, true);
        subjectContextLookupStrategy = new ChildContextLookup<>(SubjectContext.class);
    }

    /**
     * Set the {@link SessionManager} to use.
     * 
     * @param manager  session manager to use
     */
    public void setSessionManager(@Nonnull final SessionManager manager) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);

        sessionManager = Constraint.isNotNull(manager, "SessionManager cannot be null");
    }

    /**
     * Set the lookup/creation strategy for the SessionContext to update.
     * 
     * @param strategy  creation/lookup strategy
     */
    public void setSessionContextCreationStrategy(
            @Nonnull final Function<ProfileRequestContext, SessionContext> strategy) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);

        sessionContextCreationStrategy = Constraint.isNotNull(strategy,
                "SessionContext lookup/creation strategy cannot be null");
    }

    /**
     * Set the lookup strategy for the SubjectContext to access.
     * 
     * @param strategy  lookup strategy
     */
    public void setSubjectContextLookupStrategy(
            @Nonnull final Function<ProfileRequestContext, SubjectContext> strategy) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);

        subjectContextLookupStrategy = Constraint.isNotNull(strategy,
                "SubjectContext lookup strategy cannot be null");
    }

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

        if (!getActivationCondition().equals(Predicates.alwaysFalse()) && sessionManager == null) {
            throw new ComponentInitializationException("SessionManager cannot be null");
        }
    }

    /** {@inheritDoc} */
    @Override
    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
            @Nonnull final AuthenticationContext authenticationContext) {

        if (super.doPreExecute(profileRequestContext, authenticationContext)
                && authenticationContext.getAuthenticationResult() != null) {
            subjectCtx = subjectContextLookupStrategy.apply(profileRequestContext);
            sessionCtx = sessionContextCreationStrategy.apply(profileRequestContext);
            if (sessionCtx == null) {
                log.error("{} SessionContext creation failed", getLogPrefix());
                ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
                return false;
            }

            // We can only do work if a session exists or a non-empty SubjectContext exists.
            return sessionCtx.getIdPSession() != null
                    || (subjectCtx != null && subjectCtx.getPrincipalName() != null);
        }

        return false;
    }

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

        final IdPSession session = sessionCtx.getIdPSession();
        if (session != null) {
            try {
                updateIdPSession(authenticationContext, session);
            } catch (SessionException e) {
                log.error("{} Error updating session {}", getLogPrefix(), session.getId(), e);
                ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
            }
        } else {
            try {
                createIdPSession(authenticationContext);
            } catch (SessionException e) {
                log.error("{} Error creating session for principal {}", getLogPrefix(),
                        subjectCtx.getPrincipalName(), e);
                ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
            }
        }
    }

    /**
     * Update an existing session.
     * 
     * <p>If the result is the product of an attempted flow, then it's added to the session.
     * If reused, its last activity time is updated.</p>
     * 
     * @param authenticationContext current authentication context
     * @param session session to update
     * @throws SessionException if an error occurs updating the session
     */
    private void updateIdPSession(@Nonnull final AuthenticationContext authenticationContext,
            @Nonnull final IdPSession session) throws SessionException {

        if (authenticationContext.getAttemptedFlow() != null) {
            if (authenticationContext.isResultCacheable()) {
                log.debug("{} Adding new AuthenticationResult for flow {} to existing session {}", getLogPrefix(),
                        authenticationContext.getAuthenticationResult().getAuthenticationFlowId(), session.getId());
                session.addAuthenticationResult(authenticationContext.getAuthenticationResult());
            }
        } else {
            log.debug("{} Updating activity time on reused AuthenticationResult for flow {} in existing session {}",
                    getLogPrefix(), authenticationContext.getAuthenticationResult().getAuthenticationFlowId(),
                    session.getId());
            session.updateAuthenticationResultActivity(authenticationContext.getAuthenticationResult());
        }
    }

    /**
     * Create a new session and populate the SessionContext.
     * 
     * @param authenticationContext current authentication context
     * @throws SessionException if an error occurs creating the session
     */
    private void createIdPSession(@Nonnull final AuthenticationContext authenticationContext)
            throws SessionException {

        log.debug("{} Creating new session for principal {}", getLogPrefix(), subjectCtx.getPrincipalName());

        sessionCtx.setIdPSession(sessionManager.createSession(subjectCtx.getPrincipalName()));
        if (authenticationContext.isResultCacheable()) {
            sessionCtx.getIdPSession().addAuthenticationResult(authenticationContext.getAuthenticationResult());
        }
    }
}