package vnet.sms.gateway.nettysupport.login.incoming; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import org.jboss.netty.channel.ChannelEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.util.HashedWheelTimer; import org.junit.Test; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import vnet.sms.common.messages.LoginRequest; import vnet.sms.common.messages.PingRequest; import vnet.sms.common.wme.acknowledge.SendLoginRequestAckEvent; import vnet.sms.common.wme.acknowledge.SendLoginRequestNackEvent; import vnet.sms.common.wme.receive.ReceivedPingRequestEvent; import vnet.sms.gateway.nettysupport.MessageProcessingContext; import vnet.sms.gateway.nettysupport.test.ObjectSerializationTransportProtocolAdaptingUpstreamChannelHandler; import vnet.sms.gateway.nettytest.embedded.ChannelPipelineEmbedder; import vnet.sms.gateway.nettytest.embedded.DefaultChannelPipelineEmbedder; import vnet.sms.gateway.nettytest.embedded.MessageEventFilters; import vnet.sms.gateway.nettytest.embedded.TimedFuture; import com.google.common.base.Predicate; public class IncomingLoginRequestsChannelHandlerTest { @Test(expected = IllegalArgumentException.class) public final void assertThatConstructorRejectsNullAuthenticationManager() { new IncomingLoginRequestsChannelHandler<Long>(null, 10, new HashedWheelTimer()); } @Test public final void assertThatLoginChannelHandlerSendsLoginRequestAcceptedEventDownstreamIfLoginSucceeds() throws Throwable { final AuthenticationManager acceptAll = new AuthenticationManager() { @Override public Authentication authenticate( final Authentication authentication) throws AuthenticationException { return new TestingAuthenticationToken( authentication.getPrincipal(), authentication.getCredentials(), "test-role"); } }; final IncomingLoginRequestsChannelHandler<Integer> objectUnderTest = new IncomingLoginRequestsChannelHandler<Integer>( acceptAll, 10, new HashedWheelTimer()); final ChannelPipelineEmbedder embeddedPipeline = new DefaultChannelPipelineEmbedder( new ObjectSerializationTransportProtocolAdaptingUpstreamChannelHandler(), objectUnderTest); embeddedPipeline.connectChannel(); embeddedPipeline .receive(new LoginRequest( "assertThatLoginChannelHandlerSendsLoginRequestAcceptedEventDownstreamIfLoginSucceeds", "secret")); final MessageEvent sentReply = embeddedPipeline .downstreamMessageEvents().nextMessageEvent(); assertNotNull( "IncomingLoginRequestsChannelHandler did not send a reply after successful login", sentReply); assertEquals( "IncomingLoginRequestsChannelHandler sent unexpected reply after successful login", SendLoginRequestAckEvent.class, sentReply.getClass()); } @Test public final void assertThatLoginChannelHandlerSendsLoginRequestRejectedEventDownstreamIfLoginThrowsBadCredentialsException() throws Throwable { final int negativeResponseDelayMillis = 200; final long confidenceIntervalMillis = 100L; final AuthenticationManager rejectAll = new AuthenticationManager() { @Override public Authentication authenticate( final Authentication authentication) throws AuthenticationException { throw new BadCredentialsException("Bad credentials"); } }; final IncomingLoginRequestsChannelHandler<Integer> objectUnderTest = new IncomingLoginRequestsChannelHandler<Integer>( rejectAll, negativeResponseDelayMillis, new HashedWheelTimer()); final ChannelPipelineEmbedder embeddedPipeline = new DefaultChannelPipelineEmbedder( new ObjectSerializationTransportProtocolAdaptingUpstreamChannelHandler(), objectUnderTest); embeddedPipeline.connectChannel(); final TimedFuture<MessageEvent> loginRequestNack = embeddedPipeline .downstreamMessageEvents().timedWaitForMatchingMessageEvent( new Predicate<MessageEvent>() { @Override public boolean apply(final MessageEvent input) { return SendLoginRequestNackEvent.class .isInstance(input); } }); embeddedPipeline .receive(new LoginRequest( "assertThatLoginChannelHandlerSendsLoginRequestRejectedEventDownstreamIfLoginThrowsBadCredentialsException", "secret")); final TimedFuture.Value<MessageEvent> sentReplyValue = loginRequestNack .get(negativeResponseDelayMillis + 100L, MILLISECONDS); final MessageEvent sentReply = sentReplyValue.get(); final long elapsedDurationMillis = sentReplyValue .elapsedDurationMillis(); assertTrue( "IncomingLoginRequestsChannelHandler did not send a reply after rejected login", (elapsedDurationMillis > negativeResponseDelayMillis - confidenceIntervalMillis) && (elapsedDurationMillis < negativeResponseDelayMillis + confidenceIntervalMillis)); assertEquals( "IncomingLoginRequestsChannelHandler sent unexpected reply after rejected login", SendLoginRequestNackEvent.class, sentReply.getClass()); } @Test public final void assertThatLoginChannelHandlerDelaysResponseForConfiguredNumberOfMillisecondsIfLoginRequestFails() throws Throwable { final int negativeResponseDelayMillis = 500; final AuthenticationManager rejectAll = new AuthenticationManager() { @Override public Authentication authenticate( final Authentication authentication) throws AuthenticationException { throw new BadCredentialsException("Bad credentials"); } }; final IncomingLoginRequestsChannelHandler<Integer> objectUnderTest = new IncomingLoginRequestsChannelHandler<Integer>( rejectAll, negativeResponseDelayMillis, new HashedWheelTimer()); final ChannelPipelineEmbedder embeddedPipeline = new DefaultChannelPipelineEmbedder( new ObjectSerializationTransportProtocolAdaptingUpstreamChannelHandler(), objectUnderTest); embeddedPipeline.connectChannel(); embeddedPipeline .receive(new LoginRequest( "assertThatLoginChannelHandlerDelaysResponseForConfiguredNumberOfMillisecondsIfLoginRequestFails", "secret")); assertNull( "IncomingLoginRequestsChannelHandler did NOT delay response to failed login attempt", embeddedPipeline.downstreamMessageEvents().nextMessageEvent()); Thread.sleep(negativeResponseDelayMillis + 200); assertNotNull( "IncomingLoginRequestsChannelHandler did not send a response to failed login attempt after delay period has elapsed", embeddedPipeline.downstreamMessageEvents().nextMessageEvent()); } @Test public final void assertThatLoginChannelHandlerSendsNonLoginMessageReceivedOnUnauthenticatedChannelEventDownstreamIfReceivingANonLoginMessageOnUnauthenticatedChannel() throws Throwable { final AuthenticationManager rejectAll = new AuthenticationManager() { @Override public Authentication authenticate( final Authentication authentication) throws AuthenticationException { throw new BadCredentialsException("Bad credentials"); } }; final IncomingLoginRequestsChannelHandler<Integer> objectUnderTest = new IncomingLoginRequestsChannelHandler<Integer>( rejectAll, 10, new HashedWheelTimer()); final ChannelPipelineEmbedder embeddedPipeline = new DefaultChannelPipelineEmbedder( new ObjectSerializationTransportProtocolAdaptingUpstreamChannelHandler(), objectUnderTest); embeddedPipeline.connectChannel(); embeddedPipeline.receive(new PingRequest()); final MessageEvent sentReply = embeddedPipeline .downstreamMessageEvents().nextMessageEvent(); assertNotNull( "IncomingLoginRequestsChannelHandler did not send a reply after receiving non-login message on unauthenticated channel", sentReply); assertEquals( "IncomingLoginRequestsChannelHandler sent unexpected reply after receiving non-login message on unauthenticated channel", NonLoginMessageReceivedOnUnauthenticatedChannelEvent.class, sentReply.getClass()); } @Test public final void assertThatLoginChannelHandlerSendsNonLoginRequestsReceivedOnAnAuthenticatedChannelFurtherUpstream() throws Throwable { final AuthenticationManager acceptAll = new AuthenticationManager() { @Override public Authentication authenticate( final Authentication authentication) throws AuthenticationException { return new TestingAuthenticationToken( authentication.getPrincipal(), authentication.getCredentials(), "test-role"); } }; final IncomingLoginRequestsChannelHandler<Integer> objectUnderTest = new IncomingLoginRequestsChannelHandler<Integer>( acceptAll, 10, new HashedWheelTimer()); final ChannelPipelineEmbedder embeddedPipeline = new DefaultChannelPipelineEmbedder( new ObjectSerializationTransportProtocolAdaptingUpstreamChannelHandler(), objectUnderTest); embeddedPipeline.connectChannel(); embeddedPipeline .receive(new LoginRequest( "assertThatLoginChannelHandlerSendsLoginRequestAcceptedEventDownstreamIfLoginSucceeds", "secret")); embeddedPipeline.receive(new PingRequest()); final MessageEvent propagatedMessage = embeddedPipeline .upstreamMessageEvents().nextMatchingMessageEvent( MessageEventFilters .ofType(ReceivedPingRequestEvent.class)); assertNotNull( "IncomingLoginRequestsChannelHandler did not propagate non-login message received on authenticated channel", propagatedMessage); assertEquals( "IncomingLoginRequestsChannelHandler propagated unexpected message after receiving non-login message on authenticated channel", ReceivedPingRequestEvent.class, propagatedMessage.getClass()); } @Test public final void assertThatLoginChannelHandlerSendsChannelSuccessfullyAuthenticatedEventUpstreamIfLoginSucceeds() throws Throwable { final AuthenticationManager acceptAll = new AuthenticationManager() { @Override public Authentication authenticate( final Authentication authentication) throws AuthenticationException { return new TestingAuthenticationToken( authentication.getPrincipal(), authentication.getCredentials(), "test-role"); } }; final IncomingLoginRequestsChannelHandler<Integer> objectUnderTest = new IncomingLoginRequestsChannelHandler<Integer>( acceptAll, 10, new HashedWheelTimer()); final ChannelPipelineEmbedder embeddedPipeline = new DefaultChannelPipelineEmbedder( new ObjectSerializationTransportProtocolAdaptingUpstreamChannelHandler(), objectUnderTest); embeddedPipeline.connectChannel(); embeddedPipeline .receive(new LoginRequest( "assertThatLoginChannelHandlerSendsChannelSuccessfullyAuthenticatedEventUpstreamIfLoginSucceeds", "secret")); final ChannelEvent upstreamChannelEvent = embeddedPipeline .upstreamChannelEvents().nextMatchingChannelEvent( new Predicate<ChannelEvent>() { @Override public boolean apply(final ChannelEvent event) { return event instanceof ChannelSuccessfullyAuthenticatedEvent; } }); assertNotNull( "IncomingLoginRequestsChannelHandler did not send expected " + ChannelSuccessfullyAuthenticatedEvent.class.getName() + " upstream after successful login", upstreamChannelEvent); } @Test public final void assertThatLoginChannelHandlerSendsChannelAuthenticationFailedEventUpstreamIfLoginFailes() throws Throwable { final AuthenticationManager rejectAll = new AuthenticationManager() { @Override public Authentication authenticate( final Authentication authentication) throws AuthenticationException { throw new BadCredentialsException("Bad credentials"); } }; final IncomingLoginRequestsChannelHandler<Integer> objectUnderTest = new IncomingLoginRequestsChannelHandler<Integer>( rejectAll, 10, new HashedWheelTimer()); final ChannelPipelineEmbedder embeddedPipeline = new DefaultChannelPipelineEmbedder( new ObjectSerializationTransportProtocolAdaptingUpstreamChannelHandler(), objectUnderTest); embeddedPipeline.connectChannel(); embeddedPipeline .receive(new LoginRequest( "assertThatLoginChannelHandlerSendsChannelAuthenticationFailedEventUpstreamIfLoginFailes", "secret")); final ChannelEvent upstreamChannelEvent = embeddedPipeline .upstreamChannelEvents().nextMatchingChannelEvent( new Predicate<ChannelEvent>() { @Override public boolean apply(final ChannelEvent event) { return event instanceof ChannelAuthenticationFailedEvent; } }); assertNotNull( "IncomingLoginRequestsChannelHandler did not send expected " + ChannelAuthenticationFailedEvent.class.getName() + " upstream after failed login", upstreamChannelEvent); } @Test public final void assertThatLoginChannelHandlerRemovesAuthenticatedUserFromMDCAfterReturning() throws Throwable { final AuthenticationManager acceptAll = new AuthenticationManager() { @Override public Authentication authenticate( final Authentication authentication) throws AuthenticationException { return new TestingAuthenticationToken( authentication.getPrincipal(), authentication.getCredentials(), "test-role"); } }; final IncomingLoginRequestsChannelHandler<Integer> objectUnderTest = new IncomingLoginRequestsChannelHandler<Integer>( acceptAll, 10, new HashedWheelTimer()); final ChannelPipelineEmbedder embeddedPipeline = new DefaultChannelPipelineEmbedder( new ObjectSerializationTransportProtocolAdaptingUpstreamChannelHandler(), objectUnderTest); embeddedPipeline.connectChannel(); final LoginRequest loginRequest = new LoginRequest( "assertThatLoginChannelHandlerRemovesAuthenticatedUserFromMDCAfterReturning", "secret"); embeddedPipeline.receive(loginRequest); final String currentUserInMdc = MessageProcessingContext.INSTANCE .currentUserName(); assertNull( "IncomingLoginRequestsChannelHandler did not remove authenticated user from MDC after returning", currentUserInMdc); } }