From 5d164e66ad35cccbbcfaf89c4550b77f848bebee Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Sat, 14 Dec 2019 12:27:55 +0100 Subject: [PATCH] Clean-up user session listing and invalidation This mechanism is now separated from the cache invalidation. It has been factored out to ClusterwideUserSessionManagerImpl. --- gui/admin-gui/pom.xml | 11 --- .../InternalsLoggedInUsersPanel.java | 67 +++---------- .../model/api/ModelInteractionService.java | 13 ++- .../ClusterwideUserSessionManager.java | 31 ++++++ .../authentication/UserProfileService.java | 7 +- .../model/common/SystemObjectCache.java | 7 +- .../script/ScriptExpressionFactory.java | 7 +- .../model/impl/ClusterCacheListener.java | 19 ---- .../model/impl/ClusterRestService.java | 18 +--- .../ModelInteractionServiceImpl.java | 38 +++----- .../TriggerCreatorGlobalState.java | 4 - .../ClusterwideUserSessionManagerImpl.java | 95 +++++++++++++++++++ .../impl/security/UserProfileServiceImpl.java | 63 ++---------- .../TestAbstractAuthenticationEvaluator.java | 6 +- .../provisioning/impl/ConnectorManager.java | 7 +- .../midpoint/CacheInvalidationContext.java | 23 ----- .../midpoint/repo/api/RepositoryService.java | 2 +- .../midpoint/repo/cache/CacheRegistry.java | 3 + .../midpoint/repo/cache/RepositoryCache.java | 5 - .../SystemConfigurationCacheableAdapter.java | 7 +- .../common/expression/ExpressionFactory.java | 7 +- 21 files changed, 187 insertions(+), 253 deletions(-) create mode 100644 model/model-api/src/main/java/com/evolveum/midpoint/model/api/authentication/ClusterwideUserSessionManager.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/ClusterwideUserSessionManagerImpl.java diff --git a/gui/admin-gui/pom.xml b/gui/admin-gui/pom.xml index 26a60bdb076..4708d0426cb 100644 --- a/gui/admin-gui/pom.xml +++ b/gui/admin-gui/pom.xml @@ -186,17 +186,6 @@ spring-boot-actuator-autoconfigure - - javax.ws.rs - javax.ws.rs-api - compile - - - org.apache.cxf - cxf-rt-rs-client - compile - - org.webjars diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsLoggedInUsersPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsLoggedInUsersPanel.java index 0b2585501a9..71c478c7898 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsLoggedInUsersPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsLoggedInUsersPanel.java @@ -6,7 +6,6 @@ */ package com.evolveum.midpoint.web.page.admin.configuration; -import com.evolveum.midpoint.CacheInvalidationContext; import com.evolveum.midpoint.TerminateSessionEvent; import com.evolveum.midpoint.gui.api.GuiStyleConstants; import com.evolveum.midpoint.gui.api.component.BasePanel; @@ -15,6 +14,7 @@ import com.evolveum.midpoint.model.api.authentication.UserProfileService; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.ClusterExecutionOptions; +import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.web.component.data.BaseSortableDataProvider; import com.evolveum.midpoint.web.component.data.column.ColumnMenuAction; import com.evolveum.midpoint.web.component.menu.cog.ButtonInlineMenuItem; @@ -46,7 +46,8 @@ public class InternalsLoggedInUsersPanel extends BasePanel< private static final String ID_TABLE = "table"; private static final String DOT_CLASS = InternalsLoggedInUsersPanel.class.getName() + "."; - private static final String OPERATION_LOAD_PRINCIPALS_REMOTE_NODES = DOT_CLASS + "loadPrincipalsFromRemoteNodes"; + private static final String OPERATION_LOAD_LOGGED_IN_PRINCIPALS = DOT_CLASS + "loadLoggedInPrincipals"; + private static final String OPERATION_TERMINATE_SESSIONS = DOT_CLASS + "terminateSessions"; public InternalsLoggedInUsersPanel(String id) { super(id); @@ -82,7 +83,7 @@ protected BaseSortableDataProvider> initProvider() { @Override protected List load() { - return loadLoggedIUsersFromAllNodes(); + return loadLoggedInPrincipals(); } }; @@ -112,48 +113,10 @@ protected List createToolbarButtonsList(String buttonId) { add(table); } - private List loadLoggedIUsersFromAllNodes() { - OperationResult result = new OperationResult(OPERATION_LOAD_PRINCIPALS_REMOTE_NODES); - - List loggedUsers = getPageBase().getModelInteractionService().getLoggedInUsers(); - - Map usersMap = loggedUsers.stream().collect(Collectors.toMap(key -> key.getUser().getOid(), value -> value)); - - getPageBase().getClusterExecutionHelper().execute((client, operationResult) -> { - - client.path(UserProfileService.EVENT_LIST_USER_SESSION); - Response response = client.get(); - LOGGER.info("Cluster-wide cache clearance finished with status {}, {}", response.getStatusInfo().getStatusCode(), - response.getStatusInfo().getReasonPhrase()); - - if (!response.hasEntity()) { - return; - } - - UserSessionManagementListType sessionUsers = response.readEntity(UserSessionManagementListType.class); - response.close(); - - List nodeSpecificSessions = sessionUsers.getSession(); - - if (sessionUsers == null || nodeSpecificSessions == null || nodeSpecificSessions.isEmpty()) { - return; - } - - for (UserSessionManagementType user : nodeSpecificSessions) { - UserSessionManagementType existingUser = usersMap.get(user.getUser().getOid()); - if (existingUser != null) { - existingUser.setActiveSessions(existingUser.getActiveSessions() + user.getActiveSessions()); - existingUser.getNode().addAll(user.getNode()); - continue; - } - - usersMap.put(user.getUser().getOid(), user); - } - - }, new ClusterExecutionOptions().tryNodesNotCheckingIn() /* reconsider if needed */, - " list principals from remote nodes ", result); - - return new ArrayList<>(usersMap.values()); + private List loadLoggedInPrincipals() { + Task task = getPageBase().createSimpleTask(OPERATION_LOAD_LOGGED_IN_PRINCIPALS); + OperationResult result = task.getResult(); + return getPageBase().getModelInteractionService().getLoggedInPrincipals(task, result); } private List, String>> initColumns() { @@ -181,10 +144,10 @@ public InlineMenuItemAction initAction() { @Override public void onClick(AjaxRequestTarget target) { if (getRowModel() == null){ - expireSessionsFor(target, null); + terminateSessions(target, null); } else { SelectableBean rowDto = getRowModel().getObject(); - expireSessionsFor(target, rowDto.getValue()); + terminateSessions(target, rowDto.getValue()); } } }; @@ -199,7 +162,7 @@ public boolean isHeaderMenuItem(){ } - private void expireSessionsFor(AjaxRequestTarget target, F selectedObject) { + private void terminateSessions(AjaxRequestTarget target, F selectedObject) { List selected = getSelectedObjects(selectedObject); if (CollectionUtils.isEmpty(selected)) { getSession().warn(getString("InternalsLoggedInUsersPanel.expireSession.warn")); @@ -207,13 +170,13 @@ private void expireSessionsFor(AjaxRequestTarget target, F selectedObject) { return; } + Task task = getPageBase().createSimpleTask(OPERATION_TERMINATE_SESSIONS); + TerminateSessionEvent details = new TerminateSessionEvent(); details.setPrincipalOids(selected); - CacheInvalidationContext ctx = new CacheInvalidationContext(false, details); - ctx.setTerminateSession(true); - getPageBase().getCacheDispatcher().dispatchInvalidation(null, null, true, ctx); - //getPageBase().getModelInteractionService().expirePrincipals(selected); + getPageBase().getModelInteractionService().terminateSessions(details, task, task.getResult()); + getSession().success(getString("InternalsLoggedInUsersPanel.expireSession.success")); target.add(getPageBase().getFeedbackPanel()); } diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelInteractionService.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelInteractionService.java index b0795ea9d4d..da9d4c48636 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelInteractionService.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelInteractionService.java @@ -6,6 +6,7 @@ */ package com.evolveum.midpoint.model.api; +import com.evolveum.midpoint.TerminateSessionEvent; import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; import com.evolveum.midpoint.model.api.authentication.CompiledObjectCollectionView; import com.evolveum.midpoint.model.api.authentication.CompiledUserProfile; @@ -253,10 +254,14 @@ ModelContext previewChanges( CompiledUserProfile getCompiledUserProfile(Task task, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException; /** - * - * @return list of logged in users with at least 1 active session + * @return list of logged in users with at least 1 active session (clusterwide) */ - List getLoggedInUsers(); + List getLoggedInPrincipals(Task task, OperationResult result); + + /** + * Terminates specified sessions (clusterwide). + */ + void terminateSessions(TerminateSessionEvent terminateSessionEvent, Task task, OperationResult result); SystemConfigurationType getSystemConfiguration(OperationResult parentResult) throws ObjectNotFoundException, SchemaException; @@ -362,8 +367,6 @@ ExecuteCredentialResetResponseType executeCredentialsReset(PrismObject void refreshPrincipal(String oid) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException; - void expirePrincipals(List oid); - List getRelationDefinitions(); @NotNull diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/authentication/ClusterwideUserSessionManager.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/authentication/ClusterwideUserSessionManager.java new file mode 100644 index 00000000000..caf639de555 --- /dev/null +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/authentication/ClusterwideUserSessionManager.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.api.authentication; + +import com.evolveum.midpoint.TerminateSessionEvent; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.xml.ns._public.common.api_types_3.UserSessionManagementType; + +import java.util.List; + +/** + * Takes care for clusterwide user session management. + */ +public interface ClusterwideUserSessionManager { + + /** + * Terminates specified sessions (on local and remote nodes). + */ + void terminateSessions(TerminateSessionEvent terminateSessionEvent, Task task, OperationResult result); + + /** + * Collects logged in principals (on local and remote nodes). + */ + List getLoggedInPrincipals(Task task, OperationResult result); +} diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/authentication/UserProfileService.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/authentication/UserProfileService.java index 078185eb4ce..96b1474593e 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/authentication/UserProfileService.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/authentication/UserProfileService.java @@ -6,6 +6,7 @@ */ package com.evolveum.midpoint.model.api.authentication; +import com.evolveum.midpoint.TerminateSessionEvent; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.schema.result.OperationResult; @@ -23,7 +24,6 @@ import java.util.Collection; import java.util.List; -import java.util.Map; /** * @author semancik @@ -48,8 +48,7 @@ public interface UserProfileService extends MidPointPrincipalManager { @Override void updateUser(MidPointPrincipal principal, Collection> itemDeltas); - List getAllLoggedPrincipals(); - - void expirePrincipals(List principalsOid); + List getLocalLoggedInPrincipals(); + void terminateLocalSessions(TerminateSessionEvent terminateSessionEvent); } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/SystemObjectCache.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/SystemObjectCache.java index 0e3dae5597e..7a748018db5 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/SystemObjectCache.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/SystemObjectCache.java @@ -258,13 +258,8 @@ private void compileExpressionProfiles(OperationResult result) throws SchemaExce // managed by this class. @Override public void invalidate(Class type, String oid, CacheInvalidationContext context) { - if (context != null && context.isTerminateSession()) { - LOGGER.trace("Skipping invalidation request. Request is for terminate session, not for system object cache invalidation."); - return; - } - // We ignore OID for now - if (type == null || SystemConfigurationType.class.isAssignableFrom(type)) { + if (type == null || type.isAssignableFrom(SystemConfigurationType.class)) { invalidateCaches(); } } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/ScriptExpressionFactory.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/ScriptExpressionFactory.java index 4d74115ca0a..93f493b96cc 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/ScriptExpressionFactory.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/ScriptExpressionFactory.java @@ -227,12 +227,7 @@ private String getLanguage(ScriptExpressionEvaluatorType expressionType) { @Override public void invalidate(Class type, String oid, CacheInvalidationContext context) { - if (context != null && context.isTerminateSession()) { - LOGGER.trace("Skipping invalidation request. Request is for terminate session, not for script cache invalidation."); - return; - } - - if (type == null || FunctionLibraryType.class.isAssignableFrom(type)) { + if (type == null || type.isAssignableFrom(FunctionLibraryType.class)) { // Currently we don't try to select entries to be cleared based on OID customFunctionLibraryCache = null; } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ClusterCacheListener.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ClusterCacheListener.java index ec494a89510..d1a955e767a 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ClusterCacheListener.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ClusterCacheListener.java @@ -56,25 +56,6 @@ public void invalidate(Class type, String oid, boolean LOGGER.trace("Cache invalidation context {}", context); - if (context != null && context.isTerminateSession()) { - CacheInvalidationDetails details = context.getDetails(); - if (!(details instanceof TerminateSessionEvent)) { - LOGGER.warn("Cannot perform cluster-wide session termination, no details provided."); - return; - } - // We try to invoke this call also on nodes that are not checking in (but not declared dead). It is quite important - // that terminate session is executed on as wide scale as realistically possible. - clusterExecutionHelper.execute((client, result1) -> { - client.path(ClusterRestService.EVENT_TERMINATE_SESSION); - - Response response = client.post(((TerminateSessionEvent)details).toEventType()); - LOGGER.info("Cluster-wide cache clearance finished with status {}, {}", response.getStatusInfo().getStatusCode(), - response.getStatusInfo().getReasonPhrase()); - response.close(); - }, new ClusterExecutionOptions().tryNodesNotCheckingIn(), "session termination", result); - return; - } - // Regular cache invalidation can be skipped for nodes not checking in. Cache entries will expire on such nodes // eventually. (We can revisit this design decision if needed.) clusterExecutionHelper.execute((client, result1) -> { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ClusterRestService.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ClusterRestService.java index cbecd34a703..f36ec71fa73 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ClusterRestService.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ClusterRestService.java @@ -10,7 +10,7 @@ import com.evolveum.midpoint.TerminateSessionEvent; import com.evolveum.midpoint.common.configuration.api.MidpointConfiguration; import com.evolveum.midpoint.model.api.ModelInteractionService; -import com.evolveum.midpoint.model.api.authentication.MidPointUserProfilePrincipal; +import com.evolveum.midpoint.model.api.authentication.UserProfileService; import com.evolveum.midpoint.model.impl.security.NodeAuthenticationToken; import com.evolveum.midpoint.model.impl.security.SecurityHelper; import com.evolveum.midpoint.model.impl.util.RestServiceUtil; @@ -44,9 +44,6 @@ import java.io.File; import java.io.FileInputStream; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; /** * REST service used for inter-cluster communication. @@ -82,6 +79,7 @@ public class ClusterRestService { @Autowired private SecurityHelper securityHelper; @Autowired private TaskManager taskManager; @Autowired private MidpointConfiguration midpointConfiguration; + @Autowired private UserProfileService userProfileService; @Autowired private CacheDispatcher cacheDispatcher; @@ -97,7 +95,6 @@ public ClusterRestService() { @Path(EVENT_INVALIDATION + "{type}") @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) - @SuppressWarnings("RSReferenceInspection") public Response executeClusterCacheInvalidationEvent(@PathParam("type") String type, @Context MessageContext mc) { return executeClusterCacheInvalidationEvent(type, null, mc); } @@ -106,7 +103,6 @@ public Response executeClusterCacheInvalidationEvent(@PathParam("type") String t @Path(EVENT_INVALIDATION + "{type}/{oid}") @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) - @SuppressWarnings("RSReferenceInspection") public Response executeClusterCacheInvalidationEvent(@PathParam("type") String type, @PathParam("oid") String oid, @Context MessageContext mc) { Task task = RestServiceUtil.initRequest(mc); OperationResult result = new OperationResult(OPERATION_EXECUTE_CLUSTER_CACHE_INVALIDATION_EVENT); @@ -133,7 +129,6 @@ public Response executeClusterCacheInvalidationEvent(@PathParam("type") String t @Path(EVENT_TERMINATE_SESSION) @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) - @SuppressWarnings("RSReferenceInspection") public Response executeClusterTerminateSessionEvent(TerminateSessionEventType event, @Context MessageContext mc) { Task task = RestServiceUtil.initRequest(mc); OperationResult result = new OperationResult(OPERATION_EXECUTE_CLUSTER_TERMINATE_SESSION_EVENT); @@ -142,12 +137,7 @@ public Response executeClusterTerminateSessionEvent(TerminateSessionEventType ev try { checkNodeAuthentication(); - // clusterwide is false: we got this from another node so we don't need to redistribute it - CacheInvalidationContext ctx = new CacheInvalidationContext(true, TerminateSessionEvent.fromEventType(event)); - ctx.setTerminateSession(true); - - LOGGER.trace("Terminate session event - context created: {}", ctx); - cacheDispatcher.dispatchInvalidation(null, null, false, ctx); + userProfileService.terminateLocalSessions(TerminateSessionEvent.fromEventType(event)); result.recordSuccess(); response = RestServiceUtil.createResponse(Status.OK, result); @@ -169,7 +159,7 @@ public Response listUserSession(@Context MessageContext mc) { Response response; try { checkNodeAuthentication(); - List principals = modelInteractionService.getLoggedInUsers(); + List principals = userProfileService.getLocalLoggedInPrincipals(); UserSessionManagementListType list = new UserSessionManagementListType(); list.getSession().addAll(principals); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java index 3c7323c508a..994bc767330 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java @@ -24,6 +24,8 @@ import javax.xml.namespace.QName; +import com.evolveum.midpoint.TerminateSessionEvent; +import com.evolveum.midpoint.model.api.authentication.*; import com.evolveum.midpoint.schema.cache.CacheConfigurationManager; import com.evolveum.midpoint.schema.util.*; import com.evolveum.midpoint.security.enforcer.api.FilterGizmo; @@ -55,10 +57,6 @@ import com.evolveum.midpoint.model.api.ModelService; import com.evolveum.midpoint.model.api.ProgressListener; import com.evolveum.midpoint.model.api.RoleSelectionSpecification; -import com.evolveum.midpoint.model.api.authentication.CompiledObjectCollectionView; -import com.evolveum.midpoint.model.api.authentication.CompiledUserProfile; -import com.evolveum.midpoint.model.api.authentication.MidPointUserProfilePrincipal; -import com.evolveum.midpoint.model.api.authentication.UserProfileService; import com.evolveum.midpoint.model.api.context.EvaluatedAssignment; import com.evolveum.midpoint.model.api.context.EvaluatedAssignmentTarget; import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; @@ -112,18 +110,9 @@ import com.evolveum.midpoint.prism.path.ItemName; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.polystring.PolyString; -import com.evolveum.midpoint.prism.query.AllFilter; -import com.evolveum.midpoint.prism.query.AndFilter; import com.evolveum.midpoint.prism.query.EqualFilter; -import com.evolveum.midpoint.prism.query.FilterCreationUtil; -import com.evolveum.midpoint.prism.query.InOidFilter; -import com.evolveum.midpoint.prism.query.NoneFilter; -import com.evolveum.midpoint.prism.query.NotFilter; import com.evolveum.midpoint.prism.query.ObjectFilter; import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.prism.query.OrFilter; -import com.evolveum.midpoint.prism.query.RefFilter; -import com.evolveum.midpoint.prism.query.TypeFilter; import com.evolveum.midpoint.prism.util.ItemDeltaItem; import com.evolveum.midpoint.prism.util.ItemPathTypeUtil; import com.evolveum.midpoint.prism.util.ObjectDeltaObject; @@ -189,17 +178,13 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsPolicyType; import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsResetPolicyType; import com.evolveum.midpoint.xml.ns._public.common.common_3.DeploymentInformationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; import com.evolveum.midpoint.xml.ns._public.common.common_3.LayerType; import com.evolveum.midpoint.xml.ns._public.common.common_3.LensContextType; import com.evolveum.midpoint.xml.ns._public.common.common_3.LocalizableMessageTemplateType; import com.evolveum.midpoint.xml.ns._public.common.common_3.LocalizableMessageType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.LookupTableRowType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.LookupTableType; import com.evolveum.midpoint.xml.ns._public.common.common_3.MergeConfigurationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectCollectionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateItemDefinitionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.OrderConstraintsType; @@ -267,6 +252,7 @@ public class ModelInteractionServiceImpl implements ModelInteractionService { @Autowired private Clockwork clockwork; @Autowired private CollectionProcessor collectionProcessor; @Autowired private CacheConfigurationManager cacheConfigurationManager; + @Autowired private ClusterwideUserSessionManager clusterwideUserSessionManager; private static final String OPERATION_GENERATE_VALUE = ModelInteractionService.class.getName() + ".generateValue"; private static final String OPERATION_VALIDATE_VALUE = ModelInteractionService.class.getName() + ".validateValue"; @@ -756,8 +742,15 @@ public CompiledUserProfile getCompiledUserProfile(Task task, OperationResult par } @Override - public List getLoggedInUsers() { - return userProfileService.getAllLoggedPrincipals(); + public List getLoggedInPrincipals(Task task, OperationResult result) { + // TODO some authorization here? + return clusterwideUserSessionManager.getLoggedInPrincipals(task, result); + } + + @Override + public void terminateSessions(TerminateSessionEvent terminateSessionEvent, Task task, OperationResult result) { + // TODO some authorization here? + clusterwideUserSessionManager.terminateSessions(terminateSessionEvent, task, result); } @Override @@ -1685,12 +1678,6 @@ public void refreshPrincipal(String oid) throws ObjectNotFoundException, SchemaE } } - @Override - public void expirePrincipals(List principals) { - userProfileService.expirePrincipals(principals); - - } - @Override public List getRelationDefinitions() { return relationRegistry.getRelationDefinitions(); @@ -1869,5 +1856,4 @@ public CollectionStats determineCollectionStats(@NotNull throws SchemaException, ObjectNotFoundException, SecurityViolationException, ConfigurationException, CommunicationException, ExpressionEvaluationException { return collectionProcessor.determineCollectionStats(collectionView, task, result); } - } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/triggerSetter/TriggerCreatorGlobalState.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/triggerSetter/TriggerCreatorGlobalState.java index 4dcdf31f483..7445cdab95e 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/triggerSetter/TriggerCreatorGlobalState.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/triggerSetter/TriggerCreatorGlobalState.java @@ -56,10 +56,6 @@ synchronized void recordCreatedTrigger(TriggerHolderSpecification key, CreatedTr @Override public synchronized void invalidate(Class type, String oid, CacheInvalidationContext context) { - if (context != null && (context.isTerminateSession() || context.isListUsersSession())) { // fixme !!! - return; - } - if (oid != null) { // We are interested in object deletion events; just to take care of situations when an object is deleted and // a new object (of the same name) is created immediately. diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/ClusterwideUserSessionManagerImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/ClusterwideUserSessionManagerImpl.java new file mode 100644 index 00000000000..9c24439edbb --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/ClusterwideUserSessionManagerImpl.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.security; + +import com.evolveum.midpoint.TerminateSessionEvent; +import com.evolveum.midpoint.model.api.authentication.ClusterwideUserSessionManager; +import com.evolveum.midpoint.model.api.authentication.UserProfileService; +import com.evolveum.midpoint.model.impl.ClusterRestService; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.ClusterExecutionHelper; +import com.evolveum.midpoint.task.api.ClusterExecutionOptions; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.MiscUtil; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.api_types_3.UserSessionManagementListType; +import com.evolveum.midpoint.xml.ns._public.common.api_types_3.UserSessionManagementType; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Takes care for clusterwide user session management. + */ +@Component +public class ClusterwideUserSessionManagerImpl implements ClusterwideUserSessionManager { + + private static final Trace LOGGER = TraceManager.getTrace(ClusterwideUserSessionManagerImpl.class); + + @Autowired private ClusterExecutionHelper clusterExecutionHelper; + @Autowired private UserProfileService localUserProfileService; + + @Override + public void terminateSessions(TerminateSessionEvent terminateSessionEvent, Task task, OperationResult result) { + + localUserProfileService.terminateLocalSessions(terminateSessionEvent); + + // We try to invoke this call also on nodes that are not checking in (but not declared dead). It is quite important + // that terminate session is executed on as wide scale as realistically possible. + clusterExecutionHelper.execute((client, result1) -> { + client.path(ClusterRestService.EVENT_TERMINATE_SESSION); + Response response = client.post(terminateSessionEvent.toEventType()); + LOGGER.info("Remote-node user session termination finished with status {}, {}", + response.getStatusInfo().getStatusCode(), response.getStatusInfo().getReasonPhrase()); + response.close(); + }, new ClusterExecutionOptions().tryNodesNotCheckingIn(), "session termination", result); + } + + @Override + @NotNull + public List getLoggedInPrincipals(Task task, OperationResult result) { + + List loggedUsers = localUserProfileService.getLocalLoggedInPrincipals(); + + Map usersMap = loggedUsers.stream() + .collect(Collectors.toMap(key -> key.getUser().getOid(), value -> value)); + + // We try to invoke this call also on nodes that are not checking in (but not declared dead). We want to get + // information as complete as realistically possible. + clusterExecutionHelper.execute((client, result1) -> { + client.path(UserProfileService.EVENT_LIST_USER_SESSION); + Response response = client.get(); + LOGGER.info("Remote-node retrieval of user sessions finished with status {}, {}", + response.getStatusInfo().getStatusCode(), response.getStatusInfo().getReasonPhrase()); + + if (response.hasEntity()) { + UserSessionManagementListType remoteSessionsWrapper = response.readEntity(UserSessionManagementListType.class); + List remoteSessions = remoteSessionsWrapper.getSession(); + for (UserSessionManagementType remoteSession : MiscUtil.emptyIfNull(remoteSessions)) { + UserSessionManagementType existingUser = usersMap.get(remoteSession.getUser().getOid()); + if (existingUser != null) { + existingUser.setActiveSessions(existingUser.getActiveSessions() + remoteSession.getActiveSessions()); + existingUser.getNode().addAll(remoteSession.getNode()); + } else { + usersMap.put(remoteSession.getUser().getOid(), remoteSession); + } + } + } + response.close(); + }, new ClusterExecutionOptions().tryNodesNotCheckingIn(), " list principals from remote nodes ", result); + + return new ArrayList<>(usersMap.values()); + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/UserProfileServiceImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/UserProfileServiceImpl.java index 78069bc60d3..924f0ce7015 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/UserProfileServiceImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/UserProfileServiceImpl.java @@ -9,24 +9,16 @@ import java.util.*; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; import javax.naming.NamingException; import javax.naming.directory.Attribute; -import com.evolveum.midpoint.CacheInvalidationContext; import com.evolveum.midpoint.TerminateSessionEvent; -import com.evolveum.midpoint.model.impl.ClusterCacheListener; import com.evolveum.midpoint.prism.delta.ItemDelta; -import com.evolveum.midpoint.repo.api.CacheDispatcher; -import com.evolveum.midpoint.repo.api.CacheInvalidationDetails; -import com.evolveum.midpoint.repo.api.Cacheable; -import com.evolveum.midpoint.repo.cache.CacheRegistry; import com.evolveum.midpoint.security.api.SecurityContextManager; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.xml.ns._public.common.api_types_3.UserSessionManagementType; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.jetbrains.annotations.NotNull; +import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; @@ -74,14 +66,13 @@ import com.evolveum.midpoint.util.logging.TraceManager; import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; /** * @author lazyman * @author semancik */ @Service(value = "userDetailsService") -public class UserProfileServiceImpl implements UserProfileService, UserDetailsService, UserDetailsContextMapper, MessageSourceAware, Cacheable { +public class UserProfileServiceImpl implements UserProfileService, UserDetailsService, UserDetailsContextMapper, MessageSourceAware { private static final Trace LOGGER = TraceManager.getTrace(UserProfileServiceImpl.class); @@ -107,25 +98,12 @@ public class UserProfileServiceImpl implements UserProfileService, UserDetailsSe @Autowired(required = false) private SessionRegistry sessionRegistry; - @Autowired - private CacheRegistry cacheRegistry; - //optional application.yml property for LDAP authentication, marks LDAP attribute name that correlates with midPoint UserType name @Value("${auth.ldap.search.naming-attr:#{null}}") private String ldapNamingAttr; private MessageSourceAccessor messages; - @PostConstruct - public void register() { - cacheRegistry.registerCacheableService(this); - } - - @PreDestroy - public void unregister() { - cacheRegistry.unregisterCacheableService(this); - } - @Override public void setMessageSource(MessageSource messageSource) { this.messages = new MessageSourceAccessor(messageSource); @@ -184,7 +162,7 @@ public MidPointUserProfilePrincipal getPrincipal(PrismObject user, Aut } @Override - public List getAllLoggedPrincipals() { + public List getLocalLoggedInPrincipals() { String currentNodeId = taskManager.getNodeId(); @@ -219,8 +197,9 @@ public List getAllLoggedPrincipals() { } @Override - public void expirePrincipals(List principalsOid) { - if (sessionRegistry != null) { + public void terminateLocalSessions(TerminateSessionEvent terminateSessionEvent) { + List principalOids = terminateSessionEvent.getPrincipalOids(); + if (sessionRegistry != null && CollectionUtils.isNotEmpty(principalOids)) { List loggedInUsers = sessionRegistry.getAllPrincipals(); for (Object principal : loggedInUsers) { @@ -229,7 +208,7 @@ public void expirePrincipals(List principalsOid) { } MidPointUserProfilePrincipal midPointPrincipal = (MidPointUserProfilePrincipal) principal; - if (!principalsOid.contains(midPointPrincipal.getOid())) { + if (!principalOids.contains(midPointPrincipal.getOid())) { continue; } @@ -420,33 +399,5 @@ private String resolveLdapName(DirContextOperations ctx, String username) throws } return username; // fallback to typed-in username in case ldap value is missing } - - @Override - public void invalidate(Class type, String oid, CacheInvalidationContext context) { - if (context == null || !context.isTerminateSession()) { - LOGGER.trace("Skipping cache invalidation for user profile service, not terminate session event."); - return; - } - - CacheInvalidationDetails details = context.getDetails(); - if (!(details instanceof TerminateSessionEvent)) { - LOGGER.trace("Skipping cache invalidation for user profile service, no details provided. Context {}", context); - return; - } - - TerminateSessionEvent terminateSessionDetails = (TerminateSessionEvent) details; - expirePrincipals(terminateSessionDetails.getPrincipalOids()); - - } - - @NotNull - @Override - public Collection getStateInformation() { - - SingleCacheStateInformationType info = new SingleCacheStateInformationType(); - info.setName("SessionRegistry"); - return Arrays.asList(info); - - } } diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/security/TestAbstractAuthenticationEvaluator.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/security/TestAbstractAuthenticationEvaluator.java index 910257ecf6f..667dea5fdec 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/security/TestAbstractAuthenticationEvaluator.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/security/TestAbstractAuthenticationEvaluator.java @@ -14,11 +14,11 @@ import java.util.Collection; import java.util.List; import java.util.Locale; -import java.util.Map; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; +import com.evolveum.midpoint.TerminateSessionEvent; import com.evolveum.midpoint.common.LocalizationMessageSource; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.security.api.*; @@ -159,12 +159,12 @@ public MidPointUserProfilePrincipal getPrincipalByOid(String oid) throws ObjectN //TODO test maybe later? @Override - public List getAllLoggedPrincipals() { + public List getLocalLoggedInPrincipals() { return null; } @Override - public void expirePrincipals(List principalsOid) { + public void terminateLocalSessions(TerminateSessionEvent terminateSessionEvent) { //TOTO test it } }; diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ConnectorManager.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ConnectorManager.java index 8bf5face820..2aff27201e5 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ConnectorManager.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ConnectorManager.java @@ -632,12 +632,7 @@ private interface ConnectorFactoryConsumer { @Override public void invalidate(Class type, String oid, CacheInvalidationContext context) { - if (context != null && context.isTerminateSession()) { - LOGGER.trace("Skipping invalidation request. Request is for terminate session, not for connector cache invalidation."); - return; - } - - if (type == null || ConnectorType.class.isAssignableFrom(type)) { + if (type == null || type.isAssignableFrom(ConnectorType.class)) { if (StringUtils.isEmpty(oid)) { dispose(); connectorInstanceCache = new ConcurrentHashMap<>(); diff --git a/repo/repo-api/src/main/java/com/evolveum/midpoint/CacheInvalidationContext.java b/repo/repo-api/src/main/java/com/evolveum/midpoint/CacheInvalidationContext.java index 01336730d89..45754365af3 100644 --- a/repo/repo-api/src/main/java/com/evolveum/midpoint/CacheInvalidationContext.java +++ b/repo/repo-api/src/main/java/com/evolveum/midpoint/CacheInvalidationContext.java @@ -20,10 +20,6 @@ public class CacheInvalidationContext { private boolean fromRemoteNode; private CacheInvalidationDetails details; - //TODO very experimental, probably we need different invalidationEvents to describe actions and objects - private boolean terminateSession; - private boolean listUsersSession; - public CacheInvalidationContext(boolean fromRemoteNode, CacheInvalidationDetails details) { this.fromRemoteNode = fromRemoteNode; this.details = details; @@ -45,29 +41,10 @@ public void setDetails(CacheInvalidationDetails details) { this.details = details; } - public boolean isTerminateSession() { - return terminateSession; - } - - public void setTerminateSession(boolean terminateSession) { - this.terminateSession = terminateSession; - } - - public boolean isListUsersSession() { - return listUsersSession; - } - - public void setListUsersSession(boolean listUsersSession) { - this.listUsersSession = listUsersSession; - } - - @Override public String toString() { return "CacheInvalidationContext{" + "fromRemoteNode=" + fromRemoteNode + - ", terminateSession=" + terminateSession + - ", listUsersSession=" + listUsersSession + ", details=" + details + '}'; } diff --git a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/RepositoryService.java b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/RepositoryService.java index d937e2bb6cf..368b09195d3 100644 --- a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/RepositoryService.java +++ b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/RepositoryService.java @@ -221,7 +221,7 @@ PrismObject getObject(Class type, String oid, Colle * @throws IllegalArgumentException * wrong OID format, etc. */ - String getVersion(Class type,String oid, OperationResult parentResult) + String getVersion(Class type, String oid, OperationResult parentResult) throws ObjectNotFoundException, SchemaException; int countContainers(Class type, ObjectQuery query, diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheRegistry.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheRegistry.java index 1dc30c2ac8a..5f42d3ee2b5 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheRegistry.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheRegistry.java @@ -64,6 +64,9 @@ public void invalidate(Class type, String oid, boolean CacheInvalidationContext context) { // We currently ignore clusterwide parameter, because it's used by ClusterCacheListener only. // So we assume that the invalidation event - from this point on - is propagated only locally. + if (oid == null && (type == null || ObjectType.class.equals(type))) { + LOGGER.warn("Invalidation request that is too broad: OID is null and type={}", type); + } for (Cacheable cacheableService : cacheableServices) { cacheableService.invalidate(type, oid, context); } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/RepositoryCache.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/RepositoryCache.java index 61b6886e6b7..c9b79b2b36e 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/RepositoryCache.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/RepositoryCache.java @@ -1183,11 +1183,6 @@ private void invalidateCacheEntries(Class type, String // This is what is called from cache dispatcher (on local node with the full context; on remote nodes with reduced context) @Override public void invalidate(Class type, String oid, CacheInvalidationContext context) { - if (context != null && context.isTerminateSession()) { - LOGGER.trace("Skipping invalidation request. Request is for terminate session, not for repo cache invalidation."); - return; - } - if (type == null) { globalObjectCache.clear(); globalVersionCache.clear(); diff --git a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/SystemConfigurationCacheableAdapter.java b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/SystemConfigurationCacheableAdapter.java index c1c9946e596..b1e4896040a 100644 --- a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/SystemConfigurationCacheableAdapter.java +++ b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/SystemConfigurationCacheableAdapter.java @@ -49,12 +49,7 @@ public void unregister() { @Override public void invalidate(Class type, String oid, CacheInvalidationContext context) { - if (context != null && context.isTerminateSession()) { - LOGGER.trace("Skipping invalidation request. Request is for terminate session, not for system configuration cache invalidation."); - return; - } - - if (type == null || SystemConfigurationType.class.isAssignableFrom(type)) { + if (type == null || type.isAssignableFrom(SystemConfigurationType.class)) { // We ignore OID by now, assuming there's only a single system configuration object try { OperationResult result = new OperationResult(SystemConfigurationCacheableAdapter.class.getName() + ".invalidate"); diff --git a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ExpressionFactory.java b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ExpressionFactory.java index aab1e18e346..5e47a6b224c 100644 --- a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ExpressionFactory.java +++ b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ExpressionFactory.java @@ -199,12 +199,7 @@ private ExpressionFactory getOuterType() { @Override public void invalidate(Class type, String oid, CacheInvalidationContext context) { - if (context != null && context.isTerminateSession()) { - LOGGER.trace("Skipping invalidation request. Request is for terminate session, not for expression cache invalidation."); - return; - } - - if (type == null || FunctionLibraryType.class.isAssignableFrom(type)) { + if (type == null || type.isAssignableFrom(FunctionLibraryType.class)) { // Currently we don't attempt to select entries to be cleared based on function library OID cache = new HashMap<>(); }