/** * Copyright (c) Codice Foundation * <p/> * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation, either version 3 of the * License, or any later version. * <p/> * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. */ package org.codice.ddf.security; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.not; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; import static org.powermock.api.mockito.PowerMockito.mockStatic; import static org.powermock.api.mockito.PowerMockito.verifyStatic; import java.lang.reflect.InvocationTargetException; import java.net.URISyntaxException; import java.security.PrivilegedAction; import java.util.concurrent.Callable; import org.apache.cxf.ws.security.tokenstore.SecurityToken; import org.apache.shiro.SecurityUtils; import org.apache.shiro.UnavailableSecurityManagerException; import org.apache.shiro.subject.ExecutionException; import org.apache.shiro.subject.PrincipalCollection; import org.codice.ddf.security.common.Security; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceReference; import org.osgi.service.cm.Configuration; import org.osgi.service.cm.ConfigurationAdmin; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.rule.PowerMockRule; import ddf.security.Subject; import ddf.security.assertion.SecurityAssertion; import ddf.security.common.audit.SecurityLogger; import ddf.security.service.SecurityManager; import ddf.security.service.SecurityServiceException; @PrepareForTest({FrameworkUtil.class, SecurityUtils.class, SecurityLogger.class}) public class SecurityTest { @Rule public PowerMockRule rule = new PowerMockRule(); private Security security; @Mock private org.apache.shiro.subject.Subject shiroSubject; @Mock private Subject systemSubject; @Mock private Callable<String> callable; @Before public void setup() throws URISyntaxException { System.setProperty("karaf.local.roles", "admin,local"); initMocks(this); mockStatic(SecurityUtils.class); mockStatic(FrameworkUtil.class); mockStatic(SecurityLogger.class); when(shiroSubject.execute(callable)).thenReturn("Success!"); System.setProperty("javax.net.ssl.keyStoreType", "JKS"); System.setProperty("javax.net.ssl.keyStore", getClass().getResource("/secureKeystore.jks") .toURI() .getPath()); System.setProperty("javax.net.ssl.keyStorePassword", "password"); System.setProperty("ddf.home", "/ddf/home"); System.setProperty("org.codice.ddf.system.hostname", "localhost"); security = Security.getInstance(); } @Test public void testGetSubjectNoSecurityManager() throws Exception { configureMockForSecurityManager(null); Subject subject = security.getSubject("username", "password"); assertThat(subject, is(equalTo(null))); } @Test public void testGetSubjectInvalidUsernamePassword() throws Exception { SecurityManager sm = mock(SecurityManager.class); when(sm.getSubject(any())).thenThrow(new SecurityServiceException("Error")); configureMockForSecurityManager(sm); Subject subject = security.getSubject("username", "password"); assertThat(subject, is(equalTo(null))); } @Test public void testGetSubject() throws Exception { SecurityManager sm = mock(SecurityManager.class); Subject smSubject = mock(Subject.class); when(sm.getSubject(any())).thenReturn(smSubject); configureMockForSecurityManager(sm); Subject subject = security.getSubject("username", "password"); assertThat(subject, not(equalTo(null))); } @Test public void testTokenAboutToExpire() throws Exception { Subject subject = mock(Subject.class); SecurityAssertion assertion = mock(SecurityAssertion.class); PrincipalCollection pc = mock(PrincipalCollection.class); SecurityToken st = mock(SecurityToken.class); when(st.isAboutToExpire(anyLong())).thenReturn(true); assertThat(security.tokenAboutToExpire(null), equalTo(true)); assertThat(security.tokenAboutToExpire(subject), equalTo(true)); when(subject.getPrincipals()).thenReturn(pc); assertThat(security.tokenAboutToExpire(subject), equalTo(true)); when(pc.oneByType(any(Class.class))).thenReturn(assertion); when(assertion.getSecurityToken()).thenReturn(st); assertThat(security.tokenAboutToExpire(subject), equalTo(true)); when(st.isAboutToExpire(anyLong())).thenReturn(false); assertThat(security.tokenAboutToExpire(subject), equalTo(false)); } @Test public void testJavaSubjectDoesNotHaveAdminRole() throws Exception { javax.security.auth.Subject subject = new javax.security.auth.Subject(); javax.security.auth.Subject.doAs(subject, (PrivilegedAction<Object>) () -> { assertThat(security.javaSubjectHasAdminRole(), equalTo(false)); return null; }); } @Test public void testJavaSubjectHasAdminRole() throws Exception { security.runAsAdmin(() -> { assertThat(security.javaSubjectHasAdminRole(), equalTo(true)); return null; }); } @Test public void testGetSystemSubject() throws Exception { configureMocksForBundleContext("server"); security.runAsAdmin(() -> { assertThat(security.getSystemSubject(), not(equalTo(null))); return null; }); } @Test public void testGetSystemSubjectBadAlias() throws Exception { configureMocksForBundleContext("bad-alias"); assertThat(security.getSystemSubject(), equalTo(null)); } @Test public void testRunWithSubjectOrElevateWhenUserSubjectExists() throws Exception { when(SecurityUtils.getSubject()).thenReturn(shiroSubject); String result = security.runWithSubjectOrElevate(callable); assertThat(result, is("Success!")); verify(shiroSubject).execute(callable); } @Test public void testRunWithSubjectOrElevateWhenSystemSubjectHasAdminRole() throws Exception { when(SecurityUtils.getSubject()).thenThrow(new UnavailableSecurityManagerException("")); when(systemSubject.execute(callable)).thenReturn("Success!"); configureMocksForBundleContext("server"); String result = security.runAsAdminWithException(() -> security.runWithSubjectOrElevate( callable)); assertThat(result, is("Success!")); verifyStatic(); SecurityLogger.auditWarn("Elevating current user permissions to use System subject"); verifyZeroInteractions(shiroSubject); } @Test(expected = SecurityServiceException.class) public void testRunWithSubjectOrElevateWhenSystemSubjectDoesNotHaveAdminRole() throws Exception { when(SecurityUtils.getSubject()).thenThrow(new IllegalStateException()); when(systemSubject.execute(callable)).thenReturn("Success!"); configureMocksForBundleContext("server"); try { security.runWithSubjectOrElevate(callable); } catch (SecurityServiceException e) { verifyStatic(); SecurityLogger.audit( "Current user doesn't have sufficient privileges to run this command"); throw e; } } @Test public void testRunWithSubjectOrElevateWhenSystemSubjectIsNotAvailable() throws Exception { when(SecurityUtils.getSubject()).thenThrow(new IllegalStateException()); configureMocksForBundleContext("bad-alias"); boolean securityExceptionThrown = security.runAsAdmin(() -> { try { return security.runWithSubjectOrElevate(() -> false); } catch (SecurityServiceException e) { return true; } catch (Exception e) { return false; } }); assertThat(securityExceptionThrown, is(true)); verifyStatic(); SecurityLogger.audit("Current user doesn't have sufficient privileges to run this command"); verifyZeroInteractions(shiroSubject); } @Test public void testRunWithSubjectOrElevateWhenUserSubjectExistsAndCallableThrowsException() throws Exception { when(SecurityUtils.getSubject()).thenReturn(shiroSubject); when(shiroSubject.execute(callable)).thenThrow(new ExecutionException(new UnsupportedOperationException())); try { security.runWithSubjectOrElevate(callable); fail("InvocationTargetException expected"); } catch (SecurityServiceException e) { throw e; } catch (InvocationTargetException e) { assertThat(e.getCause(), is(instanceOf(UnsupportedOperationException.class))); } } @Test public void testRunWithSubjectOrElevateWhenSystemSubjectIsUsedAndCallableThrowsException() throws Exception { when(SecurityUtils.getSubject()).thenThrow(new IllegalStateException()); when(systemSubject.execute(callable)).thenThrow(new ExecutionException(new UnsupportedOperationException())); configureMocksForBundleContext("server"); Exception exception = security.runAsAdmin(() -> { try { security.runWithSubjectOrElevate(callable); fail("InvocationTargetException expected"); return null; } catch (Exception e) { return e; } }); assertThat(exception, is(instanceOf(InvocationTargetException.class))); assertThat(exception.getCause(), is(instanceOf(UnsupportedOperationException.class))); } @Test(expected = IllegalArgumentException.class) public void testRunWithSubjectOrElevateWhenCallableIsNull() throws Exception { security.runWithSubjectOrElevate(null); } private void configureMockForSecurityManager(SecurityManager sm) { mockStatic(FrameworkUtil.class); Bundle bundle = mock(Bundle.class); when(FrameworkUtil.getBundle(any(Class.class))).thenReturn(bundle); BundleContext bc = mock(BundleContext.class); when(bundle.getBundleContext()).thenReturn(bc); ServiceReference ref = mock(ServiceReference.class); when(bc.getServiceReference(any(Class.class))).thenReturn(ref); when(bc.getService(ref)).thenReturn(sm); } private void configureMocksForBundleContext(String systemHostname) throws Exception { System.setProperty("org.codice.ddf.system.hostname", systemHostname); Bundle bundle = mock(Bundle.class); when(FrameworkUtil.getBundle(any(Class.class))).thenReturn(bundle); BundleContext bundleContext = mock(BundleContext.class); when(bundle.getBundleContext()).thenReturn(bundleContext); ServiceReference adminRef = mock(ServiceReference.class); ConfigurationAdmin configAdmin = mock(ConfigurationAdmin.class); Configuration config = mock(Configuration.class); when(configAdmin.getConfiguration(anyString(), anyString())).thenReturn(config); when(bundleContext.getService(adminRef)).thenReturn(configAdmin); ServiceReference securityRef = mock(ServiceReference.class); SecurityManager securityManager = mock(SecurityManager.class); when(securityManager.getSubject(any())).thenReturn(systemSubject); when(bundleContext.getService(securityRef)).thenReturn(securityManager); when(bundleContext.getServiceReference(ConfigurationAdmin.class)).thenReturn(adminRef); when(bundleContext.getServiceReference(SecurityManager.class)).thenReturn(securityRef); } }