package gw.servlet uses com.guidewire.pl.system.dependency.PLDependencies uses com.guidewire.pl.system.service.context.ServiceToken uses com.guidewire.pl.web.auth.LoginHelper uses com.guidewire.pl.web.controller.WebServlet uses gw.api.system.PLLoggerCategory uses javax.servlet.http.HttpServlet uses javax.servlet.http.HttpServletRequest uses javax.servlet.http.HttpServletResponse uses java.net.URI uses java.net.URISyntaxException @ReadOnly abstract class AbstractCORSRequestServlet extends HttpServlet { abstract function getAllowOrigin() : String function isRefererMatchEnabled() : boolean { return false } function getAllowMethods() : List { return { "GET", "POST", "OPTIONS" } } function getAllowHeaders() : List { return Collections.emptyList() } function applyAdditionalResponseHeaders(req: HttpServletRequest, resp: HttpServletResponse) { } // From WebServlet static final var SESSION_LOCK_ATTRIBUTE = "GW_SESSION_LOCK" // Allow-Origin:* is not supported static final var ALLOW_ALL = "*" final function configureAccessControl(req: HttpServletRequest, resp: HttpServletResponse, allowOrigin: String) { if(allowOrigin.contains(ALLOW_ALL)) { PLLoggerCategory.SERVER_SERVLET.error("Wildcards not allowed for Allow-Origin: {}", allowOrigin, new IllegalStateException("Wildcards not allowed for Allow-Origin")) resp.setStatus(HttpServletResponse.SC_BAD_REQUEST) return } resp.setHeader("Access-Control-Allow-Origin", allowOrigin) resp.setHeader("Access-Control-Allow-Credentials", Boolean.TRUE.toString()) resp.setHeader("Access-Control-Allow-Methods", getAllowMethods().join(",")) var requestAllowHeaders = req.getHeader("Access-Control-Request-Headers") if(requestAllowHeaders != null && requestAllowHeaders.length() > 0) { resp.setHeader("Access-Control-Allow-Headers", getAllowHeaders().join(",")) } applyAdditionalResponseHeaders(req, resp) } final override protected function service(req: HttpServletRequest, resp: HttpServletResponse) { var allowOrigin = getAllowOrigin() if(!verifyMatchingOrigin(req, resp, allowOrigin)) { return } if("OPTIONS".equals(req.getMethod())) { configureAccessControl(req, resp, allowOrigin) return } var theSession = req.getSession(false) if(theSession == null) { handleError(resp, "No session found, returning {}", HttpServletResponse.SC_FORBIDDEN) return } var token = theSession.getAttribute(WebServlet.SERVICE_TOKEN_SESSION_ATTR) as ServiceToken if(token == null) { handleError(resp, "Session with no service token, returning {}", HttpServletResponse.SC_FORBIDDEN) return } if(!token.isAuthenticatedUser()) { handleError(resp, "Session is not authenticated, returning {}", HttpServletResponse.SC_FORBIDDEN) return } configureAccessControl(req, resp, allowOrigin) // Primarily to check session expiration, adjusted for the testing clock token = PLDependencies.getServiceTokenManager().unlockToken(token.getSessionID()) if(token == null) { // Don't return 401 here because this servlet does not support authentication. // User must log back into the application through the main screen. handleError(resp, "No service token found, user must reauthenticate, returning {}", HttpServletResponse.SC_FORBIDDEN) return } var sessionLock = theSession.getAttribute(SESSION_LOCK_ATTRIBUTE) if(sessionLock == null) { // This shouldn't happen, but there is narrow race condition that would allow it. // Responsibility for session lock creation is unsynchronized and left with WebServlet // Return 403 instead of 500 because reauthentication will resolve this issue. handleError(resp, "Unable to lock session, user should reauthenticate, returning {}", HttpServletResponse.SC_FORBIDDEN) return } using(sessionLock as IMonitorLock) { PLDependencies.getCommonDependencies().setServiceToken(token) try { LoginHelper.setupLoggingContext(token.getUser()) super.service(req, resp) } finally { PLDependencies.getCommonDependencies().setServiceToken(null) } } } private function verifyMatchingOrigin(req: HttpServletRequest, resp: HttpServletResponse, allowOrigin: String) : boolean { var result = false var origin = req.getHeader("Origin") if (origin != null && origin.equals(allowOrigin)) { PLLoggerCategory.SERVER_SERVLET.trace("Allowing matched origin {} ", origin) result = true } else if(origin == null && isRefererMatchEnabled() && hasHeader(req, "Referer")) { result = verifyMatchingReferer(req, resp, allowOrigin) } else { PLLoggerCategory.SERVER_SERVLET.error("Calling origin {} does not match allowed origin {}, returning {}", new Object[] { origin, allowOrigin, HttpServletResponse.SC_FORBIDDEN }) resp.setStatus(HttpServletResponse.SC_FORBIDDEN) } return result } private function verifyMatchingReferer(req: HttpServletRequest, resp: HttpServletResponse, allowOrigin: String) : boolean { var result = false var referer = req.getHeader("Referer") PLLoggerCategory.SERVER_SERVLET.trace("Attempting to verify referer {}", referer) try { var uri = new URI(referer) var scheme = uri.getScheme() var authority = uri.getAuthority() if(scheme != null && authority != null) { var refererOrigin = scheme + "://" + authority result = refererOrigin.equals(allowOrigin) PLLoggerCategory.SERVER_SERVLET.trace("Verifying {} against {}, match = {}", {refererOrigin, referer, result}) } else { PLLoggerCategory.SERVER_SERVLET.error("Referer invalid: missing scheme {} or authority {}", scheme, authority) } } catch (ex: URISyntaxException) { PLLoggerCategory.SERVER_SERVLET.error("Invalid referer header {} : {}", referer, ex.getMessage()) } if(!result) { PLLoggerCategory.SERVER_SERVLET.error("Calling origin is null and referer {} does not match allowed origin {}, returning {}", new Object[] { referer, allowOrigin, HttpServletResponse.SC_FORBIDDEN }) resp.setStatus(HttpServletResponse.SC_FORBIDDEN) } return result } private function hasHeader(req: HttpServletRequest, name : String) : boolean { var header = req.getHeader(name) return header != null && header.length() > 0 } private function handleError(resp: HttpServletResponse, message: String, responseCode: int ) { PLLoggerCategory.SERVER_SERVLET.error(message, responseCode) resp.setStatus(responseCode) } }