001/**
002 * Copyright 2014 Tampere University of Technology, Pori Department
003 * 
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * 
008 *   http://www.apache.org/licenses/LICENSE-2.0
009 * 
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package core.tut.pori.context;
017
018import java.io.IOException;
019
020import javax.servlet.ServletException;
021import javax.servlet.http.HttpServlet;
022import javax.servlet.http.HttpServletRequest;
023import javax.servlet.http.HttpServletResponse;
024import javax.servlet.http.HttpSession;
025
026import org.apache.log4j.Logger;
027import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
028import org.springframework.security.core.Authentication;
029import org.springframework.security.core.context.SecurityContextHolder;
030
031import service.tut.pori.users.UserCore;
032import core.tut.pori.http.Response;
033import core.tut.pori.http.Response.Status;
034import core.tut.pori.users.UserIdentity;
035
036/**
037 * This class handles to basic login/logout functionality and session creation/destruction.
038 * 
039 * This class defines the APIs available for client authentication. Note that it is also possible to authenticate by IP address without username or password check, but the functionality must be enabled per IP basis using the front-end system configuration, and is in general recommended only for analysis back-ends. The IP authentication configuration is out of scope for this documentation.
040 * The user authorization is done by checking the presence and validity of a session ID in the header list (cookie) of the executed HTTP request. The session ID can be generated by any of the login methods documented in this specification. It is also possible to provide the authentication details, such as username and password for HTTP basic authentication, for each request, though this is not necessary.
041 * The default configuration prevents multiple logins, and re-authentication will automatically invalidate the previous session, creating a new a session and a new session ID.
042 * 
043 * This class is generally bound using web.xml to be outside of REST handler, working independently from the service invocation functionality.
044 */
045public class LoginHandler extends HttpServlet{
046  private static final Logger LOGGER = Logger.getLogger(LoginHandler.class);
047  private static final String METHOD_LOGIN = "login";
048  /** serialization id */
049  private static final long serialVersionUID = 956966155730567889L;
050
051  @Override
052  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
053    handleRequest(req, resp);
054  }
055
056  @Override
057  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
058    handleRequest(req, resp);
059  }
060
061  /**
062   * 
063   * @param request
064   * @param response
065   */
066  private void handleRequest(HttpServletRequest request, HttpServletResponse response){
067    if(request.getRequestURI().endsWith(METHOD_LOGIN)){
068      login(getAuthentication(), request, response);
069    }else{ // ther are only two mappings in the web.xml, if this is not login, it must be logout
070      logout(getAuthentication(), request, response);
071    }
072  }
073  
074  /**
075   * Login the user using HTTP Basic authentication. Successful authentication attempt will return a new session ID (as a response cookie) that can be used for further authentication.
076   * 
077   * @param auth
078   * @param request
079   * @param response
080   */
081  private void login(Authentication auth, HttpServletRequest request, HttpServletResponse response){
082    LOGGER.debug("Received login request "+request.getMethod()+" from "+request.getRemoteAddr()); 
083    if(auth == null){
084      new Response(Status.UNAUTHORIZED).writeTo(response);
085      return;
086    }
087    new Response().writeTo(response);
088  }
089  
090  /**
091   * Log out the user. This will invalidate the session Id (cookie) currently used by the user. Note that in many cases the only noticeable effect of calling this method is to invalidate the session Id, as most web browsers will automatically re-authenticate the user on any following calls if user credentials are known, thus creating a new session.
092   * 
093   * @param auth
094   * @param request
095   * @param response
096   */
097  private void logout(Authentication auth, HttpServletRequest request, HttpServletResponse response){
098    LOGGER.debug("Received logout request "+request.getMethod()+" from "+request.getRemoteAddr());  
099    if(auth == null){
100      LOGGER.debug("User was not logged in.");
101      new Response().writeTo(response);
102      return;
103    }
104    auth.setAuthenticated(false);
105    HttpSession session = request.getSession(false);
106    if(session == null){
107      LOGGER.debug("No valid session.");
108    }else{
109      LOGGER.debug("Invalidating session.");
110      session.invalidate();
111    }
112
113    SecurityContextHolder.clearContext();
114    new Response().writeTo(response);
115  }
116  
117  /**
118   * 
119   * @return authentication or null if user has not authenticated
120   */
121  private Authentication getAuthentication(){
122    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
123    if(auth != null && auth.isAuthenticated()){
124      Object principal = auth.getPrincipal();
125      if(principal.getClass() == UserIdentity.class){
126        return auth;
127      }else{
128        LOGGER.debug("UserDetails not available.");
129      }     
130    }
131    return null;
132  }
133  
134  /**
135   * Set this user as authenticated for the current security context
136   * 
137   * @param userId
138   * @throws IllegalArgumentException on bad userId
139   */
140  public static void authenticate(UserIdentity userId) throws IllegalArgumentException{
141    userId = UserCore.getUserIdentity(userId.getUserId()); // retrieve all details
142    if(!UserIdentity.isValid(userId)){
143      throw new IllegalArgumentException("Bad user, id: "+userId.getUserId());
144    }
145    SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(userId, userId.getPassword(), userId.getAuthorities()));
146  }
147}