<commit_before>package stroom.security.server;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;
import com.google.gwt.user.client.Window;
import org.springframework.stereotype.Component;
import stroom.apiclients.AuthenticationServiceClient;
import stroom.auth.service.ApiException;
import stroom.security.SecurityContext;

import javax.inject.Inject;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Manages user sessions.
 *
 * Sessions here are associated with sessions in the AuthenticationService and at the User Agent.
 *
 * Because of the authentication flow, the user might leave Stroom to authenticate elsewhere,
 * or perform some other action. When they return they will have a new jSessionId,
 * which is how Stroom manages it's internal sessions. But actually this jSessionId
 * is part of the larger session. So this records the mappings of jSessionIds against
 * sessions, as well as providing recall for sessions.
 */
@Component
public class SessionManager {
    private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory.getLogger(SessionManager.class);

    SetMultimap<String, String> sessionIdToJSessionId = HashMultimap.create();
    Map<String, String> jSessionIdToSessionId = new HashMap<>();
    private SecurityContext securityContext;

    private AuthenticationServiceClient authenticationServiceClient;
    private final AuthenticationService authenticationService;

    @Inject
    public SessionManager(
            SecurityContext securityContext,
            AuthenticationService authenticationService,
            AuthenticationServiceClient authenticationServiceClient){
        this.securityContext = securityContext;
        this.authenticationService = authenticationService;
        this.authenticationServiceClient = authenticationServiceClient;
    }

    public void add(String sessionId, String jSessionId) {
        //The jSessionId might be suffixed with 'node<x>'. We don't want this.
        String plainJSessionId = jSessionId.split("\\.")[0];
        sessionIdToJSessionId.put(sessionId, plainJSessionId);
        jSessionIdToSessionId.put(plainJSessionId, sessionId);
    }

    public void forget(String sessionId){
        sessionIdToJSessionId.removeAll(sessionId);
        List<String> jSessionIdsToRemove = jSessionIdToSessionId.entrySet().stream()
                .filter(entry -> entry.getValue().equals(sessionId))
                .map(entry -> entry.getKey())
                .collect(Collectors.toList());
        jSessionIdsToRemove.forEach(jSessionIdToRemove -> jSessionIdToSessionId.remove(jSessionIdToRemove));
    }

    public String getSession(String jSessionId){
        return jSessionIdToSessionId.get(jSessionId);
    }

    public void logout() {
        // We need to let the AuthenticationService know that a logout has been initiated.
        // We don't need to call `authenticationService.logout()` here because this will
        // be called by the call to AuthenticationService.
        LOGGER.info("Logging out user.");
        String jSessionId = securityContext.getJSessionId();
        String sessionId = getSession(jSessionId);

        // This method will be called by the remote authentication service. But if
        // there's a problem with the service we still want to log the user out if we can,
        // so we'll call logout here too.
        authenticationService.logout();

        try {
            authenticationServiceClient.getAuthServiceApi().logout(sessionId);
        } catch (ApiException e) {
            LOGGER.error("Unable to log user out!", e);
        }
    }
}<commit_msg>Added comment explaining an exception/bug<commit_after>package stroom.security.server;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;
import com.google.gwt.user.client.Window;
import org.springframework.stereotype.Component;
import stroom.apiclients.AuthenticationServiceClient;
import stroom.auth.service.ApiException;
import stroom.security.SecurityContext;

import javax.inject.Inject;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Manages user sessions.
 *
 * Sessions here are associated with sessions in the AuthenticationService and at the User Agent.
 *
 * Because of the authentication flow, the user might leave Stroom to authenticate elsewhere,
 * or perform some other action. When they return they will have a new jSessionId,
 * which is how Stroom manages it's internal sessions. But actually this jSessionId
 * is part of the larger session. So this records the mappings of jSessionIds against
 * sessions, as well as providing recall for sessions.
 */
@Component
public class SessionManager {
    private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory.getLogger(SessionManager.class);

    SetMultimap<String, String> sessionIdToJSessionId = HashMultimap.create();
    Map<String, String> jSessionIdToSessionId = new HashMap<>();
    private SecurityContext securityContext;

    private AuthenticationServiceClient authenticationServiceClient;
    private final AuthenticationService authenticationService;

    @Inject
    public SessionManager(
            SecurityContext securityContext,
            AuthenticationService authenticationService,
            AuthenticationServiceClient authenticationServiceClient){
        this.securityContext = securityContext;
        this.authenticationService = authenticationService;
        this.authenticationServiceClient = authenticationServiceClient;
    }

    public void add(String sessionId, String jSessionId) {
        //The jSessionId might be suffixed with 'node<x>'. We don't want this.
        String plainJSessionId = jSessionId.split("\\.")[0];
        sessionIdToJSessionId.put(sessionId, plainJSessionId);
        jSessionIdToSessionId.put(plainJSessionId, sessionId);
    }

    public void forget(String sessionId){
        sessionIdToJSessionId.removeAll(sessionId);
        List<String> jSessionIdsToRemove = jSessionIdToSessionId.entrySet().stream()
                .filter(entry -> entry.getValue().equals(sessionId))
                .map(entry -> entry.getKey())
                .collect(Collectors.toList());
        jSessionIdsToRemove.forEach(jSessionIdToRemove -> jSessionIdToSessionId.remove(jSessionIdToRemove));
    }

    public String getSession(String jSessionId){
        return jSessionIdToSessionId.get(jSessionId);
    }

    public void logout() {
        // We need to let the AuthenticationService know that a logout has been initiated.
        // We don't need to call `authenticationService.logout()` here because this will
        // be called by the call to AuthenticationService.
        LOGGER.info("Logging out user.");
        String jSessionId = securityContext.getJSessionId();
        String sessionId = getSession(jSessionId);

        // This method will be called by the remote authentication service. But if
        // there's a problem with the service we still want to log the user out if we can,
        // so we'll call logout here too.
        authenticationService.logout();

        try {
            //TODO: FIXME: [JC 2017-10-18] This is failing! It's failing because at the point it is
            // called we have a new jSessionId, one that hasn't been registered with
            // this SessionManager. I don't know why this is happening. At the moment it
            // doesn't matter because we're completely logging out here, but when we have
            // collaborating services that need logging out this will be a problem.
            authenticationServiceClient.getAuthServiceApi().logout(sessionId);
        } catch (ApiException e) {
            LOGGER.error("Unable to log user out!", e);
        }
    }
}
{'fraction_non_alphanumeric': 0.07760588384478823, 'fraction_numerical': 0.001775297996449404, 'mean_word_length': 3.420964125560538, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '379222', 'n_tokens_mistral': 2027, 'n_tokens_neox': 1986, 'n_words': 631}