Add stack trace suppression into core as a standard.

This commit is contained in:
Jeff Thompson committed 2020-02-11 14:10:11 -07:00
1 parent a8f5167dae
commit 8b50892bd8
4 files changed
+136 -2

No files matched your search

+1 -1
View File
@@ -39,7 +39,7 @@ THE SOFTWARE.
<properties>
<staplerFork>true</staplerFork>
<stapler.version>1.258</stapler.version>
<stapler.version>1.259-rc1392.509f64eb3d44</stapler.version>
<spring.version>2.5.6.SEC03</spring.version>
<groovy.version>2.4.12</groovy.version>
</properties>
@@ -251,7 +251,7 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
private User _doCreateAccount(StaplerRequest req, StaplerResponse rsp, String formView) throws ServletException, IOException {
if(!allowsSignup())
throw HttpResponses.error(SC_UNAUTHORIZED,new Exception("User sign up is prohibited"));
throw HttpResponses.errorWithoutStack(SC_UNAUTHORIZED, "User sign up is prohibited");
boolean firstUser = !hasSomeUser();
User u = createAccount(req, rsp, enableCaptcha, formView);
@@ -0,0 +1,131 @@
package jenkins.security;
import hudson.init.InitMilestone;
import hudson.init.Initializer;
import hudson.util.PluginServletFilter;
import jenkins.model.Jenkins;
import org.acegisecurity.AccessDeniedException;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.Stapler;
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.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
@Restricted(NoExternalUse.class)
public class SuppressionFilter implements Filter {
@Initializer(after = InitMilestone.STARTED)
public static void init() throws ServletException {
HttpResponses.setErrorDetailsFilter((code, cause) -> showStackTrace(cause));
PluginServletFilter.addFilter(new SuppressionFilter());
}
public void init(FilterConfig filterConfig) throws ServletException {
// no-op
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request,response);
} catch (Throwable e) {
if (e instanceof AccessDeniedException) {
throw (AccessDeniedException) e;
}
if (e instanceof ServletException) {
if (((ServletException)e).getRootCause() instanceof AccessDeniedException)
throw (ServletException)e; // this exception needs to be pass through since Jenkins has a filter that reacts to this
}
if (Stapler.isSocketException(e)) {
return;
}
if (showStackTrace(e)) {
// thing we can throw without wrapping
if (e instanceof IOException)
throw (IOException)e;
if (e instanceof ServletException)
throw (ServletException)e;
if (e instanceof RuntimeException)
throw (RuntimeException)e;
if (e instanceof Error)
throw (Error)e;
// ServletException chaining is messed up. We go extra mile here to really
// chain the cause.
ServletException x = new ServletException("Request processing failed", e);
try {
if (x.getCause() == null) x.initCause(e);
} catch (IllegalStateException _) {
// just in case.
}
throw x;
} else if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
// We cannot decorate the stuff for non-Http responses
String errorId = reportError((HttpServletRequest) request,e);
try {
HttpServletResponse rsp = (HttpServletResponse)response;
rsp.setStatus(SC_INTERNAL_SERVER_ERROR);
rsp.setContentType("text/html;charset=UTF-8");
rsp.setHeader("Cache-Control","no-cache,must-revalidate");
PrintWriter w = null;
try {
w = rsp.getWriter();
} catch (IllegalStateException x) {
// stream mode?
w = new PrintWriter(new OutputStreamWriter(rsp.getOutputStream(),"UTF-8"));
}
w.println("<html><head><title>"+Messages.SuppressionFilter_Title()+"</title><body>");
w.println("<p>"+Messages.SuppressionFilter_ContactAdmin(errorId)+"</p>");
w.println("</body></html>");
w.close();
} catch (Error error) {
throw error; // We propagate errors upstairs
} catch (Throwable x) {
// if we fail to report this error, bail out
throw new ServletException(Messages.SuppressionFilter_ContactAdmin(errorId)); // not chaining x since it might contain something
}
}
}
}
/**
* Report an error and then generate a unique ID for that error.
*/
protected String reportError(HttpServletRequest req, Throwable e) {
String id = UUID.randomUUID().toString();
LOGGER.log(Level.WARNING, "Request processing failed. URI=" + req.getRequestURI() + " clientIP=" + req.getRemoteAddr() + " ErrorID=" + id, e);
return id;
}
/**
* Should we show this stack trace to the requesting user?
*/
private static boolean showStackTrace(Throwable t) {
// TODO: define a permission for this
return Jenkins.getInstance().hasPermission(Jenkins.ADMINISTER);
}
public void destroy() {
// no-op
}
private static final Logger LOGGER = Logger.getLogger(SuppressionFilter.class.getName());
}
@@ -47,3 +47,6 @@ ResourceDomainConfiguration.IOException=Failed to connect: {0}
ResourceDomainConfiguration.Invalid=Not a valid URL.
ResourceDomainConfiguration.SameAsJenkinsRoot=Cannot use the same host name for both Jenkins root URL and resource root URL.
ResourceDomainConfiguration.SameAsCurrent=You are currently accessing Jenkins through a URL similar to the proposed resource root URL. Saving this URL might remove your access to Jenkins.
SuppressionFilter.ContactAdmin=An error occurred processing your request. Ask your Jenkins administrator to look up details. ErrorID\={0}
SuppressionFilter.Title=Error