package org.mitre.test; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import org.apache.commons.configuration.XMLConfiguration; import org.apache.commons.lang.StringUtils; import org.apache.http.*; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.conn.params.ConnRoutePNames; import org.apache.http.impl.client.DefaultHttpClient; import org.jdom.input.SAXBuilder; import org.mitre.test.impl.TextReporter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.ErrorHandler; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; /** * Application context handles configuration and general house keeping. * Abstracts the tests from most server implementation details including * authentication and security handling. * * @author Jason Mathews, MITRE Corp. * Date: 2/20/12 10:55 AM */ public class Context { private static final Logger log = LoggerFactory.getLogger(Context.class); public static final String DEFAULT_USER = "defaultUser"; private SAXBuilder builder, validatingBuilder; private Reporter reporter; /** Validation feature id */ protected static final String VALIDATION_FEATURE = "http://xml.org/sax/features/validation"; /** Schema validation feature id */ protected static final String SCHEMA_VALIDATION_FEATURE = "http://apache.org/xml/features/validation/schema"; /** Schema full checking feature id */ protected static final String SCHEMA_FULL_CHECKING_FEATURE = "http://apache.org/xml/features/validation/schema-full-checking"; protected static final String LOAD_DTD_GRAMMAR = "http://apache.org/xml/features/nonvalidating/load-dtd-grammar"; // [TRUE] protected static final String LOAD_EXTERNAL_DTD = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; // [TRUE] protected static final String CONTINUE_AFTER_FATAL_FEATURE = "http://apache.org/xml/features/continue-after-fatal-error"; // [FALSE] @NonNull private URI baseURL; private HttpHost proxy; // security info // root.xml contents ? private XMLConfiguration config; /** * server specific implementation of HttpRequestChecker to pre-test HTTP requests * such as handling authentication */ private HttpRequestChecker httpRequestChecker; private String baseUrlString; // cached copy of baseURL.toASCIIString() private final Map<String, UserInfo> userMap = new HashMap<String, UserInfo>(); private String currentUser; @NonNull public URI getBaseURL() { return baseURL; } @NonNull public URI getBaseURL(String relativePath) throws URISyntaxException { if (StringUtils.isBlank(relativePath)) { return baseURL; } String uri = baseUrlString; // baseURL.toASCIIString(); if (relativePath.startsWith("/")) { // relative paths are relative to the baseURL and server-relative URLs // will be assumed to be relative to baseURL not the server root // if (uri.endsWith("/")) // always true // relativePath = relativePath.substring(1); uri += relativePath.substring(1); // strip off the leading '/' } else { // relative path is correctly relative so append to base URL uri += relativePath; //if (uri.endsWith("/")) uri += relativePath; // always true //else uri += "/" + relativePath; } return new URI(uri); } /** * Load configuration * * @param config * @throws IllegalArgumentException if any required configuration element is invalid or missing * @throws IllegalStateException if authentication fails * @exception NumberFormatException if any required string property does not contain a * parsable integer. */ public void load(XMLConfiguration config) { this.config = config; final String url = config.getString("baseURL"); // see http://commons.apache.org/configuration/userguide/howto_xml.html if (StringUtils.isBlank(url)) { // TODO: if any tests don't require baseURL then may this optional and have tests check and mark status = SKIPPED throw new IllegalArgumentException("baseURL property must be defined"); } else { try { baseURL = new URI(url); if (baseURL.getQuery() != null) { log.error("6.1.1 baseURL MUST NOT contain a query component, baseURL=" + baseURL); } baseUrlString = baseURL.toASCIIString(); final String baseRawString = baseURL.toString(); if (!baseUrlString.equals(baseRawString)) { log.warn("baseURL appears to have non-ASCII characters and comparisons using URL may have problems"); log.debug("baseURL ASCII String=" + baseUrlString); log.debug("baseURL raw String=" + baseRawString); } if (!baseUrlString.endsWith("/")) baseUrlString += '/'; // end the baseURL with slash } catch (URISyntaxException e) { throw new IllegalArgumentException(e); } } // setup HTTP proxy if required String proxyHost = config.getString("proxy.host"); String proxyPort = config.getString("proxy.port"); if (StringUtils.isNotBlank(proxyHost) && StringUtils.isNotBlank(proxyPort)) { proxy = new HttpHost(proxyHost, Integer.parseInt(proxyPort), "http"); } // load optional HttpRequestChecker for HTTP request handling final String httpRequestCheckerClass = config.getString("HttpRequestChecker"); if (StringUtils.isNotBlank(httpRequestCheckerClass)) { try { Class httpClass = Class.forName(httpRequestCheckerClass); httpRequestChecker = (HttpRequestChecker) httpClass.newInstance(); httpRequestChecker.setup(this); if (httpRequestChecker.getCurrentUser(this) != null) currentUser = DEFAULT_USER; } catch (ClassNotFoundException e) { throw new IllegalArgumentException(e); } catch (InstantiationException e) { throw new IllegalArgumentException(e); } catch (IllegalAccessException e) { throw new IllegalArgumentException(e); } } } /** * Get a string associated with the given configuration key. * @param key The configuration key * @return The associated string value if key is found otherwise null */ @CheckForNull public String getString(String key) { return config != null ? config.getString(key) : null; } /** * Get user property which is stored as username . property name in configuration. * @param user The username alias, never null * @param property The configuration key, never null * @return The associated string value if key is found otherwise null */ @CheckForNull public String getUserProperty(String user, String property) { return getString(user + "." + property); } /** * Get user property which is stored as username . property name in configuration. * @param user The username alias, never null * @param property The configuration key, never null * @param defaultValue The default value if property not found * @return The associated string value if key is found otherwise defaultValue as provided */ public String getUserProperty(String user, String property, String defaultValue) { String value = getUserProperty(user, property); return value == null ? defaultValue : value; } public void setProperty(String key, String value) { if (config != null) { System.out.printf("XXX: set prop %s %s%n", key, value);//debug config.setProperty(key, value); } } /** * Get named property as File from config.xml * @param key The configuration key * @return File or null if property does not found or file does not exist */ @CheckForNull public File getPropertyAsFile(String key) { String fileProp = getString(key); if (StringUtils.isBlank(fileProp)) { log.debug("property {} not found or contains empty string", key); return null; } log.trace(fileProp); File file = new File(fileProp); if (!file.isFile()) { log.info("file {} does not exist or isn't regular file", file); return null; } return file; } /** * Get named property as URI from config.xml * @param key The configuration key * @return URI or null if property does not found or not valid URI */ @CheckForNull public URI getPropertyAsURI(String key) { String value = getString(key); if (StringUtils.isBlank(value)) { log.debug("property {} not found or contains empty string", key); return null; } log.trace(value); try { return new URI(value); } catch (URISyntaxException e) { log.warn(e.toString()); return null; } } public SAXBuilder getBuilder(ErrorHandler errorHandler) { if (builder == null) { builder = new SAXBuilder(false); builder.setFeature(VALIDATION_FEATURE, false); // [false] builder.setFeature(SCHEMA_FULL_CHECKING_FEATURE, false); // [false] builder.setFeature(SCHEMA_VALIDATION_FEATURE, false); // [false] builder.setFeature(LOAD_DTD_GRAMMAR, false); // [true] builder.setFeature(LOAD_EXTERNAL_DTD, false); // [true] builder.setFeature("http://xml.org/sax/features/external-general-entities", false); // TRUE builder.setFeature("http://xml.org/sax/features/external-parameter-entities", false); // TRUE // http://xml.org/sax/features/namespace-prefixes [false] // builder.setFeature("http://xml.org/sax/features/namespaces", true); [true] } builder.setErrorHandler(errorHandler); return builder; } public SAXBuilder getValidatingBuilder(ErrorHandler errorHandler) { if (validatingBuilder == null) { validatingBuilder = new SAXBuilder(true); validatingBuilder.setFeature(VALIDATION_FEATURE, true); validatingBuilder.setFeature(SCHEMA_FULL_CHECKING_FEATURE, true); validatingBuilder.setFeature(SCHEMA_VALIDATION_FEATURE, true); validatingBuilder.setFeature(LOAD_DTD_GRAMMAR, false); validatingBuilder.setFeature(LOAD_EXTERNAL_DTD, false); } validatingBuilder.setErrorHandler(errorHandler); return validatingBuilder; } public HttpClient getHttpClient() { HttpClient client = new DefaultHttpClient(); if (proxy != null) { // System.out.println("XXX: use HTTP proxy"); client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); } return client; } /** * Wrap <tt>HttpClient.execute()</tt> to pre/post-test HTTP requests for any * server specific implementation handling such as authentication. * * @param client the HttpClient, must never be null * @param request the request to execute, must never be null * * @return the response to the request. * @throws IOException in case of a problem or the connection was aborted * @throws ClientProtocolException in case of an http protocol error */ public HttpResponse executeRequest(HttpClient client, HttpRequestBase request) throws IOException { if (httpRequestChecker != null) { return httpRequestChecker.executeRequest(this, client, request); } else { return client.execute(request); } } /** * Get current active user identity if applicable * @return user id (e.g. defaultUser) associated with active user context * if applicable otherwise null */ public String getUser() { return currentUser; } /** * * @param userId * @return true if successful sets user context, false otherwise */ public boolean setUser(String userId) { if (userId != null && httpRequestChecker != null) { UserInfo userInfo = userMap.get(userId); if(userInfo == null) { String userEmail = getUserProperty(userId, "email"); if (userEmail == null) { log.warn("user " + userId + " email not found in config"); return false; } String password = getUserProperty(userId, "password"); if (password == null) { log.warn("user " + userId + " password not found in config"); return false; } userInfo = new UserInfo(userEmail, password); userMap.put(userId, userInfo); } try { //? currentUser = null; // null or keep last value ?? httpRequestChecker.setUser(this, userId, userInfo.email, userInfo.password); if (userInfo.email.equals(httpRequestChecker.getCurrentUser(this))) { currentUser = userId; return true; } } catch(IllegalStateException e) { log.warn("failed to set user", e); return false; } catch(IllegalArgumentException e) { log.warn("failed to set user", e); return false; } } return false; } @NonNull public Reporter getReporter() { if (reporter == null) { reporter = new TextReporter(); } return reporter; } public void setReporter(Reporter reporter) { if (reporter == null) throw new NullPointerException(); this.reporter = reporter; } private static class UserInfo { private final String email; private final String password; public UserInfo(String email, String password) { this.email = email; this.password = password; } } }