/** * Copyright 2016 StreamSets Inc. * <p> * Licensed under the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.streamsets.lib.security.http; import com.streamsets.datacollector.util.Configuration; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; import java.net.URLEncoder; import java.util.Collections; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.when; public class TestAbstractSSOService { class ForTestSSOService extends AbstractSSOService { @Override public void register(Map<String, String> attributes) { } @Override protected SSOPrincipal validateUserTokenWithSecurityService(String authToken) throws ForbiddenException { throw new ForbiddenException(Collections.emptyMap()); } @Override protected SSOPrincipal validateAppTokenWithSecurityService(String authToken, String componentId) throws ForbiddenException { throw new ForbiddenException(Collections.emptyMap()); } } @Test public void testConfiguration() { ForTestSSOService service = Mockito.spy(new ForTestSSOService()); Configuration conf = new Configuration(); service.setConfiguration(conf); Mockito .verify(service) .initializePrincipalCaches(eq(AbstractSSOService.SECURITY_SERVICE_VALIDATE_AUTH_TOKEN_FREQ_DEFAULT * 1000)); conf.set(AbstractSSOService.SECURITY_SERVICE_VALIDATE_AUTH_TOKEN_FREQ_CONFIG, 30); service.setConfiguration(conf); Mockito.verify(service).initializePrincipalCaches(eq(30 * 1000L)); Assert.assertNotNull(service.getUserPrincipalCache()); Assert.assertNotNull(service.getAppPrincipalCache()); service.setLoginPageUrl("http://foo"); Assert.assertEquals("http://foo", service.getLoginPageUrl()); service.setLogoutUrl("http://bar"); Assert.assertEquals("http://bar", service.getLogoutUrl()); } @Test public void testCreateRedirectToLoginUrl() throws Exception { ForTestSSOService service = new ForTestSSOService(); service.setConfiguration(new Configuration()); service.setLoginPageUrl("http://foo"); String initialRedirUrl = "http://foo" + "?" + SSOConstants.REQUESTED_URL_PARAM + "=" + URLEncoder.encode("http://bar", "UTF-8"); String repeatedRedirUrl = "http://foo" + "?" + SSOConstants.REQUESTED_URL_PARAM + "=" + URLEncoder.encode("http://bar", "UTF-8") + "&" + SSOConstants.REPEATED_REDIRECT_PARAM + "="; Assert.assertEquals(initialRedirUrl, service.createRedirectToLoginUrl("http://bar", false)); Assert.assertEquals(repeatedRedirUrl, service.createRedirectToLoginUrl("http://bar", true)); } @Test public void testValidateUserToken() throws Exception { ForTestSSOService service = Mockito.spy(new ForTestSSOService()); service.setConfiguration(new Configuration()); //60 sec cache //invalid, unknown Assert.assertNull(service.validateUserToken("x")); Mockito.verify(service).validateUserTokenWithSecurityService(eq("x")); //invalid, cached Mockito.reset(service); Assert.assertNull(service.validateUserToken("x")); Mockito.verify(service, Mockito.never()).validateUserTokenWithSecurityService(eq("x")); //valid, unknown SSOPrincipal principal = Mockito.mock(SSOPrincipal.class); Mockito.doReturn(principal).when(service).validateUserTokenWithSecurityService(eq("a")); Assert.assertEquals(principal, service.validateUserToken("a")); Mockito.verify(service).validateUserTokenWithSecurityService(eq("a")); //valid, cached Mockito.reset(service); Assert.assertEquals(principal, service.validateUserToken("a")); Mockito.verify(service, Mockito.never()).validateUserTokenWithSecurityService(eq("a")); service.initializePrincipalCaches(1); //1 millisec cache //valid, unknown Mockito.doReturn(principal).when(service).validateUserTokenWithSecurityService(eq("b")); Assert.assertEquals(principal, service.validateUserToken("b")); Mockito.verify(service).validateUserTokenWithSecurityService(eq("b")); // cache expired Thread.sleep(2); //valid, unknown Mockito.reset(service); Mockito.doReturn(principal).when(service).validateUserTokenWithSecurityService(eq("b")); Assert.assertEquals(principal, service.validateUserToken("b")); Mockito.verify(service).validateUserTokenWithSecurityService(eq("b")); } @Test public void testInvalidateUserToken() throws Exception { ForTestSSOService service = Mockito.spy(new ForTestSSOService()); service.setConfiguration(new Configuration()); // 60 sec cache // unknown service.invalidateUserToken("x"); Assert.assertNull(service.validateUserToken("x")); // valid, unknown Mockito .doReturn(Mockito.mock(SSOPrincipal.class)) .when(service) .validateUserTokenWithSecurityService(Mockito.eq("y")); service.invalidateUserToken("y"); Assert.assertNull(service.validateUserToken("y")); // valid, cached Mockito .doReturn(Mockito.mock(SSOPrincipal.class)) .when(service).validateUserTokenWithSecurityService(eq("z")); Assert.assertNotNull(service.validateUserToken("z")); service.invalidateUserToken("z"); Assert.assertNull(service.validateUserToken("z")); } @Test public void testValidateAppToken() throws Exception { ForTestSSOService service = Mockito.spy(new ForTestSSOService()); service.setConfiguration(new Configuration()); //60 sec cache //invalid, unknown Assert.assertNull(service.validateAppToken("x", "c")); Mockito.verify(service).validateAppTokenWithSecurityService(eq("x"), eq("c")); //invalid, cached Mockito.reset(service); Assert.assertNull(service.validateAppToken("x", "c")); Mockito.verify(service, Mockito.never()).validateAppTokenWithSecurityService(eq("x"), eq("c")); //valid, unknown SSOPrincipal principal = Mockito.mock(SSOPrincipal.class); when(principal.getPrincipalId()).thenReturn("c"); Mockito.doReturn(principal).when(service).validateAppTokenWithSecurityService(Mockito.eq("a"), Mockito.eq("c")); Assert.assertEquals(principal, service.validateAppToken("a", "c")); Mockito.verify(service).validateAppTokenWithSecurityService(eq("a"), eq("c")); //valid, cached Mockito.reset(service); Assert.assertEquals(principal, service.validateAppToken("a", "c")); Mockito.verify(service, Mockito.never()).validateAppTokenWithSecurityService(eq("a"), eq("c")); //valid, incorrect component ID Assert.assertNull(service.validateAppToken("x", "cc")); service.initializePrincipalCaches(1); //1 millisec cache //valid, unknown Mockito.doReturn(principal).when(service).validateAppTokenWithSecurityService(Mockito.eq("b"), Mockito.eq("c")); Assert.assertEquals(principal, service.validateAppToken("b", "c")); Mockito.verify(service).validateAppTokenWithSecurityService(eq("b"), eq("c")); // cache expired Thread.sleep(2); //valid, unknown Mockito.reset(service); Mockito.doReturn(principal).when(service).validateAppTokenWithSecurityService(Mockito.eq("b"), Mockito.eq("c")); Assert.assertEquals(principal, service.validateAppToken("b", "c")); Mockito.verify(service).validateAppTokenWithSecurityService(eq("b"), eq("c")); } @Test public void testInvalidateAppToken() throws Exception { ForTestSSOService service = Mockito.spy(new ForTestSSOService()); service.setConfiguration(new Configuration()); // 60 sec cache // unknown service.invalidateAppToken("x"); Assert.assertNull(service.validateAppToken("x", "c")); // valid, unknown Mockito .doReturn(Mockito.mock(SSOPrincipal.class)) .when(service) .validateAppTokenWithSecurityService(Mockito.eq("y"), Mockito.eq("c")); service.invalidateAppToken("y"); Assert.assertNull(service.validateAppToken("y", "c")); // valid, cached SSOPrincipal principal = Mockito.mock(SSOPrincipal.class); Mockito.when(principal.getPrincipalId()).thenReturn("c"); Mockito.doReturn(principal).when(service).validateAppTokenWithSecurityService(Mockito.eq("z"), Mockito.eq("c")); Assert.assertNotNull(service.validateAppToken("z", "c")); service.invalidateAppToken("z"); Assert.assertNull(service.validateAppToken("z", "c")); } @Test public void testValidateTokenValidInCache() throws Exception { AbstractSSOService service = Mockito.spy(new ForTestSSOService()); final SSOPrincipal principal = Mockito.mock(SSOPrincipal.class); PrincipalCache cache = Mockito.mock(PrincipalCache.class); Mockito.when(cache.get(eq("t"))).thenReturn(principal); Assert.assertEquals(principal, service.validate(cache, null, "t", "c", "x")); Mockito.verify(cache, Mockito.times(1)).get(eq("t")); } @Test public void testValidateTokenInvalid() throws Exception { AbstractSSOService service = Mockito.spy(new ForTestSSOService()); PrincipalCache cache = Mockito.mock(PrincipalCache.class); Mockito.when(cache.get(eq("t"))).thenReturn(null); Mockito.when(cache.isInvalid(eq("t"))).thenReturn(true); Assert.assertNull(service.validate(cache, null, "t", "c", "x")); Mockito.verify(cache, Mockito.times(1)).get(eq("t")); } @Test public void testValidateTokenValidNotInCache() throws Exception { AbstractSSOService service = Mockito.spy(new ForTestSSOService()); final SSOPrincipal principal = Mockito.mock(SSOPrincipal.class); // token not in cache, no lock, valid token PrincipalCache cache = Mockito.mock(PrincipalCache.class); Mockito.when(cache.get(eq("t"))).thenReturn(null); Mockito.when(cache.isInvalid(eq("t"))).thenReturn(false); ConcurrentMap<String, Object> lockMap = service.getLockMap(); lockMap = Mockito.spy(lockMap); Mockito.doReturn(lockMap).when(service).getLockMap(); Callable<SSOPrincipal> callable = new Callable<SSOPrincipal>() { @Override public SSOPrincipal call() throws Exception { return principal; } }; Assert.assertEquals(principal, service.validate(cache, callable, "t", "c", "x")); Mockito.verify(cache, Mockito.times(2)).get(eq("t")); Mockito.verify(cache, Mockito.times(1)).isInvalid(eq("t")); Mockito.verify(cache, Mockito.times(1)).put(eq("t"), eq(principal)); Mockito.verify(cache, Mockito.times(0)).invalidate(eq("t")); Mockito.verify(lockMap, Mockito.times(1)).putIfAbsent(eq("t"), Mockito.any()); Mockito.verify(lockMap, Mockito.times(1)).remove(eq("t")); } @Test public void testValidateTokenInvalidNotInCache() throws Exception { AbstractSSOService service = Mockito.spy(new ForTestSSOService()); PrincipalCache cache = Mockito.mock(PrincipalCache.class); Mockito.when(cache.get(eq("t"))).thenReturn(null); Mockito.when(cache.isInvalid(eq("t"))).thenReturn(false); ConcurrentMap<String, Object> lockMap = service.getLockMap(); lockMap = Mockito.spy(lockMap); Mockito.doReturn(lockMap).when(service).getLockMap(); Callable<SSOPrincipal> callable = new Callable<SSOPrincipal>() { @Override public SSOPrincipal call() throws Exception { throw new ForbiddenException(Collections.emptyMap()); } }; Assert.assertNull(service.validate(cache, callable, "t", "c", "x")); Mockito.verify(cache, Mockito.times(2)).get(eq("t")); Mockito.verify(cache, Mockito.times(1)).isInvalid(eq("t")); Mockito.verify(cache, Mockito.times(0)).put(eq("t"), Mockito.any(SSOPrincipal.class)); Mockito.verify(cache, Mockito.times(1)).invalidate(eq("t")); Mockito.verify(lockMap, Mockito.times(1)).putIfAbsent(eq("t"), Mockito.any()); Mockito.verify(lockMap, Mockito.times(1)).remove(eq("t")); } @Test public void testValidateSerialization() throws Exception { final AbstractSSOService service = Mockito.spy(new ForTestSSOService()); final CountDownLatch ready = new CountDownLatch(2); final CountDownLatch done = new CountDownLatch(1); final PrincipalCache cache = new PrincipalCache(1000, 1000); final SSOPrincipal principal = Mockito.mock(SSOPrincipal.class); final Callable<SSOPrincipal> goThruCallable = new Callable<SSOPrincipal>() { @Override public SSOPrincipal call() throws Exception { done.countDown(); Thread.sleep(100); return principal; } }; final Callable<SSOPrincipal> neverCalledCallable = new Callable<SSOPrincipal>() { @Override public SSOPrincipal call() throws Exception { Assert.fail(); return null; } }; Thread t2 = new Thread() { @Override public void run() { ready.countDown(); try { done.await(); } catch (InterruptedException ex) { } Assert.assertEquals(principal, service.validate(cache, neverCalledCallable, "t", "c", "x")); } }; t2.start(); Thread t3 = new Thread() { @Override public void run() { ready.countDown(); try { done.await(); } catch (InterruptedException ex) { } Assert.assertEquals(principal, service.validate(cache, neverCalledCallable, "t", "c", "x")); } }; t3.start(); ready.await(); Assert.assertEquals(principal, service.validate(cache, goThruCallable, "t", "c", "x")); done.await(); t2.join(); t3.join(); } @Test public void testClearCaches() throws Exception { AbstractSSOService service = Mockito.spy(new ForTestSSOService()); PrincipalCache userCache = Mockito.mock(PrincipalCache.class); PrincipalCache appCache = Mockito.mock(PrincipalCache.class); Mockito.doReturn(userCache).when(service).getUserPrincipalCache(); Mockito.doReturn(appCache).when(service).getAppPrincipalCache(); Mockito.verify(userCache, Mockito.never()).clear(); Mockito.verify(appCache, Mockito.never()).clear(); service.clearCaches(); Mockito.verify(userCache, Mockito.times(1)).clear(); Mockito.verify(appCache, Mockito.times(1)).clear(); } }