How to fix unsafe implementation of X509TrustManager in Android app - android-security

Google has advised that I have an unsafe implementation of the interface X509TrustManager in my Android application and need to change my code as follows:
To properly handle SSL certificate validation, change your code in the
checkServerTrusted method of your custom X509TrustManager interface to
raise either CertificateException or IllegalArgumentException whenever
the certificate presented by the server does not meet your
expectations. For technical questions, you can post to Stack Overflow
and use the tags “android-security” and “TrustManager.”
How can the following code be modified to fix the above issue?
public EasySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(truststore);
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
mContext.init(null, new TrustManager[] { tm }, null);
}

I have solved this using the following code:
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
chain[0].checkValidity();
} catch (Exception e) {
throw new CertificateException("Certificate not valid or trusted.");
}
}

If you encounter this from external library you're using, check if appache libraray is the cause of it.
For me apache library caused the error : i was using deprecated class - MultipartEntity. This class uses SSLContextBuilder
which uses TrustManagerDelegate. TrustManagerDelegate implements X509TrustManager, which cause "unsafe implementation of TrustManager" error when uploading application to google play store.
The solution is : instead of deprecated MultipartEntity class, use MultipartEntityBuilder.
For example :
MultipartEntity httpMultipart = new MultipartEntity();
String contentType = httpMultipart.getContentType().getValue();
Will be replaced by :
MultipartEntityBuilder httpMultipart = new MultipartEntityBuilder();
String contentType = httpMultipart.build().getContentType().getValue();

Add the upgraded version of OKttps worked for me crashing in Android 10
implementation 'com.squareup.okhttp3:okhttp:4.8.0'

I have meet this problem.If your code is like that:
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
it will accept all certificate and it is a bad idea,so google send you mail.
We can make a change to accept self-signed certificate too.
I solved it,here is my question and my solution

If you are using HttpClient then the solution of #Nabeel is very nice, but if you are using HttpsUrlConnection then this code is very nice for that:
import android.util.Log;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
/**
* TrustManager that accepts all certificates and hosts.
* Useful when you want to use HTTPS but you have self-signed certificates.
* Works with HttpsUrlConnection.
* Use at your own risk and only for development.
*
* #author gotev (Aleksandar Gotev)
*/
public class AllCertificatesAndHostsTruster implements TrustManager, X509TrustManager {
#Override
public final void checkClientTrusted(final X509Certificate[] xcs, final String string)
throws CertificateException {
}
#Override
public final void checkServerTrusted(final X509Certificate[] xcs, final String string)
throws CertificateException {
}
#Override
public final X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
/**
* Gets an {#link SSLContext} which trusts all certificates.
* #return {#link SSLContext}
*/
public static SSLContext getSSLContext() {
final TrustManager[] trustAllCerts =
new TrustManager[] {new AllCertificatesAndHostsTruster()};
try {
final SSLContext context = SSLContext.getInstance("SSL");
context.init(null, trustAllCerts, new SecureRandom());
return context;
} catch (Exception exc) {
Log.e("CertHostTruster", "Unable to initialize the Trust Manager to trust all the "
+ "SSL certificates and HTTPS hosts.", exc);
return null;
}
}
/**
* Creates an hostname verifier which accepts all hosts.
* #return {#link HostnameVerifier}
*/
public static HostnameVerifier getAllHostnamesVerifier() {
return new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
}
/**
* Call this method once before all your network calls
* to accept all the self-signed certificates in HTTPS connections.
*/
public static void apply() {
final TrustManager[] trustAllCerts =
new TrustManager[] {new AllCertificatesAndHostsTruster()};
try {
final SSLContext context = SSLContext.getInstance("SSL");
context.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
} catch (Exception exc) {
Log.e("CertHostTruster", "Unable to initialize the Trust Manager to trust all the "
+ "SSL certificates and HTTPS hosts.", exc);
}
}
}
Source: https://gist.github.com/gotev/6784c1303793c6ee9e56
Then to use self-signed certificates, just invoke:
AllCertificatesAndHostsTruster.apply();
before any network calls.

Related

Corda tutorial's contract test ledget method error

