/* * $Id$ * * Copyright 2007-2014 Glencoe Software, Inc. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ package ome.server.utests.sessions; import java.util.Arrays; import java.util.Collections; import java.util.List; import net.sf.ehcache.CacheManager; import ome.api.local.LocalAdmin; import ome.api.local.LocalQuery; import ome.api.local.LocalUpdate; import ome.conditions.AuthenticationException; import ome.conditions.RemovedSessionException; import ome.conditions.SecurityViolation; import ome.conditions.SessionException; import ome.conditions.SessionTimeoutException; import ome.model.enums.EventType; import ome.model.internal.Details; import ome.model.internal.Permissions; import ome.model.internal.Permissions.Right; import ome.model.internal.Permissions.Role; import ome.model.meta.Experimenter; import ome.model.meta.ExperimenterGroup; import ome.model.meta.Node; import ome.model.meta.Session; import ome.security.basic.CurrentDetails; import ome.server.utests.DummyExecutor; import ome.services.sessions.SessionContext; import ome.services.sessions.SessionContextImpl; import ome.services.sessions.SessionManagerImpl; import ome.services.sessions.state.SessionCache; import ome.services.sessions.stats.CounterFactory; import ome.services.sessions.stats.SessionStats; import ome.services.util.Executor; import ome.system.OmeroContext; import ome.system.Principal; import ome.system.Roles; import ome.testing.MockServiceFactory; import org.jmock.Mock; import org.jmock.MockObjectTestCase; import org.jmock.core.Constraint; import org.jmock.core.Invocation; import org.jmock.core.Stub; import org.jmock.core.constraint.StringContains; import org.springframework.context.event.ApplicationEventMulticaster; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; /** * @author Josh Moore, josh at glencoesoftware.com * @since 3.0-Beta2 */ public class SessMgrUnitTest extends MockObjectTestCase { private final class DoWorkStub implements Stub { public Object invoke(Invocation i) throws Throwable { Executor.Work work = (Executor.Work) i.parameterValues.get(1); return work.doWork(null, sf); } public StringBuffer describeTo(StringBuffer sb) { sb.append("calls doWork on work"); return sb; } } private final class TestManager extends SessionManagerImpl { Session doDefine() { Session s = new Session(); define(s, "uuid", "message", System.currentTimeMillis(), defaultTimeToIdle, defaultTimeToLive, "Test", "Test", "127.0.0.1"); ExperimenterGroup group = new ExperimenterGroup(); group.getDetails().setPermissions(Permissions.COLLAB_READLINK); s.getDetails().setGroup(group); return s; } } private OmeroContext ctx; private ApplicationEventMulticaster multicaster; private final MockServiceFactory sf = new MockServiceFactory(); private TestManager mgr; private SessionCache cache; private org.hibernate.Session s; private Mock sMock; // State final Long TTL = 300 * 1000L; final Long TTI = 100 * 1000L; Session session = new Session(); Principal principal = new Principal("u", "g", "Test"); String credentials = "password"; Experimenter user = new Experimenter(1L, true); ExperimenterGroup group = new ExperimenterGroup(1L, true); List<Long> m_ids = Collections.singletonList(1L); List<Long> l_ids = Collections.singletonList(1L); List<String> userRoles = Collections.singletonList("single"); @BeforeTest public void config() { ctx = new OmeroContext("classpath:ome/services/messaging.xml"); multicaster = (ApplicationEventMulticaster) ctx .getBean("applicationEventMulticaster"); sf.mockAdmin = mock(LocalAdmin.class); sf.mockUpdate = mock(LocalUpdate.class); sf.mockQuery = mock(LocalQuery.class); sMock = mock(org.hibernate.Session.class); s = (org.hibernate.Session) sMock.proxy(); } @BeforeMethod public void setup() { cache = new SessionCache(); cache.setCacheManager(CacheManager.getInstance()); cache.setApplicationContext(ctx); mgr = new TestManager(); mgr.setCounterFactory(new CounterFactory()); mgr.setRoles(new Roles()); mgr.setSessionCache(cache); mgr.setPrincipalHolder(new CurrentDetails()); mgr.setExecutor(new DummyExecutor(s, sf)); mgr.setApplicationContext(ctx); mgr.setDefaultTimeToIdle(TTI); mgr.setDefaultTimeToLive(TTL); session = mgr.doDefine(); session.setId(1L); user.setOmeName(principal.getName()); user.setLdap(false); } @AfterMethod public void cleanupMocks() throws Exception { tearDown(); } @Test public void testCreateNewSession() throws Exception { /* * user logs in with: - non-sessioned principal / !E session => * createSession - non-sessioned principal / E session => joinSession - * sessionedPrincipal => hasSession() != null, then ok */ prepareForCreateSession(); assert session == mgr.createWithAgent(principal, credentials, "Test", "127.0.0.1"); assertNotNull(session); assertNotNull(session.getUuid()); } @Test public void testThatPermissionsAreAlreadyNonReadable() throws Exception { testCreateNewSession(); Permissions p = session.getDetails().getPermissions(); assertFalse(p.isGranted(Role.GROUP, Right.READ)); assertFalse(p.isGranted(Role.GROUP, Right.WRITE)); assertFalse(p.isGranted(Role.WORLD, Right.READ)); assertFalse(p.isGranted(Role.WORLD, Right.WRITE)); } @Test public void testThatDefaultTypeAreAssigned() throws Exception { testCreateNewSession(); assertNotNull(session.getDefaultEventType()); } @Test public void testThatStartedTimeIsSet() throws Exception { testCreateNewSession(); assertNotNull(session.getStarted()); assertTrue(session.getStarted().getTime() <= System.currentTimeMillis()); } @Test public void testThatCreatedSessionIsFindable() throws Exception { testCreateNewSession(); // now user has an active session, so basic security wiring will // allow method executions assertNotNull(mgr.find(session.getUuid())); } @Test public void testThatCreatedSessionIsUpdateable() throws Exception { Session rv = new Session(); testCreateNewSession(); session.setDefaultEventType("somethingnew"); sf.mockUpdate.expects(once()).method("saveAndReturnObject").will( returnValue(rv)); prepareForCreateSession(); Session test = mgr.update(session); assertFalse(test == rv); // insists that a copy is performed } @Test(expectedExceptions = SessionException.class) public void testThatNonexteantSessionIsNOTUpdateable() throws Exception { session.setUuid("DoesNotExist"); Session test = mgr.update(session); } @Test public void testThatCopiesHaveAllTheRightFields() throws Exception { testCreateNewSession(); Session copy = mgr.copy(session); assertFalse(copy == session); assertNotNull(copy.getId()); assertNotNull(copy.getStarted()); assertNotNull(copy.getUuid()); assertNotNull(copy.getDefaultEventType()); assertNotNull(copy.getDetails().getPermissions()); } @Test public void testThatUpdateProperlyHandlesDetails() throws Exception { testCreateNewSession(); Session updated = new Session(); updated.getDetails().copy(Details.create()); // As close to nulll as // poss } @Test public void testThatTimedOutSessionsAreMarkedAsSuch() throws Exception { // Create a session cache which checks frequently for stale keys cache = new SessionCache(); cache.setUpdateInterval(10); cache.setCacheManager(CacheManager.getInstance()); cache.setApplicationContext(ctx); prepareForCreateSession(); Session s1 = mgr.createWithAgent(principal, credentials, "Test", "127.0.0.1"); s1.setTimeToIdle(2000L); mgr.update(s1); Thread.sleep(3000L); try { mgr.find(s1.getUuid()); fail("This should throw on the lookup and not before"); } catch (SessionTimeoutException ste) { // ok } try { assertNull(mgr.find(s1.getUuid())); fail("This should also throw"); } catch (RemovedSessionException rse) { // ok } catch (SessionTimeoutException ste) { // ok; still possible. } } @Test public void testThatManagerCanCloseASession() throws Exception { testCreateNewSession(); Constraint closedSession = new Constraint() { public boolean eval(Object arg0) { Session s = (Session) arg0; return (System.currentTimeMillis() - s.getClosed().getTime()) < 1000L; } public StringBuffer describeTo(StringBuffer arg0) { return arg0.append("closed time was less than 1 sec ago"); } }; sf.mockUpdate.expects(once()).method("saveObject").with(closedSession); mgr.close(session.getUuid()); try { mgr.find(session.getUuid()); fail("Should throw"); } catch (RemovedSessionException rse) { // ok } } @Test public void testThatManagerHandleAnExceptionOnClose() throws Exception { testCreateNewSession(); Constraint closedSession = new Constraint() { public boolean eval(Object arg0) { Session s = (Session) arg0; return (System.currentTimeMillis() - s.getClosed().getTime()) < 1000L; } public StringBuffer describeTo(StringBuffer arg0) { return arg0.append("closed time was less than 1 sec ago"); } }; sf.mockUpdate.expects(once()).method("saveObject").with(closedSession); mgr.close(session.getUuid()); try { mgr.find(session.getUuid()); fail("oops"); } catch (RemovedSessionException rse) { // ok } } @Test public void testThatManagerKeepsUpWithRolesPerSession() throws Exception { testCreateNewSession(); List<String> roles = mgr.getUserRoles(session.getUuid()); assertNotNull(roles); assertTrue(roles.size() > 0); } @Test public void testReplacesNullGroupAndType() throws Exception { prepareForCreateSession(); sf.mockAdmin.expects(atLeastOnce()).method("getDefaultGroup") .will(returnValue(group)); Session session = mgr.createWithAgent(new Principal("fake", null, null), credentials, "Test", "127.0.0.1"); assertNotNull(session.getDefaultEventType()); assertNotNull(session.getDetails().getGroup()); } void prepareForCreateSession() { sf.mockAdmin.expects(atLeastOnce()).method("userProxy") .will(returnValue(user)); sf.mockAdmin.expects(atLeastOnce()).method("groupProxy").will( returnValue(group)); sf.mockAdmin.expects(atLeastOnce()).method("getMemberOfGroupIds").will( returnValue(m_ids)); sf.mockAdmin.expects(atLeastOnce()).method("getLeaderOfGroupIds").will( returnValue(l_ids)); sf.mockAdmin.expects(atLeastOnce()).method("getUserRoles").will( returnValue(userRoles)); sf.mockAdmin.expects(once()).method("checkPassword").will( returnValue(true)); // execute lookup user sf.mockQuery.expects(atLeastOnce()).method("findByQuery") .with(new StringContains("Session"), ANYTHING) .will(returnValue(session)); sf.mockQuery.expects(once()).method("projection").will( returnValue(Arrays.asList((Object)new Object[]{123L}))); EventType test = new EventType(0L, true); test.setValue("test"); sf.mockTypes.expects(atLeastOnce()).method("getEnumeration").will( returnValue(test)); sf.mockQuery.expects(atLeastOnce()).method("findByQuery") .with(new StringContains("Node"), ANYTHING) .will(returnValue(new Node())); sf.mockUpdate.expects(atLeastOnce()).method("saveAndReturnObject") .will(returnValue(session)); } @Test(expectedExceptions = AuthenticationException.class) public void testCreateSessionFailsAUEOnNullPrincipal() throws Exception { sf.mockAdmin.expects(once()).method("checkPassword").will( returnValue(false)); mgr.createWithAgent(null, "password", "Test", "127.0.0.1"); } @Test(expectedExceptions = AuthenticationException.class) public void testCreateSessionFailsAUEOnNullOmeName() throws Exception { sf.mockAdmin.expects(once()).method("checkPassword").will( returnValue(false)); mgr.createWithAgent(new Principal(null, null, null), "password", "Test", "127.0.0.1"); } @Test(expectedExceptions = AuthenticationException.class) public void testCreateSessionFailsSV() throws Exception { sf.mockAdmin.expects(once()).method("checkPassword").will( returnValue(false)); mgr.createWithAgent(principal, "password", "Test", "127.0.0.1"); } @Test public void testChecksForDefaultGroupsOnCreation() throws Exception { prepareForCreateSession(); sf.mockAdmin.expects(once()).method("getDefaultGroup").will( returnValue(group)); sf.mockQuery.expects(once()).method("findAllByQuery").will( returnValue(Collections.EMPTY_LIST)); mgr.createWithAgent(new Principal("user", "user", "User"), "user", "Test", "127.0.0.1"); } @Test public void testReferenceCounting() throws Exception { testCreateNewSession(); String uuid = session.getUuid(); SessionContext ctx = cache.getSessionContext(uuid); assertEquals(1, ctx.count().get()); assertNull(ctx.getSession().getClosed()); mgr.createWithAgent(new Principal(uuid), "Test", "127.0.0.1"); assertEquals(2, ctx.count().get()); assertNull(ctx.getSession().getClosed()); mgr.close(uuid); assertEquals(1, ctx.count().get()); assertNull(ctx.getSession().getClosed()); prepareForCreateSession(); mgr.close(uuid); assertEquals(0, ctx.count().get()); // Closing the session is now done asynchronously. // Instead, let's make sure it's removed from the // cache // assertNotNull(ctx.getSession().getClosed()); try { cache.getSessionContext(uuid); fail(uuid + " not removed"); } catch (RemovedSessionException rse) { // ok } } // Timeouts @Test public void testTimeoutDefaults() throws Exception { testCreateNewSession(); SessionContext ctx = cache .getSessionContext(session.getUuid()); assertEquals(TTL, ctx.getSession().getTimeToLive()); assertEquals(TTI, ctx.getSession().getTimeToIdle()); } @Test public void testTimeoutUpdatesValid() throws Exception { testTimeoutDefaults(); SessionContext ctx = cache .getSessionContext(session.getUuid()); Session s = mgr.copy(ctx.getSession()); s.setTimeToLive(300L); s.setTimeToIdle(100L); s = mgr.update(s); assertEquals(new Long(300L), s.getTimeToLive()); assertEquals(new Long(100L), s.getTimeToIdle()); // For this first test we want to also verify that // the values in the session context are the same as the // returned values ctx = cache.getSessionContext(session.getUuid()); assertEquals(new Long(300L), ctx.getSession().getTimeToLive()); assertEquals(new Long(100L), ctx.getSession().getTimeToIdle()); } @Test(expectedExceptions = SecurityViolation.class) public void testTimeoutUpdatesTTLNotZero() throws Exception { testTimeoutDefaults(); SessionContext ctx = cache .getSessionContext(session.getUuid()); Session s = mgr.copy(ctx.getSession()); s.setTimeToLive(0L); s.setTimeToIdle(100L); s = mgr.update(s); } @Test(expectedExceptions = SecurityViolation.class) public void testTimeoutUpdatesTTINotZero() throws Exception { testTimeoutDefaults(); SessionContext ctx = cache .getSessionContext(session.getUuid()); Session s = mgr.copy(ctx.getSession()); s.setTimeToLive(100L); s.setTimeToIdle(0L); s = mgr.update(s); } @Test public void testTimeoutUpdatesTooBig() throws Exception { testTimeoutDefaults(); SessionContext ctx = cache .getSessionContext(session.getUuid()); Session s = mgr.copy(ctx.getSession()); s.setTimeToLive(Long.MAX_VALUE); s.setTimeToIdle(Long.MAX_VALUE); s = mgr.update(s); assertEquals(3000000, s.getTimeToLive().longValue()); assertEquals(1000000, s.getTimeToIdle().longValue()); } @Test(groups = {"ticket:2804", "broken"}) public void testSessionShouldNotBeReapedDuringMethodExceution() throws Exception { testCreateNewSession(); final String uuid = session.getUuid(); final SessionContext ctx = cache.getSessionContext(uuid); final SessionStats stats = ctx.stats(); // Check reaping while user is running a method stats.methodIn(); mgr.close(uuid); assertNotNull(cache.getSessionContext(uuid)); // Try to start a new method. // fail("NYI"); // Checked reaping when user is not running a method stats.methodOut(); mgr.close(uuid); assertNull(cache.getSessionContext(uuid)); } @Test(groups = {"ticket:2803", "ticket:2804"}) public void testCopyingReferenceCounts() { SessionContextImpl s1 = new SessionContextImpl( this.session, l_ids, m_ids, userRoles, null, null); assertEquals(0, s1.count().get()); assertEquals(1, s1.count().increment()); assertEquals(2, s1.count().increment()); assertEquals(2, s1.count().get()); assertEquals(1, s1.count().decrement()); SessionContextImpl s2 = new SessionContextImpl( this.session, l_ids, m_ids, userRoles, null, s1); assertEquals(1, s2.count().get()); assertEquals(2, s2.count().increment()); assertEquals(2, s1.count().get()); } }