WebAuthFilter.java

/*
** Module   : WebAuthFilter.java
** Abstract : A filter to authenticate third-party WEB apps loaded in the same process as the FWD
**            server.
**
** Copyright (c) 2019-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- ------------------------------Description----------------------------------
** 001 MAG 20191203 Created initial version.
** 002 CA  20200915 Fail if authentication does not succeed.  Also, the context must be set as 'headless'.
** 003 GBB 20230825 SecurityManager context & legacy web methods calls updated.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program 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 Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.web;

import java.io.*;

import javax.servlet.*;
import javax.servlet.http.*;

import com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.security.SecurityManager;

/**
 * A filter which intercepts all requests for a {@link GenericWebServer#initializeWebApp web app},
 * and authenticates with the FWD server, saving a context switcher in the session's 
 * <code>fwd.switcher</code> attribute.  This allows the servlet requests to switch to the 
 * requester's context, before executing FWD appserver calls.
 */
public class WebAuthFilter
implements Filter
{
   /** The current request being processed, on this thread. */
   private static ThreadLocal<HttpServletRequest> currentRequest = new ThreadLocal<>();

   /**
    * Get the current servlet request, being executed on this thread.  This will return a non-null
    * value only for the duration of the request.
    * 
    * @return   See above.
    */
   public static HttpServletRequest getCurrentRequest()
   {
      return currentRequest.get();
   }

   /**
    * Filter the servlet request.  If the session doesn't have a <code>fwd.switcher</code> 
    * attribute set, it will authenticate to the in-process FWD server using these 
    * <code>web.xml</code> init parameters:
    * <ul>
    *    <li><code>fwd.processalias</code>, with the process alias to authenticate.</li>
    *    <li><code>fwd.keystorefile</code>, with the key-store file with the private-key.</li>
    *    <li><code>fwd.keystorepass</code>, the private-key password.</li>
    * </ul>
    * If the authentication is possible, it will save a {@link ContextSwitcher} instance as
    * returned by {@link LegacyWebSecurityManager#getContextSwitcher}, in the <code>fwd.switcher</code>
    * session attribute.
    * 
    * @param    request
    *           The servlet request.
    * @param    response
    *           The servlet response.
    * @param    filter
    *           The filter chain.
    */
   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain filter)
   throws IOException, 
          ServletException
   {
      if (request instanceof HttpServletRequest)
      {
         HttpServletRequest httpServletRequest = (HttpServletRequest) request;
         HttpSession session = httpServletRequest.getSession();
         currentRequest.set(httpServletRequest);
         
         try
         {
            if (session.getAttribute("fwd.switcher") != null)
            {
               filter.doFilter(request, response);
               return;
            }
            ServletContext servletContext = httpServletRequest.getServletContext();
            Runnable auth = () ->
            {
               String alias = servletContext.getInitParameter("fwd.processalias");
               String ksfile = servletContext.getInitParameter("fwd.keystorefile");
               String kspass = servletContext.getInitParameter("fwd.keystorepass");
   
               // authenticate the thread, as the remote call will be invoked from this thread, and
               // it needs a FWD context
               if (alias == null || ksfile == null || kspass == null)
               {
                  String webapp = request.getServletContext().getContextPath();
                  throw new IllegalStateException("The " + webapp + " is configured to start, but " +
                                                  " the authentication details are not configured.");
               }
   
               SecurityManager sm = SecurityManager.getInstance();
               if (!sm.contextSm.hasContext())
               {
                  try
                  {
                     BootstrapConfig cfg = new BootstrapConfig();
                     cfg.setConfigItem("net", "connection", "secure", "true");
                     cfg.setConfigItem("security", "keystore", "processalias", alias);
                     cfg.setConfigItem("security", "keystore", "filename", ksfile);
                     cfg.setConfigItem("access", "password", "keystore", kspass);
                     cfg.setConfigItem("net", "session", "id", session.getId());
                     
                     if (sm.authenticateServer(cfg) == null)
                     {
                        throw new IllegalStateException("Could not authenticate to FWD!");
                     }
                     
                     // the context must be headless
                     sm.contextSm.setHeadless();
                  }
                  catch (ConfigurationException | 
                         RestrictedUseException e)
                  {
                     throw new IllegalStateException("Could not authenticate to FWD!", e);
                  }
               }
            };
            
            LegacyWebSecurityManager wsm = SecurityManager.getInstance().legacyWebSm;
            ContextSwitcher switcher = wsm.getContextSwitcher(auth);
   
            if (switcher != null)
            {
               switcher.releaseContext();
               session.setAttribute("fwd.switcher", switcher);
               filter.doFilter(request, response);
            }
            else
            {
               if (response instanceof HttpServletResponse)
               {
                  HttpServletResponse httpServletResponse = (HttpServletResponse) response;
                  httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
               }
            }
         } 
         finally
         {
            currentRequest.set(null);
         }
      }
   }
   
   /**
    * No-op.
    */
   @Override
   public void destroy()
   {
   }

   /**
    * No-op.
    */
   @Override
   public void init(FilterConfig arg0) 
   throws ServletException
   {
   }
}