blob: a321f6180bc0df37e22e6f2114ea79e36d29dbcb [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.security.filter;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService;
import org.apache.fineract.infrastructure.cache.domain.CacheType;
import org.apache.fineract.infrastructure.cache.service.CacheWritePlatformService;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.security.data.PlatformRequestLog;
import org.apache.fineract.infrastructure.security.exception.InvalidTenantIdentifierException;
import org.apache.fineract.infrastructure.security.service.BasicAuthTenantDetailsService;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;
/**
*
* This filter is responsible for extracting multi-tenant from the request and setting Cross-Origin details to response.
*
* If multi-tenant are valid, the details of the tenant are stored in {@link FineractPlatformTenant} and stored in a
* {@link ThreadLocal} variable for this request using {@link ThreadLocalContextUtil}.
*
* If multi-tenant are invalid, a http error response is returned.
*
* Used to support Oauth2 authentication and the service is loaded only when "oauth" profile is active.
*/
@RequiredArgsConstructor
@Slf4j
public class TenantAwareTenantIdentifierFilter extends GenericFilterBean {
private static final AtomicBoolean FIRST_PROCESSED_REQUEST = new AtomicBoolean();
private final BasicAuthTenantDetailsService basicAuthTenantDetailsService;
private final ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer;
private final ConfigurationDomainService configurationDomainService;
private final CacheWritePlatformService cacheWritePlatformService;
private final BusinessDateReadPlatformService businessDateReadPlatformService;
private static final String TENANT_ID_REQUEST_HEADER = "Fineract-Platform-TenantId";
private static final boolean EXCEPTION_IF_HEADER_MISSING = true;
private static final String API_URI = "/api/v1/";
@Override
@SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT")
public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain)
throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
final StopWatch task = new StopWatch();
task.start();
try {
ThreadLocalContextUtil.reset();
// allows for Cross-Origin
// Requests (CORs) to be performed against the platform API.
response.setHeader("Access-Control-Allow-Origin", "*"); // NOSONAR
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
final String reqHead = request.getHeader("Access-Control-Request-Headers");
if (null != reqHead && !reqHead.isEmpty()) {
response.setHeader("Access-Control-Allow-Headers", reqHead);
}
if (!"OPTIONS".equalsIgnoreCase(request.getMethod())) {
String tenantIdentifier = request.getHeader(TENANT_ID_REQUEST_HEADER);
if (org.apache.commons.lang3.StringUtils.isBlank(tenantIdentifier)) {
tenantIdentifier = request.getParameter("tenantIdentifier");
}
if (tenantIdentifier == null && EXCEPTION_IF_HEADER_MISSING) {
throw new InvalidTenantIdentifierException("No tenant identifier found: Add request header of '"
+ TENANT_ID_REQUEST_HEADER + "' or add the parameter 'tenantIdentifier' to query string of request URL.");
}
String pathInfo = request.getRequestURI();
boolean isReportRequest = false;
if (pathInfo != null && pathInfo.contains("report")) {
isReportRequest = true;
}
final FineractPlatformTenant tenant = basicAuthTenantDetailsService.loadTenantById(tenantIdentifier, isReportRequest);
ThreadLocalContextUtil.setTenant(tenant);
HashMap<BusinessDateType, LocalDate> businessDates = businessDateReadPlatformService.getBusinessDates();
ThreadLocalContextUtil.setBusinessDates(businessDates);
String authToken = request.getHeader("Authorization");
if (authToken != null && authToken.startsWith("bearer ")) {
ThreadLocalContextUtil.setAuthToken(authToken.replaceFirst("bearer ", ""));
}
if (!FIRST_PROCESSED_REQUEST.get()) {
final String baseUrl = request.getRequestURL().toString().replace(request.getRequestURI(),
request.getContextPath() + API_URI);
System.setProperty("baseUrl", baseUrl);
final boolean ehcacheEnabled = configurationDomainService.isEhcacheEnabled();
if (ehcacheEnabled) {
cacheWritePlatformService.switchToCache(CacheType.SINGLE_NODE);
} else {
cacheWritePlatformService.switchToCache(CacheType.NO_CACHE);
}
FIRST_PROCESSED_REQUEST.set(true);
}
chain.doFilter(request, response);
}
} catch (final InvalidTenantIdentifierException e) {
// deal with exception at low level
SecurityContextHolder.getContext().setAuthentication(null);
response.addHeader("WWW-Authenticate", "Basic realm=\"" + "Fineract Platform API" + "\"");
response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
} finally {
ThreadLocalContextUtil.reset();
task.stop();
final PlatformRequestLog logRequest = PlatformRequestLog.from(task, request);
log.debug("{}", toApiJsonSerializer.serialize(logRequest));
}
}
}