[Major] Better exception handling for AccessDeniedExceptions

This commit is contained in:
Robert von Burg 2021-02-08 22:43:27 +01:00
parent f19484abc0
commit 207304932a
12 changed files with 143 additions and 113 deletions

View File

@ -52,13 +52,13 @@ import li.strolch.policy.PolicyHandler;
import li.strolch.policy.StrolchPolicy;
import li.strolch.privilege.base.AccessDeniedException;
import li.strolch.privilege.base.PrivilegeException;
import li.strolch.privilege.base.PrivilegeModelException;
import li.strolch.privilege.model.Certificate;
import li.strolch.privilege.model.PrivilegeContext;
import li.strolch.privilege.model.Restrictable;
import li.strolch.runtime.privilege.PrivilegeHandler;
import li.strolch.runtime.privilege.TransactedRestrictable;
import li.strolch.service.api.Command;
import li.strolch.utils.I18nMessage;
import li.strolch.utils.collections.MapOfMaps;
import li.strolch.utils.dbc.DBC;
import li.strolch.utils.helper.StringHelper;
@ -401,10 +401,30 @@ public abstract class AbstractTransaction implements StrolchTransaction {
private void assertQueryAllowed(StrolchQuery query) {
try {
getPrivilegeContext().validateAction(query);
} catch (PrivilegeModelException e) {
throw e;
} catch (PrivilegeException e) {
throw new StrolchAccessDeniedException(this.certificate, query, getExceptionMessage(e), e);
} catch (AccessDeniedException e) {
String username = getCertificate().getUsername();
if (getContainer().hasComponent(OperationsLog.class)) {
String realmName = getRealmName();
String queryName = query.getPrivilegeValue().equals(INTERNAL) ?
(getClass().getName() + " (INTERNAL)") :
query.getPrivilegeValue().toString();
LogMessage logMessage = new LogMessage(realmName, username,
Locator.valueOf(AGENT, PrivilegeHandler.class.getSimpleName(), query.getPrivilegeName(),
queryName), LogSeverity.Exception, LogMessageState.Information,
ResourceBundle.getBundle("strolch-agent"), "agent.query.failed.access.denied")
.value("user", username).value("query", queryName).withException(e);
OperationsLog operationsLog = getContainer().getComponent(OperationsLog.class);
operationsLog.addMessage(logMessage);
}
String queryName = query.getPrivilegeValue().equals(INTERNAL) ?
(getClass().getSimpleName() + " (INTERNAL)") :
query.getClass().getSimpleName();
I18nMessage i18n = new I18nMessage(ResourceBundle.getBundle("strolch-agent", getCertificate().getLocale()),
"agent.search.failed.access.denied").value("user", username).value("query", queryName);
throw new StrolchAccessDeniedException(this.certificate, query, i18n, e);
}
}

View File

@ -1,17 +1,26 @@
package li.strolch.search;
import static li.strolch.model.StrolchModelConstants.INTERNAL;
import static li.strolch.model.Tags.AGENT;
import java.util.Collection;
import java.util.ResourceBundle;
import java.util.stream.Stream;
import li.strolch.exception.StrolchAccessDeniedException;
import li.strolch.handler.operationslog.OperationsLog;
import li.strolch.model.Locator;
import li.strolch.model.StrolchModelConstants;
import li.strolch.model.StrolchRootElement;
import li.strolch.model.log.LogMessage;
import li.strolch.model.log.LogMessageState;
import li.strolch.model.log.LogSeverity;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.privilege.base.PrivilegeException;
import li.strolch.privilege.base.PrivilegeModelException;
import li.strolch.privilege.base.AccessDeniedException;
import li.strolch.privilege.model.Restrictable;
import li.strolch.runtime.privilege.PrivilegeHandler;
import li.strolch.utils.I18nMessage;
import li.strolch.utils.dbc.DBC;
import li.strolch.utils.helper.ExceptionHelper;
/**
* Class to perform searches on Strolch elements
@ -71,7 +80,7 @@ public abstract class StrolchSearch<T extends StrolchRootElement>
* @return this object for chaining
*/
public StrolchSearch<T> internal() {
this.privilegeValue = StrolchModelConstants.INTERNAL;
this.privilegeValue = INTERNAL;
return this;
}
@ -86,11 +95,33 @@ public abstract class StrolchSearch<T extends StrolchRootElement>
public RootElementSearchResult<T> search(StrolchTransaction tx) {
try {
tx.getPrivilegeContext().validateAction(this);
} catch (PrivilegeModelException e) {
throw e;
} catch (PrivilegeException e) {
throw new StrolchAccessDeniedException(tx.getCertificate(), this, ExceptionHelper.getExceptionMessage(e),
e);
} catch (AccessDeniedException e) {
String username = tx.getCertificate().getUsername();
if (tx.getContainer().hasComponent(OperationsLog.class)) {
String realmName = tx.getRealmName();
String searchName = this.privilegeValue.equals(INTERNAL) ?
(getClass().getName() + " (INTERNAL)") :
this.privilegeValue;
LogMessage logMessage = new LogMessage(realmName, username,
Locator.valueOf(AGENT, PrivilegeHandler.class.getSimpleName(), getPrivilegeName(), searchName),
LogSeverity.Exception, LogMessageState.Information, ResourceBundle.getBundle("strolch-agent"),
"agent.search.failed.access.denied").value("user", username).value("search", searchName)
.withException(e);
OperationsLog operationsLog = tx.getContainer().getComponent(OperationsLog.class);
operationsLog.addMessage(logMessage);
}
String searchName = this.privilegeValue.equals(INTERNAL) ?
(getClass().getSimpleName() + " (INTERNAL)") :
getClass().getSimpleName();
I18nMessage i18n = new I18nMessage(
ResourceBundle.getBundle("strolch-agent", tx.getCertificate().getLocale()),
"agent.search.failed.access.denied").value("user", username).value("search", searchName);
throw new StrolchAccessDeniedException(tx.getCertificate(), this, i18n, e);
}
// first prepare

View File

@ -1,15 +1,9 @@
package li.strolch.search;
import static li.strolch.utils.helper.ExceptionHelper.getExceptionMessage;
import java.util.Collection;
import java.util.stream.Stream;
import li.strolch.exception.StrolchAccessDeniedException;
import li.strolch.model.StrolchModelConstants;
import li.strolch.privilege.base.PrivilegeException;
import li.strolch.privilege.base.PrivilegeModelException;
import li.strolch.privilege.model.PrivilegeContext;
import li.strolch.privilege.model.Restrictable;
public class StrolchValueSearch<T> extends ValueSearch<T> implements Restrictable {
@ -47,16 +41,6 @@ public class StrolchValueSearch<T> extends ValueSearch<T> implements Restrictabl
return this;
}
protected void assertHasPrivilege(PrivilegeContext ctx) {
try {
ctx.validateAction(this);
} catch (PrivilegeModelException e) {
throw e;
} catch (PrivilegeException e) {
throw new StrolchAccessDeniedException(ctx.getCertificate(), this, getExceptionMessage(e), e);
}
}
@Override
public SearchResult<T> search(Stream<T> stream) {
return super.search(stream);

View File

@ -27,11 +27,11 @@ 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.handler.operationslog.OperationsLog;
import li.strolch.model.Locator;
import li.strolch.model.log.LogMessage;
import li.strolch.model.log.LogMessageState;
import li.strolch.model.log.LogSeverity;
import li.strolch.handler.operationslog.OperationsLog;
import li.strolch.model.Locator;
import li.strolch.privilege.base.PrivilegeException;
import li.strolch.privilege.base.PrivilegeModelException;
import li.strolch.privilege.model.Certificate;
@ -39,6 +39,7 @@ import li.strolch.privilege.model.PrivilegeContext;
import li.strolch.runtime.configuration.ComponentConfiguration;
import li.strolch.runtime.configuration.RuntimeConfiguration;
import li.strolch.runtime.privilege.PrivilegeHandler;
import li.strolch.utils.I18nMessage;
import li.strolch.utils.dbc.DBC;
/**
@ -89,28 +90,44 @@ public class DefaultServiceHandler extends StrolchComponent implements ServiceHa
long end = System.nanoTime();
String msg = "User {0}: Service {1} failed after {2} due to {3}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, username, service.getClass().getName(), formatNanoDuration(end - start),
e.getMessage());
String svcName = service.getClass().getName();
msg = MessageFormat.format(msg, username, svcName, formatNanoDuration(end - start), e.getMessage());
logger.error(msg);
if (getContainer().hasComponent(OperationsLog.class)) {
String realmName = getRealmName(argument, certificate);
LogMessage logMessage = new LogMessage(realmName, username,
Locator.valueOf(AGENT, PrivilegeHandler.class.getSimpleName(), service.getPrivilegeName(),
svcName), LogSeverity.Exception, LogMessageState.Information,
ResourceBundle.getBundle("strolch-agent"), "agent.service.failed.access.denied")
.value("user", username).value("service", svcName).withException(e);
OperationsLog operationsLog = getContainer().getComponent(OperationsLog.class);
operationsLog.addMessage(logMessage);
}
I18nMessage i18n = new I18nMessage(ResourceBundle.getBundle("strolch-agent", certificate.getLocale()),
"agent.service.failed.access.denied").value("user", username)
.value("service", service.getClass().getSimpleName());
if (!this.throwOnPrivilegeFail && service instanceof AbstractService) {
logger.error(e.getMessage(), e);
AbstractService<?, ?> abstractService = (AbstractService<?, ?>) service;
@SuppressWarnings("unchecked")
U arg = (U) abstractService.getResultInstance();
arg.setState(e instanceof PrivilegeModelException ?
U result = (U) abstractService.getResultInstance();
result.setState(e instanceof PrivilegeModelException ?
ServiceResultState.EXCEPTION :
ServiceResultState.ACCESS_DENIED);
arg.setMessage(e.getMessage());
arg.setThrowable(e);
return arg;
result.setMessage(i18n.getMessage());
result.i18n(i18n);
result.setThrowable(e);
return result;
}
if (e instanceof PrivilegeModelException)
throw new StrolchException(e.getMessage(), e);
else
throw new StrolchAccessDeniedException(certificate, service, e.getMessage(), e);
throw new StrolchException(i18n.getMessage(), e);
throw new StrolchAccessDeniedException(certificate, service, i18n, e);
}
try {
@ -220,12 +237,13 @@ public class DefaultServiceHandler extends StrolchComponent implements ServiceHa
ResourceBundle bundle = ResourceBundle.getBundle("strolch-agent");
if (throwable == null) {
logMessage = new LogMessage(realmName, username, Locator.valueOf(AGENT, svcName, getUniqueId()),
LogSeverity.Exception, LogMessageState.Information, bundle, "agent.service.failed").value("service", svcName)
.value("reason", reason);
LogSeverity.Exception, LogMessageState.Information, bundle, "agent.service.failed")
.value("service", svcName).value("reason", reason);
} else {
logMessage = new LogMessage(realmName, username, Locator.valueOf(AGENT, svcName, getUniqueId()),
LogSeverity.Exception, LogMessageState.Information, bundle, "agent.service.failed.ex").withException(throwable)
.value("service", svcName).value("reason", reason).value("exception", throwable);
LogSeverity.Exception, LogMessageState.Information, bundle, "agent.service.failed.ex")
.withException(throwable).value("service", svcName).value("reason", reason)
.value("exception", throwable);
}
OperationsLog operationsLog = getContainer().getComponent(OperationsLog.class);

View File

@ -5,4 +5,7 @@ agent.observers.update.failed=Observer handler action '{type}' failed due to: {r
agent.service.failed=Service {service} has failed due to {reason}
agent.service.failed.ex=Service {service} has failed due to {reason} due to: {exception}
strolchjob.failed=Execution of Job {jobName} has failed due to {reason}
operationsLog.persist.failed=Failed to persist OperationsLog due to: {reason}
operationsLog.persist.failed=Failed to persist OperationsLog due to: {reason}
agent.query.failed.access.denied=User {user} may not perform query {query}
agent.search.failed.access.denied=User {user} may not perform search {search}
agent.service.failed.access.denied=User {user} may not perform service {service}

View File

@ -17,37 +17,23 @@ package li.strolch.exception;
import li.strolch.privilege.model.Certificate;
import li.strolch.privilege.model.Restrictable;
import li.strolch.utils.I18nMessage;
/**
* @author Robert von Burg <eitch@eitchnet.ch>
*/
public class StrolchAccessDeniedException extends StrolchException {
private Certificate certificate;
private Restrictable restrictable;
private final Certificate certificate;
private final Restrictable restrictable;
private final I18nMessage i18n;
/**
* @param certificate
* @param restrictable
* @param message
* @param cause
*/
public StrolchAccessDeniedException(Certificate certificate, Restrictable restrictable, String message,
public StrolchAccessDeniedException(Certificate certificate, Restrictable restrictable, I18nMessage i18n,
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);
super(i18n.getMessage(), cause);
this.certificate = certificate;
this.restrictable = restrictable;
this.i18n = i18n;
}
public Certificate getCertificate() {
@ -57,4 +43,8 @@ public class StrolchAccessDeniedException extends StrolchException {
public Restrictable getRestrictable() {
return restrictable;
}
public I18nMessage getI18n() {
return this.i18n;
}
}

View File

@ -15,6 +15,7 @@ public class I18nMessageToJsonVisitor implements I18nMessageVisitor<JsonObject>
json.addProperty(Tags.Json.KEY, message.getKey());
json.addProperty(Tags.Json.MESSAGE, message.getMessage());
json.addProperty(Tags.Json.EXCEPTION, message.getStackTrace());
Properties values = message.getValues();
if (!values.isEmpty()) {

View File

@ -10,7 +10,6 @@ import com.google.gson.JsonObject;
import li.strolch.model.Locator;
import li.strolch.model.Tags.Json;
import li.strolch.utils.I18nMessage;
import li.strolch.utils.helper.ExceptionHelper;
import li.strolch.utils.helper.StringHelper;
import li.strolch.utils.iso8601.ISO8601;
@ -23,7 +22,6 @@ public class LogMessage extends I18nMessage {
private final Locator locator;
private final LogSeverity severity;
private LogMessageState state;
private String stackTrace;
public LogMessage(String realm, String username, Locator locator, LogSeverity severity, LogMessageState state,
I18nMessage i18nMessage) {
@ -100,22 +98,19 @@ public class LogMessage extends I18nMessage {
}
public LogMessage withException(Throwable t) {
this.stackTrace = ExceptionHelper.formatException(t);
super.withException(t);
return this;
}
public String getStackTrace() {
return this.stackTrace;
}
@Override
public LogMessage value(String key, Object value) {
super.value(key, value);
return this;
}
@Override
public LogMessage value(String key, Throwable e) {
super.value(key, ExceptionHelper.getExceptionMessageWithCauses(e));
super.value(key, e);
return this;
}

View File

@ -30,6 +30,7 @@ public class StrolchRestfulConstants {
public static final String STROLCH_AUTHORIZATION_EXPIRATION_DATE = "strolch.authorization.expirationDate"; //$NON-NLS-1$
public static final String MSG = "msg";
public static final String I18N = "i18n";
public static final String EXCEPTION_MSG = "exceptionMsg";
public static final String DATA = Tags.Json.DATA;
public static final String LAST_OFFSET = "lastOffset";

View File

@ -25,7 +25,6 @@ import java.text.MessageFormat;
import li.strolch.exception.StrolchAccessDeniedException;
import li.strolch.exception.StrolchNotAuthenticatedException;
import li.strolch.privilege.model.Restrictable;
import li.strolch.rest.helper.ResponseUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -45,24 +44,10 @@ public class StrolchRestfulExceptionMapper implements ExceptionMapper<Exception>
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);
// use the message
sb.append(e.getMessage());
} else {
sb.append(restrictable.getPrivilegeName());
sb.append(" - ");
sb.append(restrictable.getPrivilegeValue());
}
return ResponseUtil.toResponse(Status.FORBIDDEN, e.getI18n());
}
return Response.status(Status.FORBIDDEN).entity(sb.toString()).type(MediaType.TEXT_PLAIN).build();
} else if (ex instanceof StrolchNotAuthenticatedException) {
if (ex instanceof StrolchNotAuthenticatedException) {
StrolchNotAuthenticatedException e = (StrolchNotAuthenticatedException) ex;
logger.error("User tried to access resource, but was not authenticated: " + ex.getMessage());
return Response.status(Status.UNAUTHORIZED).entity(e.getMessage()).type(MediaType.TEXT_PLAIN).build();

View File

@ -15,6 +15,7 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import li.strolch.exception.StrolchElementNotFoundException;
import li.strolch.model.i18n.I18nMessageToJsonVisitor;
import li.strolch.privilege.base.AccessDeniedException;
import li.strolch.privilege.base.PrivilegeException;
import li.strolch.privilege.base.PrivilegeModelException;
@ -32,27 +33,22 @@ public class ResponseUtil {
public static Response toResponse() {
JsonObject response = new JsonObject();
response.addProperty(MSG, StringHelper.DASH);
String json = new Gson().toJson(response);
return Response.ok(json, MediaType.APPLICATION_JSON).build();
}
public static Response toResponse(I18nMessage msg) {
public static Response toResponse(Status status, I18nMessage msg) {
JsonObject response = new JsonObject();
response.addProperty(MSG, msg.getMessage());
response.add(I18N, msg.accept(new I18nMessageToJsonVisitor()));
String json = new Gson().toJson(response);
return Response.serverError().entity(json).type(MediaType.APPLICATION_JSON).build();
return Response.status(status).entity(json).type(MediaType.APPLICATION_JSON).build();
}
public static Response toResponse(String errorMsg) {
JsonObject response = new JsonObject();
response.addProperty(MSG, errorMsg);
String json = new Gson().toJson(response);
return Response.serverError().entity(json).type(MediaType.APPLICATION_JSON).build();
}
@ -60,9 +56,7 @@ public class ResponseUtil {
JsonObject response = new JsonObject();
response.addProperty(MSG, StringHelper.DASH);
response.addProperty(prop, value);
String json = new Gson().toJson(response);
return Response.ok(json, MediaType.APPLICATION_JSON).build();
}
@ -71,9 +65,7 @@ public class ResponseUtil {
response.addProperty(MSG, StringHelper.DASH);
response.addProperty(prop1, value1);
response.addProperty(prop2, value2);
String json = new Gson().toJson(response);
return Response.ok(json, MediaType.APPLICATION_JSON).build();
}
@ -82,9 +74,7 @@ public class ResponseUtil {
response.addProperty(MSG, StringHelper.DASH);
response.addProperty(prop1, value1);
response.add(DATA, data);
String json = new Gson().toJson(response);
return Response.ok(json, MediaType.APPLICATION_JSON).build();
}
@ -96,9 +86,7 @@ public class ResponseUtil {
JsonObject response = new JsonObject();
response.addProperty(MSG, StringHelper.DASH);
response.add(member, jsonElement);
String json = new Gson().toJson(response);
return Response.ok(json, MediaType.APPLICATION_JSON).build();
}
@ -125,7 +113,6 @@ public class ResponseUtil {
response.add(member, arrayJ);
String json = new Gson().toJson(response);
return Response.ok(json, MediaType.APPLICATION_JSON).build();
}
@ -136,7 +123,6 @@ public class ResponseUtil {
}
public static Response toResponse(ServiceResult svcResult) {
Throwable t = svcResult.getThrowable();
JsonObject response = svcResult.toJson();
String json = new Gson().toJson(response);
@ -186,7 +172,6 @@ public class ResponseUtil {
JsonObject response = new JsonObject();
response.addProperty(MSG, getExceptionMessageWithCauses(t, false));
String json = new Gson().toJson(response);
return Response.status(status).entity(json).type(MediaType.APPLICATION_JSON).build();
}

View File

@ -2,6 +2,8 @@ package li.strolch.utils;
import static java.util.Collections.emptySet;
import static li.strolch.utils.collections.SynchronizedCollections.synchronizedMapOfSets;
import static li.strolch.utils.helper.ExceptionHelper.formatException;
import static li.strolch.utils.helper.ExceptionHelper.getExceptionMessageWithCauses;
import static li.strolch.utils.helper.StringHelper.EMPTY;
import static li.strolch.utils.helper.StringHelper.isEmpty;
@ -33,6 +35,7 @@ public class I18nMessage {
private final Properties values;
private final ResourceBundle bundle;
private String message;
protected String stackTrace;
public I18nMessage(ResourceBundle bundle, String key) {
DBC.INTERIM.assertNotNull("bundle may not be null!", bundle);
@ -137,6 +140,20 @@ public class I18nMessage {
return this;
}
public I18nMessage value(String key, Throwable e) {
value(key, getExceptionMessageWithCauses(e));
return this;
}
public I18nMessage withException(Throwable t) {
this.stackTrace = formatException(t);
return this;
}
public String getStackTrace() {
return this.stackTrace;
}
public String formatMessage() {
if (this.message != null)
return this.message;