Java tutorial
/******************************************************************************* * Copyright (C) 2008-2010 The University of Manchester * * Modifications to the initial code base are copyright of their * respective authors, or their employers as appropriate. * * This program 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 2.1 of * the License, or (at your option) any later version. * * 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. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ******************************************************************************/ package net.sf.taverna.t2.security.credentialmanager.impl; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; import java.net.Authenticator; import java.net.PasswordAuthentication; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.List; import java.util.Random; import net.sf.taverna.t2.security.credentialmanager.CMException; import net.sf.taverna.t2.security.credentialmanager.CredentialManager; import net.sf.taverna.t2.security.credentialmanager.JavaTruststorePasswordProvider; import net.sf.taverna.t2.security.credentialmanager.MasterPasswordProvider; import net.sf.taverna.t2.security.credentialmanager.ServiceUsernameAndPasswordProvider; import net.sf.taverna.t2.security.credentialmanager.TrustConfirmationProvider; import net.sf.taverna.t2.security.credentialmanager.UsernamePassword; import org.apache.commons.io.FileUtils; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.mortbay.jetty.Connector; import org.mortbay.jetty.Handler; import org.mortbay.jetty.Server; import org.mortbay.jetty.handler.DefaultHandler; import org.mortbay.jetty.handler.HandlerCollection; import org.mortbay.jetty.nio.SelectChannelConnector; import org.mortbay.jetty.security.Constraint; import org.mortbay.jetty.security.ConstraintMapping; import org.mortbay.jetty.security.HashUserRealm; import org.mortbay.jetty.security.SecurityHandler; import org.mortbay.jetty.webapp.WebAppContext; /** * * Based on net.sf.tavenra.security.credentialmanager.FixedPasswordProvider from the * Taverna 2 codebase. * * @author Stian Soiland-Reyes * @author Alex Nenadic * */ public class HTTPAuthenticatorIT { protected static final String WRONG_PASSWORD = "wrongOne"; protected final static String PASSWORD = "basicPassword"; protected static final String PASSWORD2 = "password2"; protected static final String PASSWORD3 = "password3"; protected static final String PASSWORD4 = "password4"; protected static final String REALM = "realm1"; protected static final String REALM2 = "realm2"; protected final static String USERNAME = "basicUser"; protected static final int PORT = 9638; private final class CountingAuthenticator extends CredentialManagerAuthenticator { public CountingAuthenticator(CredentialManager credManager) { super(credManager); } private int calls; @Override protected PasswordAuthentication getPasswordAuthentication() { calls++; return super.getPasswordAuthentication(); } } public class NullAuthenticator extends Authenticator { } protected static final String ROLE_NAME = "user"; protected static final String HTML = "/html/"; protected static Server server; protected static HashUserRealm userRealm; private static SecurityHandler sh; private static CredentialManagerImpl credentialManager; private static File credentialManagerDirectory; private static DummyMasterPasswordProvider masterPasswordProvider; private static HTTPAuthenticatorServiceUsernameAndPasswordProvider httpAuthProvider; @BeforeClass public static void startCredentialManager() throws CMException, IOException { try { credentialManager = new CredentialManagerImpl(); } catch (CMException e) { System.out.println(e.getStackTrace()); } Random randomGenerator = new Random(); String credentialManagerDirectoryPath = System.getProperty("java.io.tmpdir") + System.getProperty("file.separator") + "taverna-security-" + randomGenerator.nextInt(1000000); System.out.println("Credential Manager's directory path: " + credentialManagerDirectoryPath); credentialManagerDirectory = new File(credentialManagerDirectoryPath); try { credentialManager.setConfigurationDirectoryPath(credentialManagerDirectory); } catch (CMException e) { System.out.println(e.getStackTrace()); } // Create the dummy master password provider masterPasswordProvider = new DummyMasterPasswordProvider(); /* Short password to avoid issues with key sizes and Java strong crypto policy*/ masterPasswordProvider.setMasterPassword("uber"); List<MasterPasswordProvider> masterPasswordProviders = new ArrayList<MasterPasswordProvider>(); masterPasswordProviders.add(masterPasswordProvider); credentialManager.setMasterPasswordProviders(masterPasswordProviders); // Put our HTTP authenticator in the list of service username and password providers httpAuthProvider = new HTTPAuthenticatorServiceUsernameAndPasswordProvider(); ArrayList<ServiceUsernameAndPasswordProvider> serviceUsernameAndPasswordProviders = new ArrayList<ServiceUsernameAndPasswordProvider>(); serviceUsernameAndPasswordProviders.add(httpAuthProvider); credentialManager.setServiceUsernameAndPasswordProviders(serviceUsernameAndPasswordProviders); // These can be empty credentialManager.setJavaTruststorePasswordProviders(new ArrayList<JavaTruststorePasswordProvider>()); credentialManager.setTrustConfirmationProviders(new ArrayList<TrustConfirmationProvider>()); } @AfterClass // Clean up the credentialManagerDirectory we created for testing public static void cleanUp() { if (credentialManagerDirectory.exists()) { try { FileUtils.deleteDirectory(credentialManagerDirectory); System.out.println( "Deleting Credential Manager's directory: " + credentialManagerDirectory.getAbsolutePath()); } catch (IOException e) { System.out.println(e.getStackTrace()); } } } @BeforeClass public static void jettyServer() throws Exception { server = new Server(); Connector connector = new SelectChannelConnector(); connector.setPort(PORT); server.setConnectors(new Connector[] { connector }); ConstraintMapping cm = new ConstraintMapping(); Constraint constraint = new Constraint(); constraint.setName(Constraint.__BASIC_AUTH); constraint.setRoles(new String[] { ROLE_NAME }); constraint.setAuthenticate(true); cm.setConstraint(constraint); cm.setPathSpec("/*"); sh = new SecurityHandler(); userRealm = new HashUserRealm(REALM); userRealm.put(USERNAME, PASSWORD); userRealm.addUserToRole(USERNAME, ROLE_NAME); sh.setUserRealm(userRealm); sh.setConstraintMappings(new ConstraintMapping[] { cm }); WebAppContext webappcontext = new WebAppContext(); webappcontext.setContextPath("/"); URL htmlRoot = HTTPAuthenticatorIT.class.getResource(HTML); assertNotNull("Could not find " + HTML, htmlRoot); webappcontext.setWar(htmlRoot.toExternalForm()); webappcontext.addHandler(sh); HandlerCollection handlers = new HandlerCollection(); handlers.setHandlers(new Handler[] { webappcontext, new DefaultHandler() }); server.setHandler(handlers); server.start(); } @AfterClass public static void shutdownJetty() throws Exception { server.stop(); } @Before @After public void resetAuthenticator() throws CMException { Authenticator.setDefault(new NullAuthenticator()); HTTPAuthenticatorServiceUsernameAndPasswordProvider.resetCalls(); } @Before public void resetAuthCache() throws CMException { credentialManager.resetAuthCache(); } @Before public void resetUserRealmPassword() { userRealm.put(USERNAME, PASSWORD); userRealm.setName(REALM); } @Test() public void failsWithoutAuthenticator() throws Exception { URL url = new URL("http://localhost:" + PORT + "/test.html"); URLConnection c = url.openConnection(); assertEquals("HTTP/1.1 401 Unauthorized", c.getHeaderField(0)); } @Test() public void withAuthenticator() throws Exception { assertEquals("Unexpected calls to password provider", 0, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getCalls()); // Set the authenticator to our Credential Manager-backed one that also // counts calls to itself CountingAuthenticator authenticator = new CountingAuthenticator(credentialManager); assertEquals("Unexpected calls to authenticator", 0, authenticator.calls); Authenticator.setDefault(authenticator); // FixedPasswordProvider.setUsernamePassword(new UsernamePassword( // USERNAME, PASSWORD)); URL url = new URL("http://localhost:" + PORT + "/test.html"); httpAuthProvider.setServiceUsernameAndPassword(url.toURI(), new UsernamePassword(USERNAME, PASSWORD)); URLConnection c = url.openConnection(); c.connect(); try { c.getContent(); } catch (Exception ex) { } System.out.println(c.getHeaderField(0)); assertEquals("Did not invoke authenticator", 1, authenticator.calls); assertEquals("Did not invoke our password provider", 1, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getCalls()); assertEquals("HTTP/1.1 200 OK", c.getHeaderField(0)); assertEquals("Unexpected prompt/realm", REALM, httpAuthProvider.getRequestMessage()); assertEquals("Unexpected URI", url.toURI().toASCIIString() + "#" + REALM, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getServiceURI().toASCIIString()); // And test Java's cache: URLConnection c2 = url.openConnection(); c2.connect(); assertEquals("HTTP/1.1 200 OK", c2.getHeaderField(0)); assertEquals("JVM invoked our authenticator again instead of caching", 1, authenticator.calls); assertEquals("Invoked our password provider again instead of caching", 1, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getCalls()); } @Test() public void withAuthenticatorResetJava() throws Exception { assertTrue("Could not reset JVMs authCache, ignore on non-Sun JVM", credentialManager.resetAuthCache()); assertEquals("Unexpected calls to password provider", 0, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getCalls()); CountingAuthenticator authenticator = new CountingAuthenticator(credentialManager); assertEquals("Unexpected calls to authenticator", 0, authenticator.calls); Authenticator.setDefault(authenticator); // FixedPasswordProvider.setUsernamePassword(new UsernamePassword( // USERNAME, PASSWORD)); URL url = new URL("http://localhost:" + PORT + "/test.html"); httpAuthProvider.setServiceUsernameAndPassword(url.toURI(), new UsernamePassword(USERNAME, PASSWORD)); URLConnection c = url.openConnection(); c.connect(); try { c.getContent(); } catch (Exception ex) { } assertEquals("HTTP/1.1 200 OK", c.getHeaderField(0)); assertEquals("Did not invoke authenticator", 1, authenticator.calls); assertEquals("Did not invoke our password provider", 1, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getCalls()); assertEquals("Unexpected prompt/realm", REALM, httpAuthProvider.getRequestMessage()); assertEquals("Unexpected URI", url.toURI().toASCIIString() + "#" + REALM, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getServiceURI().toASCIIString()); // And without Java's cache: assertTrue("Could not reset VMs authCache, ignore on non-Sun VM", credentialManager.resetAuthCache()); URLConnection c2 = url.openConnection(); c2.connect(); assertEquals("HTTP/1.1 200 OK", c2.getHeaderField(0)); assertEquals("Did not invoke our authenticator again", 2, authenticator.calls); assertEquals("Did not invoke our password provider again", 2, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getCalls()); } @Test() public void differentRealm() throws Exception { assertEquals("Unexpected calls to password provider", 0, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getCalls()); CountingAuthenticator authenticator = new CountingAuthenticator(credentialManager); assertEquals("Unexpected calls to authenticator", 0, authenticator.calls); Authenticator.setDefault(authenticator); // Different password in case resetAuthCache() did not run UsernamePassword userPassword = new UsernamePassword(USERNAME, PASSWORD4); userRealm.put(USERNAME, PASSWORD4); // userPassword.setShouldSave(true); //FixedPasswordProvider.setUsernamePassword(userPassword); URL url = new URL("http://localhost:" + PORT + "/test.html"); httpAuthProvider.setServiceUsernameAndPassword(url.toURI(), userPassword); URLConnection c = url.openConnection(); c.connect(); try { c.getContent(); } catch (Exception ex) { } assertEquals("Unexpected prompt/realm", REALM, httpAuthProvider.getRequestMessage()); assertEquals("Unexpected URI", url.toURI().toASCIIString() + "#" + REALM, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getServiceURI().toASCIIString()); assertEquals("HTTP/1.1 200 OK", c.getHeaderField(0)); assertEquals("Did not invoke authenticator", 1, authenticator.calls); assertEquals("Did not invoke our password provider", 1, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getCalls()); // different realm should be treated as a second connection, and not even use saved credentials credentialManager.resetAuthCache(); userRealm.setName(REALM2); URLConnection c2 = url.openConnection(); c2.connect(); try { c.getContent(); } catch (Exception ex) { } assertEquals("HTTP/1.1 200 OK", c2.getHeaderField(0)); assertEquals("Did not invoke authenticator again", 2, authenticator.calls); assertEquals("Did not invoke provider again", 2, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getCalls()); assertEquals("Unexpected prompt/realm", REALM2, httpAuthProvider.getRequestMessage()); assertEquals("Unexpected URI", url.toURI().toASCIIString() + "#" + REALM2, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getServiceURI().toASCIIString()); } @Test() public void wrongPasswordDontSave() throws Exception { assertEquals("Unexpected calls to password provider", 0, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getCalls()); CountingAuthenticator authenticator = new CountingAuthenticator(credentialManager); assertEquals("Unexpected calls to authenticator", 0, authenticator.calls); Authenticator.setDefault(authenticator); // Make the server expect different password so our cache is no longer // valid userRealm.put(USERNAME, PASSWORD2); // But we'll try with the old one, which we'll this time ask to save in // DB UsernamePassword usernamePassword = new UsernamePassword(USERNAME, PASSWORD); assertFalse("Should not be set to save by default", usernamePassword.isShouldSave()); //FixedPasswordProvider.setUsernamePassword(usernamePassword); URL url = new URL("http://localhost:" + PORT + "/test.html"); httpAuthProvider.setServiceUsernameAndPassword(url.toURI(), usernamePassword); URLConnection c = url.openConnection(); try { c.getContent(); } catch (Exception ex) { } assertEquals("Unexpected prompt/realm", REALM, httpAuthProvider.getRequestMessage()); assertEquals("Unexpected URI", url.toURI().toASCIIString() + "#" + REALM, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getServiceURI().toASCIIString()); assertEquals("HTTP/1.1 401 Unauthorized", c.getHeaderField(0)); assertTrue("Did not invoke authenticator enough times", authenticator.calls > 1); assertEquals("Should have asked provider as much as authenticator", authenticator.calls, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getCalls()); // Update provider to now provide the right one // HTTPAuthenticatorServiceUsernameAndPasswordProvider.setUsernamePassword(new UsernamePassword( // USERNAME, PASSWORD2)); httpAuthProvider.setServiceUsernameAndPassword(url.toURI(), new UsernamePassword(USERNAME, PASSWORD2)); HTTPAuthenticatorServiceUsernameAndPasswordProvider.resetCalls(); authenticator.calls = 0; URLConnection c2 = url.openConnection(); try { c2.getContent(); } catch (Exception ex) { } assertEquals("Did not call authenticator again with cache pw invalid", 1, authenticator.calls); assertEquals("id not called our password provider once", 1, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getCalls()); assertEquals("HTTP/1.1 200 OK", c2.getHeaderField(0)); } @Test() public void saveToDatabase() throws Exception { assertEquals("Unexpected calls to password provider", 0, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getCalls()); CountingAuthenticator authenticator = new CountingAuthenticator(credentialManager); assertEquals("Unexpected calls to authenticator", 0, authenticator.calls); Authenticator.setDefault(authenticator); // Make the server expect different password so our cache is no longer // valid (In case CredManager.resetAuthCache() did not succeed on non-Sun VMs) userRealm.put(USERNAME, PASSWORD3); // But we'll try with the old one, which we'll this time ask to save in // DB UsernamePassword usernamePassword = new UsernamePassword(USERNAME, PASSWORD2); usernamePassword.setShouldSave(true); //HTTPAuthenticatorServiceUsernameAndPasswordProvider.setUsernamePassword(usernamePassword); URL url = new URL("http://localhost:" + PORT + "/test.html"); httpAuthProvider.setServiceUsernameAndPassword(url.toURI(), usernamePassword); URLConnection c = url.openConnection(); try { c.getContent(); } catch (Exception ex) { } assertEquals("Unexpected prompt/realm", REALM, httpAuthProvider.getRequestMessage()); assertEquals("Unexpected URI", url.toURI().toASCIIString() + "#" + REALM, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getServiceURI().toASCIIString()); assertEquals("HTTP/1.1 401 Unauthorized", c.getHeaderField(0)); assertTrue("Did not invoke authenticator enough times", authenticator.calls > 1); assertEquals("Asked our provider more than once, not saved in credMan?", 1, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getCalls()); // Expect the old one again userRealm.put(USERNAME, PASSWORD2); // We'll now set our provider to give an invalid password, but we should // not be asked // as the old one (now correct agian) is stored in DB // HTTPAuthenticatorServiceUsernameAndPasswordProvider.setUsernamePassword(new UsernamePassword( // USERNAME, WRONG_PASSWORD)); httpAuthProvider.setServiceUsernameAndPassword(url.toURI(), new UsernamePassword(USERNAME, WRONG_PASSWORD)); HTTPAuthenticatorServiceUsernameAndPasswordProvider.resetCalls(); authenticator.calls = 0; URLConnection c2 = url.openConnection(); try { c2.getContent(); } catch (Exception ex) { } assertEquals("Did not call authenticator again with cache pw invalid", 1, authenticator.calls); assertEquals("Called our password provider instead of using credMan saved one", 0, HTTPAuthenticatorServiceUsernameAndPasswordProvider.getCalls()); assertEquals("HTTP/1.1 200 OK", c2.getHeaderField(0)); } }