This is an automated email from the ASF dual-hosted git repository. heneveld pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git
commit a5974dfab536f65395c9af6ca1a927c26c7f69b0 Author: Alex Heneveld <[email protected]> AuthorDate: Mon Jan 20 18:21:25 2025 +0000 Add option to disable traces in REST responses --- .../brooklyn/rest/util/DefaultExceptionMapper.java | 88 +++++++++++++++++++--- .../rest/util/MultiSessionAttributeAdapter.java | 5 +- 2 files changed, 81 insertions(+), 12 deletions(-) diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java index 0361c7c47a..8a9c65b039 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java @@ -19,21 +19,34 @@ package org.apache.brooklyn.rest.util; import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import javax.ws.rs.ext.ContextResolver; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.api.mgmt.entitlement.EntitlementClass; +import org.apache.brooklyn.api.mgmt.entitlement.EntitlementContext; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.entity.BrooklynConfigKeys; import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; +import org.apache.brooklyn.core.mgmt.entitlement.WebEntitlementContext; import org.apache.brooklyn.rest.domain.ApiError; import org.apache.brooklyn.rest.domain.ApiError.Builder; import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.core.flags.TypeCoercions; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.exceptions.UserFacingException; import org.apache.brooklyn.util.javalang.coerce.ClassCoercionException; +import org.apache.brooklyn.util.text.Identifiers; import org.apache.brooklyn.util.text.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,7 +61,10 @@ public class DefaultExceptionMapper implements ExceptionMapper<Throwable> { static Set<Class<?>> encounteredUnknownExceptions = MutableSet.of(); static Set<Object> encounteredExceptionRecords = MutableSet.of(); - + + @Context + private ContextResolver<ManagementContext> mgmt; + /** * Maps a throwable to a response. * <p/> @@ -69,13 +85,14 @@ public class DefaultExceptionMapper implements ExceptionMapper<Throwable> { return null; } + String errorReference = Identifiers.makeRandomId(13); Throwable throwable2 = Exceptions.getFirstInteresting(throwable1); if (isSevere(throwable2)) { - LOG.warn("REST request running as {} threw: {}", Entitlements.getEntitlementContext(), - Exceptions.collapseText(throwable1)); + LOG.warn("REST request running as {} threw: {} (ref {})", Entitlements.getEntitlementContext(), + Exceptions.collapseText(throwable1), errorReference); } else { - LOG.debug("REST request running as {} threw: {}", Entitlements.getEntitlementContext(), - Exceptions.collapseText(throwable1)); + LOG.debug("REST request running as {} threw: {} (ref {})", Entitlements.getEntitlementContext(), + Exceptions.collapseText(throwable1), errorReference); } logExceptionDetailsForDebugging(throwable1); @@ -114,11 +131,62 @@ public class DefaultExceptionMapper implements ExceptionMapper<Throwable> { if (userFacing instanceof UserFacingException) { return ApiError.of(userFacing.getMessage()).asBadRequestResponseJson(); } - - Builder rb = ApiError.builderFromThrowable(Exceptions.collapse(throwable2)); - if (Strings.isBlank(rb.getMessage())) - rb.message("Internal error. Contact server administrator to consult logs for more details."); - return rb.build().asResponse(Status.INTERNAL_SERVER_ERROR, MediaType.APPLICATION_JSON_TYPE); + + if (!isTraceVisibleToUser()) { + return ApiError.builder() + .message("Internal error. Contact server administrator citing reference " + errorReference +" to consult logs for more details.") + .build().asResponse(Status.INTERNAL_SERVER_ERROR, MediaType.APPLICATION_JSON_TYPE); + } else { + Builder rb = ApiError.builderFromThrowable(Exceptions.collapse(throwable2)); + if (Strings.isBlank(rb.getMessage())) { + rb.message("Internal error. Contact server administrator citing reference " + errorReference + " to consult logs for more details."); + } else { + rb.message(rb.getMessage()+" (Reference: "+errorReference+")"); + } + return rb.build().asResponse(Status.INTERNAL_SERVER_ERROR, MediaType.APPLICATION_JSON_TYPE); + } + } + + + public static final ConfigKey<ReturnStackTraceMode> REST_RETURN_STACK_TRACES = ConfigKeys.newConfigKey(ReturnStackTraceMode.class, + MultiSessionAttributeAdapter.OAB_SERVER_CONFIG_PREFIX+"returnStackTraces", + "Whether REST requests that have errors can include stack traces; " + + "'true' or 'all' to mean always, 'false' or 'none' to mean never, " + + "and otherwise 'root' or 'power' to allow users with root or java entitlement", + ReturnStackTraceMode.ALL); + + boolean isTraceVisibleToUser() { + ManagementContext m = mgmt.getContext(ManagementContext.class); + if (m==null) return true; + try { + ReturnStackTraceMode mode = m.getConfig().getConfig(REST_RETURN_STACK_TRACES); + if (mode==null) mode = ReturnStackTraceMode.ALL; + return mode.checkCurrentUser(m); + } catch (Exception e) { + LOG.warn("Error checking user permissions for nested exception; will log and return original exception, with stack traces shown here", e); + return false; + } + } + enum ReturnStackTraceMode { + ALL( (_m,_ec) -> true), + TRUE( (_m,_ec) -> true), + NONE( (_m,_ec) -> false), + FALSE( (_m,_ec) -> false), + ROOT( (m,ec) -> Entitlements.isEntitled(m.getEntitlementManager(), Entitlements.ROOT, null) ), + POWER( (m,ec) -> Entitlements.isEntitled(m.getEntitlementManager(), Entitlements.ADD_JAVA, null) ), + POWERUSER( (m,ec) -> Entitlements.isEntitled(m.getEntitlementManager(), Entitlements.ADD_JAVA, null) ), + POWER_USER( (m,ec) -> Entitlements.isEntitled(m.getEntitlementManager(), Entitlements.ADD_JAVA, null) ), + ADDJAVA( (m,ec) -> Entitlements.isEntitled(m.getEntitlementManager(), Entitlements.ADD_JAVA, null) ), + ADD_JAVA( (m,ec) -> Entitlements.isEntitled(m.getEntitlementManager(), Entitlements.ADD_JAVA, null) ), + ; + final BiPredicate<ManagementContext,EntitlementContext> fn; + ReturnStackTraceMode(BiPredicate<ManagementContext,EntitlementContext> fn) { + this.fn = fn; + } + + public boolean checkCurrentUser(ManagementContext m) { + return m==null ? false : fn.test(m, Entitlements.getEntitlementContext()); + } } protected boolean isTooUninterestingToLogWarn(Throwable throwable2) { diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/MultiSessionAttributeAdapter.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/MultiSessionAttributeAdapter.java index 2958a6e520..173a584a5d 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/MultiSessionAttributeAdapter.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/MultiSessionAttributeAdapter.java @@ -101,11 +101,12 @@ public class MultiSessionAttributeAdapter { private static final String KEY_PREFERRED_SESSION_HANDLER_INSTANCE = "org.apache.brooklyn.server.PreferredSessionHandlerInstance"; private static final String KEY_IS_PREFERRED = "org.apache.brooklyn.server.IsPreferred"; + public final static String OAB_SERVER_CONFIG_PREFIX = "org.apache.brooklyn.server"; public final static ConfigKey<Long> MAX_SESSION_AGE = ConfigKeys.newLongConfigKey( - "org.apache.brooklyn.server.maxSessionAge", "Max session age in seconds"); + OAB_SERVER_CONFIG_PREFIX+".maxSessionAge", "Max session age in seconds"); public final static ConfigKey<Integer> MAX_INACTIVE_INTERVAL = ConfigKeys.newIntegerConfigKey( - "org.apache.brooklyn.server.maxInactiveInterval", "Max inactive interval in seconds", + OAB_SERVER_CONFIG_PREFIX+".maxInactiveInterval", "Max inactive interval in seconds", 3600); private static final Object PREFERRED_SYMBOLIC_NAME =
