nl.strohalm.cyclos.webservices.rest.RestAuthenticationProvider.java Source code

Java tutorial

Introduction

Here is the source code for nl.strohalm.cyclos.webservices.rest.RestAuthenticationProvider.java

Source

/*
   This file is part of Cyclos.
    
   Cyclos 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 2 of the License, or
   (at your option) any later version.
    
   Cyclos 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 Cyclos; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    
 */
package nl.strohalm.cyclos.webservices.rest;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import nl.strohalm.cyclos.entities.access.Channel;
import nl.strohalm.cyclos.entities.access.PrincipalType;
import nl.strohalm.cyclos.entities.exceptions.EntityNotFoundException;
import nl.strohalm.cyclos.entities.members.Element;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.services.access.AccessServiceLocal;
import nl.strohalm.cyclos.services.access.ChannelServiceLocal;
import nl.strohalm.cyclos.services.access.exceptions.BlockedCredentialsException;
import nl.strohalm.cyclos.services.access.exceptions.CredentialsException;
import nl.strohalm.cyclos.services.access.exceptions.InvalidCredentialsException;
import nl.strohalm.cyclos.services.access.exceptions.UserNotFoundException;
import nl.strohalm.cyclos.services.elements.ElementServiceLocal;
import nl.strohalm.cyclos.services.transactions.exceptions.InvalidChannelException;
import nl.strohalm.cyclos.utils.access.LoggedUser;
import nl.strohalm.cyclos.webservices.WebServiceContext;
import nl.strohalm.cyclos.webservices.model.ServerErrorVO;

import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

/**
 * An {@link AuthenticationProvider} which validates the username / password in Cyclos. WARN: Can't throw AuthenticationException cause it returns 401
 * UNAUTHORIZED and that is captured by the browser, pops up an authentication form and we don't want that. A 401 freezes the mobile app.
 * @author luis
 */
public class RestAuthenticationProvider implements AuthenticationProvider {

    private ElementServiceLocal elementService;
    private AccessServiceLocal accessService;
    private ChannelServiceLocal channelService;
    private static final String INVALID_CREDENTIALS = "INVALID_CREDENTIALS";
    private static final String CHANNEL_DISABLED = "CHANNEL_DISABLED";
    private static final String BLOCKED_CREDENTIALS = "BLOCKED_CREDENTIALS";
    private static final String UNKNOWN_AUTHENTICATION_ERROR = "UNKNOWN_AUTHENTICATION_ERROR";

    @Override
    public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
        // Get / validate the principal / credentials
        String principal = authentication.getName();
        String credentials = (String) authentication.getCredentials();
        if (StringUtils.isEmpty(principal) || StringUtils.isEmpty(credentials)) {
            sendError("Empty username / password", INVALID_CREDENTIALS);
            throw new InvalidCredentialsException();
        }
        // Get the request
        HttpServletRequest request = WebServiceContext.getRequest();
        if (request == null) {
            sendError("Couldn't resolve the current request", UNKNOWN_AUTHENTICATION_ERROR);
            throw new IllegalStateException("Couldn't resolve the current request");
        }

        final String remoteAddr = request.getRemoteAddr();

        // Load the channel
        Channel channel = channelService.loadByInternalName(Channel.REST);
        final PrincipalType principalType = channelService.resolvePrincipalType(Channel.REST,
                channel.getDefaultPrincipalType().getPrincipal().name());

        // Validate the user
        String usernameToVerify = principal;
        Member member = null;
        try {
            member = elementService.loadByPrincipal(principalType, principal, Element.Relationships.USER,
                    Element.Relationships.GROUP);
            usernameToVerify = member.getUsername();
        } catch (final EntityNotFoundException e) {
            usernameToVerify = "";
        }
        // Verify username
        try {
            accessService.verifyLogin(null, usernameToVerify, remoteAddr);
        } catch (UserNotFoundException e) {
            sendError("Invalid username / password", INVALID_CREDENTIALS);
            throw new InvalidCredentialsException();
        }

        // Check if the channel is enabled for the specific member
        if (!accessService.isChannelEnabledForMember(channel, member)) {
            sendError("Channel disabled for the member", CHANNEL_DISABLED);
            throw new InvalidChannelException(member.getUsername(), channel.getInternalName());
        }

        // Check the credentials
        try {
            accessService.checkCredentials(channel, member.getMemberUser(), credentials, remoteAddr, null);
        } catch (BlockedCredentialsException e) {
            sendError("Credentials blocked", BLOCKED_CREDENTIALS);
            throw e;
        } catch (CredentialsException e) {
            sendError("Invalid username / password", INVALID_CREDENTIALS);
            throw e;
        }

        // Initialize the LoggedUser, so it is accessible from the services
        WebServiceContext.setRestMember(member);
        LoggedUser.init(member.getUser(), remoteAddr);

        // Authentication succeeded
        Collection<SimpleGrantedAuthority> authority = Collections
                .singleton(new SimpleGrantedAuthority("ROLE_REST"));
        return new UsernamePasswordAuthenticationToken(principal, credentials, authority);
    }

    public void setAccessServiceLocal(final AccessServiceLocal accessService) {
        this.accessService = accessService;
    }

    public void setChannelServiceLocal(final ChannelServiceLocal channelService) {
        this.channelService = channelService;
    }

    public void setElementServiceLocal(final ElementServiceLocal elementService) {
        this.elementService = elementService;
    }

    @Override
    public boolean supports(final Class<?> authentication) {
        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
    }

    private void sendError(final String message, final String errorCode) {
        HttpServletResponse response = WebServiceContext.getResponse();
        ObjectMapper mapper = new ObjectMapper();
        try {
            response.setContentType("application/json");
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            mapper.writeValue(response.getWriter(), new ServerErrorVO(errorCode, message));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}