org.forgerock.openam.jaspi.modules.session.LocalSSOTokenSessionModule.java Source code

Java tutorial

Introduction

Here is the source code for org.forgerock.openam.jaspi.modules.session.LocalSSOTokenSessionModule.java

Source

/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2013 ForgeRock AS.
 */

package org.forgerock.openam.jaspi.modules.session;

import com.iplanet.sso.SSOException;
import com.iplanet.sso.SSOToken;
import com.sun.identity.authentication.service.AuthUtils;
import com.sun.identity.session.util.RestrictedTokenAction;
import com.sun.identity.session.util.RestrictedTokenContext;
import org.apache.commons.lang.StringUtils;
import org.forgerock.openam.auth.shared.AuthUtilsWrapper;
import org.forgerock.openam.auth.shared.AuthnRequestUtils;
import org.forgerock.openam.auth.shared.SSOTokenFactory;
import org.forgerock.openam.guice.InjectorHolder;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.message.AuthException;
import javax.security.auth.message.AuthStatus;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.MessagePolicy;
import javax.security.auth.message.callback.CallerPrincipalCallback;
import javax.security.auth.message.module.ServerAuthModule;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

/**
 * A local implementation of a SSOToken Session module, that is designed to be deployed in an OpenAM deployment
 * not remotely, i.e. protecting resources on another server which uses OpenAM for authentication.
 *
 * The SSOToken module will validate the presents and validity of a SSOToken ID on a request, if present and valid then
 * the request is allowed to proceed. The responsibilities of this module are only to validate but never to issue
 * a SSOToken, for this the client must authenticate before trying to access the resource again.
 *
 * @author Phill Cunnington
 */
public class LocalSSOTokenSessionModule implements ServerAuthModule {

    private static final String REQUESTER_URL_PARAM = "requester";

    private final AuthUtilsWrapper authUtilsWrapper;

    private AuthnRequestUtils requestUtils;
    private SSOTokenFactory factory;
    private CallbackHandler handler;

    /**
     * Default constructor is initialised by the framework.
     */
    public LocalSSOTokenSessionModule() {
        authUtilsWrapper = new AuthUtilsWrapper();
    }

    /**
     * Test constructor exposes required dependencies.
     *
     * @param requestUtils Required for processing ServletRequests.
     * @param factory Required for generation of SSOTokens.
     * @param authUtilsWrapper Required for getting cookie name.
     */
    public LocalSSOTokenSessionModule(AuthnRequestUtils requestUtils, SSOTokenFactory factory,
            AuthUtilsWrapper authUtilsWrapper) {
        this.requestUtils = requestUtils;
        this.factory = factory;
        this.authUtilsWrapper = authUtilsWrapper;
    }

    /**
     * @return False if the dependencies have not been initialised.
     */
    private boolean isInitialised() {
        return getFactory() != null;
    }

    /**
     * Use Guice to initalise the dependencies.
     */
    private void initDependencies() {
        factory = InjectorHolder.getInstance(SSOTokenFactory.class);
        requestUtils = InjectorHolder.getInstance(AuthnRequestUtils.class);
    }