I'm trying to implement a Contract test on Java as described there.
I paste the first test's code in my project and changed import static net.corda.testing.NodeTestUtils.ledger; to import static net.corda.testing.node.NodeTestUtils.ledger;
package com.template;
import org.junit.Test;
import static net.corda.testing.node.NodeTestUtils.ledger;
public class CommercialPaperTest {
#Test
public void emptyLedger() {
ledger(l -> {
return null;
});
}
}
And I see that ledger method has an absolutely different signature, so Java says that it cannot resolve method ledger(<lambda expression>).
What am I doing wrong?
There is an error on that page. The first argument to ledger should be a MockServices instance.
For example, we might write:
public class CommercialPaperTest {
private static final TestIdentity megaCorp = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB"));
private MockServices ledgerServices;
#Before
public void setUp() {
ledgerServices = new MockServices(
singletonList("net.corda.finance.contracts"),
megaCorp,
makeTestIdentityService(megaCorp.getIdentity())
);
}
#Test
public void emptyLedger() {
ledger(ledgerServices, l -> {
return null;
});
}
}

Dropwizard: BasicAuth

Using Dropwizard Authentication 0.9.0-SNAPSHOT
I want to check the credentials against database user (UserDAO).
I get the following exception
! org.hibernate.HibernateException: No session currently bound to
execution context
How to bind the session to the Authenticator?
Or are there better ways to check against the database user?
The Authenticator Class
package com.example.helloworld.auth;
import com.example.helloworld.core.User;
import com.example.helloworld.db.UserDAO;
import com.google.common.base.Optional;
import io.dropwizard.auth.AuthenticationException;
import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.basic.BasicCredentials;
public class ExampleAuthenticator implements Authenticator<BasicCredentials, User> {
UserDAO userDAO;
public ExampleAuthenticator(UserDAO userDAO) {
this.userDAO = userDAO;
}
#Override
public Optional<User> authenticate(BasicCredentials credentials) throws AuthenticationException {
Optional<User> user;
user = (Optional<User>) this.userDAO.findByEmail(credentials.getUsername());
if ("secret".equals(credentials.getPassword())) {
return Optional.of(new User(credentials.getUsername()));
}
return Optional.absent();
}
}
The Application Class
#Override
public void run(HelloWorldConfiguration configuration, Environment environment) throws Exception {
final UserDAO userDAO = new UserDAO(hibernate.getSessionFactory());
environment.jersey().register(new AuthDynamicFeature(
new BasicCredentialAuthFilter.Builder<User>()
.setAuthenticator(new ExampleAuthenticator(userDAO))
.setAuthorizer(new ExampleAuthorizer())
.setRealm("SUPER SECRET STUFF")
.buildAuthFilter()));
environment.jersey().register(RolesAllowedDynamicFeature.class);
//If you want to use #Auth to inject a custom Principal type into your resource
environment.jersey().register(new AuthValueFactoryProvider.Binder(User.class));
environment.jersey().register(new UserResource(userDAO));
To get auth to work with 0.9+ you need the following. You can refer to this particular changeset as an example.
Include the dependency.
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-auth</artifactId>
<version>${dropwizard.version}</version>
</dependency>
Register auth related stuff.
private void registerAuthRelated(Environment environment) {
UnauthorizedHandler unauthorizedHandler = new UnAuthorizedResourceHandler();
AuthFilter basicAuthFilter = new BasicCredentialAuthFilter.Builder<User>()
.setAuthenticator(new BasicAuthenticator())
.setAuthorizer(new UserAuthorizer())
.setRealm("shire")
.setUnauthorizedHandler(unauthorizedHandler)
.setPrefix("Basic")
.buildAuthFilter();
environment.jersey().register(new AuthDynamicFeature(basicAuthFilter));
environment.jersey().register(RolesAllowedDynamicFeature.class);
environment.jersey().register(new AuthValueFactoryProvider.Binder(User.class));
environment.jersey().register(unauthorizedHandler);
}
A basic authenticator
public class BasicAuthenticator<C, P> implements Authenticator<BasicCredentials, User> {
#Override
public Optional<User> authenticate(BasicCredentials credentials) throws AuthenticationException {
//do no authentication yet. Let all users through
return Optional.fromNullable(new User(credentials.getUsername(), credentials.getPassword()));
}
}
UnAuthorizedHandler
public class UnAuthorizedResourceHandler implements UnauthorizedHandler {
#Context
private HttpServletRequest request;
#Override
public Response buildResponse(String prefix, String realm) {
Response.Status unauthorized = Response.Status.UNAUTHORIZED;
return Response.status(unauthorized).type(MediaType.APPLICATION_JSON_TYPE).entity("Can't touch this...").build();
}
#Context
public void setRequest(HttpServletRequest request) {
this.request = request;
}
}
Authorizer
public class UserAuthorizer<P> implements Authorizer<User>{
/**
* Decides if access is granted for the given principal in the given role.
*
* #param principal a {#link Principal} object, representing a user
* #param role a user role
* #return {#code true}, if the access is granted, {#code false otherwise}
*/
#Override
public boolean authorize(User principal, String role) {
return true;
}
}
Finally use it in your resource
#GET
public Response hello(#Auth User user){
return Response.ok().entity("You got permission!").build();
}
You're going to need code in your Application class that looks like this
environment.jersey().register(AuthFactory.binder(new BasicAuthFactory<>(
new ExampleAuthenticator(userDAO), "AUTHENTICATION", User.class)));
Then you can use the #Auth tag on a User parameter for a method and any incoming authentication credentials will hit the authenticate method, allowing you to return the correct User object or absent if the credentials are not in your database.
EDIT: Works for Dropwizard v0.8.4
On Latest versions starting from 0.9 onward, you can use "#Context" annotation in resource class methods as shown below:
#RolesAllowed("EMPLOYEE")
#Path("/emp")
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getEmployeeResponse(#Context SecurityContext context) {
SimplePrincipal sp = (SimplePrincipal) context.getUserPrincipal();
return Response.ok("{\"Hello\": \"Mr. " + sp.getUsername() + "\"( Valuable emp )}").build();
}

Spring Boot: Log all incoming HTTP requests when using an embedded Jetty server?

I'm building a web app using Spring Boot 1.1.5.RELEASE and I've configured an embedded Jetty as described in the related Spring Boot's documentation.
I want to log all incoming HTTP requests and the only solution I can think (after reading "how to configure Jetty" in Spring Boot's docs) is to introduce an EmbeddedServletContainerCustomizer:
package com.acme.rest;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.NCSARequestLog;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.jetty.JettyServerCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
/**
* blah blah
*
* #author Dimi
*/
#Component
public class EmbededJettyConfig implements EmbeddedServletContainerCustomizer {
/*
* (non-Javadoc)
*
* #see org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer # customize
*/
#Override
public void customize(final ConfigurableEmbeddedServletContainer container) {
// checks whether the container is Jetty
if (container instanceof JettyEmbeddedServletContainerFactory) {
((JettyEmbeddedServletContainerFactory) container)
.addServerCustomizers(jettyServerCustomizer());
}
}
#Bean
public JettyServerCustomizer jettyServerCustomizer() {
return new JettyServerCustomizer() {
/*
* (non-Javadoc)
*
* #see org.springframework.boot.context.embedded.jetty.JettyServerCustomizer #
* customize
*/
#Override
public void customize(final Server server) {
HandlerCollection handlers = new HandlerCollection();
RequestLogHandler requestLogHandler = new RequestLogHandler();
handlers.setHandlers(new Handler[] {new DefaultHandler(), requestLogHandler});
server.setHandler(handlers);
NCSARequestLog requestLog = new NCSARequestLog("logs/requests.log");
requestLog.setExtended(false);
requestLogHandler.setRequestLog(requestLog);
}
};
}
}
The application now fails to start throwing an exception:
java.lang.IllegalArgumentException: A ServletContext is required to
configure default servlet handling
In short: what is the correct way of configuring the embedded Jetty's logging in Spring Boot?
Jetty 9.3.8 has a new method setRequestLog on the server.
#Component
public class EnableRequestLog implements EmbeddedServletContainerCustomizer {
private static final JettyServerCustomizer USE_SLF4J_REQUEST_LOG =
server -> server.setRequestLog(new Slf4jRequestLog());
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (container instanceof JettyEmbeddedServletContainerFactory) {
((JettyEmbeddedServletContainerFactory) container)
.addServerCustomizers(USE_SLF4J_REQUEST_LOG);
} else {
throw new IllegalArgumentException(
"Expected a Jetty container factory but encountered " + container.getClass());
}
}
}
Instead of Jetty's Slf4jRequestLog you can implement a specific RequestLog. See http://www.eclipse.org/jetty/documentation/current/configuring-jetty-request-logs.html
Coming a bit late, but I experienced the same problem and the solution was to wrap the ServletHandler already created by boot in the RequestLogHandler like this:
#Component
public class EmbededJettyConfig implements EmbeddedServletContainerCustomizer {
/*
* (non-Javadoc)
*
* #see org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer # customize
*/
#Override
public void customize(final ConfigurableEmbeddedServletContainer container) {
// checks whether the container is Jetty
if (container instanceof JettyEmbeddedServletContainerFactory) {
((JettyEmbeddedServletContainerFactory) container)
.addServerCustomizers(jettyServerCustomizer());
}
}
#Bean
public JettyServerCustomizer jettyServerCustomizer() {
return new JettyServerCustomizer() {
/*
* (non-Javadoc)
*
* #see org.springframework.boot.context.embedded.jetty.JettyServerCustomizer #
* customize
*/
#Override
public void customize(final Server server) {
NCSARequestLog requestLog = new NCSARequestLog("logs/requests.log");
requestLog.setExtended(false);
RequestLogHandler requestLogHandler = new RequestLogHandler();
requestLogHandler.setRequestLog(requestLog);
requestLogHandler.setHandler(server.getHandler());
server.setHandler(requestLogHandler);
}
};
}
This doesn't work with Spring Boot version 1.1.9 because JettyEmbeddedServletContainer handlers only JettyEmbeddedWebAppContext (a handler) or HandlerWrapper. I think there is no way to set multiple handler to embedded Jetty in Spring Boot because of this.
Code snippet from org.springframework.boot.context.embedded.jettyJettyEmbeddedServletContainer.
#Override
public void start() throws EmbeddedServletContainerException {
this.server.setConnectors(this.connectors);
if (!this.autoStart) {
return;
}
try {
this.server.start();
for (Handler handler : this.server.getHandlers()) {
handleDeferredInitialize(handler);
}
Connector[] connectors = this.server.getConnectors();
for (Connector connector : connectors) {
connector.start();
this.logger.info("Jetty started on port: " + getLocalPort(connector));
}
}
catch (Exception ex) {
throw new EmbeddedServletContainerException(
"Unable to start embedded Jetty servlet container", ex);
}
}
private void handleDeferredInitialize(Handler handler) throws Exception {
if (handler instanceof JettyEmbeddedWebAppContext) {
((JettyEmbeddedWebAppContext) handler).deferredInitialize();
}
else if (handler instanceof HandlerWrapper) {
handleDeferredInitialize(((HandlerWrapper) handler).getHandler());
}
}

Servlet 3.0 support in embedded Jetty 8.0

For my unit-tests I use a simple test-server based on Jetty:
package eu.kostia.textanalysis.webservices.jetty;
import java.awt.Desktop;
import java.net.URI;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
public class TestServer {
static private final String CONTEXT_PATH = "/webservice";
static private final String PROJECT_HOME = System.getenv("MY_WORKSPACE_HOME") + "/WebServices";
static public final int PORT = 8080;
private Server server;
private Exception startException;
private static class SingletonHolder {
private static final TestServer INSTANCE = new TestServer();
}
/**
* Returns the singleton instance of the test server.
*
* #return the singleton instance of the test server.
*/
public static TestServer getInstance() {
return SingletonHolder.INSTANCE;
}
private TestServer() {
server = new Server(PORT);
WebAppContext context = new WebAppContext();
context.setDescriptor(PROJECT_HOME + "/web/WEB-INF/web.xml");
context.setResourceBase(PROJECT_HOME + "/web");
context.setContextPath(CONTEXT_PATH);
context.setParentLoaderPriority(true);
server.setHandler(context);
}
/**
* Start the test server. This method returns only when the server is
* complete started. There is no effect when you invoke this method and the
* server is already running.
*/
public void start() {
if (!server.isRunning()) {
startException = null;
new Thread("TestServer") {
public void run() {
try {
server.start();
server.join();
} catch (Exception exc) {
startException = exc;
}
}
}.start();
while (true) {
if (startException != null) {
throw new Error(startException);
}
// Block this method call until the server is started
if (server.isStarted()) {
return;
}
}
}
}
/**
* Stop the test server.
*/
public void stop() {
try {
if (server.isRunning()) {
server.stop();
}
} catch (Exception e) {
throw new Error(e);
}
}
/**
* Returns {#code true} is the server is running.
*
* #return {#code true} is the server is running.
*/
public boolean isRunning() {
return server.isRunning();
}
public static void main(String[] args) throws Exception {
TestServer.getInstance().start();
Desktop.getDesktop().browse(new URI("http://localhost:8080/webservice/"));
}
}
It works very well for servlet configured in web.xml but I'd now like to use the new annotation syntax introduced by the Servlet Specification 3.0, for example:
#WebServlet(urlPatterns = {"/hello"})
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter writer = response.getWriter();
writer.print("<h1>HttpServlet using Servlet 3.0</h1>");
}
}
How shoud I configure Jetty in my TestServer class to process also annotation-based servlets?
Add to your code
context.setConfigurations(new Configuration[] {
new AnnotationConfiguration(), new WebXmlConfiguration(),
new WebInfConfiguration(), new TagLibConfiguration(),
new PlusConfiguration(), new MetaInfConfiguration(),
new FragmentConfiguration(), new EnvConfiguration() });
You only need to set the AnnotationConfiguration to get the auto-discovery of annotated classes to work. The rest of the configurations are so you can enable other aspects of the container. Supposedly you should be able to do this from the commandline, using OPTIONS=annotations,jsp,(etc...), but I never got that working. At least this way it should pick up your annotated classes properly in the embedded environment.
Also as a side note it appears the Eclipse jetty project has annotation turned off by default, whereas riptide claims to have them turned on by default. I'm guessing this is a difference in the configuration files.
Answering yet another year later.
In the current version of Jetty (8.1) you can accomplish exactly what you want with the command line:
java -jar start.jar OPTIONS=annotations,plus etc/jetty-plus.xml
invoked from the jetty home directory.
Jetty 8 is implementing the servlet 3.0 specification but it's still experimental.
You could also use the embedded glassfish 3 plugin to run your tests. See the below links for some info:
http://wikis.sun.com/display/GlassFish/3.1EmbeddedOnePager
http://ocpsoft.com/java/using-embedded-glassfish-with-maven/
http://embedded-glassfish.java.net/
I realise as I write this that there are no authoritative resource for using the Glassfish plugin in the manner Jetty is often used. However it does work in a similar way.
I hope this helps at least a bit.

