From e39df60247cf674b7ef7c4f752e1794f8404995d Mon Sep 17 00:00:00 2001 From: Reto Breitenmoser Date: Sat, 17 Jan 2015 10:30:45 +0100 Subject: [PATCH 1/3] [Minor] added cache mode setting for rest http header (defaults to no-cache) --- .../strolch/rest/RestfulStrolchComponent.java | 7 +++ .../rest/filters/HttpCacheResponseFilter.java | 44 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 li.strolch.rest/src/main/java/li/strolch/rest/filters/HttpCacheResponseFilter.java diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/RestfulStrolchComponent.java b/li.strolch.rest/src/main/java/li/strolch/rest/RestfulStrolchComponent.java index d994503dd..65f0115b6 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/RestfulStrolchComponent.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/RestfulStrolchComponent.java @@ -20,6 +20,7 @@ import java.text.MessageFormat; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.StrolchComponent; import li.strolch.rest.filters.AccessControlResponseFilter; +import li.strolch.rest.filters.HttpCacheResponseFilter; import li.strolch.runtime.configuration.ComponentConfiguration; import org.glassfish.jersey.server.ServerProperties; @@ -35,6 +36,7 @@ public class RestfulStrolchComponent extends StrolchComponent { private static final String PARAM_CORS_ORIGIN = "corsOrigin"; //$NON-NLS-1$ private static final String PARAM_REST_LOGGING = "restLogging"; //$NON-NLS-1$ private static final String PARAM_REST_LOGGING_ENTITY = "restLoggingEntity"; //$NON-NLS-1$ + private static final String PARAM_HTTP_CACHE_MODE = "httpCacheMode"; //$NON-NLS-1$ /** * Allowed values: @@ -69,6 +71,7 @@ public class RestfulStrolchComponent extends StrolchComponent { private String corsOrigin; private boolean restLogging; private boolean restLoggingEntity; + private String cacheMode; /** * @param container @@ -140,6 +143,10 @@ public class RestfulStrolchComponent extends StrolchComponent { String msg = "Set restLogging={0} with logEntities={1} restTracing={2} with threshold={3}"; //$NON-NLS-1$ logger.info(MessageFormat.format(msg, this.restLogging, this.restLoggingEntity, this.restTracing, this.restTracingThreshold)); + + // set http cache mode + this.cacheMode = configuration.getString(PARAM_HTTP_CACHE_MODE, HttpCacheResponseFilter.NO_CACHE); + logger.info("HTTP header cache mode is set to {}",cacheMode); super.initialize(configuration); } diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/filters/HttpCacheResponseFilter.java b/li.strolch.rest/src/main/java/li/strolch/rest/filters/HttpCacheResponseFilter.java new file mode 100644 index 000000000..65acbb67d --- /dev/null +++ b/li.strolch.rest/src/main/java/li/strolch/rest/filters/HttpCacheResponseFilter.java @@ -0,0 +1,44 @@ +package li.strolch.rest.filters; + +import java.io.IOException; + +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.Provider; + +@Provider +@Priority(Priorities.HEADER_DECORATOR) +public class HttpCacheResponseFilter implements ContainerResponseFilter { + + public static final String NO_CACHE = "no-cache"; //$NON-NLS-1$ + + private static String cacheMode = NO_CACHE; + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) + throws IOException { + + MultivaluedMap headers = responseContext.getHeaders(); + headers.add(HttpHeaders.CACHE_CONTROL, cacheMode); + } + + /** + * @return the cacheMode + */ + public static String getCacheMode() { + return cacheMode; + } + + /** + * @param cacheMode + * the cacheMode to set + */ + public static void setCacheMode(String cacheMode) { + HttpCacheResponseFilter.cacheMode = cacheMode; + } +} \ No newline at end of file From 9505ab355ccedc6fb419c397a1c2e1e281364976 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 18 Jan 2015 18:37:04 +0100 Subject: [PATCH 2/3] [New] Added new StrolchAccessDeniedException for use in ServiceHandler Now if you add the DefaultServiceHandler property 'throwOnPrivilegeFail' and set it to true, then the service handler will throw a StrolchAccessDeniedException which in combination with the StrolchRestfulExceptionMapper allows rest services to quickly determine if the error was because the user does not have access to the requested resource. Same goes for performing queries in AbstractTransaction only there instead of throwing a privilege exception, we now also throw a StrolchAccessDeniedException --- .../persistence/api/AbstractTransaction.java | 10 ++- .../service/api/DefaultServiceHandler.java | 57 +++++++++++++---- .../StrolchAccessDeniedException.java | 64 +++++++++++++++++++ .../model/parameter/AbstractParameter.java | 4 +- .../rest/StrolchRestfulExceptionMapper.java | 28 +++++++- 5 files changed, 146 insertions(+), 17 deletions(-) create mode 100644 li.strolch.model/src/main/java/li/strolch/exception/StrolchAccessDeniedException.java diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/api/AbstractTransaction.java b/li.strolch.agent/src/main/java/li/strolch/persistence/api/AbstractTransaction.java index 1f348b8c5..e606599ba 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/api/AbstractTransaction.java +++ b/li.strolch.agent/src/main/java/li/strolch/persistence/api/AbstractTransaction.java @@ -32,6 +32,7 @@ import li.strolch.agent.impl.AuditingAuditMapFacade; import li.strolch.agent.impl.AuditingOrderMap; import li.strolch.agent.impl.AuditingResourceMap; import li.strolch.agent.impl.InternalStrolchRealm; +import li.strolch.exception.StrolchAccessDeniedException; import li.strolch.exception.StrolchException; import li.strolch.model.GroupedParameterizedElement; import li.strolch.model.Locator; @@ -65,6 +66,7 @@ import li.strolch.service.api.Command; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeContext; import ch.eitchnet.utils.dbc.DBC; @@ -237,8 +239,12 @@ public abstract class AbstractTransaction implements StrolchTransaction { } private void assertQueryAllowed(StrolchQuery query) { - PrivilegeContext privilegeContext = this.privilegeHandler.getPrivilegeContext(this.certificate); - privilegeContext.validateAction(query); + try { + PrivilegeContext privilegeContext = this.privilegeHandler.getPrivilegeContext(this.certificate); + privilegeContext.validateAction(query); + } catch (PrivilegeException e) { + throw new StrolchAccessDeniedException(this.certificate, query, e.getMessage(), e); + } } @Override diff --git a/li.strolch.agent/src/main/java/li/strolch/service/api/DefaultServiceHandler.java b/li.strolch.agent/src/main/java/li/strolch/service/api/DefaultServiceHandler.java index 4990f1ba0..7f387254a 100644 --- a/li.strolch.agent/src/main/java/li/strolch/service/api/DefaultServiceHandler.java +++ b/li.strolch.agent/src/main/java/li/strolch/service/api/DefaultServiceHandler.java @@ -19,6 +19,7 @@ import java.text.MessageFormat; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.StrolchComponent; +import li.strolch.exception.StrolchAccessDeniedException; import li.strolch.exception.StrolchException; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.runtime.configuration.RuntimeConfiguration; @@ -33,8 +34,10 @@ import ch.eitchnet.utils.helper.StringHelper; */ public class DefaultServiceHandler extends StrolchComponent implements ServiceHandler { + private static final String PARAM_THROW_ON_PRIVILEGE_FAIL = "throwOnPrivilegeFail"; private RuntimeConfiguration runtimeConfiguration; private PrivilegeHandler privilegeHandler; + private boolean throwOnPrivilegeFail; /** * @param container @@ -48,6 +51,7 @@ public class DefaultServiceHandler extends StrolchComponent implements ServiceHa public void initialize(ComponentConfiguration configuration) { this.privilegeHandler = getContainer().getPrivilegeHandler(); this.runtimeConfiguration = configuration.getRuntimeConfiguration(); + this.throwOnPrivilegeFail = configuration.getBoolean(PARAM_THROW_ON_PRIVILEGE_FAIL, Boolean.FALSE); super.initialize(configuration); } @@ -84,7 +88,7 @@ public class DefaultServiceHandler extends StrolchComponent implements ServiceHa StringHelper.formatNanoDuration(end - start), e.getMessage()); logger.error(msg, e); - if (service instanceof AbstractService) { + if (!this.throwOnPrivilegeFail && service instanceof AbstractService) { AbstractService abstractService = (AbstractService) service; @SuppressWarnings("unchecked") U arg = (U) abstractService.getResultInstance(); @@ -93,7 +97,7 @@ public class DefaultServiceHandler extends StrolchComponent implements ServiceHa arg.setThrowable(e); return arg; } else { - throw new StrolchException(e.getMessage()); + throw new StrolchAccessDeniedException(certificate, service, e.getMessage(), e); } } @@ -113,16 +117,7 @@ public class DefaultServiceHandler extends StrolchComponent implements ServiceHa } // log the result - long end = System.nanoTime(); - String msg = "User {0}: Service {1} took {2}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, username, service.getClass().getName(), - StringHelper.formatNanoDuration(end - start)); - if (serviceResult.getState() == ServiceResultState.SUCCESS) - logger.info(msg); - else if (serviceResult.getState() == ServiceResultState.WARNING) - logger.warn(msg); - else if (serviceResult.getState() == ServiceResultState.FAILED) - logger.error(msg); + logResult(service, start, username, serviceResult); return serviceResult; @@ -135,4 +130,42 @@ public class DefaultServiceHandler extends StrolchComponent implements ServiceHa throw new StrolchException(msg, e); } } + + private void logResult(Service service, long start, String username, ServiceResult serviceResult) { + + long end = System.nanoTime(); + + String msg = "User {0}: Service {1} took {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, username, service.getClass().getName(), + StringHelper.formatNanoDuration(end - start)); + + if (serviceResult.getState() == ServiceResultState.SUCCESS) { + logger.info(msg); + } else if (serviceResult.getState() == ServiceResultState.WARNING) { + + msg = ServiceResultState.WARNING + " " + msg; + logger.warn(msg); + + if (StringHelper.isNotEmpty(serviceResult.getMessage()) && serviceResult.getThrowable() != null) { + logger.warn("Reason: " + serviceResult.getMessage(), serviceResult.getThrowable()); + } else if (StringHelper.isNotEmpty(serviceResult.getMessage())) { + logger.warn("Reason: " + serviceResult.getMessage()); + } else if (serviceResult.getThrowable() != null) { + logger.warn("Reason: " + serviceResult.getThrowable().getMessage(), serviceResult.getThrowable()); + } + + } else if (serviceResult.getState() == ServiceResultState.FAILED) { + + msg = ServiceResultState.FAILED + " " + msg; + logger.error(msg); + + if (StringHelper.isNotEmpty(serviceResult.getMessage()) && serviceResult.getThrowable() != null) { + logger.error("Reason: " + serviceResult.getMessage(), serviceResult.getThrowable()); + } else if (StringHelper.isNotEmpty(serviceResult.getMessage())) { + logger.error("Reason: " + serviceResult.getMessage()); + } else if (serviceResult.getThrowable() != null) { + logger.error("Reason: " + serviceResult.getThrowable().getMessage(), serviceResult.getThrowable()); + } + } + } } diff --git a/li.strolch.model/src/main/java/li/strolch/exception/StrolchAccessDeniedException.java b/li.strolch.model/src/main/java/li/strolch/exception/StrolchAccessDeniedException.java new file mode 100644 index 000000000..6f882c4c0 --- /dev/null +++ b/li.strolch.model/src/main/java/li/strolch/exception/StrolchAccessDeniedException.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.exception; + +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.Restrictable; + +/** + * @author Robert von Burg + */ +public class StrolchAccessDeniedException extends StrolchException { + + private static final long serialVersionUID = 1L; + + private Certificate certificate; + private Restrictable restrictable; + + /** + * + * @param certificate + * @param restrictable + * @param message + * @param cause + */ + public StrolchAccessDeniedException(Certificate certificate, Restrictable restrictable, String message, + Throwable cause) { + super(message, cause); + this.certificate = certificate; + this.restrictable = restrictable; + } + + /** + * + * @param certificate + * @param restrictable + * @param message + */ + public StrolchAccessDeniedException(Certificate certificate, Restrictable restrictable, String message) { + super(message); + this.certificate = certificate; + this.restrictable = restrictable; + } + + public Certificate getCertificate() { + return certificate; + } + + public Restrictable getRestrictable() { + return restrictable; + } +} diff --git a/li.strolch.model/src/main/java/li/strolch/model/parameter/AbstractParameter.java b/li.strolch.model/src/main/java/li/strolch/model/parameter/AbstractParameter.java index d749fc55f..04110f754 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/parameter/AbstractParameter.java +++ b/li.strolch.model/src/main/java/li/strolch/model/parameter/AbstractParameter.java @@ -227,8 +227,8 @@ public abstract class AbstractParameter extends AbstractStrolchElement implem */ protected void validateValue(T value) throws StrolchException { if (value == null) { - String msg = "{0} Parameter {1} may not have a null value!"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, getType(), getId()); + String msg = "Can not set null value on Parameter {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, getLocator()); throw new StrolchException(msg); } } diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/StrolchRestfulExceptionMapper.java b/li.strolch.rest/src/main/java/li/strolch/rest/StrolchRestfulExceptionMapper.java index 02c4b9e17..f4f52a07b 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/StrolchRestfulExceptionMapper.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/StrolchRestfulExceptionMapper.java @@ -4,12 +4,16 @@ import java.text.MessageFormat; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; +import li.strolch.exception.StrolchAccessDeniedException; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.utils.helper.StringHelper; @Provider @@ -19,7 +23,29 @@ public class StrolchRestfulExceptionMapper implements ExceptionMapper @Override public Response toResponse(Exception ex) { + logger.error(MessageFormat.format("Handling exception {0}", ex.getClass()), ex); //$NON-NLS-1$ - return Response.status(500).entity(StringHelper.formatExceptionMessage(ex)).type(MediaType.TEXT_PLAIN).build(); + + if (ex instanceof StrolchAccessDeniedException) { + StrolchAccessDeniedException e = (StrolchAccessDeniedException) ex; + StringBuilder sb = new StringBuilder(); + sb.append("User "); + sb.append(e.getCertificate().getUsername()); + sb.append(" does not have access to "); + Restrictable restrictable = e.getRestrictable(); + if (restrictable == null) { + sb.append(StringHelper.NULL); + } else { + sb.append(restrictable.getPrivilegeName()); + sb.append(" - "); + sb.append(restrictable.getPrivilegeValue()); + } + + return Response.status(Status.FORBIDDEN).entity(sb.toString()).type(MediaType.TEXT_PLAIN).build(); + } + + String exceptionMessage = StringHelper.formatExceptionMessage(ex); + return Response.status(Status.INTERNAL_SERVER_ERROR).entity(exceptionMessage).type(MediaType.TEXT_PLAIN) + .build(); } } \ No newline at end of file From 15ae2f9702e0b28c2129846a45c22eef22ffc651 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 18 Jan 2015 19:17:16 +0100 Subject: [PATCH 3/3] [Minor] don't log multiple times the exception on failed service --- .../li/strolch/persistence/api/AbstractTransaction.java | 2 -- .../main/java/li/strolch/service/api/AbstractService.java | 7 +------ .../java/li/strolch/service/api/DefaultServiceHandler.java | 6 ++++-- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/api/AbstractTransaction.java b/li.strolch.agent/src/main/java/li/strolch/persistence/api/AbstractTransaction.java index e606599ba..2670445e1 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/api/AbstractTransaction.java +++ b/li.strolch.agent/src/main/java/li/strolch/persistence/api/AbstractTransaction.java @@ -569,7 +569,6 @@ public abstract class AbstractTransaction implements StrolchTransaction { if (closeDuration >= 100000000L) { sb.append(", close="); //$NON-NLS-1$ sb.append(StringHelper.formatNanoDuration(closeDuration)); - logger.info(sb.toString()); } logger.error(sb.toString()); } @@ -600,7 +599,6 @@ public abstract class AbstractTransaction implements StrolchTransaction { if (closeDuration >= 100000000L) { sb.append(", close="); //$NON-NLS-1$ sb.append(StringHelper.formatNanoDuration(closeDuration)); - logger.info(sb.toString()); } String msg = "Strolch Transaction for realm {0} failed due to {1}\n{2}"; //$NON-NLS-1$ diff --git a/li.strolch.agent/src/main/java/li/strolch/service/api/AbstractService.java b/li.strolch.agent/src/main/java/li/strolch/service/api/AbstractService.java index bea0da28d..91a118a4d 100644 --- a/li.strolch.agent/src/main/java/li/strolch/service/api/AbstractService.java +++ b/li.strolch.agent/src/main/java/li/strolch/service/api/AbstractService.java @@ -146,14 +146,9 @@ public abstract class AbstractService abstractService = (AbstractService) service; @SuppressWarnings("unchecked") U arg = (U) abstractService.getResultInstance(); @@ -126,7 +128,7 @@ public class DefaultServiceHandler extends StrolchComponent implements ServiceHa String msg = "User {0}: Service failed {1} after {2} due to {3}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, username, service.getClass().getName(), StringHelper.formatNanoDuration(end - start), e.getMessage()); - logger.error(msg, e); + logger.error(msg); throw new StrolchException(msg, e); } }