diff --git a/core/pom.xml b/core/pom.xml index 4594bbbfd5..3ac3989649 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -39,7 +39,7 @@ THE SOFTWARE. true - 1.258 + 1.259-rc1392.509f64eb3d44 2.5.6.SEC03 2.4.12 diff --git a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java index 3a404a49e5..274f25ebfd 100644 --- a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java +++ b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java @@ -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); diff --git a/core/src/main/java/jenkins/security/SuppressionFilter.java b/core/src/main/java/jenkins/security/SuppressionFilter.java new file mode 100644 index 0000000000..ed647b1def --- /dev/null +++ b/core/src/main/java/jenkins/security/SuppressionFilter.java @@ -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(""+Messages.SuppressionFilter_Title()+""); + w.println("

"+Messages.SuppressionFilter_ContactAdmin(errorId)+"

"); + w.println(""); + 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()); +} diff --git a/core/src/main/resources/jenkins/security/Messages.properties b/core/src/main/resources/jenkins/security/Messages.properties index 5ff4e1274b..b332c171b2 100644 --- a/core/src/main/resources/jenkins/security/Messages.properties +++ b/core/src/main/resources/jenkins/security/Messages.properties @@ -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 +