001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.shiro.testing.web; 020 021import org.apache.shiro.codec.Base64; 022 023import com.gargoylesoftware.htmlunit.WebClient; 024import com.github.mjeanroy.junit.servers.jetty.EmbeddedJetty; 025import com.github.mjeanroy.junit.servers.jetty.EmbeddedJettyConfiguration; 026import org.eclipse.jetty.annotations.AnnotationConfiguration; 027import org.eclipse.jetty.http.HttpVersion; 028import org.eclipse.jetty.server.HttpConfiguration; 029import org.eclipse.jetty.server.HttpConnectionFactory; 030import org.eclipse.jetty.server.SecureRequestCustomizer; 031import org.eclipse.jetty.server.Server; 032import org.eclipse.jetty.server.ServerConnector; 033import org.eclipse.jetty.server.SslConnectionFactory; 034import org.eclipse.jetty.util.resource.FileResource; 035import org.eclipse.jetty.util.ssl.SslContextFactory; 036import org.eclipse.jetty.webapp.Configuration; 037import org.eclipse.jetty.webapp.FragmentConfiguration; 038import org.eclipse.jetty.webapp.JettyWebXmlConfiguration; 039import org.eclipse.jetty.webapp.MetaInfConfiguration; 040import org.eclipse.jetty.webapp.WebAppContext; 041import org.eclipse.jetty.webapp.WebInfConfiguration; 042import org.eclipse.jetty.webapp.WebXmlConfiguration; 043import org.junit.AfterClass; 044import org.junit.Before; 045import org.junit.BeforeClass; 046 047import java.io.File; 048import java.io.FilenameFilter; 049import java.io.IOException; 050import java.io.UnsupportedEncodingException; 051import java.net.ServerSocket; 052import java.net.URL; 053import java.nio.file.Files; 054import java.nio.file.Path; 055import java.nio.file.StandardCopyOption; 056 057import static com.github.mjeanroy.junit.servers.commons.Strings.isNotBlank; 058import static org.eclipse.jetty.util.resource.Resource.newResource; 059import static org.junit.Assert.assertEquals; 060import static org.junit.Assert.assertTrue; 061 062public abstract class AbstractContainerIT { 063 064 protected static EmbeddedJetty jetty; 065 066 protected static int tlsPort; 067 068 protected final WebClient webClient = new WebClient(); 069 070 protected static final File TEST_KEYSTORE_PATH = setupKeyStore(); 071 protected static final String TEST_KEYSTORE_PASSWORD = "password"; 072 073 @BeforeClass 074 public static void startContainer() throws Exception { 075 076 EmbeddedJettyConfiguration config = EmbeddedJettyConfiguration.builder() 077 .withWebapp(getWarDir()) 078 .build(); 079 080 jetty = new EmbeddedJetty(config) { 081 082 /** 083 * Overriding with contents of this pull request, to make fragment scanning work. 084 * https://github.com/mjeanroy/junit-servers/pull/3 085 */ 086 protected WebAppContext createdWebAppContext() throws Exception { 087 final String path = configuration.getPath(); 088 final String webapp = configuration.getWebapp(); 089 final String classpath = configuration.getClasspath(); 090 091 WebAppContext ctx = new WebAppContext(); 092 ctx.setClassLoader(Thread.currentThread().getContextClassLoader()); 093 ctx.setContextPath(path); 094 095 // Useful for WebXmlConfiguration 096 ctx.setBaseResource(newResource(webapp)); 097 098 ctx.setConfigurations(new Configuration[]{ 099 new WebInfConfiguration(), 100 new WebXmlConfiguration(), 101 new AnnotationConfiguration(), 102 new JettyWebXmlConfiguration(), 103 new MetaInfConfiguration(), 104 new FragmentConfiguration(), 105 }); 106 107 if (isNotBlank(classpath)) { 108 // Fix to scan Spring WebApplicationInitializer 109 // This will add compiled classes to jetty classpath 110 // See: http://stackoverflow.com/questions/13222071/spring-3-1-webapplicationinitializer-embedded-jetty-8-annotationconfiguration 111 // And more precisely: http://stackoverflow.com/a/18449506/1215828 112 File classes = new File(classpath); 113 FileResource containerResources = new FileResource(classes.toURI()); 114 ctx.getMetaData().addContainerResource(containerResources); 115 } 116 117 Server server = getDelegate(); 118 119 // web app 120 ctx.setParentLoaderPriority(true); 121 ctx.setWar(webapp); 122 ctx.setServer(server); 123 124 // Add server context 125 server.setHandler(ctx); 126 127 return ctx; 128 } 129 }; 130 131 Server server = jetty.getDelegate(); 132 133 // TLS 134 tlsPort = getFreePort(); 135 136 final SslContextFactory sslContextFactory = new SslContextFactory.Server(); 137 sslContextFactory.setKeyStorePath(TEST_KEYSTORE_PATH.getAbsolutePath()); 138 sslContextFactory.setKeyStorePassword(TEST_KEYSTORE_PASSWORD); 139 sslContextFactory.setKeyManagerPassword(TEST_KEYSTORE_PASSWORD); 140 141 HttpConfiguration https = new HttpConfiguration(); 142 https.addCustomizer(new SecureRequestCustomizer()); 143 144 final ServerConnector httpsConnector = new ServerConnector( 145 server, 146 new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), 147 new HttpConnectionFactory(https)); 148 httpsConnector.setPort(tlsPort); 149 server.addConnector(httpsConnector); 150 151 jetty.start(); 152 153 assertTrue(jetty.isStarted()); 154 } 155 156 protected static String getBaseUri() { 157 return "http://localhost:" + jetty.getPort() + "/"; 158 } 159 160 protected static String getTlsBaseUri() { 161 return "https://localhost:" + tlsPort + "/"; 162 } 163 164 protected static String getWarDir() { 165 File[] warFiles = new File("target").listFiles(new FilenameFilter() { 166 @Override 167 public boolean accept(File dir, String name) { 168 return name.endsWith(".war"); 169 } 170 }); 171 172 assertEquals("Expected only one war file in target directory, run 'mvn clean' and try again", 1, warFiles.length); 173 174 return warFiles[0].getAbsolutePath().replaceFirst("\\.war$", ""); 175 } 176 177 protected static String getBasicAuthorizationHeaderValue(String username, String password) throws UnsupportedEncodingException { 178 String authorizationHeader = username + ":" + password; 179 byte[] valueBytes; 180 valueBytes = authorizationHeader.getBytes("UTF-8"); 181 authorizationHeader = new String(Base64.encode(valueBytes)); 182 return "Basic " + authorizationHeader; 183 } 184 185 @Before 186 public void beforeTest() { 187 webClient.getOptions().setThrowExceptionOnFailingStatusCode(true); 188 } 189 190 @AfterClass 191 public static void stopContainer() { 192 if (jetty != null) { 193 jetty.stop(); 194 } 195 } 196 197 private static int getFreePort() { 198 try (ServerSocket socket = new ServerSocket(0)) { 199 return socket.getLocalPort(); 200 } catch (IOException e) { 201 throw new IllegalStateException("Failed to allocate free port", e); 202 } 203 } 204 205 // Dealing with a keystore is NOT fun, it's easier to script one with the keytool 206 // see src/main/resources/createKeyStore.sh for more info 207 private static File setupKeyStore() { 208 try { 209 Path outKeyStoreFile = File.createTempFile("test-keystore", "jks").toPath(); 210 URL keyStoreResource = Thread.currentThread().getContextClassLoader().getResource("test-keystore.jks"); 211 Files.copy(keyStoreResource.openStream(), outKeyStoreFile, StandardCopyOption.REPLACE_EXISTING); 212 File keyStoreFile = outKeyStoreFile.toFile(); 213 214 // clients will pick up the ssl keystore this way, so just set SSL properties 215 System.setProperty("javax.net.ssl.trustStore", keyStoreFile.getAbsolutePath()); 216 System.setProperty("javax.net.ssl.trustStorePassword", TEST_KEYSTORE_PASSWORD); 217 return keyStoreFile; 218 } catch (IOException e) { 219 throw new IllegalStateException("Failed to create test keystore", e); 220 } 221 } 222}