/** * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mifosplatform.infrastructure.security.filter; import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.time.StopWatch; import org.mifosplatform.infrastructure.cache.domain.CacheType; import org.mifosplatform.infrastructure.cache.service.CacheWritePlatformService; import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService; import org.mifosplatform.infrastructure.core.domain.MifosPlatformTenant; import org.mifosplatform.infrastructure.core.serialization.ToApiJsonSerializer; import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil; import org.mifosplatform.infrastructure.security.data.PlatformRequestLog; import org.mifosplatform.infrastructure.security.exception.InvalidTenantIdentiferException; import org.mifosplatform.infrastructure.security.service.BasicAuthTenantDetailsService; import org.mifosplatform.useradministration.domain.AppUser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Profile; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.stereotype.Service; /** * A customised version of spring security's {@link BasicAuthenticationFilter}. * * This filter is responsible for extracting multi-tenant and basic auth * credentials from the request and checking that the details provided are * valid. * * If multi-tenant and basic auth credentials are valid, the details of the * tenant are stored in {@link MifosPlatformTenant} and stored in a * {@link ThreadLocal} variable for this request using * {@link ThreadLocalContextUtil}. * * If multi-tenant and basic auth credentials are invalid, a http error response * is returned. */ @Service(value = "basicAuthenticationProcessingFilter") @Profile("basicauth") public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFilter { private static boolean firstRequestProcessed = false; private final static Logger logger = LoggerFactory.getLogger(TenantAwareBasicAuthenticationFilter.class); private final BasicAuthTenantDetailsService basicAuthTenantDetailsService; private final ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer; private final ConfigurationDomainService configurationDomainService; private final CacheWritePlatformService cacheWritePlatformService; private final String tenantRequestHeader = "X-Mifos-Platform-TenantId"; private final boolean exceptionIfHeaderMissing = true; @Autowired public TenantAwareBasicAuthenticationFilter(final AuthenticationManager authenticationManager, final AuthenticationEntryPoint authenticationEntryPoint, final BasicAuthTenantDetailsService basicAuthTenantDetailsService, final ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer, final ConfigurationDomainService configurationDomainService, final CacheWritePlatformService cacheWritePlatformService) { super(authenticationManager, authenticationEntryPoint); this.basicAuthTenantDetailsService = basicAuthTenantDetailsService; this.toApiJsonSerializer = toApiJsonSerializer; this.configurationDomainService = configurationDomainService; this.cacheWritePlatformService = cacheWritePlatformService; } @Override 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 { if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { // ignore to allow 'preflight' requests from AJAX applications // in different origin (domain name) } else { String tenantIdentifier = request.getHeader(this.tenantRequestHeader); if (org.apache.commons.lang.StringUtils.isBlank(tenantIdentifier)) { tenantIdentifier = request.getParameter("tenantIdentifier"); } if (tenantIdentifier == null && this.exceptionIfHeaderMissing) { throw new InvalidTenantIdentiferException( "No tenant identifier found: Add request header of '" + this.tenantRequestHeader + "' 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 MifosPlatformTenant tenant = this.basicAuthTenantDetailsService.loadTenantById(tenantIdentifier, isReportRequest); ThreadLocalContextUtil.setTenant(tenant); String authToken = request.getHeader("Authorization"); if (authToken != null && authToken.startsWith("Basic ")) { ThreadLocalContextUtil.setAuthToken(authToken.replaceFirst("Basic ", "")); } if (!firstRequestProcessed) { final String baseUrl = request.getRequestURL().toString().replace(request.getPathInfo(), "/"); System.setProperty("baseUrl", baseUrl); final boolean ehcacheEnabled = this.configurationDomainService.isEhcacheEnabled(); if (ehcacheEnabled) { this.cacheWritePlatformService.switchToCache(CacheType.SINGLE_NODE); } else { this.cacheWritePlatformService.switchToCache(CacheType.NO_CACHE); } TenantAwareBasicAuthenticationFilter.firstRequestProcessed = true; } } super.doFilter(req, res, chain); } catch (final InvalidTenantIdentiferException e) { // deal with exception at low level SecurityContextHolder.getContext().setAuthentication(null); response.addHeader("WWW-Authenticate", "Basic realm=\"" + "Mifos Platform API" + "\""); response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); } finally { task.stop(); final PlatformRequestLog log = PlatformRequestLog.from(task, request); logger.info(this.toApiJsonSerializer.serialize(log)); } } @Override protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException { super.onSuccessfulAuthentication(request, response, authResult); AppUser user = (AppUser) authResult.getPrincipal(); String pathURL = request.getRequestURI(); boolean isSelfServiceRequest = (pathURL != null && pathURL.contains("/self/")); boolean notAllowed = ((isSelfServiceRequest && !user.isSelfServiceUser()) ||(!isSelfServiceRequest && user.isSelfServiceUser())); if(notAllowed){ throw new BadCredentialsException("User not authorised to use the requested resource."); } } }