Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed CORS filtering fail causing print of stacktrace in HTTP Response #3294

Merged
merged 5 commits into from
Apr 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion job-engine/app/web/src/main/webapp/WEB-INF/web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

<filter>
<filter-name>CORSResponseFilter</filter-name>
<filter-class>org.eclipse.kapua.app.api.core.CORSResponseFilter</filter-class>
<filter-class>org.eclipse.kapua.app.api.core.filter.CORSResponseFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CORSResponseFilter</filter-name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,13 @@
* Contributors:
* Eurotech - initial API and implementation
*******************************************************************************/
package org.eclipse.kapua.app.api.core;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
package org.eclipse.kapua.app.api.core.filter;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.net.HttpHeaders;
import liquibase.util.StringUtils;
import org.apache.shiro.web.util.WebUtils;
import org.eclipse.kapua.KapuaException;
import org.eclipse.kapua.app.api.core.settings.KapuaApiCoreSetting;
import org.eclipse.kapua.app.api.core.settings.KapuaApiCoreSettingKeys;
Expand All @@ -46,30 +32,44 @@
import org.eclipse.kapua.service.endpoint.EndpointInfoListResult;
import org.eclipse.kapua.service.endpoint.EndpointInfoQuery;
import org.eclipse.kapua.service.endpoint.EndpointInfoService;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import liquibase.util.StringUtils;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
* CORS {@link Filter} implementation.
* <p>
* This filter handles the CORS request per-scope basis.
*
* @since 1.5.0
*/
public class CORSResponseFilter implements Filter {

private final Logger logger = LoggerFactory.getLogger(CORSResponseFilter.class);

private final KapuaLocator locator = KapuaLocator.getInstance();
private final AccountService accountService = locator.getService(AccountService.class);
private final AccountFactory accountFactory = locator.getFactory(AccountFactory.class);
private final EndpointInfoService endpointInfoService = locator.getService(EndpointInfoService.class);
private final EndpointInfoFactory endpointInfoFactory = locator.getFactory(EndpointInfoFactory.class);

private static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
private static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
private static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
private static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
private static final String ORIGIN = "Origin";

private final Logger logger = LoggerFactory.getLogger(CORSResponseFilter.class);

private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
private ScheduledFuture<?> refreshTask;

Expand All @@ -78,9 +78,10 @@ public class CORSResponseFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) {
logger.info("Initializing with FilterConfig: {}", filterConfig);
logger.info("Initializing with FilterConfig: {}...", filterConfig);
int intervalSecs = KapuaApiCoreSetting.getInstance().getInt(KapuaApiCoreSettingKeys.API_CORS_REFRESH_INTERVAL, 60);
initRefreshThread(intervalSecs);
logger.info("Initializing with FilterConfig: {}... DONE!", filterConfig);
}

@Override
Expand All @@ -96,37 +97,37 @@ public void destroy() {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpResponse = WebUtils.toHttp(response);
HttpServletRequest httpRequest = WebUtils.toHttp(request);
String origin = httpRequest.getHeader(ORIGIN);

String origin = httpRequest.getHeader(HttpHeaders.ORIGIN);
if (StringUtils.isEmpty(origin)) {
// Not a CORS request. Move along.
chain.doFilter(request, response);
return;
}

httpResponse.addHeader(ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, DELETE, PUT");
httpResponse.addHeader(ACCESS_CONTROL_ALLOW_HEADERS, "X-Requested-With, Content-Type, Authorization");

if (httpRequest.getMethod().equals("OPTIONS") || KapuaSecurityUtils.getSession() == null) {
// Preflight request, or session not yet established (Authentication)
if (checkOrigin(origin, null)) {
// Origin matches at least one defined Endpoint
httpResponse.addHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
httpResponse.addHeader(ACCESS_CONTROL_ALLOW_ORIGIN, origin);
httpResponse.addHeader("Vary", ORIGIN);
} else {
throw new ServletException(String.format("HTTP Origin not allowed: %s", origin));
}
httpResponse.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, DELETE, PUT");
httpResponse.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "X-Requested-With, Content-Type, Authorization");

// Depending on the type of the request the KapuaSession might not yet be present.
// For preflight request or session not yet established the KapuaSession will be null.
// For the actual request it will be available and we will check the CORS according to the scope.
KapuaId scopeId = KapuaSecurityUtils.getSession() != null ? KapuaSecurityUtils.getSession().getScopeId() : null;

if (checkOrigin(origin, scopeId)) {
// Origin matches at least one defined Endpoint
httpResponse.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
httpResponse.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
httpResponse.addHeader("Vary", HttpHeaders.ORIGIN);
} else {
// Actual request
if (checkOrigin(origin, KapuaSecurityUtils.getSession().getScopeId())) {
// Origin matches at least one defined Endpoint
httpResponse.addHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
httpResponse.addHeader(ACCESS_CONTROL_ALLOW_ORIGIN, origin);
httpResponse.addHeader("Vary", ORIGIN);
} else {
throw new ServletException(String.format("HTTP Origin not allowed: %s", origin));
}
String msg = scopeId != null ?
String.format("HTTP Origin not allowed: %s for scope: %s", origin, scopeId.toCompactId()) :
String.format("HTTP Origin not allowed: %s", origin);

logger.error(msg);
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, msg);
return;
}

chain.doFilter(request, response);
}

