Skip to content

Commit

Permalink
Clean-up user session listing and invalidation
Browse files Browse the repository at this point in the history
This mechanism is now separated from the cache invalidation. It has
been factored out to ClusterwideUserSessionManagerImpl.
  • Loading branch information
mederly committed Dec 14, 2019
1 parent 118111e commit 5d164e6
Show file tree
Hide file tree
Showing 21 changed files with 187 additions and 253 deletions.
11 changes: 0 additions & 11 deletions gui/admin-gui/pom.xml
Expand Up @@ -186,17 +186,6 @@
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
</dependency>

<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-client</artifactId>
<scope>compile</scope>
</dependency>

<!-- webjars -->
<dependency>
<groupId>org.webjars</groupId>
Expand Down
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -46,7 +46,8 @@ public class InternalsLoggedInUsersPanel<F extends FocusType> 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);
Expand Down Expand Up @@ -82,7 +83,7 @@ protected BaseSortableDataProvider<SelectableBean<F>> initProvider() {

@Override
protected List<UserSessionManagementType> load() {
return loadLoggedIUsersFromAllNodes();
return loadLoggedInPrincipals();
}
};

Expand Down Expand Up @@ -112,48 +113,10 @@ protected List<Component> createToolbarButtonsList(String buttonId) {
add(table);
}

private List<UserSessionManagementType> loadLoggedIUsersFromAllNodes() {
OperationResult result = new OperationResult(OPERATION_LOAD_PRINCIPALS_REMOTE_NODES);

List<UserSessionManagementType> loggedUsers = getPageBase().getModelInteractionService().getLoggedInUsers();

Map<String, UserSessionManagementType> 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<UserSessionManagementType> 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<UserSessionManagementType> loadLoggedInPrincipals() {
Task task = getPageBase().createSimpleTask(OPERATION_LOAD_LOGGED_IN_PRINCIPALS);
OperationResult result = task.getResult();
return getPageBase().getModelInteractionService().getLoggedInPrincipals(task, result);
}

private List<IColumn<SelectableBean<F>, String>> initColumns() {
Expand Down Expand Up @@ -181,10 +144,10 @@ public InlineMenuItemAction initAction() {
@Override
public void onClick(AjaxRequestTarget target) {
if (getRowModel() == null){
expireSessionsFor(target, null);
terminateSessions(target, null);
} else {
SelectableBean<F> rowDto = getRowModel().getObject();
expireSessionsFor(target, rowDto.getValue());
terminateSessions(target, rowDto.getValue());
}
}
};
Expand All @@ -199,21 +162,21 @@ public boolean isHeaderMenuItem(){

}

private void expireSessionsFor(AjaxRequestTarget target, F selectedObject) {
private void terminateSessions(AjaxRequestTarget target, F selectedObject) {
List<String> selected = getSelectedObjects(selectedObject);
if (CollectionUtils.isEmpty(selected)) {
getSession().warn(getString("InternalsLoggedInUsersPanel.expireSession.warn"));
target.add(getPageBase().getFeedbackPanel());
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());
}
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -253,10 +254,14 @@ <F extends ObjectType> ModelContext<F> 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<UserSessionManagementType> getLoggedInUsers();
List<UserSessionManagementType> getLoggedInPrincipals(Task task, OperationResult result);

/**
* Terminates specified sessions (clusterwide).
*/
void terminateSessions(TerminateSessionEvent terminateSessionEvent, Task task, OperationResult result);

SystemConfigurationType getSystemConfiguration(OperationResult parentResult) throws ObjectNotFoundException, SchemaException;

Expand Down Expand Up @@ -362,8 +367,6 @@ ExecuteCredentialResetResponseType executeCredentialsReset(PrismObject<UserType>

void refreshPrincipal(String oid) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException;

void expirePrincipals(List<String> oid);

List<RelationDefinitionType> getRelationDefinitions();

@NotNull
Expand Down
@@ -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<UserSessionManagementType> getLoggedInPrincipals(Task task, OperationResult result);
}
Expand Up @@ -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;
Expand All @@ -23,7 +24,6 @@

import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
* @author semancik
Expand All @@ -48,8 +48,7 @@ public interface UserProfileService extends MidPointPrincipalManager {
@Override
void updateUser(MidPointPrincipal principal, Collection<? extends ItemDelta<?, ?>> itemDeltas);

List<UserSessionManagementType> getAllLoggedPrincipals();

void expirePrincipals(List<String> principalsOid);
List<UserSessionManagementType> getLocalLoggedInPrincipals();

void terminateLocalSessions(TerminateSessionEvent terminateSessionEvent);
}
Expand Up @@ -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();
}
}
Expand Down
Expand Up @@ -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;
}
Expand Down
Expand Up @@ -56,25 +56,6 @@ public <O extends ObjectType> void invalidate(Class<O> 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) -> {
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;

Expand All @@ -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);
}
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -169,7 +159,7 @@ public Response listUserSession(@Context MessageContext mc) {
Response response;
try {
checkNodeAuthentication();
List<UserSessionManagementType> principals = modelInteractionService.getLoggedInUsers();
List<UserSessionManagementType> principals = userProfileService.getLocalLoggedInPrincipals();

UserSessionManagementListType list = new UserSessionManagementListType();
list.getSession().addAll(principals);
Expand Down

0 comments on commit 5d164e6

Please sign in to comment.