All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.appslandia.plum.base.InitializerHandler Maven / Gradle / Ivy

// The MIT License (MIT)
// Copyright © 2015 AppsLandia. All rights reserved.

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package com.appslandia.plum.base;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

import com.appslandia.common.utils.Asserts;
import com.appslandia.common.utils.DateUtils;
import com.appslandia.plum.base.CorsPolicyHandler.CorsResult;
import com.appslandia.plum.utils.ServletUtils;

import jakarta.inject.Inject;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

/**
 *
 * @author Loc Ha
 *
 */
public class InitializerHandler extends HttpFilter {
  private static final long serialVersionUID = 1L;

  @Inject
  protected AppConfig appConfig;

  @Inject
  protected ResponseEncodingStrategy responseEncodingStrategy;

  @Inject
  protected LanguageProvider languageProvider;

  @Inject
  protected AppHeaderPolicy appHeaderPolicy;

  @Inject
  protected HeaderPolicyProvider headerPolicyProvider;

  @Inject
  protected CorsPolicyHandler corsPolicyHandler;

  @Inject
  protected CorsPolicyProvider corsPolicyProvider;

  @Inject
  protected AuthHandlerProvider authHandlerProvider;

  @Inject
  protected AuthorizePolicyProvider authorizePolicyProvider;

  @Inject
  protected ExceptionHandler exceptionHandler;

  @Inject
  protected AccessRateHandler accessRateHandler;

  @Inject
  protected RemoteClientVerifier remoteClientVerifier;

  protected void initialize(HttpServletRequest request, HttpServletResponse response, RequestContext requestContext)
      throws Exception {

    // Default Character Encoding
    if (request.getCharacterEncoding() == null) {
      request.setCharacterEncoding(StandardCharsets.UTF_8.name());
    }

    // Header Policies
    this.appHeaderPolicy.writePolicy(request, response, requestContext);

    // Enabled Header Policies
    for (String policy : this.appConfig.getStringArray(AppConfig.CONFIG_ENABLE_HEADER_POLICIES)) {
      this.headerPolicyProvider.getHeaderPolicy(policy).writePolicy(request, response, requestContext);
    }

    // Vary: Accept-Encoding
    if (this.responseEncodingStrategy.enableEncoding(requestContext)) {
      response.addHeader("Vary", "Accept-Encoding");
    }
  }

  protected void redirectLang(HttpServletRequest request, HttpServletResponse response, String pathLang)
      throws Exception {
    int status = HttpServletResponse.SC_MOVED_TEMPORARILY;

    String url = ServletUtils.getRequestUrl(request, pathLang);
    ServletUtils.sendRedirect(response, this.appConfig.isEnableSession() ? response.encodeRedirectURL(url) : url,
        status);
  }