Expand All @@ -135,6 +136,7 @@ private String getExplicitOrigin(String origin) throws MalformedURLException {
if (originUrl.getPort() != -1) {
return origin;
}

switch (originUrl.getProtocol()) {
case "http":
return origin + ":80";
Expand All @@ -152,6 +154,7 @@ private boolean checkOrigin(String origin, KapuaId scopeId) {
} catch (MalformedURLException malformedURLException) {
return false;
}

if (scopeId == null) {
// No scopeId, so the call is no authenticated. Return true only if origin
// is enabled in any account or system settings
Expand All @@ -172,6 +175,7 @@ private synchronized void initRefreshThread(int intervalSecs) {
private synchronized void refreshOrigins() {
try {
logger.info("Refreshing list of origins...");

Multimap<String, KapuaId> newAllowedOrigins = HashMultimap.create();
AccountQuery accounts = accountFactory.newQuery(null);
AccountListResult accountListResult = KapuaSecurityUtils.doPrivileged(() -> accountService.query(accounts));
Expand All @@ -184,19 +188,20 @@ private synchronized void refreshOrigins() {
logger.warn("Unable to add endpoints for account {} to CORS filter", account.getId().toCompactId(), kapuaException);
}
});

for (String allowedSystemOrigin : allowedSystemOrigins) {
try {
String explicitAllowedSystemOrigin = getExplicitOrigin(allowedSystemOrigin);
newAllowedOrigins.put(explicitAllowedSystemOrigin, KapuaId.ANY);
} catch (MalformedURLException malformedURLException) {
logger.warn(String.format("Unable to parse origin %s", allowedSystemOrigin), malformedURLException);
logger.warn("Unable to parse origin: {}", allowedSystemOrigin, malformedURLException);
}
}
allowedOrigins = newAllowedOrigins;
logger.info("Refreshing list of origins... DONE!");

logger.info("Refreshing list of origins... DONE! Loaded {} origins", allowedOrigins.size());
} catch (Exception exception) {
logger.warn("Unable to refresh list of origins", exception);
logger.warn("Refreshing list of origins... ERROR!", exception);
}
}

}
6 changes: 3 additions & 3 deletions rest-api/web/src/main/webapp/WEB-INF/web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@

<filter>
<filter-name>CORSResponseFilter</filter-name>
<filter-class>org.eclipse.kapua.app.api.core.CORSResponseFilter</filter-class>
<filter-class>org.eclipse.kapua.app.api.core.filter.CORSResponseFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CORSResponseFilter</filter-name>
Expand All @@ -64,7 +64,7 @@
<!-- -->
<!-- Servlets -->
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-name>Eclipse Kapua REST API</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
Expand All @@ -79,7 +79,7 @@
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-name>Eclipse Kapua REST API</servlet-name>
<url-pattern>/v1/*</url-pattern>
</servlet-mapping>

Expand Down