/*
 *
 * Headwind MDM: Open Source Android MDM Software
 * https://h-mdm.com
 *
 * Copyright (C) 2019 Headwind Solutions LLC (http://h-sms.com)
 *
 * 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 com.hmdm.security.jwt;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.hmdm.persistence.domain.User;
import com.hmdm.security.SecurityContext;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * <p>Filters incoming requests and sets up a security context for the request processing if a header corresponding to
 * a valid user is found.</p>
 *
 * @author isv
 */
@Singleton
public class JWTFilter implements Filter {

    /**
     * <p>A name of HTTP request's "Authorization" header.</p>
     */
    private static final String AUTHORIZATION_HEADER = "Authorization";

    /**
     * <p>An authentication token provider used for validating and parsing the authentication tokens provided by
     * incoming request.</p>
     */
    private final TokenProvider tokenProvider;

    /**
     * <p>Constructs new <code>JWTFilter</code> instance using the specified authentication token provider.</p>
     *
     * @param tokenProvider an authentication token provider used for validating and parsing the authentication tokens
     *                      provided by incoming request.
     */
    @Inject
    public JWTFilter(TokenProvider tokenProvider) {
        this.tokenProvider = tokenProvider;
    }

    /**
     * <p>Does nothing.</p>
     */
    @Override
    public void init(FilterConfig filterConfig) {
    }

    /**
     * <p>Does nothing.</p>
     */
    @Override
    public void destroy() {
    }

    /**
     * <p>Intercepts the specified request. If a valid authentication token is provided by the specified request then
     * set-ups current security context with authenticated principal based on the provided token.</p>
     *
     * @param servletRequest an incoming request.
     * @param servletResponse an outgoing response.
     * @param filterChain a filter chain mapped to specified request.
     * @throws IOException if an I/O error occurs in filter chain.
     * @throws ServletException if an unexpected error occurs in filter chain.
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
        throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String jwt = resolveToken(httpServletRequest);
        if (jwt != null && !jwt.trim().isEmpty() && this.tokenProvider.validateToken(jwt)) {
            User authUser = this.tokenProvider.getAuthentication(jwt);

            // Set-up the security context
            try {
                SecurityContext.init(authUser);
                filterChain.doFilter(servletRequest, servletResponse);
            } finally {
                SecurityContext.release();
            }
        } else {
            filterChain.doFilter(servletRequest, servletResponse);
        }
        
    }

    /**
     * <p>Gets the authentication token if any is provided by the specified request. Analyzes <code>Authorization</code>
     * request header.</p>
     *
     * @param request an incoming request.
     * @return an authentication token provided by the specified request or <code>null</code> if there is no such token
     *         provided.
     */
    private String resolveToken(HttpServletRequest request){
        String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}