/**
   * Create an authenticated connection to the Oozie server.
   *
   * <p>It uses Hadoop-auth client authentication which by default supports Kerberos HTTP SPNEGO,
   * Pseudo/Simple and anonymous.
   *
   * <p>if the Java system property {@link #USE_AUTH_TOKEN_CACHE_SYS_PROP} is set to true
   * Hadoop-auth authentication token will be cached/used in/from the '.oozie-auth-token' file in
   * the user home directory.
   *
   * @param url the URL to open a HTTP connection to.
   * @param method the HTTP method for the HTTP connection.
   * @return an authenticated connection to the Oozie server.
   * @throws IOException if an IO error occurred.
   * @throws OozieClientException if an oozie client error occurred.
   */
  @Override
  protected HttpURLConnection createConnection(URL url, String method)
      throws IOException, OozieClientException {
    boolean useAuthFile =
        System.getProperty(USE_AUTH_TOKEN_CACHE_SYS_PROP, "false").equalsIgnoreCase("true");
    AuthenticatedURL.Token readToken = new AuthenticatedURL.Token();
    AuthenticatedURL.Token currentToken = new AuthenticatedURL.Token();

    if (useAuthFile) {
      readToken = readAuthToken();
      if (readToken != null) {
        currentToken = new AuthenticatedURL.Token(readToken.toString());
      }
    }

    if (currentToken.isSet()) {
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      conn.setRequestMethod("OPTIONS");
      AuthenticatedURL.injectToken(conn, currentToken);
      if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
        AUTH_TOKEN_CACHE_FILE.delete();
        currentToken = new AuthenticatedURL.Token();
      }
    }

    if (!currentToken.isSet()) {
      Authenticator authenticator = getAuthenticator();
      try {
        new AuthenticatedURL(authenticator).openConnection(url, currentToken);
      } catch (AuthenticationException ex) {
        AUTH_TOKEN_CACHE_FILE.delete();
        throw new OozieClientException(
            OozieClientException.AUTHENTICATION, "Could not authenticate, " + ex.getMessage(), ex);
      }
    }
    if (useAuthFile && !currentToken.equals(readToken)) {
      writeAuthToken(currentToken);
    }
    HttpURLConnection conn = super.createConnection(url, method);
    AuthenticatedURL.injectToken(conn, currentToken);

    return conn;
  }
  /**
   * 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());
        }
      }
    }
  }