org.forgerock.openidm.jaspi.modules.IDMUserAuthModule.java Source code

Java tutorial

Introduction

Here is the source code for org.forgerock.openidm.jaspi.modules.IDMUserAuthModule.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 Inc.
 */

package org.forgerock.openidm.jaspi.modules;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.forgerock.json.fluent.JsonValue;
import org.forgerock.openidm.core.IdentityServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.message.AuthStatus;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.MessagePolicy;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

/**
 * Authentication Module for authenticating users against a managed users table.
 *
 * @author Phill Cunnington
 */
public abstract class IDMUserAuthModule extends IDMServerAuthModule {

    private final static Logger logger = LoggerFactory.getLogger(IDMUserAuthModule.class);

    private final String queryId;
    private final String queryOnResource;

    // A list of ports that allow authentication purely based on client certificates (SSL mutual auth)
    private Set<Integer> clientAuthOnly = new HashSet<Integer>();

    private AuthHelper authHelper;

    /**
     * Constructor used by the commons Authentication Filter framework to create an instance of this authentication
     * module.
     */
    public IDMUserAuthModule(String queryId, String queryOnResource) {
        this.queryId = queryId;
        this.queryOnResource = queryOnResource;
    }

    /**
     * Constructor used by tests to inject dependencies.
     *
     * @param authHelper A mock of an AuthHelper instance.
     */
    public IDMUserAuthModule(AuthHelper authHelper, String queryId, String queryOnResource) {
        this.authHelper = authHelper;
        this.queryId = queryId;
        this.queryOnResource = queryOnResource;
    }

