diff --git a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/LegendSecurityLogic.java b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/LegendSecurityLogic.java index 981808c..cd87298 100644 --- a/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/LegendSecurityLogic.java +++ b/legend-shared-pac4j/src/main/java/org/finos/legend/server/pac4j/LegendSecurityLogic.java @@ -19,6 +19,8 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import static org.finos.legend.server.pac4j.LegendRequestHandler.REDIRECT_PROTO_ATTRIBUTE; @@ -41,21 +43,21 @@ public R perform(C context, boolean isConstraintKerberosFlow = (boolean) context.getRequestAttribute(IS_CONSTRAINED_KERBEROS_FLOW).orElse(false); if (!isConstraintKerberosFlow) { - LOGGER.info("NonConstrained host, falling back to default handling"); + LOGGER.debug("NonConstrained host, falling back to default handling"); return callParentPerform(context, config, securityGrantedAccessAdapter, httpActionAdapter, clients, CommonHelper.isBlank(authorizers) ? "none" : authorizers, matchers, false, parameters); } boolean multiProfile = inputMultiProfile != null && inputMultiProfile; if (!multiProfile) { - LOGGER.info("MultiProfile turned off falling back to default handling"); + LOGGER.debug("MultiProfile turned off falling back to default handling"); return callParentPerform(context, config, securityGrantedAccessAdapter, httpActionAdapter, clients, authorizers, matchers, inputMultiProfile, parameters); } if (nonBrowserCall(context)) { - LOGGER.info("Non-browser call detected, falling back to default handling"); + LOGGER.debug("Non-browser call detected, falling back to default handling"); return callParentPerform(context, config, securityGrantedAccessAdapter, httpActionAdapter, clients, CommonHelper.isBlank(authorizers) ? "none" : authorizers, matchers, false, parameters); } - LOGGER.info("Browser call detected, using LegendSecurityLogic handling"); + LOGGER.debug("Browser call detected, using LegendSecurityLogic handling"); LOGGER.debug("url: {}", context.getFullRequestURL()); LOGGER.debug("clients: {}", clients); try @@ -127,6 +129,18 @@ private boolean isValidProfilePresent(List profiles, Client securityGrantedAccessAdapter, HttpActionAdapter httpActionAdapter, String clients, String authorizers, String matchers, Boolean inputMultiProfile, Object[] parameters) { + Set requested = getClientFinder().find(config.getClients(), context, clients).stream() + .map(Client::getName) + .collect(Collectors.toSet()); + + ProfileManager manager = getProfileManager(context); + boolean hasStaleProfile = manager.getAll(true).stream() + .anyMatch(p -> !requested.contains(p.getClientName())); + if (hasStaleProfile) + { + LOGGER.debug("Session contains profiles outside requested clients {} -> clearing session", requested); + manager.remove(true); + } LOGGER.debug("Calling parent perform method"); return super.perform(context, config, securityGrantedAccessAdapter, httpActionAdapter, clients, authorizers, matchers, inputMultiProfile, parameters); diff --git a/legend-shared-pac4j/src/test/java/org/finos/legend/server/pac4j/LegendSecurityLogicTest.java b/legend-shared-pac4j/src/test/java/org/finos/legend/server/pac4j/LegendSecurityLogicTest.java index 41ce9be..7048d14 100644 --- a/legend-shared-pac4j/src/test/java/org/finos/legend/server/pac4j/LegendSecurityLogicTest.java +++ b/legend-shared-pac4j/src/test/java/org/finos/legend/server/pac4j/LegendSecurityLogicTest.java @@ -664,6 +664,138 @@ public void testPerform_WithIndirectClient_ExpiredProfile() throws Exception } + @Test + public void testCallParentPerform_StaleSessionProfile_ClearsSession() throws Exception + { + UserProfile staleProfile = mock(UserProfile.class); + when(staleProfile.getClientName()).thenReturn("clientA"); + + ProfileManager profileManager = mock(ProfileManager.class); + when(profileManager.getAll(true)).thenReturn(Collections.singletonList(staleProfile)); + legendSecurityLogic.setProfileManagerFactory((c) -> profileManager); + + ClientFinder clientFinder = mock(ClientFinder.class); + IndirectClient clientB = mock(IndirectClient.class); + when(clientB.getName()).thenReturn("clientB"); + when(clientFinder.find(any(), any(), anyString())) + .thenReturn(Collections.singletonList(clientB)); + legendSecurityLogic.setClientFinder(clientFinder); + + // Make super.perform short-circuit to the grant-access path. + legendSecurityLogic.setMatchingChecker(matchingChecker); + when(matchingChecker.matches(any(), any(), any(), anyList())).thenReturn(false); + + Clients clients = mock(Clients.class); + when(config.getClients()).thenReturn(clients); + SecurityGrantedAccessAdapter adapter = mock(SecurityGrantedAccessAdapter.class); + + legendSecurityLogic.callParentPerform( + webContext, config, adapter, httpActionAdapter, + "clientB", "", "matchers", true, new Object[0]); + + + verify(profileManager, times(1)).remove(true); + verify(clientFinder, times(2)).find(any(), any(), anyString()); + } + + @Test + public void testCallParentPerform_MatchingSessionProfile_KeepsSession() throws Exception + { + UserProfile matchingProfile = mock(UserProfile.class); + when(matchingProfile.getClientName()).thenReturn("clientB"); + + ProfileManager profileManager = mock(ProfileManager.class); + when(profileManager.getAll(true)).thenReturn(Collections.singletonList(matchingProfile)); + legendSecurityLogic.setProfileManagerFactory((c) -> profileManager); + + ClientFinder clientFinder = mock(ClientFinder.class); + IndirectClient clientB = mock(IndirectClient.class); + when(clientB.getName()).thenReturn("clientB"); + when(clientFinder.find(any(), any(), anyString())) + .thenReturn(Collections.singletonList(clientB)); + legendSecurityLogic.setClientFinder(clientFinder); + + legendSecurityLogic.setMatchingChecker(matchingChecker); + when(matchingChecker.matches(any(), any(), any(), anyList())).thenReturn(false); + + Clients clients = mock(Clients.class); + when(config.getClients()).thenReturn(clients); + SecurityGrantedAccessAdapter adapter = mock(SecurityGrantedAccessAdapter.class); + + legendSecurityLogic.callParentPerform( + webContext, config, adapter, httpActionAdapter, + "clientB", "", "matchers", true, new Object[0]); + + verify(profileManager, never()).remove(anyBoolean()); + verify(clientFinder, times(2)).find(any(), any(), anyString()); + } + + @Test + public void testCallParentPerform_EmptySession_NoRemoval() throws Exception + { + ProfileManager profileManager = mock(ProfileManager.class); + when(profileManager.getAll(true)).thenReturn(Collections.emptyList()); + legendSecurityLogic.setProfileManagerFactory((c) -> profileManager); + + ClientFinder clientFinder = mock(ClientFinder.class); + IndirectClient clientB = mock(IndirectClient.class); + when(clientB.getName()).thenReturn("clientB"); + when(clientFinder.find(any(), any(), anyString())) + .thenReturn(Collections.singletonList(clientB)); + legendSecurityLogic.setClientFinder(clientFinder); + + legendSecurityLogic.setMatchingChecker(matchingChecker); + when(matchingChecker.matches(any(), any(), any(), anyList())).thenReturn(false); + + Clients clients = mock(Clients.class); + when(config.getClients()).thenReturn(clients); + SecurityGrantedAccessAdapter adapter = mock(SecurityGrantedAccessAdapter.class); + + legendSecurityLogic.callParentPerform( + webContext, config, adapter, httpActionAdapter, + "clientB", "", "matchers", true, new Object[0]); + + verify(profileManager, never()).remove(anyBoolean()); + verify(clientFinder, times(2)).find(any(), any(), anyString()); + } + + @Test + public void testCallParentPerform_MixedProfiles_AnyStaleClearsWholeSession() throws Exception + { + UserProfile validProfile = mock(UserProfile.class); + when(validProfile.getClientName()).thenReturn("clientB"); + UserProfile staleProfile = mock(UserProfile.class); + when(staleProfile.getClientName()).thenReturn("clientX"); + + ProfileManager profileManager = mock(ProfileManager.class); + when(profileManager.getAll(true)) + .thenReturn(java.util.Arrays.asList(validProfile, staleProfile)); + legendSecurityLogic.setProfileManagerFactory((c) -> profileManager); + + ClientFinder clientFinder = mock(ClientFinder.class); + IndirectClient clientB = mock(IndirectClient.class); + when(clientB.getName()).thenReturn("clientB"); + IndirectClient clientC = mock(IndirectClient.class); + when(clientC.getName()).thenReturn("clientC"); + when(clientFinder.find(any(), any(), anyString())) + .thenReturn(java.util.Arrays.asList(clientB, clientC)); + legendSecurityLogic.setClientFinder(clientFinder); + + legendSecurityLogic.setMatchingChecker(matchingChecker); + when(matchingChecker.matches(any(), any(), any(), anyList())).thenReturn(false); + + Clients clients = mock(Clients.class); + when(config.getClients()).thenReturn(clients); + SecurityGrantedAccessAdapter adapter = mock(SecurityGrantedAccessAdapter.class); + + legendSecurityLogic.callParentPerform( + webContext, config, adapter, httpActionAdapter, + "clientB,clientC", "", "matchers", true, new Object[0]); + + verify(profileManager, times(1)).remove(true); + verify(clientFinder, times(2)).find(any(), any(), anyString()); + } + private static class TestableLegendSecurityLogic extends LegendSecurityLogic { private HttpAction mockedRedirectResponse;