View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.shiro.testing.web;
20  
21  import org.apache.shiro.codec.Base64;
22  
23  import com.gargoylesoftware.htmlunit.WebClient;
24  import com.github.mjeanroy.junit.servers.jetty.EmbeddedJetty;
25  import com.github.mjeanroy.junit.servers.jetty.EmbeddedJettyConfiguration;
26  import org.eclipse.jetty.annotations.AnnotationConfiguration;
27  import org.eclipse.jetty.http.HttpVersion;
28  import org.eclipse.jetty.server.HttpConfiguration;
29  import org.eclipse.jetty.server.HttpConnectionFactory;
30  import org.eclipse.jetty.server.SecureRequestCustomizer;
31  import org.eclipse.jetty.server.Server;
32  import org.eclipse.jetty.server.ServerConnector;
33  import org.eclipse.jetty.server.SslConnectionFactory;
34  import org.eclipse.jetty.util.resource.FileResource;
35  import org.eclipse.jetty.util.ssl.SslContextFactory;
36  import org.eclipse.jetty.webapp.Configuration;
37  import org.eclipse.jetty.webapp.FragmentConfiguration;
38  import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
39  import org.eclipse.jetty.webapp.MetaInfConfiguration;
40  import org.eclipse.jetty.webapp.WebAppContext;
41  import org.eclipse.jetty.webapp.WebInfConfiguration;
42  import org.eclipse.jetty.webapp.WebXmlConfiguration;
43  import org.junit.AfterClass;
44  import org.junit.Before;
45  import org.junit.BeforeClass;
46  
47  import java.io.File;
48  import java.io.FilenameFilter;
49  import java.io.IOException;
50  import java.io.UnsupportedEncodingException;
51  import java.net.ServerSocket;
52  import java.net.URL;
53  import java.nio.file.Files;
54  import java.nio.file.Path;
55  import java.nio.file.StandardCopyOption;
56  
57  import static com.github.mjeanroy.junit.servers.commons.Strings.isNotBlank;
58  import static org.eclipse.jetty.util.resource.Resource.newResource;
59  import static org.junit.Assert.assertEquals;
60  import static org.junit.Assert.assertTrue;
61  
62  public abstract class AbstractContainerIT {
63  
64      protected static EmbeddedJetty jetty;
65  
66      protected static int tlsPort;
67  
68      protected final WebClient webClient = new WebClient();
69  
70      protected static final File TEST_KEYSTORE_PATH = setupKeyStore();
71      protected static final String TEST_KEYSTORE_PASSWORD = "password";
72  
73      @BeforeClass
74      public static void startContainer() throws Exception {
75  
76          EmbeddedJettyConfiguration config = EmbeddedJettyConfiguration.builder()
77                  .withWebapp(getWarDir())
78                  .build();
79  
80          jetty = new EmbeddedJetty(config) {
81  
82              /**
83               * Overriding with contents of this pull request, to make fragment scanning work.
84               * https://github.com/mjeanroy/junit-servers/pull/3
85               */
86              protected WebAppContext createdWebAppContext() throws Exception {
87                  final String path = configuration.getPath();
88                  final String webapp = configuration.getWebapp();
89                  final String classpath = configuration.getClasspath();
90  
91                  WebAppContext ctx = new WebAppContext();
92                  ctx.setClassLoader(Thread.currentThread().getContextClassLoader());
93                  ctx.setContextPath(path);
94  
95                  // Useful for WebXmlConfiguration
96                  ctx.setBaseResource(newResource(webapp));
97  
98                  ctx.setConfigurations(new Configuration[]{
99                          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 }