/*
 * Copyright 2002-2012 the original author or authors.
 *
 * Licensed 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
 *
 * 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 org.springframework.security.samples.servletapi.mvc;

import java.io.IOException;
import java.security.Principal;

import javax.naming.AuthenticationException;
import javax.servlet.AsyncContext;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

/**
 * A Spring MVC Controller that demonstrates Spring Security's integration with the standard Servlet API's. Specifically
 * it demonstrates the following:
 * <ul>
 * <li>{@link #authenticate(HttpServletRequest, HttpServletResponse)} - Integration with
 * {@link HttpServletRequest#authenticate(HttpServletResponse)}</li>
 * <li>{@link #login(HttpServletRequest, HttpServletResponse, LoginForm, BindingResult)} - Integration with
 * {@link HttpServletRequest#login(String, String)}</li>
 * <li>{@link #logout(HttpServletRequest, HttpServletResponse)} - Integration with {@link HttpServletRequest#logout()}</li>
 * <li>{@link #remoteUser(HttpServletRequest)} - Integration with {@link HttpServletRequest#getRemoteUser()}</li>
 * <li>{@link #userPrincipal(HttpServletRequest)} - Integration with {@link HttpServletRequest#getUserPrincipal()}</li>
 * <li>{@link #authentication(Authentication)} - Spring MVC's ability to resolve the {@link Authentication} since it is
 * found on {@link HttpServletRequest#getUserPrincipal()}</li>
 * </ul>
 *
 * @author Rob Winch
 *
 */
@Controller
public class ServletApiController {
    /**
     * Demonstrates that {@link HttpServletRequest#authenticate(HttpServletResponse)} will send the user to the log in
     * page configured within Spring Security if the user is not already authenticated.
     *
     * @param request
     * @param response
     * @return
     * @throws ServletException
     * @throws IOException
     */
    @RequestMapping("/authenticate")
    public String authenticate(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        boolean authenticate = request.authenticate(response);
        return authenticate ? "index" : null;
    }

    /**
     * Demonstrates that you can authenticate with Spring Security using
     * {@link HttpServletRequest#login(String, String)}.
     *
     * <p>
     * If we fail to authenticate, a {@link ServletException} is thrown that wraps the original
     * {@link AuthenticationException} from Spring Security. This means we can catch the {@link ServletException} to
     * display the error message. Alternatively, we could allow the {@link ServletException} to propegate and Spring
     * Security's {@link ExceptionTranslationFilter} would catch it and process it appropriately.
     * </p>
     * <p>
     * In this method we choose to use Spring MVC's {@link ModelAttribute} to make things easier for our form. However,
     * this is not necessary. We could have just as easily obtained the request parameters from the
     * {@link HttpServletRequest} object. Remember all of these examples would work in a standard {@link Servlet} or
     * anything with access to the {@link HttpServletRequest} and {@link HttpServletResponse}.
     * </p>
     *
     * @param request
     * @param response
     * @param loginForm
     * @param result
     * @return
     * @throws ServletException
     */
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String login(HttpServletRequest request, HttpServletResponse response,
            @ModelAttribute LoginForm loginForm, BindingResult result) throws ServletException {
        try {
            request.login(loginForm.getUsername(), loginForm.getPassword());
        } catch(ServletException authenticationFailed) {
            result.rejectValue(null, "authentication.failed", authenticationFailed.getMessage());
            return "login";
        }
        return "redirect:/";
    }

    /**
     * Demonstrates that invoking {@link HttpServletRequest#logout()} will log the user out. Note that the response does
     * not get processed, so you need to write something to the response.
     * @param request
     * @param response
     * @param redirect
     * @return
     * @throws ServletException
     */
    @RequestMapping("/logout")
    public String logout(HttpServletRequest request, HttpServletResponse response, RedirectAttributes redirect) throws ServletException {
        request.logout();
        return "redirect:/";
    }

    /**
     * Demonstrates Spring Security with {@link AsyncContext#start(Runnable)}. Spring Security will automatically
     * transfer the {@link SecurityContext} from the thread that {@link AsyncContext#start(Runnable)} is invoked to the
     * new Thread that invokes the {@link Runnable}.
     * @param request
     * @param response
     */
    @RequestMapping("/async")
    public void asynch(HttpServletRequest request, HttpServletResponse response) {
        final AsyncContext async = request.startAsync();
        async.start(new Runnable() {
            public void run() {
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                try {
                    final HttpServletResponse asyncResponse = (HttpServletResponse) async.getResponse();
                    asyncResponse.setStatus(HttpServletResponse.SC_OK);
                    asyncResponse.getWriter().write(String.valueOf(authentication));
                    async.complete();
                } catch(Exception e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }

    /**
     * Demonstrates that Spring Security automatically populates {@link HttpServletRequest#getRemoteUser()} with the
     * current username.
     * @param request
     * @return
     */
    @ModelAttribute("remoteUser")
    public String remoteUser(HttpServletRequest request) {
        return request.getRemoteUser();
    }

    /**
     * Demonstrates that Spring Security automatically populates {@link HttpServletRequest#getUserPrincipal()} with the
     * {@link Authentication} that is present on {@link SecurityContextHolder#getContext()}
     * @param request
     * @return
     */
    @ModelAttribute("userPrincipal")
    public Principal userPrincipal(HttpServletRequest request) {
        return request.getUserPrincipal();
    }

    /**
     * Spring MVC will automatically resolve any object that implements {@link Principal} using
     * {@link HttpServletRequest#getUserPrincipal()}. This means you can easily resolve the {@link Authentication} just
     * by adding it as an argument to your MVC controller. Alternatively, you could also have an argument of type
     * {@link Principal} which would not couple your controller to Spring Security.
     * @param authentication
     * @return
     */
    @ModelAttribute
    public Authentication authentication(Authentication authentication) {
        return authentication;
    }

    @RequestMapping("/")
    public String welcome() {
        return "index";
    }

    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String login(@ModelAttribute LoginForm loginForm) {
        return "login";
    }
}