/**
  * Returns the {@link AuthenticationToken} for the request.
  *
  * <p>It looks at the received HTTP cookies and extracts the value of the {@link
  * AuthenticatedURL#AUTH_COOKIE} if present. It verifies the signature and if correct it creates
  * the {@link AuthenticationToken} and returns it.
  *
  * <p>If this method returns <code>null</code> the filter will invoke the configured {@link
  * AuthenticationHandler} to perform user authentication.
  *
  * @param request request object.
  * @return the Authentication token if the request is authenticated, <code>null</code> otherwise.
  * @throws IOException thrown if an IO error occurred.
  * @throws AuthenticationException thrown if the token is invalid or if it has expired.
  */
 protected AuthenticationToken getToken(HttpServletRequest request)
     throws IOException, AuthenticationException {
   AuthenticationToken token = null;
   String tokenStr = null;
   Cookie[] cookies = request.getCookies();
   if (cookies != null) {
     for (Cookie cookie : cookies) {
       if (cookie.getName().equals(AuthenticatedURL.AUTH_COOKIE)) {
         tokenStr = cookie.getValue();
         try {
           tokenStr = signer.verifyAndExtract(tokenStr);
         } catch (SignerException ex) {
           throw new AuthenticationException(ex);
         }
         break;
       }
     }
   }
   if (tokenStr != null) {
     token = AuthenticationToken.parse(tokenStr);
     if (!token.getType().equals(authHandler.getType())) {
       throw new AuthenticationException("Invalid AuthenticationToken type");
     }
     if (token.isExpired()) {
       throw new AuthenticationException("AuthenticationToken expired");
     }
   }
   return token;
 }
 /**
  * Destroys the filter.
  *
  * <p>It invokes the {@link AuthenticationHandler#destroy()} method to release any resources it
  * may hold.
  */
 @Override
 public void destroy() {
   if (authHandler != null) {
     authHandler.destroy();
     authHandler = null;
   }
   if (secretProvider != null) {
     secretProvider.destroy();
   }
 }
  /**
   * If the request has a valid authentication token it allows the request to continue to the target
   * resource, otherwise it triggers an authentication sequence using the configured {@link
   * AuthenticationHandler}.
   *
   * @param request the request object.
   * @param response the response object.
   * @param filterChain the filter chain object.
   * @throws IOException thrown if an IO error occurred.
   * @throws ServletException thrown if a processing error occurred.
   */
  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
      throws IOException, ServletException {
    boolean unauthorizedResponse = true;
    int errCode = HttpServletResponse.SC_UNAUTHORIZED;
    AuthenticationException authenticationEx = null;
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletResponse httpResponse = (HttpServletResponse) response;
    boolean isHttps = "https".equals(httpRequest.getScheme());
    try {
      boolean newToken = false;
      AuthenticationToken token;
      try {
        token = getToken(httpRequest);
      } catch (AuthenticationException ex) {
        LOG.warn("AuthenticationToken ignored: " + ex.getMessage());
        // will be sent back in a 401 unless filter authenticates
        authenticationEx = ex;
        token = null;
      }
      if (authHandler.managementOperation(token, httpRequest, httpResponse)) {
        if (token == null) {
          if (LOG.isDebugEnabled()) {
            LOG.debug("Request [{}] triggering authentication", getRequestURL(httpRequest));
          }
          token = authHandler.authenticate(httpRequest, httpResponse);
          if (token != null && token.getExpires() != 0 && token != AuthenticationToken.ANONYMOUS) {
            token.setExpires(System.currentTimeMillis() + getValidity() * 1000);
          }
          newToken = true;
        }
        if (token != null) {
          unauthorizedResponse = false;
          if (LOG.isDebugEnabled()) {
            LOG.debug(
                "Request [{}] user [{}] authenticated",
                getRequestURL(httpRequest),
                token.getUserName());
          }
          final AuthenticationToken authToken = token;
          httpRequest =
              new HttpServletRequestWrapper(httpRequest) {

                @Override
                public String getAuthType() {
                  return authToken.getType();
                }

                @Override
                public String getRemoteUser() {
                  return authToken.getUserName();
                }

                @Override
                public Principal getUserPrincipal() {
                  return (authToken != AuthenticationToken.ANONYMOUS) ? authToken : null;
                }
              };
          if (newToken && !token.isExpired() && token != AuthenticationToken.ANONYMOUS) {
            String signedToken = signer.sign(token.toString());
            createAuthCookie(
                httpResponse,
                signedToken,
                getCookieDomain(),
                getCookiePath(),
                token.getExpires(),
                isHttps);
          }
          filterChain.doFilter(httpRequest, httpResponse);
        }
      } else {
        unauthorizedResponse = false;
      }
    } catch (AuthenticationException ex) {
      // exception from the filter itself is fatal
      errCode = HttpServletResponse.SC_FORBIDDEN;
      authenticationEx = ex;
      LOG.warn("Authentication exception: " + ex.getMessage(), ex);
    }
    if (unauthorizedResponse) {
      if (!httpResponse.isCommitted()) {
        createAuthCookie(httpResponse, "", getCookieDomain(), getCookiePath(), 0, isHttps);
        if (authenticationEx == null) {
          httpResponse.sendError(errCode, "Authentication required");
        } else {
          httpResponse.sendError(errCode, authenticationEx.getMessage());
        }
      }
    }
  }
  /**
   * Initializes the authentication filter.
   *
   * <p>It instantiates and initializes the specified {@link AuthenticationHandler}.
   *
   * <p>
   *
   * @param filterConfig filter configuration.
   * @throws ServletException thrown if the filter or the authentication handler could not be
   *     initialized properly.
   */
  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    String configPrefix = filterConfig.getInitParameter(CONFIG_PREFIX);
    configPrefix = (configPrefix != null) ? configPrefix + "." : "";
    Properties config = getConfiguration(configPrefix, filterConfig);
    String authHandlerName = config.getProperty(AUTH_TYPE, null);
    String authHandlerClassName;
    if (authHandlerName == null) {
      throw new ServletException(
          "Authentication type must be specified: "
              + PseudoAuthenticationHandler.TYPE
              + "|"
              + KerberosAuthenticationHandler.TYPE
              + "|<class>");
    }
    if (authHandlerName.toLowerCase(Locale.ENGLISH).equals(PseudoAuthenticationHandler.TYPE)) {
      authHandlerClassName = PseudoAuthenticationHandler.class.getName();
    } else if (authHandlerName
        .toLowerCase(Locale.ENGLISH)
        .equals(KerberosAuthenticationHandler.TYPE)) {
      authHandlerClassName = KerberosAuthenticationHandler.class.getName();
    } else {
      authHandlerClassName = authHandlerName;
    }

    try {
      Class<?> klass =
          Thread.currentThread().getContextClassLoader().loadClass(authHandlerClassName);
      authHandler = (AuthenticationHandler) klass.newInstance();
      authHandler.init(config);
    } catch (ClassNotFoundException ex) {
      throw new ServletException(ex);
    } catch (InstantiationException ex) {
      throw new ServletException(ex);
    } catch (IllegalAccessException ex) {
      throw new ServletException(ex);
    }

    validity = Long.parseLong(config.getProperty(AUTH_TOKEN_VALIDITY, "36000")) * 1000; // 10 hours
    secretProvider =
        (SignerSecretProvider)
            filterConfig.getServletContext().getAttribute(SIGNATURE_PROVIDER_ATTRIBUTE);
    if (secretProvider == null) {
      String signerSecretProviderClassName =
          config.getProperty(configPrefix + SIGNER_SECRET_PROVIDER_CLASS, null);
      if (signerSecretProviderClassName == null) {
        String signatureSecret = config.getProperty(configPrefix + SIGNATURE_SECRET, null);
        if (signatureSecret != null) {
          secretProvider = new StringSignerSecretProvider(signatureSecret);
        } else {
          secretProvider = new RandomSignerSecretProvider();
          randomSecret = true;
        }
      } else {
        try {
          Class<?> klass =
              Thread.currentThread()
                  .getContextClassLoader()
                  .loadClass(signerSecretProviderClassName);
          secretProvider = (SignerSecretProvider) klass.newInstance();
          customSecretProvider = true;
        } catch (ClassNotFoundException ex) {
          throw new ServletException(ex);
        } catch (InstantiationException ex) {
          throw new ServletException(ex);
        } catch (IllegalAccessException ex) {
          throw new ServletException(ex);
        }
      }
      try {
        secretProvider.init(config, validity);
      } catch (Exception ex) {
        throw new ServletException(ex);
      }
    } else {
      customSecretProvider = true;
    }
    signer = new Signer(secretProvider);

    cookieDomain = config.getProperty(COOKIE_DOMAIN, null);
    cookiePath = config.getProperty(COOKIE_PATH, null);
  }