    /**
     * No initialisation required for this module.
     *
     * @param requestPolicy {@inheritDoc}
     * @param responsePolicy {@inheritDoc}
     * @param handler {@inheritDoc}
     * @param options {@inheritDoc}
     * @throws AuthException {@inheritDoc}
     */
    @Override
    public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, CallbackHandler handler,
            Map options) throws AuthException {
        this.handler = handler;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Class[] getSupportedMessageTypes() {
        return new Class[] { HttpServletRequest.class, HttpServletResponse.class };
    }

    /**
     * Validates the request by checking the validity of the SSOToken ID from the AM cookie.
     * <p>
     * If the SSOToken ID is a restricted token then the request must also contain a url parameter "requester" which
     * must contain the application SSOToken ID of the application the restricted token was issued for.
     *
     * @param messageInfo {@inheritDoc}
     * @param clientSubject {@inheritDoc}
     * @param serviceSubject {@inheritDoc}
     * @return {@inheritDoc}
     * @throws AuthException If there is a problem validating the request.
     */
    @Override
    public AuthStatus validateRequest(final MessageInfo messageInfo, final Subject clientSubject,
            Subject serviceSubject) throws AuthException {
        if (!isInitialised()) {
            initDependencies();
        }

        final HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage();

        String requester = request.getParameter(REQUESTER_URL_PARAM);
        if (requester != null) {
            try {
                SSOToken requesterToken = getFactory().getTokenFromId(requester);
                if (getFactory().isTokenValid(requesterToken)) {
                    Object o = RestrictedTokenContext.doUsing(requesterToken, new RestrictedTokenAction() {
                        public Object run() throws Exception {
                            return validate(request, messageInfo, clientSubject);
                        }
                    });
                    return (AuthStatus) o;
                }
            } catch (Exception ex) {
                throw new AuthException("An error occurred whilst trying to use restricted token.");
            }
        }
        return validate(request, messageInfo, clientSubject);
    }

    /**
     * Gets the AM cookie name, as set by AM.
     *
     * @return The AM cookie name.
     */
    private String getCookieHeaderName() {
        return authUtilsWrapper.getCookieName();
    }

    /**
     * Validates the request by attempting to retrieve the SSOToken ID from the cookies on the request.
     * If the SSOToken ID cookie is not present then the method returns AuthStatus.SEND_FAILURE, otherwise if it is
     * present it is then used to retrieve the actual SSOToken from the SSOTokenManager, if valid then
     * AuthStatus.SUCCESS will be returned, otherwise AuthStatus.SEND_FAILURE will be returned.
     *
     * @param request The HttpServletRequest.
     * @param messageInfo A contextual object that encapsulates the client request and server response objects, and
     *                    that may be used to save state across a sequence of calls made to the methods of this
     *                    interface for the purpose of completing a secure message exchange.
     * @param clientSubject A Subject that represents the source of the service request. It is used by the method
     *                      implementation to store Principals and credentials validated in the request.
     * @return AuthStatus.SUCCESS if the SSOToken ID is valid, otherwise AuthStatus.SEND_FAILURE.
     * @throws AuthException If there is a problem validating the request.
     */
    private AuthStatus validate(HttpServletRequest request, MessageInfo messageInfo, Subject clientSubject)
            throws AuthException {

        String tokenId = getRequestUtils().getTokenId(request);
        if (StringUtils.isEmpty(tokenId)) {
            tokenId = request.getHeader(getCookieHeaderName());
        }
        if (!StringUtils.isEmpty(tokenId)) {
            SSOToken ssoToken = getFactory().getTokenFromId(tokenId);

            if (ssoToken != null) {

                int authLevel;
                try {
                    authLevel = ssoToken.getAuthLevel();
                    String name = ssoToken.getPrincipal().getName();
                    handler.handle(new Callback[] { new CallerPrincipalCallback(clientSubject, name) });

                    clientSubject.getPrincipals().add(ssoToken.getPrincipal());
                } catch (SSOException e) {
                    throw new AuthException(e.getMessage());
                } catch (UnsupportedCallbackException e) {
                    throw new AuthException(e.getMessage());
                } catch (IOException e) {
                    throw new AuthException(e.getMessage());
                }

                Map<String, Object> context = (Map<String, Object>) messageInfo.getMap()
                        .get("org.forgerock.authentication.context");
                context.put("authLevel", authLevel);
                context.put("tokenId", ssoToken.getTokenID().toString());
                //TODO add more properties to context map

                return AuthStatus.SUCCESS;
            }
        }
        return AuthStatus.SEND_FAILURE;
    }

    /**
     * This module will always return AuthStatus.SEND_SUCCESS as it is not designed to return a SSOToken, ony to
     * validate that one is present on the request.
     *
     * @param messageInfo {@inheritDoc}
     * @param serviceSubject {@inheritDoc}
     * @return {@inheritDoc}
     */
    @Override
    public AuthStatus secureResponse(MessageInfo messageInfo, Subject serviceSubject) {
        return AuthStatus.SEND_SUCCESS;
    }

    /**
     * No cleaning for the Subject is required for this module.
     *
     * @param messageInfo {@inheritDoc}
     * @param subject {@inheritDoc}
     * @throws AuthException {@inheritDoc}
     */
    @Override
    public void cleanSubject(MessageInfo messageInfo, Subject subject) throws AuthException {
    }

    /**
     * @return The AuthRequestUtils, Guice initialised if it has not been assigned.
     */
    public AuthnRequestUtils getRequestUtils() {
        return requestUtils;
    }

    /**
     * @return The SSOTokenFactory, Guice initialised if it has not been assigned.
     */
    public SSOTokenFactory getFactory() {
        return factory;
    }
}