diff --git a/ch.eitchnet.privilege b/ch.eitchnet.privilege
index 6a6286433..5dc94514e 160000
--- a/ch.eitchnet.privilege
+++ b/ch.eitchnet.privilege
@@ -1 +1 @@
-Subproject commit 6a62864331d93d180d4382706e9b30b8ed6cab6a
+Subproject commit 5dc94514e13d142de8e2532b3bec18b28c7855dd
diff --git a/ch.eitchnet.utils b/ch.eitchnet.utils
index f59f4c5c0..46c3db291 160000
--- a/ch.eitchnet.utils
+++ b/ch.eitchnet.utils
@@ -1 +1 @@
-Subproject commit f59f4c5c0fd4201e9e5a81ed26f853e99226725c
+Subproject commit 46c3db2913efaf4dd287c4feeb2548d85a6ac1d4
diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchComponent.java b/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchComponent.java
index 79abe678e..1ed0c109e 100644
--- a/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchComponent.java
+++ b/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchComponent.java
@@ -20,11 +20,33 @@ import java.io.InputStream;
import java.text.MessageFormat;
import java.util.Properties;
-import li.strolch.runtime.configuration.ComponentConfiguration;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import ch.eitchnet.privilege.base.PrivilegeException;
+import ch.eitchnet.privilege.handler.SystemUserAction;
+import li.strolch.runtime.StrolchConstants;
+import li.strolch.runtime.configuration.ComponentConfiguration;
+import li.strolch.runtime.privilege.RunRunnable;
+
+/**
+ *
+ * A {@link StrolchComponent} is a configurable extension to Strolch. Every major feature should be implemented as a
+ * {@link StrolchComponent} so that they can be easily added or removed from a Strolch runtime.
+ *
+ *
+ *
+ * A {@link StrolchComponent} has access to the container and can perform different operations. They can be passive or
+ * active and their life cycle is bound to the container's life cycle
+ *
+ *
+ *
+ * A {@link StrolchComponent} is registered in the Strolch configuration file and can have different configuration
+ * depending on the container's runtime environment
+ *
+ *
+ * @author Robert von Burg
+ */
public class StrolchComponent {
public static final String COMPONENT_VERSION_PROPERTIES = "/componentVersion.properties"; //$NON-NLS-1$
@@ -35,6 +57,15 @@ public class StrolchComponent {
private ComponentVersion version;
private ComponentConfiguration configuration;
+ /**
+ * Constructor which takes a reference to the container and the component's name under which it can be retrieved at
+ * runtime (although one mostly retrieves the component by interface class for automatic casting)
+ *
+ * @param container
+ * the container
+ * @param componentName
+ * the component name
+ */
public StrolchComponent(ComponentContainer container, String componentName) {
this.container = container;
this.componentName = componentName;
@@ -48,18 +79,37 @@ public class StrolchComponent {
return this.componentName;
}
+ /**
+ * Returns the current component's state
+ *
+ * @return the component's current state
+ */
public ComponentState getState() {
return this.state;
}
+ /**
+ * Returns the reference to the container for sub classes
+ *
+ * @return the reference to the container
+ */
protected ComponentContainer getContainer() {
return this.container;
}
+ /**
+ * The components current configuration dependent on the environment which is loaded
+ *
+ * @return the component's configuration
+ */
protected ComponentConfiguration getConfiguration() {
return this.configuration;
}
+ /**
+ * Can be used by sub classes to assert that the component is started and thus ready to use, before any component
+ * methods are used
+ */
protected void assertStarted() {
if (getState() != ComponentState.STARTED) {
String msg = "Component {0} is not yet started!"; //$NON-NLS-1$
@@ -67,6 +117,10 @@ public class StrolchComponent {
}
}
+ /**
+ * Can be used by sub classes to assert that the entire container is started and thus ready to use, before any
+ * component methods are used
+ */
protected void assertContainerStarted() {
if (this.container.getState() != ComponentState.STARTED) {
String msg = "Container is not yet started!"; //$NON-NLS-1$
@@ -74,27 +128,109 @@ public class StrolchComponent {
}
}
+ /**
+ * Life cycle step setup. This is a very early step in the container's startup phase.
+ *
+ * @param configuration
+ */
public void setup(ComponentConfiguration configuration) {
this.state = this.state.validateStateChange(ComponentState.SETUP);
}
+ /**
+ * Life cycle step initialize. Here you would typically read configuration values
+ *
+ * @param configuration
+ * @throws Exception
+ */
public void initialize(ComponentConfiguration configuration) throws Exception {
this.configuration = configuration;
this.state = this.state.validateStateChange(ComponentState.INITIALIZED);
}
+ /**
+ * Life cycle step start. This is the last step of startup and is where threads and connections etc. would be
+ * prepared. Can also be called after stop, to restart the component.
+ *
+ * @throws Exception
+ */
public void start() throws Exception {
this.state = this.state.validateStateChange(ComponentState.STARTED);
}
+ /**
+ * Life cycle step stop. This is the first step in the tearing down of the container. Stop all active threads and
+ * connections here. After stop is called, another start might also be called to restart the component.
+ *
+ * @throws Exception
+ */
public void stop() throws Exception {
this.state = this.state.validateStateChange(ComponentState.STOPPED);
}
+ /**
+ * Life cycle step destroy. This is the last step in the tearing down of the container. Here you would release
+ * remaining resources and the component can not be started anymore afterwards
+ *
+ * @throws Exception
+ */
public void destroy() throws Exception {
this.state = this.state.validateStateChange(ComponentState.DESTROYED);
}
+ /**
+ * Performs the given {@link SystemUserAction} as a system user with the given username. Returns the action for
+ * chaining calls
+ *
+ * @param username
+ * the name of the system user to perform the action as
+ * @param action
+ * the action to perform
+ *
+ * @return the action performed for chaining calls
+ *
+ * @throws PrivilegeException
+ */
+ protected V runAs(String username, V action) throws PrivilegeException {
+ return this.container.getPrivilegeHandler().runAsSystem(username, action);
+ }
+
+ /**
+ * Performs the given {@link SystemUserAction} as the privileged system user
+ * {@link StrolchConstants#PRIVILEGED_SYSTEM_USER}. Returns the action for chaining calls
+ *
+ * @param action
+ * the action to perform
+ *
+ * @return the action performed for chaining calls
+ *
+ * @throws PrivilegeException
+ */
+ protected V runPrivileged(V action) throws PrivilegeException {
+ return this.container.getPrivilegeHandler().runAsSystem(StrolchConstants.PRIVILEGED_SYSTEM_USER, action);
+ }
+
+ /**
+ * Performs the given {@link SystemUserAction} as the privileged system user
+ * {@link StrolchConstants#PRIVILEGED_SYSTEM_USER}. Returns the action for chaining calls
+ *
+ * @param action
+ * the action to perform
+ *
+ * @throws PrivilegeException
+ */
+ protected T runPrivileged(RunRunnable.Runnable action) throws PrivilegeException {
+ return this.container.getPrivilegeHandler()
+ .runAsSystem(StrolchConstants.PRIVILEGED_SYSTEM_USER, new RunRunnable<>(action)).getResult();
+ }
+
+ /**
+ * Returns the version of this component. The version should be stored in the file
+ * {@link #COMPONENT_VERSION_PROPERTIES}. See {@link ComponentVersion} for more information
+ *
+ * @return the component's version.
+ * @throws IOException
+ */
public ComponentVersion getVersion() throws IOException {
if (this.version == null) {
try (InputStream stream = getClass().getResourceAsStream(COMPONENT_VERSION_PROPERTIES)) {
diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/DefaultStrolchPrivilegeHandler.java b/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/DefaultStrolchPrivilegeHandler.java
index fd30c1de5..e09fd6747 100644
--- a/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/DefaultStrolchPrivilegeHandler.java
+++ b/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/DefaultStrolchPrivilegeHandler.java
@@ -95,6 +95,17 @@ public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements
try (FileInputStream inputStream = new FileInputStream(privilegeXmlFile)) {
XmlHelper.parseDocument(inputStream, xmlHandler);
+ Map parameterMap = containerModel.getParameterMap();
+
+ // set sessions data path
+ if (Boolean.valueOf(
+ parameterMap.get(ch.eitchnet.privilege.handler.PrivilegeHandler.PARAM_PERSIST_SESSIONS))) {
+ String sessionsPath = new File(configuration.getRuntimeConfiguration().getDataPath(),
+ "sessions.dat").getAbsolutePath();
+ parameterMap.put(ch.eitchnet.privilege.handler.PrivilegeHandler.PARAM_PERSIST_SESSIONS_PATH,
+ sessionsPath);
+ }
+
// set base path
if (containerModel.getPersistenceHandlerClassName().equals(XmlPersistenceHandler.class.getName())) {
Map xmlParams = containerModel.getPersistenceHandlerParameterMap();
@@ -144,7 +155,6 @@ public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements
@Override
public boolean invalidateSession(Certificate certificate) {
- assertContainerStarted();
boolean invalidateSession = this.privilegeHandler.invalidateSession(certificate);
StrolchRealm realm = getContainer().getRealm(certificate);
try (StrolchTransaction tx = realm.openTx(certificate, StrolchPrivilegeConstants.LOGOUT)) {
@@ -185,7 +195,6 @@ public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements
@Override
public ch.eitchnet.privilege.handler.PrivilegeHandler getPrivilegeHandler(Certificate certificate)
throws PrivilegeException {
- assertContainerStarted();
return this.privilegeHandler;
}
}
diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/RunAsAgent.java b/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/RunAsAgent.java
index 6dd324cd8..020846e6b 100644
--- a/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/RunAsAgent.java
+++ b/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/RunAsAgent.java
@@ -3,10 +3,19 @@ package li.strolch.runtime.privilege;
import ch.eitchnet.privilege.handler.SystemUserAction;
import ch.eitchnet.privilege.model.PrivilegeContext;
import li.strolch.service.api.AbstractService;
+import li.strolch.service.api.Service;
import li.strolch.service.api.ServiceArgument;
import li.strolch.service.api.ServiceHandler;
import li.strolch.service.api.ServiceResult;
+/**
+ * {@link SystemUserAction} to run a {@link Service} as a system user
+ *
+ * @author Robert von Burg
+ *
+ * @param
+ * @param
+ */
public class RunAsAgent extends SystemUserAction {
private ServiceHandler svcHandler;
diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/RunRunnable.java b/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/RunRunnable.java
new file mode 100644
index 000000000..1022a06c3
--- /dev/null
+++ b/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/RunRunnable.java
@@ -0,0 +1,36 @@
+package li.strolch.runtime.privilege;
+
+import ch.eitchnet.privilege.handler.SystemUserAction;
+import ch.eitchnet.privilege.model.PrivilegeContext;
+
+/**
+ * {@link SystemUserAction} to run {@link Runnable} as a system user
+ *
+ * @author Robert von Burg
+ *
+ * @param
+ * @param
+ */
+public class RunRunnable extends SystemUserAction {
+
+ private Runnable runnable;
+ private T result;
+
+ public RunRunnable(Runnable runnable) {
+ this.runnable = runnable;
+ }
+
+ @Override
+ public void execute(PrivilegeContext privilegeContext) {
+ this.result = runnable.run(privilegeContext);
+ }
+
+ public T getResult() {
+ return result;
+ }
+
+ public interface Runnable {
+
+ public T run(PrivilegeContext ctx);
+ }
+}
diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java b/li.strolch.rest/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java
index 8864a2469..fa924da5b 100644
--- a/li.strolch.rest/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java
+++ b/li.strolch.rest/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java
@@ -33,13 +33,7 @@ import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
-
-import li.strolch.agent.api.ComponentContainer;
-import li.strolch.agent.api.StrolchComponent;
-import li.strolch.exception.StrolchException;
-import li.strolch.rest.model.UserSession;
-import li.strolch.runtime.configuration.ComponentConfiguration;
-import li.strolch.runtime.privilege.PrivilegeHandler;
+import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,6 +44,12 @@ import ch.eitchnet.privilege.model.Certificate;
import ch.eitchnet.privilege.model.PrivilegeContext;
import ch.eitchnet.privilege.model.SimpleRestrictable;
import ch.eitchnet.utils.dbc.DBC;
+import li.strolch.agent.api.ComponentContainer;
+import li.strolch.agent.api.StrolchComponent;
+import li.strolch.exception.StrolchException;
+import li.strolch.rest.model.UserSession;
+import li.strolch.runtime.configuration.ComponentConfiguration;
+import li.strolch.runtime.privilege.PrivilegeHandler;
/**
* @author Robert von Burg
@@ -57,17 +57,15 @@ import ch.eitchnet.utils.dbc.DBC;
public class DefaultStrolchSessionHandler extends StrolchComponent implements StrolchSessionHandler {
public static final String PARAM_SESSION_TTL_MINUTES = "session.ttl.minutes"; //$NON-NLS-1$
+ public static final String PARAM_SESSION_RELOAD_SESSIONS = "session.reload"; //$NON-NLS-1$
private static final Logger logger = LoggerFactory.getLogger(DefaultStrolchSessionHandler.class);
private PrivilegeHandler privilegeHandler;
private Map certificateMap;
+ private boolean reloadSessions;
private long sessionTtl;
private Timer sessionTimeoutTimer;
- /**
- * @param container
- * @param componentName
- */
public DefaultStrolchSessionHandler(ComponentContainer container, String componentName) {
super(container, componentName);
}
@@ -75,6 +73,7 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
@Override
public void initialize(ComponentConfiguration configuration) throws Exception {
this.sessionTtl = TimeUnit.MINUTES.toMillis(configuration.getInt(PARAM_SESSION_TTL_MINUTES, 30));
+ this.reloadSessions = configuration.getBoolean(PARAM_SESSION_RELOAD_SESSIONS, false);
super.initialize(configuration);
}
@@ -83,6 +82,21 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
this.privilegeHandler = getContainer().getComponent(PrivilegeHandler.class);
this.certificateMap = Collections.synchronizedMap(new HashMap<>());
+ if (this.reloadSessions) {
+ List certificates = runPrivileged(ctx -> {
+ Certificate cert = ctx.getCertificate();
+ return this.privilegeHandler.getPrivilegeHandler(cert).getCertificates(cert).stream()
+ .filter(c -> !c.getUserState().isSystem()).collect(Collectors.toList());
+ });
+ for (Certificate certificate : certificates) {
+ this.certificateMap.put(certificate.getAuthToken(), certificate);
+ }
+
+ checkSessionsForTimeout();
+ logger.info("Restored " + certificates.size() + " sessions of which "
+ + (certificates.size() - this.certificateMap.size()) + " had timed out and were removed.");
+ }
+
this.sessionTimeoutTimer = new Timer("SessionTimeoutTimer", true); //$NON-NLS-1$
long checkInterval = TimeUnit.MINUTES.toMillis(1);
this.sessionTimeoutTimer.schedule(new SessionTimeoutTask(), checkInterval, checkInterval);
@@ -92,7 +106,12 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
@Override
public void stop() throws Exception {
- if (this.certificateMap != null) {
+ if (this.reloadSessions) {
+
+ runPrivileged(ctx -> getContainer().getPrivilegeHandler().getPrivilegeHandler(ctx.getCertificate())
+ .persistSessions(ctx.getCertificate()));
+
+ } else if (this.certificateMap != null) {
synchronized (this.certificateMap) {
for (Certificate certificate : this.certificateMap.values()) {
this.privilegeHandler.invalidateSession(certificate);
@@ -198,23 +217,26 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
@Override
public void run() {
+ checkSessionsForTimeout();
+ }
+ }
- Map map = getCertificateMap();
- Map certificateMap;
- synchronized (map) {
- certificateMap = new HashMap<>(map);
- }
+ private void checkSessionsForTimeout() {
+ Map map = getCertificateMap();
+ Map certificateMap;
+ synchronized (map) {
+ certificateMap = new HashMap<>(map);
+ }
- LocalDateTime timeOutTime = LocalDateTime.now().minus(sessionTtl, ChronoUnit.MILLIS);
- ZoneId systemDefault = ZoneId.systemDefault();
+ LocalDateTime timeOutTime = LocalDateTime.now().minus(sessionTtl, ChronoUnit.MILLIS);
+ ZoneId systemDefault = ZoneId.systemDefault();
- for (Certificate certificate : certificateMap.values()) {
- Instant lastAccess = certificate.getLastAccess().toInstant();
- if (timeOutTime.isAfter(LocalDateTime.ofInstant(lastAccess, systemDefault))) {
- String msg = "Session {0} for user {1} has expired, invalidating session..."; //$NON-NLS-1$
- logger.info(MessageFormat.format(msg, certificate.getAuthToken(), certificate.getUsername()));
- sessionTimeout(certificate);
- }
+ for (Certificate certificate : certificateMap.values()) {
+ Instant lastAccess = certificate.getLastAccess().toInstant();
+ if (timeOutTime.isAfter(LocalDateTime.ofInstant(lastAccess, systemDefault))) {
+ String msg = "Session {0} for user {1} has expired, invalidating session..."; //$NON-NLS-1$
+ logger.info(MessageFormat.format(msg, certificate.getSessionId(), certificate.getUsername()));
+ sessionTimeout(certificate);
}
}
}
diff --git a/li.strolch.service/src/main/java/li/strolch/service/executor/ServiceExecutionHandler.java b/li.strolch.service/src/main/java/li/strolch/service/executor/ServiceExecutionHandler.java
index 0b856adcf..f22eacbc2 100644
--- a/li.strolch.service/src/main/java/li/strolch/service/executor/ServiceExecutionHandler.java
+++ b/li.strolch.service/src/main/java/li/strolch/service/executor/ServiceExecutionHandler.java
@@ -21,6 +21,8 @@ import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
+import ch.eitchnet.privilege.model.Certificate;
+import ch.eitchnet.utils.helper.ExceptionHelper;
import li.strolch.agent.api.ComponentContainer;
import li.strolch.agent.api.StrolchComponent;
import li.strolch.exception.StrolchException;
@@ -29,7 +31,6 @@ import li.strolch.service.api.Service;
import li.strolch.service.api.ServiceArgument;
import li.strolch.service.api.ServiceHandler;
import li.strolch.service.api.ServiceResult;
-import ch.eitchnet.privilege.model.Certificate;
/**
* The {@link ServiceExecutionHandler} is used to perform long running services so that no singletons etc. are required.
@@ -62,7 +63,7 @@ public class ServiceExecutionHandler extends StrolchComponent {
doService(queue.take());
}
} catch (InterruptedException ex) {
- logger.error(ex.getLocalizedMessage());
+ logger.error(ExceptionHelper.formatExceptionMessage(ex));
}
}
}, "ServiceExecutor");
diff --git a/li.strolch.service/src/test/java/li/strolch/service/test/ServiceTest.java b/li.strolch.service/src/test/java/li/strolch/service/test/ServiceTest.java
index 7751d0eaf..601d6d495 100644
--- a/li.strolch.service/src/test/java/li/strolch/service/test/ServiceTest.java
+++ b/li.strolch.service/src/test/java/li/strolch/service/test/ServiceTest.java
@@ -23,12 +23,6 @@ import static org.junit.Assert.assertThat;
import java.util.Date;
import java.util.HashSet;
-import li.strolch.service.api.ServiceResult;
-import li.strolch.service.test.model.GreetingResult;
-import li.strolch.service.test.model.GreetingService;
-import li.strolch.service.test.model.GreetingService.GreetingArgument;
-import li.strolch.service.test.model.TestService;
-
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -36,6 +30,12 @@ import org.junit.rules.ExpectedException;
import ch.eitchnet.privilege.base.AccessDeniedException;
import ch.eitchnet.privilege.base.PrivilegeException;
import ch.eitchnet.privilege.model.Certificate;
+import ch.eitchnet.privilege.model.UserState;
+import li.strolch.service.api.ServiceResult;
+import li.strolch.service.test.model.GreetingResult;
+import li.strolch.service.test.model.GreetingService;
+import li.strolch.service.test.model.GreetingService.GreetingArgument;
+import li.strolch.service.test.model.TestService;
/**
* @author Robert von Burg
@@ -57,15 +57,15 @@ public class ServiceTest extends AbstractServiceTest {
this.thrown.expect(PrivilegeException.class);
TestService testService = new TestService();
getServiceHandler().doService(
- new Certificate(null, new Date(), null, null, null, null, null, new HashSet(), null),
+ new Certificate(null, null, null, null, null, null, new Date(), null, new HashSet(), null),
testService);
}
@Test
public void shouldFailInvalidCertificate2() {
TestService testService = new TestService();
- Certificate badCert = new Certificate(
- "1", new Date(), "bob", "Bob", "Brown", "dsdf", null, new HashSet(), null); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ Certificate badCert = new Certificate("1", "bob", "Bob", "Brown", UserState.ENABLED, "dsdf", new Date(), null, //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
+ new HashSet(), null);
ServiceResult svcResult = getServiceHandler().doService(badCert, testService);
assertThat(svcResult.getThrowable(), instanceOf(AccessDeniedException.class));
}