uk.ac.diamond.cas.shibboleth.authentication.handler.ShibbolethAuthenticationHandler.java Source code

Java tutorial

Introduction

Here is the source code for uk.ac.diamond.cas.shibboleth.authentication.handler.ShibbolethAuthenticationHandler.java

Source

/*
 * Diamond Light Source Limited 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 the 
 * following location:
 * 
 *   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 uk.ac.diamond.cas.shibboleth.authentication.handler;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.List;

import javax.management.AttributeNotFoundException;
import javax.security.auth.login.FailedLoginException;
import javax.security.sasl.AuthenticationException;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.apache.http.HttpHost;

import org.jasig.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;
import org.jasig.cas.authentication.PreventedException;
import org.jasig.cas.authentication.principal.Principal;
import org.jasig.cas.authentication.principal.SimplePrincipal;

import org.opensaml.DefaultBootstrap;
import org.opensaml.saml2.core.Attribute;
import org.opensaml.saml2.core.Response;
import org.opensaml.ws.soap.client.SOAPClientException;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.parse.BasicParserPool;
import org.opensaml.xml.schema.XSAny;
import org.opensaml.xml.schema.XSString;

import uk.ac.diamond.shibbolethecpauthclient.ShibbolethECPAuthClient;

/**
 * Shibboleth authentication handler. Tries to authenticate against an IdP
 * If requested, it consumes the returned SAML assertion on successful 
 * authentication, then updates the principal with the updated information
 * 
 * @author Stefan Paetow
 * @version $Revision$ $Date$
 * @since 4.0.0
 */
public class ShibbolethAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler {

    /** 
     * Identifies the Shibboleth IdP to try and authenticate against. 
     */
    @NotNull
    private String IdP;

    /**
     * Identifies the Shibboleth SP we want to run ECP against. 
     */
    @NotNull
    private String SP;

    /**
     * Identifies the eventual principal in the SAML response.
     */
    private String attribute;

    /**
     * Identifies an optional proxy host to connect to the IdP and the SP with.
     */
    private String proxyHost;

    /**
     * Identifies an optional proxy port to use with the proxy host.
     */
    @Min(0)
    private int proxyPort;

    /**
     * Disables certificate checking, usually used with self-signed certificates. 
     */
    @NotNull
    private boolean disableCertCheck;

    @Override
    protected final Principal authenticateUsernamePasswordInternal(final String username, final String password)
            throws GeneralSecurityException, PreventedException {

        logger.debug("Attempting to authenticate {} at {}", username, IdP);

        try {
            // Initialise the library
            DefaultBootstrap.bootstrap();
            final BasicParserPool parserPool = new BasicParserPool();
            parserPool.setNamespaceAware(true);

            // Set proxy
            HttpHost proxy = null;
            logger.debug("Setting proxy");
            if ((this.proxyHost != null) && (!this.proxyHost.isEmpty())) {
                if (this.proxyPort == 0) {
                    proxy = new HttpHost(this.proxyHost, 8080);
                } else {
                    proxy = new HttpHost(this.proxyHost, this.proxyPort);
                }
            }
            logger.debug("Set proxy successfully");

            // Instantiate a copy of the client, try to authentication, catch any errors that occur
            ShibbolethECPAuthClient ecpClient = new ShibbolethECPAuthClient(proxy, this.IdP, this.SP,
                    disableCertCheck);
            Response response = ecpClient.authenticate(username, password);
            logger.debug("Successfully authenticated {}", username);

            // if the attribute is empty, we simply authenticate and return the username as principal
            if ((this.attribute == null) || (this.attribute.isEmpty())) {
                return new SimplePrincipal(username);
            }

            // get the first assertion in the response. Any exceptions here are a problem
            List<Attribute> attributes = response.getAssertions().get(0)
                    // get the first (and should be only) attribute statement
                    .getAttributeStatements().get(0)
                    // get all attributes
                    .getAttributes();

            // if there are no attributes, we can't do a lookup.
            if (attributes.isEmpty()) {
                throw new AttributeNotFoundException("The Shibboleth Identity Provider at " + this.IdP
                        + " returned a SAML assertion with no attributes");
            }

            // trawl the attributes to check if we can find ours
            String lookupAttributeValue = null;
            boolean idFound = false;
            for (Attribute attribute : attributes) {
                if ((attribute.getName().equals(this.attribute))
                        || (attribute.getFriendlyName().equals(this.attribute))) {
                    idFound = true;
                    XMLObject attributeValue = attribute.getAttributeValues().get(0);
                    if (attributeValue instanceof XSString) {
                        lookupAttributeValue = ((XSString) attributeValue).getValue();
                    } else if (attributeValue instanceof XSAny) {
                        lookupAttributeValue = ((XSAny) attributeValue).getTextContent();
                    }
                    logger.debug("Attribute: " + this.attribute + ", value: " + lookupAttributeValue);
                    break;
                } // if getName()...
            } // for attribute...

            // Attribute was not found in the SAML statement
            if (!idFound) {
                throw new AttributeNotFoundException("The attribute " + this.attribute
                        + " was not returned by the Shibboleth Identity Provider.");
            }

            logger.info("Authentication was successful. Credential {} mapped to {}", username,
                    lookupAttributeValue);
            return new SimplePrincipal(lookupAttributeValue);

        } catch (final AttributeNotFoundException e) {
            logger.debug("AttributeNotFoundException raised: {}", e.toString());
            throw new FailedLoginException(e.toString());
        } catch (final AuthenticationException e) {
            logger.debug("AuthenticationException raised: {}", e.toString());
            throw new FailedLoginException(e.toString());
        } catch (final IOException e) {
            logger.debug("IOException raised: {}", e.toString());
            throw new PreventedException(e);
        } catch (final Exception e) {
            logger.debug("Exception raised: {}", e.toString());
            throw new PreventedException(e);
        }
    }

    /**
     * Identifies the eventual principal in the SAML response.
     * 
     * @param attribute string identifying the eventual principal in the SAML response.
     */
    public void setAttribute(final String attribute) {
        this.attribute = attribute;
    }

    /**
     * Identifies the Shibboleth IdP to try and authenticate against.
     * 
     * @param IdP string specifying the full IdP ECP URL to authenticate against.
     */
    public void setIdP(final String IdP) {
        this.IdP = IdP;
    }

    /**
     * Identifies the Shibboleth SP we want to run ECP against.
     * 
     * @param SP string specifying a Shibboleth-protected URL to initiate the authentication with. 
     */
    public void setSP(final String SP) {
        this.SP = SP;
    }

    /**
     * Identifies an optional proxy host to connect to the IdP and the SP with.
     * 
     * @param proxyHost string specifying the proxy host to connect with.
     */
    public void setProxyHost(final String proxyHost) {
        this.proxyHost = proxyHost;
    }

    /**
     * Identifies an optional proxy port to use with the proxy host.
     * 
     * @param proxyPort integer specifying the proxy port to connect with. 
     */
    public void setProxyPort(final int proxyPort) {
        this.proxyPort = proxyPort;
    }

    /**
     * Disables certificate checking, usually used with self-signed certificates.
     * 
     * @param disableCertCheck boolean specifying whether to disable certificate checking. 
     */
    public void setDisableCertCheck(final boolean disableCertCheck) {
        this.disableCertCheck = disableCertCheck;
    }
}