    /**
     * Initialises the ManagedUserAuthModule.
     *
     * @param requestPolicy {@inheritDoc}
     * @param responsePolicy {@inheritDoc}
     * @param handler {@inheritDoc}
     * @param options {@inheritDoc}
     */
    @Override
    protected void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, CallbackHandler handler,
            JsonValue options) {

        String clientAuthOnlyStr = IdentityServer.getInstance().getProperty("openidm.auth.clientauthonlyports");
        if (clientAuthOnlyStr != null) {
            String[] split = clientAuthOnlyStr.split(",");
            for (String entry : split) {
                clientAuthOnly.add(Integer.valueOf(entry));
            }
        }
        logger.info("Authentication disabled on ports: {}", clientAuthOnly);

        JsonValue properties = options.get("propertyMapping");
        String userIdProperty = properties.get("userId").asString();
        String userCredentialProperty = properties.get("userCredential").asString();
        String userRolesProperty = properties.get("userRoles").asString();
        List<String> defaultRoles = options.get("defaultUserRoles").asList(String.class);

        authHelper = new AuthHelper(userIdProperty, userCredentialProperty, userRolesProperty, defaultRoles);
    }

    /**
     * Validates the request by authenticating against either the client certificate in the request, internally or
     * Basic Authentication from the request header internally.
     *
     * @param messageInfo {@inheritDoc}
     * @param clientSubject {@inheritDoc}
     * @param serviceSubject {@inheritDoc}
     * @param authData {@inheritDoc}
     * @return {@inheritDoc}
     */
    @Override
    protected AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject,
            AuthData authData) {

        HttpServletRequest req = (HttpServletRequest) messageInfo.getRequestMessage();
        boolean authenticated;

        final String headerLogin = req.getHeader(HEADER_USERNAME);
        String basicAuth = req.getHeader("Authorization");
        // if we see the certificate port this request is for client auth only
        if (allowClientCertOnly(req)) {
            authenticated = authenticateUsingClientCert(req, authData);
            //Auth success will be logged in IDMServerAuthModule super type.
        } else if (headerLogin != null) {
            authenticated = authenticateUser(req, authData);
            //Auth success will be logged in IDMServerAuthModule super type.
        } else if (basicAuth != null) {
            authenticated = authenticateUsingBasicAuth(basicAuth, authData);
            //Auth success will be logged in IDMServerAuthModule super type.
        } else {
            //Auth failure will be logged in IDMServerAuthModule super type.
            return AuthStatus.SEND_FAILURE;
        }
        authData.setResource(queryOnResource);
        logger.debug("Found valid session for {} id {} with roles {}", authData.getUsername(), authData.getUserId(),
                authData.getRoles());

        if (authenticated) {
            clientSubject.getPrincipals().add(new Principal() {
                public String getName() {
                    return headerLogin;
                }
            });
        }

        return authenticated ? AuthStatus.SUCCESS : AuthStatus.SEND_FAILURE;
    }

    /**
     * Whether to allow authentication purely based on client certificates.
     *
     * Note that the checking of the certificates MUST be done by setting jetty up for client auth required.
     *
     * @return true if authentication via client certificate only is sufficient.
     */
    private boolean allowClientCertOnly(ServletRequest request) {
        return clientAuthOnly.contains(Integer.valueOf(request.getLocalPort()));
    }

    /**
     * Authenticates the request using the client certificate from the request.
     *
     * @param request The ServletRequest.
     * @param authData The AuthData object.
     */
    // This is currently Jetty specific
    private boolean authenticateUsingClientCert(ServletRequest request, AuthData authData) {

        logger.debug("Client certificate authentication request");
        X509Certificate[] certs = getClientCerts(request);

        if (certs != null) {
            Principal existingPrincipal = null;
            if (request instanceof HttpServletRequest) {
                ((HttpServletRequest) request).getUserPrincipal();
            }
            logger.debug("Request {} existing Principal {} has {} certificates", request, existingPrincipal,
                    certs.length);
            for (X509Certificate cert : certs) {
                logger.debug("Request {} client certificate subject DN: {}", request, cert.getSubjectDN());
            }
        }
        if (certs == null || certs.length == 0 || certs[0] == null) {
            return false;
        }
        authData.setUsername(certs[0].getSubjectDN().getName());
        authData.setUserId(authData.getUsername());
        authData.getRoles().add("openidm-cert");
        logger.debug("Authentication client certificate subject {}", authData.getUsername());
        return true;
    }

    /**
     * Gets the client certificates from the request.
     *
     * @param request The ServletRequest.
     * @return An array of X509Certificates.
     */
    // This is currently Jetty specific
    private X509Certificate[] getClientCerts(ServletRequest request) {

        Object checkCerts = request.getAttribute("javax.servlet.request.X509Certificate");
        if (checkCerts instanceof X509Certificate[]) {
            return (X509Certificate[]) checkCerts;
        } else {
            logger.warn("Unknown certificate type retrieved {}", checkCerts);
            return null;
        }
    }

    /**
     * Authenticates the request.
     *
     * @param request The HttpServletRequest.
     * @param authData The AuthData object.
     */
    private boolean authenticateUser(HttpServletRequest request, AuthData authData) {

        logger.debug("No session, authenticating user");
        String username = request.getHeader(HEADER_USERNAME);
        String password = request.getHeader(HEADER_PASSWORD);
        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            logger.debug("Failed authentication, missing or empty headers");
            return false;
        }
        authData.setUsername(username);
        return authHelper.authenticate(queryId, queryOnResource, username, password, authData);
    }

    /**
     * Authenticates the request using the contents of the Http Authorization Header.
     *
     * @param authorizationHeader The Http Authorization Header value.
     * @param authData The AuthData object.
     */
    private boolean authenticateUsingBasicAuth(String authorizationHeader, AuthData authData) {
        logger.debug("HTTP basic authentication request");
        StringTokenizer st = new StringTokenizer(authorizationHeader);
        String isBasic = st.nextToken();
        if (isBasic == null || !isBasic.equalsIgnoreCase("Basic")) {
            return false;
        }
        String creds = st.nextToken();
        if (creds == null) {
            return false;
        }
        String dcreds = new String(Base64.decodeBase64(creds.getBytes()));
        String[] t = dcreds.split(":");
        if (t.length != 2) {
            return false;
        }
        authData.setUsername(t[0]);
        return authHelper.authenticate(queryId, queryOnResource, t[0], t[1], authData);
    }

    /**
     * No work to do here so always returns AuthStatus.SEND_SUCCESS.
     *
     * @param messageInfo {@inheritDoc}
     * @param serviceSubject {@inheritDoc}
     * @return {@inheritDoc}
     */
    @Override
    public AuthStatus secureResponse(MessageInfo messageInfo, Subject serviceSubject) {
        return super.secureResponse(messageInfo, serviceSubject);
    }

    /**
     * Nothing to clean up.
     *
     * @param messageInfo {@inheritDoc}
     * @param subject {@inheritDoc}
     */
    @Override
    public void cleanSubject(MessageInfo messageInfo, Subject subject) {
    }
}