How do you create a /postResults servlet for selenium core

I looked at the documentation and all it says is to create a servlet... With what?
Is there code I need to use for this servlet?
Does it just need to be blank and have the name of postResults?
Is there a provided ant script for this?
I can't find anything on google or selenium's site that lets me in on this.
Thanks
UPDATE: I found the following example
<servlet>
<servlet-name>postResults</servlet-name>
<servlet-class>com.thoughtworks.selenium.results.servlet.SeleniumResultsServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>postResults</servlet-name>
<url-pattern>/postResults</url-pattern>
</servlet-mapping>
However I can't seem to find this Class file anywhere in my selenium jars. I have the RC and regular core downlaods but no dice. Where do I get this jar file from?
If you are using the pure html/javascript capabilities of Selenium like I am then you know that you do not get a results report when testing unless you have a postResults servlet setup somewhere to push the results to.
I found a solution by taking apart the fitRunner plug-in to determine what I would need to get one setup.
This is a java solution btw.
http://jira.openqa.org/browse/SEL-102 you can download a zip file here with everything you would need and a bunch of stuff you don't need.
In your webapp just add the servlet mapping you find in the web.xml to your web app.
make sure the package your reference is created as such below
Then add the following jars you will find in the zip to your web app library if you don't already have them.
jstl.jar
and
standard.jar
create two classes
your.package.path.SeleniumResultServlet
paste the following code in it.
package com.your.package.path;
import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class SeleniumResultsServlet extends HttpServlet {
private static TestResults results;
public static TestResults getResults() {
return results;
}
public static void setResults(TestResults testResults) {
results = testResults;
}
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
if (request.getParameter("clear") != null) {
results = null;
ServletOutputStream out = response.getOutputStream();
out.println("selenium results cleared!");
} else {
forwardToResultsPage(request, response);
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String result = request.getParameter("result");
String totalTime = request.getParameter("totalTime");
String numTestPasses = request.getParameter("numTestPasses");
String numTestFailures = request.getParameter("numTestFailures");
String numCommandPasses = request.getParameter("numCommandPasses");
String numCommandFailures = request.getParameter("numCommandFailures");
String numCommandErrors = request.getParameter("numCommandErrors");
String suite = request.getParameter("suite");
int numTotalTests = Integer.parseInt(numTestPasses) + Integer.parseInt(numTestFailures);
List testTables = createTestTables(request, numTotalTests);
results = new TestResults(result, totalTime,
numTestPasses, numTestFailures, numCommandPasses,
numCommandFailures, numCommandErrors, suite, testTables);
forwardToResultsPage(request, response);
}
private List createTestTables(HttpServletRequest request, int numTotalTests) {
List testTables = new LinkedList();
for (int i = 1; i <= numTotalTests; i++) {
String testTable = request.getParameter("testTable." + i);
System.out.println("table " + i);
System.out.println(testTable);
testTables.add(testTable);
}
return testTables;
}
private void forwardToResultsPage(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setAttribute("results", results);
request.getRequestDispatcher("/WEB-INF/jsp/viewResults.jsp").forward(request, response);
}
public static class TestResults {
private final String result;
private final String totalTime;
private final String numTestPasses;
private final String numTestFailures;
private final String numCommandPasses;
private final String numCommandFailures;
private final String numCommandErrors;
private final String suite;
private final List testTables;
public TestResults(String postedResult, String postedTotalTime,
String postedNumTestPasses, String postedNumTestFailures,
String postedNumCommandPasses, String postedNumCommandFailures,
String postedNumCommandErrors, String postedSuite, List postedTestTables) {
result = postedResult;
numCommandFailures = postedNumCommandFailures;
numCommandErrors = postedNumCommandErrors;
suite = postedSuite;
totalTime = postedTotalTime;
numTestPasses = postedNumTestPasses;
numTestFailures = postedNumTestFailures;
numCommandPasses = postedNumCommandPasses;
testTables = postedTestTables;
}
public String getDecodedTestSuite() {
return new UrlDecoder().decode(suite);
}
public List getDecodedTestTables() {
return new UrlDecoder().decodeListOfStrings(testTables);
}
public String getResult() {
return result;
}
public String getNumCommandErrors() {
return numCommandErrors;
}
public String getNumCommandFailures() {
return numCommandFailures;
}
public String getNumCommandPasses() {
return numCommandPasses;
}
public String getNumTestFailures() {
return numTestFailures;
}
public String getNumTestPasses() {
return numTestPasses;
}
public String getSuite() {
return suite;
}
public Collection getTestTables() {
return testTables;
}
public String getTotalTime() {
return totalTime;
}
public int getNumTotalTests() {
return Integer.parseInt(numTestPasses) + Integer.parseInt(numTestFailures);
}
}
}
then go ahead and create a UrlDecoder class in the same package
package your.package.path;
import java.io.UnsupportedEncodingException;import java.net.URLDecoder;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
* #author Darren Cotterill
* #author Ajit George
* #version $Revision: $
*/
public class UrlDecoder {
public String decode(String string) {
try {
return URLDecoder.decode(string, System.getProperty("file.encoding"));
} catch (UnsupportedEncodingException e) {
return string;
}
}
public List decodeListOfStrings(List list) {
List decodedList = new LinkedList();
for (Iterator i = list.iterator(); i.hasNext();) {
decodedList.add(decode((String) i.next()));
}
return decodedList;
}
}
In your web-inf create a folder called jsp
copy the viewResults.jsp into it that is in the zip file.
copy the c.tld to the /web-inf
restart your server
if you get some goofy errors when trying to load the postResults servlet from selenium try changing the taglib reference int the viewResults.jsp to use the sun url instead due to different version dependencies it may not work. servlet 1.0 vs. 2.0 stuff.
<%#taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
Then when you run the test runner and choose automatic option in your selenium screen you will have a postResults servlet that you can use and customize at will.
Hope this helps others. Having a way to format test results is a great help when trying to create documentation and the stuff that comes out of the zip with regular selenium doesn't give you this basic functionality and you have to bake it up yourself.
The postResults servlet is useful in a continuous integration environment where you want to have the selenium test results sent to a URL of your choosing ( I believe it's configurable when setting up your selenium test ) and then have that server include the selenium results as part of the build results. If you don't want to do any post-processing on the selenium test results, then you don't have to setup a postResults servlet at all.

Resources