  @Override
  protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    Asserts.isTrue(request.getDispatcherType().equals(DispatcherType.REQUEST));
    try {

      // DEBUG
      if (this.appConfig.isEnableDebug()) {
        ServletUtils.testErrorStatus(request, "__test_error_status1");
        ServletUtils.testOutStream(request, response, "__test_out_stream1");
      }

      // RequestContext
      final RequestContext requestContext = ServletUtils.getRequestContext(request);

      // Initialize
      initialize(request, response, requestContext);

      final ActionDesc actionDesc = requestContext.getActionDesc();

      // Not Found?
      if (actionDesc == null) {
        throw new NotFoundException(requestContext.res(Resources.ERROR_NOT_FOUND))
            .setTitleKey(Resources.ERROR_NOT_FOUND);
      }

      // Allow Method?
      if (!actionDesc.getAllowMethods().contains(request.getMethod())) {
        throw new HttpException(HttpServletResponse.SC_METHOD_NOT_ALLOWED,
            requestContext.res(Resources.ERROR_METHOD_NOT_ALLOWED)).setTitleKey(Resources.ERROR_METHOD_NOT_ALLOWED);
      }

      // Consume Type
      if (actionDesc.getConsumeType() != null) {
        if (!ServletUtils.isContentSupported(request.getContentType(), actionDesc.getConsumeType().value())) {

          throw new HttpException(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE,
              requestContext.res(Resources.ERROR_UNSUPPORTED_MEDIA_TYPE))
              .setTitleKey(Resources.ERROR_UNSUPPORTED_MEDIA_TYPE);
        }
      }

      // Language
      if (this.languageProvider.isMultiLanguages()) {
        if (!requestContext.isPathLanguage()) {
          if (HttpMethod.GET.equals(request.getMethod())) {
            redirectLang(request, response, requestContext.getLanguageId());
            return;
          }
          throw new BadRequestException(requestContext.res(Resources.ERROR_BAD_REQUEST));
        }
      } else {
        if (requestContext.isPathLanguage()) {
          if (HttpMethod.GET.equals(request.getMethod())) {
            redirectLang(request, response, null);
            return;
          }
          throw new BadRequestException(requestContext.res(Resources.ERROR_BAD_REQUEST));
        }
      }

      // Allow Client
      if (!this.remoteClientVerifier.allowClient(request)) {
        throw new ForbiddenException(requestContext.res(Resources.ERROR_FORBIDDEN));
      }

      // Access Rate
      this.accessRateHandler.checkRequest(request, requestContext);

      // HTTP OPTIONS
      if (HttpMethod.OPTIONS.equals(request.getMethod())) {
        doOptions(request, response, requestContext);
        return;
      }

      // RequestWrapper
      request = new RequestWrapper(request, requestContext.getPathParamMap());
      if (isMockContext()) {
        request.setAttribute(RequestWrapper.class.getName(), request);
      }

      // Authorize Origin
      String origin = this.corsPolicyHandler.getCrossOrigin(request);
      if (origin != null) {

        // Not @EnableCors
        if (actionDesc.getEnableCors() == null) {
          throw new ForbiddenException(
              requestContext.res(Resources.ERROR_FORBIDDEN_CORS, CorsResult.NOT_ALLOWED_CORS.name()));
        }
        CorsPolicy corsPolicy = this.corsPolicyProvider.getCorsPolicy(actionDesc.getEnableCors().value());
        CorsPolicyHandler.CorsResult corsResult = this.corsPolicyHandler.handleCors(request, response, origin,
            corsPolicy);

        if (corsResult != CorsPolicyHandler.CorsResult.ALLOWED) {
          throw new ForbiddenException(requestContext.res(Resources.ERROR_FORBIDDEN_CORS, corsResult.name()));
        }
      }

      // Authorize
      Authorize authorize = actionDesc.getAuthorize();
      if (authorize != null) {
        UserPrincipal principal = ServletUtils.getUserPrincipal(request);

        // Authenticate
        if (principal == null) {
          this.authHandlerProvider.getAuthHandler(requestContext.getModule()).askAuthenticate(request, response,
              requestContext);
          return;
        }

        // No @BypassAuthorization
        if (actionDesc.getBypassAuthorization() == null) {

          // Check Module
          if (!principal.isForModule(requestContext.getModule())) {

            this.authHandlerProvider.getAuthHandler(requestContext.getModule()).askAuthenticate(request, response,
                requestContext);
            return;
          }

          // Authorize
          if (!authorize(request, principal, authorize)) {
            throw new ForbiddenException(requestContext.res(Resources.ERROR_FORBIDDEN))
                .setTitleKey(Resources.ERROR_FORBIDDEN);
          }
        }

        // Re-authenticate
        if (authorize.reauth() && !isReauthSession(principal)) {
          this.authHandlerProvider.getAuthHandler(requestContext.getModule()).askReauthenticate(request, response,
              requestContext);
          return;
        }
      }

      // NOT EnableAsync?
      if (actionDesc.getEnableAsync() == null) {

        // ResponseEncoder
        ResponseEncoder responseEncoder = this.responseEncodingStrategy.getResponseEncoder(request, requestContext);

        // ETAG
        if (requestContext.isGetOrHead() && requestContext.getActionDesc().getEnableEtag() != null) {

          // ContentResponseWrapper
          ContentResponseWrapper wrapper = new ContentResponseWrapper(response, true);
          chain.doFilter(request, wrapper);

          if ((300 <= response.getStatus()) && (response.getStatus() < 400)) {
            return;
          }
          wrapper.finishWrapper();

          // Computed ETAG
          String computedEtag = ServletUtils.toEtag(wrapper.getContent().digest("MD5"));

          // If Modified?
          if (!ServletUtils.checkNotModified(request, response, computedEtag)) {

            // Encoding/ETAG
            if (responseEncoder != null) {
              responseEncoder.encode(response, wrapper.getContent());

            } else {
              response.setContentLengthLong(wrapper.getContent().size());
              wrapper.getContent().writeTo(response.getOutputStream());
            }
          }
          return;
        }

        // > NO ETAG
        if (responseEncoder != null) {
          responseEncoder.encode(request, response, chain);
          return;
        }
      }

      // Next filter?
      chain.doFilter(request, response);

    } catch (Exception ex) {
      this.exceptionHandler.handleException(request, response, ex);
    }
  }

  protected boolean authorize(HttpServletRequest request, UserPrincipal principal, Authorize authorize) {
    String[] roles = authorize.roles();
    String[] policies = authorize.policies();
    if ((roles.length == 0) && (policies.length == 0)) {
      return true;
    }
    if (roles.length > 0) {
      if (Arrays.stream(roles).anyMatch(role -> request.isUserInRole(role))) {
        return true;
      }
    }
    if (policies.length > 0) {
      if (this.authorizePolicyProvider.authorize(principal, policies)) {
        return true;
      }
    }
    return false;
  }

  protected boolean isReauthSession(UserPrincipal principal) {
    if (principal.getReauthAt() == 0) {
      return false;
    }
    long reauthTimeoutMs = this.appConfig.getLong(AppConfig.CONFIG_REAUTH_TIMEOUT_MS);
    return !DateUtils.isExpired(principal.getReauthAt() + reauthTimeoutMs, 0L);
  }

  protected void doOptions(HttpServletRequest request, HttpServletResponse response, RequestContext requestContext)
      throws Exception {
    String origin = request.getHeader(CorsPolicyHandler.HEADER_ORIGIN);
    if ((origin == null) || (request.getHeader(CorsPolicyHandler.HEADER_AC_REQUEST_METHOD) == null)) {
      throw new ForbiddenException(requestContext.res(Resources.ERROR_FORBIDDEN))
          .setTitleKey(Resources.ERROR_FORBIDDEN);
    }
    CorsPolicy corsPolicy = this.corsPolicyProvider
        .getCorsPolicy(requestContext.getActionDesc().getEnableCors().value());
    CorsPolicyHandler.CorsResult corsResult = this.corsPolicyHandler.handlePreflight(request, response, corsPolicy);

    if (corsResult != CorsPolicyHandler.CorsResult.ALLOWED) {
      throw new ForbiddenException(requestContext.res(Resources.ERROR_FORBIDDEN_CORS, corsResult.name()));
    }
  }

  public boolean isMockContext() {
    return false;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy