class AmbariAuthorizationFilter implements Filter {

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletResponse httpResponse = (HttpServletResponse) response;

    String requestURI = httpRequest.getRequestURI();

    SecurityContext context = getSecurityContext();

    Authentication authentication = context.getAuthentication();

    AuditEvent auditEvent = null;

    //  If no explicit authenticated user is set, set it to the default user (if one is specified)
    if (authentication == null || authentication instanceof AnonymousAuthenticationToken) {
      Authentication defaultAuthentication = getDefaultAuthentication();
      if (defaultAuthentication != null) {
        context.setAuthentication(defaultAuthentication);
        authentication = defaultAuthentication;
      }
    }
    if (authentication == null || authentication instanceof AnonymousAuthenticationToken ||
        !authentication.isAuthenticated()) {
      String token = httpRequest.getHeader(INTERNAL_TOKEN_HEADER);
      if (token != null) {
        InternalAuthenticationToken internalAuthenticationToken = new InternalAuthenticationToken(token);
        context.setAuthentication(internalAuthenticationToken);
        if(auditLogger.isEnabled()) {
          LoginAuditEvent loginAuditEvent = LoginAuditEvent.builder()
            .withUserName(internalAuthenticationToken.getName())
            .withProxyUserName(AuthorizationHelper.getProxyUserName(internalAuthenticationToken))
            .withRemoteIp(RequestUtils.getRemoteAddress(httpRequest))
            .withRoles(permissionHelper.getPermissionLabels(authentication))
            .withTimestamp(System.currentTimeMillis()).build();
          auditLogger.log(loginAuditEvent);
        }
      } else {
        // for view access, we should redirect to the Ambari login
        if (requestURI.matches(VIEWS_CONTEXT_ALL_PATTERN)) {
          String queryString = httpRequest.getQueryString();
          String requestedURL = queryString == null ? requestURI : (requestURI + '?' + queryString);
          String redirectURL = httpResponse.encodeRedirectURL(LOGIN_REDIRECT_BASE + requestedURL);
          httpResponse.sendRedirect(redirectURL);
        } else {
          entryPoint.commence(httpRequest, httpResponse, new AuthenticationCredentialsNotFoundException("Missing authentication token"));
        }
        return;
      }
    } else if (!authorizationPerformedInternally(requestURI)) {
      boolean authorized = false;

      if (requestURI.matches(API_BOOTSTRAP_PATTERN_ALL)) {
        authorized = AuthorizationHelper.isAuthorized(authentication,
            ResourceType.CLUSTER,
            null,
            EnumSet.of(RoleAuthorization.HOST_ADD_DELETE_HOSTS));
      }
      else {
        for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
          if (grantedAuthority instanceof AmbariGrantedAuthority) {

            AmbariGrantedAuthority ambariGrantedAuthority = (AmbariGrantedAuthority) grantedAuthority;

            PrivilegeEntity privilegeEntity = ambariGrantedAuthority.getPrivilegeEntity();
            Integer permissionId = privilegeEntity.getPermission().getId();

            // admin has full access
            if (permissionId.equals(PermissionEntity.AMBARI_ADMINISTRATOR_PERMISSION)) {
              authorized = true;
              break;
            }

            // clusters require permission
            if (!"GET".equalsIgnoreCase(httpRequest.getMethod()) && requestURI.matches(API_CREDENTIALS_AMBARI_PATTERN)) {
              // Only the administrator can operate on credentials where the alias starts with "ambari."
              if (permissionId.equals(PermissionEntity.AMBARI_ADMINISTRATOR_PERMISSION)) {
                authorized = true;
                break;
              }
            } else if (requestURI.matches(API_CLUSTERS_ALL_PATTERN)) {
              if (permissionId.equals(PermissionEntity.CLUSTER_USER_PERMISSION) ||
                  permissionId.equals(PermissionEntity.CLUSTER_ADMINISTRATOR_PERMISSION)) {
                authorized = true;
                break;
              }
            } else if (STACK_ADVISOR_REGEX.matcher(requestURI).matches()) {
              //TODO permissions model doesn't manage stacks api, but we need access to stack advisor to save configs
              if (permissionId.equals(PermissionEntity.CLUSTER_USER_PERMISSION) ||
                  permissionId.equals(PermissionEntity.CLUSTER_ADMINISTRATOR_PERMISSION)) {
                authorized = true;
                break;
              }
            } else if (requestURI.matches(API_VIEWS_ALL_PATTERN)) {
              // views require permission
              if (permissionId.equals(PermissionEntity.VIEW_USER_PERMISSION)) {
                authorized = true;
                break;
              }
            }
          }
        }

        // Allow all GETs that are not LDAP sync events...
        authorized = authorized || (httpRequest.getMethod().equals("GET") && !requestURI.matches(API_LDAP_SYNC_EVENTS_ALL_PATTERN));
      }

      if (!authorized) {
        if(auditLogger.isEnabled()) {
          auditEvent = AccessUnauthorizedAuditEvent.builder()
            .withHttpMethodName(httpRequest.getMethod())
            .withRemoteIp(RequestUtils.getRemoteAddress(httpRequest))
            .withResourcePath(httpRequest.getRequestURI())
            .withUserName(AuthorizationHelper.getAuthenticatedName())
            .withProxyUserName(AuthorizationHelper.getProxyUserName())
            .withTimestamp(System.currentTimeMillis())
            .build();
          auditLogger.log(auditEvent);
        }

        httpResponse.setHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
        httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "You do not have permissions to access this resource.");
        httpResponse.flushBuffer();
        return;
      }
    }
    if (AuthorizationHelper.getAuthenticatedName() != null) {
      httpResponse.setHeader("User", AuthorizationHelper.getAuthenticatedName());
      if (auditLogger.isEnabled() && httpResponse.getStatus() == HttpServletResponse.SC_FORBIDDEN) {
        auditEvent = AccessUnauthorizedAuditEvent.builder()
          .withHttpMethodName(httpRequest.getMethod())
          .withRemoteIp(RequestUtils.getRemoteAddress(httpRequest))
          .withResourcePath(httpRequest.getRequestURI())
          .withUserName(AuthorizationHelper.getAuthenticatedName())
          .withProxyUserName(AuthorizationHelper.getProxyUserName())
          .withTimestamp(System.currentTimeMillis())
          .build();
        auditLogger.log(auditEvent);
      }
    }
    chain.doFilter(request, response);
  }

  public  AmbariAuthorizationFilter(AmbariEntryPoint entryPoint,
                                   Configuration configuration,
                                   Users users,
                                   AuditLogger auditLogger,
                                   PermissionHelper permissionHelper);

}