org.opennms.protocols.radius.springsecurity.RadiusAuthenticationProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.opennms.protocols.radius.springsecurity.RadiusAuthenticationProvider.java

Source

/*******************************************************************************
 * This file is part of OpenNMS(R).
 *
 * Copyright (C) 2011-2012 The OpenNMS Group, Inc.
 * OpenNMS(R) is Copyright (C) 1999-2012 The OpenNMS Group, Inc.
 *
 * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
 *
 * OpenNMS(R) is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published
 * by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * OpenNMS(R) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with OpenNMS(R).  If not, see:
 *      http://www.gnu.org/licenses/
 *
 * For more information contact:
 *     OpenNMS(R) Licensing <license@opennms.org>
 *     http://www.opennms.org/
 *     http://www.opennms.com/
 *******************************************************************************/

package org.opennms.protocols.radius.springsecurity;

import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import net.jradius.client.RadiusClient;
import net.jradius.client.auth.PAPAuthenticator;
import net.jradius.client.auth.RadiusAuthenticator;
import net.jradius.dictionary.Attr_UserName;
import net.jradius.dictionary.Attr_UserPassword;
import net.jradius.exception.RadiusException;
import net.jradius.packet.AccessAccept;
import net.jradius.packet.AccessRequest;
import net.jradius.packet.RadiusPacket;
import net.jradius.packet.attribute.AttributeFactory;
import net.jradius.packet.attribute.AttributeList;
import net.jradius.packet.attribute.RadiusAttribute;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.opennms.core.utils.InetAddressUtils;
import org.opennms.web.springframework.security.Authentication;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * An org.springframework.security.providers.AuthenticationProvider implementation that provides integration with a Radius server.
 *
 * @author Paul Donohue
 */
public class RadiusAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    private static final Log logger = LogFactory.getLog(RadiusAuthenticationProvider.class);
    private String server, secret;
    private int port = 1812, timeout = 5, retries = 3;

    /**
     * There is a bug in {@link net.jradius.client.auth.PAPAuthenticator#processRequest(RadiusPacket)} that prevents 
     * instances of the class from being reused. Set the authenticator class to null to work 
     * around this problem (while still using the {@link net.jradius.client.auth.PAPAuthenticator}
     * class).
     * 
     * @see net.jradius.client.RadiusClient#authenticate(AccessRequest, RadiusAuthenticator, int)
     */
    private RadiusAuthenticator authTypeClass = null;

    private String defaultRoles = Authentication.ROLE_USER, rolesAttribute;

    /**
     * Create an instance using the supplied server and shared secret.
     *
     * @param server a {@link java.lang.String} object.
     * @param sharedSecret a {@link java.lang.String} object.
     */
    public RadiusAuthenticationProvider(String server, String sharedSecret) {
        Assert.hasLength(server, "A server must be specified");
        this.server = server;
        Assert.hasLength(sharedSecret, "A shared secret must be specified");
        this.secret = sharedSecret;
    }

    /**
     * <p>doAfterPropertiesSet</p>
     *
     * @throws java.lang.Exception if any.
     */
    @Override
    protected void doAfterPropertiesSet() throws Exception {
        Assert.notNull(this.port, "A port number must be specified");
        Assert.notNull(this.timeout, "A timeout must be specified");
        Assert.notNull(this.retries, "A retry count must be specified");
        //Assert.notNull(this.authTypeClass, "A RadiusAuthenticator object must be supplied in authTypeClass");
        Assert.notNull(this.defaultRoles, "Default Roles must be supplied in defaultRoles");
    }

    /**
     * Sets the port number the radius server is listening on
     *
     * @param port (defaults to 1812)
     */
    public void setPort(int port) {
        this.port = port;
    }

    /**
     * Sets the authentication timeout (in seconds)
     *
     * @param timeout (defaults to 5)
     */
    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    /**
     * Sets the number of times to retry a timed-out authentication request
     *
     * @param retries (defaults to 3)
     */
    public void setRetries(int retries) {
        this.retries = retries;
    }

    /**
     * Sets the authenticator, which determines the authentication type (PAP, CHAP, etc)
     *
     * @param authTypeClass An instance of net.jradius.client.auth.RadiusAuthenticator (defaults to PAPAuthenticator)
     */
    public void setAuthTypeClass(RadiusAuthenticator authTypeClass) {
        if (authTypeClass instanceof PAPAuthenticator) {
            // There is a bug in PAPAuthenticator() that prevents instances of the class
            // from being reused. Set the authenticator class to null to work around this
            // problem.
            this.authTypeClass = null;
        } else {
            this.authTypeClass = authTypeClass;
        }
    }

    /**
     * Sets the default authorities (roles) that should be assigned to authenticated users
     *
     * @param defaultRoles comma-separated list of roles (defaults to "ROLE_USER")
     */
    public void setDefaultRoles(String defaultRoles) {
        this.defaultRoles = defaultRoles;
    }

    /**
     * Sets the name of a radius attribute to be returned by the radius server
     * with a comma-separated list of authorities (roles) to be assigned to the user
     *
     * If this is not set, or if the specified attribute is not found in the reply
     * from the radius server, defaultRoles will be used to assign roles
     *
     * If JRadius's built-in attribute dictionary does not contain the desired
     * attribute name, use "Unknown-VSAttribute(<Vendor ID>:<Attribute Number>)"
     *
     * @param rolesAttribute a {@link java.lang.String} object.
     */
    public void setRolesAttribute(String rolesAttribute) {
        this.rolesAttribute = rolesAttribute;
    }

    /* (non-Javadoc)
     * @see org.springframework.security.providers.dao.AbstractUserDetailsAuthenticationProvider#additionalAuthenticationChecks(org.springframework.security.userdetails.UserDetails, org.springframework.security.providers.UsernamePasswordAuthenticationToken)
     */
    /** {@inheritDoc} */
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken token) throws AuthenticationException {
        if (!userDetails.getPassword().equals(token.getCredentials().toString())) {
            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);
        }
    }

    /* (non-Javadoc)
     * @see org.springframework.security.providers.dao.AbstractUserDetailsAuthenticationProvider#retrieveUser(java.lang.String, org.springframework.security.providers.UsernamePasswordAuthenticationToken)
     */
    /** {@inheritDoc} */
    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken token)
            throws AuthenticationException {
        if (!StringUtils.hasLength(username)) {
            logger.info("Authentication attempted with empty username");
            throw new BadCredentialsException(
                    messages.getMessage("RadiusAuthenticationProvider.emptyUsername", "Username cannot be empty"));
        }
        String password = (String) token.getCredentials();
        if (!StringUtils.hasLength(password)) {
            logger.info("Authentication attempted with empty password");
            throw new BadCredentialsException(messages
                    .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }

        InetAddress serverIP = null;
        serverIP = InetAddressUtils.addr(server);
        if (serverIP == null) {
            logger.error("Could not resolve radius server address " + server);
            throw new AuthenticationServiceException(messages.getMessage(
                    "RadiusAuthenticationProvider.unknownServer", "Could not resolve radius server address"));
        }
        AttributeFactory.loadAttributeDictionary("net.jradius.dictionary.AttributeDictionaryImpl");
        AttributeList attributeList = new AttributeList();
        attributeList.add(new Attr_UserName(username));
        attributeList.add(new Attr_UserPassword(password));
        RadiusPacket reply;
        try {
            RadiusClient radiusClient = new RadiusClient(serverIP, secret, port, port + 1, timeout);
            AccessRequest request = new AccessRequest(radiusClient, attributeList);

            logger.debug("Sending AccessRequest message to " + InetAddressUtils.str(serverIP) + ":" + port
                    + " using " + (authTypeClass == null ? "PAP" : authTypeClass.getAuthName())
                    + " protocol with timeout = " + timeout + ", retries = " + retries + ", attributes:\n"
                    + attributeList.toString());
            reply = radiusClient.authenticate(request, authTypeClass, retries);
        } catch (RadiusException e) {
            logger.error("Error connecting to radius server " + server + " : " + e);
            throw new AuthenticationServiceException(messages.getMessage("RadiusAuthenticationProvider.radiusError",
                    new Object[] { e }, "Error connecting to radius server: " + e));
        } catch (IOException e) {
            logger.error("Error connecting to radius server " + server + " : " + e);
            throw new AuthenticationServiceException(messages.getMessage("RadiusAuthenticationProvider.radiusError",
                    new Object[] { e }, "Error connecting to radius server: " + e));
        }
        if (reply == null) {
            logger.error("Timed out connecting to radius server " + server);
            throw new AuthenticationServiceException(messages.getMessage(
                    "RadiusAuthenticationProvider.radiusTimeout", "Timed out connecting to radius server"));
        }
        if (!(reply instanceof AccessAccept)) {
            logger.info("Received a reply other than AccessAccept from radius server " + server + " for user "
                    + username + " :\n" + reply.toString());
            throw new BadCredentialsException(messages
                    .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
        logger.debug("Received AccessAccept message from " + InetAddressUtils.str(serverIP) + ":" + port
                + " for user " + username + " with attributes:\n" + reply.getAttributes().toString());

        String roles = null;
        if (!StringUtils.hasLength(rolesAttribute)) {
            logger.debug("rolesAttribute not set, using default roles (" + defaultRoles + ") for user " + username);
            roles = new String(defaultRoles);
        } else {
            Iterator<RadiusAttribute> attributes = reply.getAttributes().getAttributeList().iterator();
            while (attributes.hasNext()) {
                RadiusAttribute attribute = attributes.next();
                if (rolesAttribute.equals(attribute.getAttributeName())) {
                    roles = new String(attribute.getValue().getBytes());
                    break;
                }
            }
            if (roles == null) {
                logger.info("Radius attribute " + rolesAttribute + " not found, using default roles ("
                        + defaultRoles + ") for user " + username);
                roles = new String(defaultRoles);
            }
        }

        String[] rolesArray = roles.replaceAll("\\s*", "").split(",");
        Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(rolesArray.length);
        for (String role : rolesArray) {
            authorities.add(new SimpleGrantedAuthority(role));
        }
        if (logger.isDebugEnabled()) {
            StringBuffer readRoles = new StringBuffer();
            for (GrantedAuthority authority : authorities) {
                readRoles.append(authority.toString() + ", ");
            }
            if (readRoles.length() > 0) {
                readRoles.delete(readRoles.length() - 2, readRoles.length());
            }
            logger.debug("Parsed roles " + readRoles + " for user " + username);
        }

        return new User(username, password, true, true, true, true, authorities);
    }

}