From c37fd20efb8bd01d38c84e6daf963c055f6feb00 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 16 Oct 2015 17:21:49 +0200 Subject: [PATCH] [Major] Added persisting and reloading of sessions --- ch.eitchnet.privilege | 2 +- ch.eitchnet.utils | 2 +- .../strolch/agent/api/StrolchComponent.java | 140 +++++++++++++++++- .../DefaultStrolchPrivilegeHandler.java | 13 +- .../strolch/runtime/privilege/RunAsAgent.java | 9 ++ .../runtime/privilege/RunRunnable.java | 36 +++++ .../rest/DefaultStrolchSessionHandler.java | 74 +++++---- .../executor/ServiceExecutionHandler.java | 5 +- .../li/strolch/service/test/ServiceTest.java | 18 +-- 9 files changed, 256 insertions(+), 43 deletions(-) create mode 100644 li.strolch.agent/src/main/java/li/strolch/runtime/privilege/RunRunnable.java 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)); }